zwave-js-server-python 0.65.0__tar.gz → 0.67.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 (83) hide show
  1. {zwave_js_server_python-0.65.0/zwave_js_server_python.egg-info → zwave_js_server_python-0.67.0}/PKG-INFO +2 -2
  2. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/pyproject.toml +4 -3
  3. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/__init__.py +3 -3
  4. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/event.py +1 -4
  5. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/config_manager/__init__.py +2 -2
  6. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/statistics.py +9 -4
  7. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/device_config.py +7 -7
  8. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/driver/__init__.py +74 -9
  9. zwave_js_server_python-0.67.0/zwave_js_server/model/driver/firmware.py +65 -0
  10. zwave_js_server_python-0.67.0/zwave_js_server/model/firmware.py +210 -0
  11. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/__init__.py +1 -4
  12. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/event_model.py +4 -7
  13. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/firmware.py +68 -132
  14. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/statistics.py +2 -2
  15. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/value.py +1 -1
  16. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0/zwave_js_server_python.egg-info}/PKG-INFO +2 -2
  17. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server_python.egg-info/SOURCES.txt +1 -0
  18. zwave_js_server_python-0.67.0/zwave_js_server_python.egg-info/requires.txt +2 -0
  19. zwave_js_server_python-0.65.0/zwave_js_server/model/driver/firmware.py +0 -97
  20. zwave_js_server_python-0.65.0/zwave_js_server_python.egg-info/requires.txt +0 -2
  21. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/LICENSE +0 -0
  22. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/MANIFEST.in +0 -0
  23. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/README.md +0 -0
  24. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/setup.cfg +0 -0
  25. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/__init__.py +0 -0
  26. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/__main__.py +0 -0
  27. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/client.py +0 -0
  28. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/__init__.py +0 -0
  29. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  30. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/basic.py +0 -0
  31. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/central_scene.py +0 -0
  32. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/color_switch.py +0 -0
  33. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/energy_production.py +0 -0
  34. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/entry_control.py +0 -0
  35. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  36. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/lock.py +0 -0
  37. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/meter.py +0 -0
  38. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  39. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  40. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/notification.py +0 -0
  41. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/power_level.py +0 -0
  42. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/protection.py +0 -0
  43. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/scene_activation.py +0 -0
  44. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  45. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
  46. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/wake_up.py +0 -0
  47. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/const/command_class/window_covering.py +0 -0
  48. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/dump.py +0 -0
  49. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/exceptions.py +0 -0
  50. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/firmware.py +0 -0
  51. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/__init__.py +0 -0
  52. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/association.py +0 -0
  53. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/command_class.py +0 -0
  54. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/__init__.py +0 -0
  55. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/data_model.py +0 -0
  56. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/event_model.py +0 -0
  57. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/inclusion_and_provisioning.py +0 -0
  58. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/controller/rebuild_routes.py +0 -0
  59. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/device_class.py +0 -0
  60. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/duration.py +0 -0
  61. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/endpoint.py +0 -0
  62. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/log_config.py +0 -0
  63. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/log_message.py +0 -0
  64. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/data_model.py +0 -0
  65. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/node/health_check.py +0 -0
  66. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/notification.py +0 -0
  67. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/statistics.py +0 -0
  68. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/utils.py +0 -0
  69. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/model/version.py +0 -0
  70. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/py.typed +0 -0
  71. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/__init__.py +0 -0
  72. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/command_class/__init__.py +0 -0
  73. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/command_class/energy_production.py +0 -0
  74. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/command_class/meter.py +0 -0
  75. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  76. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/helpers.py +0 -0
  77. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/lock.py +0 -0
  78. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/multicast.py +0 -0
  79. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/util/node.py +0 -0
  80. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server/version.py +0 -0
  81. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  82. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  83. {zwave_js_server_python-0.65.0 → zwave_js_server_python-0.67.0}/zwave_js_server_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zwave-js-server-python
3
- Version: 0.65.0
3
+ Version: 0.67.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Author-email: Home Assistant Team <hello@home-assistant.io>
6
6
  License-Expression: Apache-2.0
@@ -18,7 +18,7 @@ Requires-Python: >=3.12
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: aiohttp>3
21
- Requires-Dist: pydantic>=1.10.0
21
+ Requires-Dist: pydantic>=2.0.0
22
22
  Dynamic: license-file
23
23
 
24
24
  # zwave-js-server-python
@@ -19,7 +19,10 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3.13",
20
20
  "Topic :: Home Automation",
21
21
  ]
22
- dependencies = ["aiohttp>3", "pydantic>=1.10.0"]
22
+ dependencies = [
23
+ "aiohttp>3",
24
+ "pydantic>=2.0.0",
25
+ ]
23
26
  dynamic = ["version"]
24
27
 
25
28
  [project.urls]
@@ -40,8 +43,6 @@ zwave_js_server = ["py.typed"]
40
43
 
41
44
  [tool.mypy]
42
45
  plugins = ["pydantic.mypy"]
43
- follow_imports = "skip"
44
- ignore_missing_imports = true
45
46
  check_untyped_defs = true
46
47
  disallow_incomplete_defs = true
47
48
  disallow_untyped_calls = true
@@ -8,12 +8,12 @@ import logging
8
8
  from typing import TypedDict
9
9
 
10
10
  PACKAGE_NAME = "zwave-js-server-python"
11
- __version__ = "0.65.0"
11
+ __version__ = "0.67.0"
12
12
 
13
13
  # minimal server schema version we can handle
14
- MIN_SERVER_SCHEMA_VERSION = 41
14
+ MIN_SERVER_SCHEMA_VERSION = 44
15
15
  # max server schema version we can handle (and our code is compatible with)
16
- MAX_SERVER_SCHEMA_VERSION = 43
16
+ MAX_SERVER_SCHEMA_VERSION = 44
17
17
 
18
18
  VALUE_UNKNOWN = "unknown"
19
19
 
@@ -7,10 +7,7 @@ from dataclasses import dataclass, field
7
7
  import logging
8
8
  from typing import Literal
9
9
 
10
- try:
11
- from pydantic.v1 import BaseModel
12
- except ImportError:
13
- from pydantic import BaseModel
10
+ from pydantic import BaseModel
14
11
 
15
12
  LOGGER = logging.getLogger(__package__)
16
13
 
@@ -6,7 +6,7 @@ https://zwave-js.github.io/node-zwave-js/#/api/config-manager
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from typing import TYPE_CHECKING, Any, Optional
9
+ from typing import TYPE_CHECKING, Any
10
10
 
11
11
  from ..device_config import DeviceConfig
12
12
 
@@ -26,7 +26,7 @@ class ConfigManager:
26
26
  manufacturer_id: int,
27
27
  product_type: int,
28
28
  product_id: int,
29
- firmware_version: Optional[str] = None,
29
+ firmware_version: str | None = None,
30
30
  ) -> DeviceConfig | None:
31
31
  """Look up the definition of a given device in the configuration DB."""
32
32
  cmd: dict[str, Any] = {
@@ -53,6 +53,7 @@ class BackgroundRSSIDataType(TypedDict, total=False):
53
53
  channel0: ChannelRSSIDataType # required
54
54
  channel1: ChannelRSSIDataType # required
55
55
  channel2: ChannelRSSIDataType
56
+ channel3: ChannelRSSIDataType
56
57
 
57
58
 
58
59
  class ControllerStatisticsDataType(TypedDict, total=False):
@@ -94,16 +95,20 @@ class BackgroundRSSI:
94
95
  channel_0: ChannelRSSI = field(init=False)
95
96
  channel_1: ChannelRSSI = field(init=False)
96
97
  channel_2: ChannelRSSI | None = field(init=False)
98
+ channel_3: ChannelRSSI | None = field(init=False)
97
99
 
98
100
  def __post_init__(self) -> None:
99
101
  """Post initialize."""
100
102
  self.timestamp = self.data["timestamp"]
101
103
  self.channel_0 = ChannelRSSI(self.data["channel0"])
102
104
  self.channel_1 = ChannelRSSI(self.data["channel1"])
103
- if not (channel_2 := self.data.get("channel2")):
104
- self.channel_2 = None
105
- return
106
- self.channel_2 = ChannelRSSI(channel_2)
105
+ # Channels 2 and 3 may not be present, but 3 requires 2 to be present
106
+ self.channel_2 = None
107
+ self.channel_3 = None
108
+ if channel_2 := self.data.get("channel2"):
109
+ self.channel_2 = ChannelRSSI(channel_2)
110
+ if channel_3 := self.data.get("channel3"):
111
+ self.channel_3 = ChannelRSSI(channel_3)
107
112
 
108
113
 
109
114
  @dataclass
@@ -12,8 +12,8 @@ from typing import Any, Literal, TypedDict
12
12
  class DeviceDeviceDataType(TypedDict, total=False):
13
13
  """Represent a device device data dict type."""
14
14
 
15
- productType: str
16
- productId: str
15
+ productType: str | int
16
+ productId: str | int
17
17
 
18
18
 
19
19
  class DeviceDevice:
@@ -24,12 +24,12 @@ class DeviceDevice:
24
24
  self.data = data
25
25
 
26
26
  @property
27
- def product_type(self) -> str | None:
27
+ def product_type(self) -> str | int | None:
28
28
  """Return product type."""
29
29
  return self.data.get("productType")
30
30
 
31
31
  @property
32
- def product_id(self) -> str | None:
32
+ def product_id(self) -> str | int | None:
33
33
  """Return product id."""
34
34
  return self.data.get("productId")
35
35
 
@@ -125,7 +125,7 @@ class DeviceConfigDataType(TypedDict, total=False):
125
125
 
126
126
  filename: str
127
127
  manufacturer: str
128
- manufacturerId: str
128
+ manufacturerId: int
129
129
  label: str
130
130
  description: str
131
131
  devices: list[DeviceDeviceDataType]
@@ -164,8 +164,8 @@ class DeviceConfig:
164
164
  return self.data.get("manufacturer")
165
165
 
166
166
  @property
167
- def manufacturer_id(self) -> str | None: # TODO: In the dump this is an int.
168
- """Return manufacturer id (as defined in specs) as a 4-digit hex string."""
167
+ def manufacturer_id(self) -> int | None:
168
+ """Return manufacturer id (as defined in specs)."""
169
169
  return self.data.get("manufacturerId")
170
170
 
171
171
  @property
@@ -4,11 +4,18 @@ from __future__ import annotations
4
4
 
5
5
  from typing import TYPE_CHECKING, Any, Literal, cast
6
6
 
7
+ from zwave_js_server.model.firmware import (
8
+ FirmwareUpdateData,
9
+ FirmwareUpdateDataDataType,
10
+ FirmwareUpdateInfo,
11
+ FirmwareUpdateInfoDataType,
12
+ )
13
+
7
14
  from ...event import BaseEventModel, Event, EventBase
8
15
  from ..config_manager import ConfigManager
9
16
  from ..controller import Controller
10
17
  from ..log_config import LogConfig, LogConfigDataType
11
- from ..log_message import LogMessage, LogMessageDataType
18
+ from ..log_message import LogMessage, LogMessageContextDataType, LogMessageDataType
12
19
  from .firmware import (
13
20
  DriverFirmwareUpdateProgress,
14
21
  DriverFirmwareUpdateProgressDataType,
@@ -16,11 +23,6 @@ from .firmware import (
16
23
  DriverFirmwareUpdateResultDataType,
17
24
  )
18
25
 
19
- try:
20
- from pydantic.v1 import create_model_from_typeddict
21
- except ImportError:
22
- from pydantic import create_model_from_typeddict
23
-
24
26
  if TYPE_CHECKING:
25
27
  from ...client import Client
26
28
 
@@ -53,9 +55,40 @@ class AllNodesReadyEventModel(BaseDriverEventModel):
53
55
  event: Literal["all nodes ready"]
54
56
 
55
57
 
56
- LoggingEventModel = create_model_from_typeddict(
57
- LogMessageDataType, __base__=BaseDriverEventModel
58
- )
58
+ class LoggingEventModel(BaseDriverEventModel):
59
+ """Model for `logging` event data."""
60
+
61
+ event: Literal["logging"]
62
+ message: str | list[str] # required
63
+ formattedMessage: str | list[str] # required
64
+ direction: str # required
65
+ level: str # required
66
+ context: LogMessageContextDataType # required
67
+ primaryTags: str | None = None
68
+ secondaryTags: str | None = None
69
+ secondaryTagPadding: int | None = None
70
+ multiline: bool | None = None
71
+ timestamp: str | None = None
72
+ label: str | None = None
73
+
74
+ @classmethod
75
+ def from_dict(cls, data: dict) -> LoggingEventModel:
76
+ """Initialize from dict."""
77
+ return cls(
78
+ source=data["source"],
79
+ event=data["event"],
80
+ message=data["message"],
81
+ formattedMessage=data["formattedMessage"],
82
+ direction=data["direction"],
83
+ level=data["level"],
84
+ context=data["context"],
85
+ primaryTags=data.get("primaryTags"),
86
+ secondaryTags=data.get("secondaryTags"),
87
+ secondaryTagPadding=data.get("secondaryTagPadding"),
88
+ multiline=data.get("multiline"),
89
+ timestamp=data.get("timestamp"),
90
+ label=data.get("label"),
91
+ )
59
92
 
60
93
 
61
94
  class DriverReadyEventModel(BaseDriverEventModel):
@@ -216,6 +249,38 @@ class Driver(EventBase):
216
249
  )
217
250
  return cast(bool, result["success"])
218
251
 
252
+ async def async_firmware_update_otw(
253
+ self,
254
+ *,
255
+ update_data: FirmwareUpdateData | None = None,
256
+ update_info: FirmwareUpdateInfo | None = None,
257
+ ) -> DriverFirmwareUpdateResult:
258
+ """Send firmwareUpdateOTW command to Driver."""
259
+ if update_data is None and update_info is None:
260
+ raise ValueError(
261
+ "Either update_data or update_info must be provided for firmware update."
262
+ )
263
+ if update_data is not None and update_info is not None:
264
+ raise ValueError(
265
+ "Only one of update_data or update_info can be provided for firmware update."
266
+ )
267
+ params: FirmwareUpdateDataDataType | dict[str, FirmwareUpdateInfoDataType]
268
+ if update_data is not None:
269
+ params = update_data.to_dict()
270
+ elif update_info is not None:
271
+ params = {"updateInfo": update_info.to_dict()}
272
+ data = await self._async_send_command(
273
+ "firmware_update_otw", require_schema=44, **params
274
+ )
275
+ return DriverFirmwareUpdateResult(data["result"])
276
+
277
+ async def async_is_otw_firmware_update_in_progress(self) -> bool:
278
+ """Send isOTWFirmwareUpdateInProgress command to Driver."""
279
+ result = await self._async_send_command(
280
+ "is_otw_firmware_update_in_progress", require_schema=41
281
+ )
282
+ return cast(bool, result["progress"])
283
+
219
284
  async def async_set_preferred_scales(
220
285
  self, scales: dict[str | int, str | int]
221
286
  ) -> None:
@@ -0,0 +1,65 @@
1
+ """Provide a model for Z-Wave driver firmware."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import IntEnum
7
+
8
+ from zwave_js_server.model.firmware import (
9
+ FirmwareUpdateData,
10
+ FirmwareUpdateDataDataType,
11
+ FirmwareUpdateProgress,
12
+ FirmwareUpdateProgressDataType,
13
+ FirmwareUpdateResult,
14
+ FirmwareUpdateResultDataType,
15
+ )
16
+
17
+
18
+ class DriverFirmwareUpdateDataDataType(FirmwareUpdateDataDataType):
19
+ """Represent a driver firmware update data dict type."""
20
+
21
+
22
+ @dataclass
23
+ class DriverFirmwareUpdateData(FirmwareUpdateData):
24
+ """Driver firmware update data."""
25
+
26
+
27
+ class DriverFirmwareUpdateStatus(IntEnum):
28
+ """Enum with all driver firmware update status values.
29
+
30
+ https://zwave-js.github.io/node-zwave-js/#/api/driver?id=quotfirmware-update-finishedquot
31
+ """
32
+
33
+ ERROR_TIMEOUT = 0
34
+ # The maximum number of retry attempts for a firmware fragments were reached
35
+ ERROR_RETRY_LIMIT_REACHED = 1
36
+ # The update was aborted by the bootloader
37
+ ERROR_ABORTED = 2
38
+ # This driver does not support firmware updates
39
+ ERROR_NOT_SUPPORTED = 3
40
+ OK = 255
41
+
42
+
43
+ class DriverFirmwareUpdateProgressDataType(FirmwareUpdateProgressDataType):
44
+ """Represent a driver firmware update progress dict type."""
45
+
46
+
47
+ @dataclass
48
+ class DriverFirmwareUpdateProgress(FirmwareUpdateProgress):
49
+ """Model for a driver firmware update progress data."""
50
+
51
+ data: DriverFirmwareUpdateProgressDataType = field(repr=False)
52
+
53
+
54
+ class DriverFirmwareUpdateResultDataType(FirmwareUpdateResultDataType):
55
+ """Represent a driver firmware update result dict type."""
56
+
57
+
58
+ @dataclass
59
+ class DriverFirmwareUpdateResult(FirmwareUpdateResult):
60
+ """Model for driver firmware update result data."""
61
+
62
+ data: DriverFirmwareUpdateResultDataType = field(repr=False)
63
+ status: DriverFirmwareUpdateStatus = field(init=False)
64
+ success: bool = field(init=False)
65
+ _status_class = DriverFirmwareUpdateStatus
@@ -0,0 +1,210 @@
1
+ """Provide models for Z-Wave JS firmware."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import asdict, dataclass, field
6
+ from enum import IntEnum
7
+ from typing import Literal, Required, TypedDict, cast
8
+
9
+ from zwave_js_server.const import RFRegion
10
+ from zwave_js_server.util.helpers import convert_bytes_to_base64
11
+
12
+
13
+ @dataclass
14
+ class FirmwareUpdateData:
15
+ """Firmware update data."""
16
+
17
+ filename: str
18
+ file: bytes
19
+ file_format: str | None = None
20
+
21
+ def to_dict(self) -> FirmwareUpdateDataDataType:
22
+ """Convert firmware update data to dict."""
23
+ data: FirmwareUpdateDataDataType = {
24
+ "filename": self.filename,
25
+ "file": convert_bytes_to_base64(self.file),
26
+ }
27
+ if self.file_format is not None:
28
+ data["fileFormat"] = self.file_format
29
+ return data
30
+
31
+
32
+ class FirmwareUpdateDataDataType(TypedDict, total=False):
33
+ """Represent a firmware update data dict type."""
34
+
35
+ filename: Required[str]
36
+ file: Required[str]
37
+ fileFormat: str
38
+
39
+
40
+ @dataclass
41
+ class FirmwareUpdateInfo:
42
+ """Represent a firmware update info."""
43
+
44
+ version: str
45
+ changelog: str
46
+ channel: Literal["stable", "beta"]
47
+ files: list[FirmwareUpdateFileInfo]
48
+ downgrade: bool
49
+ normalized_version: str
50
+ device: FirmwareUpdateDeviceID
51
+
52
+ @classmethod
53
+ def from_dict(cls, data: FirmwareUpdateInfoDataType) -> FirmwareUpdateInfo:
54
+ """Initialize from dict."""
55
+ return cls(
56
+ version=data["version"],
57
+ changelog=data["changelog"],
58
+ channel=data["channel"],
59
+ files=[FirmwareUpdateFileInfo.from_dict(file) for file in data["files"]],
60
+ downgrade=data["downgrade"],
61
+ normalized_version=data["normalizedVersion"],
62
+ device=FirmwareUpdateDeviceID.from_dict(data["device"]),
63
+ )
64
+
65
+ def to_dict(self) -> FirmwareUpdateInfoDataType:
66
+ """Return dict representation of the object."""
67
+ return cast(
68
+ FirmwareUpdateInfoDataType,
69
+ {
70
+ "version": self.version,
71
+ "changelog": self.changelog,
72
+ "channel": self.channel,
73
+ "files": [file.to_dict() for file in self.files],
74
+ "downgrade": self.downgrade,
75
+ "normalizedVersion": self.normalized_version,
76
+ "device": self.device.to_dict(),
77
+ },
78
+ )
79
+
80
+
81
+ class FirmwareUpdateInfoDataType(TypedDict, total=False):
82
+ """Represent a firmware update info data dict type."""
83
+
84
+ version: str
85
+ changelog: str
86
+ channel: Literal["stable", "beta"]
87
+ files: list[FirmwareUpdateFileInfoDataType]
88
+ downgrade: bool
89
+ normalizedVersion: str
90
+ device: FirmwareUpdateDeviceIDDataType
91
+
92
+
93
+ @dataclass
94
+ class FirmwareUpdateDeviceID:
95
+ """Represent a firmware update device ID."""
96
+
97
+ manufacturer_id: int
98
+ product_type: int
99
+ product_id: int
100
+ firmware_version: str
101
+ rf_region: RFRegion | None
102
+
103
+ @classmethod
104
+ def from_dict(cls, data: FirmwareUpdateDeviceIDDataType) -> FirmwareUpdateDeviceID:
105
+ """Initialize from dict."""
106
+ return cls(
107
+ manufacturer_id=data["manufacturerId"],
108
+ product_type=data["productType"],
109
+ product_id=data["productId"],
110
+ firmware_version=data["firmwareVersion"],
111
+ rf_region=RFRegion(data["rfRegion"]) if "rfRegion" in data else None,
112
+ )
113
+
114
+ def to_dict(self) -> FirmwareUpdateDeviceIDDataType:
115
+ """Return dict representation of the object."""
116
+ data = {
117
+ "manufacturerId": self.manufacturer_id,
118
+ "productType": self.product_type,
119
+ "productId": self.product_id,
120
+ "firmwareVersion": self.firmware_version,
121
+ }
122
+ if self.rf_region is not None:
123
+ data["rfRegion"] = self.rf_region
124
+ return cast(FirmwareUpdateDeviceIDDataType, data)
125
+
126
+
127
+ class FirmwareUpdateDeviceIDDataType(TypedDict, total=False):
128
+ """Represent a firmware update device ID dict type."""
129
+
130
+ manufacturerId: Required[int]
131
+ productType: Required[int]
132
+ productId: Required[int]
133
+ firmwareVersion: Required[str]
134
+ rfRegion: int
135
+
136
+
137
+ @dataclass
138
+ class FirmwareUpdateFileInfo:
139
+ """Represent a firmware update file info."""
140
+
141
+ target: int
142
+ url: str
143
+ integrity: str
144
+
145
+ @classmethod
146
+ def from_dict(cls, data: FirmwareUpdateFileInfoDataType) -> FirmwareUpdateFileInfo:
147
+ """Initialize from dict."""
148
+ return cls(
149
+ target=data["target"],
150
+ url=data["url"],
151
+ integrity=data["integrity"],
152
+ )
153
+
154
+ def to_dict(self) -> FirmwareUpdateFileInfoDataType:
155
+ """Return dict representation of the object."""
156
+ return cast(FirmwareUpdateFileInfoDataType, asdict(self))
157
+
158
+
159
+ class FirmwareUpdateFileInfoDataType(TypedDict):
160
+ """Represent a firmware update file info data dict type."""
161
+
162
+ target: int
163
+ url: str
164
+ integrity: str # sha256
165
+
166
+
167
+ @dataclass
168
+ class FirmwareUpdateProgress:
169
+ """Model for a firmware update progress."""
170
+
171
+ data: FirmwareUpdateProgressDataType = field(repr=False)
172
+ sent_fragments: int = field(init=False)
173
+ total_fragments: int = field(init=False)
174
+ progress: float = field(init=False)
175
+
176
+ def __post_init__(self) -> None:
177
+ """Post initialize."""
178
+ self.sent_fragments = self.data["sentFragments"]
179
+ self.total_fragments = self.data["totalFragments"]
180
+ self.progress = float(self.data["progress"])
181
+
182
+
183
+ class FirmwareUpdateProgressDataType(TypedDict):
184
+ """Represent a firmware update progress dict type."""
185
+
186
+ sentFragments: int
187
+ totalFragments: int
188
+ progress: float
189
+
190
+
191
+ @dataclass
192
+ class FirmwareUpdateResult:
193
+ """Model for firmware update result data."""
194
+
195
+ data: FirmwareUpdateResultDataType = field(repr=False)
196
+ status: IntEnum = field(init=False)
197
+ success: bool = field(init=False)
198
+ _status_class: type[IntEnum] = field(init=False, repr=False)
199
+
200
+ def __post_init__(self) -> None:
201
+ """Post initialize."""
202
+ self.status = self._status_class(self.data["status"])
203
+ self.success = self.data["success"]
204
+
205
+
206
+ class FirmwareUpdateResultDataType(TypedDict):
207
+ """Represent a driver firmware update result dict type."""
208
+
209
+ status: int
210
+ success: bool
@@ -503,10 +503,7 @@ class Node(EventBase):
503
503
  if wait_for_result:
504
504
  result = await self.client.async_send_command(message, **kwargs)
505
505
  return result
506
- if wait_for_result is None and self.status not in (
507
- NodeStatus.ASLEEP,
508
- NodeStatus.DEAD,
509
- ):
506
+ if wait_for_result is None and self.status not in (NodeStatus.ASLEEP,):
510
507
  result_task = asyncio.create_task(
511
508
  self.client.async_send_command(message, **kwargs)
512
509
  )
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Literal
6
6
 
7
+ from pydantic import BaseModel
8
+
7
9
  from ...const import CommandClass
8
10
  from ...event import BaseEventModel
9
11
  from ..notification import (
@@ -20,11 +22,6 @@ from .firmware import (
20
22
  )
21
23
  from .statistics import NodeStatisticsDataType
22
24
 
23
- try:
24
- from pydantic.v1 import BaseModel
25
- except ImportError:
26
- from pydantic import BaseModel
27
-
28
25
 
29
26
  class BaseNodeEventModel(BaseEventModel):
30
27
  """Base model for a node event."""
@@ -101,8 +98,8 @@ class InterviewFailedEventArgsModel(BaseModel):
101
98
 
102
99
  errorMessage: str
103
100
  isFinal: bool
104
- attempt: int | None
105
- maxAttempts: int | None
101
+ attempt: int | None = None
102
+ maxAttempts: int | None = None
106
103
 
107
104
  @classmethod
108
105
  def from_dict(cls, data: dict) -> InterviewFailedEventArgsModel: