Project / Demo

Impersonating a speaker

My TV speakers obey exactly one thing: a proprietary Bluetooth dial. I wanted them to obey the TV remote — so I taught a $15 microcontroller to impersonate the speaker on one side and the remote on the other.

My TV's sound runs through a pair of Logitech Z407 computer speakers, and their only volume control is a proprietary wireless dial. The TV remote speaks infrared; the speakers listen only to Bluetooth; and Logitech neither sells the dial separately nor documents what it says. All I wanted was for volume-up on the TV remote to make the speakers louder. That takes two things: something to sit in the middle and translate, and a translation — which exists only because the logi-z407-reverse-engineering project eavesdropped on the dial and published the protocol.

The plan

The something is an ESP32 — a $15 microcontroller with WiFi and Bluetooth on one chip, able to play both sides of a Bluetooth conversation at once. To the speaker it acts like a remote and sends commands; to the dial it acts like the speaker and accepts them, forwarding everything through. One radio wearing two faces. And once it's in the middle, anything it can hear becomes a volume knob — an infrared receiver, a web page on a phone, the original dial.

The protocol turns out to be small: every command is two bytes, and the speaker acknowledges each one (volume-up comes back as C0 02). There is no command to ask where the volume is — you can only nudge it. Every Z407 controller in the world is open-loop.

The Z407's entire control vocabulary — two bytes per command, sent after a two-message handshake.
BytesMeaning
84 05, then 84 00Handshake — announce yourself, then take control
80 02 / 80 03Volume up / down
80 00 / 80 01Bass up / down
80 04Play / pause
80 05 / 80 06Next / previous track
81 01 / 81 02 / 81 03Input select — Bluetooth / AUX / USB

Talking to the speaker

The commanding half went quickly: connect, run the handshake, and any two-byte code just works. The first end-to-end test was typing v+ into a serial console and listening to the volume climb. One detail matters for everything that follows: the speaker holds exactly one control connection, and whoever gets there first owns the volume.

Fooling the dial

The dial was another matter. Dressed up as a perfect Z407 — same service identifiers, same name — the ESP32 got no attention at all. That refusal was the first real finding: the dial doesn't search for speakers, even while pairing; it seeks the one Bluetooth address it's bonded to. So the disguise has to go all the way down — the ESP32 takes over the speaker's exact address. Do that, and the dial connects within seconds, every time.

Connected is not convinced. Five more things all had to be right, the most telling being layout: the dial expects the speaker's features at exactly the table positions it memorized, so I patched the Bluetooth library to stop shifting them. The ESP32 also has to initiate pairing the instant the dial connects, just as the real speaker does in Logitech's own pair-a-replacement-dial procedure. Get it all right, and the reward is watching 80 02  80 02  80 03 scroll up the serial monitor as you turn the dial: real button presses, arriving at a computer pretending to be a speaker.

The wall

Each half worked. Together, they didn't — and the failure was maddeningly specific. With both faces on, the dial connects, pairs, encrypts — then skips the final step: subscribing to the channel the speaker answers on. Until that happens the dial considers itself not ready and ignores its own buttons. It sits there — connected, encrypted, mute — then gives up and leaves.

The controlled experiments all failed the same way. Advertising under the ESP32's own address: the dial never even tries — it hunts one address only. Putting the speaker link on a lazy, low-bandwidth schedule, in case the two connections were fighting over the radio: no change. Then the decisive one — dropping the speaker link entirely the instant the dial connects, giving it the radio to itself: pairing and encryption completed cleanly, and the dial sat idle for twenty-two seconds, encrypted and silent, and left. That run eliminated every theory I could test from inside the ESP32; whatever the dial waits for doesn't pass through either end of the link I control. Seeing it will take a $15 nRF52840 dongle running Nordic's Bluetooth sniffer — a look at the packets on the air instead of inferences from one end. Until then, the dial half is parked.

Any remote but the dial

The point was never the dial — it was the TV remote and my phone, and those only need the half that works.

Infrared: a one-dollar receiver on a single pin, captured by the ESP32's pulse-timing hardware. Instead of decoding any particular remote's protocol, the firmware hashes the raw pulse train — a trick borrowed from Ken Shirriff's IRremote library — so the same button always produces the same 32-bit number on any remote ever made. Learning a button means pressing it once and pasting a hash into the firmware. Volume-up on the TV remote now means volume-up on the speakers.

WiFi: the ESP32 joins the home network and serves its own remote-control page — volume, bass, play/pause, track skip, input select — at http://TV.local/, straight out of its flash memory to my phone. No app, no cloud, no hosting; the whole remote is a web page living inside the thing it controls. When the WiFi silently refused to connect, I added a diagnostic that prints every network the radio can actually see before each attempt — which instantly exposed a one-letter typo in the network name. The scanner stays in the firmware: the most useful debugging tool is the one that shows you what the device actually sees, not what you believe it should.

Where it stands

The TV remote changes the volume; my phone carries the full remote. The dial sits in a drawer — while the ESP32 owns the speaker's single control slot, the dial has no one to talk to, and that's the accepted trade for now. When the sniffer arrives, the twenty-two-second question reopens: what is the dial listening for? I can't see it from where I stand — which is another way of saying I need a better instrument.

← Back to all projects