velbus-aio 2023.12.0__py3-none-any.whl → 2025.11.0__py3-none-any.whl

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 (192) hide show
  1. scripts/parse_specs.py +156 -0
  2. {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/METADATA +24 -8
  3. velbus_aio-2025.11.0.dist-info/RECORD +194 -0
  4. {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/WHEEL +1 -1
  5. {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/top_level.txt +1 -0
  6. velbusaio/channels.py +35 -14
  7. velbusaio/command_registry.py +32 -5
  8. velbusaio/const.py +10 -2
  9. velbusaio/controller.py +183 -145
  10. velbusaio/handler.py +286 -154
  11. velbusaio/helpers.py +2 -1
  12. velbusaio/message.py +14 -8
  13. velbusaio/messages/__init__.py +6 -2
  14. velbusaio/messages/blind_status.py +4 -2
  15. velbusaio/messages/bus_active.py +1 -0
  16. velbusaio/messages/bus_error_counter_status.py +1 -0
  17. velbusaio/messages/bus_error_counter_status_request.py +1 -0
  18. velbusaio/messages/bus_off.py +1 -0
  19. velbusaio/messages/channel_name_part1.py +18 -0
  20. velbusaio/messages/channel_name_part2.py +18 -0
  21. velbusaio/messages/channel_name_part3.py +18 -0
  22. velbusaio/messages/channel_name_request.py +2 -1
  23. velbusaio/messages/clear_led.py +1 -0
  24. velbusaio/messages/counter_status.py +1 -0
  25. velbusaio/messages/counter_status_request.py +1 -0
  26. velbusaio/messages/counter_value.py +44 -0
  27. velbusaio/messages/cover_down.py +2 -1
  28. velbusaio/messages/cover_off.py +2 -3
  29. velbusaio/messages/cover_position.py +3 -4
  30. velbusaio/messages/cover_up.py +2 -1
  31. velbusaio/messages/dali_device_settings.py +2 -1
  32. velbusaio/messages/dali_device_settings_request.py +2 -1
  33. velbusaio/messages/dali_dim_value_status.py +4 -1
  34. velbusaio/messages/dimmer_channel_status.py +5 -1
  35. velbusaio/messages/dimmer_status.py +13 -1
  36. velbusaio/messages/edge_set_color.py +1 -0
  37. velbusaio/messages/edge_set_custom_color.py +1 -0
  38. velbusaio/messages/fast_blinking_led.py +1 -0
  39. velbusaio/messages/forced_off.py +1 -0
  40. velbusaio/messages/forced_on.py +1 -0
  41. velbusaio/messages/interface_status_request.py +1 -0
  42. velbusaio/messages/ir_receiver_status.py +1 -0
  43. velbusaio/messages/kwh_status.py +1 -0
  44. velbusaio/messages/light_value_request.py +1 -0
  45. velbusaio/messages/memo_text.py +1 -0
  46. velbusaio/messages/memory_data.py +1 -0
  47. velbusaio/messages/memory_data_block.py +1 -0
  48. velbusaio/messages/memory_dump_request.py +1 -0
  49. velbusaio/messages/module_status.py +13 -0
  50. velbusaio/messages/module_status_request.py +5 -2
  51. velbusaio/messages/module_subtype.py +4 -3
  52. velbusaio/messages/module_type.py +17 -7
  53. velbusaio/messages/module_type_request.py +1 -0
  54. velbusaio/messages/psu_load.py +56 -0
  55. velbusaio/messages/psu_values.py +53 -0
  56. velbusaio/messages/push_button_status.py +1 -0
  57. velbusaio/messages/raw.py +1 -0
  58. velbusaio/messages/read_data_block_from_memory.py +1 -0
  59. velbusaio/messages/read_data_from_memory.py +1 -0
  60. velbusaio/messages/realtime_clock_status_request.py +1 -0
  61. velbusaio/messages/receive_buffer_full.py +1 -0
  62. velbusaio/messages/receive_ready.py +1 -0
  63. velbusaio/messages/relay_status.py +1 -0
  64. velbusaio/messages/restore_dimmer.py +16 -2
  65. velbusaio/messages/select_program.py +1 -0
  66. velbusaio/messages/sensor_settings_request.py +1 -0
  67. velbusaio/messages/sensor_temp_request.py +1 -0
  68. velbusaio/messages/sensor_temperature.py +1 -0
  69. velbusaio/messages/set_date.py +5 -10
  70. velbusaio/messages/set_daylight_saving.py +3 -6
  71. velbusaio/messages/set_dimmer.py +22 -13
  72. velbusaio/messages/set_led.py +1 -0
  73. velbusaio/messages/set_realtime_clock.py +5 -10
  74. velbusaio/messages/set_temperature.py +1 -0
  75. velbusaio/messages/slider_status.py +15 -1
  76. velbusaio/messages/slow_blinking_led.py +1 -0
  77. velbusaio/messages/start_relay_blinking_timer.py +1 -0
  78. velbusaio/messages/start_relay_timer.py +1 -0
  79. velbusaio/messages/switch_relay_off.py +1 -0
  80. velbusaio/messages/switch_relay_on.py +1 -0
  81. velbusaio/messages/switch_to_comfort.py +1 -0
  82. velbusaio/messages/switch_to_day.py +1 -0
  83. velbusaio/messages/switch_to_night.py +1 -0
  84. velbusaio/messages/switch_to_safe.py +1 -0
  85. velbusaio/messages/temp_sensor_settings_part1.py +1 -0
  86. velbusaio/messages/temp_sensor_settings_part2.py +1 -0
  87. velbusaio/messages/temp_sensor_settings_part3.py +1 -0
  88. velbusaio/messages/temp_sensor_settings_part4.py +1 -0
  89. velbusaio/messages/temp_sensor_settings_request.py +1 -0
  90. velbusaio/messages/temp_sensor_status.py +1 -0
  91. velbusaio/messages/temp_set_cooling.py +1 -0
  92. velbusaio/messages/temp_set_heating.py +1 -0
  93. velbusaio/messages/update_led_status.py +1 -0
  94. velbusaio/messages/very_fast_blinking_led.py +1 -0
  95. velbusaio/messages/write_data_to_memory.py +1 -0
  96. velbusaio/messages/write_memory_block.py +1 -0
  97. velbusaio/messages/write_module_address_and_serial_number.py +1 -0
  98. velbusaio/module.py +214 -102
  99. velbusaio/module_spec/01.json +62 -0
  100. velbusaio/module_spec/02.json +16 -0
  101. velbusaio/module_spec/03.json +23 -0
  102. velbusaio/module_spec/04.json +283 -0
  103. velbusaio/module_spec/05.json +54 -0
  104. velbusaio/module_spec/06.json +110 -0
  105. velbusaio/module_spec/07.json +16 -0
  106. velbusaio/module_spec/08.json +38 -0
  107. velbusaio/module_spec/09.json +30 -0
  108. velbusaio/module_spec/0A.json +58 -0
  109. velbusaio/module_spec/0B.json +58 -0
  110. velbusaio/module_spec/0C.json +18 -0
  111. velbusaio/module_spec/0E.json +25 -0
  112. velbusaio/module_spec/0F.json +16 -0
  113. velbusaio/module_spec/10.json +111 -0
  114. velbusaio/module_spec/11.json +111 -0
  115. velbusaio/module_spec/12.json +73 -0
  116. velbusaio/module_spec/13.json +4 -0
  117. velbusaio/module_spec/14.json +16 -0
  118. velbusaio/module_spec/15.json +83 -0
  119. velbusaio/module_spec/16.json +129 -0
  120. velbusaio/module_spec/17.json +129 -0
  121. velbusaio/module_spec/18.json +129 -0
  122. velbusaio/module_spec/1A.json +79 -0
  123. velbusaio/module_spec/1B.json +107 -0
  124. velbusaio/module_spec/1D.json +89 -0
  125. velbusaio/module_spec/1E.json +306 -0
  126. velbusaio/module_spec/1F.json +178 -0
  127. velbusaio/module_spec/20.json +178 -0
  128. velbusaio/module_spec/21.json +326 -0
  129. velbusaio/module_spec/22.json +426 -0
  130. velbusaio/module_spec/23.json +129 -0
  131. velbusaio/module_spec/24.json +30 -0
  132. velbusaio/module_spec/25.json +3 -0
  133. velbusaio/module_spec/28.json +454 -0
  134. velbusaio/module_spec/29.json +235 -0
  135. velbusaio/module_spec/2A.json +239 -0
  136. velbusaio/module_spec/2B.json +239 -0
  137. velbusaio/module_spec/2C.json +257 -0
  138. velbusaio/module_spec/2D.json +270 -0
  139. velbusaio/module_spec/2E.json +215 -0
  140. velbusaio/module_spec/2F.json +211 -0
  141. velbusaio/module_spec/30.json +58 -0
  142. velbusaio/module_spec/31.json +465 -0
  143. velbusaio/module_spec/32.json +385 -0
  144. velbusaio/module_spec/33.json +249 -0
  145. velbusaio/module_spec/34.json +313 -0
  146. velbusaio/module_spec/35.json +313 -0
  147. velbusaio/module_spec/36.json +313 -0
  148. velbusaio/module_spec/37.json +333 -0
  149. velbusaio/module_spec/38.json +111 -0
  150. velbusaio/module_spec/39.json +4 -0
  151. velbusaio/module_spec/3A.json +306 -0
  152. velbusaio/module_spec/3B.json +306 -0
  153. velbusaio/module_spec/3C.json +306 -0
  154. velbusaio/module_spec/3D.json +454 -0
  155. velbusaio/module_spec/3E.json +302 -0
  156. velbusaio/module_spec/3F.json +4 -0
  157. velbusaio/module_spec/40.json +4 -0
  158. velbusaio/module_spec/41.json +241 -0
  159. velbusaio/module_spec/42.json +4 -0
  160. velbusaio/module_spec/43.json +23 -0
  161. velbusaio/module_spec/44.json +38 -0
  162. velbusaio/module_spec/45.json +4 -0
  163. velbusaio/module_spec/48.json +111 -0
  164. velbusaio/module_spec/49.json +111 -0
  165. velbusaio/module_spec/4A.json +89 -0
  166. velbusaio/module_spec/4B.json +138 -0
  167. velbusaio/module_spec/4C.json +129 -0
  168. velbusaio/module_spec/4D.json +108 -0
  169. velbusaio/module_spec/4E.json +787 -0
  170. velbusaio/module_spec/4F.json +114 -0
  171. velbusaio/module_spec/50.json +114 -0
  172. velbusaio/module_spec/51.json +114 -0
  173. velbusaio/module_spec/52.json +456 -0
  174. velbusaio/module_spec/54.json +270 -0
  175. velbusaio/module_spec/55.json +270 -0
  176. velbusaio/module_spec/56.json +270 -0
  177. velbusaio/module_spec/57.json +260 -0
  178. velbusaio/module_spec/5A.json +4 -0
  179. velbusaio/module_spec/5B.json +4 -0
  180. velbusaio/module_spec/5C.json +90 -0
  181. velbusaio/module_spec/5F.json +78 -0
  182. velbusaio/module_spec/60.json +4 -0
  183. velbusaio/module_spec/61.json +89 -0
  184. velbusaio/module_spec/broadcast.json +67 -0
  185. velbusaio/module_spec/ignore.json +22 -0
  186. velbusaio/protocol.py +34 -17
  187. velbusaio/raw_message.py +6 -6
  188. velbusaio/util.py +4 -0
  189. velbusaio/vlp_reader.py +249 -0
  190. velbus_aio-2023.12.0.dist-info/RECORD +0 -103
  191. velbusaio/moduleprotocol/protocol.json +0 -26507
  192. {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,13 +1,17 @@
1
- """
1
+ """Command registry.
2
+
2
3
  :author: Maikel Punie <maikel.punie@gmail.com> and Thomas Delaet <thomas@delaet.org>
3
4
  """
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  MODULE_DIRECTORY = {
7
9
  0x01: "VMB8PB",
8
10
  0x02: "VMB1RY",
9
11
  0x03: "VMB1BL",
12
+ 0x04: "VMBPSUMNGR-20",
10
13
  0x05: "VMB6IN",
14
+ 0x06: "VMB4LEDPWM-20",
11
15
  0x07: "VMB1DM",
12
16
  0x08: "VMB4RY",
13
17
  0x09: "VMB2BL",
@@ -36,6 +40,11 @@ MODULE_DIRECTORY = {
36
40
  0x20: "VMBGP4",
37
41
  0x21: "VMBGPO",
38
42
  0x22: "VMB7IN",
43
+ 0x23: "VMBPIRO-10",
44
+ 0x24: "VMB2DC-20",
45
+ 0x25: "VMBGPTC",
46
+ 0x26: "VMB4RYLD-20",
47
+ 0x27: "VMB4RYNO-20",
39
48
  0x28: "VMBGPOD",
40
49
  0x29: "VMB1RYNOS",
41
50
  0x2A: "VMBPIRM",
@@ -44,6 +53,7 @@ MODULE_DIRECTORY = {
44
53
  0x2D: "VMBGP4PIR",
45
54
  0x2E: "VMB1BLS",
46
55
  0x2F: "VMBDMI-R",
56
+ 0x30: "VMBRFR8S",
47
57
  0x31: "VMBMETEO",
48
58
  0x32: "VMB4AN",
49
59
  0x33: "VMBVP01",
@@ -68,23 +78,33 @@ MODULE_DIRECTORY = {
68
78
  0x48: "VMB4RYLD-10",
69
79
  0x49: "VMB4RYNO-10",
70
80
  0x4A: "VMB2BLE-10",
81
+ 0x4B: "VMB8DC-20",
71
82
  0x4C: "VMB6PB-20",
83
+ 0x4D: "VMBPIR-20",
84
+ 0x4E: "VMB8IN-20",
72
85
  0x4F: "VMBEL1-20",
73
86
  0x50: "VMBEL2-20",
74
87
  0x51: "VMBEL4-20",
75
88
  0x52: "VMBELO-20",
76
- 0x53: "VMBGP1-20",
77
- 0x54: "VMBGP2-20",
78
- 0x55: "VMBGP4-20",
79
- 0x56: "VMBGPO-20",
89
+ 0x54: "VMBGP1-20",
90
+ 0x55: "VMBGP2-20",
91
+ 0x56: "VMBGP4-20",
92
+ 0x57: "VMBGPO-20",
93
+ 0x59: "VMBPIRO-20",
80
94
  0x5A: "VMBDALI-20",
95
+ 0x5B: "VMBSIG-20",
81
96
  0x5C: "VMBEL4PIR-20",
82
97
  0x5F: "VMBGP4PIR-20",
98
+ 0x60: "VMBSIG-21",
99
+ 0x61: "VMB2BLE-20",
83
100
  }
84
101
 
85
102
 
86
103
  class CommandRegistry:
104
+ """Command registry class."""
105
+
87
106
  def __init__(self, module_directory: dict) -> None:
107
+ """Init method."""
88
108
  self._module_directory = module_directory
89
109
  self._default_commands = {}
90
110
  self._overrides = {}
@@ -92,6 +112,7 @@ class CommandRegistry:
92
112
  def register_command(
93
113
  self, command_value: int, command_class: type, module_name: str | None = None
94
114
  ) -> None:
115
+ """Register a command."""
95
116
  if command_value < 0 or command_value > 255:
96
117
  raise ValueError("Command_value should be >=0 and <=255")
97
118
  if module_name and module_name not in self._module_directory.values():
@@ -112,6 +133,7 @@ class CommandRegistry:
112
133
  def _register_override(
113
134
  self, command_value: int, command_class: type, module_type: str
114
135
  ) -> None:
136
+ """Register and override."""
115
137
  if module_type not in self._overrides:
116
138
  self._overrides[module_type] = {}
117
139
  if command_value not in self._overrides[module_type]:
@@ -122,12 +144,14 @@ class CommandRegistry:
122
144
  )
123
145
 
124
146
  def _register_default(self, command_value: int, command_class: type) -> None:
147
+ """Register a default command."""
125
148
  if command_value not in self._default_commands:
126
149
  self._default_commands[command_value] = command_class
127
150
  else:
128
151
  raise Exception("double registration in command registry")
129
152
 
130
153
  def has_command(self, command_value: int, module_type: int = 0) -> bool:
154
+ """Find a command."""
131
155
  if module_type in self._overrides:
132
156
  if command_value in self._overrides[module_type]:
133
157
  return True
@@ -136,6 +160,7 @@ class CommandRegistry:
136
160
  return False
137
161
 
138
162
  def get_command(self, command_value: int, module_type: int = 0) -> None | type:
163
+ """Search a command in the registry."""
139
164
  if module_type in self._overrides:
140
165
  if command_value in self._overrides[module_type]:
141
166
  return self._overrides[module_type][command_value]
@@ -148,6 +173,8 @@ commandRegistry = CommandRegistry(MODULE_DIRECTORY)
148
173
 
149
174
 
150
175
  def register(command_value: int, module_types: list[str] | None = None):
176
+ """Register decorator."""
177
+
151
178
  def inner_register(command_class):
152
179
  if module_types:
153
180
  for module_type in module_types:
velbusaio/const.py CHANGED
@@ -1,6 +1,8 @@
1
- """
1
+ """Constant for velbusaio.
2
+
2
3
  Author: Maikel Punie <maikel.punie@gmail.com>
3
4
  """
5
+
4
6
  from __future__ import annotations
5
7
 
6
8
  from typing import Final
@@ -36,7 +38,13 @@ RTR: Final = 0x40
36
38
  NO_RTR: Final = 0x00
37
39
 
38
40
  CACHEDIR: Final = ".velbuscache"
39
- LOAD_TIMEOUT: Final = 600
41
+
42
+ # Module scan timeout values (in mSec)
43
+ SCAN_MODULETYPE_TIMEOUT: Final = 3000 # 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
+ )
40
48
 
41
49
  DEVICE_CLASS_ILLUMINANCE: Final = "illuminance"
42
50
  DEVICE_CLASS_TEMPERATURE: Final = "temperature"
velbusaio/controller.py CHANGED
@@ -1,21 +1,33 @@
1
- """
2
- Main interface for the velbusaio lib
3
- """
1
+ """Main interface for the velbusaio lib."""
2
+
4
3
  from __future__ import annotations
5
4
 
6
5
  import asyncio
7
6
  import logging
8
7
  import pathlib
9
- import pickle
10
8
  import re
11
9
  import ssl
10
+ import time
12
11
  from urllib.parse import urlparse
13
12
 
14
13
  import serial
15
- import serial_asyncio
14
+ import serial_asyncio_fast
16
15
 
17
- from velbusaio.channels import Channel
18
- from velbusaio.const import LOAD_TIMEOUT
16
+ from velbusaio.channels import (
17
+ Blind,
18
+ Button,
19
+ ButtonCounter,
20
+ Dimmer,
21
+ EdgeLit,
22
+ LightSensor,
23
+ Memo,
24
+ Relay,
25
+ SelectedProgram,
26
+ Sensor,
27
+ SensorNumber,
28
+ Temperature,
29
+ ThermostatChannel,
30
+ )
19
31
  from velbusaio.exceptions import VelbusConnectionFailed
20
32
  from velbusaio.handler import PacketHandler
21
33
  from velbusaio.helpers import get_cache_dir
@@ -27,38 +39,42 @@ from velbusaio.messages.set_realtime_clock import SetRealtimeClock
27
39
  from velbusaio.module import Module
28
40
  from velbusaio.protocol import VelbusProtocol
29
41
  from velbusaio.raw_message import RawMessage
42
+ from velbusaio.vlp_reader import VlpFile
30
43
 
31
44
 
32
45
  class Velbus:
33
- """
34
- A velbus controller
35
- """
46
+ """A velbus controller."""
36
47
 
37
48
  def __init__(
38
49
  self,
39
50
  dsn: str,
40
51
  cache_dir: str = get_cache_dir(),
52
+ vlp_file: str | None = None,
53
+ one_address: int | None = None,
41
54
  ) -> None:
55
+ """Init the Velbus controller."""
42
56
  self._log = logging.getLogger("velbus")
43
57
 
44
58
  self._protocol = VelbusProtocol(
45
59
  message_received_callback=self._on_message_received,
46
60
  connection_lost_callback=self._on_connection_lost,
47
- end_of_scan_callback=self._on_end_of_scan,
48
61
  )
49
62
  self._closing = False
50
63
  self._auto_reconnect = True
51
64
 
52
- self._dsn = dsn
53
- self._handler = PacketHandler(self.send, self)
65
+ self._destination = dsn
66
+ self._handler = PacketHandler(self, one_address)
54
67
  self._modules: dict[int, Module] = {}
55
68
  self._submodules: list[int] = []
56
- self._send_queue = asyncio.Queue()
57
- self._cache_dir = cache_dir
58
- # make sure the cachedir exists
59
- pathlib.Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
69
+ self._send_queue: asyncio.Queue = asyncio.Queue()
70
+ self._vlp_file = vlp_file
71
+ self._cache_dir: str = cache_dir
72
+
73
+ def get_cache_dir(self) -> str:
74
+ return self._cache_dir
60
75
 
61
76
  async def _on_message_received(self, msg: RawMessage) -> None:
77
+ """On message received function."""
62
78
  await self._handler.handle(msg)
63
79
 
64
80
  def _on_connection_lost(self, exc: Exception) -> None:
@@ -67,108 +83,79 @@ class Velbus:
67
83
  self._log.debug("Reconnecting to transport")
68
84
  asyncio.ensure_future(self.connect())
69
85
 
70
- def _on_end_of_scan(self) -> None:
71
- self._handler.scan_finished()
72
-
73
86
  async def add_module(
74
87
  self,
75
88
  addr: int,
76
89
  typ: int,
77
- data: dict,
78
90
  serial: int | None = None,
79
91
  memorymap: int | None = None,
80
92
  build_year: int | None = None,
81
93
  build_week: int | None = None,
82
94
  ) -> None:
83
- """
84
- Add a found module to the module cache
85
- """
86
- mod = self._load_module_from_cache(self._cache_dir, addr)
87
- if mod is not None:
88
- self._log.info(f"Load module from CACHE: {addr}")
89
- self._modules[addr] = mod
90
- self._modules[addr].initialize(self.send)
91
- await self._modules[addr].load(True)
92
- else:
93
- self._log.info(f"Load NEW module: {typ} @ {addr}")
94
- self._modules[addr] = Module.factory(
95
- addr,
96
- typ,
97
- data,
98
- serial=serial,
99
- build_year=build_year,
100
- build_week=build_week,
101
- memorymap=memorymap,
102
- cache_dir=self._cache_dir,
103
- )
104
- self._modules[addr].initialize(self.send)
105
- await self._modules[addr].load()
95
+ """Add a found module to the module cache."""
96
+ module = Module.factory(
97
+ addr,
98
+ typ,
99
+ serial=serial,
100
+ build_year=build_year,
101
+ build_week=build_week,
102
+ memorymap=memorymap,
103
+ cache_dir=self._cache_dir,
104
+ )
105
+ await module.initialize(self.send)
106
+ self._modules[addr] = module
107
+ self._log.info(f"Found module {addr}: {module}")
106
108
 
107
- async def add_submodules(self, addr: int, subList: dict[int, int]) -> None:
109
+ def add_submodules(self, module: Module, subList: dict[int, int]) -> None:
110
+ """Add submodules address to module."""
108
111
  for sub_num, sub_addr in subList.items():
109
112
  if sub_addr == 0xFF:
110
113
  continue
111
114
  self._submodules.append(sub_addr)
112
- self._modules[addr]._sub_address[sub_num] = sub_addr
113
- self._modules[sub_addr] = self._modules[addr]
114
- self._modules[addr].cleanupSubChannels()
115
-
116
- def _load_module_from_cache(self, cache_dir: str, address: int) -> None | Module:
117
- try:
118
- cfile = pathlib.Path(f"{cache_dir}/{address}.p")
119
- with cfile.open("rb") as fl:
120
- o = pickle.load(fl)
121
- if isinstance(o, Module):
122
- return o
123
- except OSError:
124
- pass
125
- return None
115
+ module._sub_address[sub_num] = sub_addr
116
+ self._modules[sub_addr] = module
117
+ module.cleanupSubChannels()
118
+
119
+ def addr_is_submodule(self, addr: int) -> bool:
120
+ """Check if an address is a submodule."""
121
+ return addr in self._submodules
126
122
 
127
123
  def get_modules(self) -> dict:
128
- """
129
- Return the module cache
130
- """
124
+ """Return the module cache."""
131
125
  return self._modules
132
126
 
133
- def get_module(self, addr: str) -> None | Module:
134
- """
135
- Get a module on an address
136
- """
137
- if addr in self._modules.keys():
127
+ def get_module(self, addr: int) -> None | Module:
128
+ """Get a module on an address."""
129
+ if addr in self._modules:
138
130
  return self._modules[addr]
139
131
  return None
140
132
 
141
- def get_channels(self, addr: str) -> None | dict:
142
- """
143
- Get the channels for an address
144
- """
133
+ def get_channels(self, addr: int) -> None | dict:
134
+ """Get the channels for an address."""
145
135
  if addr in self._modules:
146
136
  return (self._modules[addr]).get_channels()
147
137
  return None
148
138
 
149
139
  async def stop(self) -> None:
140
+ """Stop the controller."""
150
141
  self._closing = True
151
142
  self._auto_reconnect = False
152
143
  self._protocol.close()
153
144
 
154
- async def connect(self, test_connect: bool = False) -> None:
155
- """
156
- Connect to the bus and load all the data
157
- """
158
- auth = None
145
+ async def connect(self) -> None:
146
+ """Connect to the bus and load all the data."""
147
+ await self._handler.read_protocol_data()
159
148
  # connect to the bus
160
- if ":" in self._dsn:
149
+ if ":" in self._destination:
161
150
  # tcp/ip combination
162
- if not re.search(r"^[A-Za-z0-9+.\-]+://", self._dsn):
151
+ if not re.search(r"^[A-Za-z0-9+.\-]+://", self._destination):
163
152
  # if no scheme, then add the tcp://
164
- self._dsn = f"tcp://{self._dsn}"
165
- parts = urlparse(self._dsn)
153
+ self._destination = f"tcp://{self._destination}"
154
+ parts = urlparse(self._destination)
166
155
  if parts.scheme == "tls":
167
156
  ctx = ssl._create_unverified_context()
168
157
  else:
169
158
  ctx = None
170
- if parts.username:
171
- auth = parts.username
172
159
  try:
173
160
  (
174
161
  _transport,
@@ -181,74 +168,76 @@ class Velbus:
181
168
  )
182
169
 
183
170
  except (ConnectionRefusedError, OSError) as err:
184
- raise VelbusConnectionFailed() from err
171
+ raise VelbusConnectionFailed from err
185
172
  else:
186
173
  # serial port
187
174
  try:
188
- _transport, _protocol = await serial_asyncio.create_serial_connection(
189
- asyncio.get_event_loop(),
190
- lambda: self._protocol,
191
- url=self._dsn,
192
- baudrate=38400,
193
- bytesize=serial.EIGHTBITS,
194
- parity=serial.PARITY_NONE,
195
- stopbits=serial.STOPBITS_ONE,
196
- xonxoff=0,
197
- rtscts=1,
175
+ _transport, _protocol = (
176
+ await serial_asyncio_fast.create_serial_connection(
177
+ asyncio.get_event_loop(),
178
+ lambda: self._protocol,
179
+ url=self._destination,
180
+ baudrate=38400,
181
+ bytesize=serial.EIGHTBITS,
182
+ parity=serial.PARITY_NONE,
183
+ stopbits=serial.STOPBITS_ONE,
184
+ xonxoff=0,
185
+ rtscts=1,
186
+ )
198
187
  )
199
188
  except (FileNotFoundError, serial.SerialException) as err:
200
- raise VelbusConnectionFailed() from err
201
- if test_connect:
202
- return
189
+ raise VelbusConnectionFailed from err
190
+
191
+ async def start(self) -> None:
203
192
  # if auth is required send the auth key
204
- if auth:
205
- await self._protocol.write_auth_key(auth)
193
+ parts = urlparse(self._destination)
194
+ if parts.username:
195
+ await self._protocol.write_auth_key(parts.username)
206
196
 
207
- # scan the bus
208
- await self.scan()
197
+ if self._vlp_file:
198
+ # use the vlp file to load the modules
199
+ vlp = VlpFile(self._vlp_file)
200
+ await vlp.read()
201
+ for mod_data in vlp.get():
202
+ # Convert hex address string to decimal integer
203
+ addr = mod_data.get_addr().split(",")
204
+ decimal_addr = int(addr[0], 16)
205
+ await self.add_module(
206
+ decimal_addr,
207
+ mod_data.get_type(),
208
+ serial=mod_data.get_serial(),
209
+ memorymap=mod_data.get_memory(),
210
+ build_year=int(mod_data.get_build()[0:2]),
211
+ build_week=int(mod_data.get_build()[2:4]),
212
+ )
213
+ # handle submodules
214
+ if len(addr) > 1:
215
+ self.add_submodules(
216
+ self._modules[decimal_addr], dict(enumerate(addr[1:]))
217
+ )
218
+ # load module data, special for dali
219
+ if mod_data.get_type() == 0x45 or mod_data.get_type() == 0x5A:
220
+ await self._modules[decimal_addr].load()
221
+ else:
222
+ module = self._modules[decimal_addr]
223
+ await module.load_from_vlp(mod_data)
224
+ await module.wait_for_status_messages()
225
+ else:
226
+ # make sure the cachedir exists
227
+ pathlib.Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
228
+ # scan the bus
229
+ await self._handler.scan()
209
230
 
210
231
  async def scan(self) -> None:
211
- self._handler.scan_started()
212
- for addr in range(1, 256):
213
- msg = ModuleTypeRequestMessage(addr)
214
- await self.send(msg)
215
- await self._handler._scan_complete_event.wait()
216
- # calculate how long to wait
217
- calc_timeout = len(self._modules) * 30
218
- if calc_timeout < LOAD_TIMEOUT:
219
- timeout = calc_timeout
220
- else:
221
- timeout = LOAD_TIMEOUT
222
- # create a task to wait until we have all modules loaded
223
- tsk = asyncio.Task(self._check_if_modules_are_loaded())
224
- try:
225
- await asyncio.wait_for(tsk, timeout=timeout)
226
- except asyncio.TimeoutError:
227
- self._log.error(
228
- f"Not all modules are loaded within a timeout of {LOAD_TIMEOUT} seconds, continuing with the loaded modules"
229
- )
232
+ """Service endpoint to restart the scan"""
233
+ await self._handler.scan(True)
230
234
 
231
- async def _check_if_modules_are_loaded(self) -> None:
232
- """
233
- Task to wait until modules are loaded
234
- """
235
- while True:
236
- mods_loaded = 0
237
- for mod in (self.get_modules()).values():
238
- if mod.is_loaded():
239
- mods_loaded += 1
240
- else:
241
- self._log.warning(f"Waiting for module {mod._address}")
242
- if mods_loaded == len(self.get_modules()):
243
- self._log.info("All modules loaded")
244
- return
245
- self._log.info("Not all modules loaded yet, waiting 15 seconds")
246
- await asyncio.sleep(15)
235
+ async def sendTypeRequestMessage(self, address: int) -> None:
236
+ msg = ModuleTypeRequestMessage(address)
237
+ await self.send(msg)
247
238
 
248
239
  async def send(self, msg: Message) -> None:
249
- """
250
- Send a packet
251
- """
240
+ """Send a packet."""
252
241
  await self._protocol.send_message(
253
242
  RawMessage(
254
243
  priority=msg.priority,
@@ -258,7 +247,53 @@ class Velbus:
258
247
  )
259
248
  )
260
249
 
261
- def get_all(self, class_name: str) -> list[Channel]:
250
+ def get_all_sensor(
251
+ self,
252
+ ) -> list[ButtonCounter | Temperature | LightSensor | SensorNumber]:
253
+ return self._get_all("sensor")
254
+
255
+ def get_all_switch(self) -> list[Relay]:
256
+ return self._get_all("switch")
257
+
258
+ def get_all_binary_sensor(self) -> list[Button]:
259
+ return self._get_all("binary_sensor")
260
+
261
+ def get_all_button(self) -> list[Button | ButtonCounter]:
262
+ return self._get_all("button")
263
+
264
+ def get_all_climate(self) -> list[Temperature]:
265
+ return self._get_all("climate")
266
+
267
+ def get_all_cover(self) -> list[Blind]:
268
+ return self._get_all("cover")
269
+
270
+ def get_all_select(self) -> list[SelectedProgram]:
271
+ return self._get_all("select")
272
+
273
+ def get_all_light(self) -> list[Dimmer]:
274
+ return self._get_all("light")
275
+
276
+ def get_all_led(self) -> list[Button]:
277
+ return self._get_all("led")
278
+
279
+ def _get_all(
280
+ self, class_name: str
281
+ ) -> list[
282
+ Blind
283
+ | Button
284
+ | ButtonCounter
285
+ | Sensor
286
+ | ThermostatChannel
287
+ | Dimmer
288
+ | Temperature
289
+ | SensorNumber
290
+ | LightSensor
291
+ | Relay
292
+ | EdgeLit
293
+ | Memo
294
+ | SelectedProgram
295
+ ]:
296
+ """Get all channels."""
262
297
  lst = []
263
298
  for addr, mod in (self.get_modules()).items():
264
299
  if addr in self._submodules:
@@ -269,9 +304,12 @@ class Velbus:
269
304
  return lst
270
305
 
271
306
  async def sync_clock(self) -> None:
272
- """
273
- This will send all the needed messages to sync the clock
274
- """
275
- await self.send(SetRealtimeClock())
276
- await self.send(SetDate())
277
- await self.send(SetDaylightSaving())
307
+ """Will send all the needed messages to sync the clock."""
308
+ lclt = time.localtime()
309
+ await self.send(SetRealtimeClock(wday=lclt[6], hour=lclt[3], min=lclt[4]))
310
+ await self.send(SetDate(day=lclt[2], mon=lclt[1], year=lclt[0]))
311
+ await self.send(SetDaylightSaving(ds=not lclt[8]))
312
+
313
+ async def wait_on_all_messages_sent_async(self) -> None:
314
+ """Wait for all messages to be sent."""
315
+ await self._protocol.wait_on_all_messages_sent_async()