netatmo: add battery sensor for doortags (#168202)

This commit is contained in:
Zoltán Farkasdi
2026-04-28 14:22:26 +02:00
committed by GitHub
parent 955e8362e4
commit 0280d921e5
5 changed files with 421 additions and 81 deletions
+2 -1
View File
@@ -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"
@@ -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,
+274 -75
View File
@@ -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()
@@ -495,6 +495,37 @@
'via_device_id': None,
})
# ---
# name: test_devices[netatmo-12:34:56:00:86:99]
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://home.netatmo.com/security',
'connections': set({
}),
'disabled_by': None,
'entry_type': None,
'hw_version': None,
'id': <ANY>,
'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': <ANY>,
'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': <ANY>,
'config_entries_subentries': <ANY>,
'configuration_url': 'https://my.netatmo.com/app/energy',
'configuration_url': 'https://home.netatmo.com/control',
'connections': set({
}),
'disabled_by': None,
@@ -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': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.window_hall_battery',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Battery',
'options': dict({
}),
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
'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': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.window_hall_battery',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---
# name: test_entity[sensor.window_hall_rf_strength-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.window_hall_rf_strength',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.window_hall_rf_strength',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unavailable',
})
# ---