ESPHome 2026.6.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
83
95
97 bool state_mismatch = this->action != this->compute_action_(true);
98
99 switch (this->compute_action_(true)) {
102 return state_mismatch && (!this->idle_action_ready_());
104 return state_mismatch && (!this->cooling_action_ready_());
106 return state_mismatch && (!this->heating_action_ready_());
108 return state_mismatch && (!this->fanning_action_ready_());
110 return state_mismatch && (!this->drying_action_ready_());
111 default:
112 break;
113 }
114 return false;
115}
116
118 bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_;
119 return state_mismatch && (!this->fan_mode_ready_());
120}
121
123
125
127 if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) &&
128 (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_)))
129 return false;
130
131 if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_)))
132 return false;
133
134 return true;
135}
136
138 return !std::isnan(this->humidity_hysteresis_) && this->humidity_hysteresis_ >= 0.0f &&
139 this->humidity_hysteresis_ < 100.0f;
140}
141
146
148 if (std::isnan(this->target_temperature)) {
149 // default to the midpoint between visual min and max
150 this->target_temperature =
153 } else {
154 // target_temperature must be between the visual minimum and the visual maximum
155 this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
156 this->get_traits().get_visual_max_temperature());
157 }
158}
159
160void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
161 if (!this->supports_two_points_) {
163 } else if (pin_target_temperature_high) {
164 // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
167 } else {
168 // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
171 }
172}
173
175 if (std::isnan(this->target_temperature_low)) {
177 } else {
178 float target_temperature_low_upper_limit =
181 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
183 this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
184 target_temperature_low_upper_limit);
185 }
186}
187
189 if (std::isnan(this->target_temperature_high)) {
191 } else {
192 float target_temperature_high_lower_limit =
195 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
197 this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
198 this->get_traits().get_visual_max_temperature());
199 }
200}
201
203 if (std::isnan(this->target_humidity)) {
204 this->target_humidity =
206 } else {
207 this->target_humidity = clamp<float>(this->target_humidity, this->get_traits().get_visual_min_humidity(),
208 this->get_traits().get_visual_max_humidity());
209 }
210}
211
213 bool target_temperature_high_changed = false;
214
215 auto preset = call.get_preset();
216 if (preset.has_value()) {
217 // setup_complete_ blocks modifying/resetting the temps immediately after boot
218 if (this->setup_complete_) {
219 this->change_preset_(*preset);
220 } else {
221 this->preset = preset;
222 }
223 }
224 if (call.has_custom_preset()) {
225 // setup_complete_ blocks modifying/resetting the temps immediately after boot
226 if (this->setup_complete_) {
228 } else {
229 // Use the base class method which handles pointer lookup internally
231 }
232 }
233
234 auto mode = call.get_mode();
235 if (mode.has_value()) {
236 this->mode = *mode;
237 }
238 auto fan_mode = call.get_fan_mode();
239 if (fan_mode.has_value()) {
240 this->fan_mode = fan_mode;
241 }
242 auto swing_mode = call.get_swing_mode();
243 if (swing_mode.has_value()) {
244 this->swing_mode = *swing_mode;
245 }
246 if (this->supports_two_points_) {
247 auto target_temp_low = call.get_target_temperature_low();
248 if (target_temp_low.has_value()) {
249 this->target_temperature_low = *target_temp_low;
250 }
251 auto target_temp_high = call.get_target_temperature_high();
252 if (target_temp_high.has_value()) {
253 target_temperature_high_changed = this->target_temperature_high != *target_temp_high;
254 this->target_temperature_high = *target_temp_high;
255 }
256 // ensure the two set points are valid and adjust one of them if necessary
257 this->validate_target_temperatures(target_temperature_high_changed ||
259 } else {
260 auto target_temp = call.get_target_temperature();
261 if (target_temp.has_value()) {
262 this->target_temperature = *target_temp;
264 }
265 }
267 if (target_humidity.has_value()) {
270 }
271 // make any changes happen
272 this->refresh();
273}
274
277
279
280 if (this->supports_two_points_)
282
283 if (this->humidity_sensor_ != nullptr)
285
288
289 if (this->supports_auto_)
291 if (this->supports_heat_cool_)
293 if (this->supports_cool_)
295 if (this->supports_dry_)
297 if (this->supports_fan_only_)
299 if (this->supports_heat_)
301
302 if (this->supports_fan_mode_on_)
304 if (this->supports_fan_mode_off_)
306 if (this->supports_fan_mode_auto_)
308 if (this->supports_fan_mode_low_)
312 if (this->supports_fan_mode_high_)
316 if (this->supports_fan_mode_focus_)
320 if (this->supports_fan_mode_quiet_)
322
327 if (this->supports_swing_mode_off_)
331
332 for (const auto &entry : this->preset_config_) {
333 traits.add_supported_preset(entry.preset);
334 }
335
336 // Custom presets are stored on Climate base class and wired via get_traits()
337
338 return traits;
339}
340
342 auto target_action = climate::CLIMATE_ACTION_IDLE;
343 // if any hysteresis values or current_temperature is not valid, we go to OFF;
344 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
346 }
347 // do not change the action if an "ON" timer is running
348 if ((!ignore_timers) && (this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
352 return this->action;
353 }
354
355 // ensure set point(s) is/are valid before computing the action
357 // everything has been validated so we can now safely compute the action
358 switch (this->mode) {
359 // if the climate mode is OFF then the climate action must be OFF
361 target_action = climate::CLIMATE_ACTION_OFF;
362 break;
364 if (this->fanning_required_())
365 target_action = climate::CLIMATE_ACTION_FAN;
366 break;
368 target_action = climate::CLIMATE_ACTION_DRYING;
369 break;
371 if (this->cooling_required_() && this->heating_required_()) {
372 // this is bad and should never happen, so just stop.
373 // target_action = climate::CLIMATE_ACTION_IDLE;
374 } else if (this->cooling_required_()) {
375 target_action = climate::CLIMATE_ACTION_COOLING;
376 } else if (this->heating_required_()) {
377 target_action = climate::CLIMATE_ACTION_HEATING;
378 }
379 break;
381 if (this->cooling_required_()) {
382 target_action = climate::CLIMATE_ACTION_COOLING;
383 }
384 break;
386 if (this->heating_required_()) {
387 target_action = climate::CLIMATE_ACTION_HEATING;
388 }
389 break;
391 if (this->supports_two_points_) {
392 if (this->cooling_required_() && this->heating_required_()) {
393 // this is bad and should never happen, so just stop.
394 // target_action = climate::CLIMATE_ACTION_IDLE;
395 } else if (this->cooling_required_()) {
396 target_action = climate::CLIMATE_ACTION_COOLING;
397 } else if (this->heating_required_()) {
398 target_action = climate::CLIMATE_ACTION_HEATING;
399 }
400 } else if (this->supports_cool_ && this->cooling_required_()) {
401 target_action = climate::CLIMATE_ACTION_COOLING;
402 } else if (this->supports_heat_ && this->heating_required_()) {
403 target_action = climate::CLIMATE_ACTION_HEATING;
404 }
405 break;
406 default:
407 break;
408 }
409 // do not abruptly switch actions. cycle through IDLE, first. we'll catch this at the next update.
411 (target_action == climate::CLIMATE_ACTION_HEATING)) ||
413 ((target_action == climate::CLIMATE_ACTION_COOLING) || (target_action == climate::CLIMATE_ACTION_DRYING)))) {
415 }
416
417 return target_action;
418}
419
421 auto target_action = climate::CLIMATE_ACTION_IDLE;
422 // if any hysteresis values or current_temperature is not valid, we go to OFF;
423 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
425 }
426
427 // ensure set point(s) is/are valid before computing the action
429 // everything has been validated so we can now safely compute the action
430 switch (this->mode) {
431 // if the climate mode is OFF then the climate action must be OFF
433 target_action = climate::CLIMATE_ACTION_OFF;
434 break;
437 // this is bad and should never happen, so just stop.
438 // target_action = climate::CLIMATE_ACTION_IDLE;
439 } else if (this->supplemental_cooling_required_()) {
440 target_action = climate::CLIMATE_ACTION_COOLING;
441 } else if (this->supplemental_heating_required_()) {
442 target_action = climate::CLIMATE_ACTION_HEATING;
443 }
444 break;
446 if (this->supplemental_cooling_required_()) {
447 target_action = climate::CLIMATE_ACTION_COOLING;
448 }
449 break;
451 if (this->supplemental_heating_required_()) {
452 target_action = climate::CLIMATE_ACTION_HEATING;
453 }
454 break;
455 default:
456 break;
457 }
458
459 return target_action;
460}
461
463 auto target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
464 // if hysteresis value or current_humidity is not valid, we go to OFF
465 if (std::isnan(this->current_humidity) || !this->humidity_hysteresis_valid()) {
467 }
468
469 // ensure set point is valid before computing the action
471 // everything has been validated so we can now safely compute the action
473 // this is bad and should never happen, so just stop.
474 // target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
475 } else if (this->supports_dehumidification_ && this->dehumidification_required_()) {
477 } else if (this->supports_humidification_ && this->humidification_required_()) {
479 }
480
481 return target_action;
482}
483
485 // setup_complete_ helps us ensure an action is called immediately after boot
486 if ((action == this->action) && this->setup_complete_) {
487 // already in target mode
488 return;
489 }
490
491 if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
493 this->setup_complete_) {
494 // switching from OFF to IDLE or vice-versa -- this is only a visual difference.
495 // OFF means user manually disabled, IDLE means the temperature is in target range.
496 this->action = action;
497 if (publish_state)
498 this->publish_state();
499 return;
500 }
501
502 bool action_ready = false;
503 Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr;
504 switch (action) {
507 if (this->idle_action_ready_()) {
509 if (this->action == climate::CLIMATE_ACTION_COOLING) {
512 }
513 if (this->action == climate::CLIMATE_ACTION_FAN) {
516 } else {
518 }
519 }
520 if (this->action == climate::CLIMATE_ACTION_HEATING) {
523 }
524 // trig = this->idle_action_trigger_;
525 ESP_LOGVV(TAG, "Switching to IDLE/OFF action");
526 this->cooling_max_runtime_exceeded_ = false;
527 this->heating_max_runtime_exceeded_ = false;
528 action_ready = true;
529 }
530 break;
532 if (this->cooling_action_ready_()) {
535 if (this->supports_fan_with_cooling_) {
537 trig_fan = &this->fan_only_action_trigger_;
538 }
539 this->cooling_max_runtime_exceeded_ = false;
540 trig = &this->cool_action_trigger_;
541 ESP_LOGVV(TAG, "Switching to COOLING action");
542 action_ready = true;
543 }
544 break;
546 if (this->heating_action_ready_()) {
549 if (this->supports_fan_with_heating_) {
551 trig_fan = &this->fan_only_action_trigger_;
552 }
553 this->heating_max_runtime_exceeded_ = false;
554 trig = &this->heat_action_trigger_;
555 ESP_LOGVV(TAG, "Switching to HEATING action");
556 action_ready = true;
557 }
558 break;
560 if (this->fanning_action_ready_()) {
563 } else {
565 }
566 trig = &this->fan_only_action_trigger_;
567 ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
568 action_ready = true;
569 }
570 break;
572 if (this->drying_action_ready_()) {
575 trig = &this->dry_action_trigger_;
576 ESP_LOGVV(TAG, "Switching to DRYING action");
577 action_ready = true;
578 }
579 break;
580 default:
581 // we cannot report an invalid mode back to HA (even if it asked for one)
582 // and must assume some valid value
584 // trig = this->idle_action_trigger_;
585 }
586
587 if (action_ready) {
588 if (this->prev_action_trigger_ != nullptr) {
590 this->prev_action_trigger_ = nullptr;
591 }
592 this->action = action;
593 this->prev_action_trigger_ = trig;
594 trig->trigger();
595 // if enabled, call the fan_only action with cooling/heating actions
596 if (trig_fan != nullptr) {
597 ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
598 trig_fan->trigger();
599 }
600 if (publish_state)
601 this->publish_state();
602 }
603}
604
606 // setup_complete_ helps us ensure an action is called immediately after boot
607 if ((action == this->supplemental_action_) && this->setup_complete_) {
608 // already in target mode
609 return;
610 }
611
612 switch (action) {
617 break;
620 break;
623 break;
624 default:
625 return;
626 }
627 ESP_LOGVV(TAG, "Updating supplemental action");
630}
631
633 Trigger<> *trig = nullptr;
634
635 switch (this->supplemental_action_) {
639 }
641 ESP_LOGVV(TAG, "Calling supplemental COOLING action");
642 break;
646 }
648 ESP_LOGVV(TAG, "Calling supplemental HEATING action");
649 break;
650 default:
651 break;
652 }
653
654 if (trig != nullptr) {
655 trig->trigger();
656 }
657}
658
660 // setup_complete_ helps us ensure an action is called immediately after boot
661 if ((action == this->humidification_action) && this->setup_complete_) {
662 // already in target mode
663 return;
664 }
665
667 switch (action) {
669 // trig = &this->humidity_control_off_action_trigger_;
670 ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
671 break;
674 ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
675 break;
678 ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
679 break;
681 default:
683 // trig = &this->humidity_control_off_action_trigger_;
684 }
685
686 if (this->prev_humidity_control_trigger_ != nullptr) {
688 this->prev_humidity_control_trigger_ = nullptr;
689 }
692 trig->trigger();
693}
694
696 // setup_complete_ helps us ensure an action is called immediately after boot
697 if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) {
698 // already in target mode
699 return;
700 }
701
702 this->fan_mode = fan_mode;
703 if (publish_state)
704 this->publish_state();
705
706 if (this->fan_mode_ready_()) {
707 Trigger<> *trig = &this->fan_mode_auto_trigger_;
708 switch (fan_mode) {
710 trig = &this->fan_mode_on_trigger_;
711 ESP_LOGVV(TAG, "Switching to FAN_ON mode");
712 break;
714 trig = &this->fan_mode_off_trigger_;
715 ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
716 break;
718 // trig = &this->fan_mode_auto_trigger_;
719 ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
720 break;
722 trig = &this->fan_mode_low_trigger_;
723 ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
724 break;
726 trig = &this->fan_mode_medium_trigger_;
727 ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
728 break;
730 trig = &this->fan_mode_high_trigger_;
731 ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
732 break;
734 trig = &this->fan_mode_middle_trigger_;
735 ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
736 break;
738 trig = &this->fan_mode_focus_trigger_;
739 ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
740 break;
742 trig = &this->fan_mode_diffuse_trigger_;
743 ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
744 break;
746 trig = &this->fan_mode_quiet_trigger_;
747 ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
748 break;
749 default:
750 // we cannot report an invalid mode back to HA (even if it asked for one)
751 // and must assume some valid value
753 // trig = &this->fan_mode_auto_trigger_;
754 }
755 if (this->prev_fan_mode_trigger_ != nullptr) {
757 this->prev_fan_mode_trigger_ = nullptr;
758 }
760 trig->trigger();
761 this->prev_fan_mode_ = fan_mode;
762 this->prev_fan_mode_trigger_ = trig;
763 }
764}
765
767 // setup_complete_ helps us ensure an action is called immediately after boot
768 if ((mode == this->prev_mode_) && this->setup_complete_) {
769 // already in target mode
770 return;
771 }
772
773 if (this->prev_mode_trigger_ != nullptr) {
775 this->prev_mode_trigger_ = nullptr;
776 }
777 Trigger<> *trig = &this->off_mode_trigger_;
778 switch (mode) {
780 trig = &this->auto_mode_trigger_;
781 break;
783 trig = &this->heat_cool_mode_trigger_;
784 break;
786 trig = &this->cool_mode_trigger_;
787 break;
789 trig = &this->heat_mode_trigger_;
790 break;
792 trig = &this->fan_only_mode_trigger_;
793 break;
795 trig = &this->dry_mode_trigger_;
796 break;
798 default:
799 // we cannot report an invalid mode back to HA (even if it asked for one)
800 // and must assume some valid value
802 // trig = this->off_mode_trigger_;
803 }
804 trig->trigger();
805 this->mode = mode;
806 this->prev_mode_ = mode;
807 this->prev_mode_trigger_ = trig;
808 if (publish_state) {
809 this->publish_state();
810 }
811}
812
814 // setup_complete_ helps us ensure an action is called immediately after boot
815 if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) {
816 // already in target mode
817 return;
818 }
819
820 if (this->prev_swing_mode_trigger_ != nullptr) {
822 this->prev_swing_mode_trigger_ = nullptr;
823 }
824 Trigger<> *trig = &this->swing_mode_off_trigger_;
825 switch (swing_mode) {
827 trig = &this->swing_mode_both_trigger_;
828 break;
830 trig = &this->swing_mode_horizontal_trigger_;
831 break;
833 // trig = &this->swing_mode_off_trigger_;
834 break;
836 trig = &this->swing_mode_vertical_trigger_;
837 break;
838 default:
839 // we cannot report an invalid mode back to HA (even if it asked for one)
840 // and must assume some valid value
841 swing_mode = climate::CLIMATE_SWING_OFF;
842 // trig = &this->swing_mode_off_trigger_;
843 }
844 trig->trigger();
845 this->swing_mode = swing_mode;
847 this->prev_swing_mode_trigger_ = trig;
848 if (publish_state)
849 this->publish_state();
850}
851
862
869
876
878
886
893
895 if (this->timer_duration_(timer_index) > 0) {
896 this->timer_[timer_index].started = millis();
897 this->timer_[timer_index].active = true;
898 }
899}
900
902 auto ret = this->timer_[timer_index].active;
903 this->timer_[timer_index].active = false;
904 return ret;
905}
906
908 return this->timer_[timer_index].active;
909}
910
912 return this->timer_[timer_index].time;
913}
914
916 switch (timer_index) {
919 break;
922 break;
925 break;
928 break;
931 break;
934 break;
937 break;
940 break;
943 break;
946 break;
948 default:
949 break;
950 }
951}
952
954 ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
958}
959
961 ESP_LOGVV(TAG, "cooling_off timer expired");
962 this->switch_to_action_(this->compute_action_());
964}
965
967 ESP_LOGVV(TAG, "cooling_on timer expired");
968 this->switch_to_action_(this->compute_action_());
970}
971
973 ESP_LOGVV(TAG, "fan_mode timer expired");
976 this->switch_to_action_(this->compute_action_());
978 }
979}
980
982 ESP_LOGVV(TAG, "fanning_off timer expired");
983 this->switch_to_action_(this->compute_action_());
984}
985
987 ESP_LOGVV(TAG, "fanning_on timer expired");
988 this->switch_to_action_(this->compute_action_());
989}
990
992 ESP_LOGVV(TAG, "heating_max_run_time timer expired");
996}
997
999 ESP_LOGVV(TAG, "heating_off timer expired");
1000 this->switch_to_action_(this->compute_action_());
1002}
1003
1005 ESP_LOGVV(TAG, "heating_on timer expired");
1006 this->switch_to_action_(this->compute_action_());
1008}
1009
1011 ESP_LOGVV(TAG, "idle_on timer expired");
1012 this->switch_to_action_(this->compute_action_());
1014}
1015
1017 if ((this->prev_target_humidity_ == this->target_humidity) && this->setup_complete_) {
1018 return; // nothing changed, no reason to trigger
1019 } else {
1020 // save the new temperature so we can check it again later; the trigger will fire below
1022 }
1023 // trigger the action
1024 Trigger<> *trig = &this->humidity_change_trigger_;
1025 trig->trigger();
1026}
1027
1029 if (this->supports_two_points_) {
1030 // setup_complete_ helps us ensure an action is called immediately after boot
1033 return; // nothing changed, no reason to trigger
1034 } else {
1035 // save the new temperatures so we can check them again later; the trigger will fire below
1038 }
1039 } else {
1040 if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) {
1041 return; // nothing changed, no reason to trigger
1042 } else {
1043 // save the new temperature so we can check it again later; the trigger will fire below
1045 }
1046 }
1047 // trigger the action
1049 trig->trigger();
1050}
1051
1054
1055 if (this->supports_cool_) {
1056 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1057 // if the current temperature reaches or exceeds the target + deadband, cooling is required
1058 return true;
1060 // if the current temperature is less than or equal to the target - overrun, cooling should stop
1061 return false;
1062 } else {
1063 // if we get here, the current temperature is between target + deadband and target - overrun,
1064 // so the action should not change unless it conflicts with the current mode
1065 return (this->action == climate::CLIMATE_ACTION_COOLING) &&
1067 }
1068 }
1069 return false;
1070}
1071
1074
1075 if (this->supports_fan_only_) {
1076 if (this->supports_fan_only_cooling_) {
1077 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1078 // if the current temperature reaches or exceeds the target + deadband, fanning is required
1079 return true;
1081 // if the current temperature is less than or equal to the target - overrun, fanning should stop
1082 return false;
1083 } else {
1084 // if we get here, the current temperature is between target + deadband and target - overrun,
1085 // so the action should not change unless it conflicts with the current mode
1087 }
1088 } else {
1089 return true;
1090 }
1091 }
1092 return false;
1093}
1094
1097
1098 if (this->supports_heat_) {
1100 // if the current temperature is below or equal to the target - deadband, heating is required
1101 return true;
1102 } else if (this->current_temperature >= temperature + this->heating_overrun_) {
1103 // if the current temperature is above or equal to the target + overrun, heating should stop
1104
1105 return false;
1106 } else {
1107 // if we get here, the current temperature is between target - deadband and target + overrun,
1108 // so the action should not change unless it conflicts with the current mode
1109 return (this->action == climate::CLIMATE_ACTION_HEATING) &&
1111 }
1112 }
1113 return false;
1114}
1115
1118 // the component must supports_cool_ and the climate action must be climate::CLIMATE_ACTION_COOLING. then...
1119 // supplemental cooling is required if the max delta or max runtime was exceeded or the action is already engaged
1120 return this->supports_cool_ && (this->action == climate::CLIMATE_ACTION_COOLING) &&
1124}
1125
1128 // the component must supports_heat_ and the climate action must be climate::CLIMATE_ACTION_HEATING. then...
1129 // supplemental heating is required if the max delta or max runtime was exceeded or the action is already engaged
1130 return this->supports_heat_ && (this->action == climate::CLIMATE_ACTION_HEATING) &&
1134}
1135
1137 if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1138 // if the current humidity exceeds the target + hysteresis, dehumidification is required
1139 return true;
1141 // if the current humidity is less than the target - hysteresis, dehumidification should stop
1142 return false;
1143 }
1144 // if we get here, the current humidity is between target + hysteresis and target - hysteresis,
1145 // so the action should not change
1147}
1148
1151 // if the current humidity is below the target - hysteresis, humidification is required
1152 return true;
1153 } else if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1154 // if the current humidity is above the target + hysteresis, humidification should stop
1155 return false;
1156 }
1157 // if we get here, the current humidity is between target - hysteresis and target + hysteresis,
1158 // so the action should not change
1160}
1161
1163 if (this->supports_heat_) {
1164 ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
1166 }
1167 if ((this->supports_cool_) || (this->supports_fan_only_)) {
1168 ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
1170 }
1171
1172 if (config.mode_.has_value()) {
1173 ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1174 }
1175 if (config.fan_mode_.has_value()) {
1176 ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
1177 LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1178 }
1179 if (config.swing_mode_.has_value()) {
1180 ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
1182 }
1183}
1184
1186 // Linear search through preset configurations
1187 const ThermostatClimateTargetTempConfig *config = nullptr;
1188 for (const auto &entry : this->preset_config_) {
1189 if (entry.preset == preset) {
1190 config = &entry.config;
1191 break;
1192 }
1193 }
1194
1195 if (config != nullptr) {
1196 ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1197 if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
1198 // Fire preset changed trigger
1199 Trigger<> *trig = &this->preset_change_trigger_;
1200 this->set_preset_(preset);
1201 trig->trigger();
1202
1203 this->refresh();
1204 ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1205 } else {
1206 ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1207 }
1208 } else {
1209 ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1210 }
1211}
1212
1214 // Linear search through custom preset configurations
1215 const ThermostatClimateTargetTempConfig *config = nullptr;
1216 for (const auto &entry : this->custom_preset_config_) {
1217 // Compare first len chars, then verify entry.name ends there (same length)
1218 if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') {
1219 config = &entry.config;
1220 break;
1221 }
1222 }
1223
1224 if (config != nullptr) {
1225 ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
1226 if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
1227 this->get_custom_preset() != custom_preset) {
1228 // Fire preset changed trigger
1229 Trigger<> *trig = &this->preset_change_trigger_;
1230 // Use the base class method which handles pointer lookup and preset reset internally
1231 this->set_custom_preset_(custom_preset);
1232 trig->trigger();
1233
1234 this->refresh();
1235 ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
1236 } else {
1237 ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset);
1238 // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally.
1239 // The old code had these lines here unconditionally, which was a bug (double assignment, state modification
1240 // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion.
1241 }
1242 } else {
1243 ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset);
1244 }
1245}
1246
1248 bool something_changed = false;
1249
1250 if (this->supports_two_points_) {
1251 if (this->target_temperature_low != config.default_temperature_low) {
1253 something_changed = true;
1254 }
1257 something_changed = true;
1258 }
1259 } else {
1260 if (this->target_temperature != config.default_temperature) {
1262 something_changed = true;
1263 }
1264 }
1265
1266 // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
1267 // also specifies them then the climate.control call's values will override the preset's values for that call
1268 if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
1269 ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1270 this->mode = *config.mode_;
1271 something_changed = true;
1272 }
1273
1274 if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_)) {
1275 ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1276 this->fan_mode = config.fan_mode_;
1277 something_changed = true;
1278 }
1279
1280 if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
1281 ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
1282 this->swing_mode = *config.swing_mode_;
1283 something_changed = true;
1284 }
1285
1286 return something_changed;
1287}
1288
1289void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> presets) {
1290 this->preset_config_ = presets;
1291}
1292
1293void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
1294 this->custom_preset_config_ = presets;
1295 // Populate Climate base class custom presets vector
1296 std::vector<const char *> names;
1297 names.reserve(presets.size());
1298 for (const auto &entry : this->custom_preset_config_) {
1299 names.push_back(entry.name);
1300 }
1301 this->set_supported_custom_presets(names);
1302}
1303
1305
1307 // Find the preset in custom_preset_config_ and store pointer from there
1308 for (const auto &entry : this->custom_preset_config_) {
1309 if (strcmp(entry.name, custom_preset) == 0) {
1310 this->default_custom_preset_ = entry.name;
1311 return;
1312 }
1313 }
1314 // If not found, it will be caught during validation
1315 this->default_custom_preset_ = nullptr;
1316}
1317
1319
1321 this->on_boot_restore_from_ = on_boot_restore_from;
1322}
1324 this->set_point_minimum_differential_ = differential;
1325}
1326void ThermostatClimate::set_cool_deadband(float deadband) { this->cooling_deadband_ = deadband; }
1327void ThermostatClimate::set_cool_overrun(float overrun) { this->cooling_overrun_ = overrun; }
1328void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadband_ = deadband; }
1329void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; }
1332
1334 uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1335
1336 if (this->timer_[timer_index].active) {
1337 // Timer is running, calculate elapsed time and adjust if needed
1339 uint32_t elapsed = current_time - this->timer_[timer_index].started;
1340
1341 if (elapsed >= new_duration_ms) {
1342 // Timer should complete immediately (including when new_duration_ms is 0)
1343 ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %" PRIu32 " >= new %" PRIu32 ")", timer_index, elapsed,
1344 new_duration_ms);
1345 this->timer_[timer_index].active = false;
1346 // Trigger the timer callback immediately
1347 this->call_timer_callback_(timer_index);
1348 return;
1349 } else {
1350 // Adjust timer to run for remaining time - keep original start time
1351 ESP_LOGVV(TAG, "timer %d adjusted: elapsed %" PRIu32 ", new total %" PRIu32 ", remaining %" PRIu32, timer_index,
1352 elapsed, new_duration_ms, new_duration_ms - elapsed);
1353 this->timer_[timer_index].time = new_duration_ms;
1354 return;
1355 }
1356 }
1357
1358 // Original logic for non-running timers
1359 this->timer_[timer_index].time = new_duration_ms;
1360}
1361
1394 this->humidity_sensor_ = humidity_sensor;
1395}
1396void ThermostatClimate::set_humidity_hysteresis(float humidity_hysteresis) {
1397 this->humidity_hysteresis_ = std::clamp<float>(humidity_hysteresis, 0.0f, 100.0f);
1398}
1399void ThermostatClimate::set_use_startup_delay(bool use_startup_delay) { this->use_startup_delay_ = use_startup_delay; }
1400void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
1401 this->supports_heat_cool_ = supports_heat_cool;
1402}
1403void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
1404void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
1405void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
1406void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
1408 bool supports_fan_only_action_uses_fan_mode_timer) {
1409 this->supports_fan_only_action_uses_fan_mode_timer_ = supports_fan_only_action_uses_fan_mode_timer;
1410}
1411void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) {
1412 this->supports_fan_only_cooling_ = supports_fan_only_cooling;
1413}
1414void ThermostatClimate::set_supports_fan_with_cooling(bool supports_fan_with_cooling) {
1415 this->supports_fan_with_cooling_ = supports_fan_with_cooling;
1416}
1417void ThermostatClimate::set_supports_fan_with_heating(bool supports_fan_with_heating) {
1418 this->supports_fan_with_heating_ = supports_fan_with_heating;
1419}
1420void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
1421void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) {
1422 this->supports_fan_mode_on_ = supports_fan_mode_on;
1423}
1424void ThermostatClimate::set_supports_fan_mode_off(bool supports_fan_mode_off) {
1425 this->supports_fan_mode_off_ = supports_fan_mode_off;
1426}
1427void ThermostatClimate::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
1428 this->supports_fan_mode_auto_ = supports_fan_mode_auto;
1429}
1430void ThermostatClimate::set_supports_fan_mode_low(bool supports_fan_mode_low) {
1431 this->supports_fan_mode_low_ = supports_fan_mode_low;
1432}
1433void ThermostatClimate::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
1434 this->supports_fan_mode_medium_ = supports_fan_mode_medium;
1435}
1436void ThermostatClimate::set_supports_fan_mode_high(bool supports_fan_mode_high) {
1437 this->supports_fan_mode_high_ = supports_fan_mode_high;
1438}
1439void ThermostatClimate::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
1440 this->supports_fan_mode_middle_ = supports_fan_mode_middle;
1441}
1442void ThermostatClimate::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
1443 this->supports_fan_mode_focus_ = supports_fan_mode_focus;
1444}
1445void ThermostatClimate::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
1446 this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
1447}
1448void ThermostatClimate::set_supports_fan_mode_quiet(bool supports_fan_mode_quiet) {
1449 this->supports_fan_mode_quiet_ = supports_fan_mode_quiet;
1450}
1451void ThermostatClimate::set_supports_swing_mode_both(bool supports_swing_mode_both) {
1452 this->supports_swing_mode_both_ = supports_swing_mode_both;
1453}
1454void ThermostatClimate::set_supports_swing_mode_off(bool supports_swing_mode_off) {
1455 this->supports_swing_mode_off_ = supports_swing_mode_off;
1456}
1457void ThermostatClimate::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
1458 this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
1459}
1460void ThermostatClimate::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
1461 this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
1462}
1463void ThermostatClimate::set_supports_two_points(bool supports_two_points) {
1464 this->supports_two_points_ = supports_two_points;
1465}
1466void ThermostatClimate::set_supports_dehumidification(bool supports_dehumidification) {
1467 this->supports_dehumidification_ = supports_dehumidification;
1468 if (supports_dehumidification) {
1469 this->supports_humidification_ = false;
1470 }
1471}
1472void ThermostatClimate::set_supports_humidification(bool supports_humidification) {
1473 this->supports_humidification_ = supports_humidification;
1474 if (supports_humidification) {
1475 this->supports_dehumidification_ = false;
1476 }
1477}
1478
1523
1525 LOG_CLIMATE("", "Thermostat", this);
1526
1527 ESP_LOGCONFIG(TAG,
1528 " On boot, restore from: %s\n"
1529 " Use Start-up Delay: %s",
1530 this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY",
1531 YESNO(this->use_startup_delay_));
1532 if (this->supports_two_points_) {
1533 ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
1534 }
1535 if (this->supports_cool_) {
1536 ESP_LOGCONFIG(TAG,
1537 " Cooling Parameters:\n"
1538 " Deadband: %.1f°C\n"
1539 " Overrun: %.1f°C\n"
1540 " Minimum Off Time: %" PRIu32 "s\n"
1541 " Minimum Run Time: %" PRIu32 "s",
1545 if ((this->supplemental_cool_delta_ > 0) ||
1547 ESP_LOGCONFIG(TAG,
1548 " Maximum Run Time: %" PRIu32 "s\n"
1549 " Supplemental Delta: %.1f°C",
1552 }
1553 }
1554 if (this->supports_heat_) {
1555 ESP_LOGCONFIG(TAG,
1556 " Heating Parameters:\n"
1557 " Deadband: %.1f°C\n"
1558 " Overrun: %.1f°C\n"
1559 " Minimum Off Time: %" PRIu32 "s\n"
1560 " Minimum Run Time: %" PRIu32 "s",
1564 if ((this->supplemental_heat_delta_ > 0) ||
1566 ESP_LOGCONFIG(TAG,
1567 " Maximum Run Time: %" PRIu32 "s\n"
1568 " Supplemental Delta: %.1f°C",
1571 }
1572 }
1573 if (this->supports_fan_only_) {
1574 ESP_LOGCONFIG(TAG,
1575 " Fan Parameters:\n"
1576 " Minimum Off Time: %" PRIu32 "s\n"
1577 " Minimum Run Time: %" PRIu32 "s",
1580 }
1585 ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
1587 }
1588 ESP_LOGCONFIG(TAG,
1589 " Minimum Idle Time: %" PRIu32 "s\n"
1590 " Supported MODES:\n"
1591 " AUTO: %s\n"
1592 " HEAT/COOL: %s\n"
1593 " HEAT: %s\n"
1594 " COOL: %s\n"
1595 " DRY: %s\n"
1596 " FAN_ONLY: %s\n"
1597 " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
1598 " FAN_ONLY_COOLING: %s",
1599 this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
1600 YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
1601 YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
1603 if (this->supports_cool_) {
1604 ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
1605 }
1606 if (this->supports_heat_) {
1607 ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
1608 }
1609 ESP_LOGCONFIG(TAG,
1610 " Supported FAN MODES:\n"
1611 " ON: %s\n"
1612 " OFF: %s\n"
1613 " AUTO: %s\n"
1614 " LOW: %s\n"
1615 " MEDIUM: %s\n"
1616 " HIGH: %s\n"
1617 " MIDDLE: %s\n"
1618 " FOCUS: %s\n"
1619 " DIFFUSE: %s\n"
1620 " QUIET: %s\n"
1621 " Supported SWING MODES:\n"
1622 " BOTH: %s\n"
1623 " OFF: %s\n"
1624 " HORIZONTAL: %s\n"
1625 " VERTICAL: %s\n"
1626 " Supports TWO SET POINTS: %s\n"
1627 " Supported Humidity Parameters:\n"
1628 " CURRENT: %s\n"
1629 " TARGET: %s\n"
1630 " DEHUMIDIFICATION: %s\n"
1631 " HUMIDIFICATION: %s",
1632 YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
1633 YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
1634 YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
1635 YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
1636 YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
1637 YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
1639 YESNO(this->supports_two_points_),
1640 YESNO(this->get_traits().has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)),
1642 YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
1643
1644 if (!this->preset_config_.empty()) {
1645 ESP_LOGCONFIG(TAG, " Supported PRESETS:");
1646 for (const auto &entry : this->preset_config_) {
1647 const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
1648 ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : "");
1649 this->dump_preset_config_(preset_name, entry.config);
1650 }
1651 }
1652
1653 if (!this->custom_preset_config_.empty()) {
1654 ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
1655 for (const auto &entry : this->custom_preset_config_) {
1656 const auto *preset_name = entry.name;
1657 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1658 (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
1659 ? " (default)"
1660 : "");
1661 this->dump_preset_config_(preset_name, entry.config);
1662 }
1663 }
1664}
1665
1667
1669 : default_temperature(default_temperature) {}
1670
1672 float default_temperature_high)
1673 : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
1674
1675} // 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:293
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:287
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:274
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:270
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:299
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:277
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:250
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:696
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.h:325
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:264
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:267
ClimateAction action
The active state of the climate device.
Definition climate.h:296
StringRef get_custom_preset() const
Get the active custom preset (read-only access). Returns StringRef.
Definition climate.h:305
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:290
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:284
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:279
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)
Base-class for all sensors.
Definition sensor.h:47
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 set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal)
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)
void set_supports_fan_mode_on(bool supports_fan_mode_on)
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.
void set_on_boot_restore_from(OnBootRestoreFrom on_boot_restore_from)
float cool_deadband()
Get current hysteresis values.
bool climate_action_change_delayed()
Returns true if a climate action/fan mode transition is being delayed.
void set_supports_fan_with_heating(bool supports_fan_with_heating)
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...
void set_supports_two_points(bool supports_two_points)
void set_preset_config(std::initializer_list< PresetEntry > presets)
void set_supports_fan_only_cooling(bool supports_fan_only_cooling)
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.
void set_supports_fan_only_action_uses_fan_mode_timer(bool fan_only_action_uses_fan_mode_timer)
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 set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse)
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.
void set_supports_fan_mode_auto(bool supports_fan_mode_auto)
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_supports_swing_mode_vertical(bool supports_swing_mode_vertical)
void set_supports_fan_only(bool supports_fan_only)
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 set_supports_swing_mode_off(bool supports_swing_mode_off)
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.
climate::ClimateFanMode locked_fan_mode()
Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!)
void set_humidity_sensor(sensor::Sensor *humidity_sensor)
Trigger humidity_change_trigger_
Trigger for target humidity changes.
void set_supports_fan_mode_middle(bool supports_fan_mode_middle)
void set_supports_fan_mode_low(bool supports_fan_mode_low)
bool timer_active_(ThermostatClimateTimerIndex timer_index)
float supplemental_cool_delta_
Maximum allowable temperature deltas before engaging supplemental cooling/heating actions.
void set_supports_heat_cool(bool supports_heat_cool)
void set_supports_fan_mode_quiet(bool supports_fan_mode_quiet)
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.
void set_supports_fan_mode_medium(bool supports_fan_mode_medium)
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 set_supports_swing_mode_both(bool supports_swing_mode_both)
void start_timer_(ThermostatClimateTimerIndex timer_index)
Start/cancel/get status of climate action timer.
void set_set_point_minimum_differential(float differential)
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.
void set_supports_fan_with_cooling(bool supports_fan_with_cooling)
void set_supports_fan_mode_focus(bool supports_fan_mode_focus)
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_()
void set_supports_fan_mode_off(bool supports_fan_mode_off)
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_use_startup_delay(bool use_startup_delay)
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
void set_supports_fan_mode_high(bool supports_fan_mode_high)
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
@ 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