ESPHome 2026.6.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::haier {
13
14static const char *const TAG = "haier.climate";
15constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
17constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
18constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
19constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
20const uint8_t ONE_BUF[] = {0x00, 0x01};
21const uint8_t ZERO_BUF[] = {0x00, 0x00};
22
24 : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
25 0x00, 0x00, 0x00,
26 0x00, 0x00} {
29}
30
32
34 if (state != this->settings_.beeper_state) {
36#ifdef USE_SWITCH
37 if (this->beeper_switch_ != nullptr) {
38 this->beeper_switch_->publish_state(state);
39 }
40#endif
41 this->hon_rtc_.save(&this->settings_);
42 }
43}
44
46
48 if (state != this->get_quiet_mode_state()) {
49 if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
51 this->force_send_control_ = true;
52 } else {
54 }
56#ifdef USE_SWITCH
57 if (this->quiet_mode_switch_ != nullptr) {
59 }
60#endif
61 this->hon_rtc_.save(&this->settings_);
62 }
63}
64
68
69esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
70 return this->current_vertical_swing_;
71};
72
77
78esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
79 return this->current_horizontal_swing_;
80}
81
86
88 switch (this->cleaning_status_) {
90 return "Self clean";
92 return "56°C Steri-Clean";
93 default:
94 return "No cleaning";
95 }
96}
97
99
102 ESP_LOGI(TAG, "Sending self cleaning start request");
103 this->action_request_ =
104 PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
105 }
106}
107
110 ESP_LOGI(TAG, "Sending steri cleaning start request");
111 this->action_request_ =
112 PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
113 }
114}
115
116haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
117 haier_protocol::FrameType message_type,
118 const uint8_t *data, size_t data_size) {
119 // Should check this before preprocess
120 if (message_type == haier_protocol::FrameType::INVALID) {
121 ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
122 "protocol instead of hOn");
124 return haier_protocol::HandlerError::INVALID_ANSWER;
125 }
126 haier_protocol::HandlerError result =
127 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
128 haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
129 if (result == haier_protocol::HandlerError::HANDLER_OK) {
130 if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
131 // Wrong structure
132 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
133 }
134 // All OK
136 HardwareInfo info{}; // zero-init guarantees null-termination
137 strncpy(info.protocol_version_, answr->protocol_version, HARDWARE_INFO_STR_SIZE - 1);
138 strncpy(info.software_version_, answr->software_version, HARDWARE_INFO_STR_SIZE - 1);
139 strncpy(info.hardware_version_, answr->hardware_version, HARDWARE_INFO_STR_SIZE - 1);
140 strncpy(info.device_name_, answr->device_name, HARDWARE_INFO_STR_SIZE - 1);
141 info.functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
142 info.functions_[1] = (answr->functions[1] & 0x02) != 0; // controller-device mode support
143 info.functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
144 info.functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
145 info.functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
146 this->use_crc_ = info.functions_[2];
147#ifdef USE_TEXT_SENSOR
150#endif
151 this->hvac_hardware_info_ = info;
153 return result;
154 } else {
155 this->reset_phase_();
156 return result;
157 }
158}
159
160haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
161 haier_protocol::FrameType message_type,
162 const uint8_t *data, size_t data_size) {
163 haier_protocol::HandlerError result =
164 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
165 haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
166 if (result == haier_protocol::HandlerError::HANDLER_OK) {
168 return result;
169 } else {
170 this->reset_phase_();
171 return result;
172 }
173}
174
175haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
176 haier_protocol::FrameType message_type, const uint8_t *data,
177 size_t data_size) {
178 haier_protocol::HandlerError result =
179 this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
180 haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
181 if (result == haier_protocol::HandlerError::HANDLER_OK) {
182 result = this->process_status_message_(data, data_size);
183 if (result != haier_protocol::HandlerError::HANDLER_OK) {
184 ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
185 this->reset_phase_();
186 this->action_request_.reset();
187 this->force_send_control_ = false;
188 } else {
189 if (!this->last_status_message_) {
192 this->last_status_message_.reset();
193 this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
194 };
195 if (data_size >= this->real_control_packet_size_ + 2) {
196 memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
197 this->real_control_packet_size_);
198 this->status_message_callback_.call((const char *) data, data_size);
199 } else {
200 ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_);
201 }
202 switch (this->protocol_phase_) {
204 ESP_LOGI(TAG, "First HVAC status received");
206 break;
208 // Do nothing, phase will be changed in process_phase
209 break;
212 break;
214 if (!this->control_messages_queue_.empty())
215 this->control_messages_queue_.pop();
216 if (this->control_messages_queue_.empty()) {
218 this->force_send_control_ = false;
221 } else {
223 }
224 break;
225 default:
226 break;
227 }
228 }
229 return result;
230 } else {
231 this->action_request_.reset();
232 this->force_send_control_ = false;
233 this->reset_phase_();
234 return result;
235 }
236}
237
239 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
240 size_t data_size) {
241 haier_protocol::HandlerError result = this->answer_preprocess_(
242 request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
243 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
244 if (result == haier_protocol::HandlerError::HANDLER_OK) {
246 return result;
247 } else {
249 return result;
250 }
251}
252
253haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
254 haier_protocol::FrameType message_type,
255 const uint8_t *data, size_t data_size) {
256 if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
257 if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
258 // Unexpected answer to request
260 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
261 }
264 // Don't expect this answer now
266 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
267 }
268 if (data_size < sizeof(active_alarms_) + 2)
269 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
270 this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
272 return haier_protocol::HandlerError::HANDLER_OK;
273 } else {
275 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
276 }
277}
278
279haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
280 const uint8_t *buffer, size_t size) {
281 haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
282 if (size < sizeof(this->active_alarms_) + 2) {
283 // Log error but confirm anyway to avoid to many messages
284 result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
285 }
286 this->process_alarm_message_(buffer, size, true);
287 this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
288 this->last_alarm_request_ = std::chrono::steady_clock::now();
289 return result;
290}
291
293 // Set handlers
294 this->haier_protocol_.set_answer_handler(
295 haier_protocol::FrameType::GET_DEVICE_VERSION,
296 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
297 return this->get_device_version_answer_handler_(req, msg, data, size);
298 });
299 this->haier_protocol_.set_answer_handler(
300 haier_protocol::FrameType::GET_DEVICE_ID,
301 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
302 return this->get_device_id_answer_handler_(req, msg, data, size);
303 });
304 this->haier_protocol_.set_answer_handler(
305 haier_protocol::FrameType::CONTROL,
306 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
307 return this->status_handler_(req, msg, data, size);
308 });
309 this->haier_protocol_.set_answer_handler(
310 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
311 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
312 return this->get_management_information_answer_handler_(req, msg, data, size);
313 });
314 this->haier_protocol_.set_answer_handler(
315 haier_protocol::FrameType::GET_ALARM_STATUS,
316 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
317 return this->get_alarm_status_answer_handler_(req, msg, data, size);
318 });
319 this->haier_protocol_.set_answer_handler(
320 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
321 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
322 return this->report_network_status_answer_handler_(req, msg, data, size);
323 });
324 this->haier_protocol_.set_message_handler(haier_protocol::FrameType::ALARM_STATUS,
325 [this](haier_protocol::FrameType type, const uint8_t *data, size_t size) {
326 return this->alarm_status_message_handler_(type, data, size);
327 });
328}
329
332 ESP_LOGCONFIG(TAG,
333 " Protocol version: hOn\n"
334 " Control method: %d",
335 (uint8_t) this->control_method_);
336 if (this->hvac_hardware_info_.has_value()) {
337 ESP_LOGCONFIG(TAG,
338 " Device protocol version: %s\n"
339 " Device software version: %s\n"
340 " Device hardware version: %s\n"
341 " Device name: %s",
342 this->hvac_hardware_info_.value().protocol_version_,
343 this->hvac_hardware_info_.value().software_version_,
344 this->hvac_hardware_info_.value().hardware_version_, this->hvac_hardware_info_.value().device_name_);
345 ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
346 (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
347 (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
348 (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
349 (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
350 (this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
351 ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
352 }
353}
354
355void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
356 switch (this->protocol_phase_) {
359 // Indicate device capabilities:
360 // bit 0 - if 1 module support interactive mode
361 // bit 1 - if 1 module support controller-device mode
362 // bit 2 - if 1 module support crc
363 // bit 3 - if 1 module support multiple devices
364 // bit 4..bit 15 - not used
365 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
366 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
367 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
368 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
369 }
370 break;
372 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
373 static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
374 this->send_message_(DEVICEID_REQUEST, this->use_crc_);
375 }
376 break;
379 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
380 static const haier_protocol::HaierMessage STATUS_REQUEST(
381 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
382 static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
383 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
385 (!this->should_get_big_data_())) {
386 this->send_message_(STATUS_REQUEST, this->use_crc_);
387 } else {
388 this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
389 }
390 this->last_status_request_ = now;
391 }
392 break;
393#ifdef USE_WIFI
395 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
396 static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
397 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
398 this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
399 this->last_signal_request_ = now;
400 }
401 break;
403 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
404 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
405 }
406 break;
407#else
411 break;
412#endif
415 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
416 static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
417 this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
418 this->last_alarm_request_ = now;
419 }
420 break;
422 if (this->control_messages_queue_.empty()) {
423 switch (this->control_method_) {
425 haier_protocol::HaierMessage control_message = this->get_control_message();
426 this->control_messages_queue_.push(control_message);
427 } break;
430 break;
432 ESP_LOGI(TAG, "AC control is disabled, monitor only");
433 this->reset_to_idle_();
434 return;
435 default:
436 ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
437 this->reset_to_idle_();
438 return;
439 }
440 }
441 if (this->control_messages_queue_.empty()) {
442 ESP_LOGW(TAG, "Control message queue is empty!");
443 this->reset_to_idle_();
444 } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
445 ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
446 this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
448 }
449 break;
451 if (this->action_request_.has_value()) {
452 if (this->action_request_.value().message.has_value()) {
453 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
454 this->action_request_.value().message.reset(); // NOLINT(bugprone-unchecked-optional-access)
455 } else {
456 // Message already sent, reseting request and return to idle
457 this->action_request_.reset();
459 }
460 } else {
461 ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
463 }
464 break;
468 this->forced_request_status_ = false;
469 } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
472 }
473#ifdef USE_WIFI
474 else if (this->send_wifi_signal_ &&
475 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
478 }
479#endif
480 } break;
481 default:
482 // Shouldn't get here
483 ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
486 break;
487 }
488}
489
490haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
491 if (state) {
492 static haier_protocol::HaierMessage power_on_message(
493 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
494 std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
495 return power_on_message;
496 } else {
497 static haier_protocol::HaierMessage power_off_message(
498 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
499 std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
500 return power_off_message;
501 }
502}
503
506 constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
507 this->hon_rtc_ = this->make_entity_preference<HonSettings>(restore_settings_version);
508 HonSettings recovered;
509 if (this->hon_rtc_.load(&recovered)) {
510 this->settings_ = recovered;
511 } else {
513 }
517}
518
519haier_protocol::HaierMessage HonClimate::get_control_message() {
520 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
521 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
523 control_out_buffer[4] = 0; // This byte should be cleared before setting values
524 bool has_hvac_settings = false;
525 if (this->current_hvac_settings_.valid) {
526 has_hvac_settings = true;
527 HvacSettings &climate_control = this->current_hvac_settings_;
528 if (climate_control.mode.has_value()) {
529 switch (climate_control.mode.value()) {
530 case CLIMATE_MODE_OFF:
531 out_data->ac_power = 0;
532 break;
534 out_data->ac_power = 1;
535 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
536 out_data->fan_mode = this->other_modes_fan_speed_;
537 break;
539 out_data->ac_power = 1;
540 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
541 out_data->fan_mode = this->other_modes_fan_speed_;
542 break;
543 case CLIMATE_MODE_DRY:
544 out_data->ac_power = 1;
545 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
546 out_data->fan_mode = this->other_modes_fan_speed_;
547 break;
549 out_data->ac_power = 1;
550 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
551 out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
552 // Disabling boost for Fan only
553 out_data->fast_mode = 0;
554 break;
556 out_data->ac_power = 1;
557 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
558 out_data->fan_mode = this->other_modes_fan_speed_;
559 break;
560 default:
561 ESP_LOGE("Control", "Unsupported climate mode");
562 break;
563 }
564 }
565 // Set fan speed, if we are in fan mode, reject auto in fan mode
566 if (climate_control.fan_mode.has_value()) {
567 switch (climate_control.fan_mode.value()) {
568 case CLIMATE_FAN_LOW:
569 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
570 break;
572 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
573 break;
574 case CLIMATE_FAN_HIGH:
575 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
576 break;
577 case CLIMATE_FAN_AUTO:
578 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
579 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
580 break;
581 default:
582 ESP_LOGE("Control", "Unsupported fan mode");
583 break;
584 }
585 }
586 // Set swing mode
587 if (climate_control.swing_mode.has_value()) {
588 switch (climate_control.swing_mode.value()) {
590 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
591 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
592 break;
594 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
596 break;
599 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
600 break;
604 break;
605 }
606 }
607 if (climate_control.target_temperature.has_value()) {
608 float target_temp = climate_control.target_temperature.value();
609 out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
610 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
611 }
612 if (out_data->ac_power == 0) {
613 // If AC is off - no presets allowed
614 out_data->fast_mode = 0;
615 out_data->sleep_mode = 0;
616 } else if (climate_control.preset.has_value()) {
617 switch (climate_control.preset.value()) {
619 out_data->fast_mode = 0;
620 out_data->sleep_mode = 0;
621 out_data->ten_degree = 0;
622 break;
624 // Boost is not supported in Fan only mode
625 out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
626 out_data->sleep_mode = 0;
627 out_data->ten_degree = 0;
628 break;
630 out_data->fast_mode = 0;
631 out_data->sleep_mode = 0;
632 // 10 degrees allowed only in heat mode
633 out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
634 break;
636 out_data->fast_mode = 0;
637 out_data->sleep_mode = 1;
638 out_data->ten_degree = 0;
639 break;
640 default:
641 ESP_LOGE("Control", "Unsupported preset");
642 out_data->fast_mode = 0;
643 out_data->sleep_mode = 0;
644 out_data->ten_degree = 0;
645 break;
646 }
647 }
648 }
649 if (this->pending_vertical_direction_.has_value()) {
650 out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
651 this->pending_vertical_direction_.reset();
652 }
653 if (this->pending_horizontal_direction_.has_value()) {
654 out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
655 this->pending_horizontal_direction_.reset();
656 }
657 {
658 // Quiet mode
659 if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
660 // If AC is off or in fan only mode - no quiet mode allowed
661 out_data->quiet_mode = 0;
662 } else {
663 out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
664 }
665 // Clean quiet mode state pending flag
666 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
667 }
668 out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
669 out_data->display_status = this->get_display_state() ? 1 : 0;
670 this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
671 out_data->health_mode = this->get_health_mode() ? 1 : 0;
672 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
673 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
675 control_out_buffer, this->real_control_packet_size_);
676}
677
678void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
679 constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
680 if (size >= active_alarms_size + 2) {
681 if (check_new) {
682 size_t alarm_code = 0;
683 for (int i = active_alarms_size - 1; i >= 0; i--) {
684 if (packet[2 + i] != active_alarms_[i]) {
685 uint8_t alarm_bit = 1;
686 for (int b = 0; b < 8; b++) {
687 if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
688 bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
689 int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
690 const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
692 : "Unknown";
693 esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
694 alarm_code, alarm_message);
695 if (alarm_status) {
696 this->alarm_start_callback_.call(alarm_code, alarm_message);
697 this->active_alarm_count_ += 1.0f;
698 } else {
699 this->alarm_end_callback_.call(alarm_code, alarm_message);
700 this->active_alarm_count_ -= 1.0f;
701 }
702 }
703 alarm_bit <<= 1;
704 alarm_code++;
705 }
706 active_alarms_[i] = packet[2 + i];
707 } else {
708 alarm_code += 8;
709 }
710 }
711 } else {
712 float alarm_count = 0.0f;
713 static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
714 for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
715 alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
716 }
717 this->active_alarm_count_ = alarm_count;
718 memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
719 }
720 }
721}
722
723#ifdef USE_SENSOR
727 if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
728 this->big_data_sensors_--;
729 } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
730 this->big_data_sensors_++;
731 }
732 }
733 this->sub_sensors_[(size_t) type] = sens;
734 }
735}
736
739 size_t index = (size_t) type;
740 if ((this->sub_sensors_[index] != nullptr) &&
741 ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->get_raw_state() != value)))
742 this->sub_sensors_[index]->publish_state(value);
743 }
744}
745#endif // USE_SENSOR
746
747#ifdef USE_BINARY_SENSOR
750 if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
751 this->big_data_sensors_--;
752 } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
753 this->big_data_sensors_++;
754 }
755 this->sub_binary_sensors_[(size_t) type] = sens;
756 }
757}
758
760 if (value < 2) {
761 bool converted_value = value == 1;
762 size_t index = (size_t) type;
763 if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
764 (this->sub_binary_sensors_[index]->state != converted_value)))
765 this->sub_binary_sensors_[index]->publish_state(converted_value);
766 }
767}
768#endif // USE_BINARY_SENSOR
769
770#ifdef USE_TEXT_SENSOR
772 this->sub_text_sensors_[(size_t) type] = sens;
773 switch (type) {
775 if (this->hvac_hardware_info_.has_value())
776 sens->publish_state(this->hvac_hardware_info_.value().device_name_);
777 break;
779 if (this->hvac_hardware_info_.has_value())
780 sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
781 break;
784 break;
785 default:
786 break;
787 }
788}
789
791 size_t index = (size_t) type;
792 if (this->sub_text_sensors_[index] != nullptr)
793 this->sub_text_sensors_[index]->publish_state(value);
794}
795#endif // USE_TEXT_SENSOR
796
797#ifdef USE_SWITCH
799 this->beeper_switch_ = sw;
800 if (this->beeper_switch_ != nullptr) {
802 }
803}
804
811#endif // USE_SWITCH
812
813haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
814 size_t expected_size =
816 if (size < expected_size) {
817 ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size);
818 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
819 }
820 uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
821 if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
822 // Got BigData packet
823 const hon_protocol::HaierPacketBigData *bd_packet =
824 (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
825#ifdef USE_SENSOR
831 this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
834 encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
835 this->update_sub_sensor_(
837 encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
838#endif // USE_SENSOR
839#ifdef USE_BINARY_SENSOR
847#endif // USE_BINARY_SENSOR
848 }
849 struct {
852 } packet;
853 memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
855 memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
857 if (packet.sensors.error_status != 0) {
858 ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
859 }
860#ifdef USE_SENSOR
861 if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
862 (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
863 this->got_valid_outdoor_temp_ = true;
865 (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
866 }
867 if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
868 this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
869 }
870#endif // USE_SENSOR
871 bool should_publish = false;
872 {
873 // Extra modes/presets
874 optional<ClimatePreset> old_preset = this->preset;
875 if (packet.control.fast_mode != 0) {
877 } else if (packet.control.sleep_mode != 0) {
879 } else if (packet.control.ten_degree != 0) {
881 } else {
883 }
884 should_publish = should_publish || (!old_preset.has_value()) ||
885 (old_preset.value_or(CLIMATE_PRESET_NONE) != this->preset.value_or(CLIMATE_PRESET_NONE));
886 }
887 {
888 // Target temperature
889 float old_target_temperature = this->target_temperature;
890 this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
891 should_publish = should_publish || (old_target_temperature != this->target_temperature);
892 }
893 {
894 // Current temperature
895 float old_current_temperature = this->current_temperature;
896 this->current_temperature = packet.sensors.room_temperature / 2.0f;
897 should_publish = should_publish || (old_current_temperature != this->current_temperature);
898 }
899 {
900 // Fan mode
901 optional<ClimateFanMode> old_fan_mode = this->fan_mode;
902 // remember the fan speed we last had for climate vs fan
903 if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
904 if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
905 this->fan_mode_speed_ = packet.control.fan_mode;
906 } else {
907 this->other_modes_fan_speed_ = packet.control.fan_mode;
908 }
909 switch (packet.control.fan_mode) {
910 case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
911 if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
913 } else {
914 // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
915 ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
916 }
917 break;
918 case (uint8_t) hon_protocol::FanMode::FAN_MID:
920 break;
921 case (uint8_t) hon_protocol::FanMode::FAN_LOW:
923 break;
924 case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
926 break;
927 }
928 should_publish = should_publish || (!old_fan_mode.has_value()) ||
929 (old_fan_mode.value_or(CLIMATE_FAN_ON) != this->fan_mode.value_or(CLIMATE_FAN_ON));
930 }
931 // Display status
932 // should be before "Climate mode" because it is changing this->mode
933 if (packet.control.ac_power != 0) {
934 // if AC is off display status always ON so process it only when AC is on
935 bool disp_status = packet.control.display_status != 0;
936 if (disp_status != this->get_display_state()) {
937 // Do something only if display status changed
938 if (this->mode == CLIMATE_MODE_OFF) {
939 // AC just turned on from remote need to turn off display
940 this->force_send_control_ = true;
941 } else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
942 this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
943 }
944 }
945 }
946 // Health mode
947 if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
948 bool old_health_mode = this->get_health_mode();
949 this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
950 should_publish = should_publish || (old_health_mode != this->get_health_mode());
951 }
952 {
953 CleaningState new_cleaning;
954 if (packet.control.steri_clean == 1) {
955 // Steri-cleaning
956 new_cleaning = CleaningState::STERI_CLEAN;
957 } else if (packet.control.self_cleaning_status == 1) {
958 // Self-cleaning
959 new_cleaning = CleaningState::SELF_CLEAN;
960 } else {
961 // No cleaning
962 new_cleaning = CleaningState::NO_CLEANING;
963 }
964 if (new_cleaning != this->cleaning_status_) {
965 ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
966 if (new_cleaning == CleaningState::NO_CLEANING) {
967 // Turning AC off after cleaning
968 this->action_request_ =
969 PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
970 }
971 this->cleaning_status_ = new_cleaning;
972#ifdef USE_TEXT_SENSOR
974#endif // USE_TEXT_SENSOR
975 }
976 }
977 {
978 // Climate mode
979 ClimateMode old_mode = this->mode;
980 if (packet.control.ac_power == 0) {
981 this->mode = CLIMATE_MODE_OFF;
982 } else {
983 // Check current hvac mode
984 switch (packet.control.ac_mode) {
986 this->mode = CLIMATE_MODE_COOL;
987 break;
989 this->mode = CLIMATE_MODE_HEAT;
990 break;
992 this->mode = CLIMATE_MODE_DRY;
993 break;
996 break;
999 break;
1000 }
1001 }
1002 should_publish = should_publish || (old_mode != this->mode);
1003 }
1004 {
1005 // Quiet mode, should be after climate mode
1006 if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
1007 ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
1008 // In proper mode and not in pending state
1009 bool new_quiet_mode = packet.control.quiet_mode != 0;
1010 if (new_quiet_mode != this->get_quiet_mode_state()) {
1011 this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
1012 this->settings_.quiet_mode_state = new_quiet_mode;
1013#ifdef USE_SWITCH
1014 if (this->quiet_mode_switch_ != nullptr) {
1015 this->quiet_mode_switch_->publish_state(new_quiet_mode);
1016 }
1017#endif // USE_SWITCH
1018 this->hon_rtc_.save(&this->settings_);
1019 }
1020 }
1021 }
1022 {
1023 // Swing mode
1024 ClimateSwingMode old_swing_mode = this->swing_mode;
1025 const auto &swing_modes = traits_.get_supported_swing_modes();
1026 bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL);
1027 bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL);
1028 if (horizontal_swing_supported &&
1029 (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
1030 if (vertical_swing_supported &&
1031 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1033 } else {
1035 }
1036 } else {
1037 if (vertical_swing_supported &&
1038 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1040 } else {
1042 }
1043 }
1044 // Saving last known non auto mode for vertical and horizontal swing
1045 this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
1046 this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
1049 (this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
1050 ((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
1051 (this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
1052 if (save_settings) {
1055 this->hon_rtc_.save(&this->settings_);
1056 }
1057 should_publish = should_publish || (old_swing_mode != this->swing_mode);
1058 }
1059 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
1060 if (should_publish) {
1061 this->publish_state();
1062 }
1063 if (should_publish) {
1064 ESP_LOGI(TAG, "HVAC values changed");
1065 }
1066 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1067 esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1068 esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1069 esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1070 esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1071 esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1072 return haier_protocol::HandlerError::HANDLER_OK;
1073}
1074
1076 if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1077 return;
1079 HvacSettings climate_control;
1080 climate_control = this->current_hvac_settings_;
1081 // Beeper command
1082 {
1083 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1086 this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
1087 }
1088 // Health mode
1089 {
1090 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1093 this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
1094 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
1095 }
1096 // Climate mode
1097 ClimateMode climate_mode = this->mode;
1098 bool new_power = this->mode != CLIMATE_MODE_OFF;
1099 uint8_t fan_mode_buf[] = {0x00, 0xFF};
1100 uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1101 if (climate_control.mode.has_value()) {
1102 climate_mode = climate_control.mode.value();
1103 uint8_t buffer[2] = {0x00, 0x00};
1104 switch (climate_control.mode.value()) {
1105 case CLIMATE_MODE_OFF:
1106 new_power = false;
1107 break;
1109 new_power = true;
1110 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
1111 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1114 buffer, 2);
1115 fan_mode_buf[1] = this->other_modes_fan_speed_;
1116 break;
1117 case CLIMATE_MODE_HEAT:
1118 new_power = true;
1119 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
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_DRY:
1127 new_power = true;
1128 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
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;
1136 new_power = true;
1137 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
1138 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1141 buffer, 2);
1142 fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1143 break;
1144 case CLIMATE_MODE_COOL:
1145 new_power = true;
1146 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
1147 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1150 buffer, 2);
1151 fan_mode_buf[1] = this->other_modes_fan_speed_;
1152 break;
1153 default:
1154 ESP_LOGE("Control", "Unsupported climate mode");
1155 break;
1156 }
1157 }
1158 // Climate power
1159 {
1160 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1163 new_power ? ONE_BUF : ZERO_BUF, 2);
1164 }
1165 // CLimate preset
1166 {
1167 uint8_t fast_mode_buf[] = {0x00, 0xFF};
1168 uint8_t away_mode_buf[] = {0x00, 0xFF};
1169 if (!new_power) {
1170 // If AC is off - no presets allowed
1171 fast_mode_buf[1] = 0x00;
1172 away_mode_buf[1] = 0x00;
1173 } else if (climate_control.preset.has_value()) {
1174 switch (climate_control.preset.value()) {
1176 fast_mode_buf[1] = 0x00;
1177 away_mode_buf[1] = 0x00;
1178 break;
1180 // Boost is not supported in Fan only mode
1181 fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1182 away_mode_buf[1] = 0x00;
1183 break;
1185 fast_mode_buf[1] = 0x00;
1186 away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1187 break;
1188 default:
1189 ESP_LOGE("Control", "Unsupported preset");
1190 break;
1191 }
1192 }
1193 {
1194 // Quiet mode
1195 if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
1196 quiet_mode_buf[1] = 0x01;
1197 } else {
1198 quiet_mode_buf[1] = 0x00;
1199 }
1200 // Clean quiet mode state pending flag
1201 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
1202 }
1203 auto presets = this->traits_.get_supported_presets();
1204 if (quiet_mode_buf[1] != 0xFF) {
1205 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1208 quiet_mode_buf, 2);
1209 }
1210 if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) {
1211 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1214 fast_mode_buf, 2);
1215 }
1216 if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) {
1217 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1220 away_mode_buf, 2);
1221 }
1222 }
1223 // Target temperature
1224 if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1225 uint8_t buffer[2] = {0x00, 0x00};
1226 buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1227 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1230 buffer, 2);
1231 }
1232 // Vertical swing mode
1233 if (climate_control.swing_mode.has_value()) {
1234 uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1235 uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1236 switch (climate_control.swing_mode.value()) {
1237 case CLIMATE_SWING_OFF:
1238 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1239 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1240 break;
1242 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1243 break;
1245 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1246 break;
1247 case CLIMATE_SWING_BOTH:
1248 break;
1249 }
1250 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1253 horizontal_swing_buf, 2);
1254 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1257 vertical_swing_buf, 2);
1258 }
1259 // Fan mode
1260 if (climate_control.fan_mode.has_value()) {
1261 switch (climate_control.fan_mode.value()) {
1262 case CLIMATE_FAN_LOW:
1263 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1264 break;
1265 case CLIMATE_FAN_MEDIUM:
1266 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1267 break;
1268 case CLIMATE_FAN_HIGH:
1269 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1270 break;
1271 case CLIMATE_FAN_AUTO:
1272 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1273 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1274 break;
1275 default:
1276 ESP_LOGE("Control", "Unsupported fan mode");
1277 break;
1278 }
1279 if (fan_mode_buf[1] != 0xFF) {
1280 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1283 fan_mode_buf, 2);
1284 }
1285 }
1286}
1287
1289 while (!this->control_messages_queue_.empty())
1290 this->control_messages_queue_.pop();
1291}
1292
1294 auto &action_request = this->action_request_.value(); // NOLINT(bugprone-unchecked-optional-access)
1295 switch (action_request.action) {
1298 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1299 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1300 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1301 out_data->self_cleaning_status = 1;
1302 out_data->steri_clean = 0;
1303 out_data->set_point = 0x06;
1306 out_data->ac_power = 1;
1307 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1308 out_data->light_status = 0;
1309 action_request.message = haier_protocol::HaierMessage(
1310 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1311 control_out_buffer, this->real_control_packet_size_);
1312 return true;
1314 action_request.message =
1315 haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1318 ONE_BUF, 2);
1319 return true;
1320 } else {
1321 this->action_request_.reset();
1322 return false;
1323 }
1326 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1327 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1328 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1329 out_data->self_cleaning_status = 0;
1330 out_data->steri_clean = 1;
1331 out_data->set_point = 0x06;
1334 out_data->ac_power = 1;
1335 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1336 out_data->light_status = 0;
1337 action_request.message = haier_protocol::HaierMessage(
1338 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1339 control_out_buffer, this->real_control_packet_size_);
1340 return true;
1341 } else {
1342 // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1343 this->action_request_.reset();
1344 return false;
1345 }
1346 default:
1348 }
1349}
1350
1353#ifdef USE_SENSOR
1354 for (auto &sub_sensor : this->sub_sensors_) {
1355 if ((sub_sensor != nullptr) && sub_sensor->has_state())
1356 sub_sensor->publish_state(NAN);
1357 }
1358#endif // USE_SENSOR
1359 this->got_valid_outdoor_temp_ = false;
1360 this->hvac_hardware_info_.reset();
1361 this->last_status_message_.reset(nullptr);
1362}
1363
1365 if (this->big_data_sensors_ > 0) {
1366 this->big_data_counter_ = (this->big_data_counter_ + 1) % 3;
1367 return this->big_data_counter_ == 1;
1368 }
1369 return false;
1370}
1371
1372} // namespace esphome::haier
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:437
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:167
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:175
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:155
const char * phase_to_string_(ProtocolPhases phase)
std::unique_ptr< uint8_t[]> last_status_message_
Definition haier_base.h:170
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:172
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:157
std::chrono::steady_clock::time_point last_status_request_
Definition haier_base.h:173
virtual void set_phase(ProtocolPhases phase)
std::chrono::steady_clock::time_point last_signal_request_
Definition haier_base.h:174
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:93
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:62
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)
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:79
haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, size_t size)
const char * get_cleaning_status_text() const
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_
static constexpr size_t HARDWARE_INFO_STR_SIZE
bool get_quiet_mode_state() const
switch_::Switch * quiet_mode_switch_
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 update_sub_text_sensor_(SubTextSensorType type, const char *value)
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:200
constexpr size_t HON_ALARM_COUNT
Definition hon_packet.h:254
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
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:859
static void uint32_t
esphome::optional< esphome::climate::ClimateFanMode > fan_mode
Definition haier_base.h:135
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
Definition haier_base.h:136
esphome::optional< esphome::climate::ClimateMode > mode
Definition haier_base.h:134
esphome::optional< esphome::climate::ClimatePreset > preset
Definition haier_base.h:138
esphome::optional< float > target_temperature
Definition haier_base.h:137
hon_protocol::HorizontalSwingMode last_horizontal_swing
Definition hon_climate.h:33
hon_protocol::VerticalSwingMode last_vertiacal_swing
Definition hon_climate.h:32