A wireless LED system

For the very talented drum group STICKSTOFF, I developed this wireless LED system.

The goal was to create a wearable system that could playback a video in sync with their backing track. I decided to go with a WIFI based solution, since it is much easier to use and the parts are relatively low in price. The biggest issue I had to tackle was to build a wireless system, that would work even with lots of other WIFI networks and smartphones around. (I have seen expensive wireless DMX’s systems fail, as soon as the venue filled up with people carrying smartphones.) So in order to get a reliable wireless signal, I wanted to send as little data as possible and repeat each transmission a couple of times. I could achieve this by saving the video data on the LED controller and just sending a timecode over WIFI. Therefore I wanted to use a microcontroller with some kind of integrated storage and onboard WIFI. As playback software, I planned to use Ableton Live, since it would be easy to implement a timecode mechanism with Max for Live. As a communication protocol, I chose OSC for maximum compatibility.

You can find the whole project on GitHub.

The controller

Level stepper circuit on Bonnet Proto Hat

I decided for a Raspberry Pi Zero W, since it is small, affordable and met my criteria. The Adafruit DotStar LED stripes were a good match, because they can be controlled directly with the Raspberry Pi and Adafruit had nice tutorials and even a Python library to do so. The only electronic component I needed was a level stepper, because the LEDs operate with 5V and the Pi Zero with 3.3V. To solder the circuit I used the Perma Proto Bonnet from Adafruit and enclosed everything in a Candy Bar enclosure.

As power supply I took the PowerCore Elite 20000 from Anker, that had 3 USB ports and delivers a total of 6A. The first outlet I connected to the Raspberry Pi, the second to the level stepper and the beginning of the LED strip and the third to the end of the strip. In the middle of the LED chain, I didn’t connect the +5V line, so the first and second half would draw their power from different USB ports. I used 188 LEDs per controller, which means at full brightness, they’d draw 188 x 0.06 A = 11.28 A. But as we tested the LEDs we realized, that we would not even run them at half of their brightness, since they get mighty bright and we didn’t want to blind the audience.

Programming the controller

source: https://learn.adafruit.com/assets/28914

I put the latest version of Raspbian Stretch Lite on a 16GB class 10 micro SD card using Etcher. After booting one of the boards with the SD card, I changed a few things in the config (“sudo raspi-config”):

  • In the “Network Options” I entered the login for the WIFI network.
  • Boot Options: Wait for Network at boot -> No
  • Localization: Keyboardlayout – classmate pc – swiss german
  • Localization: Change Locale de_CH.UTF8 -> en_GB.UTF-8
  • Localization: Change timezone: Europe – Z├╝rich
  • Localization: Change Wifi Country: CH
  • Enable SSH and SPI

After a reboot, I updated the Pi with “sudo apt-get update” and “sudo apt-get upgrade”. Then I installed the Adafruit DotStar Pi and the PyOSC libraries. (For the PyOSC library to work, you need to run “sudo python setup.py install”. From the Adafruit library, you only need to copy the “dotstar.so” in the same folder, from where your Python script is executed.) When I finished updating and downloading, I changed the IP of the Pi to a fixed address by editing the dhcp config file (“sudo nano /etc/dhcpcd.conf”). In addition I disabled Bluetooth on all Raspberries by adding the line “dtoverlay=pi3-disable-bt” to “/boot/config.txt”, since I wasn’t using it and didn’t want it to interfere (it uses the same 2.4 GHz band as the Wifi).

Now I could start playing with the Adafruit_DotStar library, that was working nicely. After trying out a few things, it was clear, that setting each pixel manually with the “showPixelColor()” function was too slow. So I was going to use the “show()” function, that takes an array of bytes to set the color of the whole strip. My idea was to save out the video as text files, that could be fed line by line to the show function.

In order to do so, I needed to setup the DotStar strip in manual mode:

strip = Adafruit_DotStar()
strip.begin()
strip.setBrightness(0)

Then open the text file, that contains the video data, count the number of lines, that correspond to the number of video frames and create a variable, that holds the last played frame number:

with open("/home/pi/frames/frames1.txt", "rb") as fp:
	fone = fp.readlines()
	
foneNumFrames = sum(1 for _ in fone) - 1
foneIndex = -1

The next function is called, when an OSC message with the address “/leds/fone” is received. The data parameter holds the timecode as a floating point number in the range of 0-1. To get the current frame number, the timecode is multiplied with the number of frames. Since I was planning to repeat the transmission of every frame number, the function checks, if it has received the same frame number before. If it hasn’t, it stores the new frame number as the last played frame as “foneIndex” , then it reads the corresponding line from the text file and passes it as an array of bytes to the show function.

def recFoneIndex(addr, tags, data, client_address):
	global foneIndex
	global fone	
	global foneNumFrames
	
	index = int(data[0] * foneNumFrames)

	if index != foneIndex:	
		foneIndex = index
		f = fone[index].rstrip()
		strip.show(bytearray.fromhex(f))
	return

At the end of the script, the OSC server is setup. It listens to the local IP address on the port 7000. I added some default handlers, that handle arbitrary OSC messages and some specific handlers for my functions.

s = OSC.OSCServer(("",7000))

s.addDefaultHandlers()
s.addMsgHandler('default', recSampleRate) #all not explicitly handled OSC messages
s.addMsgHandler('/_samplerate', recSampleRate)
s.addMsgHandler('/leds/fone', recFoneIndex)

Since the group wanted to be able to play different videos, I did this setup for three different files. Then I added some functionality for testing and debugging and saved the Python file as oscServer3.py to a folder called scripts.

After the Python script was in place and tested, I created a startup script, that was executed after booting the Pi. It should not only start the
oscServer3.py script, but turn off the power saving functions of the WIFI chip as well, so it wouldn’t cause any problems. Another thing I added to this script, was manually changing the MAC address of the PI. Some of the Raspberries had the same MAC address, what was leading to connection problems with the WIFI router. By giving them all a unique address, this issues could be resolved.

#!/bin/sh

#This scripts is executed automatically after the Raspberry Pi has booted. 
#It changes the mac address of the Pi, turns off the power saving features of the wifi chip
#and starts the right Python scripts.

sudo ifconfig wlan0 down
sudo ifconfig wlan0 hw ether 00:11:22:33:44:13
sudo ifconfig wlan0 up
sudo iwconfig wlan0 power off

cd /
cd home/pi/scripts
sudo python oscServer3.py &

I saved the script in the scripts folder and made it executable with the command “chmod 775 launcher.sh”. To start it automatically after booting, I created a cronjob with “sudo crontab -e” and “@reboot sh /home/pi/scripts/launcher.sh >/home/pi/logs/cronlog 2>&1”. This command would additionally put a log file called cronlog in a logs folder. In case the script would throw any errors, they’d show up in this log file, which can be displayed with “cat cronlog” .

Now, that the controller was working nicely, I cloned the SD card with Win32 Disk Imager for the other 19 controller and manually changed the IP and MAC address on each device.