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.
- scripts/parse_specs.py +156 -0
- velbus_aio-2025.11.0.dist-info/METADATA +71 -0
- velbus_aio-2025.11.0.dist-info/RECORD +194 -0
- {velbus_aio-2021.8.7.dist-info → velbus_aio-2025.11.0.dist-info}/WHEEL +1 -1
- velbus_aio-2025.11.0.dist-info/top_level.txt +3 -0
- velbusaio/channels.py +443 -109
- velbusaio/command_registry.py +126 -13
- velbusaio/const.py +36 -12
- velbusaio/controller.py +252 -177
- velbusaio/discovery.py +2 -2
- velbusaio/exceptions.py +22 -0
- velbusaio/handler.py +311 -145
- velbusaio/helpers.py +6 -18
- velbusaio/message.py +46 -132
- velbusaio/messages/__init__.py +12 -2
- velbusaio/messages/blind_status.py +16 -25
- velbusaio/messages/bus_active.py +3 -9
- velbusaio/messages/bus_error_counter_status.py +3 -4
- velbusaio/messages/bus_error_counter_status_request.py +3 -4
- velbusaio/messages/bus_off.py +3 -4
- velbusaio/messages/channel_name_part1.py +49 -33
- velbusaio/messages/channel_name_part2.py +49 -33
- velbusaio/messages/channel_name_part3.py +49 -33
- velbusaio/messages/channel_name_request.py +26 -12
- velbusaio/messages/clear_led.py +3 -4
- velbusaio/messages/counter_status.py +3 -17
- velbusaio/messages/counter_status_request.py +6 -6
- velbusaio/messages/counter_value.py +44 -0
- velbusaio/messages/cover_down.py +4 -29
- velbusaio/messages/cover_off.py +5 -29
- velbusaio/messages/cover_position.py +4 -19
- velbusaio/messages/cover_up.py +4 -27
- velbusaio/messages/dali_device_settings.py +178 -0
- velbusaio/messages/dali_device_settings_request.py +53 -0
- velbusaio/messages/dali_dim_value_status.py +44 -0
- velbusaio/messages/dimmer_channel_status.py +6 -19
- velbusaio/messages/dimmer_status.py +14 -31
- velbusaio/messages/edge_set_color.py +114 -0
- velbusaio/messages/edge_set_custom_color.py +56 -0
- velbusaio/messages/fast_blinking_led.py +3 -4
- velbusaio/messages/forced_off.py +3 -4
- velbusaio/messages/forced_on.py +3 -4
- velbusaio/messages/interface_status_request.py +3 -4
- velbusaio/messages/ir_receiver_status.py +18 -0
- velbusaio/messages/kwh_status.py +3 -19
- velbusaio/messages/light_value_request.py +3 -4
- velbusaio/messages/memo_text.py +3 -5
- velbusaio/messages/memory_data.py +3 -16
- velbusaio/messages/memory_data_block.py +3 -4
- velbusaio/messages/memory_dump_request.py +3 -4
- velbusaio/messages/module_status.py +107 -55
- velbusaio/messages/module_status_request.py +7 -6
- velbusaio/messages/module_subtype.py +11 -19
- velbusaio/messages/module_type.py +132 -21
- 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 +3 -16
- velbusaio/messages/raw.py +74 -0
- velbusaio/messages/read_data_block_from_memory.py +3 -4
- velbusaio/messages/read_data_from_memory.py +3 -4
- velbusaio/messages/realtime_clock_status_request.py +3 -4
- velbusaio/messages/receive_buffer_full.py +3 -4
- velbusaio/messages/receive_ready.py +3 -4
- velbusaio/messages/relay_status.py +13 -42
- velbusaio/messages/restore_dimmer.py +33 -24
- velbusaio/messages/select_program.py +35 -0
- velbusaio/messages/sensor_settings_request.py +3 -4
- velbusaio/messages/sensor_temp_request.py +3 -4
- velbusaio/messages/sensor_temperature.py +15 -19
- velbusaio/messages/set_date.py +10 -30
- velbusaio/messages/set_daylight_saving.py +8 -24
- velbusaio/messages/set_dimmer.py +43 -41
- velbusaio/messages/set_led.py +3 -4
- velbusaio/messages/set_realtime_clock.py +10 -30
- velbusaio/messages/set_temperature.py +3 -4
- velbusaio/messages/slider_status.py +16 -20
- velbusaio/messages/slow_blinking_led.py +3 -4
- velbusaio/messages/start_relay_blinking_timer.py +3 -4
- velbusaio/messages/start_relay_timer.py +3 -4
- velbusaio/messages/switch_relay_off.py +3 -16
- velbusaio/messages/switch_relay_on.py +3 -16
- velbusaio/messages/switch_to_comfort.py +4 -15
- velbusaio/messages/switch_to_day.py +4 -15
- velbusaio/messages/switch_to_night.py +4 -15
- velbusaio/messages/switch_to_safe.py +4 -15
- velbusaio/messages/temp_sensor_settings_part1.py +3 -4
- velbusaio/messages/temp_sensor_settings_part2.py +27 -0
- velbusaio/messages/temp_sensor_settings_part3.py +27 -0
- velbusaio/messages/temp_sensor_settings_part4.py +27 -0
- velbusaio/messages/temp_sensor_settings_request.py +3 -4
- velbusaio/messages/temp_sensor_status.py +34 -35
- velbusaio/messages/temp_set_cooling.py +3 -13
- velbusaio/messages/temp_set_heating.py +3 -13
- velbusaio/messages/update_led_status.py +3 -4
- velbusaio/messages/very_fast_blinking_led.py +3 -4
- velbusaio/messages/write_data_to_memory.py +3 -4
- velbusaio/messages/write_memory_block.py +3 -4
- velbusaio/messages/write_module_address_and_serial_number.py +3 -4
- velbusaio/module.py +680 -158
- 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 +243 -0
- velbusaio/py.typed +0 -0
- velbusaio/raw_message.py +149 -0
- velbusaio/util.py +55 -0
- velbusaio/vlp_reader.py +249 -0
- velbus_aio-2021.8.7.dist-info/METADATA +0 -66
- velbus_aio-2021.8.7.dist-info/RECORD +0 -90
- velbus_aio-2021.8.7.dist-info/top_level.txt +0 -1
- velbusaio/messages/meteo_raw.py +0 -52
- velbusaio/module_registry.py +0 -64
- velbusaio/moduleprotocol/protocol.json +0 -25540
- velbusaio/parser.py +0 -142
- {velbus_aio-2021.8.7.dist-info → velbus_aio-2025.11.0.dist-info/licenses}/LICENSE +0 -0
velbusaio/handler.py
CHANGED
|
@@ -2,184 +2,350 @@
|
|
|
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
|
|
|
8
|
+
import asyncio
|
|
9
|
+
import importlib.resources
|
|
7
10
|
import json
|
|
8
11
|
import logging
|
|
9
|
-
import
|
|
12
|
+
import os
|
|
13
|
+
import pathlib
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
from typing import TYPE_CHECKING
|
|
10
17
|
|
|
11
|
-
import
|
|
18
|
+
from aiofile import async_open
|
|
12
19
|
|
|
13
20
|
from velbusaio.command_registry import commandRegistry
|
|
14
|
-
from velbusaio.const import
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
from velbusaio.const import (
|
|
22
|
+
SCAN_MODULEINFO_TIMEOUT_INITIAL,
|
|
23
|
+
SCAN_MODULEINFO_TIMEOUT_INTERVAL,
|
|
24
|
+
SCAN_MODULETYPE_TIMEOUT,
|
|
25
|
+
)
|
|
17
26
|
from velbusaio.messages.module_subtype import ModuleSubTypeMessage
|
|
18
|
-
from velbusaio.messages.module_type import ModuleTypeMessage
|
|
27
|
+
from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
|
|
28
|
+
from velbusaio.module import Module
|
|
29
|
+
from velbusaio.raw_message import RawMessage
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from velbusaio.controller import Velbus
|
|
19
33
|
|
|
20
34
|
|
|
21
35
|
class PacketHandler:
|
|
22
36
|
"""
|
|
23
|
-
The
|
|
37
|
+
The PacketHandler class
|
|
24
38
|
"""
|
|
25
39
|
|
|
26
|
-
def __init__(
|
|
27
|
-
self
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
velbus: Velbus,
|
|
43
|
+
one_address: int | None = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
self._log = logging.getLogger("velbus-handler")
|
|
28
46
|
self._log.setLevel(logging.DEBUG)
|
|
29
|
-
self._writer = writer
|
|
30
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
|
|
31
53
|
self._scan_complete = False
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
) as protocol_file:
|
|
35
|
-
self.pdata = json.load(protocol_file)
|
|
54
|
+
self._scan_delay_msec = 0
|
|
55
|
+
self.__scan_found_addresses: dict[int, ModuleTypeMessage | None] | None = None
|
|
36
56
|
|
|
37
|
-
def
|
|
38
|
-
|
|
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())
|
|
39
86
|
|
|
40
|
-
def
|
|
41
|
-
|
|
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")
|
|
42
201
|
|
|
43
|
-
async def
|
|
202
|
+
async def __handle_module_type_response_async(self, rawmsg: RawMessage) -> None:
|
|
44
203
|
"""
|
|
45
|
-
Handle a
|
|
204
|
+
Handle a received module type response packet
|
|
46
205
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
220
|
+
|
|
221
|
+
async def handle(self, rawmsg: RawMessage) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Handle a received packet
|
|
224
|
+
"""
|
|
225
|
+
if rawmsg.address < 1 or rawmsg.address > 254:
|
|
53
226
|
return
|
|
54
|
-
if
|
|
227
|
+
if rawmsg.command is None:
|
|
55
228
|
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await self.
|
|
66
|
-
|
|
229
|
+
|
|
230
|
+
priority = rawmsg.priority
|
|
231
|
+
address = rawmsg.address
|
|
232
|
+
rtr = rawmsg.rtr
|
|
233
|
+
command_value = rawmsg.command
|
|
234
|
+
data = rawmsg.data_only
|
|
235
|
+
|
|
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
|
|
241
|
+
elif command_value in (0xB0, 0xA7, 0xA6) and not self._scan_complete:
|
|
242
|
+
msg: ModuleSubTypeMessage = ModuleSubTypeMessage()
|
|
243
|
+
msg.populate(priority, address, rtr, data)
|
|
244
|
+
if command_value == 0xB0:
|
|
245
|
+
msg.sub_address_offset = 0
|
|
246
|
+
elif command_value == 0xA7:
|
|
247
|
+
msg.sub_address_offset = 4
|
|
248
|
+
elif command_value == 0xA6:
|
|
249
|
+
msg.sub_address_offset = 8
|
|
250
|
+
async with self._scanLock:
|
|
251
|
+
self._scan_delay_msec += SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
252
|
+
self._handle_module_subtype(msg)
|
|
253
|
+
|
|
254
|
+
# ignore broadcast
|
|
255
|
+
elif command_value in self.broadcast:
|
|
67
256
|
self._log.debug(
|
|
68
257
|
"Received broadcast message {} from {}, ignoring".format(
|
|
69
|
-
self.
|
|
258
|
+
self.broadcast[str(command_value).upper()], address
|
|
70
259
|
)
|
|
71
260
|
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self._log.debug(f"Received {msg}")
|
|
79
|
-
# send the message to the modules
|
|
80
|
-
await (self._velbus.get_module(msg.address)).on_message(msg)
|
|
81
|
-
else:
|
|
82
|
-
self._log.warning(
|
|
83
|
-
"NOT FOUND IN command_registry: addr={} cmd={} packet={}".format(
|
|
84
|
-
address, command_value, ":".join(format(x, "02x") for x in data)
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
elif self._scan_complete:
|
|
88
|
-
# this should only happen once the scan is complete, of its not complete susppend the error message
|
|
89
|
-
self._log.warning(
|
|
90
|
-
"UNKNOWN module, you should iniitalize a full new velbus scan: packet={}, address={}, modules={}".format(
|
|
91
|
-
":".join(format(x, "02x") for x in data),
|
|
92
|
-
address,
|
|
93
|
-
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
|
|
94
267
|
)
|
|
95
268
|
)
|
|
96
269
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
elif todo["Convert"] == "Counter":
|
|
126
|
-
result["ValueList"].append(f"{byte:02x}")
|
|
127
|
-
elif todo["Convert"] == "Temperature":
|
|
128
|
-
print("CONVERT temperature")
|
|
129
|
-
elif todo["Convert"] == "Divider":
|
|
130
|
-
bin_str = f"{byte:08b}"
|
|
131
|
-
chan = bin_str[6:]
|
|
132
|
-
val = bin_str[:5]
|
|
133
|
-
print(f"CONVERT Divider {chan} {val}")
|
|
134
|
-
elif todo["Convert"] == "Channel":
|
|
135
|
-
print("CONVERT Channel")
|
|
136
|
-
elif todo["Convert"] == "ChannelBit":
|
|
137
|
-
print("CONVERT ChannelBit")
|
|
138
|
-
elif todo["Convert"].startswith("ChannelBitStatus"):
|
|
139
|
-
print("CONVERT ChannelBitStatus")
|
|
140
|
-
else:
|
|
141
|
-
self._log.error("UNKNOWN convert requested: {}".format(todo["Convert"]))
|
|
142
|
-
return result
|
|
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}")
|
|
143
298
|
|
|
144
|
-
async def _handle_module_type(
|
|
299
|
+
async def _handle_module_type(
|
|
300
|
+
self, msg: ModuleTypeMessage | ModuleType2Message
|
|
301
|
+
) -> None:
|
|
145
302
|
"""
|
|
146
303
|
load the module data
|
|
147
304
|
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
)
|
|
162
324
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Helper functions
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
9
|
+
from typing import Any
|
|
8
10
|
|
|
9
11
|
from velbusaio.const import CACHEDIR
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
def keys_exists(element, *keys) -> dict:
|
|
14
|
+
def keys_exists(element: dict[str, Any], *keys) -> dict:
|
|
13
15
|
"""
|
|
14
16
|
Check if *keys (nested) exists in `element` (dict).
|
|
15
17
|
"""
|
|
@@ -27,28 +29,14 @@ def keys_exists(element, *keys) -> dict:
|
|
|
27
29
|
return _element
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
def
|
|
31
|
-
"""
|
|
32
|
-
Calculate checksum of the given array.
|
|
33
|
-
The checksum is calculated by summing all values in an array, then performing the two's complement.
|
|
34
|
-
:param arr: The array of bytes of which the checksum has to be calculated of.
|
|
35
|
-
:return: The checksum of the given array.
|
|
36
|
-
"""
|
|
37
|
-
crc = sum(arr)
|
|
38
|
-
crc = crc ^ 255
|
|
39
|
-
crc = crc + 1
|
|
40
|
-
crc = crc & 255
|
|
41
|
-
return crc
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def h2(inp) -> str:
|
|
32
|
+
def h2(inp: int) -> str:
|
|
45
33
|
"""
|
|
46
|
-
Format as hex
|
|
34
|
+
Format as hex uppercase
|
|
47
35
|
"""
|
|
48
36
|
return format(inp, "02x").upper()
|
|
49
37
|
|
|
50
38
|
|
|
51
|
-
def handle_match(match_dict, data) -> dict:
|
|
39
|
+
def handle_match(match_dict: dict[str, dict[str, dict[str, str]]], data: int) -> dict:
|
|
52
40
|
"""
|
|
53
41
|
Handle memory match from the module data
|
|
54
42
|
"""
|