From f7325a7d3540986b461a03d08b79dc2d33b1b84b Mon Sep 17 00:00:00 2001 From: rajlaud <50647620+rajlaud@users.noreply.github.com> Date: Wed, 24 Jun 2020 12:04:17 -0500 Subject: [PATCH] Squeezebox dispatch helper (#37030) --- .../components/squeezebox/__init__.py | 48 +----------- .../components/squeezebox/config_flow.py | 5 +- homeassistant/components/squeezebox/const.py | 1 + .../components/squeezebox/media_player.py | 78 ++++++++++++++----- 4 files changed, 62 insertions(+), 70 deletions(-) diff --git a/homeassistant/components/squeezebox/__init__.py b/homeassistant/components/squeezebox/__init__.py index ff365640e9a..e298bee7b07 100644 --- a/homeassistant/components/squeezebox/__init__.py +++ b/homeassistant/components/squeezebox/__init__.py @@ -1,55 +1,18 @@ """The Logitech Squeezebox integration.""" -import asyncio import logging -from pysqueezebox import async_discover - from homeassistant.components.media_player import DOMAIN as MP_DOMAIN -from homeassistant.config_entries import SOURCE_DISCOVERY, ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_START +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant -from .const import DOMAIN, ENTRY_PLAYERS, KNOWN_PLAYERS, PLAYER_DISCOVERY_UNSUB +from .const import DISCOVERY_TASK, DOMAIN, PLAYER_DISCOVERY_UNSUB _LOGGER = logging.getLogger(__name__) -DISCOVERY_TASK = "discovery_task" - - -async def start_server_discovery(hass): - """Start a server discovery task.""" - - def _discovered_server(server): - asyncio.create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_DISCOVERY}, - data={ - CONF_HOST: server.host, - CONF_PORT: int(server.port), - "uuid": server.uuid, - }, - ) - ) - - hass.data.setdefault(DOMAIN, {}) - if DISCOVERY_TASK not in hass.data[DOMAIN]: - _LOGGER.debug("Adding server discovery task for squeezebox") - hass.data[DOMAIN][DISCOVERY_TASK] = hass.async_create_task( - async_discover(_discovered_server) - ) - async def async_setup(hass: HomeAssistant, config: dict): """Set up the Logitech Squeezebox component.""" - if hass.is_running: - asyncio.create_task(start_server_discovery(hass)) - else: - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, start_server_discovery(hass) - ) - return True @@ -66,13 +29,6 @@ async def async_unload_entry(hass, entry): # Stop player discovery task for this config entry. hass.data[DOMAIN][entry.entry_id][PLAYER_DISCOVERY_UNSUB]() - # Remove config entry's players from list of known players - entry_players = hass.data[DOMAIN][entry.entry_id][ENTRY_PLAYERS] - if entry_players: - for player in entry_players: - _LOGGER.debug("Remove entry player %s from list of known players.", player) - hass.data[DOMAIN][KNOWN_PLAYERS].remove(player) - # Remove stored data for this config entry hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/squeezebox/config_flow.py b/homeassistant/components/squeezebox/config_flow.py index bab53d7426b..f5ed6073104 100644 --- a/homeassistant/components/squeezebox/config_flow.py +++ b/homeassistant/components/squeezebox/config_flow.py @@ -150,12 +150,11 @@ class SqueezeboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} if user_input: error = await self._validate_input(user_input) - if error: - errors["base"] = error - else: + if not error: return self.async_create_entry( title=user_input[CONF_HOST], data=user_input ) + errors["base"] = error return self.async_show_form( step_id="edit", data_schema=self.data_schema, errors=errors diff --git a/homeassistant/components/squeezebox/const.py b/homeassistant/components/squeezebox/const.py index 8d716ffedc1..4664bbeaf43 100644 --- a/homeassistant/components/squeezebox/const.py +++ b/homeassistant/components/squeezebox/const.py @@ -3,4 +3,5 @@ DOMAIN = "squeezebox" ENTRY_PLAYERS = "entry_players" KNOWN_PLAYERS = "known_players" PLAYER_DISCOVERY_UNSUB = "player_discovery_unsub" +DISCOVERY_TASK = "discovery_task" DEFAULT_PORT = 9000 diff --git a/homeassistant/components/squeezebox/media_player.py b/homeassistant/components/squeezebox/media_player.py index 1a739e6972e..74dfc42f210 100644 --- a/homeassistant/components/squeezebox/media_player.py +++ b/homeassistant/components/squeezebox/media_player.py @@ -2,7 +2,7 @@ import asyncio import logging -from pysqueezebox import Server +from pysqueezebox import Server, async_discover import voluptuous as vol from homeassistant import config_entries @@ -23,6 +23,7 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, ) +from homeassistant.config_entries import SOURCE_DISCOVERY from homeassistant.const import ( ATTR_COMMAND, CONF_HOST, @@ -34,17 +35,20 @@ from homeassistant.const import ( STATE_OFF, STATE_PAUSED, STATE_PLAYING, - STATE_UNAVAILABLE, ) +from homeassistant.core import callback from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.util.dt import utcnow -from .__init__ import start_server_discovery from .const import ( DEFAULT_PORT, + DISCOVERY_TASK, DOMAIN, - ENTRY_PLAYERS, KNOWN_PLAYERS, PLAYER_DISCOVERY_UNSUB, ) @@ -57,6 +61,8 @@ SERVICE_UNSYNC = "unsync" ATTR_QUERY_RESULT = "query_result" ATTR_SYNC_GROUP = "sync_group" +SIGNAL_PLAYER_REDISCOVERED = "squeezebox_player_rediscovered" + _LOGGER = logging.getLogger(__name__) DISCOVERY_INTERVAL = 60 @@ -107,6 +113,30 @@ SQUEEZEBOX_MODE = { } +async def start_server_discovery(hass): + """Start a server discovery task.""" + + def _discovered_server(server): + asyncio.create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_DISCOVERY}, + data={ + CONF_HOST: server.host, + CONF_PORT: int(server.port), + "uuid": server.uuid, + }, + ) + ) + + hass.data.setdefault(DOMAIN, {}) + if DISCOVERY_TASK not in hass.data[DOMAIN]: + _LOGGER.debug("Adding server discovery task for squeezebox") + hass.data[DOMAIN][DISCOVERY_TASK] = hass.async_create_task( + async_discover(_discovered_server) + ) + + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up squeezebox platform from platform entry in configuration.yaml (deprecated).""" @@ -129,13 +159,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN].setdefault(config_entry.entry_id, {}) - known_players = hass.data[DOMAIN].get(KNOWN_PLAYERS) - if known_players is None: - hass.data[DOMAIN][KNOWN_PLAYERS] = known_players = [] - - entry_players = hass.data[DOMAIN][config_entry.entry_id].setdefault( - ENTRY_PLAYERS, [] - ) + known_players = hass.data[DOMAIN].setdefault(KNOWN_PLAYERS, []) _LOGGER.debug("Creating LMS object for %s", host) lms = Server(async_get_clientsession(hass), host, port, username, password) @@ -153,15 +177,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities): ), None, ) - if entity and not entity.available: - # check if previously unavailable player has connected + if entity: await player.async_update() - entity.available = player.connected + async_dispatcher_send( + hass, SIGNAL_PLAYER_REDISCOVERED, player.player_id, player.connected + ) + if not entity: _LOGGER.debug("Adding new entity: %s", player) entity = SqueezeBoxEntity(player) known_players.append(entity) - entry_players.append(entity) async_add_entities([entity]) players = await lms.async_get_players() @@ -227,6 +252,7 @@ class SqueezeBoxEntity(MediaPlayerEntity): self._last_update = None self._query_result = {} self._available = True + self._remove_dispatcher = None @property def device_state_attributes(self): @@ -254,16 +280,17 @@ class SqueezeBoxEntity(MediaPlayerEntity): """Return True if device connected to LMS server.""" return self._available - @available.setter - def available(self, val): - """Set available to True or False.""" - self._available = bool(val) + @callback + def rediscovered(self, unique_id, connected): + """Make a player available again.""" + if unique_id == self.unique_id and connected: + self._available = True + _LOGGER.info("Player %s is available again", self.name) + self._remove_dispatcher() @property def state(self): """Return the state of the device.""" - if not self.available: - return STATE_UNAVAILABLE if not self._player.power: return STATE_OFF if self._player.mode: @@ -282,6 +309,15 @@ class SqueezeBoxEntity(MediaPlayerEntity): _LOGGER.info("Player %s is not available", self.name) self._available = False + # start listening for restored players + self._remove_dispatcher = async_dispatcher_connect( + self.hass, SIGNAL_PLAYER_REDISCOVERED, self.rediscovered + ) + + async def async_will_remove_from_hass(self): + """Remove from list of known players when removed from hass.""" + self.hass.data[DOMAIN][KNOWN_PLAYERS].remove(self) + @property def volume_level(self): """Volume level of the media player (0..1)."""