zwave-js-server-python 0.35.0__tar.gz → 0.35.3__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 (77) hide show
  1. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/PKG-INFO +1 -1
  2. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/setup.py +1 -1
  3. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/event.py +1 -1
  4. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/exceptions.py +0 -14
  5. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/association.py +1 -1
  6. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/controller/__init__.py +97 -53
  7. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/device_config.py +2 -2
  8. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/endpoint.py +18 -0
  9. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/log_message.py +2 -2
  10. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/node/__init__.py +8 -3
  11. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/node/data_model.py +2 -2
  12. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/PKG-INFO +1 -1
  13. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/SOURCES.txt +0 -3
  14. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/top_level.txt +0 -1
  15. zwave-js-server-python-0.35.0/scripts/__init__.py +0 -1
  16. zwave-js-server-python-0.35.0/scripts/generate_multilevel_sensor_constants.py +0 -245
  17. zwave-js-server-python-0.35.0/scripts/run_mock_server.py +0 -385
  18. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/LICENSE +0 -0
  19. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/README.md +0 -0
  20. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/setup.cfg +0 -0
  21. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_client.py +0 -0
  22. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_dump.py +0 -0
  23. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_event.py +0 -0
  24. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_firmware.py +0 -0
  25. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_main.py +0 -0
  26. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/test/test_version.py +0 -0
  27. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/__init__.py +0 -0
  28. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/__main__.py +0 -0
  29. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/client.py +0 -0
  30. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/__init__.py +0 -0
  31. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/__init__.py +0 -0
  32. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/barrier_operator.py +0 -0
  33. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/color_switch.py +0 -0
  34. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/entry_control.py +0 -0
  35. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/humidity_control.py +0 -0
  36. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/lock.py +0 -0
  37. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/meter.py +0 -0
  38. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/multilevel_sensor.py +0 -0
  39. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/multilevel_switch.py +0 -0
  40. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/notification.py +0 -0
  41. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/power_level.py +0 -0
  42. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/protection.py +0 -0
  43. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/sound_switch.py +0 -0
  44. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/thermostat.py +0 -0
  45. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/const/command_class/wake_up.py +0 -0
  46. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/dump.py +0 -0
  47. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/firmware.py +0 -0
  48. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/__init__.py +0 -0
  49. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/command_class.py +0 -0
  50. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/controller/data_model.py +0 -0
  51. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/controller/event_model.py +0 -0
  52. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/controller/inclusion_and_provisioning.py +0 -0
  53. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/controller/statistics.py +0 -0
  54. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/device_class.py +0 -0
  55. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/driver.py +0 -0
  56. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/firmware.py +0 -0
  57. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/log_config.py +0 -0
  58. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/node/event_model.py +0 -0
  59. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/node/health_check.py +0 -0
  60. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/node/statistics.py +0 -0
  61. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/notification.py +0 -0
  62. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/utils.py +0 -0
  63. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/value.py +0 -0
  64. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/model/version.py +0 -0
  65. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/__init__.py +0 -0
  66. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/command_class/__init__.py +0 -0
  67. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/command_class/meter.py +0 -0
  68. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/command_class/multilevel_sensor.py +0 -0
  69. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/helpers.py +0 -0
  70. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/lock.py +0 -0
  71. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/multicast.py +0 -0
  72. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/util/node.py +0 -0
  73. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server/version.py +0 -0
  74. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/dependency_links.txt +0 -0
  75. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/entry_points.txt +0 -0
  76. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/not-zip-safe +0 -0
  77. {zwave-js-server-python-0.35.0 → zwave-js-server-python-0.35.3}/zwave_js_server_python.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.35.0
3
+ Version: 0.35.3
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Home-page: https://github.com/home-assistant-libs/zwave-js-server-python
6
6
  Author: Home Assistant Team
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
5
5
 
6
6
  PROJECT_DIR = Path(__file__).parent.resolve()
7
7
  README_FILE = PROJECT_DIR / "README.md"
8
- VERSION = "0.35.0"
8
+ VERSION = "0.35.3"
9
9
 
10
10
 
11
11
  setup(
@@ -57,7 +57,7 @@ class EventBase:
57
57
 
58
58
  def emit(self, event_name: str, data: dict) -> None:
59
59
  """Run all callbacks for an event."""
60
- for listener in self._listeners.get(event_name, []):
60
+ for listener in self._listeners.get(event_name, []).copy():
61
61
  listener(data)
62
62
 
63
63
  def _handle_event_protocol(self, event: Event) -> None:
@@ -4,7 +4,6 @@ from typing import TYPE_CHECKING, Optional
4
4
  from .const import CommandClass
5
5
 
6
6
  if TYPE_CHECKING:
7
- from .event import Event
8
7
  from .model.value import Value
9
8
 
10
9
 
@@ -157,16 +156,3 @@ class UnknownValueData(BaseZwaveJSServerError):
157
156
  "upstream issue with the driver or missing support for this data in the "
158
157
  "library"
159
158
  )
160
-
161
-
162
- class NotificationHasUnsupportedCommandClass(BaseZwaveJSServerError):
163
- """Exception raised when notification is received for an unsupported CC."""
164
-
165
- def __init__(self, event: "Event", command_class: CommandClass) -> None:
166
- """Initialize an invalid Command Class error."""
167
- self.event_data = event.data
168
- self.command_class = command_class
169
- super().__init__(
170
- "Notification received with unsupported command class "
171
- f"{command_class.name}: {event.data}"
172
- )
@@ -16,7 +16,7 @@ class AssociationGroup:
16
16
 
17
17
 
18
18
  @dataclass
19
- class Association:
19
+ class AssociationAddress:
20
20
  """Represent a association dict type."""
21
21
 
22
22
  node_id: int
@@ -12,7 +12,7 @@ from ...const import (
12
12
  )
13
13
  from ...event import Event, EventBase
14
14
  from ...util.helpers import convert_base64_to_bytes, convert_bytes_to_base64
15
- from ..association import Association, AssociationGroup
15
+ from ..association import AssociationAddress, AssociationGroup
16
16
  from ..node import Node
17
17
  from .data_model import ControllerDataType
18
18
  from .event_model import CONTROLLER_EVENT_MODEL_MAP
@@ -420,18 +420,21 @@ class Controller(EventBase):
420
420
  return cast(bool, data["failed"])
421
421
 
422
422
  async def async_get_association_groups(
423
- self, node_id: int
423
+ self, source: AssociationAddress
424
424
  ) -> Dict[int, AssociationGroup]:
425
425
  """Send getAssociationGroups command to Controller."""
426
+ source_data = {"nodeId": source.node_id}
427
+ if source.endpoint is not None:
428
+ source_data["endpoint"] = source.endpoint
426
429
  data = await self.client.async_send_command(
427
430
  {
428
431
  "command": "controller.get_association_groups",
429
- "nodeId": node_id,
432
+ **source_data,
430
433
  }
431
434
  )
432
435
  groups = {}
433
436
  for key, group in data["groups"].items():
434
- groups[key] = AssociationGroup(
437
+ groups[int(key)] = AssociationGroup(
435
438
  max_nodes=group["maxNodes"],
436
439
  is_lifeline=group["isLifeline"],
437
440
  multi_channel=group["multiChannel"],
@@ -441,84 +444,125 @@ class Controller(EventBase):
441
444
  )
442
445
  return groups
443
446
 
444
- async def async_get_associations(self, node_id: int) -> Dict[int, Association]:
447
+ async def async_get_associations(
448
+ self, source: AssociationAddress
449
+ ) -> Dict[int, List[AssociationAddress]]:
445
450
  """Send getAssociations command to Controller."""
451
+ source_data = {"nodeId": source.node_id}
452
+ if source.endpoint is not None:
453
+ source_data["endpoint"] = source.endpoint
446
454
  data = await self.client.async_send_command(
447
455
  {
448
456
  "command": "controller.get_associations",
449
- "nodeId": node_id,
457
+ **source_data,
450
458
  }
451
459
  )
452
460
  associations = {}
453
- for key, association in data["associations"].items():
454
- associations[key] = Association(
455
- node_id=association["nodeId"], endpoint=association.get("endpoint")
456
- )
461
+ for key, association_addresses in data["associations"].items():
462
+ associations[int(key)] = [
463
+ AssociationAddress(
464
+ node_id=association_address["nodeId"],
465
+ endpoint=association_address.get("endpoint"),
466
+ )
467
+ for association_address in association_addresses
468
+ ]
457
469
  return associations
458
470
 
459
471
  async def async_is_association_allowed(
460
- self, node_id: int, group: int, association: Association
472
+ self, source: AssociationAddress, group: int, association: AssociationAddress
461
473
  ) -> bool:
462
474
  """Send isAssociationAllowed command to Controller."""
475
+ source_data = {"nodeId": source.node_id}
476
+ if source.endpoint is not None:
477
+ source_data["endpoint"] = source.endpoint
478
+
479
+ association_data = {"nodeId": association.node_id}
480
+ if association.endpoint is not None:
481
+ association_data["endpoint"] = association.endpoint
463
482
  data = await self.client.async_send_command(
464
483
  {
465
484
  "command": "controller.is_association_allowed",
466
- "nodeId": node_id,
485
+ **source_data,
467
486
  "group": group,
468
- "association": {
469
- "nodeId": association.node_id,
470
- "endpoint": association.endpoint,
471
- },
487
+ "association": association_data,
472
488
  }
473
489
  )
474
490
  return cast(bool, data["allowed"])
475
491
 
476
492
  async def async_add_associations(
477
- self, node_id: int, group: int, associations: List[Association]
493
+ self,
494
+ source: AssociationAddress,
495
+ group: int,
496
+ associations: List[AssociationAddress],
497
+ wait_for_result: bool = False,
478
498
  ) -> None:
479
499
  """Send addAssociations command to Controller."""
480
- await self.client.async_send_command(
481
- {
482
- "command": "controller.add_associations",
483
- "nodeId": node_id,
484
- "group": group,
485
- "associations": [
486
- {
487
- "nodeId": association.node_id,
488
- "endpoint": association.endpoint,
489
- }
490
- for association in associations
491
- ],
492
- }
493
- )
500
+ source_data = {"nodeId": source.node_id}
501
+ if source.endpoint is not None:
502
+ source_data["endpoint"] = source.endpoint
503
+
504
+ associations_data = []
505
+ for association in associations:
506
+ association_data = {"nodeId": association.node_id}
507
+ if association.endpoint is not None:
508
+ association_data["endpoint"] = association.endpoint
509
+ associations_data.append(association_data)
510
+
511
+ cmd = {
512
+ "command": "controller.add_associations",
513
+ **source_data,
514
+ "group": group,
515
+ "associations": associations_data,
516
+ }
517
+ if wait_for_result:
518
+ await self.client.async_send_command(cmd)
519
+ else:
520
+ await self.client.async_send_command_no_wait(cmd)
494
521
 
495
522
  async def async_remove_associations(
496
- self, node_id: int, group: int, associations: List[Association]
523
+ self,
524
+ source: AssociationAddress,
525
+ group: int,
526
+ associations: List[AssociationAddress],
527
+ wait_for_result: bool = False,
497
528
  ) -> None:
498
529
  """Send removeAssociations command to Controller."""
499
- await self.client.async_send_command(
500
- {
501
- "command": "controller.remove_associations",
502
- "nodeId": node_id,
503
- "group": group,
504
- "associations": [
505
- {
506
- "nodeId": association.node_id,
507
- "endpoint": association.endpoint,
508
- }
509
- for association in associations
510
- ],
511
- }
512
- )
530
+ source_data = {"nodeId": source.node_id}
531
+ if source.endpoint is not None:
532
+ source_data["endpoint"] = source.endpoint
533
+
534
+ associations_data = []
535
+ for association in associations:
536
+ association_data = {"nodeId": association.node_id}
537
+ if association.endpoint is not None:
538
+ association_data["endpoint"] = association.endpoint
539
+ associations_data.append(association_data)
540
+
541
+ cmd = {
542
+ "command": "controller.remove_associations",
543
+ **source_data,
544
+ "group": group,
545
+ "associations": associations_data,
546
+ }
547
+ if wait_for_result:
548
+ await self.client.async_send_command(cmd)
549
+ else:
550
+ await self.client.async_send_command_no_wait(cmd)
513
551
 
514
- async def async_remove_node_from_all_associations(self, node_id: int) -> None:
552
+ async def async_remove_node_from_all_associations(
553
+ self,
554
+ node_id: int,
555
+ wait_for_result: bool = False,
556
+ ) -> None:
515
557
  """Send removeNodeFromAllAssociations command to Controller."""
516
- await self.client.async_send_command(
517
- {
518
- "command": "controller.remove_node_from_all_associations",
519
- "nodeId": node_id,
520
- }
521
- )
558
+ cmd = {
559
+ "command": "controller.remove_node_from_all_associations",
560
+ "nodeId": node_id,
561
+ }
562
+ if wait_for_result:
563
+ await self.client.async_send_command(cmd)
564
+ else:
565
+ await self.client.async_send_command_no_wait(cmd)
522
566
 
523
567
  async def async_get_node_neighbors(self, node_id: int) -> List[int]:
524
568
  """Send getNodeNeighbors command to Controller to get node's neighbors."""
@@ -3,7 +3,7 @@ Model for a Zwave Node's device config.
3
3
 
4
4
  https://zwave-js.github.io/node-zwave-js/#/api/node?id=deviceconfig
5
5
  """
6
- from typing import Dict, List, Literal, Optional, Union
6
+ from typing import Any, Dict, List, Literal, Optional, Union
7
7
 
8
8
  from ..const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
9
9
 
@@ -138,7 +138,7 @@ class DeviceConfigDataType(TypedDict, total=False):
138
138
  paramInformation: Dict[str, dict]
139
139
  supportsZWavePlus: bool
140
140
  proprietary: dict
141
- compat: Dict[str, dict]
141
+ compat: Dict[str, Any]
142
142
  metadata: DeviceMetadataDataType
143
143
  isEmbedded: bool
144
144
 
@@ -49,6 +49,24 @@ class Endpoint(EventBase):
49
49
  self.values: Dict[str, Union[ConfigurationValue, Value]] = {}
50
50
  self.update(data, values)
51
51
 
52
+ def __repr__(self) -> str:
53
+ """Return the representation."""
54
+ return f"{type(self).__name__}(node_id={self.node_id}, index={self.index})"
55
+
56
+ def __hash__(self) -> int:
57
+ """Return the hash."""
58
+ return hash((self.client.driver, self.node_id, self.index))
59
+
60
+ def __eq__(self, other: object) -> bool:
61
+ """Return whether this instance equals another."""
62
+ if not isinstance(other, Endpoint):
63
+ return False
64
+ return (
65
+ self.client.driver == other.client.driver
66
+ and self.node_id == other.node_id
67
+ and self.index == other.index
68
+ )
69
+
52
70
  @property
53
71
  def node_id(self) -> int:
54
72
  """Return node ID property."""
@@ -13,7 +13,7 @@ class LogMessageContextDataType(TypedDict, total=False):
13
13
  """Represent a log message context data dict type."""
14
14
 
15
15
  source: Literal["config", "serial", "controller", "driver"] # required
16
- type: Literal["value", "node"]
16
+ type: Literal["controller", "value", "node"]
17
17
  nodeId: int
18
18
  header: str
19
19
  direction: Literal["inbound", "outbound", "none"]
@@ -38,7 +38,7 @@ class LogMessageContext:
38
38
  return self.data["source"]
39
39
 
40
40
  @property
41
- def type(self) -> Optional[Literal["value", "node"]]:
41
+ def type(self) -> Optional[Literal["controller", "value", "node"]]:
42
42
  """Return the object type for the log message if applicable."""
43
43
  return self.data.get("type")
44
44
 
@@ -1,4 +1,5 @@
1
1
  """Provide a model for the Z-Wave JS node."""
2
+ import logging
2
3
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast
3
4
 
4
5
  from ...const import (
@@ -12,7 +13,6 @@ from ...event import Event, EventBase
12
13
  from ...exceptions import (
13
14
  FailedCommand,
14
15
  NotFoundError,
15
- NotificationHasUnsupportedCommandClass,
16
16
  UnparseableValue,
17
17
  UnwriteableValue,
18
18
  )
@@ -58,6 +58,9 @@ if TYPE_CHECKING:
58
58
  from ...client import Client
59
59
 
60
60
 
61
+ _LOGGER = logging.getLogger(__package__)
62
+
63
+
61
64
  class Node(EventBase):
62
65
  """Represent a Z-Wave JS node."""
63
66
 
@@ -152,7 +155,9 @@ class Node(EventBase):
152
155
  @property
153
156
  def is_secure(self) -> Optional[bool]:
154
157
  """Return the is_secure."""
155
- return self.data.get("isSecure")
158
+ if (is_secure := self.data.get("isSecure")) == "unknown":
159
+ return None
160
+ return is_secure
156
161
 
157
162
  @property
158
163
  def protocol_version(self) -> Optional[int]:
@@ -787,7 +792,7 @@ class Node(EventBase):
787
792
  self, cast(PowerLevelNotificationDataType, event.data)
788
793
  )
789
794
  else:
790
- raise NotificationHasUnsupportedCommandClass(event, command_class)
795
+ _LOGGER.info("Unhandled notification command class: %s", command_class.name)
791
796
 
792
797
  def handle_firmware_update_progress(self, event: Event) -> None:
793
798
  """Process a node firmware update progress event."""
@@ -1,5 +1,5 @@
1
1
  """Data model for a Z-Wave JS node."""
2
- from typing import List, Optional, Union
2
+ from typing import List, Literal, Optional, Union
3
3
 
4
4
  from ...const import TYPING_EXTENSION_FOR_TYPEDDICT_REQUIRED
5
5
  from ..device_class import DeviceClassDataType
@@ -33,7 +33,7 @@ class NodeDataType(TypedDict, total=False):
33
33
  isRouting: bool
34
34
  maxDataRate: int
35
35
  supportedDataRates: List[int]
36
- isSecure: bool
36
+ isSecure: Union[bool, Literal["unknown"]]
37
37
  supportsBeaming: bool
38
38
  supportsSecurity: bool
39
39
  protocolVersion: int
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zwave-js-server-python
3
- Version: 0.35.0
3
+ Version: 0.35.3
4
4
  Summary: Python wrapper for zwave-js-server
5
5
  Home-page: https://github.com/home-assistant-libs/zwave-js-server-python
6
6
  Author: Home Assistant Team
@@ -2,9 +2,6 @@ LICENSE
2
2
  README.md
3
3
  setup.cfg
4
4
  setup.py
5
- scripts/__init__.py
6
- scripts/generate_multilevel_sensor_constants.py
7
- scripts/run_mock_server.py
8
5
  test/test_client.py
9
6
  test/test_dump.py
10
7
  test/test_event.py
@@ -1 +0,0 @@
1
- """Scripts module."""
@@ -1,245 +0,0 @@
1
- """Script to generate Multilevel Sensor CC constants."""
2
- from __future__ import annotations
3
-
4
- import json
5
- import pathlib
6
- import re
7
- import subprocess
8
- from collections import defaultdict
9
- from typing import Callable, List
10
-
11
- import requests
12
- from slugify import slugify
13
-
14
- GITHUB_PROJECT = "zwave-js/node-zwave-js"
15
- BRANCH_NAME = "master"
16
- SENSOR_TYPES_FILE_PATH = "packages/config/config/sensorTypes.json"
17
- DEFAULT_SCALES_FILE_PATH = "packages/config/config/scales.json"
18
-
19
- CONST_FILE_PATH = pathlib.Path(__file__).parent.joinpath(
20
- "../zwave_js_server/const/command_class/multilevel_sensor.py"
21
- )
22
-
23
-
24
- def remove_comments(text: str) -> str:
25
- """Remove comments from a JSON string."""
26
- return "\n".join(
27
- line for line in text.split("\n") if not line.strip().startswith("//")
28
- )
29
-
30
-
31
- def remove_paranthesis(text: str) -> str:
32
- """Remove text in paranethesis from a string."""
33
- return re.sub(r"\([^)]*\)", "", text)
34
-
35
-
36
- def enum_name_format(name: str, should_remove_paranthesis: bool) -> str:
37
- """Convert sensor/scale name to enum format."""
38
- if should_remove_paranthesis:
39
- name = remove_paranthesis(name)
40
- return slugify(name, separator="_").upper()
41
-
42
-
43
- def normalize_name(name: str) -> str:
44
- """Convert a sensor/scale name into a normalized name."""
45
- return enum_name_format(name, True).replace("_", " ").title()
46
-
47
-
48
- def format_for_class_name(name: str) -> str:
49
- """Convert sensor/scale name to class name format."""
50
- return f"{normalize_name(name).replace(' ', '')}Scale"
51
-
52
-
53
- def normalize_scale_definition(scale_definitions: dict[str, dict]) -> dict[str, int]:
54
- """Convert a scales definition dictionary into a normalized dictionary."""
55
- scale_def_ = {}
56
- for scale_id, scale_props in scale_definitions.items():
57
- scale_id = int(scale_id, 16)
58
- scale_name_ = enum_name_format(scale_props["label"], True)
59
- scale_def_[scale_name_] = scale_id
60
-
61
- return dict(sorted(scale_def_.items(), key=lambda kv: kv[0]))
62
-
63
-
64
- sensor_types = json.loads(
65
- remove_comments(
66
- requests.get(
67
- (
68
- f"https://raw.githubusercontent.com/{GITHUB_PROJECT}/{BRANCH_NAME}/"
69
- f"{SENSOR_TYPES_FILE_PATH}"
70
- )
71
- ).text
72
- )
73
- )
74
- default_scales = json.loads(
75
- remove_comments(
76
- requests.get(
77
- (
78
- f"https://raw.githubusercontent.com/{GITHUB_PROJECT}/{BRANCH_NAME}/"
79
- f"{DEFAULT_SCALES_FILE_PATH}"
80
- )
81
- ).text
82
- )
83
- )
84
-
85
- scales = {
86
- normalize_name(scale_name): normalize_scale_definition(scale_def)
87
- for scale_name, scale_def in default_scales.items()
88
- }
89
-
90
- sensors = {}
91
- for sensor_id, sensor_props in sensor_types.items():
92
- sensor_id = int(sensor_id, 16)
93
- scale_def = sensor_props["scales"]
94
- remove_paranthesis_ = True
95
- if sensor_id in (87, 88):
96
- remove_paranthesis_ = False
97
- sensor_name = enum_name_format(sensor_props["label"], remove_paranthesis_)
98
- sensors[sensor_name] = {"id": sensor_id}
99
- if isinstance(scale_def, str):
100
- sensors[sensor_name]["scale"] = normalize_name(
101
- scale_def.replace("$SCALES:", "")
102
- )
103
- else:
104
- scales[sensor_name] = normalize_scale_definition(scale_def)
105
- sensors[sensor_name]["scale"] = normalize_name(sensor_name)
106
-
107
- scales = dict(sorted(scales.items(), key=lambda kv: kv[0]))
108
- sensors = dict(sorted(sensors.items(), key=lambda kv: kv[0]))
109
-
110
-
111
- def generate_int_enum_class_definition(
112
- class_name: str,
113
- enum_dict: dict[str, str | dict],
114
- enum_ref_url: str | None = None,
115
- get_id_func: Callable | None = None,
116
- docstring_info: str = "",
117
- ) -> List[str]:
118
- """Generate an IntEnum class definition as an array of lines of string."""
119
- class_def = []
120
- class_def.append(f"class {class_name}(IntEnum):")
121
- docstring = (
122
- f'"""Enum for known {docstring_info} multilevel sensor types."""'.replace(
123
- " ", " "
124
- )
125
- )
126
- class_def.append(f" {docstring}")
127
- if enum_ref_url:
128
- class_def.append(f" # {enum_ref_url}")
129
- for enum_name, enum_id in enum_dict.items():
130
- if get_id_func:
131
- enum_id = get_id_func(enum_id)
132
- class_def.append(f" {enum_name} = {enum_id}")
133
- return class_def
134
-
135
-
136
- SENSOR_TYPE_URL = (
137
- f"https://github.com/{GITHUB_PROJECT}/blob/{BRANCH_NAME}/{SENSOR_TYPES_FILE_PATH}"
138
- )
139
-
140
- lines = [
141
- '"""Constants for the Multilevel Sensor CC."""',
142
- "",
143
- "# ----------------------------------------------------------------------------------- #",
144
- "# **BEGINNING OF AUTOGENERATED CONTENT** (TO ADD ADDITIONAL MANUAL CONTENT, LOOK FOR #",
145
- '# THE "END OF AUTOGENERATED CONTENT" COMMENT BLOCK AND ADD YOUR CODE BELOW IT) #',
146
- "# ----------------------------------------------------------------------------------- #",
147
- "",
148
- "from enum import IntEnum",
149
- "from typing import Dict, Set, Type, Union",
150
- 'CC_SPECIFIC_SCALE = "scale"',
151
- 'CC_SPECIFIC_SENSOR_TYPE = "sensorType"',
152
- ]
153
-
154
- lines.extend(
155
- generate_int_enum_class_definition(
156
- "MultilevelSensorType",
157
- sensors,
158
- SENSOR_TYPE_URL,
159
- get_id_func=lambda x: x["id"],
160
- )
161
- )
162
-
163
- unit_name_to_enum_map = defaultdict(list)
164
- for scale_name, scale_dict in scales.items():
165
- lines.extend(
166
- generate_int_enum_class_definition(
167
- format_for_class_name(scale_name),
168
- scale_dict,
169
- SENSOR_TYPE_URL,
170
- docstring_info=f"scales for {scale_name}",
171
- )
172
- )
173
- for unit_name in scale_dict.keys():
174
- unit_name_to_enum_map[unit_name].append(
175
- f"{format_for_class_name(scale_name)}.{unit_name}"
176
- )
177
- unit_name_to_enum_map = dict(
178
- sorted(unit_name_to_enum_map.items(), key=lambda kv: kv[0])
179
- )
180
- for unit_name, enum_list in unit_name_to_enum_map.items():
181
- unit_name_to_enum_map[unit_name] = sorted(enum_list)
182
-
183
- scale_class_names = [format_for_class_name(scale_name) for scale_name in scales]
184
- lines.extend(
185
- [f"MultilevelSensorScaleType = Union[{', '.join(sorted(scale_class_names))}]", ""]
186
- )
187
-
188
- multilevel_sensor_type_to_scale_map_line = (
189
- "MULTILEVEL_SENSOR_TYPE_TO_SCALE_MAP: Dict[MultilevelSensorType, "
190
- "Type[MultilevelSensorScaleType]] = {"
191
- )
192
- for sensor_name, sensor_def in sensors.items():
193
- multilevel_sensor_type_to_scale_map_line += (
194
- f" MultilevelSensorType.{sensor_name}: "
195
- f"{format_for_class_name(sensor_def['scale'])},"
196
- )
197
- multilevel_sensor_type_to_scale_map_line += "}"
198
- lines.append(multilevel_sensor_type_to_scale_map_line)
199
- lines.append("")
200
-
201
- for unit_name, unit_enums in unit_name_to_enum_map.items():
202
- lines.append(
203
- f"UNIT_{unit_name}: Set[MultilevelSensorScaleType] = {{{','.join(sorted(unit_enums))}}}"
204
- )
205
-
206
- lines.extend(
207
- [
208
- "",
209
- "# ----------------------------------------------------------------------------------- #",
210
- "# **END OF AUTOGENERATED CONTENT** (DO NOT EDIT/REMOVE THIS COMMENT BLOCK AND DO NOT #",
211
- "# EDIT ANYTHING ABOVE IT. IF A NEW IMPORT IS NEEDED, UPDATE THE LINES AROUND 135 #",
212
- "# IN scripts/generate_multilevel_sensor_constants.py THEN RE-RUN THE SCRIPT. ALL #",
213
- "# LINES WRITTEN BELOW THIS BLOCK WILL BE PRESERVED AS LONG AS THIS BLOCK REMAINS) #",
214
- "# ----------------------------------------------------------------------------------- #",
215
- "",
216
- ]
217
- )
218
-
219
- existing_const_file = CONST_FILE_PATH.read_text(encoding="utf-8").splitlines()
220
-
221
- manually_written_code_start_idx = (
222
- next(
223
- i
224
- for i, line in enumerate(existing_const_file)
225
- if "**END OF AUTOGENERATED CONTENT**" in line
226
- )
227
- + 6
228
- )
229
- if len(existing_const_file) > manually_written_code_start_idx:
230
- lines.extend(
231
- [
232
- line.strip("\n")
233
- for line in existing_const_file[manually_written_code_start_idx:]
234
- ]
235
- )
236
-
237
- CONST_FILE_PATH.write_text("\n".join(lines), encoding="utf-8")
238
-
239
- if subprocess.run(["which", "black"], capture_output=True, check=True).stdout:
240
- subprocess.run(
241
- ["black", CONST_FILE_PATH],
242
- check=True,
243
- )
244
- else:
245
- print("Could not run black on new file, please run it to properly format it.")