ESPHome 2025.5.0
Loading...
Searching...
No Matches
tuya_climate.cpp
Go to the documentation of this file.
1#include "tuya_climate.h"
2#include "esphome/core/log.h"
3
4namespace esphome {
5namespace tuya {
6
7static const char *const TAG = "tuya.climate";
8
10 if (this->switch_id_.has_value()) {
11 this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) {
12 ESP_LOGV(TAG, "MCU reported switch is: %s", ONOFF(datapoint.value_bool));
14 if (datapoint.value_bool) {
15 if (this->supports_heat_ && this->supports_cool_) {
17 } else if (this->supports_heat_) {
19 } else if (this->supports_cool_) {
21 }
22 }
23 this->compute_state_();
24 this->publish_state();
25 });
26 }
27 if (this->heating_state_pin_ != nullptr) {
30 }
31 if (this->cooling_state_pin_ != nullptr) {
34 }
35 if (this->active_state_id_.has_value()) {
36 this->parent_->register_listener(*this->active_state_id_, [this](const TuyaDatapoint &datapoint) {
37 ESP_LOGV(TAG, "MCU reported active state is: %u", datapoint.value_enum);
38 this->active_state_ = datapoint.value_enum;
39 this->compute_state_();
40 this->publish_state();
41 });
42 }
44 this->parent_->register_listener(*this->target_temperature_id_, [this](const TuyaDatapoint &datapoint) {
46 if (this->reports_fahrenheit_) {
47 this->manual_temperature_ = (this->manual_temperature_ - 32) * 5 / 9;
48 }
49
50 ESP_LOGV(TAG, "MCU reported manual target temperature is: %.1f", this->manual_temperature_);
52 this->compute_state_();
53 this->publish_state();
54 });
55 }
57 this->parent_->register_listener(*this->current_temperature_id_, [this](const TuyaDatapoint &datapoint) {
59 if (this->reports_fahrenheit_) {
60 this->current_temperature = (this->current_temperature - 32) * 5 / 9;
61 }
62
63 ESP_LOGV(TAG, "MCU reported current temperature is: %.1f", this->current_temperature);
64 this->compute_state_();
65 this->publish_state();
66 });
67 }
68 if (this->eco_id_.has_value()) {
69 this->parent_->register_listener(*this->eco_id_, [this](const TuyaDatapoint &datapoint) {
70 this->eco_ = datapoint.value_bool;
71 ESP_LOGV(TAG, "MCU reported eco is: %s", ONOFF(this->eco_));
72 this->compute_preset_();
74 this->publish_state();
75 });
76 }
77 if (this->sleep_id_.has_value()) {
78 this->parent_->register_listener(*this->sleep_id_, [this](const TuyaDatapoint &datapoint) {
79 this->sleep_ = datapoint.value_bool;
80 ESP_LOGV(TAG, "MCU reported sleep is: %s", ONOFF(this->sleep_));
81 this->compute_preset_();
83 this->publish_state();
84 });
85 }
86 if (this->swing_vertical_id_.has_value()) {
87 this->parent_->register_listener(*this->swing_vertical_id_, [this](const TuyaDatapoint &datapoint) {
88 this->swing_vertical_ = datapoint.value_bool;
89 ESP_LOGV(TAG, "MCU reported vertical swing is: %s", ONOFF(datapoint.value_bool));
90 this->compute_swingmode_();
91 this->publish_state();
92 });
93 }
94
95 if (this->swing_horizontal_id_.has_value()) {
96 this->parent_->register_listener(*this->swing_horizontal_id_, [this](const TuyaDatapoint &datapoint) {
97 this->swing_horizontal_ = datapoint.value_bool;
98 ESP_LOGV(TAG, "MCU reported horizontal swing is: %s", ONOFF(datapoint.value_bool));
99 this->compute_swingmode_();
100 this->publish_state();
101 });
102 }
103
104 if (this->fan_speed_id_.has_value()) {
105 this->parent_->register_listener(*this->fan_speed_id_, [this](const TuyaDatapoint &datapoint) {
106 ESP_LOGV(TAG, "MCU reported Fan Speed Mode is: %u", datapoint.value_enum);
107 this->fan_state_ = datapoint.value_enum;
108 this->compute_fanmode_();
109 this->publish_state();
110 });
111 }
112}
113
115 bool state_changed = false;
116 if (this->heating_state_pin_ != nullptr) {
117 bool heating_state = this->heating_state_pin_->digital_read();
118 if (heating_state != this->heating_state_) {
119 ESP_LOGV(TAG, "Heating state pin changed to: %s", ONOFF(heating_state));
120 this->heating_state_ = heating_state;
121 state_changed = true;
122 }
123 }
124 if (this->cooling_state_pin_ != nullptr) {
125 bool cooling_state = this->cooling_state_pin_->digital_read();
126 if (cooling_state != this->cooling_state_) {
127 ESP_LOGV(TAG, "Cooling state pin changed to: %s", ONOFF(cooling_state));
128 this->cooling_state_ = cooling_state;
129 state_changed = true;
130 }
131 }
132
133 if (state_changed) {
134 this->compute_state_();
135 this->publish_state();
136 }
137}
138
140 if (call.get_mode().has_value()) {
141 const bool switch_state = *call.get_mode() != climate::CLIMATE_MODE_OFF;
142 ESP_LOGV(TAG, "Setting switch: %s", ONOFF(switch_state));
143 this->parent_->set_boolean_datapoint_value(*this->switch_id_, switch_state);
144 const climate::ClimateMode new_mode = *call.get_mode();
145
146 if (this->active_state_id_.has_value()) {
147 if (new_mode == climate::CLIMATE_MODE_HEAT && this->supports_heat_) {
149 } else if (new_mode == climate::CLIMATE_MODE_COOL && this->supports_cool_) {
151 } else if (new_mode == climate::CLIMATE_MODE_DRY && this->active_state_drying_value_.has_value()) {
153 } else if (new_mode == climate::CLIMATE_MODE_FAN_ONLY && this->active_state_fanonly_value_.has_value()) {
155 }
156 } else {
157 ESP_LOGW(TAG, "Active state (mode) datapoint not configured");
158 }
159 }
160
163
164 if (call.get_target_temperature().has_value()) {
165 float target_temperature = *call.get_target_temperature();
166 if (this->reports_fahrenheit_)
167 target_temperature = (target_temperature * 9 / 5) + 32;
168
169 ESP_LOGV(TAG, "Setting target temperature: %.1f", target_temperature);
171 (int) (target_temperature / this->target_temperature_multiplier_));
172 }
173
174 if (call.get_preset().has_value()) {
175 const climate::ClimatePreset preset = *call.get_preset();
176 if (this->eco_id_.has_value()) {
177 const bool eco = preset == climate::CLIMATE_PRESET_ECO;
178 ESP_LOGV(TAG, "Setting eco: %s", ONOFF(eco));
179 this->parent_->set_boolean_datapoint_value(*this->eco_id_, eco);
180 }
181 if (this->sleep_id_.has_value()) {
182 const bool sleep = preset == climate::CLIMATE_PRESET_SLEEP;
183 ESP_LOGV(TAG, "Setting sleep: %s", ONOFF(sleep));
184 this->parent_->set_boolean_datapoint_value(*this->sleep_id_, sleep);
185 }
186 }
187}
188
190 bool vertical_swing_changed = false;
191 bool horizontal_swing_changed = false;
192
193 if (call.get_swing_mode().has_value()) {
194 const auto swing_mode = *call.get_swing_mode();
195
196 switch (swing_mode) {
199 this->swing_vertical_ = false;
200 this->swing_horizontal_ = false;
201 vertical_swing_changed = true;
202 horizontal_swing_changed = true;
203 }
204 break;
205
208 this->swing_vertical_ = true;
209 this->swing_horizontal_ = true;
210 vertical_swing_changed = true;
211 horizontal_swing_changed = true;
212 }
213 break;
214
217 this->swing_vertical_ = true;
218 this->swing_horizontal_ = false;
219 vertical_swing_changed = true;
220 horizontal_swing_changed = true;
221 }
222 break;
223
226 this->swing_vertical_ = false;
227 this->swing_horizontal_ = true;
228 vertical_swing_changed = true;
229 horizontal_swing_changed = true;
230 }
231 break;
232
233 default:
234 break;
235 }
236 }
237
238 if (vertical_swing_changed && this->swing_vertical_id_.has_value()) {
239 ESP_LOGV(TAG, "Setting vertical swing: %s", ONOFF(swing_vertical_));
241 }
242
243 if (horizontal_swing_changed && this->swing_horizontal_id_.has_value()) {
244 ESP_LOGV(TAG, "Setting horizontal swing: %s", ONOFF(swing_horizontal_));
246 }
247
248 // Publish the state after updating the swing mode
249 this->publish_state();
250}
251
253 if (call.get_fan_mode().has_value()) {
254 climate::ClimateFanMode fan_mode = *call.get_fan_mode();
255
256 uint8_t tuya_fan_speed;
257 switch (fan_mode) {
259 tuya_fan_speed = *fan_speed_low_value_;
260 break;
262 tuya_fan_speed = *fan_speed_medium_value_;
263 break;
265 tuya_fan_speed = *fan_speed_middle_value_;
266 break;
268 tuya_fan_speed = *fan_speed_high_value_;
269 break;
271 tuya_fan_speed = *fan_speed_auto_value_;
272 break;
273 default:
274 tuya_fan_speed = 0;
275 break;
276 }
277
278 if (this->fan_speed_id_.has_value()) {
279 this->parent_->set_enum_datapoint_value(*this->fan_speed_id_, tuya_fan_speed);
280 }
281 }
282}
283
288 if (supports_heat_)
290 if (supports_cool_)
296 if (this->eco_id_.has_value()) {
298 }
299 if (this->sleep_id_.has_value()) {
301 }
302 if (this->sleep_id_.has_value() || this->eco_id_.has_value()) {
304 }
305 if (this->swing_vertical_id_.has_value() && this->swing_horizontal_id_.has_value()) {
306 std::set<climate::ClimateSwingMode> supported_swing_modes = {
309 traits.set_supported_swing_modes(std::move(supported_swing_modes));
310 } else if (this->swing_vertical_id_.has_value()) {
311 std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF,
313 traits.set_supported_swing_modes(std::move(supported_swing_modes));
314 } else if (this->swing_horizontal_id_.has_value()) {
315 std::set<climate::ClimateSwingMode> supported_swing_modes = {climate::CLIMATE_SWING_OFF,
317 traits.set_supported_swing_modes(std::move(supported_swing_modes));
318 }
319
320 if (fan_speed_id_) {
331 }
332 return traits;
333}
334
336 LOG_CLIMATE("", "Tuya Climate", this);
337 if (this->switch_id_.has_value()) {
338 ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_);
339 }
340 if (this->active_state_id_.has_value()) {
341 ESP_LOGCONFIG(TAG, " Active state has datapoint ID %u", *this->active_state_id_);
342 }
343 if (this->target_temperature_id_.has_value()) {
344 ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_);
345 }
347 ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_);
348 }
349 LOG_PIN(" Heating State Pin: ", this->heating_state_pin_);
350 LOG_PIN(" Cooling State Pin: ", this->cooling_state_pin_);
351 if (this->eco_id_.has_value()) {
352 ESP_LOGCONFIG(TAG, " Eco has datapoint ID %u", *this->eco_id_);
353 }
354 if (this->sleep_id_.has_value()) {
355 ESP_LOGCONFIG(TAG, " Sleep has datapoint ID %u", *this->sleep_id_);
356 }
357 if (this->swing_vertical_id_.has_value()) {
358 ESP_LOGCONFIG(TAG, " Swing Vertical has datapoint ID %u", *this->swing_vertical_id_);
359 }
360 if (this->swing_horizontal_id_.has_value()) {
361 ESP_LOGCONFIG(TAG, " Swing Horizontal has datapoint ID %u", *this->swing_horizontal_id_);
362 }
363}
364
366 if (this->eco_) {
368 } else if (this->sleep_) {
370 } else {
372 }
373}
374
376 if (this->swing_vertical_ && this->swing_horizontal_) {
378 } else if (this->swing_vertical_) {
380 } else if (this->swing_horizontal_) {
382 } else {
384 }
385}
386
388 if (this->fan_speed_id_.has_value()) {
389 // Use state from MCU datapoint
390 if (this->fan_speed_auto_value_.has_value() && this->fan_state_ == this->fan_speed_auto_value_) {
392 } else if (this->fan_speed_high_value_.has_value() && this->fan_state_ == this->fan_speed_high_value_) {
394 } else if (this->fan_speed_medium_value_.has_value() && this->fan_state_ == this->fan_speed_medium_value_) {
396 } else if (this->fan_speed_middle_value_.has_value() && this->fan_state_ == this->fan_speed_middle_value_) {
398 } else if (this->fan_speed_low_value_.has_value() && this->fan_state_ == this->fan_speed_low_value_) {
400 }
401 }
402}
403
405 if (this->eco_ && this->eco_temperature_.has_value()) {
407 } else {
409 }
410}
411
413 if (std::isnan(this->current_temperature) || std::isnan(this->target_temperature)) {
414 // if any control parameters are nan, go to OFF action (not IDLE!)
416 return;
417 }
418
419 if (this->mode == climate::CLIMATE_MODE_OFF) {
421 return;
422 }
423
425 if (this->heating_state_pin_ != nullptr || this->cooling_state_pin_ != nullptr) {
426 // Use state from input pins
427 if (this->heating_state_) {
428 target_action = climate::CLIMATE_ACTION_HEATING;
430 } else if (this->cooling_state_) {
431 target_action = climate::CLIMATE_ACTION_COOLING;
433 }
434 if (this->active_state_id_.has_value()) {
435 // Both are available, use MCU datapoint as mode
437 this->active_state_ == this->active_state_heating_value_) {
439 } else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() &&
440 this->active_state_ == this->active_state_cooling_value_) {
442 } else if (this->active_state_drying_value_.has_value() &&
443 this->active_state_ == this->active_state_drying_value_) {
445 } else if (this->active_state_fanonly_value_.has_value() &&
446 this->active_state_ == this->active_state_fanonly_value_) {
448 }
449 }
450 } else if (this->active_state_id_.has_value()) {
451 // Use state from MCU datapoint
453 this->active_state_ == this->active_state_heating_value_) {
454 target_action = climate::CLIMATE_ACTION_HEATING;
456 } else if (this->supports_cool_ && this->active_state_cooling_value_.has_value() &&
457 this->active_state_ == this->active_state_cooling_value_) {
458 target_action = climate::CLIMATE_ACTION_COOLING;
460 } else if (this->active_state_drying_value_.has_value() &&
461 this->active_state_ == this->active_state_drying_value_) {
462 target_action = climate::CLIMATE_ACTION_DRYING;
464 } else if (this->active_state_fanonly_value_.has_value() &&
465 this->active_state_ == this->active_state_fanonly_value_) {
466 target_action = climate::CLIMATE_ACTION_FAN;
468 }
469 } else {
470 // Fallback to active state calc based on temp and hysteresis
471 const float temp_diff = this->target_temperature - this->current_temperature;
472 if (std::abs(temp_diff) > this->hysteresis_) {
473 if (this->supports_heat_ && temp_diff > 0) {
474 target_action = climate::CLIMATE_ACTION_HEATING;
476 } else if (this->supports_cool_ && temp_diff < 0) {
477 target_action = climate::CLIMATE_ACTION_COOLING;
479 }
480 }
481 }
482
483 this->switch_to_action_(target_action);
484}
485
487 // For now this just sets the current action but could include triggers later
488 this->action = action;
489}
490
491} // namespace tuya
492} // namespace esphome
virtual void setup()=0
virtual bool digital_read()=0
This class is used to encode all control actions on a climate device.
Definition climate.h:33
const optional< ClimatePreset > & get_preset() const
Definition climate.cpp:280
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
ClimateAction action
The active state of the climate device.
Definition climate.h:176
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 add_supported_fan_mode(ClimateFanMode mode)
void set_supports_action(bool supports_action)
void add_supported_preset(ClimatePreset preset)
void set_supported_swing_modes(std::set< ClimateSwingMode > modes)
void add_supported_mode(ClimateMode mode)
void set_supports_current_temperature(bool supports_current_temperature)
bool has_value() const
Definition optional.h:87
optional< uint8_t > fan_speed_high_value_
void switch_to_action_(climate::ClimateAction action)
Switch the climate device to the given climate mode.
optional< uint8_t > swing_vertical_id_
void compute_fanmode_()
Re-Compute the fan mode of this climate controller.
optional< uint8_t > fan_speed_low_value_
optional< uint8_t > current_temperature_id_
void control_fan_mode_(const climate::ClimateCall &call)
Override control to change settings of fan mode.
void compute_target_temperature_()
Re-compute the target temperature of this climate controller.
optional< uint8_t > active_state_id_
optional< uint8_t > fan_speed_auto_value_
void compute_swingmode_()
Re-Compute the swing mode of this climate controller.
void compute_preset_()
Re-compute the active preset of this climate controller.
optional< uint8_t > swing_horizontal_id_
optional< float > eco_temperature_
optional< uint8_t > active_state_fanonly_value_
optional< uint8_t > fan_speed_medium_value_
void control_swing_mode_(const climate::ClimateCall &call)
Override control to change settings of swing mode.
optional< uint8_t > sleep_id_
void compute_state_()
Re-compute the state of this climate controller.
optional< uint8_t > fan_speed_middle_value_
optional< uint8_t > active_state_cooling_value_
optional< uint8_t > active_state_drying_value_
climate::ClimateTraits traits() override
Return the traits of this controller.
optional< uint8_t > fan_speed_id_
optional< uint8_t > active_state_heating_value_
optional< uint8_t > target_temperature_id_
void control(const climate::ClimateCall &call) override
Override control to change settings of the climate device.
optional< uint8_t > eco_id_
optional< uint8_t > switch_id_
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition tuya.cpp:569
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition tuya.cpp:581
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
Definition tuya.cpp:697
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition tuya.cpp:573
ClimatePreset
Enum for all preset modes.
@ CLIMATE_PRESET_NONE
No preset is active.
@ CLIMATE_PRESET_SLEEP
Device is prepared for sleep.
@ CLIMATE_PRESET_ECO
Device is running an energy-saving preset.
@ 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.
ClimateAction
Enum for the current action of the climate device. Values match those of ClimateMode.
@ CLIMATE_ACTION_OFF
The climate device is off (inactive or no power)
@ CLIMATE_ACTION_IDLE
The climate device is idle (monitoring climate but no action needed)
@ CLIMATE_ACTION_DRYING
The climate device is drying.
@ CLIMATE_ACTION_HEATING
The climate device is actively heating.
@ CLIMATE_ACTION_COOLING
The climate device is actively cooling.
@ CLIMATE_ACTION_FAN
The climate device is in fan only mode.
@ 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_MIDDLE
The fan mode is set to Middle.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const char *const TAG
Definition spi.cpp:8
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7