zwave-js-server-python 0.69.0__tar.gz → 0.70.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {zwave_js_server_python-0.69.0/zwave_js_server_python.egg-info → zwave_js_server_python-0.70.0}/PKG-INFO +1 -1
  2. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/__init__.py +13 -13
  3. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/lock.py +2 -2
  4. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/association.py +2 -2
  5. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/controller/__init__.py +160 -10
  6. zwave_js_server_python-0.70.0/zwave_js_server/model/controller/data_model.py +98 -0
  7. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/controller/event_model.py +71 -0
  8. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/controller/inclusion_and_provisioning.py +33 -15
  9. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/controller/rebuild_routes.py +1 -1
  10. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/controller/statistics.py +25 -25
  11. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/device_class.py +1 -1
  12. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/driver/__init__.py +130 -3
  13. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/driver/firmware.py +3 -3
  14. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/duration.py +5 -5
  15. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/endpoint.py +5 -3
  16. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/firmware.py +11 -11
  17. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/log_config.py +1 -1
  18. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/log_message.py +28 -24
  19. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/__init__.py +40 -2
  20. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/data_model.py +7 -0
  21. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/event_model.py +18 -0
  22. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/firmware.py +14 -14
  23. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/health_check.py +42 -26
  24. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/node/statistics.py +10 -10
  25. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/notification.py +39 -27
  26. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/statistics.py +4 -2
  27. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/value.py +93 -11
  28. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/version.py +1 -1
  29. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/lock.py +1 -1
  30. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/multicast.py +4 -1
  31. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0/zwave_js_server_python.egg-info}/PKG-INFO +1 -1
  32. zwave_js_server_python-0.69.0/zwave_js_server/model/controller/data_model.py +0 -36
  33. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/LICENSE +0 -0
  34. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/MANIFEST.in +0 -0
  35. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/README.md +0 -0
  36. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/pyproject.toml +0 -0
  37. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/setup.cfg +0 -0
  38. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/__init__.py +0 -0
  39. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/__main__.py +0 -0
  40. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/client.py +0 -0
  41. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/__init__.py +0 -0
  42. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  43. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/basic.py +0 -0
  44. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/battery.py +0 -0
  45. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/central_scene.py +0 -0
  46. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/color_switch.py +0 -0
  47. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/energy_production.py +0 -0
  48. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/entry_control.py +0 -0
  49. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  50. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/meter.py +0 -0
  51. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  52. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  53. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/notification.py +0 -0
  54. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/power_level.py +0 -0
  55. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/protection.py +0 -0
  56. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/scene_activation.py +0 -0
  57. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  58. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
  59. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/wake_up.py +0 -0
  60. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/const/command_class/window_covering.py +0 -0
  61. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/dump.py +0 -0
  62. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/event.py +0 -0
  63. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/exceptions.py +0 -0
  64. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/firmware.py +0 -0
  65. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/__init__.py +0 -0
  66. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/command_class.py +0 -0
  67. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/config_manager/__init__.py +0 -0
  68. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/device_config.py +0 -0
  69. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/model/utils.py +0 -0
  70. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/py.typed +0 -0
  71. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/__init__.py +0 -0
  72. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/command_class/__init__.py +0 -0
  73. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/command_class/energy_production.py +0 -0
  74. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/command_class/meter.py +0 -0
  75. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  76. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/helpers.py +0 -0
  77. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/util/node.py +0 -0
  78. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server/version.py +0 -0
  79. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server_python.egg-info/SOURCES.txt +0 -0
  80. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  81. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  82. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server_python.egg-info/requires.txt +0 -0
  83. {zwave_js_server_python-0.69.0 → zwave_js_server_python-0.70.0}/zwave_js_server_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zwave-js-server-python
3
- Version: 0.69.0
3
+ Version: 0.70.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Author-email: Home Assistant Team <hello@home-assistant.io>
6
6
  License-Expression: Apache-2.0
@@ -8,12 +8,12 @@ import logging
8
8
  from typing import TypedDict
9
9
 
10
10
  PACKAGE_NAME = "zwave-js-server-python"
11
- __version__ = "0.69.0"
11
+ __version__ = "0.70.0"
12
12
 
13
13
  # minimal server schema version we can handle
14
- MIN_SERVER_SCHEMA_VERSION = 44
14
+ MIN_SERVER_SCHEMA_VERSION = 47
15
15
  # max server schema version we can handle (and our code is compatible with)
16
- MAX_SERVER_SCHEMA_VERSION = 45
16
+ MAX_SERVER_SCHEMA_VERSION = 47
17
17
 
18
18
  VALUE_UNKNOWN = "unknown"
19
19
 
@@ -457,7 +457,7 @@ class DateAndTimeDataType(TypedDict, total=False):
457
457
  standardOffset: int
458
458
 
459
459
 
460
- @dataclass
460
+ @dataclass(frozen=True)
461
461
  class DateAndTime:
462
462
  """Represent a date and time."""
463
463
 
@@ -474,16 +474,16 @@ class DateAndTime:
474
474
 
475
475
  def __post_init__(self) -> None:
476
476
  """Post initialization."""
477
- self.hour = self.data.get("hour")
478
- self.minute = self.data.get("minute")
477
+ object.__setattr__(self, "hour", self.data.get("hour"))
478
+ object.__setattr__(self, "minute", self.data.get("minute"))
479
479
  if weekday := self.data.get("weekday"):
480
- self.weekday = Weekday(weekday)
481
- self.second = self.data.get("second")
482
- self.year = self.data.get("year")
483
- self.month = self.data.get("month")
484
- self.day = self.data.get("day")
485
- self.dst_offset = self.data.get("dstOffset")
486
- self.standard_offset = self.data.get("standardOffset")
480
+ object.__setattr__(self, "weekday", Weekday(weekday))
481
+ object.__setattr__(self, "second", self.data.get("second"))
482
+ object.__setattr__(self, "year", self.data.get("year"))
483
+ object.__setattr__(self, "month", self.data.get("month"))
484
+ object.__setattr__(self, "day", self.data.get("day"))
485
+ object.__setattr__(self, "dst_offset", self.data.get("dstOffset"))
486
+ object.__setattr__(self, "standard_offset", self.data.get("standardOffset"))
487
487
 
488
488
 
489
489
  class ControllerStatus(IntEnum):
@@ -151,7 +151,7 @@ class DoorLockCCConfigurationSetOptionsDataType(TypedDict, total=False):
151
151
  DEFAULT_HANDLE_CONFIGURATION = [True, True, True, True]
152
152
 
153
153
 
154
- @dataclass
154
+ @dataclass(frozen=True)
155
155
  class DoorLockCCConfigurationSetOptions:
156
156
  """Door Lock CC Configuration Set command options."""
157
157
 
@@ -173,7 +173,7 @@ class DoorLockCCConfigurationSetOptions:
173
173
  "outside_handles_can_open_door_configuration",
174
174
  ):
175
175
  if not getattr(self, attr_name):
176
- setattr(self, attr_name, DEFAULT_HANDLE_CONFIGURATION)
176
+ object.__setattr__(self, attr_name, list(DEFAULT_HANDLE_CONFIGURATION))
177
177
 
178
178
  def to_dict(self) -> DoorLockCCConfigurationSetOptionsDataType:
179
179
  """Convert command options to dict."""
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
  from .node import Node
11
11
 
12
12
 
13
- @dataclass
13
+ @dataclass(frozen=True)
14
14
  class AssociationGroup:
15
15
  """Represent a association group dict type."""
16
16
 
@@ -22,7 +22,7 @@ class AssociationGroup:
22
22
  issued_commands: dict[int, list[int]] = field(default_factory=dict)
23
23
 
24
24
 
25
- @dataclass
25
+ @dataclass(frozen=True)
26
26
  class AssociationAddress:
27
27
  """Represent a association dict type."""
28
28
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import dataclass
5
+ from functools import cached_property
6
6
  import logging
7
7
  from typing import TYPE_CHECKING, Any, Literal, cast
8
8
 
@@ -26,7 +26,13 @@ from ...util.helpers import convert_base64_to_bytes, convert_bytes_to_base64
26
26
  from ..association import AssociationAddress, AssociationGroup
27
27
  from ..node import Node
28
28
  from ..node.firmware import NodeFirmwareUpdateResult
29
- from .data_model import ControllerDataType
29
+ from .data_model import (
30
+ BackgroundRSSI,
31
+ ControllerDataType,
32
+ NVMProgress,
33
+ ZWaveApiVersionDataType,
34
+ ZWaveChipType,
35
+ )
30
36
  from .event_model import CONTROLLER_EVENT_MODEL_MAP
31
37
  from .inclusion_and_provisioning import (
32
38
  InclusionGrant,
@@ -64,14 +70,6 @@ DEFAULT_CONTROLLER_STATISTICS = ( # pylint: disable=invalid-name
64
70
  )
65
71
 
66
72
 
67
- @dataclass
68
- class NVMProgress:
69
- """Class to represent an NVM backup/restore progress event."""
70
-
71
- bytes_read_or_written: int
72
- total_bytes: int
73
-
74
-
75
73
  class Controller(EventBase):
76
74
  """Represent a Z-Wave JS controller."""
77
75
 
@@ -247,9 +245,37 @@ class Controller(EventBase):
247
245
  """Return whether controller supports long range or not."""
248
246
  return self.data.get("supportsLongRange")
249
247
 
248
+ @property
249
+ def is_sis(self) -> bool | None:
250
+ """Return whether the controller is the SIS (Static Information Source)."""
251
+ return self.data.get("isSIS")
252
+
253
+ @property
254
+ def max_payload_size(self) -> int | None:
255
+ """Return the maximum Z-Wave payload size in bytes."""
256
+ return self.data.get("maxPayloadSize")
257
+
258
+ @property
259
+ def max_payload_size_lr(self) -> int | None:
260
+ """Return the maximum Long Range payload size in bytes."""
261
+ return self.data.get("maxPayloadSizeLR")
262
+
263
+ @property
264
+ def zwave_api_version(self) -> ZWaveApiVersionDataType | None:
265
+ """Return the Z-Wave API version (kind + version) supported by the controller."""
266
+ return self.data.get("zwaveApiVersion")
267
+
268
+ @cached_property
269
+ def zwave_chip_type(self) -> ZWaveChipType | None:
270
+ """Return the Z-Wave chip type."""
271
+ if (raw := self.data.get("zwaveChipType")) is None:
272
+ return None
273
+ return ZWaveChipType.from_dict(raw)
274
+
250
275
  def update(self, data: ControllerDataType) -> None:
251
276
  """Update controller data."""
252
277
  self.data = data
278
+ self.__dict__.pop("zwave_chip_type", None)
253
279
  self._statistics = ControllerStatistics(
254
280
  self.data.get("statistics", DEFAULT_CONTROLLER_STATISTICS)
255
281
  )
@@ -887,6 +913,107 @@ class Controller(EventBase):
887
913
  )
888
914
  return cast(bool, data["progress"])
889
915
 
916
+ async def async_get_background_rssi(self) -> BackgroundRSSI:
917
+ """Send getBackgroundRSSI command to Controller."""
918
+ data = await self.client.async_send_command(
919
+ {"command": "controller.get_background_rssi"},
920
+ require_schema=47,
921
+ )
922
+ return BackgroundRSSI(
923
+ channel_0=data["rssiChannel0"],
924
+ channel_1=data["rssiChannel1"],
925
+ channel_2=data.get("rssiChannel2"),
926
+ channel_3=data.get("rssiChannel3"),
927
+ )
928
+
929
+ async def async_get_long_range_nodes(self) -> list[Node]:
930
+ """Send getLongRangeNodes command to Controller."""
931
+ data = await self.client.async_send_command(
932
+ {"command": "controller.get_long_range_nodes"},
933
+ require_schema=47,
934
+ )
935
+ return [self.nodes[nid] for nid in data["nodeIds"]]
936
+
937
+ async def async_get_all_association_groups(
938
+ self, node: Node
939
+ ) -> dict[int, dict[int, AssociationGroup]]:
940
+ """Send getAllAssociationGroups command to Controller."""
941
+ data = await self.client.async_send_command(
942
+ {
943
+ "command": "controller.get_all_association_groups",
944
+ "nodeId": node.node_id,
945
+ },
946
+ require_schema=47,
947
+ )
948
+ return {
949
+ int(endpoint_id): {
950
+ int(group_id): AssociationGroup(
951
+ max_nodes=group["maxNodes"],
952
+ is_lifeline=group["isLifeline"],
953
+ multi_channel=group["multiChannel"],
954
+ label=group["label"],
955
+ profile=group.get("profile"),
956
+ issued_commands={
957
+ int(cc): cmds
958
+ for cc, cmds in group.get("issuedCommands", {}).items()
959
+ },
960
+ )
961
+ for group_id, group in groups.items()
962
+ }
963
+ for endpoint_id, groups in data["groups"].items()
964
+ }
965
+
966
+ async def async_get_all_associations(
967
+ self, node: Node
968
+ ) -> dict[int, dict[int, dict[int, list[AssociationAddress]]]]:
969
+ """Send getAllAssociations command to Controller."""
970
+ data = await self.client.async_send_command(
971
+ {
972
+ "command": "controller.get_all_associations",
973
+ "nodeId": node.node_id,
974
+ },
975
+ require_schema=47,
976
+ )
977
+ return {
978
+ int(node_id_str): {
979
+ int(endpoint_id): {
980
+ int(group_id): [
981
+ AssociationAddress(
982
+ self,
983
+ node_id=addr["nodeId"],
984
+ endpoint=addr.get("endpoint"),
985
+ )
986
+ for addr in addresses
987
+ ]
988
+ for group_id, addresses in groups.items()
989
+ }
990
+ for endpoint_id, groups in endpoints.items()
991
+ }
992
+ for node_id_str, endpoints in data["associations"].items()
993
+ }
994
+
995
+ async def async_get_all_available_firmware_updates(
996
+ self,
997
+ api_key: str,
998
+ include_prereleases: bool = True,
999
+ rf_region: RFRegion | None = None,
1000
+ ) -> dict[int, list[NodeFirmwareUpdateInfo]]:
1001
+ """Send getAllAvailableFirmwareUpdates command to Controller."""
1002
+ cmd: dict[str, Any] = {
1003
+ "command": "controller.get_all_available_firmware_updates",
1004
+ "apiKey": api_key,
1005
+ "includePrereleases": include_prereleases,
1006
+ }
1007
+ if rf_region is not None:
1008
+ cmd["rfRegion"] = rf_region.value
1009
+ data = await self.client.async_send_command(cmd, require_schema=47)
1010
+ return {
1011
+ int(node_id): [
1012
+ NodeFirmwareUpdateInfo.from_dict(update) for update in updates
1013
+ ]
1014
+ for node_id, updates in data["updates"].items()
1015
+ }
1016
+
890
1017
  def receive_event(self, event: Event) -> None:
891
1018
  """Receive an event."""
892
1019
  if event.data["source"] == "node":
@@ -1014,3 +1141,26 @@ class Controller(EventBase):
1014
1141
  """Process a status changed event."""
1015
1142
  self.data["status"] = event.data["status"]
1016
1143
  event.data["status"] = ControllerStatus(event.data["status"])
1144
+
1145
+ def handle_network_found(self, _event: Event) -> None:
1146
+ """Process a `network found` event without mutating controller identity."""
1147
+ # TODO: Revisit whether `homeId` / `ownNodeId` from this event should
1148
+ # update `self.data` once upstream semantics are confirmed.
1149
+
1150
+ def handle_network_joined(self, event: Event) -> None:
1151
+ """Process a `network joined` event (schema 47+)."""
1152
+
1153
+ def handle_network_left(self, event: Event) -> None:
1154
+ """Process a `network left` event (schema 47+)."""
1155
+
1156
+ def handle_joining_network_failed(self, event: Event) -> None:
1157
+ """Process a `joining network failed` event (schema 47+)."""
1158
+
1159
+ def handle_joining_network_show_dsk(self, event: Event) -> None:
1160
+ """Process a `joining network show dsk` event (schema 47+)."""
1161
+
1162
+ def handle_joining_network_done(self, event: Event) -> None:
1163
+ """Process a `joining network done` event (schema 47+)."""
1164
+
1165
+ def handle_leaving_network_failed(self, event: Event) -> None:
1166
+ """Process a `leaving network failed` event (schema 47+)."""
@@ -0,0 +1,98 @@
1
+ """Data model for a Z-Wave JS controller."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Literal, TypedDict
7
+
8
+ from .statistics import ControllerStatisticsDataType
9
+
10
+
11
+ class ZWaveApiVersionDataType(TypedDict):
12
+ """Represent a Z-Wave API version (schema 47+)."""
13
+
14
+ kind: Literal["official", "legacy"]
15
+ version: int
16
+
17
+
18
+ class UnknownZWaveChipTypeDataType(TypedDict):
19
+ """Represent an unknown Z-Wave chip type descriptor (schema 47+)."""
20
+
21
+ type: int
22
+ version: int
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class ZWaveChipType:
27
+ """Z-Wave chip type descriptor (schema 47+).
28
+
29
+ For known chips, `name` is set (e.g. ``"ZW0700"``).
30
+ For unknown chips, `type` and `version` are set instead.
31
+ """
32
+
33
+ name: str | None = None
34
+ type: int | None = None
35
+ version: int | None = None
36
+
37
+ @classmethod
38
+ def from_dict(cls, data: str | UnknownZWaveChipTypeDataType) -> ZWaveChipType:
39
+ """Initialize from dict."""
40
+ if isinstance(data, str):
41
+ return cls(name=data)
42
+ return cls(type=data["type"], version=data["version"])
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class NVMProgress:
47
+ """Class to represent an NVM operation progress event.
48
+
49
+ Used for backup, restore, and convert progress updates.
50
+ """
51
+
52
+ bytes_read_or_written: int
53
+ total_bytes: int
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class BackgroundRSSI:
58
+ """Background RSSI noise levels for all channels."""
59
+
60
+ channel_0: int
61
+ channel_1: int
62
+ channel_2: int | None = None
63
+ channel_3: int | None = None
64
+
65
+
66
+ class ControllerDataType(TypedDict, total=False):
67
+ """Represent a controller data dict type."""
68
+
69
+ sdkVersion: str
70
+ type: int
71
+ homeId: int
72
+ ownNodeId: int
73
+ isPrimary: bool
74
+ isSUC: bool
75
+ nodeType: int
76
+ isUsingHomeIdFromOtherNetwork: bool
77
+ isSISPresent: bool
78
+ wasRealPrimary: bool
79
+ firmwareVersion: str
80
+ manufacturerId: int
81
+ productType: int
82
+ productId: int
83
+ supportedFunctionTypes: list[int]
84
+ sucNodeId: int
85
+ supportsTimers: bool
86
+ isRebuildingRoutes: bool
87
+ statistics: ControllerStatisticsDataType
88
+ inclusionState: int
89
+ rfRegion: int
90
+ status: int
91
+ rebuildRoutesProgress: dict[str, str]
92
+ supportsLongRange: bool
93
+ # Schema 47+ properties
94
+ isSIS: bool
95
+ maxPayloadSize: int
96
+ maxPayloadSizeLR: int
97
+ zwaveApiVersion: ZWaveApiVersionDataType
98
+ zwaveChipType: str | UnknownZWaveChipTypeDataType
@@ -303,11 +303,82 @@ class StatusChangedEventModel(BaseControllerEventModel):
303
303
  )
304
304
 
305
305
 
306
+ class NetworkFoundEventModel(BaseControllerEventModel):
307
+ """Model for `network found` event data (schema 47+)."""
308
+
309
+ event: Literal["network found"]
310
+ homeId: int
311
+ ownNodeId: int
312
+
313
+ @classmethod
314
+ def from_dict(cls, data: dict) -> NetworkFoundEventModel:
315
+ """Initialize from dict."""
316
+ return cls(
317
+ source=data["source"],
318
+ event=data["event"],
319
+ homeId=data["homeId"],
320
+ ownNodeId=data["ownNodeId"],
321
+ )
322
+
323
+
324
+ class NetworkJoinedEventModel(BaseControllerEventModel):
325
+ """Model for `network joined` event data (schema 47+)."""
326
+
327
+ event: Literal["network joined"]
328
+
329
+
330
+ class NetworkLeftEventModel(BaseControllerEventModel):
331
+ """Model for `network left` event data (schema 47+)."""
332
+
333
+ event: Literal["network left"]
334
+
335
+
336
+ class JoiningNetworkFailedEventModel(BaseControllerEventModel):
337
+ """Model for `joining network failed` event data (schema 47+)."""
338
+
339
+ event: Literal["joining network failed"]
340
+
341
+
342
+ class LeavingNetworkFailedEventModel(BaseControllerEventModel):
343
+ """Model for `leaving network failed` event data (schema 47+)."""
344
+
345
+ event: Literal["leaving network failed"]
346
+
347
+
348
+ class JoiningNetworkShowDSKEventModel(BaseControllerEventModel):
349
+ """Model for `joining network show dsk` event data (schema 47+)."""
350
+
351
+ event: Literal["joining network show dsk"]
352
+ dsk: str
353
+
354
+ @classmethod
355
+ def from_dict(cls, data: dict) -> JoiningNetworkShowDSKEventModel:
356
+ """Initialize from dict."""
357
+ return cls(
358
+ source=data["source"],
359
+ event=data["event"],
360
+ dsk=data["dsk"],
361
+ )
362
+
363
+
364
+ class JoiningNetworkDoneEventModel(BaseControllerEventModel):
365
+ """Model for `joining network done` event data (schema 47+)."""
366
+
367
+ event: Literal["joining network done"]
368
+
369
+
306
370
  CONTROLLER_EVENT_MODEL_MAP: dict[str, type[BaseControllerEventModel]] = {
307
371
  "exclusion failed": ExclusionFailedEventModel,
308
372
  "exclusion started": ExclusionStartedEventModel,
309
373
  "exclusion stopped": ExclusionStoppedEventModel,
310
374
  "grant security classes": GrantSecurityClassesEventModel,
375
+ "joining network done": JoiningNetworkDoneEventModel,
376
+ "joining network failed": JoiningNetworkFailedEventModel,
377
+ "joining network show dsk": JoiningNetworkShowDSKEventModel,
378
+ "leaving network failed": LeavingNetworkFailedEventModel,
379
+ "network found": NetworkFoundEventModel,
380
+ "network joined": NetworkJoinedEventModel,
381
+ "network left": NetworkLeftEventModel,
311
382
  "rebuild routes done": RebuildRoutesDoneEventModel,
312
383
  "rebuild routes progress": RebuildRoutesProgressEventModel,
313
384
  "identify": IdentifyEventModel,
@@ -16,7 +16,7 @@ class InclusionGrantDataType(TypedDict):
16
16
  clientSideAuth: bool
17
17
 
18
18
 
19
- @dataclass
19
+ @dataclass(frozen=True)
20
20
  class InclusionGrant:
21
21
  """Representation of an inclusion grant."""
22
22
 
@@ -41,7 +41,7 @@ class InclusionGrant:
41
41
  )
42
42
 
43
43
 
44
- @dataclass
44
+ @dataclass(frozen=True)
45
45
  class ProvisioningEntry:
46
46
  """Class to represent the base fields of a provisioning entry."""
47
47
 
@@ -82,19 +82,28 @@ class ProvisioningEntry:
82
82
  for k, v in data.items()
83
83
  if k not in ("dsk", "securityClasses", "requestedSecurityClasses", "status")
84
84
  }:
85
- cls_instance.additional_properties = additional_properties
85
+ object.__setattr__(
86
+ cls_instance, "additional_properties", additional_properties
87
+ )
86
88
  if "requestedSecurityClasses" in data:
87
- cls_instance.requested_security_classes = [
88
- SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
89
- ]
89
+ object.__setattr__(
90
+ cls_instance,
91
+ "requested_security_classes",
92
+ [
93
+ SecurityClass(sec_cls)
94
+ for sec_cls in data["requestedSecurityClasses"]
95
+ ],
96
+ )
90
97
  if "status" in data:
91
- cls_instance.status = ProvisioningEntryStatus(data["status"])
98
+ object.__setattr__(
99
+ cls_instance, "status", ProvisioningEntryStatus(data["status"])
100
+ )
92
101
  if "protocol" in data:
93
- cls_instance.protocol = Protocols(data["protocol"])
102
+ object.__setattr__(cls_instance, "protocol", Protocols(data["protocol"]))
94
103
  return cls_instance
95
104
 
96
105
 
97
- @dataclass
106
+ @dataclass(frozen=True)
98
107
  class QRProvisioningInformationMixin:
99
108
  """Mixin class to represent the base fields of a QR provisioning information."""
100
109
 
@@ -111,7 +120,7 @@ class QRProvisioningInformationMixin:
111
120
  supported_protocols: list[Protocols] | None
112
121
 
113
122
 
114
- @dataclass
123
+ @dataclass(frozen=True)
115
124
  class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixin):
116
125
  """Representation of provisioning information retrieved from a QR code."""
117
126
 
@@ -193,11 +202,20 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
193
202
  "status",
194
203
  )
195
204
  }:
196
- cls_instance.additional_properties = additional_properties
205
+ object.__setattr__(
206
+ cls_instance, "additional_properties", additional_properties
207
+ )
197
208
  if "requestedSecurityClasses" in data:
198
- cls_instance.requested_security_classes = [
199
- SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
200
- ]
209
+ object.__setattr__(
210
+ cls_instance,
211
+ "requested_security_classes",
212
+ [
213
+ SecurityClass(sec_cls)
214
+ for sec_cls in data["requestedSecurityClasses"]
215
+ ],
216
+ )
201
217
  if "status" in data:
202
- cls_instance.status = ProvisioningEntryStatus(data["status"])
218
+ object.__setattr__(
219
+ cls_instance, "status", ProvisioningEntryStatus(data["status"])
220
+ )
203
221
  return cls_instance
@@ -13,7 +13,7 @@ class RebuildRoutesOptionsDataType(TypedDict, total=False):
13
13
  includeSleeping: bool
14
14
 
15
15
 
16
- @dataclass
16
+ @dataclass(frozen=True)
17
17
  class RebuildRoutesOptions:
18
18
  """Represent options for rebuilding routes."""
19
19