zwave-js-server-python 0.36.0__tar.gz → 0.37.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 (75) hide show
  1. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/PKG-INFO +1 -1
  2. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/setup.py +2 -1
  3. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_main.py +1 -1
  4. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/__init__.py +10 -2
  5. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/controller/__init__.py +12 -5
  6. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/controller/event_model.py +7 -0
  7. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/controller/inclusion_and_provisioning.py +37 -4
  8. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/driver.py +1 -0
  9. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/endpoint.py +6 -2
  10. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/node/__init__.py +2 -2
  11. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/node/statistics.py +1 -1
  12. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/statistics.py +32 -3
  13. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/PKG-INFO +1 -1
  14. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/LICENSE +0 -0
  15. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/README.md +0 -0
  16. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/setup.cfg +0 -0
  17. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_client.py +0 -0
  18. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_dump.py +0 -0
  19. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_event.py +0 -0
  20. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_firmware.py +0 -0
  21. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/test/test_version.py +0 -0
  22. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/__init__.py +0 -0
  23. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/__main__.py +0 -0
  24. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/client.py +0 -0
  25. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/__init__.py +0 -0
  26. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  27. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/color_switch.py +0 -0
  28. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/entry_control.py +0 -0
  29. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  30. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/lock.py +0 -0
  31. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/meter.py +0 -0
  32. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  33. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  34. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/notification.py +0 -0
  35. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/power_level.py +0 -0
  36. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/protection.py +0 -0
  37. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  38. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
  39. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/const/command_class/wake_up.py +0 -0
  40. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/dump.py +0 -0
  41. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/event.py +0 -0
  42. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/exceptions.py +0 -0
  43. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/firmware.py +0 -0
  44. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/__init__.py +0 -0
  45. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/association.py +0 -0
  46. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/command_class.py +0 -0
  47. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/controller/data_model.py +0 -0
  48. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/controller/statistics.py +0 -0
  49. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/device_class.py +0 -0
  50. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/device_config.py +0 -0
  51. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/firmware.py +0 -0
  52. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/log_config.py +0 -0
  53. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/log_message.py +0 -0
  54. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/node/data_model.py +0 -0
  55. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/node/event_model.py +0 -0
  56. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/node/health_check.py +0 -0
  57. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/notification.py +0 -0
  58. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/utils.py +0 -0
  59. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/value.py +0 -0
  60. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/model/version.py +0 -0
  61. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/__init__.py +0 -0
  62. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/command_class/__init__.py +0 -0
  63. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/command_class/meter.py +0 -0
  64. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  65. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/helpers.py +0 -0
  66. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/lock.py +0 -0
  67. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/multicast.py +0 -0
  68. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/util/node.py +0 -0
  69. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server/version.py +0 -0
  70. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/SOURCES.txt +0 -0
  71. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  72. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  73. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/not-zip-safe +0 -0
  74. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/requires.txt +0 -0
  75. {zwave-js-server-python-0.36.0 → zwave-js-server-python-0.37.0}/zwave_js_server_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.36.0
3
+ Version: 0.37.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Home-page: https://github.com/home-assistant-libs/zwave-js-server-python
6
6
  Author: Home Assistant Team
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
5
5
 
6
6
  PROJECT_DIR = Path(__file__).parent.resolve()
7
7
  README_FILE = PROJECT_DIR / "README.md"
8
- VERSION = "0.36.0"
8
+ VERSION = "0.37.0"
9
9
 
10
10
 
11
11
  setup(
@@ -19,6 +19,7 @@ setup(
19
19
  long_description=README_FILE.read_text(encoding="utf-8"),
20
20
  long_description_content_type="text/markdown",
21
21
  packages=find_packages(exclude=["test.*", "test"]),
22
+ package_data={"zwave_js_server": ["py.typed"]},
22
23
  python_requires=">=3.8",
23
24
  install_requires=["aiohttp>3", "pydantic>=1.9.0"],
24
25
  entry_points={
@@ -64,7 +64,7 @@ def test_dump_state(
64
64
  assert captured.out == (
65
65
  "{'type': 'version', 'driverVersion': 'test_driver_version', "
66
66
  "'serverVersion': 'test_server_version', 'homeId': 'test_home_id', "
67
- "'minSchemaVersion': 0, 'maxSchemaVersion': 16}\n"
67
+ "'minSchemaVersion': 0, 'maxSchemaVersion': 17}\n"
68
68
  "{'type': 'result', 'success': True, 'result': {}, 'messageId': 'api-schema-id'}\n"
69
69
  "test_result\n"
70
70
  )
@@ -3,9 +3,9 @@ import sys
3
3
  from enum import Enum, IntEnum
4
4
 
5
5
  # minimal server schema version we can handle
6
- MIN_SERVER_SCHEMA_VERSION = 16
6
+ MIN_SERVER_SCHEMA_VERSION = 17
7
7
  # max server schema version we can handle (and our code is compatible with)
8
- MAX_SERVER_SCHEMA_VERSION = 16
8
+ MAX_SERVER_SCHEMA_VERSION = 17
9
9
 
10
10
  TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED = sys.version_info < (3, 9, 2)
11
11
 
@@ -317,3 +317,11 @@ class RssiError(IntEnum):
317
317
  NOT_AVAILABLE = 127
318
318
  RECEIVER_SATURATED = 126
319
319
  NO_SIGNAL_DETECTED = 125
320
+
321
+
322
+ class ProvisioningEntryStatus(IntEnum):
323
+ """Enum for all known provisioning entry statuses."""
324
+
325
+ # https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/controller/Inclusion.ts#L136
326
+ ACTIVE = 0
327
+ INACTIVE = 1
@@ -278,17 +278,19 @@ class Controller(EventBase):
278
278
  )
279
279
 
280
280
  async def async_get_provisioning_entry(
281
- self, dsk: str
281
+ self, dsk_or_node_id: Union[str, int]
282
282
  ) -> Optional[ProvisioningEntry]:
283
283
  """Send getProvisioningEntry command to Controller."""
284
284
  data = await self.client.async_send_command(
285
285
  {
286
286
  "command": "controller.get_provisioning_entry",
287
- "dsk": dsk,
287
+ "dskOrNodeId": dsk_or_node_id,
288
288
  },
289
- require_schema=11,
289
+ require_schema=17,
290
290
  )
291
- return ProvisioningEntry.from_dict(data["entry"])
291
+ if "entry" in data:
292
+ return ProvisioningEntry.from_dict(data["entry"])
293
+ return None
292
294
 
293
295
  async def async_get_provisioning_entries(self) -> List[ProvisioningEntry]:
294
296
  """Send getProvisioningEntries command to Controller."""
@@ -307,7 +309,9 @@ class Controller(EventBase):
307
309
  )
308
310
  return cast(bool, data["success"])
309
311
 
310
- async def async_begin_exclusion(self, unprovision: Optional[bool] = None) -> bool:
312
+ async def async_begin_exclusion(
313
+ self, unprovision: Optional[Union[bool, Literal["inactive"]]] = None
314
+ ) -> bool:
311
315
  """Send beginExclusion command to Controller."""
312
316
  payload: Dict[str, Union[str, bool]] = {"command": "controller.begin_exclusion"}
313
317
  if unprovision is not None:
@@ -766,6 +770,9 @@ class Controller(EventBase):
766
770
  def handle_validate_dsk_and_enter_pin(self, event: Event) -> None:
767
771
  """Process a validate dsk and enter pin event."""
768
772
 
773
+ def handle_inclusion_aborted(self, event: Event) -> None:
774
+ """Process an inclusion aborted event."""
775
+
769
776
  def handle_nvm_backup_progress(self, event: Event) -> None:
770
777
  """Process a nvm backup progress event."""
771
778
  event.data["nvm_backup_progress"] = NVMProgress(
@@ -64,6 +64,12 @@ class HealNetworkProgressEventModel(BaseControllerEventModel):
64
64
  progress: Dict[int, str]
65
65
 
66
66
 
67
+ class InclusionAbortedEventModel(BaseControllerEventModel):
68
+ """Model for `inclusion aborted` event data."""
69
+
70
+ event: Literal["inclusion aborted"]
71
+
72
+
67
73
  class InclusionFailedEventModel(BaseControllerEventModel):
68
74
  """Model for `inclusion failed` event data."""
69
75
 
@@ -147,6 +153,7 @@ CONTROLLER_EVENT_MODEL_MAP: Dict[str, Type["BaseControllerEventModel"]] = {
147
153
  "grant security classes": GrantSecurityClassesEventModel,
148
154
  "heal network done": HealNetworkDoneEventModel,
149
155
  "heal network progress": HealNetworkProgressEventModel,
156
+ "inclusion aborted": InclusionAbortedEventModel,
150
157
  "inclusion failed": InclusionFailedEventModel,
151
158
  "inclusion started": InclusionStartedEventModel,
152
159
  "inclusion stopped": InclusionStoppedEventModel,
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Optional
5
5
  from ...const import (
6
6
  TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED,
7
7
  Protocols,
8
+ ProvisioningEntryStatus,
8
9
  QRCodeVersion,
9
10
  SecurityClass,
10
11
  )
@@ -54,28 +55,46 @@ class ProvisioningEntry:
54
55
 
55
56
  dsk: str
56
57
  security_classes: List[SecurityClass]
58
+ requested_security_classes: Optional[List[SecurityClass]] = None
59
+ status: ProvisioningEntryStatus = ProvisioningEntryStatus.ACTIVE
57
60
  additional_properties: Optional[Dict[str, Any]] = None
58
61
 
59
62
  def to_dict(self) -> Dict[str, Any]:
60
63
  """Return PlannedProvisioning data dict from self."""
61
- return {
64
+ data = {
62
65
  "dsk": self.dsk,
63
66
  "securityClasses": [sec_cls.value for sec_cls in self.security_classes],
67
+ "status": self.status.value,
64
68
  **(self.additional_properties or {}),
65
69
  }
70
+ if self.requested_security_classes:
71
+ data["requestedSecurityClasses"] = [
72
+ sec_cls.value for sec_cls in self.requested_security_classes
73
+ ]
74
+ return data
66
75
 
67
76
  @classmethod
68
77
  def from_dict(cls, data: Dict[str, Any]) -> "ProvisioningEntry":
69
78
  """Return ProvisioningEntry from data dict."""
70
- return cls(
79
+ cls_instance = cls(
71
80
  dsk=data["dsk"],
72
81
  security_classes=[
73
82
  SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
74
83
  ],
75
84
  additional_properties={
76
- k: v for k, v in data.items() if k not in {"dsk", "securityClasses"}
85
+ k: v
86
+ for k, v in data.items()
87
+ if k
88
+ not in {"dsk", "securityClasses", "requestedSecurityClasses", "status"}
77
89
  },
78
90
  )
91
+ if "requestedSecurityClasses" in data:
92
+ cls_instance.requested_security_classes = [
93
+ SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
94
+ ]
95
+ if "status" in data:
96
+ cls_instance.status = ProvisioningEntryStatus(data["status"])
97
+ return cls_instance
79
98
 
80
99
 
81
100
  @dataclass
@@ -105,6 +124,7 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
105
124
  "version": self.version.value,
106
125
  "securityClasses": [sec_cls.value for sec_cls in self.security_classes],
107
126
  "dsk": self.dsk,
127
+ "status": self.status.value,
108
128
  "genericDeviceClass": self.generic_device_class,
109
129
  "specificDeviceClass": self.specific_device_class,
110
130
  "installerIconType": self.installer_icon_type,
@@ -114,6 +134,10 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
114
134
  "applicationVersion": self.application_version,
115
135
  **(self.additional_properties or {}),
116
136
  }
137
+ if self.requested_security_classes:
138
+ data["requestedSecurityClasses"] = [
139
+ sec_cls.value for sec_cls in self.requested_security_classes
140
+ ]
117
141
  if self.max_inclusion_request_interval is not None:
118
142
  data["maxInclusionRequestInterval"] = self.max_inclusion_request_interval
119
143
  if self.uuid is not None:
@@ -127,7 +151,7 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
127
151
  @classmethod
128
152
  def from_dict(cls, data: Dict[str, Any]) -> "QRProvisioningInformation":
129
153
  """Return QRProvisioningInformation from data dict."""
130
- return cls(
154
+ cls_instance = cls(
131
155
  version=QRCodeVersion(data["version"]),
132
156
  security_classes=[
133
157
  SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
@@ -153,6 +177,7 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
153
177
  not in {
154
178
  "version",
155
179
  "securityClasses",
180
+ "requestedSecurityClasses",
156
181
  "dsk",
157
182
  "genericDeviceClass",
158
183
  "specificDeviceClass",
@@ -164,6 +189,14 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
164
189
  "maxInclusionRequestInterval",
165
190
  "uuid",
166
191
  "supportedProtocols",
192
+ "status",
167
193
  }
168
194
  },
169
195
  )
196
+ if "requestedSecurityClasses" in data:
197
+ cls_instance.requested_security_classes = [
198
+ SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
199
+ ]
200
+ if "status" in data:
201
+ cls_instance.status = ProvisioningEntryStatus(data["status"])
202
+ return cls_instance
@@ -48,6 +48,7 @@ class CheckConfigUpdates:
48
48
 
49
49
  def __init__(self, data: dict) -> None:
50
50
  """Initialize class."""
51
+ self.installed_version: str = data["installedVersion"]
51
52
  self.update_available: bool = data["updateAvailable"]
52
53
  self.new_version: Optional[str] = data.get("newVersion")
53
54
 
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
8
8
 
9
9
  from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, NodeStatus
10
10
  from ..event import EventBase
11
- from ..exceptions import FailedCommand
11
+ from ..exceptions import FailedCommand, NotFoundError
12
12
  from .command_class import CommandClass, CommandClassInfo, CommandClassInfoDataType
13
13
  from .device_class import DeviceClass, DeviceClassDataType
14
14
  from .value import ConfigurationValue, Value
@@ -160,6 +160,10 @@ class Endpoint(EventBase):
160
160
  wait_for_result: bool = None,
161
161
  ) -> Any:
162
162
  """Call endpoint.invoke_cc_api command."""
163
+ if not any(cc.id == command_class.value for cc in self.command_classes):
164
+ raise NotFoundError(
165
+ f"Command class {command_class} not found on endpoint {self}"
166
+ )
163
167
  result = await self.async_send_command(
164
168
  "invoke_cc_api",
165
169
  commandClass=command_class.value,
@@ -168,7 +172,7 @@ class Endpoint(EventBase):
168
172
  require_schema=7,
169
173
  wait_for_result=wait_for_result,
170
174
  )
171
- if result is None:
175
+ if not result:
172
176
  return None
173
177
  return result["response"]
174
178
 
@@ -71,8 +71,8 @@ class Node(EventBase):
71
71
  super().__init__()
72
72
  self.client = client
73
73
  self.data: NodeDataType = {}
74
- self._device_config: DeviceConfig = DeviceConfig({})
75
- self._statistics: NodeStatistics = NodeStatistics(client)
74
+ self._device_config = DeviceConfig({})
75
+ self._statistics = NodeStatistics(client, data.get("statistics"))
76
76
  self._firmware_update_progress: Optional[FirmwareUpdateProgress] = None
77
77
  self.values: Dict[str, Union[ConfigurationValue, Value]] = {}
78
78
  self.endpoints: Dict[int, Endpoint] = {}
@@ -34,7 +34,7 @@ class NodeStatistics:
34
34
  """Represent a node statistics update."""
35
35
 
36
36
  def __init__(
37
- self, client: "Client", data: Optional[NodeStatisticsDataType] = None
37
+ self, client: "Client", data: Optional[NodeStatisticsDataType]
38
38
  ) -> None:
39
39
  """Initialize node statistics."""
40
40
  self.data = data or NodeStatisticsDataType(
@@ -1,6 +1,6 @@
1
1
  """Common models for statistics."""
2
2
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, List, Optional
3
+ from typing import TYPE_CHECKING, List, Optional, Tuple
4
4
 
5
5
  from zwave_js_server.exceptions import RepeaterRssiErrorReceived, RssiErrorReceived
6
6
 
@@ -26,6 +26,16 @@ class RouteStatisticsDataType(TypedDict, total=False):
26
26
  routeFailedBetween: List[int]
27
27
 
28
28
 
29
+ class RouteStatisticsDict(TypedDict):
30
+ """Represent a route statistics data dict type."""
31
+
32
+ protocol_data_rate: int
33
+ repeaters: List["Node"]
34
+ rssi: Optional[int]
35
+ repeater_rssi: List[int]
36
+ route_failed_between: Optional[Tuple["Node", "Node"]]
37
+
38
+
29
39
  @dataclass
30
40
  class RouteStatistics:
31
41
  """Represent route statistics."""
@@ -69,9 +79,28 @@ class RouteStatistics:
69
79
  return repeater_rssi
70
80
 
71
81
  @property
72
- def route_failed_between(self) -> Optional[List["Node"]]:
82
+ def route_failed_between(self) -> Optional[Tuple["Node", "Node"]]:
73
83
  """Return route failed between."""
74
84
  if (node_ids := self.data.get("routeFailedBetween")) is None:
75
85
  return None
76
86
  assert self.client.driver
77
- return [self.client.driver.controller.nodes[node_id] for node_id in node_ids]
87
+ assert len(node_ids) == 2
88
+ return (
89
+ self.client.driver.controller.nodes[node_ids[0]],
90
+ self.client.driver.controller.nodes[node_ids[1]],
91
+ )
92
+
93
+ def as_dict(self) -> RouteStatisticsDict:
94
+ """Return route statistics as dict."""
95
+ return {
96
+ "protocol_data_rate": self.protocol_data_rate.value,
97
+ "repeaters": self.repeaters,
98
+ "rssi": self.data.get("rssi"),
99
+ "repeater_rssi": self.data.get("repeaterRSSI", []),
100
+ "route_failed_between": (
101
+ self.route_failed_between[0],
102
+ self.route_failed_between[1],
103
+ )
104
+ if self.route_failed_between
105
+ else None,
106
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.36.0
3
+ Version: 0.37.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Home-page: https://github.com/home-assistant-libs/zwave-js-server-python
6
6
  Author: Home Assistant Team