ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
hon_climate.cpp
Go to the documentation of this file.
1#include <chrono>
2#include <string>
6#include "hon_climate.h"
7#include "hon_packet.h"
8
9using namespace esphome::climate;
10using namespace esphome::uart;
11
12namespace esphome {
13namespace haier {
14
15static const char *const TAG = "haier.climate";
16constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
18constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
19constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
20constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
21const uint8_t ONE_BUF[] = {0x00, 0x01};
22const uint8_t ZERO_BUF[] = {0x00, 0x00};
23
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 if (this->beeper_switch_ != nullptr) {
39 this->beeper_switch_->publish_state(state);
40 }
41#endif
42 this->hon_rtc_.save(&this->settings_);
43 }
44}
45
47
49 if (state != this->get_quiet_mode_state()) {
50 if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
52 this->force_send_control_ = true;
53 } else {
55 }
57#ifdef USE_SWITCH
58 if (this->quiet_mode_switch_ != nullptr) {
60 }
61#endif
62 this->hon_rtc_.save(&this->settings_);
63 }
64}
65
69
70esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
71 return this->current_vertical_swing_;
72};
73
78
79esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
80 return this->current_horizontal_swing_;
81}
82
87
89 switch (this->cleaning_status_) {
91 return "Self clean";
93 return "56°C Steri-Clean";
94 default:
95 return "No cleaning";
96 }
97}
98
100
103 ESP_LOGI(TAG, "Sending self cleaning start request");
104 this->action_request_ =
105 PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
106 }
107}
108
111 ESP_LOGI(TAG, "Sending steri cleaning start request");
112 this->action_request_ =
113 PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
114 }
115}
116
117haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
118 haier_protocol::FrameType message_type,
119 const uint8_t *data, size_t data_size) {
120 // Should check this before preprocess
121 if (message_type == haier_protocol::FrameType::INVALID) {
122 ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
123 "protocol instead of hOn");
125 return haier_protocol::HandlerError::INVALID_ANSWER;
126 }
127 haier_protocol::HandlerError result =
128 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
129 haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
130 if (result == haier_protocol::HandlerError::HANDLER_OK) {
131 if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
132 // Wrong structure
133 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
134 }
135 // All OK
137 char tmp[9];
138 tmp[8] = 0;
139 strncpy(tmp, answr->protocol_version, 8);
141 this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp);
142 strncpy(tmp, answr->software_version, 8);
143 this->hvac_hardware_info_.value().software_version_ = std::string(tmp);
144 strncpy(tmp, answr->hardware_version, 8);
145 this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
146 strncpy(tmp, answr->device_name, 8);
147 this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
148#ifdef USE_TEXT_SENSOR
151 this->hvac_hardware_info_.value().protocol_version_);
152#endif
153 this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
154 this->hvac_hardware_info_.value().functions_[1] =
155 (answr->functions[1] & 0x02) != 0; // controller-device mode support
156 this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
157 this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
158 this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
159 this->use_crc_ = this->hvac_hardware_info_.value().functions_[2];
161 return result;
162 } else {
163 this->reset_phase_();
164 return result;
165 }
166}
167
168haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
169 haier_protocol::FrameType message_type,
170 const uint8_t *data, size_t data_size) {
171 haier_protocol::HandlerError result =
172 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
173 haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
174 if (result == haier_protocol::HandlerError::HANDLER_OK) {
176 return result;
177 } else {
178 this->reset_phase_();
179 return result;
180 }
181}
182
183haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
184 haier_protocol::FrameType message_type, const uint8_t *data,
185 size_t data_size) {
186 haier_protocol::HandlerError result =
187 this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
188 haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
189 if (result == haier_protocol::HandlerError::HANDLER_OK) {
190 result = this->process_status_message_(data, data_size);
191 if (result != haier_protocol::HandlerError::HANDLER_OK) {
192 ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
193 this->reset_phase_();
194 this->action_request_.reset();
195 this->force_send_control_ = false;
196 } else {
197 if (!this->last_status_message_) {
200 this->last_status_message_.reset();
201 this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
202 };
203 if (data_size >= this->real_control_packet_size_ + 2) {
204 memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
205 this->real_control_packet_size_);
206 this->status_message_callback_.call((const char *) data, data_size);
207 } else {
208 ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_);
209 }
210 switch (this->protocol_phase_) {
212 ESP_LOGI(TAG, "First HVAC status received");
214 break;
216 // Do nothing, phase will be changed in process_phase
217 break;
220 break;
222 if (!this->control_messages_queue_.empty())
223 this->control_messages_queue_.pop();
224 if (this->control_messages_queue_.empty()) {
226 this->force_send_control_ = false;
229 } else {
231 }
232 break;
233 default:
234 break;
235 }
236 }
237 return result;
238 } else {
239 this->action_request_.reset();
240 this->force_send_control_ = false;
241 this->reset_phase_();
242 return result;
243 }
244}
245
247 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
248 size_t data_size) {
249 haier_protocol::HandlerError result = this->answer_preprocess_(
250 request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
251 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
252 if (result == haier_protocol::HandlerError::HANDLER_OK) {
254 return result;
255 } else {
257 return result;
258 }
259}
260
261haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
262 haier_protocol::FrameType message_type,
263 const uint8_t *data, size_t data_size) {
264 if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
265 if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
266 // Unexpected answer to request
268 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
269 }
272 // Don't expect this answer now
274 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
275 }
276 if (data_size < sizeof(active_alarms_) + 2)
277 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
278 this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
280 return haier_protocol::HandlerError::HANDLER_OK;
281 } else {
283 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
284 }
285}
286
287haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
288 const uint8_t *buffer, size_t size) {
289 haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
290 if (size < sizeof(this->active_alarms_) + 2) {
291 // Log error but confirm anyway to avoid to many messages
292 result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
293 }
294 this->process_alarm_message_(buffer, size, true);
295 this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
296 this->last_alarm_request_ = std::chrono::steady_clock::now();
297 return result;
298}
299
301 // Set handlers
302 this->haier_protocol_.set_answer_handler(
303 haier_protocol::FrameType::GET_DEVICE_VERSION,
304 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
305 return this->get_device_version_answer_handler_(req, msg, data, size);
306 });
307 this->haier_protocol_.set_answer_handler(
308 haier_protocol::FrameType::GET_DEVICE_ID,
309 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
310 return this->get_device_id_answer_handler_(req, msg, data, size);
311 });
312 this->haier_protocol_.set_answer_handler(
313 haier_protocol::FrameType::CONTROL,
314 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
315 return this->status_handler_(req, msg, data, size);
316 });
317 this->haier_protocol_.set_answer_handler(
318 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
319 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
320 return this->get_management_information_answer_handler_(req, msg, data, size);
321 });
322 this->haier_protocol_.set_answer_handler(
323 haier_protocol::FrameType::GET_ALARM_STATUS,
324 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
325 return this->get_alarm_status_answer_handler_(req, msg, data, size);
326 });
327 this->haier_protocol_.set_answer_handler(
328 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
329 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
330 return this->report_network_status_answer_handler_(req, msg, data, size);
331 });
332 this->haier_protocol_.set_message_handler(haier_protocol::FrameType::ALARM_STATUS,
333 [this](haier_protocol::FrameType type, const uint8_t *data, size_t size) {
334 return this->alarm_status_message_handler_(type, data, size);
335 });
336}
337
340 ESP_LOGCONFIG(TAG,
341 " Protocol version: hOn\n"
342 " Control method: %d",
343 (uint8_t) this->control_method_);
344 if (this->hvac_hardware_info_.has_value()) {
345 ESP_LOGCONFIG(TAG,
346 " Device protocol version: %s\n"
347 " Device software version: %s\n"
348 " Device hardware version: %s\n"
349 " Device name: %s",
350 this->hvac_hardware_info_.value().protocol_version_.c_str(),
351 this->hvac_hardware_info_.value().software_version_.c_str(),
352 this->hvac_hardware_info_.value().hardware_version_.c_str(),
353 this->hvac_hardware_info_.value().device_name_.c_str());
354 ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
355 (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
356 (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
357 (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
358 (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
359 (this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
360 ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
361 }
362}
363
364void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
365 switch (this->protocol_phase_) {
368 // Indicate device capabilities:
369 // bit 0 - if 1 module support interactive mode
370 // bit 1 - if 1 module support controller-device mode
371 // bit 2 - if 1 module support crc
372 // bit 3 - if 1 module support multiple devices
373 // bit 4..bit 15 - not used
374 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
375 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
376 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
377 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
378 }
379 break;
381 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
382 static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
383 this->send_message_(DEVICEID_REQUEST, this->use_crc_);
384 }
385 break;
388 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
389 static const haier_protocol::HaierMessage STATUS_REQUEST(
390 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
391 static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
392 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
394 (!this->should_get_big_data_())) {
395 this->send_message_(STATUS_REQUEST, this->use_crc_);
396 } else {
397 this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
398 }
399 this->last_status_request_ = now;
400 }
401 break;
402#ifdef USE_WIFI
404 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
405 static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
406 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
407 this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
408 this->last_signal_request_ = now;
409 }
410 break;
412 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
413 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
414 }
415 break;
416#else
420 break;
421#endif
424 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
425 static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
426 this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
427 this->last_alarm_request_ = now;
428 }
429 break;
431 if (this->control_messages_queue_.empty()) {
432 switch (this->control_method_) {
434 haier_protocol::HaierMessage control_message = this->get_control_message();
435 this->control_messages_queue_.push(control_message);
436 } break;
439 break;
441 ESP_LOGI(TAG, "AC control is disabled, monitor only");
442 this->reset_to_idle_();
443 return;
444 default:
445 ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
446 this->reset_to_idle_();
447 return;
448 }
449 }
450 if (this->control_messages_queue_.empty()) {
451 ESP_LOGW(TAG, "Control message queue is empty!");
452 this->reset_to_idle_();
453 } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
454 ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
455 this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
457 }
458 break;
460 if (this->action_request_.has_value()) {
461 if (this->action_request_.value().message.has_value()) {
462 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
463 this->action_request_.value().message.reset();
464 } else {
465 // Message already sent, reseting request and return to idle
466 this->action_request_.reset();
468 }
469 } else {
470 ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
472 }
473 break;
477 this->forced_request_status_ = false;
478 } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
481 }
482#ifdef USE_WIFI
483 else if (this->send_wifi_signal_ &&
484 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
487 }
488#endif
489 } break;
490 default:
491 // Shouldn't get here
492 ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
495 break;
496 }
497}
498
499haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
500 if (state) {
501 static haier_protocol::HaierMessage power_on_message(
502 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
503 std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
504 return power_on_message;
505 } else {
506 static haier_protocol::HaierMessage power_off_message(
507 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
508 std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
509 return power_off_message;
510 }
511}
512
515 constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
516 this->hon_rtc_ = this->make_entity_preference<HonSettings>(restore_settings_version);
517 HonSettings recovered;
518 if (this->hon_rtc_.load(&recovered)) {
519 this->settings_ = recovered;
520 } else {
522 }
526}
527
528haier_protocol::HaierMessage HonClimate::get_control_message() {
529 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
530 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
532 control_out_buffer[4] = 0; // This byte should be cleared before setting values
533 bool has_hvac_settings = false;
534 if (this->current_hvac_settings_.valid) {
535 has_hvac_settings = true;
536 HvacSettings &climate_control = this->current_hvac_settings_;
537 if (climate_control.mode.has_value()) {
538 switch (climate_control.mode.value()) {
539 case CLIMATE_MODE_OFF:
540 out_data->ac_power = 0;
541 break;
543 out_data->ac_power = 1;
544 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
545 out_data->fan_mode = this->other_modes_fan_speed_;
546 break;
548 out_data->ac_power = 1;
549 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
550 out_data->fan_mode = this->other_modes_fan_speed_;
551 break;
552 case CLIMATE_MODE_DRY:
553 out_data->ac_power = 1;
554 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
555 out_data->fan_mode = this->other_modes_fan_speed_;
556 break;
558 out_data->ac_power = 1;
559 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
560 out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
561 // Disabling boost for Fan only
562 out_data->fast_mode = 0;
563 break;
565 out_data->ac_power = 1;
566 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
567 out_data->fan_mode = this->other_modes_fan_speed_;
568 break;
569 default:
570 ESP_LOGE("Control", "Unsupported climate mode");
571 break;
572 }
573 }
574 // Set fan speed, if we are in fan mode, reject auto in fan mode
575 if (climate_control.fan_mode.has_value()) {
576 switch (climate_control.fan_mode.value()) {
577 case CLIMATE_FAN_LOW:
578 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
579 break;
581 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
582 break;
583 case CLIMATE_FAN_HIGH:
584 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
585 break;
586 case CLIMATE_FAN_AUTO:
587 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
588 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
589 break;
590 default:
591 ESP_LOGE("Control", "Unsupported fan mode");
592 break;
593 }
594 }
595 // Set swing mode
596 if (climate_control.swing_mode.has_value()) {
597 switch (climate_control.swing_mode.value()) {
599 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
600 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
601 break;
603 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
605 break;
608 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
609 break;
613 break;
614 }
615 }
616 if (climate_control.target_temperature.has_value()) {
617 float target_temp = climate_control.target_temperature.value();
618 out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
619 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
620 }
621 if (out_data->ac_power == 0) {
622 // If AC is off - no presets allowed
623 out_data->fast_mode = 0;
624 out_data->sleep_mode = 0;
625 } else if (climate_control.preset.has_value()) {
626 switch (climate_control.preset.value()) {
628 out_data->fast_mode = 0;
629 out_data->sleep_mode = 0;
630 out_data->ten_degree = 0;
631 break;
633 // Boost is not supported in Fan only mode
634 out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
635 out_data->sleep_mode = 0;
636 out_data->ten_degree = 0;
637 break;
639 out_data->fast_mode = 0;
640 out_data->sleep_mode = 0;
641 // 10 degrees allowed only in heat mode
642 out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
643 break;
645 out_data->fast_mode = 0;
646 out_data->sleep_mode = 1;
647 out_data->ten_degree = 0;
648 break;
649 default:
650 ESP_LOGE("Control", "Unsupported preset");
651 out_data->fast_mode = 0;
652 out_data->sleep_mode = 0;
653 out_data->ten_degree = 0;
654 break;
655 }
656 }
657 }
658 if (this->pending_vertical_direction_.has_value()) {
659 out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
660 this->pending_vertical_direction_.reset();
661 }
662 if (this->pending_horizontal_direction_.has_value()) {
663 out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
664 this->pending_horizontal_direction_.reset();
665 }
666 {
667 // Quiet mode
668 if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
669 // If AC is off or in fan only mode - no quiet mode allowed
670 out_data->quiet_mode = 0;
671 } else {
672 out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
673 }
674 // Clean quiet mode state pending flag
675 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
676 }
677 out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
678 out_data->display_status = this->get_display_state() ? 1 : 0;
679 this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
680 out_data->health_mode = this->get_health_mode() ? 1 : 0;
681 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
682 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
684 control_out_buffer, this->real_control_packet_size_);
685}
686
687void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
688 constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
689 if (size >= active_alarms_size + 2) {
690 if (check_new) {
691 size_t alarm_code = 0;
692 for (int i = active_alarms_size - 1; i >= 0; i--) {
693 if (packet[2 + i] != active_alarms_[i]) {
694 uint8_t alarm_bit = 1;
695 for (int b = 0; b < 8; b++) {
696 if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
697 bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
698 int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
699 const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
701 : "Unknown";
702 esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
703 alarm_code, alarm_message);
704 if (alarm_status) {
705 this->alarm_start_callback_.call(alarm_code, alarm_message);
706 this->active_alarm_count_ += 1.0f;
707 } else {
708 this->alarm_end_callback_.call(alarm_code, alarm_message);
709 this->active_alarm_count_ -= 1.0f;
710 }
711 }
712 alarm_bit <<= 1;
713 alarm_code++;
714 }
715 active_alarms_[i] = packet[2 + i];
716 } else {
717 alarm_code += 8;
718 }
719 }
720 } else {
721 float alarm_count = 0.0f;
722 static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
723 for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
724 alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
725 }
726 this->active_alarm_count_ = alarm_count;
727 memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
728 }
729 }
730}
731
732#ifdef USE_SENSOR
736 if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
737 this->big_data_sensors_--;
738 } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
739 this->big_data_sensors_++;
740 }
741 }
742 this->sub_sensors_[(size_t) type] = sens;
743 }
744}
745
748 size_t index = (size_t) type;
749 if ((this->sub_sensors_[index] != nullptr) &&
750 ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->get_raw_state() != value)))
751 this->sub_sensors_[index]->publish_state(value);
752 }
753}
754#endif // USE_SENSOR
755
756#ifdef USE_BINARY_SENSOR
759 if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
760 this->big_data_sensors_--;
761 } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
762 this->big_data_sensors_++;
763 }
764 this->sub_binary_sensors_[(size_t) type] = sens;
765 }
766}
767
769 if (value < 2) {
770 bool converted_value = value == 1;
771 size_t index = (size_t) type;
772 if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
773 (this->sub_binary_sensors_[index]->state != converted_value)))
774 this->sub_binary_sensors_[index]->publish_state(converted_value);
775 }
776}
777#endif // USE_BINARY_SENSOR
778
779#ifdef USE_TEXT_SENSOR
781 this->sub_text_sensors_[(size_t) type] = sens;
782 switch (type) {
784 if (this->hvac_hardware_info_.has_value())
785 sens->publish_state(this->hvac_hardware_info_.value().device_name_);
786 break;
788 if (this->hvac_hardware_info_.has_value())
789 sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
790 break;
793 break;
794 default:
795 break;
796 }
797}
798
800 size_t index = (size_t) type;
801 if (this->sub_text_sensors_[index] != nullptr)
802 this->sub_text_sensors_[index]->publish_state(value);
803}
804#endif // USE_TEXT_SENSOR
805
806#ifdef USE_SWITCH
808 this->beeper_switch_ = sw;
809 if (this->beeper_switch_ != nullptr) {
811 }
812}
813
820#endif // USE_SWITCH
821
822haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
823 size_t expected_size =
825 if (size < expected_size) {
826 ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size);
827 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
828 }
829 uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
830 if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
831 // Got BigData packet
832 const hon_protocol::HaierPacketBigData *bd_packet =
833 (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
834#ifdef USE_SENSOR
840 this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
843 encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
844 this->update_sub_sensor_(
846 encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
847#endif // USE_SENSOR
848#ifdef USE_BINARY_SENSOR
856#endif // USE_BINARY_SENSOR
857 }
858 struct {
861 } packet;
862 memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
864 memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
866 if (packet.sensors.error_status != 0) {
867 ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
868 }
869#ifdef USE_SENSOR
870 if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
871 (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
872 this->got_valid_outdoor_temp_ = true;
874 (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
875 }
876 if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
877 this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
878 }
879#endif // USE_SENSOR
880 bool should_publish = false;
881 {
882 // Extra modes/presets
883 optional<ClimatePreset> old_preset = this->preset;
884 if (packet.control.fast_mode != 0) {
886 } else if (packet.control.sleep_mode != 0) {
888 } else if (packet.control.ten_degree != 0) {
890 } else {
892 }
893 should_publish = should_publish || (!old_preset.has_value()) ||
894 (old_preset.value_or(CLIMATE_PRESET_NONE) != this->preset.value_or(CLIMATE_PRESET_NONE));
895 }
896 {
897 // Target temperature
898 float old_target_temperature = this->target_temperature;
899 this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
900 should_publish = should_publish || (old_target_temperature != this->target_temperature);
901 }
902 {
903 // Current temperature
904 float old_current_temperature = this->current_temperature;
905 this->current_temperature = packet.sensors.room_temperature / 2.0f;
906 should_publish = should_publish || (old_current_temperature != this->current_temperature);
907 }
908 {
909 // Fan mode
910 optional<ClimateFanMode> old_fan_mode = this->fan_mode;
911 // remember the fan speed we last had for climate vs fan
912 if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
913 if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
914 this->fan_mode_speed_ = packet.control.fan_mode;
915 } else {
916 this->other_modes_fan_speed_ = packet.control.fan_mode;
917 }
918 switch (packet.control.fan_mode) {
919 case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
920 if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
922 } else {
923 // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
924 ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
925 }
926 break;
927 case (uint8_t) hon_protocol::FanMode::FAN_MID:
929 break;
930 case (uint8_t) hon_protocol::FanMode::FAN_LOW:
932 break;
933 case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
935 break;
936 }
937 should_publish = should_publish || (!old_fan_mode.has_value()) ||
938 (old_fan_mode.value_or(CLIMATE_FAN_ON) != this->fan_mode.value_or(CLIMATE_FAN_ON));
939 }
940 // Display status
941 // should be before "Climate mode" because it is changing this->mode
942 if (packet.control.ac_power != 0) {
943 // if AC is off display status always ON so process it only when AC is on
944 bool disp_status = packet.control.display_status != 0;
945 if (disp_status != this->get_display_state()) {
946 // Do something only if display status changed
947 if (this->mode == CLIMATE_MODE_OFF) {
948 // AC just turned on from remote need to turn off display
949 this->force_send_control_ = true;
950 } else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
951 this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
952 }
953 }
954 }
955 // Health mode
956 if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
957 bool old_health_mode = this->get_health_mode();
958 this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
959 should_publish = should_publish || (old_health_mode != this->get_health_mode());
960 }
961 {
962 CleaningState new_cleaning;
963 if (packet.control.steri_clean == 1) {
964 // Steri-cleaning
965 new_cleaning = CleaningState::STERI_CLEAN;
966 } else if (packet.control.self_cleaning_status == 1) {
967 // Self-cleaning
968 new_cleaning = CleaningState::SELF_CLEAN;
969 } else {
970 // No cleaning
971 new_cleaning = CleaningState::NO_CLEANING;
972 }
973 if (new_cleaning != this->cleaning_status_) {
974 ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
975 if (new_cleaning == CleaningState::NO_CLEANING) {
976 // Turning AC off after cleaning
977 this->action_request_ =
978 PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
979 }
980 this->cleaning_status_ = new_cleaning;
981#ifdef USE_TEXT_SENSOR
983#endif // USE_TEXT_SENSOR
984 }
985 }
986 {
987 // Climate mode
988 ClimateMode old_mode = this->mode;
989 if (packet.control.ac_power == 0) {
990 this->mode = CLIMATE_MODE_OFF;
991 } else {
992 // Check current hvac mode
993 switch (packet.control.ac_mode) {
995 this->mode = CLIMATE_MODE_COOL;
996 break;
998 this->mode = CLIMATE_MODE_HEAT;
999 break;
1001 this->mode = CLIMATE_MODE_DRY;
1002 break;
1005 break;
1008 break;
1009 }
1010 }
1011 should_publish = should_publish || (old_mode != this->mode);
1012 }
1013 {
1014 // Quiet mode, should be after climate mode
1015 if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
1016 ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
1017 // In proper mode and not in pending state
1018 bool new_quiet_mode = packet.control.quiet_mode != 0;
1019 if (new_quiet_mode != this->get_quiet_mode_state()) {
1020 this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
1021 this->settings_.quiet_mode_state = new_quiet_mode;
1022#ifdef USE_SWITCH
1023 if (this->quiet_mode_switch_ != nullptr) {
1024 this->quiet_mode_switch_->publish_state(new_quiet_mode);
1025 }
1026#endif // USE_SWITCH
1027 this->hon_rtc_.save(&this->settings_);
1028 }
1029 }
1030 }
1031 {
1032 // Swing mode
1033 ClimateSwingMode old_swing_mode = this->swing_mode;
1034 const auto &swing_modes = traits_.get_supported_swing_modes();
1035 bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL);
1036 bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL);
1037 if (horizontal_swing_supported &&
1038 (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
1039 if (vertical_swing_supported &&
1040 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1042 } else {
1044 }
1045 } else {
1046 if (vertical_swing_supported &&
1047 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1049 } else {
1051 }
1052 }
1053 // Saving last known non auto mode for vertical and horizontal swing
1054 this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
1055 this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
1058 (this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
1059 ((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
1060 (this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
1061 if (save_settings) {
1064 this->hon_rtc_.save(&this->settings_);
1065 }
1066 should_publish = should_publish || (old_swing_mode != this->swing_mode);
1067 }
1068 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
1069 if (should_publish) {
1070 this->publish_state();
1071 }
1072 if (should_publish) {
1073 ESP_LOGI(TAG, "HVAC values changed");
1074 }
1075 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1076 esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1077 esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1078 esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1079 esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1080 esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1081 return haier_protocol::HandlerError::HANDLER_OK;
1082}
1083
1085 if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1086 return;
1088 HvacSettings climate_control;
1089 climate_control = this->current_hvac_settings_;
1090 // Beeper command
1091 {
1092 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1095 this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
1096 }
1097 // Health mode
1098 {
1099 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1102 this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
1103 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
1104 }
1105 // Climate mode
1106 ClimateMode climate_mode = this->mode;
1107 bool new_power = this->mode != CLIMATE_MODE_OFF;
1108 uint8_t fan_mode_buf[] = {0x00, 0xFF};
1109 uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1110 if (climate_control.mode.has_value()) {
1111 climate_mode = climate_control.mode.value();
1112 uint8_t buffer[2] = {0x00, 0x00};
1113 switch (climate_control.mode.value()) {
1114 case CLIMATE_MODE_OFF:
1115 new_power = false;
1116 break;
1118 new_power = true;
1119 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
1120 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1123 buffer, 2);
1124 fan_mode_buf[1] = this->other_modes_fan_speed_;
1125 break;
1126 case CLIMATE_MODE_HEAT:
1127 new_power = true;
1128 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
1129 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1132 buffer, 2);
1133 fan_mode_buf[1] = this->other_modes_fan_speed_;
1134 break;
1135 case CLIMATE_MODE_DRY:
1136 new_power = true;
1137 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
1138 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1141 buffer, 2);
1142 fan_mode_buf[1] = this->other_modes_fan_speed_;
1143 break;
1145 new_power = true;
1146 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
1147 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1150 buffer, 2);
1151 fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1152 break;
1153 case CLIMATE_MODE_COOL:
1154 new_power = true;
1155 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
1156 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1159 buffer, 2);
1160 fan_mode_buf[1] = this->other_modes_fan_speed_;
1161 break;
1162 default:
1163 ESP_LOGE("Control", "Unsupported climate mode");
1164 break;
1165 }
1166 }
1167 // Climate power
1168 {
1169 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1172 new_power ? ONE_BUF : ZERO_BUF, 2);
1173 }
1174 // CLimate preset
1175 {
1176 uint8_t fast_mode_buf[] = {0x00, 0xFF};
1177 uint8_t away_mode_buf[] = {0x00, 0xFF};
1178 if (!new_power) {
1179 // If AC is off - no presets allowed
1180 fast_mode_buf[1] = 0x00;
1181 away_mode_buf[1] = 0x00;
1182 } else if (climate_control.preset.has_value()) {
1183 switch (climate_control.preset.value()) {
1185 fast_mode_buf[1] = 0x00;
1186 away_mode_buf[1] = 0x00;
1187 break;
1189 // Boost is not supported in Fan only mode
1190 fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1191 away_mode_buf[1] = 0x00;
1192 break;
1194 fast_mode_buf[1] = 0x00;
1195 away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1196 break;
1197 default:
1198 ESP_LOGE("Control", "Unsupported preset");
1199 break;
1200 }
1201 }
1202 {
1203 // Quiet mode
1204 if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
1205 quiet_mode_buf[1] = 0x01;
1206 } else {
1207 quiet_mode_buf[1] = 0x00;
1208 }
1209 // Clean quiet mode state pending flag
1210 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
1211 }
1212 auto presets = this->traits_.get_supported_presets();
1213 if (quiet_mode_buf[1] != 0xFF) {
1214 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1217 quiet_mode_buf, 2);
1218 }
1219 if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) {
1220 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1223 fast_mode_buf, 2);
1224 }
1225 if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) {
1226 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1229 away_mode_buf, 2);
1230 }
1231 }
1232 // Target temperature
1233 if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1234 uint8_t buffer[2] = {0x00, 0x00};
1235 buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1236 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1239 buffer, 2);
1240 }
1241 // Vertical swing mode
1242 if (climate_control.swing_mode.has_value()) {
1243 uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1244 uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1245 switch (climate_control.swing_mode.value()) {
1246 case CLIMATE_SWING_OFF:
1247 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1248 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1249 break;
1251 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1252 break;
1254 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1255 break;
1256 case CLIMATE_SWING_BOTH:
1257 break;
1258 }
1259 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1262 horizontal_swing_buf, 2);
1263 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1266 vertical_swing_buf, 2);
1267 }
1268 // Fan mode
1269 if (climate_control.fan_mode.has_value()) {
1270 switch (climate_control.fan_mode.value()) {
1271 case CLIMATE_FAN_LOW:
1272 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1273 break;
1274 case CLIMATE_FAN_MEDIUM:
1275 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1276 break;
1277 case CLIMATE_FAN_HIGH:
1278 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1279 break;
1280 case CLIMATE_FAN_AUTO:
1281 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1282 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1283 break;
1284 default:
1285 ESP_LOGE("Control", "Unsupported fan mode");
1286 break;
1287 }
1288 if (fan_mode_buf[1] != 0xFF) {
1289 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1292 fan_mode_buf, 2);
1293 }
1294 }
1295}
1296
1298 while (!this->control_messages_queue_.empty())
1299 this->control_messages_queue_.pop();
1300}
1301
1303 auto &action_request = this->action_request_.value(); // NOLINT(bugprone-unchecked-optional-access)
1304 switch (action_request.action) {
1307 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1308 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1309 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1310 out_data->self_cleaning_status = 1;
1311 out_data->steri_clean = 0;
1312 out_data->set_point = 0x06;
1315 out_data->ac_power = 1;
1316 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1317 out_data->light_status = 0;
1318 action_request.message = haier_protocol::HaierMessage(
1319 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1320 control_out_buffer, this->real_control_packet_size_);
1321 return true;
1323 action_request.message =
1324 haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1327 ONE_BUF, 2);
1328 return true;
1329 } else {
1330 this->action_request_.reset();
1331 return false;
1332 }
1335 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1336 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1337 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1338 out_data->self_cleaning_status = 0;
1339 out_data->steri_clean = 1;
1340 out_data->set_point = 0x06;
1343 out_data->ac_power = 1;
1344 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1345 out_data->light_status = 0;
1346 action_request.message = haier_protocol::HaierMessage(
1347 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1348 control_out_buffer, this->real_control_packet_size_);
1349 return true;
1350 } else {
1351 // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1352 this->action_request_.reset();
1353 return false;
1354 }
1355 default:
1357 }
1358}
1359
1362#ifdef USE_SENSOR
1363 for (auto &sub_sensor : this->sub_sensors_) {
1364 if ((sub_sensor != nullptr) && sub_sensor->has_state())
1365 sub_sensor->publish_state(NAN);
1366 }
1367#endif // USE_SENSOR
1368 this->got_valid_outdoor_temp_ = false;
1369 this->hvac_hardware_info_.reset();
1370 this->last_status_message_.reset(nullptr);
1371}
1372
1374 if (this->big_data_sensors_ > 0) {
1375 this->big_data_counter_ = (this->big_data_counter_ + 1) % 3;
1376 return this->big_data_counter_ == 1;
1377 }
1378 return false;
1379}
1380
1381} // namespace haier
1382} // namespace esphome
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
bool has_state() const
constexpr size_t count(ValueType value) const
Check if the set contains a specific value (std::set compatibility) Returns 1 if present,...
Base class for all binary_sensor-type classes.
void publish_state(bool new_state)
Publish a new state to the front-end.
ClimateMode mode
The active mode of the climate device.
Definition climate.h:293
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:287
float target_temperature
The target temperature of the climate device.
Definition climate.h:274
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:299
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:267
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:436
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:290
const ClimatePresetMask & get_supported_presets() const
const ClimateSwingModeMask & get_supported_swing_modes() const
esphome::climate::ClimateTraits traits_
Definition haier_base.h:168
bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
CallbackManager< void(const char *, size_t)> status_message_callback_
Definition haier_base.h:176
bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now)
bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now)
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)
haier_protocol::ProtocolHandler haier_protocol_
Definition haier_base.h:156
const char * phase_to_string_(ProtocolPhases phase)
std::unique_ptr< uint8_t[]> last_status_message_
Definition haier_base.h:171
void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats=0, std::chrono::milliseconds interval=std::chrono::milliseconds::zero())
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)
void control(const esphome::climate::ClimateCall &call) override
std::chrono::steady_clock::time_point last_valid_status_timestamp_
Definition haier_base.h:173
haier_protocol::HaierMessage get_wifi_signal_message_()
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
esphome::optional< PendingAction > action_request_
Definition haier_base.h:158
std::chrono::steady_clock::time_point last_status_request_
Definition haier_base.h:174
virtual void set_phase(ProtocolPhases phase)
std::chrono::steady_clock::time_point last_signal_request_
Definition haier_base.h:175
void set_quiet_mode_switch(switch_::Switch *sw)
esphome::optional< hon_protocol::VerticalSwingMode > get_vertical_airflow() const
ESPPreferenceObject hon_rtc_
void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens)
haier_protocol::HandlerError status_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
HonControlMethod control_method_
void initialization() override
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)
CallbackManager< void(uint8_t, const char *)> alarm_end_callback_
esphome::optional< HardwareInfo > hvac_hardware_info_
void set_sub_sensor(SubSensorType type, sensor::Sensor *sens)
CallbackManager< void(uint8_t, const char *)> alarm_start_callback_
void update_sub_sensor_(SubSensorType type, float value)
void set_sub_binary_sensor(SubBinarySensorType type, binary_sensor::BinarySensor *sens)
haier_protocol::HaierMessage get_power_message(bool state) override
text_sensor::TextSensor * sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]
Definition hon_climate.h:94
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)
switch_::Switch * beeper_switch_
std::chrono::steady_clock::time_point last_alarm_request_
sensor::Sensor * sub_sensors_[(size_t) SubSensorType::SUB_SENSOR_TYPE_COUNT]
Definition hon_climate.h:63
void set_beeper_state(bool state)
void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction)
haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size)
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction)
CleaningState cleaning_status_
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_
esphome::optional< hon_protocol::HorizontalSwingMode > current_horizontal_swing_
void set_quiet_mode_state(bool state)
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value)
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)
binary_sensor::BinarySensor * sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]
Definition hon_climate.h:80
haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, size_t size)
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value)
haier_protocol::HaierMessage get_control_message() override
void process_phase(std::chrono::steady_clock::time_point now) override
esphome::optional< hon_protocol::HorizontalSwingMode > pending_horizontal_direction_
bool get_quiet_mode_state() const
switch_::Switch * quiet_mode_switch_
std::string get_cleaning_status_text() const
void process_protocol_reset() override
CleaningState get_cleaning_status() const
esphome::optional< hon_protocol::HorizontalSwingMode > get_horizontal_airflow() const
void set_beeper_switch(switch_::Switch *sw)
esphome::optional< hon_protocol::VerticalSwingMode > current_vertical_swing_
void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new)
bool prepare_pending_action() override
esphome::optional< hon_protocol::VerticalSwingMode > pending_vertical_direction_
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
Base class for all switches.
Definition switch.h:38
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:56
void publish_state(const std::string &state)
uint16_t type
FanDirection direction
Definition fan.h:5
bool state
Definition fan.h:2
@ CLIMATE_PRESET_NONE
No preset is active.
@ CLIMATE_PRESET_AWAY
Device is in away preset.
@ CLIMATE_PRESET_BOOST
Device is in boost preset.
@ CLIMATE_PRESET_SLEEP
Device is prepared for sleep.
ClimateSwingMode
Enum for all modes a climate swing can be in NOTE: If adding values, update ClimateSwingModeMask in c...
@ CLIMATE_SWING_OFF
The swing mode is set to Off.
@ CLIMATE_SWING_HORIZONTAL
The fan mode is set to Horizontal.
@ CLIMATE_SWING_VERTICAL
The fan mode is set to Vertical.
@ CLIMATE_SWING_BOTH
The fan mode is set to Both.
ClimateMode
Enum for all modes a climate device can be in.
@ CLIMATE_MODE_DRY
The climate device is set to dry/humidity mode.
@ CLIMATE_MODE_FAN_ONLY
The climate device only has the fan enabled, no heating or cooling is taking place.
@ CLIMATE_MODE_HEAT
The climate device is set to heat to reach the target temperature.
@ CLIMATE_MODE_COOL
The climate device is set to cool to reach the target temperature.
@ CLIMATE_MODE_HEAT_COOL
The climate device is set to heat/cool to reach the target temperature.
@ CLIMATE_MODE_OFF
The climate device is off.
@ CLIMATE_FAN_MEDIUM
The fan mode is set to Medium.
@ CLIMATE_FAN_ON
The fan mode is set to On.
@ CLIMATE_FAN_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const std::string HON_ALARM_MESSAGES[]
Definition hon_packet.h:202
constexpr size_t HON_ALARM_COUNT
Definition hon_packet.h:256
constexpr uint8_t CONTROL_MESSAGE_RETRIES
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL
constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET
const uint8_t ZERO_BUF[]
const uint8_t ONE_BUF[]
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition log.cpp:21
uint16_t size
Definition helpers.cpp:25
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:881
static void uint32_t
esphome::optional< esphome::climate::ClimateFanMode > fan_mode
Definition haier_base.h:136
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
Definition haier_base.h:137
esphome::optional< esphome::climate::ClimateMode > mode
Definition haier_base.h:135
esphome::optional< esphome::climate::ClimatePreset > preset
Definition haier_base.h:139
esphome::optional< float > target_temperature
Definition haier_base.h:138
hon_protocol::HorizontalSwingMode last_horizontal_swing
Definition hon_climate.h:34
hon_protocol::VerticalSwingMode last_vertiacal_swing
Definition hon_climate.h:33