Handle failures during initial Sonos subscription (#73456)
This commit is contained in:
parent
143e6a7adc
commit
86fde1a644
3 changed files with 47 additions and 14 deletions
|
@ -7,6 +7,10 @@ class UnknownMediaType(BrowseError):
|
||||||
"""Unknown media type."""
|
"""Unknown media type."""
|
||||||
|
|
||||||
|
|
||||||
|
class SonosSubscriptionsFailed(HomeAssistantError):
|
||||||
|
"""Subscription creation failed."""
|
||||||
|
|
||||||
|
|
||||||
class SonosUpdateError(HomeAssistantError):
|
class SonosUpdateError(HomeAssistantError):
|
||||||
"""Update failed."""
|
"""Update failed."""
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ from .const import (
|
||||||
SONOS_VANISHED,
|
SONOS_VANISHED,
|
||||||
SUBSCRIPTION_TIMEOUT,
|
SUBSCRIPTION_TIMEOUT,
|
||||||
)
|
)
|
||||||
from .exception import S1BatteryMissing, SonosUpdateError
|
from .exception import S1BatteryMissing, SonosSubscriptionsFailed, SonosUpdateError
|
||||||
from .favorites import SonosFavorites
|
from .favorites import SonosFavorites
|
||||||
from .helpers import soco_error
|
from .helpers import soco_error
|
||||||
from .media import SonosMedia
|
from .media import SonosMedia
|
||||||
|
@ -324,12 +324,29 @@ class SonosSpeaker:
|
||||||
async with self._subscription_lock:
|
async with self._subscription_lock:
|
||||||
if self._subscriptions:
|
if self._subscriptions:
|
||||||
return
|
return
|
||||||
await self._async_subscribe()
|
try:
|
||||||
|
await self._async_subscribe()
|
||||||
|
except SonosSubscriptionsFailed:
|
||||||
|
_LOGGER.warning("Creating subscriptions failed for %s", self.zone_name)
|
||||||
|
await self._async_offline()
|
||||||
|
|
||||||
async def _async_subscribe(self) -> None:
|
async def _async_subscribe(self) -> None:
|
||||||
"""Create event subscriptions."""
|
"""Create event subscriptions."""
|
||||||
_LOGGER.debug("Creating subscriptions for %s", self.zone_name)
|
_LOGGER.debug("Creating subscriptions for %s", self.zone_name)
|
||||||
|
|
||||||
|
subscriptions = [
|
||||||
|
self._subscribe(getattr(self.soco, service), self.async_dispatch_event)
|
||||||
|
for service in SUBSCRIPTION_SERVICES
|
||||||
|
]
|
||||||
|
results = await asyncio.gather(*subscriptions, return_exceptions=True)
|
||||||
|
for result in results:
|
||||||
|
self.log_subscription_result(
|
||||||
|
result, "Creating subscription", logging.WARNING
|
||||||
|
)
|
||||||
|
|
||||||
|
if any(isinstance(result, Exception) for result in results):
|
||||||
|
raise SonosSubscriptionsFailed
|
||||||
|
|
||||||
# Create a polling task in case subscriptions fail or callback events do not arrive
|
# Create a polling task in case subscriptions fail or callback events do not arrive
|
||||||
if not self._poll_timer:
|
if not self._poll_timer:
|
||||||
self._poll_timer = async_track_time_interval(
|
self._poll_timer = async_track_time_interval(
|
||||||
|
@ -342,16 +359,6 @@ class SonosSpeaker:
|
||||||
SCAN_INTERVAL,
|
SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
subscriptions = [
|
|
||||||
self._subscribe(getattr(self.soco, service), self.async_dispatch_event)
|
|
||||||
for service in SUBSCRIPTION_SERVICES
|
|
||||||
]
|
|
||||||
results = await asyncio.gather(*subscriptions, return_exceptions=True)
|
|
||||||
for result in results:
|
|
||||||
self.log_subscription_result(
|
|
||||||
result, "Creating subscription", logging.WARNING
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _subscribe(
|
async def _subscribe(
|
||||||
self, target: SubscriptionBase, sub_callback: Callable
|
self, target: SubscriptionBase, sub_callback: Callable
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -585,6 +592,11 @@ class SonosSpeaker:
|
||||||
await self.async_offline()
|
await self.async_offline()
|
||||||
|
|
||||||
async def async_offline(self) -> None:
|
async def async_offline(self) -> None:
|
||||||
|
"""Handle removal of speaker when unavailable."""
|
||||||
|
async with self._subscription_lock:
|
||||||
|
await self._async_offline()
|
||||||
|
|
||||||
|
async def _async_offline(self) -> None:
|
||||||
"""Handle removal of speaker when unavailable."""
|
"""Handle removal of speaker when unavailable."""
|
||||||
if not self.available:
|
if not self.available:
|
||||||
return
|
return
|
||||||
|
@ -602,8 +614,7 @@ class SonosSpeaker:
|
||||||
self._poll_timer()
|
self._poll_timer()
|
||||||
self._poll_timer = None
|
self._poll_timer = None
|
||||||
|
|
||||||
async with self._subscription_lock:
|
await self.async_unsubscribe()
|
||||||
await self.async_unsubscribe()
|
|
||||||
|
|
||||||
self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid)
|
self.hass.data[DATA_SONOS].discovery_known.discard(self.soco.uid)
|
||||||
|
|
||||||
|
|
|
@ -29,3 +29,21 @@ async def test_fallback_to_polling(
|
||||||
assert speaker.subscriptions_failed
|
assert speaker.subscriptions_failed
|
||||||
assert "falling back to polling" in caplog.text
|
assert "falling back to polling" in caplog.text
|
||||||
assert "Activity on Zone A from SonosSpeaker.update_volume" in caplog.text
|
assert "Activity on Zone A from SonosSpeaker.update_volume" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_subscription_creation_fails(hass: HomeAssistant, async_setup_sonos):
|
||||||
|
"""Test that subscription creation failures are handled."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.sonos.speaker.SonosSpeaker._subscribe",
|
||||||
|
side_effect=ConnectionError("Took too long"),
|
||||||
|
):
|
||||||
|
await async_setup_sonos()
|
||||||
|
|
||||||
|
speaker = list(hass.data[DATA_SONOS].discovered.values())[0]
|
||||||
|
assert not speaker._subscriptions
|
||||||
|
|
||||||
|
with patch.object(speaker, "_resub_cooldown_expires_at", None):
|
||||||
|
speaker.speaker_activity("discovery")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert speaker._subscriptions
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue