Add entity translations to Netatmo (#115104)

* Add entity translations to Netatmo

* Yes

* Remove

* Fix name
This commit is contained in:
Joost Lekkerkerker 2024-04-10 08:50:27 +02:00 committed by GitHub
parent d61db732da
commit 7e1a5b19c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1693 additions and 1544 deletions

View File

@ -1,4 +1,38 @@
{
"entity": {
"sensor": {
"temp_trend": {
"default": "mdi:trending-up"
},
"pressure_trend": {
"default": "mdi:trending-up"
},
"wind_direction": {
"default": "mdi:compass-outline"
},
"wind_angle": {
"default": "mdi:compass-outline"
},
"gust_direction": {
"default": "mdi:compass-outline"
},
"gust_angle": {
"default": "mdi:compass-outline"
},
"reachable": {
"default": "mdi:signal"
},
"rf_strength": {
"default": "mdi:signal"
},
"wifi_strength": {
"default": "mdi:wifi"
},
"health_idx": {
"default": "mdi:cloud"
}
}
},
"services": {
"set_camera_light": "mdi:led-on",
"set_schedule": "mdi:calendar-clock",

View File

@ -2,12 +2,14 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import cast
from typing import Any, cast
import pyatmo
from pyatmo import DeviceType
from pyatmo.modules import PublicWeatherArea
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -41,6 +43,7 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import (
CONF_URL_ENERGY,
@ -61,18 +64,46 @@ from .helper import NetatmoArea
_LOGGER = logging.getLogger(__name__)
SUPPORTED_PUBLIC_SENSOR_TYPES: tuple[str, ...] = (
"temperature",
"pressure",
"humidity",
"rain",
"wind_strength",
"gust_strength",
"sum_rain_1",
"sum_rain_24",
"wind_angle",
"gust_angle",
)
def process_health(health: StateType) -> str | None:
"""Process health index and return string for display."""
if not isinstance(health, int):
return None
if health == 0:
return "Healthy"
if health == 1:
return "Fine"
if health == 2:
return "Fair"
if health == 3:
return "Poor"
return "Unhealthy"
def process_rf(strength: StateType) -> str | None:
"""Process wifi signal strength and return string for display."""
if not isinstance(strength, int):
return None
if strength >= 90:
return "Low"
if strength >= 76:
return "Medium"
if strength >= 60:
return "High"
return "Full"
def process_wifi(strength: StateType) -> str | None:
"""Process wifi signal strength and return string for display."""
if not isinstance(strength, int):
return None
if strength >= 86:
return "Low"
if strength >= 71:
return "Medium"
if strength >= 56:
return "High"
return "Full"
@dataclass(frozen=True, kw_only=True)
@ -80,27 +111,25 @@ class NetatmoSensorEntityDescription(SensorEntityDescription):
"""Describes Netatmo sensor entity."""
netatmo_name: str
value_fn: Callable[[StateType], StateType] = lambda x: x
SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
NetatmoSensorEntityDescription(
key="temperature",
name="Temperature",
netatmo_name="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
suggested_display_precision=1,
),
NetatmoSensorEntityDescription(
key="temp_trend",
name="Temperature trend",
netatmo_name="temp_trend",
entity_registry_enabled_default=False,
icon="mdi:trending-up",
),
NetatmoSensorEntityDescription(
key="co2",
name="CO2",
netatmo_name="co2",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
@ -108,22 +137,19 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="pressure",
name="Pressure",
netatmo_name="pressure",
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
suggested_display_precision=1,
),
NetatmoSensorEntityDescription(
key="pressure_trend",
name="Pressure trend",
netatmo_name="pressure_trend",
entity_registry_enabled_default=False,
icon="mdi:trending-up",
),
NetatmoSensorEntityDescription(
key="noise",
name="Noise",
netatmo_name="noise",
native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
device_class=SensorDeviceClass.SOUND_PRESSURE,
@ -131,7 +157,6 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="humidity",
name="Humidity",
netatmo_name="humidity",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
@ -139,7 +164,6 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="rain",
name="Rain",
netatmo_name="rain",
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
@ -147,16 +171,15 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="sum_rain_1",
name="Rain last hour",
netatmo_name="sum_rain_1",
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=1,
),
NetatmoSensorEntityDescription(
key="sum_rain_24",
name="Rain today",
netatmo_name="sum_rain_24",
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
@ -164,7 +187,6 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="battery_percent",
name="Battery Percent",
netatmo_name="battery",
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE,
@ -173,22 +195,17 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="windangle",
name="Direction",
netatmo_name="wind_direction",
icon="mdi:compass-outline",
),
NetatmoSensorEntityDescription(
key="windangle_value",
name="Angle",
netatmo_name="wind_angle",
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
state_class=SensorStateClass.MEASUREMENT,
),
NetatmoSensorEntityDescription(
key="windstrength",
name="Wind Strength",
netatmo_name="wind_strength",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
@ -196,23 +213,18 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="gustangle",
name="Gust Direction",
netatmo_name="gust_direction",
entity_registry_enabled_default=False,
icon="mdi:compass-outline",
),
NetatmoSensorEntityDescription(
key="gustangle_value",
name="Gust Angle",
netatmo_name="gust_angle",
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
icon="mdi:compass-outline",
state_class=SensorStateClass.MEASUREMENT,
),
NetatmoSensorEntityDescription(
key="guststrength",
name="Gust Strength",
netatmo_name="gust_strength",
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
@ -221,37 +233,31 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
),
NetatmoSensorEntityDescription(
key="reachable",
name="Reachability",
netatmo_name="reachable",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:signal",
),
NetatmoSensorEntityDescription(
key="rf_status",
name="Radio",
netatmo_name="rf_strength",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:signal",
value_fn=process_rf,
),
NetatmoSensorEntityDescription(
key="wifi_status",
name="Wifi",
netatmo_name="wifi_strength",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:wifi",
value_fn=process_wifi,
),
NetatmoSensorEntityDescription(
key="health_idx",
name="Health",
netatmo_name="health_idx",
icon="mdi:cloud",
value_fn=process_health,
),
NetatmoSensorEntityDescription(
key="power",
name="Power",
netatmo_name="power",
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
@ -260,9 +266,100 @@ SENSOR_TYPES: tuple[NetatmoSensorEntityDescription, ...] = (
)
SENSOR_TYPES_KEYS = [desc.key for desc in SENSOR_TYPES]
@dataclass(frozen=True, kw_only=True)
class NetatmoPublicWeatherSensorEntityDescription(SensorEntityDescription):
"""Describes Netatmo sensor entity."""
value_fn: Callable[[PublicWeatherArea], dict[str, Any]]
PUBLIC_WEATHER_STATION_TYPES: tuple[
NetatmoPublicWeatherSensorEntityDescription, ...
] = (
NetatmoPublicWeatherSensorEntityDescription(
key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
suggested_display_precision=1,
value_fn=lambda area: area.get_latest_temperatures(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="pressure",
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
suggested_display_precision=1,
value_fn=lambda area: area.get_latest_pressures(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="humidity",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.HUMIDITY,
value_fn=lambda area: area.get_latest_humidities(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="rain",
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda area: area.get_latest_rain(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="sum_rain_1",
translation_key="sum_rain_1",
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
state_class=SensorStateClass.TOTAL,
suggested_display_precision=1,
value_fn=lambda area: area.get_60_min_rain(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="sum_rain_24",
translation_key="sum_rain_24",
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
device_class=SensorDeviceClass.PRECIPITATION,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda area: area.get_24_h_rain(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="windangle_value",
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda area: area.get_latest_wind_angles(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="windstrength",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda area: area.get_latest_wind_strengths(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="gustangle_value",
translation_key="gust_angle",
entity_registry_enabled_default=False,
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda area: area.get_latest_gust_angles(),
),
NetatmoPublicWeatherSensorEntityDescription(
key="guststrength",
translation_key="gust_strength",
entity_registry_enabled_default=False,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda area: area.get_latest_gust_strengths(),
),
)
BATTERY_SENSOR_DESCRIPTION = NetatmoSensorEntityDescription(
key="battery",
name="Battery Percent",
netatmo_name="battery",
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE,
@ -349,7 +446,7 @@ async def async_setup_entry(
if device.model == "Public Weather station"
}
new_entities = []
new_entities: list[NetatmoPublicSensor] = []
for area in [
NetatmoArea(**i) for i in entry.options.get(CONF_WEATHER_AREAS, {}).values()
]:
@ -378,11 +475,8 @@ async def async_setup_entry(
)
new_entities.extend(
[
NetatmoPublicSensor(data_handler, area, description)
for description in SENSOR_TYPES
if description.netatmo_name in SUPPORTED_PUBLIC_SENSOR_TYPES
]
NetatmoPublicSensor(data_handler, area, description)
for description in PUBLIC_WEATHER_STATION_TYPES
)
for device_id in entities.values():
@ -411,6 +505,7 @@ class NetatmoWeatherSensor(NetatmoModuleEntity, SensorEntity):
"""Initialize the sensor."""
super().__init__(netatmo_device)
self.entity_description = description
self._attr_translation_key = description.netatmo_name
category = getattr(self.device.device_category, "name")
self._publishers.extend(
[
@ -439,34 +534,20 @@ class NetatmoWeatherSensor(NetatmoModuleEntity, SensorEntity):
return super().device_type
return DeviceType(self.device.device_type.partition(".")[2])
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self.device.reachable or False
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
if (
not self.device.reachable
or (state := getattr(self.device, self.entity_description.netatmo_name))
is None
):
if self.available:
self._attr_available = False
return
if self.entity_description.netatmo_name in {
"temperature",
"pressure",
"sum_rain_1",
}:
self._attr_native_value = round(state, 1)
elif self.entity_description.netatmo_name == "rf_strength":
self._attr_native_value = process_rf(state)
elif self.entity_description.netatmo_name == "wifi_strength":
self._attr_native_value = process_wifi(state)
elif self.entity_description.netatmo_name == "health_idx":
self._attr_native_value = process_health(state)
else:
self._attr_native_value = state
self._attr_available = True
value = cast(
StateType, getattr(self.device, self.entity_description.netatmo_name)
)
if value is not None:
value = self.entity_description.value_fn(value)
self._attr_native_value = value
self.async_write_ha_state()
@ -559,41 +640,6 @@ class NetatmoSensor(NetatmoModuleEntity, SensorEntity):
self.async_write_ha_state()
def process_health(health: int) -> str:
"""Process health index and return string for display."""
if health == 0:
return "Healthy"
if health == 1:
return "Fine"
if health == 2:
return "Fair"
if health == 3:
return "Poor"
return "Unhealthy"
def process_rf(strength: int) -> str:
"""Process wifi signal strength and return string for display."""
if strength >= 90:
return "Low"
if strength >= 76:
return "Medium"
if strength >= 60:
return "High"
return "Full"
def process_wifi(strength: int) -> str:
"""Process wifi signal strength and return string for display."""
if strength >= 86:
return "Low"
if strength >= 71:
return "Medium"
if strength >= 56:
return "High"
return "Full"
class NetatmoRoomSensor(NetatmoRoomEntity, SensorEntity):
"""Implementation of a Netatmo room sensor."""
@ -636,13 +682,13 @@ class NetatmoRoomSensor(NetatmoRoomEntity, SensorEntity):
class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity):
"""Represent a single sensor in a Netatmo."""
entity_description: NetatmoSensorEntityDescription
entity_description: NetatmoPublicWeatherSensorEntityDescription
def __init__(
self,
data_handler: NetatmoDataHandler,
area: NetatmoArea,
description: NetatmoSensorEntityDescription,
description: NetatmoPublicWeatherSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(data_handler)
@ -720,28 +766,7 @@ class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity):
@callback
def async_update_callback(self) -> None:
"""Update the entity's state."""
data = None
if self.entity_description.netatmo_name == "temperature":
data = self._station.get_latest_temperatures()
elif self.entity_description.netatmo_name == "pressure":
data = self._station.get_latest_pressures()
elif self.entity_description.netatmo_name == "humidity":
data = self._station.get_latest_humidities()
elif self.entity_description.netatmo_name == "rain":
data = self._station.get_latest_rain()
elif self.entity_description.netatmo_name == "sum_rain_1":
data = self._station.get_60_min_rain()
elif self.entity_description.netatmo_name == "sum_rain_24":
data = self._station.get_24_h_rain()
elif self.entity_description.netatmo_name == "wind_strength":
data = self._station.get_latest_wind_strengths()
elif self.entity_description.netatmo_name == "gust_strength":
data = self._station.get_latest_gust_strengths()
elif self.entity_description.netatmo_name == "wind_angle":
data = self._station.get_latest_wind_angles()
elif self.entity_description.netatmo_name == "gust_angle":
data = self._station.get_latest_gust_angles()
data = self.entity_description.value_fn(self._station)
if not data:
if self.available:
@ -760,5 +785,5 @@ class NetatmoPublicSensor(NetatmoBaseEntity, SensorEntity):
elif self._mode == "max":
self._attr_native_value = max(values)
self._attr_available = self.state is not None
self._attr_available = self.native_value is not None
self.async_write_ha_state()

View File

@ -166,5 +166,51 @@
"name": "Clear temperature setting",
"description": "Clears any temperature setting for a Netatmo climate device reverting it to the current preset or schedule."
}
},
"entity": {
"sensor": {
"temp_trend": {
"name": "Temperature trend"
},
"pressure_trend": {
"name": "Pressure trend"
},
"noise": {
"name": "Noise"
},
"sum_rain_1": {
"name": "Precipitation last hour"
},
"sum_rain_24": {
"name": "Precipitation today"
},
"wind_direction": {
"name": "Wind direction"
},
"wind_angle": {
"name": "Wind angle"
},
"gust_direction": {
"name": "Gust direction"
},
"gust_angle": {
"name": "Gust angle"
},
"gust_strength": {
"name": "Gust strength"
},
"reachable": {
"name": "Reachability"
},
"rf_strength": {
"name": "Radio"
},
"wifi_strength": {
"name": "Wi-Fi"
},
"health_idx": {
"name": "Health index"
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -46,8 +46,8 @@ async def test_indoor_sensor(
assert hass.states.get(f"{prefix}temperature").state == "20.3"
assert hass.states.get(f"{prefix}humidity").state == "63"
assert hass.states.get(f"{prefix}co2").state == "494"
assert hass.states.get(f"{prefix}pressure").state == "1014.5"
assert hass.states.get(f"{prefix}carbon_dioxide").state == "494"
assert hass.states.get(f"{prefix}atmospheric_pressure").state == "1014.5"
async def test_weather_sensor(
@ -79,13 +79,13 @@ async def test_public_weather_sensor(
assert hass.states.get(f"{prefix}temperature").state == "27.4"
assert hass.states.get(f"{prefix}humidity").state == "76"
assert hass.states.get(f"{prefix}pressure").state == "1014.4"
assert hass.states.get(f"{prefix}atmospheric_pressure").state == "1014.4"
prefix = "sensor.home_avg_"
assert hass.states.get(f"{prefix}temperature").state == "22.7"
assert hass.states.get(f"{prefix}humidity").state == "63.2"
assert hass.states.get(f"{prefix}pressure").state == "1010.4"
assert hass.states.get(f"{prefix}atmospheric_pressure").state == "1010.4"
entities_before_change = len(hass.states.async_all())
@ -248,4 +248,4 @@ async def test_climate_battery_sensor(
prefix = "sensor.livingroom_"
assert hass.states.get(f"{prefix}battery_percent").state == "75"
assert hass.states.get(f"{prefix}battery").state == "75"