Files
core/homeassistant/components/starlink/time.py
T

112 lines
3.7 KiB
Python

"""Contains time pickers exposed by the Starlink integration."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import UTC, datetime, time, tzinfo
import math
from typing import override
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from .coordinator import StarlinkConfigEntry, StarlinkData, StarlinkUpdateCoordinator
from .entity import StarlinkEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: StarlinkConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up all time entities for this entry."""
async_add_entities(
StarlinkTimeEntity(config_entry.runtime_data, description)
for description in TIMES
)
@dataclass(frozen=True, kw_only=True)
class StarlinkTimeEntityDescription(TimeEntityDescription):
"""Describes a Starlink time entity."""
value_fn: Callable[[StarlinkData, tzinfo], time | None]
update_fn: Callable[[StarlinkUpdateCoordinator, time], Awaitable[None]]
available_fn: Callable[[StarlinkData], bool]
class StarlinkTimeEntity(StarlinkEntity, TimeEntity):
"""A TimeEntity for Starlink devices. Handles creating unique IDs."""
entity_description: StarlinkTimeEntityDescription
@property
@override
def native_value(self) -> time | None:
"""Return the value reported by the time."""
return self.entity_description.value_fn(
self.coordinator.data, self.coordinator.timezone
)
@property
@override
def available(self) -> bool:
"""Return True if entity is available."""
return self.entity_description.available_fn(self.coordinator.data)
@override
async def async_set_value(self, value: time) -> None:
"""Change the time."""
return await self.entity_description.update_fn(self.coordinator, value)
def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time:
hour = math.floor(utc_minutes / 60)
if hour > 23:
hour -= 24
minute = utc_minutes % 60
try:
utc = dt_util.utcnow().replace(
hour=hour, minute=minute, second=0, microsecond=0
)
except ValueError as exc:
raise HomeAssistantError from exc
return utc.astimezone(timezone).time()
def _time_to_utc_minutes(t: time, timezone: tzinfo) -> int:
try:
zoned_time = datetime.now( # pylint: disable=home-assistant-enforce-now
timezone
).replace(hour=t.hour, minute=t.minute, second=0, microsecond=0)
except ValueError as exc:
raise HomeAssistantError from exc
utc_time = zoned_time.astimezone(UTC).time()
return (utc_time.hour * 60) + utc_time.minute
TIMES = [
StarlinkTimeEntityDescription(
key="sleep_start",
translation_key="sleep_start",
value_fn=lambda data, timezone: _utc_minutes_to_time(data.sleep[0], timezone),
update_fn=lambda coordinator, time: coordinator.async_set_sleep_start(
_time_to_utc_minutes(time, coordinator.timezone)
),
available_fn=lambda data: data.sleep[2],
),
StarlinkTimeEntityDescription(
key="sleep_end",
translation_key="sleep_end",
value_fn=lambda data, timezone: _utc_minutes_to_time(
data.sleep[0] + data.sleep[1], timezone
),
update_fn=lambda coordinator, time: coordinator.async_set_sleep_duration(
_time_to_utc_minutes(time, coordinator.timezone)
),
available_fn=lambda data: data.sleep[2],
),
]