ESPHome component for AC manufactured by Hisense (RS-485 interface) Replacement of the AEH-W4G1 module and others.
This custom component provides full climate control for air conditioners manufactured by Hisense and its OEM brands (Ballu, etc.) that use the RS-485 protocol. It has been tested on:
❄️ Hisense CITY DC Inverter AS-13UW4RYRCM04G/04W
❄️ Hisense SILVER CRYSTAL SUPER DC Inverter AS-13UW4RVETG01
❄️ Newtek NT-77HSDC12
❄️ Ballu iGreen Pro DC BSAGI-07HN8, BSAGI-12HN8
❄️ Ballu iGreen Pro DC BSAGI-18HN8_V4
❄️ Ballu Platinum DC BSEI-09HN8_V3
❄️ Hisense Free Match Multi Split 4AMW81U4RJC
and should work with many other models.
The component exposes a standard Home Assistant Climate entity, along with a set of optional sensors and switches to access all advanced features of the AC (turbo, eco, quiet, sleep, swing, LED, iFeel (temperature from any sensor from the home assistant) etc.).
- An ESP32 board
- An RS-485 transceiver (e.g., MAX485) connected to UART pins
- Wiring:
- A (RS-485) → A of transceiver
- B (RS-485) → B of transceiver
- Transceiver's RXD to ESP RX pin (e.g., GPIO16)
- Transceiver's TXD to ESP TX pin (e.g., GPIO17)
- direct connection (not cross-connection)
- Power (3.3V or 5V depending on module) and GND
⚠️ The AC uses 5V logic levels on its RS‑485 port. Make sure your transceiver is 3.3V‑tolerant if you power the ESP from 3.3V.
A configuration with all optional sensors and switches:
esp32:
board: esp32dev
framework:
type: arduino
logger:
level: DEBUG
baud_rate: 0
uart:
id: ac_uart
tx_pin: 17
rx_pin: 16
baud_rate: 9600
stop_bits: 1
external_components:
- source: github://Druidblack/AC-Hi
refresh: 30s
climate:
- platform: ac_hi
name: "Hisense AC"
id: hisense_ac
uart_id: ac_uart
update_interval: 2s
enable_presets: true
configuration for the iFeel function (uses mqtt, an external IR transmitter, and any thermometer from home assistant)
mqtt:
broker: 192.168.1.71
username: admin
password: admin
discovery: false
discover_ip: false
discovery_retain: false
topic_prefix: null
log_topic: null
birth_message:
will_message:
shutdown_message:
sensor:
- platform: homeassistant
id: room_temperature_for_ifeel
entity_id: sensor.ble_temperature_a4c13836f782 # thermometer from the home assistant
internal: true
switch:
- platform: template
id: hisense_ifeel_enabled
name: "Hisense iFeel"
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
turn_on_action:
- ac_hi.ifeel:
id: hisense_ac
temperature: !lambda "return id(room_temperature_for_ifeel).state;"
enabled: true
turn_off_action:
- ac_hi.ifeel:
id: hisense_ac
temperature: 0
enabled: false
interval:
- interval: 2min
then:
- if:
condition:
switch.is_on: hisense_ifeel_enabled
then:
- ac_hi.ifeel:
id: hisense_ac
temperature: !lambda "return id(room_temperature_for_ifeel).state;"
enabled: true
climate:
- platform: ac_hi
name: "Hisense AC"
id: hisense_ac
uart_id: ac_uart
update_interval: 2s
enable_presets: true
ifeel_mqtt_topic: hisense_ac_zal/ir/kelon168/tx
ifeel_mqtt_payload: hex
ifeel_mqtt_qos: 0
ifeel_mqtt_retain: false
and the rest of the sensors from the main prime
configuration for the IR transmitter
external_components:
- source: github://Druidblack/AC-iFeel@main
components: [ kelon168_mqtt_ir ]
refresh: 30s
mqtt:
broker: 192.168.1.71
username: admin
password: admin
discovery: false
discover_ip: false
discovery_retain: false
topic_prefix: null
log_topic: null
birth_message:
will_message:
shutdown_message:
kelon168_mqtt_ir:
transmitter_id: ir_tx
topic: hisense_ac_zal/ir/kelon168/tx
qos: 0
send_times: 1
For flashing the native module (AEH-W4G1)
id: ac_uart
tx_pin: 20
rx_pin:
number: 21
inverted: true
baud_rate: 9600
- Modes:
OFF,COOL,HEAT,DRY,FAN_ONLY(auto mode is not supported by the AC, auto mode is implemented by enabling smart mode) - Fan speeds:
AUTO,QUIET,LOW,MEDIUM,HIGH,TURBO - Swing modes:
OFF,VERTICAL,HORIZONTAL,BOTH - Presets (if
enable_presets: true):ECO,BOOST(turbo),SLEEP,QUIET,+8 °C - Target temperature range: 16–30°C in steps of 1°C
- Current temperature is read from the AC and displayed
Most sensors publish raw values received from the AC:
| Sensor | Description |
|---|---|
set_temperature |
Target temperature setpoint (°C) |
room_temperature |
Current indoor temperature (°C) |
outdoor_temperature |
Outdoor air temperature (°C, signed) |
outdoor_condenser_temperature |
Outdoor condenser temperature (°C, signed) |
pipe_temperature |
Indoor pipe temperature (°C) |
wind |
Raw fan speed code (0–18) |
sleep_stage |
Sleep stage code (0–4) |
mode_code |
AC mode code (0–3) |
quiet |
Quiet mode active (binary) |
turbo |
Turbo mode active (binary) |
economy |
Eco mode active (binary) |
swing_up_down |
Vertical swing active (binary) |
swing_left_right |
Horizontal swing active (binary) |
compressor_frequency_set |
Target compressor frequency (Hz) |
compressor_frequency |
Actual compressor frequency (Hz) |
compressor_exhaust_temperature |
Compressor Exhaust Temperature (°C) |
power_status |
Text sensor showing "ON" or "OFF" |
led_switch: Controls the indoor unit’s LED backlight.command_sound: Command reception sound control.
The component communicates with the AC via a simple request/response protocol over RS‑485.
- Polling: Every
update_interval(default 1s) a short status query (command0x66) is sent. - Write commands: When you change a setting through Home Assistant, the component accumulates changes for 200 ms (debounce) and then sends a full state write (command
0x65) with a correctly calculated CRC. - Convergence logic: While HA has priority, the component continues to enforce the desired state until the AC confirms it (by sending a status frame that matches the desired signature). After convergence, remote changes (e.g., from the IR remote) are again accepted and reflected in HA.
- CRC validation: All incoming frames are checked for CRC correctness; invalid frames are discarded.
- Write lock timeout: If the AC does not acknowledge a write within 5 seconds, the lock is released to avoid permanent blocking.
We would like to thank the following people for their contributions to reverse engineering the protocol and developing this solution:
- vins.vins (4pda.to forum post) – for initial protocol research and sharing findings.
- straga (GitHub) – for providing additional protocol details and wiring diagram.
- artshevchenko (GitHub) - the source component for ESPHome.
- adipierro (GitHub - IR component for AC hisense control.
If you have contributed to the reverse engineering effort or improved the component, feel free to add your name here via a pull request!
This section documents the RS‑485 protocol used by Hisense/Ballu ACs. It may be useful for understanding the implementation or for adapting it to other models.
All frames start with header 0xF4 0xF5 and end with tail 0xF4 0xFB. The length of the frame is variable and is given by frame[4] + 9 bytes.
| Byte offset | Description |
|---|---|
| 0 | 0xF4 (header) |
| 1 | 0xF5 (header) |
| 2 | Unknown (usually 0x00) |
| 3 | Unknown (usually 0x40) |
| 4 | Declared length L – total frame size = L + 9 |
| 5 … 8 | Unknown (often 0x00) |
| 9 … 12 | Unknown (often 0x01 0xFE 0x01 0x00) |
| 13 | Command (0x65 – write, 0x66 – read status, 0x??) |
| 14 … L+4 | Payload |
| L+5 … L+6 | CRC (16‑bit sum of bytes 2 … L+4, big‑endian) |
| L+7 … L+8 | Tail 0xF4 0xFB |
When the AC receives a short query (0x66 frame), it responds with a long status frame (command 102). The payload contains all operational data.
Relevant bytes (0‑based within the frame):
| Index | Description | Encoding / Notes |
|---|---|---|
| 16 | Fan speed | Reported values: 0/1/2 = AUTO, 10 = QUIET, 12 = LOW, 14 = MEDIUM, 16 = HIGH |
| 17 | Sleep mode | Raw value. Decode: (value >> 1) gives sleep stage: 0 = off, 1 = sleep_1, 2 = sleep_2, 4 = sleep_3, 8 = sleep_4 |
| 18 | Power + Mode | Bit 3 = power (1=ON). Upper nibble = mode: 0=FAN_ONLY, 1=HEAT, 2=COOL, 3=DRY |
| 19 | Target temperature | Direct °C value (16–30) |
| 20 | Current indoor temperature | °C |
| 21 | Pipe temperature | °C |
| 32 | Swing | Bits: bit7 = up/down on, bit6 = left/right on |
| 33 | Turbo/Eco flags (RX) | bit1 = turbo, bit2 = eco |
| 35 | Quiet (RX) | bit2 = quiet active |
| 36 | LED (RX) | bit7 = LED on |
| 37 | ... | (unused) |
| 42 | Compressor target frequency | Hz |
| 43 | Compressor actual frequency | Hz |
| 44 | Outdoor air temperature | °C (signed int8_t) |
| 45 | Outdoor condenser temperature | °C (signed int8_t) |
To change settings, a full state frame is sent. The payload is built from the desired values. The CRC must be calculated over bytes 2 … L+4.
Encoding rules (TX):
| Field | Encoding |
|---|---|
| Target temperature | `((c & 0x1F) << 1) |
| Fan speed | Base codes: AUTO=1, QUIET=10, LOW=12, MEDIUM=14, HIGH=16. TX value = base + 1 |
| Sleep mode | `((code) << 1) |
| Power | low nibble of byte 18: ON = 0b1100, OFF = 0b0100 |
| Mode | high nibble of byte 18: FAN_ONLY = 0x10, HEAT = 0x30, COOL = 0x50, DRY = 0x70 |
| Turbo | byte 33: ON = 0b1100, OFF = 0b0100 (Turbo overrides Eco) |
| Eco | byte 33: ON = 0b110000, OFF = 0b010000 (combined with Turbo) |
| Quiet | byte 35: ON = 0b110000, OFF = 0b010000 |
| Swing | byte 32: UD ON = 0b11000000, OFF = 0b01000000; LR ON = 0b00110000, OFF = 0b00010000; combined by addition |
| LED | byte 36: ON = 0b11000000, OFF = 0b01000000 |
CRC is a simple 16‑bit sum (big‑endian) of bytes starting from index 2 up to index L+4 (i.e., excluding header, tail, and the CRC bytes themselves). The sum is stored as two bytes: high byte at offset L+5, low byte at L+6.
Example in C++:
uint16_t crc = 0;
for (int i = 2; i < frame.size() - 4; i++) {
crc += frame[i];
}
frame[frame.size() - 4] = (crc >> 8) & 0xFF;
frame[frame.size() - 3] = crc & 0xFF;- No communication: Check wiring, baud rate (9600), and ensure that the RS‑485 transceiver is powered correctly.
- Wrong outdoor temperature: The value is signed. If you see 244°C, it means –12°C was misinterpreted. This has been fixed in the component.
- Commands not working: Enable verbose logging (
esp_log_level: VERBOSE) and inspect the TX/RX frames. Compare them with the protocol specification.
Issues and pull requests are welcome. Please ensure your code follows the ESPHome style and includes appropriate logging.
JST-SM 2.54mm 4pin


