This project implements an infrared transmitter for controlling ForestAir air conditioners using an ESP32 and the RMT (Remote Control) peripheral. It is based on reverse-engineering an original ForestAir IR remote and reproducing its 35-bit pulse-distance encoded protocol.
The implementation includes:
- Accurate IR timing (38 kHz carrier, 33% duty)
- Complete 35-bit frame generator
- Bit-field-based payload structure for AC mode, fan, temperature, etc.
- The included protocol documentation is intended to help others understand, use, or port this implementation.
- ForestAir IR Transmitter (ESP32)
- Accurate reproduction of ForestAir IR protocol
- Clean bit-field payload representation
- Works with standard IR LED + NPN transistor driver
- Modular design (send_payload(), send_ir())
- Fully documented packet structure
- Extensible for automation, Home Assistant, ESPHome IR bridges, etc.
- ESP32 (any variant with RMT peripheral)
- IR LED (940 nm typical)
By default, the code uses:
TX Pin: GPIO14
RMT Channel: 0
Carrier: 38 kHz @ 33% duty
This project is built using ESP-IDF. If you do not already have ESP-IDF installed (or you want a cleaner workflow), you can use my ESP-IDF environment manager: idfmgr
idfmgr lets you install, switch, and manage multiple ESP-IDF versions automatically. Once installed you can build and run this project with:
idfmgr list
idfmgr install <version>
idfmgr build
idfmgr flash --monitorIf you prefer to use the manual ESP-IDF setup, follow the official installation guide and then:
idf.py build
idf.py flash
idf.py monitorForestAir AC units use a 35-bit infrared protocol with:
- 38 kHz carrier modulation
- Pulse-distance encoding
- A fixed 25-bit device header
- A 16-bit payload describing AC state
- All bits are sent LSB first
- The frame is sent once, no repeat frames were observed on the original remote
| Parameter | Value |
|---|---|
| Carrier frequency | 38 kHz |
| Duty cycle | 33% |
| Bit mark | 650 µs |
| Logic "0" space | 550 µs |
| Logic "1" space | 1650 µs |
| Stop bit | 650 µs mark + no space |
Bit '0' ───650µs HIGH───550µs LOW──────────
Bit '1' ───650µs HIGH──────────1650µs LOW──
A complete IR frame consists of:
+---------------------------+
| Header Mark + Header Space|
+---------------------------+
| 35 Data Bits (LSB first) |
+---------------------------+
| Stop Bit (650 µs high) |
+---------------------------+
[ Fixed Header (25 bits) ] [ Payload (16 bits) ]
MSB ------------------------------------------------ LSB
0x250000000 (aligned to bits 16–34 in the 35-bit frame)
Bits 0–15: Payload Bits 16–34: Always constant for ForestAir devices
Bit numbering: bit 0 = least significant bit, matching IR transmission order.
| Bits | Field | Size | Description |
|---|---|---|---|
| 0–2 | acMode | 3 | Operating mode |
| 3 | onOff | 1 | Power state |
| 4–6 | fanMode | 3 | Fan speed |
| 7 | swing | 1 | Swing on/off |
| 8–11 | temperature | 4 | 16–30 °C encoded as index |
| 12–15 | reserved | 4 | Always 0 |
| Value | Mode |
|---|---|
| 0 | Auto |
| 1 | Cool |
| 2 | Dehumidify |
| 3 | Ventilation |
| 4 | Heat |
| Value | Speed |
|---|---|
| 0 | Auto |
| 1 | Low |
| 2 | Medium |
| 3 | High |
0 = 16°C
1 = 17°C
[...]
14 = 30°C
All 35 bits are transmitted LSB first:
payload bit 0 → payload bit 15 → header LSB → ... → header MSB
Example settings:
Mode: Ventilation (3)
Power: On (1)
Fan: Low (1)
Swing: Off (0)
Temperature: 24°C → index 8
Payload fields:
acMode = 3 (bits 0–2)
onOff = 1 (bit 3)
fanMode = 1 (bits 4–6)
swing = 0 (bit 7)
temperature = 8 (bits 8–11)
reserved = 0 (bits 12–15)
Binary payload (LSB→MSB):
[0000][1000][0][001][1][011]
↑ ↑ ↑ ↑ ↑
| | | | └── acMode (3)
| | | └────── onOff (1)
| | └────────── fan (1)
| └─────────────── swing (0)
└────────────────────── temperature (8)
Final frame:
rawFrame = 0x250000000 | payload
Some ForestAir units appear to reject certain mode/temperature combinations. In testing, “Heat” mode would not power on unless the unit was already active or a compatible temperature was selected.
If your AC does not turn on:
- Try selecting Ventilation, Cool, or Auto instead.
- Confirm temperature index is within 16–30°C.
- Ensure your IR LED is driven through a transistor for adequate output.
This software is licensed under the MIT license