Files
core/homeassistant/components/smhi/sensor.py
2025-11-04 17:37:32 -08:00

318 lines
10 KiB
Python

"""Sensor platform for SMHI integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
CONF_LATITUDE,
CONF_LOCATION,
CONF_LONGITUDE,
PERCENTAGE,
UnitOfSpeed,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import (
SMHIConfigEntry,
SMHIDataUpdateCoordinator,
SMHIFireDataUpdateCoordinator,
)
from .entity import SmhiFireEntity, SmhiWeatherEntity
PARALLEL_UPDATES = 0
FWI_INDEX_MAP = {
"1": "very_low",
"2": "low",
"3": "moderate",
"4": "high",
"5": "very_high",
"6": "extreme",
}
GRASSFIRE_MAP = {
"1": "snow_cover",
"2": "season_over",
"3": "low",
"4": "moderate",
"5": "high",
"6": "very_high",
}
FORESTDRY_MAP = {
"1": "very_wet",
"2": "wet",
"3": "moderate_wet",
"4": "dry",
"5": "very_dry",
"6": "extremely_dry",
}
def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None:
"""Return percentage values in correct range."""
value: int | None = entity.coordinator.current.get(key) # type: ignore[assignment]
if value is not None and 0 <= value <= 100:
return value
if value is not None:
return 0
return None
def get_fire_index_value(entity: SMHIFireSensor, key: str) -> str:
"""Return index value as string."""
value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment]
if value is not None and value > 0:
return str(int(value))
return "0"
@dataclass(frozen=True, kw_only=True)
class SMHIWeatherEntityDescription(SensorEntityDescription):
"""Describes SMHI weather entity."""
value_fn: Callable[[SMHIWeatherSensor], StateType | datetime]
@dataclass(frozen=True, kw_only=True)
class SMHIFireEntityDescription(SensorEntityDescription):
"""Describes SMHI fire entity."""
value_fn: Callable[[SMHIFireSensor], StateType | datetime]
WEATHER_SENSOR_DESCRIPTIONS: tuple[SMHIWeatherEntityDescription, ...] = (
SMHIWeatherEntityDescription(
key="thunder",
translation_key="thunder",
value_fn=lambda entity: get_percentage_values(entity, "thunder"),
native_unit_of_measurement=PERCENTAGE,
),
SMHIWeatherEntityDescription(
key="total_cloud",
translation_key="total_cloud",
value_fn=lambda entity: get_percentage_values(entity, "total_cloud"),
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
SMHIWeatherEntityDescription(
key="low_cloud",
translation_key="low_cloud",
value_fn=lambda entity: get_percentage_values(entity, "low_cloud"),
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
SMHIWeatherEntityDescription(
key="medium_cloud",
translation_key="medium_cloud",
value_fn=lambda entity: get_percentage_values(entity, "medium_cloud"),
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
SMHIWeatherEntityDescription(
key="high_cloud",
translation_key="high_cloud",
value_fn=lambda entity: get_percentage_values(entity, "high_cloud"),
native_unit_of_measurement=PERCENTAGE,
entity_registry_enabled_default=False,
),
SMHIWeatherEntityDescription(
key="precipitation_category",
translation_key="precipitation_category",
value_fn=lambda entity: str(
get_percentage_values(entity, "precipitation_category")
),
device_class=SensorDeviceClass.ENUM,
options=["0", "1", "2", "3", "4", "5", "6"],
),
SMHIWeatherEntityDescription(
key="frozen_precipitation",
translation_key="frozen_precipitation",
value_fn=lambda entity: get_percentage_values(entity, "frozen_precipitation"),
native_unit_of_measurement=PERCENTAGE,
),
)
FIRE_SENSOR_DESCRIPTIONS: tuple[SMHIFireEntityDescription, ...] = (
SMHIFireEntityDescription(
key="fwiindex",
translation_key="fwiindex",
value_fn=(
lambda entity: FWI_INDEX_MAP.get(get_fire_index_value(entity, "fwiindex"))
),
device_class=SensorDeviceClass.ENUM,
options=[*FWI_INDEX_MAP.values()],
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="fire_weather_index",
translation_key="fire_weather_index",
value_fn=lambda entity: entity.coordinator.fire_current.get("fwi"),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="initial_spread_index",
translation_key="initial_spread_index",
value_fn=lambda entity: entity.coordinator.fire_current.get("isi"),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="build_up_index",
translation_key="build_up_index",
value_fn=(
lambda entity: entity.coordinator.fire_current.get(
"bui" # codespell:ignore bui
)
),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="fine_fuel_moisture_code",
translation_key="fine_fuel_moisture_code",
value_fn=lambda entity: entity.coordinator.fire_current.get("ffmc"),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="duff_moisture_code",
translation_key="duff_moisture_code",
value_fn=lambda entity: entity.coordinator.fire_current.get("dmc"),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="drought_code",
translation_key="drought_code",
value_fn=lambda entity: entity.coordinator.fire_current.get("dc"),
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="grassfire",
translation_key="grassfire",
value_fn=(
lambda entity: GRASSFIRE_MAP.get(get_fire_index_value(entity, "grassfire"))
),
device_class=SensorDeviceClass.ENUM,
options=[*GRASSFIRE_MAP.values()],
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="rate_of_spread",
translation_key="rate_of_spread",
value_fn=lambda entity: entity.coordinator.fire_current.get("rn"),
device_class=SensorDeviceClass.SPEED,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfSpeed.METERS_PER_MINUTE,
entity_registry_enabled_default=False,
),
SMHIFireEntityDescription(
key="forestdry",
translation_key="forestdry",
value_fn=(
lambda entity: FORESTDRY_MAP.get(get_fire_index_value(entity, "forestdry"))
),
device_class=SensorDeviceClass.ENUM,
options=[*FORESTDRY_MAP.values()],
entity_registry_enabled_default=False,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: SMHIConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up SMHI sensor platform."""
coordinator = entry.runtime_data[0]
fire_coordinator = entry.runtime_data[1]
location = entry.data
entities: list[SMHIWeatherSensor | SMHIFireSensor] = []
entities.extend(
SMHIWeatherSensor(
location[CONF_LOCATION][CONF_LATITUDE],
location[CONF_LOCATION][CONF_LONGITUDE],
coordinator=coordinator,
entity_description=description,
)
for description in WEATHER_SENSOR_DESCRIPTIONS
)
entities.extend(
SMHIFireSensor(
location[CONF_LOCATION][CONF_LATITUDE],
location[CONF_LOCATION][CONF_LONGITUDE],
coordinator=fire_coordinator,
entity_description=description,
)
for description in FIRE_SENSOR_DESCRIPTIONS
)
async_add_entities(entities)
class SMHIWeatherSensor(SmhiWeatherEntity, SensorEntity):
"""Representation of a SMHI Weather Sensor."""
entity_description: SMHIWeatherEntityDescription
def __init__(
self,
latitude: str,
longitude: str,
coordinator: SMHIDataUpdateCoordinator,
entity_description: SMHIWeatherEntityDescription,
) -> None:
"""Initiate SMHI Sensor."""
self.entity_description = entity_description
super().__init__(
latitude,
longitude,
coordinator,
)
self._attr_unique_id = f"{latitude}, {longitude}-{entity_description.key}"
def update_entity_data(self) -> None:
"""Refresh the entity data."""
if self.coordinator.data.daily:
self._attr_native_value = self.entity_description.value_fn(self)
class SMHIFireSensor(SmhiFireEntity, SensorEntity):
"""Representation of a SMHI Weather Sensor."""
entity_description: SMHIFireEntityDescription
def __init__(
self,
latitude: str,
longitude: str,
coordinator: SMHIFireDataUpdateCoordinator,
entity_description: SMHIFireEntityDescription,
) -> None:
"""Initiate SMHI Sensor."""
self.entity_description = entity_description
super().__init__(
latitude,
longitude,
coordinator,
)
self._attr_unique_id = f"{latitude}, {longitude}-{entity_description.key}"
def update_entity_data(self) -> None:
"""Refresh the entity data."""
if self.coordinator.data.fire_daily:
self._attr_native_value = self.entity_description.value_fn(self)