mirror of
https://github.com/home-assistant/core.git
synced 2025-07-26 22:57:17 +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."""
|
"""The Squeezebox integration."""
|
||||||
|
|
||||||
|
from asyncio import timeout
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from pysqueezebox import Server
|
||||||
from homeassistant.const import Platform
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
|
|
||||||
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
type SqueezeboxConfigEntry = ConfigEntry[Server]
|
||||||
"""Set up Squeezebox from a config entry."""
|
|
||||||
|
|
||||||
|
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)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
return True
|
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:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
# Stop player discovery task for this config entry.
|
# Stop player discovery task for this config entry.
|
||||||
hass.data[DOMAIN][entry.entry_id][PLAYER_DISCOVERY_UNSUB]()
|
_LOGGER.debug(
|
||||||
|
"Reached async_unload_entry for LMS=%s(%s)",
|
||||||
# Remove stored data for this config entry
|
entry.runtime_data.name or "Unknown",
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
entry.entry_id,
|
||||||
|
)
|
||||||
|
|
||||||
# Stop server discovery task if this is the last config entry.
|
# Stop server discovery task if this is the last config entry.
|
||||||
current_entries = hass.config_entries.async_entries(DOMAIN)
|
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
"""Constants for the Squeezebox component."""
|
"""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"
|
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 __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pysqueezebox import Server, async_discover
|
from pysqueezebox import Player, async_discover
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import media_source
|
from homeassistant.components import media_source
|
||||||
@ -21,22 +22,19 @@ from homeassistant.components.media_player import (
|
|||||||
RepeatMode,
|
RepeatMode,
|
||||||
async_process_play_media_url,
|
async_process_play_media_url,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY, ConfigEntry
|
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_COMMAND, CONF_HOST, CONF_PORT
|
||||||
ATTR_COMMAND,
|
|
||||||
CONF_HOST,
|
|
||||||
CONF_PASSWORD,
|
|
||||||
CONF_PORT,
|
|
||||||
CONF_USERNAME,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
config_validation as cv,
|
config_validation as cv,
|
||||||
discovery_flow,
|
discovery_flow,
|
||||||
entity_platform,
|
entity_platform,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.device_registry import (
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo, format_mac
|
CONNECTION_NETWORK_MAC,
|
||||||
|
DeviceInfo,
|
||||||
|
format_mac,
|
||||||
|
)
|
||||||
from homeassistant.helpers.dispatcher import (
|
from homeassistant.helpers.dispatcher import (
|
||||||
async_dispatcher_connect,
|
async_dispatcher_connect,
|
||||||
async_dispatcher_send,
|
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.helpers.start import async_at_start
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
|
from . import SqueezeboxConfigEntry
|
||||||
from .browse_media import (
|
from .browse_media import (
|
||||||
build_item_response,
|
build_item_response,
|
||||||
generate_playlist,
|
generate_playlist,
|
||||||
library_payload,
|
library_payload,
|
||||||
media_source_content_filter,
|
media_source_content_filter,
|
||||||
)
|
)
|
||||||
from .const import (
|
from .const import DISCOVERY_TASK, DOMAIN, KNOWN_PLAYERS, SQUEEZEBOX_SOURCE_STRINGS
|
||||||
CONF_HTTPS,
|
|
||||||
DISCOVERY_TASK,
|
|
||||||
DOMAIN,
|
|
||||||
KNOWN_PLAYERS,
|
|
||||||
PLAYER_DISCOVERY_UNSUB,
|
|
||||||
SQUEEZEBOX_SOURCE_STRINGS,
|
|
||||||
)
|
|
||||||
|
|
||||||
SERVICE_CALL_METHOD = "call_method"
|
SERVICE_CALL_METHOD = "call_method"
|
||||||
SERVICE_CALL_QUERY = "call_query"
|
SERVICE_CALL_QUERY = "call_query"
|
||||||
@ -118,29 +110,15 @@ async def start_server_discovery(hass: HomeAssistant) -> None:
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
entry: SqueezeboxConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up an LMS Server from a config entry."""
|
"""Set up an player discovery 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)
|
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN].setdefault(config_entry.entry_id, {})
|
|
||||||
|
|
||||||
known_players = hass.data[DOMAIN].setdefault(KNOWN_PLAYERS, [])
|
known_players = hass.data[DOMAIN].setdefault(KNOWN_PLAYERS, [])
|
||||||
|
lms = entry.runtime_data
|
||||||
|
|
||||||
session = async_get_clientsession(hass)
|
async def _player_discovery(now=None):
|
||||||
_LOGGER.debug("Creating LMS object for %s", host)
|
|
||||||
lms = Server(session, host, port, username, password, https=https)
|
|
||||||
|
|
||||||
async def _discovery(now=None):
|
|
||||||
"""Discover squeezebox players by polling server."""
|
"""Discover squeezebox players by polling server."""
|
||||||
|
|
||||||
async def _discovered_player(player):
|
async def _discovered_player(player):
|
||||||
@ -169,13 +147,15 @@ async def async_setup_entry(
|
|||||||
for player in players:
|
for player in players:
|
||||||
hass.async_create_task(_discovered_player(player))
|
hass.async_create_task(_discovered_player(player))
|
||||||
|
|
||||||
hass.data[DOMAIN][config_entry.entry_id][PLAYER_DISCOVERY_UNSUB] = (
|
entry.async_on_unload(
|
||||||
async_call_later(hass, DISCOVERY_INTERVAL, _discovery)
|
async_call_later(hass, DISCOVERY_INTERVAL, _player_discovery)
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Adding player discovery job for LMS server: %s", host)
|
_LOGGER.debug(
|
||||||
config_entry.async_create_background_task(
|
"Adding player discovery job for LMS server: %s", entry.data[CONF_HOST]
|
||||||
hass, _discovery(), "squeezebox.media_player.discovery"
|
)
|
||||||
|
entry.async_create_background_task(
|
||||||
|
hass, _player_discovery(), "squeezebox.media_player.player_discovery"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Register entity services
|
# Register entity services
|
||||||
@ -208,7 +188,7 @@ async def async_setup_entry(
|
|||||||
platform.async_register_entity_service(SERVICE_UNSYNC, None, "async_unsync")
|
platform.async_register_entity_service(SERVICE_UNSYNC, None, "async_unsync")
|
||||||
|
|
||||||
# Start server discovery task if not already running
|
# 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):
|
class SqueezeBoxEntity(MediaPlayerEntity):
|
||||||
@ -241,14 +221,16 @@ class SqueezeBoxEntity(MediaPlayerEntity):
|
|||||||
_last_update: datetime | None = None
|
_last_update: datetime | None = None
|
||||||
_attr_available = True
|
_attr_available = True
|
||||||
|
|
||||||
def __init__(self, player):
|
def __init__(self, player: Player) -> None:
|
||||||
"""Initialize the SqueezeBox device."""
|
"""Initialize the SqueezeBox device."""
|
||||||
self._player = player
|
self._player = player
|
||||||
self._query_result = {}
|
self._query_result: bool | dict = {}
|
||||||
self._remove_dispatcher = None
|
self._remove_dispatcher: Callable | None = None
|
||||||
self._attr_unique_id = format_mac(player.player_id)
|
self._attr_unique_id = format_mac(player.player_id)
|
||||||
self._attr_device_info = DeviceInfo(
|
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
|
@property
|
||||||
@ -265,7 +247,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
|
|||||||
"""Make a player available again."""
|
"""Make a player available again."""
|
||||||
if unique_id == self.unique_id and connected:
|
if unique_id == self.unique_id and connected:
|
||||||
self._attr_available = True
|
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()
|
self._remove_dispatcher()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -286,7 +268,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
|
|||||||
if self.media_position != last_media_position:
|
if self.media_position != last_media_position:
|
||||||
self._last_update = utcnow()
|
self._last_update = utcnow()
|
||||||
if self._player.connected is False:
|
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
|
self._attr_available = False
|
||||||
|
|
||||||
# start listening for restored players
|
# start listening for restored players
|
||||||
@ -573,7 +555,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
|
|||||||
if other_player_id := player_ids.get(other_player):
|
if other_player_id := player_ids.get(other_player):
|
||||||
await self._player.async_sync(other_player_id)
|
await self._player.async_sync(other_player_id)
|
||||||
else:
|
else:
|
||||||
_LOGGER.info(
|
_LOGGER.debug(
|
||||||
"Could not find player_id for %s. Not syncing", other_player
|
"Could not find player_id for %s. Not syncing", other_player
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user