Skip to content

Commit

Permalink
Fix Sonos races related to grouping and startup (home-assistant#71026)
Browse files Browse the repository at this point in the history
  • Loading branch information
jjlawren authored Apr 28, 2022
1 parent 7e8c6d5 commit 1f1932d
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 6 deletions.
9 changes: 9 additions & 0 deletions homeassistant/components/sonos/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@ def async_write_media_state(self, uid: str) -> None:
if self.coordinator.uid == uid:
self.async_write_ha_state()

@property
def available(self) -> bool:
"""Return if the media_player is available."""
return (
self.speaker.available
and self.speaker.sonos_group_entities
and self.media.playback_status
)

@property
def coordinator(self) -> SonosSpeaker:
"""Return the current coordinator SonosSpeaker."""
Expand Down
41 changes: 35 additions & 6 deletions homeassistant/components/sonos/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ async def async_setup_dispatchers(self, entry: ConfigEntry) -> None:

def setup(self, entry: ConfigEntry) -> None:
"""Run initial setup of the speaker."""
self.set_basic_info()
self.media.play_mode = self.soco.play_mode
self.update_volume()
self.update_groups()
if self.is_coordinator:
self.media.poll_media()

future = asyncio.run_coroutine_threadsafe(
self.async_setup_dispatchers(entry), self.hass.loop
)
Expand Down Expand Up @@ -247,11 +252,6 @@ def async_write_entity_states(self) -> None:
"""Write states for associated SonosEntity instances."""
async_dispatcher_send(self.hass, f"{SONOS_STATE_UPDATED}-{self.soco.uid}")

def set_basic_info(self) -> None:
"""Set basic information when speaker is reconnected."""
self.media.play_mode = self.soco.play_mode
self.update_volume()

#
# Properties
#
Expand Down Expand Up @@ -456,6 +456,34 @@ def async_dispatch_favorites(self, event: SonosEvent) -> None:
@callback
def async_dispatch_media_update(self, event: SonosEvent) -> None:
"""Update information about currently playing media from an event."""
# The new coordinator can be provided in a media update event but
# before the ZoneGroupState updates. If this happens the playback
# state will be incorrect and should be ignored. Switching to the
# new coordinator will use its media. The regrouping process will
# be completed during the next ZoneGroupState update.
av_transport_uri = event.variables.get("av_transport_uri", "")
current_track_uri = event.variables.get("current_track_uri", "")
if av_transport_uri == current_track_uri and av_transport_uri.startswith(
"x-rincon:"
):
new_coordinator_uid = av_transport_uri.split(":")[-1]
if new_coordinator_speaker := self.hass.data[DATA_SONOS].discovered.get(
new_coordinator_uid
):
_LOGGER.debug(
"Media update coordinator (%s) received for %s",
new_coordinator_speaker.zone_name,
self.zone_name,
)
self.coordinator = new_coordinator_speaker
else:
_LOGGER.debug(
"Media update coordinator (%s) for %s not yet available",
new_coordinator_uid,
self.zone_name,
)
return

if crossfade := event.variables.get("current_crossfade_mode"):
self.cross_fade = bool(int(crossfade))

Expand Down Expand Up @@ -774,6 +802,7 @@ def _async_regroup(group: list[str]) -> None:
self.zone_name,
uid,
)
return

if self.sonos_group_entities == sonos_group_entities:
# Useful in polling mode for speakers with stereo pairs or surrounds
Expand Down
1 change: 1 addition & 0 deletions tests/components/sonos/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def soco_fixture(
mock_soco.get_battery_info.return_value = battery_info
mock_soco.all_zones = {mock_soco}
mock_soco.visible_zones = {mock_soco}
mock_soco.group.coordinator = mock_soco
yield mock_soco


Expand Down

0 comments on commit 1f1932d

Please sign in to comment.