Convert climate.control and water_heater.template.publish temperatures into the entity unit

This commit is contained in:
J. Nick Koston
2026-10-13 20:06:05 -10:00
parent 41dad01e6e
commit 08c1659d5b
5 changed files with 67 additions and 29 deletions
+30 -7
View File
@@ -170,7 +170,15 @@ def temperature_unit_of_id(climate_id: ID) -> str:
for conf in CORE.config.get(DOMAIN, []):
if conf[CONF_ID] == climate_id:
return temperature_unit_of(conf)
return UNIT_CELSIUS
raise KeyError(f"No climate with id {climate_id}")
def temperature_in_entity_unit(
config: ConfigType, value: tuple[float, str | None]
) -> str:
"""Render a static action temperature in the unit of the climate the action targets."""
unit = temperature_unit_of_id(config[CONF_ID])
return str(cg.safe_exp(cv.convert_temperature(*value, unit)))
def _visual_temperatures_in_entity_unit(config: ConfigType) -> ConfigType:
@@ -493,9 +501,13 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Climate),
cv.Optional(CONF_MODE): cv.templatable(validate_climate_mode),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature_with_unit),
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(
cv.temperature_with_unit
),
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(
cv.temperature_with_unit
),
cv.Optional(CONF_TARGET_HUMIDITY): cv.templatable(cv.percentage_int),
cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
@@ -515,12 +527,23 @@ automation.register_apply_action(
"climate.control",
CLIMATE_CONTROL_ACTION_SCHEMA,
automation.ApplyField(CONF_MODE, "set_mode", ClimateMode),
automation.ApplyField(CONF_TARGET_TEMPERATURE, "set_target_temperature", cg.float_),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_LOW, "set_target_temperature_low", cg.float_
CONF_TARGET_TEMPERATURE,
"set_target_temperature",
cg.float_,
temperature_in_entity_unit,
),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_HIGH, "set_target_temperature_high", cg.float_
CONF_TARGET_TEMPERATURE_LOW,
"set_target_temperature_low",
cg.float_,
temperature_in_entity_unit,
),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_HIGH,
"set_target_temperature_high",
cg.float_,
temperature_in_entity_unit,
),
automation.ApplyField(CONF_TARGET_HUMIDITY, "set_target_humidity", cg.float_),
automation.ApplyField(CONF_FAN_MODE, "set_fan_mode", ClimateFanMode),
@@ -433,14 +433,6 @@ CLIMATE_TEMPLATE_PUBLISH_ACTION_SCHEMA = cv.All(
)
def _temperature_in_entity_unit(
config: ConfigType, value: tuple[float, str | None]
) -> str:
# The entity's unit is only known once every climate has been validated
unit = climate.temperature_unit_of_id(config[CONF_ID])
return str(cg.safe_exp(cv.convert_temperature(*value, unit)))
automation.register_apply_action(
"climate.template.publish",
CLIMATE_TEMPLATE_PUBLISH_ACTION_SCHEMA,
@@ -448,26 +440,26 @@ automation.register_apply_action(
CONF_CURRENT_TEMPERATURE,
"current_temperature = {}",
cg.float_,
_temperature_in_entity_unit,
climate.temperature_in_entity_unit,
),
automation.ApplyField(CONF_CURRENT_HUMIDITY, "current_humidity = {}", cg.float_),
automation.ApplyField(
CONF_TARGET_TEMPERATURE,
"set_target_temperature",
cg.float_,
_temperature_in_entity_unit,
climate.temperature_in_entity_unit,
),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_LOW,
"set_target_temperature_low",
cg.float_,
_temperature_in_entity_unit,
climate.temperature_in_entity_unit,
),
automation.ApplyField(
CONF_TARGET_TEMPERATURE_HIGH,
"set_target_temperature_high",
cg.float_,
_temperature_in_entity_unit,
climate.temperature_in_entity_unit,
),
automation.ApplyField(CONF_TARGET_HUMIDITY, "set_target_humidity", cg.float_),
automation.ApplyField(CONF_MODE, "set_mode", climate.ClimateMode),
@@ -129,8 +129,12 @@ async def to_code(config: ConfigType) -> None:
cv.Schema(
{
cv.GenerateID(): cv.use_id(TemplateWaterHeater),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_CURRENT_TEMPERATURE): cv.templatable(
cv.temperature_with_unit
),
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(
cv.temperature_with_unit
),
cv.Optional(CONF_MODE): cv.templatable(
water_heater.validate_water_heater_mode
),
@@ -149,13 +153,17 @@ async def water_heater_template_publish_to_code(
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
if current_temp := config.get(CONF_CURRENT_TEMPERATURE):
template_ = await cg.templatable(current_temp, args, cg.float_)
cg.add(var.set_current_temperature(template_))
if target_temp := config.get(CONF_TARGET_TEMPERATURE):
template_ = await cg.templatable(target_temp, args, cg.float_)
cg.add(var.set_target_temperature(template_))
# A static temperature is in the unit of the water heater it is published to
unit = water_heater.temperature_unit_of_id(config[CONF_ID])
for key, setter in (
(CONF_CURRENT_TEMPERATURE, var.set_current_temperature),
(CONF_TARGET_TEMPERATURE, var.set_target_temperature),
):
if (value := config.get(key)) is None:
continue
if isinstance(value, tuple):
value = cv.convert_temperature(*value, unit)
cg.add(setter(await cg.templatable(value, args, cg.float_)))
if mode := config.get(CONF_MODE):
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
+9 -1
View File
@@ -10,7 +10,7 @@ from esphome.const import (
CONF_VISUAL,
UNIT_CELSIUS,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
queue_entity_register,
@@ -52,6 +52,14 @@ def temperature_unit_of(config: ConfigType) -> str:
return unit if unit in TEMPERATURE_UNITS else UNIT_CELSIUS
def temperature_unit_of_id(water_heater_id: ID) -> str:
"""The unit of the water heater with this id, for actions that take static temperatures."""
for conf in CORE.config.get(DOMAIN, []):
if conf[CONF_ID] == water_heater_id:
return temperature_unit_of(conf)
raise KeyError(f"No water heater with id {water_heater_id}")
def _visual_temperatures_in_entity_unit(config: ConfigType) -> ConfigType:
"""Convert the visual temperatures into the entity's unit."""
unit = temperature_unit_of(config)
@@ -45,6 +45,13 @@ esphome:
id: template_climate_fahrenheit
current_temperature: 20°C
target_temperature: 70
- climate.control:
id: template_climate_fahrenheit
target_temperature: 22°C
- water_heater.template.publish:
id: template_water_heater_fahrenheit
current_temperature: 45°C
target_temperature: 120
- climate.template.publish:
id: template_climate_custom_modes
custom_fan_mode: "turbo"