[template] Stop water heater republishing when a temperature is unknown (#19013)

This commit is contained in:
tronikos
2026-09-14 13:36:39 +12:00
committed by GitHub
parent a0821c225a
commit 41e34c19eb
3 changed files with 57 additions and 2 deletions
@@ -1,6 +1,8 @@
#include "template_water_heater.h"
#include "esphome/core/log.h"
#include <cmath>
namespace esphome::template_ {
static const char *const TAG = "template.water_heater";
@@ -45,9 +47,12 @@ water_heater::WaterHeaterTraits TemplateWaterHeater::traits() {
void TemplateWaterHeater::loop() {
bool changed = false;
// NAN is passed through so a source that has no value yet shows as unknown, but NAN never
// equals NAN, so an already-NAN value must not count as a change or it would republish forever.
auto curr_temp = this->current_temperature_f_.call();
if (curr_temp.has_value()) {
if (*curr_temp != this->current_temperature_) {
if (*curr_temp != this->current_temperature_ &&
!(std::isnan(*curr_temp) && std::isnan(this->current_temperature_))) {
this->current_temperature_ = *curr_temp;
changed = true;
}
@@ -55,7 +60,8 @@ void TemplateWaterHeater::loop() {
auto target_temp = this->target_temperature_f_.call();
if (target_temp.has_value()) {
if (*target_temp != this->target_temperature_) {
if (*target_temp != this->target_temperature_ &&
!(std::isnan(*target_temp) && std::isnan(this->target_temperature_))) {
this->target_temperature_ = *target_temp;
changed = true;
}
@@ -0,0 +1,16 @@
esphome:
name: wh-template-unknown-test
host:
api:
logger:
water_heater:
- platform: template
id: unknown_boiler
name: Unknown Boiler
# Both temperatures stay unknown, as they do before an upstream component reports a value.
current_temperature: !lambda "return NAN;"
target_temperature: !lambda "return NAN;"
supported_modes:
- "off"
- eco
@@ -155,3 +155,36 @@ async def test_water_heater_template(
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO)
eco_state = await wait_for_state()
assert eco_state.mode == WaterHeaterMode.ECO
@pytest.mark.asyncio
async def test_water_heater_template_unknown_temperature(
yaml_config: str,
run_compiled: RunCompiledFunction,
api_client_connected: APIClientConnectedFactory,
) -> None:
"""Test a template water heater whose temperature lambdas stay unknown.
NAN never compares equal to itself, so a lambda that keeps returning NAN must not be
mistaken for a changed value and republish the state on every loop iteration.
"""
async with run_compiled(yaml_config), api_client_connected() as client:
state_count = 0
def on_state(state: aioesphomeapi.EntityState) -> None:
nonlocal state_count
if isinstance(state, WaterHeaterState):
state_count += 1
entities, _ = await client.list_entities_services()
water_heater_infos = [e for e in entities if isinstance(e, WaterHeaterInfo)]
assert len(water_heater_infos) == 1
client.subscribe_states(on_state)
# Let the device run for a while; only the single initial state may arrive.
await asyncio.sleep(1.0)
assert state_count <= 1, (
f"Expected at most 1 state publish, got {state_count} - "
"an unknown (NAN) temperature is republishing every loop"
)