Home Assistant Unofficial Reference 2024.12.1
websocket_api.py
Go to the documentation of this file.
1 """The thread websocket API."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from python_otbr_api.tlv_parser import TLVError
8 import voluptuous as vol
9 
10 from homeassistant.components import websocket_api
11 from homeassistant.core import HomeAssistant, callback
12 
13 from . import dataset_store, discovery
14 
15 
16 @callback
17 def async_setup(hass: HomeAssistant) -> None:
18  """Set up the sensor websocket API."""
19  websocket_api.async_register_command(hass, ws_add_dataset)
20  websocket_api.async_register_command(hass, ws_delete_dataset)
21  websocket_api.async_register_command(hass, ws_discover_routers)
22  websocket_api.async_register_command(hass, ws_get_dataset)
23  websocket_api.async_register_command(hass, ws_list_datasets)
24  websocket_api.async_register_command(hass, ws_set_preferred_border_agent)
25  websocket_api.async_register_command(hass, ws_set_preferred_dataset)
26 
27 
28 @websocket_api.require_admin
29 @websocket_api.websocket_command( { vol.Required("type"): "thread/add_dataset_tlv",
30  vol.Required("source"): str,
31  vol.Required("tlv"): str,
32  }
33 )
34 @websocket_api.async_response
35 async def ws_add_dataset(
36  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
37 ) -> None:
38  """Add a thread dataset."""
39  source = msg["source"]
40  tlv = msg["tlv"]
41 
42  try:
43  await dataset_store.async_add_dataset(hass, source, tlv)
44  except TLVError as exc:
45  connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(exc))
46  return
47 
48  connection.send_result(msg["id"])
49 
50 
51 @websocket_api.require_admin
52 @websocket_api.websocket_command( { vol.Required("type"): "thread/set_preferred_border_agent",
53  vol.Required("dataset_id"): str,
54  vol.Required("border_agent_id"): vol.Any(str, None),
55  vol.Required("extended_address"): str,
56  }
57 )
58 @websocket_api.async_response
60  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
61 ) -> None:
62  """Set the preferred border agent's border agent ID and extended address."""
63  dataset_id = msg["dataset_id"]
64  border_agent_id = msg["border_agent_id"]
65  extended_address = msg["extended_address"]
66  store = await dataset_store.async_get_store(hass)
67  store.async_set_preferred_border_agent(
68  dataset_id, border_agent_id, extended_address
69  )
70  connection.send_result(msg["id"])
71 
72 
73 @websocket_api.require_admin
74 @websocket_api.websocket_command( { vol.Required("type"): "thread/set_preferred_dataset",
75  vol.Required("dataset_id"): str,
76  }
77 )
78 @websocket_api.async_response
79 async def ws_set_preferred_dataset(
80  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
81 ) -> None:
82  """Add a thread dataset."""
83  dataset_id = msg["dataset_id"]
84 
85  store = await dataset_store.async_get_store(hass)
86  try:
87  store.preferred_dataset = dataset_id
88  except KeyError:
89  connection.send_error(msg["id"], websocket_api.ERR_NOT_FOUND, "unknown dataset")
90  return
91 
92  connection.send_result(msg["id"])
93 
94 
95 @websocket_api.require_admin
96 @websocket_api.websocket_command( { vol.Required("type"): "thread/delete_dataset",
97  vol.Required("dataset_id"): str,
98  }
99 )
100 @websocket_api.async_response
101 async def ws_delete_dataset(
102  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
103 ) -> None:
104  """Delete a thread dataset."""
105  dataset_id = msg["dataset_id"]
106 
107  store = await dataset_store.async_get_store(hass)
108  try:
109  store.async_delete(dataset_id)
110  except KeyError as exc:
111  connection.send_error(msg["id"], websocket_api.ERR_NOT_FOUND, str(exc))
112  return
114  connection.send_error(msg["id"], websocket_api.ERR_NOT_ALLOWED, str(exc))
115  return
116 
117  connection.send_result(msg["id"])
118 
119 
120 @websocket_api.require_admin
121 @websocket_api.websocket_command( { vol.Required("type"): "thread/get_dataset_tlv",
122  vol.Required("dataset_id"): str,
123  }
124 )
125 @websocket_api.async_response
126 async def ws_get_dataset(
127  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
128 ) -> None:
129  """Get a thread dataset in TLV format."""
130  dataset_id = msg["dataset_id"]
131 
132  store = await dataset_store.async_get_store(hass)
133  if not (dataset := store.async_get(dataset_id)):
134  connection.send_error(msg["id"], websocket_api.ERR_NOT_FOUND, "unknown dataset")
135  return
136 
137  connection.send_result(msg["id"], {"tlv": dataset.tlv})
138 
139 
140 @websocket_api.require_admin
141 @websocket_api.websocket_command( { vol.Required("type"): "thread/list_datasets",
142  }
143 )
144 @websocket_api.async_response
145 async def ws_list_datasets(
146  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
147 ) -> None:
148  """Get a list of thread datasets."""
149 
150  store = await dataset_store.async_get_store(hass)
151  preferred_dataset = store.preferred_dataset
152  result = [
153  {
154  "channel": dataset.channel,
155  "created": dataset.created,
156  "dataset_id": dataset.id,
157  "extended_pan_id": dataset.extended_pan_id,
158  "network_name": dataset.network_name,
159  "pan_id": dataset.pan_id,
160  "preferred": dataset.id == preferred_dataset,
161  "preferred_border_agent_id": dataset.preferred_border_agent_id,
162  "preferred_extended_address": dataset.preferred_extended_address,
163  "source": dataset.source,
164  }
165  for dataset in store.datasets.values()
166  ]
167 
168  connection.send_result(msg["id"], {"datasets": result})
169 
170 
171 @websocket_api.require_admin
172 @websocket_api.websocket_command( { vol.Required("type"): "thread/discover_routers",
173  }
174 )
175 @websocket_api.async_response
176 async def ws_discover_routers(
177  hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
178 ) -> None:
179  """Discover Thread routers."""
180 
181  @callback
182  def router_discovered(key: str, data: discovery.ThreadRouterDiscoveryData) -> None:
183  """Forward router discovery or update to websocket."""
184 
185  connection.send_message(
186  websocket_api.event_message(
187  msg["id"],
188  {
189  "type": "router_discovered",
190  "key": key,
191  "data": data,
192  },
193  )
194  )
195 
196  @callback
197  def router_removed(key: str) -> None:
198  """Forward router removed to websocket."""
199 
200  connection.send_message(
201  websocket_api.event_message(
202  msg["id"],
203  {
204  "type": "router_removed",
205  "key": key,
206  },
207  )
208  )
209 
210  @callback
211  def stop_discovery() -> None:
212  """Stop discovery."""
213  hass.async_create_task(thread_discovery.async_stop())
214 
215  # Start Thread router discovery
216  thread_discovery = discovery.ThreadRouterDiscovery(
217  hass, router_discovered, router_removed
218  )
219  await thread_discovery.async_start()
220  connection.subscriptions[msg["id"]] = stop_discovery
221 
222  connection.send_message(websocket_api.result_message(msg["id"]))
223 
None ws_set_preferred_dataset(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_discover_routers(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_delete_dataset(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_add_dataset(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_get_dataset(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_list_datasets(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
None ws_set_preferred_border_agent(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)