1 """Handle websocket api for Matter."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Coroutine
6 from functools
import wraps
7 from typing
import Any, Concatenate
9 from matter_server.client.models.node
import MatterNode
10 from matter_server.common.errors
import MatterError
11 from matter_server.common.helpers.util
import dataclass_to_dict
12 import voluptuous
as vol
18 from .adapter
import MatterAdapter
19 from .helpers
import MissingNode, get_matter, node_from_ha_device_id
23 DEVICE_ID =
"device_id"
26 ERROR_NODE_NOT_FOUND =
"node_not_found"
31 """Register all of our api endpoints."""
32 websocket_api.async_register_command(hass, websocket_commission)
33 websocket_api.async_register_command(hass, websocket_commission_on_network)
34 websocket_api.async_register_command(hass, websocket_set_thread_dataset)
35 websocket_api.async_register_command(hass, websocket_set_wifi_credentials)
36 websocket_api.async_register_command(hass, websocket_node_diagnostics)
37 websocket_api.async_register_command(hass, websocket_ping_node)
38 websocket_api.async_register_command(hass, websocket_open_commissioning_window)
39 websocket_api.async_register_command(hass, websocket_remove_matter_fabric)
40 websocket_api.async_register_command(hass, websocket_interview_node)
45 [HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter, MatterNode],
46 Coroutine[Any, Any,
None],
49 [HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter],
50 Coroutine[Any, Any,
None],
52 """Decorate async function to get node."""
55 async
def async_get_node_func(
57 connection: ActiveConnection,
59 matter: MatterAdapter,
61 """Provide user specific data and store to function."""
65 f
"Could not resolve Matter node from device id {msg[DEVICE_ID]}"
67 await func(hass, connection, msg, matter, node)
69 return async_get_node_func
74 [HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter],
75 Coroutine[Any, Any,
None],
78 [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any,
None]
80 """Decorate function to get the MatterAdapter."""
83 async
def _get_matter(
84 hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
86 """Provide the Matter client to the function."""
89 await func(hass, connection, msg, matter)
94 def async_handle_failed_command[**_P](
96 Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P],
97 Coroutine[Any, Any,
None],
100 Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P],
101 Coroutine[Any, Any,
None],
103 """Decorate function to handle MatterError and send relevant error."""
106 async
def async_handle_failed_command_func(
108 connection: ActiveConnection,
113 """Handle MatterError within function and send relevant error."""
115 await func(hass, connection, msg, *args, **kwargs)
116 except MatterError
as err:
117 connection.send_error(msg[ID],
str(err.error_code), err.args[0])
118 except MissingNode
as err:
119 connection.send_error(msg[ID], ERROR_NODE_NOT_FOUND, err.args[0])
121 return async_handle_failed_command_func
124 @websocket_api.require_admin
125 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/commission",
126 vol.Required(
"code"): str,
127 vol.Optional(
"network_only"): bool,
130 @websocket_api.async_response
131 @async_handle_failed_command
132 @async_get_matter_adapter
135 connection: ActiveConnection,
137 matter: MatterAdapter,
139 """Add a device to the network and commission the device."""
140 await matter.matter_client.commission_with_code(
141 msg[
"code"], network_only=msg.get(
"network_only",
True)
143 connection.send_result(msg[ID])
146 @websocket_api.require_admin
147 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/commission_on_network",
148 vol.Required(
"pin"): int,
149 vol.Optional(
"ip_addr"): str,
152 @websocket_api.async_response
153 @async_handle_failed_command
154 @async_get_matter_adapter
157 connection: ActiveConnection,
159 matter: MatterAdapter,
161 """Commission a device already on the network."""
162 await matter.matter_client.commission_on_network(
163 msg[
"pin"], ip_addr=msg.get(
"ip_addr")
165 connection.send_result(msg[ID])
168 @websocket_api.require_admin
169 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/set_thread",
170 vol.Required(
"thread_operation_dataset"): str,
173 @websocket_api.async_response
174 @async_handle_failed_command
175 @async_get_matter_adapter
178 connection: ActiveConnection,
180 matter: MatterAdapter,
182 """Set thread dataset."""
183 await matter.matter_client.set_thread_operational_dataset(
184 msg[
"thread_operation_dataset"]
186 connection.send_result(msg[ID])
189 @websocket_api.require_admin
190 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/set_wifi_credentials",
191 vol.Required(
"network_name"): str,
192 vol.Required(
"password"): str,
195 @websocket_api.async_response
196 @async_handle_failed_command
197 @async_get_matter_adapter
200 connection: ActiveConnection,
202 matter: MatterAdapter,
204 """Set WiFi credentials for a device."""
205 await matter.matter_client.set_wifi_credentials(
206 ssid=msg[
"network_name"], credentials=msg[
"password"]
208 connection.send_result(msg[ID])
211 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/node_diagnostics",
212 vol.Required(DEVICE_ID): str,
215 @websocket_api.async_response
216 @async_handle_failed_command
217 @async_get_matter_adapter
221 connection: ActiveConnection,
223 matter: MatterAdapter,
226 """Gather diagnostics for the given node."""
227 result = await matter.matter_client.node_diagnostics(node_id=node.node_id)
228 connection.send_result(msg[ID], dataclass_to_dict(result))
231 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/ping_node",
232 vol.Required(DEVICE_ID): str,
235 @websocket_api.async_response
236 @async_handle_failed_command
237 @async_get_matter_adapter
241 connection: ActiveConnection,
243 matter: MatterAdapter,
246 """Ping node on the currently known IP-adress(es)."""
247 result = await matter.matter_client.ping_node(node_id=node.node_id)
248 connection.send_result(msg[ID], result)
251 @websocket_api.require_admin
252 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/open_commissioning_window",
253 vol.Required(DEVICE_ID): str,
256 @websocket_api.async_response
257 @async_handle_failed_command
258 @async_get_matter_adapter
262 connection: ActiveConnection,
264 matter: MatterAdapter,
267 """Open a commissioning window to commission a device present on this controller to another."""
268 result = await matter.matter_client.open_commissioning_window(node_id=node.node_id)
269 connection.send_result(msg[ID], dataclass_to_dict(result))
272 @websocket_api.require_admin
273 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/remove_matter_fabric",
274 vol.Required(DEVICE_ID): str,
275 vol.Required(
"fabric_index"): int,
278 @websocket_api.async_response
279 @async_handle_failed_command
280 @async_get_matter_adapter
284 connection: ActiveConnection,
286 matter: MatterAdapter,
289 """Remove Matter fabric from a device."""
290 await matter.matter_client.remove_matter_fabric(
291 node_id=node.node_id, fabric_index=msg[
"fabric_index"]
293 connection.send_result(msg[ID])
296 @websocket_api.require_admin
297 @websocket_api.websocket_command(
{
vol.Required(TYPE):
"matter/interview_node",
298 vol.Required(DEVICE_ID): str,
301 @websocket_api.async_response
302 @async_handle_failed_command
303 @async_get_matter_adapter
307 connection: ActiveConnection,
309 matter: MatterAdapter,
312 """Interview a node."""
313 await matter.matter_client.interview_node(node_id=node.node_id)
314 connection.send_result(msg[ID])
315
None websocket_open_commissioning_window(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter, MatterNode node)
None websocket_set_thread_dataset(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter)
None websocket_node_diagnostics(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter, MatterNode node)
Callable[[HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None]] async_get_matter_adapter(Callable[[HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter], Coroutine[Any, Any, None],] func)
None websocket_remove_matter_fabric(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter, MatterNode node)
None websocket_set_wifi_credentials(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter)
None websocket_ping_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter, MatterNode node)
None async_register_api(HomeAssistant hass)
None websocket_interview_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter, MatterNode node)
None websocket_commission(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter)
Callable[[HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter], Coroutine[Any, Any, None],] async_get_node(Callable[[HomeAssistant, ActiveConnection, dict[str, Any], MatterAdapter, MatterNode], Coroutine[Any, Any, None],] func)
None websocket_commission_on_network(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, MatterAdapter matter)
MatterAdapter get_matter(HomeAssistant hass)
MatterNode|None node_from_ha_device_id(HomeAssistant hass, str ha_device_id)