1 """Config flow to configure the LaMetric integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from ipaddress
import ip_address
10 from demetriek
import (
13 LaMetricConnectionError,
23 import voluptuous
as vol
28 ATTR_UPNP_FRIENDLY_NAME,
49 from .const
import DOMAIN, LOGGER
53 """Handle a LaMetric config flow."""
58 devices: dict[str, CloudDevice]
60 discovered_serial: str
61 discovered: bool =
False
70 """Extra data that needs to be appended to the authorize url."""
71 return {
"scope":
"basic devices_read"}
74 self, user_input: dict[str, Any] |
None =
None
75 ) -> ConfigFlowResult:
76 """Handle a flow initiated by the user."""
80 self, discovery_info: SsdpServiceInfo
81 ) -> ConfigFlowResult:
82 """Handle a flow initiated by SSDP discovery."""
83 url =
URL(discovery_info.ssdp_location
or "")
84 if url.host
is None or not (
85 serial := discovery_info.upnp.get(ATTR_UPNP_SERIAL)
97 "title_placeholders": {
98 "name": discovery_info.upnp.get(
99 ATTR_UPNP_FRIENDLY_NAME,
"LaMetric TIME"
102 "configuration_url":
"https://developer.lametric.com",
112 self, entry_data: Mapping[str, Any]
113 ) -> ConfigFlowResult:
114 """Handle initiation of re-authentication with LaMetric."""
118 self, user_input: dict[str, Any] |
None =
None
119 ) -> ConfigFlowResult:
120 """Handle the user's choice.
122 Either enter the manual credentials or fetch the cloud credentials.
125 step_id=
"choice_enter_manual_or_fetch_cloud",
126 menu_options=[
"pick_implementation",
"manual_entry"],
130 self, user_input: dict[str, Any] |
None =
None
131 ) -> ConfigFlowResult:
132 """Handle the user's choice of entering the device manually."""
133 errors: dict[str, str] = {}
134 if user_input
is not None:
140 host = user_input[CONF_HOST]
144 host, user_input[CONF_API_KEY]
148 except LaMetricConnectionError
as ex:
149 LOGGER.error(
"Error connecting to LaMetric: %s", ex)
150 errors[
"base"] =
"cannot_connect"
152 LOGGER.exception(
"Unexpected error occurred")
153 errors[
"base"] =
"unknown"
162 schema = {vol.Required(CONF_HOST):
TextSelector()} | schema
165 step_id=
"manual_entry",
166 data_schema=vol.Schema(schema),
171 self, data: dict[str, Any]
172 ) -> ConfigFlowResult:
173 """Fetch information about devices from the cloud."""
174 lametric = LaMetricCloud(
175 token=data[
"token"][
"access_token"],
179 device.serial_number: device
180 for device
in sorted(await lametric.devices(), key=
lambda d: d.name)
189 self, user_input: dict[str, Any] |
None =
None
190 ) -> ConfigFlowResult:
191 """Handle device selection from devices offered by the cloud."""
196 if reauth_unique_id
not in self.
devicesdevices:
198 user_input = {CONF_DEVICE: reauth_unique_id}
199 elif len(self.
devicesdevices) == 1:
200 user_input = {CONF_DEVICE:
list(self.
devicesdevices.values())[0].serial_number}
202 errors: dict[str, str] = {}
203 if user_input
is not None:
204 device = self.
devicesdevices[user_input[CONF_DEVICE]]
207 str(device.ip), device.api_key
211 except LaMetricConnectionError
as ex:
212 LOGGER.error(
"Error connecting to LaMetric: %s", ex)
213 errors[
"base"] =
"cannot_connect"
215 LOGGER.exception(
"Unexpected error occurred")
216 errors[
"base"] =
"unknown"
219 step_id=
"cloud_select_device",
220 data_schema=vol.Schema(
224 mode=SelectSelectorMode.DROPDOWN,
227 value=device.serial_number,
230 for device
in self.
devicesdevices.values()
240 self, host: str, api_key: str
241 ) -> ConfigFlowResult:
243 lametric = LaMetricDevice(
249 device = await lametric.device()
254 updates={CONF_HOST: lametric.host, CONF_API_KEY: lametric.api_key}
257 notify_sound: Sound |
None =
None
258 if device.model !=
"sa5":
259 notify_sound = Sound(sound=NotificationSound.WIN)
261 await lametric.notify(
262 notification=Notification(
263 priority=NotificationPriority.CRITICAL,
264 icon_type=NotificationIconType.INFO,
267 frames=[Simple(text=
"Connected to Home Assistant!", icon=7956)],
277 CONF_HOST: lametric.host,
278 CONF_API_KEY: lametric.api_key,
285 CONF_API_KEY: lametric.api_key,
286 CONF_HOST: lametric.host,
287 CONF_MAC: device.wifi.mac,
292 self, discovery_info: DhcpServiceInfo
293 ) -> ConfigFlowResult:
294 """Handle dhcp discovery to update existing entries."""
298 self.hass.config_entries.async_update_entry(
300 data=entry.data | {CONF_HOST: discovery_info.ip},
302 self.hass.async_create_task(
303 self.hass.config_entries.async_reload(entry.entry_id)
312 async_oauth_create_entry = async_step_cloud_fetch_devices
ConfigFlowResult async_step_choice_enter_manual_or_fetch_cloud(self, dict[str, Any]|None user_input=None)
logging.Logger logger(self)
ConfigFlowResult async_step_cloud_select_device(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_manual_entry(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_ssdp(self, SsdpServiceInfo discovery_info)
dict[str, Any] extra_authorize_data(self)
ConfigFlowResult async_step_dhcp(self, DhcpServiceInfo discovery_info)
ConfigFlowResult async_step_cloud_fetch_devices(self, dict[str, Any] data)
ConfigFlowResult _async_step_create_entry(self, str host, str api_key)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
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)
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=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)
bool is_link_local(IPv4Address|IPv6Address address)