mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Haier component updated to support new protocol variations (#5713)
Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
This commit is contained in:
		| @@ -38,16 +38,20 @@ PROTOCOL_MIN_TEMPERATURE = 16.0 | |||||||
| PROTOCOL_MAX_TEMPERATURE = 30.0 | PROTOCOL_MAX_TEMPERATURE = 30.0 | ||||||
| PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 | PROTOCOL_TARGET_TEMPERATURE_STEP = 1.0 | ||||||
| PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 | PROTOCOL_CURRENT_TEMPERATURE_STEP = 0.5 | ||||||
|  | PROTOCOL_CONTROL_PACKET_SIZE = 10 | ||||||
|  |  | ||||||
| CODEOWNERS = ["@paveldn"] | CODEOWNERS = ["@paveldn"] | ||||||
| AUTO_LOAD = ["sensor"] | AUTO_LOAD = ["sensor"] | ||||||
| DEPENDENCIES = ["climate", "uart"] | DEPENDENCIES = ["climate", "uart"] | ||||||
| CONF_WIFI_SIGNAL = "wifi_signal" | CONF_ALTERNATIVE_SWING_CONTROL = "alternative_swing_control" | ||||||
| CONF_ANSWER_TIMEOUT = "answer_timeout" | CONF_ANSWER_TIMEOUT = "answer_timeout" | ||||||
|  | CONF_CONTROL_METHOD = "control_method" | ||||||
|  | CONF_CONTROL_PACKET_SIZE = "control_packet_size" | ||||||
| CONF_DISPLAY = "display" | CONF_DISPLAY = "display" | ||||||
|  | CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" | ||||||
| CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" | CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" | ||||||
| CONF_VERTICAL_AIRFLOW = "vertical_airflow" | CONF_VERTICAL_AIRFLOW = "vertical_airflow" | ||||||
| CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" | CONF_WIFI_SIGNAL = "wifi_signal" | ||||||
|  |  | ||||||
| PROTOCOL_HON = "HON" | PROTOCOL_HON = "HON" | ||||||
| PROTOCOL_SMARTAIR2 = "SMARTAIR2" | PROTOCOL_SMARTAIR2 = "SMARTAIR2" | ||||||
| @@ -107,6 +111,13 @@ SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { | |||||||
|     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, |     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | HonControlMethod = haier_ns.enum("HonControlMethod", True) | ||||||
|  | SUPPORTED_HON_CONTROL_METHODS = { | ||||||
|  |     "MONITOR_ONLY": HonControlMethod.MONITOR_ONLY, | ||||||
|  |     "SET_GROUP_PARAMETERS": HonControlMethod.SET_GROUP_PARAMETERS, | ||||||
|  |     "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| def validate_visual(config): | def validate_visual(config): | ||||||
|     if CONF_VISUAL in config: |     if CONF_VISUAL in config: | ||||||
| @@ -184,6 +195,9 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( |             PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(): cv.declare_id(Smartair2Climate), |                     cv.GenerateID(): cv.declare_id(Smartair2Climate), | ||||||
|  |                     cv.Optional( | ||||||
|  |                         CONF_ALTERNATIVE_SWING_CONTROL, default=False | ||||||
|  |                     ): cv.boolean, | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_SUPPORTED_PRESETS, |                         CONF_SUPPORTED_PRESETS, | ||||||
|                         default=list( |                         default=list( | ||||||
| @@ -197,7 +211,15 @@ CONFIG_SCHEMA = cv.All( | |||||||
|             PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( |             PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(): cv.declare_id(HonClimate), |                     cv.GenerateID(): cv.declare_id(HonClimate), | ||||||
|  |                     cv.Optional( | ||||||
|  |                         CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS" | ||||||
|  |                     ): cv.ensure_list( | ||||||
|  |                         cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) | ||||||
|  |                     ), | ||||||
|                     cv.Optional(CONF_BEEPER, default=True): cv.boolean, |                     cv.Optional(CONF_BEEPER, default=True): cv.boolean, | ||||||
|  |                     cv.Optional( | ||||||
|  |                         CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE | ||||||
|  |                     ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_SUPPORTED_PRESETS, |                         CONF_SUPPORTED_PRESETS, | ||||||
|                         default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), |                         default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), | ||||||
| @@ -408,6 +430,8 @@ async def to_code(config): | |||||||
|     await climate.register_climate(var, config) |     await climate.register_climate(var, config) | ||||||
|  |  | ||||||
|     cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) |     cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) | ||||||
|  |     if CONF_CONTROL_METHOD in config: | ||||||
|  |         cg.add(var.set_control_method(config[CONF_CONTROL_METHOD])) | ||||||
|     if CONF_BEEPER in config: |     if CONF_BEEPER in config: | ||||||
|         cg.add(var.set_beeper_state(config[CONF_BEEPER])) |         cg.add(var.set_beeper_state(config[CONF_BEEPER])) | ||||||
|     if CONF_DISPLAY in config: |     if CONF_DISPLAY in config: | ||||||
| @@ -423,5 +447,15 @@ async def to_code(config): | |||||||
|         cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) |         cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS])) | ||||||
|     if CONF_ANSWER_TIMEOUT in config: |     if CONF_ANSWER_TIMEOUT in config: | ||||||
|         cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) |         cg.add(var.set_answer_timeout(config[CONF_ANSWER_TIMEOUT])) | ||||||
|  |     if CONF_ALTERNATIVE_SWING_CONTROL in config: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_alternative_swing_control(config[CONF_ALTERNATIVE_SWING_CONTROL]) | ||||||
|  |         ) | ||||||
|  |     if CONF_CONTROL_PACKET_SIZE in config: | ||||||
|  |         cg.add( | ||||||
|  |             var.set_extra_control_packet_bytes_size( | ||||||
|  |                 config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|     # https://github.com/paveldn/HaierProtocol |     # https://github.com/paveldn/HaierProtocol | ||||||
|     cg.add_library("pavlodn/HaierProtocol", "0.9.20") |     cg.add_library("pavlodn/HaierProtocol", "0.9.24") | ||||||
|   | |||||||
| @@ -19,56 +19,45 @@ constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000; | |||||||
| constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; | constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; | ||||||
| constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000; | constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000; | ||||||
| constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400; | constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400; | ||||||
| constexpr size_t CONTROL_TIMEOUT_MS = 7000; |  | ||||||
| constexpr size_t NO_COMMAND = 0xFF;  // Indicate that there is no command supplied |  | ||||||
|  |  | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
| // To reduce size of binary this function only available when log level is Verbose |  | ||||||
| const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { | const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { | ||||||
|   static const char *phase_names[] = { |   static const char *phase_names[] = { | ||||||
|       "SENDING_INIT_1", |       "SENDING_INIT_1", | ||||||
|       "WAITING_INIT_1_ANSWER", |  | ||||||
|       "SENDING_INIT_2", |       "SENDING_INIT_2", | ||||||
|       "WAITING_INIT_2_ANSWER", |  | ||||||
|       "SENDING_FIRST_STATUS_REQUEST", |       "SENDING_FIRST_STATUS_REQUEST", | ||||||
|       "WAITING_FIRST_STATUS_ANSWER", |  | ||||||
|       "SENDING_ALARM_STATUS_REQUEST", |       "SENDING_ALARM_STATUS_REQUEST", | ||||||
|       "WAITING_ALARM_STATUS_ANSWER", |  | ||||||
|       "IDLE", |       "IDLE", | ||||||
|       "UNKNOWN", |  | ||||||
|       "SENDING_STATUS_REQUEST", |       "SENDING_STATUS_REQUEST", | ||||||
|       "WAITING_STATUS_ANSWER", |  | ||||||
|       "SENDING_UPDATE_SIGNAL_REQUEST", |       "SENDING_UPDATE_SIGNAL_REQUEST", | ||||||
|       "WAITING_UPDATE_SIGNAL_ANSWER", |  | ||||||
|       "SENDING_SIGNAL_LEVEL", |       "SENDING_SIGNAL_LEVEL", | ||||||
|       "WAITING_SIGNAL_LEVEL_ANSWER", |  | ||||||
|       "SENDING_CONTROL", |       "SENDING_CONTROL", | ||||||
|       "WAITING_CONTROL_ANSWER", |       "SENDING_ACTION_COMMAND", | ||||||
|       "SENDING_POWER_ON_COMMAND", |  | ||||||
|       "WAITING_POWER_ON_ANSWER", |  | ||||||
|       "SENDING_POWER_OFF_COMMAND", |  | ||||||
|       "WAITING_POWER_OFF_ANSWER", |  | ||||||
|       "UNKNOWN"  // Should be the last! |       "UNKNOWN"  // Should be the last! | ||||||
|   }; |   }; | ||||||
|  |   static_assert( | ||||||
|  |       (sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1), | ||||||
|  |       "Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases"); | ||||||
|   int phase_index = (int) phase; |   int phase_index = (int) phase; | ||||||
|   if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0)) |   if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0)) | ||||||
|     phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES; |     phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES; | ||||||
|   return phase_names[phase_index]; |   return phase_names[phase_index]; | ||||||
| } | } | ||||||
| #endif |  | ||||||
|  | bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, | ||||||
|  |                    size_t timeout) { | ||||||
|  |   return std::chrono::duration_cast<std::chrono::milliseconds>(now - tpoint).count() > timeout; | ||||||
|  | } | ||||||
|  |  | ||||||
| HaierClimateBase::HaierClimateBase() | HaierClimateBase::HaierClimateBase() | ||||||
|     : haier_protocol_(*this), |     : haier_protocol_(*this), | ||||||
|       protocol_phase_(ProtocolPhases::SENDING_INIT_1), |       protocol_phase_(ProtocolPhases::SENDING_INIT_1), | ||||||
|       action_request_(ActionRequest::NO_ACTION), |  | ||||||
|       display_status_(true), |       display_status_(true), | ||||||
|       health_mode_(false), |       health_mode_(false), | ||||||
|       force_send_control_(false), |       force_send_control_(false), | ||||||
|       forced_publish_(false), |  | ||||||
|       forced_request_status_(false), |       forced_request_status_(false), | ||||||
|       first_control_attempt_(false), |  | ||||||
|       reset_protocol_request_(false), |       reset_protocol_request_(false), | ||||||
|       send_wifi_signal_(true) { |       send_wifi_signal_(true), | ||||||
|  |       use_crc_(false) { | ||||||
|   this->traits_ = climate::ClimateTraits(); |   this->traits_ = climate::ClimateTraits(); | ||||||
|   this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, |   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, |                                      climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, | ||||||
| @@ -84,42 +73,43 @@ HaierClimateBase::~HaierClimateBase() {} | |||||||
|  |  | ||||||
| void HaierClimateBase::set_phase(ProtocolPhases phase) { | void HaierClimateBase::set_phase(ProtocolPhases phase) { | ||||||
|   if (this->protocol_phase_ != 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)); |     ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); | ||||||
| #else |  | ||||||
|     ESP_LOGV(TAG, "Phase transition: %d => %d", (int) this->protocol_phase_, (int) phase); |  | ||||||
| #endif |  | ||||||
|     this->protocol_phase_ = phase; |     this->protocol_phase_ = phase; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::check_timeout_(std::chrono::steady_clock::time_point now, | void HaierClimateBase::reset_phase_() { | ||||||
|                                       std::chrono::steady_clock::time_point tpoint, size_t timeout) { |   this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE | ||||||
|   return std::chrono::duration_cast<std::chrono::milliseconds>(now - tpoint).count() > timeout; |                                                                   : ProtocolPhases::SENDING_INIT_1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HaierClimateBase::reset_to_idle_() { | ||||||
|  |   this->force_send_control_ = false; | ||||||
|  |   if (this->current_hvac_settings_.valid) | ||||||
|  |     this->current_hvac_settings_.reset(); | ||||||
|  |   this->forced_request_status_ = true; | ||||||
|  |   this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |   this->action_request_.reset(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { | bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { | ||||||
|   return this->check_timeout_(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); |   return check_timeout(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { | bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { | ||||||
|   return this->check_timeout_(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); |   return check_timeout(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); | ||||||
| } |  | ||||||
|  |  | ||||||
| bool HaierClimateBase::is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now) { |  | ||||||
|   return this->check_timeout_(now, this->control_request_timestamp_, CONTROL_TIMEOUT_MS); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { | bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { | ||||||
|   return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); |   return check_timeout(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(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); |   return check_timeout(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef USE_WIFI | #ifdef USE_WIFI | ||||||
| haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t message_type) { | haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() { | ||||||
|   static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; |   static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; | ||||||
|   if (wifi::global_wifi_component->is_connected()) { |   if (wifi::global_wifi_component->is_connected()) { | ||||||
|     wifi_status_data[1] = 0; |     wifi_status_data[1] = 0; | ||||||
| @@ -131,7 +121,8 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_(uint8_t | |||||||
|     wifi_status_data[1] = 1; |     wifi_status_data[1] = 1; | ||||||
|     wifi_status_data[3] = 0; |     wifi_status_data[3] = 0; | ||||||
|   } |   } | ||||||
|   return haier_protocol::HaierMessage(message_type, wifi_status_data, sizeof(wifi_status_data)); |   return haier_protocol::HaierMessage(haier_protocol::FrameType::REPORT_NETWORK_STATUS, wifi_status_data, | ||||||
|  |                                       sizeof(wifi_status_data)); | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -140,7 +131,7 @@ bool HaierClimateBase::get_display_state() const { return this->display_status_; | |||||||
| void HaierClimateBase::set_display_state(bool state) { | void HaierClimateBase::set_display_state(bool state) { | ||||||
|   if (this->display_status_ != state) { |   if (this->display_status_ != state) { | ||||||
|     this->display_status_ = state; |     this->display_status_ = state; | ||||||
|     this->set_force_send_control_(true); |     this->force_send_control_ = true; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -149,15 +140,24 @@ bool HaierClimateBase::get_health_mode() const { return this->health_mode_; } | |||||||
| void HaierClimateBase::set_health_mode(bool state) { | void HaierClimateBase::set_health_mode(bool state) { | ||||||
|   if (this->health_mode_ != state) { |   if (this->health_mode_ != state) { | ||||||
|     this->health_mode_ = state; |     this->health_mode_ = state; | ||||||
|     this->set_force_send_control_(true); |     this->force_send_control_ = true; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionRequest::TURN_POWER_ON; } | void HaierClimateBase::send_power_on_command() { | ||||||
|  |   this->action_request_ = | ||||||
|  |       PendingAction({ActionRequest::TURN_POWER_ON, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } | void HaierClimateBase::send_power_off_command() { | ||||||
|  |   this->action_request_ = | ||||||
|  |       PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } | void HaierClimateBase::toggle_power() { | ||||||
|  |   this->action_request_ = | ||||||
|  |       PendingAction({ActionRequest::TOGGLE_POWER, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { | void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) { | ||||||
|   this->traits_.set_supported_swing_modes(modes); |   this->traits_.set_supported_swing_modes(modes); | ||||||
| @@ -165,9 +165,7 @@ void HaierClimateBase::set_supported_swing_modes(const std::set<climate::Climate | |||||||
|     this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); |     this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_answer_timeout(uint32_t timeout) { | void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); } | ||||||
|   this->answer_timeout_ = std::chrono::milliseconds(timeout); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { | void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) { | ||||||
|   this->traits_.set_supported_modes(modes); |   this->traits_.set_supported_modes(modes); | ||||||
| @@ -183,29 +181,42 @@ void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePres | |||||||
|  |  | ||||||
| void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } | 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, | void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &message) { | ||||||
|                                                                   uint8_t expected_request_message_type, |   this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); | ||||||
|                                                                   uint8_t answer_message_type, | } | ||||||
|                                                                   uint8_t expected_answer_message_type, |  | ||||||
|                                                                   ProtocolPhases expected_phase) { | haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( | ||||||
|  |     haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, | ||||||
|  |     haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, | ||||||
|  |     ProtocolPhases expected_phase) { | ||||||
|   haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; |   haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; | ||||||
|   if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) |   if ((expected_request_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) && | ||||||
|  |       (request_message_type != expected_request_message_type)) | ||||||
|     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; |     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||||
|   if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) |   if ((expected_answer_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) && | ||||||
|  |       (answer_message_type != expected_answer_message_type)) | ||||||
|     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; |     result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||||
|   if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) |   if (!this->haier_protocol_.is_waiting_for_answer() || | ||||||
|  |       ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_))) | ||||||
|     result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; |     result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||||
|   if (is_message_invalid(answer_message_type)) |   if (answer_message_type == haier_protocol::FrameType::INVALID) | ||||||
|     result = haier_protocol::HandlerError::INVALID_ANSWER; |     result = haier_protocol::HandlerError::INVALID_ANSWER; | ||||||
|   return result; |   return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t request_type) { | haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_handler_( | ||||||
| #if (HAIER_LOG_LEVEL > 4) |     haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|   ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", request_type, phase_to_string_(this->protocol_phase_)); |     size_t data_size) { | ||||||
| #else |   haier_protocol::HandlerError result = | ||||||
|   ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); |       this->answer_preprocess_(request_type, haier_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, | ||||||
| #endif |                                haier_protocol::FrameType::CONFIRM, ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||||
|  |   this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |   return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) { | ||||||
|  |   ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type, | ||||||
|  |            phase_to_string_(this->protocol_phase_)); | ||||||
|   if (this->protocol_phase_ > ProtocolPhases::IDLE) { |   if (this->protocol_phase_ > ProtocolPhases::IDLE) { | ||||||
|     this->set_phase(ProtocolPhases::IDLE); |     this->set_phase(ProtocolPhases::IDLE); | ||||||
|   } else { |   } else { | ||||||
| @@ -219,79 +230,95 @@ void HaierClimateBase::setup() { | |||||||
|   // Set timestamp here to give AC time to boot |   // Set timestamp here to give AC time to boot | ||||||
|   this->last_request_timestamp_ = std::chrono::steady_clock::now(); |   this->last_request_timestamp_ = std::chrono::steady_clock::now(); | ||||||
|   this->set_phase(ProtocolPhases::SENDING_INIT_1); |   this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|   this->set_handlers(); |  | ||||||
|   this->haier_protocol_.set_default_timeout_handler( |   this->haier_protocol_.set_default_timeout_handler( | ||||||
|       std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); |       std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); | ||||||
|  |   this->set_handlers(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::dump_config() { | void HaierClimateBase::dump_config() { | ||||||
|   LOG_CLIMATE("", "Haier Climate", this); |   LOG_CLIMATE("", "Haier Climate", this); | ||||||
|   ESP_LOGCONFIG(TAG, "  Device communication status: %s", |   ESP_LOGCONFIG(TAG, "  Device communication status: %s", this->valid_connection() ? "established" : "none"); | ||||||
|                 (this->protocol_phase_ >= ProtocolPhases::IDLE) ? "established" : "none"); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::loop() { | void HaierClimateBase::loop() { | ||||||
|   std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); |   std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); | ||||||
|   if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_valid_status_timestamp_).count() > |   if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_valid_status_timestamp_).count() > | ||||||
|        COMMUNICATION_TIMEOUT_MS) || |        COMMUNICATION_TIMEOUT_MS) || | ||||||
|       (this->reset_protocol_request_)) { |       (this->reset_protocol_request_ && (!this->haier_protocol_.is_waiting_for_answer()))) { | ||||||
|  |     this->last_valid_status_timestamp_ = now; | ||||||
|     if (this->protocol_phase_ >= ProtocolPhases::IDLE) { |     if (this->protocol_phase_ >= ProtocolPhases::IDLE) { | ||||||
|       // No status too long, reseting protocol |       // No status too long, reseting protocol | ||||||
|  |       // No need to reset protocol if we didn't pass initialization phase | ||||||
|       if (this->reset_protocol_request_) { |       if (this->reset_protocol_request_) { | ||||||
|         this->reset_protocol_request_ = false; |         this->reset_protocol_request_ = false; | ||||||
|         ESP_LOGW(TAG, "Protocol reset requested"); |         ESP_LOGW(TAG, "Protocol reset requested"); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "Communication timeout, reseting protocol"); |         ESP_LOGW(TAG, "Communication timeout, reseting protocol"); | ||||||
|       } |       } | ||||||
|       this->last_valid_status_timestamp_ = now; |       this->process_protocol_reset(); | ||||||
|       this->set_force_send_control_(false); |  | ||||||
|       if (this->hvac_settings_.valid) |  | ||||||
|         this->hvac_settings_.reset(); |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); |  | ||||||
|       return; |       return; | ||||||
|     } else { |  | ||||||
|       // No need to reset protocol if we didn't pass initialization phase |  | ||||||
|       this->last_valid_status_timestamp_ = now; |  | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|   if ((this->protocol_phase_ == ProtocolPhases::IDLE) || |   if ((!this->haier_protocol_.is_waiting_for_answer()) && | ||||||
|       (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || |       ((this->protocol_phase_ == ProtocolPhases::IDLE) || | ||||||
|       (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || |        (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || | ||||||
|       (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL)) { |        (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || | ||||||
|  |        (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL))) { | ||||||
|     // If control message or action is pending we should send it ASAP unless we are in initialisation |     // If control message or action is pending we should send it ASAP unless we are in initialisation | ||||||
|     // procedure or waiting for an answer |     // procedure or waiting for an answer | ||||||
|     if (this->action_request_ != ActionRequest::NO_ACTION) { |     if (this->action_request_.has_value() && this->prepare_pending_action()) { | ||||||
|       this->process_pending_action(); |       this->set_phase(ProtocolPhases::SENDING_ACTION_COMMAND); | ||||||
|     } else if (this->hvac_settings_.valid || this->force_send_control_) { |     } else if (this->next_hvac_settings_.valid || this->force_send_control_) { | ||||||
|       ESP_LOGV(TAG, "Control packet is pending..."); |       ESP_LOGV(TAG, "Control packet is pending..."); | ||||||
|       this->set_phase(ProtocolPhases::SENDING_CONTROL); |       this->set_phase(ProtocolPhases::SENDING_CONTROL); | ||||||
|  |       if (this->next_hvac_settings_.valid) { | ||||||
|  |         this->current_hvac_settings_ = this->next_hvac_settings_; | ||||||
|  |         this->next_hvac_settings_.reset(); | ||||||
|  |       } else { | ||||||
|  |         this->current_hvac_settings_.reset(); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   this->process_phase(now); |   this->process_phase(now); | ||||||
|   this->haier_protocol_.loop(); |   this->haier_protocol_.loop(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::process_pending_action() { | void HaierClimateBase::process_protocol_reset() { | ||||||
|   ActionRequest request = this->action_request_; |   this->force_send_control_ = false; | ||||||
|   if (this->action_request_ == ActionRequest::TOGGLE_POWER) { |   if (this->current_hvac_settings_.valid) | ||||||
|     request = this->mode == CLIMATE_MODE_OFF ? ActionRequest::TURN_POWER_ON : ActionRequest::TURN_POWER_OFF; |     this->current_hvac_settings_.reset(); | ||||||
|   } |   if (this->next_hvac_settings_.valid) | ||||||
|   switch (request) { |     this->next_hvac_settings_.reset(); | ||||||
|     case ActionRequest::TURN_POWER_ON: |   this->mode = CLIMATE_MODE_OFF; | ||||||
|       this->set_phase(ProtocolPhases::SENDING_POWER_ON_COMMAND); |   this->current_temperature = NAN; | ||||||
|       break; |   this->target_temperature = NAN; | ||||||
|     case ActionRequest::TURN_POWER_OFF: |   this->fan_mode.reset(); | ||||||
|       this->set_phase(ProtocolPhases::SENDING_POWER_OFF_COMMAND); |   this->preset.reset(); | ||||||
|       break; |   this->publish_state(); | ||||||
|     case ActionRequest::TOGGLE_POWER: |   this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|     case ActionRequest::NO_ACTION: | } | ||||||
|       // shouldn't get here, do nothing |  | ||||||
|       break; | bool HaierClimateBase::prepare_pending_action() { | ||||||
|     default: |   if (this->action_request_.has_value()) { | ||||||
|       ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_); |     switch (this->action_request_.value().action) { | ||||||
|       break; |       case ActionRequest::SEND_CUSTOM_COMMAND: | ||||||
|   } |         return true; | ||||||
|   this->action_request_ = ActionRequest::NO_ACTION; |       case ActionRequest::TURN_POWER_ON: | ||||||
|  |         this->action_request_.value().message = this->get_power_message(true); | ||||||
|  |         return true; | ||||||
|  |       case ActionRequest::TURN_POWER_OFF: | ||||||
|  |         this->action_request_.value().message = this->get_power_message(false); | ||||||
|  |         return true; | ||||||
|  |       case ActionRequest::TOGGLE_POWER: | ||||||
|  |         this->action_request_.value().message = this->get_power_message(this->mode == ClimateMode::CLIMATE_MODE_OFF); | ||||||
|  |         return true; | ||||||
|  |       default: | ||||||
|  |         ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_.value().action); | ||||||
|  |         this->action_request_.reset(); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |   } else | ||||||
|  |     return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| ClimateTraits HaierClimateBase::traits() { return traits_; } | ClimateTraits HaierClimateBase::traits() { return traits_; } | ||||||
| @@ -302,23 +329,22 @@ void HaierClimateBase::control(const ClimateCall &call) { | |||||||
|     ESP_LOGW(TAG, "Can't send control packet, first poll answer not received"); |     ESP_LOGW(TAG, "Can't send control packet, first poll answer not received"); | ||||||
|     return;  // cancel the control, we cant do it without a poll answer. |     return;  // cancel the control, we cant do it without a poll answer. | ||||||
|   } |   } | ||||||
|   if (this->hvac_settings_.valid) { |   if (this->current_hvac_settings_.valid) { | ||||||
|     ESP_LOGW(TAG, "Overriding old valid settings before they were applied!"); |     ESP_LOGW(TAG, "New settings come faster then processed!"); | ||||||
|   } |   } | ||||||
|   { |   { | ||||||
|     if (call.get_mode().has_value()) |     if (call.get_mode().has_value()) | ||||||
|       this->hvac_settings_.mode = call.get_mode(); |       this->next_hvac_settings_.mode = call.get_mode(); | ||||||
|     if (call.get_fan_mode().has_value()) |     if (call.get_fan_mode().has_value()) | ||||||
|       this->hvac_settings_.fan_mode = call.get_fan_mode(); |       this->next_hvac_settings_.fan_mode = call.get_fan_mode(); | ||||||
|     if (call.get_swing_mode().has_value()) |     if (call.get_swing_mode().has_value()) | ||||||
|       this->hvac_settings_.swing_mode = call.get_swing_mode(); |       this->next_hvac_settings_.swing_mode = call.get_swing_mode(); | ||||||
|     if (call.get_target_temperature().has_value()) |     if (call.get_target_temperature().has_value()) | ||||||
|       this->hvac_settings_.target_temperature = call.get_target_temperature(); |       this->next_hvac_settings_.target_temperature = call.get_target_temperature(); | ||||||
|     if (call.get_preset().has_value()) |     if (call.get_preset().has_value()) | ||||||
|       this->hvac_settings_.preset = call.get_preset(); |       this->next_hvac_settings_.preset = call.get_preset(); | ||||||
|     this->hvac_settings_.valid = true; |     this->next_hvac_settings_.valid = true; | ||||||
|   } |   } | ||||||
|   this->first_control_attempt_ = true; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::HvacSettings::reset() { | void HaierClimateBase::HvacSettings::reset() { | ||||||
| @@ -330,19 +356,9 @@ void HaierClimateBase::HvacSettings::reset() { | |||||||
|   this->preset.reset(); |   this->preset.reset(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_force_send_control_(bool status) { | void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats, | ||||||
|   this->force_send_control_ = status; |                                      std::chrono::milliseconds interval) { | ||||||
|   if (status) { |   this->haier_protocol_.send_message(command, use_crc, num_repeats, interval); | ||||||
|     this->first_control_attempt_ = true; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool 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(); |   this->last_request_timestamp_ = std::chrono::steady_clock::now(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ namespace esphome { | |||||||
| namespace haier { | namespace haier { | ||||||
|  |  | ||||||
| enum class ActionRequest : uint8_t { | enum class ActionRequest : uint8_t { | ||||||
|   NO_ACTION = 0, |   SEND_CUSTOM_COMMAND = 0, | ||||||
|   TURN_POWER_ON = 1, |   TURN_POWER_ON = 1, | ||||||
|   TURN_POWER_OFF = 2, |   TURN_POWER_OFF = 2, | ||||||
|   TOGGLE_POWER = 3, |   TOGGLE_POWER = 3, | ||||||
| @@ -33,7 +33,6 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   void control(const esphome::climate::ClimateCall &call) override; |   void control(const esphome::climate::ClimateCall &call) override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } |   float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } | ||||||
|   void set_fahrenheit(bool fahrenheit); |  | ||||||
|   void set_display_state(bool state); |   void set_display_state(bool state); | ||||||
|   bool get_display_state() const; |   bool get_display_state() const; | ||||||
|   void set_health_mode(bool state); |   void set_health_mode(bool state); | ||||||
| @@ -45,6 +44,7 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes); |   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_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes); | ||||||
|   void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); |   void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets); | ||||||
|  |   bool valid_connection() { return this->protocol_phase_ >= ProtocolPhases::IDLE; }; | ||||||
|   size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; |   size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; | ||||||
|   size_t read_array(uint8_t *data, size_t len) noexcept override { |   size_t read_array(uint8_t *data, size_t len) noexcept override { | ||||||
|     return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; |     return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; | ||||||
| @@ -55,63 +55,56 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; |   bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; | ||||||
|   void set_answer_timeout(uint32_t timeout); |   void set_answer_timeout(uint32_t timeout); | ||||||
|   void set_send_wifi(bool send_wifi); |   void set_send_wifi(bool send_wifi); | ||||||
|  |   void send_custom_command(const haier_protocol::HaierMessage &message); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   enum class ProtocolPhases { |   enum class ProtocolPhases { | ||||||
|     UNKNOWN = -1, |     UNKNOWN = -1, | ||||||
|     // INITIALIZATION |     // INITIALIZATION | ||||||
|     SENDING_INIT_1 = 0, |     SENDING_INIT_1 = 0, | ||||||
|     WAITING_INIT_1_ANSWER = 1, |     SENDING_INIT_2, | ||||||
|     SENDING_INIT_2 = 2, |     SENDING_FIRST_STATUS_REQUEST, | ||||||
|     WAITING_INIT_2_ANSWER = 3, |     SENDING_ALARM_STATUS_REQUEST, | ||||||
|     SENDING_FIRST_STATUS_REQUEST = 4, |  | ||||||
|     WAITING_FIRST_STATUS_ANSWER = 5, |  | ||||||
|     SENDING_ALARM_STATUS_REQUEST = 6, |  | ||||||
|     WAITING_ALARM_STATUS_ANSWER = 7, |  | ||||||
|     // FUNCTIONAL STATE |     // FUNCTIONAL STATE | ||||||
|     IDLE = 8, |     IDLE, | ||||||
|     SENDING_STATUS_REQUEST = 10, |     SENDING_STATUS_REQUEST, | ||||||
|     WAITING_STATUS_ANSWER = 11, |     SENDING_UPDATE_SIGNAL_REQUEST, | ||||||
|     SENDING_UPDATE_SIGNAL_REQUEST = 12, |     SENDING_SIGNAL_LEVEL, | ||||||
|     WAITING_UPDATE_SIGNAL_ANSWER = 13, |     SENDING_CONTROL, | ||||||
|     SENDING_SIGNAL_LEVEL = 14, |     SENDING_ACTION_COMMAND, | ||||||
|     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 |     NUM_PROTOCOL_PHASES | ||||||
|   }; |   }; | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|   const char *phase_to_string_(ProtocolPhases phase); |   const char *phase_to_string_(ProtocolPhases phase); | ||||||
| #endif |  | ||||||
|   virtual void set_handlers() = 0; |   virtual void set_handlers() = 0; | ||||||
|   virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; |   virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; | ||||||
|   virtual haier_protocol::HaierMessage get_control_message() = 0; |   virtual haier_protocol::HaierMessage get_control_message() = 0; | ||||||
|   virtual bool is_message_invalid(uint8_t message_type) = 0; |   virtual haier_protocol::HaierMessage get_power_message(bool state) = 0; | ||||||
|   virtual void process_pending_action(); |   virtual bool prepare_pending_action(); | ||||||
|  |   virtual void process_protocol_reset(); | ||||||
|   esphome::climate::ClimateTraits traits() override; |   esphome::climate::ClimateTraits traits() override; | ||||||
|   // Answers handlers |   // Answer handlers | ||||||
|   haier_protocol::HandlerError answer_preprocess_(uint8_t request_message_type, uint8_t expected_request_message_type, |   haier_protocol::HandlerError answer_preprocess_(haier_protocol::FrameType request_message_type, | ||||||
|                                                   uint8_t answer_message_type, uint8_t expected_answer_message_type, |                                                   haier_protocol::FrameType expected_request_message_type, | ||||||
|  |                                                   haier_protocol::FrameType answer_message_type, | ||||||
|  |                                                   haier_protocol::FrameType expected_answer_message_type, | ||||||
|                                                   ProtocolPhases expected_phase); |                                                   ProtocolPhases expected_phase); | ||||||
|  |   haier_protocol::HandlerError report_network_status_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                      haier_protocol::FrameType message_type, | ||||||
|  |                                                                      const uint8_t *data, size_t data_size); | ||||||
|   // Timeout handler |   // Timeout handler | ||||||
|   haier_protocol::HandlerError timeout_default_handler_(uint8_t request_type); |   haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type); | ||||||
|   // Helper functions |   // Helper functions | ||||||
|   void set_force_send_control_(bool status); |   void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats = 0, | ||||||
|   void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); |                      std::chrono::milliseconds interval = std::chrono::milliseconds::zero()); | ||||||
|   virtual 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, |   void reset_phase_(); | ||||||
|                       size_t timeout); |   void reset_to_idle_(); | ||||||
|   bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); |   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_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_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||||
|   bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); |   bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now); | ||||||
| #ifdef USE_WIFI | #ifdef USE_WIFI | ||||||
|   haier_protocol::HaierMessage get_wifi_signal_message_(uint8_t message_type); |   haier_protocol::HaierMessage get_wifi_signal_message_(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   struct HvacSettings { |   struct HvacSettings { | ||||||
| @@ -122,29 +115,34 @@ class HaierClimateBase : public esphome::Component, | |||||||
|     esphome::optional<esphome::climate::ClimatePreset> preset; |     esphome::optional<esphome::climate::ClimatePreset> preset; | ||||||
|     bool valid; |     bool valid; | ||||||
|     HvacSettings() : valid(false){}; |     HvacSettings() : valid(false){}; | ||||||
|  |     HvacSettings(const HvacSettings &) = default; | ||||||
|  |     HvacSettings &operator=(const HvacSettings &) = default; | ||||||
|     void reset(); |     void reset(); | ||||||
|   }; |   }; | ||||||
|  |   struct PendingAction { | ||||||
|  |     ActionRequest action; | ||||||
|  |     esphome::optional<haier_protocol::HaierMessage> message; | ||||||
|  |   }; | ||||||
|   haier_protocol::ProtocolHandler haier_protocol_; |   haier_protocol::ProtocolHandler haier_protocol_; | ||||||
|   ProtocolPhases protocol_phase_; |   ProtocolPhases protocol_phase_; | ||||||
|   ActionRequest action_request_; |   esphome::optional<PendingAction> action_request_; | ||||||
|   uint8_t fan_mode_speed_; |   uint8_t fan_mode_speed_; | ||||||
|   uint8_t other_modes_fan_speed_; |   uint8_t other_modes_fan_speed_; | ||||||
|   bool display_status_; |   bool display_status_; | ||||||
|   bool health_mode_; |   bool health_mode_; | ||||||
|   bool force_send_control_; |   bool force_send_control_; | ||||||
|   bool forced_publish_; |  | ||||||
|   bool forced_request_status_; |   bool forced_request_status_; | ||||||
|   bool first_control_attempt_; |  | ||||||
|   bool reset_protocol_request_; |   bool reset_protocol_request_; | ||||||
|  |   bool send_wifi_signal_; | ||||||
|  |   bool use_crc_; | ||||||
|   esphome::climate::ClimateTraits traits_; |   esphome::climate::ClimateTraits traits_; | ||||||
|   HvacSettings hvac_settings_; |   HvacSettings current_hvac_settings_; | ||||||
|  |   HvacSettings next_hvac_settings_; | ||||||
|  |   std::unique_ptr<uint8_t[]> last_status_message_; | ||||||
|   std::chrono::steady_clock::time_point last_request_timestamp_;       // For interval between messages |   std::chrono::steady_clock::time_point last_request_timestamp_;       // For interval between messages | ||||||
|   std::chrono::steady_clock::time_point last_valid_status_timestamp_;  // For protocol timeout |   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 last_status_request_;          // To request AC status | ||||||
|   std::chrono::steady_clock::time_point control_request_timestamp_;    // To send control message |   std::chrono::steady_clock::time_point last_signal_request_;          // To send WiFI signal level | ||||||
|   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 | }  // namespace haier | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ namespace haier { | |||||||
| static const char *const TAG = "haier.climate"; | static const char *const TAG = "haier.climate"; | ||||||
| constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | ||||||
| constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; | constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; | ||||||
|  | constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; | ||||||
|  | constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); | ||||||
|  |  | ||||||
| hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { | hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { | ||||||
|   switch (direction) { |   switch (direction) { | ||||||
| @@ -48,14 +50,11 @@ hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDir | |||||||
| } | } | ||||||
|  |  | ||||||
| HonClimate::HonClimate() | HonClimate::HonClimate() | ||||||
|     : last_status_message_(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]), |     : cleaning_status_(CleaningState::NO_CLEANING), | ||||||
|       cleaning_status_(CleaningState::NO_CLEANING), |  | ||||||
|       got_valid_outdoor_temp_(false), |       got_valid_outdoor_temp_(false), | ||||||
|       hvac_hardware_info_available_(false), |  | ||||||
|       hvac_functions_{false, false, false, false, false}, |  | ||||||
|       use_crc_(hvac_functions_[2]), |  | ||||||
|       active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, |       active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, | ||||||
|       outdoor_sensor_(nullptr) { |       outdoor_sensor_(nullptr) { | ||||||
|  |   last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]); | ||||||
|   this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; |   this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; | ||||||
|   this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; |   this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; | ||||||
| } | } | ||||||
| @@ -72,14 +71,14 @@ AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this- | |||||||
|  |  | ||||||
| void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { | void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { | ||||||
|   this->vertical_direction_ = direction; |   this->vertical_direction_ = direction; | ||||||
|   this->set_force_send_control_(true); |   this->force_send_control_ = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } | AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } | ||||||
|  |  | ||||||
| void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { | void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { | ||||||
|   this->horizontal_direction_ = direction; |   this->horizontal_direction_ = direction; | ||||||
|   this->set_force_send_control_(true); |   this->force_send_control_ = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string HonClimate::get_cleaning_status_text() const { | std::string HonClimate::get_cleaning_status_text() const { | ||||||
| @@ -98,35 +97,35 @@ CleaningState HonClimate::get_cleaning_status() const { return this->cleaning_st | |||||||
| void HonClimate::start_self_cleaning() { | void HonClimate::start_self_cleaning() { | ||||||
|   if (this->cleaning_status_ == CleaningState::NO_CLEANING) { |   if (this->cleaning_status_ == CleaningState::NO_CLEANING) { | ||||||
|     ESP_LOGI(TAG, "Sending self cleaning start request"); |     ESP_LOGI(TAG, "Sending self cleaning start request"); | ||||||
|     this->action_request_ = ActionRequest::START_SELF_CLEAN; |     this->action_request_ = | ||||||
|     this->set_force_send_control_(true); |         PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void HonClimate::start_steri_cleaning() { | void HonClimate::start_steri_cleaning() { | ||||||
|   if (this->cleaning_status_ == CleaningState::NO_CLEANING) { |   if (this->cleaning_status_ == CleaningState::NO_CLEANING) { | ||||||
|     ESP_LOGI(TAG, "Sending steri cleaning start request"); |     ESP_LOGI(TAG, "Sending steri cleaning start request"); | ||||||
|     this->action_request_ = ActionRequest::START_STERI_CLEAN; |     this->action_request_ = | ||||||
|     this->set_force_send_control_(true); |         PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, | haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                             haier_protocol::FrameType message_type, | ||||||
|                                                                             const uint8_t *data, size_t data_size) { |                                                                             const uint8_t *data, size_t data_size) { | ||||||
|   // Should check this before preprocess |   // Should check this before preprocess | ||||||
|   if (message_type == (uint8_t) hon_protocol::FrameType::INVALID) { |   if (message_type == haier_protocol::FrameType::INVALID) { | ||||||
|     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " |     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 " | ||||||
|                   "protocol instead of hOn"); |                   "protocol instead of hOn"); | ||||||
|     this->set_phase(ProtocolPhases::SENDING_INIT_1); |     this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|     return haier_protocol::HandlerError::INVALID_ANSWER; |     return haier_protocol::HandlerError::INVALID_ANSWER; | ||||||
|   } |   } | ||||||
|   haier_protocol::HandlerError result = this->answer_preprocess_( |   haier_protocol::HandlerError result = | ||||||
|       request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, |       this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type, | ||||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_INIT_1_ANSWER); |                                haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1); | ||||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { |   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|     if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { |     if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { | ||||||
|       // Wrong structure |       // Wrong structure | ||||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); |  | ||||||
|       return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; |       return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||||
|     } |     } | ||||||
|     // All OK |     // All OK | ||||||
| @@ -134,54 +133,57 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint | |||||||
|     char tmp[9]; |     char tmp[9]; | ||||||
|     tmp[8] = 0; |     tmp[8] = 0; | ||||||
|     strncpy(tmp, answr->protocol_version, 8); |     strncpy(tmp, answr->protocol_version, 8); | ||||||
|     this->hvac_protocol_version_ = std::string(tmp); |     this->hvac_hardware_info_ = HardwareInfo(); | ||||||
|  |     this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp); | ||||||
|     strncpy(tmp, answr->software_version, 8); |     strncpy(tmp, answr->software_version, 8); | ||||||
|     this->hvac_software_version_ = std::string(tmp); |     this->hvac_hardware_info_.value().software_version_ = std::string(tmp); | ||||||
|     strncpy(tmp, answr->hardware_version, 8); |     strncpy(tmp, answr->hardware_version, 8); | ||||||
|     this->hvac_hardware_version_ = std::string(tmp); |     this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp); | ||||||
|     strncpy(tmp, answr->device_name, 8); |     strncpy(tmp, answr->device_name, 8); | ||||||
|     this->hvac_device_name_ = std::string(tmp); |     this->hvac_hardware_info_.value().device_name_ = std::string(tmp); | ||||||
|     this->hvac_functions_[0] = (answr->functions[1] & 0x01) != 0;  // interactive mode support |     this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0;  // interactive mode support | ||||||
|     this->hvac_functions_[1] = (answr->functions[1] & 0x02) != 0;  // controller-device mode support |     this->hvac_hardware_info_.value().functions_[1] = | ||||||
|     this->hvac_functions_[2] = (answr->functions[1] & 0x04) != 0;  // crc support |         (answr->functions[1] & 0x02) != 0;  // controller-device mode support | ||||||
|     this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0;  // multiple AC support |     this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0;  // crc support | ||||||
|     this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0;  // roles support |     this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0;  // multiple AC support | ||||||
|     this->hvac_hardware_info_available_ = true; |     this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0;  // roles support | ||||||
|  |     this->use_crc_ = this->hvac_hardware_info_.value().functions_[2]; | ||||||
|     this->set_phase(ProtocolPhases::SENDING_INIT_2); |     this->set_phase(ProtocolPhases::SENDING_INIT_2); | ||||||
|     return result; |     return result; | ||||||
|   } else { |   } else { | ||||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |     this->reset_phase_(); | ||||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); |  | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, | haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                        haier_protocol::FrameType message_type, | ||||||
|                                                                        const uint8_t *data, size_t data_size) { |                                                                        const uint8_t *data, size_t data_size) { | ||||||
|   haier_protocol::HandlerError result = this->answer_preprocess_( |   haier_protocol::HandlerError result = | ||||||
|       request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, |       this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type, | ||||||
|       (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_INIT_2_ANSWER); |                                haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2); | ||||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { |   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; |     return result; | ||||||
|   } else { |   } else { | ||||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |     this->reset_phase_(); | ||||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); |  | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, uint8_t message_type, | haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type, | ||||||
|                                                          const uint8_t *data, size_t data_size) { |                                                          haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|  |                                                          size_t data_size) { | ||||||
|   haier_protocol::HandlerError result = |   haier_protocol::HandlerError result = | ||||||
|       this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::CONTROL, message_type, |       this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type, | ||||||
|                                (uint8_t) hon_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); |                                haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); | ||||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { |   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|     result = this->process_status_message_(data, data_size); |     result = this->process_status_message_(data, data_size); | ||||||
|     if (result != haier_protocol::HandlerError::HANDLER_OK) { |     if (result != haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); |       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); | ||||||
|       this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |       this->reset_phase_(); | ||||||
|                                                                       : ProtocolPhases::SENDING_INIT_1); |       this->action_request_.reset(); | ||||||
|  |       this->force_send_control_ = false; | ||||||
|     } else { |     } else { | ||||||
|       if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { |       if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { | ||||||
|         memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); |         memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); | ||||||
| @@ -189,36 +191,48 @@ haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, u | |||||||
|         ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, |         ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, | ||||||
|                  sizeof(hon_protocol::HaierPacketControl)); |                  sizeof(hon_protocol::HaierPacketControl)); | ||||||
|       } |       } | ||||||
|       if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { |       switch (this->protocol_phase_) { | ||||||
|         ESP_LOGI(TAG, "First HVAC status received"); |         case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||||
|         this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); |           ESP_LOGI(TAG, "First HVAC status received"); | ||||||
|       } else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || |           this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); | ||||||
|                  (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || |           break; | ||||||
|                  (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { |         case ProtocolPhases::SENDING_ACTION_COMMAND: | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |           // Do nothing, phase will be changed in process_phase | ||||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { |           break; | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |         case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||||
|         this->set_force_send_control_(false); |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|         if (this->hvac_settings_.valid) |           break; | ||||||
|           this->hvac_settings_.reset(); |         case ProtocolPhases::SENDING_CONTROL: | ||||||
|  |           if (!this->control_messages_queue_.empty()) | ||||||
|  |             this->control_messages_queue_.pop(); | ||||||
|  |           if (this->control_messages_queue_.empty()) { | ||||||
|  |             this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |             this->force_send_control_ = false; | ||||||
|  |             if (this->current_hvac_settings_.valid) | ||||||
|  |               this->current_hvac_settings_.reset(); | ||||||
|  |           } else { | ||||||
|  |             this->set_phase(ProtocolPhases::SENDING_CONTROL); | ||||||
|  |           } | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
|   } else { |   } else { | ||||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |     this->action_request_.reset(); | ||||||
|                                                                     : ProtocolPhases::SENDING_INIT_1); |     this->force_send_control_ = false; | ||||||
|  |     this->reset_phase_(); | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_(uint8_t request_type, | haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_( | ||||||
|                                                                                     uint8_t message_type, |     haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|                                                                                     const uint8_t *data, |     size_t data_size) { | ||||||
|                                                                                     size_t data_size) { |   haier_protocol::HandlerError result = this->answer_preprocess_( | ||||||
|   haier_protocol::HandlerError result = |       request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type, | ||||||
|       this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION, |       haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); | ||||||
|                                message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, |  | ||||||
|                                ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); |  | ||||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { |   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|     this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); |     this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||||
|     return result; |     return result; | ||||||
| @@ -228,25 +242,16 @@ haier_protocol::HandlerError HonClimate::get_management_information_answer_handl | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(uint8_t request_type, | haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|                                                                                uint8_t message_type, |                                                                           haier_protocol::FrameType message_type, | ||||||
|                                                                                const uint8_t *data, size_t data_size) { |  | ||||||
|   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); |  | ||||||
|   return result; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, |  | ||||||
|                                                                           const uint8_t *data, size_t data_size) { |                                                                           const uint8_t *data, size_t data_size) { | ||||||
|   if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { |   if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) { | ||||||
|     if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { |     if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { | ||||||
|       // Unexpected answer to request |       // Unexpected answer to request | ||||||
|       this->set_phase(ProtocolPhases::IDLE); |       this->set_phase(ProtocolPhases::IDLE); | ||||||
|       return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; |       return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||||
|     } |     } | ||||||
|     if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { |     if (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) { | ||||||
|       // Don't expect this answer now |       // Don't expect this answer now | ||||||
|       this->set_phase(ProtocolPhases::IDLE); |       this->set_phase(ProtocolPhases::IDLE); | ||||||
|       return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; |       return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||||
| @@ -263,27 +268,27 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_ | |||||||
| void HonClimate::set_handlers() { | void HonClimate::set_handlers() { | ||||||
|   // Set handlers |   // Set handlers | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), |       haier_protocol::FrameType::GET_DEVICE_VERSION, | ||||||
|       std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, |       std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||||
|                 std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::GET_DEVICE_ID), |       haier_protocol::FrameType::GET_DEVICE_ID, | ||||||
|       std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, |       std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||||
|                 std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::CONTROL), |       haier_protocol::FrameType::CONTROL, | ||||||
|       std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, |       std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, | ||||||
|                 std::placeholders::_4)); |                 std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION), |       haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, | ||||||
|       std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, |       std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, | ||||||
|                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::GET_ALARM_STATUS), |       haier_protocol::FrameType::GET_ALARM_STATUS, | ||||||
|       std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, |       std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||||
|                 std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (hon_protocol::FrameType::REPORT_NETWORK_STATUS), |       haier_protocol::FrameType::REPORT_NETWORK_STATUS, | ||||||
|       std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, |       std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||||
|                 std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_3, std::placeholders::_4)); | ||||||
| } | } | ||||||
| @@ -291,14 +296,18 @@ void HonClimate::set_handlers() { | |||||||
| void HonClimate::dump_config() { | void HonClimate::dump_config() { | ||||||
|   HaierClimateBase::dump_config(); |   HaierClimateBase::dump_config(); | ||||||
|   ESP_LOGCONFIG(TAG, "  Protocol version: hOn"); |   ESP_LOGCONFIG(TAG, "  Protocol version: hOn"); | ||||||
|   if (this->hvac_hardware_info_available_) { |   ESP_LOGCONFIG(TAG, "  Control method: %d", (uint8_t) this->control_method_); | ||||||
|     ESP_LOGCONFIG(TAG, "  Device protocol version: %s", this->hvac_protocol_version_.c_str()); |   if (this->hvac_hardware_info_.has_value()) { | ||||||
|     ESP_LOGCONFIG(TAG, "  Device software version: %s", this->hvac_software_version_.c_str()); |     ESP_LOGCONFIG(TAG, "  Device protocol version: %s", this->hvac_hardware_info_.value().protocol_version_.c_str()); | ||||||
|     ESP_LOGCONFIG(TAG, "  Device hardware version: %s", this->hvac_hardware_version_.c_str()); |     ESP_LOGCONFIG(TAG, "  Device software version: %s", this->hvac_hardware_info_.value().software_version_.c_str()); | ||||||
|     ESP_LOGCONFIG(TAG, "  Device name: %s", this->hvac_device_name_.c_str()); |     ESP_LOGCONFIG(TAG, "  Device hardware version: %s", this->hvac_hardware_info_.value().hardware_version_.c_str()); | ||||||
|     ESP_LOGCONFIG(TAG, "  Device features:%s%s%s%s%s", (this->hvac_functions_[0] ? " interactive" : ""), |     ESP_LOGCONFIG(TAG, "  Device name: %s", this->hvac_hardware_info_.value().device_name_.c_str()); | ||||||
|                   (this->hvac_functions_[1] ? " controller-device" : ""), (this->hvac_functions_[2] ? " crc" : ""), |     ESP_LOGCONFIG(TAG, "  Device features:%s%s%s%s%s", | ||||||
|                   (this->hvac_functions_[3] ? " multinode" : ""), (this->hvac_functions_[4] ? " role" : "")); |                   (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""), | ||||||
|  |                   (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""), | ||||||
|  |                   (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""), | ||||||
|  |                   (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""), | ||||||
|  |                   (this->hvac_hardware_info_.value().functions_[4] ? " role" : "")); | ||||||
|     ESP_LOGCONFIG(TAG, "  Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str()); |     ESP_LOGCONFIG(TAG, "  Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -307,7 +316,6 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | |||||||
|   switch (this->protocol_phase_) { |   switch (this->protocol_phase_) { | ||||||
|     case ProtocolPhases::SENDING_INIT_1: |     case ProtocolPhases::SENDING_INIT_1: | ||||||
|       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { | ||||||
|         this->hvac_hardware_info_available_ = false; |  | ||||||
|         // Indicate device capabilities: |         // Indicate device capabilities: | ||||||
|         // bit 0 - if 1 module support interactive mode |         // bit 0 - if 1 module support interactive mode | ||||||
|         // bit 1 - if 1 module support controller-device mode |         // bit 1 - if 1 module support controller-device mode | ||||||
| @@ -316,109 +324,95 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | |||||||
|         // bit 4..bit 15 - not used |         // bit 4..bit 15 - not used | ||||||
|         uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; |         uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; | ||||||
|         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( |         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( | ||||||
|             (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); |             haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); | ||||||
|         this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); |         this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); | ||||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_INIT_2: |     case ProtocolPhases::SENDING_INIT_2: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       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); |         static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID); | ||||||
|         this->send_message_(DEVICEID_REQUEST, this->use_crc_); |         this->send_message_(DEVICEID_REQUEST, this->use_crc_); | ||||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_2_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: |     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||||
|     case ProtocolPhases::SENDING_STATUS_REQUEST: |     case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST( |         static const haier_protocol::HaierMessage STATUS_REQUEST( | ||||||
|             (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); |             haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA); | ||||||
|         this->send_message_(STATUS_REQUEST, this->use_crc_); |         this->send_message_(STATUS_REQUEST, this->use_crc_); | ||||||
|         this->last_status_request_ = now; |         this->last_status_request_ = now; | ||||||
|         this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| #ifdef USE_WIFI | #ifdef USE_WIFI | ||||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: |     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST( |         static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST( | ||||||
|             (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); |             haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION); | ||||||
|         this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); |         this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); | ||||||
|         this->last_signal_request_ = now; |         this->last_signal_request_ = now; | ||||||
|         this->set_phase(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: |     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         this->send_message_(this->get_wifi_signal_message_((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS), |         this->send_message_(this->get_wifi_signal_message_(), this->use_crc_); | ||||||
|                             this->use_crc_); |  | ||||||
|         this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: |  | ||||||
|       break; |  | ||||||
| #else | #else | ||||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: |     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: |     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; |       break; | ||||||
| #endif | #endif | ||||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: |     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( |         static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS); | ||||||
|             (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS); |  | ||||||
|         this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); |         this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); | ||||||
|         this->set_phase(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_CONTROL: |     case ProtocolPhases::SENDING_CONTROL: | ||||||
|       if (this->first_control_attempt_) { |       if (this->control_messages_queue_.empty()) { | ||||||
|         this->control_request_timestamp_ = now; |         switch (this->control_method_) { | ||||||
|         this->first_control_attempt_ = false; |           case HonControlMethod::SET_GROUP_PARAMETERS: { | ||||||
|  |             haier_protocol::HaierMessage control_message = this->get_control_message(); | ||||||
|  |             this->control_messages_queue_.push(control_message); | ||||||
|  |           } break; | ||||||
|  |           case HonControlMethod::SET_SINGLE_PARAMETER: | ||||||
|  |             this->fill_control_messages_queue_(); | ||||||
|  |             break; | ||||||
|  |           case HonControlMethod::MONITOR_ONLY: | ||||||
|  |             ESP_LOGI(TAG, "AC control is disabled, monitor only"); | ||||||
|  |             this->reset_to_idle_(); | ||||||
|  |             return; | ||||||
|  |           default: | ||||||
|  |             ESP_LOGW(TAG, "Unsupported control method for hOn protocol!"); | ||||||
|  |             this->reset_to_idle_(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|       if (this->is_control_message_timeout_exceeded_(now)) { |       if (this->control_messages_queue_.empty()) { | ||||||
|         ESP_LOGW(TAG, "Sending control packet timeout!"); |         ESP_LOGW(TAG, "Control message queue is empty!"); | ||||||
|         this->set_force_send_control_(false); |         this->reset_to_idle_(); | ||||||
|         if (this->hvac_settings_.valid) |  | ||||||
|           this->hvac_settings_.reset(); |  | ||||||
|         this->forced_request_status_ = true; |  | ||||||
|         this->forced_publish_ = true; |  | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |  | ||||||
|       } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { |       } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { | ||||||
|         haier_protocol::HaierMessage control_message = get_control_message(); |         ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size()); | ||||||
|         this->send_message_(control_message, this->use_crc_); |         this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES, | ||||||
|         ESP_LOGI(TAG, "Control packet sent"); |                             CONTROL_MESSAGE_RETRIES_INTERVAL); | ||||||
|         this->set_phase(ProtocolPhases::WAITING_CONTROL_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_POWER_ON_COMMAND: |     case ProtocolPhases::SENDING_ACTION_COMMAND: | ||||||
|     case ProtocolPhases::SENDING_POWER_OFF_COMMAND: |       if (this->action_request_.has_value()) { | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |         if (this->action_request_.value().message.has_value()) { | ||||||
|         uint8_t pwr_cmd_buf[2] = {0x00, 0x00}; |           this->send_message_(this->action_request_.value().message.value(), this->use_crc_); | ||||||
|         if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) |           this->action_request_.value().message.reset(); | ||||||
|           pwr_cmd_buf[1] = 0x01; |         } else { | ||||||
|         haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, |           // Message already sent, reseting request and return to idle | ||||||
|                                                ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, |           this->action_request_.reset(); | ||||||
|                                                pwr_cmd_buf, sizeof(pwr_cmd_buf)); |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|         this->send_message_(power_cmd, this->use_crc_); |         } | ||||||
|         this->set_phase(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND |       } else { | ||||||
|                             ? ProtocolPhases::WAITING_POWER_ON_ANSWER |         ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!"); | ||||||
|                             : ProtocolPhases::WAITING_POWER_OFF_ANSWER); |         this->set_phase(ProtocolPhases::IDLE); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|  |  | ||||||
|     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: |  | ||||||
|     case ProtocolPhases::WAITING_CONTROL_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_POWER_ON_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_POWER_OFF_ANSWER: |  | ||||||
|       break; |  | ||||||
|     case ProtocolPhases::IDLE: { |     case ProtocolPhases::IDLE: { | ||||||
|       if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { |       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); | ||||||
| @@ -433,26 +427,35 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | |||||||
|     } break; |     } break; | ||||||
|     default: |     default: | ||||||
|       // Shouldn't get here |       // Shouldn't get here | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|       ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", |       ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", | ||||||
|                phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); |                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_); |  | ||||||
| #endif |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); |       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | haier_protocol::HaierMessage HonClimate::get_power_message(bool state) { | ||||||
|  |   if (state) { | ||||||
|  |     static haier_protocol::HaierMessage power_on_message( | ||||||
|  |         haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, | ||||||
|  |         std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2); | ||||||
|  |     return power_on_message; | ||||||
|  |   } else { | ||||||
|  |     static haier_protocol::HaierMessage power_off_message( | ||||||
|  |         haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1, | ||||||
|  |         std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2); | ||||||
|  |     return power_off_message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| haier_protocol::HaierMessage HonClimate::get_control_message() { | haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||||
|   uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; |   uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; | ||||||
|   memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); |   memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); | ||||||
|   hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; |   hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; | ||||||
|   bool has_hvac_settings = false; |   bool has_hvac_settings = false; | ||||||
|   if (this->hvac_settings_.valid) { |   if (this->current_hvac_settings_.valid) { | ||||||
|     has_hvac_settings = true; |     has_hvac_settings = true; | ||||||
|     HvacSettings climate_control; |     HvacSettings &climate_control = this->current_hvac_settings_; | ||||||
|     climate_control = this->hvac_settings_; |  | ||||||
|     if (climate_control.mode.has_value()) { |     if (climate_control.mode.has_value()) { | ||||||
|       switch (climate_control.mode.value()) { |       switch (climate_control.mode.value()) { | ||||||
|         case CLIMATE_MODE_OFF: |         case CLIMATE_MODE_OFF: | ||||||
| @@ -535,7 +538,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | |||||||
|     } |     } | ||||||
|     if (climate_control.target_temperature.has_value()) { |     if (climate_control.target_temperature.has_value()) { | ||||||
|       float target_temp = climate_control.target_temperature.value(); |       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->set_point = ((int) target_temp) - 16;  // set the temperature with offset 16 | ||||||
|       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; |       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; | ||||||
|     } |     } | ||||||
|     if (out_data->ac_power == 0) { |     if (out_data->ac_power == 0) { | ||||||
| @@ -587,50 +590,28 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | |||||||
|   control_out_buffer[4] = 0;  // This byte should be cleared before setting values |   control_out_buffer[4] = 0;  // This byte should be cleared before setting values | ||||||
|   out_data->display_status = this->display_status_ ? 1 : 0; |   out_data->display_status = this->display_status_ ? 1 : 0; | ||||||
|   out_data->health_mode = this->health_mode_ ? 1 : 0; |   out_data->health_mode = this->health_mode_ ? 1 : 0; | ||||||
|   switch (this->action_request_) { |   return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|     case ActionRequest::START_SELF_CLEAN: |  | ||||||
|       this->action_request_ = ActionRequest::NO_ACTION; |  | ||||||
|       out_data->self_cleaning_status = 1; |  | ||||||
|       out_data->steri_clean = 0; |  | ||||||
|       out_data->set_point = 0x06; |  | ||||||
|       out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; |  | ||||||
|       out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; |  | ||||||
|       out_data->ac_power = 1; |  | ||||||
|       out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; |  | ||||||
|       out_data->light_status = 0; |  | ||||||
|       break; |  | ||||||
|     case ActionRequest::START_STERI_CLEAN: |  | ||||||
|       this->action_request_ = ActionRequest::NO_ACTION; |  | ||||||
|       out_data->self_cleaning_status = 0; |  | ||||||
|       out_data->steri_clean = 1; |  | ||||||
|       out_data->set_point = 0x06; |  | ||||||
|       out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; |  | ||||||
|       out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; |  | ||||||
|       out_data->ac_power = 1; |  | ||||||
|       out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; |  | ||||||
|       out_data->light_status = 0; |  | ||||||
|       break; |  | ||||||
|     default: |  | ||||||
|       // No change |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|   return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL, |  | ||||||
|                                       (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, |                                       (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, | ||||||
|                                       control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); |                                       control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { | haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { | ||||||
|   if (size < sizeof(hon_protocol::HaierStatus)) |   if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_) | ||||||
|     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; |     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||||
|   hon_protocol::HaierStatus packet; |   struct { | ||||||
|   if (size < sizeof(hon_protocol::HaierStatus)) |     hon_protocol::HaierPacketControl control; | ||||||
|     size = sizeof(hon_protocol::HaierStatus); |     hon_protocol::HaierPacketSensors sensors; | ||||||
|   memcpy(&packet, packet_buffer, size); |   } packet; | ||||||
|  |   memcpy(&packet.control, packet_buffer + 2, sizeof(hon_protocol::HaierPacketControl)); | ||||||
|  |   memcpy(&packet.sensors, | ||||||
|  |          packet_buffer + 2 + sizeof(hon_protocol::HaierPacketControl) + this->extra_control_packet_bytes_, | ||||||
|  |          sizeof(hon_protocol::HaierPacketSensors)); | ||||||
|   if (packet.sensors.error_status != 0) { |   if (packet.sensors.error_status != 0) { | ||||||
|     ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); |     ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); | ||||||
|   } |   } | ||||||
|   if ((this->outdoor_sensor_ != nullptr) && (got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { |   if ((this->outdoor_sensor_ != nullptr) && | ||||||
|     got_valid_outdoor_temp_ = true; |       (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { | ||||||
|  |     this->got_valid_outdoor_temp_ = true; | ||||||
|     float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); |     float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); | ||||||
|     if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) |     if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) | ||||||
|       this->outdoor_sensor_->publish_state(otemp); |       this->outdoor_sensor_->publish_state(otemp); | ||||||
| @@ -703,7 +684,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|         // Do something only if display status changed |         // Do something only if display status changed | ||||||
|         if (this->mode == CLIMATE_MODE_OFF) { |         if (this->mode == CLIMATE_MODE_OFF) { | ||||||
|           // AC just turned on from remote need to turn off display |           // AC just turned on from remote need to turn off display | ||||||
|           this->set_force_send_control_(true); |           this->force_send_control_ = true; | ||||||
|         } else { |         } else { | ||||||
|           this->display_status_ = disp_status; |           this->display_status_ = disp_status; | ||||||
|         } |         } | ||||||
| @@ -732,7 +713,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|       ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); |       ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); | ||||||
|       if (new_cleaning == CleaningState::NO_CLEANING) { |       if (new_cleaning == CleaningState::NO_CLEANING) { | ||||||
|         // Turning AC off after cleaning |         // Turning AC off after cleaning | ||||||
|         this->action_request_ = ActionRequest::TURN_POWER_OFF; |         this->action_request_ = | ||||||
|  |             PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()}); | ||||||
|       } |       } | ||||||
|       this->cleaning_status_ = new_cleaning; |       this->cleaning_status_ = new_cleaning; | ||||||
|     } |     } | ||||||
| @@ -783,51 +765,257 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|     should_publish = should_publish || (old_swing_mode != this->swing_mode); |     should_publish = should_publish || (old_swing_mode != this->swing_mode); | ||||||
|   } |   } | ||||||
|   this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); |   this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); | ||||||
|   if (this->forced_publish_ || should_publish) { |   if (should_publish) { | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|     std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); |  | ||||||
| #endif |  | ||||||
|     this->publish_state(); |     this->publish_state(); | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|     ESP_LOGV(TAG, "Publish delay: %lld ms", |  | ||||||
|              std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - |  | ||||||
|                                                                    _publish_start) |  | ||||||
|                  .count()); |  | ||||||
| #endif |  | ||||||
|     this->forced_publish_ = false; |  | ||||||
|   } |   } | ||||||
|   if (should_publish) { |   if (should_publish) { | ||||||
|     ESP_LOGI(TAG, "HVAC values changed"); |     ESP_LOGI(TAG, "HVAC values changed"); | ||||||
|   } |   } | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG; | ||||||
|                   "HVAC Mode = 0x%X", packet.control.ac_mode); |   esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode); | ||||||
|                   "Fan speed Status = 0x%X", packet.control.fan_mode); |   esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode); | ||||||
|                   "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); |   esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |  | ||||||
|                   "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode); |  | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |  | ||||||
|                   "Set Point Status = 0x%X", packet.control.set_point); |  | ||||||
|   return haier_protocol::HandlerError::HANDLER_OK; |   return haier_protocol::HandlerError::HANDLER_OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HonClimate::is_message_invalid(uint8_t message_type) { | void HonClimate::fill_control_messages_queue_() { | ||||||
|   return message_type == (uint8_t) hon_protocol::FrameType::INVALID; |   static uint8_t one_buf[] = {0x00, 0x01}; | ||||||
|  |   static uint8_t zero_buf[] = {0x00, 0x00}; | ||||||
|  |   if (!this->current_hvac_settings_.valid && !this->force_send_control_) | ||||||
|  |     return; | ||||||
|  |   this->clear_control_messages_queue_(); | ||||||
|  |   HvacSettings climate_control; | ||||||
|  |   climate_control = this->current_hvac_settings_; | ||||||
|  |   // Beeper command | ||||||
|  |   { | ||||||
|  |     this->control_messages_queue_.push( | ||||||
|  |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                          (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, | ||||||
|  |                                      this->beeper_status_ ? zero_buf : one_buf, 2)); | ||||||
|  |   } | ||||||
|  |   // Health mode | ||||||
|  |   { | ||||||
|  |     this->control_messages_queue_.push( | ||||||
|  |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                          (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, | ||||||
|  |                                      this->health_mode_ ? one_buf : zero_buf, 2)); | ||||||
|  |   } | ||||||
|  |   // Climate mode | ||||||
|  |   bool new_power = this->mode != CLIMATE_MODE_OFF; | ||||||
|  |   uint8_t fan_mode_buf[] = {0x00, 0xFF}; | ||||||
|  |   uint8_t quiet_mode_buf[] = {0x00, 0xFF}; | ||||||
|  |   if (climate_control.mode.has_value()) { | ||||||
|  |     uint8_t buffer[2] = {0x00, 0x00}; | ||||||
|  |     switch (climate_control.mode.value()) { | ||||||
|  |       case CLIMATE_MODE_OFF: | ||||||
|  |         new_power = false; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_MODE_HEAT_COOL: | ||||||
|  |         new_power = true; | ||||||
|  |         buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO; | ||||||
|  |         this->control_messages_queue_.push( | ||||||
|  |             haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|  |                                          buffer, 2)); | ||||||
|  |         fan_mode_buf[1] = this->other_modes_fan_speed_; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_MODE_HEAT: | ||||||
|  |         new_power = true; | ||||||
|  |         buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT; | ||||||
|  |         this->control_messages_queue_.push( | ||||||
|  |             haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|  |                                          buffer, 2)); | ||||||
|  |         fan_mode_buf[1] = this->other_modes_fan_speed_; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_MODE_DRY: | ||||||
|  |         new_power = true; | ||||||
|  |         buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY; | ||||||
|  |         this->control_messages_queue_.push( | ||||||
|  |             haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|  |                                          buffer, 2)); | ||||||
|  |         fan_mode_buf[1] = this->other_modes_fan_speed_; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_MODE_FAN_ONLY: | ||||||
|  |         new_power = true; | ||||||
|  |         buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN; | ||||||
|  |         this->control_messages_queue_.push( | ||||||
|  |             haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|  |                                          buffer, 2)); | ||||||
|  |         fan_mode_buf[1] = this->other_modes_fan_speed_;  // Auto doesn't work in fan only mode | ||||||
|  |         // Disabling eco mode for Fan only | ||||||
|  |         quiet_mode_buf[1] = 0; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_MODE_COOL: | ||||||
|  |         new_power = true; | ||||||
|  |         buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL; | ||||||
|  |         this->control_messages_queue_.push( | ||||||
|  |             haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                          (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|  |                                          buffer, 2)); | ||||||
|  |         fan_mode_buf[1] = this->other_modes_fan_speed_; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         ESP_LOGE("Control", "Unsupported climate mode"); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Climate power | ||||||
|  |   { | ||||||
|  |     this->control_messages_queue_.push( | ||||||
|  |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                          (uint8_t) hon_protocol::DataParameters::AC_POWER, | ||||||
|  |                                      new_power ? one_buf : zero_buf, 2)); | ||||||
|  |   } | ||||||
|  |   // CLimate preset | ||||||
|  |   { | ||||||
|  |     uint8_t fast_mode_buf[] = {0x00, 0xFF}; | ||||||
|  |     if (!new_power) { | ||||||
|  |       // If AC is off - no presets allowed | ||||||
|  |       quiet_mode_buf[1] = 0x00; | ||||||
|  |       fast_mode_buf[1] = 0x00; | ||||||
|  |     } else if (climate_control.preset.has_value()) { | ||||||
|  |       switch (climate_control.preset.value()) { | ||||||
|  |         case CLIMATE_PRESET_NONE: | ||||||
|  |           quiet_mode_buf[1] = 0x00; | ||||||
|  |           fast_mode_buf[1] = 0x00; | ||||||
|  |           break; | ||||||
|  |         case CLIMATE_PRESET_ECO: | ||||||
|  |           // Eco is not supported in Fan only mode | ||||||
|  |           quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; | ||||||
|  |           fast_mode_buf[1] = 0x00; | ||||||
|  |           break; | ||||||
|  |         case CLIMATE_PRESET_BOOST: | ||||||
|  |           quiet_mode_buf[1] = 0x00; | ||||||
|  |           // Boost is not supported in Fan only mode | ||||||
|  |           fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           ESP_LOGE("Control", "Unsupported preset"); | ||||||
|  |           break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (quiet_mode_buf[1] != 0xFF) { | ||||||
|  |       this->control_messages_queue_.push( | ||||||
|  |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                            (uint8_t) hon_protocol::DataParameters::QUIET_MODE, | ||||||
|  |                                        quiet_mode_buf, 2)); | ||||||
|  |     } | ||||||
|  |     if (fast_mode_buf[1] != 0xFF) { | ||||||
|  |       this->control_messages_queue_.push( | ||||||
|  |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                            (uint8_t) hon_protocol::DataParameters::FAST_MODE, | ||||||
|  |                                        fast_mode_buf, 2)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // Target temperature | ||||||
|  |   if (climate_control.target_temperature.has_value()) { | ||||||
|  |     uint8_t buffer[2] = {0x00, 0x00}; | ||||||
|  |     buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16; | ||||||
|  |     this->control_messages_queue_.push( | ||||||
|  |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                          (uint8_t) hon_protocol::DataParameters::SET_POINT, | ||||||
|  |                                      buffer, 2)); | ||||||
|  |   } | ||||||
|  |   // Fan mode | ||||||
|  |   if (climate_control.fan_mode.has_value()) { | ||||||
|  |     switch (climate_control.fan_mode.value()) { | ||||||
|  |       case CLIMATE_FAN_LOW: | ||||||
|  |         fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_FAN_MEDIUM: | ||||||
|  |         fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_FAN_HIGH: | ||||||
|  |         fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH; | ||||||
|  |         break; | ||||||
|  |       case CLIMATE_FAN_AUTO: | ||||||
|  |         if (mode != CLIMATE_MODE_FAN_ONLY)  // if we are not in fan only mode | ||||||
|  |           fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO; | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         ESP_LOGE("Control", "Unsupported fan mode"); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     if (fan_mode_buf[1] != 0xFF) { | ||||||
|  |       this->control_messages_queue_.push( | ||||||
|  |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|  |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|  |                                            (uint8_t) hon_protocol::DataParameters::FAN_MODE, | ||||||
|  |                                        fan_mode_buf, 2)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void HonClimate::process_pending_action() { | void HonClimate::clear_control_messages_queue_() { | ||||||
|   switch (this->action_request_) { |   while (!this->control_messages_queue_.empty()) | ||||||
|     case ActionRequest::START_SELF_CLEAN: |     this->control_messages_queue_.pop(); | ||||||
|     case ActionRequest::START_STERI_CLEAN: | } | ||||||
|       // Will reset action with control message sending |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_CONTROL); | bool HonClimate::prepare_pending_action() { | ||||||
|       break; |   switch (this->action_request_.value().action) { | ||||||
|  |     case ActionRequest::START_SELF_CLEAN: { | ||||||
|  |       uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; | ||||||
|  |       memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); | ||||||
|  |       hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; | ||||||
|  |       out_data->self_cleaning_status = 1; | ||||||
|  |       out_data->steri_clean = 0; | ||||||
|  |       out_data->set_point = 0x06; | ||||||
|  |       out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; | ||||||
|  |       out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; | ||||||
|  |       out_data->ac_power = 1; | ||||||
|  |       out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; | ||||||
|  |       out_data->light_status = 0; | ||||||
|  |       this->action_request_.value().message = haier_protocol::HaierMessage( | ||||||
|  |           haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, | ||||||
|  |           control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); | ||||||
|  |     } | ||||||
|  |       return true; | ||||||
|  |     case ActionRequest::START_STERI_CLEAN: { | ||||||
|  |       uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; | ||||||
|  |       memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); | ||||||
|  |       hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; | ||||||
|  |       out_data->self_cleaning_status = 0; | ||||||
|  |       out_data->steri_clean = 1; | ||||||
|  |       out_data->set_point = 0x06; | ||||||
|  |       out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; | ||||||
|  |       out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; | ||||||
|  |       out_data->ac_power = 1; | ||||||
|  |       out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; | ||||||
|  |       out_data->light_status = 0; | ||||||
|  |       this->action_request_.value().message = haier_protocol::HaierMessage( | ||||||
|  |           haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, | ||||||
|  |           control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); | ||||||
|  |     } | ||||||
|  |       return true; | ||||||
|     default: |     default: | ||||||
|       HaierClimateBase::process_pending_action(); |       return HaierClimateBase::prepare_pending_action(); | ||||||
|       break; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void HonClimate::process_protocol_reset() { | ||||||
|  |   HaierClimateBase::process_protocol_reset(); | ||||||
|  |   if (this->outdoor_sensor_ != nullptr) { | ||||||
|  |     this->outdoor_sensor_->publish_state(NAN); | ||||||
|  |   } | ||||||
|  |   this->got_valid_outdoor_temp_ = false; | ||||||
|  |   this->hvac_hardware_info_.reset(); | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace haier | }  // namespace haier | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -30,6 +30,8 @@ enum class CleaningState : uint8_t { | |||||||
|   STERI_CLEAN = 2, |   STERI_CLEAN = 2, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER }; | ||||||
|  |  | ||||||
| class HonClimate : public HaierClimateBase { | class HonClimate : public HaierClimateBase { | ||||||
|  public: |  public: | ||||||
|   HonClimate(); |   HonClimate(); | ||||||
| @@ -48,44 +50,57 @@ class HonClimate : public HaierClimateBase { | |||||||
|   CleaningState get_cleaning_status() const; |   CleaningState get_cleaning_status() const; | ||||||
|   void start_self_cleaning(); |   void start_self_cleaning(); | ||||||
|   void start_steri_cleaning(); |   void start_steri_cleaning(); | ||||||
|  |   void set_extra_control_packet_bytes_size(size_t size) { this->extra_control_packet_bytes_ = size; }; | ||||||
|  |   void set_control_method(HonControlMethod method) { this->control_method_ = method; }; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void set_handlers() override; |   void set_handlers() override; | ||||||
|   void process_phase(std::chrono::steady_clock::time_point now) override; |   void process_phase(std::chrono::steady_clock::time_point now) override; | ||||||
|   haier_protocol::HaierMessage get_control_message() override; |   haier_protocol::HaierMessage get_control_message() override; | ||||||
|   bool is_message_invalid(uint8_t message_type) override; |   haier_protocol::HaierMessage get_power_message(bool state) override; | ||||||
|   void process_pending_action() override; |   bool prepare_pending_action() override; | ||||||
|  |   void process_protocol_reset() override; | ||||||
|  |  | ||||||
|   // Answers handlers |   // Answers handlers | ||||||
|   haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                   haier_protocol::FrameType message_type, | ||||||
|                                                                   const uint8_t *data, size_t data_size); |                                                                   const uint8_t *data, size_t data_size); | ||||||
|   haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                              haier_protocol::FrameType message_type, | ||||||
|                                                              const uint8_t *data, size_t data_size); |                                                              const uint8_t *data, size_t data_size); | ||||||
|   haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, |   haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|                                                size_t data_size); |                                                size_t data_size); | ||||||
|   haier_protocol::HandlerError get_management_information_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                           haier_protocol::FrameType message_type, | ||||||
|                                                                           const uint8_t *data, size_t data_size); |                                                                           const uint8_t *data, size_t data_size); | ||||||
|   haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|                                                                      const uint8_t *data, size_t data_size); |                                                                 haier_protocol::FrameType message_type, | ||||||
|   haier_protocol::HandlerError get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, |  | ||||||
|                                                                 const uint8_t *data, size_t data_size); |                                                                 const uint8_t *data, size_t data_size); | ||||||
|   // Helper functions |   // Helper functions | ||||||
|   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); |   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); | ||||||
|   std::unique_ptr<uint8_t[]> last_status_message_; |   void fill_control_messages_queue_(); | ||||||
|  |   void clear_control_messages_queue_(); | ||||||
|  |  | ||||||
|  |   struct HardwareInfo { | ||||||
|  |     std::string protocol_version_; | ||||||
|  |     std::string software_version_; | ||||||
|  |     std::string hardware_version_; | ||||||
|  |     std::string device_name_; | ||||||
|  |     bool functions_[5]; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   bool beeper_status_; |   bool beeper_status_; | ||||||
|   CleaningState cleaning_status_; |   CleaningState cleaning_status_; | ||||||
|   bool got_valid_outdoor_temp_; |   bool got_valid_outdoor_temp_; | ||||||
|   AirflowVerticalDirection vertical_direction_; |   AirflowVerticalDirection vertical_direction_; | ||||||
|   AirflowHorizontalDirection horizontal_direction_; |   AirflowHorizontalDirection horizontal_direction_; | ||||||
|   bool hvac_hardware_info_available_; |   esphome::optional<HardwareInfo> hvac_hardware_info_; | ||||||
|   std::string hvac_protocol_version_; |  | ||||||
|   std::string hvac_software_version_; |  | ||||||
|   std::string hvac_hardware_version_; |  | ||||||
|   std::string hvac_device_name_; |  | ||||||
|   bool hvac_functions_[5]; |  | ||||||
|   bool &use_crc_; |  | ||||||
|   uint8_t active_alarms_[8]; |   uint8_t active_alarms_[8]; | ||||||
|  |   int extra_control_packet_bytes_; | ||||||
|  |   HonControlMethod control_method_; | ||||||
|   esphome::sensor::Sensor *outdoor_sensor_; |   esphome::sensor::Sensor *outdoor_sensor_; | ||||||
|  |   std::queue<haier_protocol::HaierMessage> control_messages_queue_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace haier | }  // namespace haier | ||||||
|   | |||||||
| @@ -35,6 +35,20 @@ enum class ConditioningMode : uint8_t { | |||||||
|   FAN = 0x06 |   FAN = 0x06 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum class DataParameters : uint8_t { | ||||||
|  |   AC_POWER = 0x01, | ||||||
|  |   SET_POINT = 0x02, | ||||||
|  |   AC_MODE = 0x04, | ||||||
|  |   FAN_MODE = 0x05, | ||||||
|  |   USE_FAHRENHEIT = 0x07, | ||||||
|  |   TEN_DEGREE = 0x0A, | ||||||
|  |   HEALTH_MODE = 0x0B, | ||||||
|  |   BEEPER_STATUS = 0x16, | ||||||
|  |   LOCK_REMOTE = 0x17, | ||||||
|  |   QUIET_MODE = 0x19, | ||||||
|  |   FAST_MODE = 0x1A, | ||||||
|  | }; | ||||||
|  |  | ||||||
| enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; | enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; | ||||||
|  |  | ||||||
| enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; | enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; | ||||||
| @@ -124,11 +138,7 @@ struct HaierPacketSensors { | |||||||
|   uint16_t co2_value;  // CO2 value (0 PPM -  10000 PPM, 1 PPM step) |   uint16_t co2_value;  // CO2 value (0 PPM -  10000 PPM, 1 PPM step) | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct HaierStatus { | constexpr size_t HAIER_STATUS_FRAME_SIZE = 2 + sizeof(HaierPacketControl) + sizeof(HaierPacketSensors); | ||||||
|   uint16_t subcommand; |  | ||||||
|   HaierPacketControl control; |  | ||||||
|   HaierPacketSensors sensors; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| struct DeviceVersionAnswer { | struct DeviceVersionAnswer { | ||||||
|   char protocol_version[8]; |   char protocol_version[8]; | ||||||
| @@ -140,76 +150,6 @@ struct DeviceVersionAnswer { | |||||||
|   uint8_t functions[2]; |   uint8_t functions[2]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // In this section comments: |  | ||||||
| //  - module is the ESP32 control module (communication module in Haier protocol document) |  | ||||||
| //  - device is the conditioner control board (network appliances in Haier protocol document) |  | ||||||
| enum class FrameType : uint8_t { |  | ||||||
|   CONTROL = 0x01,  // Requests or sets one or multiple parameters (module <-> device, required) |  | ||||||
|   STATUS = 0x02,   // Contains one or multiple parameters values, usually answer to control frame (module <-> device, |  | ||||||
|                    // required) |  | ||||||
|   INVALID = 0x03,  // Communication error indication (module <-> device, required) |  | ||||||
|   ALARM_STATUS = 0x04,  // Alarm status report (module <-> device, interactive, required) |  | ||||||
|   CONFIRM = 0x05,  // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module |  | ||||||
|                    // <-> device, required) |  | ||||||
|   REPORT = 0x06,   // Report frame (module <-> device, interactive, required) |  | ||||||
|   STOP_FAULT_ALARM = 0x09,             // Stop fault alarm frame (module -> device, interactive, required) |  | ||||||
|   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) |  | ||||||
|   DEVICE_QUERY = 0x15,                 // Device query frame (module <- device, optional) |  | ||||||
|   DEVICE_QUERY_RESPONSE = 0x16,        // Device query response frame (module -> device, optional) |  | ||||||
|   GROUP_COMMAND = 0x60,                // Group command frame (module -> device, interactive, optional) |  | ||||||
|   GET_DEVICE_VERSION = 0x61,           // Requests device version (module -> device, required) |  | ||||||
|   GET_DEVICE_VERSION_RESPONSE = 0x62,  // Device version answer (module <- device, required_ |  | ||||||
|   GET_ALL_ADDRESSES = 0x67,            // Requests all devices addresses (module -> device, interactive, optional) |  | ||||||
|   GET_ALL_ADDRESSES_RESPONSE = |  | ||||||
|       0x68,  // Answer to request of all devices addresses (module <- device , interactive, optional) |  | ||||||
|   HANDSET_CHANGE_NOTIFICATION = 0x69,  // Handset change notification frame (module <- device , interactive, optional) |  | ||||||
|   GET_DEVICE_ID = 0x70,                // Requests Device ID (module -> device, required) |  | ||||||
|   GET_DEVICE_ID_RESPONSE = 0x71,       // Response to device ID request (module <- device , required) |  | ||||||
|   GET_ALARM_STATUS = 0x73,             // Alarm status request (module -> device, required) |  | ||||||
|   GET_ALARM_STATUS_RESPONSE = 0x74,    // Response to alarm status request (module <- device, required) |  | ||||||
|   GET_DEVICE_CONFIGURATION = 0x7C,     // Requests device configuration (module -> device, interactive, required) |  | ||||||
|   GET_DEVICE_CONFIGURATION_RESPONSE = |  | ||||||
|       0x7D,  // Response to device configuration request (module <- device, interactive, required) |  | ||||||
|   DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C,  // Downlink transparent transmission (proxy data Haier cloud -> device) |  | ||||||
|                                              // (module -> device, interactive, optional) |  | ||||||
|   UPLINK_TRANSPARENT_TRANSMISSION = 0x8D,  // Uplink transparent transmission (proxy data device -> Haier cloud) (module |  | ||||||
|                                            // <- device, interactive, optional) |  | ||||||
|   START_DEVICE_UPGRADE = 0xE1,             // Initiate device OTA upgrade (module -> device, OTA required) |  | ||||||
|   START_DEVICE_UPGRADE_RESPONSE = 0xE2,  // Response to initiate device upgrade command (module <- device, OTA required) |  | ||||||
|   GET_FIRMWARE_CONTENT = 0xE5,           // Requests to send firmware (module <- device, OTA required) |  | ||||||
|   GET_FIRMWARE_CONTENT_RESPONSE = |  | ||||||
|       0xE6,                 // Response to send firmware request (module -> device, OTA required) (multipacket?) |  | ||||||
|   CHANGE_BAUD_RATE = 0xE7,  // Requests to change port baud rate (module <- device, OTA required) |  | ||||||
|   CHANGE_BAUD_RATE_RESPONSE = 0xE8,    // Response to change port baud rate request (module -> device, OTA required) |  | ||||||
|   GET_SUBBOARD_INFO = 0xE9,            // Requests subboard information (module -> device, required) |  | ||||||
|   GET_SUBBOARD_INFO_RESPONSE = 0xEA,   // Response to subboard information request (module <- device, required) |  | ||||||
|   GET_HARDWARE_INFO = 0xEB,            // Requests information about device and subboard (module -> device, required) |  | ||||||
|   GET_HARDWARE_INFO_RESPONSE = 0xEC,   // Response to hardware information request (module <- device, required) |  | ||||||
|   GET_UPGRADE_RESULT = 0xED,           // Requests result of the firmware update (module <- device, OTA required) |  | ||||||
|   GET_UPGRADE_RESULT_RESPONSE = 0xEF,  // Response to firmware update results request (module -> device, OTA required) |  | ||||||
|   GET_NETWORK_STATUS = 0xF0,           // Requests network status (module <- device, interactive, optional) |  | ||||||
|   GET_NETWORK_STATUS_RESPONSE = 0xF1,  // Response to network status request (module -> device, interactive, optional) |  | ||||||
|   START_WIFI_CONFIGURATION = 0xF2,     // Starts WiFi configuration procedure (module <- device, interactive, required) |  | ||||||
|   START_WIFI_CONFIGURATION_RESPONSE = |  | ||||||
|       0xF3,  // Response to start WiFi configuration request (module -> device, interactive, required) |  | ||||||
|   STOP_WIFI_CONFIGURATION = 0xF4,  // Stop WiFi configuration procedure (module <- device, interactive, required) |  | ||||||
|   STOP_WIFI_CONFIGURATION_RESPONSE = |  | ||||||
|       0xF5,  // Response to stop WiFi configuration request (module -> device, interactive, required) |  | ||||||
|   REPORT_NETWORK_STATUS = 0xF7,  // Reports network status (module -> device, required) |  | ||||||
|   CLEAR_CONFIGURATION = 0xF8,    // Request to clear module configuration (module <- device, interactive, optional) |  | ||||||
|   BIG_DATA_REPORT_CONFIGURATION = |  | ||||||
|       0xFA,  // Configuration for autoreport device full status (module -> device, interactive, optional) |  | ||||||
|   BIG_DATA_REPORT_CONFIGURATION_RESPONSE = |  | ||||||
|       0xFB,  // Response to set big data configuration (module <- device, interactive, optional) |  | ||||||
|   GET_MANAGEMENT_INFORMATION = 0xFC,  // Request management information from device (module -> device, required) |  | ||||||
|   GET_MANAGEMENT_INFORMATION_RESPONSE = |  | ||||||
|       0xFD,        // Response to management information request (module <- device, required) |  | ||||||
|   WAKE_UP = 0xFE,  // Request to wake up (module <-> device, optional) |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| enum class SubcommandsControl : uint16_t { | enum class SubcommandsControl : uint16_t { | ||||||
|   GET_PARAMETERS = 0x4C01,  // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) |   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_USER_DATA = 0x4D01,   // Request all user data from device (packet content: None) | ||||||
|   | |||||||
| @@ -12,21 +12,28 @@ namespace haier { | |||||||
|  |  | ||||||
| static const char *const TAG = "haier.climate"; | static const char *const TAG = "haier.climate"; | ||||||
| constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | ||||||
|  | constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5; | ||||||
|  | constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500); | ||||||
|  | constexpr uint8_t INIT_REQUESTS_RETRY = 2; | ||||||
|  | constexpr std::chrono::milliseconds INIT_REQUESTS_RETRY_INTERVAL = std::chrono::milliseconds(2000); | ||||||
|  |  | ||||||
| Smartair2Climate::Smartair2Climate() | Smartair2Climate::Smartair2Climate() { | ||||||
|     : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]), timeouts_counter_(0) {} |   last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]); | ||||||
|  | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, | haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                haier_protocol::FrameType message_type, | ||||||
|                                                                const uint8_t *data, size_t data_size) { |                                                                const uint8_t *data, size_t data_size) { | ||||||
|   haier_protocol::HandlerError result = |   haier_protocol::HandlerError result = | ||||||
|       this->answer_preprocess_(request_type, (uint8_t) smartair2_protocol::FrameType::CONTROL, message_type, |       this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type, | ||||||
|                                (uint8_t) smartair2_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); |                                haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); | ||||||
|   if (result == haier_protocol::HandlerError::HANDLER_OK) { |   if (result == haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|     result = this->process_status_message_(data, data_size); |     result = this->process_status_message_(data, data_size); | ||||||
|     if (result != haier_protocol::HandlerError::HANDLER_OK) { |     if (result != haier_protocol::HandlerError::HANDLER_OK) { | ||||||
|       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); |       ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); | ||||||
|       this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |       this->reset_phase_(); | ||||||
|                                                                       : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); |       this->action_request_.reset(); | ||||||
|  |       this->force_send_control_ = false; | ||||||
|     } else { |     } else { | ||||||
|       if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { |       if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { | ||||||
|         memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); |         memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); | ||||||
| @@ -34,36 +41,45 @@ haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_t | |||||||
|         ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, |         ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, | ||||||
|                  sizeof(smartair2_protocol::HaierPacketControl)); |                  sizeof(smartair2_protocol::HaierPacketControl)); | ||||||
|       } |       } | ||||||
|       if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { |       switch (this->protocol_phase_) { | ||||||
|         ESP_LOGI(TAG, "First HVAC status received"); |         case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |           ESP_LOGI(TAG, "First HVAC status received"); | ||||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |           break; | ||||||
|       } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { |         case ProtocolPhases::SENDING_ACTION_COMMAND: | ||||||
|         this->set_phase(ProtocolPhases::IDLE); |           // Do nothing, phase will be changed in process_phase | ||||||
|         this->set_force_send_control_(false); |           break; | ||||||
|         if (this->hvac_settings_.valid) |         case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||||
|           this->hvac_settings_.reset(); |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |           break; | ||||||
|  |         case ProtocolPhases::SENDING_CONTROL: | ||||||
|  |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |           this->force_send_control_ = false; | ||||||
|  |           if (this->current_hvac_settings_.valid) | ||||||
|  |             this->current_hvac_settings_.reset(); | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
|   } else { |   } else { | ||||||
|     this->set_phase((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE |     this->action_request_.reset(); | ||||||
|                                                                     : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); |     this->force_send_control_ = false; | ||||||
|  |     this->reset_phase_(); | ||||||
|     return result; |     return result; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(uint8_t request_type, | haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_( | ||||||
|                                                                                   uint8_t message_type, |     haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|                                                                                   const uint8_t *data, |     size_t data_size) { | ||||||
|                                                                                   size_t data_size) { |   if (request_type != haier_protocol::FrameType::GET_DEVICE_VERSION) | ||||||
|   if (request_type != (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION) |  | ||||||
|     return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; |     return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||||
|   if (ProtocolPhases::WAITING_INIT_1_ANSWER != this->protocol_phase_) |   if (ProtocolPhases::SENDING_INIT_1 != this->protocol_phase_) | ||||||
|     return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; |     return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||||
|   // Invalid packet is expected answer |   // Invalid packet is expected answer | ||||||
|   if ((message_type == (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && |   if ((message_type == haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) && | ||||||
|       ((data[37] & 0x04) != 0)) { |       ((data[37] & 0x04) != 0)) { | ||||||
|     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " |     ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " | ||||||
|                   "instead of smartAir2"); |                   "instead of smartAir2"); | ||||||
| @@ -72,58 +88,35 @@ haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler | |||||||
|   return haier_protocol::HandlerError::HANDLER_OK; |   return haier_protocol::HandlerError::HANDLER_OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HandlerError Smartair2Climate::report_network_status_answer_handler_(uint8_t request_type, | haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cycle_for_init_( | ||||||
|                                                                                      uint8_t message_type, |     haier_protocol::FrameType 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) |   if (this->protocol_phase_ >= ProtocolPhases::IDLE) | ||||||
|     return HaierClimateBase::timeout_default_handler_(message_type); |     return HaierClimateBase::timeout_default_handler_(message_type); | ||||||
|   this->timeouts_counter_++; |   ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type, | ||||||
|   ESP_LOGI(TAG, "Answer timeout for command %02X, phase %d, timeout counter %d", message_type, |            phase_to_string_(this->protocol_phase_)); | ||||||
|            (int) this->protocol_phase_, this->timeouts_counter_); |   ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); | ||||||
|   if (this->timeouts_counter_ >= 3) { |   if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) | ||||||
|     ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); |     new_phase = ProtocolPhases::SENDING_INIT_1; | ||||||
|     if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) |   this->set_phase(new_phase); | ||||||
|       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; |   return haier_protocol::HandlerError::HANDLER_OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Smartair2Climate::set_handlers() { | void Smartair2Climate::set_handlers() { | ||||||
|   // Set handlers |   // Set handlers | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_VERSION), |       haier_protocol::FrameType::GET_DEVICE_VERSION, | ||||||
|       std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, |       std::bind(&Smartair2Climate::get_device_version_answer_handler_, this, std::placeholders::_1, | ||||||
|                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (smartair2_protocol::FrameType::CONTROL), |       haier_protocol::FrameType::CONTROL, | ||||||
|       std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, |       std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||||
|                 std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_answer_handler( |   this->haier_protocol_.set_answer_handler( | ||||||
|       (uint8_t) (smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), |       haier_protocol::FrameType::REPORT_NETWORK_STATUS, | ||||||
|       std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, |       std::bind(&Smartair2Climate::report_network_status_answer_handler_, this, std::placeholders::_1, | ||||||
|                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); |                 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); | ||||||
|   this->haier_protocol_.set_timeout_handler( |   this->haier_protocol_.set_default_timeout_handler( | ||||||
|       (uint8_t) (smartair2_protocol::FrameType::GET_DEVICE_ID), |       std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_, this, std::placeholders::_1)); | ||||||
|       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() { | void Smartair2Climate::dump_config() { | ||||||
| @@ -134,9 +127,7 @@ void Smartair2Climate::dump_config() { | |||||||
| void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { | void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { | ||||||
|   switch (this->protocol_phase_) { |   switch (this->protocol_phase_) { | ||||||
|     case ProtocolPhases::SENDING_INIT_1: |     case ProtocolPhases::SENDING_INIT_1: | ||||||
|       if (this->can_send_message() && |       if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) { | ||||||
|           (((this->timeouts_counter_ == 0) && (this->is_protocol_initialisation_interval_exceeded_(now))) || |  | ||||||
|            ((this->timeouts_counter_ > 0) && (this->is_message_interval_exceeded_(now))))) { |  | ||||||
|         // Indicate device capabilities: |         // Indicate device capabilities: | ||||||
|         // bit 0 - if 1 module support interactive mode |         // bit 0 - if 1 module support interactive mode | ||||||
|         // bit 1 - if 1 module support controller-device mode |         // bit 1 - if 1 module support controller-device mode | ||||||
| @@ -145,92 +136,65 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | |||||||
|         // bit 4..bit 15 - not used |         // bit 4..bit 15 - not used | ||||||
|         uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; |         uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; | ||||||
|         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( |         static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( | ||||||
|             (uint8_t) smartair2_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, |             haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); | ||||||
|             sizeof(module_capabilities)); |         this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL); | ||||||
|         this->send_message_(DEVICE_VERSION_REQUEST, false); |  | ||||||
|         this->set_phase(ProtocolPhases::WAITING_INIT_1_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_INIT_2: |     case ProtocolPhases::SENDING_INIT_2: | ||||||
|     case ProtocolPhases::WAITING_INIT_2_ANSWER: |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); |       this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: |     case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||||
|     case ProtocolPhases::SENDING_STATUS_REQUEST: |     case ProtocolPhases::SENDING_STATUS_REQUEST: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, |         static const haier_protocol::HaierMessage STATUS_REQUEST(haier_protocol::FrameType::CONTROL, 0x4D01); | ||||||
|                                                                  0x4D01); |         if (this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) { | ||||||
|         this->send_message_(STATUS_REQUEST, false); |           this->send_message_(STATUS_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL); | ||||||
|  |         } else { | ||||||
|  |           this->send_message_(STATUS_REQUEST, this->use_crc_); | ||||||
|  |         } | ||||||
|         this->last_status_request_ = now; |         this->last_status_request_ = now; | ||||||
|         this->set_phase((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| #ifdef USE_WIFI | #ifdef USE_WIFI | ||||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: |     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||||
|         this->send_message_( |         this->send_message_(this->get_wifi_signal_message_(), this->use_crc_); | ||||||
|             this->get_wifi_signal_message_((uint8_t) smartair2_protocol::FrameType::REPORT_NETWORK_STATUS), false); |  | ||||||
|         this->last_signal_request_ = now; |         this->last_signal_request_ = now; | ||||||
|         this->set_phase(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: |  | ||||||
|       break; |  | ||||||
| #else | #else | ||||||
|     case ProtocolPhases::SENDING_SIGNAL_LEVEL: |     case ProtocolPhases::SENDING_SIGNAL_LEVEL: | ||||||
|     case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: |  | ||||||
|       this->set_phase(ProtocolPhases::IDLE); |       this->set_phase(ProtocolPhases::IDLE); | ||||||
|       break; |       break; | ||||||
| #endif | #endif | ||||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: |     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||||
|     case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); |       this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: |     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||||
|     case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); |       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_CONTROL: |     case ProtocolPhases::SENDING_CONTROL: | ||||||
|       if (this->first_control_attempt_) { |       if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { | ||||||
|         this->control_request_timestamp_ = now; |         ESP_LOGI(TAG, "Sending control packet"); | ||||||
|         this->first_control_attempt_ = false; |         this->send_message_(get_control_message(), this->use_crc_, CONTROL_MESSAGE_RETRIES, | ||||||
|  |                             CONTROL_MESSAGE_RETRIES_INTERVAL); | ||||||
|       } |       } | ||||||
|       if (this->is_control_message_timeout_exceeded_(now)) { |       break; | ||||||
|         ESP_LOGW(TAG, "Sending control packet timeout!"); |     case ProtocolPhases::SENDING_ACTION_COMMAND: | ||||||
|         this->set_force_send_control_(false); |       if (this->action_request_.has_value()) { | ||||||
|         if (this->hvac_settings_.valid) |         if (this->action_request_.value().message.has_value()) { | ||||||
|           this->hvac_settings_.reset(); |           this->send_message_(this->action_request_.value().message.value(), this->use_crc_); | ||||||
|         this->forced_request_status_ = true; |           this->action_request_.value().message.reset(); | ||||||
|         this->forced_publish_ = true; |         } else { | ||||||
|  |           // Message already sent, reseting request and return to idle | ||||||
|  |           this->action_request_.reset(); | ||||||
|  |           this->set_phase(ProtocolPhases::IDLE); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!"); | ||||||
|         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); |  | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case ProtocolPhases::SENDING_POWER_ON_COMMAND: |  | ||||||
|     case ProtocolPhases::SENDING_POWER_OFF_COMMAND: |  | ||||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { |  | ||||||
|         haier_protocol::HaierMessage power_cmd( |  | ||||||
|             (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); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case ProtocolPhases::WAITING_INIT_1_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_STATUS_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_CONTROL_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_POWER_ON_ANSWER: |  | ||||||
|     case ProtocolPhases::WAITING_POWER_OFF_ANSWER: |  | ||||||
|       break; |  | ||||||
|     case ProtocolPhases::IDLE: { |     case ProtocolPhases::IDLE: { | ||||||
|       if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { |       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); | ||||||
| @@ -245,55 +209,55 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | |||||||
|     } break; |     } break; | ||||||
|     default: |     default: | ||||||
|       // Shouldn't get here |       // Shouldn't get here | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|       ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", |       ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", | ||||||
|                phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); |                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_); |  | ||||||
| #endif |  | ||||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); |       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | haier_protocol::HaierMessage Smartair2Climate::get_power_message(bool state) { | ||||||
|  |   if (state) { | ||||||
|  |     static haier_protocol::HaierMessage power_on_message(haier_protocol::FrameType::CONTROL, 0x4D02); | ||||||
|  |     return power_on_message; | ||||||
|  |   } else { | ||||||
|  |     static haier_protocol::HaierMessage power_off_message(haier_protocol::FrameType::CONTROL, 0x4D03); | ||||||
|  |     return power_off_message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | ||||||
|   uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)]; |   uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)]; | ||||||
|   memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl)); |   memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl)); | ||||||
|   smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer; |   smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer; | ||||||
|   out_data->cntrl = 0; |   out_data->cntrl = 0; | ||||||
|   if (this->hvac_settings_.valid) { |   if (this->current_hvac_settings_.valid) { | ||||||
|     HvacSettings climate_control; |     HvacSettings &climate_control = this->current_hvac_settings_; | ||||||
|     climate_control = this->hvac_settings_; |  | ||||||
|     if (climate_control.mode.has_value()) { |     if (climate_control.mode.has_value()) { | ||||||
|       switch (climate_control.mode.value()) { |       switch (climate_control.mode.value()) { | ||||||
|         case CLIMATE_MODE_OFF: |         case CLIMATE_MODE_OFF: | ||||||
|           out_data->ac_power = 0; |           out_data->ac_power = 0; | ||||||
|           break; |           break; | ||||||
|  |  | ||||||
|         case CLIMATE_MODE_HEAT_COOL: |         case CLIMATE_MODE_HEAT_COOL: | ||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; |           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; | ||||||
|           out_data->fan_mode = this->other_modes_fan_speed_; |           out_data->fan_mode = this->other_modes_fan_speed_; | ||||||
|           break; |           break; | ||||||
|  |  | ||||||
|         case CLIMATE_MODE_HEAT: |         case CLIMATE_MODE_HEAT: | ||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT; |           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT; | ||||||
|           out_data->fan_mode = this->other_modes_fan_speed_; |           out_data->fan_mode = this->other_modes_fan_speed_; | ||||||
|           break; |           break; | ||||||
|  |  | ||||||
|         case CLIMATE_MODE_DRY: |         case CLIMATE_MODE_DRY: | ||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY; |           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY; | ||||||
|           out_data->fan_mode = this->other_modes_fan_speed_; |           out_data->fan_mode = this->other_modes_fan_speed_; | ||||||
|           break; |           break; | ||||||
|  |  | ||||||
|         case CLIMATE_MODE_FAN_ONLY: |         case CLIMATE_MODE_FAN_ONLY: | ||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN; |           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN; | ||||||
|           out_data->fan_mode = this->fan_mode_speed_;  // Auto doesn't work in fan only mode |           out_data->fan_mode = this->fan_mode_speed_;  // Auto doesn't work in fan only mode | ||||||
|           break; |           break; | ||||||
|  |  | ||||||
|         case CLIMATE_MODE_COOL: |         case CLIMATE_MODE_COOL: | ||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL; |           out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL; | ||||||
| @@ -327,32 +291,49 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | |||||||
|     } |     } | ||||||
|     // Set swing mode |     // Set swing mode | ||||||
|     if (climate_control.swing_mode.has_value()) { |     if (climate_control.swing_mode.has_value()) { | ||||||
|       switch (climate_control.swing_mode.value()) { |       if (this->use_alternative_swing_control_) { | ||||||
|         case CLIMATE_SWING_OFF: |         switch (climate_control.swing_mode.value()) { | ||||||
|           out_data->use_swing_bits = 0; |           case CLIMATE_SWING_OFF: | ||||||
|           out_data->swing_both = 0; |             out_data->swing_mode = 0; | ||||||
|           break; |             break; | ||||||
|         case CLIMATE_SWING_VERTICAL: |           case CLIMATE_SWING_VERTICAL: | ||||||
|           out_data->swing_both = 0; |             out_data->swing_mode = 1; | ||||||
|           out_data->vertical_swing = 1; |             break; | ||||||
|           out_data->horizontal_swing = 0; |           case CLIMATE_SWING_HORIZONTAL: | ||||||
|           break; |             out_data->swing_mode = 2; | ||||||
|         case CLIMATE_SWING_HORIZONTAL: |             break; | ||||||
|           out_data->swing_both = 0; |           case CLIMATE_SWING_BOTH: | ||||||
|           out_data->vertical_swing = 0; |             out_data->swing_mode = 3; | ||||||
|           out_data->horizontal_swing = 1; |             break; | ||||||
|           break; |         } | ||||||
|         case CLIMATE_SWING_BOTH: |       } else { | ||||||
|           out_data->swing_both = 1; |         switch (climate_control.swing_mode.value()) { | ||||||
|           out_data->use_swing_bits = 0; |           case CLIMATE_SWING_OFF: | ||||||
|           out_data->vertical_swing = 0; |             out_data->use_swing_bits = 0; | ||||||
|           out_data->horizontal_swing = 0; |             out_data->swing_mode = 0; | ||||||
|           break; |             break; | ||||||
|  |           case CLIMATE_SWING_VERTICAL: | ||||||
|  |             out_data->swing_mode = 0; | ||||||
|  |             out_data->vertical_swing = 1; | ||||||
|  |             out_data->horizontal_swing = 0; | ||||||
|  |             break; | ||||||
|  |           case CLIMATE_SWING_HORIZONTAL: | ||||||
|  |             out_data->swing_mode = 0; | ||||||
|  |             out_data->vertical_swing = 0; | ||||||
|  |             out_data->horizontal_swing = 1; | ||||||
|  |             break; | ||||||
|  |           case CLIMATE_SWING_BOTH: | ||||||
|  |             out_data->swing_mode = 1; | ||||||
|  |             out_data->use_swing_bits = 0; | ||||||
|  |             out_data->vertical_swing = 0; | ||||||
|  |             out_data->horizontal_swing = 0; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (climate_control.target_temperature.has_value()) { |     if (climate_control.target_temperature.has_value()) { | ||||||
|       float target_temp = climate_control.target_temperature.value(); |       float target_temp = climate_control.target_temperature.value(); | ||||||
|       out_data->set_point = target_temp - 16;  // set the temperature with offset 16 |       out_data->set_point = ((int) target_temp) - 16;  // set the temperature with offset 16 | ||||||
|       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; |       out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0; | ||||||
|     } |     } | ||||||
|     if (out_data->ac_power == 0) { |     if (out_data->ac_power == 0) { | ||||||
| @@ -383,7 +364,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | |||||||
|   } |   } | ||||||
|   out_data->display_status = this->display_status_ ? 0 : 1; |   out_data->display_status = this->display_status_ ? 0 : 1; | ||||||
|   out_data->health_mode = this->health_mode_ ? 1 : 0; |   out_data->health_mode = this->health_mode_ ? 1 : 0; | ||||||
|   return haier_protocol::HaierMessage((uint8_t) smartair2_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, |   return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, | ||||||
|                                       sizeof(smartair2_protocol::HaierPacketControl)); |                                       sizeof(smartair2_protocol::HaierPacketControl)); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -459,13 +440,19 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | |||||||
|         // Do something only if display status changed |         // Do something only if display status changed | ||||||
|         if (this->mode == CLIMATE_MODE_OFF) { |         if (this->mode == CLIMATE_MODE_OFF) { | ||||||
|           // AC just turned on from remote need to turn off display |           // AC just turned on from remote need to turn off display | ||||||
|           this->set_force_send_control_(true); |           this->force_send_control_ = true; | ||||||
|         } else { |         } else { | ||||||
|           this->display_status_ = disp_status; |           this->display_status_ = disp_status; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |   { | ||||||
|  |     // Health mode | ||||||
|  |     bool old_health_mode = this->health_mode_; | ||||||
|  |     this->health_mode_ = packet.control.health_mode == 1; | ||||||
|  |     should_publish = should_publish || (old_health_mode != this->health_mode_); | ||||||
|  |   } | ||||||
|   { |   { | ||||||
|     // Climate mode |     // Climate mode | ||||||
|     ClimateMode old_mode = this->mode; |     ClimateMode old_mode = this->mode; | ||||||
| @@ -493,70 +480,57 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | |||||||
|     } |     } | ||||||
|     should_publish = should_publish || (old_mode != this->mode); |     should_publish = should_publish || (old_mode != this->mode); | ||||||
|   } |   } | ||||||
|   { |  | ||||||
|     // Health mode |  | ||||||
|     bool old_health_mode = this->health_mode_; |  | ||||||
|     this->health_mode_ = packet.control.health_mode == 1; |  | ||||||
|     should_publish = should_publish || (old_health_mode != this->health_mode_); |  | ||||||
|   } |  | ||||||
|   { |   { | ||||||
|     // Swing mode |     // Swing mode | ||||||
|     ClimateSwingMode old_swing_mode = this->swing_mode; |     ClimateSwingMode old_swing_mode = this->swing_mode; | ||||||
|     if (packet.control.swing_both == 0) { |     if (this->use_alternative_swing_control_) { | ||||||
|       if (packet.control.vertical_swing != 0) { |       switch (packet.control.swing_mode) { | ||||||
|         this->swing_mode = CLIMATE_SWING_VERTICAL; |         case 1: | ||||||
|       } else if (packet.control.horizontal_swing != 0) { |           this->swing_mode = CLIMATE_SWING_VERTICAL; | ||||||
|         this->swing_mode = CLIMATE_SWING_HORIZONTAL; |           break; | ||||||
|       } else { |         case 2: | ||||||
|         this->swing_mode = CLIMATE_SWING_OFF; |           this->swing_mode = CLIMATE_SWING_HORIZONTAL; | ||||||
|  |           break; | ||||||
|  |         case 3: | ||||||
|  |           this->swing_mode = CLIMATE_SWING_BOTH; | ||||||
|  |           break; | ||||||
|  |         default: | ||||||
|  |           this->swing_mode = CLIMATE_SWING_OFF; | ||||||
|  |           break; | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       swing_mode = CLIMATE_SWING_BOTH; |       if (packet.control.swing_mode == 0) { | ||||||
|  |         if (packet.control.vertical_swing != 0) { | ||||||
|  |           this->swing_mode = CLIMATE_SWING_VERTICAL; | ||||||
|  |         } else if (packet.control.horizontal_swing != 0) { | ||||||
|  |           this->swing_mode = CLIMATE_SWING_HORIZONTAL; | ||||||
|  |         } else { | ||||||
|  |           this->swing_mode = CLIMATE_SWING_OFF; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         swing_mode = CLIMATE_SWING_BOTH; | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     should_publish = should_publish || (old_swing_mode != this->swing_mode); |     should_publish = should_publish || (old_swing_mode != this->swing_mode); | ||||||
|   } |   } | ||||||
|   this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); |   this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); | ||||||
|   if (this->forced_publish_ || should_publish) { |   if (should_publish) { | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|     std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); |  | ||||||
| #endif |  | ||||||
|     this->publish_state(); |     this->publish_state(); | ||||||
| #if (HAIER_LOG_LEVEL > 4) |  | ||||||
|     ESP_LOGV(TAG, "Publish delay: %lld ms", |  | ||||||
|              std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - |  | ||||||
|                                                                    _publish_start) |  | ||||||
|                  .count()); |  | ||||||
| #endif |  | ||||||
|     this->forced_publish_ = false; |  | ||||||
|   } |   } | ||||||
|   if (should_publish) { |   if (should_publish) { | ||||||
|     ESP_LOGI(TAG, "HVAC values changed"); |     ESP_LOGI(TAG, "HVAC values changed"); | ||||||
|   } |   } | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG; | ||||||
|                   "HVAC Mode = 0x%X", packet.control.ac_mode); |   esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode); | ||||||
|                   "Fan speed Status = 0x%X", packet.control.fan_mode); |   esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |   esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing); | ||||||
|                   "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); |   esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point); | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |  | ||||||
|                   "Vertical Swing Status = 0x%X", packet.control.vertical_swing); |  | ||||||
|   esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, |  | ||||||
|                   "Set Point Status = 0x%X", packet.control.set_point); |  | ||||||
|   return haier_protocol::HandlerError::HANDLER_OK; |   return haier_protocol::HandlerError::HANDLER_OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Smartair2Climate::is_message_invalid(uint8_t message_type) { | void Smartair2Climate::set_alternative_swing_control(bool swing_control) { | ||||||
|   return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; |   this->use_alternative_swing_control_ = swing_control; | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 haier | ||||||
|   | |||||||
| @@ -13,27 +13,27 @@ class Smartair2Climate : public HaierClimateBase { | |||||||
|   Smartair2Climate &operator=(const Smartair2Climate &) = delete; |   Smartair2Climate &operator=(const Smartair2Climate &) = delete; | ||||||
|   ~Smartair2Climate(); |   ~Smartair2Climate(); | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |   void set_alternative_swing_control(bool swing_control); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void set_handlers() override; |   void set_handlers() override; | ||||||
|   void process_phase(std::chrono::steady_clock::time_point now) override; |   void process_phase(std::chrono::steady_clock::time_point now) override; | ||||||
|  |   haier_protocol::HaierMessage get_power_message(bool state) override; | ||||||
|   haier_protocol::HaierMessage get_control_message() override; |   haier_protocol::HaierMessage get_control_message() override; | ||||||
|   bool is_message_invalid(uint8_t message_type) override; |   // Answer handlers | ||||||
|   void set_phase(HaierClimateBase::ProtocolPhases phase) override; |   haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, | ||||||
|   // Answer and timeout handlers |                                                haier_protocol::FrameType message_type, const uint8_t *data, | ||||||
|   haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, |  | ||||||
|                                                size_t data_size); |                                                size_t data_size); | ||||||
|   haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                                   haier_protocol::FrameType message_type, | ||||||
|                                                                   const uint8_t *data, size_t data_size); |                                                                   const uint8_t *data, size_t data_size); | ||||||
|   haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, | ||||||
|  |                                                              haier_protocol::FrameType message_type, | ||||||
|                                                              const uint8_t *data, size_t data_size); |                                                              const uint8_t *data, size_t data_size); | ||||||
|   haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, |   haier_protocol::HandlerError messages_timeout_handler_with_cycle_for_init_(haier_protocol::FrameType message_type); | ||||||
|                                                                      const uint8_t *data, size_t data_size); |  | ||||||
|   haier_protocol::HandlerError initial_messages_timeout_handler_(uint8_t message_type); |  | ||||||
|   // Helper functions |   // Helper functions | ||||||
|   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); |   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); | ||||||
|   std::unique_ptr<uint8_t[]> last_status_message_; |   bool use_alternative_swing_control_; | ||||||
|   unsigned int timeouts_counter_; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace haier | }  // namespace haier | ||||||
|   | |||||||
| @@ -41,8 +41,9 @@ struct HaierPacketControl { | |||||||
|   // 24 |   // 24 | ||||||
|   uint8_t : 8; |   uint8_t : 8; | ||||||
|   // 25 |   // 25 | ||||||
|   uint8_t swing_both;  // If 1 - swing both direction, if 0 - horizontal_swing and vertical_swing define |   uint8_t swing_mode;  // In normal mode: If 1 - swing both direction, if 0 - horizontal_swing and | ||||||
|                        // vertical/horizontal/off |                        // vertical_swing define vertical/horizontal/off | ||||||
|  |                        // In alternative mode: 0 - off, 01 - vertical,  02 - horizontal, 03 - both | ||||||
|   // 26 |   // 26 | ||||||
|   uint8_t : 3; |   uint8_t : 3; | ||||||
|   uint8_t use_fahrenheit : 1; |   uint8_t use_fahrenheit : 1; | ||||||
| @@ -82,19 +83,6 @@ struct HaierStatus { | |||||||
|   HaierPacketControl control; |   HaierPacketControl control; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| enum class FrameType : uint8_t { |  | ||||||
|   CONTROL = 0x01, |  | ||||||
|   STATUS = 0x02, |  | ||||||
|   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, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| }  // namespace smartair2_protocol | }  // namespace smartair2_protocol | ||||||
| }  // namespace haier | }  // namespace haier | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ lib_deps = | |||||||
|     bblanchon/ArduinoJson@6.18.5           ; json |     bblanchon/ArduinoJson@6.18.5           ; json | ||||||
|     wjtje/qr-code-generator-library@1.7.0  ; qr_code |     wjtje/qr-code-generator-library@1.7.0  ; qr_code | ||||||
|     functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 |     functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 | ||||||
|     pavlodn/HaierProtocol@0.9.20           ; haier |     pavlodn/HaierProtocol@0.9.24           ; haier | ||||||
|     ; This is using the repository until a new release is published to PlatformIO |     ; 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 |     https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library | ||||||
| build_flags = | build_flags = | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user