ESPHome 2025.5.0
Loading...
Searching...
No Matches
haier_base.cpp
Go to the documentation of this file.
1#include <chrono>
2#include <string>
5#ifdef USE_WIFI
7#endif
8#include "haier_base.h"
9
10using namespace esphome::climate;
11using namespace esphome::uart;
12
13namespace esphome {
14namespace haier {
15
16static const char *const TAG = "haier.climate";
17constexpr size_t COMMUNICATION_TIMEOUT_MS = 60000;
18constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000;
19constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
20constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
21constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
22
24 static const char *phase_names[] = {
25 "SENDING_INIT_1",
26 "SENDING_INIT_2",
27 "SENDING_FIRST_STATUS_REQUEST",
28 "SENDING_FIRST_ALARM_STATUS_REQUEST",
29 "IDLE",
30 "SENDING_STATUS_REQUEST",
31 "SENDING_UPDATE_SIGNAL_REQUEST",
32 "SENDING_SIGNAL_LEVEL",
33 "SENDING_CONTROL",
34 "SENDING_ACTION_COMMAND",
35 "SENDING_ALARM_STATUS_REQUEST",
36 "UNKNOWN" // Should be the last!
37 };
38 static_assert(
39 (sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1),
40 "Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases");
41 int phase_index = (int) phase;
42 if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0))
43 phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES;
44 return phase_names[phase_index];
45}
46
47bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
48 size_t timeout) {
49 return std::chrono::duration_cast<std::chrono::milliseconds>(now - tpoint).count() > timeout;
50}
51
70
72
74 if (this->protocol_phase_ != phase) {
75 ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
76 this->protocol_phase_ = phase;
77 }
78}
79
84
93
94bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) {
95 return check_timeout(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS);
96}
97
98bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) {
99 return check_timeout(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS);
100}
101
102bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) {
103 return check_timeout(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS);
104}
105
106bool HaierClimateBase::is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now) {
107 return check_timeout(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL);
108}
109
110#ifdef USE_WIFI
111haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() {
112 static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00};
113 if (wifi::global_wifi_component->is_connected()) {
114 wifi_status_data[1] = 0;
115 int8_t rssi = wifi::global_wifi_component->wifi_rssi();
116 wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f);
117 ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]);
118 } else {
119 ESP_LOGD(TAG, "WiFi is not connected");
120 wifi_status_data[1] = 1;
121 wifi_status_data[3] = 0;
122 }
123 return haier_protocol::HaierMessage(haier_protocol::FrameType::REPORT_NETWORK_STATUS, wifi_status_data,
124 sizeof(wifi_status_data));
125}
126#endif
127
129 HaierBaseSettings settings{this->get_health_mode(), this->get_display_state()};
130 if (!this->base_rtc_.save(&settings)) {
131 ESP_LOGW(TAG, "Failed to save settings");
132 }
133}
134
138
140 if (state != this->get_display_state()) {
142 this->force_send_control_ = true;
143 this->save_settings();
144 }
145}
146
150
152 if (state != this->get_health_mode()) {
154 this->force_send_control_ = true;
155 this->save_settings();
156 }
157}
158
163
168
173
174void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
176 if (!modes.empty())
178}
179
180void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); }
181
182void HaierClimateBase::set_supported_modes(const std::set<climate::ClimateMode> &modes) {
183 this->traits_.set_supported_modes(modes);
184 this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available
186}
187
188void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
189 this->traits_.set_supported_presets(presets);
190 if (!presets.empty())
192}
193
194void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; }
195
196void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &message) {
198}
199
200void HaierClimateBase::add_status_message_callback(std::function<void(const char *, size_t)> &&callback) {
201 this->status_message_callback_.add(std::move(callback));
202}
203
204haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(
205 haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type,
206 haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type,
207 ProtocolPhases expected_phase) {
208 haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK;
209 if ((expected_request_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) &&
210 (request_message_type != expected_request_message_type))
211 result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
212 if ((expected_answer_message_type != haier_protocol::FrameType::UNKNOWN_FRAME_TYPE) &&
213 (answer_message_type != expected_answer_message_type))
214 result = haier_protocol::HandlerError::UNSUPPORTED_MESSAGE;
215 if (!this->haier_protocol_.is_waiting_for_answer() ||
216 ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)))
217 result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE;
218 if (answer_message_type == haier_protocol::FrameType::INVALID)
219 result = haier_protocol::HandlerError::INVALID_ANSWER;
220 return result;
221}
222
224 haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data,
225 size_t data_size) {
226 haier_protocol::HandlerError result =
227 this->answer_preprocess_(request_type, haier_protocol::FrameType::REPORT_NETWORK_STATUS, message_type,
228 haier_protocol::FrameType::CONFIRM, ProtocolPhases::SENDING_SIGNAL_LEVEL);
230 return result;
231}
232
233haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
234 ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
238 } else {
240 }
241 return haier_protocol::HandlerError::HANDLER_OK;
242}
243
245 ESP_LOGI(TAG, "Haier initialization...");
246 // Set timestamp here to give AC time to boot
247 this->last_request_timestamp_ = std::chrono::steady_clock::now();
249 this->haier_protocol_.set_default_timeout_handler(
250 std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
251 this->set_handlers();
252 this->initialization();
253}
254
256 LOG_CLIMATE("", "Haier Climate", this);
257 ESP_LOGCONFIG(TAG, " Device communication status: %s", this->valid_connection() ? "established" : "none");
258}
259
261 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
262 if ((std::chrono::duration_cast<std::chrono::milliseconds>(now - this->last_valid_status_timestamp_).count() >
264 (this->reset_protocol_request_ && (!this->haier_protocol_.is_waiting_for_answer()))) {
267 // No status too long, reseting protocol
268 // No need to reset protocol if we didn't pass initialization phase
269 if (this->reset_protocol_request_) {
270 this->reset_protocol_request_ = false;
271 ESP_LOGW(TAG, "Protocol reset requested");
272 } else {
273 ESP_LOGW(TAG, "Communication timeout, reseting protocol");
274 }
276 return;
277 }
278 };
279 if ((!this->haier_protocol_.is_waiting_for_answer()) &&
280 ((this->protocol_phase_ == ProtocolPhases::IDLE) ||
281 (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) ||
282 (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) ||
283 (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL))) {
284 // If control message or action is pending we should send it ASAP unless we are in initialisation
285 // procedure or waiting for an answer
286 if (this->action_request_.has_value() && this->prepare_pending_action()) {
288 } else if (this->next_hvac_settings_.valid || this->force_send_control_) {
289 ESP_LOGV(TAG, "Control packet is pending...");
291 if (this->next_hvac_settings_.valid) {
294 } else {
296 }
297 }
298 }
299 this->process_phase(now);
300 this->haier_protocol_.loop();
301#ifdef USE_SWITCH
302 if ((this->display_switch_ != nullptr) && (this->display_switch_->state != this->get_display_state())) {
304 }
305 if ((this->health_mode_switch_ != nullptr) && (this->health_mode_switch_->state != this->get_health_mode())) {
307 }
308#endif // USE_SWITCH
309}
310
312 this->force_send_control_ = false;
315 if (this->next_hvac_settings_.valid)
317 this->mode = CLIMATE_MODE_OFF;
318 this->current_temperature = NAN;
319 this->target_temperature = NAN;
320 this->fan_mode.reset();
321 this->preset.reset();
322 this->publish_state();
324}
325
327 if (this->action_request_.has_value()) {
328 switch (this->action_request_.value().action) {
330 return true;
332 this->action_request_.value().message = this->get_power_message(true);
333 return true;
335 this->action_request_.value().message = this->get_power_message(false);
336 return true;
338 this->action_request_.value().message = this->get_power_message(this->mode == ClimateMode::CLIMATE_MODE_OFF);
339 return true;
340 default:
341 ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_.value().action);
342 this->action_request_.reset();
343 return false;
344 }
345 } else {
346 return false;
347 }
348}
349
351
353 constexpr uint32_t restore_settings_version = 0xA77D21EF;
354 this->base_rtc_ =
355 global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version);
356 HaierBaseSettings recovered;
357 if (!this->base_rtc_.load(&recovered)) {
358 recovered = {false, true};
359 }
361 this->health_mode_ = recovered.health_mode ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
362#ifdef USE_SWITCH
363 if (this->display_switch_ != nullptr) {
365 }
366 if (this->health_mode_switch_ != nullptr) {
368 }
369#endif
370}
371
373 ESP_LOGD("Control", "Control call");
374 if (!this->valid_connection()) {
375 ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
376 return; // cancel the control, we cant do it without a poll answer.
377 }
378 if (this->current_hvac_settings_.valid) {
379 ESP_LOGW(TAG, "New settings come faster then processed!");
380 }
381 {
382 if (call.get_mode().has_value())
383 this->next_hvac_settings_.mode = call.get_mode();
384 if (call.get_fan_mode().has_value())
386 if (call.get_swing_mode().has_value())
388 if (call.get_target_temperature().has_value())
390 if (call.get_preset().has_value())
392 this->next_hvac_settings_.valid = true;
393 }
394}
395
396#ifdef USE_SWITCH
398 this->display_switch_ = sw;
399 if ((this->display_switch_ != nullptr) && (this->valid_connection())) {
401 }
402}
403
405 this->health_mode_switch_ = sw;
406 if ((this->health_mode_switch_ != nullptr) && (this->valid_connection())) {
408 }
409}
410#endif
411
413 this->valid = false;
414 this->mode.reset();
415 this->fan_mode.reset();
416 this->swing_mode.reset();
418 this->preset.reset();
419}
420
421void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats,
422 std::chrono::milliseconds interval) {
423 this->haier_protocol_.send_message(command, use_crc, num_repeats, interval);
424 this->last_request_timestamp_ = std::chrono::steady_clock::now();
425}
426
427} // namespace haier
428} // 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()
This class is used to encode all control actions on a climate device.
Definition climate.h:33
const optional< ClimateSwingMode > & get_swing_mode() const
Definition climate.cpp:282
const optional< float > & get_target_temperature() const
Definition climate.cpp:274
const optional< ClimatePreset > & get_preset() const
Definition climate.cpp:280
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:278
const optional< ClimateMode > & get_mode() const
Definition climate.cpp:273
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
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
This class contains all static data for climate devices.
void set_supported_modes(std::set< ClimateMode > modes)
void add_supported_preset(ClimatePreset preset)
void set_supported_swing_modes(std::set< ClimateSwingMode > modes)
void set_supported_presets(std::set< ClimatePreset > presets)
void add_supported_mode(ClimateMode mode)
void set_supported_fan_modes(std::set< ClimateFanMode > modes)
void set_supports_current_temperature(bool supports_current_temperature)
void add_supported_swing_mode(ClimateSwingMode mode)
virtual haier_protocol::HaierMessage get_power_message(bool state)=0
void add_status_message_callback(std::function< void(const char *, size_t)> &&callback)
esphome::climate::ClimateTraits traits_
Definition haier_base.h:167
ESPPreferenceObject base_rtc_
Definition haier_base.h:176
bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
void set_supported_swing_modes(const std::set< esphome::climate::ClimateSwingMode > &modes)
CallbackManager< void(const char *, size_t)> status_message_callback_
Definition haier_base.h:175
void set_supported_presets(const std::set< esphome::climate::ClimatePreset > &presets)
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)
esphome::climate::ClimateTraits traits() override
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
std::chrono::steady_clock::time_point last_request_timestamp_
Definition haier_base.h:171
const char * phase_to_string_(ProtocolPhases phase)
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
virtual void process_phase(std::chrono::steady_clock::time_point now)=0
void set_health_mode_switch(switch_::Switch *sw)
std::chrono::steady_clock::time_point last_valid_status_timestamp_
Definition haier_base.h:172
haier_protocol::HaierMessage get_wifi_signal_message_()
haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type)
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
switch_::Switch * health_mode_switch_
Definition haier_base.h:43
esphome::optional< PendingAction > action_request_
Definition haier_base.h:157
void set_answer_timeout(uint32_t timeout)
std::chrono::steady_clock::time_point last_status_request_
Definition haier_base.h:173
void set_display_switch(switch_::Switch *sw)
void set_send_wifi(bool send_wifi)
virtual void set_phase(ProtocolPhases phase)
void send_custom_command(const haier_protocol::HaierMessage &message)
void set_supported_modes(const std::set< esphome::climate::ClimateMode > &modes)
switch_::Switch * display_switch_
Definition haier_base.h:42
Base class for all switches.
Definition switch.h:39
bool state
The current reported state of the binary sensor.
Definition switch.h:53
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition switch.cpp:47
bool state
Definition fan.h:0
@ CLIMATE_PRESET_NONE
No preset is active.
@ 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.
@ 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.
constexpr size_t STATUS_REQUEST_INTERVAL_MS
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, size_t timeout)
constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS
constexpr size_t COMMUNICATION_TIMEOUT_MS
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
ESPPreferences * global_preferences
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