Replace custom OpenUV data object with coordinators (#80705)
* Replace custom OpenUV data object with coordinators * Typing * Code review
This commit is contained in:
parent
245c13e6ed
commit
60b3d6816b
6 changed files with 168 additions and 194 deletions
|
@ -2,11 +2,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import Callable
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyopenuv import Client
|
from pyopenuv import Client
|
||||||
from pyopenuv.errors import OpenUvError
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
|
@ -20,20 +18,16 @@ from homeassistant.const import (
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
aiohttp_client,
|
aiohttp_client,
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
entity_registry,
|
entity_registry,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.debounce import Debouncer
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
from homeassistant.helpers.dispatcher import (
|
|
||||||
async_dispatcher_connect,
|
|
||||||
async_dispatcher_send,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.service import verify_domain_control
|
from homeassistant.helpers.service import verify_domain_control
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity, UpdateFailed
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_FROM_WINDOW,
|
CONF_FROM_WINDOW,
|
||||||
|
@ -45,13 +39,10 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
from .coordinator import OpenUvCoordinator
|
||||||
|
|
||||||
CONF_ENTRY_ID = "entry_id"
|
CONF_ENTRY_ID = "entry_id"
|
||||||
|
|
||||||
DEFAULT_DEBOUNCER_COOLDOWN_SECONDS = 15 * 60
|
|
||||||
|
|
||||||
TOPIC_UPDATE = f"{DOMAIN}_data_update"
|
|
||||||
|
|
||||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
|
|
||||||
SERVICE_NAME_UPDATE_DATA = "update_data"
|
SERVICE_NAME_UPDATE_DATA = "update_data"
|
||||||
|
@ -127,53 +118,50 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
_verify_domain_control = verify_domain_control(hass, DOMAIN)
|
_verify_domain_control = verify_domain_control(hass, DOMAIN)
|
||||||
|
|
||||||
websession = aiohttp_client.async_get_clientsession(hass)
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
openuv = OpenUV(
|
client = Client(
|
||||||
hass,
|
entry.data[CONF_API_KEY],
|
||||||
entry,
|
entry.data.get(CONF_LATITUDE, hass.config.latitude),
|
||||||
Client(
|
entry.data.get(CONF_LONGITUDE, hass.config.longitude),
|
||||||
entry.data[CONF_API_KEY],
|
altitude=entry.data.get(CONF_ELEVATION, hass.config.elevation),
|
||||||
entry.data.get(CONF_LATITUDE, hass.config.latitude),
|
session=websession,
|
||||||
entry.data.get(CONF_LONGITUDE, hass.config.longitude),
|
|
||||||
altitude=entry.data.get(CONF_ELEVATION, hass.config.elevation),
|
|
||||||
session=websession,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_update_protection_data() -> dict[str, Any]:
|
||||||
|
"""Update binary sensor (protection window) data."""
|
||||||
|
low = entry.options.get(CONF_FROM_WINDOW, DEFAULT_FROM_WINDOW)
|
||||||
|
high = entry.options.get(CONF_TO_WINDOW, DEFAULT_TO_WINDOW)
|
||||||
|
return await client.uv_protection_window(low=low, high=high)
|
||||||
|
|
||||||
|
coordinators: dict[str, OpenUvCoordinator] = {
|
||||||
|
coordinator_name: OpenUvCoordinator(
|
||||||
|
hass,
|
||||||
|
name=coordinator_name,
|
||||||
|
latitude=client.latitude,
|
||||||
|
longitude=client.longitude,
|
||||||
|
update_method=update_method,
|
||||||
|
)
|
||||||
|
for coordinator_name, update_method in (
|
||||||
|
(DATA_UV, client.uv_index),
|
||||||
|
(DATA_PROTECTION_WINDOW, async_update_protection_data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
# We disable the client's request retry abilities here to avoid a lengthy (and
|
# We disable the client's request retry abilities here to avoid a lengthy (and
|
||||||
# blocking) startup:
|
# blocking) startup; then, if the initial update is successful, we re-enable client
|
||||||
openuv.client.disable_request_retries()
|
# request retries:
|
||||||
|
client.disable_request_retries()
|
||||||
try:
|
init_tasks = [
|
||||||
await openuv.async_update()
|
coordinator.async_config_entry_first_refresh()
|
||||||
except HomeAssistantError as err:
|
for coordinator in coordinators.values()
|
||||||
LOGGER.error("Config entry failed: %s", err)
|
]
|
||||||
raise ConfigEntryNotReady from err
|
await asyncio.gather(*init_tasks)
|
||||||
|
client.enable_request_retries()
|
||||||
# Once we've successfully authenticated, we re-enable client request retries:
|
|
||||||
openuv.client.enable_request_retries()
|
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = openuv
|
hass.data[DOMAIN][entry.entry_id] = coordinators
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
@callback
|
|
||||||
def extract_openuv(func: Callable) -> Callable:
|
|
||||||
"""Define a decorator to get the correct OpenUV object for a service call."""
|
|
||||||
|
|
||||||
async def wrapper(call: ServiceCall) -> None:
|
|
||||||
"""Wrap the service function."""
|
|
||||||
openuv: OpenUV = hass.data[DOMAIN][call.data[CONF_ENTRY_ID]]
|
|
||||||
|
|
||||||
try:
|
|
||||||
await func(call, openuv)
|
|
||||||
except OpenUvError as err:
|
|
||||||
raise HomeAssistantError(
|
|
||||||
f'Error while executing "{call.service}": {err}'
|
|
||||||
) from err
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
# We determine entity IDs needed to help the user migrate from deprecated services:
|
# We determine entity IDs needed to help the user migrate from deprecated services:
|
||||||
current_uv_index_entity_id = async_get_entity_id_from_unique_id_suffix(
|
current_uv_index_entity_id = async_get_entity_id_from_unique_id_suffix(
|
||||||
hass, entry, "current_uv_index"
|
hass, entry, "current_uv_index"
|
||||||
|
@ -183,8 +171,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
@_verify_domain_control
|
@_verify_domain_control
|
||||||
@extract_openuv
|
async def update_data(call: ServiceCall) -> None:
|
||||||
async def update_data(call: ServiceCall, openuv: OpenUV) -> None:
|
|
||||||
"""Refresh all OpenUV data."""
|
"""Refresh all OpenUV data."""
|
||||||
LOGGER.debug("Refreshing all OpenUV data")
|
LOGGER.debug("Refreshing all OpenUV data")
|
||||||
async_log_deprecated_service_call(
|
async_log_deprecated_service_call(
|
||||||
|
@ -194,12 +181,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
[protection_window_entity_id, current_uv_index_entity_id],
|
[protection_window_entity_id, current_uv_index_entity_id],
|
||||||
"2022.12.0",
|
"2022.12.0",
|
||||||
)
|
)
|
||||||
await openuv.async_update()
|
|
||||||
async_dispatcher_send(hass, TOPIC_UPDATE)
|
tasks = [coordinator.async_refresh() for coordinator in coordinators.values()]
|
||||||
|
try:
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
except UpdateFailed as err:
|
||||||
|
raise HomeAssistantError(err) from err
|
||||||
|
|
||||||
@_verify_domain_control
|
@_verify_domain_control
|
||||||
@extract_openuv
|
async def update_uv_index_data(call: ServiceCall) -> None:
|
||||||
async def update_uv_index_data(call: ServiceCall, openuv: OpenUV) -> None:
|
|
||||||
"""Refresh OpenUV UV index data."""
|
"""Refresh OpenUV UV index data."""
|
||||||
LOGGER.debug("Refreshing OpenUV UV index data")
|
LOGGER.debug("Refreshing OpenUV UV index data")
|
||||||
async_log_deprecated_service_call(
|
async_log_deprecated_service_call(
|
||||||
|
@ -209,12 +199,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
[current_uv_index_entity_id],
|
[current_uv_index_entity_id],
|
||||||
"2022.12.0",
|
"2022.12.0",
|
||||||
)
|
)
|
||||||
await openuv.async_update_uv_index_data()
|
|
||||||
async_dispatcher_send(hass, TOPIC_UPDATE)
|
try:
|
||||||
|
await coordinators[DATA_UV].async_request_refresh()
|
||||||
|
except UpdateFailed as err:
|
||||||
|
raise HomeAssistantError(err) from err
|
||||||
|
|
||||||
@_verify_domain_control
|
@_verify_domain_control
|
||||||
@extract_openuv
|
async def update_protection_data(call: ServiceCall) -> None:
|
||||||
async def update_protection_data(call: ServiceCall, openuv: OpenUV) -> None:
|
|
||||||
"""Refresh OpenUV protection window data."""
|
"""Refresh OpenUV protection window data."""
|
||||||
LOGGER.debug("Refreshing OpenUV protection window data")
|
LOGGER.debug("Refreshing OpenUV protection window data")
|
||||||
async_log_deprecated_service_call(
|
async_log_deprecated_service_call(
|
||||||
|
@ -224,8 +216,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
[protection_window_entity_id],
|
[protection_window_entity_id],
|
||||||
"2022.12.0",
|
"2022.12.0",
|
||||||
)
|
)
|
||||||
await openuv.async_update_protection_data()
|
|
||||||
async_dispatcher_send(hass, TOPIC_UPDATE)
|
try:
|
||||||
|
await coordinators[DATA_PROTECTION_WINDOW].async_request_refresh()
|
||||||
|
except UpdateFailed as err:
|
||||||
|
raise HomeAssistantError(err) from err
|
||||||
|
|
||||||
service_schema = vol.Schema(
|
service_schema = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -283,106 +278,42 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class OpenUV:
|
class OpenUvEntity(CoordinatorEntity):
|
||||||
"""Define a generic OpenUV object."""
|
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry, client: Client) -> None:
|
|
||||||
"""Initialize."""
|
|
||||||
self._update_protection_data_debouncer = Debouncer(
|
|
||||||
hass,
|
|
||||||
LOGGER,
|
|
||||||
cooldown=DEFAULT_DEBOUNCER_COOLDOWN_SECONDS,
|
|
||||||
immediate=True,
|
|
||||||
function=self._async_update_protection_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._update_uv_index_data_debouncer = Debouncer(
|
|
||||||
hass,
|
|
||||||
LOGGER,
|
|
||||||
cooldown=DEFAULT_DEBOUNCER_COOLDOWN_SECONDS,
|
|
||||||
immediate=True,
|
|
||||||
function=self._async_update_uv_index_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._entry = entry
|
|
||||||
self.client = client
|
|
||||||
self.data: dict[str, Any] = {DATA_PROTECTION_WINDOW: {}, DATA_UV: {}}
|
|
||||||
|
|
||||||
async def _async_update_protection_data(self) -> None:
|
|
||||||
"""Update binary sensor (protection window) data."""
|
|
||||||
low = self._entry.options.get(CONF_FROM_WINDOW, DEFAULT_FROM_WINDOW)
|
|
||||||
high = self._entry.options.get(CONF_TO_WINDOW, DEFAULT_TO_WINDOW)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = await self.client.uv_protection_window(low=low, high=high)
|
|
||||||
except OpenUvError as err:
|
|
||||||
raise HomeAssistantError(
|
|
||||||
f"Error during protection data update: {err}"
|
|
||||||
) from err
|
|
||||||
|
|
||||||
self.data[DATA_PROTECTION_WINDOW] = data.get("result")
|
|
||||||
|
|
||||||
async def _async_update_uv_index_data(self) -> None:
|
|
||||||
"""Update sensor (uv index, etc) data."""
|
|
||||||
try:
|
|
||||||
data = await self.client.uv_index()
|
|
||||||
except OpenUvError as err:
|
|
||||||
raise HomeAssistantError(
|
|
||||||
f"Error during UV index data update: {err}"
|
|
||||||
) from err
|
|
||||||
|
|
||||||
self.data[DATA_UV] = data.get("result")
|
|
||||||
|
|
||||||
async def async_update_protection_data(self) -> None:
|
|
||||||
"""Update binary sensor (protection window) data with a debouncer."""
|
|
||||||
await self._update_protection_data_debouncer.async_call()
|
|
||||||
|
|
||||||
async def async_update_uv_index_data(self) -> None:
|
|
||||||
"""Update sensor (uv index, etc) data with a debouncer."""
|
|
||||||
await self._update_uv_index_data_debouncer.async_call()
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Update sensor/binary sensor data."""
|
|
||||||
tasks = [self.async_update_protection_data(), self.async_update_uv_index_data()]
|
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
|
|
||||||
class OpenUvEntity(Entity):
|
|
||||||
"""Define a generic OpenUV entity."""
|
"""Define a generic OpenUV entity."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(self, openuv: OpenUV, description: EntityDescription) -> None:
|
def __init__(
|
||||||
|
self, coordinator: OpenUvCoordinator, description: EntityDescription
|
||||||
|
) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._attr_extra_state_attributes = {}
|
self._attr_extra_state_attributes = {}
|
||||||
self._attr_should_poll = False
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{openuv.client.latitude}_{openuv.client.longitude}_{description.key}"
|
f"{coordinator.latitude}_{coordinator.longitude}_{description.key}"
|
||||||
)
|
)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self.openuv = openuv
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Update the state."""
|
"""Respond to a DataUpdateCoordinator update."""
|
||||||
self.update_from_latest_data()
|
self._update_from_latest_data()
|
||||||
self.async_write_ha_state()
|
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:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Register callbacks."""
|
"""Handle entity which will be added."""
|
||||||
self.update_from_latest_data()
|
await super().async_added_to_hass()
|
||||||
self.async_on_remove(
|
self._update_from_latest_data()
|
||||||
async_dispatcher_connect(self.hass, TOPIC_UPDATE, self.async_update_state)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Update the entity.
|
"""Update the entity.
|
||||||
|
|
||||||
Only used by the generic entity update service. Should be implemented by each
|
Only used by the generic entity update service.
|
||||||
OpenUV platform.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
def update_from_latest_data(self) -> None:
|
|
||||||
"""Update the sensor using the latest data."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from homeassistant.util.dt import as_local, parse_datetime, utcnow
|
||||||
|
|
||||||
from . import OpenUvEntity
|
from . import OpenUvEntity
|
||||||
from .const import DATA_PROTECTION_WINDOW, DOMAIN, LOGGER, TYPE_PROTECTION_WINDOW
|
from .const import DATA_PROTECTION_WINDOW, DOMAIN, LOGGER, TYPE_PROTECTION_WINDOW
|
||||||
|
from .coordinator import OpenUvCoordinator
|
||||||
|
|
||||||
ATTR_PROTECTION_WINDOW_ENDING_TIME = "end_time"
|
ATTR_PROTECTION_WINDOW_ENDING_TIME = "end_time"
|
||||||
ATTR_PROTECTION_WINDOW_ENDING_UV = "end_uv"
|
ATTR_PROTECTION_WINDOW_ENDING_UV = "end_uv"
|
||||||
|
@ -26,32 +27,27 @@ BINARY_SENSOR_DESCRIPTION_PROTECTION_WINDOW = BinarySensorEntityDescription(
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# Once we've successfully authenticated, we re-enable client request retries:
|
||||||
"""Set up an OpenUV sensor based on a config entry."""
|
"""Set up an OpenUV sensor based on a config entry."""
|
||||||
openuv = hass.data[DOMAIN][entry.entry_id]
|
coordinators: dict[str, OpenUvCoordinator] = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[OpenUvBinarySensor(openuv, BINARY_SENSOR_DESCRIPTION_PROTECTION_WINDOW)]
|
[
|
||||||
|
OpenUvBinarySensor(
|
||||||
|
coordinators[DATA_PROTECTION_WINDOW],
|
||||||
|
BINARY_SENSOR_DESCRIPTION_PROTECTION_WINDOW,
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OpenUvBinarySensor(OpenUvEntity, BinarySensorEntity):
|
class OpenUvBinarySensor(OpenUvEntity, BinarySensorEntity):
|
||||||
"""Define a binary sensor for OpenUV."""
|
"""Define a binary sensor for OpenUV."""
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Update the entity.
|
|
||||||
|
|
||||||
Only used by the generic entity update service.
|
|
||||||
"""
|
|
||||||
await self.openuv.async_update_protection_data()
|
|
||||||
self.async_update_state()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_from_latest_data(self) -> None:
|
def _update_from_latest_data(self) -> None:
|
||||||
"""Update the state."""
|
"""Update the entity from the latest data."""
|
||||||
if not (data := self.openuv.data[DATA_PROTECTION_WINDOW]):
|
data = self.coordinator.data
|
||||||
self._attr_available = False
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_available = True
|
|
||||||
|
|
||||||
for key in ("from_time", "to_time", "from_uv", "to_uv"):
|
for key in ("from_time", "to_time", "from_uv", "to_uv"):
|
||||||
if not data.get(key):
|
if not data.get(key):
|
||||||
|
|
55
homeassistant/components/openuv/coordinator.py
Normal file
55
homeassistant/components/openuv/coordinator.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""Define an update coordinator for OpenUV."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Awaitable, Callable
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from pyopenuv.errors import OpenUvError
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import LOGGER
|
||||||
|
|
||||||
|
DEFAULT_DEBOUNCER_COOLDOWN_SECONDS = 15 * 60
|
||||||
|
|
||||||
|
|
||||||
|
class OpenUvCoordinator(DataUpdateCoordinator):
|
||||||
|
"""Define an OpenUV data coordinator."""
|
||||||
|
|
||||||
|
update_method: Callable[[], Awaitable[dict[str, Any]]]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
latitude: str,
|
||||||
|
longitude: str,
|
||||||
|
update_method: Callable[[], Awaitable[dict[str, Any]]],
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
LOGGER,
|
||||||
|
name=name,
|
||||||
|
update_method=update_method,
|
||||||
|
request_refresh_debouncer=Debouncer(
|
||||||
|
hass,
|
||||||
|
LOGGER,
|
||||||
|
cooldown=DEFAULT_DEBOUNCER_COOLDOWN_SECONDS,
|
||||||
|
immediate=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.latitude = latitude
|
||||||
|
self.longitude = longitude
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> dict[str, Any]:
|
||||||
|
"""Fetch data from OpenUV."""
|
||||||
|
try:
|
||||||
|
data = await self.update_method()
|
||||||
|
except OpenUvError as err:
|
||||||
|
raise UpdateFailed(f"Error during protection data update: {err}") from err
|
||||||
|
return cast(dict[str, Any], data["result"])
|
|
@ -13,8 +13,8 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import OpenUV
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
from .coordinator import OpenUvCoordinator
|
||||||
|
|
||||||
CONF_COORDINATES = "coordinates"
|
CONF_COORDINATES = "coordinates"
|
||||||
CONF_TITLE = "title"
|
CONF_TITLE = "title"
|
||||||
|
@ -33,9 +33,15 @@ async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: ConfigEntry
|
hass: HomeAssistant, entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
openuv: OpenUV = hass.data[DOMAIN][entry.entry_id]
|
coordinators: dict[str, OpenUvCoordinator] = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
return {
|
return async_redact_data(
|
||||||
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
|
{
|
||||||
"data": async_redact_data(openuv.data, TO_REDACT),
|
"entry": entry.as_dict(),
|
||||||
}
|
"data": {
|
||||||
|
coordinator_name: coordinator.data
|
||||||
|
for coordinator_name, coordinator in coordinators.items()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TO_REDACT,
|
||||||
|
)
|
||||||
|
|
|
@ -28,6 +28,7 @@ from .const import (
|
||||||
TYPE_SAFE_EXPOSURE_TIME_5,
|
TYPE_SAFE_EXPOSURE_TIME_5,
|
||||||
TYPE_SAFE_EXPOSURE_TIME_6,
|
TYPE_SAFE_EXPOSURE_TIME_6,
|
||||||
)
|
)
|
||||||
|
from .coordinator import OpenUvCoordinator
|
||||||
|
|
||||||
ATTR_MAX_UV_TIME = "time"
|
ATTR_MAX_UV_TIME = "time"
|
||||||
|
|
||||||
|
@ -122,31 +123,23 @@ async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a OpenUV sensor based on a config entry."""
|
"""Set up a OpenUV sensor based on a config entry."""
|
||||||
openuv = hass.data[DOMAIN][entry.entry_id]
|
coordinators: dict[str, OpenUvCoordinator] = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[OpenUvSensor(openuv, description) for description in SENSOR_DESCRIPTIONS]
|
[
|
||||||
|
OpenUvSensor(coordinators[DATA_UV], description)
|
||||||
|
for description in SENSOR_DESCRIPTIONS
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OpenUvSensor(OpenUvEntity, SensorEntity):
|
class OpenUvSensor(OpenUvEntity, SensorEntity):
|
||||||
"""Define a binary sensor for OpenUV."""
|
"""Define a binary sensor for OpenUV."""
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
|
||||||
"""Update the entity.
|
|
||||||
|
|
||||||
Only used by the generic entity update service.
|
|
||||||
"""
|
|
||||||
await self.openuv.async_update_uv_index_data()
|
|
||||||
self.async_update_state()
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_from_latest_data(self) -> None:
|
def _update_from_latest_data(self) -> None:
|
||||||
"""Update the state."""
|
"""Update the state."""
|
||||||
if (data := self.openuv.data[DATA_UV]) is None:
|
data = self.coordinator.data
|
||||||
self._attr_available = False
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_available = True
|
|
||||||
|
|
||||||
if self.entity_description.key == TYPE_CURRENT_OZONE_LEVEL:
|
if self.entity_description.key == TYPE_CURRENT_OZONE_LEVEL:
|
||||||
self._attr_native_value = data["ozone"]
|
self._attr_native_value = data["ozone"]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
"""Test OpenUV diagnostics."""
|
"""Test OpenUV diagnostics."""
|
||||||
from homeassistant.components.diagnostics import REDACTED
|
from homeassistant.components.diagnostics import REDACTED
|
||||||
from homeassistant.const import CONF_ENTITY_ID
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||||
|
@ -9,12 +8,6 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||||
async def test_entry_diagnostics(hass, config_entry, hass_client, setup_openuv):
|
async def test_entry_diagnostics(hass, config_entry, hass_client, setup_openuv):
|
||||||
"""Test config entry diagnostics."""
|
"""Test config entry diagnostics."""
|
||||||
await async_setup_component(hass, "homeassistant", {})
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
await hass.services.async_call(
|
|
||||||
"homeassistant",
|
|
||||||
"update_entity",
|
|
||||||
{CONF_ENTITY_ID: ["sensor.current_uv_index"]},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
|
assert await get_diagnostics_for_config_entry(hass, hass_client, config_entry) == {
|
||||||
"entry": {
|
"entry": {
|
||||||
"entry_id": config_entry.entry_id,
|
"entry_id": config_entry.entry_id,
|
||||||
|
|
Loading…
Add table
Reference in a new issue