Migrate climate attributes to own entities in AVM Fritz!SmartHome (#143394)

* migrate climate attributes to own entities

* add a comment to make it searchable

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Apply suggestions from code review

* update snapshots

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Michael 2025-04-30 18:37:58 +02:00 committed by GitHub
parent 6a514ac2de
commit e53f380710
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 247 additions and 3 deletions

View File

@ -55,6 +55,32 @@ BINARY_SENSOR_TYPES: Final[tuple[FritzBinarySensorEntityDescription, ...]] = (
suitable=lambda device: device.device_lock is not None,
is_on=lambda device: not device.device_lock,
),
FritzBinarySensorEntityDescription(
key="battery_low",
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
suitable=lambda device: device.battery_low is not None,
is_on=lambda device: device.battery_low,
entity_registry_enabled_default=False,
),
FritzBinarySensorEntityDescription(
key="holiday_active",
translation_key="holiday_active",
suitable=lambda device: device.holiday_active is not None,
is_on=lambda device: device.holiday_active,
),
FritzBinarySensorEntityDescription(
key="summer_active",
translation_key="summer_active",
suitable=lambda device: device.summer_active is not None,
is_on=lambda device: device.summer_active,
),
FritzBinarySensorEntityDescription(
key="window_open",
translation_key="window_open",
suitable=lambda device: device.window_open is not None,
is_on=lambda device: device.window_open,
),
)

View File

@ -214,6 +214,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
@property
def extra_state_attributes(self) -> ClimateExtraAttributes:
"""Return the device specific state attributes."""
# deprecated with #143394, can be removed in 2025.11
attrs: ClimateExtraAttributes = {
ATTR_STATE_BATTERY_LOW: self.data.battery_low,
}

View File

@ -1,5 +1,28 @@
{
"entity": {
"binary_sensor": {
"holiday_active": {
"default": "mdi:bag-suitcase-outline",
"state": {
"on": "mdi:bag-suitcase-outline",
"off": "mdi:bag-suitcase-off-outline"
}
},
"summer_active": {
"default": "mdi:radiator-off",
"state": {
"on": "mdi:radiator-off",
"off": "mdi:radiator"
}
},
"window_open": {
"default": "mdi:window-open",
"state": {
"on": "mdi:window-open",
"off": "mdi:window-closed"
}
}
},
"climate": {
"thermostat": {
"state_attributes": {

View File

@ -55,7 +55,10 @@
"binary_sensor": {
"alarm": { "name": "Alarm" },
"device_lock": { "name": "Button lock via UI" },
"lock": { "name": "Button lock on device" }
"holiday_active": { "name": "Holiday mode" },
"lock": { "name": "Button lock on device" },
"summer_active": { "name": "Summer mode" },
"window_open": { "name": "Open window detected" }
},
"climate": {
"thermostat": {

View File

@ -47,6 +47,54 @@
'state': 'on',
})
# ---
# name: test_setup[binary_sensor.fake_name_battery-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.fake_name_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <BinarySensorDeviceClass.BATTERY: 'battery'>,
'original_icon': None,
'original_name': 'Battery',
'platform': 'fritzbox',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12345 1234567_battery_low',
'unit_of_measurement': None,
})
# ---
# name: test_setup[binary_sensor.fake_name_battery-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'battery',
'friendly_name': 'fake_name Battery',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.fake_name_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_setup[binary_sensor.fake_name_button_lock_on_device-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -143,3 +191,144 @@
'state': 'off',
})
# ---
# name: test_setup[binary_sensor.fake_name_holiday_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.fake_name_holiday_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Holiday mode',
'platform': 'fritzbox',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'holiday_active',
'unique_id': '12345 1234567_holiday_active',
'unit_of_measurement': None,
})
# ---
# name: test_setup[binary_sensor.fake_name_holiday_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'fake_name Holiday mode',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.fake_name_holiday_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_setup[binary_sensor.fake_name_open_window_detected-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.fake_name_open_window_detected',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Open window detected',
'platform': 'fritzbox',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'window_open',
'unique_id': '12345 1234567_window_open',
'unit_of_measurement': None,
})
# ---
# name: test_setup[binary_sensor.fake_name_open_window_detected-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'fake_name Open window detected',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.fake_name_open_window_detected',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_setup[binary_sensor.fake_name_summer_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': None,
'entity_id': 'binary_sensor.fake_name_summer_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Summer mode',
'platform': 'fritzbox',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'summer_active',
'unique_id': '12345 1234567_summer_active',
'unit_of_measurement': None,
})
# ---
# name: test_setup[binary_sensor.fake_name_summer_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'fake_name Summer mode',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.fake_name_summer_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@ -4,6 +4,7 @@ from datetime import timedelta
from unittest import mock
from unittest.mock import Mock, patch
import pytest
from requests.exceptions import HTTPError
from syrupy import SnapshotAssertion
@ -23,6 +24,7 @@ from tests.common import async_fire_time_changed, snapshot_platform
ENTITY_ID = f"{BINARY_SENSOR_DOMAIN}.{CONF_FAKE_NAME}"
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,

View File

@ -105,7 +105,7 @@ async def test_coordinator_automatic_registry_cleanup(
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 11
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 19
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 2
fritz().get_devices.return_value = [
@ -119,5 +119,5 @@ async def test_coordinator_automatic_registry_cleanup(
async_fire_time_changed(hass, utcnow() + timedelta(seconds=35))
await hass.async_block_till_done(wait_background_tasks=True)
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 8
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 12
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 1