From 45195d2ea975e194c8affec4393766cd5134d865 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 15 Mar 2024 01:03:44 -1000 Subject: [PATCH] Avoid multiple context switches to setup a sonos speaker (#113378) --- homeassistant/components/sonos/speaker.py | 69 ++++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index ebc981790fa..2d92fe2f741 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -31,7 +31,7 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_send, dispatcher_send, ) -from homeassistant.helpers.event import async_track_time_interval, track_time_interval +from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import dt as dt_util from .alarms import SonosAlarms @@ -127,7 +127,6 @@ class SonosSpeaker: zone_group_state_sub.callback = self.async_dispatch_event self._subscriptions.append(zone_group_state_sub) self._subscription_lock: asyncio.Lock | None = None - self._event_dispatchers: dict[str, Callable] = {} self._last_activity: float = NEVER_TIME self._last_event_cache: dict[str, Any] = {} self.activity_stats: ActivityStatistics = ActivityStatistics(self.zone_name) @@ -179,13 +178,22 @@ class SonosSpeaker: self.snapshot_group: list[SonosSpeaker] = [] self._group_members_missing: set[str] = set() - async def async_setup(self, entry: ConfigEntry) -> None: + async def async_setup( + self, entry: ConfigEntry, has_battery: bool, dispatches: list[tuple[Any, ...]] + ) -> None: """Complete setup in async context.""" + # Battery events can be infrequent, polling is still necessary + if has_battery: + self._battery_poll_timer = async_track_time_interval( + self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL + ) + self.websocket = SonosWebsocket( self.soco.ip_address, player_id=self.soco.uid, session=async_get_clientsession(self.hass), ) + dispatch_pairs: tuple[tuple[str, Callable[..., Any]], ...] = ( (SONOS_CHECK_ACTIVITY, self.async_check_activity), (SONOS_SPEAKER_ADDED, self.async_update_group_for_uid), @@ -203,6 +211,11 @@ class SonosSpeaker: ) ) + for dispatch in dispatches: + async_dispatcher_send(self.hass, *dispatch) + + await self.async_subscribe() + def setup(self, entry: ConfigEntry) -> None: """Run initial setup of the speaker.""" self.media.play_mode = self.soco.play_mode @@ -211,53 +224,34 @@ class SonosSpeaker: if self.is_coordinator: self.media.poll_media() - future = asyncio.run_coroutine_threadsafe( - self.async_setup(entry), self.hass.loop - ) - future.result(timeout=10) - - dispatcher_send(self.hass, SONOS_CREATE_LEVELS, self) + dispatches: list[tuple[Any, ...]] = [(SONOS_CREATE_LEVELS, self)] if audio_format := self.soco.soundbar_audio_input_format: - dispatcher_send( - self.hass, SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format - ) + dispatches.append((SONOS_CREATE_AUDIO_FORMAT_SENSOR, self, audio_format)) + has_battery = False try: self.battery_info = self.fetch_battery_info() except SonosUpdateError: _LOGGER.debug("No battery available for %s", self.zone_name) else: - # Battery events can be infrequent, polling is still necessary - self._battery_poll_timer = track_time_interval( - self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL - ) + has_battery = True dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self) if (mic_enabled := self.soco.mic_enabled) is not None: self.mic_enabled = mic_enabled - dispatcher_send(self.hass, SONOS_CREATE_MIC_SENSOR, self) + dispatches.append((SONOS_CREATE_MIC_SENSOR, self)) if new_alarms := [ alarm.alarm_id for alarm in self.alarms if alarm.zone.uid == self.soco.uid ]: - dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms) + dispatches.append((SONOS_CREATE_ALARM, self, new_alarms)) - dispatcher_send(self.hass, SONOS_CREATE_SWITCHES, self) + dispatches.append((SONOS_CREATE_SWITCHES, self)) + dispatches.append((SONOS_CREATE_MEDIA_PLAYER, self)) + dispatches.append((SONOS_SPEAKER_ADDED, self.soco.uid)) - self._event_dispatchers = { - "AlarmClock": self.async_dispatch_alarms, - "AVTransport": self.async_dispatch_media_update, - "ContentDirectory": self.async_dispatch_favorites, - "DeviceProperties": self.async_dispatch_device_properties, - "RenderingControl": self.async_update_volume, - "ZoneGroupTopology": self.async_update_groups, - } - - dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self) - dispatcher_send(self.hass, SONOS_SPEAKER_ADDED, self.soco.uid) - - self.hass.create_task(self.async_subscribe()) + self.hass.create_task(self.async_setup(entry, has_battery, dispatches)) # # Entity management @@ -448,7 +442,7 @@ class SonosSpeaker: # Save most recently processed event variables for cache and diagnostics self._last_event_cache[event.service.service_type] = event.variables dispatcher = self._event_dispatchers[event.service.service_type] - dispatcher(event) + dispatcher(self, event) @callback def async_dispatch_alarms(self, event: SonosEvent) -> None: @@ -1145,3 +1139,12 @@ class SonosSpeaker: """Update information about current volume settings.""" self.volume = self.soco.volume self.muted = self.soco.mute + + _event_dispatchers = { + "AlarmClock": async_dispatch_alarms, + "AVTransport": async_dispatch_media_update, + "ContentDirectory": async_dispatch_favorites, + "DeviceProperties": async_dispatch_device_properties, + "RenderingControl": async_update_volume, + "ZoneGroupTopology": async_update_groups, + }