Remove boost and off temperature workaround from AVM Fritz!SmartHome (#142863)

* remove workaround

* remove hvacmode from mapping dict
This commit is contained in:
Michael 2025-04-23 16:12:35 +02:00 committed by GitHub
parent 1bfd585f3c
commit 253cc377b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 102 additions and 62 deletions

View File

@ -53,8 +53,11 @@ MAX_TEMPERATURE = 28
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome) # special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
ON_API_TEMPERATURE = 127.0 ON_API_TEMPERATURE = 127.0
OFF_API_TEMPERATURE = 126.5 OFF_API_TEMPERATURE = 126.5
ON_REPORT_SET_TEMPERATURE = 30.0 PRESET_API_HKR_STATE_MAPPING = {
OFF_REPORT_SET_TEMPERATURE = 0.0 PRESET_COMFORT: "comfort",
PRESET_BOOST: "on",
PRESET_ECO: "eco",
}
async def async_setup_entry( async def async_setup_entry(
@ -128,29 +131,28 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
return self.data.actual_temperature # type: ignore [no-any-return] return self.data.actual_temperature # type: ignore [no-any-return]
@property @property
def target_temperature(self) -> float: def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
if self.data.target_temperature == ON_API_TEMPERATURE: if self.data.target_temperature in [ON_API_TEMPERATURE, OFF_API_TEMPERATURE]:
return ON_REPORT_SET_TEMPERATURE return None
if self.data.target_temperature == OFF_API_TEMPERATURE:
return OFF_REPORT_SET_TEMPERATURE
return self.data.target_temperature # type: ignore [no-any-return] 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: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
if (hvac_mode := kwargs.get(ATTR_HVAC_MODE)) is HVACMode.OFF: if kwargs.get(ATTR_HVAC_MODE) is HVACMode.OFF:
await self.async_set_hvac_mode(hvac_mode) await self.async_set_hkr_state("off")
elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None: 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( await self.hass.async_add_executor_job(
self.data.set_target_temperature, target_temp, True self.data.set_target_temperature, target_temp, True
) )
await self.coordinator.async_refresh()
else: else:
return return
await self.coordinator.async_refresh()
@property @property
def hvac_mode(self) -> HVACMode: def hvac_mode(self) -> HVACMode:
@ -159,10 +161,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
return HVACMode.HEAT return HVACMode.HEAT
if self.data.summer_active: if self.data.summer_active:
return HVACMode.OFF return HVACMode.OFF
if self.data.target_temperature in ( if self.data.target_temperature == OFF_API_TEMPERATURE:
OFF_REPORT_SET_TEMPERATURE,
OFF_API_TEMPERATURE,
):
return HVACMode.OFF return HVACMode.OFF
return HVACMode.HEAT return HVACMode.HEAT
@ -180,7 +179,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
) )
return return
if hvac_mode is HVACMode.OFF: if hvac_mode is HVACMode.OFF:
await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE) await self.async_set_hkr_state("off")
else: else:
if value_scheduled_preset(self.data) == PRESET_ECO: if value_scheduled_preset(self.data) == PRESET_ECO:
target_temp = self.data.eco_temperature target_temp = self.data.eco_temperature
@ -210,12 +209,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="change_preset_while_active_mode", translation_key="change_preset_while_active_mode",
) )
if preset_mode == PRESET_COMFORT: await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode])
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)
@property @property
def extra_state_attributes(self) -> ClimateExtraAttributes: def extra_state_attributes(self) -> ClimateExtraAttributes:

View File

@ -94,7 +94,7 @@ async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock) -> None:
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert state 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: 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) state = hass.states.get(ENTITY_ID)
assert state assert state
assert state.attributes[ATTR_TEMPERATURE] == 0 assert state.attributes[ATTR_TEMPERATURE] is None
async def test_update(hass: HomeAssistant, fritz: Mock) -> 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( @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_HVAC_MODE: HVACMode.OFF,
ATTR_TEMPERATURE: 23, 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, ATTR_TEMPERATURE: 23,
}, },
[call(23, True)], [call(23, True)],
[],
), ),
], ],
) )
@ -200,7 +206,8 @@ async def test_set_temperature(
hass: HomeAssistant, hass: HomeAssistant,
fritz: Mock, fritz: Mock,
service_data: dict, 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: ) -> None:
"""Test setting temperature.""" """Test setting temperature."""
device = FritzDeviceClimateMock() device = FritzDeviceClimateMock()
@ -214,29 +221,60 @@ async def test_set_temperature(
{ATTR_ENTITY_ID: ENTITY_ID, **service_data}, {ATTR_ENTITY_ID: ENTITY_ID, **service_data},
True, True,
) )
assert device.set_target_temperature.call_count == len(expected_call_args) assert device.set_target_temperature.call_count == len(
assert device.set_target_temperature.call_args_list == expected_call_args 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( @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 # mode off always sets hkr state off
({ATTR_HVAC_MODE: HVACMode.OFF}, 22, PRESET_COMFORT, [call(0, True)]), ({ATTR_HVAC_MODE: HVACMode.OFF}, 22, PRESET_COMFORT, [], [call("off", True)]),
({ATTR_HVAC_MODE: HVACMode.OFF}, 16, PRESET_ECO, [call(0, True)]), ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, PRESET_ECO, [], [call("off", True)]),
({ATTR_HVAC_MODE: HVACMode.OFF}, 16, None, [call(0, True)]), ({ATTR_HVAC_MODE: HVACMode.OFF}, 16, None, [], [call("off", True)]),
# mode heat sets target temperature based on current scheduled preset, # mode heat sets target temperature based on current scheduled preset,
# when not already in mode heat # 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},
({ATTR_HVAC_MODE: HVACMode.HEAT}, 0.0, None, [call(22, True)]), 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 # 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_COMFORT, [], []),
({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_ECO, []), ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, PRESET_ECO, [], []),
({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, None, []), ({ATTR_HVAC_MODE: HVACMode.HEAT}, 16, None, [], []),
({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_COMFORT, []), ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_COMFORT, [], []),
({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_ECO, []), ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, PRESET_ECO, [], []),
({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, None, []), ({ATTR_HVAC_MODE: HVACMode.HEAT}, 22, None, [], []),
], ],
) )
async def test_set_hvac_mode( async def test_set_hvac_mode(
@ -245,7 +283,8 @@ async def test_set_hvac_mode(
service_data: dict, service_data: dict,
target_temperature: float, target_temperature: float,
current_preset: str, 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: ) -> None:
"""Test setting hvac mode.""" """Test setting hvac mode."""
device = FritzDeviceClimateMock() device = FritzDeviceClimateMock()
@ -269,16 +308,23 @@ async def test_set_hvac_mode(
True, True,
) )
assert device.set_target_temperature.call_count == len(expected_call_args) assert device.set_target_temperature.call_count == len(
assert device.set_target_temperature.call_args_list == expected_call_args 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( @pytest.mark.parametrize(
("comfort_temperature", "expected_call_args"), ("comfort_temperature", "expected_call_args"),
[ [
(20, [call(20, True)]), (20, [call("comfort", True)]),
(28, [call(28, True)]), (28, [call("comfort", True)]),
(ON_API_TEMPERATURE, [call(30, True)]), (ON_API_TEMPERATURE, [call("comfort", True)]),
], ],
) )
async def test_set_preset_mode_comfort( 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}, {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_COMFORT},
True, True,
) )
assert device.set_target_temperature.call_count == len(expected_call_args) assert device.set_hkr_state.call_count == len(expected_call_args)
assert device.set_target_temperature.call_args_list == expected_call_args assert device.set_hkr_state.call_args_list == expected_call_args
@pytest.mark.parametrize( @pytest.mark.parametrize(
("eco_temperature", "expected_call_args"), ("eco_temperature", "expected_call_args"),
[ [
(20, [call(20, True)]), (20, [call("eco", True)]),
(16, [call(16, True)]), (16, [call("eco", True)]),
(OFF_API_TEMPERATURE, [call(0, True)]), (OFF_API_TEMPERATURE, [call("eco", True)]),
], ],
) )
async def test_set_preset_mode_eco( 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}, {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO},
True, True,
) )
assert device.set_target_temperature.call_count == len(expected_call_args) assert device.set_hkr_state.call_count == len(expected_call_args)
assert device.set_target_temperature.call_args_list == expected_call_args assert device.set_hkr_state.call_args_list == expected_call_args
async def test_set_preset_mode_boost( 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}, {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_BOOST},
True, True,
) )
assert device.set_target_temperature.call_count == 1 assert device.set_hkr_state.call_count == 1
assert device.set_target_temperature.call_args_list == [call(30, True)] assert device.set_hkr_state.call_args_list == [call("on", True)]
async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: async def test_preset_mode_update(hass: HomeAssistant, fritz: Mock) -> None: