Status Light with Raspberry Pi Zero and W2812 Led Strip

Yet another, bit bigger, status light controlled over USB serial port…

Works great with Mobile App for Raspi W2812 Light Strip and Service Monitor Application!

Parts

For building the light you need:

  • Raspberry Pi Zero W (W for wireless – wifi, bluetooth)
  • 50PCS WS2811 RGB Full Color 12mm Pixels digital Addressable LED String DC 5V
  • Rundleuchte 100W PVC-KORB Grau (Case)
  • 2 USB (one end male Micro-B the other end Type A)

You could use also Zero without “W” but then the android app will not work, since it would be missing the Bluetooth connectivity. Also having integrated WiFi you can configure a maintenance WiFi where your light automatically connects to and you can do your maintenance.

You could also use any led strip with WS2811/WS2812 chip. This tutorial considers 50 leds in that strip. For different number you would need to adapt the applications (light script, monitor, android app, …). The interface (USB, Bluetooth) stays the same since it does not care about the HW.

Installation

First we install all necessary software to our Raspberry Pi Zero W. I went with the standard Raspberian installation with Noobs. After the installation don’t forget to enable ssh on boot by creating an empty ssh file to the boot partition. The rest we can then do over ssh.

Wiring

We will use one of the 5V pins (e.g. pin 2 or 4), one of the GND pind (e.g. pin 6) and the GPIO18 – PCM_CLK (pin 12).


I solder the 3 wires to the pins 2 (5V), 6 (GND) and 12 (GPIO18) and added a layer of plastic with a hot glue gun. These 3 wires need to be connected to the led strip. One of the USB cables (the Data Cable) will be connected directly to the Raspberry’s USB (not PWR) connector. The second USB cable (the Power Cable) needs to be connected to the LED strip directly. We use only the 5V and GND wires of the USB for additional power to the light. Since the 5V and GND wires of the LED strip are also connected to the 5V (pin 2) and GND (pin 6) on the Raspberry, the light will be working with the Power Cable or Data Cable alone. One of the USBs can then be pulled without shutting the light down. If the Data Cable is pulled the last pattern will be going in circles until the Data Cable is connected again and new pattern is sent. If one of the USB cables is pulled there may be not enough power flowing. This may result into dimmed colors or even restart of the Raspberry.

Software

Next step is to light up the leds. I found this userspace Raspberry Pi library for controlling WS281X LEDs. Simply checkout the repository and checkout out the “python” subfolder for README.md. I made some changes for this concrete setup so you may want to checkout https://github.com/kubovy/rpi_ws281x instead. In short the following commands get you there:

$ git clone https://github.com/kubovy/rpi_ws281x.git
$ cd python
$ sudo apt-get install python-dev swig
$ python ./setup.py build
$ sudo python ./setup.py install

Connect your lights and test if everything is working with the following command:

$ sudo python examples/strandtest.py

Server mode

For the server mode I’ve created a different script in the repository under python/examples/double-circle-50-24-2.py. This setup assumes a 50 LED strip, where the first 48 LEDs make 2 circles, 1.row LED 1-24, 2.row LED 25-48) and the 2 LEDs on the end of the strip are left on the top of the light. 

double-circle-50-24-2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# NeoPixel library double circle with 50LEDs 24LEDs per cirvle + 2LED on top
# Author: Tony DiCola (tony@tonydicola.com)
# Author: Jan Kubovy (jan@kubovy.eu)
#
# Direct port of the Arduino NeoPixel library strandtest example.
import time
import json
from collections import namedtuple
 
from neopixel import *
import subprocess
 
import argparse
import signal
import sys
 
import os
import platform
 
def signal_handler(signal, frame):
        colorWipe(strip, Color(0,0,0))
        sys.exit(0)
 
def opt_parse():
        parser = argparse.ArgumentParser()
        parser.add_argument('-c', action='store_true', help='clear the display on exit')
        args = parser.parse_args()
        if args.c:
                signal.signal(signal.SIGINT, signal_handler)
 
def Color(red, green, blue, white = 0):
	"""Convert the provided red, green, blue color to a 24-bit color value.
	Each color component should be a value 0-255 where 0 is the lowest intensity
	and 255 is the highest intensity.
	"""
	return (white << 24) | (red << 8)| (green << 16) | blue
 
def Color2(color):
        """Color2"""
        return Color(color.red, color.green, color.blue)
 
# LED strip configuration:
LED_COUNT      = 50      # Number of LED pixels.
LED_PIN        = 18      # GPIO pin connected to the pixels (18 uses PWM!).
#LED_PIN        = 10      # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 10      # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255     # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL    = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53
LED_STRIP      = ws.WS2811_STRIP_GRB   # Strip type and colour ordering
CONF_PATH      = '/etc/light-strip/'
 
 
# Define functions which animate LEDs in various ways.
def colorWipe(strip, color, wait_ms=50):
	"""Wipe color across display a pixel at a time."""
	for i in range(strip.numPixels()):
		strip.setPixelColor(i, color)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def theater(strip, color1, color2, color5, color6, wait_ms=50, iterations=10):
	"""Movie theater light style chaser animation."""
        half = (strip.numPixels() - 2) / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for j in range(iterations):
		for q in range(3):
			for i in range(0, half, 3):
				strip.setPixelColor(i+q, color1)
				strip.setPixelColor(i+q+half, color2)
			strip.show()
			time.sleep(wait_ms/1000.0)
                        for i in range(0, half, 3):
				strip.setPixelColor(i+q, 0)
				strip.setPixelColor(i+q+half, 0)
 
def wheel(pos):
	"""Generate rainbow colors across 0-255 positions."""
	if pos < 85:
		return Color(pos * 3, 255 - pos * 3, 0)
	elif pos < 170:
		pos -= 85
		return Color(255 - pos * 3, 0, pos * 3)
	else:
		pos -= 170
		return Color(0, pos * 3, 255 - pos * 3)
 
def rainbow(strip, wait_ms=20, iterations=1):
	"""Draw rainbow that fades across all pixels at once."""
	for j in range(256*iterations):
		for i in range(strip.numPixels()):
			strip.setPixelColor(i, wheel((i+j) & 255))
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def rainbowCycle(strip, wait_ms=20, iterations=5):
	"""Draw rainbow that uniformly distributes itself across all pixels."""
	for j in range(256*iterations):
		for i in range(strip.numPixels()):
			strip.setPixelColor(i, wheel((int(i * 256 / strip.numPixels()) + j) & 255))
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def theaterChaseRainbow(strip, wait_ms=50):
	"""Rainbow movie theater light style chaser animation."""
	for j in range(256):
		for q in range(3):
			for i in range(0, strip.numPixels(), 3):
				strip.setPixelColor(i+q, wheel((i+j) % 255))
			strip.show()
			time.sleep(wait_ms/1000.0)
			for i in range(0, strip.numPixels(), 3):
				strip.setPixelColor(i+q, 0)
 
def wipe(strip, color1, color2, color5, color6, wait_ms=50, sleep=0):
	"""Wipe color across display a pixel at a time."""
	half = (strip.numPixels() - 2) / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for i in range(half):
		strip.setPixelColor(i, color1)
		strip.setPixelColor(i + half, color2)
		strip.show()
		time.sleep(wait_ms/1000.0)
	if sleep > 0:
		time.sleep(sleep/1000.0)
		wipe(strip, Color(0, 0, 0), Color(0, 0, 0,), color5, color6, wait_ms, 0)
 
def light(strip, color1, color2, color5, color6, wait_ms=50):
	half = (strip.numPixels() - 2) / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for i in range(half):
		strip.setPixelColor(i, color1)
		strip.setPixelColor(i + half, color2)
	strip.show()
        time.sleep(wait_ms/1000.0)
 
def rotation(strip, color1, color2, color5, color6, width=3, fade=0, wait_ms=50):
	"""Rotation"""
	half = (strip.numPixels() - 2) / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for i in range(half):
		for j in range(half):
			strip.setPixelColor(j, Color(0, 0, 0))
			strip.setPixelColor(j + half, Color(0, 0, 0))
		for k in range(width):
		        white1 = (color1 & (255 << 24)) >> 24
			red1 = (color1 & (255 << 8)) >> 8
			green1 = (color1 & (255 << 16)) >> 16
			blue1 = (color1 & 255)
		        white2 = (color2 & (255 << 24)) >> 24
			red2 = (color2 & (255 << 8)) >> 8
			green2 = (color2 & (255 << 16)) >> 16
			blue2 = (color2 & 255)
 
			percent = 100.0 - float(width - k - 1) * float(fade)
			factor = percent / 100.0
 
			r1 = int(float(red1) * factor)
			g1 = int(float(green1) * factor)
			b1 = int(float(blue1) * factor)
			r2 = int(float(red2) * factor)
			g2 = int(float(green2) * factor)
			b2 = int(float(blue2) * factor)
 
			c1 = Color(r1, g1, b1)
			c2 = Color(r2, g2, b2)
 
			p = i + k if (i + k < half) else (i + k) - half
			strip.setPixelColor(p, c1)
			strip.setPixelColor(p + half, c2)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def spin(strip, color1, color2, color5, color6):
	"""Spin"""
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for w in range(5):
		rotation(strip, color1, color2, 24, 4, 50 - (w * 10))
	rotation(strip, color1, color2, 24, 4, 50 - (w * 10))
	for w in range(3):
		lighthouse(strip, color1, color2, 12, 4, 30 - (w * 10))
		lighthouse(strip, color1, color2, 12, 4, 30 - (w * 10))
	light(strip, color1, color2)
	time.sleep(10)
 
def chaise(strip, color1, color2, color5, color6, width=3, fade=0, wait_ms=50):
	"""Rotation"""
	half = (strip.numPixels() - 2) / 2
	for i in range(half):
		for j in range(half):
			strip.setPixelColor(j, Color(0, 0, 0))
			strip.setPixelColor(j + half, Color(0, 0, 0))
		for k in range(width):
		        white1 = (color1 & (255 << 24)) >> 24
			red1 = (color1 & (255 << 8)) >> 8
			green1 = (color1 & (255 << 16)) >> 16
			blue1 = (color1 & 255)
		        white2 = (color2 & (255 << 24)) >> 24
			red2 = (color2 & (255 << 8)) >> 8
			green2 = (color2 & (255 << 16)) >> 16
			blue2 = (color2 & 255)
 
			percent = 100.0 - float(width - k - 1) * float(fade)
			factor = percent / 100.0
 
			r1 = int(float(red1) * factor)
			g1 = int(float(green1) * factor)
			b1 = int(float(blue1) * factor)
			r2 = int(float(red2) * factor)
			g2 = int(float(green2) * factor)
			b2 = int(float(blue2) * factor)
 
			c1 = Color(r1, g1, b1)
			c2 = Color(r2, g2, b2)
 
			p = i + k if (i + k < half) else (i + k) - half
			q = half - (i + k) if (half - (i + k) >= 0) else (half - (i + k)) + half
			strip.setPixelColor(p, c1)
			strip.setPixelColor(q + half, c2)
                strip.setPixelColor(strip.numPixels() - 1, color5)
                strip.setPixelColor(strip.numPixels() - 2, color6)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
 
def lighthouse(strip, color1, color2, color3, color4, color5, color6, width=3, fade=0, wait_ms=50):
	"""Lighthouse"""
	half = (strip.numPixels() - 2) / 2
	quarter = half / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for i in range(half):
		for j in range(half):
			strip.setPixelColor(j, Color(0, 0, 0))
			strip.setPixelColor(j + half, Color(0, 0, 0))
		for k in range(width):
		        white1 = (color1 & (255 << 24)) >> 24
			red1 = (color1 & (255 << 8)) >> 8
			green1 = (color1 & (255 << 16)) >> 16
			blue1 = (color1 & 255)
		        white2 = (color2 & (255 << 24)) >> 24
			red2 = (color2 & (255 << 8)) >> 8
			green2 = (color2 & (255 << 16)) >> 16
			blue2 = (color2 & 255)
		        white3 = (color3 & (255 << 24)) >> 24
			red3 = (color3 & (255 << 8)) >> 8
			green3 = (color3 & (255 << 16)) >> 16
			blue3 = (color3 & 255)
		        white4 = (color4 & (255 << 24)) >> 24
			red4 = (color4 & (255 << 8)) >> 8
			green4 = (color4 & (255 << 16)) >> 16
			blue4 = (color4 & 255)
 
			percent = 100.0 - float(width - k - 1) * float(fade)
			factor = percent / 100.0
 
			r1 = int(float(red1) * factor)
			g1 = int(float(green1) * factor)
			b1 = int(float(blue1) * factor)
			r2 = int(float(red2) * factor)
			g2 = int(float(green2) * factor)
			b2 = int(float(blue2) * factor)
			r3 = int(float(red3) * factor)
			g3 = int(float(green3) * factor)
			b3 = int(float(blue3) * factor)
			r4 = int(float(red4) * factor)
			g4 = int(float(green4) * factor)
			b4 = int(float(blue4) * factor)
 
			c1 = Color(r1, g1, b1)
			c2 = Color(r2, g2, b2)
			c3 = Color(r3, g3, b3)
			c4 = Color(r4, g4, b4)
 
			p = i + k if (i + k < half) else (i + k) - half
			q = p + quarter if (p + quarter < half) else (p + quarter) - half
			strip.setPixelColor(p, c1)
			strip.setPixelColor(p + half, c3)
			strip.setPixelColor(q, c2)
			strip.setPixelColor(q + half, c4)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def fade(strip, color1, color2, color5, color6, wait_ms=50, min=0, max=100):
	"""Fade"""
	half = (strip.numPixels() - 2) / 2
 
	white1 = (color1 & (255 << 24)) >> 24
	red1 = (color1 & (255 << 8)) >> 8
	green1 = (color1 & (255 << 16)) >> 16
	blue1 = (color1 & 255)
	white2 = (color2 & (255 << 24)) >> 24
	red2 = (color2 & (255 << 8)) >> 8
	green2 = (color2 & (255 << 16)) >> 16
	blue2 = (color2 & 255)
	#print ('Input: ' + str(red) + ', ' + str(green) + ', ' + str(blue))
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for pr in range((max - min + 1) * 2):
		percent = pr + min if ((pr + min) <= max) else max - (pr + min - max)
		factor = float(percent) / 100.0
		r1 = int(float(red1) * factor)
		g1 = int(float(green1) * factor)
		b1 = int(float(blue1) * factor)
		r2 = int(float(red2) * factor)
		g2 = int(float(green2) * factor)
		b2 = int(float(blue2) * factor)
		c1 = Color(r1, g1, b1)
		c2 = Color(r2, g2, b2)
		for i in range(half):
			strip.setPixelColor(i, c1)
			strip.setPixelColor(i + half, c2)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def fadeToggle(strip, color1, color2, color5, color6, wait_ms=50, min=0, max=100):
	"""Fade Toggle"""
	half = (strip.numPixels() - 2) / 2
	white1 = (color1 & (255 << 24)) >> 24
	red1 = (color1 & (255 << 8)) >> 8
	green1 = (color1 & (255 << 16)) >> 16
	blue1 = (color1 & 255)
	white2 = (color2 & (255 << 24)) >> 24
	red2 = (color2 & (255 << 8)) >> 8
	green2 = (color2 & (255 << 16)) >> 16
	blue2 = (color2 & 255)
	strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for pr in range((max - min + 1) * 2):
		percent = pr + min if ((pr + min) <= max) else max - (pr + min - max)
		factor1 = float(percent) / 100.0
		factor2 = float(max - percent + min) / 100.0
		r1 = int(float(red1) * factor1)
		g1 = int(float(green1) * factor1)
		b1 = int(float(blue1) * factor1)
		c1 = Color(r1, g1, b1)
		r2 = int(float(red2) * factor2)
		g2 = int(float(green2) * factor2)
		b2 = int(float(blue2) * factor2)
		c2 = Color(r2, g2, b2)
		for i in range(half):
			strip.setPixelColor(i, c1)
			strip.setPixelColor(i + half, c2)
		strip.show()
		time.sleep(wait_ms/1000.0)
 
def blink(strip, color1, color2, color5, color6, wait_ms=50):
	"""Blink"""
	half = (strip.numPixels() - 2) / 2
        strip.setPixelColor(strip.numPixels() - 1, color5)
        strip.setPixelColor(strip.numPixels() - 2, color6)
	for i in range(half):
		strip.setPixelColor(i, color1)
		strip.setPixelColor(i + half, color2)
	strip.show()
	time.sleep(wait_ms/1000.0)
	for i in range(half):
		strip.setPixelColor(i, Color(0, 0, 0))
		strip.setPixelColor(i + half, Color(0, 0, 0))
	strip.show()
	time.sleep(wait_ms/1000.0)
 
data = json.loads(json.dumps([{'pattern': 'fade', 'color1': {'red': 16, 'green': 16, 'blue': 16}, 'color2': {'red': 16, 'green': 16, 'blue': 16}, 'color3': {'red': 16, 'green': 16, 'blue': 16}, 'color4': {'red': 16, 'green': 16, 'blue': 16}, 'color5': {'red': 16, 'green': 16, 'blue': 16}, 'color6': {'red': 16, 'green': 16, 'blue': 16}, 'wait': 10, 'width': 3, 'fading': 0, 'min': 50, 'max': 80}]), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
 
first = True
iteration = 0
# Main program logic follows:
if __name__ == '__main__':
        # Process arguments
        opt_parse()
 
	# Create NeoPixel object with appropriate configuration.
	strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
	# Intialize the library (must be called once before other functions).
	strip.begin()
 
	lastChange = os.path.getmtime(CONF_PATH + 'light.conf')
        while True:
		change = os.path.getmtime(CONF_PATH + 'light.conf')
		if (change != lastChange or first):
                        try:
                                data = json.load(open(CONF_PATH + 'light.conf'), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
                                print ('Changed: ' + str(len(data)) + ' items')
			        first = False
                                iteration = 0
			        lastChange = change
                        except ValueError:
                                print ('Oops!  That was no valid JSON.  Try again...')
 
                if (len(data) == 0):
                        light(strip, Color(0,0,0), Color(0,0,0), Color(0,0,0), Color(0,0,0), 50)
                        continue
 
		index = iteration
                conf = data[index]
		if conf.pattern == 'clear':
                        for i in range(index + 1):
                            data.pop(0)
                        #start = index + 1
			print ('Cleared index=' + str(index) + ', length=' + str(len(data)))# + ', start=' + str(start))
			index = 0
                        conf = data[index]
 
		if conf.pattern == 'wipe':
			wipe(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait, conf.fading)
		elif conf.pattern == 'light':
			light(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait)
		elif conf.pattern == 'rotation':
			rotation(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.width, conf.fading, conf.wait)
		elif conf.pattern == 'spin':
			spin(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6))
		elif conf.pattern == 'chaise':
			chaise(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.width, conf.fading, conf.wait)
		elif conf.pattern == 'lighthouse':
			lighthouse(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color3), Color2(conf.color4), Color2(conf.color5), Color2(conf.color6), conf.width, conf.fading, conf.wait)
		elif conf.pattern == 'fade':
			fade(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait, conf.min, conf.max)
		elif conf.pattern == 'fadeToggle':
			fadeToggle(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait, conf.min, conf.max)
		elif conf.pattern == 'blink':
			blink(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait)
                elif conf.pattern == 'theater':
                        theater(strip, Color2(conf.color1), Color2(conf.color2), Color2(conf.color5), Color2(conf.color6), conf.wait, conf.fading)
                elif conf.pattern == 'rainbow':
                        rainbow(strip, conf.wait, conf.fading)
                elif conf.pattern == 'rainbowCycle':
                        rainbowCycle(strip, conf.wait, conf.fading)
                elif conf.pattern == 'wait':
                        time.sleep(conf.wait/1000.0)
		else:
			fade(strip, Color(16, 16, 16), Color(16, 16, 16), Color(0, 0, 0), Color(0, 0, 0), 10, 50, 80)
 
		iteration = iteration + 1
                if (iteration >= len(data)):
                        iteration = 0
 
        light(strip, Color(0, 0, 0), Color(0, 0, 0))

This script expects a file in /etc/light-strip/light.conf where a list of light configurations are stored in JSON format to be displayed in sequence. It also rereads the file’s content on change.

The following patterns are available:

  • Wipe
  • Light
  • Rotation
  • Spin
  • Chaise
  • Lighthouse
  • Fade
  • Fade Toggle
  • Blink
  • Theater
  • Rainbow
  • Rainbow Cycle

With two special:

  • Wait – for just waiting without changing the light setup
  • Clear – clearing all previous items and just rotating the rest of the light configuration list

The JSON is a list of light configuration each of which contain 6 colors (RGB), wait (in ms), width (number of leds), fading/iterations (percent per led/number), minimum (in percent) and maximum (in percent) properties. Not all properties are used for every light pattern – see the table below.

PatternColor 1Color 2Color 3Color 4Color 5Color 6WaitWidthFadingMinMax
Wipe1.Row2.Row--Top ATop BYes-Fading--
Light1.Row2.Row--Top ATop BYes----
Rotation1.Row2.Row--Top ATop BYesYesFading--
Spin1.Row2.Row--Top ATop B-----
Chaise1.Row2.Row--Top ATop BYesYesFading--
LighthousePart 1
1.Row
Part 1
2.Row
Part 2
1.Row
Part 2
2.Row
Top ATop BYesYesFading--
Fade1.Row2.Row--Top ATop BYes--YesYes
Fade Toggle1.Row2.Row--Top ATop BYes--YesYes
Blink1.Row2.Row--Top ATop BYes----
Theater1.Row2.Row--Top ATop BYes-Iterations--
Rainbow------Yes-Iterations--
Rainbow Cycle------Yes-Iterations--
Wait------Yes----
Clear-----------

The expected JSON be validated with this schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
{
  "$id": "http://poterion.com/light-config.json",
  "type": "array",
  "definitions": {},
  "$schema": "http://json-schema.org/draft-07/schema#",
  "items": {
    "$id": "http://poterion.com/light-config.json/items",
    "type": "object",
    "properties": {
      "pattern": {
        "$id": "http://poterion.com/light-config.json/items/properties/pattern",
        "type": "string",
        "title": "The Pattern Schema ",
        "default": "",
        "examples": [
          "rotation"
        ]
      },
      "color1": {
        "$id": "http://poterion.com/light-config.json/items/properties/color1",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color1/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color1/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color1/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              16
            ]
          }
        }
      },
      "color2": {
        "$id": "http://poterion.com/light-config.json/items/properties/color2",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color2/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color2/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color2/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              16
            ]
          }
        }
      },
      "color3": {
        "$id": "http://poterion.com/light-config.json/items/properties/color3",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color3/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color3/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color3/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              16
            ]
          }
        }
      },
      "color4": {
        "$id": "http://poterion.com/light-config.json/items/properties/color4",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color4/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color4/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color4/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              16
            ]
          }
        }
      },
      "color5": {
        "$id": "http://poterion.com/light-config.json/items/properties/color5",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color5/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color5/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              16
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color5/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              0
            ]
          }
        }
      },
      "color6": {
        "$id": "http://poterion.com/light-config.json/items/properties/color6",
        "type": "object",
        "properties": {
          "red": {
            "$id": "http://poterion.com/light-config.json/items/properties/color6/properties/red",
            "type": "integer",
            "title": "The Red Schema ",
            "default": 0,
            "examples": [
              0
            ]
          },
          "green": {
            "$id": "http://poterion.com/light-config.json/items/properties/color6/properties/green",
            "type": "integer",
            "title": "The Green Schema ",
            "default": 0,
            "examples": [
              16
            ]
          },
          "blue": {
            "$id": "http://poterion.com/light-config.json/items/properties/color6/properties/blue",
            "type": "integer",
            "title": "The Blue Schema ",
            "default": 0,
            "examples": [
              0
            ]
          }
        }
      },
      "wait": {
        "$id": "http://poterion.com/light-config.json/items/properties/wait",
        "type": "integer",
        "title": "The Wait Schema ",
        "default": 0,
        "examples": [
          50
        ]
      },
      "width": {
        "$id": "http://poterion.com/light-config.json/items/properties/width",
        "type": "integer",
        "title": "The Width Schema ",
        "default": 0,
        "examples": [
          12
        ]
      },
      "fading": {
        "$id": "http://poterion.com/light-config.json/items/properties/fading",
        "type": "integer",
        "title": "The Fading Schema ",
        "default": 0,
        "examples": [
          8
        ]
      },
      "min": {
        "$id": "http://poterion.com/light-config.json/items/properties/min",
        "type": "integer",
        "title": "The Min Schema ",
        "default": 0,
        "examples": [
          0
        ]
      },
      "max": {
        "$id": "http://poterion.com/light-config.json/items/properties/max",
        "type": "integer",
        "title": "The Max Schema ",
        "default": 0,
        "examples": [
          100
        ]
      }
    }
  }
}

Checkout the system folder in the root of the repository. There are some scripts which can be copied to your Rasperry’s root:

  • /etc/light-strip/light.conf – the light configuration which should be displayed
  • /etc/light-strip/startup.conf – the light configuration which should be displayed when you boot the light
  • /usr/local/bin/light.sh – the script which copies the startup.conf configuration to the light.conf configuration and starts a loop calling the python script python /home/pi/rpi_ws281x/python/examples/double-circle-50-24-2.py implementing a simple restart in case some errors occur.
  • /usr/local/bin/reader.sh – the script which reads the USB input and changes the light.conf configuration based on received data (see below)

USB Reader

The reader can be found in the repository  in system/usr/local/bin/reader.sh folder.

/usr/local/bin/reader.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
 
LOG_FILE="/var/log/light-strip-refresh.log"
echo "" > $LOG_FILE 
 
function loop() {
  stty -F /dev/ttyGS0 sane
  MODE=0
  CONTENT=""
  while read line; do
    if [[ $line != "" ]]; then
      if [[ $MODE = 0 ]] && [[ $line = "START" ]]; then
        CONTENT=""
        MODE=1
        echo ">>Started<<" >> $LOG_FILE
      elif [[ $MODE = 0 ]] && [[ $line = "ID" ]]; then
        echo ">>Identifying...<<" >> $LOG_FILE
        echo -e "CONFIRMATION_BEGIN\nPOTERION IOT:{\"name\": \"Calvatia\", \"features\": [\"light-strip\"], \"properties\": [\"24x2+2\"]}\nCONFIRMATION_END\n" > /dev/ttyGS0
      elif [[ $MODE = 1 ]] && [[ $line == "END" ]]; then
        echo -e "<>\n${CONTENT}\n<>" >> $LOG_FILE
        echo -e "${CONTENT}" > /etc/light-strip/light.conf
        echo -e "CONFIRMATION_BEGIN\n${CONTENT}\nCONFIRMATION_END\n" > /dev/ttyGS0
        MODE=0
        echo ">>Finished<<" >> $LOG_FILE
      elif [[ $MODE = 1 ]]; then
        #echo ">>Read: ${line}<<" if [[ $CONTENT != "" ]]; then CONTENT="${CONTENT}\n"; fi CONTENT="${CONTENT}${line}" else echo ">>Ignored: ${line}<<" >> $LOG_FILE
      fi
    fi
  done < /dev/ttyGS0
  loop
}
 
loop

This script simply read the input from /dev/ttyGS0, which is the USB of our Raspberry, line by line. If a line containing “START” is seen it starts listening. In the listening mode all new lines are stored into a variable until a line containing “END” is seen. Then all what was received will be saved in the /etc/light-strip/light.conf file for the python script to pick up. Also the whole received content will be send back to the USB port so the other side can check if what was sent was also received correctly.

Startup

We start both scripts (system/usr/local/bin/reader.sh and system/usr/local/bin/light.sh) using crontab on each boot:

@reboot /usr/local/bin/light.sh
@reboot /usr/local/bin/reader.sh

As soon as the light boots-up we will see the /etc/light-strip/startup.conf configuration. Then we can write new configurations by simply writing them to the USB serial device on the computer our light is connected to, e.g.:

$ echo "START" > /dev/ttyACM0
$ echo "[{\"pattern\":\"lighthouse\"," > /dev/ttyACM0
$ echo "\"color1\":{\"red\":255,\"green\":0,\"blue\":0}," > /dev/ttyACM0
$ echo "\"color2\":{\"red\":0,\"green\":255,\"blue\":0}," > /dev/ttyACM0
$ echo "\"color3\":{\"red\":0,\"green\":0,\"blue\":255}," > /dev/ttyACM0
$ echo "\"color4\":{\"red\":255,\"green\":255,\"blue\":0}," > /dev/ttyACM0
$ echo "\"color5\":{\"red\":0,\"green\":255,\"blue\":255}," > /dev/ttyACM0
$ echo "\"color6\":{\"red\":255,\"green\":0,\"blue\":255}," > /dev/ttyACM0
$ echo "\"wait\":50," > /dev/ttyACM0
$ echo "\"width\":12," > /dev/ttyACM0
$ echo "\"fading\":8," > /dev/ttyACM0
$ echo "\"min\":0," > /dev/ttyACM0
$ echo "\"max\":100}," > /dev/ttyACM0
$ echo "END\n" > /dev/ttyACM0

That’s it. Enjoy building your own status light. Next time we try to make an application so we don’t have to send RAW text to the USB’s serial port.

Updates

19.12.2018: Updated backend: https://github.com/kubovy/raspi-project

Leave a Reply

Your email address will not be published. Required fields are marked *