mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
[template] Stop water heater republishing when a temperature is unknown (#19013)
This commit is contained in:
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user