1 """Flow handler for Crownstone."""
3 from __future__
import annotations
5 from collections.abc
import Callable
8 from crownstone_cloud
import CrownstoneCloud
9 from crownstone_cloud.exceptions
import (
10 CrownstoneAuthenticationError,
11 CrownstoneUnknownError,
13 import serial.tools.list_ports
14 from serial.tools.list_ports_common
import ListPortInfo
15 import voluptuous
as vol
33 CONF_USB_SPHERE_OPTION,
40 from .helpers
import list_ports_as_str
42 CONFIG_FLOW =
"config_flow"
43 OPTIONS_FLOW =
"options_flow"
47 """Represent the base flow for Crownstone."""
49 cloud: CrownstoneCloud
52 self, flow_type: str, create_entry_cb: Callable[..., ConfigFlowResult]
54 """Set up flow instance."""
57 self.
usb_pathusb_path: str |
None =
None
61 self, user_input: dict[str, Any] |
None =
None
62 ) -> ConfigFlowResult:
63 """Set up a Crownstone USB dongle."""
64 list_of_ports = await self.hass.async_add_executor_job(
65 serial.tools.list_ports.comports
67 if self.
flow_typeflow_type == CONFIG_FLOW:
72 if user_input
is not None:
73 selection = user_input[CONF_USB_PATH]
75 if selection == DONT_USE_USB:
77 if selection == MANUAL_PATH:
79 if selection != REFRESH_LIST:
80 if self.
flow_typeflow_type == OPTIONS_FLOW:
81 index = ports_as_string.index(selection)
83 index = ports_as_string.index(selection) - 1
85 selected_port: ListPortInfo = list_of_ports[index]
86 self.
usb_pathusb_path = await self.hass.async_add_executor_job(
87 usb.get_serial_by_id, selected_port.device
93 data_schema=vol.Schema(
94 {vol.Required(CONF_USB_PATH): vol.In(ports_as_string)}
99 self, user_input: dict[str, Any] |
None =
None
100 ) -> ConfigFlowResult:
101 """Manually enter Crownstone USB dongle path."""
102 if user_input
is None:
104 step_id=
"usb_manual_config",
105 data_schema=vol.Schema({vol.Required(CONF_USB_MANUAL_PATH): str}),
108 self.
usb_pathusb_path = user_input[CONF_USB_MANUAL_PATH]
112 self, user_input: dict[str, Any] |
None =
None
113 ) -> ConfigFlowResult:
114 """Select a Crownstone sphere that the USB operates in."""
115 spheres = {sphere.name: sphere.cloud_id
for sphere
in self.cloud.cloud_data}
117 sphere_id: str |
None =
None
118 if len(spheres) == 1:
119 sphere_id = next(iter(spheres.values()))
121 if user_input
is None and sphere_id
is None:
123 step_id=
"usb_sphere_config",
124 data_schema=vol.Schema({CONF_USB_SPHERE: vol.In(spheres.keys())}),
130 self.
usb_sphere_idusb_sphere_id = spheres[user_input[CONF_USB_SPHERE]]
136 """Handle a config flow for Crownstone."""
143 config_entry: ConfigEntry,
144 ) -> CrownstoneOptionsFlowHandler:
145 """Return the Crownstone options."""
149 """Initialize the flow."""
151 self.
login_infologin_info: dict[str, Any] = {}
154 self, user_input: dict[str, Any] |
None =
None
155 ) -> ConfigFlowResult:
156 """Handle the initial step."""
157 errors: dict[str, str] = {}
158 if user_input
is None:
161 data_schema=vol.Schema(
162 {vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str}
167 email=user_input[CONF_EMAIL],
168 password=user_input[CONF_PASSWORD],
169 clientsession=aiohttp_client.async_get_clientsession(self.hass),
173 await self.
cloudcloud.async_initialize()
174 except CrownstoneAuthenticationError
as auth_error:
175 if auth_error.type ==
"LOGIN_FAILED":
176 errors[
"base"] =
"invalid_auth"
177 elif auth_error.type ==
"LOGIN_FAILED_EMAIL_NOT_VERIFIED":
178 errors[
"base"] =
"account_not_verified"
179 except CrownstoneUnknownError:
180 errors[
"base"] =
"unknown"
186 data_schema=vol.Schema(
187 {vol.Required(CONF_EMAIL): str, vol.Required(CONF_PASSWORD): str}
199 """Create a new entry."""
201 title=f
"Account: {self.login_info[CONF_EMAIL]}",
203 CONF_EMAIL: self.
login_infologin_info[CONF_EMAIL],
204 CONF_PASSWORD: self.
login_infologin_info[CONF_PASSWORD],
211 """Handle Crownstone options."""
213 def __init__(self, config_entry: ConfigEntry) ->
None:
214 """Initialize Crownstone options."""
216 self.
optionsoptions = config_entry.options.copy()
219 self, user_input: dict[str, Any] |
None =
None
220 ) -> ConfigFlowResult:
221 """Manage Crownstone options."""
222 self.cloud: CrownstoneCloud = self.hass.data[DOMAIN][
226 spheres = {sphere.name: sphere.cloud_id
for sphere
in self.cloud.cloud_data}
230 options_schema = vol.Schema(
231 {vol.Optional(CONF_USE_USB_OPTION, default=usb_path
is not None): bool}
233 if usb_path
is not None and len(spheres) > 1:
234 options_schema = options_schema.extend(
237 CONF_USB_SPHERE_OPTION,
238 default=self.cloud.cloud_data.data[usb_sphere].name,
239 ): vol.In(spheres.keys())
243 if user_input
is not None:
244 if user_input[CONF_USE_USB_OPTION]
and usb_path
is None:
246 if not user_input[CONF_USE_USB_OPTION]
and usb_path
is not None:
247 self.
optionsoptions[CONF_USB_PATH] =
None
248 self.
optionsoptions[CONF_USB_SPHERE] =
None
250 CONF_USB_SPHERE_OPTION
in user_input
251 and spheres[user_input[CONF_USB_SPHERE_OPTION]] != usb_sphere
253 sphere_id = spheres[user_input[CONF_USB_SPHERE_OPTION]]
254 self.
optionsoptions[CONF_USB_SPHERE] = sphere_id
258 return self.
async_show_formasync_show_form(step_id=
"init", data_schema=options_schema)
261 """Create a new entry."""
ConfigFlowResult async_step_usb_sphere_config(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_usb_manual_config(self, dict[str, Any]|None user_input=None)
None __init__(self, str flow_type, Callable[..., ConfigFlowResult] create_entry_cb)
ConfigFlowResult async_step_usb_config(self, dict[str, Any]|None user_input=None)
CrownstoneOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_create_new_entry(self)
ConfigFlowResult async_create_new_entry(self)
None __init__(self, ConfigEntry config_entry)
ConfigFlowResult async_step_init(self, dict[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|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_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)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_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)
list[str] list_ports_as_str(list[ListPortInfo] serial_ports, bool no_usb_option=True)