diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index b7f17752097..f0219ea8cf0 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -12,7 +12,7 @@ from urllib.parse import urlparse from soco import events_asyncio import soco.config as soco_config from soco.core import SoCo -from soco.exceptions import SoCoException +from soco.exceptions import NotSupportedException, SoCoException import voluptuous as vol from homeassistant import config_entries @@ -87,6 +87,7 @@ class SonosData: self.alarms: dict[str, SonosAlarms] = {} self.topology_condition = asyncio.Condition() self.hosts_heartbeat = None + self.discovery_ignored: set[str] = set() self.discovery_known: set[str] = set() self.boot_counts: dict[str, int] = {} @@ -137,21 +138,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return True -def _create_soco(ip_address: str, source: SoCoCreationSource) -> SoCo | None: - """Create a soco instance and return if successful.""" - try: - soco = SoCo(ip_address) - # Ensure that the player is available and UID is cached - _ = soco.uid - _ = soco.volume - return soco - except (OSError, SoCoException) as ex: - _LOGGER.warning( - "Failed to connect to %s player '%s': %s", source.value, ip_address, ex - ) - return None - - class SonosDiscoveryManager: """Manage sonos discovery.""" @@ -165,6 +151,26 @@ class SonosDiscoveryManager: self.hosts = hosts self.discovery_lock = asyncio.Lock() + def _create_soco(self, ip_address: str, source: SoCoCreationSource) -> SoCo | None: + """Create a soco instance and return if successful.""" + if ip_address in self.data.discovery_ignored: + return None + + try: + soco = SoCo(ip_address) + # Ensure that the player is available and UID is cached + uid = soco.uid + _ = soco.volume + return soco + except NotSupportedException as exc: + _LOGGER.debug("Device %s is not supported, ignoring: %s", uid, exc) + self.data.discovery_ignored.add(ip_address) + except (OSError, SoCoException) as ex: + _LOGGER.warning( + "Failed to connect to %s player '%s': %s", source.value, ip_address, ex + ) + return None + async def _async_stop_event_listener(self, event: Event) -> None: await asyncio.gather( *(speaker.async_unsubscribe() for speaker in self.data.discovered.values()), @@ -213,7 +219,7 @@ class SonosDiscoveryManager: if known_uid: dispatcher_send(self.hass, f"{SONOS_SEEN}-{known_uid}") else: - soco = _create_soco(ip_addr, SoCoCreationSource.CONFIGURED) + soco = self._create_soco(ip_addr, SoCoCreationSource.CONFIGURED) if soco and soco.is_visible: self._discovered_player(soco) @@ -222,7 +228,7 @@ class SonosDiscoveryManager: ) def _discovered_ip(self, ip_address): - soco = _create_soco(ip_address, SoCoCreationSource.DISCOVERED) + soco = self._create_soco(ip_address, SoCoCreationSource.DISCOVERED) if soco and soco.is_visible: self._discovered_player(soco) @@ -238,7 +244,7 @@ class SonosDiscoveryManager: if boot_seqnum and boot_seqnum > self.data.boot_counts[uid]: self.data.boot_counts[uid] = boot_seqnum if soco := await self.hass.async_add_executor_job( - _create_soco, discovered_ip, SoCoCreationSource.REBOOTED + self._create_soco, discovered_ip, SoCoCreationSource.REBOOTED ): async_dispatcher_send(self.hass, f"{SONOS_REBOOTED}-{uid}", soco) else: diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index e571693c659..4ce5623ac38 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,7 +3,7 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["soco==0.23.1"], + "requirements": ["soco==0.23.2"], "dependencies": ["ssdp"], "after_dependencies": ["plex", "zeroconf"], "zeroconf": ["_sonos._tcp.local."], diff --git a/requirements_all.txt b/requirements_all.txt index dfbd6090ffb..d3b0759ea11 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2148,7 +2148,7 @@ smhi-pkg==1.0.15 snapcast==2.1.3 # homeassistant.components.sonos -soco==0.23.1 +soco==0.23.2 # homeassistant.components.solaredge_local solaredge-local==0.2.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 63caf5e42d3..c135613db5e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1177,7 +1177,7 @@ smarthab==0.21 smhi-pkg==1.0.15 # homeassistant.components.sonos -soco==0.23.1 +soco==0.23.2 # homeassistant.components.solaredge solaredge==0.0.2 diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index cba6967463a..0e4af2071b2 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -1,5 +1,8 @@ """Tests for the Sonos Media Player platform.""" +from unittest.mock import PropertyMock + import pytest +from soco.exceptions import NotSupportedException from homeassistant.components.sonos import DATA_SONOS, DOMAIN, media_player from homeassistant.const import STATE_IDLE @@ -40,6 +43,16 @@ async def test_async_setup_entry_discover(hass, config_entry, discover): assert media_player.state == STATE_IDLE +async def test_discovery_ignore_unsupported_device(hass, config_entry, soco, caplog): + """Test discovery setup.""" + message = f"GetVolume not supported on {soco.ip_address}" + type(soco).volume = PropertyMock(side_effect=NotSupportedException(message)) + await setup_platform(hass, config_entry, {}) + + assert message in caplog.text + assert not hass.data[DATA_SONOS].discovered + + async def test_services(hass, config_entry, config, hass_read_only_user): """Test join/unjoin requires control access.""" await setup_platform(hass, config_entry, config)