core/homeassistant/components/weatherflow/sensor.py

372 lines
14 KiB
Python

"""Sensors for the weatherflow integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from pyweatherflowudp.const import EVENT_RAPID_WIND
from pyweatherflowudp.device import (
EVENT_OBSERVATION,
EVENT_STATUS_UPDATE,
WeatherFlowDevice,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
DEGREE,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
UV_INDEX,
EntityCategory,
UnitOfElectricPotential,
UnitOfIrradiance,
UnitOfLength,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util.unit_system import METRIC_SYSTEM
from .const import DOMAIN, LOGGER, format_dispatch_call
def precipitation_raw_conversion_fn(raw_data: Enum):
"""Parse parse precipitation type."""
if raw_data.name.lower() == "unknown":
return None
return raw_data.name.lower()
@dataclass(frozen=True, kw_only=True)
class WeatherFlowSensorEntityDescription(SensorEntityDescription):
"""Describes WeatherFlow sensor entity."""
raw_data_conv_fn: Callable[[WeatherFlowDevice], datetime | StateType]
event_subscriptions: list[str] = field(default_factory=lambda: [EVENT_OBSERVATION])
imperial_suggested_unit: str | None = None
def get_native_value(self, device: WeatherFlowDevice) -> datetime | StateType:
"""Return the parsed sensor value."""
if (raw_sensor_data := getattr(device, self.key)) is None:
return None
return self.raw_data_conv_fn(raw_sensor_data)
SENSORS: tuple[WeatherFlowSensorEntityDescription, ...] = (
WeatherFlowSensorEntityDescription(
key="air_density",
translation_key="air_density",
native_unit_of_measurement="kg/m³",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=5,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="air_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="dew_point_temperature",
translation_key="dew_point",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="feels_like_temperature",
translation_key="feels_like",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wet_bulb_temperature",
translation_key="wet_bulb_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="battery",
translation_key="battery_voltage",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="illuminance",
native_unit_of_measurement=LIGHT_LUX,
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="lightning_strike_average_distance",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
translation_key="lightning_average_distance",
suggested_display_precision=2,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="lightning_strike_count",
translation_key="lightning_count",
state_class=SensorStateClass.TOTAL,
raw_data_conv_fn=lambda raw_data: raw_data,
),
WeatherFlowSensorEntityDescription(
key="precipitation_type",
translation_key="precipitation_type",
device_class=SensorDeviceClass.ENUM,
options=["none", "rain", "hail", "rain_hail", "unknown"],
raw_data_conv_fn=precipitation_raw_conversion_fn,
),
WeatherFlowSensorEntityDescription(
key="rain_accumulation_previous_minute",
native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS,
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.PRECIPITATION,
imperial_suggested_unit=UnitOfPrecipitationDepth.INCHES,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="rain_rate",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="relative_humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="rssi",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
event_subscriptions=[EVENT_STATUS_UPDATE],
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="station_pressure",
translation_key="station_pressure",
native_unit_of_measurement=UnitOfPressure.MBAR,
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=5,
imperial_suggested_unit=UnitOfPressure.INHG,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="solar_radiation",
native_unit_of_measurement=UnitOfIrradiance.WATTS_PER_SQUARE_METER,
device_class=SensorDeviceClass.IRRADIANCE,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="up_since",
translation_key="uptime",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
event_subscriptions=[EVENT_STATUS_UPDATE],
raw_data_conv_fn=lambda raw_data: raw_data,
),
WeatherFlowSensorEntityDescription(
key="uv",
translation_key="uv_index",
native_unit_of_measurement=UV_INDEX,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data,
),
WeatherFlowSensorEntityDescription(
key="vapor_pressure",
translation_key="vapor_pressure",
native_unit_of_measurement=UnitOfPressure.MBAR,
device_class=SensorDeviceClass.PRESSURE,
state_class=SensorStateClass.MEASUREMENT,
imperial_suggested_unit=UnitOfPressure.INHG,
suggested_display_precision=5,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
## Wind Sensors
WeatherFlowSensorEntityDescription(
key="wind_gust",
translation_key="wind_gust",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wind_lull",
translation_key="wind_lull",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wind_speed",
device_class=SensorDeviceClass.WIND_SPEED,
event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION],
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wind_average",
translation_key="wind_speed_average",
device_class=SensorDeviceClass.WIND_SPEED,
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wind_direction",
translation_key="wind_direction",
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
event_subscriptions=[EVENT_RAPID_WIND, EVENT_OBSERVATION],
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
WeatherFlowSensorEntityDescription(
key="wind_direction_average",
translation_key="wind_direction_average",
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
raw_data_conv_fn=lambda raw_data: raw_data.magnitude,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up WeatherFlow sensors using config entry."""
@callback
def async_add_sensor(device: WeatherFlowDevice) -> None:
"""Add WeatherFlow sensor."""
LOGGER.debug("Adding sensors for %s", device)
sensors: list[WeatherFlowSensorEntity] = [
WeatherFlowSensorEntity(
device=device,
description=description,
is_metric=(hass.config.units == METRIC_SYSTEM),
)
for description in SENSORS
if hasattr(device, description.key)
]
async_add_entities(sensors)
config_entry.async_on_unload(
async_dispatcher_connect(
hass,
format_dispatch_call(config_entry),
async_add_sensor,
)
)
class WeatherFlowSensorEntity(SensorEntity):
"""Defines a WeatherFlow sensor entity."""
entity_description: WeatherFlowSensorEntityDescription
_attr_should_poll = False
_attr_has_entity_name = True
def __init__(
self,
device: WeatherFlowDevice,
description: WeatherFlowSensorEntityDescription,
is_metric: bool = True,
) -> None:
"""Initialize a WeatherFlow sensor entity."""
self.device = device
self.entity_description = description
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.serial_number)},
manufacturer="WeatherFlow",
model=device.model,
name=device.serial_number,
sw_version=device.firmware_revision,
)
self._attr_unique_id = f"{device.serial_number}_{description.key}"
# In the case of the USA - we may want to have a suggested US unit which differs from the internal suggested units
if description.imperial_suggested_unit is not None and not is_metric:
self._attr_suggested_unit_of_measurement = (
description.imperial_suggested_unit
)
@property
def last_reset(self) -> datetime | None:
"""Return the time when the sensor was last reset, if any."""
if self.entity_description.state_class == SensorStateClass.TOTAL:
return self.device.last_report
return None
def _async_update_state(self) -> None:
"""Update entity state."""
value = self.entity_description.get_native_value(self.device)
self._attr_available = value is not None
self._attr_native_value = value
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Subscribe to events."""
self._async_update_state()
for event in self.entity_description.event_subscriptions:
self.async_on_remove(
self.device.on(event, lambda _: self._async_update_state())
)