mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Added alarm processing for Haier component (hOn protocol) (#5965)
This commit is contained in:
		| @@ -18,6 +18,7 @@ from esphome.const import ( | ||||
|     CONF_SUPPORTED_SWING_MODES, | ||||
|     CONF_TARGET_TEMPERATURE, | ||||
|     CONF_TEMPERATURE_STEP, | ||||
|     CONF_TRIGGER_ID, | ||||
|     CONF_VISUAL, | ||||
|     CONF_WIFI, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
| @@ -49,6 +50,8 @@ CONF_CONTROL_METHOD = "control_method" | ||||
| CONF_CONTROL_PACKET_SIZE = "control_packet_size" | ||||
| CONF_DISPLAY = "display" | ||||
| CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" | ||||
| CONF_ON_ALARM_START = "on_alarm_start" | ||||
| CONF_ON_ALARM_END = "on_alarm_end" | ||||
| CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" | ||||
| CONF_VERTICAL_AIRFLOW = "vertical_airflow" | ||||
| CONF_WIFI_SIGNAL = "wifi_signal" | ||||
| @@ -85,8 +88,8 @@ AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = { | ||||
| } | ||||
|  | ||||
| SUPPORTED_SWING_MODES_OPTIONS = { | ||||
|     "OFF": ClimateSwingMode.CLIMATE_SWING_OFF,  # always available | ||||
|     "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL,  # always available | ||||
|     "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, | ||||
|     "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, | ||||
|     "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, | ||||
|     "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, | ||||
| } | ||||
| @@ -101,13 +104,15 @@ SUPPORTED_CLIMATE_MODES_OPTIONS = { | ||||
| } | ||||
|  | ||||
| SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { | ||||
|     "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, | ||||
|     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, | ||||
|     "COMFORT": ClimatePreset.CLIMATE_PRESET_COMFORT, | ||||
| } | ||||
|  | ||||
| SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { | ||||
|     "ECO": ClimatePreset.CLIMATE_PRESET_ECO, | ||||
|     "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, | ||||
|     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, | ||||
|     "ECO": ClimatePreset.CLIMATE_PRESET_ECO, | ||||
|     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, | ||||
| } | ||||
|  | ||||
| @@ -118,6 +123,16 @@ SUPPORTED_HON_CONTROL_METHODS = { | ||||
|     "SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER, | ||||
| } | ||||
|  | ||||
| HaierAlarmStartTrigger = haier_ns.class_( | ||||
|     "HaierAlarmStartTrigger", | ||||
|     automation.Trigger.template(cg.uint8, cg.const_char_ptr), | ||||
| ) | ||||
|  | ||||
| HaierAlarmEndTrigger = haier_ns.class_( | ||||
|     "HaierAlarmEndTrigger", | ||||
|     automation.Trigger.template(cg.uint8, cg.const_char_ptr), | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate_visual(config): | ||||
|     if CONF_VISUAL in config: | ||||
| @@ -200,9 +215,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|                     ): cv.boolean, | ||||
|                     cv.Optional( | ||||
|                         CONF_SUPPORTED_PRESETS, | ||||
|                         default=list( | ||||
|                             SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS.keys() | ||||
|                         ), | ||||
|                         default=list(["BOOST", "COMFORT"]),  # No AWAY by default | ||||
|                     ): cv.ensure_list( | ||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS, upper=True) | ||||
|                     ), | ||||
| @@ -222,7 +235,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|                     ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), | ||||
|                     cv.Optional( | ||||
|                         CONF_SUPPORTED_PRESETS, | ||||
|                         default=list(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS.keys()), | ||||
|                         default=list(["BOOST", "ECO", "SLEEP"]),  # No AWAY by default | ||||
|                     ): cv.ensure_list( | ||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) | ||||
|                     ), | ||||
| @@ -233,6 +246,20 @@ CONFIG_SCHEMA = cv.All( | ||||
|                         device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                         state_class=STATE_CLASS_MEASUREMENT, | ||||
|                     ), | ||||
|                     cv.Optional(CONF_ON_ALARM_START): automation.validate_automation( | ||||
|                         { | ||||
|                             cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                                 HaierAlarmStartTrigger | ||||
|                             ), | ||||
|                         } | ||||
|                     ), | ||||
|                     cv.Optional(CONF_ON_ALARM_END): automation.validate_automation( | ||||
|                         { | ||||
|                             cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||
|                                 HaierAlarmEndTrigger | ||||
|                             ), | ||||
|                         } | ||||
|                     ), | ||||
|                 } | ||||
|             ), | ||||
|         }, | ||||
| @@ -457,5 +484,15 @@ async def to_code(config): | ||||
|                 config[CONF_CONTROL_PACKET_SIZE] - PROTOCOL_CONTROL_PACKET_SIZE | ||||
|             ) | ||||
|         ) | ||||
|     for conf in config.get(CONF_ON_ALARM_START, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation( | ||||
|             trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf | ||||
|         ) | ||||
|     for conf in config.get(CONF_ON_ALARM_END, []): | ||||
|         trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) | ||||
|         await automation.build_automation( | ||||
|             trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf | ||||
|         ) | ||||
|     # https://github.com/paveldn/HaierProtocol | ||||
|     cg.add_library("pavlodn/HaierProtocol", "0.9.24") | ||||
|   | ||||
| @@ -25,13 +25,14 @@ const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { | ||||
|       "SENDING_INIT_1", | ||||
|       "SENDING_INIT_2", | ||||
|       "SENDING_FIRST_STATUS_REQUEST", | ||||
|       "SENDING_ALARM_STATUS_REQUEST", | ||||
|       "SENDING_FIRST_ALARM_STATUS_REQUEST", | ||||
|       "IDLE", | ||||
|       "SENDING_STATUS_REQUEST", | ||||
|       "SENDING_UPDATE_SIGNAL_REQUEST", | ||||
|       "SENDING_SIGNAL_LEVEL", | ||||
|       "SENDING_CONTROL", | ||||
|       "SENDING_ACTION_COMMAND", | ||||
|       "SENDING_ALARM_STATUS_REQUEST", | ||||
|       "UNKNOWN"  // Should be the last! | ||||
|   }; | ||||
|   static_assert( | ||||
|   | ||||
| @@ -64,7 +64,7 @@ class HaierClimateBase : public esphome::Component, | ||||
|     SENDING_INIT_1 = 0, | ||||
|     SENDING_INIT_2, | ||||
|     SENDING_FIRST_STATUS_REQUEST, | ||||
|     SENDING_ALARM_STATUS_REQUEST, | ||||
|     SENDING_FIRST_ALARM_STATUS_REQUEST, | ||||
|     // FUNCTIONAL STATE | ||||
|     IDLE, | ||||
|     SENDING_STATUS_REQUEST, | ||||
| @@ -72,6 +72,7 @@ class HaierClimateBase : public esphome::Component, | ||||
|     SENDING_SIGNAL_LEVEL, | ||||
|     SENDING_CONTROL, | ||||
|     SENDING_ACTION_COMMAND, | ||||
|     SENDING_ALARM_STATUS_REQUEST, | ||||
|     NUM_PROTOCOL_PHASES | ||||
|   }; | ||||
|   const char *phase_to_string_(ProtocolPhases phase); | ||||
|   | ||||
| @@ -16,6 +16,7 @@ constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; | ||||
| 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); | ||||
| constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000; | ||||
|  | ||||
| hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { | ||||
|   switch (direction) { | ||||
| @@ -110,6 +111,14 @@ void HonClimate::start_steri_cleaning() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| void HonClimate::add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback) { | ||||
|   this->alarm_start_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| void HonClimate::add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback) { | ||||
|   this->alarm_end_callback_.add(std::move(callback)); | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| @@ -194,7 +203,7 @@ haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameTy | ||||
|       switch (this->protocol_phase_) { | ||||
|         case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: | ||||
|           ESP_LOGI(TAG, "First HVAC status received"); | ||||
|           this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); | ||||
|           this->set_phase(ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST); | ||||
|           break; | ||||
|         case ProtocolPhases::SENDING_ACTION_COMMAND: | ||||
|           // Do nothing, phase will be changed in process_phase | ||||
| @@ -251,12 +260,15 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_ | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE; | ||||
|     } | ||||
|     if (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) { | ||||
|     if ((this->protocol_phase_ != ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) && | ||||
|         (this->protocol_phase_ != ProtocolPhases::SENDING_ALARM_STATUS_REQUEST)) { | ||||
|       // Don't expect this answer now | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; | ||||
|     } | ||||
|     memcpy(this->active_alarms_, data + 2, 8); | ||||
|     if (data_size < sizeof(active_alarms_) + 2) | ||||
|       return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||
|     this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE); | ||||
|     this->set_phase(ProtocolPhases::IDLE); | ||||
|     return haier_protocol::HandlerError::HANDLER_OK; | ||||
|   } else { | ||||
| @@ -265,6 +277,19 @@ haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_ | ||||
|   } | ||||
| } | ||||
|  | ||||
| haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type, | ||||
|                                                                        const uint8_t *buffer, size_t size) { | ||||
|   haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; | ||||
|   if (size < sizeof(this->active_alarms_) + 2) { | ||||
|     // Log error but confirm anyway to avoid to many messages | ||||
|     result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||
|   } | ||||
|   this->process_alarm_message_(buffer, size, true); | ||||
|   this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM)); | ||||
|   this->last_alarm_request_ = std::chrono::steady_clock::now(); | ||||
|   return result; | ||||
| } | ||||
|  | ||||
| void HonClimate::set_handlers() { | ||||
|   // Set handlers | ||||
|   this->haier_protocol_.set_answer_handler( | ||||
| @@ -291,6 +316,10 @@ void HonClimate::set_handlers() { | ||||
|       haier_protocol::FrameType::REPORT_NETWORK_STATUS, | ||||
|       std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||
|                 std::placeholders::_3, std::placeholders::_4)); | ||||
|   this->haier_protocol_.set_message_handler( | ||||
|       haier_protocol::FrameType::ALARM_STATUS, | ||||
|       std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2, | ||||
|                 std::placeholders::_3)); | ||||
| } | ||||
|  | ||||
| void HonClimate::dump_config() { | ||||
| @@ -363,10 +392,12 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       break; | ||||
| #endif | ||||
|     case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
|       if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { | ||||
|         static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS); | ||||
|         this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); | ||||
|         this->last_alarm_request_ = now; | ||||
|       } | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_CONTROL: | ||||
| @@ -417,12 +448,16 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { | ||||
|       if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { | ||||
|         this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST); | ||||
|         this->forced_request_status_ = false; | ||||
|       } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() > | ||||
|                  ALARM_STATUS_REQUEST_INTERVAL_MS) { | ||||
|         this->set_phase(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); | ||||
|       } | ||||
| #ifdef USE_WIFI | ||||
|       else if (this->send_wifi_signal_ && | ||||
|                (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() > | ||||
|                 SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) | ||||
|                 SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) { | ||||
|         this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); | ||||
|       } | ||||
| #endif | ||||
|     } break; | ||||
|     default: | ||||
| @@ -452,6 +487,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||
|   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; | ||||
|   control_out_buffer[4] = 0;  // This byte should be cleared before setting values | ||||
|   bool has_hvac_settings = false; | ||||
|   if (this->current_hvac_settings_.valid) { | ||||
|     has_hvac_settings = true; | ||||
| @@ -552,31 +588,41 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||
|           out_data->quiet_mode = 0; | ||||
|           out_data->fast_mode = 0; | ||||
|           out_data->sleep_mode = 0; | ||||
|           out_data->ten_degree = 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_ECO: | ||||
|           // Eco is not supported in Fan only mode | ||||
|           out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; | ||||
|           out_data->fast_mode = 0; | ||||
|           out_data->sleep_mode = 0; | ||||
|           out_data->ten_degree = 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_BOOST: | ||||
|           out_data->quiet_mode = 0; | ||||
|           // Boost is not supported in Fan only mode | ||||
|           out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; | ||||
|           out_data->sleep_mode = 0; | ||||
|           out_data->ten_degree = 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_AWAY: | ||||
|           out_data->quiet_mode = 0; | ||||
|           out_data->fast_mode = 0; | ||||
|           out_data->sleep_mode = 0; | ||||
|           // 10 degrees allowed only in heat mode | ||||
|           out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_SLEEP: | ||||
|           out_data->quiet_mode = 0; | ||||
|           out_data->fast_mode = 0; | ||||
|           out_data->sleep_mode = 1; | ||||
|           out_data->ten_degree = 0; | ||||
|           break; | ||||
|         default: | ||||
|           ESP_LOGE("Control", "Unsupported preset"); | ||||
|           out_data->quiet_mode = 0; | ||||
|           out_data->fast_mode = 0; | ||||
|           out_data->sleep_mode = 0; | ||||
|           out_data->ten_degree = 0; | ||||
|           break; | ||||
|       } | ||||
|     } | ||||
| @@ -595,6 +641,50 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||
|                                       control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); | ||||
| } | ||||
|  | ||||
| void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) { | ||||
|   constexpr size_t active_alarms_size = sizeof(this->active_alarms_); | ||||
|   if (size >= active_alarms_size + 2) { | ||||
|     if (check_new) { | ||||
|       size_t alarm_code = 0; | ||||
|       for (int i = active_alarms_size - 1; i >= 0; i--) { | ||||
|         if (packet[2 + i] != active_alarms_[i]) { | ||||
|           uint8_t alarm_bit = 1; | ||||
|           for (int b = 0; b < 8; b++) { | ||||
|             if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) { | ||||
|               bool alarm_status = (packet[2 + i] & alarm_bit) != 0; | ||||
|               int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO; | ||||
|               const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT | ||||
|                                               ? esphome::haier::hon_protocol::HON_ALARM_MESSAGES[alarm_code].c_str() | ||||
|                                               : "Unknown"; | ||||
|               esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated", | ||||
|                               alarm_code, alarm_message); | ||||
|               if (alarm_status) { | ||||
|                 this->alarm_start_callback_.call(alarm_code, alarm_message); | ||||
|                 this->active_alarm_count_ += 1.0f; | ||||
|               } else { | ||||
|                 this->alarm_end_callback_.call(alarm_code, alarm_message); | ||||
|                 this->active_alarm_count_ -= 1.0f; | ||||
|               } | ||||
|             } | ||||
|             alarm_bit <<= 1; | ||||
|             alarm_code++; | ||||
|           } | ||||
|           active_alarms_[i] = packet[2 + i]; | ||||
|         } else | ||||
|           alarm_code += 8; | ||||
|       } | ||||
|     } else { | ||||
|       float alarm_count = 0.0f; | ||||
|       static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; | ||||
|       for (size_t i = 0; i < sizeof(this->active_alarms_); i++) { | ||||
|         alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]); | ||||
|       } | ||||
|       this->active_alarm_count_ = alarm_count; | ||||
|       memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_)); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { | ||||
|   if (size < hon_protocol::HAIER_STATUS_FRAME_SIZE + this->extra_control_packet_bytes_) | ||||
|     return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; | ||||
| @@ -626,6 +716,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | ||||
|       this->preset = CLIMATE_PRESET_BOOST; | ||||
|     } else if (packet.control.sleep_mode != 0) { | ||||
|       this->preset = CLIMATE_PRESET_SLEEP; | ||||
|     } else if (packet.control.ten_degree != 0) { | ||||
|       this->preset = CLIMATE_PRESET_AWAY; | ||||
|     } else { | ||||
|       this->preset = CLIMATE_PRESET_NONE; | ||||
|     } | ||||
| @@ -882,25 +974,35 @@ void HonClimate::fill_control_messages_queue_() { | ||||
|   // CLimate preset | ||||
|   { | ||||
|     uint8_t fast_mode_buf[] = {0x00, 0xFF}; | ||||
|     uint8_t away_mode_buf[] = {0x00, 0xFF}; | ||||
|     if (!new_power) { | ||||
|       // If AC is off - no presets allowed | ||||
|       quiet_mode_buf[1] = 0x00; | ||||
|       fast_mode_buf[1] = 0x00; | ||||
|       away_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; | ||||
|           away_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; | ||||
|           away_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; | ||||
|           away_mode_buf[1] = 0x00; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_AWAY: | ||||
|           quiet_mode_buf[1] = 0x00; | ||||
|           fast_mode_buf[1] = 0x00; | ||||
|           away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; | ||||
|           break; | ||||
|         default: | ||||
|           ESP_LOGE("Control", "Unsupported preset"); | ||||
| @@ -921,6 +1023,13 @@ void HonClimate::fill_control_messages_queue_() { | ||||
|                                            (uint8_t) hon_protocol::DataParameters::FAST_MODE, | ||||
|                                        fast_mode_buf, 2)); | ||||
|     } | ||||
|     if (away_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::TEN_DEGREE, | ||||
|                                        away_mode_buf, 2)); | ||||
|     } | ||||
|   } | ||||
|   // Target temperature | ||||
|   if (climate_control.target_temperature.has_value()) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include <chrono> | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "haier_base.h" | ||||
|  | ||||
| namespace esphome { | ||||
| @@ -52,6 +53,9 @@ class HonClimate : public HaierClimateBase { | ||||
|   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; }; | ||||
|   void add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback); | ||||
|   void add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback); | ||||
|   float get_active_alarm_count() const { return this->active_alarm_count_; } | ||||
|  | ||||
|  protected: | ||||
|   void set_handlers() override; | ||||
| @@ -77,8 +81,11 @@ class HonClimate : public HaierClimateBase { | ||||
|   haier_protocol::HandlerError get_alarm_status_answer_handler_(haier_protocol::FrameType request_type, | ||||
|                                                                 haier_protocol::FrameType message_type, | ||||
|                                                                 const uint8_t *data, size_t data_size); | ||||
|   haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, | ||||
|                                                              size_t size); | ||||
|   // Helper functions | ||||
|   haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); | ||||
|   void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new); | ||||
|   void fill_control_messages_queue_(); | ||||
|   void clear_control_messages_queue_(); | ||||
|  | ||||
| @@ -101,6 +108,26 @@ class HonClimate : public HaierClimateBase { | ||||
|   HonControlMethod control_method_; | ||||
|   esphome::sensor::Sensor *outdoor_sensor_; | ||||
|   std::queue<haier_protocol::HaierMessage> control_messages_queue_; | ||||
|   CallbackManager<void(uint8_t, const char *)> alarm_start_callback_{}; | ||||
|   CallbackManager<void(uint8_t, const char *)> alarm_end_callback_{}; | ||||
|   float active_alarm_count_{NAN}; | ||||
|   std::chrono::steady_clock::time_point last_alarm_request_; | ||||
| }; | ||||
|  | ||||
| class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { | ||||
|  public: | ||||
|   explicit HaierAlarmStartTrigger(HonClimate *parent) { | ||||
|     parent->add_alarm_start_callback( | ||||
|         [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class HaierAlarmEndTrigger : public Trigger<uint8_t, const char *> { | ||||
|  public: | ||||
|   explicit HaierAlarmEndTrigger(HonClimate *parent) { | ||||
|     parent->add_alarm_end_callback( | ||||
|         [this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| }  // namespace haier | ||||
|   | ||||
| @@ -163,6 +163,62 @@ enum class SubcommandsControl : uint16_t { | ||||
|                                   // content: all values like in status packet) | ||||
| }; | ||||
|  | ||||
| const std::string HON_ALARM_MESSAGES[] = { | ||||
|     "Outdoor module failure", | ||||
|     "Outdoor defrost sensor failure", | ||||
|     "Outdoor compressor exhaust sensor failure", | ||||
|     "Outdoor EEPROM abnormality", | ||||
|     "Indoor coil sensor failure", | ||||
|     "Indoor-outdoor communication failure", | ||||
|     "Power supply overvoltage protection", | ||||
|     "Communication failure between panel and indoor unit", | ||||
|     "Outdoor compressor overheat protection", | ||||
|     "Outdoor environmental sensor abnormality", | ||||
|     "Full water protection", | ||||
|     "Indoor EEPROM failure", | ||||
|     "Outdoor out air sensor failure", | ||||
|     "CBD and module communication failure", | ||||
|     "Indoor DC fan failure", | ||||
|     "Outdoor DC fan failure", | ||||
|     "Door switch failure", | ||||
|     "Dust filter needs cleaning reminder", | ||||
|     "Water shortage protection", | ||||
|     "Humidity sensor failure", | ||||
|     "Indoor temperature sensor failure", | ||||
|     "Manipulator limit failure", | ||||
|     "Indoor PM2.5 sensor failure", | ||||
|     "Outdoor PM2.5 sensor failure", | ||||
|     "Indoor heating overload/high load alarm", | ||||
|     "Outdoor AC current protection", | ||||
|     "Outdoor compressor operation abnormality", | ||||
|     "Outdoor DC current protection", | ||||
|     "Outdoor no-load failure", | ||||
|     "CT current abnormality", | ||||
|     "Indoor cooling freeze protection", | ||||
|     "High and low pressure protection", | ||||
|     "Compressor out air temperature is too high", | ||||
|     "Outdoor evaporator sensor failure", | ||||
|     "Outdoor cooling overload", | ||||
|     "Water pump drainage failure", | ||||
|     "Three-phase power supply failure", | ||||
|     "Four-way valve failure", | ||||
|     "External alarm/scraper flow switch failure", | ||||
|     "Temperature cutoff protection alarm", | ||||
|     "Different mode operation failure", | ||||
|     "Electronic expansion valve failure", | ||||
|     "Dual heat source sensor Tw failure", | ||||
|     "Communication failure with the wired controller", | ||||
|     "Indoor unit address duplication failure", | ||||
|     "50Hz zero crossing failure", | ||||
|     "Outdoor unit failure", | ||||
|     "Formaldehyde sensor failure", | ||||
|     "VOC sensor failure", | ||||
|     "CO2 sensor failure", | ||||
|     "Firewall failure", | ||||
| }; | ||||
|  | ||||
| constexpr size_t HON_ALARM_COUNT = sizeof(HON_ALARM_MESSAGES) / sizeof(HON_ALARM_MESSAGES[0]); | ||||
|  | ||||
| }  // namespace hon_protocol | ||||
| }  // namespace haier | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -95,7 +95,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc | ||||
|   ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type, | ||||
|            phase_to_string_(this->protocol_phase_)); | ||||
|   ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1); | ||||
|   if (new_phase >= ProtocolPhases::SENDING_ALARM_STATUS_REQUEST) | ||||
|   if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST) | ||||
|     new_phase = ProtocolPhases::SENDING_INIT_1; | ||||
|   this->set_phase(new_phase); | ||||
|   return haier_protocol::HandlerError::HANDLER_OK; | ||||
| @@ -170,9 +170,12 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) | ||||
|     case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: | ||||
|       this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
|     case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST: | ||||
|       this->set_phase(ProtocolPhases::SENDING_INIT_1); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: | ||||
|       this->set_phase(ProtocolPhases::IDLE); | ||||
|       break; | ||||
|     case ProtocolPhases::SENDING_CONTROL: | ||||
|       if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { | ||||
|         ESP_LOGI(TAG, "Sending control packet"); | ||||
| @@ -343,19 +346,29 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | ||||
|     } else if (climate_control.preset.has_value()) { | ||||
|       switch (climate_control.preset.value()) { | ||||
|         case CLIMATE_PRESET_NONE: | ||||
|           out_data->ten_degree = 0; | ||||
|           out_data->turbo_mode = 0; | ||||
|           out_data->quiet_mode = 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_BOOST: | ||||
|           out_data->ten_degree = 0; | ||||
|           out_data->turbo_mode = 1; | ||||
|           out_data->quiet_mode = 0; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_COMFORT: | ||||
|           out_data->ten_degree = 0; | ||||
|           out_data->turbo_mode = 0; | ||||
|           out_data->quiet_mode = 1; | ||||
|           break; | ||||
|         case CLIMATE_PRESET_AWAY: | ||||
|           // Only allowed in heat mode | ||||
|           out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; | ||||
|           out_data->turbo_mode = 0; | ||||
|           out_data->quiet_mode = 0; | ||||
|           break; | ||||
|         default: | ||||
|           ESP_LOGE("Control", "Unsupported preset"); | ||||
|           out_data->ten_degree = 0; | ||||
|           out_data->turbo_mode = 0; | ||||
|           out_data->quiet_mode = 0; | ||||
|           break; | ||||
| @@ -381,6 +394,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | ||||
|       this->preset = CLIMATE_PRESET_BOOST; | ||||
|     } else if (packet.control.quiet_mode != 0) { | ||||
|       this->preset = CLIMATE_PRESET_COMFORT; | ||||
|     } else if (packet.control.ten_degree != 0) { | ||||
|       this->preset = CLIMATE_PRESET_AWAY; | ||||
|     } else { | ||||
|       this->preset = CLIMATE_PRESET_NONE; | ||||
|     } | ||||
|   | ||||
| @@ -1030,7 +1030,9 @@ climate: | ||||
|     visual: | ||||
|       min_temperature: 16 °C | ||||
|       max_temperature: 30 °C | ||||
|       temperature_step: 1 °C | ||||
|       temperature_step: | ||||
|         target_temperature: 1 | ||||
|         current_temperature: 0.5 | ||||
|     supported_modes: | ||||
|     - 'OFF' | ||||
|     - HEAT_COOL | ||||
| @@ -1043,6 +1045,23 @@ climate: | ||||
|     - VERTICAL | ||||
|     - HORIZONTAL | ||||
|     - BOTH | ||||
|     supported_presets: | ||||
|     - AWAY | ||||
|     - BOOST | ||||
|     - ECO | ||||
|     - SLEEP | ||||
|     on_alarm_start: | ||||
|       then: | ||||
|         - logger.log: | ||||
|             level: DEBUG | ||||
|             format: "Alarm activated. Code: %d. Message: \"%s\"" | ||||
|             args: [ code, message] | ||||
|     on_alarm_end: | ||||
|       then: | ||||
|         - logger.log: | ||||
|             level: DEBUG | ||||
|             format: "Alarm deactivated. Code: %d. Message: \"%s\"" | ||||
|             args: [ code, message] | ||||
|  | ||||
| sprinkler: | ||||
|   - id: yard_sprinkler_ctrlr | ||||
|   | ||||
		Reference in New Issue
	
	Block a user