Fix Sonos updating when entities are disabled (#62456)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
jjlawren 2021-12-21 23:36:12 -05:00 committed by GitHub
parent cceedf766a
commit 4475e88707
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 24 additions and 32 deletions

View file

@ -1,6 +1,7 @@
"""Entity representing a Sonos power sensor.""" """Entity representing a Sonos power sensor."""
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any from typing import Any
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -16,11 +17,14 @@ from .speaker import SonosSpeaker
ATTR_BATTERY_POWER_SOURCE = "power_source" ATTR_BATTERY_POWER_SOURCE = "power_source"
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Sonos from a config entry.""" """Set up Sonos from a config entry."""
async def _async_create_entity(speaker: SonosSpeaker) -> None: async def _async_create_entity(speaker: SonosSpeaker) -> None:
_LOGGER.debug("Creating battery binary_sensor on %s", speaker.zone_name)
entity = SonosPowerEntity(speaker) entity = SonosPowerEntity(speaker)
async_add_entities([entity]) async_add_entities([entity])

View file

@ -149,7 +149,6 @@ SONOS_CREATE_BATTERY = "sonos_create_battery"
SONOS_CREATE_SWITCHES = "sonos_create_switches" SONOS_CREATE_SWITCHES = "sonos_create_switches"
SONOS_CREATE_LEVELS = "sonos_create_levels" SONOS_CREATE_LEVELS = "sonos_create_levels"
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player" SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
SONOS_ENTITY_CREATED = "sonos_entity_created"
SONOS_POLL_UPDATE = "sonos_poll_update" SONOS_POLL_UPDATE = "sonos_poll_update"
SONOS_ALARMS_UPDATED = "sonos_alarms_updated" SONOS_ALARMS_UPDATED = "sonos_alarms_updated"
SONOS_FAVORITES_UPDATED = "sonos_favorites_updated" SONOS_FAVORITES_UPDATED = "sonos_favorites_updated"

View file

@ -10,15 +10,11 @@ from soco.core import SoCo
from soco.exceptions import SoCoException from soco.exceptions import SoCoException
import homeassistant.helpers.device_registry as dr import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import DeviceInfo, Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import ( from .const import (
DOMAIN, DOMAIN,
SONOS_ENTITY_CREATED,
SONOS_FAVORITES_UPDATED, SONOS_FAVORITES_UPDATED,
SONOS_POLL_UPDATE, SONOS_POLL_UPDATE,
SONOS_STATE_UPDATED, SONOS_STATE_UPDATED,
@ -60,9 +56,6 @@ class SonosEntity(Entity):
self.async_write_ha_state, self.async_write_ha_state,
) )
) )
async_dispatcher_send(
self.hass, f"{SONOS_ENTITY_CREATED}-{self.soco.uid}", self.platform.domain
)
async def async_poll(self, now: datetime.datetime) -> None: async def async_poll(self, now: datetime.datetime) -> None:
"""Poll the entity if subscriptions fail.""" """Poll the entity if subscriptions fail."""

View file

@ -132,6 +132,7 @@ async def async_setup_entry(
@callback @callback
def async_create_entities(speaker: SonosSpeaker) -> None: def async_create_entities(speaker: SonosSpeaker) -> None:
"""Handle device discovery and create entities.""" """Handle device discovery and create entities."""
_LOGGER.debug("Creating media_player on %s", speaker.zone_name)
async_add_entities([SonosMediaPlayerEntity(speaker)]) async_add_entities([SonosMediaPlayerEntity(speaker)])
@service.verify_domain_control(hass, SONOS_DOMAIN) @service.verify_domain_control(hass, SONOS_DOMAIN)

View file

@ -1,6 +1,8 @@
"""Entity representing a Sonos number control.""" """Entity representing a Sonos number control."""
from __future__ import annotations from __future__ import annotations
import logging
from homeassistant.components.number import NumberEntity from homeassistant.components.number import NumberEntity
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -13,6 +15,8 @@ from .speaker import SonosSpeaker
LEVEL_TYPES = ("bass", "treble") LEVEL_TYPES = ("bass", "treble")
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Sonos number platform from a config entry.""" """Set up the Sonos number platform from a config entry."""
@ -21,6 +25,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
def _async_create_entities(speaker: SonosSpeaker) -> None: def _async_create_entities(speaker: SonosSpeaker) -> None:
entities = [] entities = []
for level_type in LEVEL_TYPES: for level_type in LEVEL_TYPES:
_LOGGER.debug(
"Creating %s number control on %s", level_type, speaker.zone_name
)
entities.append(SonosLevelEntity(speaker, level_type)) entities.append(SonosLevelEntity(speaker, level_type))
async_add_entities(entities) async_add_entities(entities)

View file

@ -1,6 +1,8 @@
"""Entity representing a Sonos battery level.""" """Entity representing a Sonos battery level."""
from __future__ import annotations from __future__ import annotations
import logging
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.const import PERCENTAGE from homeassistant.const import PERCENTAGE
from homeassistant.core import callback from homeassistant.core import callback
@ -11,6 +13,8 @@ from .const import SONOS_CREATE_AUDIO_FORMAT_SENSOR, SONOS_CREATE_BATTERY
from .entity import SonosEntity from .entity import SonosEntity
from .speaker import SonosSpeaker from .speaker import SonosSpeaker
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Sonos from a config entry.""" """Set up Sonos from a config entry."""
@ -19,11 +23,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
def _async_create_audio_format_entity( def _async_create_audio_format_entity(
speaker: SonosSpeaker, audio_format: str speaker: SonosSpeaker, audio_format: str
) -> None: ) -> None:
_LOGGER.debug("Creating audio input format sensor on %s", speaker.zone_name)
entity = SonosAudioInputFormatSensorEntity(speaker, audio_format) entity = SonosAudioInputFormatSensorEntity(speaker, audio_format)
async_add_entities([entity]) async_add_entities([entity])
@callback @callback
def _async_create_battery_sensor(speaker: SonosSpeaker) -> None: def _async_create_battery_sensor(speaker: SonosSpeaker) -> None:
_LOGGER.debug("Creating battery level sensor on %s", speaker.zone_name)
entity = SonosBatteryEntity(speaker) entity = SonosBatteryEntity(speaker)
async_add_entities([entity]) async_add_entities([entity])

View file

@ -20,10 +20,7 @@ from soco.music_library import MusicLibrary
from soco.plugins.sharelink import ShareLinkPlugin from soco.plugins.sharelink import ShareLinkPlugin
from soco.snapshot import Snapshot from soco.snapshot import Snapshot
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -42,7 +39,6 @@ from .const import (
BATTERY_SCAN_INTERVAL, BATTERY_SCAN_INTERVAL,
DATA_SONOS, DATA_SONOS,
DOMAIN, DOMAIN,
PLATFORMS,
SCAN_INTERVAL, SCAN_INTERVAL,
SONOS_CHECK_ACTIVITY, SONOS_CHECK_ACTIVITY,
SONOS_CREATE_ALARM, SONOS_CREATE_ALARM,
@ -51,7 +47,6 @@ from .const import (
SONOS_CREATE_LEVELS, SONOS_CREATE_LEVELS,
SONOS_CREATE_MEDIA_PLAYER, SONOS_CREATE_MEDIA_PLAYER,
SONOS_CREATE_SWITCHES, SONOS_CREATE_SWITCHES,
SONOS_ENTITY_CREATED,
SONOS_POLL_UPDATE, SONOS_POLL_UPDATE,
SONOS_REBOOTED, SONOS_REBOOTED,
SONOS_SPEAKER_ACTIVITY, SONOS_SPEAKER_ACTIVITY,
@ -161,9 +156,6 @@ class SonosSpeaker:
self._share_link_plugin: ShareLinkPlugin | None = None self._share_link_plugin: ShareLinkPlugin | None = None
self.available = True self.available = True
# Synchronization helpers
self._platforms_ready: set[str] = set()
# Subscriptions and events # Subscriptions and events
self.subscriptions_failed: bool = False self.subscriptions_failed: bool = False
self._subscriptions: list[SubscriptionBase] = [] self._subscriptions: list[SubscriptionBase] = []
@ -217,7 +209,6 @@ class SonosSpeaker:
dispatch_pairs = ( dispatch_pairs = (
(SONOS_CHECK_ACTIVITY, self.async_check_activity), (SONOS_CHECK_ACTIVITY, self.async_check_activity),
(SONOS_SPEAKER_ADDED, self.update_group_for_uid), (SONOS_SPEAKER_ADDED, self.update_group_for_uid),
(f"{SONOS_ENTITY_CREATED}-{self.soco.uid}", self.async_handle_new_entity),
(f"{SONOS_REBOOTED}-{self.soco.uid}", self.async_rebooted), (f"{SONOS_REBOOTED}-{self.soco.uid}", self.async_rebooted),
(f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", self.speaker_activity), (f"{SONOS_SPEAKER_ACTIVITY}-{self.soco.uid}", self.speaker_activity),
) )
@ -253,15 +244,11 @@ class SonosSpeaker:
self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL self.hass, self.async_poll_battery, BATTERY_SCAN_INTERVAL
) )
dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self) dispatcher_send(self.hass, SONOS_CREATE_BATTERY, self)
else:
self._platforms_ready.update({BINARY_SENSOR_DOMAIN, SENSOR_DOMAIN})
if new_alarms := [ if new_alarms := [
alarm.alarm_id for alarm in self.alarms if alarm.zone.uid == self.soco.uid alarm.alarm_id for alarm in self.alarms if alarm.zone.uid == self.soco.uid
]: ]:
dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms) dispatcher_send(self.hass, SONOS_CREATE_ALARM, self, new_alarms)
else:
self._platforms_ready.add(SWITCH_DOMAIN)
dispatcher_send(self.hass, SONOS_CREATE_SWITCHES, self) dispatcher_send(self.hass, SONOS_CREATE_SWITCHES, self)
@ -277,19 +264,11 @@ class SonosSpeaker:
dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self) dispatcher_send(self.hass, SONOS_CREATE_MEDIA_PLAYER, self)
dispatcher_send(self.hass, SONOS_SPEAKER_ADDED, self.soco.uid) dispatcher_send(self.hass, SONOS_SPEAKER_ADDED, self.soco.uid)
self.hass.create_task(self.async_subscribe())
# #
# Entity management # Entity management
# #
async def async_handle_new_entity(self, entity_type: str) -> None:
"""Listen to new entities to trigger first subscription."""
if self._platforms_ready == PLATFORMS:
return
self._platforms_ready.add(entity_type)
if self._platforms_ready == PLATFORMS:
self._resubscription_lock = asyncio.Lock()
await self.async_subscribe()
def write_entity_states(self) -> None: def write_entity_states(self) -> None:
"""Write states for associated SonosEntity instances.""" """Write states for associated SonosEntity instances."""
dispatcher_send(self.hass, f"{SONOS_STATE_UPDATED}-{self.soco.uid}") dispatcher_send(self.hass, f"{SONOS_STATE_UPDATED}-{self.soco.uid}")
@ -405,6 +384,9 @@ class SonosSpeaker:
async def async_resubscribe(self, exception: Exception) -> None: async def async_resubscribe(self, exception: Exception) -> None:
"""Attempt to resubscribe when a renewal failure is detected.""" """Attempt to resubscribe when a renewal failure is detected."""
if not self._resubscription_lock:
self._resubscription_lock = asyncio.Lock()
async with self._resubscription_lock: async with self._resubscription_lock:
if not self.available: if not self.available:
return return