Simplify state update logic for OpenUV sensors (#101972)
* Clean up OpenUV entity state logic * Reduce * Remove old file * Simplify
This commit is contained in:
parent
76e2afbce9
commit
371d988643
3 changed files with 94 additions and 70 deletions
|
@ -16,7 +16,7 @@ from homeassistant.const import (
|
||||||
CONF_SENSORS,
|
CONF_SENSORS,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
@ -132,19 +132,3 @@ class OpenUvEntity(CoordinatorEntity):
|
||||||
name="OpenUV",
|
name="OpenUV",
|
||||||
entry_type=DeviceEntryType.SERVICE,
|
entry_type=DeviceEntryType.SERVICE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
|
||||||
def _handle_coordinator_update(self) -> None:
|
|
||||||
"""Respond to a DataUpdateCoordinator update."""
|
|
||||||
self._update_from_latest_data()
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _update_from_latest_data(self) -> None:
|
|
||||||
"""Update the entity from the latest data."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
|
||||||
"""Handle entity which will be added."""
|
|
||||||
await super().async_added_to_hass()
|
|
||||||
self._update_from_latest_data()
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorEntity):
|
||||||
"""Define a binary sensor for OpenUV."""
|
"""Define a binary sensor for OpenUV."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_from_latest_data(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Update the entity from the latest data."""
|
"""Update the entity from the latest data."""
|
||||||
data = self.coordinator.data
|
data = self.coordinator.data
|
||||||
|
|
||||||
|
@ -76,3 +76,5 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorEntity):
|
||||||
ATTR_PROTECTION_WINDOW_STARTING_TIME: as_local(from_dt),
|
ATTR_PROTECTION_WINDOW_STARTING_TIME: as_local(from_dt),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
super()._handle_coordinator_update()
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
"""Support for OpenUV sensors."""
|
"""Support for OpenUV sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable, Mapping
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
|
@ -8,7 +12,7 @@ from homeassistant.components.sensor import (
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import UV_INDEX, UnitOfTime
|
from homeassistant.const import UV_INDEX, UnitOfTime
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util.dt import as_local, parse_datetime
|
from homeassistant.util.dt import as_local, parse_datetime
|
||||||
|
|
||||||
|
@ -40,79 +44,135 @@ EXPOSURE_TYPE_MAP = {
|
||||||
TYPE_SAFE_EXPOSURE_TIME_6: "st6",
|
TYPE_SAFE_EXPOSURE_TIME_6: "st6",
|
||||||
}
|
}
|
||||||
|
|
||||||
UV_LEVEL_EXTREME = "Extreme"
|
|
||||||
UV_LEVEL_VHIGH = "Very High"
|
@dataclass
|
||||||
UV_LEVEL_HIGH = "High"
|
class UvLabel:
|
||||||
UV_LEVEL_MODERATE = "Moderate"
|
"""Define a friendly UV level label and its minimum UV index."""
|
||||||
UV_LEVEL_LOW = "Low"
|
|
||||||
|
value: str
|
||||||
|
minimum_index: int
|
||||||
|
|
||||||
|
|
||||||
|
UV_LABEL_DEFINITIONS = (
|
||||||
|
UvLabel(value="Extreme", minimum_index=11),
|
||||||
|
UvLabel(value="Very High", minimum_index=8),
|
||||||
|
UvLabel(value="High", minimum_index=6),
|
||||||
|
UvLabel(value="Moderate", minimum_index=3),
|
||||||
|
UvLabel(value="Low", minimum_index=0),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_uv_label(uv_index: int) -> str:
|
||||||
|
"""Return the UV label for the UV index."""
|
||||||
|
label = next(
|
||||||
|
label for label in UV_LABEL_DEFINITIONS if uv_index >= label.minimum_index
|
||||||
|
)
|
||||||
|
return label.value
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OpenUvSensorEntityDescriptionMixin:
|
||||||
|
"""Define a mixin for OpenUV sensor descriptions."""
|
||||||
|
|
||||||
|
value_fn: Callable[[dict[str, Any]], int | str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class OpenUvSensorEntityDescription(
|
||||||
|
SensorEntityDescription, OpenUvSensorEntityDescriptionMixin
|
||||||
|
):
|
||||||
|
"""Define a class that describes OpenUV sensor entities."""
|
||||||
|
|
||||||
|
|
||||||
SENSOR_DESCRIPTIONS = (
|
SENSOR_DESCRIPTIONS = (
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_CURRENT_OZONE_LEVEL,
|
key=TYPE_CURRENT_OZONE_LEVEL,
|
||||||
translation_key="current_ozone_level",
|
translation_key="current_ozone_level",
|
||||||
native_unit_of_measurement="du",
|
native_unit_of_measurement="du",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["ozone"],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_CURRENT_UV_INDEX,
|
key=TYPE_CURRENT_UV_INDEX,
|
||||||
translation_key="current_uv_index",
|
translation_key="current_uv_index",
|
||||||
icon="mdi:weather-sunny",
|
icon="mdi:weather-sunny",
|
||||||
native_unit_of_measurement=UV_INDEX,
|
native_unit_of_measurement=UV_INDEX,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["uv"],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_CURRENT_UV_LEVEL,
|
key=TYPE_CURRENT_UV_LEVEL,
|
||||||
translation_key="current_uv_level",
|
translation_key="current_uv_level",
|
||||||
icon="mdi:weather-sunny",
|
icon="mdi:weather-sunny",
|
||||||
|
value_fn=lambda data: get_uv_label(data["uv"]),
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_MAX_UV_INDEX,
|
key=TYPE_MAX_UV_INDEX,
|
||||||
translation_key="max_uv_index",
|
translation_key="max_uv_index",
|
||||||
icon="mdi:weather-sunny",
|
icon="mdi:weather-sunny",
|
||||||
native_unit_of_measurement=UV_INDEX,
|
native_unit_of_measurement=UV_INDEX,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["uv_max"],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_1,
|
key=TYPE_SAFE_EXPOSURE_TIME_1,
|
||||||
translation_key="skin_type_1_safe_exposure_time",
|
translation_key="skin_type_1_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_1]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_2,
|
key=TYPE_SAFE_EXPOSURE_TIME_2,
|
||||||
translation_key="skin_type_2_safe_exposure_time",
|
translation_key="skin_type_2_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_2]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_3,
|
key=TYPE_SAFE_EXPOSURE_TIME_3,
|
||||||
translation_key="skin_type_3_safe_exposure_time",
|
translation_key="skin_type_3_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_3]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_4,
|
key=TYPE_SAFE_EXPOSURE_TIME_4,
|
||||||
translation_key="skin_type_4_safe_exposure_time",
|
translation_key="skin_type_4_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_4]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_5,
|
key=TYPE_SAFE_EXPOSURE_TIME_5,
|
||||||
translation_key="skin_type_5_safe_exposure_time",
|
translation_key="skin_type_5_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_5]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
OpenUvSensorEntityDescription(
|
||||||
key=TYPE_SAFE_EXPOSURE_TIME_6,
|
key=TYPE_SAFE_EXPOSURE_TIME_6,
|
||||||
translation_key="skin_type_6_safe_exposure_time",
|
translation_key="skin_type_6_safe_exposure_time",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
native_unit_of_measurement=UnitOfTime.MINUTES,
|
native_unit_of_measurement=UnitOfTime.MINUTES,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
value_fn=lambda data: data["safe_exposure_time"][
|
||||||
|
EXPOSURE_TYPE_MAP[TYPE_SAFE_EXPOSURE_TIME_6]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -134,40 +194,18 @@ async def async_setup_entry(
|
||||||
class OpenUvSensor(OpenUvEntity, SensorEntity):
|
class OpenUvSensor(OpenUvEntity, SensorEntity):
|
||||||
"""Define a binary sensor for OpenUV."""
|
"""Define a binary sensor for OpenUV."""
|
||||||
|
|
||||||
@callback
|
entity_description: OpenUvSensorEntityDescription
|
||||||
def _update_from_latest_data(self) -> None:
|
|
||||||
"""Update the state."""
|
|
||||||
data = self.coordinator.data
|
|
||||||
|
|
||||||
if self.entity_description.key == TYPE_CURRENT_OZONE_LEVEL:
|
@property
|
||||||
self._attr_native_value = data["ozone"]
|
def extra_state_attributes(self) -> Mapping[str, Any]:
|
||||||
elif self.entity_description.key == TYPE_CURRENT_UV_INDEX:
|
"""Return entity specific state attributes."""
|
||||||
self._attr_native_value = data["uv"]
|
attrs = {}
|
||||||
elif self.entity_description.key == TYPE_CURRENT_UV_LEVEL:
|
if self.entity_description.key == TYPE_MAX_UV_INDEX:
|
||||||
if data["uv"] >= 11:
|
if uv_max_time := parse_datetime(self.coordinator.data["uv_max_time"]):
|
||||||
self._attr_native_value = UV_LEVEL_EXTREME
|
attrs[ATTR_MAX_UV_TIME] = as_local(uv_max_time)
|
||||||
elif data["uv"] >= 8:
|
return attrs
|
||||||
self._attr_native_value = UV_LEVEL_VHIGH
|
|
||||||
elif data["uv"] >= 6:
|
@property
|
||||||
self._attr_native_value = UV_LEVEL_HIGH
|
def native_value(self) -> int | str:
|
||||||
elif data["uv"] >= 3:
|
"""Return the sensor value."""
|
||||||
self._attr_native_value = UV_LEVEL_MODERATE
|
return self.entity_description.value_fn(self.coordinator.data)
|
||||||
else:
|
|
||||||
self._attr_native_value = UV_LEVEL_LOW
|
|
||||||
elif self.entity_description.key == TYPE_MAX_UV_INDEX:
|
|
||||||
self._attr_native_value = data["uv_max"]
|
|
||||||
if uv_max_time := parse_datetime(data["uv_max_time"]):
|
|
||||||
self._attr_extra_state_attributes.update(
|
|
||||||
{ATTR_MAX_UV_TIME: as_local(uv_max_time)}
|
|
||||||
)
|
|
||||||
elif self.entity_description.key in (
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_1,
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_2,
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_3,
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_4,
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_5,
|
|
||||||
TYPE_SAFE_EXPOSURE_TIME_6,
|
|
||||||
):
|
|
||||||
self._attr_native_value = data["safe_exposure_time"][
|
|
||||||
EXPOSURE_TYPE_MAP[self.entity_description.key]
|
|
||||||
]
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue