ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
thermostat_climate.cpp
Go to the documentation of this file.
4#include "esphome/core/log.h"
5
6namespace esphome {
7namespace thermostat {
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
69 for (auto &timer : this->timer_) {
70 if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) {
71 timer.active = false;
72 timer.func();
73 }
74 }
75}
76
81
93
95 bool state_mismatch = this->action != this->compute_action_(true);
96
97 switch (this->compute_action_(true)) {
100 return state_mismatch && (!this->idle_action_ready_());
102 return state_mismatch && (!this->cooling_action_ready_());
104 return state_mismatch && (!this->heating_action_ready_());
106 return state_mismatch && (!this->fanning_action_ready_());
108 return state_mismatch && (!this->drying_action_ready_());
109 default:
110 break;
111 }
112 return false;
113}
114
116 bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_;
117 return state_mismatch && (!this->fan_mode_ready_());
118}
119
121
123
125 if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) &&
126 (std::isnan(this->cooling_deadband_) || std::isnan(this->cooling_overrun_)))
127 return false;
128
129 if (this->supports_heat_ && (std::isnan(this->heating_deadband_) || std::isnan(this->heating_overrun_)))
130 return false;
131
132 return true;
133}
134
136 return !std::isnan(this->humidity_hysteresis_) && this->humidity_hysteresis_ >= 0.0f &&
137 this->humidity_hysteresis_ < 100.0f;
138}
139
144
146 if (std::isnan(this->target_temperature)) {
147 // default to the midpoint between visual min and max
148 this->target_temperature =
151 } else {
152 // target_temperature must be between the visual minimum and the visual maximum
153 this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
154 this->get_traits().get_visual_max_temperature());
155 }
156}
157
158void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
159 if (!this->supports_two_points_) {
161 } else if (pin_target_temperature_high) {
162 // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
165 } else {
166 // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
169 }
170}
171
173 if (std::isnan(this->target_temperature_low)) {
175 } else {
176 float target_temperature_low_upper_limit =
179 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
181 this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
182 target_temperature_low_upper_limit);
183 }
184}
185
187 if (std::isnan(this->target_temperature_high)) {
189 } else {
190 float target_temperature_high_lower_limit =
193 this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
195 this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
196 this->get_traits().get_visual_max_temperature());
197 }
198}
199
201 if (std::isnan(this->target_humidity)) {
202 this->target_humidity =
204 } else {
205 this->target_humidity = clamp<float>(this->target_humidity, this->get_traits().get_visual_min_humidity(),
206 this->get_traits().get_visual_max_humidity());
207 }
208}
209
211 bool target_temperature_high_changed = false;
212
213 if (call.get_preset().has_value()) {
214 // setup_complete_ blocks modifying/resetting the temps immediately after boot
215 if (this->setup_complete_) {
216 this->change_preset_(call.get_preset().value());
217 } else {
218 this->preset = call.get_preset().value();
219 }
220 }
221 if (call.has_custom_preset()) {
222 // setup_complete_ blocks modifying/resetting the temps immediately after boot
223 if (this->setup_complete_) {
225 } else {
226 // Use the base class method which handles pointer lookup internally
228 }
229 }
230
231 if (call.get_mode().has_value()) {
232 this->mode = call.get_mode().value();
233 }
234 if (call.get_fan_mode().has_value()) {
235 this->fan_mode = call.get_fan_mode().value();
236 }
237 if (call.get_swing_mode().has_value()) {
238 this->swing_mode = call.get_swing_mode().value();
239 }
240 if (this->supports_two_points_) {
243 }
244 if (call.get_target_temperature_high().has_value()) {
245 target_temperature_high_changed = this->target_temperature_high != call.get_target_temperature_high().value();
247 }
248 // ensure the two set points are valid and adjust one of them if necessary
249 this->validate_target_temperatures(target_temperature_high_changed ||
251 } else {
252 if (call.get_target_temperature().has_value()) {
255 }
256 }
257 if (call.get_target_humidity().has_value()) {
260 }
261 // make any changes happen
262 this->refresh();
263}
264
267
269
270 if (this->supports_two_points_)
272
273 if (this->humidity_sensor_ != nullptr)
275
278
279 if (this->supports_auto_)
281 if (this->supports_heat_cool_)
283 if (this->supports_cool_)
285 if (this->supports_dry_)
287 if (this->supports_fan_only_)
289 if (this->supports_heat_)
291
292 if (this->supports_fan_mode_on_)
294 if (this->supports_fan_mode_off_)
296 if (this->supports_fan_mode_auto_)
298 if (this->supports_fan_mode_low_)
302 if (this->supports_fan_mode_high_)
306 if (this->supports_fan_mode_focus_)
310 if (this->supports_fan_mode_quiet_)
312
317 if (this->supports_swing_mode_off_)
321
322 for (const auto &entry : this->preset_config_) {
323 traits.add_supported_preset(entry.preset);
324 }
325
326 // Extract custom preset names from the custom_preset_config_ vector
327 if (!this->custom_preset_config_.empty()) {
328 std::vector<const char *> custom_preset_names;
329 custom_preset_names.reserve(this->custom_preset_config_.size());
330 for (const auto &entry : this->custom_preset_config_) {
331 custom_preset_names.push_back(entry.name);
332 }
333 traits.set_supported_custom_presets(custom_preset_names);
334 }
335
336 return traits;
337}
338
340 auto target_action = climate::CLIMATE_ACTION_IDLE;
341 // if any hysteresis values or current_temperature is not valid, we go to OFF;
342 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
344 }
345 // do not change the action if an "ON" timer is running
346 if ((!ignore_timers) && (this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
350 return this->action;
351 }
352
353 // ensure set point(s) is/are valid before computing the action
355 // everything has been validated so we can now safely compute the action
356 switch (this->mode) {
357 // if the climate mode is OFF then the climate action must be OFF
359 target_action = climate::CLIMATE_ACTION_OFF;
360 break;
362 if (this->fanning_required_())
363 target_action = climate::CLIMATE_ACTION_FAN;
364 break;
366 target_action = climate::CLIMATE_ACTION_DRYING;
367 break;
369 if (this->cooling_required_() && this->heating_required_()) {
370 // this is bad and should never happen, so just stop.
371 // target_action = climate::CLIMATE_ACTION_IDLE;
372 } else if (this->cooling_required_()) {
373 target_action = climate::CLIMATE_ACTION_COOLING;
374 } else if (this->heating_required_()) {
375 target_action = climate::CLIMATE_ACTION_HEATING;
376 }
377 break;
379 if (this->cooling_required_()) {
380 target_action = climate::CLIMATE_ACTION_COOLING;
381 }
382 break;
384 if (this->heating_required_()) {
385 target_action = climate::CLIMATE_ACTION_HEATING;
386 }
387 break;
389 if (this->supports_two_points_) {
390 if (this->cooling_required_() && this->heating_required_()) {
391 // this is bad and should never happen, so just stop.
392 // target_action = climate::CLIMATE_ACTION_IDLE;
393 } else if (this->cooling_required_()) {
394 target_action = climate::CLIMATE_ACTION_COOLING;
395 } else if (this->heating_required_()) {
396 target_action = climate::CLIMATE_ACTION_HEATING;
397 }
398 } else if (this->supports_cool_ && this->cooling_required_()) {
399 target_action = climate::CLIMATE_ACTION_COOLING;
400 } else if (this->supports_heat_ && this->heating_required_()) {
401 target_action = climate::CLIMATE_ACTION_HEATING;
402 }
403 break;
404 default:
405 break;
406 }
407 // do not abruptly switch actions. cycle through IDLE, first. we'll catch this at the next update.
409 (target_action == climate::CLIMATE_ACTION_HEATING)) ||
411 ((target_action == climate::CLIMATE_ACTION_COOLING) || (target_action == climate::CLIMATE_ACTION_DRYING)))) {
413 }
414
415 return target_action;
416}
417
419 auto target_action = climate::CLIMATE_ACTION_IDLE;
420 // if any hysteresis values or current_temperature is not valid, we go to OFF;
421 if (std::isnan(this->current_temperature) || !this->hysteresis_valid()) {
423 }
424
425 // ensure set point(s) is/are valid before computing the action
427 // everything has been validated so we can now safely compute the action
428 switch (this->mode) {
429 // if the climate mode is OFF then the climate action must be OFF
431 target_action = climate::CLIMATE_ACTION_OFF;
432 break;
435 // this is bad and should never happen, so just stop.
436 // target_action = climate::CLIMATE_ACTION_IDLE;
437 } else if (this->supplemental_cooling_required_()) {
438 target_action = climate::CLIMATE_ACTION_COOLING;
439 } else if (this->supplemental_heating_required_()) {
440 target_action = climate::CLIMATE_ACTION_HEATING;
441 }
442 break;
444 if (this->supplemental_cooling_required_()) {
445 target_action = climate::CLIMATE_ACTION_COOLING;
446 }
447 break;
449 if (this->supplemental_heating_required_()) {
450 target_action = climate::CLIMATE_ACTION_HEATING;
451 }
452 break;
453 default:
454 break;
455 }
456
457 return target_action;
458}
459
461 auto target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
462 // if hysteresis value or current_humidity is not valid, we go to OFF
463 if (std::isnan(this->current_humidity) || !this->humidity_hysteresis_valid()) {
465 }
466
467 // ensure set point is valid before computing the action
469 // everything has been validated so we can now safely compute the action
471 // this is bad and should never happen, so just stop.
472 // target_action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
473 } else if (this->supports_dehumidification_ && this->dehumidification_required_()) {
475 } else if (this->supports_humidification_ && this->humidification_required_()) {
477 }
478
479 return target_action;
480}
481
483 // setup_complete_ helps us ensure an action is called immediately after boot
484 if ((action == this->action) && this->setup_complete_) {
485 // already in target mode
486 return;
487 }
488
489 if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) ||
491 this->setup_complete_) {
492 // switching from OFF to IDLE or vice-versa -- this is only a visual difference.
493 // OFF means user manually disabled, IDLE means the temperature is in target range.
494 this->action = action;
495 if (publish_state)
496 this->publish_state();
497 return;
498 }
499
500 bool action_ready = false;
501 Trigger<> *trig = this->idle_action_trigger_, *trig_fan = nullptr;
502 switch (action) {
505 if (this->idle_action_ready_()) {
507 if (this->action == climate::CLIMATE_ACTION_COOLING)
509 if (this->action == climate::CLIMATE_ACTION_FAN) {
512 } else {
514 }
515 }
516 if (this->action == climate::CLIMATE_ACTION_HEATING)
518 // trig = this->idle_action_trigger_;
519 ESP_LOGVV(TAG, "Switching to IDLE/OFF action");
520 this->cooling_max_runtime_exceeded_ = false;
521 this->heating_max_runtime_exceeded_ = false;
522 action_ready = true;
523 }
524 break;
526 if (this->cooling_action_ready_()) {
529 if (this->supports_fan_with_cooling_) {
531 trig_fan = this->fan_only_action_trigger_;
532 }
533 this->cooling_max_runtime_exceeded_ = false;
534 trig = this->cool_action_trigger_;
535 ESP_LOGVV(TAG, "Switching to COOLING action");
536 action_ready = true;
537 }
538 break;
540 if (this->heating_action_ready_()) {
543 if (this->supports_fan_with_heating_) {
545 trig_fan = this->fan_only_action_trigger_;
546 }
547 this->heating_max_runtime_exceeded_ = false;
548 trig = this->heat_action_trigger_;
549 ESP_LOGVV(TAG, "Switching to HEATING action");
550 action_ready = true;
551 }
552 break;
554 if (this->fanning_action_ready_()) {
557 } else {
559 }
560 trig = this->fan_only_action_trigger_;
561 ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
562 action_ready = true;
563 }
564 break;
566 if (this->drying_action_ready_()) {
569 trig = this->dry_action_trigger_;
570 ESP_LOGVV(TAG, "Switching to DRYING action");
571 action_ready = true;
572 }
573 break;
574 default:
575 // we cannot report an invalid mode back to HA (even if it asked for one)
576 // and must assume some valid value
578 // trig = this->idle_action_trigger_;
579 }
580
581 if (action_ready) {
582 if (this->prev_action_trigger_ != nullptr) {
584 this->prev_action_trigger_ = nullptr;
585 }
586 this->action = action;
587 this->prev_action_trigger_ = trig;
588 if (trig != nullptr) {
589 trig->trigger();
590 }
591 // if enabled, call the fan_only action with cooling/heating actions
592 if (trig_fan != nullptr) {
593 ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
594 trig_fan->trigger();
595 }
596 if (publish_state)
597 this->publish_state();
598 }
599}
600
602 // setup_complete_ helps us ensure an action is called immediately after boot
603 if ((action == this->supplemental_action_) && this->setup_complete_) {
604 // already in target mode
605 return;
606 }
607
608 switch (action) {
613 break;
616 break;
619 break;
620 default:
621 return;
622 }
623 ESP_LOGVV(TAG, "Updating supplemental action");
626}
627
629 Trigger<> *trig = nullptr;
630
631 switch (this->supplemental_action_) {
635 }
637 ESP_LOGVV(TAG, "Calling supplemental COOLING action");
638 break;
642 }
644 ESP_LOGVV(TAG, "Calling supplemental HEATING action");
645 break;
646 default:
647 break;
648 }
649
650 if (trig != nullptr) {
651 trig->trigger();
652 }
653}
654
656 // setup_complete_ helps us ensure an action is called immediately after boot
657 if ((action == this->humidification_action_) && this->setup_complete_) {
658 // already in target mode
659 return;
660 }
661
663 switch (action) {
665 // trig = this->humidity_control_off_action_trigger_;
666 ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
667 break;
670 ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
671 break;
674 ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
675 break;
677 default:
679 // trig = this->humidity_control_off_action_trigger_;
680 }
681
682 if (this->prev_humidity_control_trigger_ != nullptr) {
684 this->prev_humidity_control_trigger_ = nullptr;
685 }
688 if (trig != nullptr) {
689 trig->trigger();
690 }
691}
692
694 // setup_complete_ helps us ensure an action is called immediately after boot
695 if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) {
696 // already in target mode
697 return;
698 }
699
700 this->fan_mode = fan_mode;
701 if (publish_state)
702 this->publish_state();
703
704 if (this->fan_mode_ready_()) {
705 Trigger<> *trig = this->fan_mode_auto_trigger_;
706 switch (fan_mode) {
708 trig = this->fan_mode_on_trigger_;
709 ESP_LOGVV(TAG, "Switching to FAN_ON mode");
710 break;
712 trig = this->fan_mode_off_trigger_;
713 ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
714 break;
716 // trig = this->fan_mode_auto_trigger_;
717 ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
718 break;
720 trig = this->fan_mode_low_trigger_;
721 ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
722 break;
724 trig = this->fan_mode_medium_trigger_;
725 ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
726 break;
728 trig = this->fan_mode_high_trigger_;
729 ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
730 break;
732 trig = this->fan_mode_middle_trigger_;
733 ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
734 break;
736 trig = this->fan_mode_focus_trigger_;
737 ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
738 break;
740 trig = this->fan_mode_diffuse_trigger_;
741 ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
742 break;
744 trig = this->fan_mode_quiet_trigger_;
745 ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
746 break;
747 default:
748 // we cannot report an invalid mode back to HA (even if it asked for one)
749 // and must assume some valid value
751 // trig = this->fan_mode_auto_trigger_;
752 }
753 if (this->prev_fan_mode_trigger_ != nullptr) {
755 this->prev_fan_mode_trigger_ = nullptr;
756 }
758 if (trig != nullptr) {
759 trig->trigger();
760 }
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 if (trig != nullptr) {
805 trig->trigger();
806 }
807 this->mode = mode;
808 this->prev_mode_ = mode;
809 this->prev_mode_trigger_ = trig;
810 if (publish_state) {
811 this->publish_state();
812 }
813}
814
816 // setup_complete_ helps us ensure an action is called immediately after boot
817 if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) {
818 // already in target mode
819 return;
820 }
821
822 if (this->prev_swing_mode_trigger_ != nullptr) {
824 this->prev_swing_mode_trigger_ = nullptr;
825 }
827 switch (swing_mode) {
829 trig = this->swing_mode_both_trigger_;
830 break;
833 break;
835 // trig = this->swing_mode_off_trigger_;
836 break;
838 trig = this->swing_mode_vertical_trigger_;
839 break;
840 default:
841 // we cannot report an invalid mode back to HA (even if it asked for one)
842 // and must assume some valid value
843 swing_mode = climate::CLIMATE_SWING_OFF;
844 // trig = this->swing_mode_off_trigger_;
845 }
846 if (trig != nullptr) {
847 trig->trigger();
848 }
849 this->swing_mode = swing_mode;
851 this->prev_swing_mode_trigger_ = trig;
852 if (publish_state)
853 this->publish_state();
854}
855
866
873
880
882
890
897
899 if (this->timer_duration_(timer_index) > 0) {
900 this->timer_[timer_index].started = millis();
901 this->timer_[timer_index].active = true;
902 }
903}
904
906 auto ret = this->timer_[timer_index].active;
907 this->timer_[timer_index].active = false;
908 return ret;
909}
910
912 return this->timer_[timer_index].active;
913}
914
916 return this->timer_[timer_index].time;
917}
918
920 return this->timer_[timer_index].func;
921}
922
924 ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
928}
929
931 ESP_LOGVV(TAG, "cooling_off timer expired");
932 this->switch_to_action_(this->compute_action_());
934}
935
937 ESP_LOGVV(TAG, "cooling_on timer expired");
938 this->switch_to_action_(this->compute_action_());
940}
941
943 ESP_LOGVV(TAG, "fan_mode timer expired");
946 this->switch_to_action_(this->compute_action_());
947}
948
950 ESP_LOGVV(TAG, "fanning_off timer expired");
951 this->switch_to_action_(this->compute_action_());
952}
953
955 ESP_LOGVV(TAG, "fanning_on timer expired");
956 this->switch_to_action_(this->compute_action_());
957}
958
960 ESP_LOGVV(TAG, "heating_max_run_time timer expired");
964}
965
967 ESP_LOGVV(TAG, "heating_off timer expired");
968 this->switch_to_action_(this->compute_action_());
970}
971
973 ESP_LOGVV(TAG, "heating_on timer expired");
974 this->switch_to_action_(this->compute_action_());
976}
977
979 ESP_LOGVV(TAG, "idle_on timer expired");
980 this->switch_to_action_(this->compute_action_());
982}
983
985 if ((this->prev_target_humidity_ == this->target_humidity) && this->setup_complete_) {
986 return; // nothing changed, no reason to trigger
987 } else {
988 // save the new temperature so we can check it again later; the trigger will fire below
990 }
991 // trigger the action
993 if (trig != nullptr) {
994 trig->trigger();
995 }
996}
997
999 if (this->supports_two_points_) {
1000 // setup_complete_ helps us ensure an action is called immediately after boot
1003 return; // nothing changed, no reason to trigger
1004 } else {
1005 // save the new temperatures so we can check them again later; the trigger will fire below
1008 }
1009 } else {
1010 if ((this->prev_target_temperature_ == this->target_temperature) && this->setup_complete_) {
1011 return; // nothing changed, no reason to trigger
1012 } else {
1013 // save the new temperature so we can check it again later; the trigger will fire below
1015 }
1016 }
1017 // trigger the action
1019 if (trig != nullptr) {
1020 trig->trigger();
1021 }
1022}
1023
1026
1027 if (this->supports_cool_) {
1029 // if the current temperature exceeds the target + deadband, cooling is required
1030 return true;
1032 // if the current temperature is less than the target - overrun, cooling should stop
1033 return false;
1034 } else {
1035 // if we get here, the current temperature is between target + deadband and target - overrun,
1036 // so the action should not change unless it conflicts with the current mode
1037 return (this->action == climate::CLIMATE_ACTION_COOLING) &&
1039 }
1040 }
1041 return false;
1042}
1043
1046
1047 if (this->supports_fan_only_) {
1048 if (this->supports_fan_only_cooling_) {
1050 // if the current temperature exceeds the target + deadband, fanning is required
1051 return true;
1053 // if the current temperature is less than the target - overrun, fanning should stop
1054 return false;
1055 } else {
1056 // if we get here, the current temperature is between target + deadband and target - overrun,
1057 // so the action should not change unless it conflicts with the current mode
1059 }
1060 } else {
1061 return true;
1062 }
1063 }
1064 return false;
1065}
1066
1069
1070 if (this->supports_heat_) {
1072 // if the current temperature is below the target - deadband, heating is required
1073 return true;
1074 } else if (this->current_temperature > temperature + this->heating_overrun_) {
1075 // if the current temperature is above the target + overrun, heating should stop
1076 return false;
1077 } else {
1078 // if we get here, the current temperature is between target - deadband and target + overrun,
1079 // so the action should not change unless it conflicts with the current mode
1080 return (this->action == climate::CLIMATE_ACTION_HEATING) &&
1082 }
1083 }
1084 return false;
1085}
1086
1089 // the component must supports_cool_ and the climate action must be climate::CLIMATE_ACTION_COOLING. then...
1090 // supplemental cooling is required if the max delta or max runtime was exceeded or the action is already engaged
1091 return this->supports_cool_ && (this->action == climate::CLIMATE_ACTION_COOLING) &&
1095}
1096
1099 // the component must supports_heat_ and the climate action must be climate::CLIMATE_ACTION_HEATING. then...
1100 // supplemental heating is required if the max delta or max runtime was exceeded or the action is already engaged
1101 return this->supports_heat_ && (this->action == climate::CLIMATE_ACTION_HEATING) &&
1105}
1106
1108 if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1109 // if the current humidity exceeds the target + hysteresis, dehumidification is required
1110 return true;
1112 // if the current humidity is less than the target - hysteresis, dehumidification should stop
1113 return false;
1114 }
1115 // if we get here, the current humidity is between target + hysteresis and target - hysteresis,
1116 // so the action should not change
1118}
1119
1122 // if the current humidity is below the target - hysteresis, humidification is required
1123 return true;
1124 } else if (this->current_humidity > this->target_humidity + this->humidity_hysteresis_) {
1125 // if the current humidity is above the target + hysteresis, humidification should stop
1126 return false;
1127 }
1128 // if we get here, the current humidity is between target - hysteresis and target + hysteresis,
1129 // so the action should not change
1131}
1132
1134 if (this->supports_heat_) {
1135 ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
1137 }
1138 if ((this->supports_cool_) || (this->supports_fan_only_)) {
1139 ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
1141 }
1142
1143 if (config.mode_.has_value()) {
1144 ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1145 }
1146 if (config.fan_mode_.has_value()) {
1147 ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
1148 LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1149 }
1150 if (config.swing_mode_.has_value()) {
1151 ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
1153 }
1154}
1155
1157 // Linear search through preset configurations
1158 const ThermostatClimateTargetTempConfig *config = nullptr;
1159 for (const auto &entry : this->preset_config_) {
1160 if (entry.preset == preset) {
1161 config = &entry.config;
1162 break;
1163 }
1164 }
1165
1166 if (config != nullptr) {
1167 ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1168 if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
1169 // Fire any preset changed trigger if defined
1170 Trigger<> *trig = this->preset_change_trigger_;
1171 this->set_preset_(preset);
1172 if (trig != nullptr) {
1173 trig->trigger();
1174 }
1175
1176 this->refresh();
1177 ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1178 } else {
1179 ESP_LOGI(TAG, "No changes required to apply preset %s", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1180 }
1181 } else {
1182 ESP_LOGW(TAG, "Preset %s not configured; ignoring", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
1183 }
1184}
1185
1187 // Linear search through custom preset configurations
1188 const ThermostatClimateTargetTempConfig *config = nullptr;
1189 for (const auto &entry : this->custom_preset_config_) {
1190 if (strcmp(entry.name, custom_preset) == 0) {
1191 config = &entry.config;
1192 break;
1193 }
1194 }
1195
1196 if (config != nullptr) {
1197 ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
1198 if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
1199 strcmp(this->get_custom_preset(), custom_preset) != 0) {
1200 // Fire any preset changed trigger if defined
1201 Trigger<> *trig = this->preset_change_trigger_;
1202 // Use the base class method which handles pointer lookup and preset reset internally
1203 this->set_custom_preset_(custom_preset);
1204 if (trig != nullptr) {
1205 trig->trigger();
1206 }
1207
1208 this->refresh();
1209 ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
1210 } else {
1211 ESP_LOGI(TAG, "No changes required to apply custom preset %s", custom_preset);
1212 // Note: set_custom_preset_() above handles preset.reset() and custom_preset_ assignment internally.
1213 // The old code had these lines here unconditionally, which was a bug (double assignment, state modification
1214 // even when no changes were needed). Now properly handled by the protected setter with mutual exclusion.
1215 }
1216 } else {
1217 ESP_LOGW(TAG, "Custom preset %s not configured; ignoring", custom_preset);
1218 }
1219}
1220
1222 bool something_changed = false;
1223
1224 if (this->supports_two_points_) {
1225 if (this->target_temperature_low != config.default_temperature_low) {
1227 something_changed = true;
1228 }
1231 something_changed = true;
1232 }
1233 } else {
1234 if (this->target_temperature != config.default_temperature) {
1236 something_changed = true;
1237 }
1238 }
1239
1240 // Note: The mode, fan_mode and swing_mode can all be defined in the preset but if the climate.control call
1241 // also specifies them then the climate.control call's values will override the preset's values for that call
1242 if (config.mode_.has_value() && (this->mode != config.mode_.value())) {
1243 ESP_LOGV(TAG, "Setting mode to %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
1244 this->mode = *config.mode_;
1245 something_changed = true;
1246 }
1247
1248 if (config.fan_mode_.has_value() && (this->fan_mode != config.fan_mode_.value())) {
1249 ESP_LOGV(TAG, "Setting fan mode to %s", LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
1250 this->fan_mode = *config.fan_mode_;
1251 something_changed = true;
1252 }
1253
1254 if (config.swing_mode_.has_value() && (this->swing_mode != config.swing_mode_.value())) {
1255 ESP_LOGV(TAG, "Setting swing mode to %s", LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
1256 this->swing_mode = *config.swing_mode_;
1257 something_changed = true;
1258 }
1259
1260 return something_changed;
1261}
1262
1263void ThermostatClimate::set_preset_config(std::initializer_list<PresetEntry> presets) {
1264 this->preset_config_ = presets;
1265}
1266
1267void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets) {
1268 this->custom_preset_config_ = presets;
1269}
1270
1272 : cool_action_trigger_(new Trigger<>()),
1273 supplemental_cool_action_trigger_(new Trigger<>()),
1274 cool_mode_trigger_(new Trigger<>()),
1275 dry_action_trigger_(new Trigger<>()),
1276 dry_mode_trigger_(new Trigger<>()),
1277 heat_action_trigger_(new Trigger<>()),
1278 supplemental_heat_action_trigger_(new Trigger<>()),
1279 heat_mode_trigger_(new Trigger<>()),
1280 heat_cool_mode_trigger_(new Trigger<>()),
1281 auto_mode_trigger_(new Trigger<>()),
1282 idle_action_trigger_(new Trigger<>()),
1283 off_mode_trigger_(new Trigger<>()),
1284 fan_only_action_trigger_(new Trigger<>()),
1285 fan_only_mode_trigger_(new Trigger<>()),
1286 fan_mode_on_trigger_(new Trigger<>()),
1287 fan_mode_off_trigger_(new Trigger<>()),
1288 fan_mode_auto_trigger_(new Trigger<>()),
1289 fan_mode_low_trigger_(new Trigger<>()),
1290 fan_mode_medium_trigger_(new Trigger<>()),
1291 fan_mode_high_trigger_(new Trigger<>()),
1292 fan_mode_middle_trigger_(new Trigger<>()),
1293 fan_mode_focus_trigger_(new Trigger<>()),
1294 fan_mode_diffuse_trigger_(new Trigger<>()),
1295 fan_mode_quiet_trigger_(new Trigger<>()),
1296 swing_mode_both_trigger_(new Trigger<>()),
1297 swing_mode_off_trigger_(new Trigger<>()),
1298 swing_mode_horizontal_trigger_(new Trigger<>()),
1299 swing_mode_vertical_trigger_(new Trigger<>()),
1300 humidity_change_trigger_(new Trigger<>()),
1301 temperature_change_trigger_(new Trigger<>()),
1302 preset_change_trigger_(new Trigger<>()),
1303 humidity_control_dehumidify_action_trigger_(new Trigger<>()),
1304 humidity_control_humidify_action_trigger_(new Trigger<>()),
1305 humidity_control_off_action_trigger_(new Trigger<>()) {}
1306
1308 // Find the preset in custom_preset_config_ and store pointer from there
1309 for (const auto &entry : this->custom_preset_config_) {
1310 if (strcmp(entry.name, custom_preset) == 0) {
1311 this->default_custom_preset_ = entry.name;
1312 return;
1313 }
1314 }
1315 // If not found, it will be caught during validation
1316 this->default_custom_preset_ = nullptr;
1317}
1318
1320
1322 this->on_boot_restore_from_ = on_boot_restore_from;
1323}
1325 this->set_point_minimum_differential_ = differential;
1326}
1327void ThermostatClimate::set_cool_deadband(float deadband) { this->cooling_deadband_ = deadband; }
1328void ThermostatClimate::set_cool_overrun(float overrun) { this->cooling_overrun_ = overrun; }
1329void ThermostatClimate::set_heat_deadband(float deadband) { this->heating_deadband_ = deadband; }
1330void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_ = overrun; }
1343 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1344}
1355 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1356}
1367 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1368}
1371 1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
1372}
1375 this->humidity_sensor_ = humidity_sensor;
1376}
1377void ThermostatClimate::set_humidity_hysteresis(float humidity_hysteresis) {
1378 this->humidity_hysteresis_ = std::clamp<float>(humidity_hysteresis, 0.0f, 100.0f);
1379}
1380void ThermostatClimate::set_use_startup_delay(bool use_startup_delay) { this->use_startup_delay_ = use_startup_delay; }
1381void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) {
1382 this->supports_heat_cool_ = supports_heat_cool;
1383}
1384void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; }
1385void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
1386void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; }
1387void ThermostatClimate::set_supports_fan_only(bool supports_fan_only) { this->supports_fan_only_ = supports_fan_only; }
1389 bool supports_fan_only_action_uses_fan_mode_timer) {
1390 this->supports_fan_only_action_uses_fan_mode_timer_ = supports_fan_only_action_uses_fan_mode_timer;
1391}
1392void ThermostatClimate::set_supports_fan_only_cooling(bool supports_fan_only_cooling) {
1393 this->supports_fan_only_cooling_ = supports_fan_only_cooling;
1394}
1395void ThermostatClimate::set_supports_fan_with_cooling(bool supports_fan_with_cooling) {
1396 this->supports_fan_with_cooling_ = supports_fan_with_cooling;
1397}
1398void ThermostatClimate::set_supports_fan_with_heating(bool supports_fan_with_heating) {
1399 this->supports_fan_with_heating_ = supports_fan_with_heating;
1400}
1401void ThermostatClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }
1402void ThermostatClimate::set_supports_fan_mode_on(bool supports_fan_mode_on) {
1403 this->supports_fan_mode_on_ = supports_fan_mode_on;
1404}
1405void ThermostatClimate::set_supports_fan_mode_off(bool supports_fan_mode_off) {
1406 this->supports_fan_mode_off_ = supports_fan_mode_off;
1407}
1408void ThermostatClimate::set_supports_fan_mode_auto(bool supports_fan_mode_auto) {
1409 this->supports_fan_mode_auto_ = supports_fan_mode_auto;
1410}
1411void ThermostatClimate::set_supports_fan_mode_low(bool supports_fan_mode_low) {
1412 this->supports_fan_mode_low_ = supports_fan_mode_low;
1413}
1414void ThermostatClimate::set_supports_fan_mode_medium(bool supports_fan_mode_medium) {
1415 this->supports_fan_mode_medium_ = supports_fan_mode_medium;
1416}
1417void ThermostatClimate::set_supports_fan_mode_high(bool supports_fan_mode_high) {
1418 this->supports_fan_mode_high_ = supports_fan_mode_high;
1419}
1420void ThermostatClimate::set_supports_fan_mode_middle(bool supports_fan_mode_middle) {
1421 this->supports_fan_mode_middle_ = supports_fan_mode_middle;
1422}
1423void ThermostatClimate::set_supports_fan_mode_focus(bool supports_fan_mode_focus) {
1424 this->supports_fan_mode_focus_ = supports_fan_mode_focus;
1425}
1426void ThermostatClimate::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
1427 this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
1428}
1429void ThermostatClimate::set_supports_fan_mode_quiet(bool supports_fan_mode_quiet) {
1430 this->supports_fan_mode_quiet_ = supports_fan_mode_quiet;
1431}
1432void ThermostatClimate::set_supports_swing_mode_both(bool supports_swing_mode_both) {
1433 this->supports_swing_mode_both_ = supports_swing_mode_both;
1434}
1435void ThermostatClimate::set_supports_swing_mode_off(bool supports_swing_mode_off) {
1436 this->supports_swing_mode_off_ = supports_swing_mode_off;
1437}
1438void ThermostatClimate::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) {
1439 this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal;
1440}
1441void ThermostatClimate::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) {
1442 this->supports_swing_mode_vertical_ = supports_swing_mode_vertical;
1443}
1444void ThermostatClimate::set_supports_two_points(bool supports_two_points) {
1445 this->supports_two_points_ = supports_two_points;
1446}
1447void ThermostatClimate::set_supports_dehumidification(bool supports_dehumidification) {
1448 this->supports_dehumidification_ = supports_dehumidification;
1449 if (supports_dehumidification) {
1450 this->supports_humidification_ = false;
1451 }
1452}
1453void ThermostatClimate::set_supports_humidification(bool supports_humidification) {
1454 this->supports_humidification_ = supports_humidification;
1455 if (supports_humidification) {
1456 this->supports_dehumidification_ = false;
1457 }
1458}
1459
1504
1506 LOG_CLIMATE("", "Thermostat", this);
1507
1508 ESP_LOGCONFIG(TAG,
1509 " On boot, restore from: %s\n"
1510 " Use Start-up Delay: %s",
1511 this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY",
1512 YESNO(this->use_startup_delay_));
1513 if (this->supports_two_points_) {
1514 ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
1515 }
1516 if (this->supports_cool_) {
1517 ESP_LOGCONFIG(TAG,
1518 " Cooling Parameters:\n"
1519 " Deadband: %.1f°C\n"
1520 " Overrun: %.1f°C\n"
1521 " Minimum Off Time: %" PRIu32 "s\n"
1522 " Minimum Run Time: %" PRIu32 "s",
1526 if ((this->supplemental_cool_delta_ > 0) ||
1528 ESP_LOGCONFIG(TAG,
1529 " Maximum Run Time: %" PRIu32 "s\n"
1530 " Supplemental Delta: %.1f°C",
1533 }
1534 }
1535 if (this->supports_heat_) {
1536 ESP_LOGCONFIG(TAG,
1537 " Heating 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_heat_delta_ > 0) ||
1547 ESP_LOGCONFIG(TAG,
1548 " Maximum Run Time: %" PRIu32 "s\n"
1549 " Supplemental Delta: %.1f°C",
1552 }
1553 }
1554 if (this->supports_fan_only_) {
1555 ESP_LOGCONFIG(TAG,
1556 " Fan Parameters:\n"
1557 " Minimum Off Time: %" PRIu32 "s\n"
1558 " Minimum Run Time: %" PRIu32 "s",
1561 }
1566 ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
1568 }
1569 ESP_LOGCONFIG(TAG,
1570 " Minimum Idle Time: %" PRIu32 "s\n"
1571 " Supported MODES:\n"
1572 " AUTO: %s\n"
1573 " HEAT/COOL: %s\n"
1574 " HEAT: %s\n"
1575 " COOL: %s\n"
1576 " DRY: %s\n"
1577 " FAN_ONLY: %s\n"
1578 " FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
1579 " FAN_ONLY_COOLING: %s",
1580 this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
1581 YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
1582 YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
1584 if (this->supports_cool_) {
1585 ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
1586 }
1587 if (this->supports_heat_) {
1588 ESP_LOGCONFIG(TAG, " FAN_WITH_HEATING: %s", YESNO(this->supports_fan_with_heating_));
1589 }
1590 ESP_LOGCONFIG(TAG,
1591 " Supported FAN MODES:\n"
1592 " ON: %s\n"
1593 " OFF: %s\n"
1594 " AUTO: %s\n"
1595 " LOW: %s\n"
1596 " MEDIUM: %s\n"
1597 " HIGH: %s\n"
1598 " MIDDLE: %s\n"
1599 " FOCUS: %s\n"
1600 " DIFFUSE: %s\n"
1601 " QUIET: %s\n"
1602 " Supported SWING MODES:\n"
1603 " BOTH: %s\n"
1604 " OFF: %s\n"
1605 " HORIZONTAL: %s\n"
1606 " VERTICAL: %s\n"
1607 " Supports TWO SET POINTS: %s\n"
1608 " Supported Humidity Parameters:\n"
1609 " CURRENT: %s\n"
1610 " TARGET: %s\n"
1611 " DEHUMIDIFICATION: %s\n"
1612 " HUMIDIFICATION: %s",
1613 YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
1614 YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
1615 YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
1616 YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
1617 YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
1618 YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
1620 YESNO(this->supports_two_points_),
1621 YESNO(this->get_traits().has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)),
1623 YESNO(this->supports_dehumidification_), YESNO(this->supports_humidification_));
1624
1625 if (!this->preset_config_.empty()) {
1626 ESP_LOGCONFIG(TAG, " Supported PRESETS:");
1627 for (const auto &entry : this->preset_config_) {
1628 const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(entry.preset));
1629 ESP_LOGCONFIG(TAG, " %s:%s", preset_name, entry.preset == this->default_preset_ ? " (default)" : "");
1630 this->dump_preset_config_(preset_name, entry.config);
1631 }
1632 }
1633
1634 if (!this->custom_preset_config_.empty()) {
1635 ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
1636 for (const auto &entry : this->custom_preset_config_) {
1637 const auto *preset_name = entry.name;
1638 ESP_LOGCONFIG(TAG, " %s:%s", preset_name,
1639 (this->default_custom_preset_ != nullptr && strcmp(entry.name, this->default_custom_preset_) == 0)
1640 ? " (default)"
1641 : "");
1642 this->dump_preset_config_(preset_name, entry.config);
1643 }
1644 }
1645}
1646
1648
1650 : default_temperature(default_temperature) {}
1651
1653 float default_temperature_high)
1654 : default_temperature_low(default_temperature_low), default_temperature_high(default_temperature_high) {}
1655
1656} // namespace thermostat
1657} // namespace esphome
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:169
void stop_action()
Stop any action connected to this trigger.
Definition automation.h:177
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:294
const optional< float > & get_target_humidity() const
Definition climate.cpp:290
const optional< float > & get_target_temperature_low() const
Definition climate.cpp:288
const optional< float > & get_target_temperature() const
Definition climate.cpp:287
const optional< ClimatePreset > & get_preset() const
Definition climate.cpp:295
const char * get_custom_preset() const
Definition climate.h:111
const optional< float > & get_target_temperature_high() const
Definition climate.cpp:289
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:293
const optional< ClimateMode > & get_mode() const
Definition climate.cpp:292
ClimateMode mode
The active mode of the climate device.
Definition climate.h:256
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:250
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:475
float target_temperature
The target temperature of the climate device.
Definition climate.h:237
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:233
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:262
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:240
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:678
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.cpp:680
const char * get_custom_preset() const
Get the active custom preset (read-only access).
Definition climate.h:268
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:227
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:230
ClimateAction action
The active state of the climate device.
Definition climate.h:259
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:426
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:253
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:350
float target_humidity
The target humidity of the climate device.
Definition climate.h:247
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:242
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)
bool has_value() const
Definition optional.h:92
value_type value_or(U const &v) const
Definition optional.h:98
value_type const & value() const
Definition optional.h:94
Base-class for all sensors.
Definition sensor.h:42
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:90
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:116
Trigger * swing_mode_off_trigger_
The trigger to call when the controller should switch the swing mode to "off".
void set_supports_humidification(bool supports_humidification)
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal)
Trigger * idle_action_trigger_
The trigger to call when the controller should switch to idle action/off mode.
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")
Trigger * preset_change_trigger_
The trigger to call when the preset mode changes.
float cooling_deadband_
Hysteresis values used for computing climate actions.
Trigger * heat_action_trigger_
The trigger to call when the controller should switch to heating action/mode.
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)
Trigger * humidity_control_dehumidify_action_trigger_
The trigger to call when dehumidification is required.
bool use_startup_delay_
Used to start "off" delay timers at boot.
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.
Trigger * fan_mode_low_trigger_
The trigger to call when the controller should switch the fan to "low" speed.
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)
HumidificationAction humidification_action_
The current humidification action.
Trigger * fan_mode_auto_trigger_
The trigger to call when the controller should switch the fan to "auto" mode.
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)
Trigger * humidity_control_off_action_trigger_
The trigger to call when (de)humidification should stop.
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.
Trigger * fan_mode_diffuse_trigger_
The trigger to call when the controller should switch the fan to "diffuse" position.
Trigger * fan_only_action_trigger_
The trigger to call when the controller should switch to fan-only action/mode.
Trigger * cool_action_trigger_
The trigger to call when the controller should switch to cooling action/mode.
Trigger * swing_mode_vertical_trigger_
The trigger to call when the controller should switch the swing mode to "vertical".
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)
float prev_target_humidity_
Store previously-known humidity and temperatures.
void check_humidity_change_trigger_()
Check if the humidity change trigger should be called.
Trigger * humidity_control_humidify_action_trigger_
The trigger to call when humidification is required.
void switch_to_supplemental_action_(climate::ClimateAction action)
Trigger * dry_action_trigger_
The trigger to call when the controller should switch to dry (dehumidification) mode.
void switch_to_mode_(climate::ClimateMode mode, bool publish_state=true)
Switch the climate device to the given climate mode.
Trigger * swing_mode_both_trigger_
The trigger to call when the controller should switch the swing mode to "both".
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.
Trigger * swing_mode_horizontal_trigger_
The trigger to call when the controller should switch the swing mode to "horizontal".
Trigger * fan_mode_focus_trigger_
The trigger to call when the controller should switch the fan to "focus" position.
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)
void set_supports_fan_mode_middle(bool supports_fan_mode_middle)
Trigger * temperature_change_trigger_
The trigger to call when the target temperature(s) change(es).
void set_supports_fan_mode_low(bool supports_fan_mode_low)
Trigger * fan_mode_off_trigger_
The trigger to call when the controller should switch off the fan.
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 set_supports_fan_mode_medium(bool supports_fan_mode_medium)
uint32_t timer_duration_(ThermostatClimateTimerIndex timer_index)
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.
Trigger * fan_mode_high_trigger_
The trigger to call when the controller should switch the fan to "high" speed.
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.
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 * humidity_change_trigger_
The trigger to call when the target humidity changes.
void set_supports_fan_with_cooling(bool supports_fan_with_cooling)
Trigger * fan_mode_medium_trigger_
The trigger to call when the controller should switch the fan to "medium" speed.
Trigger * fan_mode_middle_trigger_
The trigger to call when the controller should switch the fan to "middle" position.
void set_supports_fan_mode_focus(bool supports_fan_mode_focus)
void check_temperature_change_trigger_()
Check if the temperature change trigger should be called.
Trigger * fan_mode_on_trigger_
The trigger to call when the controller should switch on the fan.
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.
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)
bool idle_action_ready_()
Is the action ready to be called? Returns true if so.
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 * fan_mode_quiet_trigger_
The trigger to call when the controller should switch the fan to "quiet" position.
Trigger * heat_cool_mode_trigger_
The trigger to call when the controller should switch to heat/cool mode.
std::function< void()> timer_cbf_(ThermostatClimateTimerIndex timer_index)
Trigger * auto_mode_trigger_
The trigger to call when the controller should switch to auto 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:0
@ 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.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:30
Application App
Global storage of Application pointer - only one Application can exist.
optional< climate::ClimateSwingMode > swing_mode_
uint16_t temperature
Definition sun_gtil2.cpp:12