zwave-js-server-python 0.66.0__tar.gz → 0.67.1__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.66.0/zwave_js_server_python.egg-info → zwave_js_server_python-0.67.1}/PKG-INFO +1 -2
  2. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/pyproject.toml +0 -3
  3. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/__init__.py +3 -3
  4. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/config_manager/__init__.py +2 -2
  5. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/driver/__init__.py +73 -13
  6. zwave_js_server_python-0.67.1/zwave_js_server/model/driver/firmware.py +65 -0
  7. zwave_js_server_python-0.67.1/zwave_js_server/model/firmware.py +210 -0
  8. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/__init__.py +2 -5
  9. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/firmware.py +68 -132
  10. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1/zwave_js_server_python.egg-info}/PKG-INFO +1 -2
  11. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server_python.egg-info/SOURCES.txt +1 -0
  12. zwave_js_server_python-0.67.1/zwave_js_server_python.egg-info/requires.txt +2 -0
  13. zwave_js_server_python-0.66.0/zwave_js_server/model/driver/firmware.py +0 -97
  14. zwave_js_server_python-0.66.0/zwave_js_server_python.egg-info/requires.txt +0 -5
  15. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/LICENSE +0 -0
  16. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/MANIFEST.in +0 -0
  17. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/README.md +0 -0
  18. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/setup.cfg +0 -0
  19. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/__init__.py +0 -0
  20. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/__main__.py +0 -0
  21. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/client.py +0 -0
  22. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/__init__.py +0 -0
  23. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  24. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/basic.py +0 -0
  25. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/central_scene.py +0 -0
  26. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/color_switch.py +0 -0
  27. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/energy_production.py +0 -0
  28. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/entry_control.py +0 -0
  29. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  30. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/lock.py +0 -0
  31. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/meter.py +0 -0
  32. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  33. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  34. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/notification.py +0 -0
  35. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/power_level.py +0 -0
  36. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/protection.py +0 -0
  37. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/scene_activation.py +0 -0
  38. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  39. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/thermostat.py +0 -0
  40. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/wake_up.py +0 -0
  41. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/const/command_class/window_covering.py +0 -0
  42. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/dump.py +0 -0
  43. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/event.py +0 -0
  44. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/exceptions.py +0 -0
  45. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/firmware.py +0 -0
  46. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/__init__.py +0 -0
  47. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/association.py +0 -0
  48. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/command_class.py +0 -0
  49. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/__init__.py +0 -0
  50. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/data_model.py +0 -0
  51. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/event_model.py +0 -0
  52. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/inclusion_and_provisioning.py +0 -0
  53. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/rebuild_routes.py +0 -0
  54. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/controller/statistics.py +0 -0
  55. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/device_class.py +0 -0
  56. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/device_config.py +0 -0
  57. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/duration.py +0 -0
  58. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/endpoint.py +0 -0
  59. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/log_config.py +0 -0
  60. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/log_message.py +0 -0
  61. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/data_model.py +0 -0
  62. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/event_model.py +0 -0
  63. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/health_check.py +0 -0
  64. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/node/statistics.py +0 -0
  65. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/notification.py +0 -0
  66. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/statistics.py +0 -0
  67. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/utils.py +0 -0
  68. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/value.py +0 -0
  69. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/model/version.py +0 -0
  70. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/py.typed +0 -0
  71. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/__init__.py +0 -0
  72. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/command_class/__init__.py +0 -0
  73. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/command_class/energy_production.py +0 -0
  74. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/command_class/meter.py +0 -0
  75. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  76. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/helpers.py +0 -0
  77. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/lock.py +0 -0
  78. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/multicast.py +0 -0
  79. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/util/node.py +0 -0
  80. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server/version.py +0 -0
  81. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  82. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  83. {zwave_js_server_python-0.66.0 → zwave_js_server_python-0.67.1}/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.66.0
3
+ Version: 0.67.1
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
@@ -19,7 +19,6 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: aiohttp>3
21
21
  Requires-Dist: pydantic>=2.0.0
22
- Requires-Dist: typing_extensions>=4.13.0; python_version < "3.14"
23
22
  Dynamic: license-file
24
23
 
25
24
  # zwave-js-server-python
@@ -22,7 +22,6 @@ classifiers = [
22
22
  dependencies = [
23
23
  "aiohttp>3",
24
24
  "pydantic>=2.0.0",
25
- "typing_extensions>=4.13.0;python_version<'3.14'",
26
25
  ]
27
26
  dynamic = ["version"]
28
27
 
@@ -44,8 +43,6 @@ zwave_js_server = ["py.typed"]
44
43
 
45
44
  [tool.mypy]
46
45
  plugins = ["pydantic.mypy"]
47
- follow_imports = "skip"
48
- ignore_missing_imports = true
49
46
  check_untyped_defs = true
50
47
  disallow_incomplete_defs = true
51
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.66.0"
11
+ __version__ = "0.67.1"
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
 
@@ -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] = {
@@ -2,16 +2,20 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import sys
6
5
  from typing import TYPE_CHECKING, Any, Literal, cast
7
6
 
8
- from pydantic import create_model
7
+ from zwave_js_server.model.firmware import (
8
+ FirmwareUpdateData,
9
+ FirmwareUpdateDataDataType,
10
+ FirmwareUpdateInfo,
11
+ FirmwareUpdateInfoDataType,
12
+ )
9
13
 
10
14
  from ...event import BaseEventModel, Event, EventBase
11
15
  from ..config_manager import ConfigManager
12
16
  from ..controller import Controller
13
17
  from ..log_config import LogConfig, LogConfigDataType
14
- from ..log_message import LogMessage, LogMessageDataType
18
+ from ..log_message import LogMessage, LogMessageContextDataType, LogMessageDataType
15
19
  from .firmware import (
16
20
  DriverFirmwareUpdateProgress,
17
21
  DriverFirmwareUpdateProgressDataType,
@@ -19,11 +23,6 @@ from .firmware import (
19
23
  DriverFirmwareUpdateResultDataType,
20
24
  )
21
25
 
22
- if sys.version_info >= (3, 14):
23
- from annotationlib import get_annotations
24
- else:
25
- from typing_extensions import get_annotations
26
-
27
26
  if TYPE_CHECKING:
28
27
  from ...client import Client
29
28
 
@@ -56,11 +55,40 @@ class AllNodesReadyEventModel(BaseDriverEventModel):
56
55
  event: Literal["all nodes ready"]
57
56
 
58
57
 
59
- LoggingEventModel = create_model(
60
- "LoggingEventModel",
61
- **{k: (v, None) for k, v in get_annotations(LogMessageDataType).items()},
62
- __base__=BaseDriverEventModel,
63
- )
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
+ )
64
92
 
65
93
 
66
94
  class DriverReadyEventModel(BaseDriverEventModel):
@@ -221,6 +249,38 @@ class Driver(EventBase):
221
249
  )
222
250
  return cast(bool, result["success"])
223
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
+
224
284
  async def async_set_preferred_scales(
225
285
  self, scales: dict[str | int, str | int]
226
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
@@ -395,7 +395,7 @@ class Node(EventBase):
395
395
  values = {
396
396
  value_id: value
397
397
  for value_id, value in self.values.items()
398
- if self.index == value.endpoint
398
+ if endpoint_idx == value.endpoint
399
399
  }
400
400
  if endpoint_idx in self.endpoints:
401
401
  self.endpoints[endpoint_idx].update(endpoint, values)
@@ -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
  )
@@ -2,52 +2,56 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from dataclasses import asdict, dataclass, field
5
+ from dataclasses import dataclass, field
6
6
  from enum import IntEnum
7
- from typing import TYPE_CHECKING, Literal, TypedDict, cast
8
-
9
- from ...const import VALUE_UNKNOWN, RFRegion
10
- from ...util.helpers import convert_bytes_to_base64
7
+ from typing import TYPE_CHECKING, Required, TypedDict, cast
8
+
9
+ from zwave_js_server.model.firmware import (
10
+ FirmwareUpdateData,
11
+ FirmwareUpdateDataDataType,
12
+ FirmwareUpdateDeviceID,
13
+ FirmwareUpdateDeviceIDDataType,
14
+ FirmwareUpdateFileInfo,
15
+ FirmwareUpdateFileInfoDataType,
16
+ FirmwareUpdateInfo,
17
+ FirmwareUpdateInfoDataType,
18
+ FirmwareUpdateProgress,
19
+ FirmwareUpdateProgressDataType,
20
+ FirmwareUpdateResult,
21
+ FirmwareUpdateResultDataType,
22
+ )
23
+
24
+ from ...const import VALUE_UNKNOWN
11
25
 
12
26
  if TYPE_CHECKING:
13
27
  from . import Node
14
28
 
15
29
 
16
- class NodeFirmwareUpdateDataDataType(TypedDict, total=False):
30
+ class NodeFirmwareUpdateDataDataType(FirmwareUpdateDataDataType):
17
31
  """Represent a firmware update data dict type."""
18
32
 
19
- filename: str # required
20
- file: str # required
21
- fileFormat: str
22
33
  firmwareTarget: int
23
34
 
24
35
 
25
36
  @dataclass
26
- class NodeFirmwareUpdateData:
37
+ class NodeFirmwareUpdateData(FirmwareUpdateData):
27
38
  """Firmware update data."""
28
39
 
29
- filename: str
30
- file: bytes
31
- file_format: str | None = None
32
40
  firmware_target: int | None = None
33
41
 
34
42
  def to_dict(self) -> NodeFirmwareUpdateDataDataType:
35
43
  """Convert firmware update data to dict."""
36
- data: NodeFirmwareUpdateDataDataType = {
37
- "filename": self.filename,
38
- "file": convert_bytes_to_base64(self.file),
39
- }
40
- if self.file_format is not None:
41
- data["fileFormat"] = self.file_format
44
+ data = super().to_dict()
45
+ node_data = cast(NodeFirmwareUpdateDataDataType, data)
42
46
  if self.firmware_target is not None:
43
- data["firmwareTarget"] = self.firmware_target
44
- return data
47
+ node_data["firmwareTarget"] = self.firmware_target
48
+ return node_data
45
49
 
46
50
 
47
51
  class NodeFirmwareUpdateCapabilitiesDataType(TypedDict, total=False):
48
52
  """Represent a firmware update capabilities dict type."""
49
53
 
50
- firmwareUpgradable: bool # required
54
+ firmwareUpgradable: Required[bool]
51
55
  firmwareTargets: list[int]
52
56
  continuesToFunction: bool | str
53
57
  supportsActivation: bool | str
@@ -56,7 +60,7 @@ class NodeFirmwareUpdateCapabilitiesDataType(TypedDict, total=False):
56
60
  class NodeFirmwareUpdateCapabilitiesDict(TypedDict, total=False):
57
61
  """Represent a dict from FirmwareUpdateCapabilities."""
58
62
 
59
- firmware_upgradable: bool # required
63
+ firmware_upgradable: Required[bool]
60
64
  firmware_targets: list[int]
61
65
  continues_to_function: bool | None
62
66
  supports_activation: bool | None
@@ -133,193 +137,125 @@ class NodeFirmwareUpdateStatus(IntEnum):
133
137
  OK_RESTART_PENDING = 255
134
138
 
135
139
 
136
- class NodeFirmwareUpdateProgressDataType(TypedDict):
140
+ class NodeFirmwareUpdateProgressDataType(FirmwareUpdateProgressDataType):
137
141
  """Represent a node firmware update progress dict type."""
138
142
 
139
143
  currentFile: int
140
144
  totalFiles: int
141
- sentFragments: int
142
- totalFragments: int
143
- progress: float
144
145
 
145
146
 
146
147
  @dataclass
147
- class NodeFirmwareUpdateProgress:
148
+ class NodeFirmwareUpdateProgress(FirmwareUpdateProgress):
148
149
  """Model for a node firmware update progress data."""
149
150
 
150
151
  node: Node
151
152
  data: NodeFirmwareUpdateProgressDataType = field(repr=False)
152
153
  current_file: int = field(init=False)
153
154
  total_files: int = field(init=False)
154
- sent_fragments: int = field(init=False)
155
- total_fragments: int = field(init=False)
156
- progress: float = field(init=False)
155
+
156
+ def __init__(self, node: Node, data: NodeFirmwareUpdateProgressDataType) -> None:
157
+ """Initialize the node firmware update progress.
158
+
159
+ Explicit init method to retain backwards compatibility
160
+ that requires the node as first parameter.
161
+ """
162
+ super().__init__(data)
163
+ self.node = node
157
164
 
158
165
  def __post_init__(self) -> None:
159
166
  """Post initialize."""
167
+ super().__post_init__()
160
168
  self.current_file = self.data["currentFile"]
161
169
  self.total_files = self.data["totalFiles"]
162
- self.sent_fragments = self.data["sentFragments"]
163
- self.total_fragments = self.data["totalFragments"]
164
- self.progress = float(self.data["progress"])
165
170
 
166
171
 
167
- class NodeFirmwareUpdateResultDataType(TypedDict, total=False):
172
+ class NodeFirmwareUpdateResultDataType(FirmwareUpdateResultDataType, total=False):
168
173
  """Represent a node firmware update result dict type."""
169
174
 
170
- status: int # required
171
- success: bool # required
172
175
  waitTime: int
173
- reInterview: bool # required
176
+ reInterview: Required[bool]
174
177
 
175
178
 
176
179
  @dataclass
177
- class NodeFirmwareUpdateResult:
180
+ class NodeFirmwareUpdateResult(FirmwareUpdateResult):
178
181
  """Model for node firmware update result data."""
179
182
 
180
183
  node: Node
181
184
  data: NodeFirmwareUpdateResultDataType = field(repr=False)
182
185
  status: NodeFirmwareUpdateStatus = field(init=False)
183
- success: bool = field(init=False)
184
186
  wait_time: int | None = field(init=False)
185
187
  reinterview: bool = field(init=False)
188
+ _status_class = NodeFirmwareUpdateStatus
189
+
190
+ def __init__(self, node: Node, data: NodeFirmwareUpdateResultDataType) -> None:
191
+ """Initialize the node firmware update result.
192
+
193
+ Explicit init method to retain backwards compatibility
194
+ that requires the node as first parameter.
195
+ """
196
+ super().__init__(data)
197
+ self.node = node
186
198
 
187
199
  def __post_init__(self) -> None:
188
200
  """Post initialize."""
189
- self.status = NodeFirmwareUpdateStatus(self.data["status"])
190
- self.success = self.data["success"]
201
+ super().__post_init__()
191
202
  self.wait_time = self.data.get("waitTime")
192
203
  self.reinterview = self.data["reInterview"]
193
204
 
194
205
 
195
- class NodeFirmwareUpdateFileInfoDataType(TypedDict):
196
- """Represent a firmware update file info data dict type."""
197
-
198
- target: int
199
- url: str
200
- integrity: str # sha256
206
+ class NodeFirmwareUpdateFileInfoDataType(FirmwareUpdateFileInfoDataType):
207
+ """Represent a node firmware update file info data dict type."""
201
208
 
202
209
 
203
210
  @dataclass
204
- class NodeFirmwareUpdateFileInfo:
211
+ class NodeFirmwareUpdateFileInfo(FirmwareUpdateFileInfo):
205
212
  """Represent a firmware update file info."""
206
213
 
207
- target: int
208
- url: str
209
- integrity: str
210
-
211
214
  @classmethod
212
215
  def from_dict(
213
216
  cls, data: NodeFirmwareUpdateFileInfoDataType
214
217
  ) -> NodeFirmwareUpdateFileInfo:
215
218
  """Initialize from dict."""
216
- return cls(
217
- target=data["target"],
218
- url=data["url"],
219
- integrity=data["integrity"],
220
- )
219
+ return cast(NodeFirmwareUpdateFileInfo, super().from_dict(data))
221
220
 
222
221
  def to_dict(self) -> NodeFirmwareUpdateFileInfoDataType:
223
222
  """Return dict representation of the object."""
224
- return cast(NodeFirmwareUpdateFileInfoDataType, asdict(self))
225
-
223
+ return super().to_dict()
226
224
 
227
- class NodeFirmwareUpdateDeviceIDDataType(TypedDict, total=False):
228
- """Represent a firmware update device ID dict type."""
229
225
 
230
- manufacturerId: int # required
231
- productType: int # required
232
- productId: int # required
233
- firmwareVersion: str # required
234
- rfRegion: int
226
+ class NodeFirmwareUpdateDeviceIDDataType(FirmwareUpdateDeviceIDDataType):
227
+ """Represent a node firmware update device ID data dict type."""
235
228
 
236
229
 
237
230
  @dataclass
238
- class NodeFirmwareUpdateDeviceID:
231
+ class NodeFirmwareUpdateDeviceID(FirmwareUpdateDeviceID):
239
232
  """Represent a firmware update device ID."""
240
233
 
241
- manufacturer_id: int
242
- product_type: int
243
- product_id: int
244
- firmware_version: str
245
- rf_region: RFRegion | None
246
-
247
234
  @classmethod
248
235
  def from_dict(
249
236
  cls, data: NodeFirmwareUpdateDeviceIDDataType
250
237
  ) -> NodeFirmwareUpdateDeviceID:
251
238
  """Initialize from dict."""
252
- return cls(
253
- manufacturer_id=data["manufacturerId"],
254
- product_type=data["productType"],
255
- product_id=data["productId"],
256
- firmware_version=data["firmwareVersion"],
257
- rf_region=RFRegion(data["rfRegion"]) if "rfRegion" in data else None,
258
- )
239
+ return cast(NodeFirmwareUpdateDeviceID, super().from_dict(data))
259
240
 
260
241
  def to_dict(self) -> NodeFirmwareUpdateDeviceIDDataType:
261
242
  """Return dict representation of the object."""
262
- data = {
263
- "manufacturerId": self.manufacturer_id,
264
- "productType": self.product_type,
265
- "productId": self.product_id,
266
- "firmwareVersion": self.firmware_version,
267
- }
268
- if self.rf_region is not None:
269
- data["rfRegion"] = self.rf_region
270
- return cast(NodeFirmwareUpdateDeviceIDDataType, data)
271
-
243
+ return super().to_dict()
272
244
 
273
- class NodeFirmwareUpdateInfoDataType(TypedDict, total=False):
274
- """Represent a firmware update info data dict type."""
275
245
 
276
- version: str
277
- changelog: str
278
- channel: Literal["stable", "beta"]
279
- files: list[NodeFirmwareUpdateFileInfoDataType]
280
- downgrade: bool
281
- normalizedVersion: str
282
- device: NodeFirmwareUpdateDeviceIDDataType
246
+ class NodeFirmwareUpdateInfoDataType(FirmwareUpdateInfoDataType):
247
+ """Represent a node firmware update info data dict type."""
283
248
 
284
249
 
285
250
  @dataclass
286
- class NodeFirmwareUpdateInfo:
251
+ class NodeFirmwareUpdateInfo(FirmwareUpdateInfo):
287
252
  """Represent a firmware update info."""
288
253
 
289
- version: str
290
- changelog: str
291
- channel: Literal["stable", "beta"]
292
- files: list[NodeFirmwareUpdateFileInfo]
293
- downgrade: bool
294
- normalized_version: str
295
- device: NodeFirmwareUpdateDeviceID
296
-
297
254
  @classmethod
298
255
  def from_dict(cls, data: NodeFirmwareUpdateInfoDataType) -> NodeFirmwareUpdateInfo:
299
256
  """Initialize from dict."""
300
- return cls(
301
- version=data["version"],
302
- changelog=data["changelog"],
303
- channel=data["channel"],
304
- files=[
305
- NodeFirmwareUpdateFileInfo.from_dict(file) for file in data["files"]
306
- ],
307
- downgrade=data["downgrade"],
308
- normalized_version=data["normalizedVersion"],
309
- device=NodeFirmwareUpdateDeviceID.from_dict(data["device"]),
310
- )
257
+ return cast(NodeFirmwareUpdateInfo, super().from_dict(data))
311
258
 
312
259
  def to_dict(self) -> NodeFirmwareUpdateInfoDataType:
313
260
  """Return dict representation of the object."""
314
- return cast(
315
- NodeFirmwareUpdateInfoDataType,
316
- {
317
- "version": self.version,
318
- "changelog": self.changelog,
319
- "channel": self.channel,
320
- "files": [file.to_dict() for file in self.files],
321
- "downgrade": self.downgrade,
322
- "normalizedVersion": self.normalized_version,
323
- "device": self.device.to_dict(),
324
- },
325
- )
261
+ return super().to_dict()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zwave-js-server-python
3
- Version: 0.66.0
3
+ Version: 0.67.1
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
@@ -19,7 +19,6 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: aiohttp>3
21
21
  Requires-Dist: pydantic>=2.0.0
22
- Requires-Dist: typing_extensions>=4.13.0; python_version < "3.14"
23
22
  Dynamic: license-file
24
23
 
25
24
  # zwave-js-server-python
@@ -39,6 +39,7 @@ zwave_js_server/model/device_class.py
39
39
  zwave_js_server/model/device_config.py
40
40
  zwave_js_server/model/duration.py
41
41
  zwave_js_server/model/endpoint.py
42
+ zwave_js_server/model/firmware.py
42
43
  zwave_js_server/model/log_config.py
43
44
  zwave_js_server/model/log_message.py
44
45
  zwave_js_server/model/notification.py
@@ -0,0 +1,2 @@
1
+ aiohttp>3
2
+ pydantic>=2.0.0
@@ -1,97 +0,0 @@
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
- from typing import TypedDict
8
-
9
- from ...util.helpers import convert_bytes_to_base64
10
-
11
-
12
- class DriverFirmwareUpdateDataDataType(TypedDict, total=False):
13
- """Represent a driver firmware update data dict type."""
14
-
15
- filename: str # required
16
- file: str # required
17
- fileFormat: str
18
-
19
-
20
- @dataclass
21
- class DriverFirmwareUpdateData:
22
- """Driver firmware update data."""
23
-
24
- filename: str
25
- file: bytes
26
- file_format: str | None = None
27
-
28
- def to_dict(self) -> DriverFirmwareUpdateDataDataType:
29
- """Convert firmware update data to dict."""
30
- data: DriverFirmwareUpdateDataDataType = {
31
- "filename": self.filename,
32
- "file": convert_bytes_to_base64(self.file),
33
- }
34
- if self.file_format is not None:
35
- data["fileFormat"] = self.file_format
36
- return data
37
-
38
-
39
- class DriverFirmwareUpdateStatus(IntEnum):
40
- """Enum with all driver firmware update status values.
41
-
42
- https://zwave-js.github.io/node-zwave-js/#/api/driver?id=quotfirmware-update-finishedquot
43
- """
44
-
45
- ERROR_TIMEOUT = 0
46
- # The maximum number of retry attempts for a firmware fragments were reached
47
- ERROR_RETRY_LIMIT_REACHED = 1
48
- # The update was aborted by the bootloader
49
- ERROR_ABORTED = 2
50
- # This driver does not support firmware updates
51
- ERROR_NOT_SUPPORTED = 3
52
- OK = 255
53
-
54
-
55
- class DriverFirmwareUpdateProgressDataType(TypedDict):
56
- """Represent a driver firmware update progress dict type."""
57
-
58
- sentFragments: int
59
- totalFragments: int
60
- progress: float
61
-
62
-
63
- @dataclass
64
- class DriverFirmwareUpdateProgress:
65
- """Model for a driver firmware update progress data."""
66
-
67
- data: DriverFirmwareUpdateProgressDataType = field(repr=False)
68
- sent_fragments: int = field(init=False)
69
- total_fragments: int = field(init=False)
70
- progress: float = field(init=False)
71
-
72
- def __post_init__(self) -> None:
73
- """Post initialize."""
74
- self.sent_fragments = self.data["sentFragments"]
75
- self.total_fragments = self.data["totalFragments"]
76
- self.progress = float(self.data["progress"])
77
-
78
-
79
- class DriverFirmwareUpdateResultDataType(TypedDict):
80
- """Represent a driver firmware update result dict type."""
81
-
82
- status: int
83
- success: bool
84
-
85
-
86
- @dataclass
87
- class DriverFirmwareUpdateResult:
88
- """Model for driver firmware update result data."""
89
-
90
- data: DriverFirmwareUpdateResultDataType = field(repr=False)
91
- status: DriverFirmwareUpdateStatus = field(init=False)
92
- success: bool = field(init=False)
93
-
94
- def __post_init__(self) -> None:
95
- """Post initialize."""
96
- self.status = DriverFirmwareUpdateStatus(self.data["status"])
97
- self.success = self.data["success"]
@@ -1,5 +0,0 @@
1
- aiohttp>3
2
- pydantic>=2.0.0
3
-
4
- [:python_version < "3.14"]
5
- typing_extensions>=4.13.0