From b684801cae08c8f9e364790503fe46ca49cc7f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ethem=20Cem=20=C3=96zkan?= Date: Tue, 14 May 2024 19:27:26 +0200 Subject: [PATCH] Use integration fallback configuration for tado water heater fallback (#111014) * 103619 tado water heater fallback * extracted a method to remove code duplication * test cases and suggested changes * tests * util method for connector * Update homeassistant/components/tado/climate.py Co-authored-by: Andriy Kushnir * missing import after applies suggestion * early return * simplify if statements * simplify pr * pr requested changes * better docstring --------- Co-authored-by: Andriy Kushnir --- homeassistant/components/tado/climate.py | 26 +++------ homeassistant/components/tado/helper.py | 31 +++++++++++ homeassistant/components/tado/water_heater.py | 12 ++--- tests/components/tado/test_helper.py | 54 +++++++++++++++++++ 4 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 homeassistant/components/tado/helper.py create mode 100644 tests/components/tado/test_helper.py diff --git a/homeassistant/components/tado/climate.py b/homeassistant/components/tado/climate.py index 6d298a80e79..487bc519a26 100644 --- a/homeassistant/components/tado/climate.py +++ b/homeassistant/components/tado/climate.py @@ -36,8 +36,6 @@ 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, @@ -67,6 +65,7 @@ from .const import ( TYPE_HEATING, ) from .entity import TadoZoneEntity +from .helper import decide_overlay_mode _LOGGER = logging.getLogger(__name__) @@ -598,23 +597,12 @@ 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 - ) + overlay_mode = decide_overlay_mode( + tado=self._tado, + duration=duration, + overlay_mode=overlay_mode, + zone_id=self.zone_id, + ) # 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 = ( diff --git a/homeassistant/components/tado/helper.py b/homeassistant/components/tado/helper.py new file mode 100644 index 00000000000..fee23aef64a --- /dev/null +++ b/homeassistant/components/tado/helper.py @@ -0,0 +1,31 @@ +"""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 diff --git a/homeassistant/components/tado/water_heater.py b/homeassistant/components/tado/water_heater.py index f1257f097eb..9b449dd43cc 100644 --- a/homeassistant/components/tado/water_heater.py +++ b/homeassistant/components/tado/water_heater.py @@ -32,6 +32,7 @@ from .const import ( TYPE_HOT_WATER, ) from .entity import TadoZoneEntity +from .helper import decide_overlay_mode _LOGGER = logging.getLogger(__name__) @@ -277,12 +278,11 @@ 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, + ) _LOGGER.debug( "Switching to %s for zone %s (%d) with temperature %s", diff --git a/tests/components/tado/test_helper.py b/tests/components/tado/test_helper.py new file mode 100644 index 00000000000..ff85dfce944 --- /dev/null +++ b/tests/components/tado/test_helper.py @@ -0,0 +1,54 @@ +"""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_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="01:00:00", 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