Turn open IR code databases into ready-to-use ESPHome remote_transmitter
components. The add-on serves codes as a git repo your ESPHome YAML clones like
any other remote package — no IR codes in your config, just a path to a remote.
Two sources ship today: any
Flipper-IRDB repo or fork, built on
demand from the url: path at your ref: (…/<owner>/<name>.git), and Home
Assistant's curated
infrared-protocols
code sets (reserved …/ha-ir.git). See Adapters.
Status: working transformer + Dockerized smart-HTTP git service + Home Assistant add-on, serving the
flipper+ha-iradapters. An end-to-end CI test stands up the container and has ESPHome clone a component from each adapter to build real firmware (see CI). The flipper Sony Bravia path is also verified on real hardware against a live TV; ha-ir is compile-verified only. Next: more protocols — see Roadmap.
Press the button on a $20 M5 Atom Lite and your TV toggles. The IR codes are never hand-typed — they're pulled, at build time, straight from the open Flipper-IRDB by the codegen service.
This is the exact device that CI compiles on every PR
(firmware-test/device.yaml):
esp32:
board: m5stack-atom # M5 Atom Lite
remote_transmitter:
id: ir_tx
pin: GPIO12 # the Atom's built-in IR LED
packages: # <- codes pulled LIVE from the codegen service
sony_bravia:
url: http://<addon-host>:9418/Lucaslhm/Flipper-IRDB.git # source repo IS the path
ref: main # any branch/tag, built on demand
files: [TVs/Sony/Sony_Bravia.yaml] # the Flipper path selects the remote
binary_sensor:
- platform: gpio
pin: { number: GPIO39, inverted: true, mode: { input: true } }
on_press:
- button.press: tv_sony_bravia_power_toggle # Sony "Power" toggle, pulled from the DBThe point: there are no IR codes in your config — just a reference to a device
in a community database. The url: path is the Flipper-IRDB repo (or fork) and
ref: the branch/tag; the add-on builds that repo@ref into a .yaml-per-.ir
mirror on demand and caches it. The files: path is the selector:
TVs/Sony/Sony_Bravia.yaml is the generated form of TVs/Sony/Sony_Bravia.ir.
Swap it for any of the thousands of remotes in Flipper-IRDB and reflash — that
one line is the only place a specific remote is named, and the clone is shared
across every device that points at it. (For curated codes, point url: at the
reserved …/ha-ir.git instead — see Adapters.)
Logitech Harmony is discontinued; SofaBaton and similar universal remotes are closed, cloud-tied, and cost real money. The goal here is a fully open, local, DIY Harmony-style universal remote on commodity ESP hardware — codes sourced from an open database, no vendor lock-in, for roughly the price of a sandwich. If it lands, it's an order-of-magnitude cheaper, fully hackable alternative.
This project stands on work that already proved the .ir → ESPHome mapping is
practical:
- balloob/flipper-ir-esphome
by Paulus Schoutsen (founder of Home Assistant) — a web converter that
turns a Flipper
.irfile into ESPHome YAML. This is the direct inspiration; the transformer here is the same idea wrapped as an on-demand, ref-pinned service. Thank you, balloob. - home-assistant-libs/infrared-protocols — Home Assistant's IR protocol encoder/decoder library. This project depends on it: every parsed protocol (NEC, Sony, Samsung32, RC5, Sharp…) is encoded by this library, not by code of our own. It's the same encoder stack HA's native infrared platform uses, so the codes we emit are protocol-correct by construction.
- READYWARE Signal Editor — a browser-based multi-format IR/RF converter (Flipper ↔ ESPHome/Pronto/LIRC…).
- Lucaslhm/Flipper-IRDB — the community IR code database this consumes.
- probonopd/irdb — the classic protocol/hex IR database.
Capturing or looking up IR codes and hand-pasting them into ESPHome is tedious, and the existing converters are manual (paste in, copy out). The goal: reference a code set declaratively from your device YAML and have it generated for you.
The key constraint that shapes everything: ESPHome is static. Entities are fixed at compile time and baked into firmware — the device can't fetch a URL at runtime and spawn commands. So this runs as build-time codegen, not an on-device feature. Changing codes still means recompile + reflash; the add-on just removes the copy-paste.
In one sentence: you point it at a remote's code file in the Flipper database, and it hands ESPHome the buttons that press those codes.
Step by step:
- Pick a remote. Browse Flipper-IRDB
and find your device's
.irfile — e.g.TVs/Sony/Sony_Bravia.ir. Name it in your device'sfiles:(as…Sony_Bravia.yaml); pinref:so the result can never change under you. - The add-on translates that repo@ref on demand into a
.yaml-per-.irmirror (cached, rebuilt when the branch moves); ESPHome clones it andfiles:selects your remote's component. - Each remote button becomes a
remote_transmitter.transmit_rawaction. Parsed protocols (Sony, NEC, Samsung32, RC5, Sharp…) are encoded by the infrared-protocols library into raw timings; a Flipperrawcapture passes through directly. - Out comes a block of ESPHome YAML — one
buttonper remote key, each wired to your IR transmitter. - Include that YAML in your device config and flash. Home Assistant now has a button for every key on the original remote.
Think of it as a translator: Flipper speaks "remote button," ESPHome speaks "transmit action," and this sits in the middle. It runs while you build the firmware (not on the device), so switching remotes means regenerate + reflash.
Quick peek — CLI prints the YAML:
docker build -t esphome-ir-codegen .
docker run --rm esphome-ir-codegen \
--ref d126fb1b6f1e114c52b4a8c19839ea65e3a9c24d \
--path TVs/Sony/Sony_Bravia.ir --tx ir_tx --prefix "Bravia"Real usage — run it as a service and let ESPHome clone from it:
docker run -d -p 9418:9418 esphome-ir-codegen --serve # prebuilds reserved ha-ir.git
# --adapters flipper serve only flipper (skip the ha-ir prebuild)
# The Flipper source repo + ref are per-device (URL path + ref:), not flags.# in your ESPHome device config — the url path is the source repo, ref: the branch:
packages:
tv:
url: http://localhost:9418/Lucaslhm/Flipper-IRDB.git # or …/ha-ir.git (reserved)
ref: main
files: [TVs/Sony/Sony_Bravia.yaml]How the remote is resolved: the url: path names the Flipper-IRDB repo (or
fork) and ref: the branch/tag; the add-on clones that repo@ref and mirrors each
TVs/Sony/Sony_Bravia.ir to a generated TVs/Sony/Sony_Bravia.yaml, on the first
request and cached after. ESPHome clones that repo (smart HTTP, shallow) and
files: selects the component. The device decides everything — source repo,
ref, and remote — so one running add-on serves every device.
There are two kinds of source, both served over the same smart-HTTP service; the
shared infrared-protocols
encoder turns every command into transmit_raw, so they differ only in where the
codes come from and the path layout:
| Source | url: |
From | files: path |
|---|---|---|---|
| flipper | …/<owner>/<name>.git (on demand) |
any Flipper-IRDB repo/fork at ref: |
mirrors the tree — TVs/Sony/Sony_Bravia.yaml |
| ha-ir | …/ha-ir.git (reserved, prebuilt) |
infrared-protocols' own curated code sets | <brand>/<type>.yaml — vizio/tv.yaml |
Point a device at whichever source has your remote:
packages:
tv:
url: http://<addon-host>:9418/ha-ir.git # reserved; or …/<owner>/<name>.git
ref: main
files: [vizio/tv.yaml]git http-backend serves any repo under its root, so the reserved ha-ir.git
and the on-demand <owner>/<name>.git repos all share one encoder.
- No source config — repo + ref are per-device. The
url:path is the Flipper source repo (any fork) andref:is the branch/tag; the add-on has norepo/refoption, onlyadapters(which sources to serve). The device selects the remote viafiles:, so one running add-on serves every device. - Build on demand, keyed by
(repo, ref). Each(repo, ref)is cloned and transformed on its first request, cached as a branch in a per-repo bare repo, and rebuilt automatically when the source branch HEAD moves (no add-on restart). ESPHome reuses its own clone cache too (defaultrefresh1d— just don't set0s, which re-clones on every build). - Live-service dependency is acceptable. ESPHome clones from the add-on at
compile time; if it's down, the build fails. The device's
ref:selects the codes, so a green build is reproducible. - Button ids are
<path-prefix>_<canonical>. A path-derived prefix (tv_sony_bravia) namespaces remotes; the key is the raw name resolved to a canonical control viahomeops-ir-canonicalat generation time, so the same control is identically named on every remote (VOL+/Vol_up/VOLUME_UP→…_volume_up). Parsing/encoding ishomeops-ir-adapter; this service only renders YAML + serves git.
┌─ <owner>/<name>.git ◀─ Flipper-IRDB repo@ref (.ir, on demand)
ESPHome build ─url(=repo)/ref/files─▶ HA add-on ┤ (smart-HTTP git service)
(compile) clone + select └─ ha-ir.git ◀─ infrared-protocols code sets (reserved)
◀──────── generated ESPHome YAML component (buttons) ─────────
Transformer core (done): turn each IR signal into an ESPHome button entity
calling remote_transmitter.transmit_raw. The flipper source parses .ir files;
the ha-ir source reads infrared-protocols Commands. Both encode through the
same library, so all output is transmit_raw.
Serving layer (done): ESPHome remote packages: are git-based, so the add-on
serves smart HTTP (git http-backend, required because ESPHome does shallow
clones, which dumb HTTP can't serve). The Flipper source repo is the URL path and
the ref is the request, so each (repo, ref) is built on demand:
| Stage | Mechanism | Status |
|---|---|---|
| Now | --serve builds the requested <owner>/<name>.git@<ref> on demand (cached, HEAD-refreshed) and prebuilds the reserved ha-ir.git; ESPHome clones one repo, selecting files: [<path>.yaml]. Both exercised end-to-end in CI; flipper verified on real hardware. |
✅ |
| Next | More protocols (RC6/Kaseikyo/Panasonic) + a receiver-based correctness check. | ⏳ |
| Later | A runtime adapter that pushes codes to HA's ir_rf_proxy (no recompile to switch remotes). |
⏳ |
ESPHome usage today:
packages:
tv_codes:
url: http://<addon-host>:9418/Lucaslhm/Flipper-IRDB.git # url path = source repo
ref: main # branch/tag, built on demand
files: [TVs/Sony/Sony_Bravia.yaml] # the Flipper path = the remoteEvery parsed signal is encoded by the
infrared-protocols
library and emitted as transmit_raw (the library returns raw timings). We keep
no protocol math of our own.
| Flipper signal | ESPHome output | Status |
|---|---|---|
type: raw |
transmit_raw (sign-alternated, carrier from frequency) |
✅ direct passthrough |
type: parsed NEC / NECext |
transmit_raw via NECCommand |
✅ |
type: parsed SIRC / SIRC15 / SIRC20 (Sony) |
transmit_raw via SonyCommand |
✅ |
type: parsed Samsung32 / RC5 / RC5X / Sharp |
transmit_raw via the matching *Command |
✅ |
| other parsed (NEC42, RC6, Kaseikyo, …) | # TODO unsupported comment |
⏳ next |
Single frame + transmit-layer repeat. The library returns one frame per
command — by design, repetition is the transmit layer's job (the same split HA's
IR proxy uses). So parsed signals are emitted with a repeat: (Sony SIRC and
others need ~3 frames before a device acts); a raw capture is authoritative and
sent as-is. Validity ≠ correctness: a green compile proves the YAML is valid,
not that the code is right for your device — verify against a live
remote_receiver capture, or on the real hardware.
Requires Python ≥ 3.14 (the
infrared-protocolsdependency). The Docker image and CI use it; the esphome compile step is unaffected.
# From a pinned Flipper-IRDB ref + path
python flipper_ir_to_esphome.py \
--ref main --path TVs/Samsung/Samsung_TV.ir \
--tx ir_tx --prefix TV
# From a local file
python flipper_ir_to_esphome.py --file Samsung_TV.ir --prefix TVThe codegen ships as a Home Assistant add-on (this repo is also an add-on
repository — see addon/ and repository.yaml):
-
Settings → Add-ons → Add-on Store → ⋮ → Repositories, add
https://github.com/HomeOps/esphome-ir-codegen. -
Install ESPHome IR Codegen and Start it. Nothing to configure — the source repo and ref are per-device; the only option is
adapters(defaultflipper,ha-ir). The reserved ha-ir.git is prebuilt at startup; flipper repos build on first request (a few minutes), then cached. -
Point ESPHome at your Flipper repo (or the reserved
ha-ir.git) and pick a remote by path:packages: tv: url: http://<addon-host>:9418/Lucaslhm/Flipper-IRDB.git ref: main files: [TVs/Sony/Sony_Bravia.yaml]
The add-on image is built FROM the published GHCR image and just adds an
options-reading entrypoint (no script duplication). Full usage: addon/DOCS.md.
Every push and PR runs the real thing (.github/workflows/ci.yaml), exercising
the add-on as a live service — not a pre-generated file:
- Build the codegen Docker image.
- Start it as a running container, then warm
Lucaslhm/Flipper-IRDB.git @ main(on-demand build) and confirm the reservedha-ir.git. - Compile real ESP32 firmware, one device per source, each cloning its
component from that running container:
firmware-test/device.yaml→Lucaslhm/Flipper-IRDB.git@main,files: [TVs/Sony/Sony_Bravia.yaml]firmware-test/device-ha-ir.yaml→ha-ir.git,files: [vizio/tv.yaml]
- Capture both generated components and upload them as the build artifact
(the
.binis intentionally not kept — the useful output is the YAML).
If a served component were malformed, step 3 fails — so a green build means each
adapter produces valid firmware. (Validity ≠ correctness: that the codes are
the right ones for your device is verified separately against a live
remote_receiver capture, and on real hardware.) These compiles are the
regression test guarding every future PR.
Releases use release-please
(changelog in addon/CHANGELOG.md, where Home Assistant's
add-on update dialog reads it); a tagged release publishes the image to GHCR from
the release-please run itself (.github/workflows/release-please.yaml).
- Parsed-protocol coverage: NEC/NECext, Sony (SIRC/SIRC15/SIRC20), Samsung32, RC5/RC5X, Sharp — all via infrared-protocols. Plus raw.
-
Dockerfile+ end-to-end CI (Sony Bravia + Vizio TV → firmware). - Smart-HTTP git backend: on-demand
<owner>/<name>.git@<ref>+ reservedha-ir.git. - HA add-on packaging (
addon/config.yaml,adaptersoption; repo+ref per-device). - More parsed protocols: RC6, Panasonic/Kaseikyo, Pioneer, NEC42 — each verified against a receiver capture.
- A runtime adapter that pushes codes to HA's
ir_rf_proxy(no recompile to switch remotes). - On-demand generation (vs. whole DB at container start) to cut first-boot.
- Button-set selection / naming / dedup.