mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	New features added for Haier integration (#5196)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -14,7 +14,10 @@ from esphome.const import ( | ||||
|     CONF_MIN_TEMPERATURE, | ||||
|     CONF_PROTOCOL, | ||||
|     CONF_SUPPORTED_MODES, | ||||
|     CONF_SUPPORTED_PRESETS, | ||||
|     CONF_SUPPORTED_SWING_MODES, | ||||
|     CONF_TARGET_TEMPERATURE, | ||||
|     CONF_TEMPERATURE_STEP, | ||||
|     CONF_VISUAL, | ||||
|     CONF_WIFI, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
| @@ -23,25 +26,29 @@ from esphome.const import ( | ||||
|     UNIT_CELSIUS, | ||||
| ) | ||||
| from esphome.components.climate import ( | ||||
|     ClimateSwingMode, | ||||
|     ClimateMode, | ||||
|     ClimatePreset, | ||||
|     ClimateSwingMode, | ||||
|     CONF_CURRENT_TEMPERATURE, | ||||
| ) | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| PROTOCOL_MIN_TEMPERATURE = 16.0 | ||||
| PROTOCOL_MAX_TEMPERATURE = 30.0 | ||||
| PROTOCOL_TEMPERATURE_STEP = 1.0 | ||||
| PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 | ||||
| PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 | ||||
|  | ||||
| CODEOWNERS = ["@paveldn"] | ||||
| AUTO_LOAD = ["sensor"] | ||||
| DEPENDENCIES = ["climate", "uart"] | ||||
| CONF_WIFI_SIGNAL = "wifi_signal" | ||||
| CONF_ANSWER_TIMEOUT = "answer_timeout" | ||||
| CONF_DISPLAY = "display" | ||||
| CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" | ||||
| CONF_VERTICAL_AIRFLOW = "vertical_airflow" | ||||
| CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" | ||||
|  | ||||
|  | ||||
| PROTOCOL_HON = "HON" | ||||
| PROTOCOL_SMARTAIR2 = "SMARTAIR2" | ||||
| PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] | ||||
| @@ -89,6 +96,17 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = { | ||||
|     "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, | ||||
| } | ||||
|  | ||||
| SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { | ||||
|     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, | ||||
|     "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, | ||||
| } | ||||
|  | ||||
| SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { | ||||
|     "ECO": ClimatePreset.CLIMATE_PRESET_ECO, | ||||
|     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, | ||||
|     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, | ||||
| } | ||||
|  | ||||
|  | ||||
| def validate_visual(config): | ||||
|     if CONF_VISUAL in config: | ||||
| @@ -109,10 +127,29 @@ def validate_visual(config): | ||||
|                 ) | ||||
|         else: | ||||
|             config[CONF_VISUAL][CONF_MAX_TEMPERATURE] = PROTOCOL_MAX_TEMPERATURE | ||||
|         if CONF_TEMPERATURE_STEP in visual_config: | ||||
|             temp_step = config[CONF_VISUAL][CONF_TEMPERATURE_STEP][ | ||||
|                 CONF_TARGET_TEMPERATURE | ||||
|             ] | ||||
|             if ((int)(temp_step * 2)) / 2 != temp_step: | ||||
|                 raise cv.Invalid( | ||||
|                     f"Configured visual temperature step {temp_step} is wrong, it should be a multiple of 0.5" | ||||
|                 ) | ||||
|         else: | ||||
|             config[CONF_VISUAL][CONF_TEMPERATURE_STEP] = ( | ||||
|                 { | ||||
|                     CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, | ||||
|                     CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, | ||||
|                 }, | ||||
|             ) | ||||
|     else: | ||||
|         config[CONF_VISUAL] = { | ||||
|             CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE, | ||||
|             CONF_MAX_TEMPERATURE: PROTOCOL_MAX_TEMPERATURE, | ||||
|             CONF_TEMPERATURE_STEP: { | ||||
|                 CONF_TARGET_TEMPERATURE: PROTOCOL_TARGET_TEMPERATURE_STEP, | ||||
|                 CONF_CURRENT_TEMPERATURE: PROTOCOL_CURRENT_TEMPERATURE_STEP, | ||||
|             }, | ||||
|         } | ||||
|     return config | ||||
|  | ||||
| @@ -132,6 +169,11 @@ BASE_CONFIG_SCHEMA = ( | ||||
|                     "BOTH", | ||||
|                 ], | ||||
|             ): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)), | ||||
|             cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_DISPLAY): cv.boolean, | ||||
|             cv.Optional( | ||||
|                 CONF_ANSWER_TIMEOUT, | ||||
|             ): cv.positive_time_period_milliseconds, | ||||
|         } | ||||
|     ) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
| @@ -144,13 +186,26 @@ CONFIG_SCHEMA = cv.All( | ||||
|             PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(Smartair2Climate), | ||||
|                     cv.Optional( | ||||
|                         CONF_SUPPORTED_PRESETS, | ||||
|                         default=list( | ||||
|                             SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys() | ||||
|                         ), | ||||
|                     ): cv.ensure_list( | ||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|             PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( | ||||
|                 { | ||||
|                     cv.GenerateID(): cv.declare_id(HonClimate), | ||||
|                     cv.Optional(CONF_WIFI_SIGNAL, default=True): cv.boolean, | ||||
|                     cv.Optional(CONF_BEEPER, default=True): cv.boolean, | ||||
|                     cv.Optional( | ||||
|                         CONF_SUPPORTED_PRESETS, | ||||
|                         default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), | ||||
|                     ): cv.ensure_list( | ||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) | ||||
|                     ), | ||||
|                     cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( | ||||
|                         unit_of_measurement=UNIT_CELSIUS, | ||||
|                         icon=ICON_THERMOMETER, | ||||
| @@ -354,10 +409,11 @@ async def to_code(config): | ||||
|     await uart.register_uart_device(var, config) | ||||
|     await climate.register_climate(var, config) | ||||
|  | ||||
|     if (CONF_WIFI_SIGNAL in config) and (config[CONF_WIFI_SIGNAL]): | ||||
|         cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) | ||||
|     cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) | ||||
|     if CONF_BEEPER in config: | ||||
|         cg.add(var.set_beeper_state(config[CONF_BEEPER])) | ||||
|     if CONF_DISPLAY in config: | ||||
|         cg.add(var.set_display_state(config[CONF_DISPLAY])) | ||||
|     if CONF_OUTDOOR_TEMPERATURE in config: | ||||
|         sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) | ||||
|         cg.add(var.set_outdoor_temperature_sensor(sens)) | ||||
| @@ -365,5 +421,9 @@ async def to_code(config): | ||||
|         cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) | ||||
|     if CONF_SUPPORTED_SWING_MODES in config: | ||||
|         cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) | ||||
|     if CONF_SUPPORTED_PRESETS in config: | ||||
|         cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) | ||||
|     if CONF_ANSWER_TIMEOUT in config: | ||||
|         cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) | ||||
|     # https://github.com/paveldn/HaierProtocol | ||||
|     cg.add_library("pavlodn/HaierProtocol", "0.9.18") | ||||
|     cg.add_library("pavlodn/HaierProtocol", "0.9.20") | ||||
|   | ||||
| @@ -2,6 +2,9 @@ | ||||
| #include <string> | ||||
| #include "esphome/components/climate/climate.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #ifdef USE_WIFI | ||||
| #include "esphome/components/wifi/wifi_component.h" | ||||
| #endif | ||||
| #include "haier_base.h" | ||||
|  | ||||
| using namespace esphome::climate; | ||||
| @@ -24,14 +27,15 @@ constexpr size_t NO_COMMAND = 0xFF;  // Indicate that there is no command suppli | ||||
| const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { | ||||
|   static const char *phase_names[] = { | ||||
|       "SENDING_INIT_1", | ||||
|       "WAITING_ANSWER_INIT_1", | ||||
|       "WAITING_INIT_1_ANSWER", | ||||
|       "SENDING_INIT_2", | ||||
|       "WAITING_ANSWER_INIT_2", | ||||
|       "WAITING_INIT_2_ANSWER", | ||||
|       "SENDING_FIRST_STATUS_REQUEST", | ||||
|       "WAITING_FIRST_STATUS_ANSWER", | ||||
|       "SENDING_ALARM_STATUS_REQUEST", | ||||
|       "WAITING_ALARM_STATUS_ANSWER", | ||||
|       "IDLE", | ||||
|       "UNKNOWN", | ||||
|       "SENDING_STATUS_REQUEST", | ||||
|       "WAITING_STATUS_ANSWER", | ||||
|       "SENDING_UPDATE_SIGNAL_REQUEST", | ||||
| @@ -63,7 +67,8 @@ HaierClimateBase::HaierClimateBase() | ||||
|       forced_publish_(false), | ||||
|       forced_request_status_(false), | ||||
|       first_control_attempt_(false), | ||||
|       reset_protocol_request_(false) { | ||||
|       reset_protocol_request_(false), | ||||
|       send_wifi_signal_(true) { | ||||
|   this->traits_ = climate::ClimateTraits(); | ||||
|   this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, | ||||
|                                      climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, | ||||
| @@ -77,7 +82,7 @@ HaierClimateBase::HaierClimateBase() | ||||
|  | ||||
| HaierClimateBase::~HaierClimateBase() {} | ||||
|  | ||||
| void HaierClimateBase::set_phase_(ProtocolPhases phase) { | ||||
| void HaierClimateBase::set_phase(ProtocolPhases phase) { | ||||
|   if (this->protocol_phase_ != phase) { | ||||
| #if (HAIER_LOG_LEVEL > 4) | ||||
|     ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); | ||||
| @@ -109,10 +114,27 @@ bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady | ||||
|   return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); | ||||
| } | ||||
|  | ||||
| bool HaierClimateBase::is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now) { | ||||
| bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) { | ||||
|   return this->check_timeout_(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); | ||||
| } | ||||
|  | ||||
| #ifdef USE_WIFI | ||||
| haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) { | ||||
|   static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; | ||||
|   if (wifi::global_wifi_component->is_connected()) { | ||||
|     wifi_status_data[1] = 0; | ||||
|     int8_t rssi = wifi::global_wifi_component->wifi_rssi(); | ||||
|     wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f); | ||||
|     ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]); | ||||
|   } else { | ||||
|     ESP_LOGD(TAG, "WiFi is not connected"); | ||||
|     wifi_status_data[1] = 1; | ||||
|     wifi_status_data[3] = 0; | ||||
|   } | ||||
|   return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data)); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| bool HaierClimateBase::get_display_state() const { return this->display_status_; } | ||||
|  | ||||
| void HaierClimateBase::set_display_state(bool state) { | ||||
| @@ -136,10 +158,15 @@ void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionR | ||||
| void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } | ||||
|  | ||||
| void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } | ||||
|  | ||||
| void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { | ||||
|   this->traits_.set_supported_swing_modes(modes); | ||||
|   this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);       // Always available | ||||
|   this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);  // Always available | ||||
|   if (!modes.empty()) | ||||
|     this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); | ||||
| } | ||||
|  | ||||
| void HaierClimateBase::set_answer_timeout(uint32_t timeout) { | ||||
|   this->answer_timeout_ = std::chrono::milliseconds(timeout); | ||||
| } | ||||
|  | ||||
| void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { | ||||
| @@ -148,6 +175,14 @@ void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> | ||||
|   this->traits_.add_supported_mode(climate::CLIMATE_MODE_AUTO);  // Always available | ||||
| } | ||||
|  | ||||
| void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) { | ||||
|   this->traits_.set_supported_presets(presets); | ||||
|   if (!presets.empty()) | ||||
|     this->traits_.add_supported_preset(climate::CLIMATE_PRESET_NONE); | ||||
| } | ||||
|  | ||||
| void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } | ||||
|  | ||||
| haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t request_message_type, | ||||
|                                                                   uint8_t expected_request_message_type, | ||||
|                                                                   uint8_t answer_message_type, | ||||
| @@ -155,9 +190,9 @@ haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t reques | ||||
|                                                                   ProtocolPhases expected_phase) { | ||||
|   haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; | ||||
|   if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) | ||||
|     result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; | ||||
|     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|   if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) | ||||
|     result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; | ||||
|     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|   if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) | ||||
|     result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||
|   if (is_message_invalid(answer_message_type)) | ||||
| @@ -172,9 +207,9 @@ haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t | ||||
|   ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); | ||||
| #endif | ||||
|   if (this->protocol_phase_ > ProtocolPhases::IDLE) { | ||||
|     this->set_phase_(ProtocolPhases::IDLE); | ||||
|     this->set_phase(ProtocolPhases::IDLE); | ||||
|   } else { | ||||
|     this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|     this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|   } | ||||
|   return haier_protocol::HandlerError::HANDLER_OK; | ||||
| } | ||||
| @@ -183,8 +218,8 @@ void HaierClimateBase::setup() { | ||||
|   ESP_LOGI(TAG, "Haier initialization..."); | ||||
|   // Set timestamp here to give AC time to boot | ||||
|   this->last_request_timestamp_ = std::chrono::steady_clock::now(); | ||||
|   this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|   this->set_answers_handlers(); | ||||
|   this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|   this->set_handlers(); | ||||
|   this->haier_protocol_.set_default_timeout_handler( | ||||
|       std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); | ||||
| } | ||||
| @@ -212,7 +247,7 @@ void HaierClimateBase::loop() { | ||||
|       this->set_force_send_control_(false); | ||||
|       if (this->hvac_settings_.valid) | ||||
|         this->hvac_settings_.reset(); | ||||
|       this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       return; | ||||
|     } else { | ||||
|       // No need to reset protocol if we didn't pass initialization phase | ||||
| @@ -229,7 +264,7 @@ void HaierClimateBase::loop() { | ||||
|       this->process_pending_action(); | ||||
|     } else if (this->hvac_settings_.valid || this->force_send_control_) { | ||||
|       ESP_LOGV(TAG, "Control packet is pending..."); | ||||
|       this->set_phase_(ProtocolPhases::SENDING_CONTROL); | ||||
|       this->set_phase(ProtocolPhases::SENDING_CONTROL); | ||||
|     } | ||||
|   } | ||||
|   this->process_phase(now); | ||||
| @@ -243,10 +278,10 @@ void HaierClimateBase::process_pending_action() { | ||||
|   } | ||||
|   switch (request) { | ||||
|     case ActionRequest::TURN_POWER_ON: | ||||
|       this->set_phase_(ProtocolPhases::SENDING_POWER_ON_COMMAND); | ||||
|       this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND); | ||||
|       break; | ||||
|     case ActionRequest::TURN_POWER_OFF: | ||||
|       this->set_phase_(ProtocolPhases::SENDING_POWER_OFF_COMMAND); | ||||
|       this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND); | ||||
|       break; | ||||
|     case ActionRequest::TOGGLE_POWER: | ||||
|     case ActionRequest::NO_ACTION: | ||||
| @@ -303,7 +338,11 @@ void HaierClimateBase::set_force_send_control_(bool status) { | ||||
| } | ||||
|  | ||||
| void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) { | ||||
|   this->haier_protocol_.send_message(command, use_crc); | ||||
|   if (this->answer_timeout_.has_value()) { | ||||
|     this->haier_protocol_.send_message(command, use_crc, this->answer_timeout_.value()); | ||||
|   } else { | ||||
|     this->haier_protocol_.send_message(command, use_crc); | ||||
|   } | ||||
|   this->last_request_timestamp_ = std::chrono::steady_clock::now(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -44,6 +44,7 @@ class HaierClimateBase : public esphome::Component, | ||||
|   void reset_protocol() { this->reset_protocol_request_ = true; }; | ||||
|   void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes); | ||||
|   void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes); | ||||
|   void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); | ||||
|   size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; | ||||
|   size_t read_array(uint8_t *data, size_t len) noexcept override { | ||||
|     return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; | ||||
| @@ -52,39 +53,41 @@ class HaierClimateBase : public esphome::Component, | ||||
|     esphome::uart::UARTDevice::write_array(data, len); | ||||
|   }; | ||||
|   bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; | ||||
|   void set_answer_timeout(uint32_t timeout); | ||||
|   void set_send_wifi(bool send_wifi); | ||||
|  | ||||
|  protected: | ||||
|   enum class ProtocolPhases { | ||||
|     UNKNOWN = -1, | ||||
|     // INITIALIZATION | ||||
|     SENDING_INIT_1 = 0, | ||||
|     WAITING_ANSWER_INIT_1 = 1, | ||||
|     WAITING_INIT_1_ANSWER = 1, | ||||
|     SENDING_INIT_2 = 2, | ||||
|     WAITING_ANSWER_INIT_2 = 3, | ||||
|     WAITING_INIT_2_ANSWER = 3, | ||||
|     SENDING_FIRST_STATUS_REQUEST = 4, | ||||
|     WAITING_FIRST_STATUS_ANSWER = 5, | ||||
|     SENDING_ALARM_STATUS_REQUEST = 6, | ||||
|     WAITING_ALARM_STATUS_ANSWER = 7, | ||||
|     // FUNCTIONAL STATE | ||||
|     IDLE = 8, | ||||
|     SENDING_STATUS_REQUEST = 9, | ||||
|     WAITING_STATUS_ANSWER = 10, | ||||
|     SENDING_UPDATE_SIGNAL_REQUEST = 11, | ||||
|     WAITING_UPDATE_SIGNAL_ANSWER = 12, | ||||
|     SENDING_SIGNAL_LEVEL = 13, | ||||
|     WAITING_SIGNAL_LEVEL_ANSWER = 14, | ||||
|     SENDING_CONTROL = 15, | ||||
|     WAITING_CONTROL_ANSWER = 16, | ||||
|     SENDING_POWER_ON_COMMAND = 17, | ||||
|     WAITING_POWER_ON_ANSWER = 18, | ||||
|     SENDING_POWER_OFF_COMMAND = 19, | ||||
|     WAITING_POWER_OFF_ANSWER = 20, | ||||
|     SENDING_STATUS_REQUEST = 10, | ||||
|     WAITING_STATUS_ANSWER = 11, | ||||
|     SENDING_UPDATE_SIGNAL_REQUEST = 12, | ||||
|     WAITING_UPDATE_SIGNAL_ANSWER = 13, | ||||
|     SENDING_SIGNAL_LEVEL = 14, | ||||
|     WAITING_SIGNAL_LEVEL_ANSWER = 15, | ||||
|     SENDING_CONTROL = 16, | ||||
|     WAITING_CONTROL_ANSWER = 17, | ||||
|     SENDING_POWER_ON_COMMAND = 18, | ||||
|     WAITING_POWER_ON_ANSWER = 19, | ||||
|     SENDING_POWER_OFF_COMMAND = 20, | ||||
|     WAITING_POWER_OFF_ANSWER = 21, | ||||
|     NUM_PROTOCOL_PHASES | ||||
|   }; | ||||
| #if (HAIER_LOG_LEVEL > 4) | ||||
|   const char *phase_to_string_(ProtocolPhases phase); | ||||
| #endif | ||||
|   virtual void set_answers_handlers() = 0; | ||||
|   virtual void set_handlers() = 0; | ||||
|   virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; | ||||
|   virtual haier_protocol::HaierMessage get_control_message() = 0; | ||||
|   virtual bool is_message_invalid(uint8_t message_type) = 0; | ||||
| @@ -99,14 +102,17 @@ class HaierClimateBase : public esphome::Component, | ||||
|   // Helper functions | ||||
|   void set_force_send_control_(bool status); | ||||
|   void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); | ||||
|   void set_phase_(ProtocolPhases phase); | ||||
|   virtual void set_phase(ProtocolPhases phase); | ||||
|   bool check_timeout_(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, | ||||
|                       size_t timeout); | ||||
|   bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||
|   bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||
|   bool is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now); | ||||
|   bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||
|   bool is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now); | ||||
|   bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||
| #ifdef USE_WIFI | ||||
|   haier_protocol::HaierMessage get_wifi_signal_message_(uint8_t message_type); | ||||
| #endif | ||||
|  | ||||
|   struct HvacSettings { | ||||
|     esphome::optional<esphome::climate::ClimateMode> mode; | ||||
| @@ -136,6 +142,9 @@ class HaierClimateBase : public esphome::Component, | ||||
|   std::chrono::steady_clock::time_point last_valid_status_timestamp_;  // For protocol timeout | ||||
|   std::chrono::steady_clock::time_point last_status_request_;          // To request AC status | ||||
|   std::chrono::steady_clock::time_point control_request_timestamp_;    // To send control message | ||||
|   optional<std::chrono::milliseconds> answer_timeout_;                 // Message answer timeout | ||||
|   bool send_wifi_signal_; | ||||
|   std::chrono::steady_clock::time_point last_signal_request_;  // To send WiFI signal level | ||||
| }; | ||||
|  | ||||
| }  // namespace haier | ||||
|   | ||||
| @@ -2,9 +2,6 @@ | ||||
| #include <string> | ||||
| #include "esphome/components/climate/climate.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #ifdef USE_WIFI | ||||
| #include "esphome/components/wifi/wifi_component.h" | ||||
| #endif | ||||
| #include "hon_climate.h" | ||||
| #include "hon_packet.h" | ||||
|  | ||||
| @@ -58,14 +55,7 @@ HonClimate::HonClimate() | ||||
|       hvac_functions_{false, false, false, false, false}, | ||||
|       use_crc_(hvac_functions_[2]), | ||||
|       active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | ||||
|       outdoor_sensor_(nullptr), | ||||
|       send_wifi_signal_(true) { | ||||
|   this->traits_.set_supported_presets({ | ||||
|       climate::CLIMATE_PRESET_NONE, | ||||
|       climate::CLIMATE_PRESET_ECO, | ||||
|       climate::CLIMATE_PRESET_BOOST, | ||||
|       climate::CLIMATE_PRESET_SLEEP, | ||||
|   }); | ||||
|       outdoor_sensor_(nullptr) { | ||||
|   this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; | ||||
|   this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; | ||||
| } | ||||
| @@ -121,17 +111,22 @@ void HonClimate::start_steri_cleaning() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HonClimate::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } | ||||
|  | ||||
| haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, | ||||
|                                                                             const uint8_t *data, size_t data_size) { | ||||
|   // Should check this before preprocess | ||||
|   if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) { | ||||
|     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " | ||||
|                   "protocol instead of hOn"); | ||||
|     this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|     return haier_protocol::HandlerError::INVALID_ANSWER; | ||||
|   } | ||||
|   haier_protocol::HandlerError result = this->answer_preprocess_( | ||||
|       request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, | ||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_1); | ||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER); | ||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||
|     if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { | ||||
|       // Wrong structure | ||||
|       this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||
|     } | ||||
|     // All OK | ||||
| @@ -152,11 +147,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint | ||||
|     this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0;  // multiple AC support | ||||
|     this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0;  // roles support | ||||
|     this->hvac_hardware_info_available_ = true; | ||||
|     this->set_phase_(ProtocolPhases::SENDING_INIT_2); | ||||
|     this->set_phase(ProtocolPhases::SENDING_INIT_2); | ||||
|     return result; | ||||
|   } else { | ||||
|     this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                      : ProtocolPhases::SENDING_INIT_1); | ||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| @@ -165,13 +160,13 @@ haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t r | ||||
|                                                                        const uint8_t *data, size_t data_size) { | ||||
|   haier_protocol::HandlerError result = this->answer_preprocess_( | ||||
|       request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, | ||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_2); | ||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER); | ||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||
|     this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|     this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|     return result; | ||||
|   } else { | ||||
|     this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                      : ProtocolPhases::SENDING_INIT_1); | ||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| @@ -185,8 +180,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u | ||||
|     result = this->process_status_message_(data, data_size); | ||||
|     if (result != haier_protocol::HandlerError::HANDLER_OK) { | ||||
|       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); | ||||
|       this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                        : ProtocolPhases::SENDING_INIT_1); | ||||
|       this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                       : ProtocolPhases::SENDING_INIT_1); | ||||
|     } else { | ||||
|       if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { | ||||
|         memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); | ||||
| @@ -196,13 +191,13 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u | ||||
|       } | ||||
|       if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { | ||||
|         ESP_LOGI(TAG, "First HVAC status received"); | ||||
|         this->set_phase_(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); | ||||
|         this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); | ||||
|       } else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || | ||||
|                  (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || | ||||
|                  (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|         this->set_force_send_control_(false); | ||||
|         if (this->hvac_settings_.valid) | ||||
|           this->hvac_settings_.reset(); | ||||
| @@ -210,8 +205,8 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u | ||||
|     } | ||||
|     return result; | ||||
|   } else { | ||||
|     this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                      : ProtocolPhases::SENDING_INIT_1); | ||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| @@ -225,10 +220,10 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl | ||||
|                                message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, | ||||
|                                ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); | ||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||
|     this->set_phase_(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||
|     this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||
|     return result; | ||||
|   } else { | ||||
|     this->set_phase_(ProtocolPhases::IDLE); | ||||
|     this->set_phase(ProtocolPhases::IDLE); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| @@ -239,7 +234,7 @@ haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(u | ||||
|   haier_protocol::HandlerError result = | ||||
|       this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, | ||||
|                                (uint8_t) hon_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); | ||||
|   this->set_phase_(ProtocolPhases::IDLE); | ||||
|   this->set_phase(ProtocolPhases::IDLE); | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| @@ -248,24 +243,24 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_ | ||||
|   if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { | ||||
|     if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { | ||||
|       // Unexpected answer to request | ||||
|       this->set_phase_(ProtocolPhases::IDLE); | ||||
|       return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|     } | ||||
|     if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { | ||||
|       // Don't expect this answer now | ||||
|       this->set_phase_(ProtocolPhases::IDLE); | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||
|     } | ||||
|     memcpy(this->active_alarms_, data + 2, 8); | ||||
|     this->set_phase_(ProtocolPhases::IDLE); | ||||
|     this->set_phase(ProtocolPhases::IDLE); | ||||
|     return haier_protocol::HandlerError::HANDLER_OK; | ||||
|   } else { | ||||
|     this->set_phase_(ProtocolPhases::IDLE); | ||||
|     return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; | ||||
|     this->set_phase(ProtocolPhases::IDLE); | ||||
|     return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HonClimate::set_answers_handlers() { | ||||
| void HonClimate::set_handlers() { | ||||
|   // Set handlers | ||||
|   this->haier_protocol_.set_answer_handler( | ||||
|       (uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), | ||||
| @@ -311,7 +306,7 @@ void HonClimate::dump_config() { | ||||
| void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|   switch (this->protocol_phase_) { | ||||
|     case ProtocolPhases::SENDING_INIT_1: | ||||
|       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { | ||||
|       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { | ||||
|         this->hvac_hardware_info_available_ = false; | ||||
|         // Indicate device capabilities: | ||||
|         // bit 0 - if 1 module support interactive mode | ||||
| @@ -323,24 +318,24 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( | ||||
|             (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); | ||||
|         this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_1); | ||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_INIT_2: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         static const haier_protocol::HaierMessage DEVICEID_REQUEST((uint8_t) hon_protocol::FrameType::GET_DEVICE_ID); | ||||
|         this->send_message_(DEVICEID_REQUEST, this->use_crc_); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_2); | ||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||
|     case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST( | ||||
|             (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcomandsControl::GET_USER_DATA); | ||||
|             (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); | ||||
|         this->send_message_(STATUS_REQUEST, this->use_crc_); | ||||
|         this->last_status_request_ = now; | ||||
|         this->set_phase_((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); | ||||
|         this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); | ||||
|       } | ||||
|       break; | ||||
| #ifdef USE_WIFI | ||||
| @@ -350,26 +345,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|             (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); | ||||
|         this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); | ||||
|         this->last_signal_request_ = now; | ||||
|         this->set_phase_(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); | ||||
|         this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; | ||||
|         if (wifi::global_wifi_component->is_connected()) { | ||||
|           wifi_status_data[1] = 0; | ||||
|           int8_t rssi = wifi::global_wifi_component->wifi_rssi(); | ||||
|           wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f); | ||||
|           ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]); | ||||
|         } else { | ||||
|           ESP_LOGD(TAG, "WiFi is not connected"); | ||||
|           wifi_status_data[1] = 1; | ||||
|           wifi_status_data[3] = 0; | ||||
|         } | ||||
|         haier_protocol::HaierMessage wifi_status_request((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, | ||||
|                                                          wifi_status_data, sizeof(wifi_status_data)); | ||||
|         this->send_message_(wifi_status_request, this->use_crc_); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); | ||||
|         this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS), | ||||
|                             this->use_crc_); | ||||
|         this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: | ||||
| @@ -380,7 +363,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: | ||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: | ||||
|       this->set_phase_(ProtocolPhases::IDLE); | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       break; | ||||
| #endif | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
| @@ -388,7 +371,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|         static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( | ||||
|             (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS); | ||||
|         this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); | ||||
|         this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_CONTROL: | ||||
| @@ -403,12 +386,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|           this->hvac_settings_.reset(); | ||||
|         this->forced_request_status_ = true; | ||||
|         this->forced_publish_ = true; | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|       } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { | ||||
|         haier_protocol::HaierMessage control_message = get_control_message(); | ||||
|         this->send_message_(control_message, this->use_crc_); | ||||
|         ESP_LOGI(TAG, "Control packet sent"); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); | ||||
|         this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_POWER_ON_COMMAND: | ||||
| @@ -418,17 +401,17 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|         if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) | ||||
|           pwr_cmd_buf[1] = 0x01; | ||||
|         haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, | ||||
|                                                ((uint16_t) hon_protocol::SubcomandsControl::SET_SINGLE_PARAMETER) + 1, | ||||
|                                                ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, | ||||
|                                                pwr_cmd_buf, sizeof(pwr_cmd_buf)); | ||||
|         this->send_message_(power_cmd, this->use_crc_); | ||||
|         this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND | ||||
|                              ? ProtocolPhases::WAITING_POWER_ON_ANSWER | ||||
|                              : ProtocolPhases::WAITING_POWER_OFF_ANSWER); | ||||
|         this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND | ||||
|                             ? ProtocolPhases::WAITING_POWER_ON_ANSWER | ||||
|                             : ProtocolPhases::WAITING_POWER_OFF_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|  | ||||
|     case ProtocolPhases::WAITING_ANSWER_INIT_1: | ||||
|     case ProtocolPhases::WAITING_ANSWER_INIT_2: | ||||
|     case ProtocolPhases::WAITING_INIT_1_ANSWER: | ||||
|     case ProtocolPhases::WAITING_INIT_2_ANSWER: | ||||
|     case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: | ||||
|     case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: | ||||
|     case ProtocolPhases::WAITING_STATUS_ANSWER: | ||||
| @@ -438,14 +421,14 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|       break; | ||||
|     case ProtocolPhases::IDLE: { | ||||
|       if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { | ||||
|         this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); | ||||
|         this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); | ||||
|         this->forced_request_status_ = false; | ||||
|       } | ||||
| #ifdef USE_WIFI | ||||
|       else if (this->send_wifi_signal_ && | ||||
|                (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() > | ||||
|                 SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) | ||||
|         this->set_phase_(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); | ||||
|         this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); | ||||
| #endif | ||||
|     } break; | ||||
|     default: | ||||
| @@ -456,7 +439,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
| #else | ||||
|       ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); | ||||
| #endif | ||||
|       this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| @@ -551,11 +534,12 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||
|       } | ||||
|     } | ||||
|     if (climate_control.target_temperature.has_value()) { | ||||
|       out_data->set_point = | ||||
|           climate_control.target_temperature.value() - 16;  // set the temperature at our offset, subtract 16. | ||||
|       float target_temp = climate_control.target_temperature.value(); | ||||
|       out_data->set_point = ((int) target_temp) - 16;  // set the temperature at our offset, subtract 16. | ||||
|       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; | ||||
|     } | ||||
|     if (out_data->ac_power == 0) { | ||||
|       // If AC is off - no presets alowed | ||||
|       // If AC is off - no presets allowed | ||||
|       out_data->quiet_mode = 0; | ||||
|       out_data->fast_mode = 0; | ||||
|       out_data->sleep_mode = 0; | ||||
| @@ -631,7 +615,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||
|       break; | ||||
|   } | ||||
|   return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL, | ||||
|                                       (uint16_t) hon_protocol::SubcomandsControl::SET_GROUP_PARAMETERS, | ||||
|                                       (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, | ||||
|                                       control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); | ||||
| } | ||||
|  | ||||
| @@ -669,7 +653,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | ||||
|   { | ||||
|     // Target temperature | ||||
|     float old_target_temperature = this->target_temperature; | ||||
|     this->target_temperature = packet.control.set_point + 16.0f; | ||||
|     this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f); | ||||
|     should_publish = should_publish || (old_target_temperature != this->target_temperature); | ||||
|   } | ||||
|   { | ||||
| @@ -747,7 +731,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | ||||
|     if (new_cleaning != this->cleaning_status_) { | ||||
|       ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); | ||||
|       if (new_cleaning == CleaningState::NO_CLEANING) { | ||||
|         // Turnuin AC off after cleaning | ||||
|         // Turning AC off after cleaning | ||||
|         this->action_request_ = ActionRequest::TURN_POWER_OFF; | ||||
|       } | ||||
|       this->cleaning_status_ = new_cleaning; | ||||
| @@ -837,7 +821,7 @@ void HonClimate::process_pending_action() { | ||||
|     case ActionRequest::START_SELF_CLEAN: | ||||
|     case ActionRequest::START_STERI_CLEAN: | ||||
|       // Will reset action with control message sending | ||||
|       this->set_phase_(ProtocolPhases::SENDING_CONTROL); | ||||
|       this->set_phase(ProtocolPhases::SENDING_CONTROL); | ||||
|       break; | ||||
|     default: | ||||
|       HaierClimateBase::process_pending_action(); | ||||
|   | ||||
| @@ -48,10 +48,9 @@ class HonClimate : public HaierClimateBase { | ||||
|   CleaningState get_cleaning_status() const; | ||||
|   void start_self_cleaning(); | ||||
|   void start_steri_cleaning(); | ||||
|   void set_send_wifi(bool send_wifi); | ||||
|  | ||||
|  protected: | ||||
|   void set_answers_handlers() override; | ||||
|   void set_handlers() override; | ||||
|   void process_phase(std::chrono::steady_clock::time_point now) override; | ||||
|   haier_protocol::HaierMessage get_control_message() override; | ||||
|   bool is_message_invalid(uint8_t message_type) override; | ||||
| @@ -87,8 +86,6 @@ class HonClimate : public HaierClimateBase { | ||||
|   bool &use_crc_; | ||||
|   uint8_t active_alarms_[8]; | ||||
|   esphome::sensor::Sensor *outdoor_sensor_; | ||||
|   bool send_wifi_signal_; | ||||
|   std::chrono::steady_clock::time_point last_signal_request_;  // To send WiFI signal level | ||||
| }; | ||||
|  | ||||
| }  // namespace haier | ||||
|   | ||||
| @@ -53,12 +53,12 @@ struct HaierPacketControl { | ||||
|   // 13 | ||||
|   uint8_t : 8; | ||||
|   // 14 | ||||
|   uint8_t ten_degree : 1;          // 10 degree status | ||||
|   uint8_t display_status : 1;      // If 0 disables AC's display | ||||
|   uint8_t half_degree : 1;         // Use half degree | ||||
|   uint8_t intelegence_status : 1;  // Intelligence status | ||||
|   uint8_t pmv_status : 1;          // Comfort/PMV status | ||||
|   uint8_t use_fahrenheit : 1;      // Use Fahrenheit instead of Celsius | ||||
|   uint8_t ten_degree : 1;           // 10 degree status | ||||
|   uint8_t display_status : 1;       // If 0 disables AC's display | ||||
|   uint8_t half_degree : 1;          // Use half degree | ||||
|   uint8_t intelligence_status : 1;  // Intelligence status | ||||
|   uint8_t pmv_status : 1;           // Comfort/PMV status | ||||
|   uint8_t use_fahrenheit : 1;       // Use Fahrenheit instead of Celsius | ||||
|   uint8_t : 1; | ||||
|   uint8_t steri_clean : 1; | ||||
|   // 15 | ||||
| @@ -153,7 +153,7 @@ enum class FrameType : uint8_t { | ||||
|                    // <-> device, required) | ||||
|   REPORT = 0x06,   // Report frame (module <-> device, interactive, required) | ||||
|   STOP_FAULT_ALARM = 0x09,             // Stop fault alarm frame (module -> device, interactive, required) | ||||
|   SYSTEM_DOWNLIK = 0x11,               // System downlink frame (module -> device, optional) | ||||
|   SYSTEM_DOWNLINK = 0x11,              // System downlink frame (module -> device, optional) | ||||
|   DEVICE_UPLINK = 0x12,                // Device uplink frame (module <- device , interactive, optional) | ||||
|   SYSTEM_QUERY = 0x13,                 // System query frame (module -> device, optional) | ||||
|   SYSTEM_QUERY_RESPONSE = 0x14,        // System query response frame (module <- device , optional) | ||||
| @@ -210,7 +210,7 @@ enum class FrameType : uint8_t { | ||||
|   WAKE_UP = 0xFE,  // Request to wake up (module <-> device, optional) | ||||
| }; | ||||
|  | ||||
| enum class SubcomandsControl : uint16_t { | ||||
| enum class SubcommandsControl : uint16_t { | ||||
|   GET_PARAMETERS = 0x4C01,  // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) | ||||
|   GET_USER_DATA = 0x4D01,   // Request all user data from device (packet content: None) | ||||
|   GET_BIG_DATA = 0x4DFE,    // Request big data information from device (packet content: None) | ||||
|   | ||||
| @@ -11,15 +11,10 @@ namespace esphome { | ||||
| namespace haier { | ||||
|  | ||||
| static const char *const TAG = "haier.climate"; | ||||
| constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | ||||
|  | ||||
| Smartair2Climate::Smartair2Climate() | ||||
|     : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]) { | ||||
|   this->traits_.set_supported_presets({ | ||||
|       climate::CLIMATE_PRESET_NONE, | ||||
|       climate::CLIMATE_PRESET_BOOST, | ||||
|       climate::CLIMATE_PRESET_COMFORT, | ||||
|   }); | ||||
| } | ||||
|     : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {} | ||||
|  | ||||
| haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, | ||||
|                                                                const uint8_t *data, size_t data_size) { | ||||
| @@ -30,8 +25,8 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t | ||||
|     result = this->process_status_message_(data, data_size); | ||||
|     if (result != haier_protocol::HandlerError::HANDLER_OK) { | ||||
|       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); | ||||
|       this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                        : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|       this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                       : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|     } else { | ||||
|       if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { | ||||
|         memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); | ||||
| @@ -41,11 +36,11 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t | ||||
|       } | ||||
|       if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { | ||||
|         ESP_LOGI(TAG, "First HVAC status received"); | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|         this->set_force_send_control_(false); | ||||
|         if (this->hvac_settings_.valid) | ||||
|           this->hvac_settings_.reset(); | ||||
| @@ -53,17 +48,82 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t | ||||
|     } | ||||
|     return result; | ||||
|   } else { | ||||
|     this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                      : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||
|                                                                     : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void Smartair2Climate::set_answers_handlers() { | ||||
| haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type, | ||||
|                                                                                   uint8_t message_type, | ||||
|                                                                                   const uint8_t *data, | ||||
|                                                                                   size_t data_size) { | ||||
|   if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION) | ||||
|     return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|   if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_) | ||||
|     return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||
|   // Invalid packet is expected answer | ||||
|   if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && | ||||
|       ((data[37] & 0x04) != 0)) { | ||||
|     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " | ||||
|                   "instead of smartAir2"); | ||||
|   } | ||||
|   this->set_phase(ProtocolPhases::SENDING_INIT_2); | ||||
|   return haier_protocol::HandlerError::HANDLER_OK; | ||||
| } | ||||
|  | ||||
| haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type, | ||||
|                                                                                      uint8_t message_type, | ||||
|                                                                                      const uint8_t *data, | ||||
|                                                                                      size_t data_size) { | ||||
|   haier_protocol::HandlerError result = this->answer_preprocess_( | ||||
|       request_type, (uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, | ||||
|       (uint8_t) smartair2_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); | ||||
|   this->set_phase(ProtocolPhases::IDLE); | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| haier_protocol::HandlerError Smartair2Climate::initial_messages_timeout_handler_(uint8_t message_type) { | ||||
|   if (this->protocol_phase_ >= ProtocolPhases::IDLE) | ||||
|     return HaierClimateBase::timeout_default_handler_(message_type); | ||||
|   this->timeouts_counter_++; | ||||
|   ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type, | ||||
|            (int) this->protocol_phase_, this->timeouts_counter_); | ||||
|   if (this->timeouts_counter_ >= 3) { | ||||
|     ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); | ||||
|     if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) | ||||
|       new_phase = ProtocolPhases::SENDING_INIT_1; | ||||
|     this->set_phase(new_phase); | ||||
|   } else { | ||||
|     // Returning to the previous state to try again | ||||
|     this->set_phase((ProtocolPhases) ((int) this->protocol_phase_ - 1)); | ||||
|   } | ||||
|   return haier_protocol::HandlerError::HANDLER_OK; | ||||
| } | ||||
|  | ||||
| void Smartair2Climate::set_handlers() { | ||||
|   // Set handlers | ||||
|   this->haier_protocol_.set_answer_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), | ||||
|       std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, | ||||
|                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); | ||||
|   this->haier_protocol_.set_answer_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::CONTROL), | ||||
|       std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||
|                 std::placeholders::_3, std::placeholders::_4)); | ||||
|   this->haier_protocol_.set_answer_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), | ||||
|       std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, | ||||
|                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); | ||||
|   this->haier_protocol_.set_timeout_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID), | ||||
|       std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); | ||||
|   this->haier_protocol_.set_timeout_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), | ||||
|       std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); | ||||
|   this->haier_protocol_.set_timeout_handler( | ||||
|       (uint8_t) (smartair2_protocol::FrameType::CONTROL), | ||||
|       std::bind(&Smartair2Climate::initial_messages_timeout_handler_, this, std::placeholders::_1)); | ||||
| } | ||||
|  | ||||
| void Smartair2Climate::dump_config() { | ||||
| @@ -74,39 +134,60 @@ void Smartair2Climate::dump_config() { | ||||
| void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|   switch (this->protocol_phase_) { | ||||
|     case ProtocolPhases::SENDING_INIT_1: | ||||
|       this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|       break; | ||||
|     case ProtocolPhases::WAITING_ANSWER_INIT_1: | ||||
|     case ProtocolPhases::SENDING_INIT_2: | ||||
|     case ProtocolPhases::WAITING_ANSWER_INIT_2: | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
|     case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: | ||||
|       this->set_phase_(ProtocolPhases::SENDING_INIT_1); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: | ||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: | ||||
|       this->set_phase_(ProtocolPhases::IDLE); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||
|       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { | ||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, | ||||
|                                                                  0x4D01); | ||||
|         this->send_message_(STATUS_REQUEST, false); | ||||
|         this->last_status_request_ = now; | ||||
|         this->set_phase_(ProtocolPhases::WAITING_FIRST_STATUS_ANSWER); | ||||
|       if (this->can_send_message() && | ||||
|           (((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) || | ||||
|            ((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) { | ||||
|         // Indicate device capabilities: | ||||
|         // bit 0 - if 1 module support interactive mode | ||||
|         // bit 1 - if 1 module support controller-device mode | ||||
|         // bit 2 - if 1 module support crc | ||||
|         // bit 3 - if 1 module support multiple devices | ||||
|         // bit 4..bit 15 - not used | ||||
|         uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; | ||||
|         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( | ||||
|             (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, | ||||
|             sizeof(module_capabilities)); | ||||
|         this->send_message_(DEVICE_VERSION_REQUEST, false); | ||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_INIT_2: | ||||
|     case ProtocolPhases::WAITING_INIT_2_ANSWER: | ||||
|       this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||
|     case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, | ||||
|                                                                  0x4D01); | ||||
|         this->send_message_(STATUS_REQUEST, false); | ||||
|         this->last_status_request_ = now; | ||||
|         this->set_phase_(ProtocolPhases::WAITING_STATUS_ANSWER); | ||||
|         this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); | ||||
|       } | ||||
|       break; | ||||
| #ifdef USE_WIFI | ||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         this->send_message_( | ||||
|             this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false); | ||||
|         this->last_signal_request_ = now; | ||||
|         this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: | ||||
|       break; | ||||
| #else | ||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER this->set_phase(ProtocolPhases::IDLE); break; | ||||
| #endif | ||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: | ||||
|       this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
|     case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_CONTROL: | ||||
|       if (this->first_control_attempt_) { | ||||
|         this->control_request_timestamp_ = now; | ||||
| @@ -119,14 +200,14 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | ||||
|           this->hvac_settings_.reset(); | ||||
|         this->forced_request_status_ = true; | ||||
|         this->forced_publish_ = true; | ||||
|         this->set_phase_(ProtocolPhases::IDLE); | ||||
|         this->set_phase(ProtocolPhases::IDLE); | ||||
|       } else if (this->can_send_message() && this->is_control_message_interval_exceeded_( | ||||
|                                                  now))  // Using CONTROL_MESSAGES_INTERVAL_MS to speedup requests | ||||
|       { | ||||
|         haier_protocol::HaierMessage control_message = get_control_message(); | ||||
|         this->send_message_(control_message, false); | ||||
|         ESP_LOGI(TAG, "Control packet sent"); | ||||
|         this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); | ||||
|         this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_POWER_ON_COMMAND: | ||||
| @@ -136,11 +217,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | ||||
|             (uint8_t) smartair2_protocol::FrameType::CONTROL, | ||||
|             this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND ? 0x4D02 : 0x4D03); | ||||
|         this->send_message_(power_cmd, false); | ||||
|         this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND | ||||
|                              ? ProtocolPhases::WAITING_POWER_ON_ANSWER | ||||
|                              : ProtocolPhases::WAITING_POWER_OFF_ANSWER); | ||||
|         this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND | ||||
|                             ? ProtocolPhases::WAITING_POWER_ON_ANSWER | ||||
|                             : ProtocolPhases::WAITING_POWER_OFF_ANSWER); | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::WAITING_INIT_1_ANSWER: | ||||
|     case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: | ||||
|     case ProtocolPhases::WAITING_STATUS_ANSWER: | ||||
|     case ProtocolPhases::WAITING_CONTROL_ANSWER: | ||||
| @@ -149,14 +231,25 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | ||||
|       break; | ||||
|     case ProtocolPhases::IDLE: { | ||||
|       if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { | ||||
|         this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); | ||||
|         this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); | ||||
|         this->forced_request_status_ = false; | ||||
|       } | ||||
| #ifdef USE_WIFI | ||||
|       else if (this->send_wifi_signal_ && | ||||
|                (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() > | ||||
|                 SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) | ||||
|         this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); | ||||
| #endif | ||||
|     } break; | ||||
|     default: | ||||
|       // Shouldn't get here | ||||
| #if (HAIER_LOG_LEVEL > 4) | ||||
|       ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", | ||||
|                phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); | ||||
| #else | ||||
|       ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); | ||||
|       this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||
| #endif | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| @@ -256,11 +349,12 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | ||||
|       } | ||||
|     } | ||||
|     if (climate_control.target_temperature.has_value()) { | ||||
|       out_data->set_point = | ||||
|           climate_control.target_temperature.value() - 16;  // set the temperature at our offset, subtract 16. | ||||
|       float target_temp = climate_control.target_temperature.value(); | ||||
|       out_data->set_point = target_temp - 16;  // set the temperature with offset 16 | ||||
|       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; | ||||
|     } | ||||
|     if (out_data->ac_power == 0) { | ||||
|       // If AC is off - no presets alowed | ||||
|       // If AC is off - no presets allowed | ||||
|       out_data->turbo_mode = 0; | ||||
|       out_data->quiet_mode = 0; | ||||
|     } else if (climate_control.preset.has_value()) { | ||||
| @@ -312,7 +406,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | ||||
|   { | ||||
|     // Target temperature | ||||
|     float old_target_temperature = this->target_temperature; | ||||
|     this->target_temperature = packet.control.set_point + 16.0f; | ||||
|     this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f); | ||||
|     should_publish = should_publish || (old_target_temperature != this->target_temperature); | ||||
|   } | ||||
|   { | ||||
| @@ -333,7 +427,7 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | ||||
|     } | ||||
|     switch (packet.control.fan_mode) { | ||||
|       case (uint8_t) smartair2_protocol::FanMode::FAN_AUTO: | ||||
|         // Somtimes AC reports in fan only mode that fan speed is auto | ||||
|         // Sometimes AC reports in fan only mode that fan speed is auto | ||||
|         // but never accept this value back | ||||
|         if (packet.control.ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) { | ||||
|           this->fan_mode = CLIMATE_FAN_AUTO; | ||||
| @@ -453,5 +547,15 @@ bool Smartair2Climate::is_message_invalid(uint8_t message_type) { | ||||
|   return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; | ||||
| } | ||||
|  | ||||
| void Smartair2Climate::set_phase(HaierClimateBase::ProtocolPhases phase) { | ||||
|   int old_phase = (int) this->protocol_phase_; | ||||
|   int new_phase = (int) phase; | ||||
|   int min_p = std::min(old_phase, new_phase); | ||||
|   int max_p = std::max(old_phase, new_phase); | ||||
|   if ((min_p % 2 != 0) || (max_p - min_p > 1)) | ||||
|     this->timeouts_counter_ = 0; | ||||
|   HaierClimateBase::set_phase(phase); | ||||
| } | ||||
|  | ||||
| }  // namespace haier | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -15,16 +15,25 @@ class Smartair2Climate : public HaierClimateBase { | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   void set_answers_handlers() override; | ||||
|   void set_handlers() override; | ||||
|   void process_phase(std::chrono::steady_clock::time_point now) override; | ||||
|   haier_protocol::HaierMessage get_control_message() override; | ||||
|   bool is_message_invalid(uint8_t message_type) override; | ||||
|   // Answers handlers | ||||
|   void set_phase(HaierClimateBase::ProtocolPhases phase) override; | ||||
|   // Answer and timeout handlers | ||||
|   haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, | ||||
|                                                size_t data_size); | ||||
|   haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, | ||||
|                                                                   const uint8_t *data, size_t data_size); | ||||
|   haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, | ||||
|                                                              const uint8_t *data, size_t data_size); | ||||
|   haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, | ||||
|                                                                      const uint8_t *data, size_t data_size); | ||||
|   haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type); | ||||
|   // Helper functions | ||||
|   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); | ||||
|   std::unique_ptr<uint8_t[]> last_status_message_; | ||||
|   unsigned int timeouts_counter_; | ||||
| }; | ||||
|  | ||||
| }  // namespace haier | ||||
|   | ||||
| @@ -53,8 +53,8 @@ struct HaierPacketControl { | ||||
|   uint8_t : 2; | ||||
|   uint8_t health_mode : 1;  // Health mode on or off | ||||
|   uint8_t compressor : 1;   // Compressor on or off ??? | ||||
|   uint8_t : 1; | ||||
|   uint8_t ten_degree : 1;  // 10 degree status (only work in heat mode) | ||||
|   uint8_t half_degree : 1;  // Use half degree | ||||
|   uint8_t ten_degree : 1;   // 10 degree status (only work in heat mode) | ||||
|   uint8_t : 0; | ||||
|   // 28 | ||||
|   uint8_t : 8; | ||||
| @@ -88,6 +88,9 @@ enum class FrameType : uint8_t { | ||||
|   INVALID = 0x03, | ||||
|   CONFIRM = 0x05, | ||||
|   GET_DEVICE_VERSION = 0x61, | ||||
|   GET_DEVICE_VERSION_RESPONSE = 0x62, | ||||
|   GET_DEVICE_ID = 0x70, | ||||
|   GET_DEVICE_ID_RESPONSE = 0x71, | ||||
|   REPORT_NETWORK_STATUS = 0xF7, | ||||
|   NO_COMMAND = 0xFF, | ||||
| }; | ||||
|   | ||||
| @@ -39,7 +39,7 @@ lib_deps = | ||||
|     bblanchon/ArduinoJson@6.18.5           ; json | ||||
|     wjtje/qr-code-generator-library@1.7.0  ; qr_code | ||||
|     functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 | ||||
|     pavlodn/HaierProtocol@0.9.18           ; haier | ||||
|     pavlodn/HaierProtocol@0.9.20           ; haier | ||||
|     ; This is using the repository until a new release is published to PlatformIO | ||||
|     https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library | ||||
| build_flags = | ||||
|   | ||||
		Reference in New Issue
	
	Block a user