core/homeassistant/components/squeezebox/__init__.py

200 lines
6.2 KiB
Python

"""The Squeezebox integration."""
from asyncio import timeout
from dataclasses import dataclass
from datetime import datetime
import logging
from pysqueezebox import Player, Server
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 import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC,
DeviceEntryType,
format_mac,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from .const import (
CONF_HTTPS,
DISCOVERY_INTERVAL,
DISCOVERY_TASK,
DOMAIN,
KNOWN_PLAYERS,
KNOWN_SERVERS,
MANUFACTURER,
SERVER_MODEL,
SIGNAL_PLAYER_DISCOVERED,
SIGNAL_PLAYER_REDISCOVERED,
STATUS_API_TIMEOUT,
STATUS_QUERY_LIBRARYNAME,
STATUS_QUERY_MAC,
STATUS_QUERY_UUID,
STATUS_QUERY_VERSION,
)
from .coordinator import (
LMSStatusDataUpdateCoordinator,
SqueezeBoxPlayerUpdateCoordinator,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.MEDIA_PLAYER,
Platform.SENSOR,
]
@dataclass
class SqueezeboxData:
"""SqueezeboxData data class."""
coordinator: LMSStatusDataUpdateCoordinator
server: Server
type SqueezeboxConfigEntry = ConfigEntry[SqueezeboxData]
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]
_LOGGER.debug("LMS %s = '%s' with uuid = %s ", lms.name, host, lms.uuid)
lms.name = (
(STATUS_QUERY_LIBRARYNAME in status and status[STATUS_QUERY_LIBRARYNAME])
and status[STATUS_QUERY_LIBRARYNAME]
or host
)
version = STATUS_QUERY_VERSION in status and status[STATUS_QUERY_VERSION] or None
# mac can be missing
mac_connect = (
{(CONNECTION_NETWORK_MAC, format_mac(status[STATUS_QUERY_MAC]))}
if STATUS_QUERY_MAC in status
else None
)
device_registry = dr.async_get(hass)
device = device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, lms.uuid)},
name=lms.name,
manufacturer=MANUFACTURER,
model=SERVER_MODEL,
sw_version=version,
entry_type=DeviceEntryType.SERVICE,
connections=mac_connect,
)
_LOGGER.debug("LMS Device %s", device)
server_coordinator = LMSStatusDataUpdateCoordinator(hass, lms)
entry.runtime_data = SqueezeboxData(
coordinator=server_coordinator,
server=lms,
)
# set up player discovery
known_servers = hass.data.setdefault(DOMAIN, {}).setdefault(KNOWN_SERVERS, {})
known_players = known_servers.setdefault(lms.uuid, {}).setdefault(KNOWN_PLAYERS, [])
async def _player_discovery(now: datetime | None = None) -> None:
"""Discover squeezebox players by polling server."""
async def _discovered_player(player: Player) -> None:
"""Handle a (re)discovered player."""
if player.player_id in known_players:
await player.async_update()
async_dispatcher_send(
hass, SIGNAL_PLAYER_REDISCOVERED, player.player_id, player.connected
)
else:
_LOGGER.debug("Adding new entity: %s", player)
player_coordinator = SqueezeBoxPlayerUpdateCoordinator(
hass, player, lms.uuid
)
known_players.append(player.player_id)
async_dispatcher_send(
hass, SIGNAL_PLAYER_DISCOVERED, player_coordinator
)
if players := await lms.async_get_players():
for player in players:
hass.async_create_task(_discovered_player(player))
entry.async_on_unload(
async_call_later(hass, DISCOVERY_INTERVAL, _player_discovery)
)
await server_coordinator.async_config_entry_first_refresh()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
_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"
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) -> bool:
"""Unload a config entry."""
# Stop player discovery task for this config entry.
_LOGGER.debug(
"Reached async_unload_entry for LMS=%s(%s)",
entry.runtime_data.server.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)
if len(current_entries) == 1 and current_entries[0] == entry:
_LOGGER.debug("Stopping server discovery task")
hass.data[DOMAIN][DISCOVERY_TASK].cancel()
hass.data[DOMAIN].pop(DISCOVERY_TASK)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)