ESPHome 2026.10.2
Loading...
Searching...
No Matches
climate.cpp
Go to the documentation of this file.
1#include "climate.h"
5#include <strings.h>
6
7namespace esphome::climate {
8
9static const char *const TAG = "climate";
10
11// Memory-efficient lookup tables
12struct StringToUint8 {
13 const char *str;
14 const uint8_t value;
15};
16
17constexpr StringToUint8 CLIMATE_MODES_BY_STR[] = {
18 {"OFF", CLIMATE_MODE_OFF},
19 {"AUTO", CLIMATE_MODE_AUTO},
20 {"COOL", CLIMATE_MODE_COOL},
21 {"HEAT", CLIMATE_MODE_HEAT},
22 {"FAN_ONLY", CLIMATE_MODE_FAN_ONLY},
23 {"DRY", CLIMATE_MODE_DRY},
24 {"HEAT_COOL", CLIMATE_MODE_HEAT_COOL},
25};
26
27constexpr StringToUint8 CLIMATE_FAN_MODES_BY_STR[] = {
28 {"ON", CLIMATE_FAN_ON}, {"OFF", CLIMATE_FAN_OFF}, {"AUTO", CLIMATE_FAN_AUTO},
29 {"LOW", CLIMATE_FAN_LOW}, {"MEDIUM", CLIMATE_FAN_MEDIUM}, {"HIGH", CLIMATE_FAN_HIGH},
30 {"MIDDLE", CLIMATE_FAN_MIDDLE}, {"FOCUS", CLIMATE_FAN_FOCUS}, {"DIFFUSE", CLIMATE_FAN_DIFFUSE},
31 {"QUIET", CLIMATE_FAN_QUIET},
32};
33
34constexpr StringToUint8 CLIMATE_PRESETS_BY_STR[] = {
35 {"ECO", CLIMATE_PRESET_ECO}, {"AWAY", CLIMATE_PRESET_AWAY}, {"BOOST", CLIMATE_PRESET_BOOST},
36 {"COMFORT", CLIMATE_PRESET_COMFORT}, {"HOME", CLIMATE_PRESET_HOME}, {"SLEEP", CLIMATE_PRESET_SLEEP},
37 {"ACTIVITY", CLIMATE_PRESET_ACTIVITY}, {"NONE", CLIMATE_PRESET_NONE},
38};
39
40constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[] = {
41 {"OFF", CLIMATE_SWING_OFF},
42 {"BOTH", CLIMATE_SWING_BOTH},
43 {"VERTICAL", CLIMATE_SWING_VERTICAL},
44 {"HORIZONTAL", CLIMATE_SWING_HORIZONTAL},
45};
46
48 this->parent_->control_callback_.call(*this);
49 ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
50 this->validate_();
51 if (this->mode_.has_value()) {
52 const LogString *mode_s = climate_mode_to_string(*this->mode_);
53 ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
54 }
55 if (this->custom_fan_mode_ != nullptr) {
56 this->fan_mode_.reset();
57 ESP_LOGV(TAG, " Custom Fan: %s", this->custom_fan_mode_);
58 }
59 if (this->fan_mode_.has_value()) {
60 this->custom_fan_mode_ = nullptr;
61 const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
62 ESP_LOGV(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
63 }
64 if (this->custom_preset_ != nullptr) {
65 this->preset_.reset();
66 ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_);
67 }
68 if (this->preset_.has_value()) {
69 this->custom_preset_ = nullptr;
70 const LogString *preset_s = climate_preset_to_string(*this->preset_);
71 ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
72 }
73 if (this->swing_mode_.has_value()) {
74 const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
75 ESP_LOGV(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s));
76 }
77 if (this->target_temperature_.has_value()) {
78 ESP_LOGV(TAG, " Target Temperature: %.2f", *this->target_temperature_);
79 }
80 if (this->target_temperature_low_.has_value()) {
81 ESP_LOGV(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_);
82 }
83 if (this->target_temperature_high_.has_value()) {
84 ESP_LOGV(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
85 }
86 if (this->target_humidity_.has_value()) {
87 ESP_LOGV(TAG, " Target Humidity: %.0f", *this->target_humidity_);
88 }
89 this->parent_->control(*this);
90}
91
93 auto traits = this->parent_->get_traits();
94 if (this->mode_.has_value()) {
95 auto mode = *this->mode_;
96 if (!traits.supports_mode(mode)) {
97 ESP_LOGW(TAG, " Mode %s not supported", LOG_STR_ARG(climate_mode_to_string(mode)));
98 this->mode_.reset();
99 }
100 }
101 if (this->custom_fan_mode_ != nullptr) {
102 if (!traits.supports_custom_fan_mode(this->custom_fan_mode_)) {
103 ESP_LOGW(TAG, " Fan Mode %s not supported", this->custom_fan_mode_);
104 this->custom_fan_mode_ = nullptr;
105 }
106 } else if (this->fan_mode_.has_value()) {
107 auto fan_mode = *this->fan_mode_;
108 if (!traits.supports_fan_mode(fan_mode)) {
109 ESP_LOGW(TAG, " Fan Mode %s not supported", LOG_STR_ARG(climate_fan_mode_to_string(fan_mode)));
110 this->fan_mode_.reset();
111 }
112 }
113 if (this->custom_preset_ != nullptr) {
114 if (!traits.supports_custom_preset(this->custom_preset_)) {
115 ESP_LOGW(TAG, " Preset %s not supported", this->custom_preset_);
116 this->custom_preset_ = nullptr;
117 }
118 } else if (this->preset_.has_value()) {
119 auto preset = *this->preset_;
120 if (!traits.supports_preset(preset)) {
121 ESP_LOGW(TAG, " Preset %s not supported", LOG_STR_ARG(climate_preset_to_string(preset)));
122 this->preset_.reset();
123 }
124 }
125 if (this->swing_mode_.has_value()) {
126 auto swing_mode = *this->swing_mode_;
127 if (!traits.supports_swing_mode(swing_mode)) {
128 ESP_LOGW(TAG, " Swing Mode %s not supported", LOG_STR_ARG(climate_swing_mode_to_string(swing_mode)));
129 this->swing_mode_.reset();
130 }
131 }
132 if (this->target_temperature_.has_value()) {
133 auto target = *this->target_temperature_;
134 if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
136 ESP_LOGW(TAG, " Cannot set target temperature for climate device "
137 "with two-point target temperature");
138 this->target_temperature_.reset();
139 } else if (std::isnan(target)) {
140 ESP_LOGW(TAG, " Target temperature must not be NAN");
141 this->target_temperature_.reset();
142 }
143 }
144 if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) {
145 if (!traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
147 ESP_LOGW(TAG, " Cannot set low/high target temperature");
148 this->target_temperature_low_.reset();
149 this->target_temperature_high_.reset();
150 }
151 }
152 if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) {
153 ESP_LOGW(TAG, " Target temperature low must not be NAN");
154 this->target_temperature_low_.reset();
155 }
156 if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) {
157 ESP_LOGW(TAG, " Target temperature high must not be NAN");
158 this->target_temperature_high_.reset();
159 }
160 if (this->target_temperature_low_.has_value() && this->target_temperature_high_.has_value()) {
161 float low = *this->target_temperature_low_;
162 float high = *this->target_temperature_high_;
163 if (low > high) {
164 ESP_LOGW(TAG, " Target temperature low %.2f must be less than target temperature high %.2f", low, high);
165 this->target_temperature_low_.reset();
166 this->target_temperature_high_.reset();
167 }
168 }
169}
170
172 this->mode_ = mode;
173 return *this;
174}
175
176ClimateCall &ClimateCall::set_mode(const std::string &mode) { return this->set_mode(mode.c_str(), mode.size()); }
177
179 StringRef mode_ref(mode, len);
180 for (const auto &mode_entry : CLIMATE_MODES_BY_STR) {
181 if (str_equals_case_insensitive(mode_ref, mode_entry.str)) {
182 this->set_mode(static_cast<ClimateMode>(mode_entry.value));
183 return *this;
184 }
185 }
186 ESP_LOGW(TAG, "'%s' - Unrecognized mode %.*s", this->parent_->get_name().c_str(), (int) len, mode);
187 return *this;
188}
189
191 this->fan_mode_ = fan_mode;
192 this->custom_fan_mode_ = nullptr;
193 return *this;
194}
195
197 return this->set_fan_mode(custom_fan_mode, strlen(custom_fan_mode));
198}
199
201 return this->set_fan_mode(fan_mode.data(), fan_mode.size());
202}
203
205 // Check if it's a standard enum mode first
206 for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) {
207 if (strncasecmp(custom_fan_mode, mode_entry.str, len) == 0 && mode_entry.str[len] == '\0') {
208 return this->set_fan_mode(static_cast<ClimateFanMode>(mode_entry.value));
209 }
210 }
211 // Find the matching pointer from parent climate device
212 if (const char *mode_ptr = this->parent_->find_custom_fan_mode_(custom_fan_mode, len)) {
213 this->custom_fan_mode_ = mode_ptr;
214 this->fan_mode_.reset();
215 return *this;
216 }
217 ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %.*s", this->parent_->get_name().c_str(), (int) len, custom_fan_mode);
218 return *this;
219}
220
222 if (fan_mode.has_value()) {
223 this->set_fan_mode(fan_mode.value());
224 }
225 return *this;
226}
227
229 this->preset_ = preset;
230 this->custom_preset_ = nullptr;
231 return *this;
232}
233
235 return this->set_preset(custom_preset, strlen(custom_preset));
236}
237
239 return this->set_preset(preset.data(), preset.size());
240}
241
243 // Check if it's a standard enum preset first
244 for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) {
245 if (strncasecmp(custom_preset, preset_entry.str, len) == 0 && preset_entry.str[len] == '\0') {
246 return this->set_preset(static_cast<ClimatePreset>(preset_entry.value));
247 }
248 }
249 // Find the matching pointer from parent climate device
250 if (const char *preset_ptr = this->parent_->find_custom_preset_(custom_preset, len)) {
251 this->custom_preset_ = preset_ptr;
252 this->preset_.reset();
253 return *this;
254 }
255 ESP_LOGW(TAG, "'%s' - Unrecognized preset %.*s", this->parent_->get_name().c_str(), (int) len, custom_preset);
256 return *this;
257}
258
260 if (preset.has_value()) {
261 this->set_preset(preset.value());
262 }
263 return *this;
264}
265
270
272 return this->set_swing_mode(swing_mode.c_str(), swing_mode.size());
273}
274
276 StringRef mode_ref(swing_mode, len);
277 for (const auto &mode_entry : CLIMATE_SWING_MODES_BY_STR) {
278 if (str_equals_case_insensitive(mode_ref, mode_entry.str)) {
279 this->set_swing_mode(static_cast<ClimateSwingMode>(mode_entry.value));
280 return *this;
281 }
282 }
283 ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %.*s", this->parent_->get_name().c_str(), (int) len, swing_mode);
284 return *this;
285}
286
291
296
301
306
307const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
308const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
309const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
310const optional<float> &ClimateCall::get_target_humidity() const { return this->target_humidity_; }
311
312const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_; }
313const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
314const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
315const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
316
321
326
331
336
337ClimateCall &ClimateCall::set_mode(optional<ClimateMode> mode) {
338 this->mode_ = mode;
339 return *this;
340}
341
343 this->fan_mode_ = fan_mode;
344 this->custom_fan_mode_ = nullptr;
345 return *this;
346}
347
348ClimateCall &ClimateCall::set_preset(optional<ClimatePreset> preset) {
349 this->preset_ = preset;
350 this->custom_preset_ = nullptr;
351 return *this;
352}
353
355 this->swing_mode_ = swing_mode;
356 return *this;
357}
358
359// Random 32bit value; If this changes existing restore preferences are invalidated
360static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
361
362optional<ClimateDeviceRestoreState> Climate::restore_state_() {
363 this->rtc_ = this->make_entity_preference<ClimateDeviceRestoreState>(RESTORE_STATE_VERSION);
364 ClimateDeviceRestoreState recovered{};
365 if (!this->rtc_.load(&recovered))
366 return {};
367 return recovered;
368}
369
372 // initialize as zero (including padding) to prevent random data on stack triggering erase;
373 // the void * cast tells GCC the bytewise clear of a non-trivial type is intentional
374 // NOLINTNEXTLINE(bugprone-raw-memory-call-on-non-trivial-type) -- intentional bytewise zero for RTC save
375 memset(static_cast<void *>(&state), 0, sizeof(ClimateDeviceRestoreState));
376
377 state.mode = this->mode;
380 state.target_temperature_low = this->target_temperature_low;
381 state.target_temperature_high = this->target_temperature_high;
382 } else {
383 state.target_temperature = this->target_temperature;
384 }
386 state.target_humidity = this->target_humidity;
387 }
388 if (traits.get_supports_fan_modes() && fan_mode.has_value()) {
389 state.uses_custom_fan_mode = false;
390 state.fan_mode = this->fan_mode.value();
391 }
392 if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
393 state.uses_custom_fan_mode = true;
394 const auto &supported = traits.get_supported_custom_fan_modes();
395 // std::vector maintains insertion order
396 size_t i = 0;
397 for (const char *mode : supported) {
398 if (strcmp(mode, this->custom_fan_mode_) == 0) {
399 state.custom_fan_mode = i;
400 break;
401 }
402 i++;
403 }
404 }
405 if (traits.get_supports_presets() && preset.has_value()) {
406 state.uses_custom_preset = false;
407 state.preset = this->preset.value();
408 }
409 if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
410 state.uses_custom_preset = true;
411 const auto &supported = traits.get_supported_custom_presets();
412 // std::vector maintains insertion order
413 size_t i = 0;
414 for (const char *preset : supported) {
415 if (strcmp(preset, this->custom_preset_) == 0) {
416 state.custom_preset = i;
417 break;
418 }
419 i++;
420 }
421 }
423 state.swing_mode = this->swing_mode;
424 }
425
426 this->rtc_.save(&state);
427}
428
430 this->set_has_state(true);
431 ESP_LOGV(TAG, "'%s' >>", this->name_.c_str());
432 auto traits = this->get_traits();
433
434 ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
436 ESP_LOGV(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
437 }
438 if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
439 ESP_LOGV(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
440 }
441 if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
442 ESP_LOGV(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_);
443 }
444 if (traits.get_supports_presets() && this->preset.has_value()) {
445 ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
446 }
447 if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
448 ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_);
449 }
451 ESP_LOGV(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
452 }
454 ESP_LOGV(TAG, " Current Temperature: %.2f°C", this->current_temperature);
455 }
458 ESP_LOGV(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
460 } else {
461 ESP_LOGV(TAG, " Target Temperature: %.2f°C", this->target_temperature);
462 }
464 ESP_LOGV(TAG, " Current Humidity: %.0f%%", this->current_humidity);
465 }
467 ESP_LOGV(TAG, " Target Humidity: %.0f%%", this->target_humidity);
468 }
469
470 // Send state to frontend
471 this->state_callback_.call(*this);
472#if defined(USE_CLIMATE) && defined(USE_CONTROLLER_REGISTRY)
473 ControllerRegistry::notify_climate_update(this);
474#endif
475 // Save state
476 this->save_state_(traits);
477}
478
480 auto traits = this->traits();
481 // Wire custom mode pointers from Climate-owned storage
482 if (this->supported_custom_fan_modes_)
483 traits.set_supported_custom_fan_modes_(this->supported_custom_fan_modes_);
484 if (this->supported_custom_presets_)
485 traits.set_supported_custom_presets_(this->supported_custom_presets_);
486#ifdef USE_CLIMATE_VISUAL_OVERRIDES
487 if (!std::isnan(this->visual_min_temperature_override_)) {
489 }
490 if (!std::isnan(this->visual_max_temperature_override_)) {
492 }
493 if (!std::isnan(this->visual_target_temperature_step_override_)) {
496 }
497 if (!std::isnan(this->visual_min_humidity_override_)) {
499 }
500 if (!std::isnan(this->visual_max_humidity_override_)) {
502 }
503#endif
504 return traits;
505}
506
508
510 auto call = climate->make_call();
511 auto traits = climate->get_traits();
512 call.set_mode(this->mode);
513 if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
515 call.set_target_temperature_low(this->target_temperature_low);
516 call.set_target_temperature_high(this->target_temperature_high);
517 } else {
518 call.set_target_temperature(this->target_temperature);
519 }
520 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
521 call.set_target_humidity(this->target_humidity);
522 }
523 if (this->uses_custom_fan_mode) {
524 if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
525 call.fan_mode_.reset();
526 call.custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
527 }
528 } else if (traits.supports_fan_mode(this->fan_mode)) {
529 call.set_fan_mode(this->fan_mode);
530 }
531 if (this->uses_custom_preset) {
532 if (this->custom_preset < traits.get_supported_custom_presets().size()) {
533 call.preset_.reset();
534 call.custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
535 }
536 } else if (traits.supports_preset(this->preset)) {
537 call.set_preset(this->preset);
538 }
539 if (traits.supports_swing_mode(this->swing_mode)) {
540 call.set_swing_mode(this->swing_mode);
541 }
542 return call;
543}
544
546 auto traits = climate->get_traits();
547 // A saved mode the device no longer offers cannot be selected again, so skip it and leave the
548 // entity on the mode it already has. The other saved fields are still restored.
549 if (traits.supports_mode(this->mode)) {
550 climate->mode = this->mode;
551 } else {
552 ESP_LOGW(TAG, "'%s' - Saved mode %s is no longer supported, keeping %s", climate->get_name().c_str(),
553 LOG_STR_ARG(climate_mode_to_string(this->mode)), LOG_STR_ARG(climate_mode_to_string(climate->mode)));
554 }
555 if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
559 } else {
560 climate->target_temperature = this->target_temperature;
561 }
562 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
563 climate->target_humidity = this->target_humidity;
564 }
565 if (this->uses_custom_fan_mode) {
566 if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) {
567 climate->fan_mode.reset();
568 climate->custom_fan_mode_ = traits.get_supported_custom_fan_modes()[this->custom_fan_mode];
569 }
570 } else if (traits.supports_fan_mode(this->fan_mode)) {
571 climate->fan_mode = this->fan_mode;
572 climate->clear_custom_fan_mode_();
573 }
574 if (this->uses_custom_preset) {
575 if (this->custom_preset < traits.get_supported_custom_presets().size()) {
576 climate->preset.reset();
577 climate->custom_preset_ = traits.get_supported_custom_presets()[this->custom_preset];
578 }
579 } else if (traits.supports_preset(this->preset)) {
580 climate->preset = this->preset;
581 climate->clear_custom_preset_();
582 }
583 if (traits.supports_swing_mode(this->swing_mode)) {
584 climate->swing_mode = this->swing_mode;
585 }
586 climate->publish_state();
587}
588
608template<typename T> bool set_primary_mode(optional<T> &primary, const char *&custom_ptr, T value) {
609 // Clear the custom mode (mutual exclusion)
610 bool changed = custom_ptr != nullptr;
611 custom_ptr = nullptr;
612 // Set the primary mode
613 if (changed || !primary.has_value() || primary.value() != value) {
614 primary = value;
615 return true;
616 }
617 return false;
618}
619
641template<typename T>
642bool set_custom_mode(const char *&custom_ptr, optional<T> &primary, const char *found_ptr, bool has_custom) {
643 if (found_ptr != nullptr) {
644 // Clear the primary mode (mutual exclusion)
645 bool changed = primary.has_value();
646 primary.reset();
647 // Set the custom mode (pointer is validated by caller from traits)
648 if (changed || custom_ptr != found_ptr) {
649 custom_ptr = found_ptr;
650 return true;
651 }
652 return false;
653 }
654 // Mode not found in supported modes, clear it if currently set
655 if (has_custom) {
656 custom_ptr = nullptr;
657 return true;
658 }
659 return false;
660}
661
663 return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
664}
665
666bool Climate::set_custom_fan_mode_(const char *mode, size_t len) {
667 return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, this->find_custom_fan_mode_(mode, len),
668 this->has_custom_fan_mode());
669}
670
671void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
672
673bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
674
675bool Climate::set_custom_preset_(const char *preset, size_t len) {
676 return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, this->find_custom_preset_(preset, len),
677 this->has_custom_preset());
678}
679
680void Climate::clear_custom_preset_() { this->custom_preset_ = nullptr; }
681
683 return this->find_custom_fan_mode_(custom_fan_mode, strlen(custom_fan_mode));
684}
685
686const char *Climate::find_custom_fan_mode_(const char *custom_fan_mode, size_t len) {
687 if (this->supported_custom_fan_modes_) {
688 return vector_find(*this->supported_custom_fan_modes_, custom_fan_mode, len);
689 }
690 // Fallback for deprecated path: external components may set modes on ClimateTraits directly
691 return this->get_traits().find_custom_fan_mode_(custom_fan_mode, len);
692}
693
695 return this->find_custom_preset_(custom_preset, strlen(custom_preset));
696}
697
698const char *Climate::find_custom_preset_(const char *custom_preset, size_t len) {
699 if (this->supported_custom_presets_) {
700 return vector_find(*this->supported_custom_presets_, custom_preset, len);
701 }
702 // Fallback for deprecated path: external components may set modes on ClimateTraits directly
703 return this->get_traits().find_custom_preset_(custom_preset, len);
704}
705
706void Climate::dump_traits_(const char *tag) {
707 auto traits = this->get_traits();
708 ESP_LOGCONFIG(tag, "ClimateTraits:");
709 ESP_LOGCONFIG(tag,
710 " Visual settings:\n"
711 " - Min temperature: %.1f\n"
712 " - Max temperature: %.1f\n"
713 " - Temperature step:\n"
714 " Target: %.1f",
718 ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step());
719 }
722 ESP_LOGCONFIG(tag,
723 " - Min humidity: %.0f\n"
724 " - Max humidity: %.0f",
726 }
729 ESP_LOGCONFIG(tag, " Supports two-point target temperature");
730 }
732 ESP_LOGCONFIG(tag, " Supports current temperature");
733 }
735 ESP_LOGCONFIG(tag, " Supports target humidity");
736 }
738 ESP_LOGCONFIG(tag, " Supports current humidity");
739 }
741 ESP_LOGCONFIG(tag, " Supports action");
742 }
744 ESP_LOGCONFIG(tag, " Supported modes:");
746 ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m)));
747 }
748 }
750 ESP_LOGCONFIG(tag, " Supported fan modes:");
752 ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m)));
753 }
754 }
755 if (!traits.get_supported_custom_fan_modes().empty()) {
756 ESP_LOGCONFIG(tag, " Supported custom fan modes:");
757 for (const char *s : traits.get_supported_custom_fan_modes()) {
758 ESP_LOGCONFIG(tag, " - %s", s);
759 }
760 }
762 ESP_LOGCONFIG(tag, " Supported presets:");
764 ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p)));
765 }
766 }
767 if (!traits.get_supported_custom_presets().empty()) {
768 ESP_LOGCONFIG(tag, " Supported custom presets:");
769 for (const char *s : traits.get_supported_custom_presets()) {
770 ESP_LOGCONFIG(tag, " - %s", s);
771 }
772 }
774 ESP_LOGCONFIG(tag, " Supported swing modes:");
776 ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m)));
777 }
778 }
779}
780
781} // namespace esphome::climate
BedjetMode mode
BedJet operating mode.
uint8_t m
Definition bl0906.h:1
const StringRef & get_name() const
Definition entity_base.h:71
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
void set_has_state(bool state)
constexpr bool empty() const
Check if the set is empty.
StringRef is a reference to a string owned by something else.
Definition string_ref.h:30
constexpr const char * c_str() const
Definition string_ref.h:77
std::string str() const
Definition string_ref.h:99
This class is used to encode all control actions on a climate device.
Definition climate.h:34
const optional< ClimateSwingMode > & get_swing_mode() const
Definition climate.cpp:314
optional< float > target_temperature_high_
Definition climate.h:138
const optional< float > & get_target_humidity() const
Definition climate.cpp:310
ClimateCall & set_target_temperature(float target_temperature)
Set the target temperature of the climate device.
Definition climate.cpp:287
const optional< float > & get_target_temperature_low() const
Definition climate.cpp:308
ClimateCall & set_swing_mode(ClimateSwingMode swing_mode)
Set the swing mode of the climate device.
Definition climate.cpp:266
optional< ClimateFanMode > fan_mode_
Definition climate.h:141
ClimateCall & set_target_temperature_low(float target_temperature_low)
Set the low point target temperature of the climate device.
Definition climate.cpp:292
optional< float > target_temperature_
Definition climate.h:136
const optional< float > & get_target_temperature() const
Definition climate.cpp:307
const optional< ClimatePreset > & get_preset() const
Definition climate.cpp:315
optional< ClimateSwingMode > swing_mode_
Definition climate.h:142
optional< ClimateMode > mode_
Definition climate.h:140
ClimateCall & set_preset(ClimatePreset preset)
Set the preset of the climate device.
Definition climate.cpp:228
const optional< float > & get_target_temperature_high() const
Definition climate.cpp:309
const optional< ClimateFanMode > & get_fan_mode() const
Definition climate.cpp:313
optional< float > target_humidity_
Definition climate.h:139
ClimateCall & set_fan_mode(ClimateFanMode fan_mode)
Set the fan mode of the climate device.
Definition climate.cpp:190
optional< ClimatePreset > preset_
Definition climate.h:143
ClimateCall & set_target_humidity(float target_humidity)
Set the target humidity of the climate device.
Definition climate.cpp:302
optional< float > target_temperature_low_
Definition climate.h:137
ClimateCall & set_target_temperature_high(float target_temperature_high)
Set the high point target temperature of the climate device.
Definition climate.cpp:297
ClimateCall & set_mode(ClimateMode mode)
Set the mode of the climate device.
Definition climate.cpp:171
const optional< ClimateMode > & get_mode() const
Definition climate.cpp:312
ClimateDevice - This is the base class for all climate integrations.
Definition climate.h:195
ClimateMode mode
The active mode of the climate device.
Definition climate.h:312
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:306
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:479
float target_temperature
The target temperature of the climate device.
Definition climate.h:293
float current_humidity
The current humidity of the climate device, as reported from the integration.
Definition climate.h:289
float visual_min_humidity_override_
Definition climate.h:394
float visual_target_temperature_step_override_
Definition climate.h:392
void dump_traits_(const char *tag)
Definition climate.cpp:706
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:318
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:296
float visual_current_temperature_step_override_
Definition climate.h:393
virtual ClimateTraits traits()=0
Get the default traits of this climate device.
bool set_preset_(ClimatePreset preset)
Set preset. Reset custom preset. Return true if preset has been changed.
Definition climate.cpp:673
bool set_custom_preset_(const char *preset)
Set custom preset. Reset primary preset. Return true if preset has been changed.
Definition climate.h:344
const char * find_custom_fan_mode_(const char *custom_fan_mode)
Find and return the matching custom fan mode pointer from traits, or nullptr if not found.
Definition climate.cpp:682
float visual_min_temperature_override_
Definition climate.h:390
void clear_custom_preset_()
Clear custom preset.
Definition climate.cpp:680
bool set_fan_mode_(ClimateFanMode mode)
Set fan mode. Reset custom fan mode. Return true if fan mode has been changed.
Definition climate.cpp:662
LazyCallbackManager< void(Climate &)> state_callback_
Definition climate.h:385
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:283
const char * find_custom_preset_(const char *custom_preset)
Find and return the matching custom preset pointer from traits, or nullptr if not found.
Definition climate.cpp:694
void clear_custom_fan_mode_()
Clear custom fan mode.
Definition climate.cpp:671
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:286
float visual_max_humidity_override_
Definition climate.h:395
ClimateAction action
The active state of the climate device.
Definition climate.h:315
LazyCallbackManager< void(ClimateCall &)> control_callback_
Definition climate.h:386
ClimateCall make_call()
Make a climate device control call, this is used to control the climate device, see the ClimateCall d...
Definition climate.cpp:507
float visual_max_temperature_override_
Definition climate.h:391
virtual void control(const ClimateCall &call)=0
Control the climate device, this is a virtual method that each climate integration must implement.
void publish_state()
Publish the state of the climate device, to be called from integrations.
Definition climate.cpp:429
bool has_custom_fan_mode() const
Check if a custom fan mode is currently active.
Definition climate.h:280
ESPPreferenceObject rtc_
Definition climate.h:387
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:309
optional< ClimateDeviceRestoreState > restore_state_()
Restore the state of the climate device, call this from your setup() method.
Definition climate.cpp:362
float target_humidity
The target humidity of the climate device.
Definition climate.h:303
bool set_custom_fan_mode_(const char *mode)
Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
Definition climate.h:334
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:298
void set_visual_max_temperature(float visual_max_temperature)
const ClimatePresetMask & get_supported_presets() const
const std::vector< const char * > & get_supported_custom_fan_modes() const
const ClimateSwingModeMask & get_supported_swing_modes() const
void set_visual_target_temperature_step(float temperature_step)
float get_visual_current_temperature_step() const
void set_visual_min_temperature(float visual_min_temperature)
const ClimateFanModeMask & get_supported_fan_modes() const
float get_visual_target_temperature_step() const
void set_supported_custom_presets_(const std::vector< const char * > *presets)
void set_visual_min_humidity(float visual_min_humidity)
void set_visual_current_temperature_step(float temperature_step)
void set_supported_custom_fan_modes_(const std::vector< const char * > *modes)
Set custom mode pointers (only Climate::get_traits() should call these).
bool has_feature_flags(uint32_t feature_flags) const
void set_visual_max_humidity(float visual_max_humidity)
const char * find_custom_fan_mode_(const char *custom_fan_mode) const
Find and return the matching custom fan mode pointer from supported modes, or nullptr if not found Th...
const char * find_custom_preset_(const char *custom_preset) const
Find and return the matching custom preset pointer from supported presets, or nullptr if not found Th...
const std::vector< const char * > & get_supported_custom_presets() const
const ClimateModeMask & get_supported_modes() const
float target_temperature_high
Definition climate.h:3
float target_humidity
Definition climate.h:19
ClimateSwingMode swing_mode
Definition climate.h:11
float target_temperature
Definition climate.h:0
uint8_t custom_preset
Definition climate.h:9
ClimateFanMode fan_mode
Definition climate.h:3
ClimatePreset preset
Definition climate.h:8
float target_temperature_low
Definition climate.h:2
uint8_t custom_fan_mode
Definition climate.h:4
bool state
Definition fan.h:2
const LogString * climate_action_to_string(ClimateAction action)
Convert the given ClimateAction to a human-readable string.
constexpr StringToUint8 CLIMATE_MODES_BY_STR[]
Definition climate.cpp:17
@ CLIMATE_SUPPORTS_CURRENT_HUMIDITY
@ CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE
@ CLIMATE_SUPPORTS_CURRENT_TEMPERATURE
@ CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE
const LogString * climate_swing_mode_to_string(ClimateSwingMode swing_mode)
Convert the given ClimateSwingMode to a human-readable string.
constexpr StringToUint8 CLIMATE_PRESETS_BY_STR[]
Definition climate.cpp:34
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.
@ CLIMATE_PRESET_COMFORT
Device is in comfort preset.
@ CLIMATE_PRESET_AWAY
Device is in away preset.
@ CLIMATE_PRESET_BOOST
Device is in boost preset.
@ CLIMATE_PRESET_ACTIVITY
Device is reacting to activity (e.g., movement sensors)
@ CLIMATE_PRESET_SLEEP
Device is prepared for sleep.
@ CLIMATE_PRESET_HOME
Device is in home preset.
@ CLIMATE_PRESET_ECO
Device is running an energy-saving preset.
const LogString * climate_fan_mode_to_string(ClimateFanMode fan_mode)
Convert the given ClimateFanMode to a human-readable string.
constexpr StringToUint8 CLIMATE_FAN_MODES_BY_STR[]
Definition climate.cpp:27
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.
bool set_primary_mode(optional< T > &primary, const char *&custom_ptr, T value)
Template helper for setting primary modes (fan_mode, preset) with mutual exclusion.
Definition climate.cpp:608
constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[]
Definition climate.cpp:40
bool set_custom_mode(const char *&custom_ptr, optional< T > &primary, const char *found_ptr, bool has_custom)
Template helper for setting custom modes (custom_fan_mode_, custom_preset_) with mutual exclusion.
Definition climate.cpp:642
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
@ CLIMATE_FAN_MEDIUM
The fan mode is set to Medium.
@ CLIMATE_FAN_DIFFUSE
The fan mode is set to Diffuse.
@ CLIMATE_FAN_ON
The fan mode is set to On.
@ CLIMATE_FAN_AUTO
The fan mode is set to Auto.
@ CLIMATE_FAN_FOCUS
The fan mode is set to Focus.
@ CLIMATE_FAN_LOW
The fan mode is set to Low.
@ CLIMATE_FAN_MIDDLE
The fan mode is set to Middle.
@ CLIMATE_FAN_QUIET
The fan mode is set to Quiet.
@ CLIMATE_FAN_OFF
The fan mode is set to Off.
@ CLIMATE_FAN_HIGH
The fan mode is set to High.
const char * vector_find(const std::vector< const char * > &vec, const char *value, size_t len)
const void size_t len
Definition hal.h:64
bool str_equals_case_insensitive(const std::string &a, const std::string &b)
Compare strings for equality in case-insensitive manner.
Definition helpers.cpp:209
static void uint32_t
Struct used to save the state of the climate device in restore memory.
Definition climate.h:152
ClimateCall to_call(Climate *climate)
Convert this struct to a climate call that can be performed.
Definition climate.cpp:509
void apply(Climate *climate)
Apply these settings to the climate device.
Definition climate.cpp:545