mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 01:37:08 +00:00
Create repair issue if Sonos subscriptions fail (#87437)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
This commit is contained in:
parent
93a87d3c82
commit
d5a6840588
@ -26,7 +26,11 @@ from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOSTS, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later, async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@ -43,6 +47,8 @@ from .const import (
|
||||
SONOS_REBOOTED,
|
||||
SONOS_SPEAKER_ACTIVITY,
|
||||
SONOS_VANISHED,
|
||||
SUB_FAIL_ISSUE_ID,
|
||||
SUB_FAIL_URL,
|
||||
SUBSCRIPTION_TIMEOUT,
|
||||
UPNP_ST,
|
||||
)
|
||||
@ -227,6 +233,24 @@ class SonosDiscoveryManager:
|
||||
|
||||
async def async_subscription_failed(now: datetime.datetime) -> None:
|
||||
"""Fallback logic if the subscription callback never arrives."""
|
||||
addr, port = sub.event_listener.address
|
||||
listener_address = f"{addr}:{port}"
|
||||
if advertise_ip := soco_config.EVENT_ADVERTISE_IP:
|
||||
listener_address += f" (advertising as {advertise_ip})"
|
||||
ir.async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
SUB_FAIL_ISSUE_ID,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="subscriptions_failed",
|
||||
translation_placeholders={
|
||||
"device_ip": ip_address,
|
||||
"listener_address": listener_address,
|
||||
"sub_fail_url": SUB_FAIL_URL,
|
||||
},
|
||||
)
|
||||
|
||||
_LOGGER.warning(
|
||||
"Subscription to %s failed, attempting to poll directly", ip_address
|
||||
)
|
||||
@ -256,6 +280,11 @@ class SonosDiscoveryManager:
|
||||
"""Create SonosSpeakers when subscription callbacks successfully arrive."""
|
||||
_LOGGER.debug("Subscription to %s succeeded", ip_address)
|
||||
cancel_failure_callback()
|
||||
ir.async_delete_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
SUB_FAIL_ISSUE_ID,
|
||||
)
|
||||
_async_add_visible_zones(subscription_succeeded=True)
|
||||
|
||||
sub.callback = _async_subscription_succeeded
|
||||
|
@ -19,6 +19,9 @@ PLATFORMS = [
|
||||
Platform.SWITCH,
|
||||
]
|
||||
|
||||
SUB_FAIL_ISSUE_ID = "subscriptions_failed"
|
||||
SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements"
|
||||
|
||||
SONOS_ARTIST = "artists"
|
||||
SONOS_ALBUM = "albums"
|
||||
SONOS_PLAYLISTS = "playlists"
|
||||
|
@ -5,10 +5,8 @@ from abc import abstractmethod
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import soco.config as soco_config
|
||||
from soco.core import SoCo
|
||||
|
||||
from homeassistant.components import persistent_notification
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
@ -17,8 +15,6 @@ from .const import DATA_SONOS, DOMAIN, SONOS_FALLBACK_POLL, SONOS_STATE_UPDATED
|
||||
from .exception import SonosUpdateError
|
||||
from .speaker import SonosSpeaker
|
||||
|
||||
SUB_FAIL_URL = "https://www.home-assistant.io/integrations/sonos/#network-requirements"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -57,29 +53,6 @@ class SonosEntity(Entity):
|
||||
async def async_fallback_poll(self, now: datetime.datetime) -> None:
|
||||
"""Poll the entity if subscriptions fail."""
|
||||
if not self.speaker.subscriptions_failed:
|
||||
if soco_config.EVENT_ADVERTISE_IP:
|
||||
listener_msg = (
|
||||
f"{self.speaker.subscription_address}"
|
||||
f" (advertising as {soco_config.EVENT_ADVERTISE_IP})"
|
||||
)
|
||||
else:
|
||||
listener_msg = self.speaker.subscription_address
|
||||
message = (
|
||||
f"{self.speaker.zone_name} cannot reach {listener_msg},"
|
||||
" falling back to polling, functionality may be limited"
|
||||
)
|
||||
log_link_msg = f", see {SUB_FAIL_URL} for more details"
|
||||
notification_link_msg = (
|
||||
f'.\n\nSee <a href="{SUB_FAIL_URL}">Sonos documentation</a>'
|
||||
" for more details."
|
||||
)
|
||||
_LOGGER.warning(message + log_link_msg)
|
||||
persistent_notification.async_create(
|
||||
self.hass,
|
||||
message + notification_link_msg,
|
||||
"Sonos networking issue",
|
||||
"sonos_subscriptions_failed",
|
||||
)
|
||||
self.speaker.subscriptions_failed = True
|
||||
await self.speaker.async_unsubscribe()
|
||||
try:
|
||||
|
@ -10,5 +10,11 @@
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"subscriptions_failed": {
|
||||
"title": "Networking error: subscriptions failed",
|
||||
"description": "Falling back to polling, functionality may be limited.\n\nSonos device at {device_ip} cannot reach Home Assistant at {listener_address}.\n\nSee our [documentation]({sub_fail_url}) for more information on how to solve this issue."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
47
tests/components/sonos/test_repairs.py
Normal file
47
tests/components/sonos/test_repairs.py
Normal file
@ -0,0 +1,47 @@
|
||||
"""Test repairs handling for Sonos."""
|
||||
from unittest.mock import Mock
|
||||
|
||||
from homeassistant.components.sonos.const import (
|
||||
DOMAIN,
|
||||
SCAN_INTERVAL,
|
||||
SUB_FAIL_ISSUE_ID,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.issue_registry import async_get as async_get_issue_registry
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .conftest import SonosMockEvent
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_subscription_repair_issues(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, soco, zgs_discovery
|
||||
):
|
||||
"""Test repair issues handling for failed subscriptions."""
|
||||
issue_registry = async_get_issue_registry(hass)
|
||||
|
||||
subscription = soco.zoneGroupTopology.subscribe.return_value
|
||||
subscription.event_listener = Mock(address=("192.168.4.2", 1400))
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Ensure an issue is registered on subscription failure
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
assert issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID)
|
||||
|
||||
# Ensure the issue still exists after reload
|
||||
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID)
|
||||
|
||||
# Ensure the issue has been removed after a successful subscription callback
|
||||
variables = {"ZoneGroupState": zgs_discovery}
|
||||
event = SonosMockEvent(soco, soco.zoneGroupTopology, variables)
|
||||
sub_callback = subscription.callback
|
||||
sub_callback(event)
|
||||
await hass.async_block_till_done()
|
||||
assert not issue_registry.async_get_issue(DOMAIN, SUB_FAIL_ISSUE_ID)
|
@ -17,6 +17,7 @@ async def test_fallback_to_polling(
|
||||
speaker = list(hass.data[DATA_SONOS].discovered.values())[0]
|
||||
assert speaker.soco is soco
|
||||
assert speaker._subscriptions
|
||||
assert not speaker.subscriptions_failed
|
||||
|
||||
caplog.clear()
|
||||
|
||||
@ -29,7 +30,6 @@ async def test_fallback_to_polling(
|
||||
|
||||
assert not speaker._subscriptions
|
||||
assert speaker.subscriptions_failed
|
||||
assert "falling back to polling" in caplog.text
|
||||
assert "Activity on Zone A from SonosSpeaker.update_volume" in caplog.text
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user