Home Assistant Unofficial Reference 2024.12.1
api.py
Go to the documentation of this file.
1 """Websocket API for Z-Wave JS."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Coroutine
6 import dataclasses
7 from functools import partial, wraps
8 from typing import Any, Concatenate, Literal, cast
9 
10 from aiohttp import web, web_exceptions, web_request
11 import voluptuous as vol
12 from zwave_js_server.client import Client
13 from zwave_js_server.const import (
14  CommandClass,
15  ExclusionStrategy,
16  InclusionState,
17  InclusionStrategy,
18  LogLevel,
19  NodeStatus,
20  Protocols,
21  ProvisioningEntryStatus,
22  QRCodeVersion,
23  SecurityClass,
24  ZwaveFeature,
25 )
26 from zwave_js_server.exceptions import (
27  BaseZwaveJSServerError,
28  FailedCommand,
29  InvalidNewValue,
30  NotFoundError,
31  SetValueFailed,
32 )
33 from zwave_js_server.firmware import controller_firmware_update_otw, update_firmware
34 from zwave_js_server.model.controller import (
35  ControllerStatistics,
36  InclusionGrant,
37  ProvisioningEntry,
38  QRProvisioningInformation,
39 )
40 from zwave_js_server.model.controller.firmware import (
41  ControllerFirmwareUpdateData,
42  ControllerFirmwareUpdateProgress,
43  ControllerFirmwareUpdateResult,
44 )
45 from zwave_js_server.model.driver import Driver
46 from zwave_js_server.model.endpoint import Endpoint
47 from zwave_js_server.model.log_config import LogConfig
48 from zwave_js_server.model.log_message import LogMessage
49 from zwave_js_server.model.node import Node, NodeStatistics
50 from zwave_js_server.model.node.firmware import (
51  NodeFirmwareUpdateData,
52  NodeFirmwareUpdateProgress,
53  NodeFirmwareUpdateResult,
54 )
55 from zwave_js_server.model.utils import (
56  async_parse_qr_code_string,
57  async_try_parse_dsk_from_qr_code_string,
58 )
59 from zwave_js_server.model.value import ConfigurationValueFormat
60 from zwave_js_server.util.node import async_set_config_parameter
61 
62 from homeassistant.components import websocket_api
63 from homeassistant.components.http import KEY_HASS, HomeAssistantView, require_admin
65  ERR_INVALID_FORMAT,
66  ERR_NOT_FOUND,
67  ERR_NOT_SUPPORTED,
68  ERR_UNKNOWN_ERROR,
69  ActiveConnection,
70 )
71 from homeassistant.config_entries import ConfigEntry, ConfigEntryState
72 from homeassistant.core import HomeAssistant, callback
73 from homeassistant.helpers import config_validation as cv
74 from homeassistant.helpers.aiohttp_client import async_get_clientsession
76 from homeassistant.helpers.dispatcher import async_dispatcher_connect
77 
78 from .config_validation import BITMASK_SCHEMA
79 from .const import (
80  ATTR_COMMAND_CLASS,
81  ATTR_ENDPOINT,
82  ATTR_METHOD_NAME,
83  ATTR_PARAMETERS,
84  ATTR_WAIT_FOR_RESULT,
85  CONF_DATA_COLLECTION_OPTED_IN,
86  CONF_INSTALLER_MODE,
87  DATA_CLIENT,
88  DOMAIN,
89  EVENT_DEVICE_ADDED_TO_REGISTRY,
90  USER_AGENT,
91 )
92 from .helpers import (
93  async_enable_statistics,
94  async_get_node_from_device_id,
95  get_device_id,
96 )
97 
98 DATA_UNSUBSCRIBE = "unsubs"
99 
100 # general API constants
101 ID = "id"
102 ENTRY_ID = "entry_id"
103 ERR_NOT_LOADED = "not_loaded"
104 NODE_ID = "node_id"
105 DEVICE_ID = "device_id"
106 COMMAND_CLASS_ID = "command_class_id"
107 TYPE = "type"
108 PROPERTY = "property"
109 PROPERTY_KEY = "property_key"
110 ENDPOINT = "endpoint"
111 VALUE = "value"
112 VALUE_SIZE = "value_size"
113 VALUE_FORMAT = "value_format"
114 
115 # constants for log config commands
116 CONFIG = "config"
117 LEVEL = "level"
118 LOG_TO_FILE = "log_to_file"
119 FILENAME = "filename"
120 ENABLED = "enabled"
121 FORCE_CONSOLE = "force_console"
122 
123 # constants for setting config parameters
124 VALUE_ID = "value_id"
125 STATUS = "status"
126 
127 # constants for data collection
128 ENABLED = "enabled"
129 OPTED_IN = "opted_in"
130 
131 # constants for granting security classes
132 SECURITY_CLASSES = "securityClasses"
133 CLIENT_SIDE_AUTH = "clientSideAuth"
134 
135 # constants for inclusion
136 INCLUSION_STRATEGY = "inclusion_strategy"
137 
138 INCLUSION_STRATEGY_NOT_SMART_START: dict[
139  int,
140  Literal[
141  InclusionStrategy.DEFAULT,
142  InclusionStrategy.SECURITY_S0,
143  InclusionStrategy.SECURITY_S2,
144  InclusionStrategy.INSECURE,
145  ],
146 ] = {
147  InclusionStrategy.DEFAULT.value: InclusionStrategy.DEFAULT,
148  InclusionStrategy.SECURITY_S0.value: InclusionStrategy.SECURITY_S0,
149  InclusionStrategy.SECURITY_S2.value: InclusionStrategy.SECURITY_S2,
150  InclusionStrategy.INSECURE.value: InclusionStrategy.INSECURE,
151 }
152 PIN = "pin"
153 FORCE_SECURITY = "force_security"
154 PLANNED_PROVISIONING_ENTRY = "planned_provisioning_entry"
155 QR_PROVISIONING_INFORMATION = "qr_provisioning_information"
156 QR_CODE_STRING = "qr_code_string"
157 
158 DSK = "dsk"
159 
160 VERSION = "version"
161 GENERIC_DEVICE_CLASS = "genericDeviceClass"
162 SPECIFIC_DEVICE_CLASS = "specificDeviceClass"
163 INSTALLER_ICON_TYPE = "installerIconType"
164 MANUFACTURER_ID = "manufacturerId"
165 PRODUCT_TYPE = "productType"
166 PRODUCT_ID = "productId"
167 APPLICATION_VERSION = "applicationVersion"
168 MAX_INCLUSION_REQUEST_INTERVAL = "maxInclusionRequestInterval"
169 UUID = "uuid"
170 SUPPORTED_PROTOCOLS = "supportedProtocols"
171 ADDITIONAL_PROPERTIES = "additional_properties"
172 STATUS = "status"
173 REQUESTED_SECURITY_CLASSES = "requestedSecurityClasses"
174 
175 FEATURE = "feature"
176 STRATEGY = "strategy"
177 
178 # https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/security/QR.ts#L41
179 MINIMUM_QR_STRING_LENGTH = 52
180 
181 
182 # Helper schemas
183 PLANNED_PROVISIONING_ENTRY_SCHEMA = vol.All(
184  vol.Schema(
185  {
186  vol.Required(DSK): str,
187  vol.Required(SECURITY_CLASSES): vol.All(
188  cv.ensure_list,
189  [vol.Coerce(SecurityClass)],
190  ),
191  vol.Optional(STATUS, default=ProvisioningEntryStatus.ACTIVE): vol.Coerce(
192  ProvisioningEntryStatus
193  ),
194  vol.Optional(REQUESTED_SECURITY_CLASSES): vol.All(
195  cv.ensure_list, [vol.Coerce(SecurityClass)]
196  ),
197  },
198  # Provisioning entries can have extra keys for SmartStart
199  extra=vol.ALLOW_EXTRA,
200  ),
201  ProvisioningEntry.from_dict,
202 )
203 
204 QR_PROVISIONING_INFORMATION_SCHEMA = vol.All(
205  vol.Schema(
206  {
207  vol.Required(VERSION): vol.Coerce(QRCodeVersion),
208  vol.Required(SECURITY_CLASSES): vol.All(
209  cv.ensure_list,
210  [vol.Coerce(SecurityClass)],
211  ),
212  vol.Required(DSK): str,
213  vol.Required(GENERIC_DEVICE_CLASS): int,
214  vol.Required(SPECIFIC_DEVICE_CLASS): int,
215  vol.Required(INSTALLER_ICON_TYPE): int,
216  vol.Required(MANUFACTURER_ID): int,
217  vol.Required(PRODUCT_TYPE): int,
218  vol.Required(PRODUCT_ID): int,
219  vol.Required(APPLICATION_VERSION): str,
220  vol.Optional(MAX_INCLUSION_REQUEST_INTERVAL): vol.Any(int, None),
221  vol.Optional(UUID): vol.Any(str, None),
222  vol.Optional(SUPPORTED_PROTOCOLS): vol.All(
223  cv.ensure_list,
224  [vol.Coerce(Protocols)],
225  ),
226  vol.Optional(STATUS, default=ProvisioningEntryStatus.ACTIVE): vol.Coerce(
227  ProvisioningEntryStatus
228  ),
229  vol.Optional(REQUESTED_SECURITY_CLASSES): vol.All(
230  cv.ensure_list, [vol.Coerce(SecurityClass)]
231  ),
232  },
233  extra=vol.ALLOW_EXTRA,
234  ),
235  QRProvisioningInformation.from_dict,
236 )
237 
238 QR_CODE_STRING_SCHEMA = vol.All(str, vol.Length(min=MINIMUM_QR_STRING_LENGTH))
239 
240 
242  hass: HomeAssistant,
243  connection: ActiveConnection,
244  msg: dict[str, Any],
245  entry_id: str,
246 ) -> tuple[ConfigEntry, Client, Driver] | tuple[None, None, None]:
247  """Get config entry and client from message data."""
248  entry = hass.config_entries.async_get_entry(entry_id)
249  if entry is None:
250  connection.send_error(
251  msg[ID], ERR_NOT_FOUND, f"Config entry {entry_id} not found"
252  )
253  return None, None, None
254 
255  if entry.state is not ConfigEntryState.LOADED:
256  connection.send_error(
257  msg[ID], ERR_NOT_LOADED, f"Config entry {entry_id} not loaded"
258  )
259  return None, None, None
260 
261  client: Client = entry.runtime_data[DATA_CLIENT]
262 
263  if client.driver is None:
264  connection.send_error(
265  msg[ID],
266  ERR_NOT_LOADED,
267  f"Config entry {msg[ENTRY_ID]} not loaded, driver not ready",
268  )
269  return None, None, None
270 
271  return entry, client, client.driver
272 
273 
275  orig_func: Callable[
276  [HomeAssistant, ActiveConnection, dict[str, Any], ConfigEntry, Client, Driver],
277  Coroutine[Any, Any, None],
278  ],
279 ) -> Callable[
280  [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None]
281 ]:
282  """Decorate async function to get entry."""
283 
284  @wraps(orig_func)
285  async def async_get_entry_func(
286  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
287  ) -> None:
288  """Provide user specific data and store to function."""
289  entry, client, driver = await _async_get_entry(
290  hass, connection, msg, msg[ENTRY_ID]
291  )
292 
293  if not entry or not client or not driver:
294  return
295 
296  await orig_func(hass, connection, msg, entry, client, driver)
297 
298  return async_get_entry_func
299 
300 
301 async def _async_get_node(
302  hass: HomeAssistant, connection: ActiveConnection, msg: dict, device_id: str
303 ) -> Node | None:
304  """Get node from message data."""
305  try:
306  node = async_get_node_from_device_id(hass, device_id)
307  except ValueError as err:
308  error_code = ERR_NOT_FOUND
309  if "loaded" in err.args[0]:
310  error_code = ERR_NOT_LOADED
311  connection.send_error(msg[ID], error_code, err.args[0])
312  return None
313  return node
314 
315 
317  orig_func: Callable[
318  [HomeAssistant, ActiveConnection, dict[str, Any], Node],
319  Coroutine[Any, Any, None],
320  ],
321 ) -> Callable[
322  [HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None]
323 ]:
324  """Decorate async function to get node."""
325 
326  @wraps(orig_func)
327  async def async_get_node_func(
328  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
329  ) -> None:
330  """Provide user specific data and store to function."""
331  node = await _async_get_node(hass, connection, msg, msg[DEVICE_ID])
332  if not node:
333  return
334  await orig_func(hass, connection, msg, node)
335 
336  return async_get_node_func
337 
338 
339 def async_handle_failed_command[**_P](
340  orig_func: Callable[
341  Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P],
342  Coroutine[Any, Any, None],
343  ],
344 ) -> Callable[
345  Concatenate[HomeAssistant, ActiveConnection, dict[str, Any], _P],
346  Coroutine[Any, Any, None],
347 ]:
348  """Decorate async function to handle FailedCommand and send relevant error."""
349 
350  @wraps(orig_func)
351  async def async_handle_failed_command_func(
352  hass: HomeAssistant,
353  connection: ActiveConnection,
354  msg: dict[str, Any],
355  *args: _P.args,
356  **kwargs: _P.kwargs,
357  ) -> None:
358  """Handle FailedCommand within function and send relevant error."""
359  try:
360  await orig_func(hass, connection, msg, *args, **kwargs)
361  except FailedCommand as err:
362  # Unsubscribe to callbacks
363  if unsubs := msg.get(DATA_UNSUBSCRIBE):
364  for unsub in unsubs:
365  unsub()
366  connection.send_error(msg[ID], err.error_code, err.args[0])
367 
368  return async_handle_failed_command_func
369 
370 
371 def node_status(node: Node) -> dict[str, Any]:
372  """Get node status."""
373  return {
374  "node_id": node.node_id,
375  "is_routing": node.is_routing,
376  "status": node.status,
377  "is_secure": node.is_secure,
378  "ready": node.ready,
379  "zwave_plus_version": node.zwave_plus_version,
380  "highest_security_class": node.highest_security_class,
381  "is_controller_node": node.is_controller_node,
382  "has_firmware_update_cc": any(
383  cc.id == CommandClass.FIRMWARE_UPDATE_MD.value
384  for cc in node.command_classes
385  ),
386  }
387 
388 
389 @callback
390 def async_register_api(hass: HomeAssistant) -> None:
391  """Register all of our api endpoints."""
392  websocket_api.async_register_command(hass, websocket_network_status)
393  websocket_api.async_register_command(hass, websocket_subscribe_node_status)
394  websocket_api.async_register_command(hass, websocket_node_status)
395  websocket_api.async_register_command(hass, websocket_node_metadata)
396  websocket_api.async_register_command(hass, websocket_node_alerts)
397  websocket_api.async_register_command(hass, websocket_add_node)
398  websocket_api.async_register_command(hass, websocket_grant_security_classes)
399  websocket_api.async_register_command(hass, websocket_validate_dsk_and_enter_pin)
400  websocket_api.async_register_command(hass, websocket_provision_smart_start_node)
401  websocket_api.async_register_command(hass, websocket_unprovision_smart_start_node)
402  websocket_api.async_register_command(hass, websocket_get_provisioning_entries)
403  websocket_api.async_register_command(hass, websocket_parse_qr_code_string)
404  websocket_api.async_register_command(
405  hass, websocket_try_parse_dsk_from_qr_code_string
406  )
407  websocket_api.async_register_command(hass, websocket_supports_feature)
408  websocket_api.async_register_command(hass, websocket_stop_inclusion)
409  websocket_api.async_register_command(hass, websocket_stop_exclusion)
410  websocket_api.async_register_command(hass, websocket_remove_node)
411  websocket_api.async_register_command(hass, websocket_remove_failed_node)
412  websocket_api.async_register_command(hass, websocket_replace_failed_node)
413  websocket_api.async_register_command(hass, websocket_begin_rebuilding_routes)
414  websocket_api.async_register_command(
415  hass, websocket_subscribe_rebuild_routes_progress
416  )
417  websocket_api.async_register_command(hass, websocket_stop_rebuilding_routes)
418  websocket_api.async_register_command(hass, websocket_refresh_node_info)
419  websocket_api.async_register_command(hass, websocket_refresh_node_values)
420  websocket_api.async_register_command(hass, websocket_refresh_node_cc_values)
421  websocket_api.async_register_command(hass, websocket_rebuild_node_routes)
422  websocket_api.async_register_command(hass, websocket_set_config_parameter)
423  websocket_api.async_register_command(hass, websocket_get_config_parameters)
424  websocket_api.async_register_command(hass, websocket_get_raw_config_parameter)
425  websocket_api.async_register_command(hass, websocket_set_raw_config_parameter)
426  websocket_api.async_register_command(hass, websocket_subscribe_log_updates)
427  websocket_api.async_register_command(hass, websocket_update_log_config)
428  websocket_api.async_register_command(hass, websocket_get_log_config)
429  websocket_api.async_register_command(
430  hass, websocket_update_data_collection_preference
431  )
432  websocket_api.async_register_command(hass, websocket_data_collection_status)
433  websocket_api.async_register_command(hass, websocket_abort_firmware_update)
434  websocket_api.async_register_command(
435  hass, websocket_is_node_firmware_update_in_progress
436  )
437  websocket_api.async_register_command(
438  hass, websocket_subscribe_firmware_update_status
439  )
440  websocket_api.async_register_command(
441  hass, websocket_get_node_firmware_update_capabilities
442  )
443  websocket_api.async_register_command(
444  hass, websocket_is_any_ota_firmware_update_in_progress
445  )
446  websocket_api.async_register_command(hass, websocket_check_for_config_updates)
447  websocket_api.async_register_command(hass, websocket_install_config_update)
448  websocket_api.async_register_command(
449  hass, websocket_subscribe_controller_statistics
450  )
451  websocket_api.async_register_command(hass, websocket_subscribe_node_statistics)
452  websocket_api.async_register_command(hass, websocket_hard_reset_controller)
453  websocket_api.async_register_command(hass, websocket_node_capabilities)
454  websocket_api.async_register_command(hass, websocket_invoke_cc_api)
455  websocket_api.async_register_command(hass, websocket_get_integration_settings)
456  hass.http.register_view(FirmwareUploadView(dr.async_get(hass)))
457 
458 
459 @websocket_api.require_admin
460 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/network_status",
461  vol.Exclusive(DEVICE_ID, "id"): str,
462  vol.Exclusive(ENTRY_ID, "id"): str,
463  }
464 )
465 @websocket_api.async_response
466 async def websocket_network_status(
467  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
468 ) -> None:
469  """Get the status of the Z-Wave JS network."""
470  if ENTRY_ID in msg:
471  _, client, driver = await _async_get_entry(hass, connection, msg, msg[ENTRY_ID])
472  if not client or not driver:
473  return
474  elif DEVICE_ID in msg:
475  node = await _async_get_node(hass, connection, msg, msg[DEVICE_ID])
476  if not node:
477  return
478  client = node.client
479  assert client.driver
480  driver = client.driver
481  else:
482  connection.send_error(
483  msg[ID], ERR_INVALID_FORMAT, "Must specify either device_id or entry_id"
484  )
485  return
486  controller = driver.controller
487  controller.update(await controller.async_get_state())
488  client_version_info = client.version
489  assert client_version_info # When client is connected version info is set.
490  data = {
491  "client": {
492  "ws_server_url": client.ws_server_url,
493  "state": "connected" if client.connected else "disconnected",
494  "driver_version": client_version_info.driver_version,
495  "server_version": client_version_info.server_version,
496  "server_logging_enabled": client.server_logging_enabled,
497  },
498  "controller": {
499  "home_id": controller.home_id,
500  "sdk_version": controller.sdk_version,
501  "type": controller.controller_type,
502  "own_node_id": controller.own_node_id,
503  "is_primary": controller.is_primary,
504  "is_using_home_id_from_other_network": (
505  controller.is_using_home_id_from_other_network
506  ),
507  "is_sis_present": controller.is_SIS_present,
508  "was_real_primary": controller.was_real_primary,
509  "is_suc": controller.is_suc,
510  "node_type": controller.node_type,
511  "firmware_version": controller.firmware_version,
512  "manufacturer_id": controller.manufacturer_id,
513  "product_id": controller.product_id,
514  "product_type": controller.product_type,
515  "supported_function_types": controller.supported_function_types,
516  "suc_node_id": controller.suc_node_id,
517  "supports_timers": controller.supports_timers,
518  "is_rebuilding_routes": controller.is_rebuilding_routes,
519  "inclusion_state": controller.inclusion_state,
520  "rf_region": controller.rf_region,
521  "status": controller.status,
522  "nodes": [node_status(node) for node in driver.controller.nodes.values()],
523  },
524  }
525  connection.send_result(
526  msg[ID],
527  data,
528  )
529 
530 
531 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_node_status",
532  vol.Required(DEVICE_ID): str,
533  }
534 )
535 @websocket_api.async_response
536 @async_get_node
538  hass: HomeAssistant,
539  connection: ActiveConnection,
540  msg: dict[str, Any],
541  node: Node,
542 ) -> None:
543  """Subscribe to node status update events of a Z-Wave JS node."""
544 
545  @callback
546  def forward_event(event: dict) -> None:
547  """Forward the event."""
548  connection.send_message(
549  websocket_api.event_message(
550  msg[ID],
551  {"event": event["event"], "status": node.status, "ready": node.ready},
552  )
553  )
554 
555  @callback
556  def async_cleanup() -> None:
557  """Remove signal listeners."""
558  for unsub in unsubs:
559  unsub()
560 
561  connection.subscriptions[msg["id"]] = async_cleanup
562  msg[DATA_UNSUBSCRIBE] = unsubs = [
563  node.on(evt, forward_event)
564  for evt in ("alive", "dead", "sleep", "wake up", "ready")
565  ]
566 
567  connection.send_result(msg[ID])
568 
569 
570 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_status",
571  vol.Required(DEVICE_ID): str,
572  }
573 )
574 @websocket_api.async_response
575 @async_get_node
576 async def websocket_node_status(
577  hass: HomeAssistant,
578  connection: ActiveConnection,
579  msg: dict[str, Any],
580  node: Node,
581 ) -> None:
582  """Get the status of a Z-Wave JS node."""
583  connection.send_result(msg[ID], node_status(node))
584 
585 
586 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_metadata",
587  vol.Required(DEVICE_ID): str,
588  }
589 )
590 @websocket_api.async_response
591 @async_get_node
592 async def websocket_node_metadata(
593  hass: HomeAssistant,
594  connection: ActiveConnection,
595  msg: dict[str, Any],
596  node: Node,
597 ) -> None:
598  """Get the metadata of a Z-Wave JS node."""
599  data = {
600  "node_id": node.node_id,
601  "exclusion": node.device_config.metadata.exclusion,
602  "inclusion": node.device_config.metadata.inclusion,
603  "manual": node.device_config.metadata.manual,
604  "wakeup": node.device_config.metadata.wakeup,
605  "reset": node.device_config.metadata.reset,
606  "device_database_url": node.device_database_url,
607  }
608  connection.send_result(
609  msg[ID],
610  data,
611  )
612 
613 
614 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_alerts",
615  vol.Required(DEVICE_ID): str,
616  }
617 )
618 @websocket_api.async_response
619 @async_get_node
620 async def websocket_node_alerts(
621  hass: HomeAssistant,
622  connection: ActiveConnection,
623  msg: dict[str, Any],
624  node: Node,
625 ) -> None:
626  """Get the alerts for a Z-Wave JS node."""
627  connection.send_result(
628  msg[ID],
629  {
630  "comments": node.device_config.metadata.comments,
631  "is_embedded": node.device_config.is_embedded,
632  },
633  )
634 
635 
636 @websocket_api.require_admin
637 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/add_node",
638  vol.Required(ENTRY_ID): str,
639  vol.Optional(INCLUSION_STRATEGY, default=InclusionStrategy.DEFAULT): vol.All(
640  vol.Coerce(int),
641  vol.In(
642  [
643  strategy.value
644  for strategy in InclusionStrategy
645  if strategy != InclusionStrategy.SMART_START
646  ]
647  ),
648  ),
649  vol.Optional(FORCE_SECURITY): bool,
650  vol.Exclusive(
651  PLANNED_PROVISIONING_ENTRY, "options"
652  ): PLANNED_PROVISIONING_ENTRY_SCHEMA,
653  vol.Exclusive(
654  QR_PROVISIONING_INFORMATION, "options"
655  ): QR_PROVISIONING_INFORMATION_SCHEMA,
656  vol.Exclusive(QR_CODE_STRING, "options"): QR_CODE_STRING_SCHEMA,
657  vol.Exclusive(DSK, "options"): str,
658  }
659 )
660 @websocket_api.async_response
661 @async_handle_failed_command
662 @async_get_entry
663 async def websocket_add_node(
664  hass: HomeAssistant,
665  connection: ActiveConnection,
666  msg: dict[str, Any],
667  entry: ConfigEntry,
668  client: Client,
669  driver: Driver,
670 ) -> None:
671  """Add a node to the Z-Wave network."""
672  controller = driver.controller
673  inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY])
674  force_security = msg.get(FORCE_SECURITY)
675  provisioning = (
676  msg.get(PLANNED_PROVISIONING_ENTRY)
677  or msg.get(QR_PROVISIONING_INFORMATION)
678  or msg.get(QR_CODE_STRING)
679  )
680  dsk = msg.get(DSK)
681 
682  @callback
683  def async_cleanup() -> None:
684  """Remove signal listeners."""
685  for unsub in unsubs:
686  unsub()
687 
688  @callback
689  def forward_event(event: dict) -> None:
690  connection.send_message(
691  websocket_api.event_message(msg[ID], {"event": event["event"]})
692  )
693 
694  @callback
695  def forward_dsk(event: dict) -> None:
696  connection.send_message(
697  websocket_api.event_message(
698  msg[ID], {"event": event["event"], "dsk": event["dsk"]}
699  )
700  )
701 
702  @callback
703  def forward_node_added(
704  node: Node, low_security: bool, low_security_reason: str | None
705  ) -> None:
706  interview_unsubs = [
707  node.on("interview started", forward_event),
708  node.on("interview completed", forward_event),
709  node.on("interview stage completed", forward_stage),
710  node.on("interview failed", forward_event),
711  ]
712  unsubs.extend(interview_unsubs)
713  node_details = {
714  "node_id": node.node_id,
715  "status": node.status,
716  "ready": node.ready,
717  "low_security": low_security,
718  "low_security_reason": low_security_reason,
719  }
720  connection.send_message(
721  websocket_api.event_message(
722  msg[ID], {"event": "node added", "node": node_details}
723  )
724  )
725 
726  @callback
727  def forward_requested_grant(event: dict) -> None:
728  connection.send_message(
729  websocket_api.event_message(
730  msg[ID],
731  {
732  "event": event["event"],
733  "requested_grant": event["requested_grant"].to_dict(),
734  },
735  )
736  )
737 
738  @callback
739  def forward_stage(event: dict) -> None:
740  connection.send_message(
741  websocket_api.event_message(
742  msg[ID], {"event": event["event"], "stage": event["stageName"]}
743  )
744  )
745 
746  @callback
747  def node_found(event: dict) -> None:
748  node = event["node"]
749  node_details = {
750  "node_id": node["nodeId"],
751  }
752  connection.send_message(
753  websocket_api.event_message(
754  msg[ID], {"event": "node found", "node": node_details}
755  )
756  )
757 
758  @callback
759  def node_added(event: dict) -> None:
760  forward_node_added(
761  event["node"],
762  event["result"].get("lowSecurity", False),
763  event["result"].get("lowSecurityReason"),
764  )
765 
766  @callback
767  def device_registered(device: dr.DeviceEntry) -> None:
768  device_details = {
769  "name": device.name,
770  "id": device.id,
771  "manufacturer": device.manufacturer,
772  "model": device.model,
773  }
774  connection.send_message(
775  websocket_api.event_message(
776  msg[ID], {"event": "device registered", "device": device_details}
777  )
778  )
779 
780  connection.subscriptions[msg["id"]] = async_cleanup
781  unsubs: list[Callable[[], None]] = [
782  controller.on("inclusion started", forward_event),
783  controller.on("inclusion failed", forward_event),
784  controller.on("inclusion stopped", forward_event),
785  controller.on("validate dsk and enter pin", forward_dsk),
786  controller.on("grant security classes", forward_requested_grant),
787  controller.on("node found", node_found),
788  controller.on("node added", node_added),
790  hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered
791  ),
792  ]
793  msg[DATA_UNSUBSCRIBE] = unsubs
794 
795  if controller.inclusion_state == InclusionState.INCLUDING:
796  connection.send_result(
797  msg[ID],
798  True, # Inclusion is already in progress
799  )
800  # Check for nodes that have been added but not fully included
801  for node in controller.nodes.values():
802  if node.status != NodeStatus.DEAD and not node.ready:
803  forward_node_added(
804  node,
805  not node.is_secure,
806  None,
807  )
808  else:
809  try:
810  result = await controller.async_begin_inclusion(
811  INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value],
812  force_security=force_security,
813  provisioning=provisioning,
814  dsk=dsk,
815  )
816  except ValueError as err:
817  connection.send_error(
818  msg[ID],
819  ERR_INVALID_FORMAT,
820  err.args[0],
821  )
822  return
823 
824  connection.send_result(
825  msg[ID],
826  result,
827  )
828 
829 
830 @websocket_api.require_admin
831 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/grant_security_classes",
832  vol.Required(ENTRY_ID): str,
833  vol.Required(SECURITY_CLASSES): vol.All(
834  cv.ensure_list,
835  [vol.Coerce(SecurityClass)],
836  ),
837  vol.Optional(CLIENT_SIDE_AUTH, default=False): bool,
838  }
839 )
840 @websocket_api.async_response
841 @async_handle_failed_command
842 @async_get_entry
844  hass: HomeAssistant,
845  connection: ActiveConnection,
846  msg: dict[str, Any],
847  entry: ConfigEntry,
848  client: Client,
849  driver: Driver,
850 ) -> None:
851  """Choose SecurityClass grants as part of S2 inclusion process."""
852  inclusion_grant = InclusionGrant(
853  [SecurityClass(sec_cls) for sec_cls in msg[SECURITY_CLASSES]],
854  msg[CLIENT_SIDE_AUTH],
855  )
856  await driver.controller.async_grant_security_classes(inclusion_grant)
857  connection.send_result(msg[ID])
858 
859 
860 @websocket_api.require_admin
861 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/validate_dsk_and_enter_pin",
862  vol.Required(ENTRY_ID): str,
863  vol.Required(PIN): str,
864  }
865 )
866 @websocket_api.async_response
867 @async_handle_failed_command
868 @async_get_entry
870  hass: HomeAssistant,
871  connection: ActiveConnection,
872  msg: dict[str, Any],
873  entry: ConfigEntry,
874  client: Client,
875  driver: Driver,
876 ) -> None:
877  """Validate DSK and enter PIN as part of S2 inclusion process."""
878  await driver.controller.async_validate_dsk_and_enter_pin(msg[PIN])
879  connection.send_result(msg[ID])
880 
881 
882 @websocket_api.require_admin
883 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/provision_smart_start_node",
884  vol.Required(ENTRY_ID): str,
885  vol.Exclusive(
886  PLANNED_PROVISIONING_ENTRY, "options"
887  ): PLANNED_PROVISIONING_ENTRY_SCHEMA,
888  vol.Exclusive(
889  QR_PROVISIONING_INFORMATION, "options"
890  ): QR_PROVISIONING_INFORMATION_SCHEMA,
891  vol.Exclusive(QR_CODE_STRING, "options"): QR_CODE_STRING_SCHEMA,
892  }
893 )
894 @websocket_api.async_response
895 @async_handle_failed_command
896 @async_get_entry
898  hass: HomeAssistant,
899  connection: ActiveConnection,
900  msg: dict[str, Any],
901  entry: ConfigEntry,
902  client: Client,
903  driver: Driver,
904 ) -> None:
905  """Pre-provision a smart start node."""
906  try:
907  cv.has_at_least_one_key(
908  PLANNED_PROVISIONING_ENTRY, QR_PROVISIONING_INFORMATION, QR_CODE_STRING
909  )(msg)
910  except vol.Invalid as err:
911  connection.send_error(
912  msg[ID],
913  ERR_INVALID_FORMAT,
914  err.args[0],
915  )
916  return
917 
918  provisioning_info = (
919  msg.get(PLANNED_PROVISIONING_ENTRY)
920  or msg.get(QR_PROVISIONING_INFORMATION)
921  or msg[QR_CODE_STRING]
922  )
923 
924  if (
925  QR_PROVISIONING_INFORMATION in msg
926  and provisioning_info.version == QRCodeVersion.S2
927  ):
928  connection.send_error(
929  msg[ID],
930  ERR_INVALID_FORMAT,
931  "QR code version S2 is not supported for this command",
932  )
933  return
934  await driver.controller.async_provision_smart_start_node(provisioning_info)
935  connection.send_result(msg[ID])
936 
937 
938 @websocket_api.require_admin
939 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/unprovision_smart_start_node",
940  vol.Required(ENTRY_ID): str,
941  vol.Exclusive(DSK, "input"): str,
942  vol.Exclusive(NODE_ID, "input"): int,
943  }
944 )
945 @websocket_api.async_response
946 @async_handle_failed_command
947 @async_get_entry
949  hass: HomeAssistant,
950  connection: ActiveConnection,
951  msg: dict[str, Any],
952  entry: ConfigEntry,
953  client: Client,
954  driver: Driver,
955 ) -> None:
956  """Unprovision a smart start node."""
957  try:
958  cv.has_at_least_one_key(DSK, NODE_ID)(msg)
959  except vol.Invalid as err:
960  connection.send_error(
961  msg[ID],
962  ERR_INVALID_FORMAT,
963  err.args[0],
964  )
965  return
966  dsk_or_node_id = msg.get(DSK) or msg[NODE_ID]
967  await driver.controller.async_unprovision_smart_start_node(dsk_or_node_id)
968  connection.send_result(msg[ID])
969 
970 
971 @websocket_api.require_admin
972 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_provisioning_entries",
973  vol.Required(ENTRY_ID): str,
974  }
975 )
976 @websocket_api.async_response
977 @async_handle_failed_command
978 @async_get_entry
980  hass: HomeAssistant,
981  connection: ActiveConnection,
982  msg: dict[str, Any],
983  entry: ConfigEntry,
984  client: Client,
985  driver: Driver,
986 ) -> None:
987  """Get provisioning entries (entries that have been pre-provisioned)."""
988  provisioning_entries = await driver.controller.async_get_provisioning_entries()
989  connection.send_result(msg[ID], [entry.to_dict() for entry in provisioning_entries])
990 
991 
992 @websocket_api.require_admin
993 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/parse_qr_code_string",
994  vol.Required(ENTRY_ID): str,
995  vol.Required(QR_CODE_STRING): QR_CODE_STRING_SCHEMA,
996  }
997 )
998 @websocket_api.async_response
999 @async_handle_failed_command
1000 @async_get_entry
1002  hass: HomeAssistant,
1003  connection: ActiveConnection,
1004  msg: dict[str, Any],
1005  entry: ConfigEntry,
1006  client: Client,
1007  driver: Driver,
1008 ) -> None:
1009  """Parse a QR Code String and return QRProvisioningInformation dict."""
1010  qr_provisioning_information = await async_parse_qr_code_string(
1011  client, msg[QR_CODE_STRING]
1012  )
1013  connection.send_result(msg[ID], qr_provisioning_information.to_dict())
1014 
1015 
1016 @websocket_api.require_admin
1017 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/try_parse_dsk_from_qr_code_string",
1018  vol.Required(ENTRY_ID): str,
1019  vol.Required(QR_CODE_STRING): str,
1020  }
1021 )
1022 @websocket_api.async_response
1023 @async_handle_failed_command
1024 @async_get_entry
1026  hass: HomeAssistant,
1027  connection: ActiveConnection,
1028  msg: dict[str, Any],
1029  entry: ConfigEntry,
1030  client: Client,
1031  driver: Driver,
1032 ) -> None:
1033  """Try to parse a DSK string from a QR code."""
1034  connection.send_result(
1035  msg[ID],
1036  await async_try_parse_dsk_from_qr_code_string(client, msg[QR_CODE_STRING]),
1037  )
1038 
1039 
1040 @websocket_api.require_admin
1041 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/supports_feature",
1042  vol.Required(ENTRY_ID): str,
1043  vol.Required(FEATURE): vol.Coerce(ZwaveFeature),
1044  }
1045 )
1046 @websocket_api.async_response
1047 @async_handle_failed_command
1048 @async_get_entry
1049 async def websocket_supports_feature(
1050  hass: HomeAssistant,
1051  connection: ActiveConnection,
1052  msg: dict[str, Any],
1053  entry: ConfigEntry,
1054  client: Client,
1055  driver: Driver,
1056 ) -> None:
1057  """Check if controller supports a particular feature."""
1058  supported = await driver.controller.async_supports_feature(msg[FEATURE])
1059  connection.send_result(
1060  msg[ID],
1061  {"supported": supported},
1062  )
1063 
1064 
1065 @websocket_api.require_admin
1066 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/stop_inclusion",
1067  vol.Required(ENTRY_ID): str,
1068  }
1069 )
1070 @websocket_api.async_response
1071 @async_handle_failed_command
1072 @async_get_entry
1073 async def websocket_stop_inclusion(
1074  hass: HomeAssistant,
1075  connection: ActiveConnection,
1076  msg: dict[str, Any],
1077  entry: ConfigEntry,
1078  client: Client,
1079  driver: Driver,
1080 ) -> None:
1081  """Cancel adding a node to the Z-Wave network."""
1082  controller = driver.controller
1083  result = await controller.async_stop_inclusion()
1084  connection.send_result(
1085  msg[ID],
1086  result,
1087  )
1088 
1089 
1090 @websocket_api.require_admin
1091 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/stop_exclusion",
1092  vol.Required(ENTRY_ID): str,
1093  }
1094 )
1095 @websocket_api.async_response
1096 @async_handle_failed_command
1097 @async_get_entry
1098 async def websocket_stop_exclusion(
1099  hass: HomeAssistant,
1100  connection: ActiveConnection,
1101  msg: dict[str, Any],
1102  entry: ConfigEntry,
1103  client: Client,
1104  driver: Driver,
1105 ) -> None:
1106  """Cancel removing a node from the Z-Wave network."""
1107  controller = driver.controller
1108  result = await controller.async_stop_exclusion()
1109  connection.send_result(
1110  msg[ID],
1111  result,
1112  )
1113 
1114 
1115 @websocket_api.require_admin
1116 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/remove_node",
1117  vol.Required(ENTRY_ID): str,
1118  vol.Optional(STRATEGY): vol.Coerce(ExclusionStrategy),
1119  }
1120 )
1121 @websocket_api.async_response
1122 @async_handle_failed_command
1123 @async_get_entry
1124 async def websocket_remove_node(
1125  hass: HomeAssistant,
1126  connection: ActiveConnection,
1127  msg: dict[str, Any],
1128  entry: ConfigEntry,
1129  client: Client,
1130  driver: Driver,
1131 ) -> None:
1132  """Remove a node from the Z-Wave network."""
1133  controller = driver.controller
1134 
1135  @callback
1136  def async_cleanup() -> None:
1137  """Remove signal listeners."""
1138  for unsub in unsubs:
1139  unsub()
1140 
1141  @callback
1142  def forward_event(event: dict) -> None:
1143  connection.send_message(
1144  websocket_api.event_message(msg[ID], {"event": event["event"]})
1145  )
1146 
1147  @callback
1148  def node_removed(event: dict) -> None:
1149  node = event["node"]
1150  node_details = {
1151  "node_id": node.node_id,
1152  "reason": event["reason"],
1153  }
1154 
1155  connection.send_message(
1156  websocket_api.event_message(
1157  msg[ID], {"event": "node removed", "node": node_details}
1158  )
1159  )
1160 
1161  connection.subscriptions[msg["id"]] = async_cleanup
1162  msg[DATA_UNSUBSCRIBE] = unsubs = [
1163  controller.on("exclusion started", forward_event),
1164  controller.on("exclusion failed", forward_event),
1165  controller.on("exclusion stopped", forward_event),
1166  controller.on("node removed", node_removed),
1167  ]
1168 
1169  result = await controller.async_begin_exclusion(msg.get(STRATEGY))
1170  connection.send_result(
1171  msg[ID],
1172  result,
1173  )
1174 
1175 
1176 @websocket_api.require_admin
1177 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/replace_failed_node",
1178  vol.Required(DEVICE_ID): str,
1179  vol.Optional(INCLUSION_STRATEGY, default=InclusionStrategy.DEFAULT): vol.All(
1180  vol.Coerce(int),
1181  vol.In(
1182  [
1183  strategy.value
1184  for strategy in InclusionStrategy
1185  if strategy != InclusionStrategy.SMART_START
1186  ]
1187  ),
1188  ),
1189  vol.Optional(FORCE_SECURITY): bool,
1190  vol.Exclusive(
1191  PLANNED_PROVISIONING_ENTRY, "options"
1192  ): PLANNED_PROVISIONING_ENTRY_SCHEMA,
1193  vol.Exclusive(
1194  QR_PROVISIONING_INFORMATION, "options"
1195  ): QR_PROVISIONING_INFORMATION_SCHEMA,
1196  vol.Exclusive(QR_CODE_STRING, "options"): QR_CODE_STRING_SCHEMA,
1197  }
1198 )
1199 @websocket_api.async_response
1200 @async_handle_failed_command
1201 @async_get_node
1203  hass: HomeAssistant,
1204  connection: ActiveConnection,
1205  msg: dict[str, Any],
1206  node: Node,
1207 ) -> None:
1208  """Replace a failed node with a new node."""
1209  assert node.client.driver
1210  controller = node.client.driver.controller
1211  inclusion_strategy = InclusionStrategy(msg[INCLUSION_STRATEGY])
1212  force_security = msg.get(FORCE_SECURITY)
1213  provisioning = (
1214  msg.get(PLANNED_PROVISIONING_ENTRY)
1215  or msg.get(QR_PROVISIONING_INFORMATION)
1216  or msg.get(QR_CODE_STRING)
1217  )
1218 
1219  @callback
1220  def async_cleanup() -> None:
1221  """Remove signal listeners."""
1222  for unsub in unsubs:
1223  unsub()
1224 
1225  @callback
1226  def forward_event(event: dict) -> None:
1227  connection.send_message(
1228  websocket_api.event_message(msg[ID], {"event": event["event"]})
1229  )
1230 
1231  @callback
1232  def forward_dsk(event: dict) -> None:
1233  connection.send_message(
1234  websocket_api.event_message(
1235  msg[ID], {"event": event["event"], "dsk": event["dsk"]}
1236  )
1237  )
1239  @callback
1240  def forward_requested_grant(event: dict) -> None:
1241  connection.send_message(
1242  websocket_api.event_message(
1243  msg[ID],
1244  {
1245  "event": event["event"],
1246  "requested_grant": event["requested_grant"].to_dict(),
1247  },
1248  )
1249  )
1250 
1251  @callback
1252  def forward_stage(event: dict) -> None:
1253  connection.send_message(
1254  websocket_api.event_message(
1255  msg[ID], {"event": event["event"], "stage": event["stageName"]}
1256  )
1257  )
1258 
1259  @callback
1260  def node_found(event: dict) -> None:
1261  node = event["node"]
1262  node_details = {
1263  "node_id": node["nodeId"],
1264  }
1265  connection.send_message(
1266  websocket_api.event_message(
1267  msg[ID], {"event": "node found", "node": node_details}
1268  )
1269  )
1270 
1271  @callback
1272  def node_added(event: dict) -> None:
1273  node = event["node"]
1274  interview_unsubs = [
1275  node.on("interview started", forward_event),
1276  node.on("interview completed", forward_event),
1277  node.on("interview stage completed", forward_stage),
1278  node.on("interview failed", forward_event),
1279  ]
1280  unsubs.extend(interview_unsubs)
1281  node_details = {
1282  "node_id": node.node_id,
1283  "status": node.status,
1284  "ready": node.ready,
1285  }
1286  connection.send_message(
1287  websocket_api.event_message(
1288  msg[ID], {"event": "node added", "node": node_details}
1289  )
1290  )
1291 
1292  @callback
1293  def node_removed(event: dict) -> None:
1294  node = event["node"]
1295  node_details = {
1296  "node_id": node.node_id,
1297  }
1298 
1299  connection.send_message(
1300  websocket_api.event_message(
1301  msg[ID], {"event": "node removed", "node": node_details}
1302  )
1303  )
1304 
1305  @callback
1306  def device_registered(device: dr.DeviceEntry) -> None:
1307  device_details = {
1308  "name": device.name,
1309  "id": device.id,
1310  "manufacturer": device.manufacturer,
1311  "model": device.model,
1312  }
1313  connection.send_message(
1314  websocket_api.event_message(
1315  msg[ID], {"event": "device registered", "device": device_details}
1316  )
1317  )
1318 
1319  connection.subscriptions[msg["id"]] = async_cleanup
1320  unsubs: list[Callable[[], None]] = [
1321  controller.on("inclusion started", forward_event),
1322  controller.on("inclusion failed", forward_event),
1323  controller.on("inclusion stopped", forward_event),
1324  controller.on("validate dsk and enter pin", forward_dsk),
1325  controller.on("grant security classes", forward_requested_grant),
1326  controller.on("node removed", node_removed),
1327  controller.on("node found", node_found),
1328  controller.on("node added", node_added),
1330  hass, EVENT_DEVICE_ADDED_TO_REGISTRY, device_registered
1331  ),
1332  ]
1333  msg[DATA_UNSUBSCRIBE] = unsubs
1334 
1335  try:
1336  result = await controller.async_replace_failed_node(
1337  node,
1338  INCLUSION_STRATEGY_NOT_SMART_START[inclusion_strategy.value],
1339  force_security=force_security,
1340  provisioning=provisioning,
1341  )
1342  except ValueError as err:
1343  connection.send_error(
1344  msg[ID],
1345  ERR_INVALID_FORMAT,
1346  err.args[0],
1347  )
1348  return
1349 
1350  connection.send_result(
1351  msg[ID],
1352  result,
1353  )
1354 
1355 
1356 @websocket_api.require_admin
1357 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/remove_failed_node",
1358  vol.Required(DEVICE_ID): str,
1359  }
1360 )
1361 @websocket_api.async_response
1362 @async_handle_failed_command
1363 @async_get_node
1365  hass: HomeAssistant,
1366  connection: ActiveConnection,
1367  msg: dict[str, Any],
1368  node: Node,
1369 ) -> None:
1370  """Remove a failed node from the Z-Wave network."""
1371  driver = node.client.driver
1372  assert driver is not None # The node comes from the driver instance.
1373  controller = driver.controller
1374 
1375  @callback
1376  def async_cleanup() -> None:
1377  """Remove signal listeners."""
1378  for unsub in unsubs:
1379  unsub()
1380 
1381  @callback
1382  def node_removed(event: dict) -> None:
1383  node_details = {"node_id": event["node"].node_id}
1384 
1385  connection.send_message(
1386  websocket_api.event_message(
1387  msg[ID], {"event": "node removed", "node": node_details}
1388  )
1389  )
1390 
1391  connection.subscriptions[msg["id"]] = async_cleanup
1392  msg[DATA_UNSUBSCRIBE] = unsubs = [controller.on("node removed", node_removed)]
1393 
1394  await controller.async_remove_failed_node(node)
1395  connection.send_result(msg[ID])
1396 
1397 
1398 @websocket_api.require_admin
1399 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/begin_rebuilding_routes",
1400  vol.Required(ENTRY_ID): str,
1401  }
1403 @websocket_api.async_response
1404 @async_handle_failed_command
1405 @async_get_entry
1407  hass: HomeAssistant,
1408  connection: ActiveConnection,
1409  msg: dict[str, Any],
1410  entry: ConfigEntry,
1411  client: Client,
1412  driver: Driver,
1413 ) -> None:
1414  """Begin rebuilding Z-Wave routes."""
1415  controller = driver.controller
1416 
1417  result = await controller.async_begin_rebuilding_routes()
1418  connection.send_result(
1419  msg[ID],
1420  result,
1421  )
1422 
1423 
1424 @websocket_api.require_admin
1425 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_rebuild_routes_progress",
1426  vol.Required(ENTRY_ID): str,
1427  }
1428 )
1429 @websocket_api.async_response
1430 @async_get_entry
1432  hass: HomeAssistant,
1433  connection: ActiveConnection,
1434  msg: dict[str, Any],
1435  entry: ConfigEntry,
1436  client: Client,
1437  driver: Driver,
1438 ) -> None:
1439  """Subscribe to rebuild Z-Wave routes status updates."""
1440  controller = driver.controller
1441 
1442  @callback
1443  def async_cleanup() -> None:
1444  """Remove signal listeners."""
1445  for unsub in unsubs:
1446  unsub()
1447 
1448  @callback
1449  def forward_event(key: str, event: dict) -> None:
1450  connection.send_message(
1451  websocket_api.event_message(
1452  msg[ID], {"event": event["event"], "rebuild_routes_status": event[key]}
1453  )
1454  )
1455 
1456  connection.subscriptions[msg["id"]] = async_cleanup
1457  msg[DATA_UNSUBSCRIBE] = unsubs = [
1458  controller.on("rebuild routes progress", partial(forward_event, "progress")),
1459  controller.on("rebuild routes done", partial(forward_event, "result")),
1460  ]
1461 
1462  if controller.rebuild_routes_progress:
1463  connection.send_result(
1464  msg[ID],
1465  {
1466  node.node_id: status
1467  for node, status in controller.rebuild_routes_progress.items()
1468  },
1469  )
1470  else:
1471  connection.send_result(msg[ID], None)
1472 
1474 @websocket_api.require_admin
1475 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/stop_rebuilding_routes",
1476  vol.Required(ENTRY_ID): str,
1477  }
1478 )
1479 @websocket_api.async_response
1480 @async_handle_failed_command
1481 @async_get_entry
1483  hass: HomeAssistant,
1484  connection: ActiveConnection,
1485  msg: dict[str, Any],
1486  entry: ConfigEntry,
1487  client: Client,
1488  driver: Driver,
1489 ) -> None:
1490  """Stop rebuilding Z-Wave routes."""
1491  controller = driver.controller
1492  result = await controller.async_stop_rebuilding_routes()
1493  connection.send_result(
1494  msg[ID],
1495  result,
1496  )
1497 
1498 
1499 @websocket_api.require_admin
1500 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/rebuild_node_routes",
1501  vol.Required(DEVICE_ID): str,
1502  }
1503 )
1504 @websocket_api.async_response
1505 @async_handle_failed_command
1506 @async_get_node
1508  hass: HomeAssistant,
1509  connection: ActiveConnection,
1510  msg: dict[str, Any],
1511  node: Node,
1512 ) -> None:
1513  """Heal a node on the Z-Wave network."""
1514  driver = node.client.driver
1515  assert driver is not None # The node comes from the driver instance.
1516  controller = driver.controller
1517 
1518  result = await controller.async_rebuild_node_routes(node)
1519  connection.send_result(
1520  msg[ID],
1521  result,
1522  )
1523 
1524 
1525 @websocket_api.require_admin
1526 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_info",
1527  vol.Required(DEVICE_ID): str,
1528  },
1529 )
1530 @websocket_api.async_response
1531 @async_handle_failed_command
1532 @async_get_node
1533 async def websocket_refresh_node_info(
1534  hass: HomeAssistant,
1535  connection: ActiveConnection,
1536  msg: dict[str, Any],
1537  node: Node,
1538 ) -> None:
1539  """Re-interview a node."""
1540 
1541  @callback
1542  def async_cleanup() -> None:
1543  """Remove signal listeners."""
1544  for unsub in unsubs:
1545  unsub()
1546 
1547  @callback
1548  def forward_event(event: dict) -> None:
1549  connection.send_message(
1550  websocket_api.event_message(msg[ID], {"event": event["event"]})
1551  )
1552 
1553  @callback
1554  def forward_stage(event: dict) -> None:
1555  connection.send_message(
1556  websocket_api.event_message(
1557  msg[ID], {"event": event["event"], "stage": event["stageName"]}
1558  )
1559  )
1560 
1561  connection.subscriptions[msg["id"]] = async_cleanup
1562  msg[DATA_UNSUBSCRIBE] = unsubs = [
1563  node.on("interview started", forward_event),
1564  node.on("interview completed", forward_event),
1565  node.on("interview stage completed", forward_stage),
1566  node.on("interview failed", forward_event),
1567  ]
1568 
1569  await node.async_refresh_info()
1570  connection.send_result(msg[ID])
1571 
1572 
1573 @websocket_api.require_admin
1574 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_values",
1575  vol.Required(DEVICE_ID): str,
1576  },
1577 )
1578 @websocket_api.async_response
1579 @async_handle_failed_command
1580 @async_get_node
1582  hass: HomeAssistant,
1583  connection: ActiveConnection,
1584  msg: dict[str, Any],
1585  node: Node,
1586 ) -> None:
1587  """Refresh node values."""
1588  await node.async_refresh_values()
1589  connection.send_result(msg[ID])
1590 
1591 
1592 @websocket_api.require_admin
1593 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/refresh_node_cc_values",
1594  vol.Required(DEVICE_ID): str,
1595  vol.Required(COMMAND_CLASS_ID): int,
1596  },
1597 )
1598 @websocket_api.async_response
1599 @async_handle_failed_command
1600 @async_get_node
1602  hass: HomeAssistant,
1603  connection: ActiveConnection,
1604  msg: dict[str, Any],
1605  node: Node,
1606 ) -> None:
1607  """Refresh node values for a particular CommandClass."""
1608  command_class_id = msg[COMMAND_CLASS_ID]
1609 
1610  try:
1611  command_class = CommandClass(command_class_id)
1612  except ValueError:
1613  connection.send_error(
1614  msg[ID], ERR_NOT_FOUND, f"Command class {command_class_id} not found"
1615  )
1616  return
1617 
1618  await node.async_refresh_cc_values(command_class)
1619  connection.send_result(msg[ID])
1620 
1621 
1622 @websocket_api.require_admin
1623 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/set_config_parameter",
1624  vol.Required(DEVICE_ID): str,
1625  vol.Required(PROPERTY): int,
1626  vol.Optional(ENDPOINT, default=0): int,
1627  vol.Optional(PROPERTY_KEY): int,
1628  vol.Required(VALUE): vol.Any(int, BITMASK_SCHEMA),
1629  }
1630 )
1631 @websocket_api.async_response
1632 @async_handle_failed_command
1633 @async_get_node
1635  hass: HomeAssistant,
1636  connection: ActiveConnection,
1637  msg: dict[str, Any],
1638  node: Node,
1639 ) -> None:
1640  """Set a config parameter value for a Z-Wave node."""
1641  property_ = msg[PROPERTY]
1642  endpoint = msg[ENDPOINT]
1643  property_key = msg.get(PROPERTY_KEY)
1644  value = msg[VALUE]
1645 
1646  try:
1647  zwave_value, cmd_status = await async_set_config_parameter(
1648  node, value, property_, property_key=property_key, endpoint=endpoint
1649  )
1650  except (InvalidNewValue, NotFoundError, NotImplementedError, SetValueFailed) as err:
1651  code = ERR_UNKNOWN_ERROR
1652  if isinstance(err, NotFoundError):
1653  code = ERR_NOT_FOUND
1654  elif isinstance(err, (InvalidNewValue, NotImplementedError)):
1655  code = ERR_NOT_SUPPORTED
1656 
1657  connection.send_error(
1658  msg[ID],
1659  code,
1660  str(err),
1661  )
1662  return
1663 
1664  connection.send_result(
1665  msg[ID],
1666  {
1667  VALUE_ID: zwave_value.value_id,
1668  STATUS: cmd_status.status,
1669  },
1670  )
1671 
1672 
1673 @websocket_api.require_admin
1674 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_config_parameters",
1675  vol.Required(DEVICE_ID): str,
1676  }
1677 )
1678 @websocket_api.async_response
1679 @async_get_node
1681  hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any], node: Node
1682 ) -> None:
1683  """Get a list of configuration parameters for a Z-Wave node."""
1684  values = node.get_configuration_values()
1685  result: dict[str, Any] = {}
1686  for value_id, zwave_value in values.items():
1687  metadata = zwave_value.metadata
1688  result[value_id] = {
1689  "property": zwave_value.property_,
1690  "property_key": zwave_value.property_key,
1691  "endpoint": zwave_value.endpoint,
1692  "configuration_value_type": zwave_value.configuration_value_type.value,
1693  "metadata": {
1694  "description": metadata.description,
1695  "label": metadata.label,
1696  "type": metadata.type,
1697  "min": metadata.min,
1698  "max": metadata.max,
1699  "unit": metadata.unit,
1700  "writeable": metadata.writeable,
1701  "readable": metadata.readable,
1702  "default": metadata.default,
1703  },
1704  "value": zwave_value.value,
1705  }
1706  if zwave_value.metadata.states:
1707  result[value_id]["metadata"]["states"] = zwave_value.metadata.states
1708 
1709  connection.send_result(
1710  msg[ID],
1711  result,
1712  )
1713 
1714 
1715 @websocket_api.require_admin
1716 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/set_raw_config_parameter",
1717  vol.Required(DEVICE_ID): str,
1718  vol.Required(PROPERTY): int,
1719  vol.Required(VALUE): int,
1720  vol.Required(VALUE_SIZE): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
1721  vol.Required(VALUE_FORMAT): vol.Coerce(ConfigurationValueFormat),
1722  }
1723 )
1724 @websocket_api.async_response
1725 @async_handle_failed_command
1726 @async_get_node
1728  hass: HomeAssistant,
1729  connection: ActiveConnection,
1730  msg: dict[str, Any],
1731  node: Node,
1732 ) -> None:
1733  """Set a custom config parameter value for a Z-Wave node."""
1734  result = await node.async_set_raw_config_parameter_value(
1735  msg[VALUE],
1736  msg[PROPERTY],
1737  value_size=msg[VALUE_SIZE],
1738  value_format=msg[VALUE_FORMAT],
1739  )
1740 
1741  connection.send_result(
1742  msg[ID],
1743  {
1744  STATUS: result.status,
1745  },
1746  )
1747 
1748 
1749 @websocket_api.require_admin
1750 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_raw_config_parameter",
1751  vol.Required(DEVICE_ID): str,
1752  vol.Required(PROPERTY): int,
1753  }
1754 )
1755 @websocket_api.async_response
1756 @async_handle_failed_command
1757 @async_get_node
1759  hass: HomeAssistant,
1760  connection: ActiveConnection,
1761  msg: dict[str, Any],
1762  node: Node,
1763 ) -> None:
1764  """Get a custom config parameter value for a Z-Wave node."""
1765  value = await node.async_get_raw_config_parameter_value(
1766  msg[PROPERTY],
1767  )
1768 
1769  connection.send_result(
1770  msg[ID],
1771  {
1772  VALUE: value,
1773  },
1774  )
1775 
1776 
1777 def filename_is_present_if_logging_to_file(obj: dict) -> dict:
1778  """Validate that filename is provided if log_to_file is True."""
1779  if obj.get(LOG_TO_FILE, False) and FILENAME not in obj:
1780  raise vol.Invalid("`filename` must be provided if logging to file")
1781  return obj
1782 
1783 
1784 @websocket_api.require_admin
1785 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_log_updates",
1786  vol.Required(ENTRY_ID): str,
1787  }
1788 )
1789 @websocket_api.async_response
1790 @async_handle_failed_command
1791 @async_get_entry
1793  hass: HomeAssistant,
1794  connection: ActiveConnection,
1795  msg: dict[str, Any],
1796  entry: ConfigEntry,
1797  client: Client,
1798  driver: Driver,
1799 ) -> None:
1800  """Subscribe to log message events from the server."""
1801 
1802  @callback
1803  def async_cleanup() -> None:
1804  """Remove signal listeners."""
1805  hass.async_create_task(client.async_stop_listening_logs())
1806  for unsub in unsubs:
1807  unsub()
1808 
1809  @callback
1810  def log_messages(event: dict) -> None:
1811  log_msg: LogMessage = event["log_message"]
1812  connection.send_message(
1813  websocket_api.event_message(
1814  msg[ID],
1815  {
1816  "type": "log_message",
1817  "log_message": {
1818  "timestamp": log_msg.timestamp,
1819  "level": log_msg.level,
1820  "primary_tags": log_msg.primary_tags,
1821  "message": log_msg.formatted_message,
1822  },
1823  },
1824  )
1825  )
1826 
1827  @callback
1828  def log_config_updates(event: dict) -> None:
1829  log_config: LogConfig = event["log_config"]
1830  connection.send_message(
1831  websocket_api.event_message(
1832  msg[ID],
1833  {
1834  "type": "log_config",
1835  "log_config": dataclasses.asdict(log_config),
1836  },
1837  )
1838  )
1839 
1840  msg[DATA_UNSUBSCRIBE] = unsubs = [
1841  driver.on("logging", log_messages),
1842  driver.on("log config updated", log_config_updates),
1843  ]
1844  connection.subscriptions[msg["id"]] = async_cleanup
1845 
1846  await client.async_start_listening_logs()
1847  connection.send_result(msg[ID])
1848 
1849 
1850 @websocket_api.require_admin
1851 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/update_log_config",
1852  vol.Required(ENTRY_ID): str,
1853  vol.Required(CONFIG): vol.All(
1854  vol.Schema(
1855  {
1856  vol.Optional(ENABLED): cv.boolean,
1857  vol.Optional(LEVEL): vol.All(
1858  str,
1859  vol.Lower,
1860  vol.Coerce(LogLevel),
1861  ),
1862  vol.Optional(LOG_TO_FILE): cv.boolean,
1863  vol.Optional(FILENAME): str,
1864  vol.Optional(FORCE_CONSOLE): cv.boolean,
1865  }
1866  ),
1867  cv.has_at_least_one_key(
1868  ENABLED, FILENAME, FORCE_CONSOLE, LEVEL, LOG_TO_FILE
1869  ),
1870  filename_is_present_if_logging_to_file,
1871  ),
1872  },
1873 )
1874 @websocket_api.async_response
1875 @async_handle_failed_command
1876 @async_get_entry
1877 async def websocket_update_log_config(
1878  hass: HomeAssistant,
1879  connection: ActiveConnection,
1880  msg: dict[str, Any],
1881  entry: ConfigEntry,
1882  client: Client,
1883  driver: Driver,
1884 ) -> None:
1885  """Update the driver log config."""
1886  await driver.async_update_log_config(LogConfig(**msg[CONFIG]))
1887  connection.send_result(
1888  msg[ID],
1889  )
1890 
1891 
1892 @websocket_api.require_admin
1893 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_log_config",
1894  vol.Required(ENTRY_ID): str,
1895  },
1896 )
1897 @websocket_api.async_response
1898 @async_get_entry
1899 async def websocket_get_log_config(
1900  hass: HomeAssistant,
1901  connection: ActiveConnection,
1902  msg: dict[str, Any],
1903  entry: ConfigEntry,
1904  client: Client,
1905  driver: Driver,
1906 ) -> None:
1907  """Get log configuration for the Z-Wave JS driver."""
1908  assert client and client.driver
1909  connection.send_result(
1910  msg[ID],
1911  dataclasses.asdict(driver.log_config),
1912  )
1913 
1914 
1915 @websocket_api.require_admin
1916 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/update_data_collection_preference",
1917  vol.Required(ENTRY_ID): str,
1918  vol.Required(OPTED_IN): bool,
1919  },
1920 )
1921 @websocket_api.async_response
1922 @async_handle_failed_command
1923 @async_get_entry
1925  hass: HomeAssistant,
1926  connection: ActiveConnection,
1927  msg: dict[str, Any],
1928  entry: ConfigEntry,
1929  client: Client,
1930  driver: Driver,
1931 ) -> None:
1932  """Update preference for data collection and enable/disable collection."""
1933  opted_in = msg[OPTED_IN]
1934  if entry.data.get(CONF_DATA_COLLECTION_OPTED_IN) != opted_in:
1935  new_data = entry.data.copy()
1936  new_data[CONF_DATA_COLLECTION_OPTED_IN] = opted_in
1937  hass.config_entries.async_update_entry(entry, data=new_data)
1938 
1939  if opted_in:
1940  await async_enable_statistics(driver)
1941  else:
1942  await driver.async_disable_statistics()
1943 
1944  connection.send_result(
1945  msg[ID],
1946  )
1947 
1948 
1949 @websocket_api.require_admin
1950 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/data_collection_status",
1951  vol.Required(ENTRY_ID): str,
1952  },
1953 )
1954 @websocket_api.async_response
1955 @async_handle_failed_command
1956 @async_get_entry
1958  hass: HomeAssistant,
1959  connection: ActiveConnection,
1960  msg: dict[str, Any],
1961  entry: ConfigEntry,
1962  client: Client,
1963  driver: Driver,
1964 ) -> None:
1965  """Return data collection preference and status."""
1966  assert client and client.driver
1967  result = {
1968  OPTED_IN: entry.data.get(CONF_DATA_COLLECTION_OPTED_IN),
1969  ENABLED: await driver.async_is_statistics_enabled(),
1970  }
1971  connection.send_result(msg[ID], result)
1972 
1973 
1974 @websocket_api.require_admin
1975 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/abort_firmware_update",
1976  vol.Required(DEVICE_ID): str,
1977  }
1978 )
1979 @websocket_api.async_response
1980 @async_handle_failed_command
1981 @async_get_node
1983  hass: HomeAssistant,
1984  connection: ActiveConnection,
1985  msg: dict[str, Any],
1986  node: Node,
1987 ) -> None:
1988  """Abort a firmware update."""
1989  await node.async_abort_firmware_update()
1990  connection.send_result(msg[ID])
1991 
1993 @websocket_api.require_admin
1994 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/is_node_firmware_update_in_progress",
1995  vol.Required(DEVICE_ID): str,
1996  }
1997 )
1998 @websocket_api.async_response
1999 @async_handle_failed_command
2000 @async_get_node
2002  hass: HomeAssistant,
2003  connection: ActiveConnection,
2004  msg: dict[str, Any],
2005  node: Node,
2006 ) -> None:
2007  """Get whether firmware update is in progress for given node."""
2008  connection.send_result(msg[ID], await node.async_is_firmware_update_in_progress())
2009 
2010 
2012  progress: NodeFirmwareUpdateProgress,
2013 ) -> dict[str, int | float]:
2014  """Get a dictionary of a node's firmware update progress."""
2015  return {
2016  "current_file": progress.current_file,
2017  "total_files": progress.total_files,
2018  "sent_fragments": progress.sent_fragments,
2019  "total_fragments": progress.total_fragments,
2020  "progress": progress.progress,
2021  }
2022 
2023 
2025  progress: ControllerFirmwareUpdateProgress,
2026 ) -> dict[str, int | float]:
2027  """Get a dictionary of a controller's firmware update progress."""
2028  return {
2029  "current_file": 1,
2030  "total_files": 1,
2031  "sent_fragments": progress.sent_fragments,
2032  "total_fragments": progress.total_fragments,
2033  "progress": progress.progress,
2034  }
2035 
2036 
2037 @websocket_api.require_admin
2038 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_firmware_update_status",
2039  vol.Required(DEVICE_ID): str,
2040  }
2041 )
2042 @websocket_api.async_response
2043 @async_get_node
2045  hass: HomeAssistant,
2046  connection: ActiveConnection,
2047  msg: dict[str, Any],
2048  node: Node,
2049 ) -> None:
2050  """Subscribe to the status of a firmware update."""
2051  assert node.client.driver
2052  controller = node.client.driver.controller
2053 
2054  @callback
2055  def async_cleanup() -> None:
2056  """Remove signal listeners."""
2057  for unsub in unsubs:
2058  unsub()
2059 
2060  @callback
2061  def forward_node_progress(event: dict) -> None:
2062  progress: NodeFirmwareUpdateProgress = event["firmware_update_progress"]
2063  connection.send_message(
2064  websocket_api.event_message(
2065  msg[ID],
2066  {
2067  "event": event["event"],
2069  },
2070  )
2071  )
2072 
2073  @callback
2074  def forward_node_finished(event: dict) -> None:
2075  finished: NodeFirmwareUpdateResult = event["firmware_update_finished"]
2076  connection.send_message(
2077  websocket_api.event_message(
2078  msg[ID],
2079  {
2080  "event": event["event"],
2081  "status": finished.status,
2082  "success": finished.success,
2083  "wait_time": finished.wait_time,
2084  "reinterview": finished.reinterview,
2085  },
2086  )
2087  )
2088 
2089  @callback
2090  def forward_controller_progress(event: dict) -> None:
2091  progress: ControllerFirmwareUpdateProgress = event["firmware_update_progress"]
2092  connection.send_message(
2093  websocket_api.event_message(
2094  msg[ID],
2095  {
2096  "event": event["event"],
2098  },
2099  )
2100  )
2101 
2102  @callback
2103  def forward_controller_finished(event: dict) -> None:
2104  finished: ControllerFirmwareUpdateResult = event["firmware_update_finished"]
2105  connection.send_message(
2106  websocket_api.event_message(
2107  msg[ID],
2108  {
2109  "event": event["event"],
2110  "status": finished.status,
2111  "success": finished.success,
2112  },
2113  )
2114  )
2115 
2116  if controller.own_node == node:
2117  msg[DATA_UNSUBSCRIBE] = unsubs = [
2118  controller.on("firmware update progress", forward_controller_progress),
2119  controller.on("firmware update finished", forward_controller_finished),
2120  ]
2121  else:
2122  msg[DATA_UNSUBSCRIBE] = unsubs = [
2123  node.on("firmware update progress", forward_node_progress),
2124  node.on("firmware update finished", forward_node_finished),
2125  ]
2126  connection.subscriptions[msg["id"]] = async_cleanup
2127 
2128  connection.send_result(msg[ID])
2129  if node.is_controller_node and (
2130  controller_progress := controller.firmware_update_progress
2131  ):
2132  connection.send_message(
2133  websocket_api.event_message(
2134  msg[ID],
2135  {
2136  "event": "firmware update progress",
2138  controller_progress
2139  ),
2140  },
2141  )
2142  )
2143  elif controller.own_node != node and (
2144  node_progress := node.firmware_update_progress
2145  ):
2146  connection.send_message(
2147  websocket_api.event_message(
2148  msg[ID],
2149  {
2150  "event": "firmware update progress",
2151  **_get_node_firmware_update_progress_dict(node_progress),
2152  },
2153  )
2154  )
2155 
2156 
2157 @websocket_api.require_admin
2158 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_node_firmware_update_capabilities",
2159  vol.Required(DEVICE_ID): str,
2160  }
2161 )
2162 @websocket_api.async_response
2163 @async_handle_failed_command
2164 @async_get_node
2166  hass: HomeAssistant,
2167  connection: ActiveConnection,
2168  msg: dict[str, Any],
2169  node: Node,
2170 ) -> None:
2171  """Get a node's firmware update capabilities."""
2172  capabilities = await node.async_get_firmware_update_capabilities()
2173  connection.send_result(msg[ID], capabilities.to_dict())
2174 
2175 
2176 @websocket_api.require_admin
2177 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/is_any_ota_firmware_update_in_progress",
2178  vol.Required(ENTRY_ID): str,
2179  }
2180 )
2181 @websocket_api.async_response
2182 @async_handle_failed_command
2183 @async_get_entry
2185  hass: HomeAssistant,
2186  connection: ActiveConnection,
2187  msg: dict[str, Any],
2188  entry: ConfigEntry,
2189  client: Client,
2190  driver: Driver,
2191 ) -> None:
2192  """Get whether any firmware updates are in progress."""
2193  connection.send_result(
2194  msg[ID], await driver.controller.async_is_any_ota_firmware_update_in_progress()
2195  )
2196 
2197 
2198 class FirmwareUploadView(HomeAssistantView):
2199  """View to upload firmware."""
2200 
2201  url = r"/api/zwave_js/firmware/upload/{device_id}"
2202  name = "api:zwave_js:firmware:upload"
2203 
2204  def __init__(self, dev_reg: dr.DeviceRegistry) -> None:
2205  """Initialize view."""
2206  super().__init__()
2207  self._dev_reg = dev_reg
2208 
2209  @require_admin
2210  async def post(self, request: web.Request, device_id: str) -> web.Response:
2211  """Handle upload."""
2212  hass = request.app[KEY_HASS]
2213 
2214  try:
2215  node = async_get_node_from_device_id(hass, device_id, self._dev_reg)
2216  except ValueError as err:
2217  if "not loaded" in err.args[0]:
2218  raise web_exceptions.HTTPBadRequest from err
2219  raise web_exceptions.HTTPNotFound from err
2220 
2221  # If this was not true, we wouldn't have been able to get the node from the
2222  # device ID above
2223  assert node.client.driver
2224 
2225  # Increase max payload
2226  request._client_max_size = 1024 * 1024 * 10 # noqa: SLF001
2227 
2228  data = await request.post()
2229 
2230  if "file" not in data or not isinstance(data["file"], web_request.FileField):
2231  raise web_exceptions.HTTPBadRequest
2232 
2233  uploaded_file: web_request.FileField = data["file"]
2234 
2235  try:
2236  if node.client.driver.controller.own_node == node:
2237  await controller_firmware_update_otw(
2238  node.client.ws_server_url,
2239  ControllerFirmwareUpdateData(
2240  uploaded_file.filename,
2241  await hass.async_add_executor_job(uploaded_file.file.read),
2242  ),
2244  additional_user_agent_components=USER_AGENT,
2245  )
2246  else:
2247  firmware_target: int | None = None
2248  if "target" in data:
2249  firmware_target = int(cast(str, data["target"]))
2250  await update_firmware(
2251  node.client.ws_server_url,
2252  node,
2253  [
2254  NodeFirmwareUpdateData(
2255  uploaded_file.filename,
2256  await hass.async_add_executor_job(uploaded_file.file.read),
2257  firmware_target=firmware_target,
2258  )
2259  ],
2261  additional_user_agent_components=USER_AGENT,
2262  )
2263  except BaseZwaveJSServerError as err:
2264  raise web_exceptions.HTTPBadRequest(reason=str(err)) from err
2265 
2266  return self.json(None)
2267 
2268 
2269 @websocket_api.require_admin
2270 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/check_for_config_updates",
2271  vol.Required(ENTRY_ID): str,
2272  }
2273 )
2274 @websocket_api.async_response
2275 @async_handle_failed_command
2276 @async_get_entry
2278  hass: HomeAssistant,
2279  connection: ActiveConnection,
2280  msg: dict[str, Any],
2281  entry: ConfigEntry,
2282  client: Client,
2283  driver: Driver,
2284 ) -> None:
2285  """Check for config updates."""
2286  config_update = await driver.async_check_for_config_updates()
2287  connection.send_result(
2288  msg[ID],
2289  {
2290  "update_available": config_update.update_available,
2291  "new_version": config_update.new_version,
2292  },
2293  )
2294 
2295 
2296 @websocket_api.require_admin
2297 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/install_config_update",
2298  vol.Required(ENTRY_ID): str,
2299  }
2300 )
2301 @websocket_api.async_response
2302 @async_handle_failed_command
2303 @async_get_entry
2305  hass: HomeAssistant,
2306  connection: ActiveConnection,
2307  msg: dict[str, Any],
2308  entry: ConfigEntry,
2309  client: Client,
2310  driver: Driver,
2311 ) -> None:
2312  """Check for config updates."""
2313  success = await driver.async_install_config_update()
2314  connection.send_result(msg[ID], success)
2315 
2316 
2318  statistics: ControllerStatistics,
2319 ) -> dict[str, int]:
2320  """Get dictionary of controller statistics."""
2321  return {
2322  "messages_tx": statistics.messages_tx,
2323  "messages_rx": statistics.messages_rx,
2324  "messages_dropped_tx": statistics.messages_dropped_tx,
2325  "messages_dropped_rx": statistics.messages_dropped_rx,
2326  "nak": statistics.nak,
2327  "can": statistics.can,
2328  "timeout_ack": statistics.timeout_ack,
2329  "timout_response": statistics.timeout_response,
2330  "timeout_callback": statistics.timeout_callback,
2331  }
2332 
2333 
2334 @websocket_api.require_admin
2335 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_controller_statistics",
2336  vol.Required(ENTRY_ID): str,
2337  }
2338 )
2339 @websocket_api.async_response
2340 @async_get_entry
2342  hass: HomeAssistant,
2343  connection: ActiveConnection,
2344  msg: dict[str, Any],
2345  entry: ConfigEntry,
2346  client: Client,
2347  driver: Driver,
2348 ) -> None:
2349  """Subscribe to the statistics updates for a controller."""
2350 
2351  @callback
2352  def async_cleanup() -> None:
2353  """Remove signal listeners."""
2354  for unsub in unsubs:
2355  unsub()
2356 
2357  @callback
2358  def forward_stats(event: dict) -> None:
2359  statistics: ControllerStatistics = event["statistics_updated"]
2360  connection.send_message(
2361  websocket_api.event_message(
2362  msg[ID],
2363  {
2364  "event": event["event"],
2365  "source": "controller",
2366  **_get_controller_statistics_dict(statistics),
2367  },
2368  )
2369  )
2370 
2371  controller = driver.controller
2372 
2373  msg[DATA_UNSUBSCRIBE] = unsubs = [
2374  controller.on("statistics updated", forward_stats)
2375  ]
2376  connection.subscriptions[msg["id"]] = async_cleanup
2377 
2378  connection.send_result(msg[ID])
2379  connection.send_message(
2380  websocket_api.event_message(
2381  msg[ID],
2382  {
2383  "event": "statistics updated",
2384  "source": "controller",
2385  **_get_controller_statistics_dict(controller.statistics),
2386  },
2387  )
2388  )
2389 
2390 
2392  hass: HomeAssistant, statistics: NodeStatistics
2393 ) -> dict[str, Any]:
2394  """Get dictionary of node statistics."""
2395  dev_reg = dr.async_get(hass)
2396 
2397  def _convert_node_to_device_id(node: Node) -> str:
2398  """Convert a node to a device id."""
2399  driver = node.client.driver
2400  assert driver
2401  device = dev_reg.async_get_device(identifiers={get_device_id(driver, node)})
2402  assert device
2403  return device.id
2404 
2405  data: dict = {
2406  "commands_tx": statistics.commands_tx,
2407  "commands_rx": statistics.commands_rx,
2408  "commands_dropped_tx": statistics.commands_dropped_tx,
2409  "commands_dropped_rx": statistics.commands_dropped_rx,
2410  "timeout_response": statistics.timeout_response,
2411  "rtt": statistics.rtt,
2412  "rssi": statistics.rssi,
2413  "lwr": statistics.lwr.as_dict() if statistics.lwr else None,
2414  "nlwr": statistics.nlwr.as_dict() if statistics.nlwr else None,
2415  }
2416  for key in ("lwr", "nlwr"):
2417  if not data[key]:
2418  continue
2419  for key_2 in ("repeaters", "route_failed_between"):
2420  if not data[key][key_2]:
2421  continue
2422  data[key][key_2] = [
2423  _convert_node_to_device_id(node) for node in data[key][key_2]
2424  ]
2425 
2426  return data
2428 
2429 @websocket_api.require_admin
2430 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/subscribe_node_statistics",
2431  vol.Required(DEVICE_ID): str,
2432  }
2433 )
2434 @websocket_api.async_response
2435 @async_get_node
2437  hass: HomeAssistant,
2438  connection: ActiveConnection,
2439  msg: dict[str, Any],
2440  node: Node,
2441 ) -> None:
2442  """Subscribe to the statistics updates for a node."""
2443 
2444  @callback
2445  def async_cleanup() -> None:
2446  """Remove signal listeners."""
2447  for unsub in unsubs:
2448  unsub()
2449 
2450  @callback
2451  def forward_stats(event: dict) -> None:
2452  statistics: NodeStatistics = event["statistics_updated"]
2453  connection.send_message(
2454  websocket_api.event_message(
2455  msg[ID],
2456  {
2457  "event": event["event"],
2458  "source": "node",
2459  "node_id": node.node_id,
2460  **_get_node_statistics_dict(hass, statistics),
2461  },
2462  )
2463  )
2464 
2465  msg[DATA_UNSUBSCRIBE] = unsubs = [node.on("statistics updated", forward_stats)]
2466  connection.subscriptions[msg["id"]] = async_cleanup
2467 
2468  connection.send_result(msg[ID])
2469  connection.send_message(
2470  websocket_api.event_message(
2471  msg[ID],
2472  {
2473  "event": "statistics updated",
2474  "source": "node",
2475  "nodeId": node.node_id,
2476  **_get_node_statistics_dict(hass, node.statistics),
2477  },
2478  )
2479  )
2480 
2481 
2482 @websocket_api.require_admin
2483 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/hard_reset_controller",
2484  vol.Required(ENTRY_ID): str,
2485  }
2486 )
2487 @websocket_api.async_response
2488 @async_handle_failed_command
2489 @async_get_entry
2491  hass: HomeAssistant,
2492  connection: ActiveConnection,
2493  msg: dict[str, Any],
2494  entry: ConfigEntry,
2495  client: Client,
2496  driver: Driver,
2497 ) -> None:
2498  """Hard reset controller."""
2499 
2500  @callback
2501  def async_cleanup() -> None:
2502  """Remove signal listeners."""
2503  for unsub in unsubs:
2504  unsub()
2505  unsubs.clear()
2506 
2507  @callback
2508  def _handle_device_added(device: dr.DeviceEntry) -> None:
2509  """Handle device is added."""
2510  if entry.entry_id in device.config_entries:
2511  connection.send_result(msg[ID], device.id)
2512  async_cleanup()
2513 
2514  msg[DATA_UNSUBSCRIBE] = unsubs = [
2516  hass, EVENT_DEVICE_ADDED_TO_REGISTRY, _handle_device_added
2517  )
2518  ]
2519  await driver.async_hard_reset()
2520 
2521 
2522 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/node_capabilities",
2523  vol.Required(DEVICE_ID): str,
2524  }
2525 )
2526 @websocket_api.async_response
2527 @async_handle_failed_command
2528 @async_get_node
2529 async def websocket_node_capabilities(
2530  hass: HomeAssistant,
2531  connection: ActiveConnection,
2532  msg: dict[str, Any],
2533  node: Node,
2534 ) -> None:
2535  """Get node endpoints with their support command classes."""
2536  # consumers expect snake_case at the moment
2537  # remove that addition when consumers are updated
2538  connection.send_result(
2539  msg[ID],
2540  {
2541  idx: [
2542  command_class.to_dict() | {"is_secure": command_class.is_secure}
2543  for command_class in endpoint.command_classes
2544  ]
2545  for idx, endpoint in node.endpoints.items()
2546  },
2547  )
2548 
2549 
2550 @websocket_api.require_admin
2551 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/invoke_cc_api",
2552  vol.Required(DEVICE_ID): str,
2553  vol.Required(ATTR_COMMAND_CLASS): vol.All(
2554  vol.Coerce(int), vol.Coerce(CommandClass)
2555  ),
2556  vol.Optional(ATTR_ENDPOINT): vol.Coerce(int),
2557  vol.Required(ATTR_METHOD_NAME): cv.string,
2558  vol.Required(ATTR_PARAMETERS): list,
2559  vol.Optional(ATTR_WAIT_FOR_RESULT): cv.boolean,
2560  }
2561 )
2562 @websocket_api.async_response
2563 @async_handle_failed_command
2564 @async_get_node
2565 async def websocket_invoke_cc_api(
2566  hass: HomeAssistant,
2567  connection: ActiveConnection,
2568  msg: dict[str, Any],
2569  node: Node,
2570 ) -> None:
2571  """Call invokeCCAPI on the node or provided endpoint."""
2572  command_class: CommandClass = msg[ATTR_COMMAND_CLASS]
2573  method_name: str = msg[ATTR_METHOD_NAME]
2574  parameters: list[Any] = msg[ATTR_PARAMETERS]
2575 
2576  node_or_endpoint: Node | Endpoint = node
2577  if (endpoint := msg.get(ATTR_ENDPOINT)) is not None:
2578  node_or_endpoint = node.endpoints[endpoint]
2579 
2580  try:
2581  result = await node_or_endpoint.async_invoke_cc_api(
2582  command_class,
2583  method_name,
2584  *parameters,
2585  wait_for_result=msg.get(ATTR_WAIT_FOR_RESULT, False),
2586  )
2587  except BaseZwaveJSServerError as err:
2588  connection.send_error(msg[ID], err.__class__.__name__, str(err))
2589  else:
2590  connection.send_result(
2591  msg[ID],
2592  result,
2593  )
2594 
2595 
2596 @callback
2597 @websocket_api.require_admin
2598 @websocket_api.websocket_command( { vol.Required(TYPE): "zwave_js/get_integration_settings",
2599  }
2600 )
2602  hass: HomeAssistant,
2603  connection: ActiveConnection,
2604  msg: dict[str, Any],
2605 ) -> None:
2606  """Get Z-Wave JS integration wide configuration."""
2607  connection.send_result(
2608  msg[ID],
2609  {
2610  # list explicitly to avoid leaking other keys and to set default
2611  CONF_INSTALLER_MODE: hass.data[DOMAIN].get(CONF_INSTALLER_MODE, False),
2612  },
2613  )
2614 
None __init__(self, _AOSmithCoordinatorT coordinator, str junction_id)
Definition: entity.py:20
web.Response post(self, web.Request request, str config_key)
Definition: view.py:101
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
str get_device_id(ServerInfoMessage server_info, MatterEndpoint endpoint)
Definition: helpers.py:59
None websocket_provision_smart_start_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:922
None websocket_unprovision_smart_start_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:975
None websocket_node_metadata(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:605
None websocket_add_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:682
None async_register_api(HomeAssistant hass)
Definition: api.py:390
None websocket_remove_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1165
None websocket_get_provisioning_entries(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1008
None websocket_get_log_config(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1972
dict[str, int] _get_controller_statistics_dict(ControllerStatistics statistics)
Definition: api.py:2403
None websocket_is_node_firmware_update_in_progress(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2080
dict[str, int|float] _get_node_firmware_update_progress_dict(NodeFirmwareUpdateProgress progress)
Definition: api.py:2087
None websocket_grant_security_classes(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:864
None websocket_subscribe_node_status(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:546
None websocket_subscribe_firmware_update_status(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2125
None websocket_get_integration_settings(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: api.py:2701
None websocket_subscribe_controller_statistics(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2434
dict[str, int|float] _get_controller_firmware_update_progress_dict(ControllerFirmwareUpdateProgress progress)
Definition: api.py:2100
None websocket_remove_failed_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1407
None websocket_node_alerts(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:635
None websocket_subscribe_rebuild_routes_progress(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1480
None websocket_replace_failed_node(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1243
None websocket_install_config_update(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2395
None websocket_update_data_collection_preference(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1999
None websocket_network_status(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg)
Definition: api.py:470
None websocket_try_parse_dsk_from_qr_code_string(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1058
None websocket_set_config_parameter(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1693
Callable[[HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None]] async_get_node(Callable[[HomeAssistant, ActiveConnection, dict[str, Any], Node], Coroutine[Any, Any, None],] orig_func)
Definition: api.py:323
Callable[[HomeAssistant, ActiveConnection, dict[str, Any]], Coroutine[Any, Any, None]] async_get_entry(Callable[[HomeAssistant, ActiveConnection, dict[str, Any], ConfigEntry, Client, Driver], Coroutine[Any, Any, None],] orig_func)
Definition: api.py:281
None websocket_update_log_config(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1948
None websocket_check_for_config_updates(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2366
None websocket_supports_feature(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1084
None websocket_refresh_node_info(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1586
None websocket_subscribe_node_statistics(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2529
None websocket_node_capabilities(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2626
None websocket_stop_inclusion(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1110
None websocket_refresh_node_cc_values(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1658
dict filename_is_present_if_logging_to_file(dict obj)
Definition: api.py:1837
None websocket_get_node_firmware_update_capabilities(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2248
None websocket_rebuild_node_routes(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1558
None websocket_abort_firmware_update(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2059
None websocket_get_raw_config_parameter(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1823
None websocket_data_collection_status(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2034
None websocket_get_config_parameters(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1738
tuple[ConfigEntry, Client, Driver]|tuple[None, None, None] _async_get_entry(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, str entry_id)
Definition: api.py:246
None websocket_set_raw_config_parameter(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1790
None websocket_stop_rebuilding_routes(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1533
Node|None _async_get_node(HomeAssistant hass, ActiveConnection connection, dict msg, str device_id)
Definition: api.py:303
None websocket_hard_reset_controller(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2587
None websocket_node_status(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:587
dict[str, Any] node_status(Node node)
Definition: api.py:371
None websocket_invoke_cc_api(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:2664
None websocket_parse_qr_code_string(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1032
None websocket_stop_exclusion(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1137
None websocket_begin_rebuilding_routes(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1453
None websocket_validate_dsk_and_enter_pin(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:892
None websocket_refresh_node_values(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, Node node)
Definition: api.py:1636
None websocket_is_any_ota_firmware_update_in_progress(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:2271
dict[str, Any] _get_node_statistics_dict(HomeAssistant hass, NodeStatistics statistics)
Definition: api.py:2479
None websocket_subscribe_log_updates(HomeAssistant hass, ActiveConnection connection, dict[str, Any] msg, ConfigEntry entry, Client client, Driver driver)
Definition: api.py:1861
None async_enable_statistics(Driver driver)
Definition: helpers.py:134
ZwaveNode async_get_node_from_device_id(HomeAssistant hass, str device_id, dr.DeviceRegistry|None dev_reg=None)
Definition: helpers.py:253
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)
None async_cleanup(HomeAssistant hass, DeviceRegistry dev_reg, entity_registry.EntityRegistry ent_reg)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103