diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 57c7e2a696f..0c6c2141c12 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -53,8 +53,11 @@ MAX_TEMPERATURE = 28 # special temperatures for on/off in Fritz!Box API (modified by pyfritzhome) ON_API_TEMPERATURE = 127.0 OFF_API_TEMPERATURE = 126.5 -ON_REPORT_SET_TEMPERATURE = 30.0 -OFF_REPORT_SET_TEMPERATURE = 0.0 +PRESET_API_HKR_STATE_MAPPING = { + PRESET_COMFORT: "comfort", + PRESET_BOOST: "on", + PRESET_ECO: "eco", +} async def async_setup_entry( @@ -128,29 +131,28 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity): return self.data.actual_temperature # type: ignore [no-any-return] @property - def target_temperature(self) -> float: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" - if self.data.target_temperature == ON_API_TEMPERATURE: - return ON_REPORT_SET_TEMPERATURE - if self.data.target_temperature == OFF_API_TEMPERATURE: - return OFF_REPORT_SET_TEMPERATURE + if self.data.target_temperature in [ON_API_TEMPERATURE, OFF_API_TEMPERATURE]: + return None return self.data.target_temperature # type: ignore [no-any-return] + async def async_set_hkr_state(self, hkr_state: str) -> None: + """Set the state of the climate.""" + await self.hass.async_add_executor_job(self.data.set_hkr_state, hkr_state, True) + await self.coordinator.async_refresh() + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if (hvac_mode := kwargs.get(ATTR_HVAC_MODE)) is HVACMode.OFF: - await self.async_set_hvac_mode(hvac_mode) + if kwargs.get(ATTR_HVAC_MODE) is HVACMode.OFF: + await self.async_set_hkr_state("off") elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None: - if target_temp == OFF_API_TEMPERATURE: - target_temp = OFF_REPORT_SET_TEMPERATURE - elif target_temp == ON_API_TEMPERATURE: - target_temp = ON_REPORT_SET_TEMPERATURE await self.hass.async_add_executor_job( self.data.set_target_temperature, target_temp, True ) + await self.coordinator.async_refresh() else: return - await self.coordinator.async_refresh() @property def hvac_mode(self) -> HVACMode: @@ -159,10 +161,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity): return HVACMode.HEAT if self.data.summer_active: return HVACMode.OFF - if self.data.target_temperature in ( - OFF_REPORT_SET_TEMPERATURE, - OFF_API_TEMPERATURE, - ): + if self.data.target_temperature == OFF_API_TEMPERATURE: return HVACMode.OFF return HVACMode.HEAT @@ -180,7 +179,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity): ) return if hvac_mode is HVACMode.OFF: - await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE) + await self.async_set_hkr_state("off") else: if value_scheduled_preset(self.data) == PRESET_ECO: target_temp = self.data.eco_temperature @@ -210,12 +209,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity): translation_domain=DOMAIN, translation_key="change_preset_while_active_mode", ) - if preset_mode == PRESET_COMFORT: - await self.async_set_temperature(temperature=self.data.comfort_temperature) - elif preset_mode == PRESET_ECO: - await self.async_set_temperature(temperature=self.data.eco_temperature) - elif preset_mode == PRESET_BOOST: - await self.async_set_temperature(temperature=ON_REPORT_SET_TEMPERATURE) + await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode]) @property def extra_state_attributes(self) -> ClimateExtraAttributes: diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index e21191fcbbb..5bf81ef0238 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -94,7 +94,7 @@ async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock) -> None: state = hass.states.get(ENTITY_ID) assert state - assert state.attributes[ATTR_TEMPERATURE] == 30 + assert state.attributes[ATTR_TEMPERATURE] is None async def test_target_temperature_off(hass: HomeAssistant, fritz: Mock) -> None: @@ -107,7 +107,7 @@ async def test_target_temperature_off(hass: HomeAssistant, fritz: Mock) -> None: state = hass.states.get(ENTITY_ID) assert state - assert state.attributes[ATTR_TEMPERATURE] == 0 + assert state.attributes[ATTR_TEMPERATURE] is None async def test_update(hass: HomeAssistant, fritz: Mock) -> None: @@ -177,15 +177,20 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: @pytest.mark.parametrize( - ("service_data", "expected_call_args"), + ( + "service_data", + "expected_set_target_temperature_call_args", + "expected_set_hkr_state_call_args", + ), [ - ({ATTR_TEMPERATURE: 23}, [call(23, True)]), + ({ATTR_TEMPERATURE: 23}, [call(23, True)], []), ( { ATTR_HVAC_MODE: HVACMode.OFF, ATTR_TEMPERATURE: 23, }, - [call(0, True)], + [], + [call("off", True)], ), ( { @@ -193,6 +198,7 @@ async def test_update_error(hass: HomeAssistant, fritz: Mock) -> None: ATTR_TEMPERATURE: 23, }, [call(23, True)], + [], ), ], ) @@ -200,7 +206,8 @@ async def test_set_temperature( hass: HomeAssistant, fritz: Mock, service_data: dict, - expected_call_args: list[_Call], + expected_set_target_temperature_call_args: list[_Call], + expected_set_hkr_state_call_args: list[_Call], ) -> None: """Test setting temperature.""" device = FritzDeviceClimateMock() @@ -214,29 +221,60 @@ async def test_set_temperature( {ATTR_ENTITY_ID: ENTITY_ID, **service_data}, True, ) - assert device.set_target_temperature.call_count == len(expected_call_args) - assert device.set_target_temperature.call_args_list == expected_call_args + assert device.set_target_temperature.call_count == len( + expected_set_target_temperature_call_args + ) + assert ( + device.set_target_temperature.call_args_list + == expected_set_target_temperature_call_args + ) + assert device.set_hkr_state.call_count == len(expected_set_hkr_state_call_args) + assert device.set_hkr_state.call_args_list == expected_set_hkr_state_call_args @pytest.mark.parametrize( - ("service_data", "target_temperature", "current_preset", "expected_call_args"), + ( + "service_data", + "target_temperature", + "current_preset", + "expected_set_target_temperature_call_args", + "expected_set_hkr_state_call_args", + ), [ - # mode off always sets target temperature to 0 - ({ATTR_HVAC_MODE: HVACMode.OFF}, 22, PRESET_COMFORT, [call(0, True)]), - ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, PRESET_ECO, [call(0, True)]), - ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, None, [call(0, True)]), + # mode off always sets hkr state off + ({ATTR_HVAC_MODE: HVACMode.OFF}, 22, PRESET_COMFORT, [], [call("off", True)]), + ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, PRESET_ECO, [], [call("off", True)]), + ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, None, [], [call("off", True)]), # mode heat sets target temperature based on current scheduled preset, # when not already in mode heat - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 0.0, PRESET_COMFORT, [call(22, True)]), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 0.0, PRESET_ECO, [call(16, True)]), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 0.0, None, [call(22, True)]), + ( + {ATTR_HVAC_MODE: HVACMode.HEAT}, + OFF_API_TEMPERATURE, + PRESET_COMFORT, + [call(22, True)], + [], + ), + ( + {ATTR_HVAC_MODE: HVACMode.HEAT}, + OFF_API_TEMPERATURE, + PRESET_ECO, + [call(16, True)], + [], + ), + ( + {ATTR_HVAC_MODE: HVACMode.HEAT}, + OFF_API_TEMPERATURE, + None, + [call(22, True)], + [], + ), # mode heat does not set target temperature, when already in mode heat - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_COMFORT, []), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_ECO, []), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, None, []), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_COMFORT, []), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_ECO, []), - ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, None, []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_COMFORT, [], []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_ECO, [], []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, None, [], []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_COMFORT, [], []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_ECO, [], []), + ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, None, [], []), ], ) async def test_set_hvac_mode( @@ -245,7 +283,8 @@ async def test_set_hvac_mode( service_data: dict, target_temperature: float, current_preset: str, - expected_call_args: list[_Call], + expected_set_target_temperature_call_args: list[_Call], + expected_set_hkr_state_call_args: list[_Call], ) -> None: """Test setting hvac mode.""" device = FritzDeviceClimateMock() @@ -269,16 +308,23 @@ async def test_set_hvac_mode( True, ) - assert device.set_target_temperature.call_count == len(expected_call_args) - assert device.set_target_temperature.call_args_list == expected_call_args + assert device.set_target_temperature.call_count == len( + expected_set_target_temperature_call_args + ) + assert ( + device.set_target_temperature.call_args_list + == expected_set_target_temperature_call_args + ) + assert device.set_hkr_state.call_count == len(expected_set_hkr_state_call_args) + assert device.set_hkr_state.call_args_list == expected_set_hkr_state_call_args @pytest.mark.parametrize( ("comfort_temperature", "expected_call_args"), [ - (20, [call(20, True)]), - (28, [call(28, True)]), - (ON_API_TEMPERATURE, [call(30, True)]), + (20, [call("comfort", True)]), + (28, [call("comfort", True)]), + (ON_API_TEMPERATURE, [call("comfort", True)]), ], ) async def test_set_preset_mode_comfort( @@ -300,16 +346,16 @@ async def test_set_preset_mode_comfort( {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_COMFORT}, True, ) - assert device.set_target_temperature.call_count == len(expected_call_args) - assert device.set_target_temperature.call_args_list == expected_call_args + assert device.set_hkr_state.call_count == len(expected_call_args) + assert device.set_hkr_state.call_args_list == expected_call_args @pytest.mark.parametrize( ("eco_temperature", "expected_call_args"), [ - (20, [call(20, True)]), - (16, [call(16, True)]), - (OFF_API_TEMPERATURE, [call(0, True)]), + (20, [call("eco", True)]), + (16, [call("eco", True)]), + (OFF_API_TEMPERATURE, [call("eco", True)]), ], ) async def test_set_preset_mode_eco( @@ -331,8 +377,8 @@ async def test_set_preset_mode_eco( {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO}, True, ) - assert device.set_target_temperature.call_count == len(expected_call_args) - assert device.set_target_temperature.call_args_list == expected_call_args + assert device.set_hkr_state.call_count == len(expected_call_args) + assert device.set_hkr_state.call_args_list == expected_call_args async def test_set_preset_mode_boost( @@ -351,8 +397,8 @@ async def test_set_preset_mode_boost( {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_BOOST}, True, ) - assert device.set_target_temperature.call_count == 1 - assert device.set_target_temperature.call_args_list == [call(30, True)] + assert device.set_hkr_state.call_count == 1 + assert device.set_hkr_state.call_args_list == [call("on", True)] async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: