13 static const char *
const TAG =
"haier.climate";
20 Smartair2Climate::Smartair2Climate() {
24 haier_protocol::HandlerError Smartair2Climate::status_handler_(haier_protocol::FrameType request_type,
25 haier_protocol::FrameType message_type,
27 haier_protocol::HandlerError result =
28 this->answer_preprocess_(request_type, haier_protocol::FrameType::CONTROL, message_type,
30 if (result == haier_protocol::HandlerError::HANDLER_OK) {
31 result = this->process_status_message_(data, data_size);
32 if (result != haier_protocol::HandlerError::HANDLER_OK) {
33 ESP_LOGW(TAG,
"Error %d while parsing Status packet", (
int) result);
35 this->action_request_.reset();
36 this->force_send_control_ =
false;
40 this->status_message_callback_.call((
const char *) data, data_size);
42 ESP_LOGW(TAG,
"Status packet too small: %d (should be >= %d)", data_size,
45 switch (this->protocol_phase_) {
46 case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
47 ESP_LOGI(TAG,
"First HVAC status received");
50 case ProtocolPhases::SENDING_ACTION_COMMAND:
53 case ProtocolPhases::SENDING_STATUS_REQUEST:
56 case ProtocolPhases::SENDING_CONTROL:
58 this->force_send_control_ =
false;
59 if (this->current_hvac_settings_.valid)
60 this->current_hvac_settings_.reset();
68 this->action_request_.reset();
69 this->force_send_control_ =
false;
75 haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(
76 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type,
const uint8_t *data,
78 if (request_type != haier_protocol::FrameType::GET_DEVICE_VERSION)
79 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
80 if (ProtocolPhases::SENDING_INIT_1 != this->protocol_phase_)
81 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
83 if ((message_type == haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) &&
84 ((data[37] & 0x04) != 0)) {
85 ESP_LOGW(TAG,
"It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " 86 "instead of smartAir2");
88 this->set_phase(ProtocolPhases::SENDING_INIT_2);
89 return haier_protocol::HandlerError::HANDLER_OK;
92 haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cycle_for_init_(
93 haier_protocol::FrameType message_type) {
95 return HaierClimateBase::timeout_default_handler_(message_type);
96 ESP_LOGI(TAG,
"Answer timeout for command %02X, phase %s", (uint8_t) message_type,
97 phase_to_string_(this->protocol_phase_));
99 if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
100 new_phase = ProtocolPhases::SENDING_INIT_1;
101 this->set_phase(new_phase);
102 return haier_protocol::HandlerError::HANDLER_OK;
105 void Smartair2Climate::set_handlers() {
107 this->haier_protocol_.set_answer_handler(
108 haier_protocol::FrameType::GET_DEVICE_VERSION,
109 std::bind(&Smartair2Climate::get_device_version_answer_handler_,
this, std::placeholders::_1,
110 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
111 this->haier_protocol_.set_answer_handler(
112 haier_protocol::FrameType::CONTROL,
113 std::bind(&Smartair2Climate::status_handler_,
this, std::placeholders::_1, std::placeholders::_2,
114 std::placeholders::_3, std::placeholders::_4));
115 this->haier_protocol_.set_answer_handler(
116 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
117 std::bind(&Smartair2Climate::report_network_status_answer_handler_,
this, std::placeholders::_1,
118 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
119 this->haier_protocol_.set_default_timeout_handler(
120 std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_,
this, std::placeholders::_1));
123 void Smartair2Climate::dump_config() {
124 HaierClimateBase::dump_config();
125 ESP_LOGCONFIG(TAG,
" Protocol version: smartAir2");
128 void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) {
129 switch (this->protocol_phase_) {
130 case ProtocolPhases::SENDING_INIT_1:
131 if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) {
138 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
139 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
140 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities,
sizeof(module_capabilities));
141 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
144 case ProtocolPhases::SENDING_INIT_2:
145 this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
147 case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
148 case ProtocolPhases::SENDING_STATUS_REQUEST:
149 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
150 static const haier_protocol::HaierMessage STATUS_REQUEST(haier_protocol::FrameType::CONTROL, 0x4D01);
151 if (this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) {
152 this->send_message_(STATUS_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
154 this->send_message_(STATUS_REQUEST, this->use_crc_);
156 this->last_status_request_ = now;
160 case ProtocolPhases::SENDING_SIGNAL_LEVEL:
161 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
162 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
163 this->last_signal_request_ = now;
167 case ProtocolPhases::SENDING_SIGNAL_LEVEL:
171 case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
172 this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
174 case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST:
175 this->set_phase(ProtocolPhases::SENDING_INIT_1);
177 case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
180 case ProtocolPhases::SENDING_CONTROL:
181 if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
182 ESP_LOGI(TAG,
"Sending control packet");
183 this->send_message_(get_control_message(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
184 CONTROL_MESSAGE_RETRIES_INTERVAL);
187 case ProtocolPhases::SENDING_ACTION_COMMAND:
188 if (this->action_request_.has_value()) {
189 if (this->action_request_.value().message.has_value()) {
190 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
191 this->action_request_.value().message.reset();
194 this->action_request_.reset();
198 ESP_LOGW(TAG,
"SENDING_ACTION_COMMAND phase without action request!");
203 if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) {
204 this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST);
205 this->forced_request_status_ =
false;
208 else if (this->send_wifi_signal_ &&
209 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
210 SIGNAL_LEVEL_UPDATE_INTERVAL_MS))
211 this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
216 ESP_LOGE(TAG,
"Wrong protocol handler state: %s (%d), resetting communication",
217 phase_to_string_(this->protocol_phase_), (
int) this->protocol_phase_);
218 this->set_phase(ProtocolPhases::SENDING_INIT_1);
223 haier_protocol::HaierMessage Smartair2Climate::get_power_message(
bool state) {
225 static haier_protocol::HaierMessage power_on_message(haier_protocol::FrameType::CONTROL, 0x4D02);
226 return power_on_message;
228 static haier_protocol::HaierMessage power_off_message(haier_protocol::FrameType::CONTROL, 0x4D03);
229 return power_off_message;
233 haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
238 if (this->current_hvac_settings_.valid) {
239 HvacSettings &climate_control = this->current_hvac_settings_;
248 out_data->
fan_mode = this->other_modes_fan_speed_;
252 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT;
253 out_data->
fan_mode = this->other_modes_fan_speed_;
257 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY;
258 out_data->
fan_mode = this->other_modes_fan_speed_;
262 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN;
263 out_data->
fan_mode = this->fan_mode_speed_;
267 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL;
268 out_data->
fan_mode = this->other_modes_fan_speed_;
271 ESP_LOGE(
"Control",
"Unsupported climate mode");
282 out_data->
fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_MID;
292 ESP_LOGE(
"Control",
"Unsupported fan mode");
298 if (this->use_alternative_swing_control_) {
340 out_data->
set_point = ((int) target_temp) - 16;
341 out_data->
half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
371 ESP_LOGE(
"Control",
"Unsupported preset");
380 this->display_status_ = (
SwitchState) ((uint8_t) this->display_status_ & 0b01);
381 out_data->
health_mode = this->get_health_mode() ? 1 : 0;
382 this->health_mode_ = (
SwitchState) ((uint8_t) this->health_mode_ & 0b01);
383 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
387 haier_protocol::HandlerError Smartair2Climate::process_status_message_(
const uint8_t *packet_buffer, uint8_t size) {
389 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
391 memcpy(&packet, packet_buffer, size);
392 bool should_publish =
false;
405 should_publish = should_publish || (!old_preset.
has_value()) || (old_preset.
value() != this->
preset.value());
411 should_publish = should_publish || (old_target_temperature != this->
target_temperature);
415 float old_current_temperature = this->current_temperature;
417 should_publish = should_publish || (old_current_temperature != this->current_temperature);
423 if (packet.
control.
ac_mode == (uint8_t) smartair2_protocol::ConditioningMode::FAN) {
433 if (packet.
control.
ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) {
436 should_publish =
true;
439 case (uint8_t) smartair2_protocol::FanMode::FAN_MID:
449 should_publish = should_publish || (!old_fan_mode.
has_value()) || (old_fan_mode.
value() !=
fan_mode.value());
456 if (disp_status != this->get_display_state()) {
460 this->force_send_control_ =
true;
461 }
else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
462 this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
467 if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
468 bool old_health_mode = this->get_health_mode();
469 this->health_mode_ = packet.
control.
health_mode == 1 ? SwitchState::ON : SwitchState::OFF;
470 should_publish = should_publish || (old_health_mode != this->get_health_mode());
480 case (uint8_t) smartair2_protocol::ConditioningMode::COOL:
483 case (uint8_t) smartair2_protocol::ConditioningMode::HEAT:
486 case (uint8_t) smartair2_protocol::ConditioningMode::DRY:
489 case (uint8_t) smartair2_protocol::ConditioningMode::FAN:
497 should_publish = should_publish || (old_mode != this->
mode);
502 if (this->use_alternative_swing_control_) {
530 should_publish = should_publish || (old_swing_mode != this->
swing_mode);
532 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
533 if (should_publish) {
534 this->publish_state();
536 if (should_publish) {
537 ESP_LOGI(TAG,
"HVAC values changed");
539 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
545 return haier_protocol::HandlerError::HANDLER_OK;
548 void Smartair2Climate::set_alternative_swing_control(
bool swing_control) {
549 this->use_alternative_swing_control_ = swing_control;
The fan mode is set to Low.
value_type const & value() const
esphome::optional< float > target_temperature
The fan mode is set to Both.
esphome::optional< esphome::climate::ClimatePreset > preset
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL
The climate device is set to heat to reach the target temperature.
HaierPacketControl control
The climate device is set to dry/humidity mode.
ClimateSwingMode swing_mode
ClimateSwingMode
Enum for all modes a climate swing can be in.
Device is in away preset.
Device is in comfort preset.
The fan mode is set to Horizontal.
The climate device is set to cool to reach the target temperature.
constexpr std::chrono::milliseconds INIT_REQUESTS_RETRY_INTERVAL
The fan mode is set to Auto.
Automatically detect from MIME type.
constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS
BedjetMode mode
BedJet operating mode.
esphome::optional< esphome::climate::ClimateFanMode > fan_mode
The climate device is set to heat/cool to reach the target temperature.
The fan mode is set to Vertical.
The fan mode is set to High.
void HOT esp_log_printf_(int level, const char *tag, int line, const char *format,...)
ClimateMode
Enum for all modes a climate device can be in.
The swing mode is set to Off.
The climate device is off.
Device is in boost preset.
Implementation of SPI Controller mode.
The fan mode is set to Medium.
constexpr uint8_t CONTROL_MESSAGE_RETRIES
The climate device only has the fan enabled, no heating or cooling is taking place.
constexpr uint8_t INIT_REQUESTS_RETRY
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
esphome::optional< esphome::climate::ClimateMode > mode