Matthew Gamble's Blog
← Back to reflections
I Lost My Receiver Remote, So I Built a Smart IR Blaster with an ESP32

code

I Lost My Receiver Remote, So I Built a Smart IR Blaster with an ESP32

M
Matthew Gamble
7 min read
"I lost the remote for my Yamaha RX-V385 receiver."

I lost the remote for my Yamaha RX-V385 receiver. Not temporarily misplaced it, not left it between couch cushions. Gone. Vanished into whatever dimension claims TV remotes, single socks, and 10mm sockets. I blame the dog (she loves remotes).

The sensible thing to do would be to spend $20 on a replacement remote and move on with my life. But where's the fun in that? Instead, I spent an evening with an ESP32, an IR LED, and a mass of frustrating protocol issues to turn my "dumb" receiver into a network-controlled device I can operate from a tablet. And honestly? The end result is better than the original remote ever was.

The Starting Point

For those not familiar, the RX-V385 is Yamaha's entry-level AV receiver. No Wi-Fi, no Ethernet, no MusicCast, no network control of any kind. It's a box that takes HDMI inputs, outputs audio, and listens for infrared signals from a remote I no longer have. The only way to talk to it without getting off the couch is through that little IR sensor on the front panel.

The plan was simple: stick an ESP32 on my network, wire up an IR LED, and have it blast commands at the receiver over HTTP. Tablet sends a REST call, ESP32 fires an IR signal, receiver obeys. The architecture is clean and the parts cost under $5. A $3 ESP32 dev board, a bag of IR LEDs from Amazon (940nm, the standard for consumer IR), and a 100-ohm resistor. That's it.

Getting Started (The Easy Part)

The initial setup was almost suspiciously straightforward. The IRremoteESP8266 library handles all the heavy lifting for generating IR signals, and Yamaha publishes their IR codes in a PDF. Wire GPIO4 through a resistor to the IR LED, point it at the receiver, and fire away.

I had a test sketch running in about 15 minutes: press the boot button on the ESP32, it sends the Yamaha power toggle command, and the receiver turns on. Beautiful. Volume up, volume down, mute, all worked on the first try. I was feeling pretty good about my life choices at this point.

So what could possibly go wrong?

The Input Code Rabbit Hole

Everything worked except the one thing I needed most: switching inputs. Power, volume, mute, tuner, all fine. But HDMI1? Nothing. HDMI2? Silence. HDMI3? The receiver just sat there, smugly ignoring me.

The Yamaha IR code sheet lists commands in a format like 7A-1A for volume up (works perfectly) and 7A-4738 for HDMI1 (does not work at all). See the difference? The working codes are two bytes. The input codes are four bytes. That's where the trouble starts.

I tried everything. The standard NEC encoding that worked for volume. Pre-computed raw 32-bit values with Yamaha's proprietary check bytes. Extended NEC with 16-bit addresses. Bit-reversed commands. Bit-reversed addresses. Bit-reversed everything. I even found a blog post about LIRC bit ordering that looked promising but turned out to be solving a completely different problem.

At one point I accidentally discovered what I'm pretty sure is the sleep timer command, because my receiver just powered off mid-sweep. That was fun.

Brute Force: The Engineer's Last Resort

After exhausting every reasonable theory about how to interpret those four-byte codes, I did what any self-respecting engineer would do: I wrote a brute-force sweep. The ESP32 would cycle through all 256 possible command values at the known address, one every 1.5 seconds, while I stared at the receiver's front panel like it owed me money. Every 16 commands it sent a volume-up pulse as a sanity check to prove the LED was still firing.

The sweep confirmed two things. First, the address was correct, commands in the right ranges triggered things like tuner selection and AUX input. Second, nothing in the entire 256-command space triggered an HDMI input switch. Not one. The codes simply weren't there.

But wait, there's more. That sweep was actually crucial, because it proved the HDMI input commands don't live at the same address as everything else. The Yamaha uses a completely different encoding for input selection than it does for volume and power. The code sheet wasn't wrong exactly, I was just reading it wrong.

The Breakthrough

After hours of protocol archaeology, I stumbled onto a forum post on xbian.org that had a full LIRC configuration dump for the RAV333, which is the remote that ships with the RX-V385 family. These weren't interpreted codes from a PDF. These were actual captured values from a real remote, in raw 32-bit NEC format.

The LIRC codes for HDMI1 looked nothing like what I'd been trying. The standard NEC commands (volume, power) use the format [address][~address][command][~command], where the second and fourth bytes are just bitwise inversions of the first and third. The HDMI codes use a sub-device scheme where the second byte is a completely different value, not an inversion at all. No amount of mathematical derivation from the PDF was going to produce those values because the PDF's notation was ambiguous about which bytes were addresses and which were commands.

I plugged in the raw LIRC value for HDMI1, sent it, and the receiver switched inputs instantly. Every single HDMI and AV input code from that forum post worked on the first try.

Building the Actual Remote

With the IR codes finally sorted, building the rest was genuinely straightforward. The ESP32 runs a tiny async web server with two endpoints: POST /ir accepts a JSON body with the device and command name, looks it up in a registry, and fires the IR signal. GET /status returns a health check and lists all available commands. That's the entire firmware. No state, no logic, just an IR bridge.

The tablet UI is a single HTML file hosted on an internal web server. It talks directly to the ESP32 for the Yamaha, and I also wired up my LG TV's WebOS WebSocket API for direct network control of the TV (no IR needed for that one, LG's been shipping a local control API since around 2014). One page, two devices, no cloud services, no app store, no account creation.

The LG integration had its own set of surprises, mostly around WebSocket origin headers and a proxy I had to write to get around the TV's origin whitelist. But compared to the IR code saga, that was a minor detour.

The Bottom Line

The whole project, from first wiring to working tablet remote, took one evening. Most of that time was spent fighting Yamaha's undocumented IR protocol quirks, not writing actual code. The ESP32 firmware is maybe 120 lines. The web UI is a single file. The WebSocket proxy for the LG TV is about 30 lines of Node.

The total hardware cost was around $8 (the ESP32 board and a bag of IR LEDs), and the result is better than the original remote in every way. It's a tablet interface that controls both my receiver and TV from one screen, with discrete power on/off commands (so automation sequences don't accidentally toggle things the wrong way), all the sound modes, and every input the receiver supports.

I still need to 3D print a case so the ESP32 doesn't look like a sad little breadboard sitting on top of my media cabinet. But functionally? It's done. A "dumb" receiver from 2018 now has full network control, and it cost less than a replacement remote.

So what can we do about the pile of "dumb" IR devices in our homes? Honestly, more than you'd think. An ESP32 and a $0.10 LED can make almost anything network-controllable. The hard part isn't the electronics or the code. It's finding the right IR codes. And if Yamaha is reading this: please, for the love of all that is holy, document your sub-device NEC encoding properly. A PDF that mixes two completely different byte formats without explanation is not documentation, isn't just a puzzle, it is pure evil.

Comments (0)

Sign in to join the discussion

Loading comments...