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.
- scripts/parse_specs.py +156 -0
- {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/METADATA +24 -8
- velbus_aio-2025.11.0.dist-info/RECORD +194 -0
- {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/WHEEL +1 -1
- {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info}/top_level.txt +1 -0
- velbusaio/channels.py +35 -14
- velbusaio/command_registry.py +32 -5
- velbusaio/const.py +10 -2
- velbusaio/controller.py +183 -145
- velbusaio/handler.py +286 -154
- velbusaio/helpers.py +2 -1
- velbusaio/message.py +14 -8
- velbusaio/messages/__init__.py +6 -2
- velbusaio/messages/blind_status.py +4 -2
- velbusaio/messages/bus_active.py +1 -0
- velbusaio/messages/bus_error_counter_status.py +1 -0
- velbusaio/messages/bus_error_counter_status_request.py +1 -0
- velbusaio/messages/bus_off.py +1 -0
- velbusaio/messages/channel_name_part1.py +18 -0
- velbusaio/messages/channel_name_part2.py +18 -0
- velbusaio/messages/channel_name_part3.py +18 -0
- velbusaio/messages/channel_name_request.py +2 -1
- velbusaio/messages/clear_led.py +1 -0
- velbusaio/messages/counter_status.py +1 -0
- velbusaio/messages/counter_status_request.py +1 -0
- velbusaio/messages/counter_value.py +44 -0
- velbusaio/messages/cover_down.py +2 -1
- velbusaio/messages/cover_off.py +2 -3
- velbusaio/messages/cover_position.py +3 -4
- velbusaio/messages/cover_up.py +2 -1
- velbusaio/messages/dali_device_settings.py +2 -1
- velbusaio/messages/dali_device_settings_request.py +2 -1
- velbusaio/messages/dali_dim_value_status.py +4 -1
- velbusaio/messages/dimmer_channel_status.py +5 -1
- velbusaio/messages/dimmer_status.py +13 -1
- velbusaio/messages/edge_set_color.py +1 -0
- velbusaio/messages/edge_set_custom_color.py +1 -0
- velbusaio/messages/fast_blinking_led.py +1 -0
- velbusaio/messages/forced_off.py +1 -0
- velbusaio/messages/forced_on.py +1 -0
- velbusaio/messages/interface_status_request.py +1 -0
- velbusaio/messages/ir_receiver_status.py +1 -0
- velbusaio/messages/kwh_status.py +1 -0
- velbusaio/messages/light_value_request.py +1 -0
- velbusaio/messages/memo_text.py +1 -0
- velbusaio/messages/memory_data.py +1 -0
- velbusaio/messages/memory_data_block.py +1 -0
- velbusaio/messages/memory_dump_request.py +1 -0
- velbusaio/messages/module_status.py +13 -0
- velbusaio/messages/module_status_request.py +5 -2
- velbusaio/messages/module_subtype.py +4 -3
- velbusaio/messages/module_type.py +17 -7
- velbusaio/messages/module_type_request.py +1 -0
- velbusaio/messages/psu_load.py +56 -0
- velbusaio/messages/psu_values.py +53 -0
- velbusaio/messages/push_button_status.py +1 -0
- velbusaio/messages/raw.py +1 -0
- velbusaio/messages/read_data_block_from_memory.py +1 -0
- velbusaio/messages/read_data_from_memory.py +1 -0
- velbusaio/messages/realtime_clock_status_request.py +1 -0
- velbusaio/messages/receive_buffer_full.py +1 -0
- velbusaio/messages/receive_ready.py +1 -0
- velbusaio/messages/relay_status.py +1 -0
- velbusaio/messages/restore_dimmer.py +16 -2
- velbusaio/messages/select_program.py +1 -0
- velbusaio/messages/sensor_settings_request.py +1 -0
- velbusaio/messages/sensor_temp_request.py +1 -0
- velbusaio/messages/sensor_temperature.py +1 -0
- velbusaio/messages/set_date.py +5 -10
- velbusaio/messages/set_daylight_saving.py +3 -6
- velbusaio/messages/set_dimmer.py +22 -13
- velbusaio/messages/set_led.py +1 -0
- velbusaio/messages/set_realtime_clock.py +5 -10
- velbusaio/messages/set_temperature.py +1 -0
- velbusaio/messages/slider_status.py +15 -1
- velbusaio/messages/slow_blinking_led.py +1 -0
- velbusaio/messages/start_relay_blinking_timer.py +1 -0
- velbusaio/messages/start_relay_timer.py +1 -0
- velbusaio/messages/switch_relay_off.py +1 -0
- velbusaio/messages/switch_relay_on.py +1 -0
- velbusaio/messages/switch_to_comfort.py +1 -0
- velbusaio/messages/switch_to_day.py +1 -0
- velbusaio/messages/switch_to_night.py +1 -0
- velbusaio/messages/switch_to_safe.py +1 -0
- velbusaio/messages/temp_sensor_settings_part1.py +1 -0
- velbusaio/messages/temp_sensor_settings_part2.py +1 -0
- velbusaio/messages/temp_sensor_settings_part3.py +1 -0
- velbusaio/messages/temp_sensor_settings_part4.py +1 -0
- velbusaio/messages/temp_sensor_settings_request.py +1 -0
- velbusaio/messages/temp_sensor_status.py +1 -0
- velbusaio/messages/temp_set_cooling.py +1 -0
- velbusaio/messages/temp_set_heating.py +1 -0
- velbusaio/messages/update_led_status.py +1 -0
- velbusaio/messages/very_fast_blinking_led.py +1 -0
- velbusaio/messages/write_data_to_memory.py +1 -0
- velbusaio/messages/write_memory_block.py +1 -0
- velbusaio/messages/write_module_address_and_serial_number.py +1 -0
- velbusaio/module.py +214 -102
- velbusaio/module_spec/01.json +62 -0
- velbusaio/module_spec/02.json +16 -0
- velbusaio/module_spec/03.json +23 -0
- velbusaio/module_spec/04.json +283 -0
- velbusaio/module_spec/05.json +54 -0
- velbusaio/module_spec/06.json +110 -0
- velbusaio/module_spec/07.json +16 -0
- velbusaio/module_spec/08.json +38 -0
- velbusaio/module_spec/09.json +30 -0
- velbusaio/module_spec/0A.json +58 -0
- velbusaio/module_spec/0B.json +58 -0
- velbusaio/module_spec/0C.json +18 -0
- velbusaio/module_spec/0E.json +25 -0
- velbusaio/module_spec/0F.json +16 -0
- velbusaio/module_spec/10.json +111 -0
- velbusaio/module_spec/11.json +111 -0
- velbusaio/module_spec/12.json +73 -0
- velbusaio/module_spec/13.json +4 -0
- velbusaio/module_spec/14.json +16 -0
- velbusaio/module_spec/15.json +83 -0
- velbusaio/module_spec/16.json +129 -0
- velbusaio/module_spec/17.json +129 -0
- velbusaio/module_spec/18.json +129 -0
- velbusaio/module_spec/1A.json +79 -0
- velbusaio/module_spec/1B.json +107 -0
- velbusaio/module_spec/1D.json +89 -0
- velbusaio/module_spec/1E.json +306 -0
- velbusaio/module_spec/1F.json +178 -0
- velbusaio/module_spec/20.json +178 -0
- velbusaio/module_spec/21.json +326 -0
- velbusaio/module_spec/22.json +426 -0
- velbusaio/module_spec/23.json +129 -0
- velbusaio/module_spec/24.json +30 -0
- velbusaio/module_spec/25.json +3 -0
- velbusaio/module_spec/28.json +454 -0
- velbusaio/module_spec/29.json +235 -0
- velbusaio/module_spec/2A.json +239 -0
- velbusaio/module_spec/2B.json +239 -0
- velbusaio/module_spec/2C.json +257 -0
- velbusaio/module_spec/2D.json +270 -0
- velbusaio/module_spec/2E.json +215 -0
- velbusaio/module_spec/2F.json +211 -0
- velbusaio/module_spec/30.json +58 -0
- velbusaio/module_spec/31.json +465 -0
- velbusaio/module_spec/32.json +385 -0
- velbusaio/module_spec/33.json +249 -0
- velbusaio/module_spec/34.json +313 -0
- velbusaio/module_spec/35.json +313 -0
- velbusaio/module_spec/36.json +313 -0
- velbusaio/module_spec/37.json +333 -0
- velbusaio/module_spec/38.json +111 -0
- velbusaio/module_spec/39.json +4 -0
- velbusaio/module_spec/3A.json +306 -0
- velbusaio/module_spec/3B.json +306 -0
- velbusaio/module_spec/3C.json +306 -0
- velbusaio/module_spec/3D.json +454 -0
- velbusaio/module_spec/3E.json +302 -0
- velbusaio/module_spec/3F.json +4 -0
- velbusaio/module_spec/40.json +4 -0
- velbusaio/module_spec/41.json +241 -0
- velbusaio/module_spec/42.json +4 -0
- velbusaio/module_spec/43.json +23 -0
- velbusaio/module_spec/44.json +38 -0
- velbusaio/module_spec/45.json +4 -0
- velbusaio/module_spec/48.json +111 -0
- velbusaio/module_spec/49.json +111 -0
- velbusaio/module_spec/4A.json +89 -0
- velbusaio/module_spec/4B.json +138 -0
- velbusaio/module_spec/4C.json +129 -0
- velbusaio/module_spec/4D.json +108 -0
- velbusaio/module_spec/4E.json +787 -0
- velbusaio/module_spec/4F.json +114 -0
- velbusaio/module_spec/50.json +114 -0
- velbusaio/module_spec/51.json +114 -0
- velbusaio/module_spec/52.json +456 -0
- velbusaio/module_spec/54.json +270 -0
- velbusaio/module_spec/55.json +270 -0
- velbusaio/module_spec/56.json +270 -0
- velbusaio/module_spec/57.json +260 -0
- velbusaio/module_spec/5A.json +4 -0
- velbusaio/module_spec/5B.json +4 -0
- velbusaio/module_spec/5C.json +90 -0
- velbusaio/module_spec/5F.json +78 -0
- velbusaio/module_spec/60.json +4 -0
- velbusaio/module_spec/61.json +89 -0
- velbusaio/module_spec/broadcast.json +67 -0
- velbusaio/module_spec/ignore.json +22 -0
- velbusaio/protocol.py +34 -17
- velbusaio/raw_message.py +6 -6
- velbusaio/util.py +4 -0
- velbusaio/vlp_reader.py +249 -0
- velbus_aio-2023.12.0.dist-info/RECORD +0 -103
- velbusaio/moduleprotocol/protocol.json +0 -26507
- {velbus_aio-2023.12.0.dist-info → velbus_aio-2025.11.0.dist-info/licenses}/LICENSE +0 -0
velbusaio/handler.py
CHANGED
|
@@ -2,21 +2,30 @@
|
|
|
2
2
|
Velbus packet handler
|
|
3
3
|
:Author maikel punie <maikel.punie@gmail.com>
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
7
8
|
import asyncio
|
|
9
|
+
import importlib.resources
|
|
8
10
|
import json
|
|
9
11
|
import logging
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
+
import os
|
|
13
|
+
import pathlib
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
12
17
|
|
|
13
|
-
import
|
|
18
|
+
from aiofile import async_open
|
|
14
19
|
|
|
15
20
|
from velbusaio.command_registry import commandRegistry
|
|
16
|
-
from velbusaio.
|
|
17
|
-
|
|
21
|
+
from velbusaio.const import (
|
|
22
|
+
SCAN_MODULEINFO_TIMEOUT_INITIAL,
|
|
23
|
+
SCAN_MODULEINFO_TIMEOUT_INTERVAL,
|
|
24
|
+
SCAN_MODULETYPE_TIMEOUT,
|
|
25
|
+
)
|
|
18
26
|
from velbusaio.messages.module_subtype import ModuleSubTypeMessage
|
|
19
|
-
from velbusaio.messages.module_type import ModuleTypeMessage
|
|
27
|
+
from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
|
|
28
|
+
from velbusaio.module import Module
|
|
20
29
|
from velbusaio.raw_message import RawMessage
|
|
21
30
|
|
|
22
31
|
if TYPE_CHECKING:
|
|
@@ -25,32 +34,189 @@ if TYPE_CHECKING:
|
|
|
25
34
|
|
|
26
35
|
class PacketHandler:
|
|
27
36
|
"""
|
|
28
|
-
The
|
|
37
|
+
The PacketHandler class
|
|
29
38
|
"""
|
|
30
39
|
|
|
31
40
|
def __init__(
|
|
32
41
|
self,
|
|
33
|
-
writer: Callable[[Message], Awaitable[None]],
|
|
34
42
|
velbus: Velbus,
|
|
43
|
+
one_address: int | None = None,
|
|
35
44
|
) -> None:
|
|
36
|
-
self._log = logging.getLogger("velbus-
|
|
37
|
-
self.
|
|
45
|
+
self._log = logging.getLogger("velbus-handler")
|
|
46
|
+
self._log.setLevel(logging.DEBUG)
|
|
38
47
|
self._velbus = velbus
|
|
48
|
+
self._one_address = one_address
|
|
49
|
+
self._typeResponseReceived = asyncio.Event()
|
|
50
|
+
self._scanLock = asyncio.Lock()
|
|
51
|
+
self._fullScanLock = asyncio.Lock()
|
|
52
|
+
self._modulescan_address = 0
|
|
39
53
|
self._scan_complete = False
|
|
40
|
-
self.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
self._scan_delay_msec = 0
|
|
55
|
+
self.__scan_found_addresses: dict[int, ModuleTypeMessage | None] | None = None
|
|
56
|
+
|
|
57
|
+
async def read_protocol_data(self):
|
|
58
|
+
if sys.version_info >= (3, 13):
|
|
59
|
+
with importlib.resources.path(
|
|
60
|
+
__name__, "module_spec/broadcast.json"
|
|
61
|
+
) as fspath:
|
|
62
|
+
async with async_open(fspath) as protocol_file:
|
|
63
|
+
self.broadcast = json.loads(await protocol_file.read())
|
|
64
|
+
with importlib.resources.path(
|
|
65
|
+
__name__, "module_spec/ignore.json"
|
|
66
|
+
) as fspath:
|
|
67
|
+
async with async_open(fspath) as protocol_file:
|
|
68
|
+
self.ignore = json.loads(await protocol_file.read())
|
|
69
|
+
else:
|
|
70
|
+
async with async_open(
|
|
71
|
+
str(
|
|
72
|
+
importlib.resources.files(__name__.split(".")[0]).joinpath(
|
|
73
|
+
"module_spec/broadcast.json"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
) as protocol_file:
|
|
77
|
+
self.broadcast = json.loads(await protocol_file.read())
|
|
78
|
+
async with async_open(
|
|
79
|
+
str(
|
|
80
|
+
importlib.resources.files(__name__.split(".")[0]).joinpath(
|
|
81
|
+
"module_spec/ignore.json"
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
) as protocol_file:
|
|
85
|
+
self.ignore = json.loads(await protocol_file.read())
|
|
86
|
+
|
|
87
|
+
def empty_cache(self) -> bool:
|
|
88
|
+
if (
|
|
89
|
+
len(
|
|
90
|
+
[
|
|
91
|
+
name
|
|
92
|
+
for name in os.listdir(f"{self._velbus.get_cache_dir()}")
|
|
93
|
+
if os.path.isfile(f"{self._velbus.get_cache_dir()}/{name}")
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
== 0
|
|
97
|
+
):
|
|
98
|
+
return True
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
async def scan(self, reload_cache: bool = False) -> None:
|
|
102
|
+
start_address = 1
|
|
103
|
+
max_address = 254 + 1
|
|
104
|
+
if self._one_address is not None:
|
|
105
|
+
start_address = self._one_address
|
|
106
|
+
max_address = self._one_address + 1
|
|
107
|
+
self._log.info(
|
|
108
|
+
f"Scanning only one address {self._one_address} ({self._one_address:#02x})"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
self._log.info("Start module scan")
|
|
112
|
+
async with self._fullScanLock:
|
|
113
|
+
start_time = time.perf_counter()
|
|
114
|
+
self._scan_complete = False
|
|
115
|
+
|
|
116
|
+
self._log.debug("Waiting for Velbus bus to be ready to scan...")
|
|
117
|
+
await self._velbus.wait_on_all_messages_sent_async() # don't start a scan while messages are still in the queue
|
|
118
|
+
self._log.debug("Velbus bus is ready to scan!")
|
|
119
|
+
|
|
120
|
+
self._log.info("Sending scan type requests to all addresses...")
|
|
121
|
+
start_scan_time = time.perf_counter()
|
|
122
|
+
self.__scan_found_addresses = {}
|
|
123
|
+
for address in range(start_address, max_address):
|
|
124
|
+
cfile = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
|
|
125
|
+
if reload_cache and os.path.isfile(cfile):
|
|
126
|
+
self._log.info(
|
|
127
|
+
f"Reloading cache for address {address} ({address:#02x})"
|
|
128
|
+
)
|
|
129
|
+
os.remove(cfile)
|
|
130
|
+
|
|
131
|
+
self.__scan_found_addresses[address] = None
|
|
132
|
+
async with self._scanLock:
|
|
133
|
+
await self._velbus.sendTypeRequestMessage(address)
|
|
134
|
+
|
|
135
|
+
await self._velbus.wait_on_all_messages_sent_async()
|
|
136
|
+
scan_time = time.perf_counter() - start_scan_time
|
|
137
|
+
self._log.info(
|
|
138
|
+
f"Sent scan type requests to all addresses in {scan_time:.2f}. Going to wait for responses..."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
await asyncio.sleep(SCAN_MODULETYPE_TIMEOUT / 1000) # wait for responses
|
|
142
|
+
|
|
143
|
+
self._log.info(
|
|
144
|
+
"Waiting for responses done. Going to check for responses..."
|
|
145
|
+
)
|
|
146
|
+
for address in range(start_address, max_address):
|
|
147
|
+
start_module_scan = time.perf_counter()
|
|
148
|
+
module_type_message: ModuleTypeMessage | None = (
|
|
149
|
+
self.__scan_found_addresses[address]
|
|
150
|
+
)
|
|
151
|
+
module: Module | None = None
|
|
152
|
+
if module_type_message is None:
|
|
153
|
+
self._log.debug(
|
|
154
|
+
f"No module found at address {address} ({address:#02x}). Skipping it."
|
|
155
|
+
)
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
self._log.info(
|
|
159
|
+
f"Found module at address {address} ({address:#02x}): {module_type_message.module_type_name()}"
|
|
160
|
+
)
|
|
161
|
+
# cache_file = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
|
|
162
|
+
# TODO: check if cached file module type is the same?
|
|
163
|
+
await self._handle_module_type(module_type_message)
|
|
164
|
+
async with self._scanLock:
|
|
165
|
+
module = self._velbus.get_module(address)
|
|
166
|
+
|
|
167
|
+
if module is None:
|
|
168
|
+
self._log.info(
|
|
169
|
+
f"Module at address {address} ({address:#02x}) could not be loaded. Skipping it."
|
|
170
|
+
)
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
self._log.debug(
|
|
175
|
+
f"Module {module.get_address()} ({module.get_address():#02x}) detected: start loading"
|
|
176
|
+
)
|
|
177
|
+
await asyncio.wait_for(
|
|
178
|
+
module.load(from_cache=True),
|
|
179
|
+
SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
|
|
180
|
+
)
|
|
181
|
+
self._scan_delay_msec = module.get_initial_timeout()
|
|
182
|
+
while self._scan_delay_msec > 50 and not await module.is_loaded():
|
|
183
|
+
# self._log.debug(
|
|
184
|
+
# f"\t... waiting {self._scan_delay_msec} is_loaded={await module.is_loaded()}"
|
|
185
|
+
# )
|
|
186
|
+
self._scan_delay_msec = self._scan_delay_msec - 50
|
|
187
|
+
await asyncio.sleep(0.05)
|
|
188
|
+
module_scan_time = time.perf_counter() - start_module_scan
|
|
189
|
+
self._log.info(
|
|
190
|
+
f"Scan module {address} ({address:#02x}, {module.get_type_name()}) completed in {module_scan_time:.2f}, module loaded={await module.is_loaded()}"
|
|
191
|
+
)
|
|
192
|
+
await module.wait_for_status_messages()
|
|
193
|
+
except asyncio.TimeoutError:
|
|
194
|
+
self._log.error(
|
|
195
|
+
f"Module {address} ({address:#02x}) did not respond to info requests after successful type request"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
self._scan_complete = True
|
|
199
|
+
total_time = time.perf_counter() - start_time
|
|
200
|
+
self._log.info(f"Module scan completed in {total_time:.2f} seconds")
|
|
201
|
+
|
|
202
|
+
async def __handle_module_type_response_async(self, rawmsg: RawMessage) -> None:
|
|
203
|
+
"""
|
|
204
|
+
Handle a received module type response packet
|
|
205
|
+
"""
|
|
206
|
+
address = rawmsg.address
|
|
207
|
+
|
|
208
|
+
if self.__scan_found_addresses is None:
|
|
209
|
+
self._log.warning(
|
|
210
|
+
f"Received module type response for address {address} ({address:#02x}) but no scan in progress"
|
|
211
|
+
)
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
tmsg: ModuleTypeMessage = ModuleTypeMessage()
|
|
215
|
+
tmsg.populate(rawmsg.priority, address, rawmsg.rtr, rawmsg.data_only)
|
|
216
|
+
self._log.debug(
|
|
217
|
+
f"A '{tmsg.module_type_name()}' ({tmsg.module_type:#02x}) lives on address {address} ({address:#02x})"
|
|
218
|
+
)
|
|
219
|
+
self.__scan_found_addresses[address] = tmsg
|
|
54
220
|
|
|
55
221
|
async def handle(self, rawmsg: RawMessage) -> None:
|
|
56
222
|
"""
|
|
@@ -67,153 +233,119 @@ class PacketHandler:
|
|
|
67
233
|
command_value = rawmsg.command
|
|
68
234
|
data = rawmsg.data_only
|
|
69
235
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
236
|
+
# handle module type response message
|
|
237
|
+
if command_value == 0xFF:
|
|
238
|
+
await self.__handle_module_type_response_async(rawmsg)
|
|
239
|
+
|
|
240
|
+
# handle module subtype response message
|
|
75
241
|
elif command_value in (0xB0, 0xA7, 0xA6) and not self._scan_complete:
|
|
76
|
-
msg = ModuleSubTypeMessage()
|
|
242
|
+
msg: ModuleSubTypeMessage = ModuleSubTypeMessage()
|
|
77
243
|
msg.populate(priority, address, rtr, data)
|
|
78
|
-
|
|
79
244
|
if command_value == 0xB0:
|
|
80
245
|
msg.sub_address_offset = 0
|
|
81
246
|
elif command_value == 0xA7:
|
|
82
247
|
msg.sub_address_offset = 4
|
|
83
248
|
elif command_value == 0xA6:
|
|
84
249
|
msg.sub_address_offset = 8
|
|
85
|
-
|
|
86
|
-
|
|
250
|
+
async with self._scanLock:
|
|
251
|
+
self._scan_delay_msec += SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
252
|
+
self._handle_module_subtype(msg)
|
|
87
253
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
elif command_value in self.pdata["MessagesBroadCast"]:
|
|
254
|
+
# ignore broadcast
|
|
255
|
+
elif command_value in self.broadcast:
|
|
91
256
|
self._log.debug(
|
|
92
257
|
"Received broadcast message {} from {}, ignoring".format(
|
|
93
|
-
self.
|
|
258
|
+
self.broadcast[str(command_value).upper()], address
|
|
94
259
|
)
|
|
95
260
|
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
self._log.debug(f"Received {msg}")
|
|
103
|
-
# send the message to the modules
|
|
104
|
-
await self._velbus.get_module(msg.address).on_message(msg)
|
|
105
|
-
else:
|
|
106
|
-
self._log.warning(
|
|
107
|
-
"NOT FOUND IN command_registry: addr={} cmd={} packet={}".format(
|
|
108
|
-
address, command_value, ":".join(format(x, "02x") for x in data)
|
|
109
|
-
)
|
|
110
|
-
)
|
|
111
|
-
elif self._scan_complete:
|
|
112
|
-
# this should only happen once the scan is complete, of its not complete suspended the error message
|
|
113
|
-
self._log.warning(
|
|
114
|
-
"UNKNOWN module, you should initialize a full new velbus scan: packet={}, address={}, modules={}".format(
|
|
115
|
-
":".join(format(x, "02x") for x in data),
|
|
116
|
-
address,
|
|
117
|
-
self._velbus.get_modules().keys(),
|
|
261
|
+
|
|
262
|
+
# ignore messages
|
|
263
|
+
elif command_value in self.ignore:
|
|
264
|
+
self._log.debug(
|
|
265
|
+
"Received ignored message {} from {}, ignoring".format(
|
|
266
|
+
self.ignore[str(command_value).upper()], address
|
|
118
267
|
)
|
|
119
268
|
)
|
|
120
269
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# if "Value" in todo:
|
|
154
|
-
# result["Value"] = todo["Value"]
|
|
155
|
-
# if "Convert" in todo:
|
|
156
|
-
# result["ValueList"] = []
|
|
157
|
-
# if todo["Convert"] == "Decimal":
|
|
158
|
-
# result["ValueList"].append(int(byte))
|
|
159
|
-
# elif todo["Convert"] == "Counter":
|
|
160
|
-
# result["ValueList"].append(f"{byte:02x}")
|
|
161
|
-
# elif todo["Convert"] == "Temperature":
|
|
162
|
-
# print("CONVERT temperature")
|
|
163
|
-
# elif todo["Convert"] == "Divider":
|
|
164
|
-
# bin_str = f"{byte:08b}"
|
|
165
|
-
# chan = bin_str[6:]
|
|
166
|
-
# val = bin_str[:5]
|
|
167
|
-
# print(f"CONVERT Divider {chan} {val}")
|
|
168
|
-
# elif todo["Convert"] == "Channel":
|
|
169
|
-
# print("CONVERT Channel")
|
|
170
|
-
# elif todo["Convert"] == "ChannelBit":
|
|
171
|
-
# print("CONVERT ChannelBit")
|
|
172
|
-
# elif todo["Convert"].startswith("ChannelBitStatus"):
|
|
173
|
-
# print("CONVERT ChannelBitStatus")
|
|
174
|
-
# else:
|
|
175
|
-
# self._log.error("UNKNOWN convert requested: {}".format(todo["Convert"]))
|
|
176
|
-
# return result
|
|
177
|
-
|
|
178
|
-
async def _handle_module_type(self, msg: Message) -> None:
|
|
270
|
+
# handle other messages for modules that are already scanned
|
|
271
|
+
else:
|
|
272
|
+
module = None
|
|
273
|
+
async with self._scanLock:
|
|
274
|
+
module = self._velbus.get_module(address)
|
|
275
|
+
if module is not None:
|
|
276
|
+
module_type = module.get_type()
|
|
277
|
+
if commandRegistry.has_command(int(command_value), module_type):
|
|
278
|
+
command = commandRegistry.get_command(command_value, module_type)
|
|
279
|
+
if not command:
|
|
280
|
+
return
|
|
281
|
+
msg = command()
|
|
282
|
+
msg.populate(priority, address, rtr, data)
|
|
283
|
+
# restart the info completion time when info message received
|
|
284
|
+
if command_value in (
|
|
285
|
+
0xF0,
|
|
286
|
+
0xF1,
|
|
287
|
+
0xF2,
|
|
288
|
+
0xFB,
|
|
289
|
+
0xFE,
|
|
290
|
+
0xCC,
|
|
291
|
+
): # names, memory data, memory block
|
|
292
|
+
self._scan_delay_msec += SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
293
|
+
# self._log.debug(f"Restart timeout {msg}")
|
|
294
|
+
# send the message to the modules
|
|
295
|
+
await module.on_message(msg)
|
|
296
|
+
else:
|
|
297
|
+
self._log.warning(f"NOT FOUND IN command_registry: {rawmsg}")
|
|
298
|
+
|
|
299
|
+
async def _handle_module_type(
|
|
300
|
+
self, msg: ModuleTypeMessage | ModuleType2Message
|
|
301
|
+
) -> None:
|
|
179
302
|
"""
|
|
180
303
|
load the module data
|
|
181
304
|
"""
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
305
|
+
if msg is not None:
|
|
306
|
+
module = self._velbus.get_module(msg.address)
|
|
307
|
+
if module is None:
|
|
308
|
+
# data = keys_exists(self.pdata, "ModuleTypes", h2(msg.module_type))
|
|
309
|
+
# if not data:
|
|
310
|
+
# self._log.warning(f"Module not recognized: {msg.module_type}")
|
|
311
|
+
# return
|
|
312
|
+
await self._velbus.add_module(
|
|
313
|
+
msg.address,
|
|
314
|
+
msg.module_type,
|
|
315
|
+
memorymap=msg.memory_map_version,
|
|
316
|
+
build_year=msg.build_year,
|
|
317
|
+
build_week=msg.build_week,
|
|
318
|
+
serial=msg.serial,
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
self._log.debug(
|
|
322
|
+
f"***Module already exists scanAddr={self._modulescan_address} addr={msg.address} {msg}"
|
|
323
|
+
)
|
|
196
324
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
325
|
+
# else:
|
|
326
|
+
# self._log.debug("*** handle_module_type called without response message")
|
|
327
|
+
|
|
328
|
+
def _handle_module_subtype(self, msg: ModuleSubTypeMessage) -> None:
|
|
329
|
+
module = self._velbus.get_module(msg.address)
|
|
330
|
+
if module is not None:
|
|
331
|
+
addrList = {
|
|
332
|
+
(msg.sub_address_offset + 1): msg.sub_address_1,
|
|
333
|
+
(msg.sub_address_offset + 2): msg.sub_address_2,
|
|
334
|
+
(msg.sub_address_offset + 3): msg.sub_address_3,
|
|
335
|
+
(msg.sub_address_offset + 4): msg.sub_address_4,
|
|
336
|
+
}
|
|
337
|
+
self._velbus.add_submodules(module, addrList)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# def _channel_convert(self, module: str, channel: str, ctype: str) -> None | int:
|
|
341
|
+
# data = keys_exists(
|
|
342
|
+
# self.pdata, "ModuleTypes", h2(module), "ChannelNumbers", ctype
|
|
343
|
+
# )
|
|
344
|
+
# if data and "Map" in data and h2(channel) in data["Map"]:
|
|
345
|
+
# return data["Map"][h2(channel)]
|
|
346
|
+
# if data and "Convert" in data:
|
|
347
|
+
# return int(channel)
|
|
348
|
+
# for offset in range(0, 8):
|
|
349
|
+
# if channel & (1 << offset):
|
|
350
|
+
# return offset + 1
|
|
351
|
+
# return None
|
velbusaio/helpers.py
CHANGED
velbusaio/message.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""
|
|
2
2
|
The velbus abstract message class
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
7
|
+
import enum
|
|
6
8
|
import json
|
|
7
|
-
from typing import Optional
|
|
8
9
|
|
|
9
10
|
from velbusaio.const import PRIORITY_FIRMWARE, PRIORITY_HIGH, PRIORITY_LOW
|
|
10
11
|
|
|
@@ -20,19 +21,19 @@ class Message:
|
|
|
20
21
|
Base Velbus message
|
|
21
22
|
"""
|
|
22
23
|
|
|
23
|
-
def __init__(self, address: int =
|
|
24
|
+
def __init__(self, address: int = 0) -> None:
|
|
24
25
|
self.priority = PRIORITY_LOW
|
|
25
|
-
self.address =
|
|
26
|
-
self.rtr = False
|
|
26
|
+
self.address: int = 0
|
|
27
|
+
self.rtr: bool = False
|
|
27
28
|
self.data = bytearray()
|
|
28
29
|
self.set_defaults(address)
|
|
29
30
|
|
|
30
|
-
def set_attributes(self, priority: int, address: int, rtr:
|
|
31
|
+
def set_attributes(self, priority: int, address: int, rtr: bool) -> None:
|
|
31
32
|
self.priority = priority
|
|
32
33
|
self.address = address
|
|
33
34
|
self.rtr = rtr
|
|
34
35
|
|
|
35
|
-
def populate(self, priority: int, address: int, rtr:
|
|
36
|
+
def populate(self, priority: int, address: int, rtr: bool, data: int) -> None:
|
|
36
37
|
raise NotImplementedError
|
|
37
38
|
|
|
38
39
|
def set_defaults(self, address: int | None) -> None:
|
|
@@ -65,8 +66,13 @@ class Message:
|
|
|
65
66
|
continue
|
|
66
67
|
if callable(getattr(self, key)) or key.startswith("__"):
|
|
67
68
|
del me[key]
|
|
68
|
-
if isinstance(me[key], (bytes, bytearray)):
|
|
69
|
-
me[key] = str(me[key]
|
|
69
|
+
if isinstance(me[key], (bytes, bytearray, enum.Enum)):
|
|
70
|
+
me[key] = str(me[key])
|
|
71
|
+
else:
|
|
72
|
+
try:
|
|
73
|
+
json.dumps(me[key]) # Test if the value is JSON serializable
|
|
74
|
+
except (TypeError, ValueError):
|
|
75
|
+
me[key] = str(me[key]) # Convert non-serializable objects to string
|
|
70
76
|
return me
|
|
71
77
|
|
|
72
78
|
def to_json(self) -> str:
|
velbusaio/messages/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:author: Thomas Delaet <thomas@delaet.org>
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
from velbusaio.messages.blind_status import BlindStatusMessage, BlindStatusNgMessage
|
|
@@ -32,6 +33,7 @@ from velbusaio.messages.channel_name_request import (
|
|
|
32
33
|
from velbusaio.messages.clear_led import ClearLedMessage
|
|
33
34
|
from velbusaio.messages.counter_status import CounterStatusMessage
|
|
34
35
|
from velbusaio.messages.counter_status_request import CounterStatusRequestMessage
|
|
36
|
+
from velbusaio.messages.counter_value import CounterValueMessage
|
|
35
37
|
from velbusaio.messages.cover_down import CoverDownMessage, CoverDownMessage2
|
|
36
38
|
from velbusaio.messages.cover_off import CoverOffMessage, CoverOffMessage2
|
|
37
39
|
from velbusaio.messages.cover_position import CoverPosMessage
|
|
@@ -50,13 +52,15 @@ from velbusaio.messages.memo_text import MemoTextMessage
|
|
|
50
52
|
from velbusaio.messages.memory_data import MemoryDataMessage
|
|
51
53
|
from velbusaio.messages.memory_data_block import MemoryDataBlockMessage
|
|
52
54
|
from velbusaio.messages.memory_dump_request import MemoryDumpRequestMessage
|
|
53
|
-
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
54
55
|
from velbusaio.messages.module_status import ModuleStatusMessage, ModuleStatusMessage2
|
|
55
56
|
from velbusaio.messages.module_status_request import ModuleStatusRequestMessage
|
|
56
57
|
from velbusaio.messages.module_subtype import ModuleSubTypeMessage
|
|
57
|
-
from velbusaio.messages.module_type import
|
|
58
|
+
from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
|
|
58
59
|
from velbusaio.messages.module_type_request import ModuleTypeRequestMessage
|
|
60
|
+
from velbusaio.messages.psu_load import PsuLoadMessage
|
|
61
|
+
from velbusaio.messages.psu_values import PsuValuesMessage
|
|
59
62
|
from velbusaio.messages.push_button_status import PushButtonStatusMessage
|
|
63
|
+
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
60
64
|
from velbusaio.messages.read_data_block_from_memory import (
|
|
61
65
|
ReadDataBlockFromMemoryMessage,
|
|
62
66
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:author: Tom Dupré <gitd8400@gmail.com>
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import json
|
|
@@ -12,7 +13,7 @@ COMMAND_CODE = 0xEC
|
|
|
12
13
|
DSTATUS = {0: "off", 1: "up", 2: "down"}
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
@register(COMMAND_CODE, ["VMB1BLE", "VMB2BLE", "VMB1BLS"])
|
|
16
|
+
@register(COMMAND_CODE, ["VMB1BLE", "VMB2BLE", "VMB1BLS", "VMB2BLE-10"])
|
|
16
17
|
class BlindStatusNgMessage(Message):
|
|
17
18
|
"""
|
|
18
19
|
sent by: VMB2BLE
|
|
@@ -38,7 +39,7 @@ class BlindStatusNgMessage(Message):
|
|
|
38
39
|
self.channel = self.byte_to_channel(data[0])
|
|
39
40
|
self.timeout = data[1] # Omzetter seconden ????
|
|
40
41
|
self.status = data[2]
|
|
41
|
-
self.position = data[4]
|
|
42
|
+
self.position = data[4] # 0..255 (0=open, 255=closed)
|
|
42
43
|
|
|
43
44
|
def to_json(self):
|
|
44
45
|
"""
|
|
@@ -47,6 +48,7 @@ class BlindStatusNgMessage(Message):
|
|
|
47
48
|
json_dict = self.to_json_basic()
|
|
48
49
|
json_dict["channel"] = self.channel
|
|
49
50
|
json_dict["timeout"] = self.timeout
|
|
51
|
+
json_dict["position"] = self.position
|
|
50
52
|
json_dict["status"] = DSTATUS[self.status]
|
|
51
53
|
return json.dumps(json_dict)
|
|
52
54
|
|
velbusaio/messages/bus_active.py
CHANGED