mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Haier climate integration update (#7416)
Co-authored-by: Pavlo Dudnytskyi <pdudnytskyi@astrata.eu>
This commit is contained in:
		| @@ -166,6 +166,7 @@ esphome/components/haier/* @paveldn | |||||||
| esphome/components/haier/binary_sensor/* @paveldn | esphome/components/haier/binary_sensor/* @paveldn | ||||||
| esphome/components/haier/button/* @paveldn | esphome/components/haier/button/* @paveldn | ||||||
| esphome/components/haier/sensor/* @paveldn | esphome/components/haier/sensor/* @paveldn | ||||||
|  | esphome/components/haier/switch/* @paveldn | ||||||
| esphome/components/haier/text_sensor/* @paveldn | esphome/components/haier/text_sensor/* @paveldn | ||||||
| esphome/components/havells_solar/* @sourabhjaiswal | esphome/components/havells_solar/* @sourabhjaiswal | ||||||
| esphome/components/hbridge/fan/* @WeekendWarrior | esphome/components/hbridge/fan/* @WeekendWarrior | ||||||
|   | |||||||
| @@ -114,7 +114,6 @@ SUPPORTED_CLIMATE_PRESETS_SMARTAIR2_OPTIONS = { | |||||||
| SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { | SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS = { | ||||||
|     "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, |     "AWAY": ClimatePreset.CLIMATE_PRESET_AWAY, | ||||||
|     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, |     "BOOST": ClimatePreset.CLIMATE_PRESET_BOOST, | ||||||
|     "ECO": ClimatePreset.CLIMATE_PRESET_ECO, |  | ||||||
|     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, |     "SLEEP": ClimatePreset.CLIMATE_PRESET_SLEEP, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -240,7 +239,9 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     ): cv.ensure_list( |                     ): cv.ensure_list( | ||||||
|                         cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) |                         cv.enum(SUPPORTED_HON_CONTROL_METHODS, upper=True) | ||||||
|                     ), |                     ), | ||||||
|                     cv.Optional(CONF_BEEPER, default=True): cv.boolean, |                     cv.Optional(CONF_BEEPER): cv.invalid( | ||||||
|  |                         f"The {CONF_BEEPER} option is deprecated, use beeper_on/beeper_off actions or beeper switch for a haier platform instead" | ||||||
|  |                     ), | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE |                         CONF_CONTROL_PACKET_SIZE, default=PROTOCOL_CONTROL_PACKET_SIZE | ||||||
|                     ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), |                     ): cv.int_range(min=PROTOCOL_CONTROL_PACKET_SIZE, max=50), | ||||||
| @@ -254,7 +255,7 @@ CONFIG_SCHEMA = cv.All( | |||||||
|                     ): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE), |                     ): cv.int_range(min=PROTOCOL_STATUS_MESSAGE_HEADER_SIZE), | ||||||
|                     cv.Optional( |                     cv.Optional( | ||||||
|                         CONF_SUPPORTED_PRESETS, |                         CONF_SUPPORTED_PRESETS, | ||||||
|                         default=["BOOST", "ECO", "SLEEP"],  # No AWAY by default |                         default=["BOOST", "SLEEP"],  # No AWAY by default | ||||||
|                     ): cv.ensure_list( |                     ): cv.ensure_list( | ||||||
|                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) |                         cv.enum(SUPPORTED_CLIMATE_PRESETS_HON_OPTIONS, upper=True) | ||||||
|                     ), |                     ), | ||||||
|   | |||||||
| @@ -52,8 +52,6 @@ bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::stead | |||||||
| HaierClimateBase::HaierClimateBase() | HaierClimateBase::HaierClimateBase() | ||||||
|     : haier_protocol_(*this), |     : haier_protocol_(*this), | ||||||
|       protocol_phase_(ProtocolPhases::SENDING_INIT_1), |       protocol_phase_(ProtocolPhases::SENDING_INIT_1), | ||||||
|       display_status_(true), |  | ||||||
|       health_mode_(false), |  | ||||||
|       force_send_control_(false), |       force_send_control_(false), | ||||||
|       forced_request_status_(false), |       forced_request_status_(false), | ||||||
|       reset_protocol_request_(false), |       reset_protocol_request_(false), | ||||||
| @@ -127,21 +125,34 @@ haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() { | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| bool HaierClimateBase::get_display_state() const { return this->display_status_; } | void HaierClimateBase::save_settings() { | ||||||
|  |   HaierBaseSettings settings{this->get_health_mode(), this->get_display_state()}; | ||||||
|  |   if (!this->base_rtc_.save(&settings)) { | ||||||
|  |     ESP_LOGW(TAG, "Failed to save settings"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HaierClimateBase::get_display_state() const { | ||||||
|  |   return (this->display_status_ == SwitchState::ON) || (this->display_status_ == SwitchState::PENDING_ON); | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_display_state(bool state) { | void HaierClimateBase::set_display_state(bool state) { | ||||||
|   if (this->display_status_ != state) { |   if (state != this->get_display_state()) { | ||||||
|     this->display_status_ = state; |     this->display_status_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
|     this->force_send_control_ = true; |     this->force_send_control_ = true; | ||||||
|  |     this->save_settings(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| bool HaierClimateBase::get_health_mode() const { return this->health_mode_; } | bool HaierClimateBase::get_health_mode() const { | ||||||
|  |   return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON); | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::set_health_mode(bool state) { | void HaierClimateBase::set_health_mode(bool state) { | ||||||
|   if (this->health_mode_ != state) { |   if (state != this->get_health_mode()) { | ||||||
|     this->health_mode_ = state; |     this->health_mode_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
|     this->force_send_control_ = true; |     this->force_send_control_ = true; | ||||||
|  |     this->save_settings(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -287,6 +298,14 @@ void HaierClimateBase::loop() { | |||||||
|   } |   } | ||||||
|   this->process_phase(now); |   this->process_phase(now); | ||||||
|   this->haier_protocol_.loop(); |   this->haier_protocol_.loop(); | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |   if ((this->display_switch_ != nullptr) && (this->display_switch_->state != this->get_display_state())) { | ||||||
|  |     this->display_switch_->publish_state(this->get_display_state()); | ||||||
|  |   } | ||||||
|  |   if ((this->health_mode_switch_ != nullptr) && (this->health_mode_switch_->state != this->get_health_mode())) { | ||||||
|  |     this->health_mode_switch_->publish_state(this->get_health_mode()); | ||||||
|  |   } | ||||||
|  | #endif  // USE_SWITCH | ||||||
| } | } | ||||||
|  |  | ||||||
| void HaierClimateBase::process_protocol_reset() { | void HaierClimateBase::process_protocol_reset() { | ||||||
| @@ -329,6 +348,26 @@ bool HaierClimateBase::prepare_pending_action() { | |||||||
|  |  | ||||||
| ClimateTraits HaierClimateBase::traits() { return traits_; } | ClimateTraits HaierClimateBase::traits() { return traits_; } | ||||||
|  |  | ||||||
|  | void HaierClimateBase::initialization() { | ||||||
|  |   constexpr uint32_t restore_settings_version = 0xA77D21EF; | ||||||
|  |   this->base_rtc_ = | ||||||
|  |       global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version); | ||||||
|  |   HaierBaseSettings recovered; | ||||||
|  |   if (!this->base_rtc_.load(&recovered)) { | ||||||
|  |     recovered = {false, true}; | ||||||
|  |   } | ||||||
|  |   this->display_status_ = recovered.display_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
|  |   this->health_mode_ = recovered.health_mode ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |   if (this->display_switch_ != nullptr) { | ||||||
|  |     this->display_switch_->publish_state(this->get_display_state()); | ||||||
|  |   } | ||||||
|  |   if (this->health_mode_switch_ != nullptr) { | ||||||
|  |     this->health_mode_switch_->publish_state(this->get_health_mode()); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
| void HaierClimateBase::control(const ClimateCall &call) { | void HaierClimateBase::control(const ClimateCall &call) { | ||||||
|   ESP_LOGD("Control", "Control call"); |   ESP_LOGD("Control", "Control call"); | ||||||
|   if (!this->valid_connection()) { |   if (!this->valid_connection()) { | ||||||
| @@ -353,6 +392,22 @@ void HaierClimateBase::control(const ClimateCall &call) { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  | void HaierClimateBase::set_display_switch(switch_::Switch *sw) { | ||||||
|  |   this->display_switch_ = sw; | ||||||
|  |   if ((this->display_switch_ != nullptr) && (this->valid_connection())) { | ||||||
|  |     this->display_switch_->publish_state(this->get_display_state()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HaierClimateBase::set_health_mode_switch(switch_::Switch *sw) { | ||||||
|  |   this->health_mode_switch_ = sw; | ||||||
|  |   if ((this->health_mode_switch_ != nullptr) && (this->valid_connection())) { | ||||||
|  |     this->health_mode_switch_->publish_state(this->get_health_mode()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| void HaierClimateBase::HvacSettings::reset() { | void HaierClimateBase::HvacSettings::reset() { | ||||||
|   this->valid = false; |   this->valid = false; | ||||||
|   this->mode.reset(); |   this->mode.reset(); | ||||||
|   | |||||||
| @@ -8,6 +8,10 @@ | |||||||
| // HaierProtocol | // HaierProtocol | ||||||
| #include <protocol/haier_protocol.h> | #include <protocol/haier_protocol.h> | ||||||
|  |  | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace haier { | namespace haier { | ||||||
|  |  | ||||||
| @@ -20,10 +24,24 @@ enum class ActionRequest : uint8_t { | |||||||
|   START_STERI_CLEAN = 5,  // only hOn |   START_STERI_CLEAN = 5,  // only hOn | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct HaierBaseSettings { | ||||||
|  |   bool health_mode; | ||||||
|  |   bool display_state; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class HaierClimateBase : public esphome::Component, | class HaierClimateBase : public esphome::Component, | ||||||
|                          public esphome::climate::Climate, |                          public esphome::climate::Climate, | ||||||
|                          public esphome::uart::UARTDevice, |                          public esphome::uart::UARTDevice, | ||||||
|                          public haier_protocol::ProtocolStream { |                          public haier_protocol::ProtocolStream { | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |  public: | ||||||
|  |   void set_display_switch(switch_::Switch *sw); | ||||||
|  |   void set_health_mode_switch(switch_::Switch *sw); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   switch_::Switch *display_switch_{nullptr}; | ||||||
|  |   switch_::Switch *health_mode_switch_{nullptr}; | ||||||
|  | #endif | ||||||
|  public: |  public: | ||||||
|   HaierClimateBase(); |   HaierClimateBase(); | ||||||
|   HaierClimateBase(const HaierClimateBase &) = delete; |   HaierClimateBase(const HaierClimateBase &) = delete; | ||||||
| @@ -82,7 +100,8 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   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;          // NOLINT(readability-identifier-naming) |   virtual haier_protocol::HaierMessage get_control_message() = 0;          // NOLINT(readability-identifier-naming) | ||||||
|   virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;  // NOLINT(readability-identifier-naming) |   virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;  // NOLINT(readability-identifier-naming) | ||||||
|   virtual void initialization(){}; |   virtual void save_settings(); | ||||||
|  |   virtual void initialization(); | ||||||
|   virtual bool prepare_pending_action(); |   virtual bool prepare_pending_action(); | ||||||
|   virtual void process_protocol_reset(); |   virtual void process_protocol_reset(); | ||||||
|   esphome::climate::ClimateTraits traits() override; |   esphome::climate::ClimateTraits traits() override; | ||||||
| @@ -127,13 +146,19 @@ class HaierClimateBase : public esphome::Component, | |||||||
|     ActionRequest action; |     ActionRequest action; | ||||||
|     esphome::optional<haier_protocol::HaierMessage> message; |     esphome::optional<haier_protocol::HaierMessage> message; | ||||||
|   }; |   }; | ||||||
|  |   enum class SwitchState { | ||||||
|  |     OFF = 0b00, | ||||||
|  |     ON = 0b01, | ||||||
|  |     PENDING_OFF = 0b10, | ||||||
|  |     PENDING_ON = 0b11, | ||||||
|  |   }; | ||||||
|   haier_protocol::ProtocolHandler haier_protocol_; |   haier_protocol::ProtocolHandler haier_protocol_; | ||||||
|   ProtocolPhases protocol_phase_; |   ProtocolPhases protocol_phase_; | ||||||
|   esphome::optional<PendingAction> 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_; |   SwitchState display_status_{SwitchState::ON}; | ||||||
|   bool health_mode_; |   SwitchState health_mode_{SwitchState::OFF}; | ||||||
|   bool force_send_control_; |   bool force_send_control_; | ||||||
|   bool forced_request_status_; |   bool forced_request_status_; | ||||||
|   bool reset_protocol_request_; |   bool reset_protocol_request_; | ||||||
| @@ -148,6 +173,7 @@ class HaierClimateBase : public esphome::Component, | |||||||
|   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 last_signal_request_;          // To send WiFI signal level |   std::chrono::steady_clock::time_point last_signal_request_;          // To send WiFI signal level | ||||||
|   CallbackManager<void(const char *, size_t)> status_message_callback_{}; |   CallbackManager<void(const char *, size_t)> status_message_callback_{}; | ||||||
|  |   ESPPreferenceObject base_rtc_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class StatusMessageTrigger : public Trigger<const char *, size_t> { | class StatusMessageTrigger : public Trigger<const char *, size_t> { | ||||||
|   | |||||||
| @@ -31,9 +31,32 @@ HonClimate::HonClimate() | |||||||
|  |  | ||||||
| HonClimate::~HonClimate() {} | HonClimate::~HonClimate() {} | ||||||
|  |  | ||||||
| void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } | void HonClimate::set_beeper_state(bool state) { | ||||||
|  |   if (state != this->settings_.beeper_state) { | ||||||
|  |     this->settings_.beeper_state = state; | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |     this->beeper_switch_->publish_state(state); | ||||||
|  | #endif | ||||||
|  |     this->hon_rtc_.save(&this->settings_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| bool HonClimate::get_beeper_state() const { return this->beeper_status_; } | bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; } | ||||||
|  |  | ||||||
|  | void HonClimate::set_quiet_mode_state(bool state) { | ||||||
|  |   if (state != this->get_quiet_mode_state()) { | ||||||
|  |     this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
|  |     this->settings_.quiet_mode_state = state; | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |     this->quiet_mode_switch_->publish_state(state); | ||||||
|  | #endif | ||||||
|  |     this->hon_rtc_.save(&this->settings_); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HonClimate::get_quiet_mode_state() const { | ||||||
|  |   return (this->quiet_mode_state_ == SwitchState::ON) || (this->quiet_mode_state_ == SwitchState::PENDING_ON); | ||||||
|  | } | ||||||
|  |  | ||||||
| esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const { | esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const { | ||||||
|   return this->current_vertical_swing_; |   return this->current_vertical_swing_; | ||||||
| @@ -474,16 +497,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void HonClimate::initialization() { | void HonClimate::initialization() { | ||||||
|   constexpr uint32_t restore_settings_version = 0xE834D8DCUL; |   HaierClimateBase::initialization(); | ||||||
|   this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version); |   constexpr uint32_t restore_settings_version = 0x57EB59DDUL; | ||||||
|  |   this->hon_rtc_ = | ||||||
|  |       global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version); | ||||||
|   HonSettings recovered; |   HonSettings recovered; | ||||||
|   if (this->rtc_.load(&recovered)) { |   if (this->hon_rtc_.load(&recovered)) { | ||||||
|     this->settings_ = recovered; |     this->settings_ = recovered; | ||||||
|   } else { |   } else { | ||||||
|     this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER}; |     this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER, true, false}; | ||||||
|   } |   } | ||||||
|   this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; |   this->current_vertical_swing_ = this->settings_.last_vertiacal_swing; | ||||||
|   this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; |   this->current_horizontal_swing_ = this->settings_.last_horizontal_swing; | ||||||
|  |   this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF; | ||||||
| } | } | ||||||
|  |  | ||||||
| haier_protocol::HaierMessage HonClimate::get_control_message() { | haier_protocol::HaierMessage HonClimate::get_control_message() { | ||||||
| @@ -519,8 +545,7 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | |||||||
|           out_data->ac_power = 1; |           out_data->ac_power = 1; | ||||||
|           out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN; |           out_data->ac_mode = (uint8_t) hon_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 | ||||||
|           // Disabling boost and eco mode for Fan only |           // Disabling boost for Fan only | ||||||
|           out_data->quiet_mode = 0; |  | ||||||
|           out_data->fast_mode = 0; |           out_data->fast_mode = 0; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_MODE_COOL: |         case CLIMATE_MODE_COOL: | ||||||
| @@ -582,47 +607,34 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | |||||||
|     } |     } | ||||||
|     if (out_data->ac_power == 0) { |     if (out_data->ac_power == 0) { | ||||||
|       // If AC is off - no presets allowed |       // If AC is off - no presets allowed | ||||||
|       out_data->quiet_mode = 0; |  | ||||||
|       out_data->fast_mode = 0; |       out_data->fast_mode = 0; | ||||||
|       out_data->sleep_mode = 0; |       out_data->sleep_mode = 0; | ||||||
|     } else if (climate_control.preset.has_value()) { |     } else if (climate_control.preset.has_value()) { | ||||||
|       switch (climate_control.preset.value()) { |       switch (climate_control.preset.value()) { | ||||||
|         case CLIMATE_PRESET_NONE: |         case CLIMATE_PRESET_NONE: | ||||||
|           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->fast_mode = 0; | ||||||
|           out_data->sleep_mode = 0; |           out_data->sleep_mode = 0; | ||||||
|           out_data->ten_degree = 0; |           out_data->ten_degree = 0; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_PRESET_BOOST: |         case CLIMATE_PRESET_BOOST: | ||||||
|           out_data->quiet_mode = 0; |  | ||||||
|           // Boost is not supported in Fan only mode |           // Boost is not supported in Fan only mode | ||||||
|           out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; |           out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; | ||||||
|           out_data->sleep_mode = 0; |           out_data->sleep_mode = 0; | ||||||
|           out_data->ten_degree = 0; |           out_data->ten_degree = 0; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_PRESET_AWAY: |         case CLIMATE_PRESET_AWAY: | ||||||
|           out_data->quiet_mode = 0; |  | ||||||
|           out_data->fast_mode = 0; |           out_data->fast_mode = 0; | ||||||
|           out_data->sleep_mode = 0; |           out_data->sleep_mode = 0; | ||||||
|           // 10 degrees allowed only in heat mode |           // 10 degrees allowed only in heat mode | ||||||
|           out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; |           out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_PRESET_SLEEP: |         case CLIMATE_PRESET_SLEEP: | ||||||
|           out_data->quiet_mode = 0; |  | ||||||
|           out_data->fast_mode = 0; |           out_data->fast_mode = 0; | ||||||
|           out_data->sleep_mode = 1; |           out_data->sleep_mode = 1; | ||||||
|           out_data->ten_degree = 0; |           out_data->ten_degree = 0; | ||||||
|           break; |           break; | ||||||
|         default: |         default: | ||||||
|           ESP_LOGE("Control", "Unsupported preset"); |           ESP_LOGE("Control", "Unsupported preset"); | ||||||
|           out_data->quiet_mode = 0; |  | ||||||
|           out_data->fast_mode = 0; |           out_data->fast_mode = 0; | ||||||
|           out_data->sleep_mode = 0; |           out_data->sleep_mode = 0; | ||||||
|           out_data->ten_degree = 0; |           out_data->ten_degree = 0; | ||||||
| @@ -638,10 +650,23 @@ haier_protocol::HaierMessage HonClimate::get_control_message() { | |||||||
|     out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value(); |     out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value(); | ||||||
|     this->pending_horizontal_direction_.reset(); |     this->pending_horizontal_direction_.reset(); | ||||||
|   } |   } | ||||||
|   out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0; |   { | ||||||
|  |     // Quiet mode | ||||||
|  |     if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) { | ||||||
|  |       // If AC is off or in fan only mode - no quiet mode allowed | ||||||
|  |       out_data->quiet_mode = 0; | ||||||
|  |     } else { | ||||||
|  |       out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0; | ||||||
|  |     } | ||||||
|  |     // Clean quiet mode state pending flag | ||||||
|  |     this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01); | ||||||
|  |   } | ||||||
|  |   out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0; | ||||||
|   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->get_display_state() ? 1 : 0; | ||||||
|   out_data->health_mode = this->health_mode_ ? 1 : 0; |   this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01); | ||||||
|  |   out_data->health_mode = this->get_health_mode() ? 1 : 0; | ||||||
|  |   this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01); | ||||||
|   return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |   return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                       (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, |                                       (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS, | ||||||
|                                       control_out_buffer, this->real_control_packet_size_); |                                       control_out_buffer, this->real_control_packet_size_); | ||||||
| @@ -765,6 +790,22 @@ void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::stri | |||||||
| } | } | ||||||
| #endif  // USE_TEXT_SENSOR | #endif  // USE_TEXT_SENSOR | ||||||
|  |  | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  | void HonClimate::set_beeper_switch(switch_::Switch *sw) { | ||||||
|  |   this->beeper_switch_ = sw; | ||||||
|  |   if (this->beeper_switch_ != nullptr) { | ||||||
|  |     this->beeper_switch_->publish_state(this->get_beeper_state()); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HonClimate::set_quiet_mode_switch(switch_::Switch *sw) { | ||||||
|  |   this->quiet_mode_switch_ = sw; | ||||||
|  |   if (this->quiet_mode_switch_ != nullptr) { | ||||||
|  |     this->quiet_mode_switch_->publish_state(this->settings_.quiet_mode_state); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | #endif  // USE_SWITCH | ||||||
|  |  | ||||||
| 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) { | ||||||
|   size_t expected_size = |   size_t expected_size = | ||||||
|       2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; |       2 + this->status_message_header_size_ + this->real_control_packet_size_ + this->real_sensors_packet_size_; | ||||||
| @@ -827,9 +868,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|   { |   { | ||||||
|     // Extra modes/presets |     // Extra modes/presets | ||||||
|     optional<ClimatePreset> old_preset = this->preset; |     optional<ClimatePreset> old_preset = this->preset; | ||||||
|     if (packet.control.quiet_mode != 0) { |     if (packet.control.fast_mode != 0) { | ||||||
|       this->preset = CLIMATE_PRESET_ECO; |  | ||||||
|     } else if (packet.control.fast_mode != 0) { |  | ||||||
|       this->preset = CLIMATE_PRESET_BOOST; |       this->preset = CLIMATE_PRESET_BOOST; | ||||||
|     } else if (packet.control.sleep_mode != 0) { |     } else if (packet.control.sleep_mode != 0) { | ||||||
|       this->preset = CLIMATE_PRESET_SLEEP; |       this->preset = CLIMATE_PRESET_SLEEP; | ||||||
| @@ -883,28 +922,26 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|     } |     } | ||||||
|     should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); |     should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); | ||||||
|   } |   } | ||||||
|   { |  | ||||||
|   // Display status |   // Display status | ||||||
|   // should be before "Climate mode" because it is changing this->mode |   // should be before "Climate mode" because it is changing this->mode | ||||||
|   if (packet.control.ac_power != 0) { |   if (packet.control.ac_power != 0) { | ||||||
|     // if AC is off display status always ON so process it only when AC is on |     // if AC is off display status always ON so process it only when AC is on | ||||||
|     bool disp_status = packet.control.display_status != 0; |     bool disp_status = packet.control.display_status != 0; | ||||||
|       if (disp_status != this->display_status_) { |     if (disp_status != this->get_display_state()) { | ||||||
|       // 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->force_send_control_ = true; |         this->force_send_control_ = true; | ||||||
|         } else { |       } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) { | ||||||
|           this->display_status_ = disp_status; |         this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   } |  | ||||||
|   { |  | ||||||
|   // Health mode |   // Health mode | ||||||
|     bool old_health_mode = this->health_mode_; |   if ((((uint8_t) this->health_mode_) & 0b10) == 0) { | ||||||
|     this->health_mode_ = packet.control.health_mode == 1; |     bool old_health_mode = this->get_health_mode(); | ||||||
|     should_publish = should_publish || (old_health_mode != this->health_mode_); |     this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF; | ||||||
|  |     should_publish = should_publish || (old_health_mode != this->get_health_mode()); | ||||||
|   } |   } | ||||||
|   { |   { | ||||||
|     CleaningState new_cleaning; |     CleaningState new_cleaning; | ||||||
| @@ -958,17 +995,36 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|     } |     } | ||||||
|     should_publish = should_publish || (old_mode != this->mode); |     should_publish = should_publish || (old_mode != this->mode); | ||||||
|   } |   } | ||||||
|  |   { | ||||||
|  |     // Quiet mode, should be after climate mode | ||||||
|  |     if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) && | ||||||
|  |         ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) { | ||||||
|  |       // In proper mode and not in pending state | ||||||
|  |       bool new_quiet_mode = packet.control.quiet_mode != 0; | ||||||
|  |       if (new_quiet_mode != this->get_quiet_mode_state()) { | ||||||
|  |         this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF; | ||||||
|  |         this->settings_.quiet_mode_state = new_quiet_mode; | ||||||
|  |         this->hon_rtc_.save(&this->settings_); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   { |   { | ||||||
|     // Swing mode |     // Swing mode | ||||||
|     ClimateSwingMode old_swing_mode = this->swing_mode; |     ClimateSwingMode old_swing_mode = this->swing_mode; | ||||||
|     if (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) { |     const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes(); | ||||||
|       if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { |     bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end(); | ||||||
|  |     bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end(); | ||||||
|  |     if (horizontal_swing_supported && | ||||||
|  |         (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) { | ||||||
|  |       if (vertical_swing_supported && | ||||||
|  |           (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) { | ||||||
|         this->swing_mode = CLIMATE_SWING_BOTH; |         this->swing_mode = CLIMATE_SWING_BOTH; | ||||||
|       } else { |       } else { | ||||||
|         this->swing_mode = CLIMATE_SWING_HORIZONTAL; |         this->swing_mode = CLIMATE_SWING_HORIZONTAL; | ||||||
|       } |       } | ||||||
|     } else { |     } else { | ||||||
|       if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { |       if (vertical_swing_supported && | ||||||
|  |           (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) { | ||||||
|         this->swing_mode = CLIMATE_SWING_VERTICAL; |         this->swing_mode = CLIMATE_SWING_VERTICAL; | ||||||
|       } else { |       } else { | ||||||
|         this->swing_mode = CLIMATE_SWING_OFF; |         this->swing_mode = CLIMATE_SWING_OFF; | ||||||
| @@ -985,7 +1041,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t * | |||||||
|     if (save_settings) { |     if (save_settings) { | ||||||
|       this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value(); |       this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value(); | ||||||
|       this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value(); |       this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value(); | ||||||
|       this->rtc_.save(&this->settings_); |       this->hon_rtc_.save(&this->settings_); | ||||||
|     } |     } | ||||||
|     should_publish = should_publish || (old_swing_mode != this->swing_mode); |     should_publish = should_publish || (old_swing_mode != this->swing_mode); | ||||||
|   } |   } | ||||||
| @@ -1017,7 +1073,7 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                          (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, |                                          (uint8_t) hon_protocol::DataParameters::BEEPER_STATUS, | ||||||
|                                      this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2)); |                                      this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2)); | ||||||
|   } |   } | ||||||
|   // Health mode |   // Health mode | ||||||
|   { |   { | ||||||
| @@ -1025,13 +1081,16 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |         haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                      (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|                                          (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, |                                          (uint8_t) hon_protocol::DataParameters::HEALTH_MODE, | ||||||
|                                      this->health_mode_ ? ONE_BUF : ZERO_BUF, 2)); |                                      this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2)); | ||||||
|  |     this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01); | ||||||
|   } |   } | ||||||
|   // Climate mode |   // Climate mode | ||||||
|  |   ClimateMode climate_mode = this->mode; | ||||||
|   bool new_power = this->mode != CLIMATE_MODE_OFF; |   bool new_power = this->mode != CLIMATE_MODE_OFF; | ||||||
|   uint8_t fan_mode_buf[] = {0x00, 0xFF}; |   uint8_t fan_mode_buf[] = {0x00, 0xFF}; | ||||||
|   uint8_t quiet_mode_buf[] = {0x00, 0xFF}; |   uint8_t quiet_mode_buf[] = {0x00, 0xFF}; | ||||||
|   if (climate_control.mode.has_value()) { |   if (climate_control.mode.has_value()) { | ||||||
|  |     climate_mode = climate_control.mode.value(); | ||||||
|     uint8_t buffer[2] = {0x00, 0x00}; |     uint8_t buffer[2] = {0x00, 0x00}; | ||||||
|     switch (climate_control.mode.value()) { |     switch (climate_control.mode.value()) { | ||||||
|       case CLIMATE_MODE_OFF: |       case CLIMATE_MODE_OFF: | ||||||
| @@ -1076,8 +1135,6 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, |                                              (uint8_t) hon_protocol::DataParameters::AC_MODE, | ||||||
|                                          buffer, 2)); |                                          buffer, 2)); | ||||||
|         fan_mode_buf[1] = this->other_modes_fan_speed_;  // Auto doesn't work in fan only mode |         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; |         break; | ||||||
|       case CLIMATE_MODE_COOL: |       case CLIMATE_MODE_COOL: | ||||||
|         new_power = true; |         new_power = true; | ||||||
| @@ -1108,30 +1165,20 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|     uint8_t away_mode_buf[] = {0x00, 0xFF}; |     uint8_t away_mode_buf[] = {0x00, 0xFF}; | ||||||
|     if (!new_power) { |     if (!new_power) { | ||||||
|       // If AC is off - no presets allowed |       // If AC is off - no presets allowed | ||||||
|       quiet_mode_buf[1] = 0x00; |  | ||||||
|       fast_mode_buf[1] = 0x00; |       fast_mode_buf[1] = 0x00; | ||||||
|       away_mode_buf[1] = 0x00; |       away_mode_buf[1] = 0x00; | ||||||
|     } else if (climate_control.preset.has_value()) { |     } else if (climate_control.preset.has_value()) { | ||||||
|       switch (climate_control.preset.value()) { |       switch (climate_control.preset.value()) { | ||||||
|         case CLIMATE_PRESET_NONE: |         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; |           fast_mode_buf[1] = 0x00; | ||||||
|           away_mode_buf[1] = 0x00; |           away_mode_buf[1] = 0x00; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_PRESET_BOOST: |         case CLIMATE_PRESET_BOOST: | ||||||
|           quiet_mode_buf[1] = 0x00; |  | ||||||
|           // Boost is not supported in Fan only mode |           // Boost is not supported in Fan only mode | ||||||
|           fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; |           fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00; | ||||||
|           away_mode_buf[1] = 0x00; |           away_mode_buf[1] = 0x00; | ||||||
|           break; |           break; | ||||||
|         case CLIMATE_PRESET_AWAY: |         case CLIMATE_PRESET_AWAY: | ||||||
|           quiet_mode_buf[1] = 0x00; |  | ||||||
|           fast_mode_buf[1] = 0x00; |           fast_mode_buf[1] = 0x00; | ||||||
|           away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; |           away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00; | ||||||
|           break; |           break; | ||||||
| @@ -1140,8 +1187,18 @@ void HonClimate::fill_control_messages_queue_() { | |||||||
|           break; |           break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |     { | ||||||
|  |       // Quiet mode | ||||||
|  |       if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) { | ||||||
|  |         quiet_mode_buf[1] = 0x01; | ||||||
|  |       } else { | ||||||
|  |         quiet_mode_buf[1] = 0x00; | ||||||
|  |       } | ||||||
|  |       // Clean quiet mode state pending flag | ||||||
|  |       this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01); | ||||||
|  |     } | ||||||
|     auto presets = this->traits_.get_supported_presets(); |     auto presets = this->traits_.get_supported_presets(); | ||||||
|     if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) { |     if (quiet_mode_buf[1] != 0xFF) { | ||||||
|       this->control_messages_queue_.push( |       this->control_messages_queue_.push( | ||||||
|           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, |           haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, | ||||||
|                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + |                                        (uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER + | ||||||
|   | |||||||
| @@ -10,6 +10,9 @@ | |||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| #include "esphome/components/text_sensor/text_sensor.h" | #include "esphome/components/text_sensor/text_sensor.h" | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #endif | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "haier_base.h" | #include "haier_base.h" | ||||||
| #include "hon_packet.h" | #include "hon_packet.h" | ||||||
| @@ -28,6 +31,8 @@ enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE | |||||||
| struct HonSettings { | struct HonSettings { | ||||||
|   hon_protocol::VerticalSwingMode last_vertiacal_swing; |   hon_protocol::VerticalSwingMode last_vertiacal_swing; | ||||||
|   hon_protocol::HorizontalSwingMode last_horizontal_swing; |   hon_protocol::HorizontalSwingMode last_horizontal_swing; | ||||||
|  |   bool beeper_state; | ||||||
|  |   bool quiet_mode_state; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class HonClimate : public HaierClimateBase { | class HonClimate : public HaierClimateBase { | ||||||
| @@ -86,6 +91,15 @@ class HonClimate : public HaierClimateBase { | |||||||
|  protected: |  protected: | ||||||
|   void update_sub_text_sensor_(SubTextSensorType type, const std::string &value); |   void update_sub_text_sensor_(SubTextSensorType type, const std::string &value); | ||||||
|   text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr}; |   text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr}; | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SWITCH | ||||||
|  |  public: | ||||||
|  |   void set_beeper_switch(switch_::Switch *sw); | ||||||
|  |   void set_quiet_mode_switch(switch_::Switch *sw); | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   switch_::Switch *beeper_switch_{nullptr}; | ||||||
|  |   switch_::Switch *quiet_mode_switch_{nullptr}; | ||||||
| #endif | #endif | ||||||
|  public: |  public: | ||||||
|   HonClimate(); |   HonClimate(); | ||||||
| @@ -95,6 +109,8 @@ class HonClimate : public HaierClimateBase { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void set_beeper_state(bool state); |   void set_beeper_state(bool state); | ||||||
|   bool get_beeper_state() const; |   bool get_beeper_state() const; | ||||||
|  |   void set_quiet_mode_state(bool state); | ||||||
|  |   bool get_quiet_mode_state() const; | ||||||
|   esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const; |   esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const; | ||||||
|   void set_vertical_airflow(hon_protocol::VerticalSwingMode direction); |   void set_vertical_airflow(hon_protocol::VerticalSwingMode direction); | ||||||
|   esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const; |   esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const; | ||||||
| @@ -153,7 +169,6 @@ class HonClimate : public HaierClimateBase { | |||||||
|     bool functions_[5]; |     bool functions_[5]; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   bool beeper_status_; |  | ||||||
|   CleaningState cleaning_status_; |   CleaningState cleaning_status_; | ||||||
|   bool got_valid_outdoor_temp_; |   bool got_valid_outdoor_temp_; | ||||||
|   esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{}; |   esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{}; | ||||||
| @@ -175,7 +190,8 @@ class HonClimate : public HaierClimateBase { | |||||||
|   esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{}; |   esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{}; | ||||||
|   esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{}; |   esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{}; | ||||||
|   HonSettings settings_; |   HonSettings settings_; | ||||||
|   ESPPreferenceObject rtc_; |   ESPPreferenceObject hon_rtc_; | ||||||
|  |   SwitchState quiet_mode_state_{SwitchState::OFF}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { | class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> { | ||||||
|   | |||||||
| @@ -376,8 +376,10 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   out_data->display_status = this->display_status_ ? 0 : 1; |   out_data->display_status = this->get_display_state() ? 0 : 1; | ||||||
|   out_data->health_mode = this->health_mode_ ? 1 : 0; |   this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01); | ||||||
|  |   out_data->health_mode = this->get_health_mode() ? 1 : 0; | ||||||
|  |   this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01); | ||||||
|   return haier_protocol::HaierMessage(haier_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)); | ||||||
| } | } | ||||||
| @@ -446,28 +448,26 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin | |||||||
|     } |     } | ||||||
|     should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); |     should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); | ||||||
|   } |   } | ||||||
|   { |  | ||||||
|   // Display status |   // Display status | ||||||
|   // should be before "Climate mode" because it is changing this->mode |   // should be before "Climate mode" because it is changing this->mode | ||||||
|   if (packet.control.ac_power != 0) { |   if (packet.control.ac_power != 0) { | ||||||
|     // if AC is off display status always ON so process it only when AC is on |     // if AC is off display status always ON so process it only when AC is on | ||||||
|     bool disp_status = packet.control.display_status == 0; |     bool disp_status = packet.control.display_status == 0; | ||||||
|       if (disp_status != this->display_status_) { |     if (disp_status != this->get_display_state()) { | ||||||
|       // 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->force_send_control_ = true; |         this->force_send_control_ = true; | ||||||
|         } else { |       } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) { | ||||||
|           this->display_status_ = disp_status; |         this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   } |  | ||||||
|   { |  | ||||||
|   // Health mode |   // Health mode | ||||||
|     bool old_health_mode = this->health_mode_; |   if ((((uint8_t) this->health_mode_) & 0b10) == 0) { | ||||||
|     this->health_mode_ = packet.control.health_mode == 1; |     bool old_health_mode = this->get_health_mode(); | ||||||
|     should_publish = should_publish || (old_health_mode != this->health_mode_); |     this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF; | ||||||
|  |     should_publish = should_publish || (old_health_mode != this->get_health_mode()); | ||||||
|   } |   } | ||||||
|   { |   { | ||||||
|     // Climate mode |     // Climate mode | ||||||
|   | |||||||
							
								
								
									
										91
									
								
								esphome/components/haier/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								esphome/components/haier/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | import esphome.final_validate as fv | ||||||
|  | from esphome.components import switch | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_BEEPER, | ||||||
|  |     CONF_DISPLAY, | ||||||
|  |     ENTITY_CATEGORY_CONFIG, | ||||||
|  | ) | ||||||
|  | from ..climate import ( | ||||||
|  |     CONF_HAIER_ID, | ||||||
|  |     CONF_PROTOCOL, | ||||||
|  |     HaierClimateBase, | ||||||
|  |     haier_ns, | ||||||
|  |     PROTOCOL_HON, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CODEOWNERS = ["@paveldn"] | ||||||
|  | BeeperSwitch = haier_ns.class_("BeeperSwitch", switch.Switch) | ||||||
|  | HealthModeSwitch = haier_ns.class_("HealthModeSwitch", switch.Switch) | ||||||
|  | DisplaySwitch = haier_ns.class_("DisplaySwitch", switch.Switch) | ||||||
|  | QuietModeSwitch = haier_ns.class_("QuietModeSwitch", switch.Switch) | ||||||
|  |  | ||||||
|  | # Haier switches | ||||||
|  | CONF_HEALTH_MODE = "health_mode" | ||||||
|  | CONF_QUIET_MODE = "quiet_mode" | ||||||
|  |  | ||||||
|  | # Additional icons | ||||||
|  | ICON_LEAF = "mdi:leaf" | ||||||
|  | ICON_LED_ON = "mdi:led-on" | ||||||
|  | ICON_VOLUME_HIGH = "mdi:volume-high" | ||||||
|  | ICON_VOLUME_OFF = "mdi:volume-off" | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.Schema( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_HAIER_ID): cv.use_id(HaierClimateBase), | ||||||
|  |         cv.Optional(CONF_DISPLAY): switch.switch_schema( | ||||||
|  |             DisplaySwitch, | ||||||
|  |             icon=ICON_LED_ON, | ||||||
|  |             entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |             default_restore_mode="DISABLED", | ||||||
|  |         ), | ||||||
|  |         cv.Optional(CONF_HEALTH_MODE): switch.switch_schema( | ||||||
|  |             HealthModeSwitch, | ||||||
|  |             icon=ICON_LEAF, | ||||||
|  |             default_restore_mode="DISABLED", | ||||||
|  |         ), | ||||||
|  |         # Beeper switch is only supported for HonClimate | ||||||
|  |         cv.Optional(CONF_BEEPER): switch.switch_schema( | ||||||
|  |             BeeperSwitch, | ||||||
|  |             icon=ICON_VOLUME_HIGH, | ||||||
|  |             entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |             default_restore_mode="DISABLED", | ||||||
|  |         ), | ||||||
|  |         # Quiet mode is only supported for HonClimate | ||||||
|  |         cv.Optional(CONF_QUIET_MODE): switch.switch_schema( | ||||||
|  |             QuietModeSwitch, | ||||||
|  |             icon=ICON_VOLUME_OFF, | ||||||
|  |             entity_category=ENTITY_CATEGORY_CONFIG, | ||||||
|  |             default_restore_mode="DISABLED", | ||||||
|  |         ), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     full_config = fv.full_config.get() | ||||||
|  |     for switch_type in [CONF_BEEPER, CONF_QUIET_MODE]: | ||||||
|  |         # Check switches that are only supported for HonClimate | ||||||
|  |         if config.get(switch_type): | ||||||
|  |             climate_path = full_config.get_path_for_id(config[CONF_HAIER_ID])[:-1] | ||||||
|  |             climate_conf = full_config.get_config_for_path(climate_path) | ||||||
|  |             protocol_type = climate_conf.get(CONF_PROTOCOL) | ||||||
|  |             if protocol_type.casefold() != PROTOCOL_HON.casefold(): | ||||||
|  |                 raise cv.Invalid( | ||||||
|  |                     f"{switch_type} switch is only supported for hon climate" | ||||||
|  |                 ) | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     parent = await cg.get_variable(config[CONF_HAIER_ID]) | ||||||
|  |  | ||||||
|  |     for switch_type in [CONF_DISPLAY, CONF_HEALTH_MODE, CONF_BEEPER, CONF_QUIET_MODE]: | ||||||
|  |         if conf := config.get(switch_type): | ||||||
|  |             sw_var = await switch.new_switch(conf) | ||||||
|  |             await cg.register_parented(sw_var, parent) | ||||||
|  |             cg.add(getattr(parent, f"set_{switch_type}_switch")(sw_var)) | ||||||
							
								
								
									
										14
									
								
								esphome/components/haier/switch/beeper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/haier/switch/beeper.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #include "beeper.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | void BeeperSwitch::write_state(bool state) { | ||||||
|  |   if (this->parent_->get_beeper_state() != state) { | ||||||
|  |     this->parent_->set_beeper_state(state); | ||||||
|  |   } | ||||||
|  |   this->publish_state(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/haier/switch/beeper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/haier/switch/beeper.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../hon_climate.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | class BeeperSwitch : public switch_::Switch, public Parented<HonClimate> { | ||||||
|  |  public: | ||||||
|  |   BeeperSwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										14
									
								
								esphome/components/haier/switch/display.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/haier/switch/display.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #include "display.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | void DisplaySwitch::write_state(bool state) { | ||||||
|  |   if (this->parent_->get_display_state() != state) { | ||||||
|  |     this->parent_->set_display_state(state); | ||||||
|  |   } | ||||||
|  |   this->publish_state(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/haier/switch/display.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/haier/switch/display.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../haier_base.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | class DisplaySwitch : public switch_::Switch, public Parented<HaierClimateBase> { | ||||||
|  |  public: | ||||||
|  |   DisplaySwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										14
									
								
								esphome/components/haier/switch/health_mode.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/haier/switch/health_mode.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #include "health_mode.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | void HealthModeSwitch::write_state(bool state) { | ||||||
|  |   if (this->parent_->get_health_mode() != state) { | ||||||
|  |     this->parent_->set_health_mode(state); | ||||||
|  |   } | ||||||
|  |   this->publish_state(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/haier/switch/health_mode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/haier/switch/health_mode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../haier_base.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | class HealthModeSwitch : public switch_::Switch, public Parented<HaierClimateBase> { | ||||||
|  |  public: | ||||||
|  |   HealthModeSwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										14
									
								
								esphome/components/haier/switch/quiet_mode.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/haier/switch/quiet_mode.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #include "quiet_mode.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | void QuietModeSwitch::write_state(bool state) { | ||||||
|  |   if (this->parent_->get_quiet_mode_state() != state) { | ||||||
|  |     this->parent_->set_quiet_mode_state(state); | ||||||
|  |   } | ||||||
|  |   this->publish_state(state); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										18
									
								
								esphome/components/haier/switch/quiet_mode.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/haier/switch/quiet_mode.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/components/switch/switch.h" | ||||||
|  | #include "../hon_climate.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace haier { | ||||||
|  |  | ||||||
|  | class QuietModeSwitch : public switch_::Switch, public Parented<HonClimate> { | ||||||
|  |  public: | ||||||
|  |   QuietModeSwitch() = default; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void write_state(bool state) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace haier | ||||||
|  | }  // namespace esphome | ||||||
| @@ -16,7 +16,6 @@ climate: | |||||||
|     name: Haier AC |     name: Haier AC | ||||||
|     wifi_signal: true |     wifi_signal: true | ||||||
|     answer_timeout: 200ms |     answer_timeout: 200ms | ||||||
|     beeper: true |  | ||||||
|     visual: |     visual: | ||||||
|       min_temperature: 16 °C |       min_temperature: 16 °C | ||||||
|       max_temperature: 30 °C |       max_temperature: 30 °C | ||||||
| @@ -38,7 +37,6 @@ climate: | |||||||
|     supported_presets: |     supported_presets: | ||||||
|       - AWAY |       - AWAY | ||||||
|       - BOOST |       - BOOST | ||||||
|       - ECO |  | ||||||
|       - SLEEP |       - SLEEP | ||||||
|     on_alarm_start: |     on_alarm_start: | ||||||
|       then: |       then: | ||||||
| @@ -112,3 +110,15 @@ text_sensor: | |||||||
|       name: Haier cleaning status |       name: Haier cleaning status | ||||||
|     protocol_version: |     protocol_version: | ||||||
|       name: Haier protocol version |       name: Haier protocol version | ||||||
|  |  | ||||||
|  | switch: | ||||||
|  |   - platform: haier | ||||||
|  |     haier_id: haier_ac | ||||||
|  |     beeper: | ||||||
|  |       name: Haier beeper | ||||||
|  |     display: | ||||||
|  |       name: Haier display | ||||||
|  |     health_mode: | ||||||
|  |       name: Haier health mode | ||||||
|  |     quiet_mode: | ||||||
|  |       name: Haier quiet mode | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user