diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index b8e78a9ee5c..791039add31 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -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, + ), ) diff --git a/homeassistant/components/fritzbox/climate.py b/homeassistant/components/fritzbox/climate.py index 0c6c2141c12..194bc5621b3 100644 --- a/homeassistant/components/fritzbox/climate.py +++ b/homeassistant/components/fritzbox/climate.py @@ -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, } diff --git a/homeassistant/components/fritzbox/icons.json b/homeassistant/components/fritzbox/icons.json index 5eb819cdde8..4557b23511c 100644 --- a/homeassistant/components/fritzbox/icons.json +++ b/homeassistant/components/fritzbox/icons.json @@ -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": { diff --git a/homeassistant/components/fritzbox/strings.json b/homeassistant/components/fritzbox/strings.json index e0df30875bc..bb7d2f0fdf1 100644 --- a/homeassistant/components/fritzbox/strings.json +++ b/homeassistant/components/fritzbox/strings.json @@ -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": { diff --git a/tests/components/fritzbox/snapshots/test_binary_sensor.ambr b/tests/components/fritzbox/snapshots/test_binary_sensor.ambr index 5b3e00dfa93..1d645947ceb 100644 --- a/tests/components/fritzbox/snapshots/test_binary_sensor.ambr +++ b/tests/components/fritzbox/snapshots/test_binary_sensor.ambr @@ -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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.fake_name_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + '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': , + 'entity_id': 'binary_sensor.fake_name_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + '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': , + 'entity_id': 'binary_sensor.fake_name_holiday_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_setup[binary_sensor.fake_name_open_window_detected-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + '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': , + 'entity_id': 'binary_sensor.fake_name_open_window_detected', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_setup[binary_sensor.fake_name_summer_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + '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': , + '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': , + 'entity_id': 'binary_sensor.fake_name_summer_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/fritzbox/test_binary_sensor.py b/tests/components/fritzbox/test_binary_sensor.py index 5a300b6643a..3eac2c24953 100644 --- a/tests/components/fritzbox/test_binary_sensor.py +++ b/tests/components/fritzbox/test_binary_sensor.py @@ -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, diff --git a/tests/components/fritzbox/test_coordinator.py b/tests/components/fritzbox/test_coordinator.py index 3e51ff38260..f4f4da90181 100644 --- a/tests/components/fritzbox/test_coordinator.py +++ b/tests/components/fritzbox/test_coordinator.py @@ -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