ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
hon_climate.cpp
Go to the documentation of this file.
1#include <chrono>
2#include <string>
6#include "esphome/core/hal.h"
9#include "hon_climate.h"
10#include "hon_packet.h"
11
12using namespace esphome::climate;
13using namespace esphome::uart;
14
15namespace esphome::haier {
16
17static bool vertical_axis_is_auto(climate::ClimateSwingMode swing_mode) {
19}
20
21static bool horizontal_axis_is_auto(climate::ClimateSwingMode swing_mode) {
23}
24
25static bool is_auto(hon_protocol::VerticalSwingMode mode) {
27}
28
30
31// A position the user just set wins over the appliance's echo of the old one for this long
33static bool in_debounce(uint32_t set_time, uint32_t now) {
34 return set_time != 0 && now - set_time < AIRFLOW_SELECT_DEBOUNCE_MS;
35}
36
37ESPHOME_LOG_TAG(TAG, "haier.climate");
38constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
40constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
41constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
42constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
43const uint8_t ONE_BUF[] = {0x00, 0x01};
44const uint8_t ZERO_BUF[] = {0x00, 0x00};
45
46// Alarm texts indexed by alarm code; the last entry is the fallback for unknown codes
48 HonAlarmMessages, "Outdoor module failure", "Outdoor defrost sensor failure",
49 "Outdoor compressor exhaust sensor failure", "Outdoor EEPROM abnormality", "Indoor coil sensor failure",
50 "Indoor-outdoor communication failure", "Power supply overvoltage protection",
51 "Communication failure between panel and indoor unit", "Outdoor compressor overheat protection",
52 "Outdoor environmental sensor abnormality", "Full water protection", "Indoor EEPROM failure",
53 "Outdoor out air sensor failure", "CBD and module communication failure", "Indoor DC fan failure",
54 "Outdoor DC fan failure", "Door switch failure", "Dust filter needs cleaning reminder", "Water shortage protection",
55 "Humidity sensor failure", "Indoor temperature sensor failure", "Manipulator limit failure",
56 "Indoor PM2.5 sensor failure", "Outdoor PM2.5 sensor failure", "Indoor heating overload/high load alarm",
57 "Outdoor AC current protection", "Outdoor compressor operation abnormality", "Outdoor DC current protection",
58 "Outdoor no-load failure", "CT current abnormality", "Indoor cooling freeze protection",
59 "High and low pressure protection", "Compressor out air temperature is too high",
60 "Outdoor evaporator sensor failure", "Outdoor cooling overload", "Water pump drainage failure",
61 "Three-phase power supply failure", "Four-way valve failure", "External alarm/scraper flow switch failure",
62 "Temperature cutoff protection alarm", "Different mode operation failure", "Electronic expansion valve failure",
63 "Dual heat source sensor Tw failure", "Communication failure with the wired controller",
64 "Indoor unit address duplication failure", "50Hz zero crossing failure", "Outdoor unit failure",
65 "Formaldehyde sensor failure", "VOC sensor failure", "CO2 sensor failure", "Firewall failure", "Unknown");
66
68 : cleaning_status_(CleaningState::NO_CLEANING),
69 got_valid_outdoor_temp_(false),
70 active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} {
73}
74
76
78 if (state != this->settings_.beeper_state) {
80#ifdef USE_SWITCH
81 if (this->beeper_switch_ != nullptr) {
82 this->beeper_switch_->publish_state(state);
83 }
84#endif
85 this->hon_rtc_.save(&this->settings_);
86 }
87}
88
90
92 if (state != this->get_quiet_mode_state()) {
93 if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
95 this->force_send_control_ = true;
96 } else {
98 }
100#ifdef USE_SWITCH
101 if (this->quiet_mode_switch_ != nullptr) {
102 this->quiet_mode_switch_->publish_state(state);
103 }
104#endif
105 this->hon_rtc_.save(&this->settings_);
106 }
107}
108
112
113esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
114 if (!this->current_vertical_swing_.has_value()) {
115 return this->current_vertical_swing_;
116 }
117 // While the appliance swings, or right after the user chose a position, report the chosen position
118 if (is_auto(this->current_vertical_swing_.value()) ||
120 return this->settings_.last_vertiacal_swing;
121 }
122 return this->current_vertical_swing_;
123}
124
126 if (is_auto(direction)) {
127 ESP_LOGW(TAG, "Vertical AUTO mode is not allowed for airflow position selector");
128 return;
129 }
130 if (this->settings_.last_vertiacal_swing != direction) {
132 this->hon_rtc_.save(&this->settings_);
133 }
135 this->force_send_control_ = true;
137#ifdef USE_SELECT
139#endif
140}
141
142esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
143 if (!this->current_horizontal_swing_.has_value()) {
144 return this->current_horizontal_swing_;
145 }
146 if (is_auto(this->current_horizontal_swing_.value()) ||
148 return this->settings_.last_horizontal_swing;
149 }
150 return this->current_horizontal_swing_;
151}
152
154 if (is_auto(direction)) {
155 ESP_LOGW(TAG, "Horizontal AUTO mode is not allowed for airflow position selector");
156 return;
157 }
158 if (this->settings_.last_horizontal_swing != direction) {
160 this->hon_rtc_.save(&this->settings_);
161 }
163 this->force_send_control_ = true;
165#ifdef USE_SELECT
167#endif
168}
169
171 switch (this->cleaning_status_) {
173 return "Self clean";
175 return "56°C Steri-Clean";
176 default:
177 return "No cleaning";
178 }
179}
180
182
185 ESP_LOGI(TAG, "Sending self cleaning start request");
186 this->action_request_ =
187 PendingAction({ActionRequest::START_SELF_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
188 }
189}
190
193 ESP_LOGI(TAG, "Sending steri cleaning start request");
194 this->action_request_ =
195 PendingAction({ActionRequest::START_STERI_CLEAN, esphome::optional<haier_protocol::HaierMessage>()});
196 }
197}
198
199haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
200 haier_protocol::FrameType message_type,
201 const uint8_t *data, size_t data_size) {
202 // Should check this before preprocess
203 if (message_type == haier_protocol::FrameType::INVALID) {
204 ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
205 "protocol instead of hOn");
207 return haier_protocol::HandlerError::INVALID_ANSWER;
208 }
209 haier_protocol::HandlerError result =
210 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
211 haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
212 if (result == haier_protocol::HandlerError::HANDLER_OK) {
213 if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
214 // Wrong structure
215 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
216 }
217 // All OK
219 HardwareInfo info{}; // zero-init guarantees null-termination
220 strncpy(info.protocol_version_, answr->protocol_version, HARDWARE_INFO_STR_SIZE - 1);
221 strncpy(info.software_version_, answr->software_version, HARDWARE_INFO_STR_SIZE - 1);
222 strncpy(info.hardware_version_, answr->hardware_version, HARDWARE_INFO_STR_SIZE - 1);
223 strncpy(info.device_name_, answr->device_name, HARDWARE_INFO_STR_SIZE - 1);
224 info.functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
225 info.functions_[1] = (answr->functions[1] & 0x02) != 0; // controller-device mode support
226 info.functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
227 info.functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
228 info.functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
229 this->use_crc_ = info.functions_[2];
230#ifdef USE_TEXT_SENSOR
233#endif
234 this->hvac_hardware_info_ = info;
236 return result;
237 } else {
238 this->reset_phase_();
239 return result;
240 }
241}
242
243haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
244 haier_protocol::FrameType message_type,
245 const uint8_t *data, size_t data_size) {
246 haier_protocol::HandlerError result =
247 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
248 haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
249 if (result == haier_protocol::HandlerError::HANDLER_OK) {
251 return result;
252 } else {
253 this->reset_phase_();
254 return result;
255 }
256}
257
258haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
259 haier_protocol::FrameType message_type, const uint8_t *data,
260 size_t data_size) {
261 haier_protocol::HandlerError result =
262 this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
263 haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
264 if (result == haier_protocol::HandlerError::HANDLER_OK) {
265 result = this->process_status_message_(data, data_size);
266 if (result != haier_protocol::HandlerError::HANDLER_OK) {
267 ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
268 this->reset_phase_();
269 this->action_request_.reset();
270 this->force_send_control_ = false;
271 } else {
272 if (!this->last_status_message_) {
275 this->last_status_message_.reset();
276 this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
277 };
278 if (data_size >= this->real_control_packet_size_ + 2) {
279 memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
280 this->real_control_packet_size_);
281 this->status_message_callback_.call((const char *) data, data_size);
282 } else {
283 ESP_LOGW(TAG, "Status packet too small: %zu (should be >= %zu)", data_size, this->real_control_packet_size_);
284 }
285 switch (this->protocol_phase_) {
287 ESP_LOGI(TAG, "First HVAC status received");
289 break;
291 // Do nothing, phase will be changed in process_phase
292 break;
295 break;
297 if (!this->control_messages_queue_.empty())
298 this->control_messages_queue_.pop();
299 if (this->control_messages_queue_.empty()) {
301 this->force_send_control_ = false;
304 } else {
306 }
307 break;
308 default:
309 break;
310 }
311 }
312 return result;
313 } else {
314 this->action_request_.reset();
315 this->force_send_control_ = false;
316 this->reset_phase_();
317 return result;
318 }
319}
320
322 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
323 size_t data_size) {
324 haier_protocol::HandlerError result = this->answer_preprocess_(
325 request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
326 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
327 if (result == haier_protocol::HandlerError::HANDLER_OK) {
329 return result;
330 } else {
332 return result;
333 }
334}
335
336haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
337 haier_protocol::FrameType message_type,
338 const uint8_t *data, size_t data_size) {
339 if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
340 if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
341 // Unexpected answer to request
343 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
344 }
347 // Don't expect this answer now
349 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
350 }
351 if (data_size < sizeof(active_alarms_) + 2)
352 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
353 this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
355 return haier_protocol::HandlerError::HANDLER_OK;
356 } else {
358 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
359 }
360}
361
362haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
363 const uint8_t *buffer, size_t size) {
364 haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
365 if (size < sizeof(this->active_alarms_) + 2) {
366 // Log error but confirm anyway to avoid to many messages
367 result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
368 }
369 this->process_alarm_message_(buffer, size, true);
370 this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
371 this->last_alarm_request_ = std::chrono::steady_clock::now();
372 return result;
373}
374
376 // Set handlers
377 this->haier_protocol_.set_answer_handler(
378 haier_protocol::FrameType::GET_DEVICE_VERSION,
379 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
380 return this->get_device_version_answer_handler_(req, msg, data, size);
381 });
382 this->haier_protocol_.set_answer_handler(
383 haier_protocol::FrameType::GET_DEVICE_ID,
384 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
385 return this->get_device_id_answer_handler_(req, msg, data, size);
386 });
387 this->haier_protocol_.set_answer_handler(
388 haier_protocol::FrameType::CONTROL,
389 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
390 return this->status_handler_(req, msg, data, size);
391 });
392 this->haier_protocol_.set_answer_handler(
393 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
394 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
395 return this->get_management_information_answer_handler_(req, msg, data, size);
396 });
397 this->haier_protocol_.set_answer_handler(
398 haier_protocol::FrameType::GET_ALARM_STATUS,
399 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
400 return this->get_alarm_status_answer_handler_(req, msg, data, size);
401 });
402 this->haier_protocol_.set_answer_handler(
403 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
404 [this](haier_protocol::FrameType req, haier_protocol::FrameType msg, const uint8_t *data, size_t size) {
405 return this->report_network_status_answer_handler_(req, msg, data, size);
406 });
407 this->haier_protocol_.set_message_handler(haier_protocol::FrameType::ALARM_STATUS,
408 [this](haier_protocol::FrameType type, const uint8_t *data, size_t size) {
409 return this->alarm_status_message_handler_(type, data, size);
410 });
411}
412
415 ESP_LOGCONFIG(TAG,
416 " Protocol version: hOn\n"
417 " Control method: %d",
418 (uint8_t) this->control_method_);
419 if (this->hvac_hardware_info_.has_value()) {
420 ESP_LOGCONFIG(TAG,
421 " Device protocol version: %s\n"
422 " Device software version: %s\n"
423 " Device hardware version: %s\n"
424 " Device name: %s",
425 this->hvac_hardware_info_.value().protocol_version_,
426 this->hvac_hardware_info_.value().software_version_,
427 this->hvac_hardware_info_.value().hardware_version_, this->hvac_hardware_info_.value().device_name_);
428 ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
429 (this->hvac_hardware_info_.value().functions_[0] ? LOG_STR_LITERAL(" interactive") : ""),
430 (this->hvac_hardware_info_.value().functions_[1] ? LOG_STR_LITERAL(" controller-device") : ""),
431 (this->hvac_hardware_info_.value().functions_[2] ? LOG_STR_LITERAL(" crc") : ""),
432 (this->hvac_hardware_info_.value().functions_[3] ? LOG_STR_LITERAL(" multinode") : ""),
433 (this->hvac_hardware_info_.value().functions_[4] ? LOG_STR_LITERAL(" role") : ""));
434 ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
435 }
436}
437
438void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
439 switch (this->protocol_phase_) {
442 // Indicate device capabilities:
443 // bit 0 - if 1 module support interactive mode
444 // bit 1 - if 1 module support controller-device mode
445 // bit 2 - if 1 module support crc
446 // bit 3 - if 1 module support multiple devices
447 // bit 4..bit 15 - not used
448 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
449 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
450 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
451 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
452 }
453 break;
455 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
456 static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
457 this->send_message_(DEVICEID_REQUEST, this->use_crc_);
458 }
459 break;
462 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
463 static const haier_protocol::HaierMessage STATUS_REQUEST(
464 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
465 static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
466 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
468 (!this->should_get_big_data_())) {
469 this->send_message_(STATUS_REQUEST, this->use_crc_);
470 } else {
471 this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
472 }
473 this->last_status_request_ = now;
474 }
475 break;
476#ifdef USE_WIFI
478 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
479 static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
480 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
481 this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
482 this->last_signal_request_ = now;
483 }
484 break;
486 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
487 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
488 }
489 break;
490#else
494 break;
495#endif
498 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
499 static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
500 this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
501 this->last_alarm_request_ = now;
502 }
503 break;
505 if (this->control_messages_queue_.empty()) {
506 switch (this->control_method_) {
508 haier_protocol::HaierMessage control_message = this->get_control_message();
509 this->control_messages_queue_.push(control_message);
510 } break;
513 break;
515 ESP_LOGI(TAG, "AC control is disabled, monitor only");
516 this->reset_to_idle_();
517 return;
518 default:
519 ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
520 this->reset_to_idle_();
521 return;
522 }
523 }
524 if (this->control_messages_queue_.empty()) {
525 ESP_LOGW(TAG, "Control message queue is empty!");
526 this->reset_to_idle_();
527 } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
528 ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
529 this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
531 }
532 break;
534 if (this->action_request_.has_value()) {
535 if (this->action_request_.value().message.has_value()) {
536 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
537 this->action_request_.value().message.reset(); // NOLINT(bugprone-unchecked-optional-access)
538 } else {
539 // Message already sent, reseting request and return to idle
540 this->action_request_.reset();
542 }
543 } else {
544 ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
546 }
547 break;
551 this->forced_request_status_ = false;
552 } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
553 ALARM_STATUS_REQUEST_INTERVAL_MS) {
555 }
556#ifdef USE_WIFI
557 else if (this->send_wifi_signal_ &&
558 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
559 SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) {
561 }
562#endif
563 } break;
564 default:
565 // Shouldn't get here
566 ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
569 break;
570 }
571}
572
573haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
574 if (state) {
575 static haier_protocol::HaierMessage power_on_message(
576 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
577 std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
578 return power_on_message;
579 } else {
580 static haier_protocol::HaierMessage power_off_message(
581 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
582 std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
583 return power_off_message;
584 }
585}
586
589 constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
590 this->hon_rtc_ = this->make_entity_preference<HonSettings>(restore_settings_version);
591 HonSettings recovered;
592 if (this->hon_rtc_.load(&recovered)) {
593 this->settings_ = recovered;
594 } else {
596 }
597 bool settings_fixed = false;
598 if (is_auto(this->settings_.last_vertiacal_swing)) {
600 settings_fixed = true;
601 }
602 if (is_auto(this->settings_.last_horizontal_swing)) {
604 settings_fixed = true;
605 }
606 if (settings_fixed) {
607 this->hon_rtc_.save(&this->settings_);
608 }
612}
613
614haier_protocol::HaierMessage HonClimate::get_control_message() {
615 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
616 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
618 control_out_buffer[4] = 0; // This byte should be cleared before setting values
619 bool has_hvac_settings = false;
620 if (this->current_hvac_settings_.valid) {
621 has_hvac_settings = true;
622 HvacSettings &climate_control = this->current_hvac_settings_;
623 if (climate_control.mode.has_value()) {
624 switch (climate_control.mode.value()) {
625 case CLIMATE_MODE_OFF:
626 out_data->ac_power = 0;
627 break;
629 out_data->ac_power = 1;
630 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
631 out_data->fan_mode = this->other_modes_fan_speed_;
632 break;
634 out_data->ac_power = 1;
635 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
636 out_data->fan_mode = this->other_modes_fan_speed_;
637 break;
638 case CLIMATE_MODE_DRY:
639 out_data->ac_power = 1;
640 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
641 out_data->fan_mode = this->other_modes_fan_speed_;
642 break;
644 out_data->ac_power = 1;
645 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
646 out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
647 // Disabling boost for Fan only
648 out_data->fast_mode = 0;
649 break;
651 out_data->ac_power = 1;
652 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
653 out_data->fan_mode = this->other_modes_fan_speed_;
654 break;
655 default:
656 ESP_LOGE("Control", "Unsupported climate mode");
657 break;
658 }
659 }
660 // Set fan speed, if we are in fan mode, reject auto in fan mode
661 if (climate_control.fan_mode.has_value()) {
662 switch (climate_control.fan_mode.value()) {
663 case CLIMATE_FAN_LOW:
664 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
665 break;
667 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
668 break;
669 case CLIMATE_FAN_HIGH:
670 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
671 break;
672 case CLIMATE_FAN_AUTO:
673 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
674 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
675 break;
676 default:
677 ESP_LOGE("Control", "Unsupported fan mode");
678 break;
679 }
680 }
681 // Set swing mode
682 if (climate_control.swing_mode.has_value()) {
683 switch (climate_control.swing_mode.value()) {
685 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
686 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
687 break;
689 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
691 break;
694 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
695 break;
699 break;
700 }
701 }
702 if (climate_control.target_temperature.has_value()) {
703 float target_temp = climate_control.target_temperature.value();
704 out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
705 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49f) ? 1 : 0;
706 }
707 if (out_data->ac_power == 0) {
708 // If AC is off - no presets allowed
709 out_data->fast_mode = 0;
710 out_data->sleep_mode = 0;
711 } else if (climate_control.preset.has_value()) {
712 switch (climate_control.preset.value()) {
714 out_data->fast_mode = 0;
715 out_data->sleep_mode = 0;
716 out_data->ten_degree = 0;
717 break;
719 // Boost is not supported in Fan only mode
720 out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
721 out_data->sleep_mode = 0;
722 out_data->ten_degree = 0;
723 break;
725 out_data->fast_mode = 0;
726 out_data->sleep_mode = 0;
727 // 10 degrees allowed only in heat mode
728 out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
729 break;
731 out_data->fast_mode = 0;
732 out_data->sleep_mode = 1;
733 out_data->ten_degree = 0;
734 break;
735 default:
736 ESP_LOGE("Control", "Unsupported preset");
737 out_data->fast_mode = 0;
738 out_data->sleep_mode = 0;
739 out_data->ten_degree = 0;
740 break;
741 }
742 }
743 }
744 const ClimateSwingMode target_swing_mode = this->current_hvac_settings_.swing_mode.value_or(this->swing_mode);
745 if (this->pending_vertical_direction_.has_value()) {
746 if (!vertical_axis_is_auto(target_swing_mode)) {
747 out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
748 } else {
749 ESP_LOGD(TAG, "Vertical swing is on; the position is kept for when it is turned off");
750 }
751 this->pending_vertical_direction_.reset();
752 }
753 if (this->pending_horizontal_direction_.has_value()) {
754 if (!horizontal_axis_is_auto(target_swing_mode)) {
755 out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
756 } else {
757 ESP_LOGD(TAG, "Horizontal swing is on; the position is kept for when it is turned off");
758 }
759 this->pending_horizontal_direction_.reset();
760 }
761 {
762 // Quiet mode
763 if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
764 // If AC is off or in fan only mode - no quiet mode allowed
765 out_data->quiet_mode = 0;
766 } else {
767 out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
768 }
769 // Clean quiet mode state pending flag
770 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
771 }
772 out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
773 out_data->display_status = this->get_display_state() ? 1 : 0;
774 this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
775 out_data->health_mode = this->get_health_mode() ? 1 : 0;
776 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
777 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
779 control_out_buffer, this->real_control_packet_size_);
780}
781
782void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
783 constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
784 if (size >= active_alarms_size + 2) {
785 if (check_new) {
786 size_t alarm_code = 0;
787 for (int i = active_alarms_size - 1; i >= 0; i--) {
788 if (packet[2 + i] != active_alarms_[i]) {
789 uint8_t alarm_bit = 1;
790 for (int b = 0; b < 8; b++) {
791 if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
792 bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
793 int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
794#ifdef USE_ESP8266
795 // Static so a delayed on_alarm automation can still read it, but only until the next
796 // alarm change, including a later one in this packet; `code` stays correct
797 static char alarm_message[HonAlarmMessages::MAX_LENGTH + 1];
798 ESPHOME_strncpy_P(alarm_message,
799 reinterpret_cast<const char *>(
800 HonAlarmMessages::get_progmem_str(alarm_code, HonAlarmMessages::LAST_INDEX)),
801 sizeof(alarm_message));
802#else
803 const char *alarm_message = HonAlarmMessages::get_progmem_str(alarm_code, HonAlarmMessages::LAST_INDEX);
804#endif
805 esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
806 alarm_code, alarm_message);
807 if (alarm_status) {
808 this->alarm_start_callback_.call(alarm_code, alarm_message);
809 this->active_alarm_count_ += 1.0f;
810 } else {
811 this->alarm_end_callback_.call(alarm_code, alarm_message);
812 this->active_alarm_count_ -= 1.0f;
813 }
814 }
815 alarm_bit <<= 1;
816 alarm_code++;
817 }
818 active_alarms_[i] = packet[2 + i];
819 } else {
820 alarm_code += 8;
821 }
822 }
823 } else {
824 float alarm_count = 0.0f;
825 static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
826 for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
827 alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
828 }
829 this->active_alarm_count_ = alarm_count;
830 memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
831 }
832 }
833}
834
835#ifdef USE_SENSOR
839 if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
840 this->big_data_sensors_--;
841 } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
842 this->big_data_sensors_++;
843 }
844 }
845 this->sub_sensors_[(size_t) type] = sens;
846 }
847}
848
851 size_t index = (size_t) type;
852 if ((this->sub_sensors_[index] != nullptr) &&
853 ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->get_raw_state() != value)))
854 this->sub_sensors_[index]->publish_state(value);
855 }
856}
857#endif // USE_SENSOR
858
859#ifdef USE_BINARY_SENSOR
862 if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
863 this->big_data_sensors_--;
864 } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
865 this->big_data_sensors_++;
866 }
867 this->sub_binary_sensors_[(size_t) type] = sens;
868 }
869}
870
872 if (value < 2) {
873 bool converted_value = value == 1;
874 size_t index = (size_t) type;
875 if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
876 (this->sub_binary_sensors_[index]->state != converted_value)))
877 this->sub_binary_sensors_[index]->publish_state(converted_value);
878 }
879}
880#endif // USE_BINARY_SENSOR
881
882#ifdef USE_TEXT_SENSOR
884 this->sub_text_sensors_[(size_t) type] = sens;
885 switch (type) {
887 if (this->hvac_hardware_info_.has_value())
888 sens->publish_state(this->hvac_hardware_info_.value().device_name_);
889 break;
891 if (this->hvac_hardware_info_.has_value())
892 sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
893 break;
896 break;
897 default:
898 break;
899 }
900}
901
903 size_t index = (size_t) type;
904 if (this->sub_text_sensors_[index] != nullptr)
905 this->sub_text_sensors_[index]->publish_state(value);
906}
907#endif // USE_TEXT_SENSOR
908
909#ifdef USE_SWITCH
911 this->beeper_switch_ = sw;
912 if (this->beeper_switch_ != nullptr) {
914 }
915}
916
923#endif // USE_SWITCH
924
925#ifdef USE_SELECT
926template<typename Mode, size_t N>
927static void publish_airflow_index(select::Select *sel, const std::array<Mode, N> &order, Mode mode) {
928 if (sel == nullptr) {
929 return;
930 }
931 // Saved settings never hold AUTO after initialization(); any other value outside the table shows as Center
932 size_t index = N;
933 size_t center = 0;
934 for (size_t i = 0; i < N; i++) {
935 const Mode entry = airflow_order_at(order, i);
936 if (entry == mode) {
937 index = i;
938 } else if (entry == Mode::CENTER) {
939 center = i;
940 }
941 }
942 if (index == N) {
943 index = center;
944 }
945 const auto active = sel->active_index();
946 if (active.has_value() && *active == index) {
947 return;
948 }
949 sel->publish_state(index);
950}
951
953 publish_airflow_index(this->vertical_airflow_select_, VERTICAL_SWING_MODES_ORDER,
955 publish_airflow_index(this->horizontal_airflow_select_, HORIZONTAL_SWING_MODES_ORDER,
957}
958#endif // USE_SELECT
959
960haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
961 size_t expected_size =
963 if (size < expected_size) {
964 ESP_LOGW(TAG, "Unexpected message size %u (expexted >= %zu)", size, expected_size);
965 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
966 }
967 uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
968 if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
969 // Got BigData packet
970 const hon_protocol::HaierPacketBigData *bd_packet =
971 (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
972#ifdef USE_SENSOR
978 this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
981 encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
982 this->update_sub_sensor_(
984 encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
985#endif // USE_SENSOR
986#ifdef USE_BINARY_SENSOR
994#endif // USE_BINARY_SENSOR
995 }
996 struct {
999 } packet;
1000 memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
1002 memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
1004 if (packet.sensors.error_status != 0) {
1005 ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
1006 }
1007#ifdef USE_SENSOR
1008 if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
1009 (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
1010 this->got_valid_outdoor_temp_ = true;
1012 (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
1013 }
1014 if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
1015 this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
1016 }
1017#endif // USE_SENSOR
1018 bool should_publish = false;
1019 {
1020 // Extra modes/presets
1021 optional<ClimatePreset> old_preset = this->preset;
1022 if (packet.control.fast_mode != 0) {
1024 } else if (packet.control.sleep_mode != 0) {
1026 } else if (packet.control.ten_degree != 0) {
1028 } else {
1030 }
1031 should_publish = should_publish || (!old_preset.has_value()) ||
1032 (old_preset.value_or(CLIMATE_PRESET_NONE) != this->preset.value_or(CLIMATE_PRESET_NONE));
1033 }
1034 {
1035 // Target temperature
1036 float old_target_temperature = this->target_temperature;
1037 this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
1038 should_publish = should_publish || (old_target_temperature != this->target_temperature);
1039 }
1040 {
1041 // Current temperature
1042 float old_current_temperature = this->current_temperature;
1043 this->current_temperature = packet.sensors.room_temperature / 2.0f;
1044 should_publish = should_publish || (old_current_temperature != this->current_temperature);
1045 }
1046 {
1047 // Fan mode
1048 optional<ClimateFanMode> old_fan_mode = this->fan_mode;
1049 // remember the fan speed we last had for climate vs fan
1050 if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
1051 if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
1052 this->fan_mode_speed_ = packet.control.fan_mode;
1053 } else {
1054 this->other_modes_fan_speed_ = packet.control.fan_mode;
1055 }
1056 switch (packet.control.fan_mode) {
1057 case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
1058 if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
1059 this->fan_mode = CLIMATE_FAN_AUTO;
1060 } else {
1061 // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
1062 ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
1063 }
1064 break;
1065 case (uint8_t) hon_protocol::FanMode::FAN_MID:
1067 break;
1068 case (uint8_t) hon_protocol::FanMode::FAN_LOW:
1069 this->fan_mode = CLIMATE_FAN_LOW;
1070 break;
1071 case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
1072 this->fan_mode = CLIMATE_FAN_HIGH;
1073 break;
1074 default:
1075 break;
1076 }
1077 should_publish = should_publish || (!old_fan_mode.has_value()) ||
1078 (old_fan_mode.value_or(CLIMATE_FAN_ON) != this->fan_mode.value_or(CLIMATE_FAN_ON));
1079 }
1080 // Display status
1081 // should be before "Climate mode" because it is changing this->mode
1082 if (packet.control.ac_power != 0) {
1083 // if AC is off display status always ON so process it only when AC is on
1084 bool disp_status = packet.control.display_status != 0;
1085 if (disp_status != this->get_display_state()) {
1086 // Do something only if display status changed
1087 if (this->mode == CLIMATE_MODE_OFF) {
1088 // AC just turned on from remote need to turn off display
1089 this->force_send_control_ = true;
1090 } else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
1091 this->display_status_ = disp_status ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
1092 }
1093 }
1094 }
1095 // Health mode
1096 if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
1097 bool old_health_mode = this->get_health_mode();
1098 this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
1099 should_publish = should_publish || (old_health_mode != this->get_health_mode());
1100 }
1101 {
1102 CleaningState new_cleaning;
1103 if (packet.control.steri_clean == 1) {
1104 // Steri-cleaning
1105 new_cleaning = CleaningState::STERI_CLEAN;
1106 } else if (packet.control.self_cleaning_status == 1) {
1107 // Self-cleaning
1108 new_cleaning = CleaningState::SELF_CLEAN;
1109 } else {
1110 // No cleaning
1111 new_cleaning = CleaningState::NO_CLEANING;
1112 }
1113 if (new_cleaning != this->cleaning_status_) {
1114 ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
1115 if (new_cleaning == CleaningState::NO_CLEANING) {
1116 // Turning AC off after cleaning
1117 this->action_request_ =
1118 PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
1119 }
1120 this->cleaning_status_ = new_cleaning;
1121#ifdef USE_TEXT_SENSOR
1123#endif // USE_TEXT_SENSOR
1124 }
1125 }
1126 {
1127 // Climate mode
1128 ClimateMode old_mode = this->mode;
1129 if (packet.control.ac_power == 0) {
1130 this->mode = CLIMATE_MODE_OFF;
1131 } else {
1132 // Check current hvac mode
1133 switch (packet.control.ac_mode) {
1135 this->mode = CLIMATE_MODE_COOL;
1136 break;
1138 this->mode = CLIMATE_MODE_HEAT;
1139 break;
1141 this->mode = CLIMATE_MODE_DRY;
1142 break;
1144 this->mode = CLIMATE_MODE_FAN_ONLY;
1145 break;
1147 this->mode = CLIMATE_MODE_HEAT_COOL;
1148 break;
1149 default:
1150 break;
1151 }
1152 }
1153 should_publish = should_publish || (old_mode != this->mode);
1154 }
1155 {
1156 // Quiet mode, should be after climate mode
1157 if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
1158 ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
1159 // In proper mode and not in pending state
1160 bool new_quiet_mode = packet.control.quiet_mode != 0;
1161 if (new_quiet_mode != this->get_quiet_mode_state()) {
1162 this->quiet_mode_state_ = new_quiet_mode ? SwitchState::SWITCH_ON : SwitchState::SWITCH_OFF;
1163 this->settings_.quiet_mode_state = new_quiet_mode;
1164#ifdef USE_SWITCH
1165 if (this->quiet_mode_switch_ != nullptr) {
1166 this->quiet_mode_switch_->publish_state(new_quiet_mode);
1167 }
1168#endif // USE_SWITCH
1169 this->hon_rtc_.save(&this->settings_);
1170 }
1171 }
1172 }
1173 {
1174 // Swing mode
1175 ClimateSwingMode old_swing_mode = this->swing_mode;
1176 const auto &swing_modes = traits_.get_supported_swing_modes();
1177 bool vertical_swing_supported = swing_modes.count(CLIMATE_SWING_VERTICAL);
1178 bool horizontal_swing_supported = swing_modes.count(CLIMATE_SWING_HORIZONTAL);
1179 if (horizontal_swing_supported &&
1180 (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
1181 if (vertical_swing_supported &&
1182 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1183 this->swing_mode = CLIMATE_SWING_BOTH;
1184 } else {
1185 this->swing_mode = CLIMATE_SWING_HORIZONTAL;
1186 }
1187 } else {
1188 if (vertical_swing_supported &&
1189 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1190 this->swing_mode = CLIMATE_SWING_VERTICAL;
1191 } else {
1192 this->swing_mode = CLIMATE_SWING_OFF;
1193 }
1194 }
1195 // Saving last known non auto mode for vertical and horizontal swing
1196 this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
1197 this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
1199 bool save_vertical = !in_debounce(this->vertical_direction_set_time_, now_ms) &&
1200 !is_auto(this->current_vertical_swing_.value()) &&
1202 bool save_horizontal = !in_debounce(this->horizontal_direction_set_time_, now_ms) &&
1203 !is_auto(this->current_horizontal_swing_.value()) &&
1205 if (save_vertical || save_horizontal) {
1206 if (save_vertical) {
1208 }
1209 if (save_horizontal) {
1211 }
1212 this->hon_rtc_.save(&this->settings_);
1213 }
1214#ifdef USE_SELECT
1216#endif
1217 should_publish = should_publish || (old_swing_mode != this->swing_mode);
1218 }
1219 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
1220 if (should_publish) {
1221 this->publish_state();
1222 }
1223 if (should_publish) {
1224 ESP_LOGI(TAG, "HVAC values changed");
1225 }
1226 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1227 esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1228 esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1229 esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1230 esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1231 esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1232 return haier_protocol::HandlerError::HANDLER_OK;
1233}
1234
1236 if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1237 return;
1239 HvacSettings climate_control;
1240 climate_control = this->current_hvac_settings_;
1241 // Beeper command
1242 {
1243 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1246 this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
1247 }
1248 // Health mode
1249 {
1250 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1253 this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
1254 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
1255 }
1256 // Climate mode
1257 ClimateMode climate_mode = this->mode;
1258 bool new_power = this->mode != CLIMATE_MODE_OFF;
1259 uint8_t fan_mode_buf[] = {0x00, 0xFF};
1260 uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1261 if (climate_control.mode.has_value()) {
1262 climate_mode = climate_control.mode.value();
1263 uint8_t buffer[2] = {0x00, 0x00};
1264 switch (climate_control.mode.value()) {
1265 case CLIMATE_MODE_OFF:
1266 new_power = false;
1267 break;
1269 new_power = true;
1271 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1274 buffer, 2);
1275 fan_mode_buf[1] = this->other_modes_fan_speed_;
1276 break;
1277 case CLIMATE_MODE_HEAT:
1278 new_power = true;
1280 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1283 buffer, 2);
1284 fan_mode_buf[1] = this->other_modes_fan_speed_;
1285 break;
1286 case CLIMATE_MODE_DRY:
1287 new_power = true;
1289 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1292 buffer, 2);
1293 fan_mode_buf[1] = this->other_modes_fan_speed_;
1294 break;
1296 new_power = true;
1298 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1301 buffer, 2);
1302 fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1303 break;
1304 case CLIMATE_MODE_COOL:
1305 new_power = true;
1307 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1310 buffer, 2);
1311 fan_mode_buf[1] = this->other_modes_fan_speed_;
1312 break;
1313 default:
1314 ESP_LOGE("Control", "Unsupported climate mode");
1315 break;
1316 }
1317 }
1318 // Climate power
1319 {
1320 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1323 new_power ? ONE_BUF : ZERO_BUF, 2);
1324 }
1325 // CLimate preset
1326 {
1327 uint8_t fast_mode_buf[] = {0x00, 0xFF};
1328 uint8_t away_mode_buf[] = {0x00, 0xFF};
1329 if (!new_power) {
1330 // If AC is off - no presets allowed
1331 fast_mode_buf[1] = 0x00;
1332 away_mode_buf[1] = 0x00;
1333 } else if (climate_control.preset.has_value()) {
1334 switch (climate_control.preset.value()) {
1336 fast_mode_buf[1] = 0x00;
1337 away_mode_buf[1] = 0x00;
1338 break;
1340 // Boost is not supported in Fan only mode
1341 fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1342 away_mode_buf[1] = 0x00;
1343 break;
1345 fast_mode_buf[1] = 0x00;
1346 away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1347 break;
1348 default:
1349 ESP_LOGE("Control", "Unsupported preset");
1350 break;
1351 }
1352 }
1353 {
1354 // Quiet mode
1355 if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
1356 quiet_mode_buf[1] = 0x01;
1357 } else {
1358 quiet_mode_buf[1] = 0x00;
1359 }
1360 // Clean quiet mode state pending flag
1361 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
1362 }
1363 auto presets = this->traits_.get_supported_presets();
1364 if (quiet_mode_buf[1] != 0xFF) {
1365 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1368 quiet_mode_buf, 2);
1369 }
1370 if ((fast_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_BOOST)) {
1371 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1374 fast_mode_buf, 2);
1375 }
1376 if ((away_mode_buf[1] != 0xFF) && presets.count(climate::ClimatePreset::CLIMATE_PRESET_AWAY)) {
1377 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1380 away_mode_buf, 2);
1381 }
1382 }
1383 // Target temperature
1384 if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1385 uint8_t buffer[2] = {0x00, 0x00};
1386 buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1387 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1390 buffer, 2);
1391 }
1392 ClimateSwingMode target_swing_mode = this->swing_mode;
1393 // Vertical swing mode
1394 if (climate_control.swing_mode.has_value()) {
1395 target_swing_mode = climate_control.swing_mode.value();
1396 uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1397 uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1398 switch (climate_control.swing_mode.value()) {
1399 case CLIMATE_SWING_OFF:
1400 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1401 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1402 break;
1404 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1405 break;
1407 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1408 break;
1409 case CLIMATE_SWING_BOTH:
1410 break;
1411 }
1412 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1415 horizontal_swing_buf, 2);
1416 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1419 vertical_swing_buf, 2);
1420 }
1421 // A swing change above already sent both axes from settings_.last_*, which set_*_airflow() updated
1422 const bool swing_sent = climate_control.swing_mode.has_value();
1423 if (this->pending_vertical_direction_.has_value()) {
1424 if (vertical_axis_is_auto(target_swing_mode)) {
1425 ESP_LOGD(TAG, "Vertical swing is on; the position is kept for when it is turned off");
1426 } else if (!swing_sent) {
1427 uint8_t vertical_swing_buf[] = {0x00, (uint8_t) this->pending_vertical_direction_.value()};
1428 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1431 vertical_swing_buf, 2);
1432 }
1433 this->pending_vertical_direction_.reset();
1434 }
1435 if (this->pending_horizontal_direction_.has_value()) {
1436 if (horizontal_axis_is_auto(target_swing_mode)) {
1437 ESP_LOGD(TAG, "Horizontal swing is on; the position is kept for when it is turned off");
1438 } else if (!swing_sent) {
1439 uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) this->pending_horizontal_direction_.value()};
1440 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1443 horizontal_swing_buf, 2);
1444 }
1445 this->pending_horizontal_direction_.reset();
1446 }
1447 // Fan mode
1448 if (climate_control.fan_mode.has_value()) {
1449 switch (climate_control.fan_mode.value()) {
1450 case CLIMATE_FAN_LOW:
1451 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1452 break;
1453 case CLIMATE_FAN_MEDIUM:
1454 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1455 break;
1456 case CLIMATE_FAN_HIGH:
1457 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1458 break;
1459 case CLIMATE_FAN_AUTO:
1460 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1461 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1462 break;
1463 default:
1464 ESP_LOGE("Control", "Unsupported fan mode");
1465 break;
1466 }
1467 if (fan_mode_buf[1] != 0xFF) {
1468 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1471 fan_mode_buf, 2);
1472 }
1473 }
1474}
1475
1477 while (!this->control_messages_queue_.empty())
1478 this->control_messages_queue_.pop();
1479}
1480
1482 auto &action_request = this->action_request_.value(); // NOLINT(bugprone-unchecked-optional-access)
1483 switch (action_request.action) {
1486 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1487 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1488 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1489 out_data->self_cleaning_status = 1;
1490 out_data->steri_clean = 0;
1491 out_data->set_point = 0x06;
1494 out_data->ac_power = 1;
1495 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1496 out_data->light_status = 0;
1497 action_request.message = haier_protocol::HaierMessage(
1498 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1499 control_out_buffer, this->real_control_packet_size_);
1500 return true;
1502 action_request.message =
1503 haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1506 ONE_BUF, 2);
1507 return true;
1508 } else {
1509 this->action_request_.reset();
1510 return false;
1511 }
1514 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1515 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1516 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1517 out_data->self_cleaning_status = 0;
1518 out_data->steri_clean = 1;
1519 out_data->set_point = 0x06;
1522 out_data->ac_power = 1;
1523 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1524 out_data->light_status = 0;
1525 action_request.message = haier_protocol::HaierMessage(
1526 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1527 control_out_buffer, this->real_control_packet_size_);
1528 return true;
1529 } else {
1530 // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1531 this->action_request_.reset();
1532 return false;
1533 }
1534 default:
1536 }
1537}
1538
1541#ifdef USE_SENSOR
1542 for (auto &sub_sensor : this->sub_sensors_) {
1543 if ((sub_sensor != nullptr) && sub_sensor->has_state())
1544 sub_sensor->publish_state(NAN);
1545 }
1546#endif // USE_SENSOR
1547 this->got_valid_outdoor_temp_ = false;
1548 this->hvac_hardware_info_.reset();
1549 this->last_status_message_.reset(nullptr);
1550}
1551
1553 if (this->big_data_sensors_ > 0) {
1554 this->big_data_counter_ = (this->big_data_counter_ + 1) % 3;
1555 return this->big_data_counter_ == 1;
1556 }
1557 return false;
1558}
1559
1560} // namespace esphome::haier
BedjetMode mode
BedJet operating mode.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
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:317
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:311
float target_temperature
The target temperature of the climate device.
Definition climate.h:298
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:323
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:291
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:314
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)
select::Select * vertical_airflow_select_
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]
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:87
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)
uint32_t horizontal_direction_set_time_
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]
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
select::Select * horizontal_airflow_select_
esphome::optional< hon_protocol::HorizontalSwingMode > pending_horizontal_direction_
static constexpr size_t HARDWARE_INFO_STR_SIZE
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 selects.
Definition select.h:28
optional< size_t > active_index() const
Return the (optional) index offset of the currently active option.
Definition select.cpp:45
void publish_state(const std::string &state)
Definition select.h:35
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:65
Base class for all switches.
Definition switch.h:43
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:70
void publish_state(const std::string &state)
Definition text_sensor.h:42
ClimateSwingMode swing_mode
Definition climate.h:11
uint8_t type
FanDirection direction
Definition fan.h:5
bool state
Definition fan.h:2
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
@ 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.
Mode airflow_order_at(const std::array< Mode, N > &order, size_t index)
Definition hon_climate.h:42
constexpr uint8_t CONTROL_MESSAGE_RETRIES
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL
constexpr uint32_t AIRFLOW_SELECT_DEBOUNCE_MS
PROGMEM_STRING_TABLE(HonAlarmMessages, "Outdoor module failure", "Outdoor defrost sensor failure", "Outdoor compressor exhaust sensor failure", "Outdoor EEPROM abnormality", "Indoor coil sensor failure", "Indoor-outdoor communication failure", "Power supply overvoltage protection", "Communication failure between panel and indoor unit", "Outdoor compressor overheat protection", "Outdoor environmental sensor abnormality", "Full water protection", "Indoor EEPROM failure", "Outdoor out air sensor failure", "CBD and module communication failure", "Indoor DC fan failure", "Outdoor DC fan failure", "Door switch failure", "Dust filter needs cleaning reminder", "Water shortage protection", "Humidity sensor failure", "Indoor temperature sensor failure", "Manipulator limit failure", "Indoor PM2.5 sensor failure", "Outdoor PM2.5 sensor failure", "Indoor heating overload/high load alarm", "Outdoor AC current protection", "Outdoor compressor operation abnormality", "Outdoor DC current protection", "Outdoor no-load failure", "CT current abnormality", "Indoor cooling freeze protection", "High and low pressure protection", "Compressor out air temperature is too high", "Outdoor evaporator sensor failure", "Outdoor cooling overload", "Water pump drainage failure", "Three-phase power supply failure", "Four-way valve failure", "External alarm/scraper flow switch failure", "Temperature cutoff protection alarm", "Different mode operation failure", "Electronic expansion valve failure", "Dual heat source sensor Tw failure", "Communication failure with the wired controller", "Indoor unit address duplication failure", "50Hz zero crossing failure", "Outdoor unit failure", "Formaldehyde sensor failure", "VOC sensor failure", "CO2 sensor failure", "Firewall failure", "Unknown")
constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET
const uint8_t ZERO_BUF[]
ESPHOME_LOG_TAG(TAG, "haier.climate")
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:34
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:998
Application App
Global storage of Application pointer - only one Application can exist.
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:58
hon_protocol::VerticalSwingMode last_vertiacal_swing
Definition hon_climate.h:57