Use EntityDescription - netatmo (#53568)

* Use EntityDescription - netatmo

* Add coverage exclude

* Fix coverage exclude comment
This commit is contained in:
Marc Mueller 2021-07-28 08:18:59 +02:00 committed by GitHub
parent b3b5ee10b1
commit b7f1f2330a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,12 +1,13 @@
"""Support for the Netatmo Weather Service.""" """Support for the Netatmo Weather Service."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import logging import logging
from typing import NamedTuple, cast from typing import NamedTuple, cast
import pyatmo import pyatmo
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
ATTR_LATITUDE, ATTR_LATITUDE,
@ -48,7 +49,7 @@ from .netatmo_entity_base import NetatmoBase
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORTED_PUBLIC_SENSOR_TYPES = [ SUPPORTED_PUBLIC_SENSOR_TYPES: tuple[str, ...] = (
"temperature", "temperature",
"pressure", "pressure",
"humidity", "humidity",
@ -57,175 +58,201 @@ SUPPORTED_PUBLIC_SENSOR_TYPES = [
"guststrength", "guststrength",
"sum_rain_1", "sum_rain_1",
"sum_rain_24", "sum_rain_24",
] )
class SensorMetadata(NamedTuple): @dataclass
"""Metadata for an individual sensor.""" class NetatmoSensorEntityDescription(SensorEntityDescription):
"""Describes Netatmo sensor entity."""
name: str _netatmo_name: str | None = None
netatmo_name: str
enable_default: bool def __post_init__(self) -> None:
unit: str | None = None """Ensure all required attributes are set."""
icon: str | None = None if self._netatmo_name is None: # pragma: no cover
device_class: str | None = None raise TypeError
self.netatmo_name = self._netatmo_name
SENSOR_TYPES: dict[str, SensorMetadata] = { SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
"temperature": SensorMetadata( NetatmoSensorEntityDescription(
"Temperature", key="temperature",
netatmo_name="Temperature", name="Temperature",
enable_default=True, _netatmo_name="Temperature",
unit=TEMP_CELSIUS, entity_registry_enabled_default=True,
unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
), ),
"temp_trend": SensorMetadata( NetatmoSensorEntityDescription(
"Temperature trend", key="temp_trend",
netatmo_name="temp_trend", name="Temperature trend",
enable_default=False, _netatmo_name="temp_trend",
entity_registry_enabled_default=False,
icon="mdi:trending-up", icon="mdi:trending-up",
), ),
"co2": SensorMetadata( NetatmoSensorEntityDescription(
"CO2", key="co2",
netatmo_name="CO2", name="CO2",
unit=CONCENTRATION_PARTS_PER_MILLION, _netatmo_name="CO2",
enable_default=True, unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
entity_registry_enabled_default=True,
device_class=DEVICE_CLASS_CO2, device_class=DEVICE_CLASS_CO2,
), ),
"pressure": SensorMetadata( NetatmoSensorEntityDescription(
"Pressure", key="pressure",
netatmo_name="Pressure", name="Pressure",
enable_default=True, _netatmo_name="Pressure",
unit=PRESSURE_MBAR, entity_registry_enabled_default=True,
unit_of_measurement=PRESSURE_MBAR,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
), ),
"pressure_trend": SensorMetadata( NetatmoSensorEntityDescription(
"Pressure trend", key="pressure_trend",
netatmo_name="pressure_trend", name="Pressure trend",
enable_default=False, _netatmo_name="pressure_trend",
entity_registry_enabled_default=False,
icon="mdi:trending-up", icon="mdi:trending-up",
), ),
"noise": SensorMetadata( NetatmoSensorEntityDescription(
"Noise", key="noise",
netatmo_name="Noise", name="Noise",
enable_default=True, _netatmo_name="Noise",
unit=SOUND_PRESSURE_DB, entity_registry_enabled_default=True,
unit_of_measurement=SOUND_PRESSURE_DB,
icon="mdi:volume-high", icon="mdi:volume-high",
), ),
"humidity": SensorMetadata( NetatmoSensorEntityDescription(
"Humidity", key="humidity",
netatmo_name="Humidity", name="Humidity",
enable_default=True, _netatmo_name="Humidity",
unit=PERCENTAGE, entity_registry_enabled_default=True,
unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
), ),
"rain": SensorMetadata( NetatmoSensorEntityDescription(
"Rain", key="rain",
netatmo_name="Rain", name="Rain",
enable_default=True, _netatmo_name="Rain",
unit=LENGTH_MILLIMETERS, entity_registry_enabled_default=True,
unit_of_measurement=LENGTH_MILLIMETERS,
icon="mdi:weather-rainy", icon="mdi:weather-rainy",
), ),
"sum_rain_1": SensorMetadata( NetatmoSensorEntityDescription(
"Rain last hour", key="sum_rain_1",
enable_default=False, name="Rain last hour",
netatmo_name="sum_rain_1", _netatmo_name="sum_rain_1",
unit=LENGTH_MILLIMETERS, entity_registry_enabled_default=False,
unit_of_measurement=LENGTH_MILLIMETERS,
icon="mdi:weather-rainy", icon="mdi:weather-rainy",
), ),
"sum_rain_24": SensorMetadata( NetatmoSensorEntityDescription(
"Rain today", key="sum_rain_24",
enable_default=True, name="Rain today",
netatmo_name="sum_rain_24", _netatmo_name="sum_rain_24",
unit=LENGTH_MILLIMETERS, entity_registry_enabled_default=True,
unit_of_measurement=LENGTH_MILLIMETERS,
icon="mdi:weather-rainy", icon="mdi:weather-rainy",
), ),
"battery_percent": SensorMetadata( NetatmoSensorEntityDescription(
"Battery Percent", key="battery_percent",
netatmo_name="battery_percent", name="Battery Percent",
enable_default=True, _netatmo_name="battery_percent",
unit=PERCENTAGE, entity_registry_enabled_default=True,
unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
), ),
"windangle": SensorMetadata( NetatmoSensorEntityDescription(
"Direction", key="windangle",
netatmo_name="WindAngle", name="Direction",
enable_default=True, _netatmo_name="WindAngle",
entity_registry_enabled_default=True,
icon="mdi:compass-outline", icon="mdi:compass-outline",
), ),
"windangle_value": SensorMetadata( NetatmoSensorEntityDescription(
"Angle", key="windangle_value",
netatmo_name="WindAngle", name="Angle",
enable_default=False, _netatmo_name="WindAngle",
unit=DEGREE, entity_registry_enabled_default=False,
unit_of_measurement=DEGREE,
icon="mdi:compass-outline", icon="mdi:compass-outline",
), ),
"windstrength": SensorMetadata( NetatmoSensorEntityDescription(
"Wind Strength", key="windstrength",
netatmo_name="WindStrength", name="Wind Strength",
enable_default=True, _netatmo_name="WindStrength",
unit=SPEED_KILOMETERS_PER_HOUR, entity_registry_enabled_default=True,
unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
icon="mdi:weather-windy", icon="mdi:weather-windy",
), ),
"gustangle": SensorMetadata( NetatmoSensorEntityDescription(
"Gust Direction", key="gustangle",
netatmo_name="GustAngle", name="Gust Direction",
enable_default=False, _netatmo_name="GustAngle",
entity_registry_enabled_default=False,
icon="mdi:compass-outline", icon="mdi:compass-outline",
), ),
"gustangle_value": SensorMetadata( NetatmoSensorEntityDescription(
"Gust Angle", key="gustangle_value",
netatmo_name="GustAngle", name="Gust Angle",
enable_default=False, _netatmo_name="GustAngle",
unit=DEGREE, entity_registry_enabled_default=False,
unit_of_measurement=DEGREE,
icon="mdi:compass-outline", icon="mdi:compass-outline",
), ),
"guststrength": SensorMetadata( NetatmoSensorEntityDescription(
"Gust Strength", key="guststrength",
netatmo_name="GustStrength", name="Gust Strength",
enable_default=False, _netatmo_name="GustStrength",
unit=SPEED_KILOMETERS_PER_HOUR, entity_registry_enabled_default=False,
unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
icon="mdi:weather-windy", icon="mdi:weather-windy",
), ),
"reachable": SensorMetadata( NetatmoSensorEntityDescription(
"Reachability", key="reachable",
netatmo_name="reachable", name="Reachability",
enable_default=False, _netatmo_name="reachable",
entity_registry_enabled_default=False,
icon="mdi:signal", icon="mdi:signal",
), ),
"rf_status": SensorMetadata( NetatmoSensorEntityDescription(
"Radio", key="rf_status",
netatmo_name="rf_status", name="Radio",
enable_default=False, _netatmo_name="rf_status",
entity_registry_enabled_default=False,
icon="mdi:signal", icon="mdi:signal",
), ),
"rf_status_lvl": SensorMetadata( NetatmoSensorEntityDescription(
"Radio Level", key="rf_status_lvl",
netatmo_name="rf_status", name="Radio Level",
enable_default=False, _netatmo_name="rf_status",
unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, entity_registry_enabled_default=False,
unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
), ),
"wifi_status": SensorMetadata( NetatmoSensorEntityDescription(
"Wifi", key="wifi_status",
netatmo_name="wifi_status", name="Wifi",
enable_default=False, _netatmo_name="wifi_status",
entity_registry_enabled_default=False,
icon="mdi:wifi", icon="mdi:wifi",
), ),
"wifi_status_lvl": SensorMetadata( NetatmoSensorEntityDescription(
"Wifi Level", key="wifi_status_lvl",
netatmo_name="wifi_status", name="Wifi Level",
enable_default=False, _netatmo_name="wifi_status",
unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, entity_registry_enabled_default=False,
unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
), ),
"health_idx": SensorMetadata( NetatmoSensorEntityDescription(
"Health", key="health_idx",
enable_default=True, name="Health",
netatmo_name="health_idx", _netatmo_name="health_idx",
entity_registry_enabled_default=True,
icon="mdi:cloud", icon="mdi:cloud",
), ),
} )
SENSOR_TYPES_KEYS = [desc.key for desc in SENSOR_TYPES]
MODULE_TYPE_OUTDOOR = "NAModule1" MODULE_TYPE_OUTDOOR = "NAModule1"
MODULE_TYPE_WIND = "NAModule2" MODULE_TYPE_WIND = "NAModule2"
@ -307,17 +334,20 @@ async def async_setup_entry(
conditions = [ conditions = [
c.lower() c.lower()
for c in data_class.get_monitored_conditions(module_id=module["_id"]) for c in data_class.get_monitored_conditions(module_id=module["_id"])
if c.lower() in SENSOR_TYPES if c.lower() in SENSOR_TYPES_KEYS
] ]
for condition in conditions: for condition in conditions:
if f"{condition}_value" in SENSOR_TYPES: if f"{condition}_value" in SENSOR_TYPES_KEYS:
conditions.append(f"{condition}_value") conditions.append(f"{condition}_value")
elif f"{condition}_lvl" in SENSOR_TYPES: elif f"{condition}_lvl" in SENSOR_TYPES_KEYS:
conditions.append(f"{condition}_lvl") conditions.append(f"{condition}_lvl")
for condition in conditions: entities.extend(
entities.append( [
NetatmoSensor(data_handler, data_class_name, module, condition) NetatmoSensor(data_handler, data_class_name, module, description)
for description in SENSOR_TYPES
if description.key in conditions
]
) )
_LOGGER.debug("Adding weather sensors %s", entities) _LOGGER.debug("Adding weather sensors %s", entities)
@ -379,9 +409,12 @@ async def async_setup_entry(
nonlocal platform_not_ready nonlocal platform_not_ready
platform_not_ready = False platform_not_ready = False
for sensor_type in SUPPORTED_PUBLIC_SENSOR_TYPES: new_entities.extend(
new_entities.append( [
NetatmoPublicSensor(data_handler, area, sensor_type) NetatmoPublicSensor(data_handler, area, description)
for description in SENSOR_TYPES
if description.key in SUPPORTED_PUBLIC_SENSOR_TYPES
]
) )
for device_id in entities.values(): for device_id in entities.values():
@ -403,17 +436,18 @@ async def async_setup_entry(
class NetatmoSensor(NetatmoBase, SensorEntity): class NetatmoSensor(NetatmoBase, SensorEntity):
"""Implementation of a Netatmo sensor.""" """Implementation of a Netatmo sensor."""
entity_description: NetatmoSensorEntityDescription
def __init__( def __init__(
self, self,
data_handler: NetatmoDataHandler, data_handler: NetatmoDataHandler,
data_class_name: str, data_class_name: str,
module_info: dict, module_info: dict,
sensor_type: str, description: NetatmoSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(data_handler) super().__init__(data_handler)
self.entity_description = description
metadata: SensorMetadata = SENSOR_TYPES[sensor_type]
self._data_classes.append( self._data_classes.append(
{"name": data_class_name, SIGNAL_NAME: data_class_name} {"name": data_class_name, SIGNAL_NAME: data_class_name}
@ -437,14 +471,9 @@ class NetatmoSensor(NetatmoBase, SensorEntity):
f"{module_info.get('module_name', device['type'])}" f"{module_info.get('module_name', device['type'])}"
) )
self._attr_name = f"{MANUFACTURER} {self._device_name} {metadata.name}" self._attr_name = f"{MANUFACTURER} {self._device_name} {description.name}"
self.type = sensor_type
self._attr_device_class = metadata.device_class
self._attr_icon = metadata.icon
self._attr_unit_of_measurement = metadata.unit
self._model = device["type"] self._model = device["type"]
self._attr_unique_id = f"{self._id}-{self.type}" self._attr_unique_id = f"{self._id}-{description.key}"
self._attr_entity_registry_enabled_default = metadata.enable_default
@property @property
def _data(self) -> pyatmo.AsyncWeatherStationData: def _data(self) -> pyatmo.AsyncWeatherStationData:
@ -478,24 +507,28 @@ class NetatmoSensor(NetatmoBase, SensorEntity):
return return
try: try:
state = data[SENSOR_TYPES[self.type].netatmo_name] state = data[self.entity_description.netatmo_name]
if self.type in {"temperature", "pressure", "sum_rain_1"}: if self.entity_description.key in {"temperature", "pressure", "sum_rain_1"}:
self._attr_state = round(state, 1) self._attr_state = round(state, 1)
elif self.type in {"windangle_value", "gustangle_value"}: elif self.entity_description.key in {"windangle_value", "gustangle_value"}:
self._attr_state = fix_angle(state) self._attr_state = fix_angle(state)
elif self.type in {"windangle", "gustangle"}: elif self.entity_description.key in {"windangle", "gustangle"}:
self._attr_state = process_angle(fix_angle(state)) self._attr_state = process_angle(fix_angle(state))
elif self.type == "rf_status": elif self.entity_description.key == "rf_status":
self._attr_state = process_rf(state) self._attr_state = process_rf(state)
elif self.type == "wifi_status": elif self.entity_description.key == "wifi_status":
self._attr_state = process_wifi(state) self._attr_state = process_wifi(state)
elif self.type == "health_idx": elif self.entity_description.key == "health_idx":
self._attr_state = process_health(state) self._attr_state = process_health(state)
else: else:
self._attr_state = state self._attr_state = state
except KeyError: except KeyError:
if self.state: if self.state:
_LOGGER.debug("No %s data found for %s", self.type, self._device_name) _LOGGER.debug(
"No %s data found for %s",
self.entity_description.key,
self._device_name,
)
self._attr_state = None self._attr_state = None
return return
@ -583,11 +616,17 @@ def process_wifi(strength: int) -> str:
class NetatmoPublicSensor(NetatmoBase, SensorEntity): class NetatmoPublicSensor(NetatmoBase, SensorEntity):
"""Represent a single sensor in a Netatmo.""" """Represent a single sensor in a Netatmo."""
entity_description: NetatmoSensorEntityDescription
def __init__( def __init__(
self, data_handler: NetatmoDataHandler, area: NetatmoArea, sensor_type: str self,
data_handler: NetatmoDataHandler,
area: NetatmoArea,
description: NetatmoSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(data_handler) super().__init__(data_handler)
self.entity_description = description
self._signal_name = f"{PUBLICDATA_DATA_CLASS_NAME}-{area.uuid}" self._signal_name = f"{PUBLICDATA_DATA_CLASS_NAME}-{area.uuid}"
@ -602,20 +641,17 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity):
SIGNAL_NAME: self._signal_name, SIGNAL_NAME: self._signal_name,
} }
) )
metadata: SensorMetadata = SENSOR_TYPES[sensor_type]
self.type = sensor_type
self.area = area self.area = area
self._mode = area.mode self._mode = area.mode
self._area_name = area.area_name self._area_name = area.area_name
self._id = self._area_name self._id = self._area_name
self._device_name = f"{self._area_name}" self._device_name = f"{self._area_name}"
self._attr_name = f"{MANUFACTURER} {self._device_name} {metadata.name}" self._attr_name = f"{MANUFACTURER} {self._device_name} {description.name}"
self._attr_device_class = metadata.device_class
self._attr_icon = metadata.icon
self._attr_unit_of_measurement = metadata.unit
self._show_on_map = area.show_on_map self._show_on_map = area.show_on_map
self._attr_unique_id = f"{self._device_name.replace(' ', '-')}-{self.type}" self._attr_unique_id = (
f"{self._device_name.replace(' ', '-')}-{description.key}"
)
self._model = PUBLIC self._model = PUBLIC
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
@ -682,28 +718,30 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity):
"""Update the entity's state.""" """Update the entity's state."""
data = None data = None
if self.type == "temperature": if self.entity_description.key == "temperature":
data = self._data.get_latest_temperatures() data = self._data.get_latest_temperatures()
elif self.type == "pressure": elif self.entity_description.key == "pressure":
data = self._data.get_latest_pressures() data = self._data.get_latest_pressures()
elif self.type == "humidity": elif self.entity_description.key == "humidity":
data = self._data.get_latest_humidities() data = self._data.get_latest_humidities()
elif self.type == "rain": elif self.entity_description.key == "rain":
data = self._data.get_latest_rain() data = self._data.get_latest_rain()
elif self.type == "sum_rain_1": elif self.entity_description.key == "sum_rain_1":
data = self._data.get_60_min_rain() data = self._data.get_60_min_rain()
elif self.type == "sum_rain_24": elif self.entity_description.key == "sum_rain_24":
data = self._data.get_24_h_rain() data = self._data.get_24_h_rain()
elif self.type == "windstrength": elif self.entity_description.key == "windstrength":
data = self._data.get_latest_wind_strengths() data = self._data.get_latest_wind_strengths()
elif self.type == "guststrength": elif self.entity_description.key == "guststrength":
data = self._data.get_latest_gust_strengths() data = self._data.get_latest_gust_strengths()
if data is None: if data is None:
if self.state is None: if self.state is None:
return return
_LOGGER.debug( _LOGGER.debug(
"No station provides %s data in the area %s", self.type, self._area_name "No station provides %s data in the area %s",
self.entity_description.key,
self._area_name,
) )
self._attr_state = None self._attr_state = None
return return