Space Within Spaces is an art installation by Joseph Morris installed in the Juliana Terian Design Center Atrium on the Pratt Institute campus in Brooklyn, New York. The installation uses an 18x10 array of light bulbs to form an ambient display for graphics generated based on muon activity through a nearby particle detector.
Joe had a custom PCB created to control each bulb with an ESP8266 on board so the brightness could be modulated over WiFi. The cables supporting each line of the array provided low voltage DC power for the bulbs and their controllers.
I offered to help write the software that would push graphics to the array from the muon activity visualization that Joe had created. There were a few interesting technical questions to answer:
- How do we update the display quickly over WiFi?
- How do we keep the individual bulbs in sync while the display is animating?
- How do we know where a particular bulb is in the array?
The solution I arrived at allowed updating the image on the display at about 100Hz with each bulb synchronized to a single frame. To get there I had to use a little bit of networking knowledge to exchange 27,000 packets for 1.
Updating a Distributed LED Array at 100Hz Over WiFi
The ESP8266 in each bulb was configured to connect to a WiFi network set up specifically for the installation and get an IP address using DHCP. The original firmware that was written before I started working on the project hosted a web server on each bulb with HTTP methods implemented to trigger brightness animations. First we tried animating the display by calling these methods on each bulb for every frame. But that resulted in a refresh rate of up to tens of seconds and poor synchronization between the lights.
We needed to reduce the number and size of messages being sent per frame. Because we were using HTTP over TCP each bulb update was taking 3 packets for the handshake to open the connection, one for the HTTP request, and then one for the reply. Multiplied by every bulb that meant at least 900 packets per frame, or 27,000 packets per second at 30 frames per second. That should not be a stressful packet rate relative to the capabilities of even an ordinary laptop, but it's enough that the tools and systems we were using to build the installation were starting to choke on the task. Instead of chasing down the reasons we were unable to efficiently deliver all of those packets the better option was to find a way to reduce the number of packets being sent.
Each ESP8266 could control the brightness of the bulb it was connected to in 1024 steps from off to full brightness using PWM. Since 1024 is 2¹⁰ that means a bulb's brightness can be expressed in exactly 10 bits. We are usually forced to deal with bytes of 8 bits so a brightness update could be expressed in 2 bytes with 10 bits of actual brightness information and 6 bits left unused. We have 180 bulbs in the array so a single frame that updates every bulb's brightness should contain at a bare minimum just 360 bytes.
Networks usually have a limit on the size of a single packet which is called the Maximum Transmission Unit, or MTU. Due to efficiency considerations this limit is usually 1500 bytes, which is well above our theoretical smallest update, meaning that we could potentially fit an update for the entire array in a single packet. But how do we get a single packet to every bulb without repeating it? And how does a bulb know what part of the data to use to update its brightness?
The first problem can be solved with a network facility called a "broadcast address." Basically these addresses are treated specially by a network and a packet with a broadcast address listed as its destination will be sent to every other client (read more in RFC 919). To make use of the broadcast functionality of the network we need to shed TCP because it is designed for unicast only. Instead we send a UDP packet to the broadcast address
255.255.255.255. The network broadcasts our packet to every client, including all of our ESP8266s. Each bulb looks at the packet and pulls out the brightness that it should have and updates its PWM signal to set the brightness to match.
How does the bulb know what value in the packet to use? Each ESP8266 contains a 32 bit chip ID which is not necessarily unique, but was for the ESP8266s that we were using. To make a map of the chip IDs to bulb position we started by retrieving the IP addresses of the bulbs from the DHCP assignments in the router. For each IP we sent HTTP requests to blink the bulb and retrieve the chip ID. Noting the position in the array of the bulb that blinked we built up a map of the chip IDs to positions. This map is included in the firmware so each bulb can find its position in the array. The order of the brightness values in the update packet that is broadcast to the array matches the order of the bulb positions in the array. So each bulb can use its chip ID to calculate the offset of its own new brightness value in the update packet.
After implementing this fairly simple scheme we were able to update the display at 100Hz with very good synchronization across the bulbs in the array. Once it was up and running I spent some time playing around with different effects, like synchronizing the display to audio:
Once the display was working well I also tried driving it from TouchDesigner:
Space Within Spaces
The technical implementation of the bulb array control is one small part of the overall art installation that involved over a year of work by Joe and collaboration with several others. It's worth reading about the project on his blog and on the project page on the Pratt website.