velbus-aio 2024.4.1__tar.gz → 2024.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of velbus-aio might be problematic. Click here for more details.

Files changed (114) hide show
  1. {velbus-aio-2024.4.1/velbus_aio.egg-info → velbus_aio-2024.7.0}/PKG-INFO +2 -2
  2. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/pyproject.toml +3 -3
  3. velbus_aio-2024.7.0/requirements.txt +5 -0
  4. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0/velbus_aio.egg-info}/PKG-INFO +2 -2
  5. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbus_aio.egg-info/requires.txt +1 -1
  6. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/channels.py +5 -1
  7. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/const.py +7 -1
  8. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/controller.py +34 -66
  9. velbus_aio-2024.7.0/velbusaio/handler.py +276 -0
  10. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/message.py +6 -6
  11. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/module_subtype.py +3 -3
  12. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/module_type.py +5 -5
  13. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_dimmer.py +7 -12
  14. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/module.py +20 -27
  15. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/protocol.json +68 -0
  16. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/protocol.py +4 -10
  17. velbus-aio-2024.4.1/requirements.txt +0 -3
  18. velbus-aio-2024.4.1/velbusaio/handler.py +0 -163
  19. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/LICENSE +0 -0
  20. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/MANIFEST.in +0 -0
  21. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/README.md +0 -0
  22. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/setup.cfg +0 -0
  23. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/setup.py +0 -0
  24. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbus_aio.egg-info/SOURCES.txt +0 -0
  25. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbus_aio.egg-info/dependency_links.txt +0 -0
  26. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbus_aio.egg-info/not-zip-safe +0 -0
  27. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbus_aio.egg-info/top_level.txt +0 -0
  28. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/__init__.py +0 -0
  29. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/command_registry.py +0 -0
  30. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/discovery.py +0 -0
  31. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/exceptions.py +0 -0
  32. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/helpers.py +0 -0
  33. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/__init__.py +0 -0
  34. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/blind_status.py +0 -0
  35. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/bus_active.py +0 -0
  36. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/bus_error_counter_status.py +0 -0
  37. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/bus_error_counter_status_request.py +0 -0
  38. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/bus_off.py +0 -0
  39. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/channel_name_part1.py +0 -0
  40. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/channel_name_part2.py +0 -0
  41. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/channel_name_part3.py +0 -0
  42. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/channel_name_request.py +0 -0
  43. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/clear_led.py +0 -0
  44. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/counter_status.py +0 -0
  45. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/counter_status_request.py +0 -0
  46. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/cover_down.py +0 -0
  47. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/cover_off.py +0 -0
  48. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/cover_position.py +0 -0
  49. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/cover_up.py +0 -0
  50. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/dali_device_settings.py +0 -0
  51. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/dali_device_settings_request.py +0 -0
  52. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/dali_dim_value_status.py +0 -0
  53. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/dimmer_channel_status.py +0 -0
  54. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/dimmer_status.py +0 -0
  55. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/edge_set_color.py +0 -0
  56. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/edge_set_custom_color.py +0 -0
  57. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/fast_blinking_led.py +0 -0
  58. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/forced_off.py +0 -0
  59. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/forced_on.py +0 -0
  60. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/interface_status_request.py +0 -0
  61. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/ir_receiver_status.py +0 -0
  62. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/kwh_status.py +0 -0
  63. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/light_value_request.py +0 -0
  64. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/memo_text.py +0 -0
  65. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/memory_data.py +0 -0
  66. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/memory_data_block.py +0 -0
  67. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/memory_dump_request.py +0 -0
  68. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/module_status.py +0 -0
  69. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/module_status_request.py +0 -0
  70. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/module_type_request.py +0 -0
  71. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/push_button_status.py +0 -0
  72. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/raw.py +0 -0
  73. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/read_data_block_from_memory.py +0 -0
  74. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/read_data_from_memory.py +0 -0
  75. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/realtime_clock_status_request.py +0 -0
  76. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/receive_buffer_full.py +0 -0
  77. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/receive_ready.py +0 -0
  78. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/relay_status.py +0 -0
  79. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/restore_dimmer.py +0 -0
  80. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/select_program.py +0 -0
  81. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/sensor_settings_request.py +0 -0
  82. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/sensor_temp_request.py +0 -0
  83. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/sensor_temperature.py +0 -0
  84. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_date.py +0 -0
  85. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_daylight_saving.py +0 -0
  86. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_led.py +0 -0
  87. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_realtime_clock.py +0 -0
  88. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/set_temperature.py +0 -0
  89. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/slider_status.py +0 -0
  90. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/slow_blinking_led.py +0 -0
  91. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/start_relay_blinking_timer.py +0 -0
  92. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/start_relay_timer.py +0 -0
  93. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_relay_off.py +0 -0
  94. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_relay_on.py +0 -0
  95. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_to_comfort.py +0 -0
  96. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_to_day.py +0 -0
  97. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_to_night.py +0 -0
  98. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/switch_to_safe.py +0 -0
  99. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_settings_part1.py +0 -0
  100. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_settings_part2.py +0 -0
  101. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_settings_part3.py +0 -0
  102. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_settings_part4.py +0 -0
  103. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_settings_request.py +0 -0
  104. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_sensor_status.py +0 -0
  105. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_set_cooling.py +0 -0
  106. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/temp_set_heating.py +0 -0
  107. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/update_led_status.py +0 -0
  108. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/very_fast_blinking_led.py +0 -0
  109. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/write_data_to_memory.py +0 -0
  110. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/write_memory_block.py +0 -0
  111. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/messages/write_module_address_and_serial_number.py +0 -0
  112. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/py.typed +0 -0
  113. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/raw_message.py +0 -0
  114. {velbus-aio-2024.4.1 → velbus_aio-2024.7.0}/velbusaio/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: velbus-aio
3
- Version: 2024.4.1
3
+ Version: 2024.7.0
4
4
  Summary: Open-source home automation platform running on Python 3.
5
5
  Author-email: Maikel Punie <maikel.punie@gmail.com>
6
6
  License: MIT
@@ -26,7 +26,7 @@ Requires-Python: >=3.8.0
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: pyserial>=3.5.0
29
- Requires-Dist: pyserial-asyncio>=0.5
29
+ Requires-Dist: pyserial-asyncio_fast>=0.11
30
30
  Requires-Dist: backoff>=1.10.0
31
31
 
32
32
  ![CI](https://github.com/Cereal2nd/velbus-aio/actions/workflows/main.yml/badge.svg)
@@ -4,7 +4,7 @@ requires = ["setuptools", "wheel"]
4
4
  [project]
5
5
  name = "velbus-aio"
6
6
  license = {text = "MIT"}
7
- version = "2024.4.1"
7
+ version = "2024.7.0"
8
8
  description = "Open-source home automation platform running on Python 3."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -30,7 +30,7 @@ classifiers = [
30
30
  requires-python = ">=3.8.0"
31
31
  dependencies = [
32
32
  "pyserial>=3.5.0",
33
- "pyserial-asyncio>=0.5",
33
+ "pyserial-asyncio_fast>=0.11",
34
34
  "backoff>=1.10.0",
35
35
  ]
36
36
 
@@ -51,7 +51,7 @@ exclude_dirs = ["tests"]
51
51
  skips = ["B301", "B403", "B323", "B104", "B110"]
52
52
 
53
53
  [tool.bumpver]
54
- current_version = "2024.4.1"
54
+ current_version = "2024.7.0"
55
55
  version_pattern = "YYYY.MM.INC0"
56
56
  commit_message = "bump version {old_version} -> {new_version}"
57
57
  commit = true
@@ -0,0 +1,5 @@
1
+ aiofile
2
+ backoff>=1.10.0
3
+ pyserial>=3.5.0
4
+ pyserial-asyncio-fast>=0.11
5
+ setuptools
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: velbus-aio
3
- Version: 2024.4.1
3
+ Version: 2024.7.0
4
4
  Summary: Open-source home automation platform running on Python 3.
5
5
  Author-email: Maikel Punie <maikel.punie@gmail.com>
6
6
  License: MIT
@@ -26,7 +26,7 @@ Requires-Python: >=3.8.0
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: pyserial>=3.5.0
29
- Requires-Dist: pyserial-asyncio>=0.5
29
+ Requires-Dist: pyserial-asyncio_fast>=0.11
30
30
  Requires-Dist: backoff>=1.10.0
31
31
 
32
32
  ![CI](https://github.com/Cereal2nd/velbus-aio/actions/workflows/main.yml/badge.svg)
@@ -1,3 +1,3 @@
1
1
  pyserial>=3.5.0
2
- pyserial-asyncio>=0.5
2
+ pyserial-asyncio_fast>=0.11
3
3
  backoff>=1.10.0
@@ -137,7 +137,10 @@ class Channel:
137
137
  }
138
138
 
139
139
  def to_cache(self) -> dict:
140
- return {"name": self._name, "type": type(self).__name__}
140
+ dst = {"name": self._name, "type": type(self).__name__}
141
+ if hasattr(self, "_Unit"):
142
+ dst["Unit"] = self._Unit
143
+ return dst
141
144
 
142
145
  def __setstate__(self, state):
143
146
  self.__dict__.update(state)
@@ -505,6 +508,7 @@ class Temperature(Channel):
505
508
  _min = None
506
509
  _target = 0
507
510
  _cmode = None
511
+ _coolmode = None
508
512
  _cstatus = None
509
513
  _thermostat = False
510
514
  _sleep_timer = 0
@@ -38,7 +38,13 @@ RTR: Final = 0x40
38
38
  NO_RTR: Final = 0x00
39
39
 
40
40
  CACHEDIR: Final = ".velbuscache"
41
- LOAD_TIMEOUT: Final = 600
41
+
42
+ # Module scan timeout values (in mSec)
43
+ SCAN_MODULETYPE_TIMEOUT: Final = 2000 # time to wait for ModuleTypeRequest
44
+ SCAN_MODULEINFO_TIMEOUT_INITIAL: Final = 1000 # time to wait for first info (status)
45
+ SCAN_MODULEINFO_TIMEOUT_INTERVAL: Final = (
46
+ 150 # time to wait for info interval (between next message)
47
+ )
42
48
 
43
49
  DEVICE_CLASS_ILLUMINANCE: Final = "illuminance"
44
50
  DEVICE_CLASS_TEMPERATURE: Final = "temperature"
@@ -11,10 +11,9 @@ import time
11
11
  from urllib.parse import urlparse
12
12
 
13
13
  import serial
14
- import serial_asyncio
14
+ import serial_asyncio_fast
15
15
 
16
16
  from velbusaio.channels import Channel
17
- from velbusaio.const import LOAD_TIMEOUT
18
17
  from velbusaio.exceptions import VelbusConnectionFailed
19
18
  from velbusaio.handler import PacketHandler
20
19
  from velbusaio.helpers import get_cache_dir
@@ -42,17 +41,16 @@ class Velbus:
42
41
  self._protocol = VelbusProtocol(
43
42
  message_received_callback=self._on_message_received,
44
43
  connection_lost_callback=self._on_connection_lost,
45
- end_of_scan_callback=self._on_end_of_scan,
46
44
  )
47
45
  self._closing = False
48
46
  self._auto_reconnect = True
49
47
 
50
48
  self._dsn = dsn
51
- self._handler = PacketHandler(self.send, self)
49
+ self._handler = PacketHandler(self)
52
50
  self._modules: dict[int, Module] = {}
53
51
  self._submodules: list[int] = []
54
- self._send_queue = asyncio.Queue()
55
- self._cache_dir = cache_dir
52
+ self._send_queue: asyncio.Queue = asyncio.Queue()
53
+ self._cache_dir: str = cache_dir
56
54
  # make sure the cachedir exists
57
55
  pathlib.Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
58
56
 
@@ -66,11 +64,7 @@ class Velbus:
66
64
  self._log.debug("Reconnecting to transport")
67
65
  asyncio.ensure_future(self.connect())
68
66
 
69
- def _on_end_of_scan(self) -> None:
70
- """Notify the scan failure."""
71
- self._handler.scan_finished()
72
-
73
- async def add_module(
67
+ def add_module(
74
68
  self,
75
69
  addr: int,
76
70
  typ: int,
@@ -81,8 +75,7 @@ class Velbus:
81
75
  build_week: int | None = None,
82
76
  ) -> None:
83
77
  """Add a found module to the module cache."""
84
- self._log.info(f"Found module: type:{typ} address:{addr}")
85
- self._modules[addr] = Module.factory(
78
+ module = Module.factory(
86
79
  addr,
87
80
  typ,
88
81
  data,
@@ -92,30 +85,31 @@ class Velbus:
92
85
  memorymap=memorymap,
93
86
  cache_dir=self._cache_dir,
94
87
  )
95
- self._modules[addr].initialize(self.send)
96
- await self._modules[addr].load()
88
+ module.initialize(self.send)
89
+ self._modules[addr] = module
90
+ self._log.info(f"Found module {addr}: {module}")
97
91
 
98
- async def add_submodules(self, addr: int, subList: dict[int, int]) -> None:
92
+ def add_submodules(self, module: Module, subList: dict[int, int]) -> None:
99
93
  """Add submodules address to module."""
100
94
  for sub_num, sub_addr in subList.items():
101
95
  if sub_addr == 0xFF:
102
96
  continue
103
97
  self._submodules.append(sub_addr)
104
- self._modules[addr]._sub_address[sub_num] = sub_addr
105
- self._modules[sub_addr] = self._modules[addr]
106
- self._modules[addr].cleanupSubChannels()
98
+ module._sub_address[sub_num] = sub_addr
99
+ self._modules[sub_addr] = module
100
+ module.cleanupSubChannels()
107
101
 
108
102
  def get_modules(self) -> dict:
109
103
  """Return the module cache."""
110
104
  return self._modules
111
105
 
112
- def get_module(self, addr: str) -> None | Module:
106
+ def get_module(self, addr: int) -> None | Module:
113
107
  """Get a module on an address."""
114
108
  if addr in self._modules:
115
109
  return self._modules[addr]
116
110
  return None
117
111
 
118
- def get_channels(self, addr: str) -> None | dict:
112
+ def get_channels(self, addr: int) -> None | dict:
119
113
  """Get the channels for an address."""
120
114
  if addr in self._modules:
121
115
  return (self._modules[addr]).get_channels()
@@ -129,6 +123,7 @@ class Velbus:
129
123
 
130
124
  async def connect(self, test_connect: bool = False) -> None:
131
125
  """Connect to the bus and load all the data."""
126
+ await self._handler.read_protocol_data()
132
127
  auth = None
133
128
  # connect to the bus
134
129
  if ":" in self._dsn:
@@ -159,16 +154,18 @@ class Velbus:
159
154
  else:
160
155
  # serial port
161
156
  try:
162
- _transport, _protocol = await serial_asyncio.create_serial_connection(
163
- asyncio.get_event_loop(),
164
- lambda: self._protocol,
165
- url=self._dsn,
166
- baudrate=38400,
167
- bytesize=serial.EIGHTBITS,
168
- parity=serial.PARITY_NONE,
169
- stopbits=serial.STOPBITS_ONE,
170
- xonxoff=0,
171
- rtscts=1,
157
+ _transport, _protocol = (
158
+ await serial_asyncio_fast.create_serial_connection(
159
+ asyncio.get_event_loop(),
160
+ lambda: self._protocol,
161
+ url=self._dsn,
162
+ baudrate=38400,
163
+ bytesize=serial.EIGHTBITS,
164
+ parity=serial.PARITY_NONE,
165
+ stopbits=serial.STOPBITS_ONE,
166
+ xonxoff=0,
167
+ rtscts=1,
168
+ )
172
169
  )
173
170
  except (FileNotFoundError, serial.SerialException) as err:
174
171
  raise VelbusConnectionFailed from err
@@ -179,44 +176,15 @@ class Velbus:
179
176
  await self._protocol.write_auth_key(auth)
180
177
 
181
178
  # scan the bus
182
- await self.scan()
179
+ await self._handler.scan()
183
180
 
184
181
  async def scan(self) -> None:
185
- """Scan the bus."""
186
- self._handler.scan_started()
187
- for addr in range(1, 256):
188
- msg = ModuleTypeRequestMessage(addr)
189
- await self.send(msg)
190
- await self._handler._scan_complete_event.wait()
191
- # calculate how long to wait
192
- calc_timeout = len(self._modules) * 30
193
- if calc_timeout < LOAD_TIMEOUT:
194
- timeout = calc_timeout
195
- else:
196
- timeout = LOAD_TIMEOUT
197
- # create a task to wait until we have all modules loaded
198
- tsk = asyncio.Task(self._check_if_modules_are_loaded())
199
- try:
200
- await asyncio.wait_for(tsk, timeout=timeout)
201
- except asyncio.TimeoutError:
202
- self._log.error(
203
- f"Not all modules are loaded within a timeout of {LOAD_TIMEOUT} seconds, continuing with the loaded modules"
204
- )
182
+ """Service endpoint to restart the scan"""
183
+ await self._handler.scan(True)
205
184
 
206
- async def _check_if_modules_are_loaded(self) -> None:
207
- """Task to wait until modules are loaded."""
208
- while True:
209
- mods_loaded = 0
210
- for mod in (self.get_modules()).values():
211
- if mod.is_loaded():
212
- mods_loaded += 1
213
- else:
214
- self._log.warning(f"Waiting for module {mod._address}")
215
- if mods_loaded == len(self.get_modules()):
216
- self._log.info("All modules loaded")
217
- return
218
- self._log.info("Not all modules loaded yet, waiting 15 seconds")
219
- await asyncio.sleep(15)
185
+ async def sendTypeRequestMessage(self, address: int) -> None:
186
+ msg = ModuleTypeRequestMessage(address)
187
+ await self.send(msg)
220
188
 
221
189
  async def send(self, msg: Message) -> None:
222
190
  """Send a packet."""
@@ -0,0 +1,276 @@
1
+ """
2
+ Velbus packet handler
3
+ :Author maikel punie <maikel.punie@gmail.com>
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from velbusaio.const import SCAN_MODULETYPE_TIMEOUT
9
+ from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INITIAL
10
+ from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INTERVAL
11
+
12
+ import asyncio
13
+ import json
14
+ import logging
15
+ import threading
16
+ import os
17
+ import pathlib
18
+
19
+ from aiofile import async_open
20
+
21
+ from typing import TYPE_CHECKING, Awaitable, Callable
22
+ import pkg_resources
23
+
24
+ from velbusaio.command_registry import commandRegistry
25
+ from velbusaio.helpers import h2, keys_exists
26
+ from velbusaio.message import Message
27
+ from velbusaio.messages.module_subtype import ModuleSubTypeMessage
28
+ from velbusaio.messages.module_type import ModuleTypeMessage, ModuleType2Message
29
+ from velbusaio.raw_message import RawMessage
30
+ from velbusaio.helpers import get_cache_dir
31
+
32
+
33
+ if TYPE_CHECKING:
34
+ from velbusaio.controller import Velbus
35
+
36
+
37
+ class PacketHandler:
38
+ """
39
+ The packetHandler class
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ velbus: Velbus,
45
+ ) -> None:
46
+ self._log = logging.getLogger("velbus-handler")
47
+ self._log.setLevel(logging.DEBUG)
48
+ self._velbus = velbus
49
+ self._typeResponseReceived = asyncio.Event()
50
+ self._scanLock = threading.Lock()
51
+ self._modulescan_address = 0
52
+ self._scan_complete = False
53
+ self._scan_delay_msec = 0
54
+
55
+ async def read_protocol_data(self):
56
+ async with async_open(
57
+ pkg_resources.resource_filename(__name__, "protocol.json")
58
+ ) as protocol_file:
59
+ self.pdata = json.loads(await protocol_file.read())
60
+
61
+ def empty_cache(self) -> bool:
62
+ if (
63
+ len(
64
+ [
65
+ name
66
+ for name in os.listdir(f"{get_cache_dir()}")
67
+ if os.path.isfile(f"{get_cache_dir()}/{name}")
68
+ ]
69
+ )
70
+ == 0
71
+ ):
72
+ return True
73
+ return False
74
+
75
+ async def scan(self, reload_cache: bool = False) -> None:
76
+ if reload_cache:
77
+ self._modulescan_address = 0
78
+ self._scan_complete = False
79
+ # non-blocking check to see if the cache_dir is empty
80
+ loop = asyncio.get_running_loop()
81
+ if not reload_cache and await loop.run_in_executor(None, self.empty_cache):
82
+ self._log.info("No cache yet, so forcing a bus scan")
83
+ reload_cache = True
84
+ self._log.info("Start module scan")
85
+ while self._modulescan_address < 254:
86
+ address = 0
87
+ module = None
88
+ with self._scanLock:
89
+ self._modulescan_address = self._modulescan_address + 1
90
+ address = self._modulescan_address
91
+ module = self._velbus.get_module(address)
92
+
93
+ self._log.info(f"Starting handling scan {address}")
94
+
95
+ cfile = pathlib.Path(f"{get_cache_dir()}/{address}.json")
96
+ # cleanup the old module cache if needed
97
+ scanModule = reload_cache
98
+ if scanModule and os.path.isfile(cfile):
99
+ os.remove(cfile)
100
+ elif os.path.isfile(cfile):
101
+ scanModule = os.path.isfile(cfile)
102
+ if scanModule:
103
+ try:
104
+ self._log.info(f"Starting scan {address}")
105
+ self._typeResponseReceived.clear()
106
+ await self._velbus.sendTypeRequestMessage(address)
107
+ await asyncio.wait_for(
108
+ self._typeResponseReceived.wait(),
109
+ SCAN_MODULETYPE_TIMEOUT / 1000.0,
110
+ )
111
+ with self._scanLock:
112
+ module = self._velbus.get_module(address)
113
+ except asyncio.TimeoutError:
114
+ self._log.info(
115
+ f"Scan module {address} failed: not present or unavailable"
116
+ )
117
+ if module is not None:
118
+ try:
119
+ self._log.debug(f"Module {address} detected: start loading")
120
+ await asyncio.wait_for(
121
+ module.load(from_cache=True),
122
+ SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
123
+ )
124
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INITIAL
125
+ while self._scan_delay_msec > 50 and not module.is_loaded():
126
+ # self._log.debug(
127
+ # f"\t... waiting {self._scan_delay_msec} is_loaded={module.is_loaded()}"
128
+ # )
129
+ self._scan_delay_msec = self._scan_delay_msec - 50
130
+ await asyncio.sleep(0.05)
131
+ self._log.info(
132
+ f"Scan module {address} completed, module loaded={module.is_loaded()}"
133
+ )
134
+ except asyncio.TimeoutError:
135
+ self._log.error(
136
+ f"Module {address} did not respond to info requests after successful type request"
137
+ )
138
+ self._scan_complete = True
139
+ self._log.info("Module scan completed")
140
+
141
+ async def handle(self, rawmsg: RawMessage) -> None:
142
+ """
143
+ Handle a received packet
144
+ """
145
+ if rawmsg.address < 1 or rawmsg.address > 254:
146
+ return
147
+ if rawmsg.command is None:
148
+ return
149
+
150
+ priority = rawmsg.priority
151
+ address = rawmsg.address
152
+ rtr = rawmsg.rtr
153
+ command_value = rawmsg.command
154
+ data = rawmsg.data_only
155
+
156
+ # handle module type response message
157
+ if command_value == 0xFF:
158
+ if not self._scan_complete:
159
+ tmsg: ModuleTypeMessage = ModuleTypeMessage()
160
+ tmsg.populate(priority, address, rtr, data)
161
+ with self._scanLock:
162
+ self._handle_module_type(tmsg)
163
+ if address == self._modulescan_address:
164
+ self._typeResponseReceived.set()
165
+ else:
166
+ self._log.debug(
167
+ f"Unexpected module type message module address {address}, Velbuslink scan?"
168
+ )
169
+ self._modulescan_address = address - 1
170
+
171
+ self._typeResponseReceived.set()
172
+
173
+ # handle module subtype response message
174
+ elif command_value in (0xB0, 0xA7, 0xA6):
175
+ if not self._scan_complete:
176
+ msg: ModuleSubTypeMessage = ModuleSubTypeMessage()
177
+ msg.populate(priority, address, rtr, data)
178
+ if command_value == 0xB0:
179
+ msg.sub_address_offset = 0
180
+ elif command_value == 0xA7:
181
+ msg.sub_address_offset = 4
182
+ elif command_value == 0xA6:
183
+ msg.sub_address_offset = 8
184
+ with self._scanLock:
185
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INITIAL
186
+ self._handle_module_subtype(msg)
187
+
188
+ # ignore broadcast
189
+ elif command_value in self.pdata["MessagesBroadCast"]:
190
+ self._log.debug(
191
+ "Received broadcast message {} from {}, ignoring".format(
192
+ self.pdata["MessageBroadCast"][str(command_value).upper()], address
193
+ )
194
+ )
195
+
196
+ # handle other messages for modules that are already scanned
197
+ else:
198
+ module = None
199
+ with self._scanLock:
200
+ module = self._velbus.get_module(address)
201
+ if module is not None:
202
+ module_type = module.get_type()
203
+ if commandRegistry.has_command(int(command_value), module_type):
204
+ command = commandRegistry.get_command(command_value, module_type)
205
+ if not command:
206
+ return
207
+ msg = command()
208
+ msg.populate(priority, address, rtr, data)
209
+ # restart the info completion time when info message received
210
+ if command_value in (
211
+ 0xF0,
212
+ 0xF1,
213
+ 0xF2,
214
+ 0xFB,
215
+ 0xFE,
216
+ 0xCC,
217
+ ): # names, memory data, memory block
218
+ self._scan_delay_msec = SCAN_MODULEINFO_TIMEOUT_INTERVAL
219
+ # self._log.debug(f"Restart timeout {msg}")
220
+ # send the message to the modules
221
+ await module.on_message(msg)
222
+ else:
223
+ self._log.warning(f"NOT FOUND IN command_registry: {rawmsg}")
224
+
225
+ def _handle_module_type(self, msg: ModuleTypeMessage | ModuleType2Message) -> None:
226
+ """
227
+ load the module data
228
+ """
229
+ if msg is not None:
230
+ module = self._velbus.get_module(msg.address)
231
+ if module is None:
232
+ data = keys_exists(self.pdata, "ModuleTypes", h2(msg.module_type))
233
+ if not data:
234
+ self._log.warning(f"Module not recognized: {msg.module_type}")
235
+ return
236
+ self._velbus.add_module(
237
+ msg.address,
238
+ msg.module_type,
239
+ data,
240
+ memorymap=msg.memory_map_version,
241
+ build_year=msg.build_year,
242
+ build_week=msg.build_week,
243
+ serial=msg.serial,
244
+ )
245
+ else:
246
+ self._log.debug(
247
+ f"***Module already exists scanAddr={self._modulescan_address} addr={msg.address} {msg}"
248
+ )
249
+
250
+ # else:
251
+ # self._log.debug("*** handle_module_type called without response message")
252
+
253
+ def _handle_module_subtype(self, msg: ModuleSubTypeMessage) -> None:
254
+ module = self._velbus.get_module(msg.address)
255
+ if module is not None:
256
+ addrList = {
257
+ (msg.sub_address_offset + 1): msg.sub_address_1,
258
+ (msg.sub_address_offset + 2): msg.sub_address_2,
259
+ (msg.sub_address_offset + 3): msg.sub_address_3,
260
+ (msg.sub_address_offset + 4): msg.sub_address_4,
261
+ }
262
+ self._velbus.add_submodules(module, addrList)
263
+
264
+
265
+ # def _channel_convert(self, module: str, channel: str, ctype: str) -> None | int:
266
+ # data = keys_exists(
267
+ # self.pdata, "ModuleTypes", h2(module), "ChannelNumbers", ctype
268
+ # )
269
+ # if data and "Map" in data and h2(channel) in data["Map"]:
270
+ # return data["Map"][h2(channel)]
271
+ # if data and "Convert" in data:
272
+ # return int(channel)
273
+ # for offset in range(0, 8):
274
+ # if channel & (1 << offset):
275
+ # return offset + 1
276
+ # return None
@@ -20,19 +20,19 @@ class Message:
20
20
  Base Velbus message
21
21
  """
22
22
 
23
- def __init__(self, address: int = None) -> None:
23
+ def __init__(self, address: int = 0) -> None:
24
24
  self.priority = PRIORITY_LOW
25
- self.address = None
26
- self.rtr = False
25
+ self.address: int = 0
26
+ self.rtr: bool = False
27
27
  self.data = bytearray()
28
28
  self.set_defaults(address)
29
29
 
30
- def set_attributes(self, priority: int, address: int, rtr: int) -> None:
30
+ def set_attributes(self, priority: int, address: int, rtr: bool) -> None:
31
31
  self.priority = priority
32
32
  self.address = address
33
33
  self.rtr = rtr
34
34
 
35
- def populate(self, priority: int, address: int, rtr: int, data: int) -> None:
35
+ def populate(self, priority: int, address: int, rtr: bool, data: int) -> None:
36
36
  raise NotImplementedError
37
37
 
38
38
  def set_defaults(self, address: int | None) -> None:
@@ -66,7 +66,7 @@ class Message:
66
66
  if callable(getattr(self, key)) or key.startswith("__"):
67
67
  del me[key]
68
68
  if isinstance(me[key], (bytes, bytearray)):
69
- me[key] = str(me[key], "utf-8")
69
+ me[key] = str(me[key])
70
70
  return me
71
71
 
72
72
  def to_json(self) -> str:
@@ -25,7 +25,7 @@ class ModuleSubTypeMessage(Message):
25
25
 
26
26
  # pylint: disable-msg=R0902
27
27
 
28
- def __init__(self, address=None, sub_address_offset: int = 0):
28
+ def __init__(self, address=None, sub_address_offset: int = 0) -> None:
29
29
  Message.__init__(self)
30
30
  self.module_type = 0x00
31
31
  self.sub_address_1 = 0xFF
@@ -36,13 +36,13 @@ class ModuleSubTypeMessage(Message):
36
36
  self.serial = 0
37
37
  self.sub_address_offset = sub_address_offset
38
38
 
39
- def module_name(self):
39
+ def module_name(self) -> str:
40
40
  """
41
41
  :return: str
42
42
  """
43
43
  return "Unknown"
44
44
 
45
- def populate(self, priority, address, rtr, data):
45
+ def populate(self, priority, address, rtr, data) -> None:
46
46
  """
47
47
  :return: None
48
48
  """
@@ -98,7 +98,7 @@ class ModuleTypeMessage(Message):
98
98
 
99
99
  # pylint: disable-msg=R0902
100
100
 
101
- def __init__(self, address=None):
101
+ def __init__(self, address=None) -> None:
102
102
  Message.__init__(self)
103
103
  self.module_type = 0x00
104
104
  self.led_on = []
@@ -110,13 +110,13 @@ class ModuleTypeMessage(Message):
110
110
  self.build_week = 0
111
111
  self.set_defaults(address)
112
112
 
113
- def module_name(self):
113
+ def module_name(self) -> str:
114
114
  """
115
115
  :return: str
116
116
  """
117
117
  return "Unknown"
118
118
 
119
- def populate(self, priority, address, rtr, data):
119
+ def populate(self, priority, address, rtr, data) -> None:
120
120
  """
121
121
  :return: None
122
122
  """
@@ -151,7 +151,7 @@ class ModuleTypeMessage(Message):
151
151
  ],
152
152
  )
153
153
  class ModuleType2Message(Message):
154
- def __init__(self, address=None):
154
+ def __init__(self, address=None) -> None:
155
155
  Message.__init__(self)
156
156
  self.module_type = 0x00
157
157
  self.led_on = []
@@ -164,7 +164,7 @@ class ModuleType2Message(Message):
164
164
  self.term = 0
165
165
  self.set_defaults(address)
166
166
 
167
- def module_name(self):
167
+ def module_name(self) -> str:
168
168
  """
169
169
  :return: str
170
170
  """