Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for MusicAssistant integration."""
2 
3 from __future__ import annotations
4 
5 from typing import TYPE_CHECKING, Any
6 
7 from music_assistant_client import MusicAssistantClient
8 from music_assistant_client.exceptions import (
9  CannotConnect,
10  InvalidServerVersion,
11  MusicAssistantClientException,
12 )
13 from music_assistant_models.api import ServerInfoMessage
14 import voluptuous as vol
15 
16 from homeassistant.components import zeroconf
17 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
18 from homeassistant.const import CONF_URL
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers import aiohttp_client
21 
22 from .const import DOMAIN, LOGGER
23 
24 DEFAULT_URL = "http://mass.local:8095"
25 DEFAULT_TITLE = "Music Assistant"
26 
27 
28 def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
29  """Return a schema for the manual step."""
30  default_url = user_input.get(CONF_URL, DEFAULT_URL)
31  return vol.Schema(
32  {
33  vol.Required(CONF_URL, default=default_url): str,
34  }
35  )
36 
37 
38 async def get_server_info(hass: HomeAssistant, url: str) -> ServerInfoMessage:
39  """Validate the user input allows us to connect."""
40  async with MusicAssistantClient(
41  url, aiohttp_client.async_get_clientsession(hass)
42  ) as client:
43  if TYPE_CHECKING:
44  assert client.server_info is not None
45  return client.server_info
46 
47 
48 class MusicAssistantConfigFlow(ConfigFlow, domain=DOMAIN):
49  """Handle a config flow for MusicAssistant."""
50 
51  VERSION = 1
52 
53  def __init__(self) -> None:
54  """Set up flow instance."""
55  self.server_infoserver_info: ServerInfoMessage | None = None
56 
57  async def async_step_user(
58  self, user_input: dict[str, Any] | None = None
59  ) -> ConfigFlowResult:
60  """Handle a manual configuration."""
61  errors: dict[str, str] = {}
62  if user_input is not None:
63  try:
64  self.server_infoserver_info = await get_server_info(
65  self.hass, user_input[CONF_URL]
66  )
67  await self.async_set_unique_idasync_set_unique_id(
68  self.server_infoserver_info.server_id, raise_on_progress=False
69  )
70  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
71  updates={CONF_URL: self.server_infoserver_info.base_url},
72  reload_on_update=True,
73  )
74  except CannotConnect:
75  errors["base"] = "cannot_connect"
76  except InvalidServerVersion:
77  errors["base"] = "invalid_server_version"
78  except MusicAssistantClientException:
79  LOGGER.exception("Unexpected exception")
80  errors["base"] = "unknown"
81  else:
82  return self.async_create_entryasync_create_entryasync_create_entry(
83  title=DEFAULT_TITLE,
84  data={
85  CONF_URL: self.server_infoserver_info.base_url,
86  },
87  )
88 
89  return self.async_show_formasync_show_formasync_show_form(
90  step_id="user", data_schema=get_manual_schema(user_input), errors=errors
91  )
92 
93  return self.async_show_formasync_show_formasync_show_form(step_id="user", data_schema=get_manual_schema({}))
94 
96  self, discovery_info: zeroconf.ZeroconfServiceInfo
97  ) -> ConfigFlowResult:
98  """Handle a discovered Mass server.
99 
100  This flow is triggered by the Zeroconf component. It will check if the
101  host is already configured and delegate to the import step if not.
102  """
103  # abort if discovery info is not what we expect
104  if "server_id" not in discovery_info.properties:
105  return self.async_abortasync_abortasync_abort(reason="missing_server_id")
106  # abort if we already have exactly this server_id
107  # reload the integration if the host got updated
108  self.server_infoserver_info = ServerInfoMessage.from_dict(discovery_info.properties)
109  await self.async_set_unique_idasync_set_unique_id(self.server_infoserver_info.server_id)
110  self._abort_if_unique_id_configured_abort_if_unique_id_configured(
111  updates={CONF_URL: self.server_infoserver_info.base_url},
112  reload_on_update=True,
113  )
114  try:
115  await get_server_info(self.hass, self.server_infoserver_info.base_url)
116  except CannotConnect:
117  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
118  return await self.async_step_discovery_confirmasync_step_discovery_confirm()
119 
121  self, user_input: dict[str, Any] | None = None
122  ) -> ConfigFlowResult:
123  """Handle user-confirmation of discovered server."""
124  if TYPE_CHECKING:
125  assert self.server_infoserver_info is not None
126  if user_input is not None:
127  return self.async_create_entryasync_create_entryasync_create_entry(
128  title=DEFAULT_TITLE,
129  data={
130  CONF_URL: self.server_infoserver_info.base_url,
131  },
132  )
133  self._set_confirm_only_set_confirm_only()
134  return self.async_show_formasync_show_formasync_show_form(
135  step_id="discovery_confirm",
136  description_placeholders={"url": self.server_infoserver_info.base_url},
137  )
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:59
ConfigFlowResult async_step_discovery_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:122
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
Definition: config_flow.py:97
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ServerInfoMessage get_server_info(HomeAssistant hass, str url)
Definition: config_flow.py:38
vol.Schema get_manual_schema(dict[str, Any] user_input)
Definition: config_flow.py:28