zwave-js-server-python 0.44.0__py3-none-any.whl → 0.45.0__py3-none-any.whl

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.
zwave_js_server/client.py CHANGED
@@ -404,7 +404,7 @@ class Client:
404
404
  "record_type": "event",
405
405
  "ts": datetime.utcnow().isoformat(),
406
406
  "type": msg["event"]["event"],
407
- "event": deepcopy(msg),
407
+ "event_msg": deepcopy(msg),
408
408
  }
409
409
  )
410
410
 
@@ -7,9 +7,9 @@ PACKAGE_NAME = "zwave-js-server-python"
7
7
  __version__ = metadata.version(PACKAGE_NAME)
8
8
 
9
9
  # minimal server schema version we can handle
10
- MIN_SERVER_SCHEMA_VERSION = 24
10
+ MIN_SERVER_SCHEMA_VERSION = 25
11
11
  # max server schema version we can handle (and our code is compatible with)
12
- MAX_SERVER_SCHEMA_VERSION = 24
12
+ MAX_SERVER_SCHEMA_VERSION = 25
13
13
 
14
14
  TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED = sys.version_info < (3, 9, 2)
15
15
 
@@ -16,7 +16,7 @@ async def update_firmware(
16
16
  session: aiohttp.ClientSession,
17
17
  additional_user_agent_components: Optional[Dict[str, str]] = None,
18
18
  ) -> bool:
19
- """Send beginFirmwareUpdate command to Node."""
19
+ """Send updateFirmware command to Node."""
20
20
  client = Client(
21
21
  url, session, additional_user_agent_components=additional_user_agent_components
22
22
  )
@@ -37,3 +37,38 @@ async def update_firmware(
37
37
  receive_task.cancel()
38
38
 
39
39
  return cast(bool, data["success"])
40
+
41
+
42
+ async def controller_firmware_update_otw(
43
+ url: str,
44
+ firmware_file: FirmwareUpdateData,
45
+ session: aiohttp.ClientSession,
46
+ additional_user_agent_components: Optional[Dict[str, str]] = None,
47
+ ) -> bool:
48
+ """
49
+ Send firmwareUpdateOTW command to Controller.
50
+
51
+ Sending the wrong firmware to a controller can brick it and make it unrecoverable.
52
+ Consumers of this library should build mechanisms to ensure that users understand
53
+ the risks.
54
+ """
55
+ client = Client(
56
+ url, session, additional_user_agent_components=additional_user_agent_components
57
+ )
58
+ await client.connect()
59
+ await client.initialize()
60
+
61
+ receive_task = asyncio.get_running_loop().create_task(client.receive_until_closed())
62
+
63
+ data = await client.async_send_command(
64
+ {
65
+ "command": "controller.firmware_update_otw",
66
+ **firmware_file.to_dict(),
67
+ },
68
+ require_schema=25,
69
+ )
70
+ await client.disconnect()
71
+ if not receive_task.done():
72
+ receive_task.cancel()
73
+
74
+ return cast(bool, data["success"])
@@ -2,7 +2,10 @@
2
2
  from dataclasses import dataclass
3
3
  from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast
4
4
 
5
- from zwave_js_server.model.firmware import FirmwareUpdateFileInfo, FirmwareUpdateInfo
5
+ from zwave_js_server.model.node.firmware import (
6
+ NodeFirmwareUpdateFileInfo,
7
+ NodeFirmwareUpdateInfo,
8
+ )
6
9
 
7
10
  from ...const import (
8
11
  MINIMUM_QR_STRING_LENGTH,
@@ -20,6 +23,7 @@ from ..association import AssociationAddress, AssociationGroup
20
23
  from ..node import Node
21
24
  from .data_model import ControllerDataType
22
25
  from .event_model import CONTROLLER_EVENT_MODEL_MAP
26
+ from .firmware import ControllerFirmwareUpdateProgress, ControllerFirmwareUpdateResult
23
27
  from .inclusion_and_provisioning import (
24
28
  InclusionGrant,
25
29
  ProvisioningEntry,
@@ -88,6 +92,13 @@ class Controller(EventBase):
88
92
  """Return own_node_id."""
89
93
  return self.data.get("ownNodeId")
90
94
 
95
+ @property
96
+ def own_node(self) -> Optional[Node]:
97
+ """Return own_node."""
98
+ if self.own_node_id is None:
99
+ return None
100
+ return self.nodes.get(self.own_node_id)
101
+
91
102
  @property
92
103
  def is_primary(self) -> Optional[bool]:
93
104
  """Return is_primary."""
@@ -175,6 +186,13 @@ class Controller(EventBase):
175
186
  """Return inclusion state."""
176
187
  return InclusionState(self.data["inclusionState"])
177
188
 
189
+ @property
190
+ def rf_region(self) -> Optional[RFRegion]:
191
+ """Return RF region of controller."""
192
+ if (rf_region := self.data.get("rfRegion")) is None:
193
+ return None
194
+ return RFRegion(rf_region)
195
+
178
196
  def update(self, data: ControllerDataType) -> None:
179
197
  """Update controller data."""
180
198
  self.data = data
@@ -714,7 +732,7 @@ class Controller(EventBase):
714
732
 
715
733
  async def async_get_available_firmware_updates(
716
734
  self, node: Node, api_key: str, include_prereleases: bool = False
717
- ) -> List[FirmwareUpdateInfo]:
735
+ ) -> List[NodeFirmwareUpdateInfo]:
718
736
  """Send getAvailableFirmwareUpdates command to Controller."""
719
737
  data = await self.client.async_send_command(
720
738
  {
@@ -726,10 +744,10 @@ class Controller(EventBase):
726
744
  require_schema=24,
727
745
  )
728
746
  assert data
729
- return [FirmwareUpdateInfo.from_dict(update) for update in data["updates"]]
747
+ return [NodeFirmwareUpdateInfo.from_dict(update) for update in data["updates"]]
730
748
 
731
749
  async def async_firmware_update_ota(
732
- self, node: Node, updates: List[FirmwareUpdateFileInfo]
750
+ self, node: Node, updates: List[NodeFirmwareUpdateFileInfo]
733
751
  ) -> bool:
734
752
  """Send firmwareUpdateOTA command to Controller."""
735
753
  data = await self.client.async_send_command(
@@ -766,6 +784,18 @@ class Controller(EventBase):
766
784
  event.data["controller"] = self
767
785
  self.emit(event.type, event.data)
768
786
 
787
+ def handle_firmware_update_progress(self, event: Event) -> None:
788
+ """Process a firmware update progress event."""
789
+ event.data["firmware_update_progress"] = ControllerFirmwareUpdateProgress(
790
+ event.data["progress"]
791
+ )
792
+
793
+ def handle_firmware_update_finished(self, event: Event) -> None:
794
+ """Process a firmware update finished event."""
795
+ event.data["firmware_update_finished"] = ControllerFirmwareUpdateResult(
796
+ event.data["result"]
797
+ )
798
+
769
799
  def handle_inclusion_failed(self, event: Event) -> None:
770
800
  """Process an inclusion failed event."""
771
801
 
@@ -33,3 +33,4 @@ class ControllerDataType(TypedDict, total=False):
33
33
  isHealNetworkActive: bool
34
34
  statistics: ControllerStatisticsDataType
35
35
  inclusionState: int
36
+ rfRegion: int
@@ -4,6 +4,7 @@ from typing import Dict, Literal, Type
4
4
  from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
5
5
  from ...event import BaseEventModel
6
6
  from ..node.data_model import FoundNodeDataType, NodeDataType
7
+ from .firmware import ControllerFirmwareUpdateProgressDataType
7
8
  from .inclusion_and_provisioning import InclusionGrantDataType
8
9
  from .statistics import ControllerStatisticsDataType
9
10
 
@@ -43,6 +44,20 @@ class ExclusionStoppedEventModel(BaseControllerEventModel):
43
44
  event: Literal["exclusion stopped"]
44
45
 
45
46
 
47
+ class FirmwareUpdateFinishedEventModel(BaseControllerEventModel):
48
+ """Model for `firmware update finished` event data."""
49
+
50
+ event: Literal["firmware update finished"]
51
+ result: int
52
+
53
+
54
+ class FirmwareUpdateProgressEventModel(BaseControllerEventModel):
55
+ """Model for `firmware update progress` event data."""
56
+
57
+ event: Literal["firmware update progress"]
58
+ progress: ControllerFirmwareUpdateProgressDataType
59
+
60
+
46
61
  class GrantSecurityClassesEventModel(BaseControllerEventModel):
47
62
  """Model for `grant security classes` event data."""
48
63
 
@@ -157,6 +172,8 @@ CONTROLLER_EVENT_MODEL_MAP: Dict[str, Type["BaseControllerEventModel"]] = {
157
172
  "exclusion failed": ExclusionFailedEventModel,
158
173
  "exclusion started": ExclusionStartedEventModel,
159
174
  "exclusion stopped": ExclusionStoppedEventModel,
175
+ "firmware update finished": FirmwareUpdateFinishedEventModel,
176
+ "firmware update progress": FirmwareUpdateProgressEventModel,
160
177
  "grant security classes": GrantSecurityClassesEventModel,
161
178
  "heal network done": HealNetworkDoneEventModel,
162
179
  "heal network progress": HealNetworkProgressEventModel,
@@ -0,0 +1,73 @@
1
+ """Provide a model for Z-Wave controller firmware."""
2
+ from enum import IntEnum
3
+ from typing import TypedDict
4
+
5
+
6
+ class ControllerFirmwareUpdateStatus(IntEnum):
7
+ """Enum with all controller firmware update status values.
8
+
9
+ https://zwave-js.github.io/node-zwave-js/#/api/node?id=status
10
+ """
11
+
12
+ ERROR_TIMEOUT = 0
13
+ # The maximum number of retry attempts for a firmware fragments were reached
14
+ ERROR_RETRY_LIMIT_REACHED = 1
15
+ # The update was aborted by the bootloader
16
+ ERROR_ABORTED = 2
17
+ OK = 255
18
+
19
+
20
+ class ControllerFirmwareUpdateProgressDataType(TypedDict):
21
+ """Represent a controller firmware update progress dict type."""
22
+
23
+ sentFragments: int
24
+ totalFragments: int
25
+ progress: float
26
+
27
+
28
+ class ControllerFirmwareUpdateProgress:
29
+ """Model for a controller firmware update progress data."""
30
+
31
+ def __init__(self, data: ControllerFirmwareUpdateProgressDataType) -> None:
32
+ """Initialize."""
33
+ self.data = data
34
+
35
+ @property
36
+ def sent_fragments(self) -> int:
37
+ """Return the number of fragments sent to the device so far."""
38
+ return self.data["sentFragments"]
39
+
40
+ @property
41
+ def total_fragments(self) -> int:
42
+ """Return the total number of fragments that need to be sent to the device."""
43
+ return self.data["totalFragments"]
44
+
45
+ @property
46
+ def progress(self) -> float:
47
+ """Return progress."""
48
+ return float(self.data["progress"])
49
+
50
+
51
+ class ControllerFirmwareUpdateResultDataType(TypedDict):
52
+ """Represent a controller firmware update result dict type."""
53
+
54
+ status: int
55
+ success: bool
56
+
57
+
58
+ class ControllerFirmwareUpdateResult:
59
+ """Model for controller firmware update result data."""
60
+
61
+ def __init__(self, data: ControllerFirmwareUpdateResultDataType) -> None:
62
+ """Initialize."""
63
+ self.data = data
64
+
65
+ @property
66
+ def status(self) -> ControllerFirmwareUpdateStatus:
67
+ """Return the firmware update status."""
68
+ return ControllerFirmwareUpdateStatus(self.data["status"])
69
+
70
+ @property
71
+ def success(self) -> bool:
72
+ """Return whether the firmware update was successful."""
73
+ return self.data["success"]
@@ -166,6 +166,18 @@ class Driver(EventBase):
166
166
  """Send command to enable Sentry error reporting."""
167
167
  await self._async_send_command("enable_error_reporting", require_schema=16)
168
168
 
169
+ async def async_hard_reset(self) -> None:
170
+ """Send command to hard reset controller."""
171
+ await self._async_send_command("hard_reset", require_schema=25)
172
+
173
+ async def async_try_soft_reset(self) -> None:
174
+ """Send command to try to soft reset controller."""
175
+ await self._async_send_command("try_soft_reset", require_schema=25)
176
+
177
+ async def async_soft_reset(self) -> None:
178
+ """Send command to soft reset controller."""
179
+ await self._async_send_command("soft_reset", require_schema=25)
180
+
169
181
  def handle_logging(self, event: Event) -> None:
170
182
  """Process a driver logging event."""
171
183
  event.data["log_message"] = LogMessage(cast(LogMessageDataType, event.data))
@@ -1,9 +1,8 @@
1
1
  """Provide a model for Z-Wave firmware."""
2
- from dataclasses import asdict, dataclass
3
- from enum import IntEnum
4
- from typing import TYPE_CHECKING, List, Optional, Union, cast
2
+ from dataclasses import dataclass
3
+ from typing import Optional
5
4
 
6
- from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, VALUE_UNKNOWN
5
+ from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
7
6
  from ..util.helpers import convert_bytes_to_base64
8
7
 
9
8
  if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
@@ -11,9 +10,6 @@ if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
11
10
  else:
12
11
  from typing import TypedDict
13
12
 
14
- if TYPE_CHECKING:
15
- from .node import Node
16
-
17
13
 
18
14
  class FirmwareUpdateDataDataType(TypedDict, total=False):
19
15
  """Represent a firmware update data dict type."""
@@ -40,222 +36,3 @@ class FirmwareUpdateData:
40
36
  if self.file_format is not None:
41
37
  data["fileFormat"] = self.file_format
42
38
  return data
43
-
44
-
45
- class FirmwareUpdateCapabilitiesDataType(TypedDict, total=False):
46
- """Represent a firmware update capabilities dict type."""
47
-
48
- firmwareUpgradable: bool # required
49
- firmwareTargets: List[int]
50
- continuesToFunction: Union[bool, str]
51
- supportsActivation: Union[bool, str]
52
-
53
-
54
- class FirmwareUpdateCapabilitiesDict(TypedDict, total=False):
55
- """Represent a dict from FirmwareUpdateCapabilities."""
56
-
57
- firmware_upgradable: bool # required
58
- firmware_targets: List[int]
59
- continues_to_function: Optional[bool]
60
- supports_activation: Optional[bool]
61
-
62
-
63
- class FirmwareUpdateCapabilities:
64
- """Model for firmware update capabilities."""
65
-
66
- def __init__(self, data: FirmwareUpdateCapabilitiesDataType) -> None:
67
- """Initialize class."""
68
- self.data = data
69
-
70
- @property
71
- def firmware_upgradable(self) -> bool:
72
- """Return whether firmware is upgradable."""
73
- return self.data["firmwareUpgradable"]
74
-
75
- @property
76
- def firmware_targets(self) -> List[int]:
77
- """Return firmware targets."""
78
- if not self.firmware_upgradable:
79
- raise TypeError("Firmware is not upgradeable.")
80
- return self.data["firmwareTargets"]
81
-
82
- @property
83
- def continues_to_function(self) -> Optional[bool]:
84
- """Return whether node continues to function during update."""
85
- if not self.firmware_upgradable:
86
- raise TypeError("Firmware is not upgradeable.")
87
- if (val := self.data["continuesToFunction"]) == VALUE_UNKNOWN:
88
- return None
89
- assert isinstance(val, bool)
90
- return val
91
-
92
- @property
93
- def supports_activation(self) -> Optional[bool]:
94
- """Return whether node supports delayed activation of the new firmware."""
95
- if not self.firmware_upgradable:
96
- raise TypeError("Firmware is not upgradeable.")
97
- if (val := self.data["supportsActivation"]) == VALUE_UNKNOWN:
98
- return None
99
- assert isinstance(val, bool)
100
- return val
101
-
102
- def to_dict(self) -> FirmwareUpdateCapabilitiesDict:
103
- """Return dict representation of the object."""
104
- if not self.firmware_upgradable:
105
- return {"firmware_upgradable": self.firmware_upgradable}
106
- return {
107
- "firmware_upgradable": self.firmware_upgradable,
108
- "firmware_targets": self.firmware_targets,
109
- "continues_to_function": self.continues_to_function,
110
- "supports_activation": self.supports_activation,
111
- }
112
-
113
-
114
- class FirmwareUpdateStatus(IntEnum):
115
- """Enum with all Firmware update status values.
116
-
117
- https://zwave-js.github.io/node-zwave-js/#/api/node?id=status
118
- """
119
-
120
- ERROR_TIMEOUT = -1
121
- ERROR_CHECKSUM = 0
122
- ERROR_TRANSMISSION_FAILED = 1
123
- ERROR_INVALID_MANUFACTURER_ID = 2
124
- ERROR_INVALID_FIRMWARE_ID = 3
125
- ERROR_INVALID_FIRMWARE_TARGET = 4
126
- ERROR_INVALID_HEADER_INFORMATION = 5
127
- ERROR_INVALID_HEADER_FORMAT = 6
128
- ERROR_INSUFFICIENT_MEMORY = 7
129
- ERROR_INVALID_HARDWARE_VERSION = 8
130
- OK_WAITING_FOR_ACTIVATION = 253
131
- OK_NO_RESTART = 254
132
- OK_RESTART_PENDING = 255
133
-
134
-
135
- class FirmwareUpdateProgressDataType(TypedDict):
136
- """Represent a firmware update progress dict type."""
137
-
138
- currentFile: int
139
- totalFiles: int
140
- sentFragments: int
141
- totalFragments: int
142
- progress: float
143
-
144
-
145
- class FirmwareUpdateProgress:
146
- """Model for firmware update progress data."""
147
-
148
- def __init__(self, node: "Node", data: FirmwareUpdateProgressDataType) -> None:
149
- """Initialize."""
150
- self.data = data
151
- self.node = node
152
-
153
- @property
154
- def current_file(self) -> int:
155
- """Return current file."""
156
- return self.data["currentFile"]
157
-
158
- @property
159
- def total_files(self) -> int:
160
- """Return total files."""
161
- return self.data["totalFiles"]
162
-
163
- @property
164
- def sent_fragments(self) -> int:
165
- """Return the number of fragments sent to the device so far."""
166
- return self.data["sentFragments"]
167
-
168
- @property
169
- def total_fragments(self) -> int:
170
- """Return the total number of fragments that need to be sent to the device."""
171
- return self.data["totalFragments"]
172
-
173
- @property
174
- def progress(self) -> float:
175
- """Return progress."""
176
- return float(self.data["progress"])
177
-
178
-
179
- class FirmwareUpdateResultDataType(TypedDict, total=False):
180
- """Represent a firmware update result dict type."""
181
-
182
- status: int # required
183
- success: bool # required
184
- waitTime: int
185
- reInterview: bool # required
186
-
187
-
188
- class FirmwareUpdateResult:
189
- """Model for firmware update result data."""
190
-
191
- def __init__(self, node: "Node", data: FirmwareUpdateResultDataType) -> None:
192
- """Initialize."""
193
- self.data = data
194
- self.node = node
195
-
196
- @property
197
- def status(self) -> FirmwareUpdateStatus:
198
- """Return the firmware update status."""
199
- return FirmwareUpdateStatus(self.data["status"])
200
-
201
- @property
202
- def success(self) -> bool:
203
- """Return whether the firmware update was successful."""
204
- return self.data["success"]
205
-
206
- @property
207
- def wait_time(self) -> Optional[int]:
208
- """Return the wait time in seconds before the device is functional again."""
209
- return self.data.get("waitTime")
210
-
211
- @property
212
- def reinterview(self) -> bool:
213
- """Return whether the node will be re-interviewed."""
214
- return self.data["reInterview"]
215
-
216
-
217
- class FirmwareUpdateFileInfoDataType(TypedDict):
218
- """Represent a firmware update file info data dict type."""
219
-
220
- target: int
221
- url: str
222
- integrity: str # sha256
223
-
224
-
225
- @dataclass
226
- class FirmwareUpdateFileInfo:
227
- """Represent a firmware update file info."""
228
-
229
- target: int
230
- url: str
231
- integrity: str
232
-
233
- def to_dict(self) -> FirmwareUpdateFileInfoDataType:
234
- """Return dict representation of the object."""
235
- return cast(FirmwareUpdateFileInfoDataType, asdict(self))
236
-
237
-
238
- class FirmwareUpdateInfoDataType(TypedDict):
239
- """Represent a firmware update info data dict type."""
240
-
241
- version: str
242
- changelog: str
243
- files: List[FirmwareUpdateFileInfoDataType]
244
-
245
-
246
- @dataclass
247
- class FirmwareUpdateInfo:
248
- """Represent a firmware update info."""
249
-
250
- version: str
251
- changelog: str
252
- files: List[FirmwareUpdateFileInfo]
253
-
254
- @classmethod
255
- def from_dict(cls, data: FirmwareUpdateInfoDataType) -> "FirmwareUpdateInfo":
256
- """Initialize from dict."""
257
- return cls(
258
- version=data["version"],
259
- changelog=data["changelog"],
260
- files=[FirmwareUpdateFileInfo(**file) for file in data["files"]],
261
- )
@@ -21,14 +21,6 @@ from ..command_class import CommandClassInfo
21
21
  from ..device_class import DeviceClass
22
22
  from ..device_config import DeviceConfig
23
23
  from ..endpoint import Endpoint
24
- from ..firmware import (
25
- FirmwareUpdateCapabilities,
26
- FirmwareUpdateCapabilitiesDataType,
27
- FirmwareUpdateProgress,
28
- FirmwareUpdateProgressDataType,
29
- FirmwareUpdateResult,
30
- FirmwareUpdateResultDataType,
31
- )
32
24
  from ..notification import (
33
25
  EntryControlNotification,
34
26
  EntryControlNotificationDataType,
@@ -51,6 +43,14 @@ from ..value import (
51
43
  )
52
44
  from .data_model import NodeDataType
53
45
  from .event_model import NODE_EVENT_MODEL_MAP
46
+ from .firmware import (
47
+ NodeFirmwareUpdateCapabilities,
48
+ NodeFirmwareUpdateCapabilitiesDataType,
49
+ NodeFirmwareUpdateProgress,
50
+ NodeFirmwareUpdateProgressDataType,
51
+ NodeFirmwareUpdateResult,
52
+ NodeFirmwareUpdateResultDataType,
53
+ )
54
54
  from .health_check import (
55
55
  CheckHealthProgress,
56
56
  LifelineHealthCheckSummary,
@@ -91,7 +91,7 @@ class Node(EventBase):
91
91
  self.data: NodeDataType = {}
92
92
  self._device_config = DeviceConfig({})
93
93
  self._statistics = NodeStatistics(client, data.get("statistics"))
94
- self._firmware_update_progress: Optional[FirmwareUpdateProgress] = None
94
+ self._firmware_update_progress: Optional[NodeFirmwareUpdateProgress] = None
95
95
  self.values: Dict[str, Union[ConfigurationValue, Value]] = {}
96
96
  self.endpoints: Dict[int, Endpoint] = {}
97
97
  self.update(data)
@@ -309,7 +309,7 @@ class Node(EventBase):
309
309
  return self._statistics
310
310
 
311
311
  @property
312
- def firmware_update_progress(self) -> Optional[FirmwareUpdateProgress]:
312
+ def firmware_update_progress(self) -> Optional[NodeFirmwareUpdateProgress]:
313
313
  """Return firmware update progress."""
314
314
  return self._firmware_update_progress
315
315
 
@@ -527,7 +527,7 @@ class Node(EventBase):
527
527
 
528
528
  async def async_get_firmware_update_capabilities(
529
529
  self,
530
- ) -> FirmwareUpdateCapabilities:
530
+ ) -> NodeFirmwareUpdateCapabilities:
531
531
  """Send getFirmwareUpdateCapabilities command to Node."""
532
532
  data = await self.async_send_command(
533
533
  "get_firmware_update_capabilities",
@@ -535,13 +535,13 @@ class Node(EventBase):
535
535
  wait_for_result=True,
536
536
  )
537
537
  assert data
538
- return FirmwareUpdateCapabilities(
539
- cast(FirmwareUpdateCapabilitiesDataType, data["capabilities"])
538
+ return NodeFirmwareUpdateCapabilities(
539
+ cast(NodeFirmwareUpdateCapabilitiesDataType, data["capabilities"])
540
540
  )
541
541
 
542
542
  async def async_get_firmware_update_capabilities_cached(
543
543
  self,
544
- ) -> FirmwareUpdateCapabilities:
544
+ ) -> NodeFirmwareUpdateCapabilities:
545
545
  """Send getFirmwareUpdateCapabilitiesCached command to Node."""
546
546
  data = await self.async_send_command(
547
547
  "get_firmware_update_capabilities_cached",
@@ -549,8 +549,8 @@ class Node(EventBase):
549
549
  wait_for_result=True,
550
550
  )
551
551
  assert data
552
- return FirmwareUpdateCapabilities(
553
- cast(FirmwareUpdateCapabilitiesDataType, data["capabilities"])
552
+ return NodeFirmwareUpdateCapabilities(
553
+ cast(NodeFirmwareUpdateCapabilitiesDataType, data["capabilities"])
554
554
  )
555
555
 
556
556
  async def async_abort_firmware_update(self) -> None:
@@ -905,15 +905,15 @@ class Node(EventBase):
905
905
  """Process a node firmware update progress event."""
906
906
  self._firmware_update_progress = event.data[
907
907
  "firmware_update_progress"
908
- ] = FirmwareUpdateProgress(
909
- self, cast(FirmwareUpdateProgressDataType, event.data["progress"])
908
+ ] = NodeFirmwareUpdateProgress(
909
+ self, cast(NodeFirmwareUpdateProgressDataType, event.data["progress"])
910
910
  )
911
911
 
912
912
  def handle_firmware_update_finished(self, event: Event) -> None:
913
913
  """Process a node firmware update finished event."""
914
914
  self._firmware_update_progress = None
915
- event.data["firmware_update_finished"] = FirmwareUpdateResult(
916
- self, cast(FirmwareUpdateResultDataType, event.data["result"])
915
+ event.data["firmware_update_finished"] = NodeFirmwareUpdateResult(
916
+ self, cast(NodeFirmwareUpdateResultDataType, event.data["result"])
917
917
  )
918
918
 
919
919
  def handle_statistics_updated(self, event: Event) -> None:
@@ -6,7 +6,6 @@ from pydantic import BaseModel
6
6
  from zwave_js_server.const import CommandClass
7
7
 
8
8
  from ...event import BaseEventModel
9
- from ..firmware import FirmwareUpdateProgressDataType, FirmwareUpdateResultDataType
10
9
  from ..notification import (
11
10
  EntryControlNotificationArgsDataType,
12
11
  NotificationNotificationArgsDataType,
@@ -14,6 +13,10 @@ from ..notification import (
14
13
  )
15
14
  from ..value import ValueDataType
16
15
  from .data_model import NodeDataType
16
+ from .firmware import (
17
+ NodeFirmwareUpdateProgressDataType,
18
+ NodeFirmwareUpdateResultDataType,
19
+ )
17
20
  from .statistics import NodeStatisticsDataType
18
21
 
19
22
 
@@ -186,14 +189,14 @@ class FirmwareUpdateFinishedEventModel(BaseNodeEventModel):
186
189
  """Model for `firmware update finished` event data."""
187
190
 
188
191
  event: Literal["firmware update finished"]
189
- result: FirmwareUpdateResultDataType
192
+ result: NodeFirmwareUpdateResultDataType
190
193
 
191
194
 
192
195
  class FirmwareUpdateProgressEventModel(BaseNodeEventModel):
193
196
  """Model for `firmware update progress` event data."""
194
197
 
195
198
  event: Literal["firmware update progress"]
196
- progress: FirmwareUpdateProgressDataType
199
+ progress: NodeFirmwareUpdateProgressDataType
197
200
 
198
201
 
199
202
  NODE_EVENT_MODEL_MAP: Dict[str, Type["BaseNodeEventModel"]] = {
@@ -0,0 +1,235 @@
1
+ """Provide a model for Z-Wave firmware."""
2
+ from dataclasses import asdict, dataclass
3
+ from enum import IntEnum
4
+ from typing import TYPE_CHECKING, List, Optional, Union, cast
5
+
6
+ from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, VALUE_UNKNOWN
7
+
8
+ if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
9
+ from typing_extensions import TypedDict
10
+ else:
11
+ from typing import TypedDict
12
+
13
+ if TYPE_CHECKING:
14
+ from . import Node
15
+
16
+
17
+ class NodeFirmwareUpdateCapabilitiesDataType(TypedDict, total=False):
18
+ """Represent a firmware update capabilities dict type."""
19
+
20
+ firmwareUpgradable: bool # required
21
+ firmwareTargets: List[int]
22
+ continuesToFunction: Union[bool, str]
23
+ supportsActivation: Union[bool, str]
24
+
25
+
26
+ class NodeFirmwareUpdateCapabilitiesDict(TypedDict, total=False):
27
+ """Represent a dict from FirmwareUpdateCapabilities."""
28
+
29
+ firmware_upgradable: bool # required
30
+ firmware_targets: List[int]
31
+ continues_to_function: Optional[bool]
32
+ supports_activation: Optional[bool]
33
+
34
+
35
+ class NodeFirmwareUpdateCapabilities:
36
+ """Model for firmware update capabilities."""
37
+
38
+ def __init__(self, data: NodeFirmwareUpdateCapabilitiesDataType) -> None:
39
+ """Initialize class."""
40
+ self.data = data
41
+
42
+ @property
43
+ def firmware_upgradable(self) -> bool:
44
+ """Return whether firmware is upgradable."""
45
+ return self.data["firmwareUpgradable"]
46
+
47
+ @property
48
+ def firmware_targets(self) -> List[int]:
49
+ """Return firmware targets."""
50
+ if not self.firmware_upgradable:
51
+ raise TypeError("Firmware is not upgradeable.")
52
+ return self.data["firmwareTargets"]
53
+
54
+ @property
55
+ def continues_to_function(self) -> Optional[bool]:
56
+ """Return whether node continues to function during update."""
57
+ if not self.firmware_upgradable:
58
+ raise TypeError("Firmware is not upgradeable.")
59
+ if (val := self.data["continuesToFunction"]) == VALUE_UNKNOWN:
60
+ return None
61
+ assert isinstance(val, bool)
62
+ return val
63
+
64
+ @property
65
+ def supports_activation(self) -> Optional[bool]:
66
+ """Return whether node supports delayed activation of the new firmware."""
67
+ if not self.firmware_upgradable:
68
+ raise TypeError("Firmware is not upgradeable.")
69
+ if (val := self.data["supportsActivation"]) == VALUE_UNKNOWN:
70
+ return None
71
+ assert isinstance(val, bool)
72
+ return val
73
+
74
+ def to_dict(self) -> NodeFirmwareUpdateCapabilitiesDict:
75
+ """Return dict representation of the object."""
76
+ if not self.firmware_upgradable:
77
+ return {"firmware_upgradable": self.firmware_upgradable}
78
+ return {
79
+ "firmware_upgradable": self.firmware_upgradable,
80
+ "firmware_targets": self.firmware_targets,
81
+ "continues_to_function": self.continues_to_function,
82
+ "supports_activation": self.supports_activation,
83
+ }
84
+
85
+
86
+ class NodeFirmwareUpdateStatus(IntEnum):
87
+ """Enum with all node firmware update status values.
88
+
89
+ https://zwave-js.github.io/node-zwave-js/#/api/node?id=status
90
+ """
91
+
92
+ ERROR_TIMEOUT = -1
93
+ ERROR_CHECKSUM = 0
94
+ ERROR_TRANSMISSION_FAILED = 1
95
+ ERROR_INVALID_MANUFACTURER_ID = 2
96
+ ERROR_INVALID_FIRMWARE_ID = 3
97
+ ERROR_INVALID_FIRMWARE_TARGET = 4
98
+ ERROR_INVALID_HEADER_INFORMATION = 5
99
+ ERROR_INVALID_HEADER_FORMAT = 6
100
+ ERROR_INSUFFICIENT_MEMORY = 7
101
+ ERROR_INVALID_HARDWARE_VERSION = 8
102
+ OK_WAITING_FOR_ACTIVATION = 253
103
+ OK_NO_RESTART = 254
104
+ OK_RESTART_PENDING = 255
105
+
106
+
107
+ class NodeFirmwareUpdateProgressDataType(TypedDict):
108
+ """Represent a node firmware update progress dict type."""
109
+
110
+ currentFile: int
111
+ totalFiles: int
112
+ sentFragments: int
113
+ totalFragments: int
114
+ progress: float
115
+
116
+
117
+ class NodeFirmwareUpdateProgress:
118
+ """Model for a node firmware update progress data."""
119
+
120
+ def __init__(self, node: "Node", data: NodeFirmwareUpdateProgressDataType) -> None:
121
+ """Initialize."""
122
+ self.data = data
123
+ self.node = node
124
+
125
+ @property
126
+ def current_file(self) -> int:
127
+ """Return current file."""
128
+ return self.data["currentFile"]
129
+
130
+ @property
131
+ def total_files(self) -> int:
132
+ """Return total files."""
133
+ return self.data["totalFiles"]
134
+
135
+ @property
136
+ def sent_fragments(self) -> int:
137
+ """Return the number of fragments sent to the device so far."""
138
+ return self.data["sentFragments"]
139
+
140
+ @property
141
+ def total_fragments(self) -> int:
142
+ """Return the total number of fragments that need to be sent to the device."""
143
+ return self.data["totalFragments"]
144
+
145
+ @property
146
+ def progress(self) -> float:
147
+ """Return progress."""
148
+ return float(self.data["progress"])
149
+
150
+
151
+ class NodeFirmwareUpdateResultDataType(TypedDict, total=False):
152
+ """Represent a node firmware update result dict type."""
153
+
154
+ status: int # required
155
+ success: bool # required
156
+ waitTime: int
157
+ reInterview: bool # required
158
+
159
+
160
+ class NodeFirmwareUpdateResult:
161
+ """Model for node firmware update result data."""
162
+
163
+ def __init__(self, node: "Node", data: NodeFirmwareUpdateResultDataType) -> None:
164
+ """Initialize."""
165
+ self.data = data
166
+ self.node = node
167
+
168
+ @property
169
+ def status(self) -> NodeFirmwareUpdateStatus:
170
+ """Return the firmware update status."""
171
+ return NodeFirmwareUpdateStatus(self.data["status"])
172
+
173
+ @property
174
+ def success(self) -> bool:
175
+ """Return whether the firmware update was successful."""
176
+ return self.data["success"]
177
+
178
+ @property
179
+ def wait_time(self) -> Optional[int]:
180
+ """Return the wait time in seconds before the device is functional again."""
181
+ return self.data.get("waitTime")
182
+
183
+ @property
184
+ def reinterview(self) -> bool:
185
+ """Return whether the node will be re-interviewed."""
186
+ return self.data["reInterview"]
187
+
188
+
189
+ class NodeFirmwareUpdateFileInfoDataType(TypedDict):
190
+ """Represent a firmware update file info data dict type."""
191
+
192
+ target: int
193
+ url: str
194
+ integrity: str # sha256
195
+
196
+
197
+ @dataclass
198
+ class NodeFirmwareUpdateFileInfo:
199
+ """Represent a firmware update file info."""
200
+
201
+ target: int
202
+ url: str
203
+ integrity: str
204
+
205
+ def to_dict(self) -> NodeFirmwareUpdateFileInfoDataType:
206
+ """Return dict representation of the object."""
207
+ return cast(NodeFirmwareUpdateFileInfoDataType, asdict(self))
208
+
209
+
210
+ class NodeFirmwareUpdateInfoDataType(TypedDict):
211
+ """Represent a firmware update info data dict type."""
212
+
213
+ version: str
214
+ changelog: str
215
+ files: List[NodeFirmwareUpdateFileInfoDataType]
216
+
217
+
218
+ @dataclass
219
+ class NodeFirmwareUpdateInfo:
220
+ """Represent a firmware update info."""
221
+
222
+ version: str
223
+ changelog: str
224
+ files: List[NodeFirmwareUpdateFileInfo]
225
+
226
+ @classmethod
227
+ def from_dict(
228
+ cls, data: NodeFirmwareUpdateInfoDataType
229
+ ) -> "NodeFirmwareUpdateInfo":
230
+ """Initialize from dict."""
231
+ return cls(
232
+ version=data["version"],
233
+ changelog=data["changelog"],
234
+ files=[NodeFirmwareUpdateFileInfo(**file) for file in data["files"]],
235
+ )
@@ -1,4 +1,6 @@
1
1
  """Model for utils commands."""
2
+ from typing import Optional
3
+
2
4
  from ..client import Client
3
5
  from ..const import MINIMUM_QR_STRING_LENGTH
4
6
  from .controller import QRProvisioningInformation
@@ -19,3 +21,13 @@ async def async_parse_qr_code_string(
19
21
  {"command": "utils.parse_qr_code_string", "qr": qr_code_string}
20
22
  )
21
23
  return QRProvisioningInformation.from_dict(data["qrProvisioningInformation"])
24
+
25
+
26
+ async def async_try_parse_dsk_from_qr_code_string(
27
+ client: Client, qr_code_string: str
28
+ ) -> Optional[str]:
29
+ """Try to get DSK QR code."""
30
+ data = await client.async_send_command(
31
+ {"command": "utils.try_parse_dsk_from_qr_code_string", "qr": qr_code_string}
32
+ )
33
+ return data.get("dsk")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.44.0
3
+ Version: 0.45.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
@@ -1,13 +1,13 @@
1
1
  zwave_js_server/__init__.py,sha256=Ey3O4Tha56uU-M92oLJmQHupCJ7B9oZmxlQTo8pGUM8,45
2
2
  zwave_js_server/__main__.py,sha256=1gWC927sa3FwcA47ndmrRWW0HlfYVdiUarjEsccRF2k,3624
3
- zwave_js_server/client.py,sha256=TULMYH5ONGC5jkHJWtpSjv6rSM6b_LkJ95KOVOKHw-0,15698
3
+ zwave_js_server/client.py,sha256=J1JFbCOnDurnH-g1NeCuMSdaUPYFrz818NEtKGUvmUk,15702
4
4
  zwave_js_server/dump.py,sha256=ntHBOFS4tca2CHiS8kmd4Eo58-DEOV617-QNXvhPWpw,1532
5
5
  zwave_js_server/event.py,sha256=E2bIEb635al9MsCB5rkk3ZklU4-YkmqmXtifzlbxXaQ,1958
6
6
  zwave_js_server/exceptions.py,sha256=s-a--Stnp8wOIsIb8GfagrrRFyD8cus0iBHe2-rekEw,6097
7
- zwave_js_server/firmware.py,sha256=edHCqhB1w3vFOkRQUZmsvjN5L8kHJzN2orQf-XDu8aQ,1096
7
+ zwave_js_server/firmware.py,sha256=pLp2YkZN_yI2Lb187e7Y5h6tsiQcCR--8Kk1E8gUg0Q,2150
8
8
  zwave_js_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  zwave_js_server/version.py,sha256=RDFP2kSU3cCVIQbgMWggdeDnvhtVsZzzZg9pOjH7CC8,364
10
- zwave_js_server/const/__init__.py,sha256=W3r131B2skBAmjQ0uS1jsfMX7Iiyp4OfxpQuTMQCuKw,9131
10
+ zwave_js_server/const/__init__.py,sha256=Du-7Q-8_enu4roX5xaUKhMW0fOnhLrxMOCogiFZtE2M,9131
11
11
  zwave_js_server/const/command_class/__init__.py,sha256=WilOppnr9CXahDjEEkAXyh_j7iCq_qZ1GtBkjWLoQUg,37
12
12
  zwave_js_server/const/command_class/barrier_operator.py,sha256=NBhTp5T-6T31glpIKrEOLgbKTndkB5gqdQmCJlXFFUA,783
13
13
  zwave_js_server/const/command_class/color_switch.py,sha256=9jIDdmz8gJ5d1RdINHiK7Yh0NXg1I0U1xVzkrQJICk8,918
@@ -28,24 +28,26 @@ zwave_js_server/model/association.py,sha256=GIrgo-3uxQxFnCH8PJ6TtGRoBW9Ak3iKWTJb
28
28
  zwave_js_server/model/command_class.py,sha256=pb_aXIl1_r9iHzdEyRr300y5xu62uE6GzmF6540CQd4,1256
29
29
  zwave_js_server/model/device_class.py,sha256=KpSM101bGa_3Mig4C-_rkE4CzqImK2JPIUtlG-TFcvU,1886
30
30
  zwave_js_server/model/device_config.py,sha256=7NaMa4BZ5tGe2p0YtOQFIUjBwRVF7GYWVExLwAgIUtE,6659
31
- zwave_js_server/model/driver.py,sha256=Dy-RPYsfoU_etOLsN2H9qOlrrJvRsKI7ifIfLC7eEt8,6299
31
+ zwave_js_server/model/driver.py,sha256=fxQQMNGB1SyS-Y6GFQOs-pKsmn_msK63nPB8WXExGIU,6830
32
32
  zwave_js_server/model/endpoint.py,sha256=KRh4aeXKC4PNpTZbT3NuF5x_fDlbeOvoOSJ4u16lrLc,7972
33
- zwave_js_server/model/firmware.py,sha256=3KDV0t5035PBGCn-m_XRkJ7YJt0nL-qchSVf_JSJSes,7711
33
+ zwave_js_server/model/firmware.py,sha256=llAEyTMocofgaGL1TVgt9CktE3BKQJXe934hZ0prFnA,1047
34
34
  zwave_js_server/model/log_config.py,sha256=F-QM7oHUXAfAxbdkrvmi6k6woM4VOwxB3qRrGmTQiQA,1722
35
35
  zwave_js_server/model/log_message.py,sha256=IxZ4USpXTeHsuRNS61NDPYMSIflv1KDO_EjUy4_vavE,5859
36
36
  zwave_js_server/model/notification.py,sha256=vrkKOdNV2bj1G4gP-1n1o43HaFkGoMw060VVLBlM6wU,7269
37
37
  zwave_js_server/model/statistics.py,sha256=uG87o-iNGKHxJc4Kfr2ZJ0VY7NjCbdxwgWOqu-D-JNE,3321
38
- zwave_js_server/model/utils.py,sha256=fLs1pOKG53yWQggBWUKvPgzlT0JkK3biLzRkhPBNArI,814
38
+ zwave_js_server/model/utils.py,sha256=YTlfb3ceRnH-d7lO3jWSPrlixxqzonwa0M7Nbw7MhZY,1152
39
39
  zwave_js_server/model/value.py,sha256=4xl7E6eCOgcpGFQc6GKkYvvmnX-BhzUIFSoLVB9hPh0,8786
40
40
  zwave_js_server/model/version.py,sha256=W1H-UbhKdgMW5KdnY9MUfRFibfMZSOD3nljXOv1ZVVM,1398
41
- zwave_js_server/model/controller/__init__.py,sha256=ybQ7KUy0lWzNykm86NAXAY8_LNgwK3mvqZg56dyNQpQ,31606
42
- zwave_js_server/model/controller/data_model.py,sha256=XepG9ICR3ooRwQuMOZPL5QG9mHCcMfH5-sa_GWISAdg,949
43
- zwave_js_server/model/controller/event_model.py,sha256=pDk0t_czfnpL107Z9OiiOyO3ZpQ70KgPXR9_5YX2V3c,5161
41
+ zwave_js_server/model/controller/__init__.py,sha256=BVmHUD6YWgWrHt8dV4MVc91hoZsbw1kJixPp0YCCaPE,32660
42
+ zwave_js_server/model/controller/data_model.py,sha256=OotRUphLDQxZJ_Ik2Piwbrhz82_JgE_uw0VaxRE3TdM,967
43
+ zwave_js_server/model/controller/event_model.py,sha256=2F6iLMudRfYgDcc1wym363wVAu2y9Zt1xGhElgrD9AY,5777
44
+ zwave_js_server/model/controller/firmware.py,sha256=6emM0SHi_8CWDmEJsiCMvxSdnZ50qb10EjBm6QuDQqk,2148
44
45
  zwave_js_server/model/controller/inclusion_and_provisioning.py,sha256=01YUq4YBhQe3gmJAIsN6ctMM3pcLEaGMSyIySBI9v2c,7497
45
46
  zwave_js_server/model/controller/statistics.py,sha256=7aeFjKq_-TvcHBWc-a4xQP6wvO3GoGjr9P-m8uyOco8,4031
46
- zwave_js_server/model/node/__init__.py,sha256=mpr0O4B8RUAZ59P57gaxfLELPL02jKy-RwKvdUxFhw0,33009
47
+ zwave_js_server/model/node/__init__.py,sha256=t_huosfTraNJChqKpFC327gQKYfIqy1ltQrtO8az_qI,33080
47
48
  zwave_js_server/model/node/data_model.py,sha256=qaeK77paI3mXFDusi8BWMRAl42tXiNgQTUz0jHLEsC8,1941
48
- zwave_js_server/model/node/event_model.py,sha256=AjlCDK21zVmKwll9OiRELnlvUARTshzHMyRhS1TWZS4,6068
49
+ zwave_js_server/model/node/event_model.py,sha256=OQzbbs1LW2I9-VjwIXgVFtZ1w7dI3dmiEjVuJV7nuJ8,6096
50
+ zwave_js_server/model/node/firmware.py,sha256=CgcZSJLkFO8T7fG2LQlThMqdDow-UxloKVvrLzq_MKo,7096
49
51
  zwave_js_server/model/node/health_check.py,sha256=EAOMBkMKd5q--fhax2GvJhWdhuuv5dX_-2Hf_mczODs,6054
50
52
  zwave_js_server/model/node/statistics.py,sha256=SgAdnXnvC-Hs2GdBvENz1mkbtnXOUf5E0s2OU0iUARE,3634
51
53
  zwave_js_server/util/__init__.py,sha256=ArF1K885aW0GdVd7Lo8fl8Atf70gvsxO5Ra_WoJTG28,42
@@ -56,9 +58,9 @@ zwave_js_server/util/node.py,sha256=9cUky9gssP6psiascmOtdRCqU2MOkZ6tym6VBKsgyqs,
56
58
  zwave_js_server/util/command_class/__init__.py,sha256=sRxti47ekLTzfk8B609CMQumIbcD6mon2ZS0zwh9omY,59
57
59
  zwave_js_server/util/command_class/meter.py,sha256=Qkif1ul4KpV_k6Dc7yNXlSjlJLLTH8mCitgQk9J9W0A,1277
58
60
  zwave_js_server/util/command_class/multilevel_sensor.py,sha256=p92YLNRze5z3UWhfIDJO95F-j5tqx1PKUG9oamo9Dng,1446
59
- zwave_js_server_python-0.44.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
60
- zwave_js_server_python-0.44.0.dist-info/METADATA,sha256=Pm77zeK-CNhq2zex6ri_OgrsGpr7b52ElULMNHA3TDY,1684
61
- zwave_js_server_python-0.44.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
62
- zwave_js_server_python-0.44.0.dist-info/entry_points.txt,sha256=_8Swg1yhKiBElo6k1fxIEd6E8uPz8PDxQpwQoZ29FZE,74
63
- zwave_js_server_python-0.44.0.dist-info/top_level.txt,sha256=-hwsl-i4Av5Op_yfOHC_OP56KPmzp_iVEkeohRIN5Ng,16
64
- zwave_js_server_python-0.44.0.dist-info/RECORD,,
61
+ zwave_js_server_python-0.45.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
62
+ zwave_js_server_python-0.45.0.dist-info/METADATA,sha256=NQdmntgI38wyEk25PeAl8Sgd_MVQUjSYR7mzjXg7-cU,1684
63
+ zwave_js_server_python-0.45.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
64
+ zwave_js_server_python-0.45.0.dist-info/entry_points.txt,sha256=_8Swg1yhKiBElo6k1fxIEd6E8uPz8PDxQpwQoZ29FZE,74
65
+ zwave_js_server_python-0.45.0.dist-info/top_level.txt,sha256=-hwsl-i4Av5Op_yfOHC_OP56KPmzp_iVEkeohRIN5Ng,16
66
+ zwave_js_server_python-0.45.0.dist-info/RECORD,,