ESPHome 2026.10.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
9static const char *const 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 (const auto &entry : this->preset_config_) {
326 traits.add_supported_preset(entry.preset);
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
1179 // Linear search through preset configurations
1180 const ThermostatClimateTargetTempConfig *config = nullptr;
1181 for (const auto &entry : this->preset_config_) {
1182 if (entry.preset == preset) {
1183 config = &entry.config;
1184 break;
1185 }
1186 }
1187
1188 if (config != nullptr) {
1189 ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1190 if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
1191 // Fire preset changed trigger
1192 Trigger<> *trig = &this->preset_change_trigger_;
1193 this->set_preset_(preset);
1194 trig->trigger();
1195
1196 this->refresh();
1197 ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1198 } else {
1199 ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1200 }
1201 } else {
1202 ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1203 }
1204}
1205
1207 // Linear search through custom preset configurations
1208 const ThermostatClimateTargetTempConfig *config = nullptr;
1209 for (const auto &entry : this->custom_preset_config_) {
1210 // Compare first len chars, then verify entry.name ends there (same length)
1211 if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') {
1212 config = &entry.config;
1213 break;
1214 }
1215 }
1216
1217 if (config != nullptr) {
1218 ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
1219 if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
1220 this->get_custom_preset() != custom_preset) {
1221 // Fire preset changed trigger
1222 Trigger<> *trig = &this->preset_change_trigger_;
1223 // Use the base class method which handles pointer lookup and preset reset internally
1224 this->set_custom_preset_(custom_preset);
1225 trig->trigger();
1226
1227 this->refresh();
1228 ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
1229 } else {
1230 ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset);
1231 // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally.
1232 // The old code had these lines here unconditionally, which was a bug (double assignment, state modification
1233 // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion.
1234 }
1235 } else {
1236 ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset);
1237 }
1238}
1239
1241 bool something_changed = false;
1242
1243 if (this->supports_two_points_) {
1244 if (this->target_temperature_low != config.default_temperature_low) {
1246 something_changed = true;
1247 }
1250 something_changed = true;
1251 }
1252 } else {
1253 if (this->target_temperature != config.default_temperature) {
1255 something_changed = true;
1256 }
1257 }
1258
1259 // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
1260 // also specifies them then the climate.control call's values will override the preset's values for that call
1261 if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
1262 ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1263 this->mode = *config.mode_;
1264 something_changed = true;
1265 }
1266
1267 if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_)) {
1268 ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1269 this->fan_mode = config.fan_mode_;
1270 something_changed = true;
1271 }
1272
1273 if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
1274 ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
1275 this->swing_mode = *config.swing_mode_;
1276 something_changed = true;
1277 }
1278
1279 return something_changed;
1280}
1281
1282void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
1283 this->custom_preset_config_ = presets;
1284 // Populate Climate base class custom presets vector
1285 std::vector<const char *> names;
1286 names.reserve(presets.size());
1287 for (const auto &entry : this->custom_preset_config_) {
1288 names.push_back(entry.name);
1289 }
1290 this->set_supported_custom_presets(names);
1291}
1292
1294
1296 // Find the preset in custom_preset_config_ and store pointer from there
1297 for (const auto &entry : this->custom_preset_config_) {
1298 if (strcmp(entry.name, custom_preset) == 0) {
1299 this->default_custom_preset_ = entry.name;
1300 return;
1301 }
1302 }
1303 // If not found, it will be caught during validation
1304 this->default_custom_preset_ = nullptr;
1305}
1306
1308
1310 uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1311
1312 if (this->timer_[timer_index].active) {
1313 // Timer is running, calculate elapsed time and adjust if needed
1315 uint32_t elapsed = current_time - this->timer_[timer_index].started;
1316
1317 if (elapsed >= new_duration_ms) {
1318 // Timer should complete immediately (including when new_duration_ms is 0)
1319 ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %" PRIu32 " >= new %" PRIu32 ")", timer_index, elapsed,
1320 new_duration_ms);
1321 this->timer_[timer_index].active = false;
1322 // Trigger the timer callback immediately
1323 this->call_timer_callback_(timer_index);
1324 return;
1325 } else {
1326 // Adjust timer to run for remaining time - keep original start time
1327 ESP_LOGVV(TAG, "timer %d adjusted: elapsed %" PRIu32 ", new total %" PRIu32 ", remaining %" PRIu32, timer_index,
1328 elapsed, new_duration_ms, new_duration_ms - elapsed);
1329 this->timer_[timer_index].time = new_duration_ms;
1330 return;
1331 }
1332 }
1333
1334 // Original logic for non-running timers
1335 this->timer_[timer_index].time = new_duration_ms;
1336}
1337
1368void ThermostatClimate::set_humidity_hysteresis(float humidity_hysteresis) {
1369 this->humidity_hysteresis_ = std::clamp<float>(humidity_hysteresis, 0.0f, 100.0f);
1370}
1371void ThermostatClimate::set_supports_dehumidification(bool supports_dehumidification) {
1372 this->supports_dehumidification_ = supports_dehumidification;
1373 if (supports_dehumidification) {
1374 this->supports_humidification_ = false;
1375 }
1376}
1377void ThermostatClimate::set_supports_humidification(bool supports_humidification) {
1378 this->supports_humidification_ = supports_humidification;
1379 if (supports_humidification) {
1380 this->supports_dehumidification_ = false;
1381 }
1382}
1383
1428
1430 LOG_CLIMATE("", "Thermostat", this);
1431
1432 ESP_LOGCONFIG(TAG,
1433 " On boot, restore from: %s\n"
1434 " Use Start-up Delay: %s",
1435 this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? LOG_STR_LITERAL("DEFAULT_PRESET")
1436 : LOG_STR_LITERAL("MEMORY"),
1437 YESNO(this->use_startup_delay_));
1438 if (this->supports_two_points_) {
1439 ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
1440 }
1441 if (this->supports_cool_) {
1442 ESP_LOGCONFIG(TAG,
1443 " Cooling Parameters:\n"
1444 " Deadband: %.1f°C\n"
1445 " Overrun: %.1f°C\n"
1446 " Minimum Off Time: %" PRIu32 "s\n"
1447 " Minimum Run Time: %" PRIu32 "s",
1451 if ((this->supplemental_cool_delta_ > 0) ||
1453 ESP_LOGCONFIG(TAG,
1454 " Maximum Run Time: %" PRIu32 "s\n"
1455 " Supplemental Delta: %.1f°C",
1458 }
1459 }
1460 if (this->supports_heat_) {
1461 ESP_LOGCONFIG(TAG,
1462 " Heating Parameters:\n"
1463 " Deadband: %.1f°C\n"
1464 " Overrun: %.1f°C\n"
1465 " Minimum Off Time: %" PRIu32 "s\n"
1466 " Minimum Run Time: %" PRIu32 "s",
1470 if ((this->supplemental_heat_delta_ > 0) ||
1472 ESP_LOGCONFIG(TAG,
1473 " Maximum Run Time: %" PRIu32 "s\n"
1474 " Supplemental Delta: %.1f°C",
1477 }
1478 }
1479 if (this->supports_fan_only_) {
1480 ESP_LOGCONFIG(TAG,
1481 " Fan Parameters:\n"
1482 " Minimum Off Time: %" PRIu32 "s\n"
1483 " Minimum Run Time: %" PRIu32 "s",
1486 }
1491 ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
1493 }
1494 ESP_LOGCONFIG(TAG,
1495 " Minimum Idle Time: %" PRIu32 "s\n"
1496 " Supported MODES:\n"
1497 " AUTO: %s\n"
1498 " HEAT/COOL: %s\n"
1499 " HEAT: %s\n"
1500 " COOL: %s\n"
1501 " DRY: %s\n"
1502 " FAN_ONLY: %s\n"
1503 " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
1504 " FAN_ONLY_COOLING: %s",
1505 this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
1506 YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
1507 YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
1509 if (this->supports_cool_) {
1510 ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
1511 }
1512 if (this->supports_heat_) {
1513 ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
1514 }
1515 ESP_LOGCONFIG(TAG,
1516 " Supported FAN MODES:\n"
1517 " ON: %s\n"
1518 " OFF: %s\n"
1519 " AUTO: %s\n"
1520 " LOW: %s\n"
1521 " MEDIUM: %s\n"
1522 " HIGH: %s\n"
1523 " MIDDLE: %s\n"
1524 " FOCUS: %s\n"
1525 " DIFFUSE: %s\n"
1526 " QUIET: %s\n"
1527 " Supported SWING MODES:\n"
1528 " BOTH: %s\n"
1529 " OFF: %s\n"
1530 " HORIZONTAL: %s\n"
1531 " VERTICAL: %s\n"
1532 " Supports TWO SET POINTS: %s\n"
1533 " Supported Humidity Parameters:\n"
1534 " CURRENT: %s\n"
1535 " TARGET: %s\n"
1536 " DEHUMIDIFICATION: %s\n"
1537 " HUMIDIFICATION: %s",
1538 YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
1539 YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
1540 YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
1541 YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
1542 YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
1543 YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
1545 YESNO(this->supports_two_points_),
1546 YESNO(this->get_traits().has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)),
1548 YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
1549
1550 if (!this->preset_config_.empty()) {
1551 ESP_LOGCONFIG(TAG, " Supported PRESETS:");
1552 for (const auto &entry : this->preset_config_) {
1553 const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
1554 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1555 entry.preset == this->default_preset_ ? LOG_STR_LITERAL(" (default)") : "");
1556 this->dump_preset_config_(preset_name, entry.config);
1557 }
1558 }
1559
1560 if (!this->custom_preset_config_.empty()) {
1561 ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
1562 for (const auto &entry : this->custom_preset_config_) {
1563 const auto *preset_name = entry.name;
1564 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1565 (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
1566 ? LOG_STR_LITERAL(" (default)")
1567 : "");
1568 this->dump_preset_config_(preset_name, entry.config);
1569 }
1570 }
1571}
1572
1574
1576 : default_temperature(default_temperature) {}
1577
1579 float default_temperature_high)
1580 : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
1581
1582} // 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:469
void trigger(const Ts &...x) ESPHOME_ALWAYS_INLINE
Inform the parent automation that the event has triggered.
Definition automation.h:461
This class is used to encode all control actions on a climate device.
Definition climate.h:34
const optional< ClimateSwingMode > & get_swing_mode() const
Definition climate.cpp:314
const optional< float > & get_target_humidity() const
Definition climate.cpp:310
bool has_custom_preset() const
Definition climate.h:122
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:313
StringRef get_custom_preset() const
Definition climate.h:120
ClimateMode mode
The active mode of the climate device.
Definition climate.h:304
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:298
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:486
float target_temperature
The target temperature of the climate device.
Definition climate.h:285
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:281
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:310
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:288
void set_supported_custom_presets(std::initializer_list< const char * > presets)
Set the supported custom presets (stored on Climate, referenced by ClimateTraits).
Definition climate.h:261
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:680
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.h:336
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:275
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:278
ClimateAction action
The active state of the climate device.
Definition climate.h:307
StringRef get_custom_preset() const
Get the active custom preset (read-only access). Returns StringRef.
Definition climate.h:316
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:437
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:301
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:362
float target_humidity
The target humidity of the climate device.
Definition climate.h:295
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:290
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:119
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:138
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.
void set_custom_preset_config(std::initializer_list< CustomPresetEntry > presets)
FixedVector< CustomPresetEntry > custom_preset_config_
The set of custom preset configurations this thermostat supports (eg. "My Custom Preset")
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.
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.
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.
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_()
FixedVector< PresetEntry > preset_config_
The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO,...
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.
void cooling_max_run_time_timer_callback_()
set_timeout() callbacks for various actions (see above)
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
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.
const void size_t len
Definition hal.h:64
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
optional< climate::ClimateSwingMode > swing_mode_
uint16_t temperature
Definition sun_gtil2.cpp:12