Files

407 lines
14 KiB
Python

"""Support for the AEMET OpenData service."""
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from typing import Final, override
from aemet_opendata.const import (
AOD_CONDITION,
AOD_FEEL_TEMP,
AOD_FORECAST_CURRENT,
AOD_FORECAST_DAILY,
AOD_FORECAST_HOURLY,
AOD_HUMIDITY,
AOD_ID,
AOD_NAME,
AOD_PRECIPITATION,
AOD_PRECIPITATION_PROBABILITY,
AOD_PRESSURE,
AOD_RAIN,
AOD_RAIN_PROBABILITY,
AOD_SNOW,
AOD_SNOW_PROBABILITY,
AOD_STATION,
AOD_STORM_PROBABILITY,
AOD_TEMP,
AOD_TEMP_MAX,
AOD_TEMP_MIN,
AOD_TIMESTAMP_UTC,
AOD_TOWN,
AOD_WEATHER,
AOD_WIND_DIRECTION,
AOD_WIND_SPEED,
AOD_WIND_SPEED_MAX,
)
from aemet_opendata.helpers import dict_nested_value
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
DEGREE,
PERCENTAGE,
UnitOfPressure,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from .const import (
ATTR_API_CONDITION,
ATTR_API_FORECAST_CONDITION,
ATTR_API_FORECAST_PRECIPITATION,
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
ATTR_API_FORECAST_TEMP,
ATTR_API_FORECAST_TEMP_LOW,
ATTR_API_FORECAST_TIME,
ATTR_API_FORECAST_WIND_BEARING,
ATTR_API_FORECAST_WIND_MAX_SPEED,
ATTR_API_FORECAST_WIND_SPEED,
ATTR_API_HUMIDITY,
ATTR_API_PRESSURE,
ATTR_API_RAIN,
ATTR_API_RAIN_PROB,
ATTR_API_SNOW,
ATTR_API_SNOW_PROB,
ATTR_API_STATION_ID,
ATTR_API_STATION_NAME,
ATTR_API_STATION_TIMESTAMP,
ATTR_API_STORM_PROB,
ATTR_API_TEMPERATURE,
ATTR_API_TEMPERATURE_FEELING,
ATTR_API_TOWN_ID,
ATTR_API_TOWN_NAME,
ATTR_API_TOWN_TIMESTAMP,
ATTR_API_WIND_BEARING,
ATTR_API_WIND_MAX_SPEED,
ATTR_API_WIND_SPEED,
CONDITIONS_MAP,
)
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
from .entity import AemetEntity
@dataclass(frozen=True, kw_only=True)
class AemetSensorEntityDescription(SensorEntityDescription):
"""A class that describes AEMET OpenData sensor entities."""
keys: list[str] | None = None
value_fn: Callable[[str], datetime | float | int | str | None] = lambda value: value
FORECAST_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_CONDITION}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_CONDITION],
name="Daily forecast condition",
value_fn=CONDITIONS_MAP.get,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_CONDITION}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_CONDITION],
name="Hourly forecast condition",
value_fn=CONDITIONS_MAP.get,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_PRECIPITATION}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_PRECIPITATION],
name="Hourly forecast precipitation",
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_PRECIPITATION_PROBABILITY}",
keys=[
AOD_TOWN,
AOD_FORECAST_DAILY,
AOD_FORECAST_CURRENT,
AOD_PRECIPITATION_PROBABILITY,
],
name="Daily forecast precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_PRECIPITATION_PROBABILITY}",
keys=[
AOD_TOWN,
AOD_FORECAST_HOURLY,
AOD_FORECAST_CURRENT,
AOD_PRECIPITATION_PROBABILITY,
],
name="Hourly forecast precipitation probability",
native_unit_of_measurement=PERCENTAGE,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_TEMP}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_TEMP_MAX],
name="Daily forecast temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_TEMP_LOW}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_TEMP_MIN],
name="Daily forecast temperature low",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_TEMP}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_TEMP],
name="Hourly forecast temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_TIME}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_TIMESTAMP_UTC],
name="Daily forecast time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_TIME}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_TIMESTAMP_UTC],
name="Hourly forecast time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_WIND_BEARING}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_WIND_DIRECTION],
name="Daily forecast wind bearing",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_WIND_BEARING}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_WIND_DIRECTION],
name="Hourly forecast wind bearing",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_WIND_MAX_SPEED}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_WIND_SPEED_MAX],
name="Hourly forecast wind max speed",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
),
AemetSensorEntityDescription(
key=f"forecast-daily-{ATTR_API_FORECAST_WIND_SPEED}",
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_WIND_SPEED],
name="Daily forecast wind speed",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
key=f"forecast-hourly-{ATTR_API_FORECAST_WIND_SPEED}",
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_WIND_SPEED],
name="Hourly forecast wind speed",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
),
)
WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
AemetSensorEntityDescription(
key=ATTR_API_CONDITION,
keys=[AOD_WEATHER, AOD_CONDITION],
name="Condition",
value_fn=CONDITIONS_MAP.get,
),
AemetSensorEntityDescription(
key=ATTR_API_HUMIDITY,
keys=[AOD_WEATHER, AOD_HUMIDITY],
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_PRESSURE,
keys=[AOD_WEATHER, AOD_PRESSURE],
name="Pressure",
native_unit_of_measurement=UnitOfPressure.HPA,
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_RAIN,
keys=[AOD_WEATHER, AOD_RAIN],
name="Rain",
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_RAIN_PROB,
keys=[AOD_WEATHER, AOD_RAIN_PROBABILITY],
name="Rain probability",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_SNOW,
keys=[AOD_WEATHER, AOD_SNOW],
name="Snow",
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_SNOW_PROB,
keys=[AOD_WEATHER, AOD_SNOW_PROBABILITY],
name="Snow probability",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_STATION_ID,
keys=[AOD_STATION, AOD_ID],
name="Station ID",
),
AemetSensorEntityDescription(
key=ATTR_API_STATION_NAME,
keys=[AOD_STATION, AOD_NAME],
name="Station name",
),
AemetSensorEntityDescription(
key=ATTR_API_STATION_TIMESTAMP,
keys=[AOD_STATION, AOD_TIMESTAMP_UTC],
name="Station timestamp",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
),
AemetSensorEntityDescription(
key=ATTR_API_STORM_PROB,
keys=[AOD_WEATHER, AOD_STORM_PROBABILITY],
name="Storm probability",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_TEMPERATURE,
keys=[AOD_WEATHER, AOD_TEMP],
name="Temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING,
keys=[AOD_WEATHER, AOD_FEEL_TEMP],
name="Temperature feeling",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_TOWN_ID,
keys=[AOD_TOWN, AOD_ID],
name="Town ID",
),
AemetSensorEntityDescription(
key=ATTR_API_TOWN_NAME,
keys=[AOD_TOWN, AOD_NAME],
name="Town name",
),
AemetSensorEntityDescription(
key=ATTR_API_TOWN_TIMESTAMP,
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_TIMESTAMP_UTC],
name="Town timestamp",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
),
AemetSensorEntityDescription(
key=ATTR_API_WIND_BEARING,
keys=[AOD_WEATHER, AOD_WIND_DIRECTION],
name="Wind bearing",
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT_ANGLE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,
keys=[AOD_WEATHER, AOD_WIND_SPEED_MAX],
name="Wind max speed",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
),
AemetSensorEntityDescription(
key=ATTR_API_WIND_SPEED,
keys=[AOD_WEATHER, AOD_WIND_SPEED],
name="Wind speed",
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AemetConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up AEMET OpenData sensor entities based on a config entry."""
domain_data = config_entry.runtime_data
name = domain_data.name
coordinator = domain_data.coordinator
unique_id = config_entry.unique_id
assert unique_id is not None
async_add_entities(
AemetSensor(
name,
coordinator,
description,
unique_id,
)
for description in FORECAST_SENSORS + WEATHER_SENSORS
if dict_nested_value(coordinator.data["lib"], description.keys) is not None
)
class AemetSensor(AemetEntity, SensorEntity):
"""Implementation of an AEMET OpenData sensor."""
entity_description: AemetSensorEntityDescription
def __init__(
self,
name: str,
coordinator: WeatherUpdateCoordinator,
description: AemetSensorEntityDescription,
unique_id: str,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, name, unique_id)
self.entity_description = description
self._attr_unique_id = f"{unique_id}-{description.key}"
@property
@override
def native_value(self):
"""Return the state of the device."""
value = self.get_aemet_value(self.entity_description.keys)
return self.entity_description.value_fn(value)