ESPHome 2026.3.0
Loading...
Searching...
No Matches
thermostat_climate.cpp
Go to the documentation of this file.
4#include "esphome/core/log.h"
5
7
8static const char *const TAG = "thermostat.climate";
9
11 if (this->use_startup_delay_) {
12 // start timers so that no actions are called for a moment
18 }
19 // add a callback so that whenever the sensor state changes we can take action
20 this->sensor_->add_on_state_callback([this](float state) {
22 // required action may have changed, recompute, refresh, we'll publish_state() later
23 this->switch_to_action_(this->compute_action_(), false);
25 // current temperature and possibly action changed, so publish the new state
26 this->publish_state();
27 });
28 this->current_temperature = this->sensor_->state;
29
30 // register for humidity values and get initial state
31 if (this->humidity_sensor_ != nullptr) {
32 this->humidity_sensor_->add_on_state_callback([this](float state) {
33 this->current_humidity = state;
35 this->publish_state();
36 });
38 }
39
40 auto use_default_preset = true;
41
43 // restore all climate data, if possible
44 auto restore = this->restore_state_();
45 if (restore.has_value()) {
46 use_default_preset = false;
47 restore->to_call(this).perform();
48 }
49 }
50
51 // Either we failed to restore state or the user has requested we always apply the default preset
52 if (use_default_preset) {
55 } else if (this->default_custom_preset_ != nullptr) {
56 this->change_custom_preset_(this->default_custom_preset_);
57 }
58 }
59
60 // refresh the climate action based on the restored settings, we'll publish_state() later
61 this->switch_to_action_(this->compute_action_(), false);
63 this->setup_complete_ = true;
64 this->publish_state();
65}
66
69 for (uint8_t i = 0; i < THERMOSTAT_TIMER_COUNT; i++) {
70 auto &timer = this->timer_[i];
71 if (timer.active && (now - timer.started >= timer.time)) {
72 timer.active = false;
74 }
75 }
76}
77
82
94
96 bool state_mismatch = this->action != this->compute_action_(true);
97
98 switch (this->compute_action_(true)) {
101 return state_mismatch && (!this->idle_action_ready_());
103 return state_mismatch && (!this->cooling_action_ready_());
105 return state_mismatch && (!this->heating_action_ready_());
107 return state_mismatch && (!this->fanning_action_ready_());
109 return state_mismatch && (!this->drying_action_ready_());
110 default:
111 break;
112 }
113 return false;
114}
115
117 bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_;
118 return state_mismatch && (!this->fan_mode_ready_());
119}
120
122
124
126 if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) &&
127 (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_)))
128 return false;
129
130 if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_)))
131 return false;
132
133 return true;
134}
135
137 return !std::isnan(this->humidity_hysteresis_) && this->humidity_hysteresis_ >= 0.0f &&
138 this->humidity_hysteresis_ < 100.0f;
139}
140
145
147 if (std::isnan(this->target_temperature)) {
148 // default to the midpoint between visual min and max
149 this->target_temperature =
152 } else {
153 // target_temperature must be between the visual minimum and the visual maximum
154 this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
155 this->get_traits().get_visual_max_temperature());
156 }
157}
158
159void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
160 if (!this->supports_two_points_) {
162 } else if (pin_target_temperature_high) {
163 // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
166 } else {
167 // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
170 }
171}
172
174 if (std::isnan(this->target_temperature_low)) {
176 } else {
177 float target_temperature_low_upper_limit =
180 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
182 this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
183 target_temperature_low_upper_limit);
184 }
185}
186
188 if (std::isnan(this->target_temperature_high)) {
190 } else {
191 float target_temperature_high_lower_limit =
194 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
196 this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
197 this->get_traits().get_visual_max_temperature());
198 }
199}
200
202 if (std::isnan(this->target_humidity)) {
203 this->target_humidity =
205 } else {
206 this->target_humidity = clamp<float>(this->target_humidity, this->get_traits().get_visual_min_humidity(),
207 this->get_traits().get_visual_max_humidity());
208 }
209}
210
212 bool target_temperature_high_changed = false;
213
214 auto preset = call.get_preset();
215 if (preset.has_value()) {
216 // setup_complete_ blocks modifying/resetting the temps immediately after boot
217 if (this->setup_complete_) {
218 this->change_preset_(*preset);
219 } else {
220 this->preset = preset;
221 }
222 }
223 if (call.has_custom_preset()) {
224 // setup_complete_ blocks modifying/resetting the temps immediately after boot
225 if (this->setup_complete_) {
227 } else {
228 // Use the base class method which handles pointer lookup internally
230 }
231 }
232
233 auto mode = call.get_mode();
234 if (mode.has_value()) {
235 this->mode = *mode;
236 }
237 auto fan_mode = call.get_fan_mode();
238 if (fan_mode.has_value()) {
239 this->fan_mode = fan_mode;
240 }
241 auto swing_mode = call.get_swing_mode();
242 if (swing_mode.has_value()) {
243 this->swing_mode = *swing_mode;
244 }
245 if (this->supports_two_points_) {
246 auto target_temp_low = call.get_target_temperature_low();
247 if (target_temp_low.has_value()) {
248 this->target_temperature_low = *target_temp_low;
249 }
250 auto target_temp_high = call.get_target_temperature_high();
251 if (target_temp_high.has_value()) {
252 target_temperature_high_changed = this->target_temperature_high != *target_temp_high;
253 this->target_temperature_high = *target_temp_high;
254 }
255 // ensure the two set points are valid and adjust one of them if necessary
256 this->validate_target_temperatures(target_temperature_high_changed ||
258 } else {
259 auto target_temp = call.get_target_temperature();
260 if (target_temp.has_value()) {
261 this->target_temperature = *target_temp;
263 }
264 }
266 if (target_humidity.has_value()) {
269 }
270 // make any changes happen
271 this->refresh();
272}
273
276
278
279 if (this->supports_two_points_)
281
282 if (this->humidity_sensor_ != nullptr)
284
287
288 if (this->supports_auto_)
290 if (this->supports_heat_cool_)
292 if (this->supports_cool_)
294 if (this->supports_dry_)
296 if (this->supports_fan_only_)
298 if (this->supports_heat_)
300
301 if (this->supports_fan_mode_on_)
303 if (this->supports_fan_mode_off_)
305 if (this->supports_fan_mode_auto_)
307 if (this->supports_fan_mode_low_)
311 if (this->supports_fan_mode_high_)
315 if (this->supports_fan_mode_focus_)
319 if (this->supports_fan_mode_quiet_)
321
326 if (this->supports_swing_mode_off_)
330
331 for (const auto &entry : this->preset_config_) {
332 traits.add_supported_preset(entry.preset);
333 }
334
335 // Extract custom preset names from the custom_preset_config_ vector
336 if (!this->custom_preset_config_.empty()) {
337 std::vector<const char *> custom_preset_names;
338 custom_preset_names.reserve(this->custom_preset_config_.size());
339 for (const auto &entry : this->custom_preset_config_) {
340 custom_preset_names.push_back(entry.name);
341 }
342 traits.set_supported_custom_presets(custom_preset_names);
343 }
344
345 return traits;
346}
347
349 auto target_action = climate::CLIMATE_ACTION_IDLE;
350 // if any hysteresis values or current_temperature is not valid, we go to OFF;
351 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
353 }
354 // do not change the action if an "ON" timer is running
355 if ((!ignore_timers) && (this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
359 return this->action;
360 }
361
362 // ensure set point(s) is/are valid before computing the action
364 // everything has been validated so we can now safely compute the action
365 switch (this->mode) {
366 // if the climate mode is OFF then the climate action must be OFF
368 target_action = climate::CLIMATE_ACTION_OFF;
369 break;
371 if (this->fanning_required_())
372 target_action = climate::CLIMATE_ACTION_FAN;
373 break;
375 target_action = climate::CLIMATE_ACTION_DRYING;
376 break;
378 if (this->cooling_required_() && this->heating_required_()) {
379 // this is bad and should never happen, so just stop.
380 // target_action = climate::CLIMATE_ACTION_IDLE;
381 } else if (this->cooling_required_()) {
382 target_action = climate::CLIMATE_ACTION_COOLING;
383 } else if (this->heating_required_()) {
384 target_action = climate::CLIMATE_ACTION_HEATING;
385 }
386 break;
388 if (this->cooling_required_()) {
389 target_action = climate::CLIMATE_ACTION_COOLING;
390 }
391 break;
393 if (this->heating_required_()) {
394 target_action = climate::CLIMATE_ACTION_HEATING;
395 }
396 break;
398 if (this->supports_two_points_) {
399 if (this->cooling_required_() && this->heating_required_()) {
400 // this is bad and should never happen, so just stop.
401 // target_action = climate::CLIMATE_ACTION_IDLE;
402 } else if (this->cooling_required_()) {
403 target_action = climate::CLIMATE_ACTION_COOLING;
404 } else if (this->heating_required_()) {
405 target_action = climate::CLIMATE_ACTION_HEATING;
406 }
407 } else if (this->supports_cool_ && this->cooling_required_()) {
408 target_action = climate::CLIMATE_ACTION_COOLING;
409 } else if (this->supports_heat_ && this->heating_required_()) {
410 target_action = climate::CLIMATE_ACTION_HEATING;
411 }
412 break;
413 default:
414 break;
415 }
416 // do not abruptly switch actions. cycle through IDLE, first. we'll catch this at the next update.
418 (target_action == climate::CLIMATE_ACTION_HEATING)) ||
420 ((target_action == climate::CLIMATE_ACTION_COOLING) || (target_action == climate::CLIMATE_ACTION_DRYING)))) {
422 }
423
424 return target_action;
425}
426
428 auto target_action = climate::CLIMATE_ACTION_IDLE;
429 // if any hysteresis values or current_temperature is not valid, we go to OFF;
430 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
432 }
433
434 // ensure set point(s) is/are valid before computing the action
436 // everything has been validated so we can now safely compute the action
437 switch (this->mode) {
438 // if the climate mode is OFF then the climate action must be OFF
440 target_action = climate::CLIMATE_ACTION_OFF;
441 break;
444 // this is bad and should never happen, so just stop.
445 // target_action = climate::CLIMATE_ACTION_IDLE;
446 } else if (this->supplemental_cooling_required_()) {
447 target_action = climate::CLIMATE_ACTION_COOLING;
448 } else if (this->supplemental_heating_required_()) {
449 target_action = climate::CLIMATE_ACTION_HEATING;
450 }
451 break;
453 if (this->supplemental_cooling_required_()) {
454 target_action = climate::CLIMATE_ACTION_COOLING;
455 }
456 break;
458 if (this->supplemental_heating_required_()) {
459 target_action = climate::CLIMATE_ACTION_HEATING;
460 }
461 break;
462 default:
463 break;
464 }
465
466 return target_action;
467}
468
470 auto target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
471 // if hysteresis value or current_humidity is not valid, we go to OFF
472 if (std::isnan(this->current_humidity) || !this->humidity_hysteresis_valid()) {
474 }
475
476 // ensure set point is valid before computing the action
478 // everything has been validated so we can now safely compute the action
480 // this is bad and should never happen, so just stop.
481 // target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
482 } else if (this->supports_dehumidification_ && this->dehumidification_required_()) {
484 } else if (this->supports_humidification_ && this->humidification_required_()) {
486 }
487
488 return target_action;
489}
490
492 // setup_complete_ helps us ensure an action is called immediately after boot
493 if ((action == this->action) && this->setup_complete_) {
494 // already in target mode
495 return;
496 }
497
498 if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
500 this->setup_complete_) {
501 // switching from OFF to IDLE or vice-versa -- this is only a visual difference.
502 // OFF means user manually disabled, IDLE means the temperature is in target range.
503 this->action = action;
504 if (publish_state)
505 this->publish_state();
506 return;
507 }
508
509 bool action_ready = false;
510 Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr;
511 switch (action) {
514 if (this->idle_action_ready_()) {
516 if (this->action == climate::CLIMATE_ACTION_COOLING)
518 if (this->action == climate::CLIMATE_ACTION_FAN) {
521 } else {
523 }
524 }
525 if (this->action == climate::CLIMATE_ACTION_HEATING)
527 // trig = this->idle_action_trigger_;
528 ESP_LOGVV(TAG, "Switching to IDLE/OFF action");
529 this->cooling_max_runtime_exceeded_ = false;
530 this->heating_max_runtime_exceeded_ = false;
531 action_ready = true;
532 }
533 break;
535 if (this->cooling_action_ready_()) {
538 if (this->supports_fan_with_cooling_) {
540 trig_fan = &this->fan_only_action_trigger_;
541 }
542 this->cooling_max_runtime_exceeded_ = false;
543 trig = &this->cool_action_trigger_;
544 ESP_LOGVV(TAG, "Switching to COOLING action");
545 action_ready = true;
546 }
547 break;
549 if (this->heating_action_ready_()) {
552 if (this->supports_fan_with_heating_) {
554 trig_fan = &this->fan_only_action_trigger_;
555 }
556 this->heating_max_runtime_exceeded_ = false;
557 trig = &this->heat_action_trigger_;
558 ESP_LOGVV(TAG, "Switching to HEATING action");
559 action_ready = true;
560 }
561 break;
563 if (this->fanning_action_ready_()) {
566 } else {
568 }
569 trig = &this->fan_only_action_trigger_;
570 ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
571 action_ready = true;
572 }
573 break;
575 if (this->drying_action_ready_()) {
578 trig = &this->dry_action_trigger_;
579 ESP_LOGVV(TAG, "Switching to DRYING action");
580 action_ready = true;
581 }
582 break;
583 default:
584 // we cannot report an invalid mode back to HA (even if it asked for one)
585 // and must assume some valid value
587 // trig = this->idle_action_trigger_;
588 }
589
590 if (action_ready) {
591 if (this->prev_action_trigger_ != nullptr) {
593 this->prev_action_trigger_ = nullptr;
594 }
595 this->action = action;
596 this->prev_action_trigger_ = trig;
597 trig->trigger();
598 // if enabled, call the fan_only action with cooling/heating actions
599 if (trig_fan != nullptr) {
600 ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
601 trig_fan->trigger();
602 }
603 if (publish_state)
604 this->publish_state();
605 }
606}
607
609 // setup_complete_ helps us ensure an action is called immediately after boot
610 if ((action == this->supplemental_action_) && this->setup_complete_) {
611 // already in target mode
612 return;
613 }
614
615 switch (action) {
620 break;
623 break;
626 break;
627 default:
628 return;
629 }
630 ESP_LOGVV(TAG, "Updating supplemental action");
633}
634
636 Trigger<> *trig = nullptr;
637
638 switch (this->supplemental_action_) {
642 }
644 ESP_LOGVV(TAG, "Calling supplemental COOLING action");
645 break;
649 }
651 ESP_LOGVV(TAG, "Calling supplemental HEATING action");
652 break;
653 default:
654 break;
655 }
656
657 if (trig != nullptr) {
658 trig->trigger();
659 }
660}
661
663 // setup_complete_ helps us ensure an action is called immediately after boot
664 if ((action == this->humidification_action) && this->setup_complete_) {
665 // already in target mode
666 return;
667 }
668
670 switch (action) {
672 // trig = &this->humidity_control_off_action_trigger_;
673 ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
674 break;
677 ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
678 break;
681 ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
682 break;
684 default:
686 // trig = &this->humidity_control_off_action_trigger_;
687 }
688
689 if (this->prev_humidity_control_trigger_ != nullptr) {
691 this->prev_humidity_control_trigger_ = nullptr;
692 }
695 trig->trigger();
696}
697
699 // setup_complete_ helps us ensure an action is called immediately after boot
700 if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) {
701 // already in target mode
702 return;
703 }
704
705 this->fan_mode = fan_mode;
706 if (publish_state)
707 this->publish_state();
708
709 if (this->fan_mode_ready_()) {
710 Trigger<> *trig = &this->fan_mode_auto_trigger_;
711 switch (fan_mode) {
713 trig = &this->fan_mode_on_trigger_;
714 ESP_LOGVV(TAG, "Switching to FAN_ON mode");
715 break;
717 trig = &this->fan_mode_off_trigger_;
718 ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
719 break;
721 // trig = &this->fan_mode_auto_trigger_;
722 ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
723 break;
725 trig = &this->fan_mode_low_trigger_;
726 ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
727 break;
729 trig = &this->fan_mode_medium_trigger_;
730 ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
731 break;
733 trig = &this->fan_mode_high_trigger_;
734 ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
735 break;
737 trig = &this->fan_mode_middle_trigger_;
738 ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
739 break;
741 trig = &this->fan_mode_focus_trigger_;
742 ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
743 break;
745 trig = &this->fan_mode_diffuse_trigger_;
746 ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
747 break;
749 trig = &this->fan_mode_quiet_trigger_;
750 ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
751 break;
752 default:
753 // we cannot report an invalid mode back to HA (even if it asked for one)
754 // and must assume some valid value
756 // trig = &this->fan_mode_auto_trigger_;
757 }
758 if (this->prev_fan_mode_trigger_ != nullptr) {
760 this->prev_fan_mode_trigger_ = nullptr;
761 }
763 trig->trigger();
764 this->prev_fan_mode_ = fan_mode;
765 this->prev_fan_mode_trigger_ = trig;
766 }
767}
768
770 // setup_complete_ helps us ensure an action is called immediately after boot
771 if ((mode == this->prev_mode_) && this->setup_complete_) {
772 // already in target mode
773 return;
774 }
775
776 if (this->prev_mode_trigger_ != nullptr) {
778 this->prev_mode_trigger_ = nullptr;
779 }
780 Trigger<> *trig = &this->off_mode_trigger_;
781 switch (mode) {
783 trig = &this->auto_mode_trigger_;
784 break;
786 trig = &this->heat_cool_mode_trigger_;
787 break;
789 trig = &this->cool_mode_trigger_;
790 break;
792 trig = &this->heat_mode_trigger_;
793 break;
795 trig = &this->fan_only_mode_trigger_;
796 break;
798 trig = &this->dry_mode_trigger_;
799 break;
801 default:
802 // we cannot report an invalid mode back to HA (even if it asked for one)
803 // and must assume some valid value
805 // trig = this->off_mode_trigger_;
806 }
807 trig->trigger();
808 this->mode = mode;
809 this->prev_mode_ = mode;
810 this->prev_mode_trigger_ = trig;
811 if (publish_state) {
812 this->publish_state();
813 }
814}
815
817 // setup_complete_ helps us ensure an action is called immediately after boot
818 if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) {
819 // already in target mode
820 return;
821 }
822
823 if (this->prev_swing_mode_trigger_ != nullptr) {
825 this->prev_swing_mode_trigger_ = nullptr;
826 }
827 Trigger<> *trig = &this->swing_mode_off_trigger_;
828 switch (swing_mode) {
830 trig = &this->swing_mode_both_trigger_;
831 break;
833 trig = &this->swing_mode_horizontal_trigger_;
834 break;
836 // trig = &this->swing_mode_off_trigger_;
837 break;
839 trig = &this->swing_mode_vertical_trigger_;
840 break;
841 default:
842 // we cannot report an invalid mode back to HA (even if it asked for one)
843 // and must assume some valid value
844 swing_mode = climate::CLIMATE_SWING_OFF;
845 // trig = &this->swing_mode_off_trigger_;
846 }
847 trig->trigger();
848 this->swing_mode = swing_mode;
850 this->prev_swing_mode_trigger_ = trig;
851 if (publish_state)
852 this->publish_state();
853}
854
865
872
879
881
889
896
898 if (this->timer_duration_(timer_index) > 0) {
899 this->timer_[timer_index].started = millis();
900 this->timer_[timer_index].active = true;
901 }
902}
903
905 auto ret = this->timer_[timer_index].active;
906 this->timer_[timer_index].active = false;
907 return ret;
908}
909
911 return this->timer_[timer_index].active;
912}
913
915 return this->timer_[timer_index].time;
916}
917
919 switch (timer_index) {
922 break;
925 break;
928 break;
931 break;
934 break;
937 break;
940 break;
943 break;
946 break;
949 break;
951 default:
952 break;
953 }
954}
955
957 ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
961}
962
964 ESP_LOGVV(TAG, "cooling_off timer expired");
965 this->switch_to_action_(this->compute_action_());
967}
968
970 ESP_LOGVV(TAG, "cooling_on timer expired");
971 this->switch_to_action_(this->compute_action_());
973}
974
976 ESP_LOGVV(TAG, "fan_mode timer expired");
979 this->switch_to_action_(this->compute_action_());
980}
981
983 ESP_LOGVV(TAG, "fanning_off timer expired");
984 this->switch_to_action_(this->compute_action_());
985}
986
988 ESP_LOGVV(TAG, "fanning_on timer expired");
989 this->switch_to_action_(this->compute_action_());
990}
991
993 ESP_LOGVV(TAG, "heating_max_run_time timer expired");
997}
998
1000 ESP_LOGVV(TAG, "heating_off timer expired");
1001 this->switch_to_action_(this->compute_action_());
1003}
1004
1006 ESP_LOGVV(TAG, "heating_on timer expired");
1007 this->switch_to_action_(this->compute_action_());
1009}
1010
1012 ESP_LOGVV(TAG, "idle_on timer expired");
1013 this->switch_to_action_(this->compute_action_());
1015}
1016
1018 if ((this->prev_target_humidity_ == this->target_humidity) && this->setup_complete_) {
1019 return; // nothing changed, no reason to trigger
1020 } else {
1021 // save the new temperature so we can check it again later; the trigger will fire below
1023 }
1024 // trigger the action
1025 Trigger<> *trig = &this->humidity_change_trigger_;
1026 trig->trigger();
1027}
1028
1030 if (this->supports_two_points_) {
1031 // setup_complete_ helps us ensure an action is called immediately after boot
1034 return; // nothing changed, no reason to trigger
1035 } else {
1036 // save the new temperatures so we can check them again later; the trigger will fire below
1039 }
1040 } else {
1041 if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) {
1042 return; // nothing changed, no reason to trigger
1043 } else {
1044 // save the new temperature so we can check it again later; the trigger will fire below
1046 }
1047 }
1048 // trigger the action
1050 trig->trigger();
1051}
1052
1055
1056 if (this->supports_cool_) {
1057 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1058 // if the current temperature reaches or exceeds the target + deadband, cooling is required
1059 return true;
1061 // if the current temperature is less than or equal to the target - overrun, cooling should stop
1062 return false;
1063 } else {
1064 // if we get here, the current temperature is between target + deadband and target - overrun,
1065 // so the action should not change unless it conflicts with the current mode
1066 return (this->action == climate::CLIMATE_ACTION_COOLING) &&
1068 }
1069 }
1070 return false;
1071}
1072
1075
1076 if (this->supports_fan_only_) {
1077 if (this->supports_fan_only_cooling_) {
1078 if (this->current_temperature >= temperature + this->cooling_deadband_) {
1079 // if the current temperature reaches or exceeds the target + deadband, fanning is required
1080 return true;
1082 // if the current temperature is less than or equal to the target - overrun, fanning should stop
1083 return false;
1084 } else {
1085 // if we get here, the current temperature is between target + deadband and target - overrun,
1086 // so the action should not change unless it conflicts with the current mode
1088 }
1089 } else {
1090 return true;
1091 }
1092 }
1093 return false;
1094}
1095
1098
1099 if (this->supports_heat_) {
1101 // if the current temperature is below or equal to the target - deadband, heating is required
1102 return true;
1103 } else if (this->current_temperature >= temperature + this->heating_overrun_) {
1104 // if the current temperature is above or equal to the target + overrun, heating should stop
1105
1106 return false;
1107 } else {
1108 // if we get here, the current temperature is between target - deadband and target + overrun,
1109 // so the action should not change unless it conflicts with the current mode
1110 return (this->action == climate::CLIMATE_ACTION_HEATING) &&
1112 }
1113 }
1114 return false;
1115}
1116
1119 // the component must supports_cool_ and the climate action must be climate::CLIMATE_ACTION_COOLING. then...
1120 // supplemental cooling is required if the max delta or max runtime was exceeded or the action is already engaged
1121 return this->supports_cool_ && (this->action == climate::CLIMATE_ACTION_COOLING) &&
1125}
1126
1129 // the component must supports_heat_ and the climate action must be climate::CLIMATE_ACTION_HEATING. then...
1130 // supplemental heating is required if the max delta or max runtime was exceeded or the action is already engaged
1131 return this->supports_heat_ && (this->action == climate::CLIMATE_ACTION_HEATING) &&
1135}
1136
1138 if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1139 // if the current humidity exceeds the target + hysteresis, dehumidification is required
1140 return true;
1142 // if the current humidity is less than the target - hysteresis, dehumidification should stop
1143 return false;
1144 }
1145 // if we get here, the current humidity is between target + hysteresis and target - hysteresis,
1146 // so the action should not change
1148}
1149
1152 // if the current humidity is below the target - hysteresis, humidification is required
1153 return true;
1154 } else if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1155 // if the current humidity is above the target + hysteresis, humidification should stop
1156 return false;
1157 }
1158 // if we get here, the current humidity is between target - hysteresis and target + hysteresis,
1159 // so the action should not change
1161}
1162
1164 if (this->supports_heat_) {
1165 ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
1167 }
1168 if ((this->supports_cool_) || (this->supports_fan_only_)) {
1169 ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
1171 }
1172
1173 if (config.mode_.has_value()) {
1174 ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1175 }
1176 if (config.fan_mode_.has_value()) {
1177 ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
1178 LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1179 }
1180 if (config.swing_mode_.has_value()) {
1181 ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
1183 }
1184}
1185
1187 // Linear search through preset configurations
1188 const ThermostatClimateTargetTempConfig *config = nullptr;
1189 for (const auto &entry : this->preset_config_) {
1190 if (entry.preset == preset) {
1191 config = &entry.config;
1192 break;
1193 }
1194 }
1195
1196 if (config != nullptr) {
1197 ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1198 if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
1199 // Fire preset changed trigger
1200 Trigger<> *trig = &this->preset_change_trigger_;
1201 this->set_preset_(preset);
1202 trig->trigger();
1203
1204 this->refresh();
1205 ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1206 } else {
1207 ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1208 }
1209 } else {
1210 ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1211 }
1212}
1213
1215 // Linear search through custom preset configurations
1216 const ThermostatClimateTargetTempConfig *config = nullptr;
1217 for (const auto &entry : this->custom_preset_config_) {
1218 // Compare first len chars, then verify entry.name ends there (same length)
1219 if (strncmp(entry.name, custom_preset, len) == 0 && entry.name[len] == '\0') {
1220 config = &entry.config;
1221 break;
1222 }
1223 }
1224
1225 if (config != nullptr) {
1226 ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
1227 if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
1228 this->get_custom_preset() != custom_preset) {
1229 // Fire preset changed trigger
1230 Trigger<> *trig = &this->preset_change_trigger_;
1231 // Use the base class method which handles pointer lookup and preset reset internally
1232 this->set_custom_preset_(custom_preset);
1233 trig->trigger();
1234
1235 this->refresh();
1236 ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
1237 } else {
1238 ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset);
1239 // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally.
1240 // The old code had these lines here unconditionally, which was a bug (double assignment, state modification
1241 // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion.
1242 }
1243 } else {
1244 ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset);
1245 }
1246}
1247
1249 bool something_changed = false;
1250
1251 if (this->supports_two_points_) {
1252 if (this->target_temperature_low != config.default_temperature_low) {
1254 something_changed = true;
1255 }
1258 something_changed = true;
1259 }
1260 } else {
1261 if (this->target_temperature != config.default_temperature) {
1263 something_changed = true;
1264 }
1265 }
1266
1267 // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
1268 // also specifies them then the climate.control call's values will override the preset's values for that call
1269 if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
1270 ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1271 this->mode = *config.mode_;
1272 something_changed = true;
1273 }
1274
1275 if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_)) {
1276 ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1277 this->fan_mode = config.fan_mode_;
1278 something_changed = true;
1279 }
1280
1281 if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
1282 ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
1283 this->swing_mode = *config.swing_mode_;
1284 something_changed = true;
1285 }
1286
1287 return something_changed;
1288}
1289
1290void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> presets) {
1291 this->preset_config_ = presets;
1292}
1293
1294void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
1295 this->custom_preset_config_ = presets;
1296}
1297
1299
1301 // Find the preset in custom_preset_config_ and store pointer from there
1302 for (const auto &entry : this->custom_preset_config_) {
1303 if (strcmp(entry.name, custom_preset) == 0) {
1304 this->default_custom_preset_ = entry.name;
1305 return;
1306 }
1307 }
1308 // If not found, it will be caught during validation
1309 this->default_custom_preset_ = nullptr;
1310}
1311
1313
1315 this->on_boot_restore_from_ = on_boot_restore_from;
1316}
1318 this->set_point_minimum_differential_ = differential;
1319}
1320void ThermostatClimate::set_cool_deadband(float deadband) { this->cooling_deadband_ = deadband; }
1321void ThermostatClimate::set_cool_overrun(float overrun) { this->cooling_overrun_ = overrun; }
1322void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadband_ = deadband; }
1323void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; }
1326
1328 uint32_t new_duration_ms = 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1329
1330 if (this->timer_[timer_index].active) {
1331 // Timer is running, calculate elapsed time and adjust if needed
1333 uint32_t elapsed = current_time - this->timer_[timer_index].started;
1334
1335 if (elapsed >= new_duration_ms) {
1336 // Timer should complete immediately (including when new_duration_ms is 0)
1337 ESP_LOGVV(TAG, "timer %d completing immediately (elapsed %d >= new %d)", timer_index, elapsed, new_duration_ms);
1338 this->timer_[timer_index].active = false;
1339 // Trigger the timer callback immediately
1340 this->call_timer_callback_(timer_index);
1341 return;
1342 } else {
1343 // Adjust timer to run for remaining time - keep original start time
1344 ESP_LOGVV(TAG, "timer %d adjusted: elapsed %d, new total %d, remaining %d", timer_index, elapsed, new_duration_ms,
1345 new_duration_ms - elapsed);
1346 this->timer_[timer_index].time = new_duration_ms;
1347 return;
1348 }
1349 }
1350
1351 // Original logic for non-running timers
1352 this->timer_[timer_index].time = new_duration_ms;
1353}
1354
1387 this->humidity_sensor_ = humidity_sensor;
1388}
1389void ThermostatClimate::set_humidity_hysteresis(float humidity_hysteresis) {
1390 this->humidity_hysteresis_ = std::clamp<float>(humidity_hysteresis, 0.0f, 100.0f);
1391}
1392void ThermostatClimate::set_use_startup_delay(bool use_startup_delay) { this->use_startup_delay_ = use_startup_delay; }
1393void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
1394 this->supports_heat_cool_ = supports_heat_cool;
1395}
1396void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
1397void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
1398void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
1399void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
1401 bool supports_fan_only_action_uses_fan_mode_timer) {
1402 this->supports_fan_only_action_uses_fan_mode_timer_ = supports_fan_only_action_uses_fan_mode_timer;
1403}
1404void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) {
1405 this->supports_fan_only_cooling_ = supports_fan_only_cooling;
1406}
1407void ThermostatClimate::set_supports_fan_with_cooling(bool supports_fan_with_cooling) {
1408 this->supports_fan_with_cooling_ = supports_fan_with_cooling;
1409}
1410void ThermostatClimate::set_supports_fan_with_heating(bool supports_fan_with_heating) {
1411 this->supports_fan_with_heating_ = supports_fan_with_heating;
1412}
1413void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
1414void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) {
1415 this->supports_fan_mode_on_ = supports_fan_mode_on;
1416}
1417void ThermostatClimate::set_supports_fan_mode_off(bool supports_fan_mode_off) {
1418 this->supports_fan_mode_off_ = supports_fan_mode_off;
1419}
1420void ThermostatClimate::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
1421 this->supports_fan_mode_auto_ = supports_fan_mode_auto;
1422}
1423void ThermostatClimate::set_supports_fan_mode_low(bool supports_fan_mode_low) {
1424 this->supports_fan_mode_low_ = supports_fan_mode_low;
1425}
1426void ThermostatClimate::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
1427 this->supports_fan_mode_medium_ = supports_fan_mode_medium;
1428}
1429void ThermostatClimate::set_supports_fan_mode_high(bool supports_fan_mode_high) {
1430 this->supports_fan_mode_high_ = supports_fan_mode_high;
1431}
1432void ThermostatClimate::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
1433 this->supports_fan_mode_middle_ = supports_fan_mode_middle;
1434}
1435void ThermostatClimate::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
1436 this->supports_fan_mode_focus_ = supports_fan_mode_focus;
1437}
1438void ThermostatClimate::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
1439 this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
1440}
1441void ThermostatClimate::set_supports_fan_mode_quiet(bool supports_fan_mode_quiet) {
1442 this->supports_fan_mode_quiet_ = supports_fan_mode_quiet;
1443}
1444void ThermostatClimate::set_supports_swing_mode_both(bool supports_swing_mode_both) {
1445 this->supports_swing_mode_both_ = supports_swing_mode_both;
1446}
1447void ThermostatClimate::set_supports_swing_mode_off(bool supports_swing_mode_off) {
1448 this->supports_swing_mode_off_ = supports_swing_mode_off;
1449}
1450void ThermostatClimate::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
1451 this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
1452}
1453void ThermostatClimate::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
1454 this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
1455}
1456void ThermostatClimate::set_supports_two_points(bool supports_two_points) {
1457 this->supports_two_points_ = supports_two_points;
1458}
1459void ThermostatClimate::set_supports_dehumidification(bool supports_dehumidification) {
1460 this->supports_dehumidification_ = supports_dehumidification;
1461 if (supports_dehumidification) {
1462 this->supports_humidification_ = false;
1463 }
1464}
1465void ThermostatClimate::set_supports_humidification(bool supports_humidification) {
1466 this->supports_humidification_ = supports_humidification;
1467 if (supports_humidification) {
1468 this->supports_dehumidification_ = false;
1469 }
1470}
1471
1516
1518 LOG_CLIMATE("", "Thermostat", this);
1519
1520 ESP_LOGCONFIG(TAG,
1521 " On boot, restore from: %s\n"
1522 " Use Start-up Delay: %s",
1523 this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY",
1524 YESNO(this->use_startup_delay_));
1525 if (this->supports_two_points_) {
1526 ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
1527 }
1528 if (this->supports_cool_) {
1529 ESP_LOGCONFIG(TAG,
1530 " Cooling Parameters:\n"
1531 " Deadband: %.1f°C\n"
1532 " Overrun: %.1f°C\n"
1533 " Minimum Off Time: %" PRIu32 "s\n"
1534 " Minimum Run Time: %" PRIu32 "s",
1538 if ((this->supplemental_cool_delta_ > 0) ||
1540 ESP_LOGCONFIG(TAG,
1541 " Maximum Run Time: %" PRIu32 "s\n"
1542 " Supplemental Delta: %.1f°C",
1545 }
1546 }
1547 if (this->supports_heat_) {
1548 ESP_LOGCONFIG(TAG,
1549 " Heating Parameters:\n"
1550 " Deadband: %.1f°C\n"
1551 " Overrun: %.1f°C\n"
1552 " Minimum Off Time: %" PRIu32 "s\n"
1553 " Minimum Run Time: %" PRIu32 "s",
1557 if ((this->supplemental_heat_delta_ > 0) ||
1559 ESP_LOGCONFIG(TAG,
1560 " Maximum Run Time: %" PRIu32 "s\n"
1561 " Supplemental Delta: %.1f°C",
1564 }
1565 }
1566 if (this->supports_fan_only_) {
1567 ESP_LOGCONFIG(TAG,
1568 " Fan Parameters:\n"
1569 " Minimum Off Time: %" PRIu32 "s\n"
1570 " Minimum Run Time: %" PRIu32 "s",
1573 }
1578 ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
1580 }
1581 ESP_LOGCONFIG(TAG,
1582 " Minimum Idle Time: %" PRIu32 "s\n"
1583 " Supported MODES:\n"
1584 " AUTO: %s\n"
1585 " HEAT/COOL: %s\n"
1586 " HEAT: %s\n"
1587 " COOL: %s\n"
1588 " DRY: %s\n"
1589 " FAN_ONLY: %s\n"
1590 " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
1591 " FAN_ONLY_COOLING: %s",
1592 this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
1593 YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
1594 YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
1596 if (this->supports_cool_) {
1597 ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
1598 }
1599 if (this->supports_heat_) {
1600 ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
1601 }
1602 ESP_LOGCONFIG(TAG,
1603 " Supported FAN MODES:\n"
1604 " ON: %s\n"
1605 " OFF: %s\n"
1606 " AUTO: %s\n"
1607 " LOW: %s\n"
1608 " MEDIUM: %s\n"
1609 " HIGH: %s\n"
1610 " MIDDLE: %s\n"
1611 " FOCUS: %s\n"
1612 " DIFFUSE: %s\n"
1613 " QUIET: %s\n"
1614 " Supported SWING MODES:\n"
1615 " BOTH: %s\n"
1616 " OFF: %s\n"
1617 " HORIZONTAL: %s\n"
1618 " VERTICAL: %s\n"
1619 " Supports TWO SET POINTS: %s\n"
1620 " Supported Humidity Parameters:\n"
1621 " CURRENT: %s\n"
1622 " TARGET: %s\n"
1623 " DEHUMIDIFICATION: %s\n"
1624 " HUMIDIFICATION: %s",
1625 YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
1626 YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
1627 YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
1628 YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
1629 YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
1630 YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
1632 YESNO(this->supports_two_points_),
1633 YESNO(this->get_traits().has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)),
1635 YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
1636
1637 if (!this->preset_config_.empty()) {
1638 ESP_LOGCONFIG(TAG, " Supported PRESETS:");
1639 for (const auto &entry : this->preset_config_) {
1640 const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
1641 ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : "");
1642 this->dump_preset_config_(preset_name, entry.config);
1643 }
1644 }
1645
1646 if (!this->custom_preset_config_.empty()) {
1647 ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
1648 for (const auto &entry : this->custom_preset_config_) {
1649 const auto *preset_name = entry.name;
1650 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1651 (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
1652 ? " (default)"
1653 : "");
1654 this->dump_preset_config_(preset_name, entry.config);
1655 }
1656 }
1657}
1658
1660
1662 : default_temperature(default_temperature) {}
1663
1665 float default_temperature_high)
1666 : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
1667
1668} // 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 trigger(const Ts &...x)
Inform the parent automation that the event has triggered.
Definition automation.h:325
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:333
This class is used to encode all control actions on a climate device.
Definition climate.h:33
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:121
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:313
StringRef get_custom_preset() const
Definition climate.h:119
ClimateMode mode
The active mode of the climate device.
Definition climate.h:266
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:260
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:494
float target_temperature
The target temperature of the climate device.
Definition climate.h:247
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:243
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:272
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:250
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:700
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.h:298
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:237
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:240
ClimateAction action
The active state of the climate device.
Definition climate.h:269
StringRef get_custom_preset() const
Get the active custom preset (read-only access). Returns StringRef.
Definition climate.h:278
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:445
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:263
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:370
float target_humidity
The target humidity of the climate device.
Definition climate.h:257
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:252
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 set_supported_custom_presets(std::initializer_list< const char * > presets)
void add_supported_swing_mode(ClimateSwingMode mode)
Base-class for all sensors.
Definition sensor.h:47
void add_on_state_callback(std::function< void(float)> &&callback)
Add a callback that will be called every time a filtered value arrives.
Definition sensor.cpp:82
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:125
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.
std::string size_t len
Definition helpers.h:892
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
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