zwave-js-server-python 0.35.3__tar.gz → 0.36.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.
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/PKG-INFO +1 -1
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/setup.py +1 -1
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_main.py +1 -1
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/__init__.py +22 -2
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/multilevel_switch.py +8 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/exceptions.py +26 -3
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/__init__.py +24 -9
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/data_model.py +2 -2
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/statistics.py +40 -1
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/driver.py +4 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/__init__.py +11 -4
- zwave-js-server-python-0.36.0/zwave_js_server/model/node/statistics.py +119 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/notification.py +47 -0
- zwave-js-server-python-0.36.0/zwave_js_server/model/statistics.py +77 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/PKG-INFO +1 -1
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/SOURCES.txt +1 -0
- zwave-js-server-python-0.35.3/zwave_js_server/model/node/statistics.py +0 -67
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/LICENSE +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/README.md +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/setup.cfg +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_client.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_dump.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_event.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_firmware.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_version.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/__init__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/__main__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/client.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/__init__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/color_switch.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/entry_control.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/humidity_control.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/lock.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/meter.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/notification.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/power_level.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/protection.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/sound_switch.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/wake_up.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/dump.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/event.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/firmware.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/__init__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/association.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/command_class.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/event_model.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/inclusion_and_provisioning.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/device_class.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/device_config.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/endpoint.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/firmware.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_config.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_message.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/data_model.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/event_model.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/health_check.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/utils.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/value.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/version.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/__init__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/command_class/__init__.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/command_class/meter.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/helpers.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/lock.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/multicast.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/node.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/version.py +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/not-zip-safe +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/requires.txt +0 -0
- {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/top_level.txt +0 -0
|
@@ -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':
|
|
67
|
+
"'minSchemaVersion': 0, 'maxSchemaVersion': 16}\n"
|
|
68
68
|
"{'type': 'result', 'success': True, 'result': {}, 'messageId': 'api-schema-id'}\n"
|
|
69
69
|
"test_result\n"
|
|
70
70
|
)
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/__init__.py
RENAMED
|
@@ -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 =
|
|
6
|
+
MIN_SERVER_SCHEMA_VERSION = 16
|
|
7
7
|
# max server schema version we can handle (and our code is compatible with)
|
|
8
|
-
MAX_SERVER_SCHEMA_VERSION =
|
|
8
|
+
MAX_SERVER_SCHEMA_VERSION = 16
|
|
9
9
|
|
|
10
10
|
TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED = sys.version_info < (3, 9, 2)
|
|
11
11
|
|
|
@@ -297,3 +297,23 @@ class RFRegion(IntEnum):
|
|
|
297
297
|
KOREA = 33
|
|
298
298
|
UNKNOWN = 254
|
|
299
299
|
DEFAULT_EU = 255
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class ProtocolDataRate(IntEnum):
|
|
303
|
+
"""Enum for all known protocol data rates."""
|
|
304
|
+
|
|
305
|
+
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/capabilities/Protocols.ts#L6
|
|
306
|
+
|
|
307
|
+
ZWAVE_9K6 = 1
|
|
308
|
+
ZWAVE_40K = 2
|
|
309
|
+
ZWAVE_100K = 3
|
|
310
|
+
LONG_RANGE_100K = 4
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class RssiError(IntEnum):
|
|
314
|
+
"""Enum for all known RSSI errors."""
|
|
315
|
+
|
|
316
|
+
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/controller/SendDataShared.ts#L79
|
|
317
|
+
NOT_AVAILABLE = 127
|
|
318
|
+
RECEIVER_SATURATED = 126
|
|
319
|
+
NO_SIGNAL_DETECTED = 125
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Constants for the Multilevel Switch CC."""
|
|
2
|
+
from enum import IntEnum
|
|
2
3
|
|
|
3
4
|
COVER_OPEN_PROPERTY = "Open"
|
|
4
5
|
COVER_UP_PROPERTY = "Up"
|
|
@@ -6,3 +7,10 @@ COVER_ON_PROPERTY = "On"
|
|
|
6
7
|
COVER_CLOSE_PROPERTY = "Close"
|
|
7
8
|
COVER_DOWN_PROPERTY = "Down"
|
|
8
9
|
COVER_OFF_PROPERTY = "Off"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MultilevelSwitchCommand(IntEnum):
|
|
13
|
+
"""Enum for known multilevel switch notifications."""
|
|
14
|
+
|
|
15
|
+
START_LEVEL_CHANGE = 4
|
|
16
|
+
STOP_LEVEL_CHANGE = 5
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/exceptions.py
RENAMED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Exceptions for zwave-js-server."""
|
|
2
|
-
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
3
3
|
|
|
4
|
-
from .const import
|
|
4
|
+
from .const import RssiError
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
|
+
from .const import CommandClass
|
|
7
8
|
from .model.value import Value
|
|
8
9
|
|
|
9
10
|
|
|
@@ -128,7 +129,7 @@ class BulkSetConfigParameterFailed(BaseZwaveJSServerError):
|
|
|
128
129
|
class InvalidCommandClass(BaseZwaveJSServerError):
|
|
129
130
|
"""Exception raised when Zwave Value has an invalid command class."""
|
|
130
131
|
|
|
131
|
-
def __init__(self, value: "Value", command_class: CommandClass) -> None:
|
|
132
|
+
def __init__(self, value: "Value", command_class: "CommandClass") -> None:
|
|
132
133
|
"""Initialize an invalid Command Class error."""
|
|
133
134
|
self.value = value
|
|
134
135
|
self.command_class = command_class
|
|
@@ -156,3 +157,25 @@ 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 RssiErrorReceived(BaseZwaveJSServerError):
|
|
163
|
+
"""Exception raised when an RSSI error is received."""
|
|
164
|
+
|
|
165
|
+
def __init__(self, error: "RssiError") -> None:
|
|
166
|
+
"""Initialize an RSSI error."""
|
|
167
|
+
self.error = error
|
|
168
|
+
super().__init__()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class RepeaterRssiErrorReceived(BaseZwaveJSServerError):
|
|
172
|
+
"""Exception raised when an RSSI error is received in list of RSSIs."""
|
|
173
|
+
|
|
174
|
+
def __init__(self, rssi_list: List[int]) -> None:
|
|
175
|
+
"""Initialize an RSSI error."""
|
|
176
|
+
self.rssi_list = rssi_list
|
|
177
|
+
rssi_errors = [item.value for item in RssiError]
|
|
178
|
+
self.error_list = [
|
|
179
|
+
RssiError(rssi_) if rssi_ in rssi_errors else None for rssi_ in rssi_list
|
|
180
|
+
]
|
|
181
|
+
super().__init__()
|
|
@@ -21,7 +21,7 @@ from .inclusion_and_provisioning import (
|
|
|
21
21
|
ProvisioningEntry,
|
|
22
22
|
QRProvisioningInformation,
|
|
23
23
|
)
|
|
24
|
-
from .statistics import ControllerStatistics
|
|
24
|
+
from .statistics import ControllerLifelineRoutes, ControllerStatistics
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from ...client import Client
|
|
@@ -44,6 +44,7 @@ class Controller(EventBase):
|
|
|
44
44
|
self.client = client
|
|
45
45
|
self.nodes: Dict[int, Node] = {}
|
|
46
46
|
self._heal_network_progress: Optional[Dict[int, str]] = None
|
|
47
|
+
self._statistics = ControllerStatistics()
|
|
47
48
|
for node_state in state["nodes"]:
|
|
48
49
|
node = Node(client, node_state)
|
|
49
50
|
self.nodes[node.node_id] = node
|
|
@@ -64,9 +65,9 @@ class Controller(EventBase):
|
|
|
64
65
|
return self.home_id == other.home_id
|
|
65
66
|
|
|
66
67
|
@property
|
|
67
|
-
def
|
|
68
|
-
"""Return
|
|
69
|
-
return self.data.get("
|
|
68
|
+
def sdk_version(self) -> Optional[str]:
|
|
69
|
+
"""Return sdk_version."""
|
|
70
|
+
return self.data.get("sdkVersion")
|
|
70
71
|
|
|
71
72
|
@property
|
|
72
73
|
def controller_type(self) -> Optional[int]:
|
|
@@ -114,9 +115,9 @@ class Controller(EventBase):
|
|
|
114
115
|
return self.data.get("isSlave")
|
|
115
116
|
|
|
116
117
|
@property
|
|
117
|
-
def
|
|
118
|
-
"""Return
|
|
119
|
-
return self.data.get("
|
|
118
|
+
def firmware_version(self) -> Optional[str]:
|
|
119
|
+
"""Return firmware_version."""
|
|
120
|
+
return self.data.get("firmwareVersion")
|
|
120
121
|
|
|
121
122
|
@property
|
|
122
123
|
def manufacturer_id(self) -> Optional[int]:
|
|
@@ -673,6 +674,19 @@ class Controller(EventBase):
|
|
|
673
674
|
)
|
|
674
675
|
return cast(bool, data["success"])
|
|
675
676
|
|
|
677
|
+
async def async_get_known_lifeline_routes(
|
|
678
|
+
self,
|
|
679
|
+
) -> Dict[Node, ControllerLifelineRoutes]:
|
|
680
|
+
"""Send getKnownLifelineRoutes command to Controller."""
|
|
681
|
+
data = await self.client.async_send_command(
|
|
682
|
+
{"command": "controller.get_known_lifeline_routes"}, require_schema=16
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
self.nodes[node_id]: ControllerLifelineRoutes(self.client, lifeline_routes)
|
|
687
|
+
for node_id, lifeline_routes in data["routes"].items()
|
|
688
|
+
}
|
|
689
|
+
|
|
676
690
|
def receive_event(self, event: Event) -> None:
|
|
677
691
|
"""Receive an event."""
|
|
678
692
|
if event.data["source"] == "node":
|
|
@@ -739,8 +753,9 @@ class Controller(EventBase):
|
|
|
739
753
|
|
|
740
754
|
def handle_statistics_updated(self, event: Event) -> None:
|
|
741
755
|
"""Process a statistics updated event."""
|
|
742
|
-
self._statistics
|
|
743
|
-
|
|
756
|
+
self._statistics = event.data["statistics_updated"] = ControllerStatistics(
|
|
757
|
+
event.data["statistics"]
|
|
758
|
+
)
|
|
744
759
|
|
|
745
760
|
def handle_grant_security_classes(self, event: Event) -> None:
|
|
746
761
|
"""Process a grant security classes event."""
|
|
@@ -13,7 +13,7 @@ else:
|
|
|
13
13
|
class ControllerDataType(TypedDict, total=False):
|
|
14
14
|
"""Represent a controller data dict type."""
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
sdkVersion: str
|
|
17
17
|
type: int
|
|
18
18
|
homeId: int
|
|
19
19
|
ownNodeId: int
|
|
@@ -23,7 +23,7 @@ class ControllerDataType(TypedDict, total=False):
|
|
|
23
23
|
wasRealPrimary: bool
|
|
24
24
|
isStaticUpdateController: bool
|
|
25
25
|
isSlave: bool
|
|
26
|
-
|
|
26
|
+
firmwareVersion: str
|
|
27
27
|
manufacturerId: int
|
|
28
28
|
productType: int
|
|
29
29
|
productId: int
|
|
@@ -1,13 +1,52 @@
|
|
|
1
1
|
"""Provide a model for the Z-Wave JS controller's statistics."""
|
|
2
|
-
from
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
4
|
|
|
4
5
|
from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
|
|
6
|
+
from ..statistics import RouteStatistics, RouteStatisticsDataType
|
|
5
7
|
|
|
6
8
|
if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
|
|
7
9
|
from typing_extensions import TypedDict
|
|
8
10
|
else:
|
|
9
11
|
from typing import TypedDict
|
|
10
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from ...client import Client
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ControllerLifelineRoutesDataType(TypedDict):
|
|
18
|
+
"""Represent a controller lifeline routes data dict type."""
|
|
19
|
+
|
|
20
|
+
lwr: RouteStatisticsDataType
|
|
21
|
+
nlwr: RouteStatisticsDataType
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ControllerLifelineRoutes:
|
|
26
|
+
"""Represent controller lifeline routes."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self, client: "Client", data: ControllerLifelineRoutesDataType
|
|
30
|
+
) -> None:
|
|
31
|
+
"""Initialize controller lifeline routes."""
|
|
32
|
+
self.data = data
|
|
33
|
+
self._lwr = None
|
|
34
|
+
if lwr := self.data.get("lwr"):
|
|
35
|
+
self._lwr = RouteStatistics(client, lwr)
|
|
36
|
+
self._nlwr = None
|
|
37
|
+
if nlwr := self.data.get("nlwr"):
|
|
38
|
+
self._nlwr = RouteStatistics(client, nlwr)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def lwr(self) -> Optional[RouteStatistics]:
|
|
42
|
+
"""Return the last working route from the controller to this node."""
|
|
43
|
+
return self._lwr
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def nlwr(self) -> Optional[RouteStatistics]:
|
|
47
|
+
"""Return the next to last working route from the controller to this node."""
|
|
48
|
+
return self._nlwr
|
|
49
|
+
|
|
11
50
|
|
|
12
51
|
class ControllerStatisticsDataType(TypedDict):
|
|
13
52
|
"""Represent a controller statistics data dict type."""
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/driver.py
RENAMED
|
@@ -161,6 +161,10 @@ class Driver(EventBase):
|
|
|
161
161
|
"set_preferred_scales", scales=scales, require_schema=6
|
|
162
162
|
)
|
|
163
163
|
|
|
164
|
+
async def async_enable_error_reporting(self) -> None:
|
|
165
|
+
"""Send command to enable Sentry error reporting."""
|
|
166
|
+
await self._async_send_command("enable_error_reporting", require_schema=16)
|
|
167
|
+
|
|
164
168
|
def handle_logging(self, event: Event) -> None:
|
|
165
169
|
"""Process a driver logging event."""
|
|
166
170
|
event.data["log_message"] = LogMessage(cast(LogMessageDataType, event.data))
|
|
@@ -33,6 +33,8 @@ from ..notification import (
|
|
|
33
33
|
NotificationNotificationDataType,
|
|
34
34
|
PowerLevelNotification,
|
|
35
35
|
PowerLevelNotificationDataType,
|
|
36
|
+
MultilevelSwitchNotification,
|
|
37
|
+
MultilevelSwitchNotificationDataType,
|
|
36
38
|
)
|
|
37
39
|
from ..value import (
|
|
38
40
|
ConfigurationValue,
|
|
@@ -70,7 +72,7 @@ class Node(EventBase):
|
|
|
70
72
|
self.client = client
|
|
71
73
|
self.data: NodeDataType = {}
|
|
72
74
|
self._device_config: DeviceConfig = DeviceConfig({})
|
|
73
|
-
self._statistics: NodeStatistics = NodeStatistics()
|
|
75
|
+
self._statistics: NodeStatistics = NodeStatistics(client)
|
|
74
76
|
self._firmware_update_progress: Optional[FirmwareUpdateProgress] = None
|
|
75
77
|
self.values: Dict[str, Union[ConfigurationValue, Value]] = {}
|
|
76
78
|
self.endpoints: Dict[int, Endpoint] = {}
|
|
@@ -305,7 +307,7 @@ class Node(EventBase):
|
|
|
305
307
|
"""Update the internal state data."""
|
|
306
308
|
self.data = data
|
|
307
309
|
self._device_config = DeviceConfig(self.data.get("deviceConfig", {}))
|
|
308
|
-
self._statistics = NodeStatistics(self.data.get("statistics"))
|
|
310
|
+
self._statistics = NodeStatistics(self.client, self.data.get("statistics"))
|
|
309
311
|
|
|
310
312
|
# Remove stale values
|
|
311
313
|
value_ids = (_get_value_id_from_dict(self, val) for val in data["values"])
|
|
@@ -783,6 +785,10 @@ class Node(EventBase):
|
|
|
783
785
|
event.data["notification"] = NotificationNotification(
|
|
784
786
|
self, cast(NotificationNotificationDataType, event.data)
|
|
785
787
|
)
|
|
788
|
+
elif command_class == CommandClass.SWITCH_MULTILEVEL:
|
|
789
|
+
event.data["notification"] = MultilevelSwitchNotification(
|
|
790
|
+
self, cast(MultilevelSwitchNotificationDataType, event.data)
|
|
791
|
+
)
|
|
786
792
|
elif command_class == CommandClass.ENTRY_CONTROL:
|
|
787
793
|
event.data["notification"] = EntryControlNotification(
|
|
788
794
|
self, cast(EntryControlNotificationDataType, event.data)
|
|
@@ -811,5 +817,6 @@ class Node(EventBase):
|
|
|
811
817
|
|
|
812
818
|
def handle_statistics_updated(self, event: Event) -> None:
|
|
813
819
|
"""Process a statistics updated event."""
|
|
814
|
-
|
|
815
|
-
|
|
820
|
+
event.data["statistics_updated"] = self._statistics = NodeStatistics(
|
|
821
|
+
self.client, event.data["statistics"]
|
|
822
|
+
)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Provide a model for the Z-Wave JS node's statistics."""
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
|
|
4
|
+
from zwave_js_server.exceptions import RssiErrorReceived
|
|
5
|
+
|
|
6
|
+
from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, RssiError
|
|
7
|
+
from ..statistics import RouteStatistics, RouteStatisticsDataType
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ...client import Client
|
|
11
|
+
|
|
12
|
+
if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
|
|
13
|
+
from typing_extensions import TypedDict
|
|
14
|
+
else:
|
|
15
|
+
from typing import TypedDict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NodeStatisticsDataType(TypedDict, total=False):
|
|
19
|
+
"""Represent a node statistics data dict type."""
|
|
20
|
+
|
|
21
|
+
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/node/NodeStatistics.ts#L21-L33
|
|
22
|
+
commandsTX: int
|
|
23
|
+
commandsRX: int
|
|
24
|
+
commandsDroppedTX: int
|
|
25
|
+
commandsDroppedRX: int
|
|
26
|
+
timeoutResponse: int
|
|
27
|
+
rtt: int
|
|
28
|
+
rssi: int
|
|
29
|
+
lwr: RouteStatisticsDataType
|
|
30
|
+
nlwr: RouteStatisticsDataType
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NodeStatistics:
|
|
34
|
+
"""Represent a node statistics update."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self, client: "Client", data: Optional[NodeStatisticsDataType] = None
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Initialize node statistics."""
|
|
40
|
+
self.data = data or NodeStatisticsDataType(
|
|
41
|
+
commandsDroppedRX=0,
|
|
42
|
+
commandsDroppedTX=0,
|
|
43
|
+
commandsRX=0,
|
|
44
|
+
commandsTX=0,
|
|
45
|
+
timeoutResponse=0,
|
|
46
|
+
)
|
|
47
|
+
self.client = client
|
|
48
|
+
self._lwr = None
|
|
49
|
+
if lwr := self.data.get("lwr"):
|
|
50
|
+
self._lwr = RouteStatistics(client, lwr)
|
|
51
|
+
self._nlwr = None
|
|
52
|
+
if nlwr := self.data.get("nlwr"):
|
|
53
|
+
self._nlwr = RouteStatistics(client, nlwr)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def commands_tx(self) -> int:
|
|
57
|
+
"""Return number of commands successfully sent to node."""
|
|
58
|
+
return self.data["commandsTX"]
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def commands_rx(self) -> int:
|
|
62
|
+
"""
|
|
63
|
+
Return number of commands received from node.
|
|
64
|
+
|
|
65
|
+
Includes responses to sent commands.
|
|
66
|
+
"""
|
|
67
|
+
return self.data["commandsRX"]
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def commands_dropped_rx(self) -> int:
|
|
71
|
+
"""Return number of commands from node that were dropped by host."""
|
|
72
|
+
return self.data["commandsDroppedRX"]
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def commands_dropped_tx(self) -> int:
|
|
76
|
+
"""
|
|
77
|
+
Return number of outgoing commands that were dropped.
|
|
78
|
+
|
|
79
|
+
These commands could not be sent.
|
|
80
|
+
"""
|
|
81
|
+
return self.data["commandsDroppedTX"]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def timeout_response(self) -> int:
|
|
85
|
+
"""Return number of Get-type cmds where node's response didn't come in time."""
|
|
86
|
+
return self.data["timeoutResponse"]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def rtt(self) -> Optional[int]:
|
|
90
|
+
"""
|
|
91
|
+
Return average round trip time (RTT) to this node in milliseconds.
|
|
92
|
+
|
|
93
|
+
Consecutive measurements are combined using an exponential moving average.
|
|
94
|
+
"""
|
|
95
|
+
return self.data.get("rtt")
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def rssi(self) -> Optional[int]:
|
|
99
|
+
"""
|
|
100
|
+
Return average RSSI of frames received by this node.
|
|
101
|
+
|
|
102
|
+
Consecutive non-error measurements are combined using an exponential moving
|
|
103
|
+
average.
|
|
104
|
+
"""
|
|
105
|
+
if (rssi_ := self.data.get("rssi")) is None:
|
|
106
|
+
return None
|
|
107
|
+
if rssi_ in [item.value for item in RssiError]:
|
|
108
|
+
raise RssiErrorReceived(RssiError(rssi_))
|
|
109
|
+
return rssi_
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def lwr(self) -> Optional[RouteStatistics]:
|
|
113
|
+
"""Return last working route from the controller to this node."""
|
|
114
|
+
return self._lwr
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def nlwr(self) -> Optional[RouteStatistics]:
|
|
118
|
+
"""Return next to last working route from the controller to this node."""
|
|
119
|
+
return self._nlwr
|
|
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Union
|
|
|
8
8
|
|
|
9
9
|
from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
|
|
10
10
|
from ..const.command_class.power_level import PowerLevelTestStatus
|
|
11
|
+
from ..const.command_class.multilevel_switch import MultilevelSwitchCommand
|
|
11
12
|
from ..util.helpers import parse_buffer
|
|
12
13
|
|
|
13
14
|
if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
|
|
@@ -184,3 +185,49 @@ class PowerLevelNotification:
|
|
|
184
185
|
def acknowledged_frames(self) -> int:
|
|
185
186
|
"""Return acknowledged frames property."""
|
|
186
187
|
return self.data["args"]["acknowledgedFrames"]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class MultilevelSwitchNotificationArgsDataType(TypedDict, total=False):
|
|
191
|
+
"""Represent args for a Multi Level Switch CC notification event data dict type."""
|
|
192
|
+
|
|
193
|
+
eventType: int # required
|
|
194
|
+
direction: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class MultilevelSwitchNotificationDataType(BaseNotificationDataType):
|
|
198
|
+
"""Represent a Multi Level Switch CC notification event data dict type."""
|
|
199
|
+
|
|
200
|
+
args: MultilevelSwitchNotificationArgsDataType # required
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class MultilevelSwitchNotification:
|
|
204
|
+
"""Model for a Zwave Node's Multi Level CC notification event."""
|
|
205
|
+
|
|
206
|
+
def __init__(
|
|
207
|
+
self, node: "Node", data: MultilevelSwitchNotificationDataType
|
|
208
|
+
) -> None:
|
|
209
|
+
"""Initialize."""
|
|
210
|
+
self.node = node
|
|
211
|
+
self.data = data
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def node_id(self) -> int:
|
|
215
|
+
"""Return node ID property."""
|
|
216
|
+
return self.data["nodeId"]
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def command_class(self) -> int:
|
|
220
|
+
"""Return command class."""
|
|
221
|
+
return self.data["ccId"]
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def event_type(self) -> MultilevelSwitchCommand:
|
|
225
|
+
"""Return event type property."""
|
|
226
|
+
return MultilevelSwitchCommand(self.data["args"]["eventType"])
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def direction(self) -> Optional[str]:
|
|
230
|
+
"""Return direction property."""
|
|
231
|
+
if direction := self.data["args"].get("direction"):
|
|
232
|
+
return direction
|
|
233
|
+
return None
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Common models for statistics."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
4
|
+
|
|
5
|
+
from zwave_js_server.exceptions import RepeaterRssiErrorReceived, RssiErrorReceived
|
|
6
|
+
|
|
7
|
+
from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED, ProtocolDataRate, RssiError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..client import Client
|
|
11
|
+
from .node import Node
|
|
12
|
+
|
|
13
|
+
if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
|
|
14
|
+
from typing_extensions import TypedDict
|
|
15
|
+
else:
|
|
16
|
+
from typing import TypedDict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RouteStatisticsDataType(TypedDict, total=False):
|
|
20
|
+
"""Represent a route statistics data dict type."""
|
|
21
|
+
|
|
22
|
+
protocolDataRate: int
|
|
23
|
+
repeaters: List[int]
|
|
24
|
+
rssi: int
|
|
25
|
+
repeaterRSSI: List[int]
|
|
26
|
+
routeFailedBetween: List[int]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class RouteStatistics:
|
|
31
|
+
"""Represent route statistics."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, client: "Client", data: RouteStatisticsDataType) -> None:
|
|
34
|
+
"""Initialize route statistics."""
|
|
35
|
+
self.data = data
|
|
36
|
+
self.client = client
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def protocol_data_rate(self) -> ProtocolDataRate:
|
|
40
|
+
"""Return protocol data rate."""
|
|
41
|
+
return ProtocolDataRate(self.data["protocolDataRate"])
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def repeaters(self) -> List["Node"]:
|
|
45
|
+
"""Return repeaters."""
|
|
46
|
+
assert self.client.driver
|
|
47
|
+
return [
|
|
48
|
+
self.client.driver.controller.nodes[node_id]
|
|
49
|
+
for node_id in self.data["repeaters"]
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def rssi(self) -> Optional[int]:
|
|
54
|
+
"""Return RSSI."""
|
|
55
|
+
if (rssi := self.data.get("rssi")) is None:
|
|
56
|
+
return None
|
|
57
|
+
if rssi in [item.value for item in RssiError]:
|
|
58
|
+
raise RssiErrorReceived(RssiError(rssi))
|
|
59
|
+
return rssi
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def repeater_rssi(self) -> List[int]:
|
|
63
|
+
"""Return repeater RSSI."""
|
|
64
|
+
repeater_rssi = self.data.get("repeaterRSSI", [])
|
|
65
|
+
rssi_errors = [item.value for item in RssiError]
|
|
66
|
+
if any(rssi_ in rssi_errors for rssi_ in repeater_rssi):
|
|
67
|
+
raise RepeaterRssiErrorReceived(repeater_rssi)
|
|
68
|
+
|
|
69
|
+
return repeater_rssi
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def route_failed_between(self) -> Optional[List["Node"]]:
|
|
73
|
+
"""Return route failed between."""
|
|
74
|
+
if (node_ids := self.data.get("routeFailedBetween")) is None:
|
|
75
|
+
return None
|
|
76
|
+
assert self.client.driver
|
|
77
|
+
return [self.client.driver.controller.nodes[node_id] for node_id in node_ids]
|
|
@@ -43,6 +43,7 @@ zwave_js_server/model/firmware.py
|
|
|
43
43
|
zwave_js_server/model/log_config.py
|
|
44
44
|
zwave_js_server/model/log_message.py
|
|
45
45
|
zwave_js_server/model/notification.py
|
|
46
|
+
zwave_js_server/model/statistics.py
|
|
46
47
|
zwave_js_server/model/utils.py
|
|
47
48
|
zwave_js_server/model/value.py
|
|
48
49
|
zwave_js_server/model/version.py
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
"""Provide a model for the Z-Wave JS node's statistics."""
|
|
2
|
-
from typing import Optional
|
|
3
|
-
|
|
4
|
-
from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
|
|
5
|
-
|
|
6
|
-
if TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED:
|
|
7
|
-
from typing_extensions import TypedDict
|
|
8
|
-
else:
|
|
9
|
-
from typing import TypedDict
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class NodeStatisticsDataType(TypedDict):
|
|
13
|
-
"""Represent a node statistics data dict type."""
|
|
14
|
-
|
|
15
|
-
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/node/NodeStatistics.ts#L21-L33
|
|
16
|
-
commandsTX: int
|
|
17
|
-
commandsRX: int
|
|
18
|
-
commandsDroppedTX: int
|
|
19
|
-
commandsDroppedRX: int
|
|
20
|
-
timeoutResponse: int
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class NodeStatistics:
|
|
24
|
-
"""Represent a node statistics update."""
|
|
25
|
-
|
|
26
|
-
def __init__(self, data: Optional[NodeStatisticsDataType] = None) -> None:
|
|
27
|
-
"""Initialize node statistics."""
|
|
28
|
-
self.data = data or NodeStatisticsDataType(
|
|
29
|
-
commandsDroppedRX=0,
|
|
30
|
-
commandsDroppedTX=0,
|
|
31
|
-
commandsRX=0,
|
|
32
|
-
commandsTX=0,
|
|
33
|
-
timeoutResponse=0,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def commands_tx(self) -> int:
|
|
38
|
-
"""Return number of commands successfully sent to node."""
|
|
39
|
-
return self.data["commandsTX"]
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def commands_rx(self) -> int:
|
|
43
|
-
"""
|
|
44
|
-
Return number of commands received from node.
|
|
45
|
-
|
|
46
|
-
Includes responses to sent commands.
|
|
47
|
-
"""
|
|
48
|
-
return self.data["commandsRX"]
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def commands_dropped_rx(self) -> int:
|
|
52
|
-
"""Return number of commands from node that were dropped by host."""
|
|
53
|
-
return self.data["commandsDroppedRX"]
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def commands_dropped_tx(self) -> int:
|
|
57
|
-
"""
|
|
58
|
-
Return number of outgoing commands that were dropped.
|
|
59
|
-
|
|
60
|
-
These commands could not be sent.
|
|
61
|
-
"""
|
|
62
|
-
return self.data["commandsDroppedTX"]
|
|
63
|
-
|
|
64
|
-
@property
|
|
65
|
-
def timeout_response(self) -> int:
|
|
66
|
-
"""Return number of Get-type cmds where node's response didn't come in time."""
|
|
67
|
-
return self.data["timeoutResponse"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/__init__.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/association.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/endpoint.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/firmware.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_config.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/utils.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/value.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/version.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/helpers.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/lock.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/multicast.py
RENAMED
|
File without changes
|
{zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/node.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|