├── components └── haier │ ├── __init__.py │ ├── button │ ├── self_cleaning.cpp │ ├── steri_cleaning.cpp │ ├── self_cleaning.h │ ├── steri_cleaning.h │ └── __init__.py │ ├── logger_handler.h │ ├── switch │ ├── beeper.cpp │ ├── display.cpp │ ├── health_mode.cpp │ ├── quiet_mode.cpp │ ├── beeper.h │ ├── display.h │ ├── quiet_mode.h │ ├── health_mode.h │ └── __init__.py │ ├── logger_handler.cpp │ ├── text_sensor │ └── __init__.py │ ├── smartair2_climate.h │ ├── binary_sensor │ └── __init__.py │ ├── smartair2_packet.h │ ├── automation.h │ ├── sensor │ └── __init__.py │ ├── haier_base.h │ ├── hon_climate.h │ └── hon_packet.h ├── tests ├── .wifi-base.yaml ├── .simple-smartair2.yaml ├── .simple-hon.yaml ├── .local-haier.yaml ├── .ethernet-base.yaml ├── .gitignore ├── host-simple-hon.yaml ├── host-simple-smartair2.yaml ├── esp8266-simple-hon.yaml ├── esp8266-simple-smartair2.yaml ├── rpipicow-hon-wifi.yaml ├── esp32-arduino-hon-wifi.yaml ├── esp32-arduino-hon-ethernet.yaml ├── esp8266-hon-wifi.yaml ├── esp32-arduino-smartair2-wifi.yaml ├── rpipicow-smartair2-wifi.yaml ├── libretiny-hon.yaml ├── esp32-arduino-smartair2-ethernet.yaml ├── esp8266-smartair2-wifi.yaml ├── esp32-idf-hon-wifi.yaml ├── libretiny-smartair2.yaml ├── esp32-idf-hon-ethernet.yaml ├── esp32-idf-smartair2-wifi.yaml ├── esp32-idf-smartair2-ethernet.yaml └── run_tests.bat ├── configs ├── switch │ ├── restart.yaml │ ├── beeper.yaml │ ├── display.yaml │ ├── quiet_mode.yaml │ └── health_mode.yaml ├── sensor │ ├── power.yaml │ ├── humidity.yaml │ ├── compressor_current.yaml │ ├── outdoor_temperature.yaml │ ├── compressor_frequency.yaml │ ├── indoor_coil_temperature.yaml │ ├── outdoor_coil_temperature.yaml │ ├── outdoor_in_air_temperature.yaml │ ├── expansion_valve_open_degree.yaml │ ├── outdoor_defrost_temperature.yaml │ ├── outdoor_out_air_temperature.yaml │ ├── errors_counter.yaml │ ├── reboots_counter.yaml │ ├── warnings_counter.yaml │ └── alarm_counter.yaml ├── api │ ├── service.turn_off.yaml │ ├── service.turn_on.yaml │ └── send_custom_message.yaml ├── external_components │ └── local_haier.yaml ├── globals │ ├── errors_counter.yaml │ ├── reboots_counter.yaml │ └── warnings_counter.yaml ├── button │ ├── self_cleaning.yaml │ ├── steri_cleaning.yaml │ └── reset_protocol.yaml ├── binary_sensor │ ├── defrost_status.yaml │ ├── compressor_status.yaml │ ├── indoor_fan_status.yaml │ ├── outdoor_fan_status.yaml │ ├── four_way_valve_status.yaml │ └── indoor_electric_heating_status.yaml ├── text_sensor │ ├── appliance_name.yaml │ ├── cleaning_status.yaml │ └── protocol_version.yaml ├── core │ └── reboots_counter_increase.yaml ├── logger │ ├── errors_counter_increase.yaml │ └── warnings_counter_increase.yaml ├── climate │ ├── haier_smartair2.yaml │ └── haier_hon.yaml └── select │ ├── airflow_horizontal.yaml │ └── airflow_vertical.yaml ├── docs ├── images │ ├── KZW-W002.jpg │ ├── ESP32_back.jpg │ ├── ESP32_front.jpg │ ├── setup_diagram_esp.jpg │ └── setup_diagram_pc.jpg ├── esphome-docs │ ├── climate │ │ ├── images │ │ │ ├── usb_pinout.png │ │ │ └── haier_pinout.jpg │ │ └── haier.rst │ ├── sensor │ │ ├── images │ │ │ └── haier-climate.jpg │ │ └── haier.rst │ ├── binary_sensor │ │ ├── images │ │ │ └── haier-climate.jpg │ │ └── haier.rst │ ├── button │ │ └── haier.rst │ ├── text_sensor │ │ └── haier.rst │ └── switch │ │ └── haier.rst ├── examples │ ├── min-smartair2.yaml │ ├── min-hon.yaml │ ├── .gitignore │ ├── .base.yaml │ ├── hon_example.rst │ ├── smartair2_example.rst │ ├── usb_c3u.yaml │ ├── usb_s3.yaml │ ├── max-smartair2.yaml │ ├── usb_2_uart_boards.rst │ └── max-hon.yaml ├── additional_information.rst ├── script │ ├── process_examples.py │ └── make_doc.py ├── esp32_backup.rst ├── update_docs.cmd ├── haier_modules.rst ├── smartair2_example.rst ├── usb_2_uart_boards.rst ├── sniffing_serial_communication.rst ├── protocol_overview.rst ├── faq.rst └── hon_example.rst ├── .gitignore ├── .haier-smartair2-base.yaml ├── rpipico.yaml ├── .vscode ├── settings.json └── tasks.json ├── smartair2-esp8266.yaml ├── wemos_d1_mini.yaml ├── esp32-haier-module.yaml ├── esp32-c3.yaml ├── .github └── workflows │ ├── ci.yaml │ ├── examples.yaml │ └── documentation.yaml ├── haier-smartair2.yaml └── .haier-hon-base.yaml /components/haier/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.wifi-base.yaml: -------------------------------------------------------------------------------- 1 | wifi: 2 | ssid: test_ssid 3 | password: test_password 4 | -------------------------------------------------------------------------------- /tests/.simple-smartair2.yaml: -------------------------------------------------------------------------------- 1 | climate: 2 | - platform: haier 3 | name: ${device_name} 4 | -------------------------------------------------------------------------------- /configs/switch/restart.yaml: -------------------------------------------------------------------------------- 1 | switch: 2 | - platform: restart 3 | name: ${device_name} restart 4 | -------------------------------------------------------------------------------- /docs/images/KZW-W002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/images/KZW-W002.jpg -------------------------------------------------------------------------------- /docs/images/ESP32_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/images/ESP32_back.jpg -------------------------------------------------------------------------------- /docs/images/ESP32_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/images/ESP32_front.jpg -------------------------------------------------------------------------------- /tests/.simple-hon.yaml: -------------------------------------------------------------------------------- 1 | climate: 2 | - platform: haier 3 | protocol: hon 4 | name: ${device_name} 5 | -------------------------------------------------------------------------------- /configs/switch/beeper.yaml: -------------------------------------------------------------------------------- 1 | switch: 2 | - platform: haier 3 | beeper: 4 | name: ${device_name} beeper 5 | -------------------------------------------------------------------------------- /configs/switch/display.yaml: -------------------------------------------------------------------------------- 1 | switch: 2 | - platform: haier 3 | display: 4 | name: ${device_name} display 5 | -------------------------------------------------------------------------------- /docs/images/setup_diagram_esp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/images/setup_diagram_esp.jpg -------------------------------------------------------------------------------- /docs/images/setup_diagram_pc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/images/setup_diagram_pc.jpg -------------------------------------------------------------------------------- /configs/switch/quiet_mode.yaml: -------------------------------------------------------------------------------- 1 | switch: 2 | - platform: haier 3 | quiet_mode: 4 | name: ${device_name} quiet mode 5 | -------------------------------------------------------------------------------- /configs/switch/health_mode.yaml: -------------------------------------------------------------------------------- 1 | switch: 2 | - platform: haier 3 | health_mode: 4 | name: ${device_name} health mode 5 | -------------------------------------------------------------------------------- /tests/.local-haier.yaml: -------------------------------------------------------------------------------- 1 | external_components: 2 | source: 3 | type: local 4 | path: ../components 5 | components: [ haier ] -------------------------------------------------------------------------------- /configs/sensor/power.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | power: 5 | name: ${device_name} Power 6 | -------------------------------------------------------------------------------- /tests/.ethernet-base.yaml: -------------------------------------------------------------------------------- 1 | ethernet: 2 | type: LAN8720 3 | mdc_pin: GPIO23 4 | mdio_pin: GPIO18 5 | clk_mode: GPIO0_IN 6 | phy_addr: 0 7 | -------------------------------------------------------------------------------- /configs/api/service.turn_off.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | services: 3 | - service: turn_off 4 | then: 5 | - climate.haier.power_off: ${device_id} 6 | -------------------------------------------------------------------------------- /configs/api/service.turn_on.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | services: 3 | - service: turn_on 4 | then: 5 | - climate.haier.power_on: ${device_id} 6 | -------------------------------------------------------------------------------- /configs/external_components/local_haier.yaml: -------------------------------------------------------------------------------- 1 | external_components: 2 | source: 3 | type: local 4 | path: ./components 5 | components: [ haier ] -------------------------------------------------------------------------------- /configs/globals/errors_counter.yaml: -------------------------------------------------------------------------------- 1 | globals: 2 | - id: ${errors_counter} 3 | type: int 4 | restore_value: yes 5 | initial_value: '0' 6 | -------------------------------------------------------------------------------- /configs/globals/reboots_counter.yaml: -------------------------------------------------------------------------------- 1 | globals: 2 | - id: ${reboots_counter} 3 | type: int 4 | restore_value: yes 5 | initial_value: '0' 6 | -------------------------------------------------------------------------------- /docs/esphome-docs/climate/images/usb_pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/esphome-docs/climate/images/usb_pinout.png -------------------------------------------------------------------------------- /configs/globals/warnings_counter.yaml: -------------------------------------------------------------------------------- 1 | globals: 2 | - id: ${warnings_counter} 3 | type: int 4 | restore_value: yes 5 | initial_value: '0' 6 | -------------------------------------------------------------------------------- /docs/esphome-docs/climate/images/haier_pinout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/esphome-docs/climate/images/haier_pinout.jpg -------------------------------------------------------------------------------- /docs/esphome-docs/sensor/images/haier-climate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/esphome-docs/sensor/images/haier-climate.jpg -------------------------------------------------------------------------------- /configs/sensor/humidity.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | humidity: 5 | name: ${device_name} Indoor Humidity 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | /.esphome/ 3 | __pycache__/ 4 | /.vscode/ 5 | /secrets.yaml 6 | /buildlog.txt 7 | *.bak 8 | *.local.yaml 9 | *.tmp -------------------------------------------------------------------------------- /docs/esphome-docs/binary_sensor/images/haier-climate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paveldn/haier-esphome/HEAD/docs/esphome-docs/binary_sensor/images/haier-climate.jpg -------------------------------------------------------------------------------- /docs/examples/min-smartair2.yaml: -------------------------------------------------------------------------------- 1 | uart: 2 | baud_rate: 9600 3 | tx_pin: 17 4 | rx_pin: 16 5 | 6 | climate: 7 | - platform: haier 8 | name: Haier hOn Climate 9 | -------------------------------------------------------------------------------- /configs/button/self_cleaning.yaml: -------------------------------------------------------------------------------- 1 | button: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | self_cleaning: 5 | name: ${device_name} start self cleaning 6 | -------------------------------------------------------------------------------- /configs/sensor/compressor_current.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | compressor_current: 5 | name: ${device_name} Compressor Current 6 | -------------------------------------------------------------------------------- /configs/binary_sensor/defrost_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | defrost_status: 5 | name: ${device_name} Defrost Status 6 | -------------------------------------------------------------------------------- /configs/sensor/outdoor_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_temperature: 5 | name: ${device_name} outdoor temperature 6 | -------------------------------------------------------------------------------- /configs/sensor/compressor_frequency.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | compressor_frequency: 5 | name: ${device_name} Compressor Frequency 6 | -------------------------------------------------------------------------------- /configs/text_sensor/appliance_name.yaml: -------------------------------------------------------------------------------- 1 | text_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | appliance_name: 5 | name: ${device_name} appliance name 6 | 7 | 8 | -------------------------------------------------------------------------------- /configs/binary_sensor/compressor_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | compressor_status: 5 | name: ${device_name} Compressor Status 6 | -------------------------------------------------------------------------------- /configs/binary_sensor/indoor_fan_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | indoor_fan_status: 5 | name: ${device_name} Indoor Fan Status 6 | -------------------------------------------------------------------------------- /configs/binary_sensor/outdoor_fan_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_fan_status: 5 | name: ${device_name} Outdoor Fan Status 6 | -------------------------------------------------------------------------------- /configs/sensor/indoor_coil_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | indoor_coil_temperature: 5 | name: ${device_name} Indoor Coil Temperature 6 | -------------------------------------------------------------------------------- /configs/text_sensor/cleaning_status.yaml: -------------------------------------------------------------------------------- 1 | text_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | cleaning_status: 5 | name: ${device_name} cleaning status 6 | 7 | 8 | -------------------------------------------------------------------------------- /configs/text_sensor/protocol_version.yaml: -------------------------------------------------------------------------------- 1 | text_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | protocol_version: 5 | name: ${device_name} protocol version 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/examples/min-hon.yaml: -------------------------------------------------------------------------------- 1 | uart: 2 | baud_rate: 9600 3 | tx_pin: 17 4 | rx_pin: 16 5 | 6 | climate: 7 | - platform: haier 8 | protocol: hon 9 | name: Haier hOn Climate 10 | -------------------------------------------------------------------------------- /configs/sensor/outdoor_coil_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_coil_temperature: 5 | name: ${device_name} Outdoor Coil Temperature 6 | -------------------------------------------------------------------------------- /configs/binary_sensor/four_way_valve_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | four_way_valve_status: 5 | name: ${device_name} Four-way Valve Status 6 | -------------------------------------------------------------------------------- /configs/sensor/outdoor_in_air_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_in_air_temperature: 5 | name: ${device_name} Outdoor In Air Temperature 6 | -------------------------------------------------------------------------------- /configs/sensor/expansion_valve_open_degree.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | expansion_valve_open_degree: 5 | name: ${device_name} Expansion Valve Open Degree 6 | -------------------------------------------------------------------------------- /configs/sensor/outdoor_defrost_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_defrost_temperature: 5 | name: ${device_name} Outdoor Defrost Temperature 6 | -------------------------------------------------------------------------------- /configs/sensor/outdoor_out_air_temperature.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | outdoor_out_air_temperature: 5 | name: ${device_name} Outdoor Out Air Temperature 6 | -------------------------------------------------------------------------------- /docs/examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | # This is an example and may include too much for your use-case. 3 | # You can modify this file to suit your needs. 4 | /.esphome/ 5 | /secrets.yaml 6 | /__test__.yaml -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Gitignore settings for ESPHome 2 | # This is an example and may include too much for your use-case. 3 | # You can modify this file to suit your needs. 4 | /.esphome/ 5 | /secrets.yaml 6 | /buildlog.txt 7 | -------------------------------------------------------------------------------- /configs/binary_sensor/indoor_electric_heating_status.yaml: -------------------------------------------------------------------------------- 1 | binary_sensor: 2 | - platform: haier 3 | haier_id: ${device_id} 4 | indoor_electric_heating_status: 5 | name: ${device_name} Indoor Electric Heating Status 6 | -------------------------------------------------------------------------------- /configs/button/steri_cleaning.yaml: -------------------------------------------------------------------------------- 1 | button: 2 | # Introduced in (some) models starting from 2021 3 | - platform: haier 4 | haier_id: ${device_id} 5 | steri_cleaning: 6 | name: ${device_name} start 56°C steri-cleaning 7 | -------------------------------------------------------------------------------- /configs/core/reboots_counter_increase.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | on_boot: 3 | - priority: 250.0 4 | then: 5 | - globals.set: 6 | id: ${reboots_counter} 7 | value: !lambda |- 8 | return id(${reboots_counter}) + 1; 9 | -------------------------------------------------------------------------------- /components/haier/button/self_cleaning.cpp: -------------------------------------------------------------------------------- 1 | #include "self_cleaning.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); } 7 | 8 | } // namespace haier 9 | } // namespace esphome 10 | -------------------------------------------------------------------------------- /components/haier/button/steri_cleaning.cpp: -------------------------------------------------------------------------------- 1 | #include "steri_cleaning.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); } 7 | 8 | } // namespace haier 9 | } // namespace esphome 10 | -------------------------------------------------------------------------------- /tests/host-simple-hon.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: host-simple-hon 3 | 4 | host: 5 | 6 | uart: 7 | baud_rate: 9600 8 | port: /dev/ttyUSB0 9 | 10 | logger: 11 | level: DEBUG 12 | baud_rate: 0 13 | 14 | packages: 15 | local_haier: !include .local-haier.yaml 16 | haier_base: !include .simple-hon.yaml 17 | -------------------------------------------------------------------------------- /configs/sensor/errors_counter.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: template 3 | id: errors_counter_sensor 4 | name: "Errors counter" 5 | accuracy_decimals: 0 6 | state_class: total_increasing 7 | entity_category: diagnostic 8 | lambda: |- 9 | return id(${errors_counter}); 10 | update_interval: 15s 11 | -------------------------------------------------------------------------------- /configs/button/reset_protocol.yaml: -------------------------------------------------------------------------------- 1 | button: 2 | # Use for testing purposes only! Reset communication with AC to initial state 3 | - platform: template 4 | name: ${device_name} reset protocol 5 | icon: mdi:restart 6 | on_press: 7 | then: 8 | - lambda: | 9 | id(${device_id}).reset_protocol(); 10 | -------------------------------------------------------------------------------- /configs/sensor/reboots_counter.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: template 3 | id: reboots_counter_sensor 4 | name: "Reboots counter" 5 | accuracy_decimals: 0 6 | state_class: total_increasing 7 | entity_category: diagnostic 8 | lambda: |- 9 | return id(${reboots_counter}); 10 | update_interval: 15s 11 | -------------------------------------------------------------------------------- /tests/host-simple-smartair2.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: host-simple-smartair2 3 | 4 | host: 5 | 6 | uart: 7 | baud_rate: 9600 8 | port: /dev/ttyUSB0 9 | 10 | logger: 11 | level: DEBUG 12 | baud_rate: 0 13 | 14 | packages: 15 | local_haier: !include .local-haier.yaml 16 | haier_base: !include .simple-smartair2.yaml 17 | -------------------------------------------------------------------------------- /configs/sensor/warnings_counter.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: template 3 | id: warnings_counter_sensor 4 | name: "Warnings counter" 5 | accuracy_decimals: 0 6 | state_class: total_increasing 7 | entity_category: diagnostic 8 | lambda: |- 9 | return id(${warnings_counter}); 10 | update_interval: 15s 11 | -------------------------------------------------------------------------------- /components/haier/logger_handler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // HaierProtocol 4 | #include 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | // This file is called in the code generated by python script 10 | // Do not use it directly! 11 | void init_haier_protocol_logging(); 12 | 13 | } // namespace haier 14 | } // namespace esphome 15 | -------------------------------------------------------------------------------- /docs/examples/.base.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: esp32-haier-example 3 | 4 | esp32: 5 | board: esp32dev 6 | 7 | logger: 8 | level: DEBUG 9 | 10 | wifi: 11 | ssid: !secret wifi_ssid 12 | password: !secret wifi_password 13 | 14 | external_components: 15 | source: 16 | type: local 17 | path: ../../components 18 | components: [ haier ] 19 | 20 | -------------------------------------------------------------------------------- /components/haier/switch/beeper.cpp: -------------------------------------------------------------------------------- 1 | #include "beeper.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void BeeperSwitch::write_state(bool state) { 7 | if (this->parent_->get_beeper_state() != state) { 8 | this->parent_->set_beeper_state(state); 9 | } 10 | this->publish_state(state); 11 | } 12 | 13 | } // namespace haier 14 | } // namespace esphome 15 | -------------------------------------------------------------------------------- /configs/sensor/alarm_counter.yaml: -------------------------------------------------------------------------------- 1 | sensor: 2 | - platform: template 3 | id: alarms_counter_sensor 4 | name: ${device_name} Alarm Count 5 | accuracy_decimals: 0 6 | entity_category: diagnostic 7 | state_class: total 8 | icon: mdi:alert-outline 9 | lambda: |- 10 | return id(${device_id}).get_active_alarm_count(); 11 | update_interval: 15s 12 | -------------------------------------------------------------------------------- /components/haier/switch/display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void DisplaySwitch::write_state(bool state) { 7 | if (this->parent_->get_display_state() != state) { 8 | this->parent_->set_display_state(state); 9 | } 10 | this->publish_state(state); 11 | } 12 | 13 | } // namespace haier 14 | } // namespace esphome 15 | -------------------------------------------------------------------------------- /components/haier/switch/health_mode.cpp: -------------------------------------------------------------------------------- 1 | #include "health_mode.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void HealthModeSwitch::write_state(bool state) { 7 | if (this->parent_->get_health_mode() != state) { 8 | this->parent_->set_health_mode(state); 9 | } 10 | this->publish_state(state); 11 | } 12 | 13 | } // namespace haier 14 | } // namespace esphome 15 | -------------------------------------------------------------------------------- /tests/esp8266-simple-hon.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: esp8266-hon-wifi 3 | 4 | esp8266: 5 | board: esp01_1m 6 | 7 | uart: 8 | baud_rate: 9600 9 | tx_pin: 1 10 | rx_pin: 3 11 | 12 | logger: 13 | level: DEBUG 14 | baud_rate: 0 15 | 16 | packages: 17 | local_haier: !include .local-haier.yaml 18 | haier_base: !include .simple-hon.yaml 19 | wifi: !include .wifi-base.yaml 20 | -------------------------------------------------------------------------------- /components/haier/switch/quiet_mode.cpp: -------------------------------------------------------------------------------- 1 | #include "quiet_mode.h" 2 | 3 | namespace esphome { 4 | namespace haier { 5 | 6 | void QuietModeSwitch::write_state(bool state) { 7 | if (this->parent_->get_quiet_mode_state() != state) { 8 | this->parent_->set_quiet_mode_state(state); 9 | } 10 | this->publish_state(state); 11 | } 12 | 13 | } // namespace haier 14 | } // namespace esphome 15 | -------------------------------------------------------------------------------- /configs/logger/errors_counter_increase.yaml: -------------------------------------------------------------------------------- 1 | logger: 2 | on_message: 3 | - level: ERROR 4 | then: 5 | - globals.set: 6 | id: ${errors_counter} 7 | value: !lambda |- 8 | if ((strcmp(tag, "haier.protocol") == 0) || (strcmp(tag, "haier.climate") == 0)) 9 | return id(${errors_counter}) + 1; 10 | return id(${errors_counter}); 11 | -------------------------------------------------------------------------------- /tests/esp8266-simple-smartair2.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: esp8266-smartair2-wifi 3 | 4 | esp8266: 5 | board: esp01_1m 6 | 7 | uart: 8 | baud_rate: 9600 9 | tx_pin: 1 10 | rx_pin: 3 11 | 12 | logger: 13 | level: DEBUG 14 | baud_rate: 0 15 | 16 | packages: 17 | local_haier: !include .local-haier.yaml 18 | haier_base: !include .simple-smartair2.yaml 19 | wifi: !include .wifi-base.yaml 20 | -------------------------------------------------------------------------------- /.haier-smartair2-base.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | smartair2_climate: !include configs/climate/haier_smartair2.yaml 3 | turn_on_service: !include configs/api/service.turn_on.yaml 4 | turn_off_service: !include configs/api/service.turn_off.yaml 5 | display_switch: !include configs/switch/display.yaml 6 | health_mode_switch: !include configs/switch/health_mode.yaml 7 | restart_switch: !include configs/switch/restart.yaml 8 | -------------------------------------------------------------------------------- /configs/logger/warnings_counter_increase.yaml: -------------------------------------------------------------------------------- 1 | logger: 2 | on_message: 3 | - level: WARN 4 | then: 5 | - globals.set: 6 | id: ${warnings_counter} 7 | value: !lambda |- 8 | if ((strcmp(tag, "haier.protocol") == 0) || (strcmp(tag, "haier.climate") == 0)) 9 | return id(${warnings_counter}) + 1; 10 | return id(${warnings_counter}); 11 | -------------------------------------------------------------------------------- /docs/examples/hon_example.rst: -------------------------------------------------------------------------------- 1 | Example of climate configuration for hOn protocol 2 | ================================================= 3 | 4 | Configuration of your climate will depend on capabilities specific model. 5 | 6 | Minimal configuration will look like this: 7 | 8 | .. example_yaml:: min-hon.yaml 9 | 10 | Maximum configuration witch will use all possible options will look like this: 11 | 12 | .. example_yaml:: max-hon.yaml 13 | -------------------------------------------------------------------------------- /components/haier/switch/beeper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/switch/switch.h" 4 | #include "../hon_climate.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class BeeperSwitch : public switch_::Switch, public Parented { 10 | public: 11 | BeeperSwitch() = default; 12 | 13 | protected: 14 | void write_state(bool state) override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /components/haier/button/self_cleaning.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/button/button.h" 4 | #include "../hon_climate.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class SelfCleaningButton : public button::Button, public Parented { 10 | public: 11 | SelfCleaningButton() = default; 12 | 13 | protected: 14 | void press_action() override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /components/haier/switch/display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/switch/switch.h" 4 | #include "../haier_base.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class DisplaySwitch : public switch_::Switch, public Parented { 10 | public: 11 | DisplaySwitch() = default; 12 | 13 | protected: 14 | void write_state(bool state) override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /components/haier/switch/quiet_mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/switch/switch.h" 4 | #include "../hon_climate.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class QuietModeSwitch : public switch_::Switch, public Parented { 10 | public: 11 | QuietModeSwitch() = default; 12 | 13 | protected: 14 | void write_state(bool state) override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /components/haier/button/steri_cleaning.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/button/button.h" 4 | #include "../hon_climate.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class SteriCleaningButton : public button::Button, public Parented { 10 | public: 11 | SteriCleaningButton() = default; 12 | 13 | protected: 14 | void press_action() override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /docs/examples/smartair2_example.rst: -------------------------------------------------------------------------------- 1 | Example of climate configuration for smartair2 protocol 2 | ======================================================= 3 | 4 | Configuration of your climate will depend on capabilities specific model. 5 | 6 | Minimal configuration will look like this: 7 | 8 | .. example_yaml:: min-smartair2.yaml 9 | 10 | Maximum configuration witch will use all possible options will look like this: 11 | 12 | .. example_yaml:: max-smartair2.yaml 13 | -------------------------------------------------------------------------------- /components/haier/switch/health_mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/switch/switch.h" 4 | #include "../haier_base.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class HealthModeSwitch : public switch_::Switch, public Parented { 10 | public: 11 | HealthModeSwitch() = default; 12 | 13 | protected: 14 | void write_state(bool state) override; 15 | }; 16 | 17 | } // namespace haier 18 | } // namespace esphome 19 | -------------------------------------------------------------------------------- /docs/examples/usb_c3u.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: haier 3 | platformio_options: 4 | board_build.flash_mode: dio 5 | 6 | esp32: 7 | board: esp32-c3-devkitm-1 8 | framework: 9 | type: arduino 10 | 11 | wifi: 12 | ssid: !secret wifi_ssid 13 | password: !secret wifi_password 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 18 18 | rx_pin: 19 19 | 20 | logger: 21 | level: WARN 22 | 23 | climate: 24 | - platform: haier 25 | name: Haier AC 26 | -------------------------------------------------------------------------------- /docs/examples/usb_s3.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: haier 3 | platformio_options: 4 | board_build.flash_mode: dio 5 | 6 | esp32: 7 | board: esp32-s3-devkitc-1 8 | framework: 9 | type: arduino 10 | 11 | wifi: 12 | ssid: !secret wifi_ssid 13 | password: !secret wifi_password 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 19 18 | rx_pin: 20 19 | 20 | logger: 21 | level: WARN 22 | 23 | climate: 24 | - platform: haier 25 | name: Haier AC 26 | -------------------------------------------------------------------------------- /tests/rpipicow-hon-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: pipcow-hon-wifi 9 | 10 | rp2040: 11 | board: rpipicow 12 | 13 | logger: 14 | level: DEBUG 15 | 16 | uart: 17 | baud_rate: 9600 18 | id: ${uart_id} 19 | tx_pin: GPIO0 20 | rx_pin: GPIO1 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-hon-base.yaml 25 | wifi: !include .wifi-base.yaml 26 | 27 | -------------------------------------------------------------------------------- /tests/esp32-arduino-hon-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp32-idf-hon-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 17 16 | rx_pin: 16 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-hon-base.yaml 25 | wifi: !include .wifi-base.yaml 26 | 27 | -------------------------------------------------------------------------------- /tests/esp32-arduino-hon-ethernet.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "false" 6 | 7 | esphome: 8 | name: esp32-idf-hon-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 17 16 | rx_pin: 16 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-hon-base.yaml 25 | ethernet: !include .ethernet-base.yaml 26 | -------------------------------------------------------------------------------- /tests/esp8266-hon-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp8266-hon-wifi 9 | 10 | esp8266: 11 | board: esp01_1m 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 1 16 | rx_pin: 3 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | baud_rate: 0 22 | 23 | packages: 24 | local_haier: !include .local-haier.yaml 25 | haier_base: !include ../.haier-hon-base.yaml 26 | wifi: !include .wifi-base.yaml 27 | -------------------------------------------------------------------------------- /tests/esp32-arduino-smartair2-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp32-idf-smartair2-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 17 16 | rx_pin: 16 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-smartair2-base.yaml 25 | wifi: !include .wifi-base.yaml 26 | -------------------------------------------------------------------------------- /tests/rpipicow-smartair2-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: pipcow-smartair2-wifi 9 | 10 | rp2040: 11 | board: rpipicow 12 | 13 | logger: 14 | level: DEBUG 15 | 16 | uart: 17 | baud_rate: 9600 18 | id: ${uart_id} 19 | tx_pin: GPIO0 20 | rx_pin: GPIO1 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-smartair2-base.yaml 25 | wifi: !include .wifi-base.yaml 26 | -------------------------------------------------------------------------------- /tests/libretiny-hon.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: libretiny-hon 9 | 10 | bk72xx: 11 | board: generic-bk7231n-qfn32-tuya 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 10 16 | rx_pin: 11 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | baud_rate: 0 22 | 23 | packages: 24 | local_haier: !include .local-haier.yaml 25 | haier_base: !include ../.haier-hon-base.yaml 26 | wifi: !include .wifi-base.yaml 27 | -------------------------------------------------------------------------------- /tests/esp32-arduino-smartair2-ethernet.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "false" 6 | 7 | esphome: 8 | name: esp32-idf-smartair2-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 17 16 | rx_pin: 16 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | 22 | packages: 23 | local_haier: !include .local-haier.yaml 24 | haier_base: !include ../.haier-smartair2-base.yaml 25 | ethernet: !include .ethernet-base.yaml 26 | -------------------------------------------------------------------------------- /tests/esp8266-smartair2-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp8266-smartair2-wifi 9 | 10 | esp8266: 11 | board: esp01_1m 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 1 16 | rx_pin: 3 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | baud_rate: 0 22 | 23 | packages: 24 | local_haier: !include .local-haier.yaml 25 | haier_base: !include ../.haier-smartair2-base.yaml 26 | wifi: !include .wifi-base.yaml 27 | -------------------------------------------------------------------------------- /tests/esp32-idf-hon-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp32-idf-hon-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | framework: 13 | type: esp-idf 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 17 18 | rx_pin: 16 19 | id: ${uart_id} 20 | 21 | logger: 22 | level: DEBUG 23 | 24 | packages: 25 | local_haier: !include .local-haier.yaml 26 | haier_base: !include ../.haier-hon-base.yaml 27 | wifi: !include .wifi-base.yaml 28 | 29 | -------------------------------------------------------------------------------- /tests/libretiny-smartair2.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: libretiny-smartair2 9 | 10 | bk72xx: 11 | board: generic-bk7231n-qfn32-tuya 12 | 13 | uart: 14 | baud_rate: 9600 15 | tx_pin: 10 16 | rx_pin: 11 17 | id: ${uart_id} 18 | 19 | logger: 20 | level: DEBUG 21 | baud_rate: 0 22 | 23 | packages: 24 | local_haier: !include .local-haier.yaml 25 | haier_base: !include ../.haier-smartair2-base.yaml 26 | wifi: !include .wifi-base.yaml 27 | -------------------------------------------------------------------------------- /tests/esp32-idf-hon-ethernet.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "false" 6 | 7 | esphome: 8 | name: esp32-idf-hon-ethernet 9 | 10 | esp32: 11 | board: esp32dev 12 | framework: 13 | type: esp-idf 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 17 18 | rx_pin: 16 19 | id: ${uart_id} 20 | 21 | logger: 22 | level: DEBUG 23 | 24 | packages: 25 | local_haier: !include .local-haier.yaml 26 | haier_base: !include ../.haier-hon-base.yaml 27 | ethernet: !include .ethernet-base.yaml 28 | -------------------------------------------------------------------------------- /tests/esp32-idf-smartair2-wifi.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: esp32-idf-smartair2-wifi 9 | 10 | esp32: 11 | board: esp32dev 12 | framework: 13 | type: esp-idf 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 17 18 | rx_pin: 16 19 | id: ${uart_id} 20 | 21 | logger: 22 | level: DEBUG 23 | 24 | packages: 25 | local_haier: !include .local-haier.yaml 26 | haier_base: !include ../.haier-smartair2-base.yaml 27 | wifi: !include .wifi-base.yaml 28 | -------------------------------------------------------------------------------- /tests/esp32-idf-smartair2-ethernet.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: "Haier AC" 3 | device_id: "haier_climate" 4 | uart_id: "ac_port" 5 | send_wifi: "false" 6 | 7 | esphome: 8 | name: esp32-idf-smartair2-ethernet 9 | 10 | esp32: 11 | board: esp32dev 12 | framework: 13 | type: esp-idf 14 | 15 | uart: 16 | baud_rate: 9600 17 | tx_pin: 17 18 | rx_pin: 16 19 | id: ${uart_id} 20 | 21 | logger: 22 | level: DEBUG 23 | 24 | packages: 25 | local_haier: !include .local-haier.yaml 26 | haier_base: !include ../.haier-smartair2-base.yaml 27 | ethernet: !include .ethernet-base.yaml 28 | -------------------------------------------------------------------------------- /rpipico.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: Haier AC 3 | device_id: haier_climate 4 | uart_id: ac_port 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: haier 9 | name_add_mac_suffix: true 10 | 11 | rp2040: 12 | board: rpipicow 13 | 14 | wifi: 15 | ssid: !secret wifi_ssid 16 | password: !secret wifi_password 17 | 18 | logger: 19 | level: DEBUG 20 | 21 | uart: 22 | baud_rate: 9600 23 | id: ${uart_id} 24 | tx_pin: GPIO0 25 | rx_pin: GPIO1 26 | 27 | api: 28 | 29 | ota: 30 | - platform: esphome 31 | 32 | packages: 33 | local_haier: !include configs/external_components/local_haier.yaml 34 | haier_base: !include .haier-hon-base.yaml 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "functional": "cpp", 4 | "set": "cpp", 5 | "chrono": "cpp", 6 | "system_error": "cpp", 7 | "xlocale": "cpp", 8 | "array": "cpp", 9 | "vector": "cpp", 10 | "xstring": "cpp", 11 | "xutility": "cpp", 12 | "filesystem": "cpp", 13 | "xlocinfo": "cpp", 14 | "ostream": "cpp", 15 | "istream": "cpp", 16 | "sstream": "cpp", 17 | "optional": "cpp", 18 | "queue": "cpp" 19 | }, 20 | "files.trimTrailingWhitespace": true, 21 | "files.exclude": { 22 | "**/.esphome": false, 23 | "**/__pycache__": true 24 | } 25 | } -------------------------------------------------------------------------------- /smartair2-esp8266.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: Haier AC 3 | device_id: haier_climate 4 | uart_id: ac_port 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: haier 9 | name_add_mac_suffix: true 10 | 11 | esp8266: 12 | board: esp01_1m 13 | 14 | wifi: 15 | ssid: !secret wifi_ssid 16 | password: !secret wifi_password 17 | 18 | uart: 19 | baud_rate: 9600 20 | tx_pin: 1 21 | rx_pin: 3 22 | id: ${uart_id} 23 | 24 | logger: 25 | level: DEBUG 26 | hardware_uart: uart1 27 | 28 | web_server: 29 | 30 | api: 31 | 32 | ota: 33 | - platform: esphome 34 | 35 | packages: 36 | local_haier: !include configs/external_components/local_haier.yaml 37 | haier_base: !include .haier-smartair2-base.yaml 38 | -------------------------------------------------------------------------------- /wemos_d1_mini.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: Haier AC 3 | device_id: haier_climate 4 | uart_id: ac_port 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: haier 9 | name_add_mac_suffix: true 10 | 11 | esp8266: 12 | board: d1_mini 13 | 14 | wifi: 15 | ssid: !secret wifi_ssid 16 | password: !secret wifi_password 17 | 18 | uart: 19 | - id: ${uart_id} 20 | baud_rate: 9600 21 | # You can also use pins 15 (TX) and 13 (RX) 22 | tx_pin: 1 23 | rx_pin: 3 24 | 25 | logger: 26 | level: DEBUG 27 | baud_rate: 0 28 | 29 | web_server: 30 | 31 | api: 32 | 33 | ota: 34 | - platform: esphome 35 | 36 | packages: 37 | local_haier: !include configs/external_components/local_haier.yaml 38 | haier_base: !include .haier-hon-base.yaml 39 | -------------------------------------------------------------------------------- /esp32-haier-module.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: Haier AC 3 | device_id: haier_climate 4 | uart_id: ac_port 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: haier 9 | name_add_mac_suffix: true 10 | 11 | esp32: 12 | board: esp32dev 13 | framework: 14 | type: esp-idf 15 | sdkconfig_options: 16 | CONFIG_FREERTOS_UNICORE: y 17 | 18 | wifi: 19 | ssid: !secret wifi_ssid 20 | password: !secret wifi_password 21 | 22 | uart: 23 | baud_rate: 9600 24 | tx_pin: 17 25 | rx_pin: 16 26 | id: ${uart_id} 27 | 28 | logger: 29 | level: DEBUG 30 | 31 | api: 32 | 33 | ota: 34 | - platform: esphome 35 | 36 | packages: 37 | local_haier: !include configs/external_components/local_haier.yaml 38 | haier_base: !include .haier-hon-base.yaml 39 | -------------------------------------------------------------------------------- /esp32-c3.yaml: -------------------------------------------------------------------------------- 1 | substitutions: 2 | device_name: Haier AC 3 | device_id: haier_climate 4 | uart_id: ac_port 5 | send_wifi: "true" 6 | 7 | esphome: 8 | name: haier 9 | name_add_mac_suffix: true 10 | platformio_options: 11 | board_build.flash_mode: dio 12 | 13 | esp32: 14 | board: esp32-c3-devkitm-1 15 | variant: esp32c3 16 | framework: 17 | type: esp-idf 18 | 19 | wifi: 20 | ssid: !secret wifi_ssid 21 | password: !secret wifi_password 22 | 23 | uart: 24 | baud_rate: 9600 25 | tx_pin: 19 26 | rx_pin: 18 27 | id: ${uart_id} 28 | 29 | logger: 30 | level: DEBUG 31 | 32 | api: 33 | 34 | ota: 35 | - platform: esphome 36 | 37 | packages: 38 | local_haier: !include configs/external_components/local_haier.yaml 39 | haier_base: !include .haier-hon-base.yaml 40 | -------------------------------------------------------------------------------- /docs/additional_information.rst: -------------------------------------------------------------------------------- 1 | Additional information 2 | ====================== 3 | 4 | - `FAQ <./docs/faq.rst>`_ 5 | - `HaierProtocol `_ 6 | - `Haier smart modules <./docs/haier_modules.rst>`_ 7 | - `Haier protocol overview <./docs/protocol_overview.rst>`_ 8 | - `Example of climate configuration for smartair2 protocol <./docs/smartair2_example.rst>`_ 9 | - `Example of climate configuration for hOn protocol <./docs/hon_example.rst>`_ 10 | - `ESPHome Haier Climate `_ 11 | - `ESPHome Haier Climate Sensors `_ 12 | - `ESPHome Haier Climate Binary Sensors `_ 13 | - `Esptool.py Documentation `_ 14 | - `Sniffing serial communication <./docs/sniffing_serial_communication.rst>`_ -------------------------------------------------------------------------------- /configs/api/send_custom_message.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | services: 3 | - service: send_custom_message 4 | variables: 5 | message_type: int 6 | message_data: int[] 7 | then: 8 | - lambda: |- 9 | if ((message_type < 0) || (message_type > 255)) { 10 | ESP_LOGE("send_custom_command", "Wrong custom message type (should be byte)"); 11 | return; 12 | } 13 | if (message_data.size() == 0) { 14 | id(${device_id}).send_custom_command(haier_protocol::HaierMessage((haier_protocol::FrameType) message_type)); 15 | } else { 16 | size_t sz = message_data.size(); 17 | std::unique_ptr msg_buffer(new uint8_t [sz]); 18 | for (unsigned int i = 0; i < sz; i++) 19 | msg_buffer[i] = message_data[i]; 20 | id(${device_id}).send_custom_command(haier_protocol::HaierMessage((haier_protocol::FrameType) message_type, msg_buffer.get(), sz)); 21 | } 22 | -------------------------------------------------------------------------------- /configs/climate/haier_smartair2.yaml: -------------------------------------------------------------------------------- 1 | climate: 2 | - platform: haier 3 | id: ${device_id} 4 | protocol: smartAir2 5 | name: ${device_name} 6 | uart_id: ${uart_id} 7 | alternative_swing_control: false 8 | wifi_signal: ${send_wifi} # Optional, default true, enables WiFI signal transmission from ESP to AC 9 | visual: # Optional, you can use it to limit min and max temperatures in UI (not working for remote!) 10 | min_temperature: 16 °C 11 | max_temperature: 30 °C 12 | temperature_step: 1 °C 13 | supported_modes: # Optional, can be used to disable some modes if you don't need them 14 | - 'OFF' # always available 15 | - HEAT_COOL # always available 16 | - COOL 17 | - HEAT 18 | - DRY 19 | - FAN_ONLY 20 | supported_swing_modes: # Optional, can be used to disable some swing modes if your AC does not support it 21 | - 'OFF' 22 | - VERTICAL 23 | - HORIZONTAL 24 | - BOTH 25 | supported_presets: # Optional, can be used to disable some presets if your AC does not support it 26 | - BOOST 27 | - COMFORT 28 | -------------------------------------------------------------------------------- /docs/esphome-docs/button/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate Buttons 2 | ===================== 3 | 4 | .. seo:: 5 | :description: Instructions for setting up additional buttons for Haier climate devices. 6 | :image: haier.svg 7 | 8 | Additional buttons for Haier AC cleaning. **These buttons are supported only by the hOn protocol**. 9 | 10 | .. code-block:: yaml 11 | 12 | # Example configuration entry 13 | button: 14 | - platform: haier 15 | haier_id: haier_ac 16 | self_cleaning: 17 | name: Haier start self cleaning 18 | steri_cleaning: 19 | name: Haier start 56°C steri-cleaning 20 | 21 | Configuration variables: 22 | ------------------------ 23 | 24 | - **haier_id** (**Required**, :ref:`config-id`): The id of Haier climate component 25 | - **self_cleaning** (*Optional*): A button that starts Haier climate self cleaning. 26 | All options from :ref:`Button `. 27 | - **steri_cleaning** (*Optional*): A button that starts Haier climate 56°C Steri-Clean. 28 | All options from :ref:`Button `. 29 | 30 | See Also 31 | -------- 32 | 33 | - :doc:`Haier Climate ` 34 | - :ghedit:`Edit` 35 | -------------------------------------------------------------------------------- /docs/examples/max-smartair2.yaml: -------------------------------------------------------------------------------- 1 | uart: 2 | baud_rate: 9600 3 | tx_pin: 17 4 | rx_pin: 16 5 | id: haier_uart 6 | 7 | api: 8 | services: 9 | - service: turn_on 10 | then: 11 | - climate.haier.power_on: haier_ac 12 | - service: turn_off 13 | then: 14 | - climate.haier.power_off: haier_ac 15 | 16 | climate: 17 | - platform: haier 18 | id: haier_ac 19 | protocol: smartAir2 20 | name: Haier SmartAir2 Climate 21 | uart_id: haier_uart 22 | alternative_swing_control: false 23 | wifi_signal: true 24 | visual: 25 | min_temperature: 16 °C 26 | max_temperature: 30 °C 27 | temperature_step: 1 °C 28 | supported_modes: 29 | - 'OFF' 30 | - HEAT_COOL 31 | - COOL 32 | - HEAT 33 | - DRY 34 | - FAN_ONLY 35 | supported_swing_modes: 36 | - 'OFF' 37 | - VERTICAL 38 | - HORIZONTAL 39 | - BOTH 40 | supported_presets: 41 | - BOOST 42 | - COMFORT 43 | - AWAY 44 | 45 | switch: 46 | - platform: haier 47 | health_mode: 48 | name: Haier SmartAir2 Climate health mode 49 | display: 50 | name: Haier SmartAir2 Climate display 51 | -------------------------------------------------------------------------------- /docs/script/process_examples.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import os 4 | 5 | document_header = [ 6 | ".. This file is automatically generated by ./docs/script/process_examples.py Python script.\n", 7 | " Please, don't change. In case you need to make corrections or changes change\n", 8 | " source documentation in ./doc folder or script.\n\n" 9 | ] 10 | 11 | if len(sys.argv) < 3: 12 | print("Usage: python process_examples.py ") 13 | sys.exit(1) 14 | 15 | input_file = sys.argv[1] 16 | output_file = sys.argv[2] 17 | 18 | with open(input_file, "r") as f: 19 | fpath = os.path.dirname(os.path.abspath(input_file)) 20 | ofile = open(output_file, "w") 21 | lines = f.readlines() 22 | ofile.writelines(document_header) 23 | for line in lines: 24 | m = re.match(r"^.. example_yaml:: ([^\n]+)", line) 25 | if m: 26 | print("Processing example: " + m.group(1)) 27 | ofile.write(".. code-block:: yaml\n\n") 28 | example = open(os.path.join(fpath, m.group(1)), "r") 29 | for l in example.readlines(): 30 | ofile.write(" " + l) 31 | ofile.write("\n") 32 | else: 33 | ofile.write(line) -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "hOn: esp32-haier-module.yaml", 6 | "type": "shell", 7 | "command": "esphome compile esp32-haier-module.yaml", 8 | "group": { 9 | "kind": "build", 10 | } 11 | }, 12 | { 13 | "label": "hOn: esp32-c3.yaml", 14 | "type": "shell", 15 | "command": "esphome compile esp32-c3.yaml", 16 | "group": { 17 | "kind": "build", 18 | } 19 | }, 20 | { 21 | "label": "hOn: rpipico.yaml", 22 | "type": "shell", 23 | "command": "esphome compile rpipico.yaml", 24 | "group": { 25 | "kind": "build", 26 | } 27 | }, 28 | { 29 | "label": "hOn: wemos_d1_mini.yaml", 30 | "type": "shell", 31 | "command": "esphome compile wemos_d1_mini.yaml", 32 | "group": { 33 | "kind": "build", 34 | } 35 | }, 36 | { 37 | "label": "smartAir2: smartair2-esp8266.yaml", 38 | "type": "shell", 39 | "command": "esphome compile esp32-haier-module.yaml", 40 | "group": { 41 | "kind": "build", 42 | } 43 | }, 44 | { 45 | "label": "Run all tests", 46 | "type": "shell", 47 | "command": "& \"${workspaceFolder}/tests/run_tests.bat\"", 48 | "options": { 49 | "cwd": "${workspaceFolder}/tests/" 50 | }, 51 | "group": { 52 | "kind": "test", 53 | } 54 | }, 55 | ] 56 | } -------------------------------------------------------------------------------- /components/haier/logger_handler.cpp: -------------------------------------------------------------------------------- 1 | #include "logger_handler.h" 2 | #include "esphome/core/log.h" 3 | 4 | namespace esphome { 5 | namespace haier { 6 | 7 | void esphome_logger(haier_protocol::HaierLogLevel level, const char *tag, const char *message) { 8 | switch (level) { 9 | case haier_protocol::HaierLogLevel::LEVEL_ERROR: 10 | esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, "%s", message); 11 | break; 12 | case haier_protocol::HaierLogLevel::LEVEL_WARNING: 13 | esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, "%s", message); 14 | break; 15 | case haier_protocol::HaierLogLevel::LEVEL_INFO: 16 | esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, "%s", message); 17 | break; 18 | case haier_protocol::HaierLogLevel::LEVEL_DEBUG: 19 | esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, "%s", message); 20 | break; 21 | case haier_protocol::HaierLogLevel::LEVEL_VERBOSE: 22 | esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, "%s", message); 23 | break; 24 | default: 25 | // Just ignore everything else 26 | break; 27 | } 28 | } 29 | 30 | void init_haier_protocol_logging() { haier_protocol::set_log_handler(esphome::haier::esphome_logger); }; 31 | 32 | } // namespace haier 33 | } // namespace esphome 34 | -------------------------------------------------------------------------------- /components/haier/button/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import button 3 | import esphome.config_validation as cv 4 | 5 | from ..climate import CONF_HAIER_ID, HonClimate, haier_ns 6 | 7 | CODEOWNERS = ["@paveldn"] 8 | SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button) 9 | SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button) 10 | 11 | 12 | # Haier buttons 13 | CONF_SELF_CLEANING = "self_cleaning" 14 | CONF_STERI_CLEANING = "steri_cleaning" 15 | 16 | # Additional icons 17 | ICON_SPRAY_BOTTLE = "mdi:spray-bottle" 18 | 19 | CONFIG_SCHEMA = cv.Schema( 20 | { 21 | cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate), 22 | cv.Optional(CONF_SELF_CLEANING): button.button_schema( 23 | SelfCleaningButton, 24 | icon=ICON_SPRAY_BOTTLE, 25 | ), 26 | cv.Optional(CONF_STERI_CLEANING): button.button_schema( 27 | SteriCleaningButton, 28 | icon=ICON_SPRAY_BOTTLE, 29 | ), 30 | } 31 | ) 32 | 33 | 34 | async def to_code(config): 35 | for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]: 36 | if conf := config.get(button_type): 37 | btn = await button.new_button(conf) 38 | await cg.register_parented(btn, config[CONF_HAIER_ID]) 39 | -------------------------------------------------------------------------------- /docs/esp32_backup.rst: -------------------------------------------------------------------------------- 1 | How to backup the original image and flash ESPHome to the ESP32 Haier module 2 | ============================================================================ 3 | 4 | **It is strongly recommended to make a backup of the original flash 5 | content before flashing ESPHome!** 6 | 7 | To make a backup and to flash the new firmware you will need to use a 8 | USB to TTL converter and solder wires to access UART0 on board (or use 9 | something like this: `Pogo Pin Probe Clip 2x5p 2.54 10 | mm `__) 11 | 12 | **UART0 pinout:** 13 | 14 | .. figure:: esphome-docs/climate/images/haier_pinout.jpg 15 | :align: center 16 | :width: 70.0% 17 | 18 | To put the device in the flash mode you will need to shortcut GPIO0 to 19 | the ground before powering the device. 20 | 21 | Once the device is in flash mode you can make a full backup of the 22 | original firmware in case you would like to return the module to its 23 | factory state. To make a backup you can use 24 | `esptool `__. Command to make a 25 | full flash backup: 26 | 27 | **python esptool.py -b 115200 –port read_flash 0x00000 0x400000 28 | flash_4M.bin** 29 | 30 | After this, you can flash firmware using ESPHome tools (dashboard, 31 | website, esphome command, etc) 32 | -------------------------------------------------------------------------------- /tests/run_tests.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for %%G in ( *.yaml ) do ( 3 | call :append_config_file_name %%G 4 | if %errorlevel% NEQ 0 ( 5 | echo Error while building %%G 6 | goto :eof 7 | ) 8 | ) 9 | set "configs_list=%configs_list:~1%" 10 | set items_cout=0 11 | for %%i in (%configs_list%) do set /a cnt+=1 12 | if /i "%1"=="show" ( 13 | echo List of tests: 14 | echo ---------------------------------------------------- 15 | for %%i in (%configs_list%) do ( 16 | echo %%i 17 | ) 18 | echo ---------------------------------------------------- 19 | echo Total: %cnt% items 20 | goto :eof 21 | ) 22 | for %%i in (%configs_list%) do ( 23 | echo ---------------------------------------------------- 24 | echo Compiling: %%i 25 | echo ---------------------------------------------------- 26 | if /i "%1"=="wsl" ( 27 | wsl -e sh -lc "esphome compile %%i" 28 | ) else ( 29 | esphome compile %%i 30 | ) 31 | if %ERRORLEVEL% NEQ 0 ( 32 | echo ---------------------------------------------------- 33 | echo Failed to build: %%i 34 | echo ---------------------------------------------------- 35 | goto :eof 36 | ) 37 | ) 38 | goto :eof 39 | 40 | :append_config_file_name 41 | set config_file=%1 42 | if "%config_file:~0,1%" NEQ "." ( 43 | set "configs_list=%configs_list%,%config_file%" 44 | ) else ( 45 | echo Skipping %config_file% 46 | ) 47 | goto :eof 48 | -------------------------------------------------------------------------------- /docs/esphome-docs/text_sensor/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate Text Sensors 2 | ========================== 3 | 4 | .. seo:: 5 | :description: Instructions for setting up additional Text sensors for Haier climate devices. 6 | :image: haier.svg 7 | 8 | Additional sensors for Haier Climate device. **These sensors are supported only by the hOn protocol**. 9 | 10 | .. code-block:: yaml 11 | 12 | # Example configuration entry 13 | text_sensor: 14 | - platform: haier 15 | haier_id: haier_ac 16 | appliance_name: 17 | name: Haier appliance name 18 | cleaning_status: 19 | name: Haier cleaning status 20 | protocol_version: 21 | name: Haier protocol version 22 | 23 | Configuration variables: 24 | ------------------------ 25 | 26 | - **haier_id** (**Required**, :ref:`config-id`): The id of haier climate component 27 | - **appliance_name** (*Optional*): A text sensor that indicates Haier appliance name. 28 | All options from :ref:`Text Sensor `. 29 | - **cleaning_status** (*Optional*): A text sensor that indicates cleaning status. Possible values "No cleaning", "Self clean", "56°C Steri-Clean". 30 | All options from :ref:`Text Sensor `. 31 | - **protocol_version** (*Optional*): A text sensor that indicates Haier protocol version. 32 | All options from :ref:`Text Sensor `. 33 | 34 | See Also 35 | -------- 36 | 37 | - :doc:`Haier Climate ` 38 | - :ghedit:`Edit` 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # Build component for different platforms 2 | name: build CI test 3 | 4 | on: 5 | push: 6 | branches-ignore: [ "doc_test" ] 7 | pull_request: 8 | branches: [ "master", "dev" , "experimental" ] 9 | 10 | jobs: 11 | tests: 12 | name: Building ${{ matrix.file }} 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | file: 18 | - esp32-arduino-hon-ethernet.yaml 19 | - esp32-arduino-hon-wifi.yaml 20 | - esp32-arduino-smartair2-ethernet.yaml 21 | - esp32-arduino-smartair2-wifi.yaml 22 | - esp32-idf-hon-ethernet.yaml 23 | - esp32-idf-hon-wifi.yaml 24 | - esp32-idf-smartair2-ethernet.yaml 25 | - esp32-idf-smartair2-wifi.yaml 26 | - esp8266-hon-wifi.yaml 27 | - esp8266-smartair2-wifi.yaml 28 | - rpipicow-hon-wifi.yaml 29 | - rpipicow-smartair2-wifi.yaml 30 | - esp8266-simple-smartair2.yaml 31 | - esp8266-simple-hon.yaml 32 | - libretiny-hon.yaml 33 | - libretiny-smartair2.yaml 34 | - host-simple-hon.yaml 35 | - host-simple-smartair2.yaml 36 | steps: 37 | - name: Checkout code 38 | uses: actions/checkout@v4.1.3 39 | - name: Install esphome 40 | run: pip3 install -U esphome 41 | - name: Version esphome 42 | run: esphome version 43 | - name: Install libsodium 44 | run: sudo apt-get install -y libsodium-dev 45 | - name: Build ESPHome config 46 | run: esphome compile tests/${{ matrix.file }} 47 | -------------------------------------------------------------------------------- /docs/esphome-docs/switch/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate Switches 2 | ====================== 3 | 4 | .. seo:: 5 | :description: Instructions for setting up additional switches for Haier climate devices. 6 | :image: haier.svg 7 | 8 | Additional switches to support additional features for Haier AC. 9 | 10 | .. code-block:: yaml 11 | 12 | # Example configuration entry 13 | switch: 14 | - platform: haier 15 | beeper: 16 | name: Haier beeper 17 | health_mode: 18 | name: Haier health mode 19 | display: 20 | name: Haier display 21 | quiet_mode: 22 | name: Haier quiet mode 23 | 24 | Configuration variables: 25 | ------------------------ 26 | 27 | - **haier_id** (**Required**, :ref:`config-id`): The id of Haier climate component 28 | - **beeper** (*Optional*): (supported only by hOn) A switch that enables or disables Haier climate sound feedback. 29 | All options from :ref:`Switch `. 30 | - **health_mode** (*Optional*): A switch that enables or disables Haier climate health mode (`UV light sterilization `__). 31 | All options from :ref:`Switch `. 32 | - **display** (*Optional*): A switch that enables or disables Haier climate led display. 33 | All options from :ref:`Switch `. 34 | - **quiet_mode** (*Optional*): (supported only by hOn) A switch that enables or disables Haier climate quiet mode. Quiet mode not supported in Fan only mode. 35 | All options from :ref:`Switch `. 36 | 37 | See Also 38 | -------- 39 | 40 | - :doc:`Haier Climate ` 41 | - :ghedit:`Edit` 42 | -------------------------------------------------------------------------------- /docs/examples/usb_2_uart_boards.rst: -------------------------------------------------------------------------------- 1 | List of confirmed board that supports that have native USB support and can communicate using UART protocol 2 | ========================================================================================================== 3 | 4 | Here you can find a list of confirmed boards that have native USB support and can communicate using UART protocol with sample configuration for each case. Thease configurations not compleate and should be considered as a starting point for integrating your Haier AC. 5 | 6 | 7 | ESP32-S3 based boards 8 | --------------------- 9 | 10 | Currently, the following boards have native USB support and can communicate using UART protocol: 11 | 12 | - `M5Stack AtomS3U `_ 13 | - `Lilygo T-Dongle S3 `_ 14 | - `M5Stamp ESP32S3 Module `_ with USB-C to USB-A male adapter. 15 | 16 | **Sample ESPHome Configuration that works for all this boards:** 17 | 18 | .. example_yaml:: usb_s3.yaml 19 | 20 | ESP32-C3 based boards 21 | --------------------- 22 | 23 | Currently, only one board with ESP32-C3 confirmed that have native USB support and can communicate using UART protocol: 24 | 25 | - `M5Stamp C3U (white color) `_ with USB-C to USB-A male adapter. **But be careful: M5Stamp C3 board (black color, without U at the end) have a dedicated chip for USB and can't be used for UART communication!** 26 | 27 | **Sample ESPHome Configuration that works for this board:** 28 | 29 | .. example_yaml:: usb_c3u.yaml -------------------------------------------------------------------------------- /docs/update_docs.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | SET base_path=%~dp0 3 | cd %base_path%.. 4 | echo =============================================================================================================== 5 | echo Updaiting README.rst 6 | echo =============================================================================================================== 7 | python %base_path%script/make_doc.py README.rst 8 | echo =============================================================================================================== 9 | echo Updaiting docs/hon_example.rst 10 | echo =============================================================================================================== 11 | python %base_path%script/process_examples.py %base_path%examples/hon_example.rst %base_path%hon_example.rst 12 | echo =============================================================================================================== 13 | echo Updaiting docs/smartair2_example.rst 14 | echo =============================================================================================================== 15 | python %base_path%script/process_examples.py %base_path%examples/smartair2_example.rst %base_path%smartair2_example.rst 16 | echo =============================================================================================================== 17 | echo Updaiting docs/usb_2_uart_boards.rst 18 | echo =============================================================================================================== 19 | python %base_path%script/process_examples.py %base_path%examples/usb_2_uart_boards.rst %base_path%usb_2_uart_boards.rst 20 | cd %base_path% -------------------------------------------------------------------------------- /components/haier/text_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import text_sensor 3 | import esphome.config_validation as cv 4 | from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_NONE 5 | 6 | from ..climate import CONF_HAIER_ID, HonClimate 7 | 8 | CODEOWNERS = ["@paveldn"] 9 | TextSensorTypeEnum = HonClimate.enum("SubTextSensorType", True) 10 | 11 | # Haier text sensors 12 | CONF_CLEANING_STATUS = "cleaning_status" 13 | CONF_PROTOCOL_VERSION = "protocol_version" 14 | CONF_APPLIANCE_NAME = "appliance_name" 15 | 16 | # Additional icons 17 | ICON_SPRAY_BOTTLE = "mdi:spray-bottle" 18 | ICON_TEXT_BOX = "mdi:text-box-outline" 19 | 20 | TEXT_SENSOR_TYPES = { 21 | CONF_CLEANING_STATUS: text_sensor.text_sensor_schema( 22 | icon=ICON_SPRAY_BOTTLE, 23 | entity_category=ENTITY_CATEGORY_NONE, 24 | ), 25 | CONF_PROTOCOL_VERSION: text_sensor.text_sensor_schema( 26 | icon=ICON_TEXT_BOX, 27 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 28 | ), 29 | CONF_APPLIANCE_NAME: text_sensor.text_sensor_schema( 30 | icon=ICON_TEXT_BOX, 31 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 32 | ), 33 | } 34 | 35 | CONFIG_SCHEMA = cv.Schema( 36 | { 37 | cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate), 38 | } 39 | ).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()}) 40 | 41 | 42 | async def to_code(config): 43 | paren = await cg.get_variable(config[CONF_HAIER_ID]) 44 | 45 | for type_ in TEXT_SENSOR_TYPES: 46 | if conf := config.get(type_): 47 | sens = await text_sensor.new_text_sensor(conf) 48 | text_sensor_type = getattr(TextSensorTypeEnum, type_.upper()) 49 | cg.add(paren.set_sub_text_sensor(text_sensor_type, sens)) 50 | -------------------------------------------------------------------------------- /.github/workflows/examples.yaml: -------------------------------------------------------------------------------- 1 | # Build component for different platforms 2 | name: build examples 3 | 4 | on: 5 | push: 6 | branches-ignore: [ "doc_test" ] 7 | pull_request: 8 | branches: [ "master", "dev" , "experimental" ] 9 | 10 | jobs: 11 | tests_with_base: 12 | name: Building ${{ matrix.file }} 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | file: 18 | - max-hon.yaml 19 | - max-smartair2.yaml 20 | - min-hon.yaml 21 | - min-smartair2.yaml 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4.1.3 25 | - name: Install esphome 26 | run: pip3 install -U esphome 27 | - name: Version esphome 28 | run: esphome version 29 | - name: Prepering test file 30 | run: cat docs/examples/.base.yaml docs/examples/${{ matrix.file }} > ./docs/examples/__test__.yaml 31 | - name: Preparing secrets.yaml 32 | run: | 33 | echo "wifi_ssid: test_ssid" > ./docs/examples/secrets.yaml 34 | echo "wifi_password: test_pass" >> ./docs/examples/secrets.yaml 35 | - name: Build ESPHome config 36 | run: esphome compile ./docs/examples/__test__.yaml 37 | tests_without_base: 38 | name: Building ${{ matrix.file }} 39 | runs-on: ubuntu-latest 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | file: 44 | - usb_s3.yaml 45 | - usb_c3u.yaml 46 | steps: 47 | - name: Checkout code 48 | uses: actions/checkout@v4.1.3 49 | - name: Install esphome 50 | run: pip3 install -U esphome 51 | - name: Version esphome 52 | run: esphome version 53 | - name: Preparing secrets.yaml 54 | run: | 55 | echo "wifi_ssid: test_ssid" > ./docs/examples/secrets.yaml 56 | echo "wifi_password: test_pass" >> ./docs/examples/secrets.yaml 57 | - name: Build ESPHome config 58 | run: esphome compile docs/examples/${{ matrix.file }} 59 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | name: documentation check 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ "master", "dev" , "experimental" ] 7 | 8 | jobs: 9 | readme_check: 10 | name: Readme check 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4.1.3 15 | - name: Run script and save results 16 | run: python docs/script/make_doc.py README.tmp 17 | - name: Compare temp file with readme.rst 18 | run: diff -u --strip-trailing-cr README.rst README.tmp 19 | hon_examples_check: 20 | name: Check hon_example 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4.1.3 25 | - name: Run script and save results 26 | run: python docs/script/process_examples.py docs/examples/hon_example.rst docs/hon_example.tmp 27 | - name: Compare temp file with hon_example.rst 28 | run: diff -u --strip-trailing-cr docs/hon_example.rst docs/hon_example.tmp 29 | smartair2_examples_check: 30 | name: Check smartair2_example 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v4.1.3 35 | - name: Run script and save results 36 | run: python docs/script/process_examples.py docs/examples/smartair2_example.rst docs/smartair2_example.tmp 37 | - name: Compare temp file with smartair2_example.rst 38 | run: diff -u --strip-trailing-cr docs/smartair2_example.rst docs/smartair2_example.tmp 39 | usb_2_uart_boards: 40 | name: Check usb_2_uart_boards 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout code 44 | uses: actions/checkout@v4.1.3 45 | - name: Run script and save results 46 | run: python docs/script/process_examples.py docs/examples/usb_2_uart_boards.rst docs/usb_2_uart_boards.tmp 47 | - name: Compare temp file with usb_2_uart_boards.rst 48 | run: diff -u --strip-trailing-cr docs/usb_2_uart_boards.rst docs/usb_2_uart_boards.tmp 49 | -------------------------------------------------------------------------------- /components/haier/smartair2_climate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "haier_base.h" 5 | 6 | namespace esphome { 7 | namespace haier { 8 | 9 | class Smartair2Climate : public HaierClimateBase { 10 | public: 11 | Smartair2Climate(); 12 | Smartair2Climate(const Smartair2Climate &) = delete; 13 | Smartair2Climate &operator=(const Smartair2Climate &) = delete; 14 | ~Smartair2Climate(); 15 | void dump_config() override; 16 | void set_alternative_swing_control(bool swing_control); 17 | 18 | protected: 19 | void set_handlers() override; 20 | void process_phase(std::chrono::steady_clock::time_point now) override; 21 | haier_protocol::HaierMessage get_power_message(bool state) override; 22 | haier_protocol::HaierMessage get_control_message() override; 23 | // Answer handlers 24 | haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, 25 | haier_protocol::FrameType message_type, const uint8_t *data, 26 | size_t data_size); 27 | haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, 28 | haier_protocol::FrameType message_type, 29 | const uint8_t *data, size_t data_size); 30 | haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, 31 | haier_protocol::FrameType message_type, 32 | const uint8_t *data, size_t data_size); 33 | haier_protocol::HandlerError messages_timeout_handler_with_cycle_for_init_(haier_protocol::FrameType message_type); 34 | // Helper functions 35 | haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); 36 | bool use_alternative_swing_control_; 37 | }; 38 | 39 | } // namespace haier 40 | } // namespace esphome 41 | -------------------------------------------------------------------------------- /docs/haier_modules.rst: -------------------------------------------------------------------------------- 1 | Haier Smart Modules 2 | =================== 3 | 4 | Older Haier Models 5 | ------------------ 6 | 7 | Older Haier models controlled by the SmartAir2 application use the KZW-W002 module, which looks like this: 8 | 9 | .. raw:: html 10 | 11 |

12 | 13 | This module is based on ARM architecture and cannot be reused for ESPHome. The module works exclusively with the SmartAir2 application but supports both versions of Haier protocols. Therefore, it is possible that an HVAC system using the KZW-W002 module and the SmartAir2 application might also support the hOn protocol. 14 | 15 | Newer Haier Models 16 | ------------------ 17 | 18 | Newer Haier models use a module called ESP32-for-Haier. This module is an ESP32 single-core board with an ESP32-S0WD chip. The module board looks like this: 19 | 20 | **Front:** 21 | 22 | .. raw:: html 23 | 24 |

25 | 26 | **Back:** 27 | 28 | .. raw:: html 29 | 30 | 31 | 32 | These boards support only the newer Haier protocol and can work with the hOn application. Additionally, in some regions, the EVO application is used instead of hOn. 33 | 34 | Sometimes these modules can be reused for ESPHome. **Make sure to back up your module before attempting to flash it.** Some of the newer ESP32-for-Haier modules are encrypted. 35 | 36 | Example of ESPHome Configuration 37 | -------------------------------- 38 | 39 | Here is an example of an ESPHome configuration for this module (can work only with esp-idf framework): 40 | 41 | .. code-block:: yaml 42 | 43 | esphome: 44 | name: haier 45 | 46 | esp32: 47 | board: esp32dev 48 | framework: 49 | type: esp-idf 50 | sdkconfig_options: 51 | CONFIG_FREERTOS_UNICORE: y 52 | 53 | wifi: 54 | ssid: !secret wifi_ssid 55 | password: !secret wifi_password 56 | -------------------------------------------------------------------------------- /configs/select/airflow_horizontal.yaml: -------------------------------------------------------------------------------- 1 | select: 2 | - platform: template 3 | id: ${device_id}_horizontal_direction 4 | name: ${device_name} airflow horizontal 5 | entity_category: config 6 | icon: mdi:arrow-expand-horizontal 7 | update_interval: 5s 8 | options: 9 | - Max Left 10 | - Left 11 | - Center 12 | - Right 13 | - Max Right 14 | - Auto 15 | lambda: >- 16 | switch (id(${device_id}).get_horizontal_airflow().value_or(esphome::haier::hon_protocol::HorizontalSwingMode::CENTER)) 17 | { 18 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT: 19 | return std::string("Max Left"); 20 | case esphome::haier::hon_protocol::HorizontalSwingMode::LEFT: 21 | return std::string("Left"); 22 | default: 23 | case esphome::haier::hon_protocol::HorizontalSwingMode::CENTER: 24 | return std::string("Center"); 25 | case esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT: 26 | return std::string("Right"); 27 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT: 28 | return std::string("Max Right"); 29 | case esphome::haier::hon_protocol::HorizontalSwingMode::AUTO: 30 | return std::string("Auto"); 31 | } 32 | set_action: 33 | - climate.haier.set_horizontal_airflow: 34 | id: ${device_id} 35 | horizontal_airflow: !lambda >- 36 | if (x == "Max Left") 37 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT; 38 | else if (x == "Left") 39 | return esphome::haier::hon_protocol::HorizontalSwingMode::LEFT; 40 | else if (x == "Right") 41 | return esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT; 42 | else if (x == "Max Right") 43 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT; 44 | else if (x == "Auto") 45 | return esphome::haier::hon_protocol::HorizontalSwingMode::AUTO; 46 | else 47 | return esphome::haier::hon_protocol::HorizontalSwingMode::CENTER; 48 | -------------------------------------------------------------------------------- /docs/smartair2_example.rst: -------------------------------------------------------------------------------- 1 | .. This file is automatically generated by ./docs/script/process_examples.py Python script. 2 | Please, don't change. In case you need to make corrections or changes change 3 | source documentation in ./doc folder or script. 4 | 5 | Example of climate configuration for smartair2 protocol 6 | ======================================================= 7 | 8 | Configuration of your climate will depend on capabilities specific model. 9 | 10 | Minimal configuration will look like this: 11 | 12 | .. code-block:: yaml 13 | 14 | uart: 15 | baud_rate: 9600 16 | tx_pin: 17 17 | rx_pin: 16 18 | 19 | climate: 20 | - platform: haier 21 | name: Haier hOn Climate 22 | 23 | 24 | Maximum configuration witch will use all possible options will look like this: 25 | 26 | .. code-block:: yaml 27 | 28 | uart: 29 | baud_rate: 9600 30 | tx_pin: 17 31 | rx_pin: 16 32 | id: haier_uart 33 | 34 | api: 35 | services: 36 | - service: turn_on 37 | then: 38 | - climate.haier.power_on: haier_ac 39 | - service: turn_off 40 | then: 41 | - climate.haier.power_off: haier_ac 42 | 43 | climate: 44 | - platform: haier 45 | id: haier_ac 46 | protocol: smartAir2 47 | name: Haier SmartAir2 Climate 48 | uart_id: haier_uart 49 | alternative_swing_control: false 50 | wifi_signal: true 51 | visual: 52 | min_temperature: 16 °C 53 | max_temperature: 30 °C 54 | temperature_step: 1 °C 55 | supported_modes: 56 | - 'OFF' 57 | - HEAT_COOL 58 | - COOL 59 | - HEAT 60 | - DRY 61 | - FAN_ONLY 62 | supported_swing_modes: 63 | - 'OFF' 64 | - VERTICAL 65 | - HORIZONTAL 66 | - BOTH 67 | supported_presets: 68 | - BOOST 69 | - COMFORT 70 | - AWAY 71 | 72 | switch: 73 | - platform: haier 74 | health_mode: 75 | name: Haier SmartAir2 Climate health mode 76 | display: 77 | name: Haier SmartAir2 Climate display 78 | 79 | -------------------------------------------------------------------------------- /docs/esphome-docs/binary_sensor/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate Binary Sensors 2 | ============================ 3 | 4 | .. seo:: 5 | :description: Instructions for setting up additional binary sensors for Haier climate devices. 6 | :image: haier.svg 7 | 8 | Additional sensors for Haier Climate device. **These sensors are supported only by the hOn protocol**. 9 | 10 | 11 | .. figure:: images/haier-climate.jpg 12 | :align: center 13 | :width: 50.0% 14 | 15 | .. code-block:: yaml 16 | 17 | # Example configuration entry 18 | binary_sensor: 19 | - platform: haier 20 | haier_id: haier_ac 21 | compressor_status: 22 | name: Haier Outdoor Compressor Status 23 | defrost_status: 24 | name: Haier Defrost Status 25 | four_way_valve_status: 26 | name: Haier Four Way Valve Status 27 | indoor_electric_heating_status: 28 | name: Haier Indoor Electric Heating Status 29 | indoor_fan_status: 30 | name: Haier Indoor Fan Status 31 | outdoor_fan_status: 32 | name: Haier Outdoor Fan Status 33 | 34 | Configuration variables: 35 | ------------------------ 36 | 37 | - **haier_id** (**Required**, :ref:`config-id`): The id of haier climate component 38 | - **compressor_status** (*Optional*): A binary sensor that indicates Haier climate compressor activity. 39 | All options from :ref:`Binary Sensor `. 40 | - **defrost_status** (*Optional*): A binary sensor that indicates defrost procedure activity. 41 | All options from :ref:`Binary Sensor `. 42 | - **four_way_valve_status** (*Optional*): A binary sensor that indicates four way valve status. 43 | All options from :ref:`Binary Sensor `. 44 | - **indoor_electric_heating_status** (*Optional*): A binary sensor that indicates electrical heating system activity. 45 | All options from :ref:`Binary Sensor `. 46 | - **indoor_fan_status** (*Optional*): A binary sensor that indicates indoor fan activity. 47 | All options from :ref:`Binary Sensor `. 48 | - **outdoor_fan_status** (*Optional*): A binary sensor that indicates outdoor fan activity. 49 | All options from :ref:`Binary Sensor `. 50 | 51 | See Also 52 | -------- 53 | 54 | - :doc:`Haier Climate ` 55 | - :ghedit:`Edit` 56 | -------------------------------------------------------------------------------- /haier-smartair2.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: haier 3 | platform: ESP8266 4 | board: d1_mini 5 | 6 | # Set statul led for Wemos D1 mini 7 | status_led: 8 | pin: GPIO2 9 | 10 | wifi: 11 | ssid: "ASUS88" 12 | password: "3503470936" 13 | 14 | # Enable fallback hotspot (captive portal) in case wifi connection fails 15 | ap: 16 | ssid: "Haier_wifi" 17 | password: "Haier_wifi" 18 | 19 | captive_portal: 20 | 21 | # Enable web server (can be disabled) 22 | web_server: 23 | port: 80 24 | 25 | time: 26 | - platform: sntp 27 | id: sntp_time 28 | timezone: "Europe/ Moscow" 29 | 30 | # Enable logging 31 | logger: 32 | level: DEBUG 33 | baud_rate: 0 #Important. You can't use serial port 34 | 35 | # Enable Home Assistant API 36 | api: 37 | password: "3503470936" 38 | 39 | ota: 40 | - platform: esphome 41 | password: "3503470936" 42 | 43 | uart: 44 | - baud_rate: 9600 45 | tx_pin: 1 46 | rx_pin: 3 47 | 48 | 49 | climate: 50 | - platform: haier 51 | id: haier_ac 52 | name: "Haier" 53 | alternative_swing_control: false 54 | wifi_signal: true 55 | supported_modes: 56 | - 'OFF' 57 | - HEAT_COOL 58 | - COOL 59 | - HEAT 60 | - DRY 61 | - FAN_ONLY 62 | supported_swing_modes: 63 | - 'OFF' 64 | - VERTICAL 65 | - HORIZONTAL 66 | - BOTH 67 | supported_presets: 68 | - BOOST 69 | - COMFORT 70 | 71 | switch: 72 | - platform: template 73 | id: haier_ac_health_mode 74 | name: Haier SmartAir2 Climate health mode 75 | icon: mdi:leaf 76 | restore_mode: RESTORE_DEFAULT_OFF 77 | lambda: |- 78 | return id(haier_ac).get_health_mode(); 79 | turn_on_action: 80 | climate.haier.health_on: haier_ac 81 | turn_off_action: 82 | climate.haier.health_off: haier_ac 83 | - platform: template 84 | id: haier_ac_display_switch 85 | name: Haier SmartAir2 Climate display 86 | icon: mdi:led-on 87 | entity_category: config 88 | restore_mode: RESTORE_DEFAULT_ON 89 | lambda: |- 90 | return id(haier_ac).get_display_state(); 91 | turn_on_action: 92 | climate.haier.display_on: haier_ac 93 | turn_off_action: 94 | climate.haier.display_off: haier_ac 95 | 96 | mqtt: 97 | broker: 192.168.31.58 98 | port: 44444 99 | username: mqtt 100 | password: mqtt -------------------------------------------------------------------------------- /components/haier/binary_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import binary_sensor 3 | import esphome.config_validation as cv 4 | from esphome.const import ENTITY_CATEGORY_DIAGNOSTIC, ICON_FAN, ICON_RADIATOR 5 | 6 | from ..climate import CONF_HAIER_ID, HonClimate 7 | 8 | CODEOWNERS = ["@paveldn"] 9 | BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True) 10 | 11 | # Haier sensors 12 | CONF_OUTDOOR_FAN_STATUS = "outdoor_fan_status" 13 | CONF_DEFROST_STATUS = "defrost_status" 14 | CONF_COMPRESSOR_STATUS = "compressor_status" 15 | CONF_INDOOR_FAN_STATUS = "indoor_fan_status" 16 | CONF_FOUR_WAY_VALVE_STATUS = "four_way_valve_status" 17 | CONF_INDOOR_ELECTRIC_HEATING_STATUS = "indoor_electric_heating_status" 18 | 19 | # Additional icons 20 | ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" 21 | ICON_HVAC = "mdi:hvac" 22 | ICON_VALVE = "mdi:valve" 23 | 24 | SENSOR_TYPES = { 25 | CONF_OUTDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( 26 | icon=ICON_FAN, 27 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 28 | ), 29 | CONF_DEFROST_STATUS: binary_sensor.binary_sensor_schema( 30 | icon=ICON_SNOWFLAKE_THERMOMETER, 31 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 32 | ), 33 | CONF_COMPRESSOR_STATUS: binary_sensor.binary_sensor_schema( 34 | icon=ICON_HVAC, 35 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 36 | ), 37 | CONF_INDOOR_FAN_STATUS: binary_sensor.binary_sensor_schema( 38 | icon=ICON_FAN, 39 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 40 | ), 41 | CONF_FOUR_WAY_VALVE_STATUS: binary_sensor.binary_sensor_schema( 42 | icon=ICON_VALVE, 43 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 44 | ), 45 | CONF_INDOOR_ELECTRIC_HEATING_STATUS: binary_sensor.binary_sensor_schema( 46 | icon=ICON_RADIATOR, 47 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 48 | ), 49 | } 50 | 51 | CONFIG_SCHEMA = cv.Schema( 52 | { 53 | cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate), 54 | } 55 | ).extend({cv.Optional(type): schema for type, schema in SENSOR_TYPES.items()}) 56 | 57 | 58 | async def to_code(config): 59 | paren = await cg.get_variable(config[CONF_HAIER_ID]) 60 | 61 | for type_ in SENSOR_TYPES: 62 | if conf := config.get(type_): 63 | sens = await binary_sensor.new_binary_sensor(conf) 64 | binary_sensor_type = getattr(BinarySensorTypeEnum, type_.upper()) 65 | cg.add(paren.set_sub_binary_sensor(binary_sensor_type, sens)) 66 | -------------------------------------------------------------------------------- /configs/select/airflow_vertical.yaml: -------------------------------------------------------------------------------- 1 | select: 2 | - platform: template 3 | id: ${device_id}_vertical_direction 4 | name: ${device_name} airflow vertical 5 | entity_category: config 6 | icon: mdi:arrow-expand-vertical 7 | update_interval: 5s 8 | options: 9 | - Health Up 10 | - Max Up 11 | - Up 12 | - Center 13 | - Down 14 | - Max Down 15 | - Health Down 16 | - Auto 17 | lambda: >- 18 | switch (id(${device_id}).get_vertical_airflow().value_or(esphome::haier::hon_protocol::VerticalSwingMode::CENTER)) 19 | { 20 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP: 21 | return std::string("Health Up"); 22 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP: 23 | return std::string("Max Up"); 24 | case esphome::haier::hon_protocol::VerticalSwingMode::UP: 25 | return std::string("Up"); 26 | default: 27 | case esphome::haier::hon_protocol::VerticalSwingMode::CENTER: 28 | return std::string("Center"); 29 | case esphome::haier::hon_protocol::VerticalSwingMode::DOWN: 30 | return std::string("Down"); 31 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN: 32 | return std::string("Max Down"); 33 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN: 34 | return std::string("Health Down"); 35 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO: 36 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO_SPECIAL: 37 | return std::string("Auto"); 38 | } 39 | set_action: 40 | - climate.haier.set_vertical_airflow: 41 | id: ${device_id} 42 | vertical_airflow: !lambda >- 43 | if (x == "Health Up") 44 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP; 45 | else if (x == "Max Up") 46 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP; 47 | else if (x == "Up") 48 | return esphome::haier::hon_protocol::VerticalSwingMode::UP; 49 | else if (x == "Down") 50 | return esphome::haier::hon_protocol::VerticalSwingMode::DOWN; 51 | else if (x == "Max Down") 52 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN; 53 | else if (x == "Health Down") 54 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN; 55 | else if (x == "Auto") 56 | return esphome::haier::hon_protocol::VerticalSwingMode::AUTO; 57 | else 58 | return esphome::haier::hon_protocol::VerticalSwingMode::CENTER; 59 | 60 | -------------------------------------------------------------------------------- /configs/climate/haier_hon.yaml: -------------------------------------------------------------------------------- 1 | climate: 2 | - platform: haier 3 | id: ${device_id} 4 | protocol: hon 5 | name: ${device_name} 6 | uart_id: ${uart_id} 7 | wifi_signal: ${send_wifi} # Optional, default true, enables WiFI signal transmission from ESP to AC 8 | visual: # Optional, you can use it to limit min and max temperatures in UI (not working for remote!) 9 | min_temperature: 16 °C 10 | max_temperature: 30 °C 11 | temperature_step: 12 | target_temperature: 1 13 | current_temperature: 0.5 14 | supported_modes: # Optional, can be used to disable some modes if you don't need them 15 | - 'OFF' # always available 16 | - HEAT_COOL # always available 17 | - COOL 18 | - HEAT 19 | - DRY 20 | - FAN_ONLY 21 | supported_swing_modes: # Optional, can be used to disable some swing modes if your AC does not support it 22 | - 'OFF' 23 | - VERTICAL 24 | - HORIZONTAL 25 | - BOTH 26 | supported_presets: # Optional, can be used to disable some presets if your AC does not support it 27 | - BOOST 28 | - SLEEP 29 | # Next two triggers calling Home Assistant services to notify user about alarms triggered by AC 30 | # this functionality is disabled by default. To enable it check "Allow the device to make Home Assistant service calls" 31 | # checkbox in Home Assistant's device settings 32 | on_alarm_start: 33 | then: 34 | - homeassistant.service: 35 | service: logbook.log 36 | data: 37 | domain: climate 38 | name: ${device_name} 39 | data_template: 40 | message: "Alarm activated ({{ alarm_code }}): {{alarm_message}}" 41 | variables: 42 | alarm_message: !lambda "return message;" 43 | alarm_code: !lambda "return code;" 44 | - homeassistant.service: 45 | service: notify.persistent_notification 46 | data: 47 | title: "${device_name}: alarm activated" 48 | data_template: 49 | message: "Code: {{ alarm_code }}, message: \"{{ alarm_message }}\"" 50 | variables: 51 | alarm_message: !lambda "return message;" 52 | alarm_code: !lambda "return code;" 53 | on_alarm_end: 54 | then: 55 | - homeassistant.service: 56 | service: logbook.log 57 | data: 58 | domain: climate 59 | name: ${device_name} 60 | data_template: 61 | message: "Alarm deactivated ({{ alarm_code }}): {{alarm_message}}" 62 | variables: 63 | alarm_message: !lambda "return message;" 64 | alarm_code: !lambda "return code;" 65 | -------------------------------------------------------------------------------- /components/haier/smartair2_packet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace esphome { 4 | namespace haier { 5 | namespace smartair2_protocol { 6 | 7 | enum class ConditioningMode : uint8_t { AUTO = 0x00, COOL = 0x01, HEAT = 0x02, FAN = 0x03, DRY = 0x04 }; 8 | 9 | enum class FanMode : uint8_t { FAN_HIGH = 0x00, FAN_MID = 0x01, FAN_LOW = 0x02, FAN_AUTO = 0x03 }; 10 | 11 | struct HaierPacketControl { 12 | // Control bytes starts here 13 | // 10 14 | uint8_t : 8; // Temperature high byte 15 | // 11 16 | uint8_t room_temperature; // current room temperature 1°C step 17 | // 12 18 | uint8_t : 8; // Humidity high byte 19 | // 13 20 | uint8_t room_humidity; // Humidity 0%-100% with 1% step 21 | // 14 22 | uint8_t : 8; 23 | // 15 24 | uint8_t cntrl; // In AC => ESP packets - 0x7F, in ESP => AC packets - 0x00 25 | // 16 26 | uint8_t : 8; 27 | // 17 28 | uint8_t : 8; 29 | // 18 30 | uint8_t : 8; 31 | // 19 32 | uint8_t : 8; 33 | // 20 34 | uint8_t : 8; 35 | // 21 36 | uint8_t ac_mode; // See enum ConditioningMode 37 | // 22 38 | uint8_t : 8; 39 | // 23 40 | uint8_t fan_mode; // See enum FanMode 41 | // 24 42 | uint8_t : 8; 43 | // 25 44 | uint8_t swing_mode; // In normal mode: If 1 - swing both direction, if 0 - horizontal_swing and 45 | // vertical_swing define vertical/horizontal/off 46 | // In alternative mode: 0 - off, 01 - vertical, 02 - horizontal, 03 - both 47 | // 26 48 | uint8_t : 3; 49 | uint8_t use_fahrenheit : 1; 50 | uint8_t : 3; 51 | uint8_t lock_remote : 1; // Disable remote 52 | // 27 53 | uint8_t ac_power : 1; // Is ac on or off 54 | uint8_t : 2; 55 | uint8_t health_mode : 1; // Health mode on or off 56 | uint8_t compressor : 1; // Compressor on or off ??? 57 | uint8_t half_degree : 1; // Use half degree 58 | uint8_t ten_degree : 1; // 10 degree status (only work in heat mode) 59 | uint8_t : 0; 60 | // 28 61 | uint8_t : 8; 62 | // 29 63 | uint8_t use_swing_bits : 1; // Indicate if horizontal_swing and vertical_swing should be used 64 | uint8_t turbo_mode : 1; // Turbo mode 65 | uint8_t quiet_mode : 1; // Sleep mode 66 | uint8_t horizontal_swing : 1; // Horizontal swing (if swing_both == 0) 67 | uint8_t vertical_swing : 1; // Vertical swing (if swing_both == 0) if vertical_swing and horizontal_swing both 0 => 68 | // swing off 69 | uint8_t display_status : 1; // Led on or off 70 | uint8_t : 0; 71 | // 30 72 | uint8_t : 8; 73 | // 31 74 | uint8_t : 8; 75 | // 32 76 | uint8_t : 8; // Target temperature high byte 77 | // 33 78 | uint8_t set_point; // Target temperature with 16°C offset, 1°C step 79 | }; 80 | 81 | struct HaierStatus { 82 | uint16_t subcommand; 83 | HaierPacketControl control; 84 | }; 85 | 86 | } // namespace smartair2_protocol 87 | } // namespace haier 88 | } // namespace esphome 89 | -------------------------------------------------------------------------------- /docs/usb_2_uart_boards.rst: -------------------------------------------------------------------------------- 1 | .. This file is automatically generated by ./docs/script/process_examples.py Python script. 2 | Please, don't change. In case you need to make corrections or changes change 3 | source documentation in ./doc folder or script. 4 | 5 | List of confirmed board that supports that have native USB support and can communicate using UART protocol 6 | ========================================================================================================== 7 | 8 | Here you can find a list of confirmed boards that have native USB support and can communicate using UART protocol with sample configuration for each case. Thease configurations not compleate and should be considered as a starting point for integrating your Haier AC. 9 | 10 | 11 | ESP32-S3 based boards 12 | --------------------- 13 | 14 | Currently, the following boards have native USB support and can communicate using UART protocol: 15 | 16 | - `M5Stack AtomS3U `_ 17 | - `Lilygo T-Dongle S3 `_ 18 | - `M5Stamp ESP32S3 Module `_ with USB-C to USB-A male adapter. 19 | 20 | **Sample ESPHome Configuration that works for all this boards:** 21 | 22 | .. code-block:: yaml 23 | 24 | esphome: 25 | name: haier 26 | platformio_options: 27 | board_build.flash_mode: dio 28 | 29 | esp32: 30 | board: esp32-s3-devkitc-1 31 | framework: 32 | type: arduino 33 | 34 | wifi: 35 | ssid: !secret wifi_ssid 36 | password: !secret wifi_password 37 | 38 | uart: 39 | baud_rate: 9600 40 | tx_pin: 19 41 | rx_pin: 20 42 | 43 | logger: 44 | level: WARN 45 | 46 | climate: 47 | - platform: haier 48 | name: Haier AC 49 | 50 | 51 | ESP32-C3 based boards 52 | --------------------- 53 | 54 | Currently, only one board with ESP32-C3 confirmed that have native USB support and can communicate using UART protocol: 55 | 56 | - `M5Stamp C3U (white color) `_ with USB-C to USB-A male adapter. **But be careful: M5Stamp C3 board (black color, without U at the end) have a dedicated chip for USB and can't be used for UART communication!** 57 | 58 | **Sample ESPHome Configuration that works for this board:** 59 | 60 | .. code-block:: yaml 61 | 62 | esphome: 63 | name: haier 64 | platformio_options: 65 | board_build.flash_mode: dio 66 | 67 | esp32: 68 | board: esp32-c3-devkitm-1 69 | framework: 70 | type: arduino 71 | 72 | wifi: 73 | ssid: !secret wifi_ssid 74 | password: !secret wifi_password 75 | 76 | uart: 77 | baud_rate: 9600 78 | tx_pin: 18 79 | rx_pin: 19 80 | 81 | logger: 82 | level: WARN 83 | 84 | climate: 85 | - platform: haier 86 | name: Haier AC 87 | 88 | -------------------------------------------------------------------------------- /.haier-hon-base.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | hon_climate: !include configs/climate/haier_hon.yaml 3 | turn_on_service: !include configs/api/service.turn_on.yaml 4 | turn_off_service: !include configs/api/service.turn_off.yaml 5 | self_cleaning_button: !include configs/button/self_cleaning.yaml 6 | steri_cleaning_button: !include configs/button/steri_cleaning.yaml 7 | cleaning_status_text_sensor: !include configs/text_sensor/cleaning_status.yaml 8 | display_switch: !include configs/switch/display.yaml 9 | beeper_switch: !include configs/switch/beeper.yaml 10 | health_mode_switch: !include configs/switch/health_mode.yaml 11 | quiet_mode_switch: !include configs/switch/quiet_mode.yaml 12 | restart_switch: !include configs/switch/restart.yaml 13 | # Vertical airflow direction setting. Works only if the vertical swing is 14 | # off. You can remove this control if you don't need it 15 | airflow_vertical_select: !include configs/select/airflow_vertical.yaml 16 | # Horizontal airflow direction setting. Works only if the horizontal swing 17 | # is off. You can remove this control if you don't need it 18 | airflow_horizontal_select: !include configs/select/airflow_horizontal.yaml 19 | # Additional sensors 20 | outdoor_temperature_sensor: !include configs/sensor/outdoor_temperature.yaml 21 | # Make sure that your AC supports humidity before uncommenting it 22 | # (in the opposite case value will always be 0) 23 | #indoor_humidity_sensor: !include configs/sensor/humidity.yaml 24 | # Diagnostic sensors 25 | indoor_coil_temperature_sensor: !include configs/sensor/indoor_coil_temperature.yaml 26 | outdoor_coil_temperature_sensor: !include configs/sensor/outdoor_coil_temperature.yaml 27 | outdoor_defrost_temperature_sensor: !include configs/sensor/outdoor_defrost_temperature.yaml 28 | outdoor_in_air_temperature_sensor: !include configs/sensor/outdoor_in_air_temperature.yaml 29 | outdoor_out_air_temperature_sensor: !include configs/sensor/outdoor_out_air_temperature.yaml 30 | power_sensor: !include configs/sensor/power.yaml 31 | compressor_frequency_sensor: !include configs/sensor/compressor_frequency.yaml 32 | compressor_current_sensor: !include configs/sensor/compressor_current.yaml 33 | expansion_valve_open_degree_sensor: !include configs/sensor/expansion_valve_open_degree.yaml 34 | # Diagnostic binary sensors 35 | outdoor_fan_status_binary_sensor: !include configs/binary_sensor/outdoor_fan_status.yaml 36 | defrost_status_binary_sensor: !include configs/binary_sensor/defrost_status.yaml 37 | compressor_status_binary_sensor: !include configs/binary_sensor/compressor_status.yaml 38 | indoor_fan_status_binary_sensor: !include configs/binary_sensor/indoor_fan_status.yaml 39 | four_way_valve_status_binary_sensor: !include configs/binary_sensor/four_way_valve_status.yaml 40 | indoor_electric_heating_status_binary_sensor: !include configs/binary_sensor/indoor_electric_heating_status.yaml 41 | # Diagnostic text sensors 42 | appliance_name_text_sensor: !include configs/text_sensor/appliance_name.yaml 43 | protocol_version_text_sensor: !include configs/text_sensor/protocol_version.yaml 44 | -------------------------------------------------------------------------------- /components/haier/switch/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import switch 3 | import esphome.config_validation as cv 4 | from esphome.const import CONF_BEEPER, CONF_DISPLAY, ENTITY_CATEGORY_CONFIG 5 | import esphome.final_validate as fv 6 | 7 | from ..climate import ( 8 | CONF_HAIER_ID, 9 | CONF_PROTOCOL, 10 | PROTOCOL_HON, 11 | HaierClimateBase, 12 | haier_ns, 13 | ) 14 | 15 | CODEOWNERS = ["@paveldn"] 16 | BeeperSwitch = haier_ns.class_("BeeperSwitch", switch.Switch) 17 | HealthModeSwitch = haier_ns.class_("HealthModeSwitch", switch.Switch) 18 | DisplaySwitch = haier_ns.class_("DisplaySwitch", switch.Switch) 19 | QuietModeSwitch = haier_ns.class_("QuietModeSwitch", switch.Switch) 20 | 21 | # Haier switches 22 | CONF_HEALTH_MODE = "health_mode" 23 | CONF_QUIET_MODE = "quiet_mode" 24 | 25 | # Additional icons 26 | ICON_LEAF = "mdi:leaf" 27 | ICON_LED_ON = "mdi:led-on" 28 | ICON_VOLUME_HIGH = "mdi:volume-high" 29 | ICON_VOLUME_OFF = "mdi:volume-off" 30 | 31 | CONFIG_SCHEMA = cv.Schema( 32 | { 33 | cv.GenerateID(CONF_HAIER_ID): cv.use_id(HaierClimateBase), 34 | cv.Optional(CONF_DISPLAY): switch.switch_schema( 35 | DisplaySwitch, 36 | icon=ICON_LED_ON, 37 | entity_category=ENTITY_CATEGORY_CONFIG, 38 | default_restore_mode="DISABLED", 39 | ), 40 | cv.Optional(CONF_HEALTH_MODE): switch.switch_schema( 41 | HealthModeSwitch, 42 | icon=ICON_LEAF, 43 | default_restore_mode="DISABLED", 44 | ), 45 | # Beeper switch is only supported for HonClimate 46 | cv.Optional(CONF_BEEPER): switch.switch_schema( 47 | BeeperSwitch, 48 | icon=ICON_VOLUME_HIGH, 49 | entity_category=ENTITY_CATEGORY_CONFIG, 50 | default_restore_mode="DISABLED", 51 | ), 52 | # Quiet mode is only supported for HonClimate 53 | cv.Optional(CONF_QUIET_MODE): switch.switch_schema( 54 | QuietModeSwitch, 55 | icon=ICON_VOLUME_OFF, 56 | entity_category=ENTITY_CATEGORY_CONFIG, 57 | default_restore_mode="DISABLED", 58 | ), 59 | } 60 | ) 61 | 62 | 63 | def _final_validate(config): 64 | full_config = fv.full_config.get() 65 | for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]: 66 | # Check switches that are only supported for HonClimate 67 | if config.get(switch_type): 68 | climate_path = full_config.get_path_for_id(config[CONF_HAIER_ID])[:-1] 69 | climate_conf = full_config.get_config_for_path(climate_path) 70 | protocol_type = climate_conf.get(CONF_PROTOCOL) 71 | if protocol_type.casefold() != PROTOCOL_HON.casefold(): 72 | raise cv.Invalid( 73 | f"{switch_type} switch is only supported for hon climate" 74 | ) 75 | return config 76 | 77 | 78 | FINAL_VALIDATE_SCHEMA = _final_validate 79 | 80 | 81 | async def to_code(config): 82 | parent = await cg.get_variable(config[CONF_HAIER_ID]) 83 | 84 | for switch_type in [CONF_DISPLAY, CONF_HEALTH_MODE, CONF_BEEPER, CONF_QUIET_MODE]: 85 | if conf := config.get(switch_type): 86 | sw_var = await switch.new_switch(conf) 87 | await cg.register_parented(sw_var, parent) 88 | cg.add(getattr(parent, f"set_{switch_type}_switch")(sw_var)) 89 | -------------------------------------------------------------------------------- /docs/sniffing_serial_communication.rst: -------------------------------------------------------------------------------- 1 | Sniffing Protocol for Haier Appliance Communication 2 | ==================================================== 3 | 4 | If your Haier appliance is not supported or lacks certain features, you may attempt to log the communication between the native dongle and the appliance to "sniff" the protocol. 5 | 6 | Requirements 7 | ------------ 8 | 9 | To achieve this, you will need to add additional wires to provide input for a device that will perform the sniffing. This device can be a computer equipped with two TTL to USB converters or an ESP32 module with the ESHome firmware. 10 | 11 | Concept Overview 12 | ---------------- 13 | 14 | Haier appliances typically have four wires to connect to the native dongle: +5V, Ground (GND), RX, and TX. For sniffing, we will need three of these wires (RX, TX, GND). The +5V wire can be used to power the ESP device if it is being used for sniffing. 15 | 16 | We will create two pairs from these three wires: RX and GND, and TX and GND. Each pair will connect to a separate UART port on a PC or ESP device, serving as input data. Only RX and GND will be used on the "sniffer" side, as we are merely listening, not transmitting. This setup allows us to monitor the communication between the Haier dongle and the appliance separately. 17 | 18 | .. note:: 19 | Ensure that cutting wires is acceptable before proceeding. 20 | 21 | Using a PC with Two TTL to USB Converters 22 | ----------------------------------------- 23 | 24 | **Wiring Diagram:** 25 | 26 | .. raw:: HTML 27 | 28 |

29 | 30 | To capture the data, you will need to use terminal applications such as Termite, HTerm, CoolTerm, etc. The port configuration should be set as follows: 31 | 32 | - Baud rate: 9600 33 | - Data bits: 8 34 | - Stop bits: 1 35 | - Parity: None 36 | 37 | Using an ESP Module 38 | ------------------- 39 | 40 | The approach is similar, but instead of a PC, we will use an ESP device with two UART ports (one for each direction). 41 | 42 | **Wiring Diagram:** 43 | 44 | .. raw:: HTML 45 | 46 |

47 | 48 | **Sample Configuration for ESP32 Sniffer Board:** 49 | 50 | .. code-block:: yaml 51 | 52 | substitutions: 53 | device_name: "Dual UART Sniffer for Haier Project" 54 | device_id: uart_sniffer 55 | 56 | esphome: 57 | name: ${device_id} 58 | comment: ${device_name} 59 | 60 | esp32: 61 | board: esp32dev 62 | 63 | wifi: 64 | ssid: !secret wifi_ssid 65 | password: !secret wifi_password 66 | 67 | api: 68 | reboot_timeout: 0s 69 | 70 | ota: 71 | 72 | web_server: 73 | 74 | logger: 75 | tx_buffer_size: 4096 76 | baud_rate: 0 77 | level: DEBUG 78 | 79 | uart: 80 | - id: esp_board 81 | rx_pin: GPIO3 82 | baud_rate: 9600 83 | debug: 84 | direction: RX 85 | dummy_receiver: true 86 | sequence: 87 | - lambda: UARTDebug::log_hex(uart::UART_DIRECTION_RX, bytes, ' '); 88 | - id: haier_appliance 89 | rx_pin: GPIO16 90 | baud_rate: 9600 91 | debug: 92 | direction: RX 93 | dummy_receiver: true 94 | sequence: 95 | - lambda: UARTDebug::log_hex(uart::UART_DIRECTION_TX, bytes, ' '); 96 | 97 | This configuration will enable you to capture and analyze the communication between the Haier dongle and the appliance effectively. 98 | -------------------------------------------------------------------------------- /docs/esphome-docs/sensor/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate Sensors 2 | ===================== 3 | 4 | .. seo:: 5 | :description: Instructions for setting up additional sensors for Haier climate devices. 6 | :image: haier.svg 7 | 8 | Additional sensors for Haier Climate device. **These sensors are supported only by the hOn protocol**. 9 | 10 | 11 | .. figure:: images/haier-climate.jpg 12 | :align: center 13 | :width: 50.0% 14 | 15 | .. code-block:: yaml 16 | 17 | # Example configuration entry 18 | sensor: 19 | - platform: haier 20 | haier_id: haier_ac 21 | outdoor_temperature: 22 | name: Haier outdoor temperature 23 | humidity: 24 | name: Haier Indoor Humidity 25 | compressor_current: 26 | name: Haier Compressor Current 27 | compressor_frequency: 28 | name: Haier Compressor Frequency 29 | expansion_valve_open_degree: 30 | name: Haier Expansion Valve Open Degree 31 | indoor_coil_temperature: 32 | name: Haier Indoor Coil Temperature 33 | outdoor_coil_temperature: 34 | name: Haier Outdoor Coil Temperature 35 | outdoor_defrost_temperature: 36 | name: Haier Outdoor Defrost Temperature 37 | outdoor_in_air_temperature: 38 | name: Haier Outdoor In Air Temperature 39 | outdoor_out_air_temperature: 40 | name: Haier Outdoor Out Air Temperature 41 | power: 42 | name: Haier Power 43 | 44 | Configuration variables: 45 | ------------------------ 46 | 47 | - **haier_id** (**Required**, :ref:`config-id`): The id of haier climate component 48 | - **outdoor_temperature** (*Optional*): Temperature sensor for outdoor temperature. 49 | All options from :ref:`Sensor `. 50 | - **humidity** (*Optional*): Sensor for indoor humidity. Make sure that your climate model supports this type of sensor. 51 | All options from :ref:`Sensor `. 52 | - **compressor_current** (*Optional*): Sensor for climate compressor current. Make sure that your climate model supports this type of sensor. 53 | All options from :ref:`Sensor `. 54 | - **compressor_frequency** (*Optional*): Sensor for climate compressor frequency. Make sure that your climate model supports this type of sensor. 55 | All options from :ref:`Sensor `. 56 | - **expansion_valve_open_degree** (*Optional*): Sensor for climate's expansion valve open degree. Make sure that your climate model supports this type of sensor. 57 | All options from :ref:`Sensor `. 58 | - **indoor_coil_temperature** (*Optional*): Temperature sensor for indoor coil temperature. Make sure that your climate model supports this type of sensor. 59 | All options from :ref:`Sensor `. 60 | - **outdoor_coil_temperature** (*Optional*): Temperature sensor for outdoor coil temperature. Make sure that your climate model supports this type of sensor. 61 | All options from :ref:`Sensor `. 62 | - **outdoor_defrost_temperature** (*Optional*): Temperature sensor for outdoor defrost temperature. Make sure that your climate model supports this type of sensor. 63 | All options from :ref:`Sensor `. 64 | - **outdoor_in_air_temperature** (*Optional*): Temperature sensor incoming air temperature. 65 | All options from :ref:`Sensor `. 66 | - **outdoor_out_air_temperature** (*Optional*): Temperature sensor for outgoing air temperature. 67 | All options from :ref:`Sensor `. 68 | - **power** (*Optional*): Sensor for climate power consumption. Make sure that your climate model supports this type of sensor. 69 | All options from :ref:`Sensor `. 70 | 71 | 72 | See Also 73 | -------- 74 | 75 | - :doc:`Haier Climate ` 76 | - :ref:`sensor-filters` 77 | - :ghedit:`Edit` 78 | -------------------------------------------------------------------------------- /components/haier/automation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/automation.h" 4 | #include "haier_base.h" 5 | #include "hon_climate.h" 6 | 7 | namespace esphome { 8 | namespace haier { 9 | 10 | template class DisplayOnAction : public Action { 11 | public: 12 | DisplayOnAction(HaierClimateBase *parent) : parent_(parent) {} 13 | void play(const Ts &...x) { this->parent_->set_display_state(true); } 14 | 15 | protected: 16 | HaierClimateBase *parent_; 17 | }; 18 | 19 | template class DisplayOffAction : public Action { 20 | public: 21 | DisplayOffAction(HaierClimateBase *parent) : parent_(parent) {} 22 | void play(const Ts &...x) { this->parent_->set_display_state(false); } 23 | 24 | protected: 25 | HaierClimateBase *parent_; 26 | }; 27 | 28 | template class BeeperOnAction : public Action { 29 | public: 30 | BeeperOnAction(HonClimate *parent) : parent_(parent) {} 31 | void play(const Ts &...x) { this->parent_->set_beeper_state(true); } 32 | 33 | protected: 34 | HonClimate *parent_; 35 | }; 36 | 37 | template class BeeperOffAction : public Action { 38 | public: 39 | BeeperOffAction(HonClimate *parent) : parent_(parent) {} 40 | void play(const Ts &...x) { this->parent_->set_beeper_state(false); } 41 | 42 | protected: 43 | HonClimate *parent_; 44 | }; 45 | 46 | template class VerticalAirflowAction : public Action { 47 | public: 48 | VerticalAirflowAction(HonClimate *parent) : parent_(parent) {} 49 | TEMPLATABLE_VALUE(hon_protocol::VerticalSwingMode, direction) 50 | void play(const Ts &...x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); } 51 | 52 | protected: 53 | HonClimate *parent_; 54 | }; 55 | 56 | template class HorizontalAirflowAction : public Action { 57 | public: 58 | HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {} 59 | TEMPLATABLE_VALUE(hon_protocol::HorizontalSwingMode, direction) 60 | void play(const Ts &...x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); } 61 | 62 | protected: 63 | HonClimate *parent_; 64 | }; 65 | 66 | template class HealthOnAction : public Action { 67 | public: 68 | HealthOnAction(HaierClimateBase *parent) : parent_(parent) {} 69 | void play(const Ts &...x) { this->parent_->set_health_mode(true); } 70 | 71 | protected: 72 | HaierClimateBase *parent_; 73 | }; 74 | 75 | template class HealthOffAction : public Action { 76 | public: 77 | HealthOffAction(HaierClimateBase *parent) : parent_(parent) {} 78 | void play(const Ts &...x) { this->parent_->set_health_mode(false); } 79 | 80 | protected: 81 | HaierClimateBase *parent_; 82 | }; 83 | 84 | template class StartSelfCleaningAction : public Action { 85 | public: 86 | StartSelfCleaningAction(HonClimate *parent) : parent_(parent) {} 87 | void play(const Ts &...x) { this->parent_->start_self_cleaning(); } 88 | 89 | protected: 90 | HonClimate *parent_; 91 | }; 92 | 93 | template class StartSteriCleaningAction : public Action { 94 | public: 95 | StartSteriCleaningAction(HonClimate *parent) : parent_(parent) {} 96 | void play(const Ts &...x) { this->parent_->start_steri_cleaning(); } 97 | 98 | protected: 99 | HonClimate *parent_; 100 | }; 101 | 102 | template class PowerOnAction : public Action { 103 | public: 104 | PowerOnAction(HaierClimateBase *parent) : parent_(parent) {} 105 | void play(const Ts &...x) { this->parent_->send_power_on_command(); } 106 | 107 | protected: 108 | HaierClimateBase *parent_; 109 | }; 110 | 111 | template class PowerOffAction : public Action { 112 | public: 113 | PowerOffAction(HaierClimateBase *parent) : parent_(parent) {} 114 | void play(const Ts &...x) { this->parent_->send_power_off_command(); } 115 | 116 | protected: 117 | HaierClimateBase *parent_; 118 | }; 119 | 120 | template class PowerToggleAction : public Action { 121 | public: 122 | PowerToggleAction(HaierClimateBase *parent) : parent_(parent) {} 123 | void play(const Ts &...x) { this->parent_->toggle_power(); } 124 | 125 | protected: 126 | HaierClimateBase *parent_; 127 | }; 128 | 129 | } // namespace haier 130 | } // namespace esphome 131 | -------------------------------------------------------------------------------- /docs/script/make_doc.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import sys 4 | 5 | doc_file_path = [ 6 | "/".join(["esphome-docs", "climate", "haier.rst"]), 7 | "/".join(["esphome-docs", "sensor", "haier.rst"]), 8 | "/".join(["esphome-docs", "binary_sensor", "haier.rst"]), 9 | "/".join(["esphome-docs", "text_sensor", "haier.rst"]), 10 | "/".join(["esphome-docs", "button", "haier.rst"]), 11 | "/".join(["esphome-docs", "switch", "haier.rst"]), 12 | "esp32_backup.rst", 13 | "additional_information.rst", 14 | ] 15 | 16 | def process_esphome_refs(line, l_num): 17 | esphome_refs = [ 18 | (":ref:`UART bus `", "`UART Bus `_"), 19 | (":ref:`config-id`", "`ID `_"), 20 | (":ref:`config-time`", "`Time `_"), 21 | (":ref:`Automation `", "`Automation `_"), 22 | (":ref:`haier-on_alarm_start`", "`on_alarm_start Trigger`_"), 23 | (":ref:`haier-on_alarm_end`", "`on_alarm_end Trigger`_"), 24 | (":ref:`haier-on_status_message`", "`on_status_message Trigger`_"), 25 | (":ref:`Climate `", "`Climate `_"), 26 | (":ref:`lambdas `", "`lambdas `_"), 27 | (":ref:`Sensor `", "`Sensor `_"), 28 | (":ref:`Binary Sensor `", "`Binary Sensor `_"), 29 | (":ref:`Text Sensor `", "`Text Sensor `_"), 30 | (":ref:`Button `", "`Button `_"), 31 | (":ref:`Switch `", "`Switch `_"), 32 | 33 | ] 34 | res = line 35 | for o, r in esphome_refs: 36 | res = res.replace(o, r) 37 | if res.find(":ref:") != -1: 38 | print(f"\tWarning: ref found, line #{l_num}") 39 | return res 40 | 41 | def process_figure(section, output_file, pth): 42 | m = re.match(r"^( *\.\. +figure:: +)(.+) *$", section[0]) 43 | if m: 44 | new_url = "./docs/" 45 | if len(pth) > 0: 46 | new_url += pth + "/" 47 | new_url += m.group(2) 48 | width = "100" 49 | center = False 50 | desc = "" 51 | for sl in section[1:]: 52 | if not sl.strip().startswith(":"): 53 | if len(sl.strip()) > 0: 54 | desc += f"
 {sl.strip()}" 55 | if re.match(r"^ *:align: +center *$", sl): 56 | center = True 57 | else: 58 | m = re.match(r"^ *:width: +(\d+)(?:\.\d+)?\%$", sl) 59 | if m: 60 | width = m.group(1) 61 | htm = f"" 62 | output_file.write(f".. raw:: HTML\n\n

{htm}{desc}

\n\n") 63 | 64 | def process_section(section, output_file, pth): 65 | print(f"\tProcessing section: {section[0].strip()}") 66 | if section[0] == ".. seo::\n": 67 | pass 68 | elif section[0].startswith(".. figure::"): 69 | process_figure(section, output_file, pth) 70 | else: 71 | output_file.writelines(section) 72 | 73 | document_header = [ 74 | ".. This file is automatically generated by ./docs/script/make_doc.py Python script.\n", 75 | " Please, don't change. In case you need to make corrections or changes change\n", 76 | " source documentation in ./doc folder or script.\n\n" 77 | ] 78 | 79 | script_path = os.path.dirname(__file__) 80 | output_file_name = "../../README.rst" 81 | if len(sys.argv) > 1: 82 | output_file_name = sys.argv[1] 83 | output_file = open(output_file_name, "w") 84 | output_file.writelines(document_header) 85 | for in_f in doc_file_path: 86 | print(f"Processing: {in_f}") 87 | output_file.write(f".. Generated from {in_f}\n\n") 88 | input_file = open(os.path.join(script_path, "..", in_f), "r") 89 | p = os.path.dirname(in_f) 90 | lines = input_file.readlines() 91 | is_seo = False 92 | is_section = False 93 | section = [] 94 | l_counter = 0 95 | for line in lines: 96 | l_counter += 1 97 | if line == "See Also\n": 98 | break 99 | if is_section: 100 | if (line in ['\n', '\r\n']) or line.startswith(" "): 101 | section.append(line) 102 | continue 103 | else: 104 | is_section = False 105 | process_section(section, output_file, p) 106 | if (not is_section) and line.startswith(".. "): 107 | is_section = True 108 | section = [ line ] 109 | continue 110 | if line == ".. seo::\n": 111 | is_seo = True 112 | continue 113 | if is_seo: 114 | if not line.startswith(" :"): 115 | is_seo = False 116 | continue 117 | l = process_esphome_refs(line, l_counter) 118 | output_file.write(l) 119 | print(f"\tProcessed {l_counter} line(s)") -------------------------------------------------------------------------------- /components/haier/sensor/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | from esphome.components import sensor 3 | import esphome.config_validation as cv 4 | from esphome.const import ( 5 | CONF_HUMIDITY, 6 | CONF_OUTDOOR_TEMPERATURE, 7 | CONF_POWER, 8 | DEVICE_CLASS_CURRENT, 9 | DEVICE_CLASS_FREQUENCY, 10 | DEVICE_CLASS_HUMIDITY, 11 | DEVICE_CLASS_POWER, 12 | DEVICE_CLASS_TEMPERATURE, 13 | ENTITY_CATEGORY_DIAGNOSTIC, 14 | ICON_CURRENT_AC, 15 | ICON_FLASH, 16 | ICON_GAUGE, 17 | ICON_HEATING_COIL, 18 | ICON_PULSE, 19 | ICON_THERMOMETER, 20 | ICON_WATER_PERCENT, 21 | ICON_WEATHER_WINDY, 22 | STATE_CLASS_MEASUREMENT, 23 | UNIT_AMPERE, 24 | UNIT_CELSIUS, 25 | UNIT_HERTZ, 26 | UNIT_PERCENT, 27 | UNIT_WATT, 28 | ) 29 | 30 | from ..climate import CONF_HAIER_ID, HonClimate 31 | 32 | CODEOWNERS = ["@paveldn"] 33 | SensorTypeEnum = HonClimate.enum("SubSensorType", True) 34 | 35 | # Haier sensors 36 | CONF_COMPRESSOR_CURRENT = "compressor_current" 37 | CONF_COMPRESSOR_FREQUENCY = "compressor_frequency" 38 | CONF_EXPANSION_VALVE_OPEN_DEGREE = "expansion_valve_open_degree" 39 | CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature" 40 | CONF_OUTDOOR_COIL_TEMPERATURE = "outdoor_coil_temperature" 41 | CONF_OUTDOOR_DEFROST_TEMPERATURE = "outdoor_defrost_temperature" 42 | CONF_OUTDOOR_IN_AIR_TEMPERATURE = "outdoor_in_air_temperature" 43 | CONF_OUTDOOR_OUT_AIR_TEMPERATURE = "outdoor_out_air_temperature" 44 | 45 | # Additional icons 46 | ICON_SNOWFLAKE_THERMOMETER = "mdi:snowflake-thermometer" 47 | 48 | SENSOR_TYPES = { 49 | CONF_COMPRESSOR_CURRENT: sensor.sensor_schema( 50 | unit_of_measurement=UNIT_AMPERE, 51 | icon=ICON_CURRENT_AC, 52 | accuracy_decimals=1, 53 | device_class=DEVICE_CLASS_CURRENT, 54 | state_class=STATE_CLASS_MEASUREMENT, 55 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 56 | ), 57 | CONF_COMPRESSOR_FREQUENCY: sensor.sensor_schema( 58 | unit_of_measurement=UNIT_HERTZ, 59 | icon=ICON_PULSE, 60 | accuracy_decimals=0, 61 | device_class=DEVICE_CLASS_FREQUENCY, 62 | state_class=STATE_CLASS_MEASUREMENT, 63 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 64 | ), 65 | CONF_EXPANSION_VALVE_OPEN_DEGREE: sensor.sensor_schema( 66 | unit_of_measurement=UNIT_PERCENT, 67 | icon=ICON_GAUGE, 68 | accuracy_decimals=2, 69 | state_class=STATE_CLASS_MEASUREMENT, 70 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 71 | ), 72 | CONF_HUMIDITY: sensor.sensor_schema( 73 | unit_of_measurement=UNIT_PERCENT, 74 | icon=ICON_WATER_PERCENT, 75 | accuracy_decimals=0, 76 | device_class=DEVICE_CLASS_HUMIDITY, 77 | state_class=STATE_CLASS_MEASUREMENT, 78 | ), 79 | CONF_INDOOR_COIL_TEMPERATURE: sensor.sensor_schema( 80 | unit_of_measurement=UNIT_CELSIUS, 81 | icon=ICON_HEATING_COIL, 82 | accuracy_decimals=0, 83 | device_class=DEVICE_CLASS_TEMPERATURE, 84 | state_class=STATE_CLASS_MEASUREMENT, 85 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 86 | ), 87 | CONF_OUTDOOR_COIL_TEMPERATURE: sensor.sensor_schema( 88 | unit_of_measurement=UNIT_CELSIUS, 89 | icon=ICON_HEATING_COIL, 90 | accuracy_decimals=0, 91 | device_class=DEVICE_CLASS_TEMPERATURE, 92 | state_class=STATE_CLASS_MEASUREMENT, 93 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 94 | ), 95 | CONF_OUTDOOR_DEFROST_TEMPERATURE: sensor.sensor_schema( 96 | unit_of_measurement=UNIT_CELSIUS, 97 | icon=ICON_SNOWFLAKE_THERMOMETER, 98 | accuracy_decimals=0, 99 | device_class=DEVICE_CLASS_TEMPERATURE, 100 | state_class=STATE_CLASS_MEASUREMENT, 101 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 102 | ), 103 | CONF_OUTDOOR_IN_AIR_TEMPERATURE: sensor.sensor_schema( 104 | unit_of_measurement=UNIT_CELSIUS, 105 | icon=ICON_WEATHER_WINDY, 106 | accuracy_decimals=0, 107 | device_class=DEVICE_CLASS_TEMPERATURE, 108 | state_class=STATE_CLASS_MEASUREMENT, 109 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 110 | ), 111 | CONF_OUTDOOR_OUT_AIR_TEMPERATURE: sensor.sensor_schema( 112 | unit_of_measurement=UNIT_CELSIUS, 113 | icon=ICON_WEATHER_WINDY, 114 | accuracy_decimals=0, 115 | device_class=DEVICE_CLASS_TEMPERATURE, 116 | state_class=STATE_CLASS_MEASUREMENT, 117 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 118 | ), 119 | CONF_OUTDOOR_TEMPERATURE: sensor.sensor_schema( 120 | unit_of_measurement=UNIT_CELSIUS, 121 | icon=ICON_THERMOMETER, 122 | accuracy_decimals=0, 123 | device_class=DEVICE_CLASS_TEMPERATURE, 124 | state_class=STATE_CLASS_MEASUREMENT, 125 | ), 126 | CONF_POWER: sensor.sensor_schema( 127 | unit_of_measurement=UNIT_WATT, 128 | icon=ICON_FLASH, 129 | accuracy_decimals=0, 130 | device_class=DEVICE_CLASS_POWER, 131 | state_class=STATE_CLASS_MEASUREMENT, 132 | entity_category=ENTITY_CATEGORY_DIAGNOSTIC, 133 | ), 134 | } 135 | 136 | CONFIG_SCHEMA = cv.Schema( 137 | { 138 | cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate), 139 | } 140 | ).extend({cv.Optional(type_): schema for type_, schema in SENSOR_TYPES.items()}) 141 | 142 | 143 | async def to_code(config): 144 | paren = await cg.get_variable(config[CONF_HAIER_ID]) 145 | 146 | for type_ in SENSOR_TYPES: 147 | if conf := config.get(type_): 148 | sens = await sensor.new_sensor(conf) 149 | sensor_type = getattr(SensorTypeEnum, type_.upper()) 150 | cg.add(paren.set_sub_sensor(sensor_type, sens)) 151 | -------------------------------------------------------------------------------- /docs/protocol_overview.rst: -------------------------------------------------------------------------------- 1 | Overview of Communication Protocol between Haier Air Conditioner and ESP 2 | ======================================================================== 3 | 4 | The data packets exchanged between the air conditioner and the ESP are structured as follows: 5 | 6 | - FF FF: Packet start indicator 7 | - 1 byte: Length 8 | - 1 byte: Flags (typically 0x40 for hOn indicating the packet contains a CRC16 checksum at the end, and 0x00 for smartAir2 without CRC16) 9 | - 5 bytes: Reserved for future use (usually zeros for hOn and zeros with 01 at the end for smartAir2) 10 | - 1 byte: Packet type 11 | - n bytes: Data 12 | - 1 byte: Checksum (modulo 256 sum of all packet bytes) 13 | - 2 bytes: CRC16 (if specified by flags) 14 | 15 | To view the complete packet, enable logs at the VERBOSE level. Generally, this is unnecessary as the transport protocol remains consistent. There are additional nuances, but we will not delve too deeply into them here. 16 | 17 | As far as I can tell, Haier uses this same protocol for other equipment as well (at least for washing machines, dryers, and dishwashers). I have a C++ library independent of ESPHome that works with this protocol: `HaierProtocol `_ 18 | 19 | We will now focus solely on hOn. Our current interest lies in the packet type and data. Typically, requests to the air conditioner use an odd packet type ids, and responses use an even type ids: 20 | 21 | - 0x01: Status request or control 22 | - 0x02: Status response from the air conditioner 23 | 24 | All packet types can be found here: `Haier Frame Types. `_ 25 | 26 | Our focus will be on packet type 0x01 and response 0x02. For some packets, including these, the first 2 bytes are subcommands. We are interested in the following 3 subcommands: 27 | 28 | - 0x4D01: Request normal data 29 | - 0x4DFE: Request extended data (big data) 30 | - 0x5C01: Send state to the air conditioner (control) 31 | 32 | Example log entries (DEBUG level) illustrate this: 33 | 34 | Request for normal data: 35 | 36 | .. code-block:: 37 | 38 | 20:07:46 [D] [haier.protocol:019] Sending frame: type 01, data: 4D 01 39 | 40 | 41 | Response: 42 | 43 | .. code-block:: 44 | 45 | 20:07:46 [D] [haier.protocol:019] Frame found: type 02, data: 6D 01 07 06 C2 00 02 00 00 07 00 00 3E 00 50 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 46 | 47 | Request for extended data: 48 | 49 | .. code-block:: 50 | 51 | 20:07:57 [D] [haier.protocol:019] Sending frame: type 01, data: 4D FE 52 | 53 | Response: 54 | 55 | .. code-block:: 56 | 57 | 20:07:57 [D] [haier.protocol:019] Frame found: type 02, data: 7D 01 07 06 C2 00 02 00 00 07 00 00 3E 00 50 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 66 4A 4D 4D 4C 00 01 FF 02 20 00 32 58 | 59 | 60 | The status response usually appears as follows: 61 | 62 | - 2 bytes: Subcommand (6D 01 for normal data or control confirmation, 7D 01 for extended data) 63 | - 10 bytes: Air conditioner parameters (mode, flaps, temperature, on/off status, cleaning, etc.) 64 | - 16 bytes: Additional sensors, most of which are unused (possibly for future models) 65 | - 2 bytes: Zeros (possibly reserved for future use) 66 | 67 | For extended data: 68 | - 14 bytes: Extended sensors (energy consumption, temperatures of different parts, current strength, etc.), not all of which are used 69 | 70 | The structures for all these data points can be found here: `Haier ESPHome Packet Structures. `_ 71 | 72 | This structure works for 99% of air conditioners, though variations exist with different inserts between these sections. For instance, some manufacturers add an extra byte for special parameter control, shifting all sensor data. 73 | 74 | Note that my component requests extended data once every 2 intervals only if at least one sensor using these data is added. Therefore, packet part sizes can vary. 75 | 76 | Finding Packet Sizes 77 | ==================== 78 | 79 | For Big Data: 80 | ------------- 81 | 82 | Compare the response to the request type 01, data: 4D 01 with the response to type 01, data: 4D FE. 83 | The size difference is the big data packet size (everything else is the same as a normal packet). 84 | To identify the start of the control packet (sometimes preceded by an insignificant header to be skipped), use the following algorithm: 85 | 86 | For control part: 87 | ----------------- 88 | 89 | Turn on the air conditioner using the remote (set any mode where the temperature can be adjusted). 90 | Set the temperature to 20 degrees. In the status packet, the target temperature is always at the start of the air conditioner parameters block, encoded as: 91 | 92 | - 0 -> 16°C 93 | - 1 -> 17°C 94 | - 2 -> 18°C 95 | - ... 96 | 97 | For 20°C, look for the value 0x04 in the packet. If multiple such bytes exist, set 21°C and look for 0x05, etc., until found uniquely. This will likely be byte 3, immediately after the subcommand. 98 | To locate the start of the sensor part, follow these steps: 99 | 100 | For sensors part: 101 | ----------------- 102 | 103 | The sensors always start with the room temperature (1 byte), available at all times and encoded in 0.5°C increments. Match the temperature displayed on your air conditioner's screen. 104 | For example, if the display shows 25.5°C, search for 25.5 x 2 = 51 => 0x33 in the packet. The next byte after room temperature is humidity (percentage), often unsupported by most air conditioners, likely resulting in 0. 105 | The third byte in the sensor packet is the outdoor temperature with an offset of -64°C. For instance, if the outdoor temperature is 28°C, search for 28 + 64 = 92 => 0x5C. Note that if the air conditioner is off, this value remains unchanged, reflecting the last measured value. 106 | Look for approximate values with a tolerance of ±3°C for outdoor temperature. 107 | 108 | Configuring the Component 109 | ------------------------- 110 | 111 | Once you have identified all sizes and offsets, configure the component with the following parameters: 112 | 113 | - status_message_header_size: Number of bytes to skip before the control packet starts (default is 0, likely unnecessary). I have seen only one case with a header: `Issue Comment `_. 114 | - control_packet_size: Size of the control packet, with a minimum and default value of 10. Count the bytes from the first control packet byte (target temperature) to room temperature. 115 | - sensors_packet_size: Size of the sensor packet, with a minimum and default value of 18. Count the bytes from room temperature to the end of the short packet. 116 | 117 | For some models, I have observed a control_packet_size of 12. For some ducted air conditioners, it is 18. The only exception is the model Casarte CAS35MW1/R3-W, 1U35MW1/R3 with: 118 | 119 | .. code-block:: 120 | 121 | status_message_header_size: 38 122 | control_packet_size: 10 123 | sensors_packet_size: 24 124 | 125 | 126 | -------------------------------------------------------------------------------- /components/haier/haier_base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "esphome/components/climate/climate.h" 5 | #include "esphome/components/uart/uart.h" 6 | #include "esphome/core/automation.h" 7 | // HaierProtocol 8 | #include 9 | 10 | #ifdef USE_SWITCH 11 | #include "esphome/components/switch/switch.h" 12 | #endif 13 | 14 | namespace esphome { 15 | namespace haier { 16 | 17 | enum class ActionRequest : uint8_t { 18 | SEND_CUSTOM_COMMAND = 0, 19 | TURN_POWER_ON = 1, 20 | TURN_POWER_OFF = 2, 21 | TOGGLE_POWER = 3, 22 | START_SELF_CLEAN = 4, // only hOn 23 | START_STERI_CLEAN = 5, // only hOn 24 | }; 25 | 26 | struct HaierBaseSettings { 27 | bool health_mode; 28 | bool display_state; 29 | }; 30 | 31 | class HaierClimateBase : public esphome::Component, 32 | public esphome::climate::Climate, 33 | public esphome::uart::UARTDevice, 34 | public haier_protocol::ProtocolStream { 35 | #ifdef USE_SWITCH 36 | public: 37 | void set_display_switch(switch_::Switch *sw); 38 | void set_health_mode_switch(switch_::Switch *sw); 39 | 40 | protected: 41 | switch_::Switch *display_switch_{nullptr}; 42 | switch_::Switch *health_mode_switch_{nullptr}; 43 | #endif 44 | public: 45 | HaierClimateBase(); 46 | HaierClimateBase(const HaierClimateBase &) = delete; 47 | HaierClimateBase &operator=(const HaierClimateBase &) = delete; 48 | ~HaierClimateBase(); 49 | void setup() override; 50 | void loop() override; 51 | void control(const esphome::climate::ClimateCall &call) override; 52 | void dump_config() override; 53 | float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } 54 | void set_display_state(bool state); 55 | bool get_display_state() const; 56 | void set_health_mode(bool state); 57 | bool get_health_mode() const; 58 | void send_power_on_command(); 59 | void send_power_off_command(); 60 | void toggle_power(); 61 | void reset_protocol() { this->reset_protocol_request_ = true; }; 62 | void set_supported_modes(esphome::climate::ClimateModeMask modes); 63 | void set_supported_swing_modes(esphome::climate::ClimateSwingModeMask modes); 64 | void set_supported_presets(esphome::climate::ClimatePresetMask presets); 65 | bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; 66 | size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; 67 | size_t read_array(uint8_t *data, size_t len) noexcept override { 68 | return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; 69 | }; 70 | void write_array(const uint8_t *data, size_t len) noexcept override { 71 | esphome::uart::UARTDevice::write_array(data, len); 72 | }; 73 | bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; 74 | void set_answer_timeout(uint32_t timeout); 75 | void set_send_wifi(bool send_wifi); 76 | void send_custom_command(const haier_protocol::HaierMessage &message); 77 | void add_status_message_callback(std::function &&callback); 78 | 79 | protected: 80 | enum class ProtocolPhases { 81 | UNKNOWN = -1, 82 | // INITIALIZATION 83 | SENDING_INIT_1 = 0, 84 | SENDING_INIT_2, 85 | SENDING_FIRST_STATUS_REQUEST, 86 | SENDING_FIRST_ALARM_STATUS_REQUEST, 87 | // FUNCTIONAL STATE 88 | IDLE, 89 | SENDING_STATUS_REQUEST, 90 | SENDING_UPDATE_SIGNAL_REQUEST, 91 | SENDING_SIGNAL_LEVEL, 92 | SENDING_CONTROL, 93 | SENDING_ACTION_COMMAND, 94 | SENDING_ALARM_STATUS_REQUEST, 95 | NUM_PROTOCOL_PHASES 96 | }; 97 | const char *phase_to_string_(ProtocolPhases phase); 98 | virtual void set_handlers() = 0; 99 | virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; 100 | virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming) 101 | virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; // NOLINT(readability-identifier-naming) 102 | virtual void save_settings(); 103 | virtual void initialization(); 104 | virtual bool prepare_pending_action(); 105 | virtual void process_protocol_reset(); 106 | esphome::climate::ClimateTraits traits() override; 107 | // Answer handlers 108 | haier_protocol::HandlerError answer_preprocess_(haier_protocol::FrameType request_message_type, 109 | haier_protocol::FrameType expected_request_message_type, 110 | haier_protocol::FrameType answer_message_type, 111 | haier_protocol::FrameType expected_answer_message_type, 112 | ProtocolPhases expected_phase); 113 | haier_protocol::HandlerError report_network_status_answer_handler_(haier_protocol::FrameType request_type, 114 | haier_protocol::FrameType message_type, 115 | const uint8_t *data, size_t data_size); 116 | // Timeout handler 117 | haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type); 118 | // Helper functions 119 | void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats = 0, 120 | std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); 121 | virtual void set_phase(ProtocolPhases phase); 122 | void reset_phase_(); 123 | void reset_to_idle_(); 124 | bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); 125 | bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); 126 | bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); 127 | bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); 128 | #ifdef USE_WIFI 129 | haier_protocol::HaierMessage get_wifi_signal_message_(); 130 | #endif 131 | 132 | struct HvacSettings { 133 | esphome::optional mode; 134 | esphome::optional fan_mode; 135 | esphome::optional swing_mode; 136 | esphome::optional target_temperature; 137 | esphome::optional preset; 138 | bool valid; 139 | HvacSettings() : valid(false){}; 140 | HvacSettings(const HvacSettings &) = default; 141 | HvacSettings &operator=(const HvacSettings &) = default; 142 | void reset(); 143 | }; 144 | struct PendingAction { 145 | ActionRequest action; 146 | esphome::optional message; 147 | }; 148 | enum class SwitchState { 149 | OFF = 0b00, 150 | ON = 0b01, 151 | PENDING_OFF = 0b10, 152 | PENDING_ON = 0b11, 153 | }; 154 | haier_protocol::ProtocolHandler haier_protocol_; 155 | ProtocolPhases protocol_phase_; 156 | esphome::optional action_request_; 157 | uint8_t fan_mode_speed_; 158 | uint8_t other_modes_fan_speed_; 159 | SwitchState display_status_{SwitchState::ON}; 160 | SwitchState health_mode_{SwitchState::OFF}; 161 | bool force_send_control_; 162 | bool forced_request_status_; 163 | bool reset_protocol_request_; 164 | bool send_wifi_signal_; 165 | bool use_crc_; 166 | esphome::climate::ClimateTraits traits_; 167 | HvacSettings current_hvac_settings_; 168 | HvacSettings next_hvac_settings_; 169 | std::unique_ptr last_status_message_{nullptr}; 170 | std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages 171 | std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout 172 | std::chrono::steady_clock::time_point last_status_request_; // To request AC status 173 | std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level 174 | CallbackManager status_message_callback_{}; 175 | ESPPreferenceObject base_rtc_; 176 | }; 177 | 178 | class StatusMessageTrigger : public Trigger { 179 | public: 180 | explicit StatusMessageTrigger(HaierClimateBase *parent) { 181 | parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); }); 182 | } 183 | }; 184 | 185 | } // namespace haier 186 | } // namespace esphome 187 | -------------------------------------------------------------------------------- /components/haier/hon_climate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #ifdef USE_SENSOR 5 | #include "esphome/components/sensor/sensor.h" 6 | #endif 7 | #ifdef USE_BINARY_SENSOR 8 | #include "esphome/components/binary_sensor/binary_sensor.h" 9 | #endif 10 | #ifdef USE_TEXT_SENSOR 11 | #include "esphome/components/text_sensor/text_sensor.h" 12 | #endif 13 | #ifdef USE_SWITCH 14 | #include "esphome/components/switch/switch.h" 15 | #endif 16 | #include "esphome/core/automation.h" 17 | #include "haier_base.h" 18 | #include "hon_packet.h" 19 | 20 | namespace esphome { 21 | namespace haier { 22 | 23 | enum class CleaningState : uint8_t { 24 | NO_CLEANING = 0, 25 | SELF_CLEAN = 1, 26 | STERI_CLEAN = 2, 27 | }; 28 | 29 | enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; 30 | 31 | struct HonSettings { 32 | hon_protocol::VerticalSwingMode last_vertiacal_swing; 33 | hon_protocol::HorizontalSwingMode last_horizontal_swing; 34 | bool beeper_state; 35 | bool quiet_mode_state; 36 | }; 37 | 38 | class HonClimate : public HaierClimateBase { 39 | #ifdef USE_SENSOR 40 | public: 41 | enum class SubSensorType { 42 | // Used data based sensors 43 | OUTDOOR_TEMPERATURE = 0, 44 | HUMIDITY, 45 | // Big data based sensors 46 | INDOOR_COIL_TEMPERATURE, 47 | OUTDOOR_COIL_TEMPERATURE, 48 | OUTDOOR_DEFROST_TEMPERATURE, 49 | OUTDOOR_IN_AIR_TEMPERATURE, 50 | OUTDOOR_OUT_AIR_TEMPERATURE, 51 | POWER, 52 | COMPRESSOR_FREQUENCY, 53 | COMPRESSOR_CURRENT, 54 | EXPANSION_VALVE_OPEN_DEGREE, 55 | SUB_SENSOR_TYPE_COUNT, 56 | BIG_DATA_FRAME_SUB_SENSORS = INDOOR_COIL_TEMPERATURE, 57 | }; 58 | void set_sub_sensor(SubSensorType type, sensor::Sensor *sens); 59 | 60 | protected: 61 | void update_sub_sensor_(SubSensorType type, float value); 62 | sensor::Sensor *sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]{nullptr}; 63 | #endif 64 | #ifdef USE_BINARY_SENSOR 65 | public: 66 | enum class SubBinarySensorType { 67 | OUTDOOR_FAN_STATUS = 0, 68 | DEFROST_STATUS, 69 | COMPRESSOR_STATUS, 70 | INDOOR_FAN_STATUS, 71 | FOUR_WAY_VALVE_STATUS, 72 | INDOOR_ELECTRIC_HEATING_STATUS, 73 | SUB_BINARY_SENSOR_TYPE_COUNT, 74 | }; 75 | void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens); 76 | 77 | protected: 78 | void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value); 79 | binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr}; 80 | #endif 81 | #ifdef USE_TEXT_SENSOR 82 | public: 83 | enum class SubTextSensorType { 84 | CLEANING_STATUS = 0, 85 | PROTOCOL_VERSION, 86 | APPLIANCE_NAME, 87 | SUB_TEXT_SENSOR_TYPE_COUNT, 88 | }; 89 | void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens); 90 | 91 | protected: 92 | void update_sub_text_sensor_(SubTextSensorType type, const std::string &value); 93 | text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr}; 94 | #endif 95 | #ifdef USE_SWITCH 96 | public: 97 | void set_beeper_switch(switch_::Switch *sw); 98 | void set_quiet_mode_switch(switch_::Switch *sw); 99 | 100 | protected: 101 | switch_::Switch *beeper_switch_{nullptr}; 102 | switch_::Switch *quiet_mode_switch_{nullptr}; 103 | #endif 104 | public: 105 | HonClimate(); 106 | HonClimate(const HonClimate &) = delete; 107 | HonClimate &operator=(const HonClimate &) = delete; 108 | ~HonClimate(); 109 | void dump_config() override; 110 | void set_beeper_state(bool state); 111 | bool get_beeper_state() const; 112 | void set_quiet_mode_state(bool state); 113 | bool get_quiet_mode_state() const; 114 | esphome::optional get_vertical_airflow() const; 115 | void set_vertical_airflow(hon_protocol::VerticalSwingMode direction); 116 | esphome::optional get_horizontal_airflow() const; 117 | void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction); 118 | std::string get_cleaning_status_text() const; 119 | CleaningState get_cleaning_status() const; 120 | void start_self_cleaning(); 121 | void start_steri_cleaning(); 122 | void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; 123 | void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; }; 124 | void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; }; 125 | void set_control_method(HonControlMethod method) { this->control_method_ = method; }; 126 | void add_alarm_start_callback(std::function &&callback); 127 | void add_alarm_end_callback(std::function &&callback); 128 | float get_active_alarm_count() const { return this->active_alarm_count_; } 129 | 130 | protected: 131 | void set_handlers() override; 132 | void process_phase(std::chrono::steady_clock::time_point now) override; 133 | haier_protocol::HaierMessage get_control_message() override; 134 | haier_protocol::HaierMessage get_power_message(bool state) override; 135 | void initialization() override; 136 | bool prepare_pending_action() override; 137 | void process_protocol_reset() override; 138 | bool should_get_big_data_(); 139 | 140 | // Answers handlers 141 | haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, 142 | haier_protocol::FrameType message_type, 143 | const uint8_t *data, size_t data_size); 144 | haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, 145 | haier_protocol::FrameType message_type, 146 | const uint8_t *data, size_t data_size); 147 | haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, 148 | haier_protocol::FrameType message_type, const uint8_t *data, 149 | size_t data_size); 150 | haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type, 151 | haier_protocol::FrameType message_type, 152 | const uint8_t *data, size_t data_size); 153 | haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, 154 | haier_protocol::FrameType message_type, 155 | const uint8_t *data, size_t data_size); 156 | haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, 157 | size_t size); 158 | // Helper functions 159 | haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); 160 | void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new); 161 | void fill_control_messages_queue_(); 162 | void clear_control_messages_queue_(); 163 | 164 | struct HardwareInfo { 165 | std::string protocol_version_; 166 | std::string software_version_; 167 | std::string hardware_version_; 168 | std::string device_name_; 169 | bool functions_[5]; 170 | }; 171 | 172 | CleaningState cleaning_status_; 173 | bool got_valid_outdoor_temp_; 174 | esphome::optional pending_vertical_direction_{}; 175 | esphome::optional pending_horizontal_direction_{}; 176 | esphome::optional hvac_hardware_info_{}; 177 | uint8_t active_alarms_[8]; 178 | int extra_control_packet_bytes_{0}; 179 | int extra_sensors_packet_bytes_{4}; 180 | int status_message_header_size_{0}; 181 | size_t real_control_packet_size_{sizeof(hon_protocol::HaierPacketControl)}; 182 | int real_sensors_packet_size_{sizeof(hon_protocol::HaierPacketSensors) + 4}; 183 | HonControlMethod control_method_; 184 | std::queue control_messages_queue_; 185 | CallbackManager alarm_start_callback_{}; 186 | CallbackManager alarm_end_callback_{}; 187 | float active_alarm_count_{NAN}; 188 | std::chrono::steady_clock::time_point last_alarm_request_; 189 | int big_data_sensors_{0}; 190 | esphome::optional current_vertical_swing_{}; 191 | esphome::optional current_horizontal_swing_{}; 192 | HonSettings settings_; 193 | ESPPreferenceObject hon_rtc_; 194 | SwitchState quiet_mode_state_{SwitchState::OFF}; 195 | }; 196 | 197 | class HaierAlarmStartTrigger : public Trigger { 198 | public: 199 | explicit HaierAlarmStartTrigger(HonClimate *parent) { 200 | parent->add_alarm_start_callback( 201 | [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); 202 | } 203 | }; 204 | 205 | class HaierAlarmEndTrigger : public Trigger { 206 | public: 207 | explicit HaierAlarmEndTrigger(HonClimate *parent) { 208 | parent->add_alarm_end_callback( 209 | [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); 210 | } 211 | }; 212 | 213 | } // namespace haier 214 | } // namespace esphome 215 | -------------------------------------------------------------------------------- /docs/examples/max-hon.yaml: -------------------------------------------------------------------------------- 1 | uart: 2 | baud_rate: 9600 3 | tx_pin: 17 4 | rx_pin: 16 5 | id: haier_uart 6 | 7 | api: 8 | services: 9 | - service: turn_on 10 | then: 11 | - climate.haier.power_on: haier_ac 12 | - service: turn_off 13 | then: 14 | - climate.haier.power_off: haier_ac 15 | 16 | climate: 17 | - platform: haier 18 | id: haier_ac 19 | protocol: hon 20 | name: Haier hOn Climate 21 | uart_id: haier_uart 22 | wifi_signal: true 23 | visual: 24 | min_temperature: 16 °C 25 | max_temperature: 30 °C 26 | temperature_step: 27 | target_temperature: 1 28 | current_temperature: 0.5 29 | supported_modes: 30 | - 'OFF' 31 | - HEAT_COOL 32 | - COOL 33 | - HEAT 34 | - DRY 35 | - FAN_ONLY 36 | supported_swing_modes: 37 | - 'OFF' 38 | - VERTICAL 39 | - HORIZONTAL 40 | - BOTH 41 | supported_presets: 42 | - BOOST 43 | - SLEEP 44 | on_alarm_start: 45 | then: 46 | - homeassistant.service: 47 | service: logbook.log 48 | data: 49 | domain: climate 50 | name: Haier hOn Climate 51 | data_template: 52 | message: "Alarm activated ({{ alarm_code }}): {{alarm_message}}" 53 | variables: 54 | alarm_message: !lambda "return message;" 55 | alarm_code: !lambda "return code;" 56 | - homeassistant.service: 57 | service: notify.persistent_notification 58 | data: 59 | title: "Haier hOn Climate: alarm activated" 60 | data_template: 61 | message: "Code: {{ alarm_code }}, message: \"{{ alarm_message }}\"" 62 | variables: 63 | alarm_message: !lambda "return message;" 64 | alarm_code: !lambda "return code;" 65 | on_alarm_end: 66 | then: 67 | - homeassistant.service: 68 | service: logbook.log 69 | data: 70 | domain: climate 71 | name: Haier hOn Climate 72 | data_template: 73 | message: "Alarm deactivated ({{ alarm_code }}): {{alarm_message}}" 74 | variables: 75 | alarm_message: !lambda "return message;" 76 | alarm_code: !lambda "return code;" 77 | 78 | button: 79 | - platform: haier 80 | haier_id: haier_ac 81 | self_cleaning: 82 | name: Haier hOn Climate start self cleaning 83 | steri_cleaning: 84 | name: Haier hOn Climate start 56°C steri-cleaning 85 | 86 | text_sensor: 87 | - platform: haier 88 | haier_id: haier_ac 89 | cleaning_status: 90 | name: Haier hOn Climate cleaning status 91 | protocol_version: 92 | name: Haier hOn Climate protocol version 93 | 94 | switch: 95 | - platform: haier 96 | beeper: 97 | name: Haier hOn Climate beeper 98 | health_mode: 99 | name: Haier hOn Climate health mode 100 | display: 101 | name: Haier hOn Climate display 102 | quiet_mode: 103 | name: Haier hOn Climate quiet mode 104 | 105 | select: 106 | - platform: template 107 | id: haier_ac_vertical_direction 108 | name: Haier hOn Climate airflow vertical 109 | entity_category: config 110 | icon: mdi:arrow-expand-vertical 111 | update_interval: 5s 112 | options: 113 | - Health Up 114 | - Max Up 115 | - Up 116 | - Center 117 | - Down 118 | - Max Down 119 | - Health Down 120 | - Auto 121 | lambda: >- 122 | switch (id(haier_ac).get_vertical_airflow().value_or(esphome::haier::hon_protocol::VerticalSwingMode::CENTER)) 123 | { 124 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP: 125 | return std::string("Health Up"); 126 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP: 127 | return std::string("Max Up"); 128 | case esphome::haier::hon_protocol::VerticalSwingMode::UP: 129 | return std::string("Up"); 130 | default: 131 | case esphome::haier::hon_protocol::VerticalSwingMode::CENTER: 132 | return std::string("Center"); 133 | case esphome::haier::hon_protocol::VerticalSwingMode::DOWN: 134 | return std::string("Down"); 135 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN: 136 | return std::string("Max Down"); 137 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN: 138 | return std::string("Health Down"); 139 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO: 140 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO_SPECIAL: 141 | return std::string("Auto"); 142 | } 143 | set_action: 144 | - climate.haier.set_vertical_airflow: 145 | id: haier_ac 146 | vertical_airflow: !lambda >- 147 | if (x == "Health Up") 148 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP; 149 | else if (x == "Max Up") 150 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP; 151 | else if (x == "Up") 152 | return esphome::haier::hon_protocol::VerticalSwingMode::UP; 153 | else if (x == "Down") 154 | return esphome::haier::hon_protocol::VerticalSwingMode::DOWN; 155 | else if (x == "Max Down") 156 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN; 157 | else if (x == "Health Down") 158 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN; 159 | else if (x == "Auto") 160 | return esphome::haier::hon_protocol::VerticalSwingMode::AUTO; 161 | else 162 | return esphome::haier::hon_protocol::VerticalSwingMode::CENTER; 163 | - platform: template 164 | id: haier_ac_horizontal_direction 165 | name: Haier hOn Climate airflow horizontal 166 | entity_category: config 167 | icon: mdi:arrow-expand-horizontal 168 | update_interval: 5s 169 | options: 170 | - Max Left 171 | - Left 172 | - Center 173 | - Right 174 | - Max Right 175 | - Auto 176 | lambda: >- 177 | switch (id(haier_ac).get_horizontal_airflow().value_or(esphome::haier::hon_protocol::HorizontalSwingMode::CENTER)) 178 | { 179 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT: 180 | return std::string("Max Left"); 181 | case esphome::haier::hon_protocol::HorizontalSwingMode::LEFT: 182 | return std::string("Left"); 183 | default: 184 | case esphome::haier::hon_protocol::HorizontalSwingMode::CENTER: 185 | return std::string("Center"); 186 | case esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT: 187 | return std::string("Right"); 188 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT: 189 | return std::string("Max Right"); 190 | case esphome::haier::hon_protocol::HorizontalSwingMode::AUTO: 191 | return std::string("Auto"); 192 | } 193 | set_action: 194 | - climate.haier.set_horizontal_airflow: 195 | id: haier_ac 196 | horizontal_airflow: !lambda >- 197 | if (x == "Max Left") 198 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT; 199 | else if (x == "Left") 200 | return esphome::haier::hon_protocol::HorizontalSwingMode::LEFT; 201 | else if (x == "Right") 202 | return esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT; 203 | else if (x == "Max Right") 204 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT; 205 | else if (x == "Auto") 206 | return esphome::haier::hon_protocol::HorizontalSwingMode::AUTO; 207 | else 208 | return esphome::haier::hon_protocol::HorizontalSwingMode::CENTER; 209 | 210 | sensor: 211 | - platform: haier 212 | haier_id: haier_ac 213 | compressor_current: 214 | name: Haier hOn Climate Compressor Current 215 | compressor_frequency: 216 | name: Haier hOn Climate Compressor Frequency 217 | expansion_valve_open_degree: 218 | name: Haier hOn Climate Expansion Valve Open Degree 219 | humidity: 220 | name: Haier hOn Climate Indoor Humidity 221 | indoor_coil_temperature: 222 | name: Haier hOn Climate Indoor Coil Temperature 223 | outdoor_coil_temperature: 224 | name: Haier hOn Climate Outdoor Coil Temperature 225 | outdoor_defrost_temperature: 226 | name: Haier hOn Climate Outdoor Defrost Temperature 227 | outdoor_in_air_temperature: 228 | name: Haier hOn Climate Outdoor In Air Temperature 229 | outdoor_out_air_temperature: 230 | name: Haier hOn Climate Outdoor Out Air Temperature 231 | outdoor_temperature: 232 | name: Haier hOn Climate outdoor temperature 233 | power: 234 | name: Haier hOn Climate Power 235 | 236 | binary_sensor: 237 | - platform: haier 238 | haier_id: haier_ac 239 | compressor_status: 240 | name: Haier hOn Climate Compressor Status 241 | defrost_status: 242 | name: Haier hOn Climate Defrost Status 243 | four_way_valve_status: 244 | name: Haier hOn Climate Four-way Valve Status 245 | indoor_electric_heating_status: 246 | name: Haier hOn Climate Indoor Electric Heating Status 247 | indoor_fan_status: 248 | name: Haier hOn Climate Indoor Fan Status 249 | outdoor_fan_status: 250 | name: Haier hOn Climate Outdoor Fan Status 251 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently Asked Questions 2 | ========================== 3 | 4 | .. contents:: Table of Contents 5 | 6 | Compatibility and Support 7 | ------------------------- 8 | 9 | **Q:** Is there a list of supported models? 10 | ******************************************* 11 | 12 | **A:** No, Haier has a wide range of models, and different models are available in different regions. Sometimes the same model can have different names depending on the country where it is sold. The only way to know if your model is supported is to try it out. 13 | 14 | **Q:** How can I be sure that your component will support the selected model when buying a new AC? 15 | ************************************************************************************************** 16 | 17 | **A:** Unfortunately, there is no guarantee until you try it. The best way is to check the user manual. Usually, if the AC supports ESP modules, there will be information about which application the native module works with. So far, all models that work with smartAir2, hOn, and EVO applications also work with this component, although sometimes additional tuning may be required. 18 | 19 | **Q:** How should the ESP module be connected to the AC? 20 | ******************************************************** 21 | 22 | **A:** On the AC side there is a connector usually marked as CN34 or CN35 it is a 4-pin connector with pins marked as RXD, TXD, GND, +5V. It is 4 pin 5264 Molex connector. On the ESP side use a USB Type-A connector (it is not real USB just UART with USB connector) or JST SM04B-GHS-TB connector. 23 | 24 | **Q:** Can I use ESP8266 with this component for my Haier AC? 25 | ************************************************************* 26 | 27 | **A:** ESP8266 is powerful enough to handle communication with Haier AC. But it has limited resources and in case you also want some advanced features of ESPHome such as web_server, mqtt, etc. it may not be enough. It is recommended to use ESP32. 28 | 29 | **Q:** Can I use an ESP board with a USB Type-A connector for my Haier AC that has a USB Type-A port? 30 | ***************************************************************************************************** 31 | 32 | **A:** The USB Type-A port on your Haier AC operates using UART protocol, not standard USB communication. Most ESP32 modules use a separate chip (like CP2102 or CH340) for USB communication, which only supports USB protocol and cannot interface with UART protocol directly. Therefore, these cannot communicate with your AC's UART-over-USB Type-A port. 33 | 34 | However, ESP modules equipped with the ESP32-S3 chip have native USB support and can potentially communicate using UART protocol if the USB Type-A connector is directly connected to the ESP pins without an intermediary chip. This setup allows for creating an ESPHome configuration that utilizes the same pins for UART communication. 35 | 36 | Here you can find `list of confirmed board that supports that have native USB support and can communicate using UART protocol <./usb_2_uart_boards.rst>`_ with sample configuration for each case. 37 | 38 | Troubleshooting 39 | --------------- 40 | 41 | **Q:** I connected the ESP module but all I can see in the logs is "Answer timeout for command 61, phase SENDING_INIT_1" and nothing is working. What should I do? 42 | ****************************************************************************************************************************************************************** 43 | 44 | **A:** These warnings mean that the ESP module is not receiving any response from the AC. It can be caused by several reasons: 45 | 46 | - **Configuration Issues:** There might be issues with your ESPHome configuration. 47 | - **Hardware Problems:** The problem could lie with the ESP module or other hardware components. 48 | - **Wiring Issues:** Incorrect wiring or problems with the pins could be causing communication failures. Most common mistake is to swap RX and TX pins. 49 | - **Protocol Mismatch:** The AC might use a different protocol for communication or may not support serial communication at all. 50 | 51 | **Troubleshooting Steps:** 52 | 53 | 1. Check the Wiring: Ensure all connections are secure. Try using different pins for communication. 54 | 2. Test with Different Hardware: If possible, test with a different ESP module or AC unit to isolate the issue. 55 | 3. Use a Simulator: If you're familiar with C++ and cmake, consider using a simulator to diagnose the issue. I have simulator applications for both the hOn and smartAir2 protocols: 56 | 57 | - hOn Simulator: `hOn simulator application `_ 58 | - smartAir2 Simulator: `smartAir2 simulator application `_ 59 | 60 | These simulators are compatible with Windows and Linux. 61 | 62 | **Using the Simulator:** 63 | 64 | 1. Connect your ESP32 to a PC using a TTL to USB converter. 65 | 2. Start the appropriate simulator. You should see requests from the ESP module in the simulator's console. 66 | 67 | - No Requests Seen: If the simulator does not show any requests from the ESP, there may be an issue with the TX wire. 68 | - No Responses in ESPHome Logs: If you see requests in the simulator but no responses in the ESPHome logs, the problem could be with the RX wire. 69 | - Requests and Responses Visible: If both requests and responses are visible, your ESP is working correctly. The issue may be on the AC side. 70 | 71 | **Q:** I am constantly seeing the warning "Component haier took a long time for an operation" in the logs, is this normal? 72 | ************************************************************************************************************************** 73 | 74 | **A:** Yes, this message has always been there, but in the latest versions, it was changed to a warning. This message is shown when the component is too busy processing something. The biggest delays are usually related to logging and operations with the web server. If you want to reduce the chance of seeing this message, you can decrease the log level to "warning". The warning level is sufficient for a fully working system. This message is usually not a problem but can indicate that some other component (like Wi-Fi) that should work in real-time is suffering from delays. For more information, refer to: https://github.com/esphome/issues/issues/4717 75 | 76 | **Q:** I am seeing the warning "Answer handler error, msg=01, answ=03, err=7". What should I do? 77 | ************************************************************************************************ 78 | 79 | **A:** This warning means that the AC denied the control command. It can happen in two cases: either the AC is using a different type of control or the structure of the status packet is different. You can try using the `control_method: SET_SINGLE_PARAMETER`. If that doesn't help, you can try to figure out the size of different parts of the status packet using this method: `Haier protocol overview <./protocol_overview.rst>`_. If nothing helps, you can create an issue on GitHub. 80 | 81 | **Q:** What does the "Unsupported message received: type xx" message in the logs indicate? 82 | ******************************************************************************************* 83 | 84 | **A:** This message may appear for several reasons: 85 | 86 | 1. **Slow AC Response:** Your AC unit is responding slowly to requests, consider increasing the `response_timeout` parameter from its default value of 200 ms to 400 ms. 87 | 2. **Overloaded ESP:** Your ESP module is too busy to process messages in time, increasing `response_timeout` won't resolve the issue. Instead, try disabling some components, lowering the log level, or upgrading to a more powerful ESP board. 88 | 3. **Unrecognized Messages:** Your AC might be sending new types of messages that the component does not recognize. If adjusting the timeout and optimizing ESP performance doesn't help, capture the logs and create an issue on GitHub for further assistance. 89 | 90 | **Q:** My ESP is communicating with the AC, but I can't control it. Or I can control it, but my sensors show the wrong information. 91 | *********************************************************************************************************************************** 92 | 93 | **A:** Most likely, you have one of two problems: either the wrong control method or the wrong status packet structure. You can try using the `control_method: SET_SINGLE_PARAMETER`. If that doesn't help, you can try to figure out the size of different parts of the status packet using this method: `Haier protocol overview <./protocol_overview.rst>`_. 94 | 95 | Feature Requests 96 | ---------------- 97 | 98 | **Q:** My AC has a cool feature that is not supported by your component. Can you add it? 99 | **************************************************************************************** 100 | 101 | **A:** First, you need to figure out if the feature is supported by the serial protocol. Some functionality is supported only by the IR remote. The easiest way to check is by using the IR remote: 102 | 103 | - Start capturing logs from your ESP modules. 104 | - Wait 10 - 15 seconds. 105 | - Enable the feature using the remote. 106 | - Wait 10 - 15 seconds. 107 | - Disable the feature using the remote. 108 | - Wait 10 - 15 seconds. 109 | - Stop capturing logs. 110 | - Check the logs for changes in the status packet. 111 | 112 | If all messages that look like this "Frame found: type 02, data: 6D 01 ..." are the same, the feature you want to add is not supported by the serial protocol. If you see some changes in the status packet, you can create a feature request on GitHub with the logs you collected. 113 | 114 | Another option is to try to record a log of communication between the original Haier ESP and the Haier appliance. You can use the `Sniffing serial communication <./sniffing_serial_communication.rst>`_ guide to do that. 115 | -------------------------------------------------------------------------------- /components/haier/hon_packet.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace esphome { 6 | namespace haier { 7 | namespace hon_protocol { 8 | 9 | enum class VerticalSwingMode : uint8_t { 10 | HEALTH_UP = 0x01, 11 | MAX_UP = 0x02, 12 | HEALTH_DOWN = 0x03, 13 | UP = 0x04, 14 | CENTER = 0x06, 15 | DOWN = 0x08, 16 | MAX_DOWN = 0x0A, 17 | AUTO = 0x0C, 18 | // Auto for special modes 19 | AUTO_SPECIAL = 0x0E 20 | }; 21 | 22 | enum class HorizontalSwingMode : uint8_t { 23 | CENTER = 0x00, 24 | MAX_LEFT = 0x03, 25 | LEFT = 0x04, 26 | RIGHT = 0x05, 27 | MAX_RIGHT = 0x06, 28 | AUTO = 0x07 29 | }; 30 | 31 | enum class ConditioningMode : uint8_t { 32 | AUTO = 0x00, 33 | COOL = 0x01, 34 | DRY = 0x02, 35 | HEALTHY_DRY = 0x03, 36 | HEAT = 0x04, 37 | ENERGY_SAVING = 0x05, 38 | FAN = 0x06 39 | }; 40 | 41 | enum class DataParameters : uint8_t { 42 | AC_POWER = 0x01, 43 | SET_POINT = 0x02, 44 | VERTICAL_SWING_MODE = 0x03, 45 | AC_MODE = 0x04, 46 | FAN_MODE = 0x05, 47 | USE_FAHRENHEIT = 0x07, 48 | DISPLAY_STATUS = 0x09, 49 | TEN_DEGREE = 0x0A, 50 | HEALTH_MODE = 0x0B, 51 | HORIZONTAL_SWING_MODE = 0x0C, 52 | SELF_CLEANING = 0x0D, 53 | BEEPER_STATUS = 0x16, 54 | LOCK_REMOTE = 0x17, 55 | QUIET_MODE = 0x19, 56 | FAST_MODE = 0x1A, 57 | SLEEP_MODE = 0x1B, 58 | }; 59 | 60 | enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; 61 | 62 | enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; 63 | 64 | struct HaierPacketControl { 65 | // Control bytes starts here 66 | // 1 67 | uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C) 68 | // 2 69 | uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode 70 | uint8_t : 0; 71 | // 3 72 | uint8_t fan_mode : 3; // See enum FanMode 73 | uint8_t special_mode : 2; // See enum SpecialMode 74 | uint8_t ac_mode : 3; // See enum ConditioningMode 75 | // 4 76 | uint8_t : 8; 77 | // 5 78 | uint8_t ten_degree : 1; // 10 degree status 79 | uint8_t display_status : 1; // If 0 disables AC's display 80 | uint8_t half_degree : 1; // Use half degree 81 | uint8_t intelligence_status : 1; // Intelligence status 82 | uint8_t pmv_status : 1; // Comfort/PMV status 83 | uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius 84 | uint8_t : 1; 85 | uint8_t steri_clean : 1; 86 | // 6 87 | uint8_t ac_power : 1; // Is ac on or off 88 | uint8_t health_mode : 1; // Health mode (negative ions) on or off 89 | uint8_t electric_heating_status : 1; // Electric heating status 90 | uint8_t fast_mode : 1; // Fast mode 91 | uint8_t quiet_mode : 1; // Quiet mode 92 | uint8_t sleep_mode : 1; // Sleep mode 93 | uint8_t lock_remote : 1; // Disable remote 94 | uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command) 95 | // 7 96 | uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%) 97 | // 8 98 | uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode 99 | uint8_t : 3; 100 | uint8_t human_sensing_status : 2; // Human sensing status 101 | // 9 102 | uint8_t change_filter : 1; // Filter need replacement 103 | uint8_t : 0; 104 | // 10 105 | uint8_t fresh_air_status : 1; // Fresh air status 106 | uint8_t humidification_status : 1; // Humidification status 107 | uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status 108 | uint8_t ch2o_cleaning_status : 1; // CH2O cleaning status 109 | uint8_t self_cleaning_status : 1; // Self cleaning status 110 | uint8_t light_status : 1; // Light status 111 | uint8_t energy_saving_status : 1; // Energy saving status 112 | uint8_t cleaning_time_status : 1; // Cleaning time (0 - accumulation, 1 - clear) 113 | }; 114 | 115 | struct HaierPacketSensors { 116 | // 11 117 | uint8_t room_temperature; // 0.5°C step 118 | // 12 119 | uint8_t room_humidity; // 0%-100% with 1% step 120 | // 13 121 | uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C) 122 | // 14 123 | uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) 124 | uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) 125 | uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) 126 | uint8_t : 1; 127 | uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only) 128 | // 15 129 | uint8_t error_status; // See enum ErrorStatus 130 | // 16 131 | uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) 132 | uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) 133 | uint8_t : 3; 134 | uint8_t err_confirmation : 1; // If 1 clear error status 135 | // 17 136 | uint16_t total_cleaning_time; // Cleaning cumulative time (1h step) 137 | // 19 138 | uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) 139 | // 21 140 | uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) 141 | // 23 142 | uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step) 143 | // 25 144 | uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step) 145 | // 27 146 | uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step) 147 | }; 148 | 149 | struct HaierPacketBigData { 150 | // 29 151 | uint8_t power[2]; // AC power consumption (0W - 65535W, 1W step) 152 | // 31 153 | uint8_t indoor_coil_temperature; // 0.5°C step, -20°C offset (0=-20°C) 154 | // 32 155 | uint8_t outdoor_out_air_temperature; // 1°C step, -64°C offset (0=-64°C) 156 | // 33 157 | uint8_t outdoor_coil_temperature; // 1°C step, -64°C offset (0=-64°C) 158 | // 34 159 | uint8_t outdoor_in_air_temperature; // 1°C step, -64°C offset (0=-64°C) 160 | // 35 161 | uint8_t outdoor_defrost_temperature; // 1°C step, -64°C offset (0=-64°C) 162 | // 36 163 | uint8_t compressor_frequency; // 1Hz step, 0Hz - 127Hz 164 | // 37 165 | uint8_t compressor_current[2]; // 0.1A step, 0.0A - 51.1A (0x0000 - 0x01FF) 166 | // 39 167 | uint8_t outdoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available 168 | uint8_t defrost_status : 2; // 0 - off, 1 - on, 2 - information not available 169 | uint8_t : 0; 170 | // 40 171 | uint8_t compressor_status : 2; // 0 - off, 1 - on, 2 - information not available 172 | uint8_t indoor_fan_status : 2; // 0 - off, 1 - on, 2 - information not available 173 | uint8_t four_way_valve_status : 2; // 0 - off, 1 - on, 2 - information not available 174 | uint8_t indoor_electric_heating_status : 2; // 0 - off, 1 - on, 2 - information not available 175 | // 41 176 | uint8_t expansion_valve_open_degree[2]; // 0 - 4095 177 | }; 178 | 179 | struct DeviceVersionAnswer { 180 | char protocol_version[8]; 181 | char software_version[8]; 182 | uint8_t encryption[3]; 183 | char hardware_version[8]; 184 | uint8_t : 8; 185 | char device_name[8]; 186 | uint8_t functions[2]; 187 | }; 188 | 189 | enum class SubcommandsControl : uint16_t { 190 | GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) 191 | GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None) 192 | GET_BIG_DATA = 0x4DFE, // Request big data information from device (packet content: None) 193 | SET_PARAMETERS = 0x5C01, // Set parameters of the device and device return parameters (packet content: parameter ID1 194 | // + parameter data1 + parameter ID2 + parameter data 2 + ...) 195 | SET_SINGLE_PARAMETER = 0x5D00, // Set single parameter (0x5DXX second byte define parameter ID) and return all user 196 | // data (packet content: ???) 197 | SET_GROUP_PARAMETERS = 0x6001, // Set group parameters to device (0x60XX second byte define parameter is group ID, 198 | // the only group mentioned in document is 1) and return all user data (packet 199 | // content: all values like in status packet) 200 | }; 201 | 202 | const std::string HON_ALARM_MESSAGES[] = { 203 | "Outdoor module failure", 204 | "Outdoor defrost sensor failure", 205 | "Outdoor compressor exhaust sensor failure", 206 | "Outdoor EEPROM abnormality", 207 | "Indoor coil sensor failure", 208 | "Indoor-outdoor communication failure", 209 | "Power supply overvoltage protection", 210 | "Communication failure between panel and indoor unit", 211 | "Outdoor compressor overheat protection", 212 | "Outdoor environmental sensor abnormality", 213 | "Full water protection", 214 | "Indoor EEPROM failure", 215 | "Outdoor out air sensor failure", 216 | "CBD and module communication failure", 217 | "Indoor DC fan failure", 218 | "Outdoor DC fan failure", 219 | "Door switch failure", 220 | "Dust filter needs cleaning reminder", 221 | "Water shortage protection", 222 | "Humidity sensor failure", 223 | "Indoor temperature sensor failure", 224 | "Manipulator limit failure", 225 | "Indoor PM2.5 sensor failure", 226 | "Outdoor PM2.5 sensor failure", 227 | "Indoor heating overload/high load alarm", 228 | "Outdoor AC current protection", 229 | "Outdoor compressor operation abnormality", 230 | "Outdoor DC current protection", 231 | "Outdoor no-load failure", 232 | "CT current abnormality", 233 | "Indoor cooling freeze protection", 234 | "High and low pressure protection", 235 | "Compressor out air temperature is too high", 236 | "Outdoor evaporator sensor failure", 237 | "Outdoor cooling overload", 238 | "Water pump drainage failure", 239 | "Three-phase power supply failure", 240 | "Four-way valve failure", 241 | "External alarm/scraper flow switch failure", 242 | "Temperature cutoff protection alarm", 243 | "Different mode operation failure", 244 | "Electronic expansion valve failure", 245 | "Dual heat source sensor Tw failure", 246 | "Communication failure with the wired controller", 247 | "Indoor unit address duplication failure", 248 | "50Hz zero crossing failure", 249 | "Outdoor unit failure", 250 | "Formaldehyde sensor failure", 251 | "VOC sensor failure", 252 | "CO2 sensor failure", 253 | "Firewall failure", 254 | }; 255 | 256 | constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]); 257 | 258 | } // namespace hon_protocol 259 | } // namespace haier 260 | } // namespace esphome 261 | -------------------------------------------------------------------------------- /docs/hon_example.rst: -------------------------------------------------------------------------------- 1 | .. This file is automatically generated by ./docs/script/process_examples.py Python script. 2 | Please, don't change. In case you need to make corrections or changes change 3 | source documentation in ./doc folder or script. 4 | 5 | Example of climate configuration for hOn protocol 6 | ================================================= 7 | 8 | Configuration of your climate will depend on capabilities specific model. 9 | 10 | Minimal configuration will look like this: 11 | 12 | .. code-block:: yaml 13 | 14 | uart: 15 | baud_rate: 9600 16 | tx_pin: 17 17 | rx_pin: 16 18 | 19 | climate: 20 | - platform: haier 21 | protocol: hon 22 | name: Haier hOn Climate 23 | 24 | 25 | Maximum configuration witch will use all possible options will look like this: 26 | 27 | .. code-block:: yaml 28 | 29 | uart: 30 | baud_rate: 9600 31 | tx_pin: 17 32 | rx_pin: 16 33 | id: haier_uart 34 | 35 | api: 36 | services: 37 | - service: turn_on 38 | then: 39 | - climate.haier.power_on: haier_ac 40 | - service: turn_off 41 | then: 42 | - climate.haier.power_off: haier_ac 43 | 44 | climate: 45 | - platform: haier 46 | id: haier_ac 47 | protocol: hon 48 | name: Haier hOn Climate 49 | uart_id: haier_uart 50 | wifi_signal: true 51 | visual: 52 | min_temperature: 16 °C 53 | max_temperature: 30 °C 54 | temperature_step: 55 | target_temperature: 1 56 | current_temperature: 0.5 57 | supported_modes: 58 | - 'OFF' 59 | - HEAT_COOL 60 | - COOL 61 | - HEAT 62 | - DRY 63 | - FAN_ONLY 64 | supported_swing_modes: 65 | - 'OFF' 66 | - VERTICAL 67 | - HORIZONTAL 68 | - BOTH 69 | supported_presets: 70 | - BOOST 71 | - SLEEP 72 | on_alarm_start: 73 | then: 74 | - homeassistant.service: 75 | service: logbook.log 76 | data: 77 | domain: climate 78 | name: Haier hOn Climate 79 | data_template: 80 | message: "Alarm activated ({{ alarm_code }}): {{alarm_message}}" 81 | variables: 82 | alarm_message: !lambda "return message;" 83 | alarm_code: !lambda "return code;" 84 | - homeassistant.service: 85 | service: notify.persistent_notification 86 | data: 87 | title: "Haier hOn Climate: alarm activated" 88 | data_template: 89 | message: "Code: {{ alarm_code }}, message: \"{{ alarm_message }}\"" 90 | variables: 91 | alarm_message: !lambda "return message;" 92 | alarm_code: !lambda "return code;" 93 | on_alarm_end: 94 | then: 95 | - homeassistant.service: 96 | service: logbook.log 97 | data: 98 | domain: climate 99 | name: Haier hOn Climate 100 | data_template: 101 | message: "Alarm deactivated ({{ alarm_code }}): {{alarm_message}}" 102 | variables: 103 | alarm_message: !lambda "return message;" 104 | alarm_code: !lambda "return code;" 105 | 106 | button: 107 | - platform: haier 108 | haier_id: haier_ac 109 | self_cleaning: 110 | name: Haier hOn Climate start self cleaning 111 | steri_cleaning: 112 | name: Haier hOn Climate start 56°C steri-cleaning 113 | 114 | text_sensor: 115 | - platform: haier 116 | haier_id: haier_ac 117 | cleaning_status: 118 | name: Haier hOn Climate cleaning status 119 | protocol_version: 120 | name: Haier hOn Climate protocol version 121 | 122 | switch: 123 | - platform: haier 124 | beeper: 125 | name: Haier hOn Climate beeper 126 | health_mode: 127 | name: Haier hOn Climate health mode 128 | display: 129 | name: Haier hOn Climate display 130 | quiet_mode: 131 | name: Haier hOn Climate quiet mode 132 | 133 | select: 134 | - platform: template 135 | id: haier_ac_vertical_direction 136 | name: Haier hOn Climate airflow vertical 137 | entity_category: config 138 | icon: mdi:arrow-expand-vertical 139 | update_interval: 5s 140 | options: 141 | - Health Up 142 | - Max Up 143 | - Up 144 | - Center 145 | - Down 146 | - Max Down 147 | - Health Down 148 | - Auto 149 | lambda: >- 150 | switch (id(haier_ac).get_vertical_airflow().value_or(esphome::haier::hon_protocol::VerticalSwingMode::CENTER)) 151 | { 152 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP: 153 | return std::string("Health Up"); 154 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP: 155 | return std::string("Max Up"); 156 | case esphome::haier::hon_protocol::VerticalSwingMode::UP: 157 | return std::string("Up"); 158 | default: 159 | case esphome::haier::hon_protocol::VerticalSwingMode::CENTER: 160 | return std::string("Center"); 161 | case esphome::haier::hon_protocol::VerticalSwingMode::DOWN: 162 | return std::string("Down"); 163 | case esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN: 164 | return std::string("Max Down"); 165 | case esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN: 166 | return std::string("Health Down"); 167 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO: 168 | case esphome::haier::hon_protocol::VerticalSwingMode::AUTO_SPECIAL: 169 | return std::string("Auto"); 170 | } 171 | set_action: 172 | - climate.haier.set_vertical_airflow: 173 | id: haier_ac 174 | vertical_airflow: !lambda >- 175 | if (x == "Health Up") 176 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_UP; 177 | else if (x == "Max Up") 178 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_UP; 179 | else if (x == "Up") 180 | return esphome::haier::hon_protocol::VerticalSwingMode::UP; 181 | else if (x == "Down") 182 | return esphome::haier::hon_protocol::VerticalSwingMode::DOWN; 183 | else if (x == "Max Down") 184 | return esphome::haier::hon_protocol::VerticalSwingMode::MAX_DOWN; 185 | else if (x == "Health Down") 186 | return esphome::haier::hon_protocol::VerticalSwingMode::HEALTH_DOWN; 187 | else if (x == "Auto") 188 | return esphome::haier::hon_protocol::VerticalSwingMode::AUTO; 189 | else 190 | return esphome::haier::hon_protocol::VerticalSwingMode::CENTER; 191 | - platform: template 192 | id: haier_ac_horizontal_direction 193 | name: Haier hOn Climate airflow horizontal 194 | entity_category: config 195 | icon: mdi:arrow-expand-horizontal 196 | update_interval: 5s 197 | options: 198 | - Max Left 199 | - Left 200 | - Center 201 | - Right 202 | - Max Right 203 | - Auto 204 | lambda: >- 205 | switch (id(haier_ac).get_horizontal_airflow().value_or(esphome::haier::hon_protocol::HorizontalSwingMode::CENTER)) 206 | { 207 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT: 208 | return std::string("Max Left"); 209 | case esphome::haier::hon_protocol::HorizontalSwingMode::LEFT: 210 | return std::string("Left"); 211 | default: 212 | case esphome::haier::hon_protocol::HorizontalSwingMode::CENTER: 213 | return std::string("Center"); 214 | case esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT: 215 | return std::string("Right"); 216 | case esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT: 217 | return std::string("Max Right"); 218 | case esphome::haier::hon_protocol::HorizontalSwingMode::AUTO: 219 | return std::string("Auto"); 220 | } 221 | set_action: 222 | - climate.haier.set_horizontal_airflow: 223 | id: haier_ac 224 | horizontal_airflow: !lambda >- 225 | if (x == "Max Left") 226 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_LEFT; 227 | else if (x == "Left") 228 | return esphome::haier::hon_protocol::HorizontalSwingMode::LEFT; 229 | else if (x == "Right") 230 | return esphome::haier::hon_protocol::HorizontalSwingMode::RIGHT; 231 | else if (x == "Max Right") 232 | return esphome::haier::hon_protocol::HorizontalSwingMode::MAX_RIGHT; 233 | else if (x == "Auto") 234 | return esphome::haier::hon_protocol::HorizontalSwingMode::AUTO; 235 | else 236 | return esphome::haier::hon_protocol::HorizontalSwingMode::CENTER; 237 | 238 | sensor: 239 | - platform: haier 240 | haier_id: haier_ac 241 | compressor_current: 242 | name: Haier hOn Climate Compressor Current 243 | compressor_frequency: 244 | name: Haier hOn Climate Compressor Frequency 245 | expansion_valve_open_degree: 246 | name: Haier hOn Climate Expansion Valve Open Degree 247 | humidity: 248 | name: Haier hOn Climate Indoor Humidity 249 | indoor_coil_temperature: 250 | name: Haier hOn Climate Indoor Coil Temperature 251 | outdoor_coil_temperature: 252 | name: Haier hOn Climate Outdoor Coil Temperature 253 | outdoor_defrost_temperature: 254 | name: Haier hOn Climate Outdoor Defrost Temperature 255 | outdoor_in_air_temperature: 256 | name: Haier hOn Climate Outdoor In Air Temperature 257 | outdoor_out_air_temperature: 258 | name: Haier hOn Climate Outdoor Out Air Temperature 259 | outdoor_temperature: 260 | name: Haier hOn Climate outdoor temperature 261 | power: 262 | name: Haier hOn Climate Power 263 | 264 | binary_sensor: 265 | - platform: haier 266 | haier_id: haier_ac 267 | compressor_status: 268 | name: Haier hOn Climate Compressor Status 269 | defrost_status: 270 | name: Haier hOn Climate Defrost Status 271 | four_way_valve_status: 272 | name: Haier hOn Climate Four-way Valve Status 273 | indoor_electric_heating_status: 274 | name: Haier hOn Climate Indoor Electric Heating Status 275 | indoor_fan_status: 276 | name: Haier hOn Climate Indoor Fan Status 277 | outdoor_fan_status: 278 | name: Haier hOn Climate Outdoor Fan Status 279 | 280 | -------------------------------------------------------------------------------- /docs/esphome-docs/climate/haier.rst: -------------------------------------------------------------------------------- 1 | Haier Climate 2 | ============= 3 | 4 | .. seo:: 5 | :description: Instructions for setting up Haier climate devices. 6 | :image: air-conditioner.svg 7 | 8 | This is an implementation of the ESPHome component to control HVAC on the base of the SmartAir2 and hOn Haier protocols (AC that is controlled by the hOn or SmartAir2 application). 9 | 10 | There are two versions of the Haier protocol: the older version uses an application called SmartAir2 while the newer version uses an application called hOn. Both protocols are compatible on the transport level but utilize different commands to control appliances. 11 | 12 | Older Haier models controlled by the SmartAir2 application are using the KZW-W002 module. This module can't be reused, and you need to replace it with an ESP or RPI Pico W module. The USB connector on the board doesn't support the USB protocol. It is a UART port that just uses a USB connector. To connect the ESP board to your AC you can cut a USB type A cable and connect wires to the climate connector. 13 | 14 | .. list-table:: Haier UART pinout 15 | :header-rows: 1 16 | 17 | * - Board 18 | - USB 19 | - Wire color 20 | - ESP8266 21 | * - 5V 22 | - VCC 23 | - red 24 | - 5V 25 | * - GND 26 | - GND 27 | - black 28 | - GND 29 | * - TX 30 | - DATA+ 31 | - green 32 | - RX 33 | * - RX 34 | - DATA- 35 | - white 36 | - TX 37 | 38 | .. figure:: images/usb_pinout.png 39 | :align: center 40 | :width: 70.0% 41 | 42 | KZW-W002 module pinout 43 | 44 | Newer Haier models using a module called ESP32-for-Haier. It is an ESP32 single-core board with an ESP32-S0WD chip. In some cases, you can reuse this module and flash it with ESPHome, but some new modules don't support this. They look the same but have encryption enabled. 45 | 46 | **Warning!** The new generation of ESP32-Haier devices has encryption enabled, so they can only be flashed with firmware that is signed with a private key. There is no way to make them work with ESPHome, so if you try to do it, the board will get into a boot loop with the error ``rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)`` The only way to recover this board is to flash it with the original image. So before starting your experiments make a backup image. 47 | 48 | .. figure:: images/haier_pinout.jpg 49 | :align: center 50 | :width: 70.0% 51 | 52 | ESP32-for-Haier UART0 pinout 53 | 54 | Also, you can use any other ESP32, ESP8266, or an RPI pico W board. In this case, you will need to cut the original wire or make a connector yourself (the board has a JST SM04B-GHS-TB connector) 55 | 56 | This component requires a :ref:`UART bus ` to be setup. 57 | 58 | .. code-block:: yaml 59 | 60 | # Example configuration entry 61 | climate: 62 | - platform: haier 63 | id: haier_ac 64 | protocol: hon 65 | name: Haier AC 66 | uart_id: ac_port 67 | wifi_signal: true 68 | display: true 69 | visual: 70 | min_temperature: 16 °C 71 | max_temperature: 30 °C 72 | temperature_step: 1 °C 73 | supported_modes: 74 | - 'OFF' 75 | - HEAT_COOL 76 | - COOL 77 | - HEAT 78 | - DRY 79 | - FAN_ONLY 80 | supported_swing_modes: 81 | - 'OFF' 82 | - VERTICAL 83 | - HORIZONTAL 84 | - BOTH 85 | supported_presets: 86 | - AWAY 87 | - BOOST 88 | - SLEEP 89 | on_alarm_start: 90 | then: 91 | - logger.log: 92 | level: WARN 93 | format: "Alarm activated. Code: %d. Message: \"%s\"" 94 | args: [ code, message] 95 | on_alarm_end: 96 | then: 97 | - logger.log: 98 | level: INFO 99 | format: "Alarm deactivated. Code: %d. Message: \"%s\"" 100 | args: [ code, message] 101 | on_status_message: 102 | then: 103 | - logger.log: 104 | level: INFO 105 | format: "New status message received, size=%d, subcmd=%02X%02X" 106 | args: [ 'data_size', 'data[0]', 'data[1]' ] 107 | 108 | 109 | Configuration variables: 110 | ------------------------ 111 | 112 | - **uart_id** (*Optional*, :ref:`config-id`): ID of the UART port to communicate with AC. 113 | - **protocol** (*Optional*, string): Defines communication protocol with AC. Possible values: ``hon`` or ``smartair2``. The default value is ``smartair2``. 114 | - **wifi_signal** (*Optional*, boolean): If ``true`` - send wifi signal level to AC. 115 | - **answer_timeout** (*Optional*, :ref:`config-time`): Responce timeout. The default value is ``200ms``. 116 | - **alternative_swing_control** (*Optional*, boolean): (supported by smartAir2 only) If ``true`` - use alternative values to control swing mode. Use only if the original control method is not working for your AC. 117 | - **status_message_header_size** (*Optional*, int): (supported only by hOn) Define the header size of the status message. Can be used to handle some protocol variations. Use only if you are sure what you are doing. The default value: ``0``. 118 | - **control_packet_size** (*Optional*, int): (supported only by hOn) Define the size of the control packet. Can help with some newer models of ACs that use bigger packets. The default value: ``10``. 119 | - **sensors_packet_size** (*Optional*, int): (supported only by hOn) Define the size of the sensor packet of the status message. Can help with some models of ACs that have bigger sensor packet. The default value: ``22``, minimum value: ``18``. 120 | - **control_method** (*Optional*, list): (supported only by hOn) Defines control method (should be supported by AC). Supported values: ``MONITOR_ONLY`` - no control, just monitor status, ``SET_GROUP_PARAMETERS`` - set all AC parameters with one command (default method), ``SET_SINGLE_PARAMETER`` - set each parameter individually (this method is supported by some new ceiling ACs like AD71S2SM3FA) 121 | - **display** (*Optional*, boolean): Can be used to set the AC display off. 122 | - **beeper** (*Optional*, boolean): Can be used to disable beeping on commands from AC. Supported only by hOn protocol. 123 | - **supported_modes** (*Optional*, list): Can be used to disable some of AC modes. Possible values: ``'OFF'``, ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. 124 | - **supported_swing_modes** (*Optional*, list): Can be used to disable some swing modes if your AC does not support it. Possible values: ``'OFF'``, ``VERTICAL``, ``HORIZONTAL``, ``BOTH``. 125 | - **supported_presets** (*Optional*, list): Can be used to disable some presets. Possible values for smartair2 are: ``AWAY``, ``BOOST``, ``COMFORT``. Possible values for hOn are: ``AWAY``, ``BOOST``, ``SLEEP``. ``AWAY`` preset can be enabled only in ``HEAT`` mode, it is disabled by default. 126 | - **on_alarm_start** (*Optional*, :ref:`Automation `): (supported only by hOn) Automation to perform when AC activates a new alarm. See :ref:`haier-on_alarm_start`. 127 | - **on_alarm_end** (*Optional*, :ref:`Automation `): (supported only by hOn) Automation to perform when AC deactivates a new alarm. See :ref:`haier-on_alarm_end`. 128 | - **on_status_message** (*Optional*, :ref:`Automation `): Automation to perform when status message received from AC. See :ref:`haier-on_status_message`. 129 | - All other options from :ref:`Climate `. 130 | 131 | Automations 132 | ----------- 133 | 134 | .. _haier-on_alarm_start: 135 | 136 | ``on_alarm_start`` Trigger 137 | ************************** 138 | 139 | This automation will be triggered when a new alarm is activated by AC. The error code of the alarm will be given in the variable ``code`` (``uint8_t``), error message in the variable ``message`` (``const char *``). Those variables can be used in :ref:`lambdas `. 140 | 141 | .. code-block:: yaml 142 | 143 | climate: 144 | - protocol: hon 145 | on_alarm_start: 146 | then: 147 | - logger.log: 148 | level: WARN 149 | format: "Alarm activated. Code: %d. Message: \"%s\"" 150 | args: [ 'code', 'message' ] 151 | 152 | .. _haier-on_alarm_end: 153 | 154 | ``on_alarm_end`` Trigger 155 | ************************ 156 | 157 | This automation will be triggered when a previously activated alarm is deactivated by AC. The error code of the alarm will be given in the variable ``code`` (``uint8_t``), error message in the variable ``message`` (``const char *``). Those variables can be used in :ref:`lambdas `. 158 | 159 | .. code-block:: yaml 160 | 161 | climate: 162 | - protocol: hon 163 | on_alarm_end: 164 | then: 165 | - logger.log: 166 | level: INFO 167 | format: "Alarm deactivated. Code: %d. Message: \"%s\"" 168 | args: [ 'code', 'message' ] 169 | 170 | .. _haier-on_status_message: 171 | 172 | ``on_status_message`` Trigger 173 | ***************************** 174 | 175 | This automation will be triggered when component receives new status packet from AC. Raw message binary (without header and checksum) will be provided in the variable ``data`` (``const char *``), message length in the variable ``data_size`` (``uint8_t``). Those variables can be used in :ref:`lambdas `. 176 | This trigger can be used to support some features that unique for the model and not supported by others. 177 | 178 | .. code-block:: yaml 179 | 180 | climate: 181 | - protocol: hon 182 | on_status_message: 183 | then: 184 | - logger.log: 185 | level: INFO 186 | format: "New status message received, size=%d, subcmd=%02X%02X" 187 | args: [ 'data_size', 'data[0]', 'data[1]' ] 188 | 189 | ``climate.haier.power_on`` Action 190 | ********************************* 191 | 192 | This action turns AC power on. 193 | 194 | .. code-block:: yaml 195 | 196 | on_...: 197 | then: 198 | climate.haier.power_on: device_id 199 | 200 | ``climate.haier.power_off`` Action 201 | ********************************** 202 | 203 | This action turns AC power off 204 | 205 | .. code-block:: yaml 206 | 207 | on_...: 208 | then: 209 | climate.haier.power_off: device_id 210 | 211 | ``climate.haier.power_toggle`` Action 212 | ************************************* 213 | 214 | This action toggles AC power 215 | 216 | .. code-block:: yaml 217 | 218 | on_...: 219 | then: 220 | climate.haier.power_toggle: device_id 221 | 222 | ``climate.haier.display_on`` Action 223 | *********************************** 224 | 225 | This action turns the AC display on. 226 | 227 | .. code-block:: yaml 228 | 229 | on_...: 230 | then: 231 | climate.haier.display_on: device_id 232 | 233 | ``climate.haier.display_off`` Action 234 | ************************************ 235 | 236 | This action turns the AC display off. 237 | 238 | .. code-block:: yaml 239 | 240 | on_...: 241 | then: 242 | climate.haier.display_off: device_id 243 | 244 | ``climate.haier.health_on`` Action 245 | ********************************** 246 | 247 | Turn on health mode (`UV light sterilization `__). 248 | 249 | .. code-block:: yaml 250 | 251 | on_...: 252 | then: 253 | climate.haier.health_on: device_id 254 | 255 | ``climate.haier.health_off`` Action 256 | *********************************** 257 | 258 | Turn off health mode. 259 | 260 | .. code-block:: yaml 261 | 262 | on_...: 263 | then: 264 | climate.haier.health_off: device_id 265 | 266 | ``climate.haier.beeper_on`` Action 267 | ********************************** 268 | 269 | (supported only by hOn) This action enables beep feedback on every command sent to AC. 270 | 271 | .. code-block:: yaml 272 | 273 | on_...: 274 | then: 275 | climate.haier.beeper_on: device_id 276 | 277 | ``climate.haier.beeper_off`` Action 278 | *********************************** 279 | 280 | (supported only by hOn) This action disables beep feedback on every command sent to AC (keep in mind that this will not work for IR remote commands). 281 | 282 | .. code-block:: yaml 283 | 284 | on_...: 285 | then: 286 | climate.haier.beeper_off: device_id 287 | 288 | ``climate.haier.set_vertical_airflow`` Action 289 | ********************************************* 290 | 291 | (supported only by hOn) Set direction for vertical airflow if the vertical swing is disabled. Possible values: Health_Up, Max_Up, Up, Center, Down, Health_Down. 292 | 293 | .. code-block:: yaml 294 | 295 | on_...: 296 | then: 297 | - climate.haier.set_vertical_airflow: 298 | id: device_id 299 | vertical_airflow: Up 300 | 301 | ``climate.haier.set_horizontal_airflow`` Action 302 | *********************************************** 303 | 304 | (supported only by hOn) Set direction for horizontal airflow if the horizontal swing is disabled. Possible values: ``Max_Left``, ``Left``, ``Center``, ``Right``, ``Max_Right``. 305 | 306 | .. code-block:: yaml 307 | 308 | on_...: 309 | then: 310 | - climate.haier.set_horizontal_airflow: 311 | id: device_id 312 | vertical_airflow: Right 313 | 314 | ``climate.haier.start_self_cleaning`` Action 315 | ******************************************** 316 | 317 | (supported only by hOn) Start `self-cleaning `__. 318 | 319 | .. code-block:: yaml 320 | 321 | on_...: 322 | then: 323 | - climate.haier.start_self_cleaning: device_id 324 | 325 | ``climate.haier.start_steri_cleaning`` Action 326 | ********************************************* 327 | 328 | (supported only by hOn) Start 56°C steri-cleaning. 329 | 330 | .. code-block:: yaml 331 | 332 | on_...: 333 | then: 334 | - climate.haier.start_steri_cleaning: device_id 335 | 336 | See Also 337 | -------- 338 | 339 | - `haier-esphome `__ 340 | - :doc:`Haier Climate Sensors ` 341 | - :doc:`Haier Climate Binary Sensors ` 342 | - :doc:`Haier Climate Text Sensors ` 343 | - :doc:`Haier Climate Buttons ` 344 | - :doc:`Haier Climate Switches ` 345 | - :doc:`/components/climate/index` 346 | - :apiref:`haier/climate/haier_base.h` 347 | - :ghedit:`Edit` 348 | --------------------------------------------------------------------------------