From 0280d921e521896c8d8668daba33a81da4e3e255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Farkasdi?= <93778865+farkasdi@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:22:26 +0200 Subject: [PATCH] netatmo: add battery sensor for doortags (#168202) --- homeassistant/components/netatmo/const.py | 3 +- .../components/netatmo/data_handler.py | 10 +- homeassistant/components/netatmo/sensor.py | 349 ++++++++++++++---- .../netatmo/snapshots/test_init.ambr | 33 +- .../netatmo/snapshots/test_sensor.ambr | 107 ++++++ 5 files changed, 421 insertions(+), 81 deletions(-) diff --git a/homeassistant/components/netatmo/const.py b/homeassistant/components/netatmo/const.py index e8812407e479..b10d2309770d 100644 --- a/homeassistant/components/netatmo/const.py +++ b/homeassistant/components/netatmo/const.py @@ -39,14 +39,15 @@ API_SCOPES_EXCLUDED_FROM_CLOUD = [ "write_mhs1", ] -NETATMO_CREATE_BATTERY = "netatmo_create_battery" NETATMO_CREATE_CAMERA = "netatmo_create_camera" NETATMO_CREATE_CAMERA_LIGHT = "netatmo_create_camera_light" NETATMO_CREATE_CLIMATE = "netatmo_create_climate" +NETATMO_CREATE_CLIMATE_BATTERY_SENSOR = "netatmo_create_climate_battery_sensor" NETATMO_CREATE_COVER = "netatmo_create_cover" NETATMO_CREATE_CONNECTIVITY_BINARY_SENSOR = "netatmo_create_connectivity_binary_sensor" NETATMO_CREATE_BUTTON = "netatmo_create_button" NETATMO_CREATE_FAN = "netatmo_create_fan" +NETATMO_CREATE_LEGACY_SENSOR = "netatmo_create_legacy_sensor" NETATMO_CREATE_LIGHT = "netatmo_create_light" NETATMO_CREATE_OPENING_BINARY_SENSOR = "netatmo_create_opening_binary_sensor" NETATMO_CREATE_ROOM_SENSOR = "netatmo_create_room_sensor" diff --git a/homeassistant/components/netatmo/data_handler.py b/homeassistant/components/netatmo/data_handler.py index 887549eac5fb..4961ea3bf973 100644 --- a/homeassistant/components/netatmo/data_handler.py +++ b/homeassistant/components/netatmo/data_handler.py @@ -33,14 +33,15 @@ from .const import ( DATA_SCHEDULES, DOMAIN, MANUFACTURER, - NETATMO_CREATE_BATTERY, NETATMO_CREATE_BUTTON, NETATMO_CREATE_CAMERA, NETATMO_CREATE_CAMERA_LIGHT, NETATMO_CREATE_CLIMATE, + NETATMO_CREATE_CLIMATE_BATTERY_SENSOR, NETATMO_CREATE_CONNECTIVITY_BINARY_SENSOR, NETATMO_CREATE_COVER, NETATMO_CREATE_FAN, + NETATMO_CREATE_LEGACY_SENSOR, NETATMO_CREATE_LIGHT, NETATMO_CREATE_OPENING_BINARY_SENSOR, NETATMO_CREATE_ROOM_SENSOR, @@ -372,13 +373,14 @@ class NetatmoDataHandler: NetatmoDeviceCategory.switch: [ NETATMO_CREATE_LIGHT, NETATMO_CREATE_SWITCH, - NETATMO_CREATE_SENSOR, + NETATMO_CREATE_LEGACY_SENSOR, ], - NetatmoDeviceCategory.meter: [NETATMO_CREATE_SENSOR], + NetatmoDeviceCategory.meter: [NETATMO_CREATE_LEGACY_SENSOR], NetatmoDeviceCategory.fan: [NETATMO_CREATE_FAN], NetatmoDeviceCategory.opening: [ NETATMO_CREATE_CONNECTIVITY_BINARY_SENSOR, NETATMO_CREATE_OPENING_BINARY_SENSOR, + NETATMO_CREATE_SENSOR, ], } for module in home.modules.values(): @@ -431,7 +433,7 @@ class NetatmoDataHandler: if module.device_category is NetatmoDeviceCategory.climate: async_dispatcher_send( self.hass, - NETATMO_CREATE_BATTERY, + NETATMO_CREATE_CLIMATE_BATTERY_SENSOR, NetatmoDevice( self, module, diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 058d948da629..a77765a5a08f 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -4,11 +4,13 @@ from __future__ import annotations from collections.abc import Callable from dataclasses import dataclass +from functools import partial import logging -from typing import Any, cast +from typing import Any, Final, cast import pyatmo from pyatmo.modules import PublicWeatherArea +from pyatmo.modules.device_types import DeviceCategory as NetatmoDeviceCategory from homeassistant.components.sensor import ( SensorDeviceClass, @@ -41,11 +43,14 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType from .const import ( + CONF_URL_CONTROL, CONF_URL_ENERGY, CONF_URL_PUBLIC_WEATHER, + CONF_URL_SECURITY, CONF_WEATHER_AREAS, DOMAIN, - NETATMO_CREATE_BATTERY, + NETATMO_CREATE_CLIMATE_BATTERY_SENSOR, + NETATMO_CREATE_LEGACY_SENSOR, NETATMO_CREATE_ROOM_SENSOR, NETATMO_CREATE_SENSOR, NETATMO_CREATE_WEATHER_SENSOR, @@ -123,11 +128,21 @@ def process_wifi(strength: StateType) -> str | None: class NetatmoSensorEntityDescription(SensorEntityDescription): """Describes Netatmo sensor entity.""" - netatmo_name: str + # For legacy sensors netatmo_name is set and is used as the translation_key! + # Legacy sensors are: weather, climate, switch and meter sensors, as they were the first ones implemented. + # For new sensors, translation_key should be set explicitly on key + # and netatmo_name should be used only to retrieve the value from the device. + # If the netatmo_name is not set, the key is used to retrieve the value from the device. + netatmo_name: str | None = None + # Mark sensors whose last known native_value may be retained when fresh data is unavailable. + # This is intended for sensors where the last reported value remains useful, such as battery + # level or a last known state. This flag does not by itself keep the entity available; the + # entity may still become unavailable when the device is unreachable. + is_sticky: bool | None = None value_fn: Callable[[StateType], StateType] = lambda x: x -SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = ( +NETATMO_WEATHER_SENSOR_DESCRIPTIONS: Final[list[NetatmoSensorEntityDescription]] = [ NetatmoSensorEntityDescription( key="temperature", netatmo_name="temperature", @@ -286,8 +301,7 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = ( state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.POWER, ), -) -SENSOR_TYPES_KEYS = [desc.key for desc in SENSOR_TYPES] +] @dataclass(frozen=True, kw_only=True) @@ -383,14 +397,73 @@ PUBLIC_WEATHER_STATION_TYPES: tuple[ ), ) -BATTERY_SENSOR_DESCRIPTION = NetatmoSensorEntityDescription( - key="battery", - netatmo_name="battery", - entity_category=EntityCategory.DIAGNOSTIC, - native_unit_of_measurement=PERCENTAGE, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.BATTERY, -) +NETATMO_CLIMATE_BATTERY_SENSOR_DESCRIPTIONS: Final[ + list[NetatmoSensorEntityDescription] +] = [ + NetatmoSensorEntityDescription( + key="battery", + netatmo_name="battery", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.BATTERY, + ) +] + +NETATMO_OPENING_SENSOR_DESCRIPTIONS: Final[list[NetatmoSensorEntityDescription]] = [ + NetatmoSensorEntityDescription( + key="battery", + netatmo_name="battery", + entity_category=EntityCategory.DIAGNOSTIC, + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.BATTERY, + is_sticky=True, + ), + NetatmoSensorEntityDescription( + key="rf_status", + netatmo_name="rf_strength", + entity_registry_enabled_default=False, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=process_rf, + ), +] + +DEVICE_CATEGORY_CLIMATE_BATTERY_SENSORS: Final[ + dict[NetatmoDeviceCategory, list[NetatmoSensorEntityDescription]] +] = { + NetatmoDeviceCategory.climate: NETATMO_CLIMATE_BATTERY_SENSOR_DESCRIPTIONS, +} + +DEVICE_CATEGORY_NEW_SENSORS: Final[ + dict[NetatmoDeviceCategory, list[NetatmoSensorEntityDescription]] +] = { + NetatmoDeviceCategory.opening: NETATMO_OPENING_SENSOR_DESCRIPTIONS, +} + +DEVICE_CATEGORY_WEATHER_SENSORS: Final[ + dict[NetatmoDeviceCategory, list[NetatmoSensorEntityDescription]] +] = { + NetatmoDeviceCategory.air_care: NETATMO_WEATHER_SENSOR_DESCRIPTIONS, + NetatmoDeviceCategory.weather: NETATMO_WEATHER_SENSOR_DESCRIPTIONS, +} + +# Duplicate for meter, climate, switch sensors for legacy reasons +# (as originally weather definitions reused - target for future simplification) +DEVICE_CATEGORY_LEGACY_SENSORS: Final[ + dict[NetatmoDeviceCategory, list[NetatmoSensorEntityDescription]] +] = { + NetatmoDeviceCategory.meter: NETATMO_WEATHER_SENSOR_DESCRIPTIONS, + NetatmoDeviceCategory.switch: NETATMO_WEATHER_SENSOR_DESCRIPTIONS, + NetatmoDeviceCategory.climate: NETATMO_WEATHER_SENSOR_DESCRIPTIONS, +} + +DEVICE_CATEGORY_SENSOR_URLS: Final[dict[NetatmoDeviceCategory, str]] = { + NetatmoDeviceCategory.climate: CONF_URL_ENERGY, + NetatmoDeviceCategory.meter: CONF_URL_ENERGY, + NetatmoDeviceCategory.opening: CONF_URL_SECURITY, + NetatmoDeviceCategory.switch: CONF_URL_CONTROL, +} async def async_setup_entry( @@ -401,46 +474,76 @@ async def async_setup_entry( """Set up the Netatmo sensor platform.""" @callback - def _create_battery_entity(netatmo_device: NetatmoDevice) -> None: - if not hasattr(netatmo_device.device, "battery"): + def _create_base_sensor_entity( + sensorClass: type[NetatmoBaseSensor], + descriptions: dict[NetatmoDeviceCategory, list[NetatmoSensorEntityDescription]], + netatmo_device: NetatmoDevice, + ) -> None: + """Create sensor entities for a Netatmo device.""" + + if netatmo_device.device.device_category is None: return - entity = NetatmoClimateBatterySensor(netatmo_device) - async_add_entities([entity]) - entry.async_on_unload( - async_dispatcher_connect(hass, NETATMO_CREATE_BATTERY, _create_battery_entity) - ) - - @callback - def _create_weather_sensor_entity(netatmo_device: NetatmoDevice) -> None: - async_add_entities( - NetatmoWeatherSensor(netatmo_device, description) - for description in SENSOR_TYPES - if description.netatmo_name in netatmo_device.device.features + descriptions_to_add = descriptions.get( + netatmo_device.device.device_category, [] ) - entry.async_on_unload( - async_dispatcher_connect( - hass, NETATMO_CREATE_WEATHER_SENSOR, _create_weather_sensor_entity - ) - ) + entities: list[NetatmoBaseSensor] = [] - @callback - def _create_sensor_entity(netatmo_device: NetatmoDevice) -> None: - _LOGGER.debug( - "Adding %s sensor %s", - netatmo_device.device.device_category, - netatmo_device.device.name, - ) - async_add_entities( - NetatmoSensor(netatmo_device, description) - for description in SENSOR_TYPES - if description.key in netatmo_device.device.features - ) + # Create sensors for module + for description in descriptions_to_add: + if description.netatmo_name is None: + feature_check = description.key + else: + feature_check = description.netatmo_name + if feature_check in netatmo_device.device.features: + _LOGGER.debug( + 'Adding key = "%s" / netatmo_name = "%s" sensor for device %s', + description.key, + description.netatmo_name, + netatmo_device.device.name, + ) + entities.append( + sensorClass( + netatmo_device, + description, + ) + ) - entry.async_on_unload( - async_dispatcher_connect(hass, NETATMO_CREATE_SENSOR, _create_sensor_entity) - ) + if entities: + async_add_entities(entities) + + sensor_subscriptions = [ + ( + NETATMO_CREATE_CLIMATE_BATTERY_SENSOR, + NetatmoClimateBatterySensor, + DEVICE_CATEGORY_CLIMATE_BATTERY_SENSORS, + ), + ( + NETATMO_CREATE_SENSOR, + NetatmoSensor, + DEVICE_CATEGORY_NEW_SENSORS, + ), + ( + NETATMO_CREATE_WEATHER_SENSOR, + NetatmoWeatherSensor, + DEVICE_CATEGORY_WEATHER_SENSORS, + ), + ( + NETATMO_CREATE_LEGACY_SENSOR, + NetatmoLegacySensor, + DEVICE_CATEGORY_LEGACY_SENSORS, + ), + ] + + for signal, sensor_class, descriptions in sensor_subscriptions: + entry.async_on_unload( + async_dispatcher_connect( + hass, + signal, + partial(_create_base_sensor_entity, sensor_class, descriptions), + ) + ) @callback def _create_room_sensor_entity(netatmo_device: NetatmoRoom) -> None: @@ -448,9 +551,14 @@ async def async_setup_entry( msg = f"No climate type found for this room: {netatmo_device.room.name}" _LOGGER.debug(msg) return + + descriptions_to_add = DEVICE_CATEGORY_LEGACY_SENSORS.get( + NetatmoDeviceCategory.climate, [] + ) + async_add_entities( NetatmoRoomSensor(netatmo_device, description) - for description in SENSOR_TYPES + for description in descriptions_to_add if description.key in netatmo_device.room.features ) @@ -518,7 +626,54 @@ async def async_setup_entry( await add_public_entities(False) -class NetatmoWeatherSensor(NetatmoWeatherModuleEntity, SensorEntity): +class NetatmoBaseSensor(NetatmoModuleEntity, SensorEntity): + """Implementation of a Netatmo sensor.""" + + entity_description: NetatmoSensorEntityDescription + + def __init__( + self, + netatmo_device: NetatmoDevice, + description: NetatmoSensorEntityDescription, + **kwargs: Any, + ) -> None: + """Initialize the sensor.""" + + # To prevent exception about missing URL we need to set it explicitly + if netatmo_device.device.device_category is not None: + if ( + DEVICE_CATEGORY_SENSOR_URLS.get(netatmo_device.device.device_category) + is not None + ): + self._attr_configuration_url = DEVICE_CATEGORY_SENSOR_URLS[ + netatmo_device.device.device_category + ] + + super().__init__(netatmo_device, **kwargs) + self.entity_description = description + + # Legacy value retrieval for weather, climate, switch and meter sensors to prevent breaking changes, + # as they were the first ones implemented. + @callback + def async_update_callback(self) -> None: + """Update the entity's state (the legacy way).""" + # Keep the last known value for these legacy sensors when the device is + # unreachable to preserve the historical behavior expected by existing entities. + if not self.device.reachable: + if self.available: + self._attr_available = False + return + + if (state := getattr(self.device, self.entity_description.key)) is None: + return + + self._attr_available = True + self._attr_native_value = state + + self.async_write_ha_state() + + +class NetatmoWeatherSensor(NetatmoWeatherModuleEntity, NetatmoBaseSensor): """Implementation of a Netatmo weather/home coach sensor.""" entity_description: NetatmoSensorEntityDescription @@ -529,7 +684,7 @@ class NetatmoWeatherSensor(NetatmoWeatherModuleEntity, SensorEntity): description: NetatmoSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device) + super().__init__(netatmo_device, description=description) self.entity_description = description self._attr_translation_key = description.netatmo_name self._attr_unique_id = f"{self.device.entity_id}-{description.key}" @@ -539,14 +694,22 @@ class NetatmoWeatherSensor(NetatmoWeatherModuleEntity, SensorEntity): """Return True if entity is available.""" return ( self.device.reachable - or getattr(self.device, self.entity_description.netatmo_name) is not None + or getattr( + self.device, + self.entity_description.netatmo_name or self.entity_description.key, + ) + is not None ) @callback def async_update_callback(self) -> None: """Update the entity's state.""" value = cast( - StateType, getattr(self.device, self.entity_description.netatmo_name) + StateType, + getattr( + self.device, + self.entity_description.netatmo_name or self.entity_description.key, + ), ) if value is not None: value = self.entity_description.value_fn(value) @@ -554,28 +717,53 @@ class NetatmoWeatherSensor(NetatmoWeatherModuleEntity, SensorEntity): self.async_write_ha_state() -class NetatmoClimateBatterySensor(NetatmoModuleEntity, SensorEntity): - """Implementation of a Netatmo sensor.""" +class NetatmoLegacySensor(NetatmoBaseSensor): + """Implementation of a Netatmo legacy sensor.""" + + # Legacy sensors are sensors that were implemented before the refactor (like climate, meter and switch) + # and that still use the old way (weather style) of retrieving values from the device, entity_description: NetatmoSensorEntityDescription - device: pyatmo.modules.NRV - _attr_configuration_url = CONF_URL_ENERGY - def __init__(self, netatmo_device: NetatmoDevice) -> None: + def __init__( + self, + netatmo_device: NetatmoDevice, + description: NetatmoSensorEntityDescription, + ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device) - self.entity_description = BATTERY_SENSOR_DESCRIPTION + super().__init__(netatmo_device, description=description) + + self.entity_description = description self._publishers.extend( [ { "name": HOME, - "home_id": netatmo_device.device.home.entity_id, + "home_id": self.home.entity_id, SIGNAL_NAME: netatmo_device.signal_name, }, ] ) + self._attr_unique_id = ( + f"{self.device.entity_id}-{self.device.entity_id}-{description.key}" + ) + + +class NetatmoClimateBatterySensor(NetatmoLegacySensor): + """Implementation of a Netatmo Climate Battery sensor.""" + + entity_description: NetatmoSensorEntityDescription + device: pyatmo.modules.NRV + + def __init__( + self, + netatmo_device: NetatmoDevice, + description: NetatmoSensorEntityDescription, + ) -> None: + """Initialize the sensor.""" + super().__init__(netatmo_device, description=description) + self._attr_unique_id = f"{netatmo_device.parent_id}-{self.device.entity_id}-{self.entity_description.key}" self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, netatmo_device.parent_id)}, @@ -595,13 +783,13 @@ class NetatmoClimateBatterySensor(NetatmoModuleEntity, SensorEntity): self._attr_available = True self._attr_native_value = self.device.battery + self.async_write_ha_state() -class NetatmoSensor(NetatmoModuleEntity, SensorEntity): - """Implementation of a Netatmo sensor.""" +class NetatmoSensor(NetatmoBaseSensor): + """Implementation of a Netatmo refactored sensor.""" entity_description: NetatmoSensorEntityDescription - _attr_configuration_url = CONF_URL_ENERGY def __init__( self, @@ -609,36 +797,47 @@ class NetatmoSensor(NetatmoModuleEntity, SensorEntity): description: NetatmoSensorEntityDescription, ) -> None: """Initialize the sensor.""" - super().__init__(netatmo_device) + super().__init__(netatmo_device, description=description) self.entity_description = description + self._attr_translation_key = description.netatmo_name + self._attr_unique_id = f"{self.device.entity_id}-{description.key}" self._publishers.extend( [ { - "name": HOME, + "name": self.home.entity_id, "home_id": self.home.entity_id, SIGNAL_NAME: netatmo_device.signal_name, }, ] ) - self._attr_unique_id = ( - f"{self.device.entity_id}-{self.device.entity_id}-{description.key}" - ) - + # New sensor implementation optional netatmo_name to retrieve value from device, if not set key is used + # Value is set unavailable if device is not reachable except is_sticky, + # otherwise it is set to the processed value @callback def async_update_callback(self) -> None: """Update the entity's state.""" if not self.device.reachable: if self.available: self._attr_available = False - return + if not self.entity_description.is_sticky: + self._attr_native_value = None + else: + if self.entity_description.netatmo_name is None: + raw_value = getattr(self.device, self.entity_description.key, None) + else: + raw_value = getattr( + self.device, self.entity_description.netatmo_name, None + ) - if (state := getattr(self.device, self.entity_description.key)) is None: - return + if raw_value is not None: + value = self.entity_description.value_fn(raw_value) + else: + value = None - self._attr_available = True - self._attr_native_value = state + self._attr_available = True + self._attr_native_value = value self.async_write_ha_state() diff --git a/tests/components/netatmo/snapshots/test_init.ambr b/tests/components/netatmo/snapshots/test_init.ambr index 3f8d924bdbf1..fd12cb9fb69c 100644 --- a/tests/components/netatmo/snapshots/test_init.ambr +++ b/tests/components/netatmo/snapshots/test_init.ambr @@ -495,6 +495,37 @@ 'via_device_id': None, }) # --- +# name: test_devices[netatmo-12:34:56:00:86:99] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': 'https://home.netatmo.com/security', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'netatmo', + '12:34:56:00:86:99', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Netatmo', + 'model': 'Smart Door/Window Sensors', + 'model_id': None, + 'name': 'Window Hall', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': None, + 'via_device_id': None, + }) +# --- # name: test_devices[netatmo-12:34:56:00:f1:62] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -779,7 +810,7 @@ 'area_id': None, 'config_entries': , 'config_entries_subentries': , - 'configuration_url': 'https://my.netatmo.com/app/energy', + 'configuration_url': 'https://home.netatmo.com/control', 'connections': set({ }), 'disabled_by': None, diff --git a/tests/components/netatmo/snapshots/test_sensor.ambr b/tests/components/netatmo/snapshots/test_sensor.ambr index 480629e6c11f..86d92d2c4f66 100644 --- a/tests/components/netatmo/snapshots/test_sensor.ambr +++ b/tests/components/netatmo/snapshots/test_sensor.ambr @@ -8187,3 +8187,110 @@ 'state': 'High', }) # --- +# name: test_entity[sensor.window_hall_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.window_hall_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Battery', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'netatmo', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'battery', + 'unique_id': '12:34:56:00:86:99-battery', + 'unit_of_measurement': '%', + }) +# --- +# name: test_entity[sensor.window_hall_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Netatmo', + 'device_class': 'battery', + 'friendly_name': 'Window Hall Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.window_hall_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# --- +# name: test_entity[sensor.window_hall_rf_strength-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.window_hall_rf_strength', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'RF strength', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'RF strength', + 'platform': 'netatmo', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'rf_strength', + 'unique_id': '12:34:56:00:86:99-rf_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_entity[sensor.window_hall_rf_strength-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by Netatmo', + 'friendly_name': 'Window Hall RF strength', + }), + 'context': , + 'entity_id': 'sensor.window_hall_rf_strength', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unavailable', + }) +# ---