Refactor Sonos polling (#65722)
* Refactor Sonos polling Explicitly rename fallback polling Catch soco exceptions centrally where possible Create SonosPollingEntity subclass Remove unnecessary soco_error fixture argument Remove unnecessary polling in update_volume() Adjust log levels and wording Set explicit timeout on library * Adjust logging to use raised exceptions * Simplify availabiliity checks when using built-in poller * Fix typing for return values
This commit is contained in:
parent
4efebcb86c
commit
a7fd477c64
14 changed files with 89 additions and 85 deletions
|
@ -119,6 +119,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Sonos from a config entry."""
|
||||
soco_config.EVENTS_MODULE = events_asyncio
|
||||
soco_config.REQUEST_TIMEOUT = 9.5
|
||||
|
||||
if DATA_SONOS not in hass.data:
|
||||
hass.data[DATA_SONOS] = SonosData()
|
||||
|
|
|
@ -8,11 +8,11 @@ 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 soco.exceptions import SoCoException
|
||||
|
||||
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:
|
||||
|
@ -71,13 +71,10 @@ class SonosAlarms(SonosHouseholdCoordinator):
|
|||
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."""
|
||||
try:
|
||||
self.alarms.update(soco)
|
||||
except (OSError, SoCoException) as err:
|
||||
_LOGGER.error("Could not update alarms using %s: %s", soco, err)
|
||||
return False
|
||||
self.alarms.update(soco)
|
||||
|
||||
if update_id and self.alarms.last_id < update_id:
|
||||
# Skip updates if latest query result is outdated or lagging
|
||||
|
|
|
@ -64,7 +64,7 @@ class SonosPowerEntity(SonosEntity, BinarySensorEntity):
|
|||
self._attr_unique_id = f"{self.soco.uid}-power"
|
||||
self._attr_name = f"{self.speaker.zone_name} Power"
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
await self.speaker.async_poll_battery()
|
||||
|
||||
|
@ -98,7 +98,7 @@ class SonosMicrophoneSensorEntity(SonosEntity, BinarySensorEntity):
|
|||
self._attr_unique_id = f"{self.soco.uid}-microphone"
|
||||
self._attr_name = f"{self.speaker.zone_name} Microphone"
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Stub for abstract class implementation. Not a pollable attribute."""
|
||||
|
||||
@property
|
||||
|
|
|
@ -153,7 +153,7 @@ SONOS_CREATE_MIC_SENSOR = "sonos_create_mic_sensor"
|
|||
SONOS_CREATE_SWITCHES = "sonos_create_switches"
|
||||
SONOS_CREATE_LEVELS = "sonos_create_levels"
|
||||
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
|
||||
SONOS_POLL_UPDATE = "sonos_poll_update"
|
||||
SONOS_FALLBACK_POLL = "sonos_fallback_poll"
|
||||
SONOS_ALARMS_UPDATED = "sonos_alarms_updated"
|
||||
SONOS_FAVORITES_UPDATED = "sonos_favorites_updated"
|
||||
SONOS_SPEAKER_ACTIVITY = "sonos_speaker_activity"
|
||||
|
|
|
@ -7,7 +7,6 @@ import logging
|
|||
|
||||
import soco.config as soco_config
|
||||
from soco.core import SoCo
|
||||
from soco.exceptions import SoCoException
|
||||
|
||||
from homeassistant.components import persistent_notification
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
|
@ -17,10 +16,11 @@ from homeassistant.helpers.entity import DeviceInfo, Entity
|
|||
from .const import (
|
||||
DATA_SONOS,
|
||||
DOMAIN,
|
||||
SONOS_FALLBACK_POLL,
|
||||
SONOS_FAVORITES_UPDATED,
|
||||
SONOS_POLL_UPDATE,
|
||||
SONOS_STATE_UPDATED,
|
||||
)
|
||||
from .exception import SonosUpdateError
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements"
|
||||
|
@ -43,8 +43,8 @@ class SonosEntity(Entity):
|
|||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{SONOS_POLL_UPDATE}-{self.soco.uid}",
|
||||
self.async_poll,
|
||||
f"{SONOS_FALLBACK_POLL}-{self.soco.uid}",
|
||||
self.async_fallback_poll,
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
|
@ -66,7 +66,7 @@ class SonosEntity(Entity):
|
|||
"""Clean up when entity is removed."""
|
||||
del self.hass.data[DATA_SONOS].entity_id_mappings[self.entity_id]
|
||||
|
||||
async def async_poll(self, now: datetime.datetime) -> None:
|
||||
async def async_fallback_poll(self, now: datetime.datetime) -> None:
|
||||
"""Poll the entity if subscriptions fail."""
|
||||
if not self.speaker.subscriptions_failed:
|
||||
if soco_config.EVENT_ADVERTISE_IP:
|
||||
|
@ -86,13 +86,16 @@ class SonosEntity(Entity):
|
|||
self.speaker.subscriptions_failed = True
|
||||
await self.speaker.async_unsubscribe()
|
||||
try:
|
||||
await self._async_poll()
|
||||
except (OSError, SoCoException) as ex:
|
||||
_LOGGER.debug("Error connecting to %s: %s", self.entity_id, ex)
|
||||
await self._async_fallback_poll()
|
||||
except SonosUpdateError as err:
|
||||
_LOGGER.debug("Could not fallback poll: %s", err)
|
||||
|
||||
@abstractmethod
|
||||
async def _async_poll(self) -> None:
|
||||
"""Poll the specific functionality. Should be implemented by platforms if needed."""
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Poll the specific functionality if subscriptions fail.
|
||||
|
||||
Should be implemented by platforms if needed.
|
||||
"""
|
||||
|
||||
@property
|
||||
def soco(self) -> SoCo:
|
||||
|
@ -120,3 +123,20 @@ class SonosEntity(Entity):
|
|||
def available(self) -> bool:
|
||||
"""Return whether this device is available."""
|
||||
return self.speaker.available
|
||||
|
||||
|
||||
class SonosPollingEntity(SonosEntity):
|
||||
"""Representation of a Sonos entity which may not support updating by subscriptions."""
|
||||
|
||||
@abstractmethod
|
||||
def poll_state(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update the state using the built-in entity poller."""
|
||||
if not self.available:
|
||||
return
|
||||
try:
|
||||
self.poll_state()
|
||||
except SonosUpdateError as err:
|
||||
_LOGGER.debug("Could not poll: %s", err)
|
||||
|
|
|
@ -7,5 +7,5 @@ class UnknownMediaType(BrowseError):
|
|||
"""Unknown media type."""
|
||||
|
||||
|
||||
class SpeakerUnavailable(HomeAssistantError):
|
||||
"""Speaker is unavailable."""
|
||||
class SonosUpdateError(HomeAssistantError):
|
||||
"""Update failed."""
|
||||
|
|
|
@ -14,6 +14,7 @@ from soco.exceptions import SoCoException
|
|||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import SONOS_FAVORITES_UPDATED
|
||||
from .helpers import soco_error
|
||||
from .household_coordinator import SonosHouseholdCoordinator
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -90,6 +91,7 @@ class SonosFavorites(SonosHouseholdCoordinator):
|
|||
self.last_processed_event_id = event_id
|
||||
await self.async_update_entities(speaker.soco, container_id)
|
||||
|
||||
@soco_error()
|
||||
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
|
||||
"""Update cache of known favorites and return if cache has changed."""
|
||||
new_favorites = soco.music_library.get_sonos_favorites()
|
||||
|
|
|
@ -5,17 +5,18 @@ from collections.abc import Callable
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from soco import SoCo
|
||||
from soco.exceptions import SoCoException, SoCoUPnPException
|
||||
from typing_extensions import Concatenate, ParamSpec
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import SONOS_SPEAKER_ACTIVITY
|
||||
from .exception import SpeakerUnavailable
|
||||
from .exception import SonosUpdateError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .entity import SonosEntity
|
||||
from .household_coordinator import SonosHouseholdCoordinator
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
UID_PREFIX = "RINCON_"
|
||||
|
@ -23,13 +24,13 @@ UID_POSTFIX = "01400"
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_T = TypeVar("_T", "SonosSpeaker", "SonosEntity")
|
||||
_T = TypeVar("_T", "SonosSpeaker", "SonosEntity", "SonosHouseholdCoordinator")
|
||||
_R = TypeVar("_R")
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
|
||||
def soco_error(
|
||||
errorcodes: list[str] | None = None, raise_on_err: bool = True
|
||||
errorcodes: list[str] | None = None,
|
||||
) -> Callable[ # type: ignore[misc]
|
||||
[Callable[Concatenate[_T, _P], _R]], Callable[Concatenate[_T, _P], _R | None]
|
||||
]:
|
||||
|
@ -42,10 +43,9 @@ def soco_error(
|
|||
|
||||
def wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> _R | None:
|
||||
"""Wrap for all soco UPnP exception."""
|
||||
args_soco = next((arg for arg in args if isinstance(arg, SoCo)), None)
|
||||
try:
|
||||
result = funct(self, *args, **kwargs)
|
||||
except SpeakerUnavailable:
|
||||
return None
|
||||
except (OSError, SoCoException, SoCoUPnPException) as err:
|
||||
error_code = getattr(err, "error_code", None)
|
||||
function = funct.__qualname__
|
||||
|
@ -55,20 +55,22 @@ def soco_error(
|
|||
)
|
||||
return None
|
||||
|
||||
# In order of preference:
|
||||
# * SonosSpeaker instance
|
||||
# * SoCo instance passed as an arg
|
||||
# * SoCo instance (as self)
|
||||
speaker_or_soco = getattr(self, "speaker", args_soco or self)
|
||||
zone_name = speaker_or_soco.zone_name
|
||||
# Prefer the entity_id if available, zone name as a fallback
|
||||
# Needed as SonosSpeaker instances are not entities
|
||||
zone_name = getattr(self, "speaker", self).zone_name
|
||||
target = getattr(self, "entity_id", zone_name)
|
||||
message = f"Error calling {function} on {target}: {err}"
|
||||
if raise_on_err:
|
||||
raise HomeAssistantError(message) from err
|
||||
|
||||
_LOGGER.warning(message)
|
||||
return None
|
||||
raise SonosUpdateError(message) from err
|
||||
|
||||
dispatch_soco = args_soco or self.soco
|
||||
dispatcher_send(
|
||||
self.hass,
|
||||
f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}",
|
||||
f"{SONOS_SPEAKER_ACTIVITY}-{dispatch_soco.uid}",
|
||||
funct.__qualname__,
|
||||
)
|
||||
return result
|
||||
|
|
|
@ -6,12 +6,12 @@ from collections.abc import Callable, Coroutine
|
|||
import logging
|
||||
|
||||
from soco import SoCo
|
||||
from soco.exceptions import SoCoException
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
|
||||
from .const import DATA_SONOS
|
||||
from .exception import SonosUpdateError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -56,11 +56,10 @@ class SonosHouseholdCoordinator:
|
|||
_LOGGER.debug("Polling %s using %s", self.class_type, speaker.soco)
|
||||
try:
|
||||
await self.async_update_entities(speaker.soco)
|
||||
except (OSError, SoCoException) as err:
|
||||
except SonosUpdateError as err:
|
||||
_LOGGER.error(
|
||||
"Could not refresh %s using %s: %s",
|
||||
"Could not refresh %s: %s",
|
||||
self.class_type,
|
||||
speaker.soco,
|
||||
err,
|
||||
)
|
||||
else:
|
||||
|
|
|
@ -283,7 +283,7 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||
return STATE_PLAYING
|
||||
return STATE_IDLE
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Retrieve latest state by polling."""
|
||||
await self.hass.data[DATA_SONOS].favorites[
|
||||
self.speaker.household_id
|
||||
|
|
|
@ -12,7 +12,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
|
||||
from .const import SONOS_CREATE_LEVELS
|
||||
from .entity import SonosEntity
|
||||
from .exception import SpeakerUnavailable
|
||||
from .helpers import soco_error
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
|
@ -75,16 +74,13 @@ class SonosLevelEntity(SonosEntity, NumberEntity):
|
|||
self.level_type = level_type
|
||||
self._attr_min_value, self._attr_max_value = valid_range
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Poll the value if subscriptions are not working."""
|
||||
await self.hass.async_add_executor_job(self.update)
|
||||
|
||||
@soco_error(raise_on_err=False)
|
||||
def update(self) -> None:
|
||||
"""Fetch number state if necessary."""
|
||||
if not self.available:
|
||||
raise SpeakerUnavailable
|
||||
await self.hass.async_add_executor_job(self.poll_state)
|
||||
|
||||
@soco_error()
|
||||
def poll_state(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
state = getattr(self.soco, self.level_type)
|
||||
setattr(self.speaker, self.level_type, state)
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ from homeassistant.helpers.entity import EntityCategory
|
|||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY
|
||||
from .entity import SonosEntity
|
||||
from .entity import SonosEntity, SonosPollingEntity
|
||||
from .helpers import soco_error
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -64,7 +65,7 @@ class SonosBatteryEntity(SonosEntity, SensorEntity):
|
|||
self._attr_unique_id = f"{self.soco.uid}-battery"
|
||||
self._attr_name = f"{self.speaker.zone_name} Battery"
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
await self.speaker.async_poll_battery()
|
||||
|
||||
|
@ -79,7 +80,7 @@ class SonosBatteryEntity(SonosEntity, SensorEntity):
|
|||
return self.speaker.available and self.speaker.power_source
|
||||
|
||||
|
||||
class SonosAudioInputFormatSensorEntity(SonosEntity, SensorEntity):
|
||||
class SonosAudioInputFormatSensorEntity(SonosPollingEntity, SensorEntity):
|
||||
"""Representation of a Sonos audio import format sensor entity."""
|
||||
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
@ -93,9 +94,10 @@ class SonosAudioInputFormatSensorEntity(SonosEntity, SensorEntity):
|
|||
self._attr_name = f"{self.speaker.zone_name} Audio Input Format"
|
||||
self._attr_native_value = audio_format
|
||||
|
||||
def update(self) -> None:
|
||||
@soco_error()
|
||||
def poll_state(self) -> None:
|
||||
"""Poll the device for the current state."""
|
||||
self._attr_native_value = self.soco.soundbar_audio_input_format
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Provide a stub for required ABC method."""
|
||||
|
|
|
@ -16,7 +16,7 @@ import defusedxml.ElementTree as ET
|
|||
from soco.core import MUSIC_SRC_LINE_IN, MUSIC_SRC_RADIO, MUSIC_SRC_TV, SoCo
|
||||
from soco.data_structures import DidlAudioBroadcast, DidlPlaylistContainer
|
||||
from soco.events_base import Event as SonosEvent, SubscriptionBase
|
||||
from soco.exceptions import SoCoException, SoCoSlaveException, SoCoUPnPException
|
||||
from soco.exceptions import SoCoException, SoCoUPnPException
|
||||
from soco.music_library import MusicLibrary
|
||||
from soco.plugins.plex import PlexPlugin
|
||||
from soco.plugins.sharelink import ShareLinkPlugin
|
||||
|
@ -50,7 +50,7 @@ from .const import (
|
|||
SONOS_CREATE_MEDIA_PLAYER,
|
||||
SONOS_CREATE_MIC_SENSOR,
|
||||
SONOS_CREATE_SWITCHES,
|
||||
SONOS_POLL_UPDATE,
|
||||
SONOS_FALLBACK_POLL,
|
||||
SONOS_REBOOTED,
|
||||
SONOS_SPEAKER_ACTIVITY,
|
||||
SONOS_SPEAKER_ADDED,
|
||||
|
@ -354,7 +354,7 @@ class SonosSpeaker:
|
|||
partial(
|
||||
async_dispatcher_send,
|
||||
self.hass,
|
||||
f"{SONOS_POLL_UPDATE}-{self.soco.uid}",
|
||||
f"{SONOS_FALLBACK_POLL}-{self.soco.uid}",
|
||||
),
|
||||
SCAN_INTERVAL,
|
||||
)
|
||||
|
@ -568,7 +568,7 @@ class SonosSpeaker:
|
|||
if not self.available:
|
||||
return
|
||||
|
||||
_LOGGER.debug(
|
||||
_LOGGER.warning(
|
||||
"No recent activity and cannot reach %s, marking unavailable",
|
||||
self.zone_name,
|
||||
)
|
||||
|
@ -1044,18 +1044,11 @@ class SonosSpeaker:
|
|||
#
|
||||
# Media and playback state handlers
|
||||
#
|
||||
@soco_error(raise_on_err=False)
|
||||
@soco_error()
|
||||
def update_volume(self) -> None:
|
||||
"""Update information about current volume settings."""
|
||||
self.volume = self.soco.volume
|
||||
self.muted = self.soco.mute
|
||||
self.night_mode = self.soco.night_mode
|
||||
self.dialog_level = self.soco.dialog_level
|
||||
|
||||
try:
|
||||
self.cross_fade = self.soco.cross_fade
|
||||
except SoCoSlaveException:
|
||||
pass
|
||||
|
||||
@soco_error()
|
||||
def update_media(self, event: SonosEvent | None = None) -> None:
|
||||
|
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import datetime
|
||||
import logging
|
||||
|
||||
from soco.exceptions import SoCoException, SoCoSlaveException, SoCoUPnPException
|
||||
from soco.exceptions import SoCoSlaveException, SoCoUPnPException
|
||||
|
||||
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -22,8 +22,7 @@ from .const import (
|
|||
SONOS_CREATE_ALARM,
|
||||
SONOS_CREATE_SWITCHES,
|
||||
)
|
||||
from .entity import SonosEntity
|
||||
from .exception import SpeakerUnavailable
|
||||
from .entity import SonosEntity, SonosPollingEntity
|
||||
from .helpers import soco_error
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
|
@ -143,7 +142,7 @@ async def async_setup_entry(
|
|||
)
|
||||
|
||||
|
||||
class SonosSwitchEntity(SonosEntity, SwitchEntity):
|
||||
class SonosSwitchEntity(SonosPollingEntity, SwitchEntity):
|
||||
"""Representation of a Sonos feature switch."""
|
||||
|
||||
def __init__(self, feature_type: str, speaker: SonosSpeaker) -> None:
|
||||
|
@ -163,17 +162,14 @@ class SonosSwitchEntity(SonosEntity, SwitchEntity):
|
|||
self._attr_entity_registry_enabled_default = False
|
||||
self._attr_should_poll = True
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Handle polling for subscription-based switches when subscription fails."""
|
||||
if not self.should_poll:
|
||||
await self.hass.async_add_executor_job(self.update)
|
||||
|
||||
@soco_error(raise_on_err=False)
|
||||
def update(self) -> None:
|
||||
"""Fetch switch state if necessary."""
|
||||
if not self.available:
|
||||
raise SpeakerUnavailable
|
||||
await self.hass.async_add_executor_job(self.poll_state)
|
||||
|
||||
@soco_error()
|
||||
def poll_state(self) -> None:
|
||||
"""Poll the current state of the switch."""
|
||||
state = getattr(self.soco, self.feature_type)
|
||||
setattr(self.speaker, self.feature_type, state)
|
||||
|
||||
|
@ -244,7 +240,7 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity):
|
|||
str(self.alarm.start_time)[0:5],
|
||||
)
|
||||
|
||||
async def _async_poll(self) -> None:
|
||||
async def _async_fallback_poll(self) -> None:
|
||||
"""Call the central alarm polling method."""
|
||||
await self.hass.data[DATA_SONOS].alarms[self.household_id].async_poll()
|
||||
|
||||
|
@ -267,7 +263,6 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity):
|
|||
if not self.async_check_if_available():
|
||||
return
|
||||
|
||||
_LOGGER.debug("Updating alarm: %s", self.entity_id)
|
||||
if self.speaker.soco.uid != self.alarm.zone.uid:
|
||||
self.speaker = self.hass.data[DATA_SONOS].discovered.get(
|
||||
self.alarm.zone.uid
|
||||
|
@ -350,14 +345,11 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity):
|
|||
"""Turn alarm switch off."""
|
||||
await self.async_handle_switch_on_off(turn_on=False)
|
||||
|
||||
@soco_error()
|
||||
async def async_handle_switch_on_off(self, turn_on: bool) -> None:
|
||||
"""Handle turn on/off of alarm switch."""
|
||||
try:
|
||||
_LOGGER.debug("Toggling the state of %s", self.entity_id)
|
||||
self.alarm.enabled = turn_on
|
||||
await self.hass.async_add_executor_job(self.alarm.save)
|
||||
except (OSError, SoCoException, SoCoUPnPException) as exc:
|
||||
_LOGGER.error("Could not update %s: %s", self.entity_id, exc)
|
||||
self.alarm.enabled = turn_on
|
||||
await self.hass.async_add_executor_job(self.alarm.save)
|
||||
|
||||
|
||||
@callback
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue