diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 7c84678963772..68d89d6270c48 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -27,6 +27,7 @@ ATTR_STATE_HOLIDAY_MODE, ATTR_STATE_SUMMER_MODE, ATTR_STATE_WINDOW_OPEN, + ATTR_STATE_WINDOW_OPEN_ENDTIME, CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, ) @@ -37,7 +38,7 @@ MIN_TEMPERATURE = 8 MAX_TEMPERATURE = 28 -PRESET_MANUAL = "manual" +PRESET_WINDOW_OPEN = "Window open" # special temperatures for on/off in Fritz!Box API (modified by pyfritzhome) ON_API_TEMPERATURE = 127.0 @@ -100,6 +101,16 @@ async def async_set_temperature(self, **kwargs: Any) -> None: ) await self.coordinator.async_refresh() + async def async_set_window_open(self, window_open: bool) -> None: + """Set the thermostate to off (window open) for a pre defined time.""" + if window_open: + timeout = 60 * 15 + else: + timeout = 0 + + await self.hass.async_add_executor_job(self.data.set_window_open, timeout) + await self.coordinator.async_refresh() + @property def hvac_mode(self) -> HVACMode: """Return the current operation mode.""" @@ -126,6 +137,8 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: @property def preset_mode(self) -> str | None: """Return current preset mode.""" + if self.data.window_open: + return PRESET_WINDOW_OPEN if self.data.target_temperature == self.data.comfort_temperature: return PRESET_COMFORT if self.data.target_temperature == self.data.eco_temperature: @@ -135,7 +148,7 @@ def preset_mode(self) -> str | None: @property def preset_modes(self) -> list[str]: """Return supported preset modes.""" - return [PRESET_ECO, PRESET_COMFORT] + return [PRESET_ECO, PRESET_COMFORT, PRESET_WINDOW_OPEN] async def async_set_preset_mode(self, preset_mode: str) -> None: """Set preset mode.""" @@ -144,6 +157,11 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: elif preset_mode == PRESET_ECO: await self.async_set_temperature(temperature=self.data.eco_temperature) + if preset_mode == PRESET_WINDOW_OPEN: + await self.async_set_window_open(True) + else: + await self.async_set_window_open(False) + @property def min_temp(self) -> int: """Return the minimum temperature.""" @@ -170,5 +188,7 @@ def extra_state_attributes(self) -> ClimateExtraAttributes: attrs[ATTR_STATE_SUMMER_MODE] = self.data.summer_active if self.data.window_open is not None: attrs[ATTR_STATE_WINDOW_OPEN] = self.data.window_open + if self.data.window_open_endtime is not None: + attrs[ATTR_STATE_WINDOW_OPEN_ENDTIME] = self.data.window_open_endtime return attrs diff --git a/homeassistant/components/fritzbox/const.py b/homeassistant/components/fritzbox/const.py index 791da4540a4e4..61cc0cb08a5b0 100644 --- a/homeassistant/components/fritzbox/const.py +++ b/homeassistant/components/fritzbox/const.py @@ -10,6 +10,7 @@ ATTR_STATE_HOLIDAY_MODE: Final = "holiday_mode" ATTR_STATE_SUMMER_MODE: Final = "summer_mode" ATTR_STATE_WINDOW_OPEN: Final = "window_open" +ATTR_STATE_WINDOW_OPEN_ENDTIME: Final = "window_open_endtime" COLOR_MODE: Final = "1" COLOR_TEMP_MODE: Final = "4" diff --git a/homeassistant/components/fritzbox/model.py b/homeassistant/components/fritzbox/model.py index 3c3275e0ff00b..37f4cae16ecfc 100644 --- a/homeassistant/components/fritzbox/model.py +++ b/homeassistant/components/fritzbox/model.py @@ -16,6 +16,7 @@ class ClimateExtraAttributes(TypedDict, total=False): holiday_mode: bool summer_mode: bool window_open: bool + window_open_endtime: float @dataclass diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index 15ff04f372078..18f6239f005ae 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -96,7 +96,7 @@ class FritzDeviceClimateMock(FritzEntityBaseMock): present = True summer_active = "fake_summer" target_temperature = 19.5 - window_open = "fake_window" + window_open = False nextchange_temperature = 22.0 nextchange_endperiod = 0 nextchange_preset = PRESET_COMFORT diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index d49b5710a128c..55941ffcc2191 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -20,6 +20,7 @@ SERVICE_SET_TEMPERATURE, HVACMode, ) +from homeassistant.components.fritzbox.climate import PRESET_WINDOW_OPEN from homeassistant.components.fritzbox.const import ( ATTR_STATE_BATTERY_LOW, ATTR_STATE_HOLIDAY_MODE, @@ -65,11 +66,15 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None: assert state.attributes[ATTR_MAX_TEMP] == 28 assert state.attributes[ATTR_MIN_TEMP] == 8 assert state.attributes[ATTR_PRESET_MODE] is None - assert state.attributes[ATTR_PRESET_MODES] == [PRESET_ECO, PRESET_COMFORT] + assert state.attributes[ATTR_PRESET_MODES] == [ + PRESET_ECO, + PRESET_COMFORT, + PRESET_WINDOW_OPEN, + ] assert state.attributes[ATTR_STATE_BATTERY_LOW] is True assert state.attributes[ATTR_STATE_HOLIDAY_MODE] == "fake_holiday" assert state.attributes[ATTR_STATE_SUMMER_MODE] == "fake_summer" - assert state.attributes[ATTR_STATE_WINDOW_OPEN] == "fake_window" + assert state.attributes[ATTR_STATE_WINDOW_OPEN] is False assert state.attributes[ATTR_TEMPERATURE] == 19.5 assert ATTR_STATE_CLASS not in state.attributes assert state.state == HVACMode.HEAT @@ -368,6 +373,22 @@ async def test_set_preset_mode_eco(hass: HomeAssistant, fritz: Mock) -> None: assert device.set_target_temperature.call_args_list == [call(16)] +async def test_set_preset_mode_window_open(hass: HomeAssistant, fritz: Mock) -> None: + """Test setting preset mode.""" + device = FritzDeviceClimateMock() + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + assert await hass.services.async_call( + DOMAIN, + SERVICE_SET_PRESET_MODE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_WINDOW_OPEN}, + True, + ) + assert device.set_window_open.call_args_list == [call(900)] + + async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: """Test preset mode.""" device = FritzDeviceClimateMock()