Status Light with Raspberry Pi Zero and W2812 Led Strip

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

[embedyt] https://www.youtube.com/watch?v=3I_7YExmYb4[/embedyt]

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.  [bg_collapse view=”button-blue” icon=”eye” color=”#ffffff” expand_text=”Show Code” collapse_text=”Hide Code” ]

# 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))

[/bg_collapse]

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.

[table id=1 /]

The expected JSON be validated with this schema [bg_collapse view=”button-blue” icon=”eye” color=”#ffffff” expand_text=”Show Schema” collapse_text=”Hide Schema” ]

{
  "$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
        ]
      }
    }
  }
}

[/bg_collapse]

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.

#!/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 *