WLED has loads of features and can be used to create all sorts of lighting effects that look wonderful, but that we won’t use in this tutorial today. If you’re setting up some LED lighting, though, it’s well worth browsing the project website, kno.wled.ge. One of the really great features is how easy it is to get up and running. You don’t need any programming experience or even any programming environment: just point your web browser to: install.wled.me. If you plug your ESP32 device into your computer’s USB port and click Install, you should end up with WLED installed on your device.
Next, we need to connect our LED matrix. When it comes to LED matrices, there are only two common ones: HUB75 matrices that typically come on stiff PCBs (and are sometimes called Px where x is the spacing of the LEDs in millimetres, such as P5), and WS2812B matrices that usually come on flexible PCBs. WLED only works with the latter of these, so make sure you get the right one.
Connecting your controller to the matrix is pretty simple. You just need to wire 5 V to the power input, ground to ground, and a microcontroller pin to the data-in pin. For simple testing, supplying power directly from the microcontroller like this is usually OK (and WLED has power control, as we shall see), but if you want to control a lot of LEDs and have them bright, you’ll need to wire up a more capable power source.
With the LEDs connected and the software installed, it’s time to connect. When you first start WLED, it will start its own WiFi access point called WLED_AP. This has the password wled1234. If you connect to this, it will take you to a ‘captive portal’. That may show up as a ‘this network needs you to sign in’ message, or it may show up when you try to navigate to a website. Either way, it’ll take you to a web page that lets you configure the WiFi settings. If you enter your WiFi SSID and password in there and save, the WLED_AP will shut down and the ESP32 device will try and connect to your local network.
You are now ready to connect to the device, but you need to know the IP address of it. If you have access to your WiFi router’s settings, you can take a look at the connected device settings and you should see WLED listed there with an IP address. If you don’t, you can install the WLED app on Android or iPhone. This will scan for any connected WLED instances.
Once you’ve found the IP address, you can connect to your LEDs using either the app or just by typing the IP address into a web browser. There, you can set the colours and add effects.
Remote access
There are a few ways of controlling WLED remotely. There’s an API, but this is mostly used for changing between different colours and effects. What we want is a way to remotely control all the LEDs – a bit like using it as a remote screen. For this, we can turn to DMX512. This is a protocol that was designed to control lighting effects. It was originally a wired protocol, but through the E1.31 protocol, it can run over WiFi, and this is what we’ll use.
One slight quirk of DMX is that it splits the lights up into ‘universes’ with each universe holding 512 different values. A value could be anything – it could be the value of a dimmer, it could be used to trigger a sound effect, but for us, it’ll be the value of either red, blue, or green on an LED. Since each LED needs three values, that means that we can fit at most 170 LEDs into one universe. Since we’re using a 16×16 matrix, we’ll need to use two universes. That’s not a huge problem, but it’s something that we need to remember when sending data.
Let’s now turn our attention to the software running on the control computer. We’ve tested this out on Windows, but as far as we can see, there’s nothing platform-dependent here, so it should work on Linux (including Raspberry Pi) or macOS as well. We’re using Python 3 with two libraries: sacn and PIL (via Pillow). You can install these with:
pip3 install sacn
pip3 install pillow
Now, let’s look at a really simple test – just setting all the pixels red:
import sacn
import time
ip_address = "192.168.1.81"
<b>
</b>
num_leds = 256
#connect to matrix
sender = sacn.sACNsender(fps=40)
sender.start() # start the sending thread
sender.activate_output(1)
sender[1].destination = ip_address
sender.activate_output(2)
sender[2].destination = ip_address
data = []
<b>
</b>
for i in range(num_leds):
data.append(10)
data.append(0)
data.append(0)
def send_two_universes(data, sender):
sender[1].dmx_data = data[:510]
sender[2].dmx_data = data[510:]
send_two_universes(data, sender)
time.sleep(5)
sender.stop()
There are a few decisions we’ve made in this code. The first is to connect to a specific device. This isn’t actually necessary. E1.31 senders often send data multicast to every device on a network and then use the universe numbers for making sure only the right things light up, but Windows doesn’t really like doing that, so we’re using a specific IP address instead. As you can see, we’ve set a destination for each universe and activated it. We’ve then created a set of data that’s just the RGB colour 10,0,0 over and over again. We could do something a bit more artistic, but this is just a test and the artistic bit comes next. We’ve used Python’s list slicing to extract the data for the first and second universes, and then sent them.
WLED will only display this while the E1.31 connection remains open. In this case, we’ve paused for five seconds and then stopped the connection, which will revert your LED matrix back to whatever it was doing before.
If you run this, hopefully your matrix will light up red for five seconds.
You could use this to implement all sorts of effects.
Let’s now add in Pillow to display an animated GIF.
import sacn
import time
time.sleep(5)
from PIL import Image
ip_address = "192.168.1.81"
filename = "fire.gif"
width=16
height = 16
matrix_size = (width,height)
frame_pause = 0.5
def generate_image_data(image):
image_data = []
for y in range(height):
for x in range(width):
if (y%2==0):
for val in image.getpixel((15-x,y)):
image_data.append(val)
else:
for val in image.getpixel((x,y)):
image_data.append(val)
return image_data
def generate_image_data_palette(image):
image_data = []
for y in range(height):
for x in range(width):
if (y%2==0):
start_val = image.getpixel((15-x,y))*3
for i in range(start_val, start_val+3):
image_data.append(image.getpalette()[i])
else:
start_val = image.getpixel((x,y))*3
for i in range(start_val, start_val+3):
image_data.append(image.getpalette()[i])
return image_data
def send_two_universes(data, sender):
sender[1].dmx_data = data[:510]
sender[2].dmx_data = data[510:]
#load a gif
img = Image.open(filename)
img.seek(1)
out_imgs = []
out_datas = []
try:
while 1:
this_img = img.resize(matrix_size)
out_datas.append(generate_image_data
palette(this_img))
img.seek(img.tell() + 1)
except EOFError:
pass
#connect to matrix
sender = sacn.sACNsender(fps=40)
sender.start() # start the sending thread
sender.activate_output(1)
sender[1].destination = ip_address
sender.activate_output(2)
sender[2].destination = ip_address
sender.manual_flush = True
for i in range(5):
for data in out_datas:
send_two_universes(data, sender)
sender.flush()
time.sleep(frame_pause)
sender.stop()
First, we have to load in the data from the animated GIF. Pillow is really designed to work with static images, but you can use image sequences. Basically, the image object will act like a static image, but you can use the seek method to change the particular static image in the sequence that it will use. In this code, we resize any image to 16×16, regardless of whether that makes any sense. It then extracts the colours. There are two methods here for extracting colours. On some images (such as JPEGs), the getpixel() method will return R, G, B values for that particular pixel. In GIFs, it instead returns a colour number that references the image palette, which is an index of the colours used in the image. We’ve included both methods here to help, but only use the palette version with GIFs. We also have to re-sequence the data because our matrix is wired in a zig-zag pattern, with each row going in an opposite direction. Typically, when you set a universe’s dmx_data, it will send that instantly, but if you have more than one universe, this could cause tearing of the image. Instead, we’re setting manual flush, which means that both parts of the image will be set at the same time. That’s our basic method of displaying an animated GIF on an LED matrix. You should be able to use the same basic method for displaying all sorts of images and effects on WS2812B LED matrices.