1 """Config flow for Awair."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from typing
import Any, Self, cast
8 from aiohttp.client_exceptions
import ClientError
9 from python_awair
import Awair, AwairLocal, AwairLocalDevice
10 from python_awair.exceptions
import AuthError, AwairError
11 from python_awair.user
import AwairUser
12 import voluptuous
as vol
20 from .const
import DOMAIN, LOGGER
24 """Config flow for Awair."""
28 _device: AwairLocalDevice
32 self, discovery_info: zeroconf.ZeroconfServiceInfo
33 ) -> ConfigFlowResult:
34 """Handle zeroconf discovery."""
36 self.
hosthost = discovery_info.host
37 LOGGER.debug(
"Discovered device: %s", self.
hosthost)
41 if self._device
is not None:
44 updates={CONF_HOST: self._device.device_addr},
45 error=
"already_configured_device",
49 "title_placeholders": {
50 "model": self._device.model,
51 "device_id": self._device.device_id,
60 self, user_input: dict[str, Any] |
None =
None
61 ) -> ConfigFlowResult:
62 """Confirm discovery."""
63 if user_input
is not None or not onboarding.async_is_onboarded(self.hass):
64 title = f
"{self._device.model} ({self._device.device_id})"
67 data={CONF_HOST: self._device.device_addr},
72 "model": self._device.model,
73 "device_id": self._device.device_id,
76 step_id=
"discovery_confirm",
77 description_placeholders=placeholders,
81 self, user_input: dict[str, str] |
None =
None
82 ) -> ConfigFlowResult:
83 """Handle a flow initialized by the user."""
85 return self.
async_show_menuasync_show_menu(step_id=
"user", menu_options=[
"local",
"cloud"])
88 """Handle collecting and verifying Awair Cloud API credentials."""
92 if user_input
is not None:
94 user_input[CONF_ACCESS_TOKEN]
104 if error
and error !=
"invalid_access_token":
107 errors = {CONF_ACCESS_TOKEN:
"invalid_access_token"}
111 data_schema=vol.Schema({vol.Optional(CONF_ACCESS_TOKEN): str}),
112 description_placeholders={
113 "url":
"https://developer.getawair.com/onboard/login"
120 """Get discovered entries."""
121 entries: dict[str, str] = {}
125 self.hass.config_entries.flow._handler_progress_index.get(DOMAIN)
or set(),
128 if flow.source != SOURCE_ZEROCONF:
130 info = flow.context[
"title_placeholders"]
131 entries[flow.host] = f
"{info['model']} ({info['device_id']})"
135 self, user_input: Mapping[str, Any] |
None =
None
136 ) -> ConfigFlowResult:
137 """Show how to enable local API."""
138 if user_input
is not None:
143 description_placeholders={
144 "url":
"https://support.getawair.com/hc/en-us/articles/360049221014-Awair-Element-Local-API-Feature#h_01F40FBBW5323GBPV7D6XMG4J8"
149 self, user_input: Mapping[str, Any] |
None =
None
150 ) -> ConfigFlowResult:
151 """Handle collecting and verifying Awair Local API hosts."""
162 if user_input
and user_input.get(CONF_DEVICE) !=
"manual":
163 if CONF_DEVICE
in user_input:
164 user_input = {CONF_HOST: user_input[CONF_DEVICE]}
167 user_input.get(CONF_DEVICE)
or user_input[CONF_HOST]
170 if self._device
is not None:
172 self._device.mac_address, raise_on_progress=
False
174 title = f
"{self._device.model} ({self._device.device_id})"
177 if error
is not None:
178 errors = {
"base": error}
182 if not discovered
or (user_input
and user_input.get(CONF_DEVICE) ==
"manual"):
183 data_schema = vol.Schema({vol.Required(CONF_HOST): str})
186 discovered[
"manual"] =
"Manual"
187 data_schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(discovered)})
190 step_id=
"local_pick",
191 data_schema=data_schema,
196 self, entry_data: Mapping[str, Any]
197 ) -> ConfigFlowResult:
198 """Handle re-auth if token invalid."""
202 self, user_input: dict[str, Any] |
None =
None
203 ) -> ConfigFlowResult:
204 """Confirm reauth dialog."""
207 if user_input
is not None:
208 access_token = user_input[CONF_ACCESS_TOKEN]
216 if error !=
"invalid_access_token":
219 errors = {CONF_ACCESS_TOKEN: error}
222 step_id=
"reauth_confirm",
223 data_schema=vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str}),
228 self, device_address: str
229 ) -> tuple[AwairLocalDevice |
None, str |
None]:
230 """Check the access token is valid."""
232 awair = AwairLocal(session=session, device_addrs=[device_address])
235 devices = await awair.devices()
236 return (devices[0],
None)
238 except ClientError
as err:
239 LOGGER.error(
"Unable to connect error: %s", err)
240 return (
None,
"unreachable")
242 except AwairError
as err:
243 LOGGER.error(
"Unexpected API error: %s", err)
244 return (
None,
"unknown")
247 self, access_token: str
248 ) -> tuple[AwairUser |
None, str |
None]:
249 """Check the access token is valid."""
251 awair = Awair(access_token=access_token, session=session)
254 user = await awair.user()
255 devices = await user.devices()
257 return (
None,
"invalid_access_token")
258 except AwairError
as err:
259 LOGGER.error(
"Unexpected API error: %s", err)
260 return (
None,
"unknown")
263 return (
None,
"no_devices_found")
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_discovery_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_local(self, Mapping[str, Any]|None user_input=None)
dict[str, str] _get_discovered_entries(self)
tuple[AwairLocalDevice|None, str|None] _check_local_connection(self, str device_address)
tuple[AwairUser|None, str|None] _check_cloud_connection(self, str access_token)
ConfigFlowResult async_step_cloud(self, Mapping[str, Any] user_input)
ConfigFlowResult async_step_local_pick(self, Mapping[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
None _set_confirm_only(self)
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_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
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_show_menu(self, *str|None step_id=None, Container[str] menu_options, Mapping[str, str]|None description_placeholders=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)
IssData update(pyiss.ISS iss)
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)