├── .gitignore
├── README.md
├── build.gradle
├── demo-app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── xavirigau
│ │ └── ledcontroller
│ │ ├── BoardDefaults.java
│ │ ├── DiyLedActivity.java
│ │ ├── RainbowLedActivity.java
│ │ └── SimpleLedActivity.java
│ └── res
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── rpi3-schematics.png
├── settings.gradle
└── ws2801-driver
├── .android-things-driver.json
├── build.gradle
├── proguard-rules.pro
└── src
├── main
├── AndroidManifest.xml
└── java
│ └── com
│ └── xrigau
│ └── driver
│ └── ws2801
│ ├── ColorUnpacker.java
│ └── Ws2801.java
└── test
└── java
└── com
└── xrigau
└── driver
└── ws2801
├── ColorUnpackerTest.java
└── Ws2801Test.java
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | local.properties
4 | .idea/
5 | .DS_Store
6 | build/
7 | captures/
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # androidthings-ws2801-driver [  ](https://bintray.com/xrigau/maven/ws2801-driver/_latestVersion)
2 |
3 | This repo contains the Android Things driver to control an LED strip based on the WS2801 chip (and should work on similar LED strips as long as the protocol is the same).
4 |
5 | The driver can be found in the `ws2801-driver` module and there's a demo app showing how to use the driver in the `demo-app` module.
6 |
7 | Only tested with a Raspberry Pi 3 but should work with other devices too.
8 |
9 | ## Schematics
10 |
11 | 
12 |
13 |
14 | ## Using
15 |
16 | The driver is now available on jCenter so you can include it as a gradle dependency:
17 |
18 | ```groovy
19 | dependencies {
20 | compile 'com.xrigau:ws2801-driver:1.0.0'
21 | }
22 | ```
23 |
24 | And you can start using it in your Android Things project:
25 |
26 | ```java
27 | Ws2801 ledstrip = Ws2801.create(SPI_DEVICE_NAME, Ws2801.Mode.RGB);
28 | ledstrip.write(new int[]{Color.parseColor("#0face0")});
29 |
30 | // Later on
31 | ledstrip.close();
32 | ```
33 | More on the WS2801: https://cdn-shop.adafruit.com/datasheets/WS2801.pdf
34 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:2.2.1'
7 | classpath 'com.novoda:bintray-release:0.4.0'
8 | }
9 | }
10 |
11 | allprojects {
12 | repositories {
13 | jcenter()
14 | }
15 | }
16 |
17 | task clean(type: Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
--------------------------------------------------------------------------------
/demo-app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.xavirigau.ledcontroller"
9 | minSdkVersion 24
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | provided 'com.google.android.things:androidthings:0.1-devpreview'
24 | compile project(':ws2801-driver')
25 | }
26 |
--------------------------------------------------------------------------------
/demo-app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.4.1_1/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/demo-app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo-app/src/main/java/com/xavirigau/ledcontroller/BoardDefaults.java:
--------------------------------------------------------------------------------
1 | package com.xavirigau.ledcontroller;
2 |
3 | import android.os.Build;
4 |
5 | @SuppressWarnings("WeakerAccess")
6 | public class BoardDefaults {
7 | private static final String DEVICE_EDISON = "edison";
8 | private static final String DEVICE_RPI3 = "rpi3";
9 | private static final String DEVICE_NXP = "imx6ul";
10 | /**
11 | * Return the preferred I2C port for each board.
12 | */
13 | public static String getSPIPort() {
14 | switch (Build.DEVICE) {
15 | // same for Edison Arduino breakout and Edison SOM
16 | case DEVICE_EDISON:
17 | return "SPI2";
18 | case DEVICE_RPI3:
19 | return "SPI0.0";
20 | case DEVICE_NXP:
21 | return "SPI3_0";
22 | default:
23 | throw new IllegalStateException("Unknown Build.DEVICE " + Build.DEVICE);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demo-app/src/main/java/com/xavirigau/ledcontroller/DiyLedActivity.java:
--------------------------------------------------------------------------------
1 | package com.xavirigau.ledcontroller;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 |
7 | import com.google.android.things.pio.PeripheralManagerService;
8 | import com.google.android.things.pio.SpiDevice;
9 |
10 | import java.io.IOException;
11 |
12 | // Works with LED strip WS2801
13 | public class DiyLedActivity extends Activity {
14 |
15 | private static final String TAG = DiyLedActivity.class.getSimpleName();
16 |
17 | private SpiDevice mDevice;
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 |
23 | try {
24 | PeripheralManagerService manager = new PeripheralManagerService();
25 | mDevice = manager.openSpiDevice(BoardDefaults.getSPIPort());
26 | configureSpiDevice(mDevice);
27 | byte[] b = {10/*R*/, 100 /*B*/, 40/*G*/};
28 | sendCommand(mDevice, b);
29 | } catch (IOException e) {
30 | Log.w(TAG, "Unable to access SPI device", e);
31 | }
32 | }
33 |
34 | public void configureSpiDevice(SpiDevice device) throws IOException {
35 | device.setMode(SpiDevice.MODE2);
36 | device.setFrequency(1000000);
37 | device.setBitsPerWord(8);
38 | }
39 |
40 | // Half-duplex data transfer
41 | public void sendCommand(SpiDevice device, byte[] buffer) throws IOException {
42 | device.write(buffer, buffer.length);
43 | }
44 |
45 | @Override
46 | protected void onDestroy() {
47 | if (mDevice != null) {
48 | try {
49 | mDevice.close();
50 | mDevice = null;
51 | } catch (IOException e) {
52 | Log.w(TAG, "Unable to close SPI device", e);
53 | }
54 | }
55 |
56 | super.onDestroy();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/demo-app/src/main/java/com/xavirigau/ledcontroller/RainbowLedActivity.java:
--------------------------------------------------------------------------------
1 | package com.xavirigau.ledcontroller;
2 |
3 | import com.xrigau.driver.ws2801.Ws2801;
4 |
5 | import android.app.Activity;
6 | import android.graphics.Color;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.os.HandlerThread;
10 | import android.util.Log;
11 |
12 | import java.io.IOException;
13 | import java.util.Arrays;
14 |
15 | public class RainbowLedActivity extends Activity {
16 |
17 | private static final String TAG = RainbowLedActivity.class.getSimpleName();
18 | private static final int FRAME_DELAY_MS = 15;
19 | private static final int NUM_LEDS = 1;
20 |
21 | private final int[] mLedColors = new int[NUM_LEDS];
22 |
23 | private Ws2801 mLedstrip;
24 | private HandlerThread mPioThread;
25 | private Handler mHandler;
26 |
27 | private float hue = 0.0f;
28 | private float increment = 0.002f;
29 |
30 | @Override
31 | public void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 |
34 | mPioThread = new HandlerThread("pioThread");
35 | mPioThread.start();
36 | mHandler = new Handler(mPioThread.getLooper());
37 |
38 | try {
39 | Log.d(TAG, "Initializing LED strip");
40 | mLedstrip = Ws2801.create(BoardDefaults.getSPIPort(), Ws2801.Mode.RBG);
41 | mHandler.post(mAnimateRunnable);
42 | // mLedstrip.write(new int[]{Color.BLACK}); // Uncomment this line and comment the previous one to turn off
43 | } catch (IOException e) {
44 | Log.e(TAG, "Error initializing LED strip", e);
45 | }
46 | }
47 |
48 | private Runnable mAnimateRunnable = new Runnable() {
49 | @Override
50 | public void run() {
51 | try {
52 | Arrays.fill(mLedColors, hue2Rgb()); // all LEDs will have the same color
53 | mLedstrip.write(mLedColors);
54 |
55 | hue += increment;
56 | if (hue >= 1.0f || hue <= 0.0f) {
57 | hue = Math.max(0.0f, Math.min(hue, 1.0f));
58 | increment = -increment;
59 | }
60 | } catch (IOException e) {
61 | Log.e(TAG, "Error while writing to LED strip", e);
62 | }
63 | mHandler.postDelayed(mAnimateRunnable, FRAME_DELAY_MS);
64 | }
65 | };
66 |
67 | private int hue2Rgb() {
68 | float l = 0.5f; // luminosity?
69 | float s = 0.8f; // saturation?
70 |
71 | float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
72 | float p = 2f * l - q;
73 |
74 | int r = (int) (convert(p, q, hue + (1 / 3f)) * 255);
75 | int g = (int) (convert(p, q, hue) * 255);
76 | int b = (int) (convert(p, q, hue - (1 / 3f)) * 255);
77 |
78 | return Color.rgb(r, g, b);
79 | }
80 |
81 | private float convert(float p, float q, float t) {
82 | if (t < 0) t += 1f;
83 | if (t > 1) t -= 1f;
84 | if (t < 1 / 6f) return p + (q - p) * 6f * t;
85 | if (t < 1 / 2f) return q;
86 | if (t < 2 / 3f) return p + (q - p) * (2 / 3f - t) * 6f;
87 | return p;
88 | }
89 |
90 | @Override
91 | public void onDestroy() {
92 | mHandler.removeCallbacks(mAnimateRunnable);
93 | mPioThread.quitSafely();
94 |
95 | try {
96 | mLedColors[0] = Color.BLACK;
97 | mLedstrip.write(mLedColors);
98 | mLedstrip.close();
99 | } catch (IOException e) {
100 | Log.e(TAG, "Exception closing LED strip", e);
101 | }
102 | super.onDestroy();
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/demo-app/src/main/java/com/xavirigau/ledcontroller/SimpleLedActivity.java:
--------------------------------------------------------------------------------
1 | package com.xavirigau.ledcontroller;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 |
8 | import com.xrigau.driver.ws2801.Ws2801;
9 |
10 | import java.io.IOException;
11 |
12 | public class SimpleLedActivity extends Activity {
13 |
14 | private static final String TAG = SimpleLedActivity.class.getSimpleName();
15 |
16 | private Ws2801 mLedstrip;
17 |
18 | @Override
19 | public void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 |
22 | try {
23 | mLedstrip = Ws2801.create(BoardDefaults.getSPIPort(), Ws2801.Mode.RGB);
24 | mLedstrip.write(new int[]{Color.parseColor("#0face0")});
25 | Log.d(TAG, "Done!");
26 | } catch (IOException e) {
27 | Log.e(TAG, "Error initializing LED strip", e);
28 | }
29 | }
30 |
31 | @Override
32 | public void onDestroy() {
33 | try {
34 | mLedstrip.close();
35 | } catch (IOException e) {
36 | Log.e(TAG, "Exception closing LED strip", e);
37 | }
38 | super.onDestroy();
39 | }
40 |
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/demo-app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/demo-app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo-app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/demo-app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo-app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/demo-app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/demo-app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/demo-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo-app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | LedController
3 |
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Dec 21 23:48:55 CET 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.2-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/rpi3-schematics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xrigau/androidthings-ws2801-driver/b867e5dbb49d596e8b57c26576ab2dfc0f4923c5/rpi3-schematics.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo-app', ':ws2801-driver'
2 |
--------------------------------------------------------------------------------
/ws2801-driver/.android-things-driver.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "ws2801-driver",
3 | "category": "driver",
4 | "samples": [
5 | "https://github.com/xrigau/androidthings-ws2801-driver/tree/master/demo-app"
6 | ],
7 | "published-maven": {
8 | "maven-url": "https://bintray.com/xrigau/maven/ws2801-driver/1.0.0",
9 | "groupid": "com.xrigau",
10 | "artifactid": "ws2801-driver"
11 | },
12 | "compatible-with": [
13 | {
14 | "title": "WS2801-based LED strip",
15 | "photo": "https://cdn.sparkfun.com//assets/parts/4/7/1/9/10312-00.jpg",
16 | "url": "https://www.adafruit.com/product/322"
17 | }
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/ws2801-driver/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release' // must be applied after your artifact generating plugin (eg. java / com.android.library)
3 |
4 | android {
5 | compileSdkVersion 27
6 | buildToolsVersion "27.0.2"
7 |
8 | defaultConfig {
9 | minSdkVersion 24
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | provided 'com.google.android.things:androidthings:1.0'
27 |
28 | testCompile 'junit:junit:4.12'
29 | testCompile "org.mockito:mockito-core:1.10.19"
30 | }
31 |
32 | publish {
33 | userOrg = 'xrigau'
34 | groupId = 'com.xrigau'
35 | artifactId = 'ws2801-driver'
36 | publishVersion = '1.0.0'
37 | desc = 'Android Things driver for the WS2801 RGB LED strsip.'
38 | website = 'https://github.com/xrigau/androidthings-ws2801-driver'
39 | }
40 |
--------------------------------------------------------------------------------
/ws2801-driver/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.4.1_1/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/ws2801-driver/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ws2801-driver/src/main/java/com/xrigau/driver/ws2801/ColorUnpacker.java:
--------------------------------------------------------------------------------
1 | package com.xrigau.driver.ws2801;
2 |
3 | import com.xrigau.driver.ws2801.Ws2801.Mode;
4 |
5 | import android.graphics.Color;
6 |
7 | class ColorUnpacker {
8 |
9 | private final Mode ledMode;
10 |
11 | ColorUnpacker(Mode ledMode) {
12 | this.ledMode = ledMode;
13 | }
14 |
15 | /**
16 | * Returns an WS2801 packet corresponding to the current brightness and given {@link Color}.
17 | *
18 | * @param color The {@link Color} to retrieve the protocol packet for.
19 | * @return WS2801 packet corresponding to the current brightness and given {@link Color}.
20 | */
21 | byte[] unpack(int color) {
22 | int r = Color.red(color);
23 | int g = Color.green(color);
24 | int b = Color.blue(color);
25 | return getOrderedRgbBytes(ledMode, (byte) r, (byte) g, (byte) b);
26 | }
27 |
28 | static byte[] getOrderedRgbBytes(Mode ledMode, byte r, byte g, byte b) {
29 | switch (ledMode) {
30 | case RGB:
31 | return new byte[]{r, g, b};
32 | case RBG:
33 | return new byte[]{r, b, g};
34 | case BGR:
35 | return new byte[]{b, g, r};
36 | case BRG:
37 | return new byte[]{b, r, g};
38 | case GRB:
39 | return new byte[]{g, r, b};
40 | case GBR:
41 | return new byte[]{g, b, r};
42 | default:
43 | throw new IllegalArgumentException(ledMode.name() + " is an unknown " + Mode.class.getSimpleName());
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/ws2801-driver/src/main/java/com/xrigau/driver/ws2801/Ws2801.java:
--------------------------------------------------------------------------------
1 | package com.xrigau.driver.ws2801;
2 |
3 | import android.graphics.Color;
4 |
5 | import com.google.android.things.pio.PeripheralManager;
6 | import com.google.android.things.pio.SpiDevice;
7 |
8 | import java.io.IOException;
9 |
10 | /**
11 | * Device driver for WS2801 RGB LEDs using 2-wire SPI.
12 | *
13 | * For more information on SPI, see:
14 | * https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
15 | * For information on the WS2801 protocol, see:
16 | * https://cdn-shop.adafruit.com/datasheets/WS2801.pdf
17 | */
18 | public class Ws2801 implements AutoCloseable {
19 |
20 | // Device SPI Configuration constants
21 | private static final int SPI_BPW = 8; // Bits per word
22 | private static final int SPI_FREQUENCY = 1_000_000;
23 | private static final int SPI_MODE = SpiDevice.MODE0; // Mode 0 seems to work best for WS2801
24 | private static final int WS2801_PACKET_LENGTH = 3; // R, G & B in any order
25 |
26 | /**
27 | * Color ordering for the RGB LED messages; the most common modes are BGR and RGB.
28 | */
29 | public enum Mode {
30 | RGB,
31 | RBG,
32 | GRB,
33 | GBR,
34 | BRG,
35 | BGR
36 | }
37 |
38 | public enum Direction {
39 | NORMAL,
40 | REVERSED,
41 | }
42 |
43 | private final SpiDevice device;
44 | private final ColorUnpacker colorUnpacker;
45 | // Direction of the led strip;
46 | private final Direction direction;
47 |
48 | /**
49 | * Create a new Ws2801 driver.
50 | *
51 | * @param spiBusPort Name of the SPI bus
52 | */
53 | public static Ws2801 create(String spiBusPort) throws IOException {
54 | return create(spiBusPort, Mode.RGB);
55 | }
56 |
57 | /**
58 | * Create a new Ws2801 driver.
59 | *
60 | * @param spiBusPort Name of the SPI bus
61 | * @param ledMode The {@link Mode} indicating the red/green/blue byte ordering for the device.
62 | */
63 | public static Ws2801 create(String spiBusPort, Mode ledMode) throws IOException {
64 | return create(spiBusPort, ledMode, Direction.NORMAL);
65 | }
66 |
67 | /**
68 | * Create a new Ws2801 driver.
69 | *
70 | * @param spiBusPort Name of the SPI bus
71 | * @param ledMode The {@link Mode} indicating the red/green/blue byte ordering for the device.
72 | * @param direction The {@link Direction} or the LED strip.
73 | */
74 | public static Ws2801 create(String spiBusPort, Mode ledMode, Direction direction) throws IOException {
75 | PeripheralManager pioService = PeripheralManager.getInstance();
76 | try {
77 | return new Ws2801(pioService.openSpiDevice(spiBusPort), new ColorUnpacker(ledMode), direction);
78 | } catch (IOException e) {
79 | throw new IOException("Unable to open SPI device in bus port " + spiBusPort, e);
80 | }
81 | }
82 |
83 | Ws2801(SpiDevice device, ColorUnpacker colorUnpacker, Direction direction) throws IOException {
84 | this.device = device;
85 | this.colorUnpacker = colorUnpacker;
86 | this.direction = direction;
87 | configure(device);
88 | }
89 |
90 | private static void configure(SpiDevice device) throws IOException {
91 | device.setFrequency(SPI_FREQUENCY);
92 | device.setMode(SPI_MODE);
93 | device.setBitsPerWord(SPI_BPW);
94 | }
95 |
96 | /**
97 | * Writes the current RGB LED data to the peripheral bus.
98 | *
99 | * @param colors An array of integers corresponding to a {@link Color}.
100 | * The size of the array should match the number of LEDs in the strip.
101 | * @throws IOException if writing to the SPI interface fails.
102 | */
103 | public void write(int[] colors) throws IOException {
104 | byte[] ledData = new byte[WS2801_PACKET_LENGTH * colors.length];
105 |
106 | for (int i = 0; i < colors.length; i++) {
107 | int outputPosition = i * WS2801_PACKET_LENGTH;
108 | int di = direction == Direction.NORMAL ? i : colors.length - i - 1;
109 | System.arraycopy(colorUnpacker.unpack(colors[di]), 0, ledData, outputPosition, WS2801_PACKET_LENGTH);
110 | }
111 |
112 | device.write(ledData, ledData.length);
113 | }
114 |
115 | /**
116 | * Releases the SPI interface.
117 | */
118 | @Override
119 | public void close() throws IOException {
120 | device.close();
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/ws2801-driver/src/test/java/com/xrigau/driver/ws2801/ColorUnpackerTest.java:
--------------------------------------------------------------------------------
1 | package com.xrigau.driver.ws2801;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | public class ColorUnpackerTest {
8 |
9 | private static final byte R = (byte) 111;
10 | private static final byte G = (byte) 222;
11 | private static final byte B = (byte) 333;
12 |
13 | @Test
14 | public void orderedBytesWhenModeIsRBG() {
15 | Ws2801.Mode mode = Ws2801.Mode.RBG;
16 |
17 | byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);
18 |
19 | assertBytesOrder(result, R, B, G);
20 | }
21 |
22 | @Test
23 | public void orderedBytesWhenModeIsBGR() {
24 | Ws2801.Mode mode = Ws2801.Mode.BGR;
25 |
26 | byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);
27 |
28 | assertBytesOrder(result, B, G, R);
29 | }
30 |
31 | @Test
32 | public void orderedBytesWhenModeIsBRG() {
33 | Ws2801.Mode mode = Ws2801.Mode.BRG;
34 |
35 | byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);
36 |
37 | assertBytesOrder(result, B, R, G);
38 | }
39 |
40 | @Test
41 | public void orderedBytesWhenModeIsGRB() {
42 | Ws2801.Mode mode = Ws2801.Mode.GRB;
43 |
44 | byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);
45 |
46 | assertBytesOrder(result, G, R, B);
47 | }
48 |
49 | @Test
50 | public void orderedBytesWhenModeIsGBR() {
51 | Ws2801.Mode mode = Ws2801.Mode.GBR;
52 |
53 | byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);
54 |
55 | assertBytesOrder(result, G, B, R);
56 | }
57 |
58 | private void assertBytesOrder(byte[] bytes, byte... order) {
59 | assertEquals(order[0], bytes[0]);
60 | assertEquals(order[1], bytes[1]);
61 | assertEquals(order[2], bytes[2]);
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/ws2801-driver/src/test/java/com/xrigau/driver/ws2801/Ws2801Test.java:
--------------------------------------------------------------------------------
1 | package com.xrigau.driver.ws2801;
2 |
3 | import static org.mockito.Matchers.any;
4 | import static org.mockito.Matchers.anyInt;
5 | import static org.mockito.Mockito.verify;
6 | import static org.mockito.Mockito.when;
7 |
8 | import com.google.android.things.pio.SpiDevice;
9 | import com.xrigau.driver.ws2801.Ws2801.Direction;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.runners.MockitoJUnitRunner;
15 |
16 | import android.graphics.Color;
17 |
18 | import java.io.IOException;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class Ws2801Test {
22 |
23 | private static final byte[] ANY_RGB = {0, 1, 42};
24 |
25 | @Mock
26 | private SpiDevice device;
27 | @Mock
28 | private ColorUnpacker unpacker;
29 |
30 | private Ws2801 driver;
31 |
32 | @Before
33 | public void setUp() throws IOException {
34 | driver = new Ws2801(device, unpacker, Direction.NORMAL);
35 | when(unpacker.unpack(anyInt())).thenReturn(ANY_RGB);
36 | }
37 |
38 | @Test
39 | public void configures1MHzClockFrequencyWhenCreated() throws Exception {
40 | verify(device).setFrequency(1_000_000);
41 | }
42 |
43 | @Test
44 | public void configuresClockToTransmitOnLeadingEdgeModeWhenCreated() throws Exception {
45 | verify(device).setMode(SpiDevice.MODE0);
46 | }
47 |
48 | @Test
49 | public void configuresBusToSend8BitsPerColorComponentWhenCreated() throws Exception {
50 | verify(device).setBitsPerWord(8);
51 | }
52 |
53 | @Test
54 | public void writesToSpiDeviceWhenWriting() throws Exception {
55 | int[] anyColors = {Color.RED, Color.DKGRAY, Color.GREEN, Color.WHITE, Color.YELLOW};
56 | driver.write(anyColors);
57 |
58 | verify(device).write(any(byte[].class), anyInt());
59 | }
60 |
61 | @Test
62 | public void reversesColorsWhenUsingReversedDirection() throws Exception {
63 | driver = new Ws2801(device, unpacker, Direction.REVERSED);
64 |
65 | driver.write(new int[]{0x0, 0x1});
66 |
67 | verify(unpacker).unpack(0x1);
68 | verify(unpacker).unpack(0X0);
69 | }
70 |
71 | @Test
72 | public void doesNotReverseColorsWhenUsingNormalDirection() throws Exception {
73 | driver = new Ws2801(device, unpacker, Direction.NORMAL);
74 |
75 | driver.write(new int[]{0x0, 0x1});
76 |
77 | verify(unpacker).unpack(0x0);
78 | verify(unpacker).unpack(0x1);
79 | }
80 |
81 | @Test
82 | public void closesSpiDeviceWhenClosing() throws Exception {
83 | driver.close();
84 |
85 | verify(device).close();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------