Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Rabbit Air integration."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from rabbitair import UdpClient
9 import voluptuous as vol
10 
11 from homeassistant.components import zeroconf
12 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
13 from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_MAC
14 from homeassistant.core import HomeAssistant
15 from homeassistant.exceptions import HomeAssistantError
16 from homeassistant.helpers import device_registry as dr
17 
18 from .const import DOMAIN
19 
20 _LOGGER = logging.getLogger(__name__)
21 
22 
23 async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
24  """Validate the user input allows us to connect."""
25  try:
26  try:
27  zeroconf_instance = await zeroconf.async_get_async_instance(hass)
28  with UdpClient(
29  data[CONF_HOST], data[CONF_ACCESS_TOKEN], zeroconf=zeroconf_instance
30  ) as client:
31  info = await client.get_info()
32  except Exception as err:
33  _LOGGER.debug("Connection attempt failed: %s", err)
34  raise
35  except ValueError as err:
36  # Most likely caused by the invalid access token.
37  raise InvalidAccessToken from err
38  except TimeoutError as err:
39  # Either the host doesn't respond or the auth failed.
40  raise TimeoutConnect from err
41  except OSError as err:
42  # Most likely caused by the invalid host.
43  raise InvalidHost from err
44  except Exception as err:
45  # Other possible errors.
46  raise CannotConnect from err
47 
48  # Return info to store in the config entry.
49  return {"mac": info.mac}
50 
51 
52 class RabbitAirConfigFlow(ConfigFlow, domain=DOMAIN):
53  """Handle a config flow for Rabbit Air."""
54 
55  VERSION = 1
56 
57  _discovered_host: str | None = None
58 
59  async def async_step_user(
60  self, user_input: dict[str, Any] | None = None
61  ) -> ConfigFlowResult:
62  """Handle the initial step."""
63  errors = {}
64 
65  if user_input is not None:
66  try:
67  info = await validate_input(self.hass, user_input)
68  except CannotConnect:
69  errors["base"] = "cannot_connect"
70  except InvalidAccessToken:
71  errors["base"] = "invalid_access_token"
72  except InvalidHost:
73  errors["base"] = "invalid_host"
74  except TimeoutConnect:
75  errors["base"] = "timeout_connect"
76  except Exception as err: # noqa: BLE001
77  _LOGGER.debug("Unexpected exception: %s", err)
78  errors["base"] = "unknown"
79  else:
80  user_input[CONF_MAC] = info["mac"]
81  await self.async_set_unique_idasync_set_unique_id(dr.format_mac(info["mac"]))
82  self._abort_if_unique_id_configured_abort_if_unique_id_configured(updates=user_input)
83  return self.async_create_entryasync_create_entryasync_create_entry(title="Rabbit Air", data=user_input)
84 
85  user_input = user_input or {}
86  host = user_input.get(CONF_HOST, self._discovered_host_discovered_host)
87  token = user_input.get(CONF_ACCESS_TOKEN)
88  return self.async_show_formasync_show_formasync_show_form(
89  step_id="user",
90  data_schema=vol.Schema(
91  {
92  vol.Required(CONF_HOST, default=host): str,
93  vol.Required(CONF_ACCESS_TOKEN, default=token): vol.All(
94  str, vol.Length(min=32, max=32)
95  ),
96  }
97  ),
98  errors=errors,
99  )
100 
101  async def async_step_zeroconf(
102  self, discovery_info: zeroconf.ZeroconfServiceInfo
103  ) -> ConfigFlowResult:
104  """Handle zeroconf discovery."""
105  mac = dr.format_mac(discovery_info.properties["id"])
106  await self.async_set_unique_idasync_set_unique_id(mac)
107  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
108  self._discovered_host_discovered_host = discovery_info.hostname.rstrip(".")
109  return await self.async_step_userasync_step_userasync_step_user()
110 
111 
113  """Error to indicate we cannot connect."""
114 
115 
117  """Error to indicate the access token is not valid."""
118 
119 
121  """Error to indicate the host is not valid."""
122 
123 
125  """Error to indicate the connection attempt is timed out."""
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:61
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_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
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)
dict[str, Any] validate_input(HomeAssistant hass, dict[str, Any] data)
Definition: config_flow.py:23