Prevent Sonos from pinging disabled device (#176893)

This commit is contained in:
Pete Sage
2026-08-20 03:35:44 -04:00
committed by GitHub
parent ea8c1fd076
commit a0795b3b96
2 changed files with 80 additions and 2 deletions
+18 -2
View File
@@ -73,6 +73,12 @@ DISCOVERY_IGNORED_MODELS = ["Sonos Boost"]
ZGS_SUBSCRIPTION_TIMEOUT = 2
SHUTDOWN_TIMEOUT = 10
def _get_soco_uid(soco: SoCo) -> str:
"""Get SoCo uid as a typed helper for executor jobs."""
return soco.uid
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
@@ -531,9 +537,11 @@ class SonosDiscoveryManager:
),
None,
)
if not known_speaker:
if known_speaker:
uid = known_speaker.uid
else:
try:
uid = await self.hass.async_add_executor_job(getattr, soco, "uid")
uid = await self.hass.async_add_executor_job(_get_soco_uid, soco)
except HTTPError as err:
await self._process_http_connection_error(err, ip_addr)
continue
@@ -545,6 +553,14 @@ class SonosDiscoveryManager:
) as ex:
_LOGGER.warning("Could not get Sonos uid from %s: %s", ip_addr, ex)
continue
if self.is_device_disabled(uid):
_LOGGER.debug(
"Skipping manual poll for disabled Sonos device: %s",
uid,
)
continue
if not known_speaker:
try:
await self._async_handle_discovery_message(
uid,
+62
View File
@@ -8,6 +8,7 @@ import logging
from typing import Any
from unittest.mock import MagicMock, Mock, PropertyMock, patch
from freezegun import freeze_time
from freezegun.api import FrozenDateTimeFactory
import pytest
from requests import Response
@@ -605,6 +606,67 @@ async def test_async_poll_manual_hosts_6(
await hass.async_block_till_done(wait_background_tasks=True)
async def test_async_poll_manual_hosts_skips_ping_for_disabled_device(
hass: HomeAssistant,
soco_factory: SoCoMockFactory,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test disabled manual-host speakers are not pinged on heartbeat."""
soco = soco_factory.cache_mock(MockSoCo(), "10.10.10.1", "Living Room")
soco.renderingControl = Mock()
soco.renderingControl.GetVolume = Mock()
await _setup_hass(hass)
assert "media_player.living_room" in entity_registry.entities
# Mark the speaker unavailable via ZGS event with VanishedDevices.
async def fire_vanish_event():
subscription = soco.zoneGroupTopology.subscribe.return_value
sub_callback = await subscription.wait_for_callback_to_be_set()
zgs_with_vanished = f"""<ZoneGroupState>
<ZoneGroups>
<ZoneGroup Coordinator="{soco.uid}" ID="{soco.uid}:1384750254">
<ZoneGroupMember UUID="{soco.uid}" Location="http://192.168.4.2:1400/xml/device_description.xml" ZoneName="Living Room"/>
</ZoneGroup>
</ZoneGroups>
<VanishedDevices>
<ZoneGroupMember UUID="{soco.uid}" Reason="powered off" ZoneName="Living Room"/>
</VanishedDevices>
</ZoneGroupState>"""
event = SonosMockEvent(
soco, soco.zoneGroupTopology, {"ZoneGroupState": zgs_with_vanished}
)
sub_callback(event)
await hass.async_block_till_done(wait_background_tasks=True)
await fire_vanish_event()
# Verify the speaker is marked unavailable.
state = hass.states.get("media_player.living_room")
assert state is not None
assert state.state == "unavailable"
# Now disable the device.
device = device_registry.async_get_device(identifiers={(sonos.DOMAIN, soco.uid)})
assert device is not None
device_registry.async_update_device(
device.id,
disabled_by=dr.DeviceEntryDisabler.USER,
)
# SonosSpeaker.ping uses RenderingControl.GetVolume under the hood.
soco.renderingControl.GetVolume.reset_mock()
with freeze_time(dt_util.utcnow()) as freezer:
freezer.tick(DISCOVERY_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
# The disabled speaker should not have been pinged.
soco.renderingControl.GetVolume.assert_not_called()
async def test_async_poll_manual_hosts_7(
hass: HomeAssistant,
soco_factory: SoCoMockFactory,