Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Medcom BlE integration."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 
8 from bleak import BleakError
9 from bluetooth_data_tools import human_readable_name
10 from medcom_ble import MedcomBleDevice, MedcomBleDeviceData
11 from medcom_ble.const import INSPECTOR_SERVICE_UUID
12 import voluptuous as vol
13 
14 from homeassistant.components import bluetooth
16  BluetoothServiceInfo,
17  async_discovered_service_info,
18 )
19 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
20 from homeassistant.const import CONF_ADDRESS
21 from homeassistant.data_entry_flow import AbortFlow
22 
23 from .const import DOMAIN
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 
28 class InspectorBLEConfigFlow(ConfigFlow, domain=DOMAIN):
29  """Handle a config flow for Medcom BLE radiation monitors."""
30 
31  VERSION = 1
32 
33  def __init__(self) -> None:
34  """Initialize the config flow."""
35  self._discovery_info_discovery_info: BluetoothServiceInfo | None = None
36  self._discovered_devices: dict[str, BluetoothServiceInfo] = {}
37 
38  async def _get_device_data(
39  self, service_info: BluetoothServiceInfo
40  ) -> MedcomBleDevice:
41  ble_device = bluetooth.async_ble_device_from_address(
42  self.hass, service_info.address
43  )
44  if ble_device is None:
45  _LOGGER.debug("no ble_device in _get_device_data")
46  raise AbortFlow("cannot_connect")
47 
48  inspector = MedcomBleDeviceData(_LOGGER)
49 
50  return await inspector.update_device(ble_device)
51 
53  self, discovery_info: BluetoothServiceInfo
54  ) -> ConfigFlowResult:
55  """Handle the bluetooth discovery step."""
56  _LOGGER.debug("Discovered BLE device: %s", discovery_info.name)
57  await self.async_set_unique_idasync_set_unique_id(discovery_info.address)
58  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
59  self._discovery_info_discovery_info = discovery_info
60  self.context["title_placeholders"] = {
61  "name": human_readable_name(
62  None, discovery_info.name, discovery_info.address
63  )
64  }
65 
66  return await self.async_step_bluetooth_confirmasync_step_bluetooth_confirm()
67 
69  self, user_input: dict[str, Any] | None = None
70  ) -> ConfigFlowResult:
71  """Confirm discovery."""
72  # We always will have self._discovery_info be a BluetoothServiceInfo at this point
73  # and this helps mypy not complain
74  assert self._discovery_info_discovery_info is not None
75 
76  if user_input is None:
77  name = self._discovery_info_discovery_info.name or self._discovery_info_discovery_info.address
78  return self.async_show_formasync_show_formasync_show_form(
79  step_id="bluetooth_confirm",
80  description_placeholders={"name": name},
81  )
82 
83  return await self.async_step_check_connectionasync_step_check_connection()
84 
85  async def async_step_user(
86  self, user_input: dict[str, Any] | None = None
87  ) -> ConfigFlowResult:
88  """Handle the user step to pick discovered device."""
89  if user_input is not None:
90  address = user_input[CONF_ADDRESS]
91  await self.async_set_unique_idasync_set_unique_id(address, raise_on_progress=False)
92  self._abort_if_unique_id_configured_abort_if_unique_id_configured()
93  self._discovery_info_discovery_info = self._discovered_devices[address]
94  return await self.async_step_check_connectionasync_step_check_connection()
95 
96  current_addresses = self._async_current_ids_async_current_ids()
97  for discovery_info in async_discovered_service_info(self.hass):
98  address = discovery_info.address
99  if address in current_addresses or address in self._discovered_devices:
100  _LOGGER.debug(
101  "Detected a device that's already configured: %s", address
102  )
103  continue
104 
105  if INSPECTOR_SERVICE_UUID not in discovery_info.service_uuids:
106  continue
107 
108  self._discovered_devices[discovery_info.address] = discovery_info
109 
110  if not self._discovered_devices:
111  return self.async_abortasync_abortasync_abort(reason="no_devices_found")
112 
113  titles = {
114  address: discovery.name
115  for address, discovery in self._discovered_devices.items()
116  }
117  return self.async_show_formasync_show_formasync_show_form(
118  step_id="user",
119  data_schema=vol.Schema(
120  {
121  vol.Required(CONF_ADDRESS): vol.In(titles),
122  },
123  ),
124  )
125 
126  async def async_step_check_connection(self) -> ConfigFlowResult:
127  """Check we can connect to the device before considering the configuration is successful."""
128  # We always will have self._discovery_info be a BluetoothServiceInfo at this point
129  # and this helps mypy not complain
130  assert self._discovery_info_discovery_info is not None
131 
132  _LOGGER.debug("Checking device connection: %s", self._discovery_info_discovery_info.name)
133  try:
134  await self._get_device_data_get_device_data(self._discovery_info_discovery_info)
135  except BleakError:
136  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
137  except AbortFlow:
138  raise
139  except Exception:
140  _LOGGER.exception(
141  "Error occurred reading information from %s",
142  self._discovery_info_discovery_info.address,
143  )
144  return self.async_abortasync_abortasync_abort(reason="unknown")
145  _LOGGER.debug("Device connection successful, proceeding")
146  return self.async_create_entryasync_create_entryasync_create_entry(title=self._discovery_info_discovery_info.name, data={})
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:87
MedcomBleDevice _get_device_data(self, BluetoothServiceInfo service_info)
Definition: config_flow.py:40
ConfigFlowResult async_step_bluetooth(self, BluetoothServiceInfo discovery_info)
Definition: config_flow.py:54
ConfigFlowResult async_step_bluetooth_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:70
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)
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)
Iterable[BluetoothServiceInfoBleak] async_discovered_service_info(HomeAssistant hass, bool connectable=True)
Definition: api.py:72
str human_readable_name(str hostname, str vendor, str mac_address)
Definition: __init__.py:50