velbus-aio 2024.4.0__py3-none-any.whl → 2024.5.1__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.

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: velbus-aio
3
- Version: 2024.4.0
3
+ Version: 2024.5.1
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,19 +1,19 @@
1
1
  velbusaio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- velbusaio/channels.py,sha256=RHX0YdaeyCMgXSV7f3VkN-grSIJu-lGStjC8CzGlU2s,22998
3
- velbusaio/command_registry.py,sha256=7oFAR6w-exVZJB9AVzQv5pBdvVmaR7tNVfwOsAaB_8Q,4838
4
- velbusaio/const.py,sha256=tqw1zC4Aq2iKMt4lHf_5h-W0aqDQR-XR3G0oeQLG1Ks,1551
5
- velbusaio/controller.py,sha256=zqoRPaWsaxZdiLf1jWWZqUmga9Hk3_n0xFZs1WOCxuY,9254
2
+ velbusaio/channels.py,sha256=1fqxsNHmJmb6lYRr4UqP9HLeTprbvektzrjQ8QBvWi0,22889
3
+ velbusaio/command_registry.py,sha256=j9KuLmada41PMEdtfPvLmXVOAZOkBM9zi-3kluwYffk,5140
4
+ velbusaio/const.py,sha256=aysTlQcpACZaDVqoXAjWmaEDGlCB7KT04Rv-SAusPFc,1575
5
+ velbusaio/controller.py,sha256=txOd0JuYi_OGMzO1t2u7KXlF085HWa1HVlszz7A2O5c,8846
6
6
  velbusaio/discovery.py,sha256=Px6qoZl4QhF17aMz6JxstCORBpLzZGWEK9h4Vyvg57o,1649
7
7
  velbusaio/exceptions.py,sha256=FHkXaM3dK5Gkk-QGAf9dLE3FPlCU2FRZWUyY-4KRNnA,515
8
- velbusaio/handler.py,sha256=xqUguhhEeyagSEyCdtZ1IFnZSPd91atfhZ-sYSzygFQ,5907
9
- velbusaio/helpers.py,sha256=zIHWVIizhvqJXOgbkLB3IromoGyeyk0DYqeVLCbMpkg,2543
10
- velbusaio/message.py,sha256=7lf2kzI74SDXQubvnQm8weqHS4YGUSTquPm_KvNR3q0,5049
11
- velbusaio/module.py,sha256=cLkNVWMchUfa_THLf17VRySly8Oe-GXkUJM7juVYxRk,34978
12
- velbusaio/protocol.json,sha256=KEyfib_UUkntP9WGmCeutUQw1WgH4nT7GkGLB-Grg4E,250285
8
+ velbusaio/handler.py,sha256=jTWIV26ibAqs3rcTku81NIHlPUeyWjStSih1Nfx-an0,5946
9
+ velbusaio/helpers.py,sha256=iqpoereRH4JY5WAkozIqWvXWyRmhko-J-UGXDylFyEM,2537
10
+ velbusaio/message.py,sha256=gnhXpA5NAG4qnI9vlxwlWQKETWneDb1lp0P9w_-nxhQ,5020
11
+ velbusaio/module.py,sha256=_yFxZIi71eqq9xxNal-9ATNHGMjZDOr-dcpKYIxa7_8,35458
12
+ velbusaio/protocol.json,sha256=7PifRZXctU5zSsodDGsUAhnnSsGNlcGcim1gr2su720,250396
13
13
  velbusaio/protocol.py,sha256=ofDwJfwyrVQDQ40WX2QdH1nTK-y2HibQPTo5dFY7SkQ,8224
14
14
  velbusaio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  velbusaio/raw_message.py,sha256=ODoTPVAvyXXQSMXxtQO1U5OxcoQ4W8JJN5fotEDGSTo,4690
16
- velbusaio/util.py,sha256=eCsGQJ9nuzk_B2skBlv3MsNaMaHDXo1a1qSPjObzXH4,1540
16
+ velbusaio/util.py,sha256=FW6YCiPYWOCgqHDs8-LbzME0h81mqftYVG0qqZ-oo7Y,1568
17
17
  velbusaio/messages/__init__.py,sha256=hLkLhsJ5hcgRnhaoyFWz1g2vphdnjrXURxgwezkDBwU,5893
18
18
  velbusaio/messages/blind_status.py,sha256=yzjoYLBxH85tBjihiGzLqebRg1urz0obBA-3q1pqSCE,3418
19
19
  velbusaio/messages/bus_active.py,sha256=AB1mEvbMXRuOaC2CQ7hzKSLmbaJnyFqqXrARi-aSdtg,749
@@ -28,8 +28,8 @@ velbusaio/messages/clear_led.py,sha256=AcGRCD0OdlHcFonvHA075nVwvOtZ6KfyjrXeE6A7D
28
28
  velbusaio/messages/counter_status.py,sha256=UgSRw-pipFb8C2DxauHw8Pjem1qTvzUFpiahui73-Is,1240
29
29
  velbusaio/messages/counter_status_request.py,sha256=VLFaI7MFnTCH1s8acfKlcAGscJElwxYRC56eT3bbB7Y,919
30
30
  velbusaio/messages/cover_down.py,sha256=tdjsRijrEa5oNBZk6uKWUK39k5gIjcWq0kgfOf4f1lQ,2552
31
- velbusaio/messages/cover_off.py,sha256=Y4dzJL_BKSATmztD444KOWlEnPuoAy3OfQ7CWaYRbMs,2258
32
- velbusaio/messages/cover_position.py,sha256=3ZcNSMpj-3Y355vb7bu4IiGGgjw_2nENlo4wEseya1Q,1283
31
+ velbusaio/messages/cover_off.py,sha256=0RHAJb4et99yAoo8LD85Rzlnhrqr40Wjba-BhzHSr4o,2243
32
+ velbusaio/messages/cover_position.py,sha256=PhRocdkbwf8CUfdL8-rZHEDFD2Z9u7DLZOpH3h6gAtg,1268
33
33
  velbusaio/messages/cover_up.py,sha256=P20AZx8qQScbyV0sxtZOlO03Q8pFO8NunpqIr4mZ6Lw,2547
34
34
  velbusaio/messages/dali_device_settings.py,sha256=KzJ7OFkFRPuCXSOThuplM48J_kH0I3RE82ZRCpmiqYA,4870
35
35
  velbusaio/messages/dali_device_settings_request.py,sha256=InTnrwLD7l9gCHXKlJ69FHMXcejtYMUZBtnffx9nDQU,1476
@@ -67,11 +67,11 @@ velbusaio/messages/select_program.py,sha256=9jLhsCuYGQsw-FMxpmNljPZifGeE_oJ6A5bg
67
67
  velbusaio/messages/sensor_settings_request.py,sha256=Uwywf0JGwBwc_2ZlfxVJicm3oUlWBNEcclcHA2_Mv5I,819
68
68
  velbusaio/messages/sensor_temp_request.py,sha256=bTChj9udOjIww9vJ2SWmMtbry_8jLDvbWSAy_ZPmrb4,599
69
69
  velbusaio/messages/sensor_temperature.py,sha256=ThA_4FFhQutCSt8147yCy7ocUFfizWaUV2JHoNzesNc,1558
70
- velbusaio/messages/set_date.py,sha256=SH11ufAZgcGDd0Fwf9Lm2UoIsL_02ZkUfsWZxl5hej4,1460
71
- velbusaio/messages/set_daylight_saving.py,sha256=A3sEEVi1Wnj9AaWbNyuW_Itp7pfKNYjjMBrMTQe_QHY,1118
72
- velbusaio/messages/set_dimmer.py,sha256=f2XWLbxoo0Tbj8_DUu28XSiib32VLUDtw8QRzRZBDdA,1975
70
+ velbusaio/messages/set_date.py,sha256=jcWb8c5mB71HNBwLCQ9ocEPSHQdZP0yfGJL4_swsRWQ,1359
71
+ velbusaio/messages/set_daylight_saving.py,sha256=CpoKrSt62UzzNxl8SDcpDcb0GKnLcD1f73-ULzqDEzU,1049
72
+ velbusaio/messages/set_dimmer.py,sha256=b32lBHrvZRQ7kKIFhQwuBqk9rXagnBtcJxo1V7V5x_0,2050
73
73
  velbusaio/messages/set_led.py,sha256=X8tIDzJgtf5EsH92rjoUyrECNFW-ECgAQoQ9TnvJEQk,900
74
- velbusaio/messages/set_realtime_clock.py,sha256=c52JLvULnf-WgKfju1sYHZRGZ4PjP3v1GS8fbxYxIBc,1309
74
+ velbusaio/messages/set_realtime_clock.py,sha256=h34pCvyPm7tSQ5BdB8yC8SRulrtldeX-ZJFv3Wcvdk0,1209
75
75
  velbusaio/messages/set_temperature.py,sha256=78LfGOBZo2bw7eF38uyXOjUcJrsSvQPDW2qbXLOYd0o,959
76
76
  velbusaio/messages/slider_status.py,sha256=AlBI1DJPLUwxOFfPIKYv15bFOnVXm__Tym7AW4xGMxo,1418
77
77
  velbusaio/messages/slow_blinking_led.py,sha256=UktzpRtyM4mR7_KQuNjqenPEeM3P1vgTyMW8dnVwylQ,909
@@ -96,8 +96,8 @@ velbusaio/messages/very_fast_blinking_led.py,sha256=vlMEern8PoOvtO5JaAk9erMR4IKJ
96
96
  velbusaio/messages/write_data_to_memory.py,sha256=gr6bi4SzK8Mw8fnp8yV-STq5jts7NoeV7zZgdptH5Vs,1039
97
97
  velbusaio/messages/write_memory_block.py,sha256=zGnNxx_M66HpBQ8S7kagtNw8_qSRHsOLk1MuiS0uygM,1032
98
98
  velbusaio/messages/write_module_address_and_serial_number.py,sha256=6y57j-md3btNtQddX5CUREtSs1Dzgkd953sQPZ3Pioo,1597
99
- velbus_aio-2024.4.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
100
- velbus_aio-2024.4.0.dist-info/METADATA,sha256=3PZCfpL0kS3j7hbvvbv7DwzkZe4kiUgemAP3jnrhNjo,3253
101
- velbus_aio-2024.4.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
102
- velbus_aio-2024.4.0.dist-info/top_level.txt,sha256=W0-lSOwD23mm8FqaIe9vY20fKicBMIdUVjF-zmfxRnY,15
103
- velbus_aio-2024.4.0.dist-info/RECORD,,
99
+ velbus_aio-2024.5.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
100
+ velbus_aio-2024.5.1.dist-info/METADATA,sha256=z1Q-cGFQfsDQYGHAppFwXJUDplFi6qaFKIGJW0Me3c4,3259
101
+ velbus_aio-2024.5.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
102
+ velbus_aio-2024.5.1.dist-info/top_level.txt,sha256=W0-lSOwD23mm8FqaIe9vY20fKicBMIdUVjF-zmfxRnY,15
103
+ velbus_aio-2024.5.1.dist-info/RECORD,,
velbusaio/channels.py CHANGED
@@ -136,17 +136,11 @@ class Channel:
136
136
  if k != "_writer" and k != "_on_status_update" and k != "_name_parts"
137
137
  }
138
138
 
139
- def to_json(self) -> dict:
140
- d = self.__dict__
141
- return {
142
- k: d[k]
143
- for k in d
144
- if k != "_writer"
145
- and k != "_on_status_update"
146
- and k != "_name_parts"
147
- and k != "_module"
148
- and k != "__name__"
149
- }
139
+ def to_cache(self) -> dict:
140
+ dst = {"name": self._name, "type": type(self).__name__}
141
+ if hasattr(self, "_Unit"):
142
+ dst["Unit"] = self._Unit
143
+ return dst
150
144
 
151
145
  def __setstate__(self, state):
152
146
  self.__dict__.update(state)
@@ -1,4 +1,5 @@
1
- """
1
+ """Command registry.
2
+
2
3
  :author: Maikel Punie <maikel.punie@gmail.com> and Thomas Delaet <thomas@delaet.org>
3
4
  """
4
5
 
@@ -87,7 +88,10 @@ MODULE_DIRECTORY = {
87
88
 
88
89
 
89
90
  class CommandRegistry:
91
+ """Command registry class."""
92
+
90
93
  def __init__(self, module_directory: dict) -> None:
94
+ """Init method."""
91
95
  self._module_directory = module_directory
92
96
  self._default_commands = {}
93
97
  self._overrides = {}
@@ -95,6 +99,7 @@ class CommandRegistry:
95
99
  def register_command(
96
100
  self, command_value: int, command_class: type, module_name: str | None = None
97
101
  ) -> None:
102
+ """Register a command."""
98
103
  if command_value < 0 or command_value > 255:
99
104
  raise ValueError("Command_value should be >=0 and <=255")
100
105
  if module_name and module_name not in self._module_directory.values():
@@ -115,6 +120,7 @@ class CommandRegistry:
115
120
  def _register_override(
116
121
  self, command_value: int, command_class: type, module_type: str
117
122
  ) -> None:
123
+ """Register and override."""
118
124
  if module_type not in self._overrides:
119
125
  self._overrides[module_type] = {}
120
126
  if command_value not in self._overrides[module_type]:
@@ -125,12 +131,14 @@ class CommandRegistry:
125
131
  )
126
132
 
127
133
  def _register_default(self, command_value: int, command_class: type) -> None:
134
+ """Register a default command."""
128
135
  if command_value not in self._default_commands:
129
136
  self._default_commands[command_value] = command_class
130
137
  else:
131
138
  raise Exception("double registration in command registry")
132
139
 
133
140
  def has_command(self, command_value: int, module_type: int = 0) -> bool:
141
+ """Find a command."""
134
142
  if module_type in self._overrides:
135
143
  if command_value in self._overrides[module_type]:
136
144
  return True
@@ -139,6 +147,7 @@ class CommandRegistry:
139
147
  return False
140
148
 
141
149
  def get_command(self, command_value: int, module_type: int = 0) -> None | type:
150
+ """Search a command in the registry."""
142
151
  if module_type in self._overrides:
143
152
  if command_value in self._overrides[module_type]:
144
153
  return self._overrides[module_type][command_value]
@@ -151,6 +160,8 @@ commandRegistry = CommandRegistry(MODULE_DIRECTORY)
151
160
 
152
161
 
153
162
  def register(command_value: int, module_types: list[str] | None = None):
163
+ """Register decorator."""
164
+
154
165
  def inner_register(command_class):
155
166
  if module_types:
156
167
  for module_type in module_types:
velbusaio/const.py CHANGED
@@ -1,4 +1,5 @@
1
- """
1
+ """Constant for velbusaio.
2
+
2
3
  Author: Maikel Punie <maikel.punie@gmail.com>
3
4
  """
4
5
 
velbusaio/controller.py CHANGED
@@ -1,19 +1,17 @@
1
- """
2
- Main interface for the velbusaio lib
3
- """
1
+ """Main interface for the velbusaio lib."""
4
2
 
5
3
  from __future__ import annotations
6
4
 
7
5
  import asyncio
8
6
  import logging
9
7
  import pathlib
10
- import pickle
11
8
  import re
12
9
  import ssl
10
+ import time
13
11
  from urllib.parse import urlparse
14
12
 
15
13
  import serial
16
- import serial_asyncio
14
+ import serial_asyncio_fast
17
15
 
18
16
  from velbusaio.channels import Channel
19
17
  from velbusaio.const import LOAD_TIMEOUT
@@ -31,15 +29,14 @@ from velbusaio.raw_message import RawMessage
31
29
 
32
30
 
33
31
  class Velbus:
34
- """
35
- A velbus controller
36
- """
32
+ """A velbus controller."""
37
33
 
38
34
  def __init__(
39
35
  self,
40
36
  dsn: str,
41
37
  cache_dir: str = get_cache_dir(),
42
38
  ) -> None:
39
+ """Init the Velbus controller."""
43
40
  self._log = logging.getLogger("velbus")
44
41
 
45
42
  self._protocol = VelbusProtocol(
@@ -60,6 +57,7 @@ class Velbus:
60
57
  pathlib.Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
61
58
 
62
59
  async def _on_message_received(self, msg: RawMessage) -> None:
60
+ """On message received function."""
63
61
  await self._handler.handle(msg)
64
62
 
65
63
  def _on_connection_lost(self, exc: Exception) -> None:
@@ -69,6 +67,7 @@ class Velbus:
69
67
  asyncio.ensure_future(self.connect())
70
68
 
71
69
  def _on_end_of_scan(self) -> None:
70
+ """Notify the scan failure."""
72
71
  self._handler.scan_finished()
73
72
 
74
73
  async def add_module(
@@ -81,31 +80,23 @@ class Velbus:
81
80
  build_year: int | None = None,
82
81
  build_week: int | None = None,
83
82
  ) -> None:
84
- """
85
- Add a found module to the module cache
86
- """
87
- mod = self._load_module_from_cache(self._cache_dir, addr)
88
- if mod is not None:
89
- self._log.info(f"Load module from CACHE: {addr}")
90
- self._modules[addr] = mod
91
- self._modules[addr].initialize(self.send)
92
- await self._modules[addr].load(True)
93
- else:
94
- self._log.info(f"Load NEW module: {typ} @ {addr}")
95
- self._modules[addr] = Module.factory(
96
- addr,
97
- typ,
98
- data,
99
- serial=serial,
100
- build_year=build_year,
101
- build_week=build_week,
102
- memorymap=memorymap,
103
- cache_dir=self._cache_dir,
104
- )
105
- self._modules[addr].initialize(self.send)
106
- await self._modules[addr].load()
83
+ """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(
86
+ addr,
87
+ typ,
88
+ data,
89
+ serial=serial,
90
+ build_year=build_year,
91
+ build_week=build_week,
92
+ memorymap=memorymap,
93
+ cache_dir=self._cache_dir,
94
+ )
95
+ self._modules[addr].initialize(self.send)
96
+ await self._modules[addr].load()
107
97
 
108
98
  async def add_submodules(self, addr: int, subList: dict[int, int]) -> None:
99
+ """Add submodules address to module."""
109
100
  for sub_num, sub_addr in subList.items():
110
101
  if sub_addr == 0xFF:
111
102
  continue
@@ -114,48 +105,30 @@ class Velbus:
114
105
  self._modules[sub_addr] = self._modules[addr]
115
106
  self._modules[addr].cleanupSubChannels()
116
107
 
117
- def _load_module_from_cache(self, cache_dir: str, address: int) -> None | Module:
118
- try:
119
- cfile = pathlib.Path(f"{cache_dir}/{address}.p")
120
- with cfile.open("rb") as fl:
121
- o = pickle.load(fl)
122
- if isinstance(o, Module):
123
- return o
124
- except OSError:
125
- pass
126
- return None
127
-
128
108
  def get_modules(self) -> dict:
129
- """
130
- Return the module cache
131
- """
109
+ """Return the module cache."""
132
110
  return self._modules
133
111
 
134
112
  def get_module(self, addr: str) -> None | Module:
135
- """
136
- Get a module on an address
137
- """
138
- if addr in self._modules.keys():
113
+ """Get a module on an address."""
114
+ if addr in self._modules:
139
115
  return self._modules[addr]
140
116
  return None
141
117
 
142
118
  def get_channels(self, addr: str) -> None | dict:
143
- """
144
- Get the channels for an address
145
- """
119
+ """Get the channels for an address."""
146
120
  if addr in self._modules:
147
121
  return (self._modules[addr]).get_channels()
148
122
  return None
149
123
 
150
124
  async def stop(self) -> None:
125
+ """Stop the controller."""
151
126
  self._closing = True
152
127
  self._auto_reconnect = False
153
128
  self._protocol.close()
154
129
 
155
130
  async def connect(self, test_connect: bool = False) -> None:
156
- """
157
- Connect to the bus and load all the data
158
- """
131
+ """Connect to the bus and load all the data."""
159
132
  auth = None
160
133
  # connect to the bus
161
134
  if ":" in self._dsn:
@@ -182,23 +155,25 @@ class Velbus:
182
155
  )
183
156
 
184
157
  except (ConnectionRefusedError, OSError) as err:
185
- raise VelbusConnectionFailed() from err
158
+ raise VelbusConnectionFailed from err
186
159
  else:
187
160
  # serial port
188
161
  try:
189
- _transport, _protocol = await serial_asyncio.create_serial_connection(
190
- asyncio.get_event_loop(),
191
- lambda: self._protocol,
192
- url=self._dsn,
193
- baudrate=38400,
194
- bytesize=serial.EIGHTBITS,
195
- parity=serial.PARITY_NONE,
196
- stopbits=serial.STOPBITS_ONE,
197
- xonxoff=0,
198
- rtscts=1,
162
+ _transport, _protocol = (
163
+ await serial_asyncio_fast.create_serial_connection(
164
+ asyncio.get_event_loop(),
165
+ lambda: self._protocol,
166
+ url=self._dsn,
167
+ baudrate=38400,
168
+ bytesize=serial.EIGHTBITS,
169
+ parity=serial.PARITY_NONE,
170
+ stopbits=serial.STOPBITS_ONE,
171
+ xonxoff=0,
172
+ rtscts=1,
173
+ )
199
174
  )
200
175
  except (FileNotFoundError, serial.SerialException) as err:
201
- raise VelbusConnectionFailed() from err
176
+ raise VelbusConnectionFailed from err
202
177
  if test_connect:
203
178
  return
204
179
  # if auth is required send the auth key
@@ -209,6 +184,7 @@ class Velbus:
209
184
  await self.scan()
210
185
 
211
186
  async def scan(self) -> None:
187
+ """Scan the bus."""
212
188
  self._handler.scan_started()
213
189
  for addr in range(1, 256):
214
190
  msg = ModuleTypeRequestMessage(addr)
@@ -230,9 +206,7 @@ class Velbus:
230
206
  )
231
207
 
232
208
  async def _check_if_modules_are_loaded(self) -> None:
233
- """
234
- Task to wait until modules are loaded
235
- """
209
+ """Task to wait until modules are loaded."""
236
210
  while True:
237
211
  mods_loaded = 0
238
212
  for mod in (self.get_modules()).values():
@@ -247,9 +221,7 @@ class Velbus:
247
221
  await asyncio.sleep(15)
248
222
 
249
223
  async def send(self, msg: Message) -> None:
250
- """
251
- Send a packet
252
- """
224
+ """Send a packet."""
253
225
  await self._protocol.send_message(
254
226
  RawMessage(
255
227
  priority=msg.priority,
@@ -260,6 +232,7 @@ class Velbus:
260
232
  )
261
233
 
262
234
  def get_all(self, class_name: str) -> list[Channel]:
235
+ """Get all channels."""
263
236
  lst = []
264
237
  for addr, mod in (self.get_modules()).items():
265
238
  if addr in self._submodules:
@@ -270,9 +243,8 @@ class Velbus:
270
243
  return lst
271
244
 
272
245
  async def sync_clock(self) -> None:
273
- """
274
- This will send all the needed messages to sync the clock
275
- """
276
- await self.send(SetRealtimeClock())
277
- await self.send(SetDate())
278
- await self.send(SetDaylightSaving())
246
+ """Will send all the needed messages to sync the clock."""
247
+ lclt = time.localtime()
248
+ await self.send(SetRealtimeClock(wday=lclt[6], hour=lclt[3], min=lclt[4]))
249
+ await self.send(SetDate(day=lclt[2], mon=lclt[1], year=lclt[0]))
250
+ await self.send(SetDaylightSaving(ds=not lclt[8]))
velbusaio/handler.py CHANGED
@@ -8,7 +8,6 @@ from __future__ import annotations
8
8
  import asyncio
9
9
  import json
10
10
  import logging
11
- import re
12
11
  from typing import TYPE_CHECKING, Awaitable, Callable
13
12
  import pkg_resources
14
13
 
@@ -105,7 +104,9 @@ class PacketHandler:
105
104
  else:
106
105
  self._log.warning(
107
106
  "NOT FOUND IN command_registry: addr={} cmd={} packet={}".format(
108
- address, command_value, ":".join(format(x, "02x") for x in data)
107
+ address,
108
+ command_value,
109
+ ":".join(format(x, "02x") for x in data),
109
110
  )
110
111
  )
111
112
  elif self._scan_complete:
velbusaio/helpers.py CHANGED
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  import os
8
8
  import re
9
- from typing import Any, Dict
9
+ from typing import Any
10
10
 
11
11
  from velbusaio.const import CACHEDIR
12
12
 
velbusaio/message.py CHANGED
@@ -5,7 +5,6 @@ The velbus abstract message class
5
5
  from __future__ import annotations
6
6
 
7
7
  import json
8
- from typing import Optional
9
8
 
10
9
  from velbusaio.const import PRIORITY_FIRMWARE, PRIORITY_HIGH, PRIORITY_LOW
11
10
 
@@ -4,8 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import struct
8
-
9
7
  from velbusaio.command_registry import register
10
8
  from velbusaio.message import Message
11
9
 
@@ -4,8 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import struct
8
-
9
7
  from velbusaio.command_registry import register
10
8
  from velbusaio.message import Message
11
9
 
@@ -4,8 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import time
8
-
9
7
  from velbusaio.command_registry import register
10
8
  from velbusaio.message import Message
11
9
 
@@ -18,11 +16,11 @@ class SetDate(Message):
18
16
  received by all modules
19
17
  """
20
18
 
21
- def __init__(self, address=0x00) -> None:
19
+ def __init__(self, address=0x00, day=None, mon=None, year=None) -> None:
22
20
  Message.__init__(self)
23
- self._day = None
24
- self._mon = None
25
- self._year = None
21
+ self._day = day
22
+ self._mon = mon
23
+ self._year = year
26
24
  self.set_defaults(address)
27
25
 
28
26
  def set_defaults(self, address) -> None:
@@ -30,10 +28,6 @@ class SetDate(Message):
30
28
  self.set_address(address)
31
29
  self.set_low_priority()
32
30
  self.set_no_rtr()
33
- lclt = time.localtime()
34
- self._day = lclt[2]
35
- self._mon = lclt[1]
36
- self._year = lclt[0]
37
31
 
38
32
  def populate(self, priority, address, rtr, data) -> None:
39
33
  """
@@ -4,8 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import time
8
-
9
7
  from velbusaio.command_registry import register
10
8
  from velbusaio.message import Message
11
9
 
@@ -18,9 +16,9 @@ class SetDaylightSaving(Message):
18
16
  received by all modules
19
17
  """
20
18
 
21
- def __init__(self, address=0x00) -> None:
19
+ def __init__(self, address=0x00, ds=None) -> None:
22
20
  Message.__init__(self)
23
- self._ds = None
21
+ self._ds = ds
24
22
  self.set_defaults(address)
25
23
 
26
24
  def set_defaults(self, address) -> None:
@@ -28,8 +26,6 @@ class SetDaylightSaving(Message):
28
26
  self.set_address(address)
29
27
  self.set_low_priority()
30
28
  self.set_no_rtr()
31
- lclt = time.localtime()
32
- self._ds = not lclt[8]
33
29
 
34
30
  def populate(self, priority, address, rtr, data) -> None:
35
31
  """
@@ -1,5 +1,4 @@
1
- """
2
- :author: Frank van Breugel
1
+ """:author: Frank van Breugel
3
2
  """
4
3
 
5
4
  from __future__ import annotations
@@ -12,10 +11,10 @@ COMMAND_CODE = 0x07
12
11
 
13
12
  @register(
14
13
  COMMAND_CODE,
15
- ["VMB1DM", "VMBDME", "VMB4DC", "VMBDMI", "VMBDMI-R", "VMB1LED", "VMB8DC-20"],
14
+ ["VMB1DM", "VMBDME", "VMB4DC", "VMB1LED"],
16
15
  )
17
16
  class SetDimmerMessage(Message):
18
- """
17
+ """with this message the channel numbering is a bitnumber
19
18
  send by:
20
19
  received by: VMBDME, VMB4DC
21
20
  """
@@ -34,9 +33,7 @@ class SetDimmerMessage(Message):
34
33
  self.set_no_rtr()
35
34
 
36
35
  def populate(self, priority, address, rtr, data):
37
- """
38
- :return: None
39
- """
36
+ """:return: None"""
40
37
  self.needs_high_priority(priority)
41
38
  self.needs_no_rtr(rtr)
42
39
  self.needs_data(data, 4)
@@ -48,9 +45,7 @@ class SetDimmerMessage(Message):
48
45
  )
49
46
 
50
47
  def data_to_binary(self):
51
- """
52
- :return: bytes
53
- """
48
+ """:return: bytes"""
54
49
  return bytes(
55
50
  [
56
51
  COMMAND_CODE,
@@ -60,9 +55,9 @@ class SetDimmerMessage(Message):
60
55
  ) + self.dimmer_transitiontime.to_bytes(2, byteorder="big", signed=False)
61
56
 
62
57
 
63
- @register(COMMAND_CODE, ["VMBDALI", "VMBDALI-20"])
58
+ @register(COMMAND_CODE, ["VMBDALI", "VMBDALI-20", "VMBDMI", "VMBDMI-R", "VMB8DC-20"])
64
59
  class SetDimmerMessage2(SetDimmerMessage):
65
- """
60
+ """This with this message the channel numbering is an integer
66
61
  send by:
67
62
  received by: VMBDALI
68
63
  """
@@ -4,8 +4,6 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import time
8
-
9
7
  from velbusaio.command_registry import register
10
8
  from velbusaio.message import Message
11
9
 
@@ -18,11 +16,11 @@ class SetRealtimeClock(Message):
18
16
  received by all modules
19
17
  """
20
18
 
21
- def __init__(self, address=0x00) -> None:
19
+ def __init__(self, address=0x00, wday=None, hour=None, min=None) -> None:
22
20
  Message.__init__(self)
23
- self._wday = None
24
- self._hour = None
25
- self._min = None
21
+ self._wday = wday
22
+ self._hour = hour
23
+ self._min = min
26
24
  self.set_defaults(address)
27
25
 
28
26
  def set_defaults(self, address) -> None:
@@ -30,10 +28,6 @@ class SetRealtimeClock(Message):
30
28
  self.set_address(address)
31
29
  self.set_low_priority()
32
30
  self.set_no_rtr()
33
- lclt = time.localtime()
34
- self._wday = lclt[6]
35
- self._hour = lclt[3]
36
- self._min = lclt[4]
37
31
 
38
32
  def populate(self, priority, address, rtr, data) -> None:
39
33
  """
velbusaio/module.py CHANGED
@@ -5,11 +5,10 @@ This represents a velbus module
5
5
  from __future__ import annotations
6
6
 
7
7
  import logging
8
- import os
9
8
  import pathlib
10
- import pickle
11
9
  import struct
12
10
  import sys
11
+ import json
13
12
  from typing import Awaitable, Callable
14
13
 
15
14
  from velbusaio.channels import (
@@ -37,7 +36,7 @@ from velbusaio.const import (
37
36
  )
38
37
  from velbusaio.helpers import handle_match, keys_exists
39
38
  from velbusaio.message import Message
40
- from velbusaio.messages import DaliDeviceSettingMsg
39
+ from velbusaio.messages.dali_device_settings import DaliDeviceSettingMsg
41
40
  from velbusaio.messages.blind_status import BlindStatusMessage, BlindStatusNgMessage
42
41
  from velbusaio.messages.channel_name_part1 import (
43
42
  ChannelNamePart1Message,
@@ -81,8 +80,6 @@ from velbusaio.messages.module_status import (
81
80
  ModuleStatusPirMessage,
82
81
  )
83
82
  from velbusaio.messages.module_status_request import ModuleStatusRequestMessage
84
- from velbusaio.messages.module_subtype import ModuleSubTypeMessage
85
- from velbusaio.messages.module_type import ModuleTypeMessage, ModuleType2Message
86
83
  from velbusaio.messages.push_button_status import PushButtonStatusMessage
87
84
  from velbusaio.messages.read_data_from_memory import ReadDataFromMemoryMessage
88
85
  from velbusaio.messages.relay_status import RelayStatusMessage, RelayStatusMessage2
@@ -188,9 +185,9 @@ class Module:
188
185
  del self._channels[i]
189
186
 
190
187
  def _cache(self) -> None:
191
- cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.p")
192
- with cfile.open("wb") as fl:
193
- pickle.dump(self, fl)
188
+ cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
189
+ with cfile.open("w") as fl:
190
+ json.dump(self.to_cache(), fl, indent=4)
194
191
 
195
192
  def __getstate__(self) -> dict:
196
193
  d = self.__dict__
@@ -201,20 +198,17 @@ class Module:
201
198
  self.__dict__ = state
202
199
 
203
200
  def __repr__(self) -> str:
204
- return (
205
- "<{}: {{{}}} @ {{{}}} loaded:{{{}}} loading:{{{}}} channels{{:{}}}>".format(
206
- self._name,
207
- self._type,
208
- self._address,
209
- self.loaded,
210
- self._is_loading,
211
- self._channels,
212
- )
213
- )
201
+ return f"<{self._name} type:{self._type} address:{self._address} loaded:{self.loaded} loading:{self._is_loading} channels: {self._channels}>"
214
202
 
215
203
  def __str__(self) -> str:
216
204
  return self.__repr__()
217
205
 
206
+ def to_cache(self) -> dict:
207
+ d = {"name": self._name, "channels": {}}
208
+ for num, chan in self._channels.items():
209
+ d["channels"][num] = chan.to_cache()
210
+ return d
211
+
218
212
  def get_addresses(self) -> list:
219
213
  """
220
214
  Get all addresses for this module
@@ -232,7 +226,9 @@ class Module:
232
226
  return self._type
233
227
 
234
228
  def get_type_name(self) -> str:
235
- return self._data["Type"]
229
+ if "Type" in self._data:
230
+ return self._data["Type"]
231
+ return "UNKNOWN"
236
232
 
237
233
  def get_serial(self) -> str | None:
238
234
  return self.serial
@@ -241,12 +237,7 @@ class Module:
241
237
  return self._name
242
238
 
243
239
  def get_sw_version(self) -> str:
244
- return "{}-{}.{}.{}".format(
245
- self.serial,
246
- self.memory_map_version,
247
- self.build_year,
248
- self.build_week,
249
- )
240
+ return f"{self.serial}-{self.memory_map_version}.{self.build_year}.{self.build_week}"
250
241
 
251
242
  def calc_channel_offset(self, address: int) -> int:
252
243
  _channel_offset = 0
@@ -549,14 +540,31 @@ class Module:
549
540
  self._log.info("Load Module")
550
541
  # start the loading
551
542
  self._is_loading = True
543
+ # see if we have a cache
544
+ try:
545
+ cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
546
+ with cfile.open("r") as fl:
547
+ cache = json.load(fl)
548
+ except OSError:
549
+ cache = {}
552
550
  # load default channels
553
- await self.__load_default_channels()
551
+ await self._load_default_channels()
554
552
  # load the data from memory ( the stuff that we need)
555
- await self.__load_memory()
553
+ if "name" in cache and cache["name"] != "":
554
+ self._name = cache["name"]
555
+ else:
556
+ await self.__load_memory()
556
557
  # load the module status
557
558
  await self._request_module_status()
558
559
  # load the channel names
559
- await self._request_channel_name()
560
+ if "channels" in cache:
561
+ for num, chan in cache["channels"].items():
562
+ self._channels[int(num)]._name = chan["name"]
563
+ if "Unit" in chan:
564
+ self._channels[int(num)]._Unit = chan["Unit"]
565
+ self._channels[int(num)]._is_loaded = True
566
+ else:
567
+ await self._request_channel_name()
560
568
  # load the module specific stuff
561
569
  self._load()
562
570
  # stop the loading
@@ -720,7 +728,7 @@ class Module:
720
728
  msg.low_address = addr[1]
721
729
  await self._writer(msg)
722
730
 
723
- async def __load_default_channels(self) -> None:
731
+ async def _load_default_channels(self) -> None:
724
732
  if "Channels" not in self._data:
725
733
  return
726
734
 
@@ -768,19 +776,15 @@ class VmbDali(Module):
768
776
  )
769
777
  self.group_members: dict[int, set[int]] = {}
770
778
 
771
- async def load(self, from_cache: bool = False) -> None:
772
- await super().load(from_cache)
773
-
774
- if not from_cache:
775
- for chan in range(1, 64 + 1):
776
- self._channels[chan] = Channel(
777
- self, chan, "placeholder", True, self._writer, self._address
778
- )
779
- # Placeholders will keep this module loading
780
- # Until the DaliDeviceSettings messages either delete or replace these placeholder's
781
- # with actual channels
782
-
783
- await self._request_dali_channels()
779
+ async def _load_default_channels(self) -> None:
780
+ for chan in range(1, 64 + 1):
781
+ self._channels[chan] = Channel(
782
+ self, chan, "placeholder", True, self._writer, self._address
783
+ )
784
+ # Placeholders will keep this module loading
785
+ # Until the DaliDeviceSettings messages either delete or replace these placeholder's
786
+ # with actual channels
787
+ await self._request_dali_channels()
784
788
 
785
789
  async def _request_dali_channels(self):
786
790
  msg_type = commandRegistry.get_command(
velbusaio/protocol.json CHANGED
@@ -8792,7 +8792,7 @@
8792
8792
  },
8793
8793
  "45": {
8794
8794
  "Info": "VMBDALI-20 DALI gateway module",
8795
- "Type": "VMBDALI"
8795
+ "Type": "VMBDALI-20"
8796
8796
  },
8797
8797
  "48": {
8798
8798
  "Channels": {
@@ -9361,7 +9361,8 @@
9361
9361
  }
9362
9362
  },
9363
9363
  "TemperatureChannel": "09",
9364
- "ThermostatAddr": "0"
9364
+ "ThermostatAddr": "0",
9365
+ "Type": "VMBEL1-20"
9365
9366
  },
9366
9367
  "50": {
9367
9368
  "AllChannelStatus": "FF",
@@ -9449,7 +9450,8 @@
9449
9450
  }
9450
9451
  },
9451
9452
  "TemperatureChannel": "09",
9452
- "ThermostatAddr": "0"
9453
+ "ThermostatAddr": "0",
9454
+ "Type": "VMBEL2-20"
9453
9455
  },
9454
9456
  "51": {
9455
9457
  "AllChannelStatus": "FF",
@@ -9537,7 +9539,8 @@
9537
9539
  }
9538
9540
  },
9539
9541
  "TemperatureChannel": "09",
9540
- "ThermostatAddr": "0"
9542
+ "ThermostatAddr": "0",
9543
+ "Type": "VMBEL4-20"
9541
9544
  },
9542
9545
  "52": {
9543
9546
  "AllChannelStatus": "FF",
@@ -9725,7 +9728,8 @@
9725
9728
  }
9726
9729
  },
9727
9730
  "TemperatureChannel": "33",
9728
- "ThermostatAddr": "3"
9731
+ "ThermostatAddr": "3",
9732
+ "Type": "VMBELO-20"
9729
9733
  },
9730
9734
  "54": {
9731
9735
  "Info": "1 Button Touch panel",
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