1 """Config flow for the Open Thread Border Router integration."""
3 from __future__
import annotations
5 from contextlib
import suppress
7 from typing
import TYPE_CHECKING, cast
10 import python_otbr_api
11 from python_otbr_api
import tlv_parser
12 from python_otbr_api.tlv_parser
import MeshcopTLVType
13 import voluptuous
as vol
26 from .const
import DEFAULT_CHANNEL, DOMAIN
28 compose_default_network_name,
29 generate_random_pan_id,
34 from .
import OTBRConfigEntry
36 _LOGGER = logging.getLogger(__name__)
40 """Raised when the router is already configured."""
45 """Get the add-on manager."""
46 return AddonManager(hass, _LOGGER,
"OpenThread Border Router", slug)
50 """Return True if Home Assistant is running on a Home Assistant Yellow."""
52 yellow_hardware.async_info(hass)
53 except HomeAssistantError:
58 async
def _title(hass: HomeAssistant, discovery_info: HassioServiceInfo) -> str:
59 """Return config entry title."""
60 device: str |
None =
None
63 with suppress(AddonError):
64 addon_info = await addon_manager.async_get_addon_info()
65 device = addon_info.options.get(
"device")
67 if _is_yellow(hass)
and device ==
"/dev/ttyAMA1":
68 return f
"Home Assistant Yellow ({discovery_info.name})"
70 if device
and "SkyConnect" in device:
71 return f
"Home Assistant SkyConnect ({discovery_info.name})"
73 if device
and "Connect_ZBT-1" in device:
74 return f
"Home Assistant Connect ZBT-1 ({discovery_info.name})"
76 return discovery_info.name
80 """Handle a config flow for Open Thread Border Router."""
84 async
def _set_dataset(self, api: python_otbr_api.OTBR, otbr_url: str) ->
None:
85 """Connect to the OTBR and create or apply a dataset if it doesn't have one."""
86 if await api.get_active_dataset_tlvs()
is None:
89 thread_dataset_channel =
None
91 if thread_dataset_tlv:
92 dataset = tlv_parser.parse_tlv(thread_dataset_tlv)
93 if channel := dataset.get(MeshcopTLVType.CHANNEL):
94 thread_dataset_channel = cast(tlv_parser.Channel, channel).channel
96 if thread_dataset_tlv
is not None and (
97 not allowed_channel
or allowed_channel == thread_dataset_channel
99 await api.set_active_dataset_tlvs(bytes.fromhex(thread_dataset_tlv))
102 "not importing TLV with channel %s for %s",
103 thread_dataset_channel,
107 await api.create_active_dataset(
108 python_otbr_api.ActiveDataSet(
109 channel=allowed_channel
if allowed_channel
else DEFAULT_CHANNEL,
114 await api.set_enabled(
True)
117 """Return True if another config entry's OTBR has the same border agent id."""
118 config_entry: OTBRConfigEntry
119 for config_entry
in self.hass.config_entries.async_loaded_entries(DOMAIN):
120 data = config_entry.runtime_data
122 other_border_agent_id = await data.get_border_agent_id()
123 except HomeAssistantError:
125 "Could not read border agent id from %s", data.url, exc_info=
True
129 "border agent id for existing url %s: %s",
131 other_border_agent_id.hex(),
133 if border_agent_id == other_border_agent_id:
138 """Connect to the router and configure it if needed.
140 Will raise if the router's border agent id is in use by another config entry.
141 Returns the router's border agent id.
144 border_agent_id = await api.get_border_agent_id()
145 _LOGGER.debug(
"border agent id for url %s: %s", otbr_url, border_agent_id.hex())
148 raise AlreadyConfigured
152 return border_agent_id
155 self, user_input: dict[str, str] |
None =
None
156 ) -> ConfigFlowResult:
157 """Set up by user."""
160 if user_input
is not None:
161 url = user_input[CONF_URL].rstrip(
"/")
164 except AlreadyConfigured:
165 errors[
"base"] =
"already_configured"
167 python_otbr_api.OTBRError,
171 _LOGGER.debug(
"Failed to communicate with OTBR@%s: %s", url, exc)
172 errors[
"base"] =
"cannot_connect"
176 title=
"Open Thread Border Router",
177 data={CONF_URL: url},
180 data_schema = vol.Schema({CONF_URL: str})
182 step_id=
"user", data_schema=data_schema, errors=errors
186 self, discovery_info: HassioServiceInfo
187 ) -> ConfigFlowResult:
188 """Handle hassio discovery."""
189 config = discovery_info.config
190 url = f
"http://{config['host']}:{config['port']}"
191 config_entry_data = {
"url": url}
194 for current_entry
in current_entries:
195 if current_entry.source != SOURCE_HASSIO:
197 current_url = yarl.URL(current_entry.data[
"url"])
198 if not (unique_id := current_entry.unique_id):
203 unique_id = discovery_info.uuid
205 unique_id != discovery_info.uuid
206 or current_url.host != config[
"host"]
207 or current_url.port == config[
"port"]
211 self.hass.config_entries.async_update_entry(
213 data=config_entry_data,
220 except AlreadyConfigured:
223 python_otbr_api.OTBRError,
227 _LOGGER.warning(
"Failed to communicate with OTBR@%s: %s", url, exc)
232 title=await
_title(self.hass, discovery_info),
233 data=config_entry_data,
ConfigFlowResult async_step_hassio(self, HassioServiceInfo discovery_info)
bytes _connect_and_configure_router(self, str otbr_url)
bool _is_border_agent_id_configured(self, bytes border_agent_id)
ConfigFlowResult async_step_user(self, dict[str, str]|None user_input=None)
None _set_dataset(self, python_otbr_api.OTBR api, str otbr_url)
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)
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)
AddonManager get_addon_manager(HomeAssistant hass, str slug)
str _title(HomeAssistant hass, HassioServiceInfo discovery_info)
bool _is_yellow(HomeAssistant hass)
int|None get_allowed_channel(HomeAssistant hass, str otbr_url)
int generate_random_pan_id()
str compose_default_network_name(int pan_id)
str|None async_get_preferred_dataset(HomeAssistant hass)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)