From 3d2f696d73f0e9c8d26f1e3062e007d3d955b5bf Mon Sep 17 00:00:00 2001 From: jjlawren Date: Sat, 29 May 2021 09:08:46 -0500 Subject: [PATCH] Reorganize SonosSpeaker class for readability (#51222) --- homeassistant/components/sonos/speaker.py | 112 ++++++++++++++-------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 079b916a4bc..701fe5aa8c4 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -146,36 +146,43 @@ class SonosSpeaker: self.household_id: str = soco.household_id self.media = SonosMedia(soco) + # Synchronization helpers self.is_first_poll: bool = True self._is_ready: bool = False + self._platforms_ready: set[str] = set() # Subscriptions and events self._subscriptions: list[SubscriptionBase] = [] self._resubscription_lock: asyncio.Lock | None = None self._event_dispatchers: dict[str, Callable] = {} + # Scheduled callback handles self._poll_timer: Callable | None = None self._seen_timer: Callable | None = None - self._platforms_ready: set[str] = set() + # Dispatcher handles self._entity_creation_dispatcher: Callable | None = None self._group_dispatcher: Callable | None = None self._seen_dispatcher: Callable | None = None + # Device information self.mac_address = speaker_info["mac_address"] self.model_name = speaker_info["model_name"] self.version = speaker_info["display_version"] self.zone_name = speaker_info["zone_name"] + # Battery self.battery_info: dict[str, Any] | None = None self._last_battery_event: datetime.datetime | None = None self._battery_poll_timer: Callable | None = None + # Volume / Sound self.volume: int | None = None self.muted: bool | None = None self.night_mode: bool | None = None self.dialog_mode: bool | None = None + # Grouping self.coordinator: SonosSpeaker | None = None self.sonos_group: list[SonosSpeaker] = [self] self.sonos_group_entities: list[str] = [] @@ -232,6 +239,9 @@ class SonosSpeaker: dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self) + # + # Entity management + # async def async_handle_new_entity(self, entity_type: str) -> None: """Listen to new entities to trigger first subscription.""" self._platforms_ready.add(entity_type) @@ -254,11 +264,32 @@ class SonosSpeaker: self.media.play_mode = self.soco.play_mode self.update_volume() + # + # Properties + # @property def available(self) -> bool: """Return whether this speaker is available.""" return self._seen_timer is not None + @property + def favorites(self) -> SonosFavorites: + """Return the SonosFavorites instance for this household.""" + return self.hass.data[DATA_SONOS].favorites[self.household_id] + + @property + def is_coordinator(self) -> bool: + """Return true if player is a coordinator.""" + return self.coordinator is None + + @property + def subscription_address(self) -> str | None: + """Return the current subscription callback address if any.""" + if self._subscriptions: + addr, port = self._subscriptions[0].event_listener.address + return ":".join([addr, str(port)]) + return None + # # Subscription handling and event dispatchers # @@ -295,6 +326,30 @@ class SonosSpeaker: subscription.auto_renew_fail = self.async_renew_failed self._subscriptions.append(subscription) + @callback + def async_renew_failed(self, exception: Exception) -> None: + """Handle a failed subscription renewal.""" + self.hass.async_create_task(self.async_resubscribe(exception)) + + async def async_resubscribe(self, exception: Exception) -> None: + """Attempt to resubscribe when a renewal failure is detected.""" + async with self._resubscription_lock: + if not self.available: + return + + if getattr(exception, "status", None) == 412: + _LOGGER.warning( + "Subscriptions for %s failed, speaker may have lost power", + self.zone_name, + ) + else: + _LOGGER.error( + "Subscription renewals for %s failed", + self.zone_name, + exc_info=exception, + ) + await self.async_unseen() + @callback def async_dispatch_event(self, event: SonosEvent) -> None: """Handle callback event and route as needed.""" @@ -349,6 +404,9 @@ class SonosSpeaker: self.async_write_entity_states() + # + # Speaker availability methods + # async def async_seen(self, soco: SoCo | None = None) -> None: """Record that this speaker was seen right now.""" if soco is not None: @@ -386,28 +444,6 @@ class SonosSpeaker: self.async_write_entity_states() - async def async_resubscribe(self, exception: Exception) -> None: - """Attempt to resubscribe when a renewal failure is detected.""" - async with self._resubscription_lock: - if self.available: - if getattr(exception, "status", None) == 412: - _LOGGER.warning( - "Subscriptions for %s failed, speaker may have lost power", - self.zone_name, - ) - else: - _LOGGER.error( - "Subscription renewals for %s failed", - self.zone_name, - exc_info=exception, - ) - await self.async_unseen() - - @callback - def async_renew_failed(self, exception: Exception) -> None: - """Handle a failed subscription renewal.""" - self.hass.async_create_task(self.async_resubscribe(exception)) - async def async_unseen(self, now: datetime.datetime | None = None) -> None: """Make this player unavailable when it was not seen recently.""" self.async_write_entity_states() @@ -425,6 +461,9 @@ class SonosSpeaker: self._subscriptions = [] + # + # Alarm management + # def update_alarms_for_speaker(self) -> set[str]: """Update current alarm instances. @@ -453,6 +492,9 @@ class SonosSpeaker: dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms) dispatcher_send(self.hass, SONOS_ALARM_UPDATE) + # + # Battery management + # async def async_update_battery_info(self, battery_dict: dict[str, Any]) -> None: """Update battery info using the decoded SonosEvent.""" self._last_battery_event = dt_util.utcnow() @@ -477,11 +519,6 @@ class SonosSpeaker: ): self.battery_info = battery_info - @property - def is_coordinator(self) -> bool: - """Return true if player is a coordinator.""" - return self.coordinator is None - @property def power_source(self) -> str | None: """Return the name of the current power source. @@ -516,6 +553,9 @@ class SonosSpeaker: self.battery_info = battery_info self.async_write_entity_states() + # + # Group management + # def update_groups(self, event: SonosEvent | None = None) -> None: """Handle callback for topology change event.""" coro = self.create_update_groups_coro(event) @@ -786,11 +826,9 @@ class SonosSpeaker: for speaker in hass.data[DATA_SONOS].discovered.values(): speaker.soco._zgs_cache.clear() # pylint: disable=protected-access - @property - def favorites(self) -> SonosFavorites: - """Return the SonosFavorites instance for this household.""" - return self.hass.data[DATA_SONOS].favorites[self.household_id] - + # + # Media and playback state handlers + # def update_volume(self) -> None: """Update information about current volume settings.""" self.volume = self.soco.volume @@ -951,11 +989,3 @@ class SonosSpeaker: elif update_media_position: self.media.position = current_position self.media.position_updated_at = dt_util.utcnow() - - @property - def subscription_address(self) -> str | None: - """Return the current subscription callback address if any.""" - if self._subscriptions: - addr, port = self._subscriptions[0].event_listener.address - return ":".join([addr, str(port)]) - return None