mirror of https://github.com/home-assistant/core
405 lines
14 KiB
Python
405 lines
14 KiB
Python
"""Support for the AEMET OpenData service."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Final
|
|
|
|
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 AddEntitiesCallback
|
|
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,
|
|
),
|
|
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,
|
|
),
|
|
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,
|
|
),
|
|
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: AddEntitiesCallback,
|
|
) -> 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
|
|
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)
|