Files
core/homeassistant/components/vistapool/time.py
T
2026-08-24 22:48:36 +02:00

133 lines
4.4 KiB
Python

"""Vistapool Time entities."""
from dataclasses import dataclass
from datetime import time
from typing import override
from aioaquarite import AquariteError
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import VistapoolConfigEntry
from .const import DOMAIN, SIGNAL_NEW_POOL
from .coordinator import VistapoolDataUpdateCoordinator
from .entity import VistapoolEntity
PARALLEL_UPDATES = 1
_SECONDS_PER_HOUR = 3600
_SECONDS_PER_MINUTE = 60
@dataclass(frozen=True, kw_only=True)
class VistapoolTimeEntityDescription(TimeEntityDescription):
"""Describes a Vistapool time entity."""
value_path: str
TIME_DESCRIPTIONS: tuple[VistapoolTimeEntityDescription, ...] = tuple(
VistapoolTimeEntityDescription(
key=f"filtration_interval_{interval}_{bound}",
translation_key=f"filtration_interval_{bound}",
translation_placeholders={"number": str(interval)},
entity_category=EntityCategory.CONFIG,
value_path=f"filtration.interval{interval}.{api_field}",
)
for interval in (1, 2, 3)
for bound, api_field in (("start", "from"), ("end", "to"))
)
def _build_time_entities(
coordinator: VistapoolDataUpdateCoordinator,
) -> list[TimeEntity]:
"""Build the time entities for a single pool."""
return [
VistapoolTime(coordinator, description) for description in TIME_DESCRIPTIONS
]
async def async_setup_entry(
hass: HomeAssistant,
entry: VistapoolConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Vistapool time entities for every pool on the account."""
entities: list[TimeEntity] = []
for coordinator in entry.runtime_data.coordinators.values():
entities.extend(_build_time_entities(coordinator))
async_add_entities(entities)
@callback
def _async_add_pool(coordinator: VistapoolDataUpdateCoordinator) -> None:
async_add_entities(_build_time_entities(coordinator))
entry.async_on_unload(
async_dispatcher_connect(
hass, f"{SIGNAL_NEW_POOL}_{entry.entry_id}", _async_add_pool
)
)
class VistapoolTime(VistapoolEntity, TimeEntity):
"""Generic Vistapool time driven by an entity description."""
entity_description: VistapoolTimeEntityDescription
def __init__(
self,
coordinator: VistapoolDataUpdateCoordinator,
description: VistapoolTimeEntityDescription,
) -> None:
"""Initialize the time entity."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = self.build_unique_id(description.key)
@property
@override
def native_value(self) -> time | None:
"""Return the interval bound as a time, decoded from seconds since midnight."""
raw = self.coordinator.get_value(self.entity_description.value_path)
if raw is None:
return None
try:
seconds = int(raw)
return time(
seconds // _SECONDS_PER_HOUR,
(seconds % _SECONDS_PER_HOUR) // _SECONDS_PER_MINUTE,
seconds % _SECONDS_PER_MINUTE,
)
except TypeError, ValueError:
# Also covers out-of-range values (negative or >= 24h), which make
# the time() constructor raise instead of silently wrapping.
return None
@override
async def async_set_value(self, value: time) -> None:
"""Send the interval bound to the controller as seconds since midnight."""
seconds = (
value.hour * _SECONDS_PER_HOUR
+ value.minute * _SECONDS_PER_MINUTE
+ value.second
)
try:
await self.coordinator.api.set_value(
self.coordinator.pool_id,
self.entity_description.value_path,
seconds,
)
except AquariteError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_failed",
translation_placeholders={"entity": self.entity_id},
) from err
self.coordinator.apply_optimistic(self.entity_description.value_path, seconds)