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.
Files changed (76) hide show
  1. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/PKG-INFO +1 -1
  2. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/setup.py +1 -1
  3. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_main.py +1 -1
  4. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/__init__.py +22 -2
  5. {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
  6. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/exceptions.py +26 -3
  7. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/__init__.py +24 -9
  8. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/data_model.py +2 -2
  9. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/statistics.py +40 -1
  10. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/driver.py +4 -0
  11. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/__init__.py +11 -4
  12. zwave-js-server-python-0.36.0/zwave_js_server/model/node/statistics.py +119 -0
  13. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/notification.py +47 -0
  14. zwave-js-server-python-0.36.0/zwave_js_server/model/statistics.py +77 -0
  15. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/PKG-INFO +1 -1
  16. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/SOURCES.txt +1 -0
  17. zwave-js-server-python-0.35.3/zwave_js_server/model/node/statistics.py +0 -67
  18. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/LICENSE +0 -0
  19. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/README.md +0 -0
  20. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/setup.cfg +0 -0
  21. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_client.py +0 -0
  22. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_dump.py +0 -0
  23. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_event.py +0 -0
  24. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_firmware.py +0 -0
  25. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/test/test_version.py +0 -0
  26. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/__init__.py +0 -0
  27. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/__main__.py +0 -0
  28. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/client.py +0 -0
  29. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/__init__.py +0 -0
  30. {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
  31. {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
  32. {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
  33. {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
  34. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/lock.py +0 -0
  35. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/meter.py +0 -0
  36. {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
  37. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/notification.py +0 -0
  38. {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
  39. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/protection.py +0 -0
  40. {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
  41. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
  42. {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
  43. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/dump.py +0 -0
  44. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/event.py +0 -0
  45. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/firmware.py +0 -0
  46. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/__init__.py +0 -0
  47. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/association.py +0 -0
  48. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/command_class.py +0 -0
  49. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/controller/event_model.py +0 -0
  50. {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
  51. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/device_class.py +0 -0
  52. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/device_config.py +0 -0
  53. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/endpoint.py +0 -0
  54. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/firmware.py +0 -0
  55. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_config.py +0 -0
  56. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/log_message.py +0 -0
  57. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/data_model.py +0 -0
  58. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/event_model.py +0 -0
  59. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/node/health_check.py +0 -0
  60. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/utils.py +0 -0
  61. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/value.py +0 -0
  62. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/model/version.py +0 -0
  63. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/__init__.py +0 -0
  64. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/command_class/__init__.py +0 -0
  65. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/command_class/meter.py +0 -0
  66. {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
  67. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/helpers.py +0 -0
  68. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/lock.py +0 -0
  69. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/multicast.py +0 -0
  70. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/util/node.py +0 -0
  71. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server/version.py +0 -0
  72. {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
  73. {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
  74. {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
  75. {zwave-js-server-python-0.35.3 → zwave-js-server-python-0.36.0}/zwave_js_server_python.egg-info/requires.txt +0 -0
  76. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.35.3
3
+ Version: 0.36.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Home-page: https://github.com/home-assistant-libs/zwave-js-server-python
6
6
  Author: Home Assistant Team
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
5
5
 
6
6
  PROJECT_DIR = Path(__file__).parent.resolve()
7
7
  README_FILE = PROJECT_DIR / "README.md"
8
- VERSION = "0.35.3"
8
+ VERSION = "0.36.0"
9
9
 
10
10
 
11
11
  setup(
@@ -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': 15}\n"
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
  )
@@ -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 = 15
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 = 15
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
@@ -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 CommandClass
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 library_version(self) -> Optional[str]:
68
- """Return library_version."""
69
- return self.data.get("libraryVersion")
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 serial_api_version(self) -> Optional[str]:
118
- """Return serial_api_version."""
119
- return self.data.get("serialApiVersion")
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.data.update(event.data["statistics"])
743
- event.data["statistics_updated"] = self.statistics
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
- libraryVersion: str
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
- serialApiVersion: str
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 typing import Optional
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."""
@@ -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
- self._statistics.data.update(event.data["statistics"])
815
- event.data["statistics_updated"] = self.statistics
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]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.35.3
3
+ Version: 0.36.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
@@ -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"]