#include "climate.h" #include "esphome/core/macros.h" namespace esphome { namespace climate { static const char *const TAG = "climate"; // Memory-efficient lookup tables struct StringToUint8 { const char *str; const uint8_t value; }; constexpr StringToUint8 CLIMATE_MODES_BY_STR[] = { {"OFF", CLIMATE_MODE_OFF}, {"AUTO", CLIMATE_MODE_AUTO}, {"COOL", CLIMATE_MODE_COOL}, {"HEAT", CLIMATE_MODE_HEAT}, {"FAN_ONLY", CLIMATE_MODE_FAN_ONLY}, {"DRY", CLIMATE_MODE_DRY}, {"HEAT_COOL", CLIMATE_MODE_HEAT_COOL}, }; constexpr StringToUint8 CLIMATE_FAN_MODES_BY_STR[] = { {"ON", CLIMATE_FAN_ON}, {"OFF", CLIMATE_FAN_OFF}, {"AUTO", CLIMATE_FAN_AUTO}, {"LOW", CLIMATE_FAN_LOW}, {"MEDIUM", CLIMATE_FAN_MEDIUM}, {"HIGH", CLIMATE_FAN_HIGH}, {"MIDDLE", CLIMATE_FAN_MIDDLE}, {"FOCUS", CLIMATE_FAN_FOCUS}, {"DIFFUSE", CLIMATE_FAN_DIFFUSE}, {"QUIET", CLIMATE_FAN_QUIET}, }; constexpr StringToUint8 CLIMATE_PRESETS_BY_STR[] = { {"ECO", CLIMATE_PRESET_ECO}, {"AWAY", CLIMATE_PRESET_AWAY}, {"BOOST", CLIMATE_PRESET_BOOST}, {"COMFORT", CLIMATE_PRESET_COMFORT}, {"HOME", CLIMATE_PRESET_HOME}, {"SLEEP", CLIMATE_PRESET_SLEEP}, {"ACTIVITY", CLIMATE_PRESET_ACTIVITY}, {"NONE", CLIMATE_PRESET_NONE}, }; constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[] = { {"OFF", CLIMATE_SWING_OFF}, {"BOTH", CLIMATE_SWING_BOTH}, {"VERTICAL", CLIMATE_SWING_VERTICAL}, {"HORIZONTAL", CLIMATE_SWING_HORIZONTAL}, }; void ClimateCall::perform() { this->parent_->control_callback_.call(*this); ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); if (this->mode_.has_value()) { const LogString *mode_s = climate_mode_to_string(*this->mode_); ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s)); } if (this->custom_fan_mode_.has_value()) { this->fan_mode_.reset(); ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_.value().c_str()); } if (this->fan_mode_.has_value()) { this->custom_fan_mode_.reset(); const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_); ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s)); } if (this->custom_preset_.has_value()) { this->preset_.reset(); ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_.value().c_str()); } if (this->preset_.has_value()) { this->custom_preset_.reset(); const LogString *preset_s = climate_preset_to_string(*this->preset_); ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s)); } if (this->swing_mode_.has_value()) { const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_); ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s)); } if (this->target_temperature_.has_value()) { ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_); } if (this->target_temperature_low_.has_value()) { ESP_LOGD(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_); } if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } if (this->target_humidity_.has_value()) { ESP_LOGD(TAG, " Target Humidity: %.0f", *this->target_humidity_); } this->parent_->control(*this); } void ClimateCall::validate_() { auto traits = this->parent_->get_traits(); if (this->mode_.has_value()) { auto mode = *this->mode_; if (!traits.supports_mode(mode)) { ESP_LOGW(TAG, " Mode %s not supported", LOG_STR_ARG(climate_mode_to_string(mode))); this->mode_.reset(); } } if (this->custom_fan_mode_.has_value()) { auto custom_fan_mode = *this->custom_fan_mode_; if (!traits.supports_custom_fan_mode(custom_fan_mode)) { ESP_LOGW(TAG, " Fan Mode %s not supported", custom_fan_mode.c_str()); this->custom_fan_mode_.reset(); } } else if (this->fan_mode_.has_value()) { auto fan_mode = *this->fan_mode_; if (!traits.supports_fan_mode(fan_mode)) { ESP_LOGW(TAG, " Fan Mode %s not supported", LOG_STR_ARG(climate_fan_mode_to_string(fan_mode))); this->fan_mode_.reset(); } } if (this->custom_preset_.has_value()) { auto custom_preset = *this->custom_preset_; if (!traits.supports_custom_preset(custom_preset)) { ESP_LOGW(TAG, " Preset %s not supported", custom_preset.c_str()); this->custom_preset_.reset(); } } else if (this->preset_.has_value()) { auto preset = *this->preset_; if (!traits.supports_preset(preset)) { ESP_LOGW(TAG, " Preset %s not supported", LOG_STR_ARG(climate_preset_to_string(preset))); this->preset_.reset(); } } if (this->swing_mode_.has_value()) { auto swing_mode = *this->swing_mode_; if (!traits.supports_swing_mode(swing_mode)) { ESP_LOGW(TAG, " Swing Mode %s not supported", LOG_STR_ARG(climate_swing_mode_to_string(swing_mode))); this->swing_mode_.reset(); } } if (this->target_temperature_.has_value()) { auto target = *this->target_temperature_; if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { ESP_LOGW(TAG, " Cannot set target temperature for climate device " "with two-point target temperature"); this->target_temperature_.reset(); } else if (std::isnan(target)) { ESP_LOGW(TAG, " Target temperature must not be NAN"); this->target_temperature_.reset(); } } if (this->target_temperature_low_.has_value() || this->target_temperature_high_.has_value()) { if (!traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { ESP_LOGW(TAG, " Cannot set low/high target temperature"); this->target_temperature_low_.reset(); this->target_temperature_high_.reset(); } } if (this->target_temperature_low_.has_value() && std::isnan(*this->target_temperature_low_)) { ESP_LOGW(TAG, " Target temperature low must not be NAN"); this->target_temperature_low_.reset(); } if (this->target_temperature_high_.has_value() && std::isnan(*this->target_temperature_high_)) { ESP_LOGW(TAG, " Target temperature high must not be NAN"); this->target_temperature_high_.reset(); } if (this->target_temperature_low_.has_value() && this->target_temperature_high_.has_value()) { float low = *this->target_temperature_low_; float high = *this->target_temperature_high_; if (low > high) { ESP_LOGW(TAG, " Target temperature low %.2f must be less than target temperature high %.2f", low, high); this->target_temperature_low_.reset(); this->target_temperature_high_.reset(); } } } ClimateCall &ClimateCall::set_mode(ClimateMode mode) { this->mode_ = mode; return *this; } ClimateCall &ClimateCall::set_mode(const std::string &mode) { for (const auto &mode_entry : CLIMATE_MODES_BY_STR) { if (str_equals_case_insensitive(mode, mode_entry.str)) { this->set_mode(static_cast(mode_entry.value)); return *this; } } ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str()); return *this; } ClimateCall &ClimateCall::set_fan_mode(ClimateFanMode fan_mode) { this->fan_mode_ = fan_mode; this->custom_fan_mode_.reset(); return *this; } ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { for (const auto &mode_entry : CLIMATE_FAN_MODES_BY_STR) { if (str_equals_case_insensitive(fan_mode, mode_entry.str)) { this->set_fan_mode(static_cast(mode_entry.value)); return *this; } } if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; this->fan_mode_.reset(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized fan mode %s", this->parent_->get_name().c_str(), fan_mode.c_str()); } return *this; } ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { if (fan_mode.has_value()) { this->set_fan_mode(fan_mode.value()); } return *this; } ClimateCall &ClimateCall::set_preset(ClimatePreset preset) { this->preset_ = preset; this->custom_preset_.reset(); return *this; } ClimateCall &ClimateCall::set_preset(const std::string &preset) { for (const auto &preset_entry : CLIMATE_PRESETS_BY_STR) { if (str_equals_case_insensitive(preset, preset_entry.str)) { this->set_preset(static_cast(preset_entry.value)); return *this; } } if (this->parent_->get_traits().supports_custom_preset(preset)) { this->custom_preset_ = preset; this->preset_.reset(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized preset %s", this->parent_->get_name().c_str(), preset.c_str()); } return *this; } ClimateCall &ClimateCall::set_preset(optional preset) { if (preset.has_value()) { this->set_preset(preset.value()); } return *this; } ClimateCall &ClimateCall::set_swing_mode(ClimateSwingMode swing_mode) { this->swing_mode_ = swing_mode; return *this; } ClimateCall &ClimateCall::set_swing_mode(const std::string &swing_mode) { for (const auto &mode_entry : CLIMATE_SWING_MODES_BY_STR) { if (str_equals_case_insensitive(swing_mode, mode_entry.str)) { this->set_swing_mode(static_cast(mode_entry.value)); return *this; } } ESP_LOGW(TAG, "'%s' - Unrecognized swing mode %s", this->parent_->get_name().c_str(), swing_mode.c_str()); return *this; } ClimateCall &ClimateCall::set_target_temperature(float target_temperature) { this->target_temperature_ = target_temperature; return *this; } ClimateCall &ClimateCall::set_target_temperature_low(float target_temperature_low) { this->target_temperature_low_ = target_temperature_low; return *this; } ClimateCall &ClimateCall::set_target_temperature_high(float target_temperature_high) { this->target_temperature_high_ = target_temperature_high; return *this; } ClimateCall &ClimateCall::set_target_humidity(float target_humidity) { this->target_humidity_ = target_humidity; return *this; } const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } const optional &ClimateCall::get_target_humidity() const { return this->target_humidity_; } const optional &ClimateCall::get_mode() const { return this->mode_; } const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { this->target_temperature_high_ = target_temperature_high; return *this; } ClimateCall &ClimateCall::set_target_temperature_low(optional target_temperature_low) { this->target_temperature_low_ = target_temperature_low; return *this; } ClimateCall &ClimateCall::set_target_temperature(optional target_temperature) { this->target_temperature_ = target_temperature; return *this; } ClimateCall &ClimateCall::set_target_humidity(optional target_humidity) { this->target_humidity_ = target_humidity; return *this; } ClimateCall &ClimateCall::set_mode(optional mode) { this->mode_ = mode; return *this; } ClimateCall &ClimateCall::set_fan_mode(optional fan_mode) { this->fan_mode_ = fan_mode; this->custom_fan_mode_.reset(); return *this; } ClimateCall &ClimateCall::set_preset(optional preset) { this->preset_ = preset; this->custom_preset_.reset(); return *this; } ClimateCall &ClimateCall::set_swing_mode(optional swing_mode) { this->swing_mode_ = swing_mode; return *this; } void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } void Climate::add_on_control_callback(std::function &&callback) { this->control_callback_.add(std::move(callback)); } // Random 32bit value; If this changes existing restore preferences are invalidated static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; optional Climate::restore_state_() { this->rtc_ = global_preferences->make_preference(this->get_preference_hash() ^ RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; return recovered; } void Climate::save_state_() { #if (defined(USE_ESP_IDF) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \ !defined(CLANG_TIDY) #pragma GCC diagnostic ignored "-Wclass-memaccess" #define TEMP_IGNORE_MEMACCESS #endif ClimateDeviceRestoreState state{}; // initialize as zero to prevent random data on stack triggering erase memset(&state, 0, sizeof(ClimateDeviceRestoreState)); #ifdef TEMP_IGNORE_MEMACCESS #pragma GCC diagnostic pop #undef TEMP_IGNORE_MEMACCESS #endif state.mode = this->mode; auto traits = this->get_traits(); if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { state.target_temperature_low = this->target_temperature_low; state.target_temperature_high = this->target_temperature_high; } else { state.target_temperature = this->target_temperature; } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { state.target_humidity = this->target_humidity; } if (traits.get_supports_fan_modes() && fan_mode.has_value()) { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { state.uses_custom_fan_mode = true; const auto &supported = traits.get_supported_custom_fan_modes(); // std::vector maintains insertion order size_t i = 0; for (const auto &mode : supported) { if (mode == custom_fan_mode) { state.custom_fan_mode = i; break; } i++; } } if (traits.get_supports_presets() && preset.has_value()) { state.uses_custom_preset = false; state.preset = this->preset.value(); } if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { state.uses_custom_preset = true; const auto &supported = traits.get_supported_custom_presets(); // std::vector maintains insertion order size_t i = 0; for (const auto &preset : supported) { if (preset == custom_preset) { state.custom_preset = i; break; } i++; } } if (traits.get_supports_swing_modes()) { state.swing_mode = this->swing_mode; } this->rtc_.save(&state); } void Climate::publish_state() { ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); auto traits = this->get_traits(); ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode))); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action))); } if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) { ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value()))); } if (!traits.get_supported_custom_fan_modes().empty() && this->custom_fan_mode.has_value()) { ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode.value().c_str()); } if (traits.get_supports_presets() && this->preset.has_value()) { ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value()))); } if (!traits.get_supported_custom_presets().empty() && this->custom_preset.has_value()) { ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset.value().c_str()); } if (traits.get_supports_swing_modes()) { ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode))); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature); } if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low, this->target_temperature_high); } else { ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) { ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity); } // Send state to frontend this->state_callback_.call(*this); // Save state this->save_state_(); } ClimateTraits Climate::get_traits() { auto traits = this->traits(); if (this->visual_min_temperature_override_.has_value()) { traits.set_visual_min_temperature(*this->visual_min_temperature_override_); } if (this->visual_max_temperature_override_.has_value()) { traits.set_visual_max_temperature(*this->visual_max_temperature_override_); } if (this->visual_target_temperature_step_override_.has_value()) { traits.set_visual_target_temperature_step(*this->visual_target_temperature_step_override_); traits.set_visual_current_temperature_step(*this->visual_current_temperature_step_override_); } if (this->visual_min_humidity_override_.has_value()) { traits.set_visual_min_humidity(*this->visual_min_humidity_override_); } if (this->visual_max_humidity_override_.has_value()) { traits.set_visual_max_humidity(*this->visual_max_humidity_override_); } return traits; } void Climate::set_visual_min_temperature_override(float visual_min_temperature_override) { this->visual_min_temperature_override_ = visual_min_temperature_override; } void Climate::set_visual_max_temperature_override(float visual_max_temperature_override) { this->visual_max_temperature_override_ = visual_max_temperature_override; } void Climate::set_visual_temperature_step_override(float target, float current) { this->visual_target_temperature_step_override_ = target; this->visual_current_temperature_step_override_ = current; } void Climate::set_visual_min_humidity_override(float visual_min_humidity_override) { this->visual_min_humidity_override_ = visual_min_humidity_override; } void Climate::set_visual_max_humidity_override(float visual_max_humidity_override) { this->visual_max_humidity_override_ = visual_max_humidity_override; } ClimateCall Climate::make_call() { return ClimateCall(this); } ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { auto call = climate->make_call(); auto traits = climate->get_traits(); call.set_mode(this->mode); if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { call.set_target_temperature_low(this->target_temperature_low); call.set_target_temperature_high(this->target_temperature_high); } else { call.set_target_temperature(this->target_temperature); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { call.set_target_humidity(this->target_humidity); } if (this->uses_custom_fan_mode) { if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) { call.fan_mode_.reset(); call.custom_fan_mode_ = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode); } } else if (traits.supports_fan_mode(this->fan_mode)) { call.set_fan_mode(this->fan_mode); } if (this->uses_custom_preset) { if (this->custom_preset < traits.get_supported_custom_presets().size()) { call.preset_.reset(); call.custom_preset_ = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset); } } else if (traits.supports_preset(this->preset)) { call.set_preset(this->preset); } if (traits.supports_swing_mode(this->swing_mode)) { call.set_swing_mode(this->swing_mode); } return call; } void ClimateDeviceRestoreState::apply(Climate *climate) { auto traits = climate->get_traits(); climate->mode = this->mode; if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { climate->target_temperature_low = this->target_temperature_low; climate->target_temperature_high = this->target_temperature_high; } else { climate->target_temperature = this->target_temperature; } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { climate->target_humidity = this->target_humidity; } if (this->uses_custom_fan_mode) { if (this->custom_fan_mode < traits.get_supported_custom_fan_modes().size()) { climate->fan_mode.reset(); climate->custom_fan_mode = *std::next(traits.get_supported_custom_fan_modes().cbegin(), this->custom_fan_mode); } } else if (traits.supports_fan_mode(this->fan_mode)) { climate->fan_mode = this->fan_mode; climate->custom_fan_mode.reset(); } if (this->uses_custom_preset) { if (this->custom_preset < traits.get_supported_custom_presets().size()) { climate->preset.reset(); climate->custom_preset = *std::next(traits.get_supported_custom_presets().cbegin(), this->custom_preset); } } else if (traits.supports_preset(this->preset)) { climate->preset = this->preset; climate->custom_preset.reset(); } if (traits.supports_swing_mode(this->swing_mode)) { climate->swing_mode = this->swing_mode; } climate->publish_state(); } template bool set_alternative(optional &dst, optional &alt, const T1 &src) { bool is_changed = alt.has_value(); alt.reset(); if (is_changed || dst != src) { dst = src; is_changed = true; } return is_changed; } bool Climate::set_fan_mode_(ClimateFanMode mode) { return set_alternative(this->fan_mode, this->custom_fan_mode, mode); } bool Climate::set_custom_fan_mode_(const std::string &mode) { return set_alternative(this->custom_fan_mode, this->fan_mode, mode); } bool Climate::set_preset_(ClimatePreset preset) { return set_alternative(this->preset, this->custom_preset, preset); } bool Climate::set_custom_preset_(const std::string &preset) { return set_alternative(this->custom_preset, this->preset, preset); } void Climate::dump_traits_(const char *tag) { auto traits = this->get_traits(); ESP_LOGCONFIG(tag, "ClimateTraits:"); ESP_LOGCONFIG(tag, " Visual settings:\n" " - Min temperature: %.1f\n" " - Max temperature: %.1f\n" " - Temperature step:\n" " Target: %.1f", traits.get_visual_min_temperature(), traits.get_visual_max_temperature(), traits.get_visual_target_temperature_step()); if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { ESP_LOGCONFIG(tag, " Current: %.1f", traits.get_visual_current_temperature_step()); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY | climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) { ESP_LOGCONFIG(tag, " - Min humidity: %.0f\n" " - Max humidity: %.0f", traits.get_visual_min_humidity(), traits.get_visual_max_humidity()); } if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE | CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) { ESP_LOGCONFIG(tag, " Supports two-point target temperature"); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) { ESP_LOGCONFIG(tag, " Supports current temperature"); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) { ESP_LOGCONFIG(tag, " Supports target humidity"); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) { ESP_LOGCONFIG(tag, " Supports current humidity"); } if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) { ESP_LOGCONFIG(tag, " Supports action"); } if (!traits.get_supported_modes().empty()) { ESP_LOGCONFIG(tag, " Supported modes:"); for (ClimateMode m : traits.get_supported_modes()) ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_mode_to_string(m))); } if (!traits.get_supported_fan_modes().empty()) { ESP_LOGCONFIG(tag, " Supported fan modes:"); for (ClimateFanMode m : traits.get_supported_fan_modes()) ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_fan_mode_to_string(m))); } if (!traits.get_supported_custom_fan_modes().empty()) { ESP_LOGCONFIG(tag, " Supported custom fan modes:"); for (const std::string &s : traits.get_supported_custom_fan_modes()) ESP_LOGCONFIG(tag, " - %s", s.c_str()); } if (!traits.get_supported_presets().empty()) { ESP_LOGCONFIG(tag, " Supported presets:"); for (ClimatePreset p : traits.get_supported_presets()) ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_preset_to_string(p))); } if (!traits.get_supported_custom_presets().empty()) { ESP_LOGCONFIG(tag, " Supported custom presets:"); for (const std::string &s : traits.get_supported_custom_presets()) ESP_LOGCONFIG(tag, " - %s", s.c_str()); } if (!traits.get_supported_swing_modes().empty()) { ESP_LOGCONFIG(tag, " Supported swing modes:"); for (ClimateSwingMode m : traits.get_supported_swing_modes()) ESP_LOGCONFIG(tag, " - %s", LOG_STR_ARG(climate_swing_mode_to_string(m))); } } } // namespace climate } // namespace esphome