Files

242 lines
7.8 KiB
Python

"""Sensors for National Weather Service (NWS)."""
from dataclasses import dataclass
from datetime import datetime
from typing import override
from pynws import SimpleNWS
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
DEGREE,
PERCENTAGE,
UnitOfLength,
UnitOfPressure,
UnitOfSpeed,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
TimestampDataUpdateCoordinator,
)
from homeassistant.util.dt import parse_datetime
from homeassistant.util.unit_conversion import (
DistanceConverter,
PressureConverter,
SpeedConverter,
)
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
from . import NWSConfigEntry, NWSData, device_info, get_base_unique_id
from .const import ATTRIBUTION
PARALLEL_UPDATES = 0
@dataclass(frozen=True)
class NWSSensorEntityDescription(SensorEntityDescription):
"""Class describing NWSSensor entities."""
unit_convert: str | None = None
SENSOR_TYPES: tuple[NWSSensorEntityDescription, ...] = (
NWSSensorEntityDescription(
key="dewpoint",
name="Dew Point",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
unit_convert=UnitOfTemperature.CELSIUS,
),
NWSSensorEntityDescription(
key="temperature",
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
unit_convert=UnitOfTemperature.CELSIUS,
),
NWSSensorEntityDescription(
key="windChill",
name="Wind Chill",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
unit_convert=UnitOfTemperature.CELSIUS,
),
NWSSensorEntityDescription(
key="heatIndex",
name="Heat Index",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
unit_convert=UnitOfTemperature.CELSIUS,
),
NWSSensorEntityDescription(
key="relativeHumidity",
name="Relative Humidity",
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
unit_convert=PERCENTAGE,
),
NWSSensorEntityDescription(
key="windSpeed",
name="Wind Speed",
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
unit_convert=UnitOfSpeed.MILES_PER_HOUR,
),
NWSSensorEntityDescription(
key="windGust",
name="Wind Gust",
device_class=SensorDeviceClass.WIND_SPEED,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
unit_convert=UnitOfSpeed.MILES_PER_HOUR,
),
# statistics currently doesn't handle circular statistics
NWSSensorEntityDescription(
key="windDirection",
name="Wind Direction",
icon="mdi:compass-rose",
native_unit_of_measurement=DEGREE,
unit_convert=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
state_class=SensorStateClass.MEASUREMENT_ANGLE,
),
NWSSensorEntityDescription(
key="barometricPressure",
name="Barometric Pressure",
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfPressure.PA,
unit_convert=UnitOfPressure.INHG,
),
NWSSensorEntityDescription(
key="seaLevelPressure",
name="Sea Level Pressure",
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfPressure.PA,
unit_convert=UnitOfPressure.INHG,
),
NWSSensorEntityDescription(
key="visibility",
name="Visibility",
icon="mdi:eye",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfLength.METERS,
unit_convert=UnitOfLength.MILES,
),
NWSSensorEntityDescription(
key="timestamp",
name="Latest Observation Time",
device_class=SensorDeviceClass.TIMESTAMP,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: NWSConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the NWS weather platform."""
nws_data = entry.runtime_data
async_add_entities(
NWSSensor(
hass=hass,
entry=entry,
nws_data=nws_data,
description=description,
)
for description in SENSOR_TYPES
)
class NWSSensor(CoordinatorEntity[TimestampDataUpdateCoordinator[None]], SensorEntity):
"""An NWS Sensor Entity."""
entity_description: NWSSensorEntityDescription
_attr_attribution = ATTRIBUTION
_attr_entity_registry_enabled_default = False
def __init__(
self,
hass: HomeAssistant,
entry: ConfigEntry,
nws_data: NWSData,
description: NWSSensorEntityDescription,
) -> None:
"""Initialise the platform with a data instance."""
super().__init__(nws_data.coordinator_observation)
self._nws_data = nws_data
self.entity_description = description
if hass.config.units is US_CUSTOMARY_SYSTEM:
self._attr_native_unit_of_measurement = description.unit_convert
self._attr_device_info = device_info(entry, nws_data)
self._attr_unique_id = f"{get_base_unique_id(entry)}_{description.key}"
@property
def _nws(self) -> SimpleNWS:
"""Return the current SimpleNWS API instance."""
return self._nws_data.api
@property
@override
def name(self) -> str:
"""Return the sensor name with current station."""
return f"{self._nws.station} {self.entity_description.name}"
@property
@override
def native_value(self) -> float | datetime | None:
"""Return the state."""
if (
not (observation := self._nws.observation)
or (value := observation.get(self.entity_description.key)) is None
):
return None
# Set alias to unit property -> prevent unnecessary hasattr calls
unit_of_measurement = self.native_unit_of_measurement
if unit_of_measurement == UnitOfSpeed.MILES_PER_HOUR:
return round(
SpeedConverter.convert(
value, UnitOfSpeed.KILOMETERS_PER_HOUR, UnitOfSpeed.MILES_PER_HOUR
)
)
if unit_of_measurement == UnitOfLength.MILES:
return round(
DistanceConverter.convert(
value, UnitOfLength.METERS, UnitOfLength.MILES
)
)
if unit_of_measurement == UnitOfPressure.INHG:
return round(
PressureConverter.convert(
value, UnitOfPressure.PA, UnitOfPressure.INHG
),
2,
)
if unit_of_measurement == UnitOfTemperature.CELSIUS:
return round(value, 1)
if unit_of_measurement == PERCENTAGE:
return round(value)
if self.device_class == SensorDeviceClass.TIMESTAMP:
return parse_datetime(value)
return value