Improve Sonos polling (#51170)

* Improve Sonos polling

Warn user if polling is being used
Provide callback IP:port to help user fix networking
Fix radio handling when polling (no event payload)
Clarify dispatch target to reflect polling action

* Lint

* Revert method removal
This commit is contained in:
jjlawren 2021-05-28 05:07:58 -05:00 committed by GitHub
parent e45196f9c9
commit 39e62f9c90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 35 additions and 17 deletions

View file

@ -1,7 +1,6 @@
"""Entity representing a Sonos power sensor."""
from __future__ import annotations
import datetime
import logging
from typing import Any
@ -50,7 +49,7 @@ class SonosPowerEntity(SonosEntity, BinarySensorEntity):
"""Return the entity's device class."""
return DEVICE_CLASS_BATTERY_CHARGING
async def async_update(self, now: datetime.datetime | None = None) -> None:
async def async_update(self) -> None:
"""Poll the device for the current state."""
await self.speaker.async_poll_battery()

View file

@ -136,7 +136,7 @@ SONOS_CREATE_ALARM = "sonos_create_alarm"
SONOS_CREATE_BATTERY = "sonos_create_battery"
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
SONOS_ENTITY_CREATED = "sonos_entity_created"
SONOS_ENTITY_UPDATE = "sonos_entity_update"
SONOS_POLL_UPDATE = "sonos_poll_update"
SONOS_GROUP_UPDATE = "sonos_group_update"
SONOS_HOUSEHOLD_UPDATED = "sonos_household_updated"
SONOS_ALARM_UPDATE = "sonos_alarm_update"

View file

@ -1,6 +1,7 @@
"""Entity representing a Sonos player."""
from __future__ import annotations
import datetime
import logging
from pysonos.core import SoCo
@ -15,8 +16,8 @@ from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import (
DOMAIN,
SONOS_ENTITY_CREATED,
SONOS_ENTITY_UPDATE,
SONOS_HOUSEHOLD_UPDATED,
SONOS_POLL_UPDATE,
SONOS_STATE_UPDATED,
)
from .speaker import SonosSpeaker
@ -38,8 +39,8 @@ class SonosEntity(Entity):
self.async_on_remove(
async_dispatcher_connect(
self.hass,
f"{SONOS_ENTITY_UPDATE}-{self.soco.uid}",
self.async_update, # pylint: disable=no-member
f"{SONOS_POLL_UPDATE}-{self.soco.uid}",
self.async_poll,
)
)
self.async_on_remove(
@ -60,6 +61,17 @@ class SonosEntity(Entity):
self.hass, f"{SONOS_ENTITY_CREATED}-{self.soco.uid}", self.platform.domain
)
async def async_poll(self, now: datetime.datetime) -> None:
"""Poll the entity if subscriptions fail."""
if self.speaker.is_first_poll:
_LOGGER.warning(
"%s cannot reach [%s], falling back to polling, functionality may be limited",
self.speaker.zone_name,
self.speaker.subscription_address,
)
self.speaker.is_first_poll = False
await self.async_update() # pylint: disable=no-member
@property
def soco(self) -> SoCo:
"""Return the speaker SoCo instance."""

View file

@ -293,13 +293,12 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
return STATE_PLAYING
return STATE_IDLE
async def async_update(self, now: datetime.datetime | None = None) -> None:
async def async_update(self) -> None:
"""Retrieve latest state."""
await self.hass.async_add_executor_job(self._update, now)
await self.hass.async_add_executor_job(self._update)
def _update(self, now: datetime.datetime | None = None) -> None:
def _update(self) -> None:
"""Retrieve latest state."""
_LOGGER.debug("Polling speaker %s", self.speaker.zone_name)
try:
self.speaker.update_groups()
self.speaker.update_volume()

View file

@ -1,7 +1,6 @@
"""Entity representing a Sonos battery level."""
from __future__ import annotations
import datetime
import logging
from homeassistant.components.sensor import SensorEntity
@ -50,7 +49,7 @@ class SonosBatteryEntity(SonosEntity, SensorEntity):
"""Get the unit of measurement."""
return PERCENTAGE
async def async_update(self, now: datetime.datetime | None = None) -> None:
async def async_update(self) -> None:
"""Poll the device for the current state."""
await self.speaker.async_poll_battery()

View file

@ -44,8 +44,8 @@ from .const import (
SONOS_CREATE_BATTERY,
SONOS_CREATE_MEDIA_PLAYER,
SONOS_ENTITY_CREATED,
SONOS_ENTITY_UPDATE,
SONOS_GROUP_UPDATE,
SONOS_POLL_UPDATE,
SONOS_SEEN,
SONOS_STATE_PLAYING,
SONOS_STATE_TRANSITIONING,
@ -138,6 +138,7 @@ class SonosSpeaker:
self.household_id: str = soco.household_id
self.media = SonosMedia(soco)
self.is_first_poll: bool = True
self._is_ready: bool = False
self._subscriptions: list[SubscriptionBase] = []
self._resubscription_lock: asyncio.Lock | None = None
@ -322,7 +323,7 @@ class SonosSpeaker:
partial(
async_dispatcher_send,
self.hass,
f"{SONOS_ENTITY_UPDATE}-{self.soco.uid}",
f"{SONOS_POLL_UPDATE}-{self.soco.uid}",
),
SCAN_INTERVAL,
)
@ -418,7 +419,7 @@ class SonosSpeaker:
):
async_dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms)
async_dispatcher_send(self.hass, SONOS_ALARM_UPDATE, self)
async_dispatcher_send(self.hass, SONOS_ALARM_UPDATE)
async def async_update_battery_info(self, battery_dict: dict[str, Any]) -> None:
"""Update battery info using the decoded SonosEvent."""
@ -875,7 +876,7 @@ class SonosSpeaker:
if not self.media.artist:
try:
self.media.artist = variables["current_track_meta_data"].creator
except (KeyError, AttributeError):
except (TypeError, KeyError, AttributeError):
pass
# Radios without tagging can have part of the radio URI as title.
@ -948,3 +949,11 @@ class SonosSpeaker:
elif update_media_position:
self.media.position = current_position
self.media.position_updated_at = dt_util.utcnow()
@property
def subscription_address(self) -> str | None:
"""Return the current subscription callback address if any."""
if self._subscriptions:
addr, port = self._subscriptions[0].event_listener.address
return ":".join([addr, str(port)])
return None

View file

@ -112,7 +112,7 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity):
return False
async def async_update(self, now: datetime.datetime | None = None) -> None:
async def async_update(self) -> None:
"""Poll the device for the current state."""
if await self.async_check_if_available():
await self.hass.async_add_executor_job(self.update_alarm)