mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Revert "Revert Use integration fallback configuration for tado water fallback" (#119526)
* Revert "Revert Use integration fallback configuration for tado water heater fallback (#119466)" This reverts commit ade936e6d5088c4a4d809111417fb3c7080825d5. * add decide method for duration * add repair issue to let users know * test module for repairs * Update strings.json Co-authored-by: Franck Nijhof <frenck@frenck.nl> * repair issue should not be persistent * use issue_registery fixture instead of mocking * fix comment * parameterize repair issue created test case --------- Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
410ef8ce14
commit
9065042960
@ -36,10 +36,7 @@ from .const import (
|
||||
CONST_MODE_OFF,
|
||||
CONST_MODE_SMART_SCHEDULE,
|
||||
CONST_OVERLAY_MANUAL,
|
||||
CONST_OVERLAY_TADO_DEFAULT,
|
||||
CONST_OVERLAY_TADO_MODE,
|
||||
CONST_OVERLAY_TADO_OPTIONS,
|
||||
CONST_OVERLAY_TIMER,
|
||||
DATA,
|
||||
DOMAIN,
|
||||
HA_TERMINATION_DURATION,
|
||||
@ -67,6 +64,7 @@ from .const import (
|
||||
TYPE_HEATING,
|
||||
)
|
||||
from .entity import TadoZoneEntity
|
||||
from .helper import decide_duration, decide_overlay_mode
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -598,31 +596,18 @@ class TadoClimate(TadoZoneEntity, ClimateEntity):
|
||||
self._tado.reset_zone_overlay(self.zone_id)
|
||||
return
|
||||
|
||||
# If user gave duration then overlay mode needs to be timer
|
||||
if duration:
|
||||
overlay_mode = CONST_OVERLAY_TIMER
|
||||
# If no duration or timer set to fallback setting
|
||||
if overlay_mode is None:
|
||||
overlay_mode = (
|
||||
self._tado.fallback
|
||||
if self._tado.fallback is not None
|
||||
else CONST_OVERLAY_TADO_MODE
|
||||
)
|
||||
# If default is Tado default then look it up
|
||||
if overlay_mode == CONST_OVERLAY_TADO_DEFAULT:
|
||||
overlay_mode = (
|
||||
self._tado_zone_data.default_overlay_termination_type
|
||||
if self._tado_zone_data.default_overlay_termination_type is not None
|
||||
else CONST_OVERLAY_TADO_MODE
|
||||
)
|
||||
# If we ended up with a timer but no duration, set a default duration
|
||||
if overlay_mode == CONST_OVERLAY_TIMER and duration is None:
|
||||
duration = (
|
||||
int(self._tado_zone_data.default_overlay_termination_duration)
|
||||
if self._tado_zone_data.default_overlay_termination_duration is not None
|
||||
else 3600
|
||||
)
|
||||
|
||||
overlay_mode = decide_overlay_mode(
|
||||
tado=self._tado,
|
||||
duration=duration,
|
||||
overlay_mode=overlay_mode,
|
||||
zone_id=self.zone_id,
|
||||
)
|
||||
duration = decide_duration(
|
||||
tado=self._tado,
|
||||
duration=duration,
|
||||
zone_id=self.zone_id,
|
||||
overlay_mode=overlay_mode,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
(
|
||||
"Switching to %s for zone %s (%d) with temperature %s °C and duration"
|
||||
|
@ -212,3 +212,5 @@ SERVICE_ADD_METER_READING = "add_meter_reading"
|
||||
CONF_CONFIG_ENTRY = "config_entry"
|
||||
CONF_READING = "reading"
|
||||
ATTR_MESSAGE = "message"
|
||||
|
||||
WATER_HEATER_FALLBACK_REPAIR = "water_heater_fallback"
|
||||
|
51
homeassistant/components/tado/helper.py
Normal file
51
homeassistant/components/tado/helper.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""Helper methods for Tado."""
|
||||
|
||||
from . import TadoConnector
|
||||
from .const import (
|
||||
CONST_OVERLAY_TADO_DEFAULT,
|
||||
CONST_OVERLAY_TADO_MODE,
|
||||
CONST_OVERLAY_TIMER,
|
||||
)
|
||||
|
||||
|
||||
def decide_overlay_mode(
|
||||
tado: TadoConnector,
|
||||
duration: int | None,
|
||||
zone_id: int,
|
||||
overlay_mode: str | None = None,
|
||||
) -> str:
|
||||
"""Return correct overlay mode based on the action and defaults."""
|
||||
# If user gave duration then overlay mode needs to be timer
|
||||
if duration:
|
||||
return CONST_OVERLAY_TIMER
|
||||
# If no duration or timer set to fallback setting
|
||||
if overlay_mode is None:
|
||||
overlay_mode = tado.fallback or CONST_OVERLAY_TADO_MODE
|
||||
# If default is Tado default then look it up
|
||||
if overlay_mode == CONST_OVERLAY_TADO_DEFAULT:
|
||||
overlay_mode = (
|
||||
tado.data["zone"][zone_id].default_overlay_termination_type
|
||||
or CONST_OVERLAY_TADO_MODE
|
||||
)
|
||||
|
||||
return overlay_mode
|
||||
|
||||
|
||||
def decide_duration(
|
||||
tado: TadoConnector,
|
||||
duration: int | None,
|
||||
zone_id: int,
|
||||
overlay_mode: str | None = None,
|
||||
) -> None | int:
|
||||
"""Return correct duration based on the selected overlay mode/duration and tado config."""
|
||||
# If we ended up with a timer but no duration, set a default duration
|
||||
# If we ended up with a timer but no duration, set a default duration
|
||||
if overlay_mode == CONST_OVERLAY_TIMER and duration is None:
|
||||
duration = (
|
||||
int(tado.data["zone"][zone_id].default_overlay_termination_duration)
|
||||
if tado.data["zone"][zone_id].default_overlay_termination_duration
|
||||
is not None
|
||||
else 3600
|
||||
)
|
||||
|
||||
return duration
|
34
homeassistant/components/tado/repairs.py
Normal file
34
homeassistant/components/tado/repairs.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""Repair implementations."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .const import (
|
||||
CONST_OVERLAY_MANUAL,
|
||||
CONST_OVERLAY_TADO_DEFAULT,
|
||||
DOMAIN,
|
||||
WATER_HEATER_FALLBACK_REPAIR,
|
||||
)
|
||||
|
||||
|
||||
def manage_water_heater_fallback_issue(
|
||||
hass: HomeAssistant,
|
||||
water_heater_entities: list,
|
||||
integration_overlay_fallback: str | None,
|
||||
) -> None:
|
||||
"""Notify users about water heater respecting fallback setting."""
|
||||
if (
|
||||
integration_overlay_fallback
|
||||
in [CONST_OVERLAY_TADO_DEFAULT, CONST_OVERLAY_MANUAL]
|
||||
and len(water_heater_entities) > 0
|
||||
):
|
||||
for water_heater_entity in water_heater_entities:
|
||||
ir.async_create_issue(
|
||||
hass=hass,
|
||||
domain=DOMAIN,
|
||||
issue_id=f"{WATER_HEATER_FALLBACK_REPAIR}_{water_heater_entity.zone_name}",
|
||||
is_fixable=False,
|
||||
is_persistent=False,
|
||||
severity=ir.IssueSeverity.WARNING,
|
||||
translation_key=WATER_HEATER_FALLBACK_REPAIR,
|
||||
)
|
@ -165,6 +165,10 @@
|
||||
"import_failed_invalid_auth": {
|
||||
"title": "Failed to import, invalid credentials",
|
||||
"description": "Failed to import the configuration for the Tado Device Tracker, due to invalid credentials. Please fix the YAML configuration and restart Home Assistant. Alternatively you can use the UI to configure Tado. Don't forget to delete the YAML configuration, once the import is successful."
|
||||
},
|
||||
"water_heater_fallback": {
|
||||
"title": "Tado Water Heater entities now support fallback options",
|
||||
"description": "Due to added support for water heaters entities, these entities may use different overlay. Please configure integration entity and tado app water heater zone overlay options."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ from .const import (
|
||||
TYPE_HOT_WATER,
|
||||
)
|
||||
from .entity import TadoZoneEntity
|
||||
from .helper import decide_duration, decide_overlay_mode
|
||||
from .repairs import manage_water_heater_fallback_issue
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -79,6 +81,12 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
manage_water_heater_fallback_issue(
|
||||
hass=hass,
|
||||
water_heater_entities=entities,
|
||||
integration_overlay_fallback=tado.fallback,
|
||||
)
|
||||
|
||||
|
||||
def _generate_entities(tado: TadoConnector) -> list[WaterHeaterEntity]:
|
||||
"""Create all water heater entities."""
|
||||
@ -277,13 +285,17 @@ class TadoWaterHeater(TadoZoneEntity, WaterHeaterEntity):
|
||||
self._tado.set_zone_off(self.zone_id, CONST_OVERLAY_MANUAL, TYPE_HOT_WATER)
|
||||
return
|
||||
|
||||
overlay_mode = CONST_OVERLAY_MANUAL
|
||||
if duration:
|
||||
overlay_mode = CONST_OVERLAY_TIMER
|
||||
elif self._tado.fallback:
|
||||
# Fallback to Smart Schedule at next Schedule switch if we have fallback enabled
|
||||
overlay_mode = CONST_OVERLAY_TADO_MODE
|
||||
|
||||
overlay_mode = decide_overlay_mode(
|
||||
tado=self._tado,
|
||||
duration=duration,
|
||||
zone_id=self.zone_id,
|
||||
)
|
||||
duration = decide_duration(
|
||||
tado=self._tado,
|
||||
duration=duration,
|
||||
zone_id=self.zone_id,
|
||||
overlay_mode=overlay_mode,
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Switching to %s for zone %s (%d) with temperature %s",
|
||||
self._current_tado_hvac_mode,
|
||||
|
87
tests/components/tado/test_helper.py
Normal file
87
tests/components/tado/test_helper.py
Normal file
@ -0,0 +1,87 @@
|
||||
"""Helper method tests."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.tado import TadoConnector
|
||||
from homeassistant.components.tado.const import (
|
||||
CONST_OVERLAY_MANUAL,
|
||||
CONST_OVERLAY_TADO_DEFAULT,
|
||||
CONST_OVERLAY_TADO_MODE,
|
||||
CONST_OVERLAY_TIMER,
|
||||
)
|
||||
from homeassistant.components.tado.helper import decide_duration, decide_overlay_mode
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
def dummy_tado_connector(hass: HomeAssistant, fallback) -> TadoConnector:
|
||||
"""Return dummy tado connector."""
|
||||
return TadoConnector(hass, username="dummy", password="dummy", fallback=fallback)
|
||||
|
||||
|
||||
async def test_overlay_mode_duration_set(hass: HomeAssistant) -> None:
|
||||
"""Test overlay method selection when duration is set."""
|
||||
tado = dummy_tado_connector(hass=hass, fallback=CONST_OVERLAY_TADO_MODE)
|
||||
overlay_mode = decide_overlay_mode(tado=tado, duration=3600, zone_id=1)
|
||||
# Must select TIMER overlay
|
||||
assert overlay_mode == CONST_OVERLAY_TIMER
|
||||
|
||||
|
||||
async def test_overlay_mode_next_time_block_fallback(hass: HomeAssistant) -> None:
|
||||
"""Test overlay method selection when duration is not set."""
|
||||
integration_fallback = CONST_OVERLAY_TADO_MODE
|
||||
tado = dummy_tado_connector(hass=hass, fallback=integration_fallback)
|
||||
overlay_mode = decide_overlay_mode(tado=tado, duration=None, zone_id=1)
|
||||
# Must fallback to integration wide setting
|
||||
assert overlay_mode == integration_fallback
|
||||
|
||||
|
||||
async def test_overlay_mode_tado_default_fallback(hass: HomeAssistant) -> None:
|
||||
"""Test overlay method selection when tado default is selected."""
|
||||
integration_fallback = CONST_OVERLAY_TADO_DEFAULT
|
||||
zone_fallback = CONST_OVERLAY_MANUAL
|
||||
tado = dummy_tado_connector(hass=hass, fallback=integration_fallback)
|
||||
|
||||
class MockZoneData:
|
||||
def __init__(self) -> None:
|
||||
self.default_overlay_termination_type = zone_fallback
|
||||
|
||||
zone_id = 1
|
||||
|
||||
zone_data = {"zone": {zone_id: MockZoneData()}}
|
||||
with patch.dict(tado.data, zone_data):
|
||||
overlay_mode = decide_overlay_mode(tado=tado, duration=None, zone_id=zone_id)
|
||||
# Must fallback to zone setting
|
||||
assert overlay_mode == zone_fallback
|
||||
|
||||
|
||||
async def test_duration_enabled_without_tado_default(hass: HomeAssistant) -> None:
|
||||
"""Test duration decide method when overlay is timer and duration is set."""
|
||||
overlay = CONST_OVERLAY_TIMER
|
||||
expected_duration = 600
|
||||
tado = dummy_tado_connector(hass=hass, fallback=CONST_OVERLAY_MANUAL)
|
||||
duration = decide_duration(
|
||||
tado=tado, duration=expected_duration, overlay_mode=overlay, zone_id=0
|
||||
)
|
||||
# Should return the same duration value
|
||||
assert duration == expected_duration
|
||||
|
||||
|
||||
async def test_duration_enabled_with_tado_default(hass: HomeAssistant) -> None:
|
||||
"""Test overlay method selection when ended up with timer overlay and None duration."""
|
||||
zone_fallback = CONST_OVERLAY_TIMER
|
||||
expected_duration = 45000
|
||||
tado = dummy_tado_connector(hass=hass, fallback=zone_fallback)
|
||||
|
||||
class MockZoneData:
|
||||
def __init__(self) -> None:
|
||||
self.default_overlay_termination_duration = expected_duration
|
||||
|
||||
zone_id = 1
|
||||
|
||||
zone_data = {"zone": {zone_id: MockZoneData()}}
|
||||
with patch.dict(tado.data, zone_data):
|
||||
duration = decide_duration(
|
||||
tado=tado, duration=None, zone_id=zone_id, overlay_mode=zone_fallback
|
||||
)
|
||||
# Must fallback to zone timer setting
|
||||
assert duration == expected_duration
|
64
tests/components/tado/test_repairs.py
Normal file
64
tests/components/tado/test_repairs.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""Repair tests."""
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.tado.const import (
|
||||
CONST_OVERLAY_MANUAL,
|
||||
CONST_OVERLAY_TADO_DEFAULT,
|
||||
CONST_OVERLAY_TADO_MODE,
|
||||
DOMAIN,
|
||||
WATER_HEATER_FALLBACK_REPAIR,
|
||||
)
|
||||
from homeassistant.components.tado.repairs import manage_water_heater_fallback_issue
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
|
||||
class MockWaterHeater:
|
||||
"""Mock Water heater entity."""
|
||||
|
||||
def __init__(self, zone_name) -> None:
|
||||
"""Init mock entity class."""
|
||||
self.zone_name = zone_name
|
||||
|
||||
|
||||
async def test_manage_water_heater_fallback_issue_not_created(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test water heater fallback issue is not needed."""
|
||||
zone_name = "Hot Water"
|
||||
expected_issue_id = f"{WATER_HEATER_FALLBACK_REPAIR}_{zone_name}"
|
||||
water_heater_entities = [MockWaterHeater(zone_name)]
|
||||
manage_water_heater_fallback_issue(
|
||||
water_heater_entities=water_heater_entities,
|
||||
integration_overlay_fallback=CONST_OVERLAY_TADO_MODE,
|
||||
hass=hass,
|
||||
)
|
||||
assert (
|
||||
issue_registry.async_get_issue(issue_id=expected_issue_id, domain=DOMAIN)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"integration_overlay_fallback", [CONST_OVERLAY_TADO_DEFAULT, CONST_OVERLAY_MANUAL]
|
||||
)
|
||||
async def test_manage_water_heater_fallback_issue_created(
|
||||
hass: HomeAssistant,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
integration_overlay_fallback: str,
|
||||
) -> None:
|
||||
"""Test water heater fallback issue created cases."""
|
||||
zone_name = "Hot Water"
|
||||
expected_issue_id = f"{WATER_HEATER_FALLBACK_REPAIR}_{zone_name}"
|
||||
water_heater_entities = [MockWaterHeater(zone_name)]
|
||||
manage_water_heater_fallback_issue(
|
||||
water_heater_entities=water_heater_entities,
|
||||
integration_overlay_fallback=integration_overlay_fallback,
|
||||
hass=hass,
|
||||
)
|
||||
assert (
|
||||
issue_registry.async_get_issue(issue_id=expected_issue_id, domain=DOMAIN)
|
||||
is not None
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user