Send and receive infrared (IR) signals from ESP-IDF.
This is an ESP-IDF port of
IRremoteESP8266 v2.9.0. Every
protocol decoder, encoder and air-conditioner state class is carried over
unchanged, so the protocol behaviour is identical — the same 1669 unit tests
from upstream run against this code and pass. What was rewritten is the layer
underneath: the Arduino core dependency is gone, replaced by native ESP-IDF
GPIO, esp_timer and (optionally) LEDC.
- 130 protocols, most of them both send and decode.
- Full climate control for 60+ A/C brands via the vendor-neutral
IRacclass — mode, temperature, fan, swing, quiet, turbo, econo, sleep, clock. - No Arduino compatibility layer to install. It is a plain ESP-IDF
component with a
Kconfig, a manifest and nothing else. - Builds on ESP32, ESP32-S2/S3, ESP32-C2/C3/C5/C6, ESP32-H2 and ESP32-P4.
idf.py add-dependency "jorgecis/IRremoteIDF^1.0.2"git submodule add https://github.com/jorgecis/IRremoteIDF.git \
components/IRremoteIDFEither way, no further CMake changes are needed. Include the headers and go:
#include "IRsend.h"
static IRsend irsend(4); // GPIO driving the IR LED.
extern "C" void app_main(void) {
irsend.begin();
irsend.sendNEC(0x00FFE01FUL);
}| Function | Typical part | Notes |
|---|---|---|
| Receive | TSOP38238, VS1838B, TSOP4838 | 38 kHz demodulator. Output straight to a GPIO. |
| Transmit | 940 nm IR LED + NPN/MOSFET | Do not drive the LED from a GPIO directly; the current will exceed the pad rating. |
Pick a GPIO that is free at boot. GPIO 14 is a poor choice on the ESP32-C3 (strapping); GPIO 10 works there.
Transmit is bit-banged from the calling task, so the frame is only as clean
as that task's timing. Three settings matter, and the examples ship the
first one in their sdkconfig.defaults:
CONFIG_FREERTOS_HZ=1000(default 100). Spaces longer than 16 ms yield whole ticks to the scheduler and busy-wait the rest, so the tick sets the busy-wait tail before the next mark; 1 ms is what Arduino uses. The frame timing is correct at 100 Hz too, but protocols with long gaps (Kelvinator/Gree, Daikin, Mitsubishi...) spend up to 20 ms spinning per gap.CONFIG_IRREMOTE_TX_HW_CARRIER=y. Without it the 38 kHz carrier is toggled in software, and its shape depends on the optimisation level and on anything that preempts the task mid-mark.- Do not do other work from the sending task while a frame is on the air:
no logging, no LED updates, no RMT. Anything that blocks mid-frame
stretches a mark or a space. Do it before or after
send().
#include "IRrecv.h"
#include "IRutils.h"
#include "IRac.h"
static IRrecv irrecv(/*pin=*/14, /*bufsize=*/1024, /*timeout_ms=*/50,
/*save_buffer=*/true);
static decode_results results;
extern "C" void app_main(void) {
irrecv.enableIRIn();
while (true) {
if (irrecv.decode(&results)) {
printf("%s\n", resultToHumanReadableBasic(&results).c_str());
String ac = IRAcUtils::resultAcToString(&results);
if (ac.length()) printf("%s\n", ac.c_str());
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}timeout_ms is how long the line must stay quiet before a message is
considered finished. 15 ms suits normal remotes; A/C remotes need 50 ms or more.
IRac exposes one struct that maps onto every supported climate protocol:
IRac ac(4);
ac.next.protocol = decode_type_t::COOLIX;
ac.next.power = true;
ac.next.mode = stdAc::opmode_t::kCool;
ac.next.degrees = 24;
ac.next.celsius = true;
ac.next.fanspeed = stdAc::fanspeed_t::kMedium;
ac.sendAc();Switching brands is a one-line change. See docs/SupportedProtocols.md for the full list and which features each protocol carries.
Under Component config → IRremote (infrared send/receive):
| Option | Default | Effect |
|---|---|---|
| Enable every supported protocol | on | Turn off to pick protocols individually and shrink the build. |
Protocols → <name>: send / decode |
follows the above | One switch per protocol direction, 241 in total. |
| Generate the transmit carrier in hardware (LEDC) | off | Offloads the 30–60 kHz carrier to an LEDC channel. |
| Language/locale for human readable output | en-AU | 15 locales available, es-ES among them. |
| Print library debug output | off | Enables the internal DPRINT tracing. |
Trimming is worth real flash: the ir_receive_dump example is 318 KB with every
protocol enabled and 188 KB with NEC only.
Command-line -D flags still take priority, so an existing recipe such as
-D_IR_ENABLE_DEFAULT_=false -DDECODE_NEC=true keeps working:
target_compile_definitions(${COMPONENT_LIB} PUBLIC
_IR_ENABLE_DEFAULT_=false DECODE_NEC=true SEND_NEC=true)| Example | What it shows |
|---|---|
ir_send_demo |
Sending NEC / Sony / Samsung / RC5 and raw timings. |
ir_receive_dump |
Decoding, human-readable output and a pasteable raw dump. |
ac_control |
Driving an air conditioner through IRac. |
ir_repeater |
Capture and rebroadcast, including unknown protocols. |
cd examples/ir_receive_dump
idf.py set-target esp32
idf.py build flash monitorThe complete upstream test suite runs on the host against these sources — 83 binaries, 1669 cases:
git clone -b v1.12.x --depth 1 https://github.com/google/googletest.git lib/googletest
make -C test run| Workflow | Runs on | What it checks |
|---|---|---|
ci.yml |
push, PR | Host unit tests; the 4 examples on esp32/s3/c3 against IDF 5.1, 5.5 and 6.0; ir_receive_dump on all 9 declared targets; the LEDC carrier, debug, locale and minimal-protocol build options; that compote component pack accepts the manifest; that Kconfig matches the generator. |
lint.yml |
push, PR | cpplint over src/ and include/ with the upstream style rules, flake8 over tools/, yamllint over the manifests and workflows. |
docs.yml |
push to main | Doxygen (warnings are errors), published to GitHub Pages. |
release.yml |
tag v* |
Checks the tag matches idf_component.yml and has a CHANGELOG.md section, uploads the component to the ESP Component Registry, then cuts a GitHub Release from that section. Safe to re-run: a version already published or a release already created is skipped. |
release.yml needs an IDF_COMPONENT_API_TOKEN secret, generated from your
account on components.espressif.com.
To release, bump version: in idf_component.yml, rename ## Unreleased in
CHANGELOG.md to the same version, merge, then push a tag from main:
git tag -s v1.0.1 -m v1.0.1 && git push origin v1.0.1Pushing the tag is the whole release; there is no need to draft it in the GitHub web UI.
The user-facing API is unchanged apart from a few platform-specific corners. See docs/PORTING.md for the details and how the transmit and receive paths are implemented on ESP-IDF.
LGPL-2.1, inherited from IRremoteESP8266. The original copyright notices are kept in every file. See LICENSE.
Upstream authors: David Conran, Mark Szabo, Sebastien Warin, Ken Shirriff, Roi Dayan, Massimiliano Pinto, Christian Nilsson and the IRremoteESP8266 contributors.