For a long time I’ve wanted to make some interactive art for my wife and her partner to celebrate their connection. With the onset of the pandemic isolation, which is currently keeping them apart, I realized that maybe I could do more. Maybe I could build something that might help them maintain that connection despite the separation they are now enduring.
That simple idea eventually turned into the Love Lamps. They are a pair of (hopefully) beautiful lamps which know how to talk to each other via the internet. In their default state they are glowing, solid colored cubes that slowly change color over time. Touching one for a moment sends a message of love to the other lamp. On the touched lamp that looks like a pulsing rainbow moving upward, and on the receiving lamp it looks like a pulsing rainbow moving downward. Touching the receiving lamp for a moment sends a message of love back and both lamps turn into a swirl of color for a few minutes to celebrate the love connection.
Even though we live in an age of constant communication and ubiquitous cell phones I hoped that these lamps might offer a novel way to reach out and touch each other, just a bit. I finally delivered the lamps yesterday and so far it seems like they’re doing just that. I’m very curious to see how they get used over time, but I hope they bring some joy and beauty to a difficult time.
Here’s a video of me running them through their paces in one of the many testing phases of the project. Please excuse the poor quality. Capturing light art on camera is a difficult thing and these lamps seem to be especially difficult since camera doesn’t handle the big color shifts terribly well.
Hardware
The hardware for this project is pretty straight forward. Each lamp is driven by a single Adafruit ESP 32 Feather microcontroller which drives a series of DotStar LEDs through a level shifter. Really the only tricky bit was getting the hardware into a form factor that allowed it to fit well inside the lamp body I chose. The second revision of the hardware is quite a bit shorter which helped to achieve uniform lighting of the cube.
Software
While the hardware was fairly straight forward, the software proved to be quite a bit trickier. That complication was mainly down to the internet getting involved. More on that in a moment.
Fundamentally each Love Lamp is a simple state machine which responds to both local input, via the touch interface, and the replicated state of the remote lamp. There are 7 regular states, plus a couple of additional states for tracking the configuration of the network interface via WPS and the initial wifi connection process. Each state has an associated animation so the user can follow what the lamp is doing. Note that some states share animations.
When I started this project I was still using my old workstation which had an unfortunate USB issue that caused me to have to reboot every third time I reconnected a microcontroller (otherwise the machine wouldn’t recognize the microcontroller and I couldn’t upload new code). This was tremendously frustrating and led me to design the codebase to be as multiplatform as possible so I could do testing and debugging on my Windows machine before uploading the code to the ESP32.
I’ve implemented hardware abstraction layers before for previous projects, but this was absolutely the most thorough and fully featured version of that approach I’ve used to date. At times it felt a bit silly to be building so much abstraction into this little embedded project, but having functional unit tests and the ability to debug the hardware independent portions of the code on my workstation was absolutely worth it in the end. I definitely caught a number of tricky state bugs that way, and having regression tests for the bugs I encountered in my on-hardware tests absolutely helped my piece of mind as I was wrapping up the project.
Issues
The usual foibles of software development aside, there were two major issues I encountered during this project: Getting the touch interface to be reliable and, much harder, getting the lamp communication to be reliable.
Touch Reliability
The touch interface was a perfect example of the impact that small hardware changes can have on a project. During my initial testing I was working with the electronics on a breadboard and I had a short (maybe 2 inch) piece of the brass wire standing in as the touch interface. This was working great, but as soon as I started using the actual brass wire attached to the lamp as the touch interface it became terribly unreliable.
This kind of touch interface uses capacitive sensing. Unsurprisingly, a much larger wire has quite a different capacitance. This had the effect of narrowing the sensing band and made my initial “click” based interface thoroughly unreliable. It both registered false clicks and missed clicks regularly. Not ideal. In the end the solution was a combination of robust debouncing and moving to an interface based entirely on the duration of a touch. Touching for more than a quarter second, but less than a second is a single click, holding the touch for more than a second (indicated to the user by a special pulse of the lamp) is a double click, and holding for more than five seconds (indicated to the user by a second special pulse) is treated as a triple click. These touches are debounced on both ends (hence the quarter second minimum touch time) and are now entirely reliable. You can still see the old interface in action in the video near the beginning of this post.
Reliable Communication
The second issue, reliable network communication, was much more complicated to deal with. The lamps need to talk to each other in order to replicate state, but they can’t talk directly to each other as I didn’t want anyone having to worry about NATs, firewalls, etc. The user experience was meant to be as simple as possible. This requires using a back-end server to host the replicated state, with the lamps querying periodically to check for remote state changes and pushing their own state to the server when it changes locally.
Initially I wanted to avoid having to run that server myself, so I tried to use one of the existing IoT backend services available. These tend to be based around MQTT and desiged around the idea of data collection. They are absolutely not set up for reliable communication between devices, but I thought maybe I could misuse them to achieve my goal of remote state replication. That worked… sort of. Using MQTT’s built in reliability mechansims wasn’t sufficient, and even attempting to build a reliability layer on top of MQTT wasn’t giving me the rock solid results I was looking for. In the end I had to stand up my own service to get the job done. Of course that involved replacing the MQTT library with a more generic HTTP client on the lamp hardware in addition to dusting off my Mysql and CGI scripting skills. Many, many tests later I have quite reliable communication between the lamps (so long as the server doesn’t run into any issues).
I run the service on my web host, same as this site, which is convenient because I don’t have to do anything to maintain the server itself. The downside is that I don’t have root access and my debugging facilities are limited. So when, during testing, I ran into various issues with intermittent CGI script timeouts I had to make guesses about what was going on and rely on technical support from my hosting provider. I’m not 100% comfortable with this situation - it’s never a great feeling to not know exactly what the issue was or why their configuration changes fixed it - but it seems to be stable enough now that we’ve worked the kinks out. I’m keeping my fingers crossed that I don’t need to go a step further and manage a server myself to ensure better reliability.
User Documentation
Since this project was created as a gift I decided to put together some friendly documentation. These sheets are just the right size to sit on top of the lamp, where they were tied with a ribbon when the gift was delivered. It was great fun to combine my love of tiny signs with a light art project. Everything might get a set of instruction sheets from now on!