Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Modem Caller ID integration."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from phone_modem import PhoneModem
8 import serial.tools.list_ports
9 from serial.tools.list_ports_common import ListPortInfo
10 import voluptuous as vol
11 
12 from homeassistant.components import usb
13 from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
14 from homeassistant.const import CONF_DEVICE, CONF_NAME
15 
16 from .const import DEFAULT_NAME, DOMAIN, EXCEPTIONS
17 
18 DATA_SCHEMA = vol.Schema({"name": str, "device": str})
19 
20 
21 def _generate_unique_id(port: ListPortInfo) -> str:
22  """Generate unique id from usb attributes."""
23  return f"{port.vid}:{port.pid}_{port.serial_number}_{port.manufacturer}_{port.description}"
24 
25 
26 class PhoneModemFlowHandler(ConfigFlow, domain=DOMAIN):
27  """Handle a config flow for Phone Modem."""
28 
29  def __init__(self) -> None:
30  """Set up flow instance."""
31  self._device_device: str | None = None
32 
33  async def async_step_usb(
34  self, discovery_info: usb.UsbServiceInfo
35  ) -> ConfigFlowResult:
36  """Handle USB Discovery."""
37  dev_path = discovery_info.device
38  unique_id = f"{discovery_info.vid}:{discovery_info.pid}_{discovery_info.serial_number}_{discovery_info.manufacturer}_{discovery_info.description}"
39  if (
40  await self.validate_device_errorsvalidate_device_errors(dev_path=dev_path, unique_id=unique_id)
41  is None
42  ):
43  self._device_device = dev_path
44  return await self.async_step_usb_confirmasync_step_usb_confirm()
45  return self.async_abortasync_abortasync_abort(reason="cannot_connect")
46 
48  self, user_input: dict[str, Any] | None = None
49  ) -> ConfigFlowResult:
50  """Handle USB Discovery confirmation."""
51  if user_input is not None:
52  return self.async_create_entryasync_create_entryasync_create_entry(
53  title=user_input.get(CONF_NAME, DEFAULT_NAME),
54  data={CONF_DEVICE: self._device_device},
55  )
56  self._set_confirm_only_set_confirm_only()
57  return self.async_show_formasync_show_formasync_show_form(step_id="usb_confirm")
58 
59  async def async_step_user(
60  self, user_input: dict[str, Any] | None = None
61  ) -> ConfigFlowResult:
62  """Handle a flow initiated by the user."""
63  errors: dict[str, str] | None = {}
64  if self._async_in_progress_async_in_progress():
65  return self.async_abortasync_abortasync_abort(reason="already_in_progress")
66  ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
67  existing_devices = [
68  entry.data[CONF_DEVICE] for entry in self._async_current_entries_async_current_entries()
69  ]
70  unused_ports = [
71  usb.human_readable_device_name(
72  port.device,
73  port.serial_number,
74  port.manufacturer,
75  port.description,
76  port.vid,
77  port.pid,
78  )
79  for port in ports
80  if port.device not in existing_devices
81  ]
82  if not unused_ports:
83  return self.async_abortasync_abortasync_abort(reason="no_devices_found")
84 
85  if user_input is not None:
86  port = ports[unused_ports.index(str(user_input.get(CONF_DEVICE)))]
87  dev_path = await self.hass.async_add_executor_job(
88  usb.get_serial_by_id, port.device
89  )
90  errors = await self.validate_device_errorsvalidate_device_errors(
91  dev_path=dev_path, unique_id=_generate_unique_id(port)
92  )
93  if errors is None:
94  return self.async_create_entryasync_create_entryasync_create_entry(
95  title=user_input.get(CONF_NAME, DEFAULT_NAME),
96  data={CONF_DEVICE: dev_path},
97  )
98  user_input = user_input or {}
99  schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(unused_ports)})
100  return self.async_show_formasync_show_formasync_show_form(step_id="user", data_schema=schema, errors=errors)
101 
103  self, dev_path: str, unique_id: str
104  ) -> dict[str, str] | None:
105  """Handle common flow input validation."""
106  self._async_abort_entries_match_async_abort_entries_match({CONF_DEVICE: dev_path})
107  await self.async_set_unique_idasync_set_unique_id(unique_id)
108  self._abort_if_unique_id_configured_abort_if_unique_id_configured(updates={CONF_DEVICE: dev_path})
109  try:
110  api = PhoneModem()
111  await api.test(dev_path)
112  except EXCEPTIONS:
113  return {"base": "cannot_connect"}
114 
115  return None
ConfigFlowResult async_step_usb(self, usb.UsbServiceInfo discovery_info)
Definition: config_flow.py:35
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:61
ConfigFlowResult async_step_usb_confirm(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:49
dict[str, str]|None validate_device_errors(self, str dev_path, str unique_id)
Definition: config_flow.py:104
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)
list[ConfigEntry] _async_current_entries(self, bool|None include_ignore=None)
list[ConfigFlowResult] _async_in_progress(self, bool include_uninitialized=False, dict[str, Any]|None match_context=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)
str
_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)