zwave-js-server-python 0.56.0__tar.gz → 0.58.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/PKG-INFO +3 -3
  2. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/pyproject.toml +18 -16
  3. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/__init__.py +16 -3
  4. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/lock.py +1 -0
  5. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/meter.py +4 -1
  6. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/event.py +8 -0
  7. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/association.py +13 -0
  8. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/command_class.py +4 -0
  9. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/__init__.py +10 -7
  10. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/event_model.py +139 -0
  11. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/inclusion_and_provisioning.py +36 -32
  12. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/device_class.py +12 -17
  13. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/driver.py +10 -1
  14. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/__init__.py +14 -16
  15. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/event_model.py +127 -0
  16. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/firmware.py +5 -1
  17. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/value.py +0 -8
  18. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server_python.egg-info/PKG-INFO +3 -3
  19. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/LICENSE +0 -0
  20. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/MANIFEST.in +0 -0
  21. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/README.md +0 -0
  22. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/setup.cfg +0 -0
  23. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/__init__.py +0 -0
  24. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/__main__.py +0 -0
  25. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/client.py +0 -0
  26. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/__init__.py +0 -0
  27. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  28. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/basic.py +0 -0
  29. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/central_scene.py +0 -0
  30. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/color_switch.py +0 -0
  31. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/energy_production.py +0 -0
  32. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/entry_control.py +0 -0
  33. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  34. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  35. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  36. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/notification.py +0 -0
  37. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/power_level.py +0 -0
  38. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/protection.py +0 -0
  39. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/scene_activation.py +0 -0
  40. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  41. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/thermostat.py +0 -0
  42. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/wake_up.py +0 -0
  43. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/const/command_class/window_covering.py +0 -0
  44. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/dump.py +0 -0
  45. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/exceptions.py +0 -0
  46. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/firmware.py +0 -0
  47. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/__init__.py +0 -0
  48. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/data_model.py +0 -0
  49. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/firmware.py +0 -0
  50. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/rebuild_routes.py +0 -0
  51. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/controller/statistics.py +0 -0
  52. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/device_config.py +0 -0
  53. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/duration.py +0 -0
  54. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/endpoint.py +0 -0
  55. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/log_config.py +0 -0
  56. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/log_message.py +0 -0
  57. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/data_model.py +0 -0
  58. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/health_check.py +0 -0
  59. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/node/statistics.py +0 -0
  60. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/notification.py +0 -0
  61. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/statistics.py +0 -0
  62. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/utils.py +0 -0
  63. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/model/version.py +0 -0
  64. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/py.typed +0 -0
  65. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/__init__.py +0 -0
  66. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/command_class/__init__.py +0 -0
  67. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/command_class/energy_production.py +0 -0
  68. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/command_class/meter.py +0 -0
  69. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  70. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/helpers.py +0 -0
  71. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/lock.py +0 -0
  72. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/multicast.py +0 -0
  73. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/util/node.py +0 -0
  74. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server/version.py +0 -0
  75. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server_python.egg-info/SOURCES.txt +0 -0
  76. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  77. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  78. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.0}/zwave_js_server_python.egg-info/requires.txt +0 -0
  79. {zwave_js_server_python-0.56.0 → zwave_js_server_python-0.58.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.56.0
3
+ Version: 0.58.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Author-email: Home Assistant Team <hello@home-assistant.io>
6
6
  License: Apache License
@@ -212,9 +212,9 @@ Classifier: Development Status :: 4 - Beta
212
212
  Classifier: Intended Audience :: Developers
213
213
  Classifier: Natural Language :: English
214
214
  Classifier: Programming Language :: Python :: 3
215
- Classifier: Programming Language :: Python :: 3.11
215
+ Classifier: Programming Language :: Python :: 3.12
216
216
  Classifier: Topic :: Home Automation
217
- Requires-Python: >=3.11
217
+ Requires-Python: >=3.12
218
218
  Description-Content-Type: text/markdown
219
219
  License-File: LICENSE
220
220
  Requires-Dist: aiohttp>3
@@ -7,7 +7,7 @@ name = "zwave-js-server-python"
7
7
  authors = [{ name = "Home Assistant Team", email = "hello@home-assistant.io" }]
8
8
  description = "Python wrapper for zwave-js-server"
9
9
  readme = "README.md"
10
- requires-python = ">=3.11"
10
+ requires-python = ">=3.12"
11
11
  license = { file = "LICENSE" }
12
12
  keywords = ["home", "automation", "zwave", "zwave-js"]
13
13
  classifiers = [
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Intended Audience :: Developers",
16
16
  "Natural Language :: English",
17
17
  "Programming Language :: Python :: 3",
18
- "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
19
  "Topic :: Home Automation",
20
20
  ]
21
21
  dependencies = ["aiohttp>3", "pydantic>=1.10.0"]
@@ -88,6 +88,20 @@ expected-line-ending-format = "LF"
88
88
  asyncio_mode = "auto"
89
89
 
90
90
  [tool.ruff]
91
+ exclude = [
92
+ ".venv",
93
+ ".git",
94
+ ".tox",
95
+ "docs",
96
+ "venv",
97
+ "bin",
98
+ "lib",
99
+ "deps",
100
+ "build",
101
+ ]
102
+ line-length = 88
103
+
104
+ [tool.ruff.lint]
91
105
  select = ["D", "E", "F", "G", "I", "PLC", "PLE", "PLR", "PLW", "UP", "W"]
92
106
  ignore = [
93
107
  "D202",
@@ -104,22 +118,10 @@ ignore = [
104
118
  "UP006", # keep type annotation style as is
105
119
  "UP007", # keep type annotation style as is
106
120
  ]
107
- exclude = [
108
- ".venv",
109
- ".git",
110
- ".tox",
111
- "docs",
112
- "venv",
113
- "bin",
114
- "lib",
115
- "deps",
116
- "build",
117
- ]
118
- line-length = 88
119
121
 
120
- [tool.ruff.isort]
122
+ [tool.ruff.lint.isort]
121
123
  force-sort-within-sections = true
122
- known-first-party = ["zwave_js_server"]
124
+ known-first-party = ["zwave_js_server", "test"]
123
125
  combine-as-imports = true
124
126
  split-on-trailing-comma = false
125
127
  case-sensitive = 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.56.0"
11
+ __version__ = "0.58.0"
12
12
 
13
13
  # minimal server schema version we can handle
14
- MIN_SERVER_SCHEMA_VERSION = 35
14
+ MIN_SERVER_SCHEMA_VERSION = 37
15
15
  # max server schema version we can handle (and our code is compatible with)
16
- MAX_SERVER_SCHEMA_VERSION = 35
16
+ MAX_SERVER_SCHEMA_VERSION = 37
17
17
 
18
18
  VALUE_UNKNOWN = "unknown"
19
19
 
@@ -505,3 +505,16 @@ class SupervisionStatus(IntEnum):
505
505
  WORKING = 1
506
506
  FAIL = 2
507
507
  SUCCESS = 255
508
+
509
+
510
+ class AssociationCheckResult(IntEnum):
511
+ """Enum for all known association check results."""
512
+
513
+ # https://github.com/zwave-js/node-zwave-js/blob/master/packages/cc/src/lib/_Types.ts#L47
514
+ OK = 1
515
+ FORBIDDEN_DESTINATION_IS_LONG_RANGE = 2
516
+ FORBIDDEN_SOURCE_IS_LONG_RANGE = 3
517
+ FORBIDDEN_SELF_ASSOCIATION = 4
518
+ FORBIDDEN_SECURITY_CLASS_MISMATCH = 5
519
+ FORBIDDEN_DESTINATION_SECURITY_CLASS_NOT_GRANTED = 6
520
+ FORBIDDEN_NO_SUPPORTED_CCS = 7
@@ -186,6 +186,7 @@ class DoorLockCCConfigurationSetOptions:
186
186
  ),
187
187
  )
188
188
  for prop_name, val in (
189
+ (TARGET_LOCK_TIMEOUT_PROPERTY, self.lock_timeout_configuration),
189
190
  (TARGET_AUTO_RELOCK_TIME_PROPERTY, self.auto_relock_time),
190
191
  (TARGET_HOLD_AND_RELEASE_TIME_PROPERTY, self.hold_and_release_time),
191
192
  (TARGET_TWIST_ASSIST_PROPERTY, self.twist_assist),
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from enum import IntEnum
6
6
 
7
7
  VALUE_PROPERTY = "value"
8
+ RESET_PROPERTY = "reset"
8
9
 
9
10
  CC_SPECIFIC_SCALE = "scale"
10
11
  CC_SPECIFIC_METER_TYPE = "meterType"
@@ -13,9 +14,11 @@ CC_SPECIFIC_RATE_TYPE = "rateType"
13
14
  RESET_METER_CC_API = "reset"
14
15
 
15
16
  # optional attributes when calling the Meter CC reset API.
16
- # https://github.com/zwave-js/node-zwave-js/blob/master/packages/zwave-js/src/lib/commandclass/MeterCC.ts#L873-L881
17
+ # https://github.com/zwave-js/node-zwave-js/blob/master/packages/cc/src/cc/MeterCC.ts
17
18
  RESET_METER_OPTION_TARGET_VALUE = "targetValue"
18
19
  RESET_METER_OPTION_TYPE = "type"
20
+ RESET_METER_OPTION_SCALE = "scale"
21
+ RESET_METER_OPTION_RATE_TYPE = "rateType"
19
22
 
20
23
 
21
24
  # https://github.com/zwave-js/node-zwave-js/blob/master/packages/config/config/meters.json
@@ -21,6 +21,14 @@ class BaseEventModel(BaseModel):
21
21
  source: Literal["controller", "driver", "node"]
22
22
  event: str
23
23
 
24
+ @classmethod
25
+ def from_dict(cls, data: dict) -> BaseEventModel:
26
+ """Initialize from dict."""
27
+ return cls(
28
+ source=data["source"],
29
+ event=data["event"],
30
+ )
31
+
24
32
 
25
33
  @dataclass
26
34
  class Event:
@@ -3,6 +3,11 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from .controller import Controller
10
+ from .node import Node
6
11
 
7
12
 
8
13
  @dataclass
@@ -21,5 +26,13 @@ class AssociationGroup:
21
26
  class AssociationAddress:
22
27
  """Represent a association dict type."""
23
28
 
29
+ controller: Controller
24
30
  node_id: int
25
31
  endpoint: int | None = None
32
+
33
+ @property
34
+ def node(self) -> Node | None:
35
+ """Return the node."""
36
+ if self.node_id in self.controller.nodes:
37
+ return self.controller.nodes[self.node_id]
38
+ return None
@@ -51,3 +51,7 @@ class CommandClassInfo:
51
51
  def is_secure(self) -> bool:
52
52
  """Return if the CommandClass is used securely on this node/endpoint."""
53
53
  return self.data["isSecure"]
54
+
55
+ def to_dict(self) -> CommandClassInfoDataType:
56
+ """Create a dictionary from itself."""
57
+ return self.data.copy()
@@ -9,6 +9,7 @@ from zwave_js_server.model.node.firmware import NodeFirmwareUpdateInfo
9
9
 
10
10
  from ...const import (
11
11
  MINIMUM_QR_STRING_LENGTH,
12
+ AssociationCheckResult,
12
13
  ControllerStatus,
13
14
  ExclusionStrategy,
14
15
  InclusionState,
@@ -574,6 +575,7 @@ class Controller(EventBase):
574
575
  for key, association_addresses in data["associations"].items():
575
576
  associations[int(key)] = [
576
577
  AssociationAddress(
578
+ self,
577
579
  node_id=association_address["nodeId"],
578
580
  endpoint=association_address.get("endpoint"),
579
581
  )
@@ -581,10 +583,10 @@ class Controller(EventBase):
581
583
  ]
582
584
  return associations
583
585
 
584
- async def async_is_association_allowed(
586
+ async def async_check_association(
585
587
  self, source: AssociationAddress, group: int, association: AssociationAddress
586
- ) -> bool:
587
- """Send isAssociationAllowed command to Controller."""
588
+ ) -> AssociationCheckResult:
589
+ """Send checkAssociation command to Controller."""
588
590
  source_data = {"nodeId": source.node_id}
589
591
  if source.endpoint is not None:
590
592
  source_data["endpoint"] = source.endpoint
@@ -594,13 +596,14 @@ class Controller(EventBase):
594
596
  association_data["endpoint"] = association.endpoint
595
597
  data = await self.client.async_send_command(
596
598
  {
597
- "command": "controller.is_association_allowed",
599
+ "command": "controller.check_association",
598
600
  **source_data,
599
601
  "group": group,
600
602
  "association": association_data,
601
- }
603
+ },
604
+ require_schema=37,
602
605
  )
603
- return cast(bool, data["allowed"])
606
+ return AssociationCheckResult(data["result"])
604
607
 
605
608
  async def async_add_associations(
606
609
  self,
@@ -869,7 +872,7 @@ class Controller(EventBase):
869
872
  f"{event.data}"
870
873
  )
871
874
 
872
- CONTROLLER_EVENT_MODEL_MAP[event.type](**event.data)
875
+ CONTROLLER_EVENT_MODEL_MAP[event.type].from_dict(event.data)
873
876
 
874
877
  self._handle_event_protocol(event)
875
878
 
@@ -52,6 +52,15 @@ class FirmwareUpdateFinishedEventModel(BaseControllerEventModel):
52
52
  event: Literal["firmware update finished"]
53
53
  result: ControllerFirmwareUpdateResultDataType
54
54
 
55
+ @classmethod
56
+ def from_dict(cls, data: dict) -> FirmwareUpdateFinishedEventModel:
57
+ """Initialize from dict."""
58
+ return cls(
59
+ source=data["source"],
60
+ event=data["event"],
61
+ result=data["result"],
62
+ )
63
+
55
64
 
56
65
  class FirmwareUpdateProgressEventModel(BaseControllerEventModel):
57
66
  """Model for `firmware update progress` event data."""
@@ -59,6 +68,15 @@ class FirmwareUpdateProgressEventModel(BaseControllerEventModel):
59
68
  event: Literal["firmware update progress"]
60
69
  progress: ControllerFirmwareUpdateProgressDataType
61
70
 
71
+ @classmethod
72
+ def from_dict(cls, data: dict) -> FirmwareUpdateProgressEventModel:
73
+ """Initialize from dict."""
74
+ return cls(
75
+ source=data["source"],
76
+ event=data["event"],
77
+ progress=data["progress"],
78
+ )
79
+
62
80
 
63
81
  class GrantSecurityClassesEventModel(BaseControllerEventModel):
64
82
  """Model for `grant security classes` event data."""
@@ -66,6 +84,15 @@ class GrantSecurityClassesEventModel(BaseControllerEventModel):
66
84
  event: Literal["grant security classes"]
67
85
  requested: InclusionGrantDataType
68
86
 
87
+ @classmethod
88
+ def from_dict(cls, data: dict) -> GrantSecurityClassesEventModel:
89
+ """Initialize from dict."""
90
+ return cls(
91
+ source=data["source"],
92
+ event=data["event"],
93
+ requested=data["requested"],
94
+ )
95
+
69
96
 
70
97
  class RebuildRoutesDoneEventModel(BaseControllerEventModel):
71
98
  """Model for `rebuild routes done` event data."""
@@ -73,6 +100,15 @@ class RebuildRoutesDoneEventModel(BaseControllerEventModel):
73
100
  event: Literal["rebuild routes done"]
74
101
  result: dict[str, str]
75
102
 
103
+ @classmethod
104
+ def from_dict(cls, data: dict) -> RebuildRoutesDoneEventModel:
105
+ """Initialize from dict."""
106
+ return cls(
107
+ source=data["source"],
108
+ event=data["event"],
109
+ result=data["result"],
110
+ )
111
+
76
112
 
77
113
  class RebuildRoutesProgressEventModel(BaseControllerEventModel):
78
114
  """Model for `rebuild routes progress` event data."""
@@ -80,6 +116,15 @@ class RebuildRoutesProgressEventModel(BaseControllerEventModel):
80
116
  event: Literal["rebuild routes progress"]
81
117
  progress: dict[str, str]
82
118
 
119
+ @classmethod
120
+ def from_dict(cls, data: dict) -> RebuildRoutesProgressEventModel:
121
+ """Initialize from dict."""
122
+ return cls(
123
+ source=data["source"],
124
+ event=data["event"],
125
+ progress=data["progress"],
126
+ )
127
+
83
128
 
84
129
  class InclusionAbortedEventModel(BaseControllerEventModel):
85
130
  """Model for `inclusion aborted` event data."""
@@ -99,6 +144,15 @@ class InclusionStartedEventModel(BaseControllerEventModel):
99
144
  event: Literal["inclusion started"]
100
145
  secure: bool
101
146
 
147
+ @classmethod
148
+ def from_dict(cls, data: dict) -> InclusionStartedEventModel:
149
+ """Initialize from dict."""
150
+ return cls(
151
+ source=data["source"],
152
+ event=data["event"],
153
+ secure=data["secure"],
154
+ )
155
+
102
156
 
103
157
  class InclusionStoppedEventModel(BaseControllerEventModel):
104
158
  """Model for `inclusion stopped` event data."""
@@ -113,6 +167,16 @@ class NodeAddedEventModel(BaseControllerEventModel):
113
167
  node: NodeDataType
114
168
  result: InclusionResultDataType
115
169
 
170
+ @classmethod
171
+ def from_dict(cls, data: dict) -> NodeAddedEventModel:
172
+ """Initialize from dict."""
173
+ return cls(
174
+ source=data["source"],
175
+ event=data["event"],
176
+ node=data["node"],
177
+ result=data["result"],
178
+ )
179
+
116
180
 
117
181
  class NodeFoundEventModel(BaseControllerEventModel):
118
182
  """Model for `node found` event data."""
@@ -120,6 +184,15 @@ class NodeFoundEventModel(BaseControllerEventModel):
120
184
  event: Literal["node found"]
121
185
  node: FoundNodeDataType
122
186
 
187
+ @classmethod
188
+ def from_dict(cls, data: dict) -> NodeFoundEventModel:
189
+ """Initialize from dict."""
190
+ return cls(
191
+ source=data["source"],
192
+ event=data["event"],
193
+ node=data["node"],
194
+ )
195
+
123
196
 
124
197
  class NodeRemovedEventModel(BaseControllerEventModel):
125
198
  """Model for `node removed` event data."""
@@ -128,6 +201,16 @@ class NodeRemovedEventModel(BaseControllerEventModel):
128
201
  node: NodeDataType
129
202
  reason: RemoveNodeReason
130
203
 
204
+ @classmethod
205
+ def from_dict(cls, data: dict) -> NodeRemovedEventModel:
206
+ """Initialize from dict."""
207
+ return cls(
208
+ source=data["source"],
209
+ event=data["event"],
210
+ node=data["node"],
211
+ reason=data["reason"],
212
+ )
213
+
131
214
 
132
215
  class NVMBackupAndConvertProgressEventModel(BaseControllerEventModel):
133
216
  """Base model for `nvm backup progress` and `nvm convert progress` event data."""
@@ -135,6 +218,16 @@ class NVMBackupAndConvertProgressEventModel(BaseControllerEventModel):
135
218
  bytesRead: int
136
219
  total: int
137
220
 
221
+ @classmethod
222
+ def from_dict(cls, data: dict) -> NVMBackupAndConvertProgressEventModel:
223
+ """Initialize from dict."""
224
+ return cls(
225
+ source=data["source"],
226
+ event=data["event"],
227
+ bytesRead=data["bytesRead"],
228
+ total=data["total"],
229
+ )
230
+
138
231
 
139
232
  class NVMBackupProgressEventModel(NVMBackupAndConvertProgressEventModel):
140
233
  """Model for `nvm backup progress` event data."""
@@ -155,6 +248,16 @@ class NVMRestoreProgressEventModel(BaseControllerEventModel):
155
248
  bytesWritten: int
156
249
  total: int
157
250
 
251
+ @classmethod
252
+ def from_dict(cls, data: dict) -> NVMRestoreProgressEventModel:
253
+ """Initialize from dict."""
254
+ return cls(
255
+ source=data["source"],
256
+ event=data["event"],
257
+ bytesWritten=data["bytesWritten"],
258
+ total=data["total"],
259
+ )
260
+
158
261
 
159
262
  class StatisticsUpdatedEventModel(BaseControllerEventModel):
160
263
  """Model for `statistics updated` event data."""
@@ -162,6 +265,15 @@ class StatisticsUpdatedEventModel(BaseControllerEventModel):
162
265
  event: Literal["statistics updated"]
163
266
  statistics: ControllerStatisticsDataType
164
267
 
268
+ @classmethod
269
+ def from_dict(cls, data: dict) -> StatisticsUpdatedEventModel:
270
+ """Initialize from dict."""
271
+ return cls(
272
+ source=data["source"],
273
+ event=data["event"],
274
+ statistics=data["statistics"],
275
+ )
276
+
165
277
 
166
278
  class ValidateDSKAndEnterPINEventModel(BaseControllerEventModel):
167
279
  """Model for `validate dsk and enter pin` event data."""
@@ -169,6 +281,15 @@ class ValidateDSKAndEnterPINEventModel(BaseControllerEventModel):
169
281
  event: Literal["validate dsk and enter pin"]
170
282
  dsk: str
171
283
 
284
+ @classmethod
285
+ def from_dict(cls, data: dict) -> ValidateDSKAndEnterPINEventModel:
286
+ """Initialize from dict."""
287
+ return cls(
288
+ source=data["source"],
289
+ event=data["event"],
290
+ dsk=data["dsk"],
291
+ )
292
+
172
293
 
173
294
  class IdentifyEventModel(BaseControllerEventModel):
174
295
  """Model for `identify` event data."""
@@ -176,6 +297,15 @@ class IdentifyEventModel(BaseControllerEventModel):
176
297
  event: Literal["identify"]
177
298
  nodeId: int
178
299
 
300
+ @classmethod
301
+ def from_dict(cls, data: dict) -> IdentifyEventModel:
302
+ """Initialize from dict."""
303
+ return cls(
304
+ source=data["source"],
305
+ event=data["event"],
306
+ nodeId=data["nodeId"],
307
+ )
308
+
179
309
 
180
310
  class StatusChangedEventModel(BaseControllerEventModel):
181
311
  """Model for `status changed` event data."""
@@ -183,6 +313,15 @@ class StatusChangedEventModel(BaseControllerEventModel):
183
313
  event: Literal["status changed"]
184
314
  status: int
185
315
 
316
+ @classmethod
317
+ def from_dict(cls, data: dict) -> StatusChangedEventModel:
318
+ """Initialize from dict."""
319
+ return cls(
320
+ source=data["source"],
321
+ event=data["event"],
322
+ status=data["status"],
323
+ )
324
+
186
325
 
187
326
  CONTROLLER_EVENT_MODEL_MAP: dict[str, type[BaseControllerEventModel]] = {
188
327
  "exclusion failed": ExclusionFailedEventModel,
@@ -76,13 +76,13 @@ class ProvisioningEntry:
76
76
  security_classes=[
77
77
  SecurityClass(sec_cls) for sec_cls in data["securityClasses"]
78
78
  ],
79
- additional_properties={
80
- k: v
81
- for k, v in data.items()
82
- if k
83
- not in {"dsk", "securityClasses", "requestedSecurityClasses", "status"}
84
- },
85
79
  )
80
+ if additional_properties := {
81
+ k: v
82
+ for k, v in data.items()
83
+ if k not in ("dsk", "securityClasses", "requestedSecurityClasses", "status")
84
+ }:
85
+ cls_instance.additional_properties = additional_properties
86
86
  if "requestedSecurityClasses" in data:
87
87
  cls_instance.requested_security_classes = [
88
88
  SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
@@ -148,6 +148,12 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
148
148
  @classmethod
149
149
  def from_dict(cls, data: dict[str, Any]) -> QRProvisioningInformation:
150
150
  """Return QRProvisioningInformation from data dict."""
151
+ supported_protocols: list[Protocols] | None = None
152
+ if "supportedProtocols" in data:
153
+ supported_protocols = [
154
+ Protocols(supported_protocol)
155
+ for supported_protocol in data["supportedProtocols"]
156
+ ]
151
157
  cls_instance = cls(
152
158
  version=QRCodeVersion(data["version"]),
153
159
  security_classes=[
@@ -163,33 +169,31 @@ class QRProvisioningInformation(ProvisioningEntry, QRProvisioningInformationMixi
163
169
  application_version=data["applicationVersion"],
164
170
  max_inclusion_request_interval=data.get("maxInclusionRequestInterval"),
165
171
  uuid=data.get("uuid"),
166
- supported_protocols=[
167
- Protocols(supported_protocol)
168
- for supported_protocol in data.get("supportedProtocols", [])
169
- ],
170
- additional_properties={
171
- k: v
172
- for k, v in data.items()
173
- if k
174
- not in {
175
- "version",
176
- "securityClasses",
177
- "requestedSecurityClasses",
178
- "dsk",
179
- "genericDeviceClass",
180
- "specificDeviceClass",
181
- "installerIconType",
182
- "manufacturerId",
183
- "productType",
184
- "productId",
185
- "applicationVersion",
186
- "maxInclusionRequestInterval",
187
- "uuid",
188
- "supportedProtocols",
189
- "status",
190
- }
191
- },
172
+ supported_protocols=supported_protocols,
192
173
  )
174
+ if additional_properties := {
175
+ k: v
176
+ for k, v in data.items()
177
+ if k
178
+ not in (
179
+ "version",
180
+ "securityClasses",
181
+ "requestedSecurityClasses",
182
+ "dsk",
183
+ "genericDeviceClass",
184
+ "specificDeviceClass",
185
+ "installerIconType",
186
+ "manufacturerId",
187
+ "productType",
188
+ "productId",
189
+ "applicationVersion",
190
+ "maxInclusionRequestInterval",
191
+ "uuid",
192
+ "supportedProtocols",
193
+ "status",
194
+ )
195
+ }:
196
+ cls_instance.additional_properties = additional_properties
193
197
  if "requestedSecurityClasses" in data:
194
198
  cls_instance.requested_security_classes = [
195
199
  SecurityClass(sec_cls) for sec_cls in data["requestedSecurityClasses"]
@@ -23,8 +23,6 @@ class DeviceClassDataType(TypedDict):
23
23
  basic: DeviceClassItemDataType
24
24
  generic: DeviceClassItemDataType
25
25
  specific: DeviceClassItemDataType
26
- mandatorySupportedCCs: list[int]
27
- mandatoryControlledCCs: list[int]
28
26
 
29
27
 
30
28
  @dataclass
@@ -40,11 +38,18 @@ class DeviceClass:
40
38
 
41
39
  def __init__(self, data: DeviceClassDataType) -> None:
42
40
  """Initialize."""
43
- self._basic = DeviceClassItem(**data["basic"])
44
- self._generic = DeviceClassItem(**data["generic"])
45
- self._specific = DeviceClassItem(**data["specific"])
46
- self._mandatory_supported_ccs: list[int] = data["mandatorySupportedCCs"]
47
- self._mandatory_controlled_ccs: list[int] = data["mandatoryControlledCCs"]
41
+ self._basic = DeviceClassItem(
42
+ key=data["basic"]["key"],
43
+ label=data["basic"]["label"],
44
+ )
45
+ self._generic = DeviceClassItem(
46
+ key=data["generic"]["key"],
47
+ label=data["generic"]["label"],
48
+ )
49
+ self._specific = DeviceClassItem(
50
+ key=data["specific"]["key"],
51
+ label=data["specific"]["label"],
52
+ )
48
53
 
49
54
  @property
50
55
  def basic(self) -> DeviceClassItem:
@@ -60,13 +65,3 @@ class DeviceClass:
60
65
  def specific(self) -> DeviceClassItem:
61
66
  """Return specific DeviceClass."""
62
67
  return self._specific
63
-
64
- @property
65
- def mandatory_supported_ccs(self) -> list[int]:
66
- """Return list of mandatory Supported CC id's."""
67
- return self._mandatory_supported_ccs
68
-
69
- @property
70
- def mandatory_controlled_ccs(self) -> list[int]:
71
- """Return list of mandatory Controlled CC id's."""
72
- return self._mandatory_controlled_ccs
@@ -30,6 +30,15 @@ class LogConfigUpdatedEventModel(BaseDriverEventModel):
30
30
  event: Literal["log config updated"]
31
31
  config: LogConfigDataType
32
32
 
33
+ @classmethod
34
+ def from_dict(cls, data: dict) -> LogConfigUpdatedEventModel:
35
+ """Initialize from dict."""
36
+ return cls(
37
+ source=data["source"],
38
+ event=data["event"],
39
+ config=data["config"],
40
+ )
41
+
33
42
 
34
43
  class AllNodesReadyEventModel(BaseDriverEventModel):
35
44
  """Model for `all nodes ready` event data."""
@@ -87,7 +96,7 @@ class Driver(EventBase):
87
96
  self.controller.receive_event(event)
88
97
  return
89
98
 
90
- DRIVER_EVENT_MODEL_MAP[event.type](**event.data)
99
+ DRIVER_EVENT_MODEL_MAP[event.type].from_dict(event.data)
91
100
 
92
101
  self._handle_event_protocol(event)
93
102
 
@@ -19,12 +19,7 @@ from ...const import (
19
19
  SecurityClass,
20
20
  )
21
21
  from ...event import Event, EventBase
22
- from ...exceptions import (
23
- FailedCommand,
24
- NotFoundError,
25
- UnparseableValue,
26
- UnwriteableValue,
27
- )
22
+ from ...exceptions import NotFoundError, UnparseableValue, UnwriteableValue
28
23
  from ..command_class import CommandClassInfo
29
24
  from ..device_class import DeviceClass
30
25
  from ..device_config import DeviceConfig
@@ -50,7 +45,6 @@ from ..value import (
50
45
  ValueMetadata,
51
46
  ValueNotification,
52
47
  _get_value_id_str_from_dict,
53
- _init_value,
54
48
  )
55
49
  from .data_model import NodeDataType
56
50
  from .event_model import NODE_EVENT_MODEL_MAP
@@ -139,6 +133,12 @@ class Node(EventBase):
139
133
  self.client.driver == other.client.driver and self.node_id == other.node_id
140
134
  )
141
135
 
136
+ def _init_value(self, val: ValueDataType) -> Value | ConfigurationValue:
137
+ """Initialize a Value object from ValueDataType."""
138
+ if val["commandClass"] == CommandClass.CONFIGURATION:
139
+ return ConfigurationValue(self, val)
140
+ return Value(self, val)
141
+
142
142
  @property
143
143
  def node_id(self) -> int:
144
144
  """Return node ID property."""
@@ -429,7 +429,7 @@ class Node(EventBase):
429
429
  if value_id in self.values:
430
430
  self.values[value_id].update(val)
431
431
  else:
432
- self.values[value_id] = _init_value(self, val)
432
+ self.values[value_id] = self._init_value(val)
433
433
  except UnparseableValue:
434
434
  # If we can't parse the value, don't store it
435
435
  pass
@@ -448,8 +448,9 @@ class Node(EventBase):
448
448
  )
449
449
  if last_seen := data.get("lastSeen"):
450
450
  self._last_seen = datetime.fromisoformat(last_seen)
451
- if not self._statistics.last_seen:
451
+ if not self._statistics.last_seen and self.last_seen:
452
452
  self._statistics.last_seen = self.last_seen
453
+ self._statistics.data["lastSeen"] = self.last_seen.isoformat()
453
454
 
454
455
  self._update_values(self.data.pop("values"))
455
456
  self._update_endpoints(self.data.pop("endpoints"))
@@ -474,7 +475,7 @@ class Node(EventBase):
474
475
 
475
476
  def receive_event(self, event: Event) -> None:
476
477
  """Receive an event."""
477
- NODE_EVENT_MODEL_MAP[event.type](**event.data)
478
+ NODE_EVENT_MODEL_MAP[event.type].from_dict(event.data)
478
479
 
479
480
  self._handle_event_protocol(event)
480
481
  event.data["node"] = self
@@ -593,12 +594,9 @@ class Node(EventBase):
593
594
  data = await self.async_send_command(
594
595
  "get_defined_value_ids", wait_for_result=True
595
596
  )
596
-
597
- if data is None:
598
- # We should never reach this code
599
- raise FailedCommand("Command failed", "failed_command")
597
+ assert data
600
598
  return [
601
- _init_value(self, cast(ValueDataType, value_id))
599
+ self._init_value(cast(ValueDataType, value_id))
602
600
  for value_id in data["valueIds"]
603
601
  ]
604
602
 
@@ -1061,7 +1059,7 @@ class Node(EventBase):
1061
1059
  value_id = _get_value_id_str_from_dict(self, evt_val_data)
1062
1060
  value = self.values.get(value_id)
1063
1061
  if value is None:
1064
- value = _init_value(self, evt_val_data)
1062
+ value = self._init_value(evt_val_data)
1065
1063
  self.values[value.value_id] = event.data["value"] = value
1066
1064
  else:
1067
1065
  value.receive_event(event)
@@ -32,6 +32,15 @@ class BaseNodeEventModel(BaseEventModel):
32
32
  source: Literal["node"]
33
33
  nodeId: int
34
34
 
35
+ @classmethod
36
+ def from_dict(cls, data: dict) -> BaseNodeEventModel:
37
+ """Initialize from dict."""
38
+ return cls(
39
+ source=data["source"],
40
+ event=data["event"],
41
+ nodeId=data["nodeId"],
42
+ )
43
+
35
44
 
36
45
  class AliveEventModel(BaseNodeEventModel):
37
46
  """Model for `alive` event data."""
@@ -50,6 +59,18 @@ class CheckHealthProgressEventModel(BaseNodeEventModel):
50
59
  totalRounds: int
51
60
  lastRating: int
52
61
 
62
+ @classmethod
63
+ def from_dict(cls, data: dict) -> CheckHealthProgressEventModel:
64
+ """Initialize from dict."""
65
+ return cls(
66
+ source=data["source"],
67
+ event=data["event"],
68
+ nodeId=data["nodeId"],
69
+ rounds=data["rounds"],
70
+ totalRounds=data["totalRounds"],
71
+ lastRating=data["lastRating"],
72
+ )
73
+
53
74
 
54
75
  class CheckLifelineHealthProgressEventModel(CheckHealthProgressEventModel):
55
76
  """Model for `check lifeline health progress` event data."""
@@ -83,6 +104,19 @@ class InterviewFailedEventArgsModel(BaseModel):
83
104
  attempt: int | None
84
105
  maxAttempts: int | None
85
106
 
107
+ @classmethod
108
+ def from_dict(cls, data: dict) -> InterviewFailedEventArgsModel:
109
+ """Initialize from dict."""
110
+ return cls(
111
+ source=data["source"],
112
+ event=data["event"],
113
+ nodeId=data["nodeId"],
114
+ errorMessage=data["errorMessage"],
115
+ isFinal=data["isFinal"],
116
+ attempt=data["attempt"],
117
+ maxAttempts=data["maxAttempts"],
118
+ )
119
+
86
120
 
87
121
  class InterviewFailedEventModel(BaseNodeEventModel):
88
122
  """Model for `interview failed` event data."""
@@ -90,6 +124,16 @@ class InterviewFailedEventModel(BaseNodeEventModel):
90
124
  event: Literal["interview failed"]
91
125
  args: InterviewFailedEventArgsModel
92
126
 
127
+ @classmethod
128
+ def from_dict(cls, data: dict) -> InterviewFailedEventModel:
129
+ """Initialize from dict."""
130
+ return cls(
131
+ source=data["source"],
132
+ event=data["event"],
133
+ nodeId=data["nodeId"],
134
+ args=data["args"],
135
+ )
136
+
93
137
 
94
138
  class InterviewStageCompletedEventModel(BaseNodeEventModel):
95
139
  """Model for `interview stage completed` event data."""
@@ -97,6 +141,16 @@ class InterviewStageCompletedEventModel(BaseNodeEventModel):
97
141
  event: Literal["interview stage completed"]
98
142
  stageName: str
99
143
 
144
+ @classmethod
145
+ def from_dict(cls, data: dict) -> InterviewStageCompletedEventModel:
146
+ """Initialize from dict."""
147
+ return cls(
148
+ source=data["source"],
149
+ event=data["event"],
150
+ nodeId=data["nodeId"],
151
+ stageName=data["stageName"],
152
+ )
153
+
100
154
 
101
155
  class InterviewStartedEventModel(BaseNodeEventModel):
102
156
  """Model for `interview started` event data."""
@@ -118,6 +172,18 @@ class NotificationEventModel(BaseNodeEventModel):
118
172
  | MultilevelSwitchNotificationArgsDataType
119
173
  )
120
174
 
175
+ @classmethod
176
+ def from_dict(cls, data: dict) -> NotificationEventModel:
177
+ """Initialize from dict."""
178
+ return cls(
179
+ source=data["source"],
180
+ event=data["event"],
181
+ nodeId=data["nodeId"],
182
+ endpointIndex=data["endpointIndex"],
183
+ ccId=data["ccId"],
184
+ args=data["args"],
185
+ )
186
+
121
187
 
122
188
  class ReadyEventModel(BaseNodeEventModel):
123
189
  """Model for `ready` event data."""
@@ -125,6 +191,16 @@ class ReadyEventModel(BaseNodeEventModel):
125
191
  event: Literal["ready"]
126
192
  nodeState: NodeDataType
127
193
 
194
+ @classmethod
195
+ def from_dict(cls, data: dict) -> ReadyEventModel:
196
+ """Initialize from dict."""
197
+ return cls(
198
+ source=data["source"],
199
+ event=data["event"],
200
+ nodeId=data["nodeId"],
201
+ nodeState=data["nodeState"],
202
+ )
203
+
128
204
 
129
205
  class SleepEventModel(BaseNodeEventModel):
130
206
  """Model for `sleep` event data."""
@@ -138,6 +214,16 @@ class StatisticsUpdatedEventModel(BaseNodeEventModel):
138
214
  event: Literal["statistics updated"]
139
215
  statistics: NodeStatisticsDataType
140
216
 
217
+ @classmethod
218
+ def from_dict(cls, data: dict) -> StatisticsUpdatedEventModel:
219
+ """Initialize from dict."""
220
+ return cls(
221
+ source=data["source"],
222
+ event=data["event"],
223
+ nodeId=data["nodeId"],
224
+ statistics=data["statistics"],
225
+ )
226
+
141
227
 
142
228
  class TestPowerLevelProgressEventModel(BaseNodeEventModel):
143
229
  """Model for `test powerlevel progress` event data."""
@@ -146,6 +232,17 @@ class TestPowerLevelProgressEventModel(BaseNodeEventModel):
146
232
  acknowledged: int
147
233
  total: int
148
234
 
235
+ @classmethod
236
+ def from_dict(cls, data: dict) -> TestPowerLevelProgressEventModel:
237
+ """Initialize from dict."""
238
+ return cls(
239
+ source=data["source"],
240
+ event=data["event"],
241
+ nodeId=data["nodeId"],
242
+ acknowledged=data["acknowledged"],
243
+ total=data["total"],
244
+ )
245
+
149
246
 
150
247
  class ValueEventModel(BaseNodeEventModel):
151
248
  """
@@ -157,6 +254,16 @@ class ValueEventModel(BaseNodeEventModel):
157
254
 
158
255
  args: ValueDataType
159
256
 
257
+ @classmethod
258
+ def from_dict(cls, data: dict) -> ValueEventModel:
259
+ """Initialize from dict."""
260
+ return cls(
261
+ source=data["source"],
262
+ event=data["event"],
263
+ nodeId=data["nodeId"],
264
+ args=data["args"],
265
+ )
266
+
160
267
 
161
268
  class MetadataUpdatedEventModel(ValueEventModel):
162
269
  """Model for `metadata updated` event data."""
@@ -200,6 +307,16 @@ class FirmwareUpdateFinishedEventModel(BaseNodeEventModel):
200
307
  event: Literal["firmware update finished"]
201
308
  result: NodeFirmwareUpdateResultDataType
202
309
 
310
+ @classmethod
311
+ def from_dict(cls, data: dict) -> FirmwareUpdateFinishedEventModel:
312
+ """Initialize from dict."""
313
+ return cls(
314
+ source=data["source"],
315
+ event=data["event"],
316
+ nodeId=data["nodeId"],
317
+ result=data["result"],
318
+ )
319
+
203
320
 
204
321
  class FirmwareUpdateProgressEventModel(BaseNodeEventModel):
205
322
  """Model for `firmware update progress` event data."""
@@ -207,6 +324,16 @@ class FirmwareUpdateProgressEventModel(BaseNodeEventModel):
207
324
  event: Literal["firmware update progress"]
208
325
  progress: NodeFirmwareUpdateProgressDataType
209
326
 
327
+ @classmethod
328
+ def from_dict(cls, data: dict) -> FirmwareUpdateProgressEventModel:
329
+ """Initialize from dict."""
330
+ return cls(
331
+ source=data["source"],
332
+ event=data["event"],
333
+ nodeId=data["nodeId"],
334
+ progress=data["progress"],
335
+ )
336
+
210
337
 
211
338
  NODE_EVENT_MODEL_MAP: dict[str, type[BaseNodeEventModel]] = {
212
339
  "alive": AliveEventModel,
@@ -213,7 +213,11 @@ class NodeFirmwareUpdateFileInfo:
213
213
  cls, data: NodeFirmwareUpdateFileInfoDataType
214
214
  ) -> NodeFirmwareUpdateFileInfo:
215
215
  """Initialize from dict."""
216
- return cls(**data)
216
+ return cls(
217
+ target=data["target"],
218
+ url=data["url"],
219
+ integrity=data["integrity"],
220
+ )
217
221
 
218
222
  def to_dict(self) -> NodeFirmwareUpdateFileInfoDataType:
219
223
  """Return dict representation of the object."""
@@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, Any, TypedDict
8
8
 
9
9
  from ..const import (
10
10
  VALUE_UNKNOWN,
11
- CommandClass,
12
11
  CommandStatus,
13
12
  ConfigurationValueType,
14
13
  SetValueStatus,
@@ -75,13 +74,6 @@ class ValueDataType(TypedDict, total=False):
75
74
  ccVersion: int # required
76
75
 
77
76
 
78
- def _init_value(node: Node, val: ValueDataType) -> Value | ConfigurationValue:
79
- """Initialize a Value object from ValueDataType."""
80
- if val["commandClass"] == CommandClass.CONFIGURATION:
81
- return ConfigurationValue(node, val)
82
- return Value(node, val)
83
-
84
-
85
77
  def _get_value_id_str_from_dict(node: Node, val: ValueDataType) -> str:
86
78
  """Return string ID of value from ValueDataType dict."""
87
79
  return get_value_id_str(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.56.0
3
+ Version: 0.58.0
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Author-email: Home Assistant Team <hello@home-assistant.io>
6
6
  License: Apache License
@@ -212,9 +212,9 @@ Classifier: Development Status :: 4 - Beta
212
212
  Classifier: Intended Audience :: Developers
213
213
  Classifier: Natural Language :: English
214
214
  Classifier: Programming Language :: Python :: 3
215
- Classifier: Programming Language :: Python :: 3.11
215
+ Classifier: Programming Language :: Python :: 3.12
216
216
  Classifier: Topic :: Home Automation
217
- Requires-Python: >=3.11
217
+ Requires-Python: >=3.12
218
218
  Description-Content-Type: text/markdown
219
219
  License-File: LICENSE
220
220
  Requires-Dist: aiohttp>3