ESPHome  2024.12.2
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 
10 using namespace esphome::climate;
11 using namespace esphome::uart;
12 
13 namespace esphome {
14 namespace haier {
15 
16 static const char *const TAG = "haier.climate";
17 constexpr size_t COMMUNICATION_TIMEOUT_MS = 60000;
18 constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000;
19 constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
20 constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
21 constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
22 
23 const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
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 
47 bool 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 
52 HaierClimateBase::HaierClimateBase()
53  : haier_protocol_(*this),
54  protocol_phase_(ProtocolPhases::SENDING_INIT_1),
55  force_send_control_(false),
56  forced_request_status_(false),
57  reset_protocol_request_(false),
58  send_wifi_signal_(true),
59  use_crc_(false) {
69 }
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 
83 }
84 
86  this->force_send_control_ = false;
87  if (this->current_hvac_settings_.valid)
89  this->forced_request_status_ = true;
91  this->action_request_.reset();
92 }
93 
94 bool 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 
98 bool 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 
102 bool 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 
106 bool 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
111 haier_protocol::HaierMessage HaierClimateBase::get_wifi_signal_message_() {
112  static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00};
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 
137 }
138 
140  if (state != this->get_display_state()) {
142  this->force_send_control_ = true;
143  this->save_settings();
144  }
145 }
146 
148  return (this->health_mode_ == SwitchState::ON) || (this->health_mode_ == SwitchState::PENDING_ON);
149 }
150 
152  if (state != this->get_health_mode()) {
154  this->force_send_control_ = true;
155  this->save_settings();
156  }
157 }
158 
160  this->action_request_ =
162 }
163 
165  this->action_request_ =
167 }
168 
170  this->action_request_ =
172 }
173 
174 void HaierClimateBase::set_supported_swing_modes(const std::set<climate::ClimateSwingMode> &modes) {
175  this->traits_.set_supported_swing_modes(modes);
176  if (!modes.empty())
178 }
179 
180 void HaierClimateBase::set_answer_timeout(uint32_t timeout) { this->haier_protocol_.set_answer_timeout(timeout); }
181 
182 void 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 
188 void HaierClimateBase::set_supported_presets(const std::set<climate::ClimatePreset> &presets) {
189  this->traits_.set_supported_presets(presets);
190  if (!presets.empty())
192 }
193 
194 void HaierClimateBase::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; }
195 
196 void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &message) {
198 }
199 
200 void HaierClimateBase::add_status_message_callback(std::function<void(const char *, size_t)> &&callback) {
201  this->status_message_callback_.add(std::move(callback));
202 }
203 
204 haier_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 
233 haier_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() >
263  COMMUNICATION_TIMEOUT_MS) ||
264  (this->reset_protocol_request_ && (!this->haier_protocol_.is_waiting_for_answer()))) {
265  this->last_valid_status_timestamp_ = now;
266  if (this->protocol_phase_ >= ProtocolPhases::IDLE) {
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  }
275  this->process_protocol_reset();
276  return;
277  }
278  };
279  if ((!this->haier_protocol_.is_waiting_for_answer()) &&
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) {
293  this->next_hvac_settings_.reset();
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;
313  if (this->current_hvac_settings_.valid)
315  if (this->next_hvac_settings_.valid)
316  this->next_hvac_settings_.reset();
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 
350 
352  constexpr uint32_t restore_settings_version = 0xA77D21EF;
353  this->base_rtc_ =
354  global_preferences->make_preference<HaierBaseSettings>(this->get_object_id_hash() ^ restore_settings_version);
355  HaierBaseSettings recovered;
356  if (!this->base_rtc_.load(&recovered)) {
357  recovered = {false, true};
358  }
359  this->display_status_ = recovered.display_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
360  this->health_mode_ = recovered.health_mode ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
361 #ifdef USE_SWITCH
362  if (this->display_switch_ != nullptr) {
364  }
365  if (this->health_mode_switch_ != nullptr) {
367  }
368 #endif
369 }
370 
372  ESP_LOGD("Control", "Control call");
373  if (!this->valid_connection()) {
374  ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
375  return; // cancel the control, we cant do it without a poll answer.
376  }
377  if (this->current_hvac_settings_.valid) {
378  ESP_LOGW(TAG, "New settings come faster then processed!");
379  }
380  {
381  if (call.get_mode().has_value())
382  this->next_hvac_settings_.mode = call.get_mode();
383  if (call.get_fan_mode().has_value())
385  if (call.get_swing_mode().has_value())
387  if (call.get_target_temperature().has_value())
389  if (call.get_preset().has_value())
390  this->next_hvac_settings_.preset = call.get_preset();
391  this->next_hvac_settings_.valid = true;
392  }
393 }
394 
395 #ifdef USE_SWITCH
397  this->display_switch_ = sw;
398  if ((this->display_switch_ != nullptr) && (this->valid_connection())) {
400  }
401 }
402 
404  this->health_mode_switch_ = sw;
405  if ((this->health_mode_switch_ != nullptr) && (this->valid_connection())) {
407  }
408 }
409 #endif
410 
412  this->valid = false;
413  this->mode.reset();
414  this->fan_mode.reset();
415  this->swing_mode.reset();
416  this->target_temperature.reset();
417  this->preset.reset();
418 }
419 
420 void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats,
421  std::chrono::milliseconds interval) {
422  this->haier_protocol_.send_message(command, use_crc, num_repeats, interval);
423  this->last_request_timestamp_ = std::chrono::steady_clock::now();
424 }
425 
426 } // namespace haier
427 } // namespace esphome
This class is used to encode all control actions on a climate device.
Definition: climate.h:33
Base class for all switches.
Definition: switch.h:39
The fan mode is set to Low.
Definition: climate_mode.h:54
constexpr size_t COMMUNICATION_TIMEOUT_MS
Definition: haier_base.cpp:17
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:202
esphome::optional< float > target_temperature
Definition: haier_base.h:137
The fan mode is set to Both.
Definition: climate_mode.h:74
esphome::optional< esphome::climate::ClimatePreset > preset
Definition: haier_base.h:138
constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL
Definition: haier_base.cpp:19
float target_temperature
The target temperature of the climate device.
Definition: climate.h:186
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)
Definition: haier_base.cpp:223
const optional< ClimateMode > & get_mode() const
Definition: climate.cpp:273
virtual void set_phase(ProtocolPhases phase)
Definition: haier_base.cpp:73
This class contains all static data for climate devices.
void control(const esphome::climate::ClimateCall &call) override
Definition: haier_base.cpp:371
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS
Definition: haier_base.cpp:20
std::chrono::steady_clock::time_point last_status_request_
Definition: haier_base.h:173
The climate device is set to heat to reach the target temperature.
Definition: climate_mode.h:18
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:173
ESPPreferenceObject base_rtc_
Definition: haier_base.h:176
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition: climate.h:179
void set_send_wifi(bool send_wifi)
Definition: haier_base.cpp:194
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)
Definition: haier_base.cpp:204
bool has_value() const
Definition: optional.h:87
The climate device is set to dry/humidity mode.
Definition: climate_mode.h:22
void set_supported_presets(std::set< ClimatePreset > presets)
virtual void process_phase(std::chrono::steady_clock::time_point now)=0
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
void set_display_switch(switch_::Switch *sw)
Definition: haier_base.cpp:396
bool save(const T *src)
Definition: preferences.h:21
void add_supported_preset(ClimatePreset preset)
esphome::climate::ClimateTraits traits() override
Definition: haier_base.cpp:349
haier_protocol::ProtocolHandler haier_protocol_
Definition: haier_base.h:155
void add_supported_swing_mode(ClimateSwingMode mode)
bool is_protocol_initialisation_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:106
void set_display_state(bool state)
Definition: haier_base.cpp:139
The fan mode is set to Horizontal.
Definition: climate_mode.h:78
The climate device is set to cool to reach the target temperature.
Definition: climate_mode.h:16
const optional< ClimatePreset > & get_preset() const
Definition: climate.cpp:280
void set_supported_fan_modes(std::set< ClimateFanMode > modes)
The fan mode is set to Auto.
Definition: climate_mode.h:52
void set_answer_timeout(uint32_t timeout)
Definition: haier_base.cpp:180
void send_message_(const haier_protocol::HaierMessage &command, bool use_crc, uint8_t num_repeats=0, std::chrono::milliseconds interval=std::chrono::milliseconds::zero())
Definition: haier_base.cpp:420
haier_protocol::HaierMessage get_wifi_signal_message_()
Definition: haier_base.cpp:111
optional< ClimatePreset > preset
The active preset of the climate device.
Definition: climate.h:208
const char * phase_to_string_(ProtocolPhases phase)
Definition: haier_base.cpp:23
ESPPreferences * global_preferences
constexpr size_t STATUS_REQUEST_INTERVAL_MS
Definition: haier_base.cpp:18
void set_supported_modes(std::set< ClimateMode > modes)
esphome::optional< PendingAction > action_request_
Definition: haier_base.h:157
void set_supported_modes(const std::set< esphome::climate::ClimateMode > &modes)
Definition: haier_base.cpp:182
esphome::optional< esphome::climate::ClimateFanMode > fan_mode
Definition: haier_base.h:135
The climate device is set to heat/cool to reach the target temperature.
Definition: climate_mode.h:14
void set_supported_swing_modes(const std::set< esphome::climate::ClimateSwingMode > &modes)
Definition: haier_base.cpp:174
The fan mode is set to Vertical.
Definition: climate_mode.h:76
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS
Definition: haier_base.cpp:21
WiFiComponent * global_wifi_component
const optional< float > & get_target_temperature() const
Definition: climate.cpp:274
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition: climate.cpp:395
The fan mode is set to High.
Definition: climate_mode.h:58
virtual haier_protocol::HaierMessage get_power_message(bool state)=0
The swing mode is set to Off.
Definition: climate_mode.h:72
The climate device is off.
Definition: climate_mode.h:12
void add_status_message_callback(std::function< void(const char *, size_t)> &&callback)
Definition: haier_base.cpp:200
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:199
switch_::Switch * health_mode_switch_
Definition: haier_base.h:43
const optional< ClimateFanMode > & get_fan_mode() const
Definition: climate.cpp:278
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:94
const optional< ClimateSwingMode > & get_swing_mode() const
Definition: climate.cpp:282
bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:98
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, size_t timeout)
Definition: haier_base.cpp:47
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
std::chrono::steady_clock::time_point last_request_timestamp_
Definition: haier_base.h:171
void set_supported_swing_modes(std::set< ClimateSwingMode > modes)
haier_protocol::HandlerError timeout_default_handler_(haier_protocol::FrameType request_type)
Definition: haier_base.cpp:233
void send_custom_command(const haier_protocol::HaierMessage &message)
Definition: haier_base.cpp:196
void set_supported_presets(const std::set< esphome::climate::ClimatePreset > &presets)
Definition: haier_base.cpp:188
void set_supports_current_temperature(bool supports_current_temperature)
The fan mode is set to Medium.
Definition: climate_mode.h:56
void set_health_mode_switch(switch_::Switch *sw)
Definition: haier_base.cpp:403
switch_::Switch * display_switch_
Definition: haier_base.h:42
The climate device only has the fan enabled, no heating or cooling is taking place.
Definition: climate_mode.h:20
void publish_state(bool state)
Publish a state to the front-end from the back-end.
Definition: switch.cpp:47
bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now)
Definition: haier_base.cpp:102
CallbackManager< void(const char *, size_t)> status_message_callback_
Definition: haier_base.h:175
bool state
The current reported state of the binary sensor.
Definition: switch.h:53
void add_supported_mode(ClimateMode mode)
uint32_t get_object_id_hash()
Definition: entity_base.cpp:76
esphome::climate::ClimateTraits traits_
Definition: haier_base.h:167
esphome::optional< esphome::climate::ClimateSwingMode > swing_mode
Definition: haier_base.h:136
bool state
Definition: fan.h:34
std::chrono::steady_clock::time_point last_valid_status_timestamp_
Definition: haier_base.h:172
esphome::optional< esphome::climate::ClimateMode > mode
Definition: haier_base.h:134