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
velbusaio/protocol.py CHANGED
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import binascii
5
5
  import logging
6
+ import time
6
7
  import typing as t
7
8
  from asyncio import transports
8
9
 
@@ -15,9 +16,7 @@ from velbusaio.raw_message import create as create_message_info
15
16
 
16
17
  def _on_write_backoff(details):
17
18
  logging.debug(
18
- "Transport is not open, waiting {wait} seconds after {tries}",
19
- wait=details.wait,
20
- tries=details.tries,
19
+ f"Transport is not open, waiting {details.wait} seconds after {details.tries}"
21
20
  )
22
21
 
23
22
 
@@ -31,13 +30,11 @@ class VelbusProtocol(asyncio.BufferedProtocol):
31
30
  self,
32
31
  message_received_callback: t.Callable[[RawMessage], t.Awaitable[None]],
33
32
  connection_lost_callback=None,
34
- end_of_scan_callback=None,
35
33
  ) -> None:
36
34
  super().__init__()
37
35
  self._log = logging.getLogger("velbus-protocol")
38
36
  self._message_received_callback = message_received_callback
39
37
  self._connection_lost_callback = connection_lost_callback
40
- self._end_of_scan_callback = end_of_scan_callback
41
38
 
42
39
  # everything for reading from Velbus
43
40
  self._buffer = bytearray(MAXIMUM_MESSAGE_SIZE)
@@ -63,14 +60,14 @@ class VelbusProtocol(asyncio.BufferedProtocol):
63
60
  self._restart_writer = True
64
61
  self.restart_writing()
65
62
 
66
- async def pause_writing(self):
63
+ async def pause_writing(self) -> None:
67
64
  """Pause writing."""
68
65
  self._restart_writer = False
69
66
  if self._writer_task:
70
67
  self._send_queue.put_nowait(None)
71
68
  await asyncio.sleep(0.1)
72
69
 
73
- def restart_writing(self):
70
+ def restart_writing(self) -> None:
74
71
  """Resume writing."""
75
72
  if self._restart_writer and not self._write_transport_lock.locked():
76
73
  self._writer_task = asyncio.ensure_future(
@@ -78,7 +75,7 @@ class VelbusProtocol(asyncio.BufferedProtocol):
78
75
  )
79
76
  self._writer_task.add_done_callback(lambda _future: self.restart_writing())
80
77
 
81
- def close(self):
78
+ def close(self) -> None:
82
79
  self._closing = True
83
80
  self._restart_writer = False
84
81
  if self.transport:
@@ -165,7 +162,7 @@ class VelbusProtocol(asyncio.BufferedProtocol):
165
162
  self._buffer_view = memoryview(self._buffer)
166
163
 
167
164
  async def _process_message(self, msg: RawMessage) -> None:
168
- self._log.debug(f"RX: {msg}")
165
+ # self._log.debug(f"RX: {msg}")
169
166
  await self._message_received_callback(msg)
170
167
 
171
168
  # Everything write-related
@@ -184,22 +181,22 @@ class VelbusProtocol(asyncio.BufferedProtocol):
184
181
  await self._write_transport_lock.acquire()
185
182
  while self._restart_writer:
186
183
  # wait for an item from the queue
187
- msg_info = await self._send_queue.get()
184
+ msg_info: RawMessage | None = await self._send_queue.get()
188
185
  if msg_info is None:
189
186
  self._restart_writer = False
190
187
  return
191
188
  message_sent = False
192
189
  try:
190
+ start_time = time.perf_counter()
193
191
  while not message_sent:
194
192
  message_sent = await self._write_message(msg_info)
195
- if msg_info.command == 0xEF:
196
- # 'channel name request' command provokes in worst case 99 answer packets from VMBGPOD
197
- queue_sleep_time = SLEEP_TIME * 33
198
- else:
199
- queue_sleep_time = SLEEP_TIME
200
- if msg_info.rtr and msg_info.address == 0xFF:
201
- self._end_of_scan_callback()
193
+ send_time = time.perf_counter() - start_time
194
+
195
+ self._send_queue.task_done() # indicate that the item of the queue has been processed
196
+
197
+ queue_sleep_time = self._calculate_queue_sleep_time(msg_info, send_time)
202
198
  await asyncio.sleep(queue_sleep_time)
199
+
203
200
  except (asyncio.CancelledError, GeneratorExit) as exc:
204
201
  if not self._closing:
205
202
  self._log.error(f"Stopping Velbus writer due to {exc!r}")
@@ -211,6 +208,22 @@ class VelbusProtocol(asyncio.BufferedProtocol):
211
208
  self._write_transport_lock.release()
212
209
  self._log.debug("Ending Velbus write message from send queue")
213
210
 
211
+ @staticmethod
212
+ def _calculate_queue_sleep_time(msg_info, send_time):
213
+ sleep_time = SLEEP_TIME
214
+
215
+ if msg_info.rtr:
216
+ sleep_time = SLEEP_TIME # this is a scan command. We could be quicker?
217
+
218
+ if msg_info.command == 0xEF:
219
+ # 'channel name request' command provokes in worst case 99 answer packets from VMBGPOD
220
+ sleep_time = SLEEP_TIME * 33 # TODO make this adaptable on module_type
221
+
222
+ if send_time > sleep_time:
223
+ return 0 # no need to wait, we are already late
224
+ else:
225
+ return sleep_time - send_time
226
+
214
227
  @backoff.on_predicate(
215
228
  backoff.expo,
216
229
  lambda is_sent: not is_sent,
@@ -224,3 +237,7 @@ class VelbusProtocol(asyncio.BufferedProtocol):
224
237
  return True
225
238
  else:
226
239
  return False
240
+
241
+ async def wait_on_all_messages_sent_async(self) -> None:
242
+ self._log.debug("Waiting on all messages sent")
243
+ await self._send_queue.join()
velbusaio/raw_message.py CHANGED
@@ -130,12 +130,12 @@ def _trim_buffer_garbage(rawmessage: bytearray) -> bytearray:
130
130
  if rawmessage and rawmessage[0] != START_BYTE:
131
131
  start_index = rawmessage.find(START_BYTE)
132
132
  if start_index > -1:
133
- logging.debug(
134
- "Trimming leading garbage from buffer content: {buffer} becomes {new_buffer}".format(
135
- buffer=binascii.hexlify(rawmessage),
136
- new_buffer=binascii.hexlify(rawmessage[start_index:]),
137
- )
138
- )
133
+ # logging.debug(
134
+ # "Trimming leading garbage from buffer content: {buffer} becomes {new_buffer}".format(
135
+ # buffer=binascii.hexlify(rawmessage),
136
+ # new_buffer=binascii.hexlify(rawmessage[start_index:]),
137
+ # )
138
+ # )
139
139
  return rawmessage[start_index:]
140
140
  else:
141
141
  logging.debug(
velbusaio/util.py CHANGED
@@ -1,3 +1,7 @@
1
+ """
2
+ Some common utils.
3
+ """
4
+
1
5
  from typing import Union
2
6
 
3
7
  from velbusaio.const import MAXIMUM_MESSAGE_SIZE, MINIMUM_MESSAGE_SIZE
@@ -0,0 +1,249 @@
1
+ import importlib.resources
2
+ import json
3
+ import logging
4
+ import sys
5
+
6
+ from aiofile import async_open
7
+ from bs4 import BeautifulSoup
8
+
9
+ from velbusaio.command_registry import MODULE_DIRECTORY
10
+ from velbusaio.helpers import h2
11
+
12
+
13
+ class VlpFile:
14
+
15
+ def __init__(self, file_path) -> None:
16
+ self._file_path = file_path
17
+ self._modules = []
18
+ self._log = logging.getLogger("velbus-vlpFile")
19
+
20
+ def get(self) -> dict:
21
+ return self._modules
22
+
23
+ async def read(self) -> None:
24
+ async with async_open(self._file_path) as file:
25
+ xml_content = await file.read()
26
+ _soup = BeautifulSoup(xml_content, "xml")
27
+ for module in _soup.find_all("Module"):
28
+ mod = vlpModule(
29
+ module.find("Caption").get_text(),
30
+ module["address"],
31
+ module["build"],
32
+ module["serial"],
33
+ module["type"],
34
+ module.find("Memory").get_text(),
35
+ )
36
+ self._modules.append(mod)
37
+ await mod.parse()
38
+ self._modules.sort(key=lambda mod: mod.get_decimal_addr())
39
+
40
+ def dump(self) -> None:
41
+ for m in self._modules:
42
+ print(f"Module {m.get_decimal_addr()}: {m._name}, type {m._type_id}")
43
+ for key, value in m._channels.items():
44
+ name = value["Name"]
45
+ print(f" {key} => {name}")
46
+
47
+
48
+ class vlpModule:
49
+
50
+ def __init__(self, name, addresses, build, serial, type, memory) -> None:
51
+ self._name = name
52
+ self._addresses = addresses
53
+ self._build = build
54
+ self._serial = serial
55
+ self._type = type
56
+ self._memory = memory
57
+ self._spec = {}
58
+ self._channels = {}
59
+ self._type_id = next(
60
+ (key for key, value in MODULE_DIRECTORY.items() if value == self._type),
61
+ None,
62
+ )
63
+ self._log = logging.getLogger("velbus-vlpFile")
64
+ self._log.info(
65
+ f"=> Created vlpModule address: {self._addresses} type: {self._type} ({self._type_id})"
66
+ )
67
+
68
+ def get(self) -> None:
69
+ print(self._channels)
70
+ print(self)
71
+
72
+ def get_addr(self) -> str:
73
+ return self._addresses
74
+
75
+ def get_name(self) -> str:
76
+ return self._name
77
+
78
+ def get_type(self) -> int | None:
79
+ return self._type_id
80
+
81
+ def get_serial(self) -> str:
82
+ return self._serial
83
+
84
+ def get_memory(self) -> str:
85
+ return self._memory
86
+
87
+ def get_build(self) -> str:
88
+ return self._build
89
+
90
+ def get_channels(self) -> dict:
91
+ return self._channels
92
+
93
+ def __str__(self):
94
+ return f"vlpModule(name={self._name}, addresses={self._addresses}, build={self._build}, serial={self._serial}, type={self._type})"
95
+
96
+ def get_decimal_addr(
97
+ self,
98
+ ) -> int: # lgor: get the numeric value of primary module address
99
+ addr = self._addresses.split(",")[0]
100
+ return int(addr, 16)
101
+
102
+ async def parse(self) -> None:
103
+ await self._load_module_spec()
104
+
105
+ if "Memory" not in self._spec:
106
+ self._log.debug(" => no Memory locations found")
107
+ return None
108
+
109
+ # channel names
110
+ self._channels = self._spec.get("Channels", {})
111
+ for addr, chan in self._channels.items():
112
+ self._log.debug(f" => Processing channel {addr}:")
113
+ if ("Editable" in chan) and (chan["Editable"] == "yes"):
114
+ self._log.debug(f" => channel {addr} is editable, getting name")
115
+ name = self._get_channel_name(int(addr))
116
+ if name:
117
+ self._log.debug(f" => got name '{name}' for channel {addr}")
118
+ self._channels[addr]["Name"] = name
119
+ self._channels[addr]["_is_loaded"] = True
120
+
121
+ # extra
122
+ self._load_extra_data()
123
+
124
+ def _load_extra_data(self) -> None:
125
+ self._log.debug(" => Getting extra data")
126
+ if "Extras" not in self._spec["Memory"]:
127
+ self._log.debug(" => no Extra Memory locations found")
128
+ return None
129
+ for addr, extra in self._spec["Memory"]["Extras"].items():
130
+ byte_data = bytes.fromhex(self._read_from_memory(addr))
131
+ self._log.debug(
132
+ f" => got extra data {byte_data.hex().upper()} from address {addr}"
133
+ )
134
+ if "Translate" in extra:
135
+ translation_found = False
136
+ for translate_key, translate_value in extra["Translate"].items():
137
+ if translate_key.startswith("%"):
138
+ # Binary pattern matching
139
+ if self._match_binary_pattern(translate_key, byte_data):
140
+ self._log.debug(
141
+ f" => Binary pattern {translate_key} matched, value: {translate_value}"
142
+ )
143
+ self._channels[translate_value["Channel"]][
144
+ translate_value["SubName"]
145
+ ] = translate_value["Value"]
146
+ translation_found = True
147
+ else:
148
+ # Direct value matching (existing behavior for integer keys)
149
+ try:
150
+ int_key = int(translate_key)
151
+ if len(byte_data) > 0 and byte_data[0] == int_key:
152
+ self._log.debug(
153
+ f" => Direct match for value {int_key}: {translate_value}"
154
+ )
155
+ translation_found = True
156
+ except ValueError:
157
+ # Not an integer key, skip
158
+ continue
159
+ if not translation_found:
160
+ self._log.error(
161
+ f" => No translation found for data {byte_data.hex().upper()}"
162
+ )
163
+
164
+ def _match_binary_pattern(self, pattern: str, byte_data: bytes) -> bool:
165
+ """
166
+ Match a binary pattern like %......00 against byte data.
167
+ % indicates binary pattern
168
+ . means don't care bit
169
+ 0/1 are specific bits that must match
170
+ """
171
+ if not pattern.startswith("%"):
172
+ return False
173
+
174
+ # Remove the % prefix
175
+ binary_pattern = pattern[1:]
176
+
177
+ # Convert byte_data to binary string (without '0b' prefix)
178
+ if len(byte_data) == 0:
179
+ return False
180
+
181
+ # Take the first byte for pattern matching
182
+ byte_value = byte_data[0]
183
+ binary_data = format(byte_value, "08b")
184
+
185
+ # Check if pattern length matches
186
+ if len(binary_pattern) != len(binary_data):
187
+ return False
188
+
189
+ # Check each bit position
190
+ for i, (pattern_bit, data_bit) in enumerate(zip(binary_pattern, binary_data)):
191
+ if pattern_bit == ".":
192
+ # Don't care bit, skip
193
+ continue
194
+ elif pattern_bit != data_bit:
195
+ # Specific bit must match
196
+ return False
197
+
198
+ return True
199
+
200
+ def _get_channel_name(self, chan: int) -> str | None:
201
+ if "Channels" not in self._spec["Memory"]:
202
+ self._log.debug(" => no Channels Memory locations found")
203
+ return None
204
+ dchan = format(chan, "02d")
205
+ if dchan not in self._spec["Memory"]["Channels"]:
206
+ self._log.debug(f" => no chan {chan} Memory locations found")
207
+ return None
208
+ byte_data = bytes.fromhex(
209
+ self._read_from_memory(self._spec["Memory"]["Channels"][dchan]).replace(
210
+ "FF", ""
211
+ )
212
+ )
213
+ try:
214
+ name = byte_data.decode("ascii")
215
+ except UnicodeDecodeError as e:
216
+ self._log.error(f" => UnicodeDecodeError: {e}")
217
+ name = byte_data
218
+ finally:
219
+ return name
220
+
221
+ async def _load_module_spec(self) -> None:
222
+ self._log.debug(f" => Load module spec for {self._type_id}")
223
+ if sys.version_info >= (3, 13):
224
+ with importlib.resources.path(
225
+ __name__, f"module_spec/{h2(self._type_id)}.json"
226
+ ) as fspath:
227
+ async with async_open(fspath) as protocol_file:
228
+ self._spec = json.loads(await protocol_file.read())
229
+ else:
230
+ async with async_open(
231
+ str(
232
+ importlib.resources.files(__name__.split(".")[0]).joinpath(
233
+ f"module_spec/{h2(self._type_id)}.json"
234
+ )
235
+ )
236
+ ) as protocol_file:
237
+ self._spec = json.loads(await protocol_file.read())
238
+
239
+ def _read_from_memory(self, address_range) -> str | None:
240
+ # its a single address
241
+ if "-" not in address_range:
242
+ start = int(address_range, 16) * 2
243
+ end = (int(address_range, 16) + 1) * 2
244
+ return self._memory[start:end]
245
+ # its a range
246
+ start_str, end_str = address_range.split("-")
247
+ start = int(start_str, 16) * 2
248
+ end = (int(end_str, 16) + 1) * 2
249
+ return self._memory[start:end]
@@ -1,103 +0,0 @@
1
- velbusaio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- velbusaio/channels.py,sha256=ezviD9AepEneuBezcWpk0tWoPZxbTit8wuMcBO44MsM,22848
3
- velbusaio/command_registry.py,sha256=UJOknKYGDPb0pPuvsH42GetEVC-Sa3bMkiSExE6DfPc,4787
4
- velbusaio/const.py,sha256=9JY8oMrE4galRPAv247RXP_xDOdYkyPc7lBlptJius4,1550
5
- velbusaio/controller.py,sha256=h8LProppbTYJNdyyS5rQ3ZweiTjcKsdbKBzNoOS_IAk,9253
6
- velbusaio/discovery.py,sha256=Px6qoZl4QhF17aMz6JxstCORBpLzZGWEK9h4Vyvg57o,1649
7
- velbusaio/exceptions.py,sha256=FHkXaM3dK5Gkk-QGAf9dLE3FPlCU2FRZWUyY-4KRNnA,515
8
- velbusaio/handler.py,sha256=gI0Zma9SbcVEvAzYno4IJbqwtEYQk5Cp6zxVNtmox54,8342
9
- velbusaio/helpers.py,sha256=8PWyD13UA244ESk5YwPcRzvjK3OBAqmhwAwC6E9W4B4,2542
10
- velbusaio/message.py,sha256=ZFy0iup_DK_so_wZsr2F-3A4AJgQGxf5C3TXjK4BMW0,5047
11
- velbusaio/module.py,sha256=GmuUthbqlluI8n8HHgTjMsPbzSWWDUa5Y2XPSbJArms,35355
12
- velbusaio/protocol.py,sha256=sut7jf3IJiJRx59w1EJs0AX4Aa15vrPNgIWJrtQkMjc,8220
13
- velbusaio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- velbusaio/raw_message.py,sha256=5604X_0Itav6Ts89tXWvXs6sdx_KWRvPvSHjtFjXlMA,4613
15
- velbusaio/util.py,sha256=eCsGQJ9nuzk_B2skBlv3MsNaMaHDXo1a1qSPjObzXH4,1540
16
- velbusaio/messages/__init__.py,sha256=7L1ziMh5u5HE62MjzcDRhKVof294puZzKFCbBrqxeqw,5892
17
- velbusaio/messages/blind_status.py,sha256=3K5JRRn34_N1oQRKNBCkNwWIwhaURYL1ZNZhQZWFnSU,3417
18
- velbusaio/messages/bus_active.py,sha256=7aSGEASI4WxWZV9HI6P1b_p6wU1UvbJYmT90Hz_I8ls,748
19
- velbusaio/messages/bus_error_counter_status.py,sha256=GYMAxyIHUZLzse-pzyUgegLMY75ptFLKNP5qADbuNFg,1177
20
- velbusaio/messages/bus_error_counter_status_request.py,sha256=9Y-yW4Z8FPPbn-ky5JTxGkt6NQg7ISDQVHPaVnm7P4U,702
21
- velbusaio/messages/bus_off.py,sha256=SUi9lOp8BNwl6aWacjp1TC0xcleTKSQt1sM8yWUoeGg,678
22
- velbusaio/messages/channel_name_part1.py,sha256=TiziwJb7idwi7YeAxDVGMVrG1VPRrBnCswMZZXA0V4w,2577
23
- velbusaio/messages/channel_name_part2.py,sha256=YUQFBvSRkft79FSRNGd3-gV2hpiDx11TgyNH65IcG1w,2577
24
- velbusaio/messages/channel_name_part3.py,sha256=46LiGIwm0HKTwd7CMfKFUv5xjebAK85uQwopAlBEYjY,2578
25
- velbusaio/messages/channel_name_request.py,sha256=11HK4IDjJOg_K57ssEZ5ZAV-P0y5ysjJVUX_3di3uTk,2363
26
- velbusaio/messages/clear_led.py,sha256=O5InexnnPpzJcmKBfVVILTtIZ7ojljJ2xn-hRXRP8cM,911
27
- velbusaio/messages/counter_status.py,sha256=FW1Vaf3U-Mp5Zg--HcBs24Oh739sZ00EocFaeF5Maf4,1239
28
- velbusaio/messages/counter_status_request.py,sha256=0SeJemPRheX5k1DjZlnvaY0dWhHBVqh4d60kMe8RVxc,918
29
- velbusaio/messages/cover_down.py,sha256=r1PzvwyhmMF5BiKlxQELBwsdqVqSW_9lPYO0B_4O0WU,2551
30
- velbusaio/messages/cover_off.py,sha256=a8a1iRQwcEycQQvyjMjRHTeWoSHejjBAJiiQtkzf5SQ,2257
31
- velbusaio/messages/cover_position.py,sha256=UsBaENP1x61iTQTx8oPTH6GS1iwI8g0xah-b5hnxbwg,1282
32
- velbusaio/messages/cover_up.py,sha256=-P6Ze1OUDSs-YTqFPV0Tl3_EpCL97OJ2CeFB1ZDsLgk,2546
33
- velbusaio/messages/dali_device_settings.py,sha256=JeiUGw5oPPQde7buNz0T7euZ-QJVxmtTZF2DYVJxFsM,4855
34
- velbusaio/messages/dali_device_settings_request.py,sha256=3IqaXOxqidJCJ1FxFMDofaXiX75SCgFcPyOQk0p6L_Q,1461
35
- velbusaio/messages/dali_dim_value_status.py,sha256=V2Fs8CZNihIi9ZxQs7Bxkt9qCInFXxMZnF6vKhIUCnQ,1044
36
- velbusaio/messages/dimmer_channel_status.py,sha256=nJ_83uVe0FIiwWmGt7XNXcRYu3ikNUwxOOg5nHzaOPw,2470
37
- velbusaio/messages/dimmer_status.py,sha256=QwWbVoCLndhymsOcj-jw0scqUuQRU3m6J6q1FwgNgn4,2937
38
- velbusaio/messages/edge_set_color.py,sha256=enwPshhNrleAQQwc3RobPt5foZ1ynE_74SlBEuzd86w,3305
39
- velbusaio/messages/edge_set_custom_color.py,sha256=fw4hQTrTxNBb63XLTY9iLFtPYrNh0mAAE28DCxG9h1A,1337
40
- velbusaio/messages/fast_blinking_led.py,sha256=Q5scu34RQJZ43EDZCR1kJy63gULSWHN5bpKpqo_XOOc,908
41
- velbusaio/messages/forced_off.py,sha256=QQiwMbVUW6wegyTLrf1daJiS8KggPXZvepGsBU9_FUo,590
42
- velbusaio/messages/forced_on.py,sha256=tS7dUayO_0Grco5TfiOMWBUOc0CY44w_7FcpU-ZViJU,589
43
- velbusaio/messages/interface_status_request.py,sha256=2vAbB2Qha3pp1ZLepM8TWJyPkX_6I_lmGBIKxJxfajU,694
44
- velbusaio/messages/ir_receiver_status.py,sha256=_STATc_X7R4o-uyG5iQyyasyB_Np6ubkNi__5w_IeT0,374
45
- velbusaio/messages/kwh_status.py,sha256=K4twkBx-XhdmqAhnAZ6xuHX4kiNQuhDQl5JHDnxdlnQ,1413
46
- velbusaio/messages/light_value_request.py,sha256=sF7YYlbTESEBp05bygcLVpjFpejM9m-N8sbL_hDUPO0,598
47
- velbusaio/messages/memo_text.py,sha256=yg7Qc4mHWoclMN9Rk6L_lCmh_NjPTdwSvkAYalUY0Vw,1030
48
- velbusaio/messages/memory_data.py,sha256=ZTf8R8r8LpaBg2KOK8CnQJ8RU6gH2DEWfr7phqY5pjE,1030
49
- velbusaio/messages/memory_data_block.py,sha256=VdZ3s1f3xRgaMRqbceLgByEjoRPSaDU82BLdGQXUvc8,1044
50
- velbusaio/messages/memory_dump_request.py,sha256=g8jWuy3IwqG6UvTz2_eVng1e0TAPy8XF3Mt6u0l5GVU,698
51
- velbusaio/messages/module_status.py,sha256=PLsmTheV-fTGHc5FhrzNXMxvGKP8EeYFjadY07ENOn0,6399
52
- velbusaio/messages/module_status_request.py,sha256=Iv9Y2UXzPFyz3FiL0yH-o231S5_dOfv9z72AYgEjLjM,960
53
- velbusaio/messages/module_subtype.py,sha256=aDoXFR6fnVFitPLnB9RNLnR4S_t4MZP1uoMMzg4JfB0,1463
54
- velbusaio/messages/module_type.py,sha256=2VhKxtPF_8VKPsyWGUvBcwTlxVIxgNnRi5HVYdAAwgU,4115
55
- velbusaio/messages/module_type_request.py,sha256=v8LBuPG_nxvh2lid9XG254M522PN-zSVezEMCOLrGis,753
56
- velbusaio/messages/push_button_status.py,sha256=AkhZJ5xhMjWDKidOffLZewIi8qWDTu7q8qt4joNenIw,1553
57
- velbusaio/messages/raw.py,sha256=m7xaGhDqmvHxy0UzWC4gijAbkqXkNEdfrsKRDKWX1JM,1963
58
- velbusaio/messages/read_data_block_from_memory.py,sha256=Z3hacc_wHtgkkeA3ZsfJv-1xTIt73M5AKbvggZdZtew,974
59
- velbusaio/messages/read_data_from_memory.py,sha256=I__kKm8xcoQz6e5DKr8G3w3NnpXfuv0a3Fn_EIg0Tw4,977
60
- velbusaio/messages/realtime_clock_status_request.py,sha256=KEvcmWlgN4XbpHSA90fg3RFVU2bsNGq6sfugw9IbdTg,607
61
- velbusaio/messages/receive_buffer_full.py,sha256=dgyzmZ8VxhQk8r4vUs2uWuojut2Akj4IMazkqfDa1e4,857
62
- velbusaio/messages/receive_ready.py,sha256=f1LdUVcUTG7rj71wCVHgF03zbkIx4CpJKG7zgXIERf4,686
63
- velbusaio/messages/relay_status.py,sha256=BBJHM54gzbOTAoQ_N8ls3IiJoOIklKFCvBU4odntXvM,2836
64
- velbusaio/messages/restore_dimmer.py,sha256=Xk3GqthYwqCMc6rPXCCc5EXWoXZWpWrk1r8nevfdfso,1783
65
- velbusaio/messages/select_program.py,sha256=RHvOvWXC7w8wxMhco0NUDPIMK6r6Q2LCd87Kdnl7hxI,834
66
- velbusaio/messages/sensor_settings_request.py,sha256=njSYjNTxhNyRKJLP7wexaWHnYiSWbrSlOm_F7myuBdw,818
67
- velbusaio/messages/sensor_temp_request.py,sha256=R-VNtqqiDbgZjPNfj5nE0_bTuGLD74Zy-FUsrCcyXFI,598
68
- velbusaio/messages/sensor_temperature.py,sha256=Erh7pIrbxpmurg3x9_di6A6Os_2Sgpid7T6LgHuWQyA,1557
69
- velbusaio/messages/set_date.py,sha256=aV8qO3kJsIMsLZPwVZB5PKivBVa7YHpottj6sTgUSiM,1459
70
- velbusaio/messages/set_daylight_saving.py,sha256=bpP4BN-uyFIRt4Y6finaaYBIzI08A5AVsUFJfnpxzkg,1117
71
- velbusaio/messages/set_dimmer.py,sha256=gS3Bm6BvKj9ZnHDOGwTzrEGukXnoPVkAQCRwLOG-E9Q,1936
72
- velbusaio/messages/set_led.py,sha256=hNmbmS2hXqTBVfXMWYhyf10-BOvd5ZwimMd74Cyj5PQ,899
73
- velbusaio/messages/set_realtime_clock.py,sha256=2EVdIKLB0M1syBZyIpD-vxiEJMriqElxnw3cO773Nw4,1308
74
- velbusaio/messages/set_temperature.py,sha256=PMs_uXWMhggIX0s2ikediwTJ4WSpvrXzahCJ0Ak_hks,958
75
- velbusaio/messages/slider_status.py,sha256=poNQY8hvdTVqD0qfLxvmp-w3js2MVozTLR657cZOI8o,1393
76
- velbusaio/messages/slow_blinking_led.py,sha256=a5Kpbx_IAYzOoahkgaqcc61j3ecFjy6fT9Dox6BlUiM,908
77
- velbusaio/messages/start_relay_blinking_timer.py,sha256=KdTqYxXquPn07_Con_vSfbi7KthdsUv4SwkxJj2MEGk,1297
78
- velbusaio/messages/start_relay_timer.py,sha256=fhaIJrVOP2MrIpzzlswyENC4XwhS8YOgr4p6rShSYyA,1289
79
- velbusaio/messages/switch_relay_off.py,sha256=nTE-l9_lpvxneq1txoTrX_C-gJ2aDQ-0czqd8JLwNts,1098
80
- velbusaio/messages/switch_relay_on.py,sha256=Vj4tjTCrK7CBRJBFnF81fAp0qFWGqX_JAeeioV3x9aE,1097
81
- velbusaio/messages/switch_to_comfort.py,sha256=9NuqVHwrWLsFUmx2ayra_0oubc5ovKTAawoG44w61h8,790
82
- velbusaio/messages/switch_to_day.py,sha256=ZqUdZkZDtZsUsZNLj08G3PlJ8jd6H0uos8vdnivPwwg,786
83
- velbusaio/messages/switch_to_night.py,sha256=FhoKB-a7TWQLm3MsLJ-5ttUNVgT9CIvS-HogrF1MZTg,788
84
- velbusaio/messages/switch_to_safe.py,sha256=DD8z7QwFU0Z_f2OT9ANjPfu7jLNKktqaID6nUce3K-U,787
85
- velbusaio/messages/temp_sensor_settings_part1.py,sha256=QDAQDmdpJNWNWRqigtkDzAYmy4lxo7ZtX4CuThwUOb0,604
86
- velbusaio/messages/temp_sensor_settings_part2.py,sha256=kQBlvVBroETdZjxz-oB6nAJlImbwGBNPhJVArDf26-M,583
87
- velbusaio/messages/temp_sensor_settings_part3.py,sha256=_HugqJ5wVj84TCBC_4Y3NcdUNVE4SevIW9uskiAKKWs,583
88
- velbusaio/messages/temp_sensor_settings_part4.py,sha256=GxxW8VttzeYlSgLAdkCFkMvIm5Q_Mzgy10WLgSXKhxA,583
89
- velbusaio/messages/temp_sensor_settings_request.py,sha256=YSKKGjqAbeBbtmstPg5tCaEbXIRI5i_2NustkXIm7wY,606
90
- velbusaio/messages/temp_sensor_status.py,sha256=rMHQ3-I9jMWn1fE2xjmaoSRoXfBA4JxccR5XQ92J71w,3827
91
- velbusaio/messages/temp_set_cooling.py,sha256=9HyGzdcr5R-EoXvem8Ajgx8_12WB7yUYUcvVxZXqT-8,723
92
- velbusaio/messages/temp_set_heating.py,sha256=F0B1RsQNdq7F25z3Sure6Hobph-HJVglOhK7K-lHQFE,723
93
- velbusaio/messages/update_led_status.py,sha256=1b4gWqmmtAYUAeaoJdkda_zlW02lLwsIPoXMRGD8OCg,1300
94
- velbusaio/messages/very_fast_blinking_led.py,sha256=3gB3TYXFFUdna_VaGrfSWGpOpX2iOu_8i5L9qQ2YG1s,912
95
- velbusaio/messages/write_data_to_memory.py,sha256=2bC-vuv_RK3hGxvKeba1zR405ffj-CkYh3hJ9LVEaME,1038
96
- velbusaio/messages/write_memory_block.py,sha256=5pP4hCRV7tc5xYxHO_51t-uCYgNtVHiaPic_ZGG2Y9s,1031
97
- velbusaio/messages/write_module_address_and_serial_number.py,sha256=gU-yAHxICP6LPQX7BRj-WUfiVpTk0sV1CMlXeElAwl4,1596
98
- velbusaio/moduleprotocol/protocol.json,sha256=zChwegR8Ic_Msw5iVMV_W3kVmSyK5DwYof46wD7l9H8,883766
99
- velbus_aio-2023.12.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
100
- velbus_aio-2023.12.0.dist-info/METADATA,sha256=nl4xVLuNrlqrwm2OcgO0V1tkXSFJZ31P2QmYgeIvb0Q,3079
101
- velbus_aio-2023.12.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
102
- velbus_aio-2023.12.0.dist-info/top_level.txt,sha256=W0-lSOwD23mm8FqaIe9vY20fKicBMIdUVjF-zmfxRnY,15
103
- velbus_aio-2023.12.0.dist-info/RECORD,,