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)
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:

View File

@ -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: