ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
thermostat_climate.cpp
Go to the documentation of this file.
4#include "esphome/core/log.h"
5#include <cinttypes>
6
8
9ESPHOME_LOG_TAG(TAG, "thermostat.climate");
10
12 if (this->use_startup_delay_) {
13 // start timers so that no actions are called for a moment
19 }
20 // add a callback so that whenever the sensor state changes we can take action
21 this->sensor_->add_on_state_callback([this](float state) {
23 // required action may have changed, recompute, refresh, we'll publish_state() later
24 this->switch_to_action_(this->compute_action_(), false);
26 // current temperature and possibly action changed, so publish the new state
27 this->publish_state();
28 });
29 this->current_temperature = this->sensor_->state;
30
31 // register for humidity values and get initial state
32 if (this->humidity_sensor_ != nullptr) {
33 this->humidity_sensor_->add_on_state_callback([this](float state) {
34 this->current_humidity = state;
36 this->publish_state();
37 });
39 }
40
41 auto use_default_preset = true;
42
44 // restore all climate data, if possible
45 auto restore = this->restore_state_();
46 if (restore.has_value()) {
47 use_default_preset = false;
48 restore->to_call(this).perform();
49 }
50 }
51
52 // Either we failed to restore state or the user has requested we always apply the default preset
53 if (use_default_preset) {
56 } else if (this->default_custom_preset_ != nullptr) {
57 this->change_custom_preset_(this->default_custom_preset_);
58 }
59 }
60
61 // refresh the climate action based on the restored settings, we'll publish_state() later
62 this->switch_to_action_(this->compute_action_(), false);
64 this->setup_complete_ = true;
65 this->publish_state();
66}
67
70 for (uint8_t i = 0; i < THERMOSTAT_TIMER_COUNT; i++) {
71 auto &timer = this->timer_[i];
72 if (timer.active && (now - timer.started >= timer.time)) {
73 timer.active = false;
75 }
76 }
77}
78
90
92 bool state_mismatch = this->action != this->compute_action_(true);
93
94 switch (this->compute_action_(true)) {
97 return state_mismatch && (!this->idle_action_ready_());
99 return state_mismatch && (!this->cooling_action_ready_());
101 return state_mismatch && (!this->heating_action_ready_());
103 return state_mismatch && (!this->fanning_action_ready_());
105 return state_mismatch && (!this->drying_action_ready_());
106 default:
107 break;
108 }
109 return false;
110}
111
113 bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_;
114 return state_mismatch && (!this->fan_mode_ready_());
115}
116
118
120 if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) &&
121 (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_)))
122 return false;
123
124 if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_)))
125 return false;
126
127 return true;
128}
129
131 return !std::isnan(this->humidity_hysteresis_) && this->humidity_hysteresis_ >= 0.0f &&
132 this->humidity_hysteresis_ < 100.0f;
133}
134
139
141 if (std::isnan(this->target_temperature)) {
142 // default to the midpoint between visual min and max
143 this->target_temperature =
146 } else {
147 // target_temperature must be between the visual minimum and the visual maximum
148 this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
149 this->get_traits().get_visual_max_temperature());
150 }
151}
152
153void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
154 if (!this->supports_two_points_) {
156 } else if (pin_target_temperature_high) {
157 // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
160 } else {
161 // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
164 }
165}
166
168 if (std::isnan(this->target_temperature_low)) {
170 } else {
171 float target_temperature_low_upper_limit =
174 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
176 this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
177 target_temperature_low_upper_limit);
178 }
179}
180
182 if (std::isnan(this->target_temperature_high)) {
184 } else {
185 float target_temperature_high_lower_limit =
188 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
190 this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
191 this->get_traits().get_visual_max_temperature());
192 }
193}
194
196 if (std::isnan(this->target_humidity)) {
197 this->target_humidity =
199 } else {
200 this->target_humidity = clamp<float>(this->target_humidity, this->get_traits().get_visual_min_humidity(),
201 this->get_traits().get_visual_max_humidity());
202 }
203}
204
206 bool target_temperature_high_changed = false;
207
208 auto preset = call.get_preset();
209 if (preset.has_value()) {
210 // setup_complete_ blocks modifying/resetting the temps immediately after boot
211 if (this->setup_complete_) {
212 this->change_preset_(*preset);
213 } else {
214 this->preset = preset;
215 }
216 }
217 if (call.has_custom_preset()) {
218 // setup_complete_ blocks modifying/resetting the temps immediately after boot
219 if (this->setup_complete_) {
221 } else {
222 // Use the base class method which handles pointer lookup internally
224 }
225 }
226
227 auto mode = call.get_mode();
228 if (mode.has_value()) {
229 this->mode = *mode;
230 }
231 auto fan_mode = call.get_fan_mode();
232 if (fan_mode.has_value()) {
233 this->fan_mode = fan_mode;
234 }
235 auto swing_mode = call.get_swing_mode();
236 if (swing_mode.has_value()) {
237 this->swing_mode = *swing_mode;
238 }
239 if (this->supports_two_points_) {
240 auto target_temp_low = call.get_target_temperature_low();
241 if (target_temp_low.has_value()) {
242 this->target_temperature_low = *target_temp_low;
243 }
244 auto target_temp_high = call.get_target_temperature_high();
245 if (target_temp_high.has_value()) {
246 target_temperature_high_changed = this->target_temperature_high != *target_temp_high;
247 this->target_temperature_high = *target_temp_high;
248 }
249 // ensure the two set points are valid and adjust one of them if necessary
250 this->validate_target_temperatures(target_temperature_high_changed ||
252 } else {
253 auto target_temp = call.get_target_temperature();
254 if (target_temp.has_value()) {
255 this->target_temperature = *target_temp;
257 }
258 }
260 if (target_humidity.has_value()) {
263 }
264 // make any changes happen
265 this->refresh();
266}
267
270
272
273 if (this->supports_two_points_)
275
276 if (this->humidity_sensor_ != nullptr)
278
281
282 if (this->supports_auto_)
284 if (this->supports_heat_cool_)
286 if (this->supports_cool_)
288 if (this->supports_dry_)
290 if (this->supports_fan_only_)
292 if (this->supports_heat_)
294
295 if (this->supports_fan_mode_on_)
297 if (this->supports_fan_mode_off_)
299 if (this->supports_fan_mode_auto_)
301 if (this->supports_fan_mode_low_)
305 if (this->supports_fan_mode_high_)
309 if (this->supports_fan_mode_focus_)
313 if (this->supports_fan_mode_quiet_)
315
320 if (this->supports_swing_mode_off_)
324
325 for (uint8_t i = 0; i < this->preset_count_; i++) {
327 }
328
329 // Custom presets are stored on Climate base class and wired via get_traits()
330
331 return traits;
332}
333
335 auto target_action = climate::CLIMATE_ACTION_IDLE;
336 // if any hysteresis values or current_temperature is not valid, we go to OFF;
337 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
339 }
340 // do not change the action if an "ON" timer is running
341 if ((!ignore_timers) && (this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
345 return this->action;
346 }
347
348 // ensure set point(s) is/are valid before computing the action
350 // everything has been validated so we can now safely compute the action
351 switch (this->mode) {
352 // if the climate mode is OFF then the climate action must be OFF
354 target_action = climate::CLIMATE_ACTION_OFF;
355 break;
357 if (this->fanning_required_())
358 target_action = climate::CLIMATE_ACTION_FAN;
359 break;
361 target_action = climate::CLIMATE_ACTION_DRYING;
362 break;
364 if (this->cooling_required_() && this->heating_required_()) {
365 // this is bad and should never happen, so just stop.
366 // target_action = climate::CLIMATE_ACTION_IDLE;
367 } else if (this->cooling_required_()) {
368 target_action = climate::CLIMATE_ACTION_COOLING;
369 } else if (this->heating_required_()) {
370 target_action = climate::CLIMATE_ACTION_HEATING;
371 }
372 break;
374 if (this->cooling_required_()) {
375 target_action = climate::CLIMATE_ACTION_COOLING;
376 }
377 break;
379 if (this->heating_required_()) {
380 target_action = climate::CLIMATE_ACTION_HEATING;
381 }
382 break;
384 if (this->supports_two_points_) {
385 if (this->cooling_required_() && this->heating_required_()) {
386 // this is bad and should never happen, so just stop.
387 // target_action = climate::CLIMATE_ACTION_IDLE;
388 } else if (this->cooling_required_()) {
389 target_action = climate::CLIMATE_ACTION_COOLING;
390 } else if (this->heating_required_()) {
391 target_action = climate::CLIMATE_ACTION_HEATING;
392 }
393 } else if (this->supports_cool_ && this->cooling_required_()) {
394 target_action = climate::CLIMATE_ACTION_COOLING;
395 } else if (this->supports_heat_ && this->heating_required_()) {
396 target_action = climate::CLIMATE_ACTION_HEATING;
397 }
398 break;
399 default:
400 break;
401 }
402 // do not abruptly switch actions. cycle through IDLE, first. we'll catch this at the next update.
404 (target_action == climate::CLIMATE_ACTION_HEATING)) ||
406 ((target_action == climate::CLIMATE_ACTION_COOLING) || (target_action == climate::CLIMATE_ACTION_DRYING)))) {
408 }
409
410 return target_action;
411}
412
414 auto target_action = climate::CLIMATE_ACTION_IDLE;
415 // if any hysteresis values or current_temperature is not valid, we go to OFF;
416 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
418 }
419
420 // ensure set point(s) is/are valid before computing the action
422 // everything has been validated so we can now safely compute the action
423 switch (this->mode) {
424 // if the climate mode is OFF then the climate action must be OFF
426 target_action = climate::CLIMATE_ACTION_OFF;
427 break;
430 // this is bad and should never happen, so just stop.
431 // target_action = climate::CLIMATE_ACTION_IDLE;
432 } else if (this->supplemental_cooling_required_()) {
433 target_action = climate::CLIMATE_ACTION_COOLING;
434 } else if (this->supplemental_heating_required_()) {
435 target_action = climate::CLIMATE_ACTION_HEATING;
436 }
437 break;
439 if (this->supplemental_cooling_required_()) {
440 target_action = climate::CLIMATE_ACTION_COOLING;
441 }
442 break;
444 if (this->supplemental_heating_required_()) {
445 target_action = climate::CLIMATE_ACTION_HEATING;
446 }
447 break;
448 default:
449 break;
450 }
451
452 return target_action;
453}
454
456 auto target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
457 // if hysteresis value or current_humidity is not valid, we go to OFF
458 if (std::isnan(this->current_humidity) || !this->humidity_hysteresis_valid()) {
460 }
461
462 // ensure set point is valid before computing the action
464 // everything has been validated so we can now safely compute the action
466 // this is bad and should never happen, so just stop.
467 // target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
468 } else if (this->supports_dehumidification_ && this->dehumidification_required_()) {
470 } else if (this->supports_humidification_ && this->humidification_required_()) {
472 }
473
474 return target_action;
475}
476
478 // setup_complete_ helps us ensure an action is called immediately after boot
479 if ((action == this->action) && this->setup_complete_) {
480 // already in target mode
481 return;
482 }
483
484 if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
486 this->setup_complete_) {
487 // switching from OFF to IDLE or vice-versa -- this is only a visual difference.
488 // OFF means user manually disabled, IDLE means the temperature is in target range.
489 this->action = action;
490 if (publish_state)
491 this->publish_state();
492 return;
493 }
494
495 bool action_ready = false;
496 Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr;
497 switch (action) {
500 if (this->idle_action_ready_()) {
502 if (this->action == climate::CLIMATE_ACTION_COOLING) {
505 }
506 if (this->action == climate::CLIMATE_ACTION_FAN) {
509 } else {
511 }
512 }
513 if (this->action == climate::CLIMATE_ACTION_HEATING) {
516 }
517 // trig = this->idle_action_trigger_;
518 ESP_LOGVV(TAG, "Switching to IDLE/OFF action");
519 this->cooling_max_runtime_exceeded_ = false;
520 this->heating_max_runtime_exceeded_ = false;
521 action_ready = true;
522 }
523 break;
525 if (this->cooling_action_ready_()) {
528 if (this->supports_fan_with_cooling_) {
530 trig_fan = &this->fan_only_action_trigger_;
531 }
532 this->cooling_max_runtime_exceeded_ = false;
533 trig = &this->cool_action_trigger_;
534 ESP_LOGVV(TAG, "Switching to COOLING action");
535 action_ready = true;
536 }
537 break;
539 if (this->heating_action_ready_()) {
542 if (this->supports_fan_with_heating_) {
544 trig_fan = &this->fan_only_action_trigger_;
545 }
546 this->heating_max_runtime_exceeded_ = false;
547 trig = &this->heat_action_trigger_;
548 ESP_LOGVV(TAG, "Switching to HEATING action");
549 action_ready = true;
550 }
551 break;
553 if (this->fanning_action_ready_()) {
556 } else {
558 }
559 trig = &this->fan_only_action_trigger_;
560 ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
561 action_ready = true;
562 }
563 break;
565 if (this->drying_action_ready_()) {
568 trig = &this->dry_action_trigger_;
569 ESP_LOGVV(TAG, "Switching to DRYING action");
570 action_ready = true;
571 }
572 break;
573 default:
574 // we cannot report an invalid mode back to HA (even if it asked for one)
575 // and must assume some valid value
577 // trig = this->idle_action_trigger_;
578 }
579
580 if (action_ready) {
581 if (this->prev_action_trigger_ != nullptr) {
583 this->prev_action_trigger_ = nullptr;
584 }
585 this->action = action;
586 this->prev_action_trigger_ = trig;
587 trig->trigger();
588 // if enabled, call the fan_only action with cooling/heating actions
589 if (trig_fan != nullptr) {
590 ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
591 trig_fan->trigger();
592 }
593 if (publish_state)
594 this->publish_state();
595 }
596}
597
599 // setup_complete_ helps us ensure an action is called immediately after boot
600 if ((action == this->supplemental_action_) && this->setup_complete_) {
601 // already in target mode
602 return;
603 }
604
605 switch (action) {
610 break;
613 break;
616 break;
617 default:
618 return;
619 }
620 ESP_LOGVV(TAG, "Updating supplemental action");
623}
624
626 Trigger<> *trig = nullptr;
627
628 switch (this->supplemental_action_) {
632 }
634 ESP_LOGVV(TAG, "Calling supplemental COOLING action");
635 break;
639 }
641 ESP_LOGVV(TAG, "Calling supplemental HEATING action");
642 break;
643 default:
644 break;
645 }
646
647 if (trig != nullptr) {
648 trig->trigger();
649 }
650}
651
653 // setup_complete_ helps us ensure an action is called immediately after boot
654 if ((action == this->humidification_action) && this->setup_complete_) {
655 // already in target mode
656 return;
657 }
658
660 switch (action) {
662 // trig = &this->humidity_control_off_action_trigger_;
663 ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
664 break;
667 ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
668 break;
671 ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
672 break;
674 default:
676 // trig = &this->humidity_control_off_action_trigger_;
677 }
678
679 if (this->prev_humidity_control_trigger_ != nullptr) {
681 this->prev_humidity_control_trigger_ = nullptr;
682 }
685 trig->trigger();
686}
687
689 // setup_complete_ helps us ensure an action is called immediately after boot
690 if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) {
691 // already in target mode
692 return;
693 }
694
695 this->fan_mode = fan_mode;
696 if (publish_state)
697 this->publish_state();
698
699 if (this->fan_mode_ready_()) {
700 Trigger<> *trig = &this->fan_mode_auto_trigger_;
701 switch (fan_mode) {
703 trig = &this->fan_mode_on_trigger_;
704 ESP_LOGVV(TAG, "Switching to FAN_ON mode");
705 break;
707 trig = &this->fan_mode_off_trigger_;
708 ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
709 break;
711 // trig = &this->fan_mode_auto_trigger_;
712 ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
713 break;
715 trig = &this->fan_mode_low_trigger_;
716 ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
717 break;
719 trig = &this->fan_mode_medium_trigger_;
720 ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
721 break;
723 trig = &this->fan_mode_high_trigger_;
724 ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
725 break;
727 trig = &this->fan_mode_middle_trigger_;
728 ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
729 break;
731 trig = &this->fan_mode_focus_trigger_;
732 ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
733 break;
735 trig = &this->fan_mode_diffuse_trigger_;
736 ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
737 break;
739 trig = &this->fan_mode_quiet_trigger_;
740 ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
741 break;
742 default:
743 // we cannot report an invalid mode back to HA (even if it asked for one)
744 // and must assume some valid value
746 // trig = &this->fan_mode_auto_trigger_;
747 }
748 if (this->prev_fan_mode_trigger_ != nullptr) {
750 this->prev_fan_mode_trigger_ = nullptr;
751 }
753 trig->trigger();
754 this->prev_fan_mode_ = fan_mode;
755 this->prev_fan_mode_trigger_ = trig;
756 }
757}
758
760 // setup_complete_ helps us ensure an action is called immediately after boot
761 if ((mode == this->prev_mode_) && this->setup_complete_) {
762 // already in target mode
763 return;
764 }
765
766 if (this->prev_mode_trigger_ != nullptr) {
768 this->prev_mode_trigger_ = nullptr;
769 }
770 Trigger<> *trig = &this->off_mode_trigger_;
771 switch (mode) {
773 trig = &this->auto_mode_trigger_;
774 break;
776 trig = &this->heat_cool_mode_trigger_;
777 break;
779 trig = &this->cool_mode_trigger_;
780 break;
782 trig = &this->heat_mode_trigger_;
783 break;
785 trig = &this->fan_only_mode_trigger_;
786 break;
788 trig = &this->dry_mode_trigger_;
789 break;
791 default:
792 // we cannot report an invalid mode back to HA (even if it asked for one)
793 // and must assume some valid value
795 // trig = this->off_mode_trigger_;
796 }
797 trig->trigger();
798 this->mode = mode;
799 this->prev_mode_ = mode;
800 this->prev_mode_trigger_ = trig;
801 if (publish_state) {
802 this->publish_state();
803 }
804}
805
807 // setup_complete_ helps us ensure an action is called immediately after boot
808 if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) {
809 // already in target mode
810 return;
811 }
812
813 if (this->prev_swing_mode_trigger_ != nullptr) {
815 this->prev_swing_mode_trigger_ = nullptr;
816 }
817 Trigger<> *trig = &this->swing_mode_off_trigger_;
818 switch (swing_mode) {
820 trig = &this->swing_mode_both_trigger_;
821 break;
823 trig = &this->swing_mode_horizontal_trigger_;
824 break;
826 // trig = &this->swing_mode_off_trigger_;
827 break;
829 trig = &this->swing_mode_vertical_trigger_;
830 break;
831 default:
832 // we cannot report an invalid mode back to HA (even if it asked for one)
833 // and must assume some valid value
834 swing_mode = climate::CLIMATE_SWING_OFF;
835 // trig = &this->swing_mode_off_trigger_;
836 }
837 trig->trigger();
838 this->swing_mode = swing_mode;
840 this->prev_swing_mode_trigger_ = trig;
841 if (publish_state)
842 this->publish_state();
843}
844
855
862
869
871
879
886
888 if (this->timer_duration_(timer_index) > 0) {
889 this->timer_[timer_index].started = millis();
890 this->timer_[timer_index].active = true;
891 }
892}
893
895 auto ret = this->timer_[timer_index].active;
896 this->timer_[timer_index].active = false;
897 return ret;
898}
899
901 return this->timer_[timer_index].active;
902}
903
905 return this->timer_[timer_index].time;
906}
907
909 switch (timer_index) {
912 break;
915 break;
918 break;
921 break;
924 break;
927 break;
930 break;
933 break;
936 break;
939 break;
941 default:
942 break;
943 }
944}
945
947 ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
951}
952
954 ESP_LOGVV(TAG, "cooling_off timer expired");
955 this->switch_to_action_(this->compute_action_());
957}
958
960 ESP_LOGVV(TAG, "cooling_on timer expired");
961 this->switch_to_action_(this->compute_action_());
963}
964
966 ESP_LOGVV(TAG, "fan_mode timer expired");
969 this->switch_to_action_(this->compute_action_());
971 }
972}
973
975 ESP_LOGVV(TAG, "fanning_off timer expired");
976 this->switch_to_action_(this->compute_action_());
977}
978
980 ESP_LOGVV(TAG, "fanning_on timer expired");
981 this->switch_to_action_(this->compute_action_());
982}
983
985 ESP_LOGVV(TAG, "heating_max_run_time timer expired");
989}
990
992 ESP_LOGVV(TAG, "heating_off timer expired");
993 this->switch_to_action_(this->compute_action_());
995}
996
998 ESP_LOGVV(TAG, "heating_on timer expired");
999 this->switch_to_action_(this->compute_action_());
1001}
1002
1004 ESP_LOGVV(TAG, "idle_on timer expired");
1005 this->switch_to_action_(this->compute_action_());
1007}
1008
1010 if ((this->prev_target_humidity_ == this->target_humidity) && this->setup_complete_) {
1011 return; // nothing changed, no reason to trigger
1012 } else {
1013 // save the new temperature so we can check it again later; the trigger will fire below
1015 }
1016 // trigger the action
1017 Trigger<> *trig = &this->humidity_change_trigger_;
1018 trig->trigger();
1019}
1020
1022 if (this->supports_two_points_) {
1023 // setup_complete_ helps us ensure an action is called immediately after boot
1026 return; // nothing changed, no reason to trigger
1027 } else {
1028 // save the new temperatures so we can check them again later; the trigger will fire below
1031 }
1032 } else {
1033 if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) {
1034 return; // nothing changed, no reason to trigger
1035 } else {
1036 // save the new temperature so we can check it again later; the trigger will fire below
1038 }
1039 }
1040 // trigger the action
1042 trig->trigger();
1043}
1044
1047
1048 if (this->supports_cool_) {
1049 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1050 // if the current temperature reaches or exceeds the target + deadband, cooling is required
1051 return true;
1053 // if the current temperature is less than or equal to the target - overrun, cooling should stop
1054 return false;
1055 } else {
1056 // if we get here, the current temperature is between target + deadband and target - overrun,
1057 // so the action should not change unless it conflicts with the current mode
1058 return (this->action == climate::CLIMATE_ACTION_COOLING) &&
1060 }
1061 }
1062 return false;
1063}
1064
1067
1068 if (this->supports_fan_only_) {
1069 if (this->supports_fan_only_cooling_) {
1070 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1071 // if the current temperature reaches or exceeds the target + deadband, fanning is required
1072 return true;
1074 // if the current temperature is less than or equal to the target - overrun, fanning should stop
1075 return false;
1076 } else {
1077 // if we get here, the current temperature is between target + deadband and target - overrun,
1078 // so the action should not change unless it conflicts with the current mode
1080 }
1081 } else {
1082 return true;
1083 }
1084 }
1085 return false;
1086}
1087
1090
1091 if (this->supports_heat_) {
1093 // if the current temperature is below or equal to the target - deadband, heating is required
1094 return true;
1095 } else if (this->current_temperature >= temperature + this->heating_overrun_) {
1096 // if the current temperature is above or equal to the target + overrun, heating should stop
1097
1098 return false;
1099 } else {
1100 // if we get here, the current temperature is between target - deadband and target + overrun,
1101 // so the action should not change unless it conflicts with the current mode
1102 return (this->action == climate::CLIMATE_ACTION_HEATING) &&
1104 }
1105 }
1106 return false;
1107}
1108
1111 // the component must supports_cool_ and the climate action must be climate::CLIMATE_ACTION_COOLING. then...
1112 // supplemental cooling is required if the max delta or max runtime was exceeded or the action is already engaged
1113 return this->supports_cool_ && (this->action == climate::CLIMATE_ACTION_COOLING) &&
1117}
1118
1121 // the component must supports_heat_ and the climate action must be climate::CLIMATE_ACTION_HEATING. then...
1122 // supplemental heating is required if the max delta or max runtime was exceeded or the action is already engaged
1123 return this->supports_heat_ && (this->action == climate::CLIMATE_ACTION_HEATING) &&
1127}
1128
1130 if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1131 // if the current humidity exceeds the target + hysteresis, dehumidification is required
1132 return true;
1134 // if the current humidity is less than the target - hysteresis, dehumidification should stop
1135 return false;
1136 }
1137 // if we get here, the current humidity is between target + hysteresis and target - hysteresis,
1138 // so the action should not change
1140}
1141
1144 // if the current humidity is below the target - hysteresis, humidification is required
1145 return true;
1146 } else if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1147 // if the current humidity is above the target + hysteresis, humidification should stop
1148 return false;
1149 }
1150 // if we get here, the current humidity is between target - hysteresis and target + hysteresis,
1151 // so the action should not change
1153}
1154
1156 if (this->supports_heat_) {
1157 ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
1159 }
1160 if ((this->supports_cool_) || (this->supports_fan_only_)) {
1161 ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
1163 }
1164
1165 if (config.mode_.has_value()) {
1166 ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1167 }
1168 if (config.fan_mode_.has_value()) {
1169 ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
1170 LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1171 }
1172 if (config.swing_mode_.has_value()) {
1173 ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
1175 }
1176}
1177
1180 progmem_memcpy(&entry, &this->preset_config_[index], sizeof(entry));
1181 return entry;
1182}
1183
1186 progmem_memcpy(&entry, &this->custom_preset_config_[index], sizeof(entry));
1187 return entry;
1188}
1189
1191 // Linear search through preset configurations
1192 optional<ThermostatClimateTargetTempConfig> config;
1193 for (uint8_t i = 0; i < this->preset_count_; i++) {
1194 auto entry = this->preset_entry_(i);
1195 if (entry.preset == preset) {
1196 config = entry.config;
1197 break;
1198 }
1199 }
1200
1201 if (config.has_value()) {
1202 ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1203 if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
1204 // Fire preset changed trigger
1205 Trigger<> *trig = &this->preset_change_trigger_;
1206 this->set_preset_(preset);
1207 trig->trigger();
1208
1209 this->refresh();
1210 ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1211 } else {
1212 ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1213 }
1214 } else {
1215 ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1216 }
1217}
1218
1220 // Linear search through custom preset configurations
1221 optional<ThermostatClimateTargetTempConfig> config;
1222 for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
1223 auto entry = this->custom_preset_entry_(i);
1224 // Compare first len chars, then verify entry.name ends there (same length)
1225 if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') {
1226 config = entry.config;
1227 break;
1228 }
1229 }
1230
1231 if (config.has_value()) {
1232 ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
1233 if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
1234 this->get_custom_preset() != custom_preset) {
1235 // Fire preset changed trigger
1236 Trigger<> *trig = &this->preset_change_trigger_;
1237 // Use the base class method which handles pointer lookup and preset reset internally
1238 this->set_custom_preset_(custom_preset);
1239 trig->trigger();
1240
1241 this->refresh();
1242 ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
1243 } else {
1244 ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset);
1245 // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally.
1246 // The old code had these lines here unconditionally, which was a bug (double assignment, state modification
1247 // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion.
1248 }
1249 } else {
1250 ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset);
1251 }
1252}
1253
1255 bool something_changed = false;
1256
1257 if (this->supports_two_points_) {
1258 if (this->target_temperature_low != config.default_temperature_low) {
1260 something_changed = true;
1261 }
1264 something_changed = true;
1265 }
1266 } else {
1267 if (this->target_temperature != config.default_temperature) {
1269 something_changed = true;
1270 }
1271 }
1272
1273 // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
1274 // also specifies them then the climate.control call's values will override the preset's values for that call
1275 if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
1276 ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1277 this->mode = *config.mode_;
1278 something_changed = true;
1279 }
1280
1281 if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_)) {
1282 ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1283 this->fan_mode = config.fan_mode_;
1284 something_changed = true;
1285 }
1286
1287 if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
1288 ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
1289 this->swing_mode = *config.swing_mode_;
1290 something_changed = true;
1291 }
1292
1293 return something_changed;
1294}
1295
1297 this->custom_preset_config_ = presets;
1299 // Populate Climate base class custom presets vector
1300 std::vector<const char *> names;
1301 names.reserve(count);
1302 for (uint8_t i = 0; i < count; i++) {
1303 names.push_back(this->custom_preset_entry_(i).name);
1304 }
1305 this->set_supported_custom_presets(names);
1306}
1307
1309
1311 // Find the preset in custom_preset_config_ and store pointer from there
1312 for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
1313 auto entry = this->custom_preset_entry_(i);
1314 if (strcmp(entry.name, custom_preset) == 0) {
1315 this->default_custom_preset_ = entry.name;
1316 return;
1317 }
1318 }
1319 // If not found, it will be caught during validation
1320 this->default_custom_preset_ = nullptr;
1321}
1322
1324 uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1325
1326 if (this->timer_[timer_index].active) {
1327 // Timer is running, calculate elapsed time and adjust if needed
1329 uint32_t elapsed = current_time - this->timer_[timer_index].started;
1330
1331 if (elapsed >= new_duration_ms) {
1332 // Timer should complete immediately (including when new_duration_ms is 0)
1333 ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %" PRIu32 " >= new %" PRIu32 ")", timer_index, elapsed,
1334 new_duration_ms);
1335 this->timer_[timer_index].active = false;
1336 // Trigger the timer callback immediately
1337 this->call_timer_callback_(timer_index);
1338 return;
1339 } else {
1340 // Adjust timer to run for remaining time - keep original start time
1341 ESP_LOGVV(TAG, "timer %d adjusted: elapsed %" PRIu32 ", new total %" PRIu32 ", remaining %" PRIu32, timer_index,
1342 elapsed, new_duration_ms, new_duration_ms - elapsed);
1343 this->timer_[timer_index].time = new_duration_ms;
1344 return;
1345 }
1346 }
1347
1348 // Original logic for non-running timers
1349 this->timer_[timer_index].time = new_duration_ms;
1350}
1351
1382void ThermostatClimate::set_humidity_hysteresis(float humidity_hysteresis) {
1383 this->humidity_hysteresis_ = std::clamp<float>(humidity_hysteresis, 0.0f, 100.0f);
1384}
1385void ThermostatClimate::set_supports_dehumidification(bool supports_dehumidification) {
1386 this->supports_dehumidification_ = supports_dehumidification;
1387 if (supports_dehumidification) {
1388 this->supports_humidification_ = false;
1389 }
1390}
1391void ThermostatClimate::set_supports_humidification(bool supports_humidification) {
1392 this->supports_humidification_ = supports_humidification;
1393 if (supports_humidification) {
1394 this->supports_dehumidification_ = false;
1395 }
1396}
1397
1442
1444 LOG_CLIMATE("", "Thermostat", this);
1445
1446 ESP_LOGCONFIG(TAG,
1447 " On boot, restore from: %s\n"
1448 " Use Start-up Delay: %s",
1449 this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? LOG_STR_LITERAL("DEFAULT_PRESET")
1450 : LOG_STR_LITERAL("MEMORY"),
1451 YESNO(this->use_startup_delay_));
1452 if (this->supports_two_points_) {
1453 ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
1454 }
1455 if (this->supports_cool_) {
1456 ESP_LOGCONFIG(TAG,
1457 " Cooling Parameters:\n"
1458 " Deadband: %.1f°C\n"
1459 " Overrun: %.1f°C\n"
1460 " Minimum Off Time: %" PRIu32 "s\n"
1461 " Minimum Run Time: %" PRIu32 "s",
1465 if ((this->supplemental_cool_delta_ > 0) ||
1467 ESP_LOGCONFIG(TAG,
1468 " Maximum Run Time: %" PRIu32 "s\n"
1469 " Supplemental Delta: %.1f°C",
1472 }
1473 }
1474 if (this->supports_heat_) {
1475 ESP_LOGCONFIG(TAG,
1476 " Heating Parameters:\n"
1477 " Deadband: %.1f°C\n"
1478 " Overrun: %.1f°C\n"
1479 " Minimum Off Time: %" PRIu32 "s\n"
1480 " Minimum Run Time: %" PRIu32 "s",
1484 if ((this->supplemental_heat_delta_ > 0) ||
1486 ESP_LOGCONFIG(TAG,
1487 " Maximum Run Time: %" PRIu32 "s\n"
1488 " Supplemental Delta: %.1f°C",
1491 }
1492 }
1493 if (this->supports_fan_only_) {
1494 ESP_LOGCONFIG(TAG,
1495 " Fan Parameters:\n"
1496 " Minimum Off Time: %" PRIu32 "s\n"
1497 " Minimum Run Time: %" PRIu32 "s",
1500 }
1505 ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
1507 }
1508 ESP_LOGCONFIG(TAG,
1509 " Minimum Idle Time: %" PRIu32 "s\n"
1510 " Supported MODES:\n"
1511 " AUTO: %s\n"
1512 " HEAT/COOL: %s\n"
1513 " HEAT: %s\n"
1514 " COOL: %s\n"
1515 " DRY: %s\n"
1516 " FAN_ONLY: %s\n"
1517 " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
1518 " FAN_ONLY_COOLING: %s",
1519 this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
1520 YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
1521 YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
1523 if (this->supports_cool_) {
1524 ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
1525 }
1526 if (this->supports_heat_) {
1527 ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
1528 }
1529 ESP_LOGCONFIG(TAG,
1530 " Supported FAN MODES:\n"
1531 " ON: %s\n"
1532 " OFF: %s\n"
1533 " AUTO: %s\n"
1534 " LOW: %s\n"
1535 " MEDIUM: %s\n"
1536 " HIGH: %s\n"
1537 " MIDDLE: %s\n"
1538 " FOCUS: %s\n"
1539 " DIFFUSE: %s\n"
1540 " QUIET: %s\n"
1541 " Supported SWING MODES:\n"
1542 " BOTH: %s\n"
1543 " OFF: %s\n"
1544 " HORIZONTAL: %s\n"
1545 " VERTICAL: %s\n"
1546 " Supports TWO SET POINTS: %s\n"
1547 " Supported Humidity Parameters:\n"
1548 " CURRENT: %s\n"
1549 " TARGET: %s\n"
1550 " DEHUMIDIFICATION: %s\n"
1551 " HUMIDIFICATION: %s",
1552 YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
1553 YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
1554 YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
1555 YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
1556 YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
1557 YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
1559 YESNO(this->supports_two_points_),
1560 YESNO(this->get_traits().has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)),
1562 YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
1563
1564 if (this->preset_count_ > 0) {
1565 ESP_LOGCONFIG(TAG, " Supported PRESETS:");
1566 for (uint8_t i = 0; i < this->preset_count_; i++) {
1567 auto entry = this->preset_entry_(i);
1568 const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
1569 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1570 entry.preset == this->default_preset_ ? LOG_STR_LITERAL(" (default)") : "");
1571 this->dump_preset_config_(preset_name, entry.config);
1572 }
1573 }
1574
1575 if (this->custom_preset_count_ > 0) {
1576 ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
1577 for (uint8_t i = 0; i < this->custom_preset_count_; i++) {
1578 auto entry = this->custom_preset_entry_(i);
1579 const auto *preset_name = entry.name;
1580 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1581 (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
1582 ? LOG_STR_LITERAL(" (default)")
1583 : "");
1584 this->dump_preset_config_(preset_name, entry.config);
1585 }
1586 }
1587}
1588
1589} // namespace esphome::thermostat
BedjetMode mode
BedJet operating mode.
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:564
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:556
This class is used to encode all control actions on a climate device.
Definition climate.h:31
const optional< ClimateSwingMode > & get_swing_mode() const
Definition climate.cpp:317
const optional< float > & get_target_humidity() const
Definition climate.cpp:313
bool has_custom_preset() const
Definition climate.h:127
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:316
StringRef get_custom_preset() const
Definition climate.h:125
ClimateMode mode
The active mode of the climate device.
Definition climate.h:317
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:311
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:482
float target_temperature
The target temperature of the climate device.
Definition climate.h:298
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:294
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:323
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:301
void set_supported_custom_presets(std::initializer_list< const char * > presets)
Set the supported custom presets; the list is copied (stored on Climate, referenced by ClimateTraits)...
Definition climate.h:270
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:674
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.h:349
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:288
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:291
ClimateAction action
The active state of the climate device.
Definition climate.h:320
StringRef get_custom_preset() const
Get the active custom preset (read-only access). Returns StringRef.
Definition climate.h:329
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:432
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:314
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:365
float target_humidity
The target humidity of the climate device.
Definition climate.h:308
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:303
void add_feature_flags(uint32_t feature_flags)
void add_supported_fan_mode(ClimateFanMode mode)
void add_supported_preset(ClimatePreset preset)
void add_supported_mode(ClimateMode mode)
void add_supported_swing_mode(ClimateSwingMode mode)
void add_on_state_callback(F &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition sensor.h:121
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:140
void set_supports_humidification(bool supports_humidification)
void switch_to_action_(climate::ClimateAction action, bool publish_state=true)
Switch the climate device to the given climate action.
float cooling_deadband_
Hysteresis values used for computing climate actions.
void control(const climate::ClimateCall &call) override
Override control to change settings of the climate device.
void validate_target_temperatures(bool pin_target_temperature_high)
HumidificationAction humidification_action
The current humidification action.
void set_timer_duration_in_sec_(ThermostatClimateTimerIndex timer_index, uint32_t time)
Enhanced timer duration setter with running timer adjustment.
bool use_startup_delay_
Used to start "off" delay timers at boot.
Trigger temperature_change_trigger_
Trigger for target temperature changes.
bool climate_action_change_delayed()
Returns true if a climate action/fan mode transition is being delayed.
CustomPresetEntry custom_preset_entry_(uint8_t index) const
bool cooling_max_runtime_exceeded_
Flags indicating if maximum allowable run time was exceeded.
const uint8_t min_timer_duration_
Minimum allowable duration in seconds for action timers.
OnBootRestoreFrom on_boot_restore_from_
If set to DEFAULT_PRESET then the default preset is always used.
void change_custom_preset_(const char *custom_preset)
Change to a provided custom preset setting; will reset temperature, mode, fan, and swing modes accord...
bool supports_dehumidification_
Whether the controller supports dehumidification and/or humidification.
const CustomPresetEntry * custom_preset_config_
Custom preset configurations (eg. "My Custom Preset"), a PROGMEM table of custom_preset_count_ entrie...
bool supports_fan_mode_on_
Whether the controller supports turning on or off just the fan.
float set_point_minimum_differential_
Minimum differential required between set points.
bool supports_fan_with_cooling_
Special flags – enables fan_only action to be called with cooling/heating actions.
void switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state=true)
Switch the climate device to the given climate swing mode.
bool change_preset_internal_(const ThermostatClimateTargetTempConfig &config)
Applies the temperature, mode, fan, and swing modes of the provided config.
void set_supports_dehumidification(bool supports_dehumidification)
bool hysteresis_valid()
Set point and hysteresis validation.
float humidity_hysteresis_
Hysteresis values used for computing humidification action.
Trigger cool_action_trigger_
Trigger for cooling action/mode.
float prev_target_humidity_
Store previously-known humidity and temperatures.
void check_humidity_change_trigger_()
Check if the humidity change trigger should be called.
void switch_to_supplemental_action_(climate::ClimateAction action)
void switch_to_mode_(climate::ClimateMode mode, bool publish_state=true)
Switch the climate device to the given climate mode.
void set_custom_preset_config(const CustomPresetEntry *presets, uint8_t count)
bool supports_fan_only_action_uses_fan_mode_timer_
Special flag – enables fan_modes to share timer with fan_only climate action.
void switch_to_humidity_control_action_(HumidificationAction action)
void switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state=true)
Switch the climate device to the given climate fan mode.
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config)
bool supports_swing_mode_both_
Whether the controller supports various swing modes.
climate::ClimateTraits traits() override
Return the traits of this controller.
Trigger humidity_change_trigger_
Trigger for target humidity changes.
bool timer_active_(ThermostatClimateTimerIndex timer_index)
float supplemental_cool_delta_
Maximum allowable temperature deltas before engaging supplemental cooling/heating actions.
climate::ClimateAction delayed_climate_action()
Returns the climate action that is being delayed (check climate_action_change_delayed(),...
sensor::Sensor * sensor_
The sensor used for getting the current temperature.
void set_default_preset(const char *custom_preset)
void call_timer_callback_(ThermostatClimateTimerIndex timer_index)
Call the appropriate timer callback based on timer index.
Trigger idle_action_trigger_
Trigger for idle action/off mode.
uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index)
Trigger humidity_control_dehumidify_action_trigger_
Humidity control triggers.
Trigger fan_only_action_trigger_
Trigger for fan-only action/mode.
void start_timer_(ThermostatClimateTimerIndex timer_index)
Start/cancel/get status of climate action timer.
bool supports_fan_mode_low_
Whether the controller supports various fan speeds and/or positions.
bool supports_fan_only_cooling_
Special flag – enables fan to be switched based on target_temperature_high.
Trigger * prev_action_trigger_
A reference to the trigger that was previously active.
bool supports_auto_
Whether the controller supports auto/cooling/drying/fanning/heating.
Trigger swing_mode_both_trigger_
Swing mode triggers.
climate::ClimateAction supplemental_action_
The current supplemental action.
std::array< ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT > timer_
Climate action timers.
void set_fan_mode_minimum_switching_time_in_sec(uint32_t time)
Trigger heat_cool_mode_trigger_
Trigger for heat/cool mode.
Trigger preset_change_trigger_
Trigger for preset mode changes.
void check_temperature_change_trigger_()
Check if the temperature change trigger should be called.
HumidificationAction compute_humidity_control_action_()
climate::ClimateFanMode prev_fan_mode_
Store previously-known states.
void set_humidity_hysteresis(float humidity_hysteresis)
bool cancel_timer_(ThermostatClimateTimerIndex timer_index)
climate::ClimateAction compute_supplemental_action_()
climate::ClimatePreset default_preset_
Default standard preset to use on start up.
sensor::Sensor * humidity_sensor_
The sensor used for getting the current humidity.
const PresetEntry * preset_config_
Standard preset configurations (Eg. AWAY, ECO, etc), a PROGMEM table of preset_count_ entries.
void cooling_max_run_time_timer_callback_()
set_timeout() callbacks for various actions (see above)
PresetEntry preset_entry_(uint8_t index) const
Copies of table entries; the tables live in PROGMEM and hold byte-sized fields.
bool supports_fan_mode_auto_
Whether the controller supports fan auto mode.
Trigger fan_mode_on_trigger_
Fan mode triggers.
climate::ClimateAction compute_action_(bool ignore_timers=false)
Re-compute the required action of this climate controller.
bool supports_two_points_
Whether the controller supports two set points.
bool setup_complete_
setup_complete_ blocks modifying/resetting the temps immediately after boot
Trigger auto_mode_trigger_
Trigger for auto mode.
bool idle_action_ready_()
Is the action ready to be called? Returns true if so.
Trigger dry_action_trigger_
Trigger for dry (dehumidification) mode.
void change_preset_(climate::ClimatePreset preset)
Change to a provided preset setting; will reset temperature, mode, fan, and swing modes accordingly.
void refresh()
Call triggers based on updated climate states (modes/actions)
Trigger heat_action_trigger_
Trigger for heating action/mode.
bool cooling_required_()
Check if cooling/fanning/heating actions are required; returns true if so.
ClimateSwingMode swing_mode
Definition climate.h:11
uint8_t custom_preset
Definition climate.h:9
ClimateFanMode fan_mode
Definition climate.h:3
ClimatePreset preset
Definition climate.h:8
bool state
Definition fan.h:2
unsigned count
Definition gif.cpp:51
int ret
@ CLIMATE_SUPPORTS_CURRENT_HUMIDITY
@ CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE
@ CLIMATE_SUPPORTS_CURRENT_TEMPERATURE
const LogString * climate_swing_mode_to_string(ClimateSwingMode swing_mode)
Convert the given ClimateSwingMode to a human-readable string.
const LogString * climate_preset_to_string(ClimatePreset preset)
Convert the given PresetMode to a human-readable string.
ClimatePreset
Enum for all preset modes NOTE: If adding values, update ClimatePresetMask in climate_traits....
@ CLIMATE_PRESET_NONE
No preset is active.
const LogString * climate_fan_mode_to_string(ClimateFanMode fan_mode)
Convert the given ClimateFanMode to a human-readable string.
ClimateSwingMode
Enum for all modes a climate swing can be in NOTE: If adding values, update ClimateSwingModeMask in c...
@ 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.
@ CLIMATE_MODE_AUTO
The climate device is adjusting the temperature dynamically.
const LogString * climate_mode_to_string(ClimateMode mode)
Convert the given ClimateMode to a human-readable string.
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.
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
@ CLIMATE_FAN_MEDIUM
The fan mode is set to Medium.
@ CLIMATE_FAN_DIFFUSE
The fan mode is set to Diffuse.
@ CLIMATE_FAN_ON
The fan mode is set to On.
@ CLIMATE_FAN_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_FOCUS
The fan mode is set to Focus.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_MIDDLE
The fan mode is set to Middle.
@ CLIMATE_FAN_QUIET
The fan mode is set to Quiet.
@ CLIMATE_FAN_OFF
The fan mode is set to Off.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
ESPHOME_LOG_TAG(TAG, "thermostat.climate")
void progmem_memcpy(void *dst, const void *src, size_t len)
Definition hal.h:56
const void size_t len
Definition hal.h:68
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
optional< climate::ClimateSwingMode > swing_mode_
Entry for custom preset lookup.
Entry for standard preset lookup.
uint16_t temperature
Definition sun_gtil2.cpp:12