1 """Music Assistant (music-assistant.io) integration."""
3 from __future__
import annotations
6 from dataclasses
import dataclass
7 from typing
import TYPE_CHECKING
9 from music_assistant_client
import MusicAssistantClient
10 from music_assistant_client.exceptions
import CannotConnect, InvalidServerVersion
11 from music_assistant_models.enums
import EventType
12 from music_assistant_models.errors
import MusicAssistantError
26 from .const
import DOMAIN, LOGGER
29 from music_assistant_models.event
import MassEvent
31 PLATFORMS = [Platform.MEDIA_PLAYER]
34 LISTEN_READY_TIMEOUT = 30
36 type MusicAssistantConfigEntry = ConfigEntry[MusicAssistantEntryData]
41 """Hold Mass data for the config entry."""
43 mass: MusicAssistantClient
44 listen_task: asyncio.Task
48 hass: HomeAssistant, entry: MusicAssistantConfigEntry
50 """Set up Music Assistant from a config entry."""
52 mass_url = entry.data[CONF_URL]
53 mass = MusicAssistantClient(mass_url, http_session)
56 async
with asyncio.timeout(CONNECT_TIMEOUT):
58 except (TimeoutError, CannotConnect)
as err:
60 f
"Failed to connect to music assistant server {mass_url}"
62 except InvalidServerVersion
as err:
66 "invalid_server_version",
68 severity=IssueSeverity.ERROR,
69 translation_key=
"invalid_server_version",
72 except MusicAssistantError
as err:
73 LOGGER.exception(
"Failed to connect to music assistant server", exc_info=err)
75 f
"Unknown error connecting to the Music Assistant server {mass_url}"
80 async
def on_hass_stop(event: Event) ->
None:
81 """Handle incoming stop event from Home Assistant."""
82 await mass.disconnect()
84 entry.async_on_unload(
85 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
90 init_ready = asyncio.Event()
91 listen_task = asyncio.create_task(
_client_listen(hass, entry, mass, init_ready))
94 async
with asyncio.timeout(LISTEN_READY_TIMEOUT):
95 await init_ready.wait()
96 except TimeoutError
as err:
104 if listen_task.done()
and (listen_error := listen_task.exception())
is not None:
105 await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
107 await mass.disconnect()
112 await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
115 async
def handle_player_removed(event: MassEvent) ->
None:
116 """Handle Mass Player Removed event."""
117 if event.object_id
is None:
119 dev_reg = dr.async_get(hass)
120 if hass_device := dev_reg.async_get_device({(DOMAIN, event.object_id)}):
121 dev_reg.async_update_device(
122 hass_device.id, remove_config_entry_id=entry.entry_id
125 entry.async_on_unload(
126 mass.subscribe(handle_player_removed, EventType.PLAYER_REMOVED)
135 mass: MusicAssistantClient,
136 init_ready: asyncio.Event,
138 """Listen with the client."""
140 await mass.start_listening(init_ready)
141 except MusicAssistantError
as err:
142 if entry.state != ConfigEntryState.LOADED:
144 LOGGER.error(
"Failed to listen: %s", err)
145 except Exception
as err:
147 if entry.state != ConfigEntryState.LOADED:
149 LOGGER.exception(
"Unexpected exception: %s", err)
151 if not hass.is_stopping:
152 LOGGER.debug(
"Disconnected from server. Reloading integration")
153 hass.async_create_task(hass.config_entries.async_reload(entry.entry_id))
157 """Unload a config entry."""
158 unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
161 mass_entry_data: MusicAssistantEntryData = entry.runtime_data
162 mass_entry_data.listen_task.cancel()
163 await mass_entry_data.mass.disconnect()
None _client_listen(HomeAssistant hass, ConfigEntry entry, MusicAssistantClient mass, asyncio.Event init_ready)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, MusicAssistantConfigEntry entry)
None async_create_issue(HomeAssistant hass, str entry_id)
None async_delete_issue(HomeAssistant hass, str entry_id)
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)