mirror of
https://github.com/esphome/esphome.git
synced 2026-05-28 21:59:59 +08:00
[water_heater] Add On/Off and Away mode support to template platform (#13839)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
@@ -3,6 +3,7 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import water_heater
|
from esphome.components import water_heater
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_AWAY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_OPTIMISTIC,
|
CONF_OPTIMISTIC,
|
||||||
@@ -18,6 +19,7 @@ from esphome.types import ConfigType
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
|
CONF_IS_ON = "is_on"
|
||||||
|
|
||||||
TemplateWaterHeater = template_ns.class_(
|
TemplateWaterHeater = template_ns.class_(
|
||||||
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
||||||
@@ -51,6 +53,8 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||||
water_heater.validate_water_heater_mode
|
water_heater.validate_water_heater_mode
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_AWAY): cv.returning_lambda,
|
||||||
|
cv.Optional(CONF_IS_ON): cv.returning_lambda,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
@@ -98,6 +102,22 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
if CONF_SUPPORTED_MODES in config:
|
if CONF_SUPPORTED_MODES in config:
|
||||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||||
|
|
||||||
|
if CONF_AWAY in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_AWAY],
|
||||||
|
[],
|
||||||
|
return_type=cg.optional.template(bool),
|
||||||
|
)
|
||||||
|
cg.add(var.set_away_lambda(template_))
|
||||||
|
|
||||||
|
if CONF_IS_ON in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_IS_ON],
|
||||||
|
[],
|
||||||
|
return_type=cg.optional.template(bool),
|
||||||
|
)
|
||||||
|
cg.add(var.set_is_on_lambda(template_))
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
"water_heater.template.publish",
|
"water_heater.template.publish",
|
||||||
@@ -110,6 +130,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
cv.Optional(CONF_MODE): cv.templatable(
|
cv.Optional(CONF_MODE): cv.templatable(
|
||||||
water_heater.validate_water_heater_mode
|
water_heater.validate_water_heater_mode
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||||
|
cv.Optional(CONF_IS_ON): cv.templatable(cv.boolean),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -134,4 +156,12 @@ async def water_heater_template_publish_to_code(
|
|||||||
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
|
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
|
||||||
cg.add(var.set_mode(template_))
|
cg.add(var.set_mode(template_))
|
||||||
|
|
||||||
|
if CONF_AWAY in config:
|
||||||
|
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
|
||||||
|
cg.add(var.set_away(template_))
|
||||||
|
|
||||||
|
if CONF_IS_ON in config:
|
||||||
|
template_ = await cg.templatable(config[CONF_IS_ON], args, bool)
|
||||||
|
cg.add(var.set_is_on(template_))
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<T
|
|||||||
TEMPLATABLE_VALUE(float, current_temperature)
|
TEMPLATABLE_VALUE(float, current_temperature)
|
||||||
TEMPLATABLE_VALUE(float, target_temperature)
|
TEMPLATABLE_VALUE(float, target_temperature)
|
||||||
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
|
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
|
||||||
|
TEMPLATABLE_VALUE(bool, away)
|
||||||
|
TEMPLATABLE_VALUE(bool, is_on)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override {
|
||||||
if (this->current_temperature_.has_value()) {
|
if (this->current_temperature_.has_value()) {
|
||||||
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
|
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
|
||||||
}
|
}
|
||||||
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value();
|
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value() || this->away_.has_value() ||
|
||||||
|
this->is_on_.has_value();
|
||||||
if (needs_call) {
|
if (needs_call) {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
if (this->target_temperature_.has_value()) {
|
if (this->target_temperature_.has_value()) {
|
||||||
@@ -25,6 +28,12 @@ class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<T
|
|||||||
if (this->mode_.has_value()) {
|
if (this->mode_.has_value()) {
|
||||||
call.set_mode(this->mode_.value(x...));
|
call.set_mode(this->mode_.value(x...));
|
||||||
}
|
}
|
||||||
|
if (this->away_.has_value()) {
|
||||||
|
call.set_away(this->away_.value(x...));
|
||||||
|
}
|
||||||
|
if (this->is_on_.has_value()) {
|
||||||
|
call.set_on(this->is_on_.value(x...));
|
||||||
|
}
|
||||||
call.perform();
|
call.perform();
|
||||||
} else {
|
} else {
|
||||||
this->parent_->publish_state();
|
this->parent_->publish_state();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ void TemplateWaterHeater::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->current_temperature_f_.has_value() && !this->target_temperature_f_.has_value() &&
|
if (!this->current_temperature_f_.has_value() && !this->target_temperature_f_.has_value() &&
|
||||||
!this->mode_f_.has_value())
|
!this->mode_f_.has_value() && !this->away_f_.has_value() && !this->is_on_f_.has_value())
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +32,12 @@ water_heater::WaterHeaterTraits TemplateWaterHeater::traits() {
|
|||||||
if (this->target_temperature_f_.has_value()) {
|
if (this->target_temperature_f_.has_value()) {
|
||||||
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE);
|
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE);
|
||||||
}
|
}
|
||||||
|
if (this->away_f_.has_value()) {
|
||||||
|
traits.set_supports_away_mode(true);
|
||||||
|
}
|
||||||
|
if (this->is_on_f_.has_value()) {
|
||||||
|
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_ON_OFF);
|
||||||
|
}
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +68,22 @@ void TemplateWaterHeater::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto away = this->away_f_.call();
|
||||||
|
if (away.has_value()) {
|
||||||
|
if (*away != this->is_away()) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_AWAY, *away);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto is_on = this->is_on_f_.call();
|
||||||
|
if (is_on.has_value()) {
|
||||||
|
if (*is_on != this->is_on()) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_ON, *is_on);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
@@ -90,6 +112,17 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (call.get_away().has_value()) {
|
||||||
|
if (this->optimistic_) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_AWAY, *call.get_away());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (call.get_on().has_value()) {
|
||||||
|
if (this->optimistic_) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_ON, *call.get_on());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->set_trigger_.trigger();
|
this->set_trigger_.trigger();
|
||||||
|
|
||||||
if (this->optimistic_) {
|
if (this->optimistic_) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
|||||||
this->target_temperature_f_.set(std::forward<F>(f));
|
this->target_temperature_f_.set(std::forward<F>(f));
|
||||||
}
|
}
|
||||||
template<typename F> void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward<F>(f)); }
|
template<typename F> void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward<F>(f)); }
|
||||||
|
template<typename F> void set_away_lambda(F &&f) { this->away_f_.set(std::forward<F>(f)); }
|
||||||
|
template<typename F> void set_is_on_lambda(F &&f) { this->is_on_f_.set(std::forward<F>(f)); }
|
||||||
|
|
||||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||||
@@ -49,6 +51,8 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
|||||||
TemplateLambda<float> current_temperature_f_;
|
TemplateLambda<float> current_temperature_f_;
|
||||||
TemplateLambda<float> target_temperature_f_;
|
TemplateLambda<float> target_temperature_f_;
|
||||||
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
|
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
|
||||||
|
TemplateLambda<bool> away_f_;
|
||||||
|
TemplateLambda<bool> is_on_f_;
|
||||||
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};
|
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};
|
||||||
water_heater::WaterHeaterModeMask supported_modes_;
|
water_heater::WaterHeaterModeMask supported_modes_;
|
||||||
bool optimistic_{true};
|
bool optimistic_{true};
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ esphome:
|
|||||||
id: template_water_heater
|
id: template_water_heater
|
||||||
target_temperature: 50.0
|
target_temperature: 50.0
|
||||||
mode: ECO
|
mode: ECO
|
||||||
|
away: false
|
||||||
|
is_on: true
|
||||||
|
|
||||||
# Templated
|
# Templated
|
||||||
- water_heater.template.publish:
|
- water_heater.template.publish:
|
||||||
@@ -20,6 +22,8 @@ esphome:
|
|||||||
current_temperature: !lambda "return 45.0;"
|
current_temperature: !lambda "return 45.0;"
|
||||||
target_temperature: !lambda "return 55.0;"
|
target_temperature: !lambda "return 55.0;"
|
||||||
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
|
||||||
|
away: !lambda "return true;"
|
||||||
|
is_on: !lambda "return false;"
|
||||||
|
|
||||||
# Test C++ API: set_template() with stateless lambda (no captures)
|
# Test C++ API: set_template() with stateless lambda (no captures)
|
||||||
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
||||||
@@ -414,6 +418,8 @@ water_heater:
|
|||||||
current_temperature: !lambda "return 42.0f;"
|
current_temperature: !lambda "return 42.0f;"
|
||||||
target_temperature: !lambda "return 60.0f;"
|
target_temperature: !lambda "return 60.0f;"
|
||||||
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
|
||||||
|
away: !lambda "return false;"
|
||||||
|
is_on: !lambda "return true;"
|
||||||
supported_modes:
|
supported_modes:
|
||||||
- "OFF"
|
- "OFF"
|
||||||
- ECO
|
- ECO
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ host:
|
|||||||
api:
|
api:
|
||||||
logger:
|
logger:
|
||||||
|
|
||||||
|
globals:
|
||||||
|
- id: global_away
|
||||||
|
type: bool
|
||||||
|
initial_value: "false"
|
||||||
|
- id: global_is_on
|
||||||
|
type: bool
|
||||||
|
initial_value: "true"
|
||||||
|
|
||||||
water_heater:
|
water_heater:
|
||||||
- platform: template
|
- platform: template
|
||||||
id: test_boiler
|
id: test_boiler
|
||||||
@@ -11,6 +19,8 @@ water_heater:
|
|||||||
optimistic: true
|
optimistic: true
|
||||||
current_temperature: !lambda "return 45.0f;"
|
current_temperature: !lambda "return 45.0f;"
|
||||||
target_temperature: !lambda "return 60.0f;"
|
target_temperature: !lambda "return 60.0f;"
|
||||||
|
away: !lambda "return id(global_away);"
|
||||||
|
is_on: !lambda "return id(global_is_on);"
|
||||||
# Note: No mode lambda - we want optimistic mode changes to stick
|
# Note: No mode lambda - we want optimistic mode changes to stick
|
||||||
# A mode lambda would override mode changes in loop()
|
# A mode lambda would override mode changes in loop()
|
||||||
supported_modes:
|
supported_modes:
|
||||||
@@ -22,3 +32,8 @@ water_heater:
|
|||||||
min_temperature: 30.0
|
min_temperature: 30.0
|
||||||
max_temperature: 85.0
|
max_temperature: 85.0
|
||||||
target_temperature_step: 0.5
|
target_temperature_step: 0.5
|
||||||
|
set_action:
|
||||||
|
- lambda: |-
|
||||||
|
// Sync optimistic state back to globals so lambdas reflect the change
|
||||||
|
id(global_away) = id(test_boiler).is_away();
|
||||||
|
id(global_is_on) = id(test_boiler).is_on();
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import aioesphomeapi
|
import aioesphomeapi
|
||||||
from aioesphomeapi import WaterHeaterInfo, WaterHeaterMode, WaterHeaterState
|
from aioesphomeapi import (
|
||||||
|
WaterHeaterFeature,
|
||||||
|
WaterHeaterInfo,
|
||||||
|
WaterHeaterMode,
|
||||||
|
WaterHeaterState,
|
||||||
|
WaterHeaterStateFlag,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .state_utils import InitialStateHelper
|
from .state_utils import InitialStateHelper
|
||||||
@@ -22,18 +28,25 @@ async def test_water_heater_template(
|
|||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||||
states: dict[int, aioesphomeapi.EntityState] = {}
|
states: dict[int, aioesphomeapi.EntityState] = {}
|
||||||
gas_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future()
|
state_future: asyncio.Future[WaterHeaterState] | None = None
|
||||||
eco_mode_future: asyncio.Future[WaterHeaterState] = loop.create_future()
|
|
||||||
|
|
||||||
def on_state(state: aioesphomeapi.EntityState) -> None:
|
def on_state(state: aioesphomeapi.EntityState) -> None:
|
||||||
states[state.key] = state
|
states[state.key] = state
|
||||||
if isinstance(state, WaterHeaterState):
|
if (
|
||||||
# Wait for GAS mode
|
isinstance(state, WaterHeaterState)
|
||||||
if state.mode == WaterHeaterMode.GAS and not gas_mode_future.done():
|
and state_future is not None
|
||||||
gas_mode_future.set_result(state)
|
and not state_future.done()
|
||||||
# Wait for ECO mode (we start at OFF, so test transitioning to ECO)
|
):
|
||||||
elif state.mode == WaterHeaterMode.ECO and not eco_mode_future.done():
|
state_future.set_result(state)
|
||||||
eco_mode_future.set_result(state)
|
|
||||||
|
async def wait_for_state(timeout: float = 5.0) -> WaterHeaterState:
|
||||||
|
"""Wait for next water heater state change."""
|
||||||
|
nonlocal state_future
|
||||||
|
state_future = loop.create_future()
|
||||||
|
try:
|
||||||
|
return await asyncio.wait_for(state_future, timeout)
|
||||||
|
finally:
|
||||||
|
state_future = None
|
||||||
|
|
||||||
# Get entities and set up state synchronization
|
# Get entities and set up state synchronization
|
||||||
entities, services = await client.list_entities_services()
|
entities, services = await client.list_entities_services()
|
||||||
@@ -89,24 +102,52 @@ async def test_water_heater_template(
|
|||||||
f"Expected target temp 60.0, got {initial_state.target_temperature}"
|
f"Expected target temp 60.0, got {initial_state.target_temperature}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Verify supported features: away mode and on/off (fixture has away + is_on lambdas)
|
||||||
|
assert (
|
||||||
|
test_water_heater.supported_features & WaterHeaterFeature.SUPPORTS_AWAY_MODE
|
||||||
|
) != 0, "Expected SUPPORTS_AWAY_MODE in supported_features"
|
||||||
|
assert (
|
||||||
|
test_water_heater.supported_features & WaterHeaterFeature.SUPPORTS_ON_OFF
|
||||||
|
) != 0, "Expected SUPPORTS_ON_OFF in supported_features"
|
||||||
|
|
||||||
|
# Verify initial state: on (is_on lambda returns true), not away (away lambda returns false)
|
||||||
|
assert (initial_state.state & WaterHeaterStateFlag.ON) != 0, (
|
||||||
|
"Expected initial state to include ON flag"
|
||||||
|
)
|
||||||
|
assert (initial_state.state & WaterHeaterStateFlag.AWAY) == 0, (
|
||||||
|
"Expected initial state to not include AWAY flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test turning on away mode
|
||||||
|
client.water_heater_command(test_water_heater.key, away=True)
|
||||||
|
away_on_state = await wait_for_state()
|
||||||
|
assert (away_on_state.state & WaterHeaterStateFlag.AWAY) != 0
|
||||||
|
# ON flag should still be set (is_on lambda returns true)
|
||||||
|
assert (away_on_state.state & WaterHeaterStateFlag.ON) != 0
|
||||||
|
|
||||||
|
# Test turning off away mode
|
||||||
|
client.water_heater_command(test_water_heater.key, away=False)
|
||||||
|
away_off_state = await wait_for_state()
|
||||||
|
assert (away_off_state.state & WaterHeaterStateFlag.AWAY) == 0
|
||||||
|
assert (away_off_state.state & WaterHeaterStateFlag.ON) != 0
|
||||||
|
|
||||||
|
# Test turning off (on=False)
|
||||||
|
client.water_heater_command(test_water_heater.key, on=False)
|
||||||
|
off_state = await wait_for_state()
|
||||||
|
assert (off_state.state & WaterHeaterStateFlag.ON) == 0
|
||||||
|
assert (off_state.state & WaterHeaterStateFlag.AWAY) == 0
|
||||||
|
|
||||||
|
# Test turning back on (on=True)
|
||||||
|
client.water_heater_command(test_water_heater.key, on=True)
|
||||||
|
on_state = await wait_for_state()
|
||||||
|
assert (on_state.state & WaterHeaterStateFlag.ON) != 0
|
||||||
|
|
||||||
# Test changing to GAS mode
|
# Test changing to GAS mode
|
||||||
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS)
|
client.water_heater_command(test_water_heater.key, mode=WaterHeaterMode.GAS)
|
||||||
|
gas_state = await wait_for_state()
|
||||||
try:
|
|
||||||
gas_state = await asyncio.wait_for(gas_mode_future, timeout=5.0)
|
|
||||||
except TimeoutError:
|
|
||||||
pytest.fail("GAS mode change not received within 5 seconds")
|
|
||||||
|
|
||||||
assert isinstance(gas_state, WaterHeaterState)
|
|
||||||
assert gas_state.mode == WaterHeaterMode.GAS
|
assert gas_state.mode == WaterHeaterMode.GAS
|
||||||
|
|
||||||
# Test changing to ECO mode (from GAS)
|
# Test changing to ECO mode (from GAS)
|
||||||
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()
|
||||||
try:
|
|
||||||
eco_state = await asyncio.wait_for(eco_mode_future, timeout=5.0)
|
|
||||||
except TimeoutError:
|
|
||||||
pytest.fail("ECO mode change not received within 5 seconds")
|
|
||||||
|
|
||||||
assert isinstance(eco_state, WaterHeaterState)
|
|
||||||
assert eco_state.mode == WaterHeaterMode.ECO
|
assert eco_state.mode == WaterHeaterMode.ECO
|
||||||
|
|||||||
Reference in New Issue
Block a user