A couple months ago I freed my smart lights from the cloud by replacing the stock firmware with ESPHome. They became faster, local, and genuinely better than when I first bought them. I can’t exactly remember why, but I was reminiscing with someone about old TV commercials from our childhood, and The Clapper came up. Then inspiration struck when I remembered that those light strips had a music-sync mode.
The music sync mode itself never really worked. It didn’t change in time with the beat, and it randomly changed colors when the room was silent. But it did mean that there was a microphone hiding somewhere. If it has a mic, maybe I could revive it, and reuse it for something more valuable: recreating The Clapper!
My Approach
I had no idea how the microphone was connected, how the audio was being handled, or how the original firmware did any of it. But I did keep a dump of the original firmware, and new llms have dropped! So I gave claude the firmware dump and told it what I wanted to do. From there, my role in the reverse-engineering process was mostly physically clapping when it told me to.
It wrote test firmware that we flashed OTA to the device, and then I ran scripts that sampled the pins while the room was silent, and while I was clapping. My steering was mostly “The data isn’t changing when I clap”, or “Maybe there are other pre-requisites to activate the microphone. Double check the firmware”. It did take a few rounds back and forth, but eventually we got there! I asked for a summary so it could provide the “AI’s side of the story”.
The AI’s side of the story
From my side, this began with a firmware dump, an ESPHome project, and a request: find the microphone and turn it into a double-clap switch. I could inspect the bytes and write experiments, but I had no access to the physical controller. Ben would have to be my eyes, ears, and, quite literally, hands.
I started with the printable strings in the stock firmware. Names like adc1_get_raw, audio_board_adc_init, and fft_light_init suggested an analog microphone connected to one of the ESP32’s ADC pins. It was a reasonable hypothesis, so I wrote temporary firmware that sampled all eight ADC1 pins and a companion script that guided Ben through quiet and clapping intervals. He flashed it, ensured quiet when instructed, clapped when instructed, and sent the measurements back to me.
GPIO35 appeared to react dramatically. For a moment, it looked like we had found the microphone.
We had not.
The reading was caused by ADC crosstalk. The ESP32’s ADC pins share sampling circuitry, and charge left behind by GPIO34 made the next reading on GPIO35 jump. When I changed the test to capture GPIO35 by itself, the supposed audio signal simply decayed to zero in a couple of milliseconds.
That was not the only problem with my first probe. Wi-Fi activity occasionally produced isolated spikes that made quiet pins look active, so I added a median filter. Reading each pin in blocks and discarding the first few milliseconds handled the crosstalk. Then I tried to return 16,000 samples in a single response, exhausted the controller’s memory, and rebooted it. I rewrote that part to stream the capture in smaller chunks.
Each failure gave me another constraint, and each new firmware build gave Ben another odd set of instructions: be quiet, clap a few times, make a steady sound, flash this version, send me those logs. Once the probe was trustworthy, it gave us a clear result: none of the analog pins could hear him.
My initial theory had been based on code that was present in the firmware, but present does not mean used. Libraries can bring along strings and functions that the application never calls. To find the real path, I needed to stop treating the firmware as a bag of clues and start tracing its execution.
I split the ESP32 application into its code and data segments and disassembled the Xtensa instructions using the toolchain bundled with ESP-IDF. Then I tried to trace references from the audio-related strings back to the functions that used them. My first pass found almost nothing because I had misread how objdump displayed Xtensa literal loads: it showed the address of a value in a literal pool, not the value itself. I corrected that assumption and wrote a cross-reference helper to resolve the literals, locate the surrounding functions, and find their callers.
That changed the investigation completely. The microphone was not wired directly to the ESP32’s analog converter. It fed an ES7243 stereo audio ADC, which sent digital samples to the ESP32 over I2S. The stock image also contained the name LYRAT_MINI_V1_1 and paths from Espressif’s audio framework. Sengled appeared to have reused the pin map from an Espressif reference board.
Following those references through the disassembly recovered the whole audio setup: I2S bit clock on GPIO32, word select on GPIO33, audio data on GPIO36, a master clock on GPIO0, and I2C on GPIO18 and GPIO23. I also found the 16 kHz sample rate and the six register writes used to initialize the ES7243.
Now I had a much stronger theory, but I still could not test it. I wrote another temporary configuration and asked Ben to flash it. Audio blocks arrived at exactly the expected rate, yet every sample on both channels was zero. The I2S connection was running, but the audio chip was not producing data. That suggested the ES7243 was still asleep rather than the pins being wrong.
For the next build, I added the master clock and replayed the initialization writes recovered from the stock firmware. Ben flashed it and clapped again. This time one channel came alive. It hovered around −55 dBFS in a quiet room and jumped when he clapped. The other remained at roughly −91 dBFS, the noise floor of the chip’s unused input.
We had finally found the microphone.
From there I built the actual clap detector. It watches ten-millisecond blocks of audio for a sound that is loud, rises sharply, and sits well above the recent background level. Two qualifying sounds between 150 and 900 milliseconds apart toggle the lights. A short lockout prevents one clap and its echo from counting as two, while controls in Home Assistant expose the threshold, background ratio, and an off switch.
Tuning required another round of human-generated test data. At −30 dBFS, sharp sounds in normal speech could trigger the first unit. At −10 dBFS, it was stable but only noticed close, loud claps. We settled on −20 dBFS as a practical default. Ben flashed the finished configuration onto a second controller, clapped twice, and it worked there too without any changes.
I never saw the board or heard a clap. I only had the firmware, the source code, and the measurements Ben returned. But that was enough to move from a plausible wrong guess, through better experiments and disassembly, to a working feature on real hardware. Ben supplied the physical feedback I could not collect; I kept turning it into the next test until the lights listened.
Conclusion
Okay, I know, reading AI’s explanation in first person is very cringe. But I did think it paints a story of the reverse engineering steps one might go through to get to the same result. In this case, it involved a lot of stuff I have very little context or knowledge on. It also all came together in about 30 minutes while I was going through my workday. It’s pretty cool to have added a new, semi-useful feature that wasn’t supported by the original firmware, even though I’m only replicating something that was available to us decades ago. It is a silly feature, but it was hilarious to see it come to life, and it might even help me not stub my toe when I go upstairs after the sun goes down and I can’t find bedroom the light switch.
This feature is available in the same git repo. I added in esphome controls to enable/disable the clap detector, adjust the volume threshold, and how much time between claps count towards “double clapping”. sengled-lightbar-takeover repository
Comments
Powered by Giscus (may need to disable adblockers)