1 """Config flow to configure roomba component."""
3 from __future__
import annotations
6 from functools
import partial
9 from roombapy
import RoombaFactory, RoombaInfo
10 from roombapy.discovery
import RoombaDiscovery
11 from roombapy.getpassword
import RoombaPassword
12 import voluptuous
as vol
24 from .
import CannotConnect, async_connect_or_timeout, async_disconnect_or_timeout
34 ROOMBA_DISCOVERY_LOCK =
"roomba_discovery_lock"
39 DEFAULT_OPTIONS = {CONF_CONTINUOUS: DEFAULT_CONTINUOUS, CONF_DELAY: DEFAULT_DELAY}
41 MAX_NUM_DEVICES_TO_DISCOVER = 25
43 AUTH_HELP_URL_KEY =
"auth_help_url"
44 AUTH_HELP_URL_VALUE = (
45 "https://www.home-assistant.io/integrations/roomba/#retrieving-your-credentials"
49 async
def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
50 """Validate the user input allows us to connect.
52 Data has the keys from DATA_SCHEMA with values provided by the user.
54 roomba = await hass.async_add_executor_job(
56 RoombaFactory.create_roomba,
57 address=data[CONF_HOST],
59 password=data[CONF_PASSWORD],
61 delay=data[CONF_DELAY],
70 ROOMBA_SESSION: info[ROOMBA_SESSION],
71 CONF_NAME: info[CONF_NAME],
72 CONF_HOST: data[CONF_HOST],
77 """Roomba configuration flow."""
81 name: str |
None =
None
83 host: str |
None =
None
86 """Initialize the roomba flow."""
92 config_entry: ConfigEntry,
93 ) -> RoombaOptionsFlowHandler:
94 """Get the options flow for this handler."""
98 self, discovery_info: zeroconf.ZeroconfServiceInfo
99 ) -> ConfigFlowResult:
100 """Handle zeroconf discovery."""
102 discovery_info.host, discovery_info.hostname.lower().removesuffix(
".local.")
106 self, discovery_info: dhcp.DhcpServiceInfo
107 ) -> ConfigFlowResult:
108 """Handle dhcp discovery."""
110 discovery_info.ip, discovery_info.hostname
114 self, ip_address: str, hostname: str
115 ) -> ConfigFlowResult:
116 """Handle any discovery."""
119 if not hostname.startswith((
"irobot-",
"roomba-")):
133 flow_unique_id = progress[
"context"].
get(
"unique_id")
134 if not flow_unique_id:
136 if flow_unique_id.startswith(self.
blidblid):
138 if self.
blidblid.startswith(flow_unique_id):
139 self.hass.config_entries.flow.async_abort(progress[
"flow_id"])
141 self.context[
"title_placeholders"] = {
"host": self.
hosthost,
"name": self.
blidblid}
148 self.
blidblid = device.blid
149 self.
namename = device.robot_name
155 self, user_input: dict[str, Any] |
None =
None
156 ) -> ConfigFlowResult:
157 """Handle a flow start."""
159 if user_input
is not None and not user_input.get(CONF_HOST):
163 user_input
is not None
167 self.
hosthost = user_input[CONF_HOST]
178 for device
in devices
179 if device.blid
not in already_configured
184 self.context[
"title_placeholders"] = {
185 "host": self.
hosthost,
193 hosts: dict[str |
None, str] = {
195 device.ip: f
"{device.robot_name} ({device.ip})"
196 for device
in devices
197 if device.blid
not in already_configured
199 None:
"Manually add a Roomba or Braava",
204 data_schema=vol.Schema({vol.Optional(
"host"): vol.In(hosts)}),
208 self, user_input: dict[str, Any] |
None =
None
209 ) -> ConfigFlowResult:
210 """Handle manual device setup."""
211 if user_input
is None:
214 description_placeholders={AUTH_HELP_URL_KEY: AUTH_HELP_URL_VALUE},
215 data_schema=vol.Schema(
216 {vol.Required(CONF_HOST, default=self.
hosthost): str}
222 self.
hosthost = user_input[CONF_HOST]
227 self.
blidblid = devices[0].blid
228 self.
namename = devices[0].robot_name
235 self, user_input: dict[str, Any] |
None =
None
236 ) -> ConfigFlowResult:
237 """Attempt to link with the Roomba.
239 Given a configured host, will ask the user to press the home and target buttons
240 to connect to the device.
242 if user_input
is None:
245 description_placeholders={CONF_NAME: self.
namename
or self.
blidblid},
248 roomba_pw = RoombaPassword(self.
hosthost)
251 password = await self.hass.async_add_executor_job(roomba_pw.get_password)
259 CONF_HOST: self.
hosthost,
260 CONF_BLID: self.
blidblid,
261 CONF_PASSWORD: password,
265 if not self.
namename:
268 except CannotConnect:
271 self.
namename = info[CONF_NAME]
276 self, user_input: dict[str, Any] |
None =
None
277 ) -> ConfigFlowResult:
278 """Handle manual linking."""
281 if user_input
is not None:
283 CONF_HOST: self.
hosthost,
284 CONF_BLID: self.
blidblid,
285 CONF_PASSWORD: user_input[CONF_PASSWORD],
290 except CannotConnect:
291 errors = {
"base":
"cannot_connect"}
296 step_id=
"link_manual",
297 description_placeholders={AUTH_HELP_URL_KEY: AUTH_HELP_URL_VALUE},
298 data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}),
304 """Handle options."""
307 self, user_input: dict[str, Any] |
None =
None
308 ) -> ConfigFlowResult:
309 """Manage the options."""
310 if user_input
is not None:
316 data_schema=vol.Schema(
320 default=options.get(CONF_CONTINUOUS, DEFAULT_CONTINUOUS),
324 default=options.get(CONF_DELAY, DEFAULT_DELAY),
333 """Create a discovery object."""
334 discovery = RoombaDiscovery()
335 discovery.amount_of_broadcasted_messages = MAX_NUM_DEVICES_TO_DISCOVER
341 """Extract the blid from the hostname."""
342 return hostname.split(
"-")[1].split(
".")[0].upper()
346 hass: HomeAssistant, host: str |
None =
None
347 ) -> list[RoombaInfo]:
348 discovered_hosts: set[str] = set()
349 devices: list[RoombaInfo] = []
350 discover_lock = hass.data.setdefault(ROOMBA_DISCOVERY_LOCK, asyncio.Lock())
351 discover_attempts = HOST_ATTEMPTS
if host
else ALL_ATTEMPTS
353 for attempt
in range(discover_attempts + 1):
354 async
with discover_lock:
356 discovered: set[RoombaInfo] = set()
359 device = await hass.async_add_executor_job(discovery.get, host)
361 discovered.add(device)
363 discovered = await hass.async_add_executor_job(discovery.get_all)
366 await asyncio.sleep(ROOMBA_WAKE_TIME * attempt)
369 for device
in discovered:
370 if device.ip
in discovered_hosts:
372 discovered_hosts.add(device.ip)
373 devices.append(device)
375 discovery.server_socket.close()
377 if host
and host
in discovered_hosts:
380 await asyncio.sleep(ROOMBA_WAKE_TIME)
ConfigFlowResult _async_step_discovery(self, str ip_address, str hostname)
ConfigFlowResult async_step_link_manual(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
ConfigFlowResult _async_start_link(self)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_zeroconf(self, zeroconf.ZeroconfServiceInfo discovery_info)
RoombaOptionsFlowHandler async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_manual(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_link(self, dict[str, Any]|None user_input=None)
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")
set[str|None] _async_current_ids(self, bool include_ignore=True)
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[ConfigFlowResult] _async_in_progress(self, bool include_uninitialized=False, dict[str, Any]|None match_context=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=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)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
web.Response get(self, web.Request request, str config_key)
str _async_blid_from_hostname(str hostname)
list[RoombaInfo] _async_discover_roombas(HomeAssistant hass, str|None host=None)
RoombaDiscovery _async_get_roomba_discovery()
dict[str, Any] validate_input(HomeAssistant hass, dict[str, Any] data)
dict[str, Any] async_connect_or_timeout(HomeAssistant hass, Roomba roomba)
None async_disconnect_or_timeout(HomeAssistant hass, Roomba roomba)