Add squeezebox server device with common init (#122396)

* squeezebox moves common elements into __init__ to allow for server sensors and device, improves player device

* Update with feedback from PR

* squeezebox Formating fixes, Logging Fixes, remove nasty stored callback

* squeezebox Formating fixes, Logging Fixes, remove nasty stored callback

* squeezebox refactor to use own ConfigEntry and Data

* squeezebox remove own data class

* Update homeassistant/components/squeezebox/__init__.py

Correct typo

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Update homeassistant/components/squeezebox/media_player.py

Stronger typing on entry setup SqueezeboxConfigEntry

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* squeezebox add SqueezeboxConfigEntry

* squeezebox fix mypy type errors

* squeezebox use right Callable

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Phill (pssc) 2024-07-23 14:53:58 +01:00 committed by GitHub
parent ff467463f8
commit f260d63c58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 112 additions and 67 deletions

View File

@ -1,20 +1,80 @@
"""The Squeezebox integration."""
from asyncio import timeout
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from pysqueezebox import Server
from .const import DISCOVERY_TASK, DOMAIN, PLAYER_DISCOVERY_UNSUB
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import (
CONF_HTTPS,
DISCOVERY_TASK,
DOMAIN,
STATUS_API_TIMEOUT,
STATUS_QUERY_LIBRARYNAME,
STATUS_QUERY_UUID,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.MEDIA_PLAYER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Squeezebox from a config entry."""
type SqueezeboxConfigEntry = ConfigEntry[Server]
async def async_setup_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) -> bool:
"""Set up an LMS Server from a config entry."""
config = entry.data
session = async_get_clientsession(hass)
_LOGGER.debug(
"Reached async_setup_entry for host=%s(%s)", config[CONF_HOST], entry.entry_id
)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
https = config.get(CONF_HTTPS, False)
host = config[CONF_HOST]
port = config[CONF_PORT]
lms = Server(session, host, port, username, password, https=https)
_LOGGER.debug("LMS object for %s", lms)
try:
async with timeout(STATUS_API_TIMEOUT):
status = await lms.async_query(
"serverstatus", "-", "-", "prefs:libraryname"
)
except Exception as err:
raise ConfigEntryNotReady(
f"Error communicating config not read for {host}"
) from err
if not status:
raise ConfigEntryNotReady(f"Error Config Not read for {host}")
_LOGGER.debug("LMS Status for setup = %s", status)
lms.uuid = status[STATUS_QUERY_UUID]
lms.name = (
(STATUS_QUERY_LIBRARYNAME in status and status[STATUS_QUERY_LIBRARYNAME])
and status[STATUS_QUERY_LIBRARYNAME]
or host
)
_LOGGER.debug("LMS %s = '%s' with uuid = %s ", lms.name, host, lms.uuid)
entry.runtime_data = lms
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -22,10 +82,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
# Stop player discovery task for this config entry.
hass.data[DOMAIN][entry.entry_id][PLAYER_DISCOVERY_UNSUB]()
# Remove stored data for this config entry
hass.data[DOMAIN].pop(entry.entry_id)
_LOGGER.debug(
"Reached async_unload_entry for LMS=%s(%s)",
entry.runtime_data.name or "Unknown",
entry.entry_id,
)
# Stop server discovery task if this is the last config entry.
current_entries = hass.config_entries.async_entries(DOMAIN)

View File

@ -1,10 +1,12 @@
"""Constants for the Squeezebox component."""
DOMAIN = "squeezebox"
ENTRY_PLAYERS = "entry_players"
KNOWN_PLAYERS = "known_players"
PLAYER_DISCOVERY_UNSUB = "player_discovery_unsub"
DISCOVERY_TASK = "discovery_task"
DEFAULT_PORT = 9000
SQUEEZEBOX_SOURCE_STRINGS = ("source:", "wavin:", "spotify:")
CONF_HTTPS = "https"
DISCOVERY_TASK = "discovery_task"
DOMAIN = "squeezebox"
DEFAULT_PORT = 9000
KNOWN_PLAYERS = "known_players"
SENSOR_UPDATE_INTERVAL = 60
STATUS_API_TIMEOUT = 10
STATUS_QUERY_LIBRARYNAME = "libraryname"
STATUS_QUERY_UUID = "uuid"
SQUEEZEBOX_SOURCE_STRINGS = ("source:", "wavin:", "spotify:")

View File

@ -2,12 +2,13 @@
from __future__ import annotations
from collections.abc import Callable
from datetime import datetime
import json
import logging
from typing import Any
from pysqueezebox import Server, async_discover
from pysqueezebox import Player, async_discover
import voluptuous as vol
from homeassistant.components import media_source
@ -21,22 +22,19 @@ from homeassistant.components.media_player import (
RepeatMode,
async_process_play_media_url,
)
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
from homeassistant.const import (
ATTR_COMMAND,
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
)
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY
from homeassistant.const import ATTR_COMMAND, CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
config_validation as cv,
discovery_flow,
entity_platform,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo, format_mac
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceInfo,
format_mac,
)
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
@ -46,20 +44,14 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.start import async_at_start
from homeassistant.util.dt import utcnow
from . import SqueezeboxConfigEntry
from .browse_media import (
build_item_response,
generate_playlist,
library_payload,
media_source_content_filter,
)
from .const import (
CONF_HTTPS,
DISCOVERY_TASK,
DOMAIN,
KNOWN_PLAYERS,
PLAYER_DISCOVERY_UNSUB,
SQUEEZEBOX_SOURCE_STRINGS,
)
from .const import DISCOVERY_TASK, DOMAIN, KNOWN_PLAYERS, SQUEEZEBOX_SOURCE_STRINGS
SERVICE_CALL_METHOD = "call_method"
SERVICE_CALL_QUERY = "call_query"
@ -118,29 +110,15 @@ async def start_server_discovery(hass: HomeAssistant) -> None:
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
entry: SqueezeboxConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up an LMS Server from a config entry."""
config = config_entry.data
_LOGGER.debug("Reached async_setup_entry for host=%s", config[CONF_HOST])
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config[CONF_HOST]
port = config[CONF_PORT]
https = config.get(CONF_HTTPS, False)
"""Set up an player discovery from a config entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault(config_entry.entry_id, {})
known_players = hass.data[DOMAIN].setdefault(KNOWN_PLAYERS, [])
lms = entry.runtime_data
session = async_get_clientsession(hass)
_LOGGER.debug("Creating LMS object for %s", host)
lms = Server(session, host, port, username, password, https=https)
async def _discovery(now=None):
async def _player_discovery(now=None):
"""Discover squeezebox players by polling server."""
async def _discovered_player(player):
@ -169,13 +147,15 @@ async def async_setup_entry(
for player in players:
hass.async_create_task(_discovered_player(player))
hass.data[DOMAIN][config_entry.entry_id][PLAYER_DISCOVERY_UNSUB] = (
async_call_later(hass, DISCOVERY_INTERVAL, _discovery)
entry.async_on_unload(
async_call_later(hass, DISCOVERY_INTERVAL, _player_discovery)
)
_LOGGER.debug("Adding player discovery job for LMS server: %s", host)
config_entry.async_create_background_task(
hass, _discovery(), "squeezebox.media_player.discovery"
_LOGGER.debug(
"Adding player discovery job for LMS server: %s", entry.data[CONF_HOST]
)
entry.async_create_background_task(
hass, _player_discovery(), "squeezebox.media_player.player_discovery"
)
# Register entity services
@ -208,7 +188,7 @@ async def async_setup_entry(
platform.async_register_entity_service(SERVICE_UNSYNC, None, "async_unsync")
# Start server discovery task if not already running
config_entry.async_on_unload(async_at_start(hass, start_server_discovery))
entry.async_on_unload(async_at_start(hass, start_server_discovery))
class SqueezeBoxEntity(MediaPlayerEntity):
@ -241,14 +221,16 @@ class SqueezeBoxEntity(MediaPlayerEntity):
_last_update: datetime | None = None
_attr_available = True
def __init__(self, player):
def __init__(self, player: Player) -> None:
"""Initialize the SqueezeBox device."""
self._player = player
self._query_result = {}
self._remove_dispatcher = None
self._query_result: bool | dict = {}
self._remove_dispatcher: Callable | None = None
self._attr_unique_id = format_mac(player.player_id)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)}, name=player.name
identifiers={(DOMAIN, self._attr_unique_id)},
name=player.name,
connections={(CONNECTION_NETWORK_MAC, self._attr_unique_id)},
)
@property
@ -265,7 +247,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
"""Make a player available again."""
if unique_id == self.unique_id and connected:
self._attr_available = True
_LOGGER.info("Player %s is available again", self.name)
_LOGGER.debug("Player %s is available again", self.name)
self._remove_dispatcher()
@property
@ -286,7 +268,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
if self.media_position != last_media_position:
self._last_update = utcnow()
if self._player.connected is False:
_LOGGER.info("Player %s is not available", self.name)
_LOGGER.debug("Player %s is not available", self.name)
self._attr_available = False
# start listening for restored players
@ -573,7 +555,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
if other_player_id := player_ids.get(other_player):
await self._player.async_sync(other_player_id)
else:
_LOGGER.info(
_LOGGER.debug(
"Could not find player_id for %s. Not syncing", other_player
)