Configuring and Programming a Raspberry Pi to control an LED strip

Gregg Larson
15 min readDec 27, 2019

--

I like to play around with LEDs and micro controllers in my free time. From holiday lights to wearable’s, I’ve dabbled in lots of different ways of using LEDs, and all of them based on either Arduino and Raspberry Pi. The rPi requires more configuration in order to make it work with LED’s, so I thought I’d write down how I set up my Raspberry Pi as a headless bluetooth low energy LED controller.

In this article, I am focusing on how to set up a Raspberry Pi from scratch, and configure it as a Bluetooth Low Energy (BLE) device, and write some code that can control a series of WS2812 LEDs. I am using a Raspberry Pi Zero, a 5 meter WS2812B LED Strip, and an external 5V power supply for the LED Strip. I will show you how to write a simple javascript application for the Raspberry Pi that will give your device some services, such as changing the brightness and setting the color.

Once you figure out how to do this, there are many directions you can go with it. I like to play around with holiday lights, but I’ve taken a liking to LED wearables, in particular, my LED tuxedo, which is a hit every time I put it on. People who do this are known as “glowers”, and I’ve seen dresses, ties, costumes, shoes and plenty other wearables. What makes connecting them to a microcontroller unique is that you can make them respond to sound, footsteps, proximity, or simply programming a sequence, like the rainbow, which is pretty entertaining.

The wiring diagram associated with the project is shown above. It is important to know that you should be using an external power supply for the LEDs, and a separate power supply for the rPi. The LED strip has a simple connection, with one data line, represented as the green line, the 5V input, represented by the red line, and the ground, represented by the black line. Connect the data line to pin 12 (GPIO 18)of the rPi, and tie the grounds together (pin 14 of rPI).

Setting up a Headless Raspberry Pi Zero

I chose the rPi zero for my project, because I wanted a small footprint and a microcontroller that would easily fit into my jacket pocket. Setting up a new raspberry pi to be used in remote situations can be “cumbersome” because, instead of plugging in a keyboard, mouse, and monitor, and using a GUI, we’ll be using wifi and bluetooth as our only means of communication.

The first step is to build out an image. After you download and unzip the image that you want to use, you can burn it to an SD card. For Windows, I use Etcher, but there are others available.

Etcher makes it easy to burn your Raspberry Pi Images

Enable SSH by Default: Once that is done the file system on the SD card will not be readable on Windows computers, however, the boot partition is available, and you can add two files in this location to enable both WIFI and SSH. In your boot partition, you need a blank file with no extension called “ssh”. Secondarily, you need to add a file called “wpa_supplicant.conf”, which is used to configure your WIFI. the RPi will move this to the correct location when it boots.

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=GB
network={
ssid="<your SSID"
psk="<your password>"
key_mgmt=WPA-PSK
}

In regards to the ext4 file system on windows, there are a few other choices available, but most of them are read only viewers (at least for my experience), like “ext2read” and Disk Internals’ “Linux Reader”. Then there are a few options from Paragon Software, which have a free trial, but will cost money after the trial which allow you to save the necessary SSH file in the root of the partition. They also allow you full access to the entire filesystem of the RPi, which you may find useful. However, since my tasks can be done through the boot partition, I did not find them necessary.

Finding the IP Address: After powering up the RPI, I let it run for a while, and, then using a free tool called Advanced IP Scanner, I was able to locate the IP address of the new device.

Then I open up Putty and connect to the RPi:

Working with WS2011 LEDs

One of the downfalls of working with the ws2811 library on the RPi is that you cannot simultaneously use audio and control the lights. Since this project requires no audio, this is not an issue for me.

Install the packages

sudo apt-get update
sudo apt-get install gcc make build-essential python-dev git scons swig

Open the following file

sudo nano /etc/modprobe.d/snd-blacklist.conf

Add the following line:

blacklist snd_bcm2835

Edit the configuration file by finding:

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

and change to

# Enable audio (loads snd_bcm2835)
#dtparam=audio=on

restart the system

sudo reboot

Install Pip

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python get-pip.py

Install the libraries

sudo pip install rpi_ws281x

Getting Bluetooth enabled on RPI Zero W

Before you can use the bluetooth interface with the Pi, you need to configure it. Since we are headless, we’ll need to ssh into the device and use the command line interface. There are a few commands on the raspberry pi that help you understand bluetooth. First is the unix command hciconfig which allows you to configure bluetooth devices.

One of the first things you should do is update the bluetooth libraries on your RPi. The BLE features are new, and the installed version in Raspbian is out of date. I followed this article from adafruit to enable your RPi as a Bluetooth Low Engery Device

hciconfig -a

will output something like

hci0:   Type: Primary  Bus: UART
BD Address: B8:27:EB:00:66:4E ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN
RX bytes:6492 acl:0 sco:0 events:168 errors:0
TX bytes:3193 acl:0 sco:0 commands:100 errors:0
Features: 0xbf 0xfe 0xcf 0xfe 0xdb 0xff 0x7b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
Link policy: RSWITCH SNIFF
Link mode: SLAVE ACCEPT
Name: 'raspberrypi'
Class: 0x000000
Service Classes: Unspecified
Device Class: Miscellaneous,
HCI Version: 4.1 (0x7) Revision: 0x168
LMP Version: 4.1 (0x7) Subversion: 0x2209
Manufacturer: Broadcom Corporation (15)

you can also use the help command to see all the possible instructions

hciconfig -h

There is also a utility called bluetoothctl which acts as your agent to negotiate passcodes and the ability to be discoverable.

$ sudo bluetoothctl

will enter you into configuration mode

[NEW] Controller B8:27:EB:00:66:4E raspberrypi [default]
[bluetooth]# agent on
Agent registered
[bluetooth]# discoverable on
Changing discoverable on succeeded
[CHG] Controller B8:27:EB:00:66:4E Discoverable: yes
[bluetooth]#

There are a few additional steps that must be taken in order to have your Raspberry Pi Zero W work properly with the react native bluetooth library. Adafruit has a few awesome articles that will help you with this. What it comes down to is updating the bluez libraries on you RPi, enabling an experimental mode to allow for BLE communication, and then set up the advertising protocol so that BLE listeners can find the device.

I found out about this because I was not seeing my raspberry pi as a device in the sample react code. After searching the git repo for clues, I came across a link to an a blog post “Turning a Raspberry Pi 3 into a Bluetooth Low Energy peripheral”. It’s a great discussion on the steps required, and references two Adafruit Articles: How to compile and install bluez, the Linux Bluetooth classic & low energy system, on the Raspberry Pi. and Introduction to Bluetooth Low Energy which lead you through the steps to do this. Even after these steps, there are still a few more. You need to now enable the advertising protocol for BLE. I found an article on stackoverflow that talked about how to do this without installing additional software or writing your own code. It comes down to these additional commands:

sudo hcitool -i hci0 cmd 0x08 0x0008 1e 02 01 1a 1a ff 4c 00 02 15 e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 00 00 00 00 c5 00 00 00 00 00 00 00 00 00 00 00 00 000 00 00 00   
hciconfig hci0 leadv 0

If you want to read more, here is a link to the article: Using hcitool to set ad packets. When complete, if you run the show command within bluetoothctl you should see something like:

Controller B8:27:EB:00:66:4E (public)
Name: raspberrypi
Alias: raspberrypi
Class: 0x00000000
Powered: yes
Discoverable: no
Pairable: yes
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d0532
Discovering: no

At this point you should be able to use your phone or computer to scan for Bluetooth devices and you should see a “raspberrypi” available.

Enabling Bluetooth on Startup

After all of these steps, your device will still keep bluetooth power down and it will not be discoverable. The code in Part II requires that the device is in a powered up state, so it is nice to have your device power up bluetooth in the boot sequence. To do this, the most reliable way the I found was to edit your rc.local file and first add the command to power up.

First edit rc.local:

sudo nano /etc/rc.local

then append the command that I have hightlighted in bold

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
sudo hciconfig hci0 up
exit 0

Save the file and reboot your device, when you log in if you run the command

sudo hciconfig

the output should be:

hci0:   Type: Primary  Bus: UART
BD Address: B8:27:EB:00:66:4E ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN
RX bytes:1357 acl:0 sco:0 events:73 errors:0
TX bytes:1183 acl:0 sco:0 commands:73 errors:0

If you’ve made it this far, it’s time to move on to Part II, where we will program the device!

Programming the Device

I wrote my device code in JavaScript and run it using node.js. I’ll go it step by step and then give a link to my github account at the end so you can download it. There are two libraries that do the heavy lifting: Bleno to control the Bluetooth connection, and rpi-ws281x-native, which is a node.js package to control ws281x-LEDs.

To start, create a new folder, and install Bleno and rpi-ws281x-native, using the instructions they provide. Once installed, create a new javascript file, and add the packages to your project

const bleno = require("bleno");var ws281x = require('rpi-ws281x-native/lib/ws281x-native');

I added the ability to add an input variable that determines the amount of LEDs in your strip, the default is 83 (which was the amount of LEDs on my Tuxedo). My 5 meter strips have 300 LEDs. NUM_LEDs is an integer and pixelData is an array used to control the state of each LED.

var NUM_LEDS = parseInt(process.argv[2], 10) || 83,pixelData = new Uint32Array(NUM_LEDS);

The next set of code is used to clean up your LEDs and make your rPi stable if it is interrupted

process.on('SIGINT', function () {   ws281x.reset();   process.nextTick(function () { process.exit(0); });});

We now get into programming the Bluetooth interface. The first thing you need to do is create UUIDs for your service, as well as each Characteristic (function) that your device will advertise it is capable of. For this example, I allow two Characteristics: the ability to change the color (LED_PATTERN_UUID), and brightness (LED_BRIGHTNESS_UUID).

const LED_SERVICE_UUID = "00010000-89BD-43C8-9231-40F6E305F96D";const LED_PATTERN_UUID = "00010001-89BD-43C8-9231-40F6E305F96D";const LED_BRIGHTNESS_UUID = "00010002-89BD-43C8-9231-40F6E305F96D";

Each Characteristic needs to implement the Bleno Characteristic class. This defines what to do when the device receives a read and write request. Here is the characteristic for changing the brightness. We’ll see later how the class is instantiated, but, the main points of the class are to define the properties (read, write), and the functions onWriteRequest and onReadRequest. The important function here is onWriteRequest, because this is where the LED brightness is set. The setBrightness function for the LEDs take an integer between 0 and 300, so I take the text value from the Bluetooth protocol, and convert it to an integer.

class LEDBrightnessCharacteristic extends bleno.Characteristic {
constructor(uuid, name) {
super({
uuid: uuid,
properties: ["read","write"],
value: null,
descriptors: [
new bleno.Descriptor({
uuid: "2901",
value: name
})
]
});
this.argument = 0;
this.name = name;
}
onWriteRequest(data, offset, withoutResponse, callback) {
try {
if(data.length == 0) {
callback(this.RESULT_INVALID_ATTRIBUTE_LENGTH);
return;
}
console.log(`raw data ${data}`);
this.argument = data;
ws281x.setBrightness(parseInt(data));
console.log(`LED Brightness ${this.name} is now ${this.argument}`);

callback(this.RESULT_SUCCESS);
} catch (err) {
console.error(err);
callback(this.RESULT_UNLIKELY_ERROR);
}
}
onReadRequest(offset, callback) {
try {
const result = 255;
console.log(`LEDBrightnessCharacteristic result: ${result}`);
let data = new Buffer(1);
data.writeUInt8(result, 0);
callback(this.RESULT_SUCCESS, data);
} catch (err) {
console.error(err);
callback(this.RESULT_UNLIKELY_ERROR);
}
}
}

Once you have your Characteristics created, you will need to configure Bleno to use them, as well as configure the Bluetooth service to advertise your service. To start, you will need to listen for Bluetooth state changes, and, when the Bluetooth service is powered on, you can begin advertising your service:

bleno.on("stateChange", state => {    if (state === "poweredOn") {

bleno.startAdvertising("PartyLapel", [PARTYLAPEL_SERVICE_UUID], err => {
if (err) console.log(err);
});
} else {
console.log("Stopping...");
bleno.stopAdvertising();
}
});

The last step is to configure the advertising service to broadcast your service, and it’s characteristics. This is where we will instantiate the characteristic classes you created above. For this example, we have two, one to change the color, the other to change the brightness. As show, I create an instance of each, and pass into the the UUID and name of the characteristic that will be broadcast.

bleno.on("advertisingStart", err => {    console.log("Configuring services...");

if(err) {
console.error(err);
return;
}
let LEDpattern = new LEDPatternCharacteristic(LED_PATTERN_UUID, "LED Pattern");
let LEDBrightness = new LEDBrightnessCharacteristic(LED_BRIGHTNESS_UUID, "LED Brightness");
let partylapel = new bleno.PrimaryService({
uuid: PARTYLAPEL_SERVICE_UUID,
characteristics: [
LEDpattern,
LEDBrightness
]
});
bleno.setServices([partylapel], err => {
if(err)
console.log(err);
else
console.log("Services configured");
});
});

Running the Code

Before this code can run, you need to make sure that your the Bluetooth on your raspberry Pi is up and discoverable. If you followed the steps in the first article, you would have added these commands to your rc.local file on the device. If not, and you are SSH’ing into your device, use the hciconfig tool, which can accomplish this in one command:

sudo hciconfig hci0 up

running the command

hciconfig

should show you the following output (I highlighted the UP RUNNNG PSCAN)

hciconfig
hci0: Type: Primary Bus: UART
BD Address: B8:27:EB:00:66:4E ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN
RX bytes:1357 acl:0 sco:0 events:73 errors:0
TX bytes:1183 acl:0 sco:0 commands:73 errors:0

you are now able to run the code with the following command (assuming you are using ledcontroller.js as your file)

sudo node ledcontroller.js 300

the output should be

Starting bleno...
Press <ctrl>+C to exit.
Bleno: Adapter changed state to poweredOn
Configuring services...
Bleno: servicesSet
Services configured
Bleno: advertisingStart

Testing the Code

Before diving in to writing code, you can do a dry run of your setup using an BLE scanner app for your phone. I use an app call BLE Scanner 4.0, but there are plenty of choices available for both iOS and Android. There are apps for Mac and Windows as well, such as BlueSee BLE Debugger for the mac.

When you scan for devices, you should see yours listed:

Opening the Device will show you the proper Connection Information

09:21:22.5120: connected in underlying BLE layer.
09:21:22.5130: discovered services: (
"00010000-89BD-43C8-9231-40F6E305F96D"
)
09:21:22.8600: discovered characteristics for service 00010000-89BD-43C8-9231-40F6E305F96D: (
"00010001-89BD-43C8-9231-40F6E305F96D",
"00010002-89BD-43C8-9231-40F6E305F96D"
)
09:21:22.8600: state changed to 'Connected'.

After connecting to the device, you should see the following in your rPi terminal:

Bleno: accept 38:f9:d3:26:da:be

Clicking on the service will open a window to show you the services and how to interact with them. Make sure you select “text” or “ASCII” . The service “00010002–89BD-43C8–9231–40F6E305F96D” correlates to the LED Brightness, so I set it to 100.

pressing the write button shows the following in the debug logs:

09:44:00.2000: wrote to characteristic 00010002-89BD-43C8-9231-40F6E305F96D___00010000-89BD-43C8-9231-40F6E305F96D: <313030>

and looking at the rPi terminal

raw data 100
LED Brightness LED Brightness is now 100

and your lights should have dimmed!

Enabling on Startup

If you followed the steps in the first article, you can also add your application to the rc.local script so that, in addition to starting Bluetooth at boot, your application will also be running.

First edit rc.local:

sudo nano /etc/rc.local

then append the command that I have hightlighted in bold (remember to change the path to the location of your file)

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
sudo hciconfig hci0 up
sudo node <enter your path to file>/partylapel.js 300
exit 0

Why would you want to do this?

Because you are a maker! I consider this a base setup for experimenting with a connected rPi Zero, and pretty much perform these steps with each new rPI Zero I use. I can get these for $5.00 at our local Micro Center, they have a nice protective case, and can easily be powered using a usb power stick. Using BLE, you can write mobile apps that communicate to your device to control it. Using WIFI, you can connect your device to the cloud or an IoT platform and start streaming sensor data. Also, the support libraries for python are quite large, and the npm modules are quickly catching up in number and reliability. So, while my desire was for LED art, the ideas are limitless once you have your device configured correctly! I hope this helps get you started.

React Native Application

I do have an article that describes how to build a React Native application that can communicate with this device and control it. If you are interested, here is the link:

About Me

Helpful Links

Here are some articles I found along the way that helped me figure things out, maybe you’ll find something interesting:

--

--

Gregg Larson

Guitar Tab Creator, rukind.com, LED's, and staff engineer at Outside, Inc.