An infrared blaster and receiver carrier board for CPU modules with the Seeed Studio XIAO footprint, with ESPHome firmware.
The board provides an IR transmitter, an IRM-8601S IR receiver, five
capacitive touch pads, and I2C and UART headers. KiCad sources for
revisions 0.1 and 0.2 are under hw/, and the IR receiver datasheet
is in datasheets/.
cp esphome/secrets.yaml.example esphome/secrets.yaml # add your WiFi
$EDITOR esphome/secrets.yaml
docker compose run --rm esphome compile ir-blastr.yaml
docker compose run --rm esphome run ir-blastr.yaml # build and flash
docker compose up -d # dashboard, port 6052On Docker Desktop for Mac, the dashboard is published with an explicit port
mapping (6052:6052). Do not use network_mode: host there — it binds
inside Docker's Linux VM and localhost:6052 on the Mac never sees it.
docker compose pins the same ESPHome version CI uses, so a build that
works locally works in CI. Override it with ESPHOME_VERSION in a .env
file if you want to try a different release.
Toolchains and build output live in a named Docker volume rather than the repo, so nothing needs cleaning up between builds.
Reusable pieces live under esphome/packages/. The
example-*.yaml files at the top of esphome/ are complete
device configs you can copy and adapt; they are not meant to be included
from other configs.
| Package | What it provides |
|---|---|
ir-blastr.yaml |
Device identity, WiFi, API, OTA, IR transmitter and receiver |
boards/xiao-s3.yaml |
Build settings and pin map for a XIAO ESP32-S3 |
touch.yaml |
The five capacitive touch pads |
ceiling-fan-homekit.yaml |
HomeKit fan and light bridge 1 |
| Example config | What it demonstrates |
|---|---|
ir-blastr.yaml |
Bare board: packages only, no appliance |
example-ceiling-fan.yaml |
Pronto codes for a 6-button ceiling fan remote |
example-midea-ac.yaml |
Midea AC as a Home Assistant thermostat |
example-sharp-tv.yaml |
Sharp TV keys via a protocol-generated raw script |
example-honeywell-mn12ces.yaml |
Honeywell MN12CES portable AC via NEC button codes |
The three examples solve the "how do I talk to this thing" problem in
different ways. example-ceiling-fan.yaml transmits pronto codes captured
from the original remote. example-midea-ac.yaml uses a climate platform,
and ships alternative stanzas to work through until the unit responds.
example-sharp-tv.yaml generates Sharp IR frames in a lambda because
ESPHome has no Sharp codec.
A minimal configuration of your own:
substitutions:
device_name: den-ir-blastr
friendly_name: "Den IR BlastR"
packages:
board: !include packages/boards/xiao-s3.yaml
ir_blastr: !include packages/ir-blastr.yaml
touch: !include packages/touch.yamlOr, to keep your own WiFi, API and OTA conventions, copy only
packages/boards/ and the IR pin definitions you need.
Files named esphome/local*.yaml are git ignored. Put configurations for
your actual devices there and they will stay out of the repository while
still being visible to the ESPHome dashboard.
The board is a carrier for any module with the XIAO footprint, so nothing
outside packages/boards/ refers to a GPIO number. Board packages define
substitutions for the board's logical signals, and every other package
consumes those:
| Signal | Substitution |
|---|---|
| IR_XMT | ir_transmitter_pin |
| IR_RCV | ir_receiver_pin |
| T0 - T4 | touch_pad_0_pin … touch_pad_4_pin |
| SDA, SCL | i2c_sda_pin, i2c_scl_pin |
| TX, RX | uart_tx_pin, uart_rx_pin |
To add a module, copy packages/boards/xiao-s3.yaml, change the esp32:
block, and remap the substitutions. Modules whose chip has no capacitive
touch peripheral simply can't be used with packages/touch.yaml.
Anything worth changing is a substitution with a default, overridable from your device config. The most useful ones:
| Substitution | Default | Notes |
|---|---|---|
ir_receiver_dump |
pronto |
Set to all while identifying an unknown remote |
ir_receiver_inverted |
false |
Depends on the receiver module |
ir_carrier_duty_percent |
50% |
|
touch_setup_mode |
false |
Set true to log raw touch values for tuning |
touch_pad_N_threshold |
1000 |
Tune per pad against the logged values |
To capture codes from an existing remote, build with dump enabled, watch
docker compose run --rm esphome logs example-ceiling-fan.yaml, and press
buttons. Copy the resulting pronto strings into your own config.
CI compiles every reference configuration on
each push and pull request and uploads the resulting firmware as a build
artifact. Lint runs yamllint over the
repository, actionlint over the workflows, and esphome config over each
configuration - the last expands all packages and substitutions and runs
every component's schema validation without paying for a full compile.
Both workflows and docker-compose.yaml pin the same ESPHOME_VERSION;
bump all three together.
Run the same checks locally:
yamllint --strict .
docker run --rm -v "$PWD:/repo" --workdir /repo rhysd/actionlint
docker compose run --rm esphome config ir-blastr.yamlMIT. See also the code of conduct.
Footnotes
-
HomeKit support comes from the alpha HAP-ESPHome external component, which does not compile against ESPHome 2026.7.4 - it pulls libsodium in as a managed IDF component, which ESPHome no longer adds to the generated
srccomponent'sREQUIRES.esphome/ir-blastr-homekit.yamlis still schema-validated by the lint workflow but is excluded from the CI build matrix until that is fixed upstream. ↩