velbus-aio 2021.8.7__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 (200) hide show
  1. scripts/parse_specs.py +156 -0
  2. velbus_aio-2025.11.0.dist-info/METADATA +71 -0
  3. velbus_aio-2025.11.0.dist-info/RECORD +194 -0
  4. {velbus_aio-2021.8.7.dist-info → velbus_aio-2025.11.0.dist-info}/WHEEL +1 -1
  5. velbus_aio-2025.11.0.dist-info/top_level.txt +3 -0
  6. velbusaio/channels.py +443 -109
  7. velbusaio/command_registry.py +126 -13
  8. velbusaio/const.py +36 -12
  9. velbusaio/controller.py +252 -177
  10. velbusaio/discovery.py +2 -2
  11. velbusaio/exceptions.py +22 -0
  12. velbusaio/handler.py +311 -145
  13. velbusaio/helpers.py +6 -18
  14. velbusaio/message.py +46 -132
  15. velbusaio/messages/__init__.py +12 -2
  16. velbusaio/messages/blind_status.py +16 -25
  17. velbusaio/messages/bus_active.py +3 -9
  18. velbusaio/messages/bus_error_counter_status.py +3 -4
  19. velbusaio/messages/bus_error_counter_status_request.py +3 -4
  20. velbusaio/messages/bus_off.py +3 -4
  21. velbusaio/messages/channel_name_part1.py +49 -33
  22. velbusaio/messages/channel_name_part2.py +49 -33
  23. velbusaio/messages/channel_name_part3.py +49 -33
  24. velbusaio/messages/channel_name_request.py +26 -12
  25. velbusaio/messages/clear_led.py +3 -4
  26. velbusaio/messages/counter_status.py +3 -17
  27. velbusaio/messages/counter_status_request.py +6 -6
  28. velbusaio/messages/counter_value.py +44 -0
  29. velbusaio/messages/cover_down.py +4 -29
  30. velbusaio/messages/cover_off.py +5 -29
  31. velbusaio/messages/cover_position.py +4 -19
  32. velbusaio/messages/cover_up.py +4 -27
  33. velbusaio/messages/dali_device_settings.py +178 -0
  34. velbusaio/messages/dali_device_settings_request.py +53 -0
  35. velbusaio/messages/dali_dim_value_status.py +44 -0
  36. velbusaio/messages/dimmer_channel_status.py +6 -19
  37. velbusaio/messages/dimmer_status.py +14 -31
  38. velbusaio/messages/edge_set_color.py +114 -0
  39. velbusaio/messages/edge_set_custom_color.py +56 -0
  40. velbusaio/messages/fast_blinking_led.py +3 -4
  41. velbusaio/messages/forced_off.py +3 -4
  42. velbusaio/messages/forced_on.py +3 -4
  43. velbusaio/messages/interface_status_request.py +3 -4
  44. velbusaio/messages/ir_receiver_status.py +18 -0
  45. velbusaio/messages/kwh_status.py +3 -19
  46. velbusaio/messages/light_value_request.py +3 -4
  47. velbusaio/messages/memo_text.py +3 -5
  48. velbusaio/messages/memory_data.py +3 -16
  49. velbusaio/messages/memory_data_block.py +3 -4
  50. velbusaio/messages/memory_dump_request.py +3 -4
  51. velbusaio/messages/module_status.py +107 -55
  52. velbusaio/messages/module_status_request.py +7 -6
  53. velbusaio/messages/module_subtype.py +11 -19
  54. velbusaio/messages/module_type.py +132 -21
  55. velbusaio/messages/module_type_request.py +1 -0
  56. velbusaio/messages/psu_load.py +56 -0
  57. velbusaio/messages/psu_values.py +53 -0
  58. velbusaio/messages/push_button_status.py +3 -16
  59. velbusaio/messages/raw.py +74 -0
  60. velbusaio/messages/read_data_block_from_memory.py +3 -4
  61. velbusaio/messages/read_data_from_memory.py +3 -4
  62. velbusaio/messages/realtime_clock_status_request.py +3 -4
  63. velbusaio/messages/receive_buffer_full.py +3 -4
  64. velbusaio/messages/receive_ready.py +3 -4
  65. velbusaio/messages/relay_status.py +13 -42
  66. velbusaio/messages/restore_dimmer.py +33 -24
  67. velbusaio/messages/select_program.py +35 -0
  68. velbusaio/messages/sensor_settings_request.py +3 -4
  69. velbusaio/messages/sensor_temp_request.py +3 -4
  70. velbusaio/messages/sensor_temperature.py +15 -19
  71. velbusaio/messages/set_date.py +10 -30
  72. velbusaio/messages/set_daylight_saving.py +8 -24
  73. velbusaio/messages/set_dimmer.py +43 -41
  74. velbusaio/messages/set_led.py +3 -4
  75. velbusaio/messages/set_realtime_clock.py +10 -30
  76. velbusaio/messages/set_temperature.py +3 -4
  77. velbusaio/messages/slider_status.py +16 -20
  78. velbusaio/messages/slow_blinking_led.py +3 -4
  79. velbusaio/messages/start_relay_blinking_timer.py +3 -4
  80. velbusaio/messages/start_relay_timer.py +3 -4
  81. velbusaio/messages/switch_relay_off.py +3 -16
  82. velbusaio/messages/switch_relay_on.py +3 -16
  83. velbusaio/messages/switch_to_comfort.py +4 -15
  84. velbusaio/messages/switch_to_day.py +4 -15
  85. velbusaio/messages/switch_to_night.py +4 -15
  86. velbusaio/messages/switch_to_safe.py +4 -15
  87. velbusaio/messages/temp_sensor_settings_part1.py +3 -4
  88. velbusaio/messages/temp_sensor_settings_part2.py +27 -0
  89. velbusaio/messages/temp_sensor_settings_part3.py +27 -0
  90. velbusaio/messages/temp_sensor_settings_part4.py +27 -0
  91. velbusaio/messages/temp_sensor_settings_request.py +3 -4
  92. velbusaio/messages/temp_sensor_status.py +34 -35
  93. velbusaio/messages/temp_set_cooling.py +3 -13
  94. velbusaio/messages/temp_set_heating.py +3 -13
  95. velbusaio/messages/update_led_status.py +3 -4
  96. velbusaio/messages/very_fast_blinking_led.py +3 -4
  97. velbusaio/messages/write_data_to_memory.py +3 -4
  98. velbusaio/messages/write_memory_block.py +3 -4
  99. velbusaio/messages/write_module_address_and_serial_number.py +3 -4
  100. velbusaio/module.py +680 -158
  101. velbusaio/module_spec/01.json +62 -0
  102. velbusaio/module_spec/02.json +16 -0
  103. velbusaio/module_spec/03.json +23 -0
  104. velbusaio/module_spec/04.json +283 -0
  105. velbusaio/module_spec/05.json +54 -0
  106. velbusaio/module_spec/06.json +110 -0
  107. velbusaio/module_spec/07.json +16 -0
  108. velbusaio/module_spec/08.json +38 -0
  109. velbusaio/module_spec/09.json +30 -0
  110. velbusaio/module_spec/0A.json +58 -0
  111. velbusaio/module_spec/0B.json +58 -0
  112. velbusaio/module_spec/0C.json +18 -0
  113. velbusaio/module_spec/0E.json +25 -0
  114. velbusaio/module_spec/0F.json +16 -0
  115. velbusaio/module_spec/10.json +111 -0
  116. velbusaio/module_spec/11.json +111 -0
  117. velbusaio/module_spec/12.json +73 -0
  118. velbusaio/module_spec/13.json +4 -0
  119. velbusaio/module_spec/14.json +16 -0
  120. velbusaio/module_spec/15.json +83 -0
  121. velbusaio/module_spec/16.json +129 -0
  122. velbusaio/module_spec/17.json +129 -0
  123. velbusaio/module_spec/18.json +129 -0
  124. velbusaio/module_spec/1A.json +79 -0
  125. velbusaio/module_spec/1B.json +107 -0
  126. velbusaio/module_spec/1D.json +89 -0
  127. velbusaio/module_spec/1E.json +306 -0
  128. velbusaio/module_spec/1F.json +178 -0
  129. velbusaio/module_spec/20.json +178 -0
  130. velbusaio/module_spec/21.json +326 -0
  131. velbusaio/module_spec/22.json +426 -0
  132. velbusaio/module_spec/23.json +129 -0
  133. velbusaio/module_spec/24.json +30 -0
  134. velbusaio/module_spec/25.json +3 -0
  135. velbusaio/module_spec/28.json +454 -0
  136. velbusaio/module_spec/29.json +235 -0
  137. velbusaio/module_spec/2A.json +239 -0
  138. velbusaio/module_spec/2B.json +239 -0
  139. velbusaio/module_spec/2C.json +257 -0
  140. velbusaio/module_spec/2D.json +270 -0
  141. velbusaio/module_spec/2E.json +215 -0
  142. velbusaio/module_spec/2F.json +211 -0
  143. velbusaio/module_spec/30.json +58 -0
  144. velbusaio/module_spec/31.json +465 -0
  145. velbusaio/module_spec/32.json +385 -0
  146. velbusaio/module_spec/33.json +249 -0
  147. velbusaio/module_spec/34.json +313 -0
  148. velbusaio/module_spec/35.json +313 -0
  149. velbusaio/module_spec/36.json +313 -0
  150. velbusaio/module_spec/37.json +333 -0
  151. velbusaio/module_spec/38.json +111 -0
  152. velbusaio/module_spec/39.json +4 -0
  153. velbusaio/module_spec/3A.json +306 -0
  154. velbusaio/module_spec/3B.json +306 -0
  155. velbusaio/module_spec/3C.json +306 -0
  156. velbusaio/module_spec/3D.json +454 -0
  157. velbusaio/module_spec/3E.json +302 -0
  158. velbusaio/module_spec/3F.json +4 -0
  159. velbusaio/module_spec/40.json +4 -0
  160. velbusaio/module_spec/41.json +241 -0
  161. velbusaio/module_spec/42.json +4 -0
  162. velbusaio/module_spec/43.json +23 -0
  163. velbusaio/module_spec/44.json +38 -0
  164. velbusaio/module_spec/45.json +4 -0
  165. velbusaio/module_spec/48.json +111 -0
  166. velbusaio/module_spec/49.json +111 -0
  167. velbusaio/module_spec/4A.json +89 -0
  168. velbusaio/module_spec/4B.json +138 -0
  169. velbusaio/module_spec/4C.json +129 -0
  170. velbusaio/module_spec/4D.json +108 -0
  171. velbusaio/module_spec/4E.json +787 -0
  172. velbusaio/module_spec/4F.json +114 -0
  173. velbusaio/module_spec/50.json +114 -0
  174. velbusaio/module_spec/51.json +114 -0
  175. velbusaio/module_spec/52.json +456 -0
  176. velbusaio/module_spec/54.json +270 -0
  177. velbusaio/module_spec/55.json +270 -0
  178. velbusaio/module_spec/56.json +270 -0
  179. velbusaio/module_spec/57.json +260 -0
  180. velbusaio/module_spec/5A.json +4 -0
  181. velbusaio/module_spec/5B.json +4 -0
  182. velbusaio/module_spec/5C.json +90 -0
  183. velbusaio/module_spec/5F.json +78 -0
  184. velbusaio/module_spec/60.json +4 -0
  185. velbusaio/module_spec/61.json +89 -0
  186. velbusaio/module_spec/broadcast.json +67 -0
  187. velbusaio/module_spec/ignore.json +22 -0
  188. velbusaio/protocol.py +243 -0
  189. velbusaio/py.typed +0 -0
  190. velbusaio/raw_message.py +149 -0
  191. velbusaio/util.py +55 -0
  192. velbusaio/vlp_reader.py +249 -0
  193. velbus_aio-2021.8.7.dist-info/METADATA +0 -66
  194. velbus_aio-2021.8.7.dist-info/RECORD +0 -90
  195. velbus_aio-2021.8.7.dist-info/top_level.txt +0 -1
  196. velbusaio/messages/meteo_raw.py +0 -52
  197. velbusaio/module_registry.py +0 -64
  198. velbusaio/moduleprotocol/protocol.json +0 -25540
  199. velbusaio/parser.py +0 -142
  200. {velbus_aio-2021.8.7.dist-info → velbus_aio-2025.11.0.dist-info/licenses}/LICENSE +0 -0
velbusaio/controller.py CHANGED
@@ -1,227 +1,299 @@
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
- import pickle
7
+ import pathlib
8
+ import re
9
9
  import ssl
10
+ import time
11
+ from urllib.parse import urlparse
10
12
 
11
13
  import serial
12
- import serial_asyncio
14
+ import serial_asyncio_fast
13
15
 
14
- 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
+ )
31
+ from velbusaio.exceptions import VelbusConnectionFailed
15
32
  from velbusaio.handler import PacketHandler
16
33
  from velbusaio.helpers import get_cache_dir
34
+ from velbusaio.message import Message
17
35
  from velbusaio.messages.module_type_request import ModuleTypeRequestMessage
18
36
  from velbusaio.messages.set_date import SetDate
19
37
  from velbusaio.messages.set_daylight_saving import SetDaylightSaving
20
38
  from velbusaio.messages.set_realtime_clock import SetRealtimeClock
21
39
  from velbusaio.module import Module
22
- from velbusaio.parser import VelbusParser
40
+ from velbusaio.protocol import VelbusProtocol
41
+ from velbusaio.raw_message import RawMessage
42
+ from velbusaio.vlp_reader import VlpFile
23
43
 
24
44
 
25
45
  class Velbus:
26
- """
27
- A velbus controller
28
- """
46
+ """A velbus controller."""
29
47
 
30
- def __init__(self, dsn) -> None:
48
+ def __init__(
49
+ self,
50
+ dsn: str,
51
+ cache_dir: str = get_cache_dir(),
52
+ vlp_file: str | None = None,
53
+ one_address: int | None = None,
54
+ ) -> None:
55
+ """Init the Velbus controller."""
31
56
  self._log = logging.getLogger("velbus")
32
- self._log.setLevel(logging.DEBUG)
33
- self._dsn = dsn
34
- self._parser = VelbusParser()
35
- self._handler = PacketHandler(self.send, self)
36
- self._writer = None
37
- self._reader = None
38
- self._modules = {}
39
- self._submodules = []
40
- self._send_queue = asyncio.Queue()
41
- self._tasks = []
57
+
58
+ self._protocol = VelbusProtocol(
59
+ message_received_callback=self._on_message_received,
60
+ connection_lost_callback=self._on_connection_lost,
61
+ )
62
+ self._closing = False
63
+ self._auto_reconnect = True
64
+
65
+ self._destination = dsn
66
+ self._handler = PacketHandler(self, one_address)
67
+ self._modules: dict[int, Module] = {}
68
+ self._submodules: list[int] = []
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
75
+
76
+ async def _on_message_received(self, msg: RawMessage) -> None:
77
+ """On message received function."""
78
+ await self._handler.handle(msg)
79
+
80
+ def _on_connection_lost(self, exc: Exception) -> None:
81
+ """Respond to Protocol connection lost."""
82
+ if self._auto_reconnect and not self._closing:
83
+ self._log.debug("Reconnecting to transport")
84
+ asyncio.ensure_future(self.connect())
42
85
 
43
86
  async def add_module(
44
87
  self,
45
- addr: str,
46
- typ: str,
47
- data: dict,
48
- serial=None,
49
- memorymap=None,
50
- build_year=None,
51
- build_week=None,
88
+ addr: int,
89
+ typ: int,
90
+ serial: int | None = None,
91
+ memorymap: int | None = None,
92
+ build_year: int | None = None,
93
+ build_week: int | None = None,
52
94
  ) -> None:
53
- """
54
- Add a founc module to the module cache
55
- """
56
- mod = self._load_module_from_cache(addr)
57
- if mod:
58
- self._log.info(f"Load module from CACHE: {addr}")
59
- self._modules[addr] = mod
60
- else:
61
- self._log.info(f"Load NEW module: {typ} @ {addr}")
62
- self._modules[addr] = Module(
63
- addr,
64
- typ,
65
- data,
66
- serial=serial,
67
- build_year=build_year,
68
- build_week=build_week,
69
- memorymap=memorymap,
70
- )
71
- self._modules[addr].initialize(self.send)
72
- 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}")
73
108
 
74
- async def add_submodules(self, addr, subList) -> None:
109
+ def add_submodules(self, module: Module, subList: dict[int, int]) -> None:
110
+ """Add submodules address to module."""
75
111
  for sub_num, sub_addr in subList.items():
76
112
  if sub_addr == 0xFF:
77
113
  continue
78
114
  self._submodules.append(sub_addr)
79
- self._modules[addr]._sub_address[sub_num] = sub_addr
80
- self._modules[sub_addr] = self._modules[addr]
81
- self._modules[addr].cleanupSubChannels()
115
+ module._sub_address[sub_num] = sub_addr
116
+ self._modules[sub_addr] = module
117
+ module.cleanupSubChannels()
82
118
 
83
- def _load_module_from_cache(self, address) -> None | str:
84
- try:
85
- with open(f"{get_cache_dir()}/{address}.p", "rb") as fl:
86
- return pickle.load(fl)
87
- except OSError:
88
- pass
119
+ def addr_is_submodule(self, addr: int) -> bool:
120
+ """Check if an address is a submodule."""
121
+ return addr in self._submodules
89
122
 
90
123
  def get_modules(self) -> dict:
91
- """
92
- Return the module cache
93
- """
124
+ """Return the module cache."""
94
125
  return self._modules
95
126
 
96
- def get_module(self, addr: str) -> None | Module:
97
- """
98
- Get a module on an address
99
- """
100
- 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:
101
130
  return self._modules[addr]
102
131
  return None
103
132
 
104
- def get_channels(self, addr: str) -> None | dict:
105
- """
106
- Get the channels for an address
107
- """
133
+ def get_channels(self, addr: int) -> None | dict:
134
+ """Get the channels for an address."""
108
135
  if addr in self._modules:
109
136
  return (self._modules[addr]).get_channels()
110
137
  return None
111
138
 
112
139
  async def stop(self) -> None:
113
- for task in self._tasks:
114
- task.cancel()
115
- self._writer.close()
116
- await self._writer.wait_closed()
117
-
118
- async def connect(self, test_connect: bool = False) -> None:
119
- """
120
- Connect to the bus and load all the data
121
- """
140
+ """Stop the controller."""
141
+ self._closing = True
142
+ self._auto_reconnect = False
143
+ self._protocol.close()
144
+
145
+ async def connect(self) -> None:
146
+ """Connect to the bus and load all the data."""
147
+ await self._handler.read_protocol_data()
122
148
  # connect to the bus
123
- if ":" in self._dsn:
149
+ if ":" in self._destination:
124
150
  # tcp/ip combination
125
- if self._dsn.startswith("tls://"):
126
- tmp = self._dsn.replace("tls://", "").split(":")
151
+ if not re.search(r"^[A-Za-z0-9+.\-]+://", self._destination):
152
+ # if no scheme, then add the tcp://
153
+ self._destination = f"tcp://{self._destination}"
154
+ parts = urlparse(self._destination)
155
+ if parts.scheme == "tls":
127
156
  ctx = ssl._create_unverified_context()
128
157
  else:
129
- tmp = self._dsn.split(":")
130
158
  ctx = None
131
- self._reader, self._writer = await asyncio.open_connection(
132
- tmp[0], tmp[1], ssl=ctx
133
- )
159
+ try:
160
+ (
161
+ _transport,
162
+ _protocol,
163
+ ) = await asyncio.get_event_loop().create_connection(
164
+ lambda: self._protocol,
165
+ host=parts.hostname,
166
+ port=parts.port,
167
+ ssl=ctx,
168
+ )
169
+
170
+ except (ConnectionRefusedError, OSError) as err:
171
+ raise VelbusConnectionFailed from err
134
172
  else:
135
173
  # serial port
136
- self._reader, self._writer = await serial_asyncio.open_serial_connection(
137
- url=self._dsn,
138
- baudrate=38400,
139
- bytesize=serial.EIGHTBITS,
140
- parity=serial.PARITY_NONE,
141
- stopbits=serial.STOPBITS_ONE,
142
- xonxoff=0,
143
- rtscts=1,
144
- )
145
- if test_connect:
146
- return
147
- # create reader, parser and writer tasks
148
- self._tasks.append(asyncio.Task(self._socket_read_task()))
149
- self._tasks.append(asyncio.Task(self._socket_send_task()))
150
- self._tasks.append(asyncio.Task(self._parser_task()))
151
- # scan the bus
152
- await self.scan()
174
+ try:
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
+ )
187
+ )
188
+ except (FileNotFoundError, serial.SerialException) as err:
189
+ raise VelbusConnectionFailed from err
153
190
 
154
- async def scan(self) -> None:
155
- self._handler.scan_started()
156
- for addr in range(1, 255):
157
- msg = ModuleTypeRequestMessage(addr)
158
- await self.send(msg)
159
- await asyncio.sleep(30)
160
- self._handler.scan_finished()
161
- # calculate how long to wait
162
- calc_timeout = len(self._modules) * 30
163
- if calc_timeout < LOAD_TIMEOUT:
164
- timeout = calc_timeout
191
+ async def start(self) -> None:
192
+ # if auth is required send the auth key
193
+ parts = urlparse(self._destination)
194
+ if parts.username:
195
+ await self._protocol.write_auth_key(parts.username)
196
+
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()
165
225
  else:
166
- timeout = LOAD_TIMEOUT
167
- # create a task to wait until we have all modules loaded
168
- tsk = asyncio.Task(self._check_if_modules_are_loaded())
169
- try:
170
- await asyncio.wait_for(tsk, timeout=timeout)
171
- except asyncio.TimeoutError:
172
- self._log.error(
173
- f"Not all modules are laoded within a timeout of {LOAD_TIMEOUT} seconds, continuing with the loaded modules"
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()
230
+
231
+ async def scan(self) -> None:
232
+ """Service endpoint to restart the scan"""
233
+ await self._handler.scan(True)
234
+
235
+ async def sendTypeRequestMessage(self, address: int) -> None:
236
+ msg = ModuleTypeRequestMessage(address)
237
+ await self.send(msg)
238
+
239
+ async def send(self, msg: Message) -> None:
240
+ """Send a packet."""
241
+ await self._protocol.send_message(
242
+ RawMessage(
243
+ priority=msg.priority,
244
+ address=msg.address,
245
+ rtr=msg.rtr,
246
+ data=msg.data_to_binary(),
174
247
  )
248
+ )
249
+
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")
175
257
 
176
- async def _check_if_modules_are_loaded(self) -> None:
177
- """
178
- Task to wait until modules are loaded
179
- """
180
- while True:
181
- mods_loaded = 0
182
- for mod in (self.get_modules()).values():
183
- if mod.is_loaded():
184
- mods_loaded += 1
185
- if mods_loaded == len(self.get_modules()):
186
- self._log.info("All modules loaded")
187
- return
188
- self._log.info("Not all modules loaded yet, waiting 30 seconds")
189
- await asyncio.sleep(230)
190
-
191
- async def send(self, msg) -> None:
192
- """
193
- Send a packet
194
- """
195
- await self._send_queue.put(msg)
196
-
197
- async def _socket_send_task(self) -> None:
198
- """
199
- Task to send the packet from the queue to the bus
200
- """
201
- while self._send_queue:
202
- msg = await self._send_queue.get()
203
- self._log.debug(f"SENDING message: {msg}")
204
- # print(':'.join('{:02X}'.format(x) for x in msg.to_binary()))
205
- self._writer.write(msg.to_binary())
206
- await asyncio.sleep(0.11)
207
-
208
- async def _socket_read_task(self) -> None:
209
- """
210
- Task to read from a socket and push into a queue
211
- """
212
- while True:
213
- data = await self._reader.read(10)
214
- self._parser.feed(data)
215
-
216
- async def _parser_task(self) -> None:
217
- """
218
- Task to parser the received queue
219
- """
220
- while True:
221
- packet = await self._parser.wait_for_packet()
222
- await self._handler.handle(packet)
223
-
224
- def get_all(self, class_name: str) -> list:
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."""
225
297
  lst = []
226
298
  for addr, mod in (self.get_modules()).items():
227
299
  if addr in self._submodules:
@@ -232,9 +304,12 @@ class Velbus:
232
304
  return lst
233
305
 
234
306
  async def sync_clock(self) -> None:
235
- """
236
- This will send all the needed messages to sync the clock
237
- """
238
- await self.send(SetRealtimeClock())
239
- await self.send(SetDate())
240
- 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()
velbusaio/discovery.py CHANGED
@@ -12,7 +12,7 @@ class VelbusDiscoveryProtocol(asyncio.DatagramProtocol):
12
12
  def __init__(self, target: Address):
13
13
  self.target = target
14
14
 
15
- def connection_made(self, transport: asyncio.transports.DatagramTransport):
15
+ def connection_made(self, transport: asyncio.transports.DatagramTransport) -> None:
16
16
  self.transport = transport
17
17
  sock = transport.get_extra_info("socket")
18
18
  sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -20,7 +20,7 @@ class VelbusDiscoveryProtocol(asyncio.DatagramProtocol):
20
20
  string = "Velbus Navigation Request"
21
21
  self.transport.sendto(string.encode(), self.target)
22
22
 
23
- def datagram_received(self, data: bytes | str, addr: Address):
23
+ def datagram_received(self, data: bytes | str, addr: Address) -> None:
24
24
  # data received: b'{"message": "Velbus Navigation Guidance", "hostname": "Velbus", "model": "signum18", "id": "7b95834e", "velbus_port": 27015, "velbus_auth": false}' ('192.168.1.9', 32767)
25
25
  try:
26
26
  json_data = json.loads(data)
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python
2
+
3
+
4
+ class VelbusException(Exception):
5
+ """Velbus Exception."""
6
+
7
+ def __init__(self, value: str) -> None:
8
+ Exception.__init__(self)
9
+ self.value = value
10
+
11
+ def __str__(self):
12
+ return repr(self.value)
13
+
14
+
15
+ class VelbusConnectionFailed(VelbusException):
16
+ def __init__(self) -> None:
17
+ super().__init__("Connection setup failed")
18
+
19
+
20
+ class VelbusConnectionTerminated(VelbusException):
21
+ def __init__(self) -> None:
22
+ super().__init__("Connection terminated")