Project · April 2026 · working

IR remote wizard: ESP32 firmware + a code-discovery add-on

A standalone ESP32 IR blaster written as a proper ESPHome component, paired with a Home Assistant add-on that discovers IR codes from the Flipper-IRDB database — where most of the work was getting IR protocol encoding right.

Embedded C++ESPHome componentESP32Python / FastAPIIR protocolsState machineSPIFFSHome Assistant

Repository ↗

HERO: IR blaster board + web UI on a phone

What it is

Two related pieces that solve one problem — controlling any IR device when you’ve lost the remote or want it on your network:

Together they took a couple of thousand lines of C++ and over four thousand of Python across a real release history (the add-on is at v0.7.5).

Problem / motivation

IR is deceptively hard. “Send the power code” hides a dozen protocol-specific encodings, each with its own bit order, frame format, and repeat behavior, and the failure mode is silent — the TV just doesn’t respond and you have no idea whether the code, the encoding, or the LED is wrong. I wanted a system where discovery was automatic and the encoding was actually correct, which meant getting into the protocol details.

Architecture / approach

Firmware side: the ESPHome component wraps ESPHome’s IR TX/RX primitives, routes a small REST API through a custom async HTTP handler, and persists a flat JSON array of device profiles to SPIFFS. A learn mode captures codes from a physical remote across a dozen protocols (NEC and variants, Samsung, Sony SIRC, RC5/RC6, LG, Panasonic, JVC, and more).

Add-on side: a FastAPI app drives a session state machine (connect → device type → brand → discover → results), builds an SQLite database from Flipper-IRDB at image-build time, talks to the ESP32 over ESPHome’s native API (aioesphomeapi), and translates each Flipper protocol string into the right ESPHome service call.

DIAGRAM: firmware ↔ wizard ↔ Home Assistant + Flipper-IRDB

What went wrong and how it was diagnosed

Almost every bug was a protocol-encoding bug, and each one is invisible until you test against a real device:

Samsung codes did nothing — two bugs at once. Samsung TVs were completely unresponsive. The converter had two independent faults: it was missing the LSB→MSB bit reversal (Flipper stores codes LSB-first, ESPHome expects MSB-first), and it used the wrong frame layout (addr + ~addr instead of Samsung32’s addr + addr + cmd + ~cmd). A power code that should have encoded to 0xE0E040BF was coming out as garbage. Fixing both the bit order and the frame format brought the entire Samsung protocol back.

Sony devices ignored single presses. Sony’s SIRC protocol requires each command to be sent at least three times with ~45 ms gaps to register — a single transmission is spec-legal but ignored by the device. Added an explicit repeat count (default 1, set to 3 for SIRC) with the inter-frame delays. SIRC also needed its own LSB-first bit reversal, same root cause as Samsung but a different protocol.

NEC frames decoded as JVC. On the firmware side, NEC-encoded frames were malformed — a loopback decode came back as JVC instead of NEC. ESPHome’s NEC encoder defaults to zero command-repeats, which omits the repeat frame and leaves an invalid burst. Hard-coding one repeat fixed it. NEC is the most common TV protocol, so this one mattered.

The learn mode drowned in noise. During discovery the wizard was being spammed with false Pronto/CanalSat decodes coming off electrical noise on the protoboard, because the receiver defaulted to dumping every protocol it thought it saw. Setting the receiver’s dump list to empty (the real protocol handlers were already gated behind a listen-mode flag) cleaned up the logs and killed the false positives.

ESP-IDF’s web server rejected JSON POSTs. Getting the standalone firmware’s REST API to work meant discovering that ESP-IDF’s HTTP server is stricter than Arduino’s — it wouldn’t invoke the body handler for application/json, and it has no DELETE method at all. Both needed workarounds (reading the raw request body directly; routing deletes through POST endpoints).

Results

Tools & skills demonstrated

Firmware architecture beyond sketch level (a real ESPHome component, async HTTP, SPIFFS persistence), IR protocol internals (bit order, frame formats, repeat timing) debugged against real devices, Python/FastAPI with a session state machine, database-backed discovery, and the ESP-IDF vs. Arduino distinctions that only surface when you leave the training wheels behind.


← All projects