mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
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:
parent
ff467463f8
commit
f260d63c58
@ -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)
|
||||
|
@ -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:")
|
||||
|
@ -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
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user