mirror of
https://github.com/home-assistant/core.git
synced 2026-09-10 15:41:22 +01:00
netatmo: add battery sensor for doortags (#168202)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
# ---
|
||||
|
||||
Reference in New Issue
Block a user