ESPHome  2024.7.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 
36 bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
37 
39  return this->current_vertical_swing_;
40 };
41 
44  this->force_send_control_ = true;
45 }
46 
48  return this->current_horizontal_swing_;
49 }
50 
53  this->force_send_control_ = true;
54 }
55 
57  switch (this->cleaning_status_) {
59  return "Self clean";
61  return "56°C Steri-Clean";
62  default:
63  return "No cleaning";
64  }
65 }
66 
68 
71  ESP_LOGI(TAG, "Sending self cleaning start request");
72  this->action_request_ =
74  }
75 }
76 
79  ESP_LOGI(TAG, "Sending steri cleaning start request");
80  this->action_request_ =
82  }
83 }
84 
85 void HonClimate::add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback) {
86  this->alarm_start_callback_.add(std::move(callback));
87 }
88 
89 void HonClimate::add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback) {
90  this->alarm_end_callback_.add(std::move(callback));
91 }
92 
93 haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
94  haier_protocol::FrameType message_type,
95  const uint8_t *data, size_t data_size) {
96  // Should check this before preprocess
97  if (message_type == haier_protocol::FrameType::INVALID) {
98  ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
99  "protocol instead of hOn");
101  return haier_protocol::HandlerError::INVALID_ANSWER;
102  }
103  haier_protocol::HandlerError result =
104  this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
105  haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
106  if (result == haier_protocol::HandlerError::HANDLER_OK) {
107  if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
108  // Wrong structure
109  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
110  }
111  // All OK
113  char tmp[9];
114  tmp[8] = 0;
115  strncpy(tmp, answr->protocol_version, 8);
117  this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp);
118  strncpy(tmp, answr->software_version, 8);
119  this->hvac_hardware_info_.value().software_version_ = std::string(tmp);
120  strncpy(tmp, answr->hardware_version, 8);
121  this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
122  strncpy(tmp, answr->device_name, 8);
123  this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
124 #ifdef USE_TEXT_SENSOR
127  this->hvac_hardware_info_.value().protocol_version_);
128 #endif
129  this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
130  this->hvac_hardware_info_.value().functions_[1] =
131  (answr->functions[1] & 0x02) != 0; // controller-device mode support
132  this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
133  this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
134  this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
135  this->use_crc_ = this->hvac_hardware_info_.value().functions_[2];
137  return result;
138  } else {
139  this->reset_phase_();
140  return result;
141  }
142 }
143 
144 haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
145  haier_protocol::FrameType message_type,
146  const uint8_t *data, size_t data_size) {
147  haier_protocol::HandlerError result =
148  this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
149  haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
150  if (result == haier_protocol::HandlerError::HANDLER_OK) {
152  return result;
153  } else {
154  this->reset_phase_();
155  return result;
156  }
157 }
158 
159 haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
160  haier_protocol::FrameType message_type, const uint8_t *data,
161  size_t data_size) {
162  haier_protocol::HandlerError result =
163  this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
164  haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
165  if (result == haier_protocol::HandlerError::HANDLER_OK) {
166  result = this->process_status_message_(data, data_size);
167  if (result != haier_protocol::HandlerError::HANDLER_OK) {
168  ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
169  this->reset_phase_();
170  this->action_request_.reset();
171  this->force_send_control_ = false;
172  } else {
173  if (!this->last_status_message_) {
176  this->last_status_message_.reset();
177  this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
178  };
179  if (data_size >= this->real_control_packet_size_ + 2) {
180  memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
182  this->status_message_callback_.call((const char *) data, data_size);
183  } else {
184  ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
185  }
186  switch (this->protocol_phase_) {
188  ESP_LOGI(TAG, "First HVAC status received");
190  break;
192  // Do nothing, phase will be changed in process_phase
193  break;
196  break;
198  if (!this->control_messages_queue_.empty())
199  this->control_messages_queue_.pop();
200  if (this->control_messages_queue_.empty()) {
202  this->force_send_control_ = false;
203  if (this->current_hvac_settings_.valid)
205  } else {
207  }
208  break;
209  default:
210  break;
211  }
212  }
213  return result;
214  } else {
215  this->action_request_.reset();
216  this->force_send_control_ = false;
217  this->reset_phase_();
218  return result;
219  }
220 }
221 
223  haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
224  size_t data_size) {
225  haier_protocol::HandlerError result = this->answer_preprocess_(
226  request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
227  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
228  if (result == haier_protocol::HandlerError::HANDLER_OK) {
230  return result;
231  } else {
233  return result;
234  }
235 }
236 
237 haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
238  haier_protocol::FrameType message_type,
239  const uint8_t *data, size_t data_size) {
240  if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
241  if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
242  // Unexpected answer to request
244  return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
245  }
248  // Don't expect this answer now
250  return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
251  }
252  if (data_size < sizeof(active_alarms_) + 2)
253  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
254  this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
256  return haier_protocol::HandlerError::HANDLER_OK;
257  } else {
259  return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
260  }
261 }
262 
263 haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
264  const uint8_t *buffer, size_t size) {
265  haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
266  if (size < sizeof(this->active_alarms_) + 2) {
267  // Log error but confirm anyway to avoid to many messages
268  result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
269  }
270  this->process_alarm_message_(buffer, size, true);
271  this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
272  this->last_alarm_request_ = std::chrono::steady_clock::now();
273  return result;
274 }
275 
277  // Set handlers
278  this->haier_protocol_.set_answer_handler(
279  haier_protocol::FrameType::GET_DEVICE_VERSION,
280  std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
281  std::placeholders::_3, std::placeholders::_4));
282  this->haier_protocol_.set_answer_handler(
283  haier_protocol::FrameType::GET_DEVICE_ID,
284  std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
285  std::placeholders::_3, std::placeholders::_4));
286  this->haier_protocol_.set_answer_handler(
287  haier_protocol::FrameType::CONTROL,
288  std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
289  std::placeholders::_4));
290  this->haier_protocol_.set_answer_handler(
291  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
292  std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1,
293  std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
294  this->haier_protocol_.set_answer_handler(
295  haier_protocol::FrameType::GET_ALARM_STATUS,
296  std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
297  std::placeholders::_3, std::placeholders::_4));
298  this->haier_protocol_.set_answer_handler(
299  haier_protocol::FrameType::REPORT_NETWORK_STATUS,
300  std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
301  std::placeholders::_3, std::placeholders::_4));
302  this->haier_protocol_.set_message_handler(
303  haier_protocol::FrameType::ALARM_STATUS,
304  std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2,
305  std::placeholders::_3));
306 }
307 
310  ESP_LOGCONFIG(TAG, " Protocol version: hOn");
311  ESP_LOGCONFIG(TAG, " Control method: %d", (uint8_t) this->control_method_);
312  if (this->hvac_hardware_info_.has_value()) {
313  ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_hardware_info_.value().protocol_version_.c_str());
314  ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_hardware_info_.value().software_version_.c_str());
315  ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_info_.value().hardware_version_.c_str());
316  ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_hardware_info_.value().device_name_.c_str());
317  ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
318  (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
319  (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
320  (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
321  (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
322  (this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
323  ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
324  }
325 }
326 
327 void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
328  switch (this->protocol_phase_) {
331  // Indicate device capabilities:
332  // bit 0 - if 1 module support interactive mode
333  // bit 1 - if 1 module support controller-device mode
334  // bit 2 - if 1 module support crc
335  // bit 3 - if 1 module support multiple devices
336  // bit 4..bit 15 - not used
337  uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
338  static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
339  haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
340  this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
341  }
342  break;
344  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
345  static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
346  this->send_message_(DEVICEID_REQUEST, this->use_crc_);
347  }
348  break;
351  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
352  static const haier_protocol::HaierMessage STATUS_REQUEST(
353  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
354  static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
355  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
357  (!this->should_get_big_data_())) {
358  this->send_message_(STATUS_REQUEST, this->use_crc_);
359  } else {
360  this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
361  }
362  this->last_status_request_ = now;
363  }
364  break;
365 #ifdef USE_WIFI
367  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
368  static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
369  haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
370  this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
371  this->last_signal_request_ = now;
372  }
373  break;
375  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
376  this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
377  }
378  break;
379 #else
383  break;
384 #endif
387  if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
388  static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
389  this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
390  this->last_alarm_request_ = now;
391  }
392  break;
394  if (this->control_messages_queue_.empty()) {
395  switch (this->control_method_) {
397  haier_protocol::HaierMessage control_message = this->get_control_message();
398  this->control_messages_queue_.push(control_message);
399  } break;
402  break;
404  ESP_LOGI(TAG, "AC control is disabled, monitor only");
405  this->reset_to_idle_();
406  return;
407  default:
408  ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
409  this->reset_to_idle_();
410  return;
411  }
412  }
413  if (this->control_messages_queue_.empty()) {
414  ESP_LOGW(TAG, "Control message queue is empty!");
415  this->reset_to_idle_();
416  } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
417  ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
420  }
421  break;
423  if (this->action_request_.has_value()) {
424  if (this->action_request_.value().message.has_value()) {
425  this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
426  this->action_request_.value().message.reset();
427  } else {
428  // Message already sent, reseting request and return to idle
429  this->action_request_.reset();
431  }
432  } else {
433  ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
435  }
436  break;
437  case ProtocolPhases::IDLE: {
440  this->forced_request_status_ = false;
441  } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
442  ALARM_STATUS_REQUEST_INTERVAL_MS) {
444  }
445 #ifdef USE_WIFI
446  else if (this->send_wifi_signal_ &&
447  (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
448  SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) {
450  }
451 #endif
452  } break;
453  default:
454  // Shouldn't get here
455  ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
456  phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
458  break;
459  }
460 }
461 
462 haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
463  if (state) {
464  static haier_protocol::HaierMessage power_on_message(
465  haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
466  std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
467  return power_on_message;
468  } else {
469  static haier_protocol::HaierMessage power_off_message(
470  haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
471  std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
472  return power_off_message;
473  }
474 }
475 
477  constexpr uint32_t restore_settings_version = 0xE834D8DCUL;
478  this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
479  HonSettings recovered;
480  if (this->rtc_.load(&recovered)) {
481  this->settings_ = recovered;
482  } else {
484  }
487 }
488 
489 haier_protocol::HaierMessage HonClimate::get_control_message() {
490  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
491  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
492  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
493  control_out_buffer[4] = 0; // This byte should be cleared before setting values
494  bool has_hvac_settings = false;
495  if (this->current_hvac_settings_.valid) {
496  has_hvac_settings = true;
497  HvacSettings &climate_control = this->current_hvac_settings_;
498  if (climate_control.mode.has_value()) {
499  switch (climate_control.mode.value()) {
500  case CLIMATE_MODE_OFF:
501  out_data->ac_power = 0;
502  break;
504  out_data->ac_power = 1;
505  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
506  out_data->fan_mode = this->other_modes_fan_speed_;
507  break;
508  case CLIMATE_MODE_HEAT:
509  out_data->ac_power = 1;
510  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
511  out_data->fan_mode = this->other_modes_fan_speed_;
512  break;
513  case CLIMATE_MODE_DRY:
514  out_data->ac_power = 1;
515  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
516  out_data->fan_mode = this->other_modes_fan_speed_;
517  break;
519  out_data->ac_power = 1;
520  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
521  out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
522  // Disabling boost and eco mode for Fan only
523  out_data->quiet_mode = 0;
524  out_data->fast_mode = 0;
525  break;
526  case CLIMATE_MODE_COOL:
527  out_data->ac_power = 1;
528  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
529  out_data->fan_mode = this->other_modes_fan_speed_;
530  break;
531  default:
532  ESP_LOGE("Control", "Unsupported climate mode");
533  break;
534  }
535  }
536  // Set fan speed, if we are in fan mode, reject auto in fan mode
537  if (climate_control.fan_mode.has_value()) {
538  switch (climate_control.fan_mode.value()) {
539  case CLIMATE_FAN_LOW:
540  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
541  break;
542  case CLIMATE_FAN_MEDIUM:
543  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
544  break;
545  case CLIMATE_FAN_HIGH:
546  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
547  break;
548  case CLIMATE_FAN_AUTO:
549  if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
550  out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
551  break;
552  default:
553  ESP_LOGE("Control", "Unsupported fan mode");
554  break;
555  }
556  }
557  // Set swing mode
558  if (climate_control.swing_mode.has_value()) {
559  switch (climate_control.swing_mode.value()) {
560  case CLIMATE_SWING_OFF:
561  out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
562  out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
563  break;
565  out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
566  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
567  break;
569  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
570  out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
571  break;
572  case CLIMATE_SWING_BOTH:
573  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
574  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
575  break;
576  }
577  }
578  if (climate_control.target_temperature.has_value()) {
579  float target_temp = climate_control.target_temperature.value();
580  out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
581  out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
582  }
583  if (out_data->ac_power == 0) {
584  // If AC is off - no presets allowed
585  out_data->quiet_mode = 0;
586  out_data->fast_mode = 0;
587  out_data->sleep_mode = 0;
588  } else if (climate_control.preset.has_value()) {
589  switch (climate_control.preset.value()) {
590  case CLIMATE_PRESET_NONE:
591  out_data->quiet_mode = 0;
592  out_data->fast_mode = 0;
593  out_data->sleep_mode = 0;
594  out_data->ten_degree = 0;
595  break;
596  case CLIMATE_PRESET_ECO:
597  // Eco is not supported in Fan only mode
598  out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
599  out_data->fast_mode = 0;
600  out_data->sleep_mode = 0;
601  out_data->ten_degree = 0;
602  break;
604  out_data->quiet_mode = 0;
605  // Boost is not supported in Fan only mode
606  out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
607  out_data->sleep_mode = 0;
608  out_data->ten_degree = 0;
609  break;
610  case CLIMATE_PRESET_AWAY:
611  out_data->quiet_mode = 0;
612  out_data->fast_mode = 0;
613  out_data->sleep_mode = 0;
614  // 10 degrees allowed only in heat mode
615  out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
616  break;
618  out_data->quiet_mode = 0;
619  out_data->fast_mode = 0;
620  out_data->sleep_mode = 1;
621  out_data->ten_degree = 0;
622  break;
623  default:
624  ESP_LOGE("Control", "Unsupported preset");
625  out_data->quiet_mode = 0;
626  out_data->fast_mode = 0;
627  out_data->sleep_mode = 0;
628  out_data->ten_degree = 0;
629  break;
630  }
631  }
632  }
634  out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
636  }
638  out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
640  }
641  out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
642  control_out_buffer[4] = 0; // This byte should be cleared before setting values
643  out_data->display_status = this->display_status_ ? 1 : 0;
644  out_data->health_mode = this->health_mode_ ? 1 : 0;
645  return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
647  control_out_buffer, this->real_control_packet_size_);
648 }
649 
650 void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
651  constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
652  if (size >= active_alarms_size + 2) {
653  if (check_new) {
654  size_t alarm_code = 0;
655  for (int i = active_alarms_size - 1; i >= 0; i--) {
656  if (packet[2 + i] != active_alarms_[i]) {
657  uint8_t alarm_bit = 1;
658  for (int b = 0; b < 8; b++) {
659  if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
660  bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
661  int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
662  const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
664  : "Unknown";
665  esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
666  alarm_code, alarm_message);
667  if (alarm_status) {
668  this->alarm_start_callback_.call(alarm_code, alarm_message);
669  this->active_alarm_count_ += 1.0f;
670  } else {
671  this->alarm_end_callback_.call(alarm_code, alarm_message);
672  this->active_alarm_count_ -= 1.0f;
673  }
674  }
675  alarm_bit <<= 1;
676  alarm_code++;
677  }
678  active_alarms_[i] = packet[2 + i];
679  } else
680  alarm_code += 8;
681  }
682  } else {
683  float alarm_count = 0.0f;
684  static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
685  for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
686  alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
687  }
688  this->active_alarm_count_ = alarm_count;
689  memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
690  }
691  }
692 }
693 
694 #ifdef USE_SENSOR
698  if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
699  this->big_data_sensors_--;
700  } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
701  this->big_data_sensors_++;
702  }
703  }
704  this->sub_sensors_[(size_t) type] = sens;
705  }
706 }
707 
710  size_t index = (size_t) type;
711  if ((this->sub_sensors_[index] != nullptr) &&
712  ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
713  this->sub_sensors_[index]->publish_state(value);
714  }
715 }
716 #endif // USE_SENSOR
717 
718 #ifdef USE_BINARY_SENSOR
721  if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
722  this->big_data_sensors_--;
723  } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
724  this->big_data_sensors_++;
725  }
726  this->sub_binary_sensors_[(size_t) type] = sens;
727  }
728 }
729 
731  if (value < 2) {
732  bool converted_value = value == 1;
733  size_t index = (size_t) type;
734  if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
735  (this->sub_binary_sensors_[index]->state != converted_value)))
736  this->sub_binary_sensors_[index]->publish_state(converted_value);
737  }
738 }
739 #endif // USE_BINARY_SENSOR
740 
741 #ifdef USE_TEXT_SENSOR
743  this->sub_text_sensors_[(size_t) type] = sens;
744  switch (type) {
746  if (this->hvac_hardware_info_.has_value())
747  sens->publish_state(this->hvac_hardware_info_.value().device_name_);
748  break;
750  if (this->hvac_hardware_info_.has_value())
751  sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
752  break;
755  break;
756  default:
757  break;
758  }
759 }
760 
762  size_t index = (size_t) type;
763  if (this->sub_text_sensors_[index] != nullptr)
764  this->sub_text_sensors_[index]->publish_state(value);
765 }
766 #endif // USE_TEXT_SENSOR
767 
768 haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
769  size_t expected_size =
771  if (size < expected_size) {
772  ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
773  return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
774  }
775  uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
776  if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
777  // Got BigData packet
778  const hon_protocol::HaierPacketBigData *bd_packet =
779  (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
780 #ifdef USE_SENSOR
786  this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
789  encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
790  this->update_sub_sensor_(
792  encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
793 #endif // USE_SENSOR
794 #ifdef USE_BINARY_SENSOR
801  bd_packet->indoor_electric_heating_status);
802 #endif // USE_BINARY_SENSOR
803  }
804  struct {
807  } packet;
808  memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
810  memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
812  if (packet.sensors.error_status != 0) {
813  ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
814  }
815 #ifdef USE_SENSOR
816  if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
817  (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
818  this->got_valid_outdoor_temp_ = true;
820  (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
821  }
822  if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
823  this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
824  }
825 #endif // USE_SENSOR
826  bool should_publish = false;
827  {
828  // Extra modes/presets
829  optional<ClimatePreset> old_preset = this->preset;
830  if (packet.control.quiet_mode != 0) {
831  this->preset = CLIMATE_PRESET_ECO;
832  } else if (packet.control.fast_mode != 0) {
834  } else if (packet.control.sleep_mode != 0) {
836  } else if (packet.control.ten_degree != 0) {
837  this->preset = CLIMATE_PRESET_AWAY;
838  } else {
839  this->preset = CLIMATE_PRESET_NONE;
840  }
841  should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value());
842  }
843  {
844  // Target temperature
845  float old_target_temperature = this->target_temperature;
846  this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
847  should_publish = should_publish || (old_target_temperature != this->target_temperature);
848  }
849  {
850  // Current temperature
851  float old_current_temperature = this->current_temperature;
852  this->current_temperature = packet.sensors.room_temperature / 2.0f;
853  should_publish = should_publish || (old_current_temperature != this->current_temperature);
854  }
855  {
856  // Fan mode
857  optional<ClimateFanMode> old_fan_mode = this->fan_mode;
858  // remember the fan speed we last had for climate vs fan
859  if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
860  if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
861  this->fan_mode_speed_ = packet.control.fan_mode;
862  } else {
863  this->other_modes_fan_speed_ = packet.control.fan_mode;
864  }
865  switch (packet.control.fan_mode) {
866  case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
867  if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
868  this->fan_mode = CLIMATE_FAN_AUTO;
869  } else {
870  // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
871  ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
872  }
873  break;
874  case (uint8_t) hon_protocol::FanMode::FAN_MID:
876  break;
877  case (uint8_t) hon_protocol::FanMode::FAN_LOW:
878  this->fan_mode = CLIMATE_FAN_LOW;
879  break;
880  case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
881  this->fan_mode = CLIMATE_FAN_HIGH;
882  break;
883  }
884  should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
885  }
886  {
887  // Display status
888  // should be before "Climate mode" because it is changing this->mode
889  if (packet.control.ac_power != 0) {
890  // if AC is off display status always ON so process it only when AC is on
891  bool disp_status = packet.control.display_status != 0;
892  if (disp_status != this->display_status_) {
893  // Do something only if display status changed
894  if (this->mode == CLIMATE_MODE_OFF) {
895  // AC just turned on from remote need to turn off display
896  this->force_send_control_ = true;
897  } else {
898  this->display_status_ = disp_status;
899  }
900  }
901  }
902  }
903  {
904  // Health mode
905  bool old_health_mode = this->health_mode_;
906  this->health_mode_ = packet.control.health_mode == 1;
907  should_publish = should_publish || (old_health_mode != this->health_mode_);
908  }
909  {
910  CleaningState new_cleaning;
911  if (packet.control.steri_clean == 1) {
912  // Steri-cleaning
913  new_cleaning = CleaningState::STERI_CLEAN;
914  } else if (packet.control.self_cleaning_status == 1) {
915  // Self-cleaning
916  new_cleaning = CleaningState::SELF_CLEAN;
917  } else {
918  // No cleaning
919  new_cleaning = CleaningState::NO_CLEANING;
920  }
921  if (new_cleaning != this->cleaning_status_) {
922  ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
923  if (new_cleaning == CleaningState::NO_CLEANING) {
924  // Turning AC off after cleaning
925  this->action_request_ =
927  }
928  this->cleaning_status_ = new_cleaning;
929 #ifdef USE_TEXT_SENSOR
931 #endif // USE_TEXT_SENSOR
932  }
933  }
934  {
935  // Climate mode
936  ClimateMode old_mode = this->mode;
937  if (packet.control.ac_power == 0) {
938  this->mode = CLIMATE_MODE_OFF;
939  } else {
940  // Check current hvac mode
941  switch (packet.control.ac_mode) {
943  this->mode = CLIMATE_MODE_COOL;
944  break;
946  this->mode = CLIMATE_MODE_HEAT;
947  break;
949  this->mode = CLIMATE_MODE_DRY;
950  break;
952  this->mode = CLIMATE_MODE_FAN_ONLY;
953  break;
956  break;
957  }
958  }
959  should_publish = should_publish || (old_mode != this->mode);
960  }
961  {
962  // Swing mode
963  ClimateSwingMode old_swing_mode = this->swing_mode;
964  if (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) {
965  if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) {
967  } else {
969  }
970  } else {
971  if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) {
973  } else {
975  }
976  }
977  // Saving last known non auto mode for vertical and horizontal swing
978  this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
979  this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
980  bool save_settings = ((this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO) &&
985  if (save_settings) {
988  this->rtc_.save(&this->settings_);
989  }
990  should_publish = should_publish || (old_swing_mode != this->swing_mode);
991  }
992  this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
993  if (should_publish) {
994  this->publish_state();
995  }
996  if (should_publish) {
997  ESP_LOGI(TAG, "HVAC values changed");
998  }
999  int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1000  esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1001  esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1002  esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1003  esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1004  esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1005  return haier_protocol::HandlerError::HANDLER_OK;
1006 }
1007 
1009  if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1010  return;
1012  HvacSettings climate_control;
1013  climate_control = this->current_hvac_settings_;
1014  // Beeper command
1015  {
1016  this->control_messages_queue_.push(
1017  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1020  this->beeper_status_ ? ZERO_BUF : ONE_BUF, 2));
1021  }
1022  // Health mode
1023  {
1024  this->control_messages_queue_.push(
1025  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1028  this->health_mode_ ? ONE_BUF : ZERO_BUF, 2));
1029  }
1030  // Climate mode
1031  bool new_power = this->mode != CLIMATE_MODE_OFF;
1032  uint8_t fan_mode_buf[] = {0x00, 0xFF};
1033  uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1034  if (climate_control.mode.has_value()) {
1035  uint8_t buffer[2] = {0x00, 0x00};
1036  switch (climate_control.mode.value()) {
1037  case CLIMATE_MODE_OFF:
1038  new_power = false;
1039  break;
1041  new_power = true;
1042  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
1043  this->control_messages_queue_.push(
1044  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1047  buffer, 2));
1048  fan_mode_buf[1] = this->other_modes_fan_speed_;
1049  break;
1050  case CLIMATE_MODE_HEAT:
1051  new_power = true;
1052  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
1053  this->control_messages_queue_.push(
1054  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1056  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1057  buffer, 2));
1058  fan_mode_buf[1] = this->other_modes_fan_speed_;
1059  break;
1060  case CLIMATE_MODE_DRY:
1061  new_power = true;
1062  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
1063  this->control_messages_queue_.push(
1064  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1066  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1067  buffer, 2));
1068  fan_mode_buf[1] = this->other_modes_fan_speed_;
1069  break;
1070  case CLIMATE_MODE_FAN_ONLY:
1071  new_power = true;
1072  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
1073  this->control_messages_queue_.push(
1074  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1076  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1077  buffer, 2));
1078  fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1079  // Disabling eco mode for Fan only
1080  quiet_mode_buf[1] = 0;
1081  break;
1082  case CLIMATE_MODE_COOL:
1083  new_power = true;
1084  buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
1085  this->control_messages_queue_.push(
1086  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1088  (uint8_t) hon_protocol::DataParameters::AC_MODE,
1089  buffer, 2));
1090  fan_mode_buf[1] = this->other_modes_fan_speed_;
1091  break;
1092  default:
1093  ESP_LOGE("Control", "Unsupported climate mode");
1094  break;
1095  }
1096  }
1097  // Climate power
1098  {
1099  this->control_messages_queue_.push(
1100  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1103  new_power ? ONE_BUF : ZERO_BUF, 2));
1104  }
1105  // CLimate preset
1106  {
1107  uint8_t fast_mode_buf[] = {0x00, 0xFF};
1108  uint8_t away_mode_buf[] = {0x00, 0xFF};
1109  if (!new_power) {
1110  // If AC is off - no presets allowed
1111  quiet_mode_buf[1] = 0x00;
1112  fast_mode_buf[1] = 0x00;
1113  away_mode_buf[1] = 0x00;
1114  } else if (climate_control.preset.has_value()) {
1115  switch (climate_control.preset.value()) {
1116  case CLIMATE_PRESET_NONE:
1117  quiet_mode_buf[1] = 0x00;
1118  fast_mode_buf[1] = 0x00;
1119  away_mode_buf[1] = 0x00;
1120  break;
1121  case CLIMATE_PRESET_ECO:
1122  // Eco is not supported in Fan only mode
1123  quiet_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1124  fast_mode_buf[1] = 0x00;
1125  away_mode_buf[1] = 0x00;
1126  break;
1127  case CLIMATE_PRESET_BOOST:
1128  quiet_mode_buf[1] = 0x00;
1129  // Boost is not supported in Fan only mode
1130  fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1131  away_mode_buf[1] = 0x00;
1132  break;
1133  case CLIMATE_PRESET_AWAY:
1134  quiet_mode_buf[1] = 0x00;
1135  fast_mode_buf[1] = 0x00;
1136  away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1137  break;
1138  default:
1139  ESP_LOGE("Control", "Unsupported preset");
1140  break;
1141  }
1142  }
1143  auto presets = this->traits_.get_supported_presets();
1144  if ((quiet_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_ECO) != presets.end()))) {
1145  this->control_messages_queue_.push(
1146  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1149  quiet_mode_buf, 2));
1150  }
1151  if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
1152  this->control_messages_queue_.push(
1153  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1156  fast_mode_buf, 2));
1157  }
1158  if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
1159  this->control_messages_queue_.push(
1160  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1163  away_mode_buf, 2));
1164  }
1165  }
1166  // Target temperature
1167  if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1168  uint8_t buffer[2] = {0x00, 0x00};
1169  buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1170  this->control_messages_queue_.push(
1171  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1174  buffer, 2));
1175  }
1176  // Vertical swing mode
1177  if (climate_control.swing_mode.has_value()) {
1178  uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1179  uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1180  switch (climate_control.swing_mode.value()) {
1181  case CLIMATE_SWING_OFF:
1182  horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1183  vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1184  break;
1186  horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1187  break;
1189  vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1190  break;
1191  case CLIMATE_SWING_BOTH:
1192  break;
1193  }
1194  this->control_messages_queue_.push(
1195  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1198  horizontal_swing_buf, 2));
1199  this->control_messages_queue_.push(
1200  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1203  vertical_swing_buf, 2));
1204  }
1205  // Fan mode
1206  if (climate_control.fan_mode.has_value()) {
1207  switch (climate_control.fan_mode.value()) {
1208  case CLIMATE_FAN_LOW:
1209  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1210  break;
1211  case CLIMATE_FAN_MEDIUM:
1212  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1213  break;
1214  case CLIMATE_FAN_HIGH:
1215  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1216  break;
1217  case CLIMATE_FAN_AUTO:
1218  if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1219  fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1220  break;
1221  default:
1222  ESP_LOGE("Control", "Unsupported fan mode");
1223  break;
1224  }
1225  if (fan_mode_buf[1] != 0xFF) {
1226  this->control_messages_queue_.push(
1227  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1230  fan_mode_buf, 2));
1231  }
1232  }
1233 }
1234 
1236  while (!this->control_messages_queue_.empty())
1237  this->control_messages_queue_.pop();
1238 }
1239 
1241  switch (this->action_request_.value().action) {
1244  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1245  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1246  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1247  out_data->self_cleaning_status = 1;
1248  out_data->steri_clean = 0;
1249  out_data->set_point = 0x06;
1250  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
1251  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
1252  out_data->ac_power = 1;
1253  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1254  out_data->light_status = 0;
1255  this->action_request_.value().message = haier_protocol::HaierMessage(
1256  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1257  control_out_buffer, this->real_control_packet_size_);
1258  return true;
1260  this->action_request_.value().message =
1261  haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1264  ONE_BUF, 2);
1265  return true;
1266  } else {
1267  this->action_request_.reset();
1268  return false;
1269  }
1272  uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1273  memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1274  hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1275  out_data->self_cleaning_status = 0;
1276  out_data->steri_clean = 1;
1277  out_data->set_point = 0x06;
1278  out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER;
1279  out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER;
1280  out_data->ac_power = 1;
1281  out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1282  out_data->light_status = 0;
1283  this->action_request_.value().message = haier_protocol::HaierMessage(
1284  haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1285  control_out_buffer, this->real_control_packet_size_);
1286  return true;
1287  } else {
1288  // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1289  this->action_request_.reset();
1290  return false;
1291  }
1292  default:
1294  }
1295 }
1296 
1299 #ifdef USE_SENSOR
1300  for (auto &sub_sensor : this->sub_sensors_) {
1301  if ((sub_sensor != nullptr) && sub_sensor->has_state())
1302  sub_sensor->publish_state(NAN);
1303  }
1304 #endif // USE_SENSOR
1305  this->got_valid_outdoor_temp_ = false;
1306  this->hvac_hardware_info_.reset();
1307  this->last_status_message_.reset(nullptr);
1308 }
1309 
1311  if (this->big_data_sensors_ > 0) {
1312  static uint8_t counter = 0;
1313  counter = (counter + 1) % 3;
1314  return counter == 1;
1315  }
1316  return false;
1317 }
1318 
1319 } // namespace haier
1320 } // namespace esphome
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:175
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:159
esphome::optional< float > target_temperature
Definition: haier_base.h:118
hon_protocol::HorizontalSwingMode last_horizontal_swing
Definition: hon_climate.h:30
CallbackManager< void(uint8_t, const char *)> alarm_start_callback_
Definition: hon_climate.h:170
esphome::optional< esphome::climate::ClimatePreset > preset
Definition: haier_base.h:119
CallbackManager< void(uint8_t, const char *)> alarm_end_callback_
Definition: hon_climate.h:171
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size)
std::unique_ptr< uint8_t[]> last_status_message_
Definition: haier_base.h:145
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)
Definition: hon_climate.cpp:89
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:212
virtual void set_phase(ProtocolPhases phase)
Definition: haier_base.cpp:75
ESPPreferenceObject rtc_
Definition: hon_climate.h:178
void control(const esphome::climate::ClimateCall &call) override
Definition: haier_base.cpp:332
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:148
std::chrono::steady_clock::time_point last_signal_request_
Definition: haier_base.h:149
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:42
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:157
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:193
bool has_value() const
Definition: optional.h:87
HonControlMethod control_method_
Definition: hon_climate.h:168
esphome::optional< hon_protocol::HorizontalSwingMode > get_horizontal_airflow() const
Definition: hon_climate.cpp:47
esphome::optional< HardwareInfo > hvac_hardware_info_
Definition: hon_climate.h:161
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
esphome::optional< hon_protocol::HorizontalSwingMode > current_horizontal_swing_
Definition: hon_climate.h:176
haier_protocol::ProtocolHandler haier_protocol_
Definition: haier_base.h:130
FanDirection direction
Definition: fan.h:37
bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:108
void add_alarm_start_callback(std::function< void(uint8_t, const char *)> &&callback)
Definition: hon_climate.cpp:85
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:365
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS
Definition: hon_climate.cpp:16
haier_protocol::HaierMessage get_wifi_signal_message_()
Definition: haier_base.cpp:113
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
esphome::optional< hon_protocol::VerticalSwingMode > get_vertical_airflow() const
Definition: hon_climate.cpp:38
esphome::optional< hon_protocol::HorizontalSwingMode > pending_horizontal_direction_
Definition: hon_climate.h:160
CleaningState get_cleaning_status() const
Definition: hon_climate.cpp:67
void set_sub_sensor(SubSensorType type, sensor::Sensor *sens)
esphome::optional< PendingAction > action_request_
Definition: haier_base.h:132
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:57
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:116
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.
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:88
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)
Definition: hon_climate.cpp:93
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:56
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:96
std::chrono::steady_clock::time_point last_alarm_request_
Definition: hon_climate.h:173
bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:100
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:51
bool prepare_pending_action() override
constexpr uint8_t CONTROL_MESSAGE_RETRIES
Definition: hon_climate.cpp:18
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:104
CallbackManager< void(const char *, size_t)> status_message_callback_
Definition: haier_base.h:150
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:169
binary_sensor::BinarySensor * sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]
Definition: hon_climate.h:74
hon_protocol::VerticalSwingMode last_vertiacal_swing
Definition: hon_climate.h:29
uint32_t get_object_id_hash()
Definition: entity_base.cpp:76
esphome::climate::ClimateTraits traits_
Definition: haier_base.h:142
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
Definition: haier_base.h:117
bool state
Definition: fan.h:34
std::chrono::steady_clock::time_point last_valid_status_timestamp_
Definition: haier_base.h:147
esphome::optional< esphome::climate::ClimateMode > mode
Definition: haier_base.h:115
void dump_config() override