1 """Websocket API for OTBR."""
3 from collections.abc
import Callable, Coroutine
4 from functools
import wraps
5 from typing
import TYPE_CHECKING, Any, cast
8 from python_otbr_api
import PENDING_DATASET_DELAY_TIMER, tlv_parser
9 from python_otbr_api.tlv_parser
import MeshcopTLVType
10 import voluptuous
as vol
20 from .const
import DEFAULT_CHANNEL, DOMAIN
23 compose_default_network_name,
24 generate_random_pan_id,
30 from .
import OTBRConfigEntry
35 """Set up the OTBR Websocket API."""
36 websocket_api.async_register_command(hass, websocket_info)
37 websocket_api.async_register_command(hass, websocket_create_network)
38 websocket_api.async_register_command(hass, websocket_set_channel)
39 websocket_api.async_register_command(hass, websocket_set_network)
42 @websocket_api.websocket_command(
{
"type": "otbr/info",
}
)
43 @websocket_api.require_admin
44 @websocket_api.async_response
49 config_entries: list[OTBRConfigEntry]
50 config_entries = hass.config_entries.async_loaded_entries(DOMAIN)
52 if not config_entries:
53 connection.send_error(msg[
"id"],
"not_loaded",
"No OTBR API loaded")
56 response: dict[str, dict[str, Any]] = {}
58 for config_entry
in config_entries:
59 data = config_entry.runtime_data
61 border_agent_id = await data.get_border_agent_id()
62 dataset = await data.get_active_dataset()
63 dataset_tlvs = await data.get_active_dataset_tlvs()
64 extended_address = (await data.get_extended_address()).hex()
65 except HomeAssistantError
as exc:
66 connection.send_error(msg[
"id"],
"otbr_info_failed",
str(exc))
71 assert border_agent_id
is not None
74 dataset.extended_pan_id.lower()
75 if dataset
and dataset.extended_pan_id
78 response[extended_address] = {
79 "active_dataset_tlvs": dataset_tlvs.hex()
if dataset_tlvs
else None,
80 "border_agent_id": border_agent_id.hex(),
81 "channel": dataset.channel
if dataset
else None,
82 "extended_address": extended_address,
83 "extended_pan_id": extended_pan_id,
87 connection.send_result(msg[
"id"], response)
93 Coroutine[Any, Any,
None],
98 """Decorate function to get OTBR data."""
101 async
def async_check_extended_address_func(
104 """Fetch OTBR data and pass to orig_func."""
105 config_entries: list[OTBRConfigEntry]
106 config_entries = hass.config_entries.async_loaded_entries(DOMAIN)
108 if not config_entries:
109 connection.send_error(msg[
"id"],
"not_loaded",
"No OTBR API loaded")
112 for config_entry
in config_entries:
113 data = config_entry.runtime_data
115 extended_address = await data.get_extended_address()
116 except HomeAssistantError
as exc:
117 connection.send_error(
118 msg[
"id"],
"get_extended_address_failed",
str(exc)
121 if extended_address.hex() != msg[
"extended_address"]:
124 await orig_func(hass, connection, msg, data)
127 connection.send_error(msg[
"id"],
"unknown_router",
"")
129 return async_check_extended_address_func
132 @websocket_api.websocket_command(
{
"type": "otbr/create_network",
vol.Required("extended_address"): str,
135 @websocket_api.require_admin
136 @websocket_api.async_response
144 """Create a new Thread network."""
148 await data.set_enabled(
False)
149 except HomeAssistantError
as exc:
150 connection.send_error(msg[
"id"],
"set_enabled_failed",
str(exc))
154 await data.factory_reset(hass)
155 except HomeAssistantError
as exc:
156 connection.send_error(msg[
"id"],
"factory_reset_failed",
str(exc))
161 await data.create_active_dataset(
162 python_otbr_api.ActiveDataSet(
168 except HomeAssistantError
as exc:
169 connection.send_error(msg[
"id"],
"create_active_dataset_failed",
str(exc))
173 await data.set_enabled(
True)
174 except HomeAssistantError
as exc:
175 connection.send_error(msg[
"id"],
"set_enabled_failed",
str(exc))
179 dataset_tlvs = await data.get_active_dataset_tlvs()
180 except HomeAssistantError
as exc:
181 connection.send_error(msg[
"id"],
"get_active_dataset_tlvs_failed",
str(exc))
184 connection.send_error(msg[
"id"],
"get_active_dataset_tlvs_empty",
"")
192 connection.send_result(msg[
"id"])
195 @websocket_api.websocket_command(
{
"type": "otbr/set_network",
vol.Required("extended_address"): str,
196 vol.Required(
"dataset_id"): str,
199 @websocket_api.require_admin
200 @websocket_api.async_response
208 """Set the Thread network to be used by the OTBR."""
212 connection.send_error(msg[
"id"],
"unknown_dataset",
"Unknown dataset")
214 dataset = tlv_parser.parse_tlv(dataset_tlv)
215 if channel := dataset.get(MeshcopTLVType.CHANNEL):
216 thread_dataset_channel = cast(tlv_parser.Channel, channel).channel
220 if allowed_channel
and thread_dataset_channel != allowed_channel:
221 connection.send_error(
224 f
"Can't connect to network on channel {thread_dataset_channel}, ZHA is "
225 f
"using channel {allowed_channel}",
230 await data.set_enabled(
False)
231 except HomeAssistantError
as exc:
232 connection.send_error(msg[
"id"],
"set_enabled_failed",
str(exc))
236 await data.set_active_dataset_tlvs(bytes.fromhex(dataset_tlv))
237 except HomeAssistantError
as exc:
238 connection.send_error(msg[
"id"],
"set_active_dataset_tlvs_failed",
str(exc))
242 await data.set_enabled(
True)
243 except HomeAssistantError
as exc:
244 connection.send_error(msg[
"id"],
"set_enabled_failed",
str(exc))
250 connection.send_result(msg[
"id"])
253 @websocket_api.websocket_command(
{
"type": "otbr/set_channel",
vol.Required("extended_address"): str,
254 vol.Required(
"channel"): int,
257 @websocket_api.require_admin
258 @websocket_api.async_response
266 """Set current channel."""
268 connection.send_error(
270 "multiprotocol_enabled",
271 "Channel change not allowed when in multiprotocol mode",
275 channel: int = msg[
"channel"]
276 delay: float = PENDING_DATASET_DELAY_TIMER / 1000
279 await data.set_channel(channel)
280 except HomeAssistantError
as exc:
281 connection.send_error(msg[
"id"],
"set_channel_failed",
str(exc))
284 connection.send_result(msg[
"id"], {
"delay": delay})
285
bool is_multiprotocol_url(str url)
int|None get_allowed_channel(HomeAssistant hass, str otbr_url)
int generate_random_pan_id()
str compose_default_network_name(int pan_id)
None update_issues(HomeAssistant hass, OTBRData otbrdata, bytes dataset_tlvs)
None websocket_create_network(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, OTBRData data)
None websocket_set_channel(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, OTBRData data)
Callable[[HomeAssistant, websocket_api.ActiveConnection, dict], Coroutine[Any, Any, None]] async_get_otbr_data(Callable[[HomeAssistant, websocket_api.ActiveConnection, dict, OTBRData], Coroutine[Any, Any, None],] orig_func)
None async_setup(HomeAssistant hass)
None websocket_info(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg)
None websocket_set_network(HomeAssistant hass, websocket_api.ActiveConnection connection, dict msg, OTBRData data)
None async_add_dataset(HomeAssistant hass, str source, str tlv, *str|None preferred_border_agent_id=None, str|None preferred_extended_address=None)
str|None async_get_dataset(HomeAssistant hass, str dataset_id)