ESPHome  2024.10.2
hon_climate.cpp
Go to the documentation of this file.
1 #include <chrono>
2 #include <string>
5 #include "esphome/core/helpers.h"
6 #include "hon_climate.h"
7 #include "hon_packet.h"
8 
9 using namespace esphome::climate;
10 using namespace esphome::uart;
11 
12 namespace esphome {
13 namespace haier {
14 
15 static const char *const TAG = "haier.climate";
16 constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
18 constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
19 constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
20 constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
21 const uint8_t ONE_BUF[] = {0x00, 0x01};
22 const uint8_t ZERO_BUF[] = {0x00, 0x00};
23 
24 HonClimate::HonClimate()
25  : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
26  0x00, 0x00, 0x00,
27  0x00, 0x00} {
30 }
31 
33 
35  if (state != this->settings_.beeper_state) {
37 #ifdef USE_SWITCH
38  this->beeper_switch_->publish_state(state);
39 #endif
40  this->hon_rtc_.save(&this->settings_);
41  }
42 }
43 
44 bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state; }
45 
47  if (state != this->get_quiet_mode_state()) {
50 #ifdef USE_SWITCH
51  this->quiet_mode_switch_->publish_state(state);
52 #endif
53  this->hon_rtc_.save(&this->settings_);
54  }
55 }
56 
59 }
60 
62  return this->current_vertical_swing_;
63 };
64 
67  this->force_send_control_ = true;
68 }
69 
71  return this->current_horizontal_swing_;
72 }
73 
76  this->force_send_control_ = true;
77 }
78 
80  switch (this->cleaning_status_) {
82  return "Self clean";
84  return "56°C Steri-Clean";
85  default:
86  return "No cleaning";
87  }
88 }
89 
91 
94  ESP_LOGI(TAG, "Sending self cleaning start request");
95  this->action_request_ =
97  }
98 }
99 
102  ESP_LOGI(TAG, "Sending steri cleaning start request");
103  this->action_request_ =
105  }
106 }
107 
108 void HonClimate::add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback) {
109  this->alarm_start_callback_.add(std::move(callback));
110 }
111 
112 void HonClimate::add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback) {
113  this->alarm_end_callback_.add(std::move(callback));
114 }
115 
116 haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
117  haier_protocol::FrameType message_type,
118  const uint8_t *data, size_t data_size) {
119  // Should check this before preprocess
120  if (message_type == haier_protocol::FrameType::INVALID) {
121  ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
122  "protocol instead of hOn");
124  return haier_protocol::HandlerError::INVALID_ANSWER;
125  }
126  haier_protocol::HandlerError result =
127  this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
128  haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
129  if (result == haier_protocol::HandlerError::HANDLER_OK) {
130  if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
131  // Wrong structure
132  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
133  }
134  // All OK
136  char tmp[9];
137  tmp[8] = 0;
138  strncpy(tmp, answr->protocol_version, 8);
140  this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp);
141  strncpy(tmp, answr->software_version, 8);
142  this->hvac_hardware_info_.value().software_version_ = std::string(tmp);
143  strncpy(tmp, answr->hardware_version, 8);
144  this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
145  strncpy(tmp, answr->device_name, 8);
146  this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
147 #ifdef USE_TEXT_SENSOR
150  this->hvac_hardware_info_.value().protocol_version_);
151 #endif
152  this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
153  this->hvac_hardware_info_.value().functions_[1] =
154  (answr->functions[1] & 0x02) != 0; // controller-device mode support
155  this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
156  this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
157  this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
158  this->use_crc_ = this->hvac_hardware_info_.value().functions_[2];
160  return result;
161  } else {
162  this->reset_phase_();
163  return result;
164  }
165 }
166 
167 haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
168  haier_protocol::FrameType message_type,
169  const uint8_t *data, size_t data_size) {
170  haier_protocol::HandlerError result =
171  this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
172  haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
173  if (result == haier_protocol::HandlerError::HANDLER_OK) {
175  return result;
176  } else {
177  this->reset_phase_();
178  return result;
179  }
180 }
181 
182 haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
183  haier_protocol::FrameType message_type, const uint8_t *data,
184  size_t data_size) {
185  haier_protocol::HandlerError result =
186  this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
188  if (result == haier_protocol::HandlerError::HANDLER_OK) {
189  result = this->process_status_message_(data, data_size);
190  if (result != haier_protocol::HandlerError::HANDLER_OK) {
191  ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
192  this->reset_phase_();
193  this->action_request_.reset();
194  this->force_send_control_ = false;
195  } else {
196  if (!this->last_status_message_) {
199  this->last_status_message_.reset();
200  this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
201  };
202  if (data_size >= this->real_control_packet_size_ + 2) {
203  memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
205  this->status_message_callback_.call((const char *) data, data_size);
206  } else {
207  ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
208  }
209  switch (this->protocol_phase_) {
211  ESP_LOGI(TAG, "First HVAC status received");
213  break;
215  // Do nothing, phase will be changed in process_phase
216  break;
219  break;
221  if (!this->control_messages_queue_.empty())
222  this->control_messages_queue_.pop();
223  if (this->control_messages_queue_.empty()) {
225  this->force_send_control_ = false;
226  if (this->current_hvac_settings_.valid)
228  } else {
230  }
231  break;
232  default:
233  break;
234  }
235  }
236  return result;
237  } else {
238  this->action_request_.reset();
239  this->force_send_control_ = false;
240  this->reset_phase_();
241  return result;
242  }
243 }
244 
246  haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
247  size_t data_size) {
248  haier_protocol::HandlerError result = this->answer_preprocess_(
249  request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
250  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
251  if (result == haier_protocol::HandlerError::HANDLER_OK) {
253  return result;
254  } else {
256  return result;
257  }
258 }
259 
260 haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
261  haier_protocol::FrameType message_type,
262  const uint8_t *data, size_t data_size) {
263  if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
264  if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
265  // Unexpected answer to request
267  return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
268  }
271  // Don't expect this answer now
273  return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
274  }
275  if (data_size < sizeof(active_alarms_) + 2)
276  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
277  this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
279  return haier_protocol::HandlerError::HANDLER_OK;
280  } else {
282  return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
283  }
284 }
285 
286 haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
287  const uint8_t *buffer, size_t size) {
288  haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
289  if (size < sizeof(this->active_alarms_) + 2) {
290  // Log error but confirm anyway to avoid to many messages
291  result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
292  }
293  this->process_alarm_message_(buffer, size, true);
294  this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
295  this->last_alarm_request_ = std::chrono::steady_clock::now();
296  return result;
297 }
298 
300  // Set handlers
301  this->haier_protocol_.set_answer_handler(
302  haier_protocol::FrameType::GET_DEVICE_VERSION,
303  std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
304  std::placeholders::_3, std::placeholders::_4));
305  this->haier_protocol_.set_answer_handler(
306  haier_protocol::FrameType::GET_DEVICE_ID,
307  std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
308  std::placeholders::_3, std::placeholders::_4));
309  this->haier_protocol_.set_answer_handler(
310  haier_protocol::FrameType::CONTROL,
311  std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
312  std::placeholders::_4));
313  this->haier_protocol_.set_answer_handler(
314  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
315  std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1,
316  std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
317  this->haier_protocol_.set_answer_handler(
318  haier_protocol::FrameType::GET_ALARM_STATUS,
319  std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
320  std::placeholders::_3, std::placeholders::_4));
321  this->haier_protocol_.set_answer_handler(
322  haier_protocol::FrameType::REPORT_NETWORK_STATUS,
323  std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
324  std::placeholders::_3, std::placeholders::_4));
325  this->haier_protocol_.set_message_handler(
326  haier_protocol::FrameType::ALARM_STATUS,
327  std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2,
328  std::placeholders::_3));
329 }
330 
333  ESP_LOGCONFIG(TAG, " Protocol version: hOn");
334  ESP_LOGCONFIG(TAG, " Control method: %d", (uint8_t) this->control_method_);
335  if (this->hvac_hardware_info_.has_value()) {
336  ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_hardware_info_.value().protocol_version_.c_str());
337  ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_hardware_info_.value().software_version_.c_str());
338  ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_info_.value().hardware_version_.c_str());
339  ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_hardware_info_.value().device_name_.c_str());
340  ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
341  (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
342  (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
343  (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
344  (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
345  (this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
346  ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
347  }
348 }
349 
350 void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
351  switch (this->protocol_phase_) {
354  // Indicate device capabilities:
355  // bit 0 - if 1 module support interactive mode
356  // bit 1 - if 1 module support controller-device mode
357  // bit 2 - if 1 module support crc
358  // bit 3 - if 1 module support multiple devices
359  // bit 4..bit 15 - not used
360  uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
361  static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
362  haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
363  this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
364  }
365  break;
367  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
368  static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
369  this->send_message_(DEVICEID_REQUEST, this->use_crc_);
370  }
371  break;
374  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
375  static const haier_protocol::HaierMessage STATUS_REQUEST(
376  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
377  static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
378  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
380  (!this->should_get_big_data_())) {
381  this->send_message_(STATUS_REQUEST, this->use_crc_);
382  } else {
383  this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
384  }
385  this->last_status_request_ = now;
386  }
387  break;
388 #ifdef USE_WIFI
390  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
391  static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
392  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
393  this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
394  this->last_signal_request_ = now;
395  }
396  break;
398  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
399  this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
400  }
401  break;
402 #else
406  break;
407 #endif
410  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
411  static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
412  this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
413  this->last_alarm_request_ = now;
414  }
415  break;
417  if (this->control_messages_queue_.empty()) {
418  switch (this->control_method_) {
420  haier_protocol::HaierMessage control_message = this->get_control_message();
421  this->control_messages_queue_.push(control_message);
422  } break;
425  break;
427  ESP_LOGI(TAG, "AC control is disabled, monitor only");
428  this->reset_to_idle_();
429  return;
430  default:
431  ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
432  this->reset_to_idle_();
433  return;
434  }
435  }
436  if (this->control_messages_queue_.empty()) {
437  ESP_LOGW(TAG, "Control message queue is empty!");
438  this->reset_to_idle_();
439  } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
440  ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
443  }
444  break;
446  if (this->action_request_.has_value()) {
447  if (this->action_request_.value().message.has_value()) {
448  this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
449  this->action_request_.value().message.reset();
450  } else {
451  // Message already sent, reseting request and return to idle
452  this->action_request_.reset();
454  }
455  } else {
456  ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
458  }
459  break;
460  case ProtocolPhases::IDLE: {
463  this->forced_request_status_ = false;
464  } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
465  ALARM_STATUS_REQUEST_INTERVAL_MS) {
467  }
468 #ifdef USE_WIFI
469  else if (this->send_wifi_signal_ &&
470  (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
471  SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) {
473  }
474 #endif
475  } break;
476  default:
477  // Shouldn't get here
478  ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
479  phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
481  break;
482  }
483 }
484 
485 haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
486  if (state) {
487  static haier_protocol::HaierMessage power_on_message(
488  haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
489  std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
490  return power_on_message;
491  } else {
492  static haier_protocol::HaierMessage power_off_message(
493  haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
494  std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
495  return power_off_message;
496  }
497 }
498 
501  constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
502  this->hon_rtc_ =
503  global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
504  HonSettings recovered;
505  if (this->hon_rtc_.load(&recovered)) {
506  this->settings_ = recovered;
507  } else {
509  }
513 }
514 
515 haier_protocol::HaierMessage HonClimate::get_control_message() {
516  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
517  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
518  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
519  control_out_buffer[4] = 0; // This byte should be cleared before setting values
520  bool has_hvac_settings = false;
521  if (this->current_hvac_settings_.valid) {
522  has_hvac_settings = true;
523  HvacSettings &climate_control = this->current_hvac_settings_;
524  if (climate_control.mode.has_value()) {
525  switch (climate_control.mode.value()) {
526  case CLIMATE_MODE_OFF:
527  out_data->ac_power = 0;
528  break;
530  out_data->ac_power = 1;
531  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
532  out_data->fan_mode = this->other_modes_fan_speed_;
533  break;
534  case CLIMATE_MODE_HEAT:
535  out_data->ac_power = 1;
536  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
537  out_data->fan_mode = this->other_modes_fan_speed_;
538  break;
539  case CLIMATE_MODE_DRY:
540  out_data->ac_power = 1;
541  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
542  out_data->fan_mode = this->other_modes_fan_speed_;
543  break;
545  out_data->ac_power = 1;
546  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
547  out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
548  // Disabling boost for Fan only
549  out_data->fast_mode = 0;
550  break;
551  case CLIMATE_MODE_COOL:
552  out_data->ac_power = 1;
553  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
554  out_data->fan_mode = this->other_modes_fan_speed_;
555  break;
556  default:
557  ESP_LOGE("Control", "Unsupported climate mode");
558  break;
559  }
560  }
561  // Set fan speed, if we are in fan mode, reject auto in fan mode
562  if (climate_control.fan_mode.has_value()) {
563  switch (climate_control.fan_mode.value()) {
564  case CLIMATE_FAN_LOW:
565  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
566  break;
567  case CLIMATE_FAN_MEDIUM:
568  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
569  break;
570  case CLIMATE_FAN_HIGH:
571  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
572  break;
573  case CLIMATE_FAN_AUTO:
574  if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
575  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
576  break;
577  default:
578  ESP_LOGE("Control", "Unsupported fan mode");
579  break;
580  }
581  }
582  // Set swing mode
583  if (climate_control.swing_mode.has_value()) {
584  switch (climate_control.swing_mode.value()) {
585  case CLIMATE_SWING_OFF:
586  out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
587  out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
588  break;
590  out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
591  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
592  break;
594  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
595  out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
596  break;
597  case CLIMATE_SWING_BOTH:
598  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
599  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
600  break;
601  }
602  }
603  if (climate_control.target_temperature.has_value()) {
604  float target_temp = climate_control.target_temperature.value();
605  out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
606  out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
607  }
608  if (out_data->ac_power == 0) {
609  // If AC is off - no presets allowed
610  out_data->fast_mode = 0;
611  out_data->sleep_mode = 0;
612  } else if (climate_control.preset.has_value()) {
613  switch (climate_control.preset.value()) {
614  case CLIMATE_PRESET_NONE:
615  out_data->fast_mode = 0;
616  out_data->sleep_mode = 0;
617  out_data->ten_degree = 0;
618  break;
620  // Boost is not supported in Fan only mode
621  out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
622  out_data->sleep_mode = 0;
623  out_data->ten_degree = 0;
624  break;
625  case CLIMATE_PRESET_AWAY:
626  out_data->fast_mode = 0;
627  out_data->sleep_mode = 0;
628  // 10 degrees allowed only in heat mode
629  out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
630  break;
632  out_data->fast_mode = 0;
633  out_data->sleep_mode = 1;
634  out_data->ten_degree = 0;
635  break;
636  default:
637  ESP_LOGE("Control", "Unsupported preset");
638  out_data->fast_mode = 0;
639  out_data->sleep_mode = 0;
640  out_data->ten_degree = 0;
641  break;
642  }
643  }
644  }
646  out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
648  }
650  out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
652  }
653  {
654  // Quiet mode
655  if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
656  // If AC is off or in fan only mode - no quiet mode allowed
657  out_data->quiet_mode = 0;
658  } else {
659  out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
660  }
661  // Clean quiet mode state pending flag
662  this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
663  }
664  out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
665  control_out_buffer[4] = 0; // This byte should be cleared before setting values
666  out_data->display_status = this->get_display_state() ? 1 : 0;
667  this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
668  out_data->health_mode = this->get_health_mode() ? 1 : 0;
669  this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
670  return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
672  control_out_buffer, this->real_control_packet_size_);
673 }
674 
675 void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
676  constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
677  if (size >= active_alarms_size + 2) {
678  if (check_new) {
679  size_t alarm_code = 0;
680  for (int i = active_alarms_size - 1; i >= 0; i--) {
681  if (packet[2 + i] != active_alarms_[i]) {
682  uint8_t alarm_bit = 1;
683  for (int b = 0; b < 8; b++) {
684  if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
685  bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
686  int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
687  const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
689  : "Unknown";
690  esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
691  alarm_code, alarm_message);
692  if (alarm_status) {
693  this->alarm_start_callback_.call(alarm_code, alarm_message);
694  this->active_alarm_count_ += 1.0f;
695  } else {
696  this->alarm_end_callback_.call(alarm_code, alarm_message);
697  this->active_alarm_count_ -= 1.0f;
698  }
699  }
700  alarm_bit <<= 1;
701  alarm_code++;
702  }
703  active_alarms_[i] = packet[2 + i];
704  } else
705  alarm_code += 8;
706  }
707  } else {
708  float alarm_count = 0.0f;
709  static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
710  for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
711  alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
712  }
713  this->active_alarm_count_ = alarm_count;
714  memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
715  }
716  }
717 }
718 
719 #ifdef USE_SENSOR
723  if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
724  this->big_data_sensors_--;
725  } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
726  this->big_data_sensors_++;
727  }
728  }
729  this->sub_sensors_[(size_t) type] = sens;
730  }
731 }
732 
735  size_t index = (size_t) type;
736  if ((this->sub_sensors_[index] != nullptr) &&
737  ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
738  this->sub_sensors_[index]->publish_state(value);
739  }
740 }
741 #endif // USE_SENSOR
742 
743 #ifdef USE_BINARY_SENSOR
746  if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
747  this->big_data_sensors_--;
748  } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
749  this->big_data_sensors_++;
750  }
751  this->sub_binary_sensors_[(size_t) type] = sens;
752  }
753 }
754 
756  if (value < 2) {
757  bool converted_value = value == 1;
758  size_t index = (size_t) type;
759  if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
760  (this->sub_binary_sensors_[index]->state != converted_value)))
761  this->sub_binary_sensors_[index]->publish_state(converted_value);
762  }
763 }
764 #endif // USE_BINARY_SENSOR
765 
766 #ifdef USE_TEXT_SENSOR
768  this->sub_text_sensors_[(size_t) type] = sens;
769  switch (type) {
771  if (this->hvac_hardware_info_.has_value())
772  sens->publish_state(this->hvac_hardware_info_.value().device_name_);
773  break;
775  if (this->hvac_hardware_info_.has_value())
776  sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
777  break;
780  break;
781  default:
782  break;
783  }
784 }
785 
787  size_t index = (size_t) type;
788  if (this->sub_text_sensors_[index] != nullptr)
789  this->sub_text_sensors_[index]->publish_state(value);
790 }
791 #endif // USE_TEXT_SENSOR
792 
793 #ifdef USE_SWITCH
795  this->beeper_switch_ = sw;
796  if (this->beeper_switch_ != nullptr) {
798  }
799 }
800 
802  this->quiet_mode_switch_ = sw;
803  if (this->quiet_mode_switch_ != nullptr) {
805  }
806 }
807 #endif // USE_SWITCH
808 
809 haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
810  size_t expected_size =
812  if (size < expected_size) {
813  ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
814  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
815  }
816  uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
817  if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
818  // Got BigData packet
819  const hon_protocol::HaierPacketBigData *bd_packet =
820  (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
821 #ifdef USE_SENSOR
827  this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
830  encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
831  this->update_sub_sensor_(
833  encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
834 #endif // USE_SENSOR
835 #ifdef USE_BINARY_SENSOR
842  bd_packet->indoor_electric_heating_status);
843 #endif // USE_BINARY_SENSOR
844  }
845  struct {
848  } packet;
849  memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
851  memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
853  if (packet.sensors.error_status != 0) {
854  ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
855  }
856 #ifdef USE_SENSOR
857  if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
858  (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
859  this->got_valid_outdoor_temp_ = true;
861  (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
862  }
863  if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
864  this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
865  }
866 #endif // USE_SENSOR
867  bool should_publish = false;
868  {
869  // Extra modes/presets
870  optional<ClimatePreset> old_preset = this->preset;
871  if (packet.control.fast_mode != 0) {
873  } else if (packet.control.sleep_mode != 0) {
875  } else if (packet.control.ten_degree != 0) {
876  this->preset = CLIMATE_PRESET_AWAY;
877  } else {
878  this->preset = CLIMATE_PRESET_NONE;
879  }
880  should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value());
881  }
882  {
883  // Target temperature
884  float old_target_temperature = this->target_temperature;
885  this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
886  should_publish = should_publish || (old_target_temperature != this->target_temperature);
887  }
888  {
889  // Current temperature
890  float old_current_temperature = this->current_temperature;
891  this->current_temperature = packet.sensors.room_temperature / 2.0f;
892  should_publish = should_publish || (old_current_temperature != this->current_temperature);
893  }
894  {
895  // Fan mode
896  optional<ClimateFanMode> old_fan_mode = this->fan_mode;
897  // remember the fan speed we last had for climate vs fan
898  if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
899  if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
900  this->fan_mode_speed_ = packet.control.fan_mode;
901  } else {
902  this->other_modes_fan_speed_ = packet.control.fan_mode;
903  }
904  switch (packet.control.fan_mode) {
905  case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
906  if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
907  this->fan_mode = CLIMATE_FAN_AUTO;
908  } else {
909  // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
910  ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
911  }
912  break;
913  case (uint8_t) hon_protocol::FanMode::FAN_MID:
915  break;
916  case (uint8_t) hon_protocol::FanMode::FAN_LOW:
917  this->fan_mode = CLIMATE_FAN_LOW;
918  break;
919  case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
920  this->fan_mode = CLIMATE_FAN_HIGH;
921  break;
922  }
923  should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
924  }
925  // Display status
926  // should be before "Climate mode" because it is changing this->mode
927  if (packet.control.ac_power != 0) {
928  // if AC is off display status always ON so process it only when AC is on
929  bool disp_status = packet.control.display_status != 0;
930  if (disp_status != this->get_display_state()) {
931  // Do something only if display status changed
932  if (this->mode == CLIMATE_MODE_OFF) {
933  // AC just turned on from remote need to turn off display
934  this->force_send_control_ = true;
935  } else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
936  this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
937  }
938  }
939  }
940  // Health mode
941  if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
942  bool old_health_mode = this->get_health_mode();
943  this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
944  should_publish = should_publish || (old_health_mode != this->get_health_mode());
945  }
946  {
947  CleaningState new_cleaning;
948  if (packet.control.steri_clean == 1) {
949  // Steri-cleaning
950  new_cleaning = CleaningState::STERI_CLEAN;
951  } else if (packet.control.self_cleaning_status == 1) {
952  // Self-cleaning
953  new_cleaning = CleaningState::SELF_CLEAN;
954  } else {
955  // No cleaning
956  new_cleaning = CleaningState::NO_CLEANING;
957  }
958  if (new_cleaning != this->cleaning_status_) {
959  ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
960  if (new_cleaning == CleaningState::NO_CLEANING) {
961  // Turning AC off after cleaning
962  this->action_request_ =
964  }
965  this->cleaning_status_ = new_cleaning;
966 #ifdef USE_TEXT_SENSOR
968 #endif // USE_TEXT_SENSOR
969  }
970  }
971  {
972  // Climate mode
973  ClimateMode old_mode = this->mode;
974  if (packet.control.ac_power == 0) {
975  this->mode = CLIMATE_MODE_OFF;
976  } else {
977  // Check current hvac mode
978  switch (packet.control.ac_mode) {
980  this->mode = CLIMATE_MODE_COOL;
981  break;
983  this->mode = CLIMATE_MODE_HEAT;
984  break;
986  this->mode = CLIMATE_MODE_DRY;
987  break;
989  this->mode = CLIMATE_MODE_FAN_ONLY;
990  break;
993  break;
994  }
995  }
996  should_publish = should_publish || (old_mode != this->mode);
997  }
998  {
999  // Quiet mode, should be after climate mode
1000  if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
1001  ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
1002  // In proper mode and not in pending state
1003  bool new_quiet_mode = packet.control.quiet_mode != 0;
1004  if (new_quiet_mode != this->get_quiet_mode_state()) {
1005  this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
1006  this->settings_.quiet_mode_state = new_quiet_mode;
1007  this->hon_rtc_.save(&this->settings_);
1008  }
1009  }
1010  }
1011  {
1012  // Swing mode
1013  ClimateSwingMode old_swing_mode = this->swing_mode;
1014  const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes();
1015  bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end();
1016  bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end();
1017  if (horizontal_swing_supported &&
1018  (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
1019  if (vertical_swing_supported &&
1020  (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1022  } else {
1024  }
1025  } else {
1026  if (vertical_swing_supported &&
1027  (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1029  } else {
1030  this->swing_mode = CLIMATE_SWING_OFF;
1031  }
1032  }
1033  // Saving last known non auto mode for vertical and horizontal swing
1034  this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
1035  this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
1041  if (save_settings) {
1044  this->hon_rtc_.save(&this->settings_);
1045  }
1046  should_publish = should_publish || (old_swing_mode != this->swing_mode);
1047  }
1048  this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
1049  if (should_publish) {
1050  this->publish_state();
1051  }
1052  if (should_publish) {
1053  ESP_LOGI(TAG, "HVAC values changed");
1054  }
1055  int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1056  esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1057  esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1058  esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1059  esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1060  esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1061  return haier_protocol::HandlerError::HANDLER_OK;
1062 }
1063 
1065  if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1066  return;
1068  HvacSettings climate_control;
1069  climate_control = this->current_hvac_settings_;
1070  // Beeper command
1071  {
1072  this->control_messages_queue_.push(
1073  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1076  this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2));
1077  }
1078  // Health mode
1079  {
1080  this->control_messages_queue_.push(
1081  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1084  this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2));
1085  this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
1086  }
1087  // Climate mode
1088  ClimateMode climate_mode = this->mode;
1089  bool new_power = this->mode != CLIMATE_MODE_OFF;
1090  uint8_t fan_mode_buf[] = {0x00, 0xFF};
1091  uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1092  if (climate_control.mode.has_value()) {
1093  climate_mode = climate_control.mode.value();
1094  uint8_t buffer[2] = {0x00, 0x00};
1095  switch (climate_control.mode.value()) {
1096  case CLIMATE_MODE_OFF:
1097  new_power = false;
1098  break;
1100  new_power = true;
1101  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
1102  this->control_messages_queue_.push(
1103  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1106  buffer, 2));
1107  fan_mode_buf[1] = this->other_modes_fan_speed_;
1108  break;
1109  case CLIMATE_MODE_HEAT:
1110  new_power = true;
1111  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
1112  this->control_messages_queue_.push(
1113  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1115  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1116  buffer, 2));
1117  fan_mode_buf[1] = this->other_modes_fan_speed_;
1118  break;
1119  case CLIMATE_MODE_DRY:
1120  new_power = true;
1121  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
1122  this->control_messages_queue_.push(
1123  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1125  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1126  buffer, 2));
1127  fan_mode_buf[1] = this->other_modes_fan_speed_;
1128  break;
1129  case CLIMATE_MODE_FAN_ONLY:
1130  new_power = true;
1131  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
1132  this->control_messages_queue_.push(
1133  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1135  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1136  buffer, 2));
1137  fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1138  break;
1139  case CLIMATE_MODE_COOL:
1140  new_power = true;
1141  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
1142  this->control_messages_queue_.push(
1143  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1145  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1146  buffer, 2));
1147  fan_mode_buf[1] = this->other_modes_fan_speed_;
1148  break;
1149  default:
1150  ESP_LOGE("Control", "Unsupported climate mode");
1151  break;
1152  }
1153  }
1154  // Climate power
1155  {
1156  this->control_messages_queue_.push(
1157  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1160  new_power ? ONE_BUF : ZERO_BUF, 2));
1161  }
1162  // CLimate preset
1163  {
1164  uint8_t fast_mode_buf[] = {0x00, 0xFF};
1165  uint8_t away_mode_buf[] = {0x00, 0xFF};
1166  if (!new_power) {
1167  // If AC is off - no presets allowed
1168  fast_mode_buf[1] = 0x00;
1169  away_mode_buf[1] = 0x00;
1170  } else if (climate_control.preset.has_value()) {
1171  switch (climate_control.preset.value()) {
1172  case CLIMATE_PRESET_NONE:
1173  fast_mode_buf[1] = 0x00;
1174  away_mode_buf[1] = 0x00;
1175  break;
1176  case CLIMATE_PRESET_BOOST:
1177  // Boost is not supported in Fan only mode
1178  fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1179  away_mode_buf[1] = 0x00;
1180  break;
1181  case CLIMATE_PRESET_AWAY:
1182  fast_mode_buf[1] = 0x00;
1183  away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1184  break;
1185  default:
1186  ESP_LOGE("Control", "Unsupported preset");
1187  break;
1188  }
1189  }
1190  {
1191  // Quiet mode
1192  if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
1193  quiet_mode_buf[1] = 0x01;
1194  } else {
1195  quiet_mode_buf[1] = 0x00;
1196  }
1197  // Clean quiet mode state pending flag
1198  this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
1199  }
1200  auto presets = this->traits_.get_supported_presets();
1201  if (quiet_mode_buf[1] != 0xFF) {
1202  this->control_messages_queue_.push(
1203  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1206  quiet_mode_buf, 2));
1207  }
1208  if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
1209  this->control_messages_queue_.push(
1210  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1213  fast_mode_buf, 2));
1214  }
1215  if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
1216  this->control_messages_queue_.push(
1217  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1220  away_mode_buf, 2));
1221  }
1222  }
1223  // Target temperature
1224  if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1225  uint8_t buffer[2] = {0x00, 0x00};
1226  buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1227  this->control_messages_queue_.push(
1228  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1231  buffer, 2));
1232  }
1233  // Vertical swing mode
1234  if (climate_control.swing_mode.has_value()) {
1235  uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1236  uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1237  switch (climate_control.swing_mode.value()) {
1238  case CLIMATE_SWING_OFF:
1239  horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1240  vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1241  break;
1243  horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1244  break;
1246  vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1247  break;
1248  case CLIMATE_SWING_BOTH:
1249  break;
1250  }
1251  this->control_messages_queue_.push(
1252  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1255  horizontal_swing_buf, 2));
1256  this->control_messages_queue_.push(
1257  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1260  vertical_swing_buf, 2));
1261  }
1262  // Fan mode
1263  if (climate_control.fan_mode.has_value()) {
1264  switch (climate_control.fan_mode.value()) {
1265  case CLIMATE_FAN_LOW:
1266  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1267  break;
1268  case CLIMATE_FAN_MEDIUM:
1269  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1270  break;
1271  case CLIMATE_FAN_HIGH:
1272  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1273  break;
1274  case CLIMATE_FAN_AUTO:
1275  if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1276  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1277  break;
1278  default:
1279  ESP_LOGE("Control", "Unsupported fan mode");
1280  break;
1281  }
1282  if (fan_mode_buf[1] != 0xFF) {
1283  this->control_messages_queue_.push(
1284  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1287  fan_mode_buf, 2));
1288  }
1289  }
1290 }
1291 
1293  while (!this->control_messages_queue_.empty())
1294  this->control_messages_queue_.pop();
1295 }
1296 
1298  switch (this->action_request_.value().action) {
1301  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1302  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1303  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1304  out_data->self_cleaning_status = 1;
1305  out_data->steri_clean = 0;
1306  out_data->set_point = 0x06;
1307  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
1308  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
1309  out_data->ac_power = 1;
1310  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1311  out_data->light_status = 0;
1312  this->action_request_.value().message = haier_protocol::HaierMessage(
1313  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1314  control_out_buffer, this->real_control_packet_size_);
1315  return true;
1317  this->action_request_.value().message =
1318  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1321  ONE_BUF, 2);
1322  return true;
1323  } else {
1324  this->action_request_.reset();
1325  return false;
1326  }
1329  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1330  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1331  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1332  out_data->self_cleaning_status = 0;
1333  out_data->steri_clean = 1;
1334  out_data->set_point = 0x06;
1335  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
1336  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
1337  out_data->ac_power = 1;
1338  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1339  out_data->light_status = 0;
1340  this->action_request_.value().message = haier_protocol::HaierMessage(
1341  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1342  control_out_buffer, this->real_control_packet_size_);
1343  return true;
1344  } else {
1345  // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1346  this->action_request_.reset();
1347  return false;
1348  }
1349  default:
1351  }
1352 }
1353 
1356 #ifdef USE_SENSOR
1357  for (auto &sub_sensor : this->sub_sensors_) {
1358  if ((sub_sensor != nullptr) && sub_sensor->has_state())
1359  sub_sensor->publish_state(NAN);
1360  }
1361 #endif // USE_SENSOR
1362  this->got_valid_outdoor_temp_ = false;
1363  this->hvac_hardware_info_.reset();
1364  this->last_status_message_.reset(nullptr);
1365 }
1366 
1368  if (this->big_data_sensors_ > 0) {
1369  static uint8_t counter = 0;
1370  counter = (counter + 1) % 3;
1371  return counter == 1;
1372  }
1373  return false;
1374 }
1375 
1376 } // namespace haier
1377 } // namespace esphome
Base class for all switches.
Definition: switch.h:39
value_type const & value() const
Definition: optional.h:89
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:202
esphome::optional< hon_protocol::VerticalSwingMode > current_vertical_swing_
Definition: hon_climate.h:190
haier_protocol::HaierMessage get_control_message() override
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)
void update_sub_sensor_(SubSensorType type, float value)
esphome::optional< hon_protocol::VerticalSwingMode > pending_vertical_direction_
Definition: hon_climate.h:174
esphome::optional< float > target_temperature
Definition: haier_base.h:137
hon_protocol::HorizontalSwingMode last_horizontal_swing
Definition: hon_climate.h:33
CallbackManager< void(uint8_t, const char *)> alarm_start_callback_
Definition: hon_climate.h:185
esphome::optional< esphome::climate::ClimatePreset > preset
Definition: haier_base.h:138
CallbackManager< void(uint8_t, const char *)> alarm_end_callback_
Definition: hon_climate.h:186
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size)
const std::set< ClimateSwingMode > & get_supported_swing_modes() const
std::unique_ptr< uint8_t[]> last_status_message_
Definition: haier_base.h:170
constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET
Definition: hon_climate.cpp:17
float target_temperature
The target temperature of the climate device.
Definition: climate.h:186
void add_alarm_end_callback(std::function< void(uint8_t, const char *)> &&callback)
haier_protocol::HandlerError report_network_status_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
Definition: haier_base.cpp:223
virtual void set_phase(ProtocolPhases phase)
Definition: haier_base.cpp:73
void set_quiet_mode_switch(switch_::Switch *sw)
void control(const esphome::climate::ClimateCall &call) override
Definition: haier_base.cpp:371
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value)
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL
Definition: hon_climate.cpp:19
std::chrono::steady_clock::time_point last_status_request_
Definition: haier_base.h:173
std::chrono::steady_clock::time_point last_signal_request_
Definition: haier_base.h:174
const uint8_t ONE_BUF[]
Definition: hon_climate.cpp:21
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:173
void set_handlers() override
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction)
Definition: hon_climate.cpp:65
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition: climate.h:179
void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens)
CleaningState cleaning_status_
Definition: hon_climate.h:172
void publish_state(const std::string &state)
Definition: text_sensor.cpp:9
haier_protocol::HandlerError answer_preprocess_(haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, ProtocolPhases expected_phase)
Definition: haier_base.cpp:204
bool has_value() const
Definition: optional.h:87
HonControlMethod control_method_
Definition: hon_climate.h:183
esphome::optional< hon_protocol::HorizontalSwingMode > get_horizontal_airflow() const
Definition: hon_climate.cpp:70
esphome::optional< HardwareInfo > hvac_hardware_info_
Definition: hon_climate.h:176
bool get_quiet_mode_state() const
Definition: hon_climate.cpp:57
void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens)
haier_protocol::HandlerError get_device_id_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
ClimateSwingMode
Enum for all modes a climate swing can be in.
Definition: climate_mode.h:70
void process_phase(std::chrono::steady_clock::time_point now) override
bool save(const T *src)
Definition: preferences.h:21
void initialization() override
void set_quiet_mode_state(bool state)
Definition: hon_climate.cpp:46
esphome::optional< hon_protocol::HorizontalSwingMode > current_horizontal_swing_
Definition: hon_climate.h:191
haier_protocol::ProtocolHandler haier_protocol_
Definition: haier_base.h:155
FanDirection direction
Definition: fan.h:37
bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:106
void add_alarm_start_callback(std::function< void(uint8_t, const char *)> &&callback)
constexpr size_t HON_ALARM_COUNT
Definition: hon_packet.h:256
void set_beeper_state(bool state)
Definition: hon_climate.cpp:34
void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats=0, std::chrono::milliseconds interval=std::chrono::milliseconds::zero())
Definition: haier_base.cpp:420
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS
Definition: hon_climate.cpp:16
haier_protocol::HaierMessage get_wifi_signal_message_()
Definition: haier_base.cpp:111
optional< ClimatePreset > preset
The active preset of the climate device.
Definition: climate.h:208
const char * phase_to_string_(ProtocolPhases phase)
Definition: haier_base.cpp:23
virtual bool has_state() const
Return whether this binary sensor has outputted a state.
const std::set< climate::ClimatePreset > & get_supported_presets() const
ESPPreferences * global_preferences
void set_beeper_switch(switch_::Switch *sw)
esphome::optional< hon_protocol::VerticalSwingMode > get_vertical_airflow() const
Definition: hon_climate.cpp:61
esphome::optional< hon_protocol::HorizontalSwingMode > pending_horizontal_direction_
Definition: hon_climate.h:175
CleaningState get_cleaning_status() const
Definition: hon_climate.cpp:90
void set_sub_sensor(SubSensorType type, sensor::Sensor *sens)
ESPPreferenceObject hon_rtc_
Definition: hon_climate.h:193
esphome::optional< PendingAction > action_request_
Definition: haier_base.h:157
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
sensor::Sensor * sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]
Definition: hon_climate.h:62
haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
uint8_t type
esphome::optional< esphome::climate::ClimateFanMode > fan_mode
Definition: haier_base.h:135
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition: helpers.h:182
void publish_state(bool state)
Publish a new state to the front-end.
switch_::Switch * beeper_switch_
Definition: hon_climate.h:101
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS
Definition: hon_climate.cpp:20
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition: climate.cpp:395
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition: log.cpp:11
text_sensor::TextSensor * sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]
Definition: hon_climate.h:93
ClimateMode
Enum for all modes a climate device can be in.
Definition: climate_mode.h:10
haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
switch_::Switch * quiet_mode_switch_
Definition: hon_climate.h:102
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:199
const uint8_t ZERO_BUF[]
Definition: hon_climate.cpp:22
std::string get_cleaning_status_text() const
Definition: hon_climate.cpp:79
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value)
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:94
std::chrono::steady_clock::time_point last_alarm_request_
Definition: hon_climate.h:188
bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:98
haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, size_t size)
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool has_state() const
Return whether this sensor has gotten a full state (that passed through all filters) yet...
Definition: sensor.cpp:97
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:37
void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new)
void process_protocol_reset() override
haier_protocol::HaierMessage get_power_message(bool state) override
Base-class for all sensors.
Definition: sensor.h:57
const std::string HON_ALARM_MESSAGES[]
Definition: hon_packet.h:202
void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction)
Definition: hon_climate.cpp:74
bool prepare_pending_action() override
constexpr uint8_t CONTROL_MESSAGE_RETRIES
Definition: hon_climate.cpp:18
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition: switch.cpp:47
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:102
CallbackManager< void(const char *, size_t)> status_message_callback_
Definition: haier_base.h:175
haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
std::queue< haier_protocol::HaierMessage > control_messages_queue_
Definition: hon_climate.h:184
binary_sensor::BinarySensor * sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]
Definition: hon_climate.h:79
hon_protocol::VerticalSwingMode last_vertiacal_swing
Definition: hon_climate.h:32
uint32_t get_object_id_hash()
Definition: entity_base.cpp:76
esphome::climate::ClimateTraits traits_
Definition: haier_base.h:167
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
Definition: haier_base.h:136
bool state
Definition: fan.h:34
std::chrono::steady_clock::time_point last_valid_status_timestamp_
Definition: haier_base.h:172
esphome::optional< esphome::climate::ClimateMode > mode
Definition: haier_base.h:134
void dump_config() override