ESPHome 2025.6.3
Loading...
Searching...
No Matches
hon_climate.cpp
Go to the documentation of this file.
1#include <chrono>
2#include <string>
6#include "hon_climate.h"
7#include "hon_packet.h"
8
9using namespace esphome::climate;
10using namespace esphome::uart;
11
12namespace esphome {
13namespace haier {
14
15static const char *const TAG = "haier.climate";
16constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000;
18constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
19constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
20constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
21const uint8_t ONE_BUF[] = {0x00, 0x01};
22const uint8_t ZERO_BUF[] = {0x00, 0x00};
23
25 : cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
26 0x00, 0x00, 0x00,
27 0x00, 0x00} {
30}
31
33
35 if (state != this->settings_.beeper_state) {
37#ifdef USE_SWITCH
38 if (this->beeper_switch_ != nullptr) {
39 this->beeper_switch_->publish_state(state);
40 }
41#endif
42 this->hon_rtc_.save(&this->settings_);
43 }
44}
45
47
49 if (state != this->get_quiet_mode_state()) {
50 if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
52 this->force_send_control_ = true;
53 } else {
55 }
57#ifdef USE_SWITCH
58 if (this->quiet_mode_switch_ != nullptr) {
60 }
61#endif
62 this->hon_rtc_.save(&this->settings_);
63 }
64}
65
69
73
78
82
87
89 switch (this->cleaning_status_) {
91 return "Self clean";
93 return "56°C Steri-Clean";
94 default:
95 return "No cleaning";
96 }
97}
98
100
103 ESP_LOGI(TAG, "Sending self cleaning start request");
104 this->action_request_ =
106 }
107}
108
111 ESP_LOGI(TAG, "Sending steri cleaning start request");
112 this->action_request_ =
114 }
115}
116
117void HonClimate::add_alarm_start_callback(std::function<void(uint8_t, const char *)> &&callback) {
118 this->alarm_start_callback_.add(std::move(callback));
119}
120
121void HonClimate::add_alarm_end_callback(std::function<void(uint8_t, const char *)> &&callback) {
122 this->alarm_end_callback_.add(std::move(callback));
123}
124
125haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type,
126 haier_protocol::FrameType message_type,
127 const uint8_t *data, size_t data_size) {
128 // Should check this before preprocess
129 if (message_type == haier_protocol::FrameType::INVALID) {
130 ESP_LOGW(TAG, "It looks like your ESPHome Haier climate configuration is wrong. You should use the smartAir2 "
131 "protocol instead of hOn");
133 return haier_protocol::HandlerError::INVALID_ANSWER;
134 }
135 haier_protocol::HandlerError result =
136 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_VERSION, message_type,
137 haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::SENDING_INIT_1);
138 if (result == haier_protocol::HandlerError::HANDLER_OK) {
139 if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) {
140 // Wrong structure
141 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
142 }
143 // All OK
145 char tmp[9];
146 tmp[8] = 0;
147 strncpy(tmp, answr->protocol_version, 8);
149 this->hvac_hardware_info_.value().protocol_version_ = std::string(tmp);
150 strncpy(tmp, answr->software_version, 8);
151 this->hvac_hardware_info_.value().software_version_ = std::string(tmp);
152 strncpy(tmp, answr->hardware_version, 8);
153 this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
154 strncpy(tmp, answr->device_name, 8);
155 this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
156#ifdef USE_TEXT_SENSOR
159 this->hvac_hardware_info_.value().protocol_version_);
160#endif
161 this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
162 this->hvac_hardware_info_.value().functions_[1] =
163 (answr->functions[1] & 0x02) != 0; // controller-device mode support
164 this->hvac_hardware_info_.value().functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support
165 this->hvac_hardware_info_.value().functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support
166 this->hvac_hardware_info_.value().functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support
167 this->use_crc_ = this->hvac_hardware_info_.value().functions_[2];
169 return result;
170 } else {
171 this->reset_phase_();
172 return result;
173 }
174}
175
176haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(haier_protocol::FrameType request_type,
177 haier_protocol::FrameType message_type,
178 const uint8_t *data, size_t data_size) {
179 haier_protocol::HandlerError result =
180 this->answer_preprocess_(request_type, haier_protocol::FrameType::GET_DEVICE_ID, message_type,
181 haier_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::SENDING_INIT_2);
182 if (result == haier_protocol::HandlerError::HANDLER_OK) {
184 return result;
185 } else {
186 this->reset_phase_();
187 return result;
188 }
189}
190
191haier_protocol::HandlerError HonClimate::status_handler_(haier_protocol::FrameType request_type,
192 haier_protocol::FrameType message_type, const uint8_t *data,
193 size_t data_size) {
194 haier_protocol::HandlerError result =
195 this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
196 haier_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN);
197 if (result == haier_protocol::HandlerError::HANDLER_OK) {
198 result = this->process_status_message_(data, data_size);
199 if (result != haier_protocol::HandlerError::HANDLER_OK) {
200 ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result);
201 this->reset_phase_();
202 this->action_request_.reset();
203 this->force_send_control_ = false;
204 } else {
205 if (!this->last_status_message_) {
208 this->last_status_message_.reset();
209 this->last_status_message_ = std::unique_ptr<uint8_t[]>(new uint8_t[this->real_control_packet_size_]);
210 };
211 if (data_size >= this->real_control_packet_size_ + 2) {
212 memcpy(this->last_status_message_.get(), data + 2 + this->status_message_header_size_,
213 this->real_control_packet_size_);
214 this->status_message_callback_.call((const char *) data, data_size);
215 } else {
216 ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, this->real_control_packet_size_);
217 }
218 switch (this->protocol_phase_) {
220 ESP_LOGI(TAG, "First HVAC status received");
222 break;
224 // Do nothing, phase will be changed in process_phase
225 break;
228 break;
230 if (!this->control_messages_queue_.empty())
231 this->control_messages_queue_.pop();
232 if (this->control_messages_queue_.empty()) {
234 this->force_send_control_ = false;
237 } else {
239 }
240 break;
241 default:
242 break;
243 }
244 }
245 return result;
246 } else {
247 this->action_request_.reset();
248 this->force_send_control_ = false;
249 this->reset_phase_();
250 return result;
251 }
252}
253
255 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
256 size_t data_size) {
257 haier_protocol::HandlerError result = this->answer_preprocess_(
258 request_type, haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION, message_type,
259 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
260 if (result == haier_protocol::HandlerError::HANDLER_OK) {
262 return result;
263 } else {
265 return result;
266 }
267}
268
269haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(haier_protocol::FrameType request_type,
270 haier_protocol::FrameType message_type,
271 const uint8_t *data, size_t data_size) {
272 if (request_type == haier_protocol::FrameType::GET_ALARM_STATUS) {
273 if (message_type != haier_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) {
274 // Unexpected answer to request
276 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
277 }
280 // Don't expect this answer now
282 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
283 }
284 if (data_size < sizeof(active_alarms_) + 2)
285 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
286 this->process_alarm_message_(data, data_size, this->protocol_phase_ >= ProtocolPhases::IDLE);
288 return haier_protocol::HandlerError::HANDLER_OK;
289 } else {
291 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
292 }
293}
294
295haier_protocol::HandlerError HonClimate::alarm_status_message_handler_(haier_protocol::FrameType type,
296 const uint8_t *buffer, size_t size) {
297 haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
298 if (size < sizeof(this->active_alarms_) + 2) {
299 // Log error but confirm anyway to avoid to many messages
300 result = haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
301 }
302 this->process_alarm_message_(buffer, size, true);
303 this->haier_protocol_.send_answer(haier_protocol::HaierMessage(haier_protocol::FrameType::CONFIRM));
304 this->last_alarm_request_ = std::chrono::steady_clock::now();
305 return result;
306}
307
309 // Set handlers
310 this->haier_protocol_.set_answer_handler(
311 haier_protocol::FrameType::GET_DEVICE_VERSION,
312 std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
313 std::placeholders::_3, std::placeholders::_4));
314 this->haier_protocol_.set_answer_handler(
315 haier_protocol::FrameType::GET_DEVICE_ID,
316 std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
317 std::placeholders::_3, std::placeholders::_4));
318 this->haier_protocol_.set_answer_handler(
319 haier_protocol::FrameType::CONTROL,
320 std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
321 std::placeholders::_4));
322 this->haier_protocol_.set_answer_handler(
323 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION,
324 std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1,
325 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
326 this->haier_protocol_.set_answer_handler(
327 haier_protocol::FrameType::GET_ALARM_STATUS,
328 std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
329 std::placeholders::_3, std::placeholders::_4));
330 this->haier_protocol_.set_answer_handler(
331 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
332 std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2,
333 std::placeholders::_3, std::placeholders::_4));
334 this->haier_protocol_.set_message_handler(
335 haier_protocol::FrameType::ALARM_STATUS,
336 std::bind(&HonClimate::alarm_status_message_handler_, this, std::placeholders::_1, std::placeholders::_2,
337 std::placeholders::_3));
338}
339
342 ESP_LOGCONFIG(TAG,
343 " Protocol version: hOn\n"
344 " Control method: %d",
345 (uint8_t) this->control_method_);
346 if (this->hvac_hardware_info_.has_value()) {
347 ESP_LOGCONFIG(TAG,
348 " Device protocol version: %s\n"
349 " Device software version: %s\n"
350 " Device hardware version: %s\n"
351 " Device name: %s",
352 this->hvac_hardware_info_.value().protocol_version_.c_str(),
353 this->hvac_hardware_info_.value().software_version_.c_str(),
354 this->hvac_hardware_info_.value().hardware_version_.c_str(),
355 this->hvac_hardware_info_.value().device_name_.c_str());
356 ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s",
357 (this->hvac_hardware_info_.value().functions_[0] ? " interactive" : ""),
358 (this->hvac_hardware_info_.value().functions_[1] ? " controller-device" : ""),
359 (this->hvac_hardware_info_.value().functions_[2] ? " crc" : ""),
360 (this->hvac_hardware_info_.value().functions_[3] ? " multinode" : ""),
361 (this->hvac_hardware_info_.value().functions_[4] ? " role" : ""));
362 ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str());
363 }
364}
365
366void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
367 switch (this->protocol_phase_) {
370 // Indicate device capabilities:
371 // bit 0 - if 1 module support interactive mode
372 // bit 1 - if 1 module support controller-device mode
373 // bit 2 - if 1 module support crc
374 // bit 3 - if 1 module support multiple devices
375 // bit 4..bit 15 - not used
376 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
377 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
378 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities));
379 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_);
380 }
381 break;
383 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
384 static const haier_protocol::HaierMessage DEVICEID_REQUEST(haier_protocol::FrameType::GET_DEVICE_ID);
385 this->send_message_(DEVICEID_REQUEST, this->use_crc_);
386 }
387 break;
390 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
391 static const haier_protocol::HaierMessage STATUS_REQUEST(
392 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_USER_DATA);
393 static const haier_protocol::HaierMessage BIG_DATA_REQUEST(
394 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::GET_BIG_DATA);
396 (!this->should_get_big_data_())) {
397 this->send_message_(STATUS_REQUEST, this->use_crc_);
398 } else {
399 this->send_message_(BIG_DATA_REQUEST, this->use_crc_);
400 }
401 this->last_status_request_ = now;
402 }
403 break;
404#ifdef USE_WIFI
406 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
407 static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST(
408 haier_protocol::FrameType::GET_MANAGEMENT_INFORMATION);
409 this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_);
410 this->last_signal_request_ = now;
411 }
412 break;
414 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
415 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
416 }
417 break;
418#else
422 break;
423#endif
426 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
427 static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST(haier_protocol::FrameType::GET_ALARM_STATUS);
428 this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_);
429 this->last_alarm_request_ = now;
430 }
431 break;
433 if (this->control_messages_queue_.empty()) {
434 switch (this->control_method_) {
436 haier_protocol::HaierMessage control_message = this->get_control_message();
437 this->control_messages_queue_.push(control_message);
438 } break;
441 break;
443 ESP_LOGI(TAG, "AC control is disabled, monitor only");
444 this->reset_to_idle_();
445 return;
446 default:
447 ESP_LOGW(TAG, "Unsupported control method for hOn protocol!");
448 this->reset_to_idle_();
449 return;
450 }
451 }
452 if (this->control_messages_queue_.empty()) {
453 ESP_LOGW(TAG, "Control message queue is empty!");
454 this->reset_to_idle_();
455 } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
456 ESP_LOGI(TAG, "Sending control packet, queue size %d", this->control_messages_queue_.size());
457 this->send_message_(this->control_messages_queue_.front(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
459 }
460 break;
462 if (this->action_request_.has_value()) {
463 if (this->action_request_.value().message.has_value()) {
464 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
465 this->action_request_.value().message.reset();
466 } else {
467 // Message already sent, reseting request and return to idle
468 this->action_request_.reset();
470 }
471 } else {
472 ESP_LOGW(TAG, "SENDING_ACTION_COMMAND phase without action request!");
474 }
475 break;
479 this->forced_request_status_ = false;
480 } else if (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_alarm_request_).count() >
483 }
484#ifdef USE_WIFI
485 else if (this->send_wifi_signal_ &&
486 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
489 }
490#endif
491 } break;
492 default:
493 // Shouldn't get here
494 ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
497 break;
498 }
499}
500
501haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
502 if (state) {
503 static haier_protocol::HaierMessage power_on_message(
504 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
505 std::initializer_list<uint8_t>({0x00, 0x01}).begin(), 2);
506 return power_on_message;
507 } else {
508 static haier_protocol::HaierMessage power_off_message(
509 haier_protocol::FrameType::CONTROL, ((uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER) + 1,
510 std::initializer_list<uint8_t>({0x00, 0x00}).begin(), 2);
511 return power_off_message;
512 }
513}
514
517 constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
518 this->hon_rtc_ =
519 global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
520 HonSettings recovered;
521 if (this->hon_rtc_.load(&recovered)) {
522 this->settings_ = recovered;
523 } else {
525 }
529}
530
531haier_protocol::HaierMessage HonClimate::get_control_message() {
532 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
533 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
535 control_out_buffer[4] = 0; // This byte should be cleared before setting values
536 bool has_hvac_settings = false;
537 if (this->current_hvac_settings_.valid) {
538 has_hvac_settings = true;
539 HvacSettings &climate_control = this->current_hvac_settings_;
540 if (climate_control.mode.has_value()) {
541 switch (climate_control.mode.value()) {
542 case CLIMATE_MODE_OFF:
543 out_data->ac_power = 0;
544 break;
546 out_data->ac_power = 1;
547 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO;
548 out_data->fan_mode = this->other_modes_fan_speed_;
549 break;
551 out_data->ac_power = 1;
552 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT;
553 out_data->fan_mode = this->other_modes_fan_speed_;
554 break;
555 case CLIMATE_MODE_DRY:
556 out_data->ac_power = 1;
557 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
558 out_data->fan_mode = this->other_modes_fan_speed_;
559 break;
561 out_data->ac_power = 1;
562 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN;
563 out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode
564 // Disabling boost for Fan only
565 out_data->fast_mode = 0;
566 break;
568 out_data->ac_power = 1;
569 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL;
570 out_data->fan_mode = this->other_modes_fan_speed_;
571 break;
572 default:
573 ESP_LOGE("Control", "Unsupported climate mode");
574 break;
575 }
576 }
577 // Set fan speed, if we are in fan mode, reject auto in fan mode
578 if (climate_control.fan_mode.has_value()) {
579 switch (climate_control.fan_mode.value()) {
580 case CLIMATE_FAN_LOW:
581 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW;
582 break;
584 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID;
585 break;
586 case CLIMATE_FAN_HIGH:
587 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
588 break;
589 case CLIMATE_FAN_AUTO:
590 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
591 out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
592 break;
593 default:
594 ESP_LOGE("Control", "Unsupported fan mode");
595 break;
596 }
597 }
598 // Set swing mode
599 if (climate_control.swing_mode.has_value()) {
600 switch (climate_control.swing_mode.value()) {
602 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
603 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
604 break;
606 out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
608 break;
611 out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
612 break;
616 break;
617 }
618 }
619 if (climate_control.target_temperature.has_value()) {
620 float target_temp = climate_control.target_temperature.value();
621 out_data->set_point = ((int) target_temp) - 16; // set the temperature with offset 16
622 out_data->half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
623 }
624 if (out_data->ac_power == 0) {
625 // If AC is off - no presets allowed
626 out_data->fast_mode = 0;
627 out_data->sleep_mode = 0;
628 } else if (climate_control.preset.has_value()) {
629 switch (climate_control.preset.value()) {
631 out_data->fast_mode = 0;
632 out_data->sleep_mode = 0;
633 out_data->ten_degree = 0;
634 break;
636 // Boost is not supported in Fan only mode
637 out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0;
638 out_data->sleep_mode = 0;
639 out_data->ten_degree = 0;
640 break;
642 out_data->fast_mode = 0;
643 out_data->sleep_mode = 0;
644 // 10 degrees allowed only in heat mode
645 out_data->ten_degree = (this->mode == CLIMATE_MODE_HEAT) ? 1 : 0;
646 break;
648 out_data->fast_mode = 0;
649 out_data->sleep_mode = 1;
650 out_data->ten_degree = 0;
651 break;
652 default:
653 ESP_LOGE("Control", "Unsupported preset");
654 out_data->fast_mode = 0;
655 out_data->sleep_mode = 0;
656 out_data->ten_degree = 0;
657 break;
658 }
659 }
660 }
662 out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
664 }
666 out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
668 }
669 {
670 // Quiet mode
671 if ((out_data->ac_power == 0) || (out_data->ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN)) {
672 // If AC is off or in fan only mode - no quiet mode allowed
673 out_data->quiet_mode = 0;
674 } else {
675 out_data->quiet_mode = this->get_quiet_mode_state() ? 1 : 0;
676 }
677 // Clean quiet mode state pending flag
678 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
679 }
680 out_data->beeper_status = ((!this->get_beeper_state()) || (!has_hvac_settings)) ? 1 : 0;
681 control_out_buffer[4] = 0; // This byte should be cleared before setting values
682 out_data->display_status = this->get_display_state() ? 1 : 0;
683 this->display_status_ = (SwitchState) ((uint8_t) this->display_status_ & 0b01);
684 out_data->health_mode = this->get_health_mode() ? 1 : 0;
685 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
686 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
688 control_out_buffer, this->real_control_packet_size_);
689}
690
691void HonClimate::process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new) {
692 constexpr size_t active_alarms_size = sizeof(this->active_alarms_);
693 if (size >= active_alarms_size + 2) {
694 if (check_new) {
695 size_t alarm_code = 0;
696 for (int i = active_alarms_size - 1; i >= 0; i--) {
697 if (packet[2 + i] != active_alarms_[i]) {
698 uint8_t alarm_bit = 1;
699 for (int b = 0; b < 8; b++) {
700 if ((packet[2 + i] & alarm_bit) != (this->active_alarms_[i] & alarm_bit)) {
701 bool alarm_status = (packet[2 + i] & alarm_bit) != 0;
702 int log_level = alarm_status ? ESPHOME_LOG_LEVEL_WARN : ESPHOME_LOG_LEVEL_INFO;
703 const char *alarm_message = alarm_code < esphome::haier::hon_protocol::HON_ALARM_COUNT
705 : "Unknown";
706 esp_log_printf_(log_level, TAG, __LINE__, "Alarm %s (%d): %s", alarm_status ? "activated" : "deactivated",
707 alarm_code, alarm_message);
708 if (alarm_status) {
709 this->alarm_start_callback_.call(alarm_code, alarm_message);
710 this->active_alarm_count_ += 1.0f;
711 } else {
712 this->alarm_end_callback_.call(alarm_code, alarm_message);
713 this->active_alarm_count_ -= 1.0f;
714 }
715 }
716 alarm_bit <<= 1;
717 alarm_code++;
718 }
719 active_alarms_[i] = packet[2 + i];
720 } else {
721 alarm_code += 8;
722 }
723 }
724 } else {
725 float alarm_count = 0.0f;
726 static uint8_t nibble_bits_count[] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
727 for (size_t i = 0; i < sizeof(this->active_alarms_); i++) {
728 alarm_count += (float) (nibble_bits_count[packet[2 + i] & 0x0F] + nibble_bits_count[packet[2 + i] >> 4]);
729 }
730 this->active_alarm_count_ = alarm_count;
731 memcpy(this->active_alarms_, packet + 2, sizeof(this->active_alarms_));
732 }
733 }
734}
735
736#ifdef USE_SENSOR
740 if ((this->sub_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
741 this->big_data_sensors_--;
742 } else if ((this->sub_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
743 this->big_data_sensors_++;
744 }
745 }
746 this->sub_sensors_[(size_t) type] = sens;
747 }
748}
749
752 size_t index = (size_t) type;
753 if ((this->sub_sensors_[index] != nullptr) &&
754 ((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
755 this->sub_sensors_[index]->publish_state(value);
756 }
757}
758#endif // USE_SENSOR
759
760#ifdef USE_BINARY_SENSOR
763 if ((this->sub_binary_sensors_[(size_t) type] != nullptr) && (sens == nullptr)) {
764 this->big_data_sensors_--;
765 } else if ((this->sub_binary_sensors_[(size_t) type] == nullptr) && (sens != nullptr)) {
766 this->big_data_sensors_++;
767 }
768 this->sub_binary_sensors_[(size_t) type] = sens;
769 }
770}
771
773 if (value < 2) {
774 bool converted_value = value == 1;
775 size_t index = (size_t) type;
776 if ((this->sub_binary_sensors_[index] != nullptr) && ((!this->sub_binary_sensors_[index]->has_state()) ||
777 (this->sub_binary_sensors_[index]->state != converted_value)))
778 this->sub_binary_sensors_[index]->publish_state(converted_value);
779 }
780}
781#endif // USE_BINARY_SENSOR
782
783#ifdef USE_TEXT_SENSOR
785 this->sub_text_sensors_[(size_t) type] = sens;
786 switch (type) {
788 if (this->hvac_hardware_info_.has_value())
789 sens->publish_state(this->hvac_hardware_info_.value().device_name_);
790 break;
792 if (this->hvac_hardware_info_.has_value())
793 sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
794 break;
797 break;
798 default:
799 break;
800 }
801}
802
804 size_t index = (size_t) type;
805 if (this->sub_text_sensors_[index] != nullptr)
806 this->sub_text_sensors_[index]->publish_state(value);
807}
808#endif // USE_TEXT_SENSOR
809
810#ifdef USE_SWITCH
812 this->beeper_switch_ = sw;
813 if (this->beeper_switch_ != nullptr) {
815 }
816}
817
824#endif // USE_SWITCH
825
826haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
827 size_t expected_size =
829 if (size < expected_size) {
830 ESP_LOGW(TAG, "Unexpected message size %d (expexted >= %d)", size, expected_size);
831 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
832 }
833 uint16_t subtype = (((uint16_t) packet_buffer[0]) << 8) + packet_buffer[1];
834 if ((subtype == 0x7D01) && (size >= expected_size + sizeof(hon_protocol::HaierPacketBigData))) {
835 // Got BigData packet
836 const hon_protocol::HaierPacketBigData *bd_packet =
837 (const hon_protocol::HaierPacketBigData *) (&packet_buffer[expected_size]);
838#ifdef USE_SENSOR
844 this->update_sub_sensor_(SubSensorType::POWER, encode_uint16(bd_packet->power[0], bd_packet->power[1]));
847 encode_uint16(bd_packet->compressor_current[0], bd_packet->compressor_current[1]) / 10.0);
848 this->update_sub_sensor_(
850 encode_uint16(bd_packet->expansion_valve_open_degree[0], bd_packet->expansion_valve_open_degree[1]) / 4095.0);
851#endif // USE_SENSOR
852#ifdef USE_BINARY_SENSOR
860#endif // USE_BINARY_SENSOR
861 }
862 struct {
865 } packet;
866 memcpy(&packet.control, packet_buffer + 2 + this->status_message_header_size_,
868 memcpy(&packet.sensors, packet_buffer + 2 + this->status_message_header_size_ + this->real_control_packet_size_,
870 if (packet.sensors.error_status != 0) {
871 ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status);
872 }
873#ifdef USE_SENSOR
874 if ((this->sub_sensors_[(size_t) SubSensorType::OUTDOOR_TEMPERATURE] != nullptr) &&
875 (this->got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) {
876 this->got_valid_outdoor_temp_ = true;
878 (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET));
879 }
880 if ((this->sub_sensors_[(size_t) SubSensorType::HUMIDITY] != nullptr) && (packet.sensors.room_humidity <= 100)) {
881 this->update_sub_sensor_(SubSensorType::HUMIDITY, (float) packet.sensors.room_humidity);
882 }
883#endif // USE_SENSOR
884 bool should_publish = false;
885 {
886 // Extra modes/presets
887 optional<ClimatePreset> old_preset = this->preset;
888 if (packet.control.fast_mode != 0) {
890 } else if (packet.control.sleep_mode != 0) {
892 } else if (packet.control.ten_degree != 0) {
894 } else {
896 }
897 should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value());
898 }
899 {
900 // Target temperature
901 float old_target_temperature = this->target_temperature;
902 this->target_temperature = packet.control.set_point + 16.0f + ((packet.control.half_degree == 1) ? 0.5f : 0.0f);
903 should_publish = should_publish || (old_target_temperature != this->target_temperature);
904 }
905 {
906 // Current temperature
907 float old_current_temperature = this->current_temperature;
908 this->current_temperature = packet.sensors.room_temperature / 2.0f;
909 should_publish = should_publish || (old_current_temperature != this->current_temperature);
910 }
911 {
912 // Fan mode
913 optional<ClimateFanMode> old_fan_mode = this->fan_mode;
914 // remember the fan speed we last had for climate vs fan
915 if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) {
916 if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO)
917 this->fan_mode_speed_ = packet.control.fan_mode;
918 } else {
919 this->other_modes_fan_speed_ = packet.control.fan_mode;
920 }
921 switch (packet.control.fan_mode) {
922 case (uint8_t) hon_protocol::FanMode::FAN_AUTO:
923 if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) {
925 } else {
926 // Shouldn't accept fan speed auto in fan-only mode even if AC reports it
927 ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring");
928 }
929 break;
930 case (uint8_t) hon_protocol::FanMode::FAN_MID:
932 break;
933 case (uint8_t) hon_protocol::FanMode::FAN_LOW:
935 break;
936 case (uint8_t) hon_protocol::FanMode::FAN_HIGH:
938 break;
939 }
940 should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
941 }
942 // Display status
943 // should be before "Climate mode" because it is changing this->mode
944 if (packet.control.ac_power != 0) {
945 // if AC is off display status always ON so process it only when AC is on
946 bool disp_status = packet.control.display_status != 0;
947 if (disp_status != this->get_display_state()) {
948 // Do something only if display status changed
949 if (this->mode == CLIMATE_MODE_OFF) {
950 // AC just turned on from remote need to turn off display
951 this->force_send_control_ = true;
952 } else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
953 this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
954 }
955 }
956 }
957 // Health mode
958 if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
959 bool old_health_mode = this->get_health_mode();
960 this->health_mode_ = packet.control.health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
961 should_publish = should_publish || (old_health_mode != this->get_health_mode());
962 }
963 {
964 CleaningState new_cleaning;
965 if (packet.control.steri_clean == 1) {
966 // Steri-cleaning
967 new_cleaning = CleaningState::STERI_CLEAN;
968 } else if (packet.control.self_cleaning_status == 1) {
969 // Self-cleaning
970 new_cleaning = CleaningState::SELF_CLEAN;
971 } else {
972 // No cleaning
973 new_cleaning = CleaningState::NO_CLEANING;
974 }
975 if (new_cleaning != this->cleaning_status_) {
976 ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning);
977 if (new_cleaning == CleaningState::NO_CLEANING) {
978 // Turning AC off after cleaning
979 this->action_request_ =
981 }
982 this->cleaning_status_ = new_cleaning;
983#ifdef USE_TEXT_SENSOR
985#endif // USE_TEXT_SENSOR
986 }
987 }
988 {
989 // Climate mode
990 ClimateMode old_mode = this->mode;
991 if (packet.control.ac_power == 0) {
992 this->mode = CLIMATE_MODE_OFF;
993 } else {
994 // Check current hvac mode
995 switch (packet.control.ac_mode) {
997 this->mode = CLIMATE_MODE_COOL;
998 break;
1000 this->mode = CLIMATE_MODE_HEAT;
1001 break;
1003 this->mode = CLIMATE_MODE_DRY;
1004 break;
1007 break;
1010 break;
1011 }
1012 }
1013 should_publish = should_publish || (old_mode != this->mode);
1014 }
1015 {
1016 // Quiet mode, should be after climate mode
1017 if ((this->mode != CLIMATE_MODE_FAN_ONLY) && (this->mode != CLIMATE_MODE_OFF) &&
1018 ((((uint8_t) this->quiet_mode_state_) & 0b10) == 0)) {
1019 // In proper mode and not in pending state
1020 bool new_quiet_mode = packet.control.quiet_mode != 0;
1021 if (new_quiet_mode != this->get_quiet_mode_state()) {
1022 this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
1023 this->settings_.quiet_mode_state = new_quiet_mode;
1024#ifdef USE_SWITCH
1025 if (this->quiet_mode_switch_ != nullptr) {
1026 this->quiet_mode_switch_->publish_state(new_quiet_mode);
1027 }
1028#endif // USE_SWITCH
1029 this->hon_rtc_.save(&this->settings_);
1030 }
1031 }
1032 }
1033 {
1034 // Swing mode
1035 ClimateSwingMode old_swing_mode = this->swing_mode;
1036 const std::set<ClimateSwingMode> &swing_modes = traits_.get_supported_swing_modes();
1037 bool vertical_swing_supported = swing_modes.find(CLIMATE_SWING_VERTICAL) != swing_modes.end();
1038 bool horizontal_swing_supported = swing_modes.find(CLIMATE_SWING_HORIZONTAL) != swing_modes.end();
1039 if (horizontal_swing_supported &&
1040 (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)) {
1041 if (vertical_swing_supported &&
1042 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1044 } else {
1046 }
1047 } else {
1048 if (vertical_swing_supported &&
1049 (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO)) {
1051 } else {
1053 }
1054 }
1055 // Saving last known non auto mode for vertical and horizontal swing
1056 this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
1057 this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
1060 (this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
1061 ((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
1062 (this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
1063 if (save_settings) {
1066 this->hon_rtc_.save(&this->settings_);
1067 }
1068 should_publish = should_publish || (old_swing_mode != this->swing_mode);
1069 }
1070 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
1071 if (should_publish) {
1072 this->publish_state();
1073 }
1074 if (should_publish) {
1075 ESP_LOGI(TAG, "HVAC values changed");
1076 }
1077 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
1078 esp_log_printf_(log_level, TAG, __LINE__, "HVAC Mode = 0x%X", packet.control.ac_mode);
1079 esp_log_printf_(log_level, TAG, __LINE__, "Fan speed Status = 0x%X", packet.control.fan_mode);
1080 esp_log_printf_(log_level, TAG, __LINE__, "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode);
1081 esp_log_printf_(log_level, TAG, __LINE__, "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode);
1082 esp_log_printf_(log_level, TAG, __LINE__, "Set Point Status = 0x%X", packet.control.set_point);
1083 return haier_protocol::HandlerError::HANDLER_OK;
1084}
1085
1087 if (!this->current_hvac_settings_.valid && !this->force_send_control_)
1088 return;
1090 HvacSettings climate_control;
1091 climate_control = this->current_hvac_settings_;
1092 // Beeper command
1093 {
1094 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1097 this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
1098 }
1099 // Health mode
1100 {
1101 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1104 this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
1105 this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
1106 }
1107 // Climate mode
1108 ClimateMode climate_mode = this->mode;
1109 bool new_power = this->mode != CLIMATE_MODE_OFF;
1110 uint8_t fan_mode_buf[] = {0x00, 0xFF};
1111 uint8_t quiet_mode_buf[] = {0x00, 0xFF};
1112 if (climate_control.mode.has_value()) {
1113 climate_mode = climate_control.mode.value();
1114 uint8_t buffer[2] = {0x00, 0x00};
1115 switch (climate_control.mode.value()) {
1116 case CLIMATE_MODE_OFF:
1117 new_power = false;
1118 break;
1120 new_power = true;
1121 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
1122 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1125 buffer, 2);
1126 fan_mode_buf[1] = this->other_modes_fan_speed_;
1127 break;
1128 case CLIMATE_MODE_HEAT:
1129 new_power = true;
1130 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
1131 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1134 buffer, 2);
1135 fan_mode_buf[1] = this->other_modes_fan_speed_;
1136 break;
1137 case CLIMATE_MODE_DRY:
1138 new_power = true;
1139 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
1140 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1143 buffer, 2);
1144 fan_mode_buf[1] = this->other_modes_fan_speed_;
1145 break;
1147 new_power = true;
1148 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
1149 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1152 buffer, 2);
1153 fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
1154 break;
1155 case CLIMATE_MODE_COOL:
1156 new_power = true;
1157 buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
1158 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1161 buffer, 2);
1162 fan_mode_buf[1] = this->other_modes_fan_speed_;
1163 break;
1164 default:
1165 ESP_LOGE("Control", "Unsupported climate mode");
1166 break;
1167 }
1168 }
1169 // Climate power
1170 {
1171 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1174 new_power ? ONE_BUF : ZERO_BUF, 2);
1175 }
1176 // CLimate preset
1177 {
1178 uint8_t fast_mode_buf[] = {0x00, 0xFF};
1179 uint8_t away_mode_buf[] = {0x00, 0xFF};
1180 if (!new_power) {
1181 // If AC is off - no presets allowed
1182 fast_mode_buf[1] = 0x00;
1183 away_mode_buf[1] = 0x00;
1184 } else if (climate_control.preset.has_value()) {
1185 switch (climate_control.preset.value()) {
1187 fast_mode_buf[1] = 0x00;
1188 away_mode_buf[1] = 0x00;
1189 break;
1191 // Boost is not supported in Fan only mode
1192 fast_mode_buf[1] = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 0x01 : 0x00;
1193 away_mode_buf[1] = 0x00;
1194 break;
1196 fast_mode_buf[1] = 0x00;
1197 away_mode_buf[1] = (this->mode == CLIMATE_MODE_HEAT) ? 0x01 : 0x00;
1198 break;
1199 default:
1200 ESP_LOGE("Control", "Unsupported preset");
1201 break;
1202 }
1203 }
1204 {
1205 // Quiet mode
1206 if (new_power && (climate_mode != CLIMATE_MODE_FAN_ONLY) && this->get_quiet_mode_state()) {
1207 quiet_mode_buf[1] = 0x01;
1208 } else {
1209 quiet_mode_buf[1] = 0x00;
1210 }
1211 // Clean quiet mode state pending flag
1212 this->quiet_mode_state_ = (SwitchState) ((uint8_t) this->quiet_mode_state_ & 0b01);
1213 }
1214 auto presets = this->traits_.get_supported_presets();
1215 if (quiet_mode_buf[1] != 0xFF) {
1216 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1219 quiet_mode_buf, 2);
1220 }
1221 if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
1222 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1225 fast_mode_buf, 2);
1226 }
1227 if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
1228 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1231 away_mode_buf, 2);
1232 }
1233 }
1234 // Target temperature
1235 if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
1236 uint8_t buffer[2] = {0x00, 0x00};
1237 buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
1238 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1241 buffer, 2);
1242 }
1243 // Vertical swing mode
1244 if (climate_control.swing_mode.has_value()) {
1245 uint8_t vertical_swing_buf[] = {0x00, (uint8_t) hon_protocol::VerticalSwingMode::AUTO};
1246 uint8_t horizontal_swing_buf[] = {0x00, (uint8_t) hon_protocol::HorizontalSwingMode::AUTO};
1247 switch (climate_control.swing_mode.value()) {
1248 case CLIMATE_SWING_OFF:
1249 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1250 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1251 break;
1253 horizontal_swing_buf[1] = (uint8_t) this->settings_.last_horizontal_swing;
1254 break;
1256 vertical_swing_buf[1] = (uint8_t) this->settings_.last_vertiacal_swing;
1257 break;
1258 case CLIMATE_SWING_BOTH:
1259 break;
1260 }
1261 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1264 horizontal_swing_buf, 2);
1265 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1268 vertical_swing_buf, 2);
1269 }
1270 // Fan mode
1271 if (climate_control.fan_mode.has_value()) {
1272 switch (climate_control.fan_mode.value()) {
1273 case CLIMATE_FAN_LOW:
1274 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_LOW;
1275 break;
1276 case CLIMATE_FAN_MEDIUM:
1277 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_MID;
1278 break;
1279 case CLIMATE_FAN_HIGH:
1280 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_HIGH;
1281 break;
1282 case CLIMATE_FAN_AUTO:
1283 if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode
1284 fan_mode_buf[1] = (uint8_t) hon_protocol::FanMode::FAN_AUTO;
1285 break;
1286 default:
1287 ESP_LOGE("Control", "Unsupported fan mode");
1288 break;
1289 }
1290 if (fan_mode_buf[1] != 0xFF) {
1291 this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
1294 fan_mode_buf, 2);
1295 }
1296 }
1297}
1298
1300 while (!this->control_messages_queue_.empty())
1301 this->control_messages_queue_.pop();
1302}
1303
1305 switch (this->action_request_.value().action) {
1308 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1309 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1310 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1311 out_data->self_cleaning_status = 1;
1312 out_data->steri_clean = 0;
1313 out_data->set_point = 0x06;
1316 out_data->ac_power = 1;
1317 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1318 out_data->light_status = 0;
1319 this->action_request_.value().message = haier_protocol::HaierMessage(
1320 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1321 control_out_buffer, this->real_control_packet_size_);
1322 return true;
1324 this->action_request_.value().message =
1325 haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
1328 ONE_BUF, 2);
1329 return true;
1330 } else {
1331 this->action_request_.reset();
1332 return false;
1333 }
1336 uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
1337 memcpy(control_out_buffer, this->last_status_message_.get(), this->real_control_packet_size_);
1338 hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer;
1339 out_data->self_cleaning_status = 0;
1340 out_data->steri_clean = 1;
1341 out_data->set_point = 0x06;
1344 out_data->ac_power = 1;
1345 out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
1346 out_data->light_status = 0;
1347 this->action_request_.value().message = haier_protocol::HaierMessage(
1348 haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
1349 control_out_buffer, this->real_control_packet_size_);
1350 return true;
1351 } else {
1352 // No Steri clean support (yet?) in SET_SINGLE_PARAMETER
1353 this->action_request_.reset();
1354 return false;
1355 }
1356 default:
1358 }
1359}
1360
1363#ifdef USE_SENSOR
1364 for (auto &sub_sensor : this->sub_sensors_) {
1365 if ((sub_sensor != nullptr) && sub_sensor->has_state())
1366 sub_sensor->publish_state(NAN);
1367 }
1368#endif // USE_SENSOR
1369 this->got_valid_outdoor_temp_ = false;
1370 this->hvac_hardware_info_.reset();
1371 this->last_status_message_.reset(nullptr);
1372}
1373
1375 if (this->big_data_sensors_ > 0) {
1376 static uint8_t counter = 0;
1377 counter = (counter + 1) % 3;
1378 return counter == 1;
1379 }
1380 return false;
1381}
1382
1383} // namespace haier
1384} // namespace esphome
bool save(const T *src)
Definition preferences.h:21
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
uint32_t get_object_id_hash()
bool has_state() const
Definition entity_base.h:53
Base class for all binary_sensor-type classes.
void publish_state(bool state)
Publish a new state to the front-end.
ClimateMode mode
The active mode of the climate device.
Definition climate.h:173
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:199
float target_temperature
The target temperature of the climate device.
Definition climate.h:186
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:202
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:179
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:395
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:208
const std::set< climate::ClimatePreset > & get_supported_presets() const
const std::set< ClimateSwingMode > & 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
void add_alarm_end_callback(std::function< void(uint8_t, const char *)> &&callback)
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 add_alarm_start_callback(std::function< void(uint8_t, const char *)> &&callback)
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction)
CleaningState cleaning_status_
haier_protocol::HandlerError get_management_information_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
std::queue< haier_protocol::HaierMessage > control_messages_queue_
esphome::optional< hon_protocol::HorizontalSwingMode > current_horizontal_swing_
void set_quiet_mode_state(bool state)
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value)
haier_protocol::HandlerError get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size)
binary_sensor::BinarySensor * sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]
Definition hon_climate.h:79
haier_protocol::HandlerError alarm_status_message_handler_(haier_protocol::FrameType type, const uint8_t *buffer, size_t size)
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value)
haier_protocol::HaierMessage get_control_message() override
void process_phase(std::chrono::steady_clock::time_point now) override
esphome::optional< hon_protocol::HorizontalSwingMode > pending_horizontal_direction_
bool get_quiet_mode_state() const
switch_::Switch * quiet_mode_switch_
std::string get_cleaning_status_text() const
void process_protocol_reset() override
CleaningState get_cleaning_status() const
esphome::optional< hon_protocol::HorizontalSwingMode > get_horizontal_airflow() const
void set_beeper_switch(switch_::Switch *sw)
esphome::optional< hon_protocol::VerticalSwingMode > current_vertical_swing_
void process_alarm_message_(const uint8_t *packet, uint8_t size, bool check_new)
bool prepare_pending_action() override
esphome::optional< hon_protocol::VerticalSwingMode > pending_vertical_direction_
bool has_value() const
Definition optional.h:87
value_type const & value() const
Definition optional.h:89
Base-class for all sensors.
Definition sensor.h:62
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:39
Base class for all switches.
Definition switch.h:39
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:47
void publish_state(const std::string &state)
uint8_t type
FanDirection direction
Definition fan.h:3
bool state
Definition fan.h:0
@ 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.
@ 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_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const std::string HON_ALARM_MESSAGES[]
Definition hon_packet.h:202
constexpr size_t HON_ALARM_COUNT
Definition hon_packet.h:256
constexpr uint8_t CONTROL_MESSAGE_RETRIES
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL
constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET
const uint8_t ZERO_BUF[]
const uint8_t ONE_BUF[]
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
Definition log.cpp:11
ESPPreferences * global_preferences
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:192
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