Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Squeezebox integration."""
2 
3 from asyncio import timeout
4 from dataclasses import dataclass
5 from datetime import datetime
6 import logging
7 
8 from pysqueezebox import Player, Server
9 
10 from homeassistant.config_entries import ConfigEntry
11 from homeassistant.const import (
12  CONF_HOST,
13  CONF_PASSWORD,
14  CONF_PORT,
15  CONF_USERNAME,
16  Platform,
17 )
18 from homeassistant.core import HomeAssistant
19 from homeassistant.exceptions import ConfigEntryNotReady
20 from homeassistant.helpers import device_registry as dr
21 from homeassistant.helpers.aiohttp_client import async_get_clientsession
23  CONNECTION_NETWORK_MAC,
24  DeviceEntryType,
25  format_mac,
26 )
27 from homeassistant.helpers.dispatcher import async_dispatcher_send
28 from homeassistant.helpers.event import async_call_later
29 
30 from .const import (
31  CONF_HTTPS,
32  DISCOVERY_INTERVAL,
33  DISCOVERY_TASK,
34  DOMAIN,
35  KNOWN_PLAYERS,
36  KNOWN_SERVERS,
37  MANUFACTURER,
38  SERVER_MODEL,
39  SIGNAL_PLAYER_DISCOVERED,
40  SIGNAL_PLAYER_REDISCOVERED,
41  STATUS_API_TIMEOUT,
42  STATUS_QUERY_LIBRARYNAME,
43  STATUS_QUERY_MAC,
44  STATUS_QUERY_UUID,
45  STATUS_QUERY_VERSION,
46 )
47 from .coordinator import (
48  LMSStatusDataUpdateCoordinator,
49  SqueezeBoxPlayerUpdateCoordinator,
50 )
51 
52 _LOGGER = logging.getLogger(__name__)
53 
54 PLATFORMS = [
55  Platform.BINARY_SENSOR,
56  Platform.MEDIA_PLAYER,
57  Platform.SENSOR,
58 ]
59 
60 
61 @dataclass
63  """SqueezeboxData data class."""
64 
65  coordinator: LMSStatusDataUpdateCoordinator
66  server: Server
67 
68 
69 type SqueezeboxConfigEntry = ConfigEntry[SqueezeboxData]
70 
71 
72 async def async_setup_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) -> bool:
73  """Set up an LMS Server from a config entry."""
74  config = entry.data
75  session = async_get_clientsession(hass)
76  _LOGGER.debug(
77  "Reached async_setup_entry for host=%s(%s)", config[CONF_HOST], entry.entry_id
78  )
79 
80  username = config.get(CONF_USERNAME)
81  password = config.get(CONF_PASSWORD)
82  https = config.get(CONF_HTTPS, False)
83  host = config[CONF_HOST]
84  port = config[CONF_PORT]
85 
86  lms = Server(session, host, port, username, password, https=https)
87  _LOGGER.debug("LMS object for %s", lms)
88 
89  try:
90  async with timeout(STATUS_API_TIMEOUT):
91  status = await lms.async_query(
92  "serverstatus", "-", "-", "prefs:libraryname"
93  )
94  except Exception as err:
95  raise ConfigEntryNotReady(
96  f"Error communicating config not read for {host}"
97  ) from err
98 
99  if not status:
100  raise ConfigEntryNotReady(f"Error Config Not read for {host}")
101  _LOGGER.debug("LMS Status for setup = %s", status)
102 
103  lms.uuid = status[STATUS_QUERY_UUID]
104  _LOGGER.debug("LMS %s = '%s' with uuid = %s ", lms.name, host, lms.uuid)
105  lms.name = (
106  (STATUS_QUERY_LIBRARYNAME in status and status[STATUS_QUERY_LIBRARYNAME])
107  and status[STATUS_QUERY_LIBRARYNAME]
108  or host
109  )
110  version = STATUS_QUERY_VERSION in status and status[STATUS_QUERY_VERSION] or None
111  # mac can be missing
112  mac_connect = (
113  {(CONNECTION_NETWORK_MAC, format_mac(status[STATUS_QUERY_MAC]))}
114  if STATUS_QUERY_MAC in status
115  else None
116  )
117 
118  device_registry = dr.async_get(hass)
119  device = device_registry.async_get_or_create(
120  config_entry_id=entry.entry_id,
121  identifiers={(DOMAIN, lms.uuid)},
122  name=lms.name,
123  manufacturer=MANUFACTURER,
124  model=SERVER_MODEL,
125  sw_version=version,
126  entry_type=DeviceEntryType.SERVICE,
127  connections=mac_connect,
128  )
129  _LOGGER.debug("LMS Device %s", device)
130 
131  server_coordinator = LMSStatusDataUpdateCoordinator(hass, lms)
132 
133  entry.runtime_data = SqueezeboxData(
134  coordinator=server_coordinator,
135  server=lms,
136  )
137 
138  # set up player discovery
139  known_servers = hass.data.setdefault(DOMAIN, {}).setdefault(KNOWN_SERVERS, {})
140  known_players = known_servers.setdefault(lms.uuid, {}).setdefault(KNOWN_PLAYERS, [])
141 
142  async def _player_discovery(now: datetime | None = None) -> None:
143  """Discover squeezebox players by polling server."""
144 
145  async def _discovered_player(player: Player) -> None:
146  """Handle a (re)discovered player."""
147  if player.player_id in known_players:
148  await player.async_update()
150  hass, SIGNAL_PLAYER_REDISCOVERED, player.player_id, player.connected
151  )
152  else:
153  _LOGGER.debug("Adding new entity: %s", player)
154  player_coordinator = SqueezeBoxPlayerUpdateCoordinator(
155  hass, player, lms.uuid
156  )
157  known_players.append(player.player_id)
159  hass, SIGNAL_PLAYER_DISCOVERED, player_coordinator
160  )
161 
162  if players := await lms.async_get_players():
163  for player in players:
164  hass.async_create_task(_discovered_player(player))
165 
166  entry.async_on_unload(
167  async_call_later(hass, DISCOVERY_INTERVAL, _player_discovery)
168  )
169 
170  await server_coordinator.async_config_entry_first_refresh()
171  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
172 
173  _LOGGER.debug(
174  "Adding player discovery job for LMS server: %s", entry.data[CONF_HOST]
175  )
176  entry.async_create_background_task(
177  hass, _player_discovery(), "squeezebox.media_player.player_discovery"
178  )
179 
180  return True
181 
182 
183 async def async_unload_entry(hass: HomeAssistant, entry: SqueezeboxConfigEntry) -> bool:
184  """Unload a config entry."""
185  # Stop player discovery task for this config entry.
186  _LOGGER.debug(
187  "Reached async_unload_entry for LMS=%s(%s)",
188  entry.runtime_data.server.name or "Unknown",
189  entry.entry_id,
190  )
191 
192  # Stop server discovery task if this is the last config entry.
193  current_entries = hass.config_entries.async_entries(DOMAIN)
194  if len(current_entries) == 1 and current_entries[0] == entry:
195  _LOGGER.debug("Stopping server discovery task")
196  hass.data[DOMAIN][DISCOVERY_TASK].cancel()
197  hass.data[DOMAIN].pop(DISCOVERY_TASK)
198 
199  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
bool async_setup_entry(HomeAssistant hass, SqueezeboxConfigEntry entry)
Definition: __init__.py:72
bool async_unload_entry(HomeAssistant hass, SqueezeboxConfigEntry entry)
Definition: __init__.py:183
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597