core/homeassistant/components/sonos/alarms.py

99 lines
3.3 KiB
Python

"""Class representing Sonos alarms."""
from __future__ import annotations
from collections.abc import Iterator
import logging
from typing import TYPE_CHECKING, Any
from soco import SoCo
from soco.alarms import Alarm, Alarms
from soco.events_base import Event as SonosEvent
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import DATA_SONOS, SONOS_ALARMS_UPDATED, SONOS_CREATE_ALARM
from .helpers import soco_error
from .household_coordinator import SonosHouseholdCoordinator
if TYPE_CHECKING:
from .speaker import SonosSpeaker
_LOGGER = logging.getLogger(__name__)
class SonosAlarms(SonosHouseholdCoordinator):
"""Coordinator class for Sonos alarms."""
def __init__(self, *args: Any) -> None:
"""Initialize the data."""
super().__init__(*args)
self.alarms: Alarms = Alarms()
self.created_alarm_ids: set[str] = set()
def __iter__(self) -> Iterator:
"""Return an iterator for the known alarms."""
return iter(self.alarms)
def get(self, alarm_id: str) -> Alarm | None:
"""Get an Alarm instance."""
return self.alarms.get(alarm_id)
async def async_update_entities(
self, soco: SoCo, update_id: int | None = None
) -> None:
"""Create and update alarms entities, return success."""
updated = await self.hass.async_add_executor_job(
self.update_cache, soco, update_id
)
if not updated:
return
for alarm_id, alarm in self.alarms.alarms.items():
if alarm_id in self.created_alarm_ids:
continue
speaker = self.hass.data[DATA_SONOS].discovered.get(alarm.zone.uid)
if speaker:
async_dispatcher_send(
self.hass, SONOS_CREATE_ALARM, speaker, [alarm_id]
)
async_dispatcher_send(self.hass, f"{SONOS_ALARMS_UPDATED}-{self.household_id}")
async def async_process_event(
self, event: SonosEvent, speaker: SonosSpeaker
) -> None:
"""Process the event payload in an async lock and update entities."""
event_id = event.variables["alarm_list_version"].split(":")[-1]
event_id = int(event_id)
async with self.cache_update_lock:
if event_id <= self.last_processed_event_id:
# Skip updates if this event_id has already been seen
return
speaker.event_stats.process(event)
await self.async_update_entities(speaker.soco, event_id)
@soco_error()
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
"""Update cache of known alarms and return if cache has changed."""
self.alarms.update(soco)
if update_id and self.alarms.last_id < update_id:
# Skip updates if latest query result is outdated or lagging
return False
if (
self.last_processed_event_id
and self.alarms.last_id <= self.last_processed_event_id
):
# Skip updates already processed
return False
_LOGGER.debug(
"Updating processed event %s from %s (was %s)",
self.alarms.last_id,
soco,
self.last_processed_event_id,
)
self.last_processed_event_id = self.alarms.last_id
return True