mirror of
https://github.com/esphome/esphome.git
synced 2026-10-03 01:35:59 +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 "template_water_heater.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace esphome::template_ {
|
namespace esphome::template_ {
|
||||||
|
|
||||||
static const char *const TAG = "template.water_heater";
|
static const char *const TAG = "template.water_heater";
|
||||||
@@ -45,9 +47,12 @@ water_heater::WaterHeaterTraits TemplateWaterHeater::traits() {
|
|||||||
void TemplateWaterHeater::loop() {
|
void TemplateWaterHeater::loop() {
|
||||||
bool changed = false;
|
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();
|
auto curr_temp = this->current_temperature_f_.call();
|
||||||
if (curr_temp.has_value()) {
|
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;
|
this->current_temperature_ = *curr_temp;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -55,7 +60,8 @@ void TemplateWaterHeater::loop() {
|
|||||||
|
|
||||||
auto target_temp = this->target_temperature_f_.call();
|
auto target_temp = this->target_temperature_f_.call();
|
||||||
if (target_temp.has_value()) {
|
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;
|
this->target_temperature_ = *target_temp;
|
||||||
changed = true;
|
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)
|
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.ECO)
|
||||||
eco_state = await wait_for_state()
|
eco_state = await wait_for_state()
|
||||||
assert eco_state.mode == WaterHeaterMode.ECO
|
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