Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Husqvarna Bluetooth integration."""
2 
3 from __future__ import annotations
4 
5 import random
6 from typing import Any
7 
8 from automower_ble.mower import Mower
9 from bleak import BleakError
10 import voluptuous as vol
11 
12 from homeassistant.components import bluetooth
13 from homeassistant.components.bluetooth import BluetoothServiceInfo
14 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
15 from homeassistant.const import CONF_ADDRESS, CONF_CLIENT_ID
16 
17 from .const import DOMAIN, LOGGER
18 
19 
20 def _is_supported(discovery_info: BluetoothServiceInfo):
21  """Check if device is supported."""
22 
23  LOGGER.debug(
24  "%s manufacturer data: %s",
25  discovery_info.address,
26  discovery_info.manufacturer_data,
27  )
28 
29  manufacturer = any(key == 1062 for key in discovery_info.manufacturer_data)
30  service_husqvarna = any(
31  service == "98bd0001-0b0e-421a-84e5-ddbf75dc6de4"
32  for service in discovery_info.service_uuids
33  )
34  service_generic = any(
35  service == "00001800-0000-1000-8000-00805f9b34fb"
36  for service in discovery_info.service_uuids
37  )
38 
39  return manufacturer and service_husqvarna and service_generic
40 
41 
43  """Handle a config flow for Husqvarna Bluetooth."""
44 
45  VERSION = 1
46 
47  def __init__(self) -> None:
48  """Initialize the config flow."""
49  self.addressaddress: str | None
50 
52  self, discovery_info: BluetoothServiceInfo
53  ) -> ConfigFlowResult:
54  """Handle the bluetooth discovery step."""
55 
56  LOGGER.debug("Discovered device: %s", discovery_info)
57  if not _is_supported(discovery_info):
58  return self.async_abortasync_abortasync_abort(reason="no_devices_found")
59 
60  self.addressaddress = discovery_info.address
61  await self.async_set_unique_idasync_set_unique_id(self.addressaddress)
62  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
63  return await self.async_step_confirmasync_step_confirm()
64 
65  async def async_step_confirm(
66  self, user_input: dict[str, Any] | None = None
67  ) -> ConfigFlowResult:
68  """Confirm discovery."""
69  assert self.addressaddress
70 
71  device = bluetooth.async_ble_device_from_address(
72  self.hass, self.addressaddress, connectable=True
73  )
74  channel_id = random.randint(1, 0xFFFFFFFF)
75 
76  try:
77  (manufacturer, device_type, model) = await Mower(
78  channel_id, self.addressaddress
79  ).probe_gatts(device)
80  except (BleakError, TimeoutError) as exception:
81  LOGGER.exception("Failed to connect to device: %s", exception)
82  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
83 
84  title = manufacturer + " " + device_type
85 
86  LOGGER.debug("Found device: %s", title)
87 
88  if user_input is not None:
89  return self.async_create_entryasync_create_entryasync_create_entry(
90  title=title,
91  data={CONF_ADDRESS: self.addressaddress, CONF_CLIENT_ID: channel_id},
92  )
93 
94  self.context["title_placeholders"] = {
95  "name": title,
96  }
97 
98  self._set_confirm_only_set_confirm_only()
99  return self.async_show_formasync_show_formasync_show_form(
100  step_id="confirm",
101  description_placeholders=self.context["title_placeholders"],
102  )
103 
104  async def async_step_user(
105  self, user_input: dict[str, Any] | None = None
106  ) -> ConfigFlowResult:
107  """Handle the initial step."""
108  if user_input is not None:
109  self.addressaddress = user_input[CONF_ADDRESS]
110  await self.async_set_unique_idasync_set_unique_id(self.addressaddress, raise_on_progress=False)
111  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
112  return await self.async_step_confirmasync_step_confirm()
113 
114  return self.async_show_formasync_show_formasync_show_form(
115  step_id="user",
116  data_schema=vol.Schema(
117  {
118  vol.Required(CONF_ADDRESS): str,
119  },
120  ),
121  )
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:106
ConfigFlowResult async_step_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:67
ConfigFlowResult async_step_bluetooth(self, BluetoothServiceInfo discovery_info)
Definition: config_flow.py:53
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_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_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)
def _is_supported(BluetoothServiceInfo discovery_info)
Definition: config_flow.py:20