Fix check for locked device in AVM Fritz!SmartHome (#141697)

* feat: raise execption on hvac mode while device is locked

* fix: test for setting hvac mode while device is locked.

* feat: update translation

* feat: add separate translations for HVAC and temperature

* fix: test cases

* fix: test cases for test_set_preset_mode_boost

* rev: code review

* rev: exception string

* feat: updated  error message and added helper function

* Update homeassistant/components/fritzbox/strings.json

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>

* fix: translation key

* remove check_active_or_lock_mode from async_set_preset_mode

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
This commit is contained in:
Florian Sabonchi 2025-05-03 20:25:27 +02:00 committed by GitHub
parent debec3bfbc
commit aea5760424
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 130 additions and 18 deletions

View File

@ -144,6 +144,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
self.check_active_or_lock_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:
@ -168,11 +169,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new operation mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_hvac_while_active_mode",
)
self.check_active_or_lock_mode()
if self.hvac_mode is hvac_mode:
LOGGER.debug(
"%s is already in requested hvac mode %s", self.name, hvac_mode
@ -204,11 +201,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_preset_while_active_mode",
)
self.check_active_or_lock_mode()
await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode])
@property
@ -230,3 +223,17 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
attrs[ATTR_STATE_WINDOW_OPEN] = self.data.window_open
return attrs
def check_active_or_lock_mode(self) -> None:
"""Check if in summer/vacation mode or lock enabled."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_active_mode",
)
if self.data.lock:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_lock_enabled",
)

View File

@ -88,11 +88,11 @@
"manual_switching_disabled": {
"message": "Can't toggle switch while manual switching is disabled for the device."
},
"change_preset_while_active_mode": {
"message": "Can't change preset while holiday or summer mode is active on the device."
"change_settings_while_lock_enabled": {
"message": "Can't change settings while manual access for telephone, app, or user interface is disabled on the device"
},
"change_hvac_while_active_mode": {
"message": "Can't change HVAC mode while holiday or summer mode is active on the device."
"change_settings_while_active_mode": {
"message": "Can't change settings while holiday or summer mode is active on the device."
}
}
}

View File

@ -211,6 +211,8 @@ async def test_set_temperature(
) -> None:
"""Test setting temperature."""
device = FritzDeviceClimateMock()
device.lock = False
await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
@ -288,6 +290,8 @@ async def test_set_hvac_mode(
) -> None:
"""Test setting hvac mode."""
device = FritzDeviceClimateMock()
device.lock = False
device.target_temperature = target_temperature
if current_preset is PRESET_COMFORT:
@ -335,6 +339,8 @@ async def test_set_preset_mode_comfort(
) -> None:
"""Test setting preset mode."""
device = FritzDeviceClimateMock()
device.lock = False
device.comfort_temperature = comfort_temperature
await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
@ -366,6 +372,8 @@ async def test_set_preset_mode_eco(
) -> None:
"""Test setting preset mode."""
device = FritzDeviceClimateMock()
device.lock = False
device.eco_temperature = eco_temperature
await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
@ -387,6 +395,8 @@ async def test_set_preset_mode_boost(
) -> None:
"""Test setting preset mode."""
device = FritzDeviceClimateMock()
device.lock = False
await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
@ -471,11 +481,106 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None:
assert state
@pytest.mark.parametrize(
"service_data",
[
{ATTR_TEMPERATURE: 23},
{
ATTR_HVAC_MODE: HVACMode.HEAT,
ATTR_TEMPERATURE: 25,
},
],
)
async def test_set_temperature_lock(
hass: HomeAssistant,
fritz: Mock,
service_data: dict,
) -> None:
"""Test setting temperature while device is locked."""
device = FritzDeviceClimateMock()
device.lock = True
assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
with pytest.raises(
HomeAssistantError,
match="Can't change settings while manual access for telephone, app, or user interface is disabled on the device",
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{ATTR_ENTITY_ID: ENTITY_ID, **service_data},
True,
)
@pytest.mark.parametrize(
("service_data", "target_temperature", "current_preset", "expected_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 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)]),
# 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, []),
],
)
async def test_set_hvac_mode_lock(
hass: HomeAssistant,
fritz: Mock,
service_data: dict,
target_temperature: float,
current_preset: str,
expected_call_args: list[_Call],
) -> None:
"""Test setting hvac mode while device is locked."""
device = FritzDeviceClimateMock()
device.lock = True
device.target_temperature = target_temperature
if current_preset is PRESET_COMFORT:
device.nextchange_temperature = device.eco_temperature
elif current_preset is PRESET_ECO:
device.nextchange_temperature = device.comfort_temperature
else:
device.nextchange_endperiod = 0
assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
with pytest.raises(
HomeAssistantError,
match="Can't change settings while manual access for telephone, app, or user interface is disabled on the device",
):
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: ENTITY_ID, **service_data},
True,
)
async def test_holidy_summer_mode(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, fritz: Mock
) -> None:
"""Test holiday and summer mode."""
device = FritzDeviceClimateMock()
device.lock = False
await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)
@ -510,7 +615,7 @@ async def test_holidy_summer_mode(
with pytest.raises(
HomeAssistantError,
match="Can't change HVAC mode while holiday or summer mode is active on the device",
match="Can't change settings while holiday or summer mode is active on the device",
):
await hass.services.async_call(
"climate",
@ -520,7 +625,7 @@ async def test_holidy_summer_mode(
)
with pytest.raises(
HomeAssistantError,
match="Can't change preset while holiday or summer mode is active on the device",
match="Can't change settings while holiday or summer mode is active on the device",
):
await hass.services.async_call(
"climate",
@ -546,7 +651,7 @@ async def test_holidy_summer_mode(
with pytest.raises(
HomeAssistantError,
match="Can't change HVAC mode while holiday or summer mode is active on the device",
match="Can't change settings while holiday or summer mode is active on the device",
):
await hass.services.async_call(
"climate",
@ -556,7 +661,7 @@ async def test_holidy_summer_mode(
)
with pytest.raises(
HomeAssistantError,
match="Can't change preset while holiday or summer mode is active on the device",
match="Can't change settings while holiday or summer mode is active on the device",
):
await hass.services.async_call(
"climate",