Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for the Home Assistant SkyConnect integration."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import TYPE_CHECKING, Any, Protocol
7 
8 from universal_silabs_flasher.const import ApplicationType
9 
10 from homeassistant.components import usb
12  firmware_config_flow,
13  silabs_multiprotocol_addon,
14 )
15 from homeassistant.config_entries import (
16  ConfigEntry,
17  ConfigEntryBaseFlow,
18  ConfigFlowContext,
19  ConfigFlowResult,
20  OptionsFlow,
21 )
22 from homeassistant.core import callback
23 
24 from .const import DOCS_WEB_FLASHER_URL, DOMAIN, HardwareVariant
25 from .util import get_hardware_variant, get_usb_service_info
26 
27 _LOGGER = logging.getLogger(__name__)
28 
29 
30 if TYPE_CHECKING:
31 
33  """Protocol describing `BaseFirmwareInstallFlow`'s translation placeholders."""
34 
35  def _get_translation_placeholders(self) -> dict[str, str]:
36  return {}
37 else:
38  # Multiple inheritance with `Protocol` seems to break
39  TranslationPlaceholderProtocol = object
40 
41 
43  """Translation placeholder mixin for Home Assistant SkyConnect."""
44 
45  context: ConfigFlowContext
46 
47  def _get_translation_placeholders(self) -> dict[str, str]:
48  """Shared translation placeholders."""
49  placeholders = {
51  "docs_web_flasher_url": DOCS_WEB_FLASHER_URL,
52  }
53 
54  self.context["title_placeholders"] = placeholders
55 
56  return placeholders
57 
58 
60  SkyConnectTranslationMixin,
61  firmware_config_flow.BaseFirmwareConfigFlow,
62  domain=DOMAIN,
63 ):
64  """Handle a config flow for Home Assistant SkyConnect."""
65 
66  VERSION = 1
67  MINOR_VERSION = 2
68 
69  def __init__(self, *args: Any, **kwargs: Any) -> None:
70  """Initialize the config flow."""
71  super().__init__(*args, **kwargs)
72 
73  self._usb_info_usb_info: usb.UsbServiceInfo | None = None
74  self._hw_variant_hw_variant: HardwareVariant | None = None
75 
76  @staticmethod
77  @callback
79  config_entry: ConfigEntry,
80  ) -> OptionsFlow:
81  """Return the options flow."""
82  firmware_type = ApplicationType(config_entry.data["firmware"])
83 
84  if firmware_type is ApplicationType.CPC:
86 
88 
89  async def async_step_usb(
90  self, discovery_info: usb.UsbServiceInfo
91  ) -> ConfigFlowResult:
92  """Handle usb discovery."""
93  device = discovery_info.device
94  vid = discovery_info.vid
95  pid = discovery_info.pid
96  serial_number = discovery_info.serial_number
97  manufacturer = discovery_info.manufacturer
98  description = discovery_info.description
99  unique_id = f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
100 
101  if await self.async_set_unique_id(unique_id):
102  self._abort_if_unique_id_configured(updates={"device": device})
103 
104  discovery_info.device = await self.hass.async_add_executor_job(
105  usb.get_serial_by_id, discovery_info.device
106  )
107 
108  self._usb_info_usb_info = discovery_info
109 
110  assert description is not None
111  self._hw_variant_hw_variant = HardwareVariant.from_usb_product_name(description)
112 
113  # Set parent class attributes
114  self._device_device = self._usb_info_usb_info.device
115  self._hardware_name_hardware_name = self._hw_variant_hw_variant.full_name
116 
117  return await self.async_step_confirm()
118 
119  def _async_flow_finished(self) -> ConfigFlowResult:
120  """Create the config entry."""
121  assert self._usb_info_usb_info is not None
122  assert self._hw_variant_hw_variant is not None
123  assert self._probed_firmware_type is not None
124 
125  return self.async_create_entryasync_create_entry(
126  title=self._hw_variant_hw_variant.full_name,
127  data={
128  "vid": self._usb_info_usb_info.vid,
129  "pid": self._usb_info_usb_info.pid,
130  "serial_number": self._usb_info_usb_info.serial_number,
131  "manufacturer": self._usb_info_usb_info.manufacturer,
132  "description": self._usb_info_usb_info.description, # For backwards compatibility
133  "product": self._usb_info_usb_info.description,
134  "device": self._usb_info_usb_info.device,
135  "firmware": self._probed_firmware_type.value,
136  },
137  )
138 
139 
141  silabs_multiprotocol_addon.OptionsFlowHandler
142 ):
143  """Multi-PAN options flow for Home Assistant SkyConnect."""
144 
146  self,
147  ) -> silabs_multiprotocol_addon.SerialPortSettings:
148  """Return the radio serial port settings."""
149  usb_dev = self.config_entry.data["device"]
150  # The call to get_serial_by_id can be removed in HA Core 2024.1
151  dev_path = await self.hass.async_add_executor_job(usb.get_serial_by_id, usb_dev)
152  return silabs_multiprotocol_addon.SerialPortSettings(
153  device=dev_path,
154  baudrate="115200",
155  flow_control=True,
156  )
157 
158  async def _async_zha_physical_discovery(self) -> dict[str, Any]:
159  """Return ZHA discovery data when multiprotocol FW is not used.
160 
161  Passed to ZHA do determine if the ZHA config entry is connected to the radio
162  being migrated.
163  """
164  return {"usb": get_usb_service_info(self.config_entry)}
165 
166  @property
167  def _hw_variant(self) -> HardwareVariant:
168  """Return the hardware variant."""
169  return get_hardware_variant(self.config_entry)
170 
171  def _zha_name(self) -> str:
172  """Return the ZHA name."""
173  return f"{self._hw_variant.short_name} Multiprotocol"
174 
175  def _hardware_name(self) -> str:
176  """Return the name of the hardware."""
177  return self._hw_variant_hw_variant.full_name
178 
180  self, user_input: dict[str, Any] | None = None
181  ) -> ConfigFlowResult:
182  """Finish flashing and update the config entry."""
183  self.hass.config_entries.async_update_entry(
184  entry=self.config_entry,
185  data={
186  **self.config_entry.data,
187  "firmware": ApplicationType.EZSP.value,
188  },
189  options=self.config_entry.options,
190  )
191 
192  return await super().async_step_flashing_complete(user_input)
193 
194 
196  SkyConnectTranslationMixin, firmware_config_flow.BaseFirmwareOptionsFlow
197 ):
198  """Zigbee and Thread options flow handlers."""
199 
200  def __init__(self, *args: Any, **kwargs: Any) -> None:
201  """Instantiate options flow."""
202  super().__init__(*args, **kwargs)
203 
204  self._usb_info_usb_info = get_usb_service_info(self.config_entry)
205  self._hw_variant_hw_variant = HardwareVariant.from_usb_product_name(
206  self.config_entry.data["product"]
207  )
208  self._hardware_name_hardware_name = self._hw_variant_hw_variant.full_name
209  self._device_device = self._usb_info_usb_info.device
210 
211  # Regenerate the translation placeholders
212  self._get_translation_placeholders_get_translation_placeholders_get_translation_placeholders()
213 
214  def _async_flow_finished(self) -> ConfigFlowResult:
215  """Create the config entry."""
216  assert self._probed_firmware_type is not None
217 
218  self.hass.config_entries.async_update_entry(
219  entry=self.config_entry,
220  data={
221  **self.config_entry.data,
222  "firmware": self._probed_firmware_type.value,
223  },
224  options=self.config_entry.options,
225  )
226 
227  return self.async_create_entryasync_create_entry(title="", data={})
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
HardwareVariant get_hardware_variant(ConfigEntry config_entry)
Definition: util.py:27
usb.UsbServiceInfo get_usb_service_info(ConfigEntry config_entry)
Definition: util.py:15