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;
41 ESP_LOGW(TAG,
"Status packet too small: %d (should be >= %d)", data_size,
44 switch (this->protocol_phase_) {
45 case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
46 ESP_LOGI(TAG,
"First HVAC status received");
49 case ProtocolPhases::SENDING_ACTION_COMMAND:
52 case ProtocolPhases::SENDING_STATUS_REQUEST:
55 case ProtocolPhases::SENDING_CONTROL:
57 this->force_send_control_ =
false;
58 if (this->current_hvac_settings_.valid)
59 this->current_hvac_settings_.reset();
67 this->action_request_.reset();
68 this->force_send_control_ =
false;
74 haier_protocol::HandlerError Smartair2Climate::get_device_version_answer_handler_(
75 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type,
const uint8_t *data,
77 if (request_type != haier_protocol::FrameType::GET_DEVICE_VERSION)
78 return haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
79 if (ProtocolPhases::SENDING_INIT_1 != this->protocol_phase_)
80 return haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
82 if ((message_type == haier_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE) && (data_size >= 39) &&
83 ((data[37] & 0x04) != 0)) {
84 ESP_LOGW(TAG,
"It looks like your ESPHome Haier climate configuration is wrong. You should use the hOn protocol " 85 "instead of smartAir2");
87 this->set_phase(ProtocolPhases::SENDING_INIT_2);
88 return haier_protocol::HandlerError::HANDLER_OK;
91 haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cycle_for_init_(
92 haier_protocol::FrameType message_type) {
94 return HaierClimateBase::timeout_default_handler_(message_type);
95 ESP_LOGI(TAG,
"Answer timeout for command %02X, phase %s", (uint8_t) message_type,
96 phase_to_string_(this->protocol_phase_));
98 if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
99 new_phase = ProtocolPhases::SENDING_INIT_1;
100 this->set_phase(new_phase);
101 return haier_protocol::HandlerError::HANDLER_OK;
104 void Smartair2Climate::set_handlers() {
106 this->haier_protocol_.set_answer_handler(
107 haier_protocol::FrameType::GET_DEVICE_VERSION,
108 std::bind(&Smartair2Climate::get_device_version_answer_handler_,
this, std::placeholders::_1,
109 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
110 this->haier_protocol_.set_answer_handler(
111 haier_protocol::FrameType::CONTROL,
112 std::bind(&Smartair2Climate::status_handler_,
this, std::placeholders::_1, std::placeholders::_2,
113 std::placeholders::_3, std::placeholders::_4));
114 this->haier_protocol_.set_answer_handler(
115 haier_protocol::FrameType::REPORT_NETWORK_STATUS,
116 std::bind(&Smartair2Climate::report_network_status_answer_handler_,
this, std::placeholders::_1,
117 std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
118 this->haier_protocol_.set_default_timeout_handler(
119 std::bind(&Smartair2Climate::messages_timeout_handler_with_cycle_for_init_,
this, std::placeholders::_1));
122 void Smartair2Climate::dump_config() {
123 HaierClimateBase::dump_config();
124 ESP_LOGCONFIG(TAG,
" Protocol version: smartAir2");
127 void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) {
128 switch (this->protocol_phase_) {
129 case ProtocolPhases::SENDING_INIT_1:
130 if (this->can_send_message() && this->is_protocol_initialisation_interval_exceeded_(now)) {
137 uint8_t module_capabilities[2] = {0b00000000, 0b00000111};
138 static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST(
139 haier_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities,
sizeof(module_capabilities));
140 this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
143 case ProtocolPhases::SENDING_INIT_2:
144 this->set_phase(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST);
146 case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST:
147 case ProtocolPhases::SENDING_STATUS_REQUEST:
148 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
149 static const haier_protocol::HaierMessage STATUS_REQUEST(haier_protocol::FrameType::CONTROL, 0x4D01);
150 if (this->protocol_phase_ == ProtocolPhases::SENDING_FIRST_STATUS_REQUEST) {
151 this->send_message_(STATUS_REQUEST, this->use_crc_, INIT_REQUESTS_RETRY, INIT_REQUESTS_RETRY_INTERVAL);
153 this->send_message_(STATUS_REQUEST, this->use_crc_);
155 this->last_status_request_ = now;
159 case ProtocolPhases::SENDING_SIGNAL_LEVEL:
160 if (this->can_send_message() && this->is_message_interval_exceeded_(now)) {
161 this->send_message_(this->get_wifi_signal_message_(), this->use_crc_);
162 this->last_signal_request_ = now;
166 case ProtocolPhases::SENDING_SIGNAL_LEVEL:
170 case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST:
171 this->set_phase(ProtocolPhases::SENDING_SIGNAL_LEVEL);
173 case ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST:
174 this->set_phase(ProtocolPhases::SENDING_INIT_1);
176 case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST:
179 case ProtocolPhases::SENDING_CONTROL:
180 if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) {
181 ESP_LOGI(TAG,
"Sending control packet");
182 this->send_message_(get_control_message(), this->use_crc_, CONTROL_MESSAGE_RETRIES,
183 CONTROL_MESSAGE_RETRIES_INTERVAL);
186 case ProtocolPhases::SENDING_ACTION_COMMAND:
187 if (this->action_request_.has_value()) {
188 if (this->action_request_.value().message.has_value()) {
189 this->send_message_(this->action_request_.value().message.value(), this->use_crc_);
190 this->action_request_.value().message.reset();
193 this->action_request_.reset();
197 ESP_LOGW(TAG,
"SENDING_ACTION_COMMAND phase without action request!");
202 if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) {
203 this->set_phase(ProtocolPhases::SENDING_STATUS_REQUEST);
204 this->forced_request_status_ =
false;
207 else if (this->send_wifi_signal_ &&
208 (std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_signal_request_).count() >
209 SIGNAL_LEVEL_UPDATE_INTERVAL_MS))
210 this->set_phase(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST);
215 ESP_LOGE(TAG,
"Wrong protocol handler state: %s (%d), resetting communication",
216 phase_to_string_(this->protocol_phase_), (
int) this->protocol_phase_);
217 this->set_phase(ProtocolPhases::SENDING_INIT_1);
222 haier_protocol::HaierMessage Smartair2Climate::get_power_message(
bool state) {
224 static haier_protocol::HaierMessage power_on_message(haier_protocol::FrameType::CONTROL, 0x4D02);
225 return power_on_message;
227 static haier_protocol::HaierMessage power_off_message(haier_protocol::FrameType::CONTROL, 0x4D03);
228 return power_off_message;
232 haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
237 if (this->current_hvac_settings_.valid) {
238 HvacSettings &climate_control = this->current_hvac_settings_;
246 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO;
247 out_data->
fan_mode = this->other_modes_fan_speed_;
251 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT;
252 out_data->
fan_mode = this->other_modes_fan_speed_;
256 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY;
257 out_data->
fan_mode = this->other_modes_fan_speed_;
261 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN;
262 out_data->
fan_mode = this->fan_mode_speed_;
266 out_data->
ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL;
267 out_data->
fan_mode = this->other_modes_fan_speed_;
270 ESP_LOGE(
"Control",
"Unsupported climate mode");
281 out_data->
fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_MID;
291 ESP_LOGE(
"Control",
"Unsupported fan mode");
297 if (this->use_alternative_swing_control_) {
339 out_data->
set_point = ((int) target_temp) - 16;
340 out_data->
half_degree = (target_temp - ((int) target_temp) >= 0.49) ? 1 : 0;
370 ESP_LOGE(
"Control",
"Unsupported preset");
379 out_data->
health_mode = this->health_mode_ ? 1 : 0;
380 return haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer,
384 haier_protocol::HandlerError Smartair2Climate::process_status_message_(
const uint8_t *packet_buffer, uint8_t size) {
386 return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
388 memcpy(&packet, packet_buffer, size);
389 bool should_publish =
false;
402 should_publish = should_publish || (!old_preset.
has_value()) || (old_preset.
value() != this->
preset.value());
408 should_publish = should_publish || (old_target_temperature != this->
target_temperature);
412 float old_current_temperature = this->current_temperature;
414 should_publish = should_publish || (old_current_temperature != this->current_temperature);
420 if (packet.
control.
ac_mode == (uint8_t) smartair2_protocol::ConditioningMode::FAN) {
430 if (packet.
control.
ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) {
433 should_publish =
true;
436 case (uint8_t) smartair2_protocol::FanMode::FAN_MID:
446 should_publish = should_publish || (!old_fan_mode.
has_value()) || (old_fan_mode.
value() !=
fan_mode.value());
454 if (disp_status != this->display_status_) {
458 this->force_send_control_ =
true;
460 this->display_status_ = disp_status;
467 bool old_health_mode = this->health_mode_;
469 should_publish = should_publish || (old_health_mode != this->health_mode_);
479 case (uint8_t) smartair2_protocol::ConditioningMode::COOL:
482 case (uint8_t) smartair2_protocol::ConditioningMode::HEAT:
485 case (uint8_t) smartair2_protocol::ConditioningMode::DRY:
488 case (uint8_t) smartair2_protocol::ConditioningMode::FAN:
491 case (uint8_t) smartair2_protocol::ConditioningMode::AUTO:
496 should_publish = should_publish || (old_mode != this->
mode);
501 if (this->use_alternative_swing_control_) {
529 should_publish = should_publish || (old_swing_mode != this->
swing_mode);
531 this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
532 if (should_publish) {
533 this->publish_state();
535 if (should_publish) {
536 ESP_LOGI(TAG,
"HVAC values changed");
538 int log_level = should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG;
544 return haier_protocol::HandlerError::HANDLER_OK;
547 void Smartair2Climate::set_alternative_swing_control(
bool swing_control) {
548 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.
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.
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
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