From a210c27d141ea411e3a6bc2a776cff88799cefc5 Mon Sep 17 00:00:00 2001 From: Johan Henkens Date: Thu, 23 Jul 2026 16:58:39 -0700 Subject: [PATCH] [api][climate][water_heater][core] Add temperature unit support to climate and water heater c++ entities (#16477) Co-authored-by: Claude Sonnet 4.6 --- esphome/components/api/api_connection.cpp | 2 ++ esphome/components/climate/__init__.py | 2 +- esphome/components/climate/climate_traits.h | 4 ++++ esphome/components/water_heater/__init__.py | 2 +- esphome/components/water_heater/water_heater.h | 4 ++++ esphome/core/helpers.h | 6 ++++++ 6 files changed, 18 insertions(+), 2 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1f7f59128a..c61daf539a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -794,6 +794,7 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection msg.supports_action = traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION); // Current feature flags and other supported parameters msg.feature_flags = traits.get_feature_flags(); + msg.temperature_unit = static_cast(traits.get_temperature_unit()); msg.supported_modes = &traits.get_supported_modes(); msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); @@ -1468,6 +1469,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec msg.target_temperature_step = traits.get_target_temperature_step(); msg.supported_modes = &traits.get_supported_modes(); msg.supported_features = traits.get_feature_flags(); + msg.temperature_unit = static_cast(traits.get_temperature_unit()); return fill_and_encode_entity_info(wh, msg, conn, remaining_size); } diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index fc1b0f368e..fe050fca22 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -274,7 +274,7 @@ def climate_schema( @setup_entity("climate") async def setup_climate_core_(var, config): - visual = config[CONF_VISUAL] + visual = config.get(CONF_VISUAL, {}) if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: cg.add_define("USE_CLIMATE_VISUAL_OVERRIDES") cg.add(var.set_visual_min_temperature_override(min_temp)) diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 599894c8a9..6c776e0228 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -205,6 +205,9 @@ class ClimateTraits { float get_visual_max_humidity() const { return this->visual_max_humidity_; } void set_visual_max_humidity(float visual_max_humidity) { this->visual_max_humidity_ = visual_max_humidity; } + TemperatureUnit get_temperature_unit() const { return this->temperature_unit_; } + void set_temperature_unit(TemperatureUnit unit) { this->temperature_unit_ = unit; } + protected: void set_mode_support_(climate::ClimateMode mode, bool supported) { if (supported) { @@ -274,6 +277,7 @@ class ClimateTraits { climate::ClimateFanModeMask supported_fan_modes_; climate::ClimateSwingModeMask supported_swing_modes_; climate::ClimatePresetMask supported_presets_; + TemperatureUnit temperature_unit_{TemperatureUnit::CELSIUS}; /** Custom mode storage - pointers to vectors owned by the Climate base class. * diff --git a/esphome/components/water_heater/__init__.py b/esphome/components/water_heater/__init__.py index f3eec16a40..6bb2b2f8fe 100644 --- a/esphome/components/water_heater/__init__.py +++ b/esphome/components/water_heater/__init__.py @@ -76,7 +76,7 @@ def water_heater_schema( @setup_entity("water_heater") async def setup_water_heater_core_(var: cg.Pvariable, config: ConfigType) -> None: """Set up the core water heater properties in C++.""" - visual = config[CONF_VISUAL] + visual = config.get(CONF_VISUAL, {}) if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None: cg.add_define("USE_WATER_HEATER_VISUAL_OVERRIDES") cg.add(var.set_visual_min_temperature_override(min_temp)) diff --git a/esphome/components/water_heater/water_heater.h b/esphome/components/water_heater/water_heater.h index dfec26859f..995b815440 100644 --- a/esphome/components/water_heater/water_heater.h +++ b/esphome/components/water_heater/water_heater.h @@ -183,6 +183,9 @@ class WaterHeaterTraits { const WaterHeaterModeMask &get_supported_modes() const { return this->supported_modes_; } bool supports_mode(WaterHeaterMode mode) const { return this->supported_modes_.count(mode); } + TemperatureUnit get_temperature_unit() const { return this->temperature_unit_; } + void set_temperature_unit(TemperatureUnit unit) { this->temperature_unit_ = unit; } + protected: // Ordered to minimize padding: 4-byte members first uint32_t feature_flags_{0}; @@ -190,6 +193,7 @@ class WaterHeaterTraits { float max_temperature_{0.0f}; float target_temperature_step_{0.0f}; WaterHeaterModeMask supported_modes_; + TemperatureUnit temperature_unit_{TemperatureUnit::CELSIUS}; }; class WaterHeater : public EntityBase { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e862d015da..b897b927e2 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1625,6 +1625,12 @@ constexpr float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f /// Convert degrees Fahrenheit to degrees Celsius. constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } +enum class TemperatureUnit : uint8_t { + CELSIUS = 0, + FAHRENHEIT = 1, + KELVIN = 2, +}; + ///@} /// @name Utilities