zwave-js-server-python 0.34.0__py3-none-any.whl → 0.35.2__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.
Files changed (39) hide show
  1. zwave_js_server/client.py +3 -3
  2. zwave_js_server/const/__init__.py +5 -2
  3. zwave_js_server/const/command_class/power_level.py +11 -0
  4. zwave_js_server/event.py +11 -2
  5. zwave_js_server/exceptions.py +14 -0
  6. zwave_js_server/model/command_class.py +5 -3
  7. zwave_js_server/model/{controller.py → controller/__init__.py} +18 -201
  8. zwave_js_server/model/controller/data_model.py +35 -0
  9. zwave_js_server/model/controller/event_model.py +160 -0
  10. zwave_js_server/model/controller/inclusion_and_provisioning.py +169 -0
  11. zwave_js_server/model/{controller_statistics.py → controller/statistics.py} +8 -1
  12. zwave_js_server/model/device_class.py +8 -1
  13. zwave_js_server/model/device_config.py +33 -8
  14. zwave_js_server/model/driver.py +51 -17
  15. zwave_js_server/model/endpoint.py +14 -3
  16. zwave_js_server/model/firmware.py +8 -1
  17. zwave_js_server/model/log_config.py +8 -3
  18. zwave_js_server/model/log_message.py +7 -2
  19. zwave_js_server/model/{node.py → node/__init__.py} +64 -74
  20. zwave_js_server/model/node/data_model.py +59 -0
  21. zwave_js_server/model/node/event_model.py +219 -0
  22. zwave_js_server/model/{node_health_check.py → node/health_check.py} +7 -2
  23. zwave_js_server/model/{node_statistics.py → node/statistics.py} +8 -1
  24. zwave_js_server/model/notification.py +75 -20
  25. zwave_js_server/model/utils.py +1 -1
  26. zwave_js_server/model/value.py +18 -5
  27. zwave_js_server/model/version.py +7 -1
  28. zwave_js_server/util/command_class/multilevel_sensor.py +1 -1
  29. zwave_js_server/util/multicast.py +1 -2
  30. {zwave_js_server_python-0.34.0.dist-info → zwave_js_server_python-0.35.2.dist-info}/METADATA +2 -1
  31. zwave_js_server_python-0.35.2.dist-info/RECORD +62 -0
  32. {zwave_js_server_python-0.34.0.dist-info → zwave_js_server_python-0.35.2.dist-info}/top_level.txt +0 -1
  33. scripts/__init__.py +0 -1
  34. scripts/generate_multilevel_sensor_constants.py +0 -245
  35. scripts/run_mock_server.py +0 -385
  36. zwave_js_server_python-0.34.0.dist-info/RECORD +0 -59
  37. {zwave_js_server_python-0.34.0.dist-info → zwave_js_server_python-0.35.2.dist-info}/LICENSE +0 -0
  38. {zwave_js_server_python-0.34.0.dist-info → zwave_js_server_python-0.35.2.dist-info}/WHEEL +0 -0
  39. {zwave_js_server_python-0.34.0.dist-info → zwave_js_server_python-0.35.2.dist-info}/entry_points.txt +0 -0
zwave_js_server/client.py CHANGED
@@ -1,12 +1,12 @@
1
1
  """Client."""
2
2
  import asyncio
3
+ import logging
4
+ import pprint
5
+ import uuid
3
6
  from collections import defaultdict
4
7
  from copy import deepcopy
5
8
  from datetime import datetime
6
- import logging
7
9
  from operator import itemgetter
8
- import pprint
9
- import uuid
10
10
  from types import TracebackType
11
11
  from typing import Any, DefaultDict, Dict, List, Optional, cast
12
12
 
@@ -1,10 +1,13 @@
1
1
  """Constants for the Z-Wave JS python library."""
2
+ import sys
2
3
  from enum import Enum, IntEnum
3
4
 
4
5
  # minimal server schema version we can handle
5
- MIN_SERVER_SCHEMA_VERSION = 14
6
+ MIN_SERVER_SCHEMA_VERSION = 15
6
7
  # max server schema version we can handle (and our code is compatible with)
7
- MAX_SERVER_SCHEMA_VERSION = 14
8
+ MAX_SERVER_SCHEMA_VERSION = 15
9
+
10
+ TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED = sys.version_info < (3, 9, 2)
8
11
 
9
12
  VALUE_UNKNOWN = "unknown"
10
13
 
@@ -0,0 +1,11 @@
1
+ """Constants for the Power Level Command Class."""
2
+ from enum import IntEnum
3
+
4
+
5
+ class PowerLevelTestStatus(IntEnum):
6
+ """Enum with all known power level test statuses."""
7
+
8
+ # https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/commandclass/PowerlevelCC.ts#L52
9
+ FAILED = 0
10
+ SUCCESS = 1
11
+ IN_PROGRESS = 2
zwave_js_server/event.py CHANGED
@@ -1,11 +1,20 @@
1
1
  """Provide Event base classes for Z-Wave JS."""
2
2
  import logging
3
3
  from dataclasses import dataclass, field
4
- from typing import Callable, Dict, List
4
+ from typing import Callable, Dict, List, Literal
5
+
6
+ from pydantic import BaseModel
5
7
 
6
8
  LOGGER = logging.getLogger(__package__)
7
9
 
8
10
 
11
+ class BaseEventModel(BaseModel):
12
+ """Base model for an event."""
13
+
14
+ source: Literal["controller", "driver", "node"]
15
+ event: str
16
+
17
+
9
18
  @dataclass
10
19
  class Event:
11
20
  """Represent an event."""
@@ -48,7 +57,7 @@ class EventBase:
48
57
 
49
58
  def emit(self, event_name: str, data: dict) -> None:
50
59
  """Run all callbacks for an event."""
51
- for listener in self._listeners.get(event_name, []):
60
+ for listener in self._listeners.get(event_name, []).copy():
52
61
  listener(data)
53
62
 
54
63
  def _handle_event_protocol(self, event: Event) -> None:
@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional
4
4
  from .const import CommandClass
5
5
 
6
6
  if TYPE_CHECKING:
7
+ from .event import Event
7
8
  from .model.value import Value
8
9
 
9
10
 
@@ -156,3 +157,16 @@ class UnknownValueData(BaseZwaveJSServerError):
156
157
  "upstream issue with the driver or missing support for this data in the "
157
158
  "library"
158
159
  )
160
+
161
+
162
+ class NotificationHasUnsupportedCommandClass(BaseZwaveJSServerError):
163
+ """Exception raised when notification is received for an unsupported CC."""
164
+
165
+ def __init__(self, event: "Event", command_class: CommandClass) -> None:
166
+ """Initialize an invalid Command Class error."""
167
+ self.event_data = event.data
168
+ self.command_class = command_class
169
+ super().__init__(
170
+ "Notification received with unsupported command class "
171
+ f"{command_class.name}: {event.data}"
172
+ )
@@ -3,10 +3,12 @@ Model for Zwave Command Class Info.
3
3
 
4
4
  https://zwave-js.github.io/node-zwave-js/#/api/endpoint?id=commandclasses
5
5
  """
6
+ from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, CommandClass
6
7
 
7
- from typing import TypedDict
8
-
9
- from ..const import CommandClass
8
+ if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
9
+ from typing_extensions import TypedDict
10
+ else:
11
+ from typing import TypedDict
10
12
 
11
13
 
12
14
  class CommandClassInfoDataType(TypedDict):
@@ -1,101 +1,30 @@
1
1
  """Provide a model for the Z-Wave JS controller."""
2
2
  from dataclasses import dataclass
3
- from typing import (
4
- TYPE_CHECKING,
5
- Any,
6
- Dict,
7
- List,
8
- Literal,
9
- Optional,
10
- TypedDict,
11
- Union,
12
- cast,
13
- )
3
+ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast
14
4
 
15
- from ..const import (
5
+ from ...const import (
16
6
  MINIMUM_QR_STRING_LENGTH,
17
7
  InclusionState,
18
8
  InclusionStrategy,
19
- Protocols,
20
9
  QRCodeVersion,
21
10
  RFRegion,
22
- SecurityClass,
23
11
  ZwaveFeature,
24
12
  )
25
- from .controller_statistics import ControllerStatistics, ControllerStatisticsDataType
26
- from ..event import Event, EventBase
27
- from .association import Association, AssociationGroup
28
- from .node import Node
29
- from ..util.helpers import (
30
- convert_base64_to_bytes,
31
- convert_bytes_to_base64,
13
+ from ...event import Event, EventBase
14
+ from ...util.helpers import convert_base64_to_bytes, convert_bytes_to_base64
15
+ from ..association import Association, AssociationGroup
16
+ from ..node import Node
17
+ from .data_model import ControllerDataType
18
+ from .event_model import CONTROLLER_EVENT_MODEL_MAP
19
+ from .inclusion_and_provisioning import (
20
+ InclusionGrant,
21
+ ProvisioningEntry,
22
+ QRProvisioningInformation,
32
23
  )
24
+ from .statistics import ControllerStatistics
33
25
 
34
26
  if TYPE_CHECKING:
35
- from ..client import Client
36
-
37
-
38
- class InclusionGrantDataType(TypedDict):
39
- """Representation of an inclusion grant data dict type."""
40
-
41
- # https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/controller/Inclusion.ts#L48-L56
42
- securityClasses: List[int]
43
- clientSideAuth: bool
44
-
45
-
46
- @dataclass
47
- class InclusionGrant:
48
- """Representation of an inclusion grant."""
49
-
50
- security_classes: List[SecurityClass]
51
- client_side_auth: bool
52
-
53
- def to_dict(self) -> InclusionGrantDataType:
54
- """Return InclusionGrantDataType dict from self."""
55
- return {
56
- "securityClasses": [sec_cls.value for sec_cls in self.security_classes],
57
- "clientSideAuth": self.client_side_auth,
58
- }
59
-
60
- @classmethod
61
- def from_dict(cls, data: InclusionGrantDataType) -> "InclusionGrant":
62
- """Return InclusionGrant from InclusionGrantDataType dict."""
63
- return cls(
64
- security_classes=[
65
- SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
66
- ],
67
- client_side_auth=data["clientSideAuth"],
68
- )
69
-
70
-
71
- @dataclass
72
- class ProvisioningEntry:
73
- """Class to represent the base fields of a provisioning entry."""
74
-
75
- dsk: str
76
- security_classes: List[SecurityClass]
77
- additional_properties: Optional[Dict[str, Any]] = None
78
-
79
- def to_dict(self) -> Dict[str, Any]:
80
- """Return PlannedProvisioning data dict from self."""
81
- return {
82
- "dsk": self.dsk,
83
- "securityClasses": [sec_cls.value for sec_cls in self.security_classes],
84
- **(self.additional_properties or {}),
85
- }
86
-
87
- @classmethod
88
- def from_dict(cls, data: Dict[str, Any]) -> "ProvisioningEntry":
89
- """Return ProvisioningEntry from data dict."""
90
- return cls(
91
- dsk=data["dsk"],
92
- security_classes=[
93
- SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
94
- ],
95
- additional_properties={
96
- k: v for k, v in data.items() if k not in {"dsk", "securityClasses"}
97
- },
98
- )
27
+ from ...client import Client
99
28
 
100
29
 
101
30
  @dataclass
@@ -106,122 +35,6 @@ class NVMProgress:
106
35
  total_bytes: int
107
36
 
108
37
 
109
- @dataclass
110
- class QRProvisioningInformationMixin:
111
- """Mixin class to represent the base fields of a QR provisioning information."""
112
-
113
- version: QRCodeVersion
114
- generic_device_class: int
115
- specific_device_class: int
116
- installer_icon_type: int
117
- manufacturer_id: int
118
- product_type: int
119
- product_id: int
120
- application_version: str
121
- max_inclusion_request_interval: Optional[int]
122
- uuid: Optional[str]
123
- supported_protocols: Optional[List[Protocols]]
124
-
125
-
126
- @dataclass
127
- class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixin):
128
- """Representation of provisioning information retrieved from a QR code."""
129
-
130
- def to_dict(self) -> Dict[str, Any]:
131
- """Return QRProvisioningInformation data dict from self."""
132
- data = {
133
- "version": self.version.value,
134
- "securityClasses": [sec_cls.value for sec_cls in self.security_classes],
135
- "dsk": self.dsk,
136
- "genericDeviceClass": self.generic_device_class,
137
- "specificDeviceClass": self.specific_device_class,
138
- "installerIconType": self.installer_icon_type,
139
- "manufacturerId": self.manufacturer_id,
140
- "productType": self.product_type,
141
- "productId": self.product_id,
142
- "applicationVersion": self.application_version,
143
- **(self.additional_properties or {}),
144
- }
145
- if self.max_inclusion_request_interval is not None:
146
- data["maxInclusionRequestInterval"] = self.max_inclusion_request_interval
147
- if self.uuid is not None:
148
- data["uuid"] = self.uuid
149
- if self.supported_protocols is not None:
150
- data["supportedProtocols"] = [
151
- protocol.value for protocol in self.supported_protocols
152
- ]
153
- return data
154
-
155
- @classmethod
156
- def from_dict(cls, data: Dict[str, Any]) -> "QRProvisioningInformation":
157
- """Return QRProvisioningInformation from data dict."""
158
- return cls(
159
- version=QRCodeVersion(data["version"]),
160
- security_classes=[
161
- SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
162
- ],
163
- dsk=data["dsk"],
164
- generic_device_class=data["genericDeviceClass"],
165
- specific_device_class=data["specificDeviceClass"],
166
- installer_icon_type=data["installerIconType"],
167
- manufacturer_id=data["manufacturerId"],
168
- product_type=data["productType"],
169
- product_id=data["productId"],
170
- application_version=data["applicationVersion"],
171
- max_inclusion_request_interval=data.get("maxInclusionRequestInterval"),
172
- uuid=data.get("uuid"),
173
- supported_protocols=[
174
- Protocols(supported_protocol)
175
- for supported_protocol in data.get("supportedProtocols", [])
176
- ],
177
- additional_properties={
178
- k: v
179
- for k, v in data.items()
180
- if k
181
- not in {
182
- "version",
183
- "securityClasses",
184
- "dsk",
185
- "genericDeviceClass",
186
- "specificDeviceClass",
187
- "installerIconType",
188
- "manufacturerId",
189
- "productType",
190
- "productId",
191
- "applicationVersion",
192
- "maxInclusionRequestInterval",
193
- "uuid",
194
- "supportedProtocols",
195
- }
196
- },
197
- )
198
-
199
-
200
- class ControllerDataType(TypedDict, total=False):
201
- """Represent a controller data dict type."""
202
-
203
- libraryVersion: str
204
- type: int
205
- homeId: int
206
- ownNodeId: int
207
- isSecondary: bool # TODO: The following items are missing in the docs.
208
- isUsingHomeIdFromOtherNetwork: bool
209
- isSISPresent: bool
210
- wasRealPrimary: bool
211
- isStaticUpdateController: bool
212
- isSlave: bool
213
- serialApiVersion: str
214
- manufacturerId: int
215
- productType: int
216
- productId: int
217
- supportedFunctionTypes: List[int]
218
- sucNodeId: int
219
- supportsTimers: bool
220
- isHealNetworkActive: bool
221
- statistics: ControllerStatisticsDataType
222
- inclusionState: int
223
-
224
-
225
38
  class Controller(EventBase):
226
39
  """Represent a Z-Wave JS controller."""
227
40
 
@@ -833,6 +646,8 @@ class Controller(EventBase):
833
646
  f"Controller doesn't know how to handle/forward this event: {event.data}"
834
647
  )
835
648
 
649
+ CONTROLLER_EVENT_MODEL_MAP[event.type](**event.data)
650
+
836
651
  self._handle_event_protocol(event)
837
652
 
838
653
  event.data["controller"] = self
@@ -864,6 +679,8 @@ class Controller(EventBase):
864
679
  def handle_node_removed(self, event: Event) -> None:
865
680
  """Process a node removed event."""
866
681
  event.data["node"] = self.nodes.pop(event.data["node"]["nodeId"])
682
+ # Remove client from node since it's no longer connected to the controller
683
+ event.data["node"].client = None
867
684
 
868
685
  def handle_heal_network_progress(self, event: Event) -> None:
869
686
  """Process a heal network progress event."""
@@ -0,0 +1,35 @@
1
+ """Data model for a Z-Wave JS controller."""
2
+ from typing import List
3
+
4
+ from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
5
+ from .statistics import ControllerStatisticsDataType
6
+
7
+ if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
8
+ from typing_extensions import TypedDict
9
+ else:
10
+ from typing import TypedDict
11
+
12
+
13
+ class ControllerDataType(TypedDict, total=False):
14
+ """Represent a controller data dict type."""
15
+
16
+ libraryVersion: str
17
+ type: int
18
+ homeId: int
19
+ ownNodeId: int
20
+ isSecondary: bool # TODO: The following items are missing in the docs.
21
+ isUsingHomeIdFromOtherNetwork: bool
22
+ isSISPresent: bool
23
+ wasRealPrimary: bool
24
+ isStaticUpdateController: bool
25
+ isSlave: bool
26
+ serialApiVersion: str
27
+ manufacturerId: int
28
+ productType: int
29
+ productId: int
30
+ supportedFunctionTypes: List[int]
31
+ sucNodeId: int
32
+ supportsTimers: bool
33
+ isHealNetworkActive: bool
34
+ statistics: ControllerStatisticsDataType
35
+ inclusionState: int
@@ -0,0 +1,160 @@
1
+ """Provide a model for the Z-Wave JS controller's events."""
2
+ from typing import Dict, Literal, Type
3
+
4
+ from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
5
+ from ...event import BaseEventModel
6
+ from ..node.data_model import NodeDataType
7
+ from .inclusion_and_provisioning import InclusionGrantDataType
8
+ from .statistics import ControllerStatisticsDataType
9
+
10
+ if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
11
+ from typing_extensions import TypedDict
12
+ else:
13
+ from typing import TypedDict
14
+
15
+
16
+ class InclusionResultDataType(TypedDict, total=False):
17
+ """Represent an inclusion result data dict type."""
18
+
19
+ lowSecurity: bool
20
+
21
+
22
+ class BaseControllerEventModel(BaseEventModel):
23
+ """Base model for a controller event."""
24
+
25
+ source: Literal["controller"]
26
+
27
+
28
+ class ExclusionFailedEventModel(BaseControllerEventModel):
29
+ """Model for `exclusion failed` event data."""
30
+
31
+ event: Literal["exclusion failed"]
32
+
33
+
34
+ class ExclusionStartedEventModel(BaseControllerEventModel):
35
+ """Model for `exclusion started` event data."""
36
+
37
+ event: Literal["exclusion started"]
38
+
39
+
40
+ class ExclusionStoppedEventModel(BaseControllerEventModel):
41
+ """Model for `exclusion stopped` event data."""
42
+
43
+ event: Literal["exclusion stopped"]
44
+
45
+
46
+ class GrantSecurityClassesEventModel(BaseControllerEventModel):
47
+ """Model for `grant security classes` event data."""
48
+
49
+ event: Literal["grant security classes"]
50
+ requested: InclusionGrantDataType
51
+
52
+
53
+ class HealNetworkDoneEventModel(BaseControllerEventModel):
54
+ """Model for `heal network done` event data."""
55
+
56
+ event: Literal["heal network done"]
57
+ result: Dict[int, str]
58
+
59
+
60
+ class HealNetworkProgressEventModel(BaseControllerEventModel):
61
+ """Model for `heal network progress` event data."""
62
+
63
+ event: Literal["heal network progress"]
64
+ progress: Dict[int, str]
65
+
66
+
67
+ class InclusionFailedEventModel(BaseControllerEventModel):
68
+ """Model for `inclusion failed` event data."""
69
+
70
+ event: Literal["inclusion failed"]
71
+
72
+
73
+ class InclusionStartedEventModel(BaseControllerEventModel):
74
+ """Model for `inclusion started` event data."""
75
+
76
+ event: Literal["inclusion started"]
77
+ secure: bool
78
+
79
+
80
+ class InclusionStoppedEventModel(BaseControllerEventModel):
81
+ """Model for `inclusion stopped` event data."""
82
+
83
+ event: Literal["inclusion stopped"]
84
+
85
+
86
+ class NodeAddedEventModel(BaseControllerEventModel):
87
+ """Model for `node added` event data."""
88
+
89
+ event: Literal["node added"]
90
+ node: NodeDataType
91
+ result: InclusionResultDataType
92
+
93
+
94
+ class NodeRemovedEventModel(BaseControllerEventModel):
95
+ """Model for `node removed` event data."""
96
+
97
+ event: Literal["node removed"]
98
+ node: NodeDataType
99
+ replaced: bool
100
+
101
+
102
+ class NVMBackupAndConvertProgressEventModel(BaseControllerEventModel):
103
+ """Base model for `nvm backup progress` and `nvm convert progress` event data."""
104
+
105
+ bytesRead: int
106
+ total: int
107
+
108
+
109
+ class NVMBackupProgressEventModel(NVMBackupAndConvertProgressEventModel):
110
+ """Model for `nvm backup progress` event data."""
111
+
112
+ event: Literal["nvm backup progress"]
113
+
114
+
115
+ class NVMConvertProgressEventModel(NVMBackupAndConvertProgressEventModel):
116
+ """Model for `nvm convert progress` event data."""
117
+
118
+ event: Literal["nvm convert progress"]
119
+
120
+
121
+ class NVMRestoreProgressEventModel(BaseControllerEventModel):
122
+ """Model for `nvm restore progress` event data."""
123
+
124
+ event: Literal["nvm restore progress"]
125
+ bytesWritten: int
126
+ total: int
127
+
128
+
129
+ class StatisticsUpdatedEventModel(BaseControllerEventModel):
130
+ """Model for `statistics updated` event data."""
131
+
132
+ event: Literal["statistics updated"]
133
+ statistics: ControllerStatisticsDataType
134
+
135
+
136
+ class ValidateDSKAndEnterPINEventModel(BaseControllerEventModel):
137
+ """Model for `validate dsk and enter pin` event data."""
138
+
139
+ event: Literal["validate dsk and enter pin"]
140
+ dsk: str
141
+
142
+
143
+ CONTROLLER_EVENT_MODEL_MAP: Dict[str, Type["BaseControllerEventModel"]] = {
144
+ "exclusion failed": ExclusionFailedEventModel,
145
+ "exclusion started": ExclusionStartedEventModel,
146
+ "exclusion stopped": ExclusionStoppedEventModel,
147
+ "grant security classes": GrantSecurityClassesEventModel,
148
+ "heal network done": HealNetworkDoneEventModel,
149
+ "heal network progress": HealNetworkProgressEventModel,
150
+ "inclusion failed": InclusionFailedEventModel,
151
+ "inclusion started": InclusionStartedEventModel,
152
+ "inclusion stopped": InclusionStoppedEventModel,
153
+ "node added": NodeAddedEventModel,
154
+ "node removed": NodeRemovedEventModel,
155
+ "nvm backup progress": NVMBackupProgressEventModel,
156
+ "nvm convert progress": NVMConvertProgressEventModel,
157
+ "nvm restore progress": NVMRestoreProgressEventModel,
158
+ "statistics updated": StatisticsUpdatedEventModel,
159
+ "validate dsk and enter pin": ValidateDSKAndEnterPINEventModel,
160
+ }