velbus-aio 2024.7.6__py3-none-any.whl → 2024.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.
Potentially problematic release.
This version of velbus-aio might be problematic. Click here for more details.
- {velbus_aio-2024.7.6.dist-info → velbus_aio-2024.11.0.dist-info}/METADATA +17 -5
- velbus_aio-2024.11.0.dist-info/RECORD +183 -0
- {velbus_aio-2024.7.6.dist-info → velbus_aio-2024.11.0.dist-info}/WHEEL +1 -1
- velbusaio/channels.py +16 -4
- velbusaio/command_registry.py +7 -5
- velbusaio/const.py +1 -1
- velbusaio/controller.py +8 -5
- velbusaio/handler.py +87 -61
- velbusaio/messages/__init__.py +2 -2
- velbusaio/messages/blind_status.py +1 -1
- velbusaio/messages/channel_name_part1.py +1 -0
- velbusaio/messages/channel_name_part2.py +1 -0
- velbusaio/messages/channel_name_part3.py +1 -0
- velbusaio/messages/cover_down.py +1 -1
- velbusaio/messages/cover_off.py +1 -1
- velbusaio/messages/cover_position.py +1 -1
- velbusaio/messages/cover_up.py +1 -1
- velbusaio/messages/edge_set_color.py +1 -0
- velbusaio/module.py +77 -29
- velbusaio/module_spec/01.json +50 -0
- velbusaio/module_spec/02.json +11 -0
- velbusaio/module_spec/03.json +18 -0
- velbusaio/module_spec/05.json +42 -0
- velbusaio/module_spec/06.json +105 -0
- velbusaio/module_spec/07.json +11 -0
- velbusaio/module_spec/08.json +30 -0
- velbusaio/module_spec/09.json +24 -0
- velbusaio/module_spec/0A.json +46 -0
- velbusaio/module_spec/0B.json +46 -0
- velbusaio/module_spec/0C.json +13 -0
- velbusaio/module_spec/0E.json +25 -0
- velbusaio/module_spec/0F.json +11 -0
- velbusaio/module_spec/10.json +104 -0
- velbusaio/module_spec/11.json +104 -0
- velbusaio/module_spec/12.json +67 -0
- velbusaio/module_spec/13.json +4 -0
- velbusaio/module_spec/14.json +11 -0
- velbusaio/module_spec/15.json +80 -0
- velbusaio/module_spec/16.json +119 -0
- velbusaio/module_spec/17.json +119 -0
- velbusaio/module_spec/18.json +119 -0
- velbusaio/module_spec/1A.json +73 -0
- velbusaio/module_spec/1B.json +100 -0
- velbusaio/module_spec/1D.json +83 -0
- velbusaio/module_spec/1E.json +295 -0
- velbusaio/module_spec/1F.json +167 -0
- velbusaio/module_spec/20.json +167 -0
- velbusaio/module_spec/21.json +291 -0
- velbusaio/module_spec/22.json +330 -0
- velbusaio/module_spec/23.json +129 -0
- velbusaio/module_spec/25.json +3 -0
- velbusaio/module_spec/28.json +419 -0
- velbusaio/module_spec/29.json +228 -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 +295 -0
- velbusaio/module_spec/2E.json +212 -0
- velbusaio/module_spec/2F.json +208 -0
- velbusaio/module_spec/30.json +46 -0
- velbusaio/module_spec/31.json +465 -0
- velbusaio/module_spec/32.json +365 -0
- velbusaio/module_spec/33.json +239 -0
- velbusaio/module_spec/34.json +157 -0
- velbusaio/module_spec/35.json +157 -0
- velbusaio/module_spec/36.json +157 -0
- velbusaio/module_spec/37.json +297 -0
- velbusaio/module_spec/38.json +102 -0
- velbusaio/module_spec/39.json +4 -0
- velbusaio/module_spec/3A.json +295 -0
- velbusaio/module_spec/3B.json +295 -0
- velbusaio/module_spec/3C.json +295 -0
- velbusaio/module_spec/3D.json +419 -0
- velbusaio/module_spec/3E.json +295 -0
- velbusaio/module_spec/3F.json +4 -0
- velbusaio/module_spec/40.json +4 -0
- velbusaio/module_spec/41.json +233 -0
- velbusaio/module_spec/42.json +4 -0
- velbusaio/module_spec/43.json +11 -0
- velbusaio/module_spec/44.json +26 -0
- velbusaio/module_spec/45.json +4 -0
- velbusaio/module_spec/48.json +104 -0
- velbusaio/module_spec/49.json +104 -0
- velbusaio/module_spec/4A.json +83 -0
- velbusaio/module_spec/4B.json +126 -0
- velbusaio/module_spec/4C.json +119 -0
- velbusaio/module_spec/4D.json +108 -0
- velbusaio/module_spec/4E.json +451 -0
- velbusaio/module_spec/4F.json +89 -0
- velbusaio/module_spec/50.json +89 -0
- velbusaio/module_spec/51.json +89 -0
- velbusaio/module_spec/52.json +189 -0
- velbusaio/module_spec/54.json +4 -0
- velbusaio/module_spec/55.json +4 -0
- velbusaio/module_spec/56.json +4 -0
- velbusaio/module_spec/57.json +222 -0
- velbusaio/module_spec/5A.json +4 -0
- velbusaio/module_spec/5C.json +87 -0
- velbusaio/module_spec/5F.json +66 -0
- velbusaio/module_spec/broadcast.json +67 -0
- velbusaio/protocol.py +1 -1
- velbus_aio-2024.7.6.dist-info/RECORD +0 -103
- velbusaio/protocol.json +0 -10194
- {velbus_aio-2024.7.6.dist-info → velbus_aio-2024.11.0.dist-info}/LICENSE +0 -0
- {velbus_aio-2024.7.6.dist-info → velbus_aio-2024.11.0.dist-info}/top_level.txt +0 -0
velbusaio/handler.py
CHANGED
|
@@ -5,30 +5,30 @@ Velbus packet handler
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from velbusaio.const import SCAN_MODULETYPE_TIMEOUT
|
|
9
|
-
from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INITIAL
|
|
10
|
-
from velbusaio.const import SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
11
|
-
|
|
12
8
|
import asyncio
|
|
9
|
+
import importlib.resources
|
|
13
10
|
import json
|
|
14
11
|
import logging
|
|
15
|
-
import threading
|
|
16
12
|
import os
|
|
17
13
|
import pathlib
|
|
14
|
+
import pprint
|
|
15
|
+
import sys
|
|
16
|
+
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
18
17
|
|
|
19
18
|
from aiofile import async_open
|
|
20
19
|
|
|
21
|
-
from typing import TYPE_CHECKING, Awaitable, Callable
|
|
22
|
-
import pkg_resources
|
|
23
|
-
|
|
24
20
|
from velbusaio.command_registry import commandRegistry
|
|
21
|
+
from velbusaio.const import (
|
|
22
|
+
SCAN_MODULEINFO_TIMEOUT_INITIAL,
|
|
23
|
+
SCAN_MODULEINFO_TIMEOUT_INTERVAL,
|
|
24
|
+
SCAN_MODULETYPE_TIMEOUT,
|
|
25
|
+
)
|
|
25
26
|
from velbusaio.helpers import h2, keys_exists
|
|
26
27
|
from velbusaio.message import Message
|
|
27
28
|
from velbusaio.messages.module_subtype import ModuleSubTypeMessage
|
|
28
|
-
from velbusaio.messages.module_type import
|
|
29
|
+
from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
|
|
29
30
|
from velbusaio.raw_message import RawMessage
|
|
30
31
|
|
|
31
|
-
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
from velbusaio.controller import Velbus
|
|
34
34
|
|
|
@@ -41,21 +41,34 @@ class PacketHandler:
|
|
|
41
41
|
def __init__(
|
|
42
42
|
self,
|
|
43
43
|
velbus: Velbus,
|
|
44
|
+
one_address: int | None = None,
|
|
44
45
|
) -> None:
|
|
45
46
|
self._log = logging.getLogger("velbus-handler")
|
|
46
47
|
self._log.setLevel(logging.DEBUG)
|
|
47
48
|
self._velbus = velbus
|
|
49
|
+
self._one_address = one_address
|
|
48
50
|
self._typeResponseReceived = asyncio.Event()
|
|
49
|
-
self._scanLock =
|
|
51
|
+
self._scanLock = asyncio.Lock()
|
|
50
52
|
self._modulescan_address = 0
|
|
51
53
|
self._scan_complete = False
|
|
52
54
|
self._scan_delay_msec = 0
|
|
53
55
|
|
|
54
56
|
async def read_protocol_data(self):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
if sys.version_info >= (3, 13):
|
|
58
|
+
with importlib.resources.path(
|
|
59
|
+
__name__, "module_spec/broadcast.json"
|
|
60
|
+
) as fspath:
|
|
61
|
+
async with async_open(fspath) as protocol_file:
|
|
62
|
+
self.broadcast = json.loads(await protocol_file.read())
|
|
63
|
+
else:
|
|
64
|
+
async with async_open(
|
|
65
|
+
str(
|
|
66
|
+
importlib.resources.files(__name__.split(".")[0]).joinpath(
|
|
67
|
+
"module_spec/broadcast.json"
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
) as protocol_file:
|
|
71
|
+
self.broadcast = json.loads(await protocol_file.read())
|
|
59
72
|
|
|
60
73
|
def empty_cache(self) -> bool:
|
|
61
74
|
if (
|
|
@@ -84,12 +97,22 @@ class PacketHandler:
|
|
|
84
97
|
while self._modulescan_address < 254:
|
|
85
98
|
address = 0
|
|
86
99
|
module = None
|
|
87
|
-
with self._scanLock:
|
|
100
|
+
async with self._scanLock:
|
|
88
101
|
self._modulescan_address = self._modulescan_address + 1
|
|
89
102
|
address = self._modulescan_address
|
|
103
|
+
if self._velbus.addr_is_submodule(address):
|
|
104
|
+
self._log.info(
|
|
105
|
+
f"Skipping submodule address {address}, already handled"
|
|
106
|
+
)
|
|
107
|
+
continue
|
|
108
|
+
self._log.info(f"Starting handling scan {address}")
|
|
90
109
|
module = self._velbus.get_module(address)
|
|
91
110
|
|
|
92
|
-
self.
|
|
111
|
+
if self._one_address is not None and address != int(self._one_address):
|
|
112
|
+
self._log.info(
|
|
113
|
+
f"Skipping address {address} as we requested to only scan one address {self._one_address}"
|
|
114
|
+
)
|
|
115
|
+
continue
|
|
93
116
|
|
|
94
117
|
cfile = pathlib.Path(f"{self._velbus.get_cache_dir()}/{address}.json")
|
|
95
118
|
# cleanup the old module cache if needed
|
|
@@ -107,7 +130,7 @@ class PacketHandler:
|
|
|
107
130
|
self._typeResponseReceived.wait(),
|
|
108
131
|
SCAN_MODULETYPE_TIMEOUT / 1000.0,
|
|
109
132
|
)
|
|
110
|
-
with self._scanLock:
|
|
133
|
+
async with self._scanLock:
|
|
111
134
|
module = self._velbus.get_module(address)
|
|
112
135
|
except asyncio.TimeoutError:
|
|
113
136
|
self._log.info(
|
|
@@ -115,20 +138,24 @@ class PacketHandler:
|
|
|
115
138
|
)
|
|
116
139
|
if module is not None:
|
|
117
140
|
try:
|
|
118
|
-
self._log.debug(
|
|
141
|
+
self._log.debug(
|
|
142
|
+
f"Module {module._address} detected: start loading"
|
|
143
|
+
)
|
|
119
144
|
await asyncio.wait_for(
|
|
120
145
|
module.load(from_cache=True),
|
|
121
146
|
SCAN_MODULEINFO_TIMEOUT_INITIAL / 1000.0,
|
|
122
147
|
)
|
|
123
|
-
self._scan_delay_msec =
|
|
124
|
-
while
|
|
148
|
+
self._scan_delay_msec = module.get_initial_timeout()
|
|
149
|
+
while (
|
|
150
|
+
self._scan_delay_msec > 50 and not await module.is_loaded()
|
|
151
|
+
):
|
|
125
152
|
# self._log.debug(
|
|
126
|
-
# f"\t... waiting {self._scan_delay_msec} is_loaded={module.is_loaded()}"
|
|
153
|
+
# f"\t... waiting {self._scan_delay_msec} is_loaded={await module.is_loaded()}"
|
|
127
154
|
# )
|
|
128
155
|
self._scan_delay_msec = self._scan_delay_msec - 50
|
|
129
156
|
await asyncio.sleep(0.05)
|
|
130
157
|
self._log.info(
|
|
131
|
-
f"Scan module {address} completed, module loaded={module.is_loaded()}"
|
|
158
|
+
f"Scan module {address} completed, module loaded={await module.is_loaded()}"
|
|
132
159
|
)
|
|
133
160
|
except asyncio.TimeoutError:
|
|
134
161
|
self._log.error(
|
|
@@ -153,49 +180,47 @@ class PacketHandler:
|
|
|
153
180
|
data = rawmsg.data_only
|
|
154
181
|
|
|
155
182
|
# handle module type response message
|
|
156
|
-
if command_value == 0xFF:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
self._modulescan_address = address - 1
|
|
183
|
+
if command_value == 0xFF and not self._scan_complete:
|
|
184
|
+
tmsg: ModuleTypeMessage = ModuleTypeMessage()
|
|
185
|
+
tmsg.populate(priority, address, rtr, data)
|
|
186
|
+
async with self._scanLock:
|
|
187
|
+
await self._handle_module_type(tmsg)
|
|
188
|
+
if address == self._modulescan_address:
|
|
189
|
+
self._typeResponseReceived.set()
|
|
190
|
+
else:
|
|
191
|
+
self._log.debug(
|
|
192
|
+
f"Unexpected module type message module address {address}, Velbuslink scan?"
|
|
193
|
+
)
|
|
194
|
+
self._modulescan_address = address - 1
|
|
169
195
|
|
|
170
|
-
|
|
196
|
+
self._typeResponseReceived.set()
|
|
171
197
|
|
|
172
198
|
# handle module subtype response message
|
|
173
|
-
elif command_value in (0xB0, 0xA7, 0xA6):
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
self._handle_module_subtype(msg)
|
|
199
|
+
elif command_value in (0xB0, 0xA7, 0xA6) and not self._scan_complete:
|
|
200
|
+
msg: ModuleSubTypeMessage = ModuleSubTypeMessage()
|
|
201
|
+
msg.populate(priority, address, rtr, data)
|
|
202
|
+
if command_value == 0xB0:
|
|
203
|
+
msg.sub_address_offset = 0
|
|
204
|
+
elif command_value == 0xA7:
|
|
205
|
+
msg.sub_address_offset = 4
|
|
206
|
+
elif command_value == 0xA6:
|
|
207
|
+
msg.sub_address_offset = 8
|
|
208
|
+
async with self._scanLock:
|
|
209
|
+
self._scan_delay_msec += SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
210
|
+
self._handle_module_subtype(msg)
|
|
186
211
|
|
|
187
212
|
# ignore broadcast
|
|
188
|
-
elif command_value in self.
|
|
213
|
+
elif command_value in self.broadcast:
|
|
189
214
|
self._log.debug(
|
|
190
215
|
"Received broadcast message {} from {}, ignoring".format(
|
|
191
|
-
self.
|
|
216
|
+
self.broadcast[str(command_value).upper()], address
|
|
192
217
|
)
|
|
193
218
|
)
|
|
194
219
|
|
|
195
220
|
# handle other messages for modules that are already scanned
|
|
196
221
|
else:
|
|
197
222
|
module = None
|
|
198
|
-
with self._scanLock:
|
|
223
|
+
async with self._scanLock:
|
|
199
224
|
module = self._velbus.get_module(address)
|
|
200
225
|
if module is not None:
|
|
201
226
|
module_type = module.get_type()
|
|
@@ -214,28 +239,29 @@ class PacketHandler:
|
|
|
214
239
|
0xFE,
|
|
215
240
|
0xCC,
|
|
216
241
|
): # names, memory data, memory block
|
|
217
|
-
self._scan_delay_msec
|
|
242
|
+
self._scan_delay_msec += SCAN_MODULEINFO_TIMEOUT_INTERVAL
|
|
218
243
|
# self._log.debug(f"Restart timeout {msg}")
|
|
219
244
|
# send the message to the modules
|
|
220
245
|
await module.on_message(msg)
|
|
221
246
|
else:
|
|
222
247
|
self._log.warning(f"NOT FOUND IN command_registry: {rawmsg}")
|
|
223
248
|
|
|
224
|
-
def _handle_module_type(
|
|
249
|
+
async def _handle_module_type(
|
|
250
|
+
self, msg: ModuleTypeMessage | ModuleType2Message
|
|
251
|
+
) -> None:
|
|
225
252
|
"""
|
|
226
253
|
load the module data
|
|
227
254
|
"""
|
|
228
255
|
if msg is not None:
|
|
229
256
|
module = self._velbus.get_module(msg.address)
|
|
230
257
|
if module is None:
|
|
231
|
-
data = keys_exists(self.pdata, "ModuleTypes", h2(msg.module_type))
|
|
232
|
-
if not data:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
self._velbus.add_module(
|
|
258
|
+
# data = keys_exists(self.pdata, "ModuleTypes", h2(msg.module_type))
|
|
259
|
+
# if not data:
|
|
260
|
+
# self._log.warning(f"Module not recognized: {msg.module_type}")
|
|
261
|
+
# return
|
|
262
|
+
await self._velbus.add_module(
|
|
236
263
|
msg.address,
|
|
237
264
|
msg.module_type,
|
|
238
|
-
data,
|
|
239
265
|
memorymap=msg.memory_map_version,
|
|
240
266
|
build_year=msg.build_year,
|
|
241
267
|
build_week=msg.build_week,
|
velbusaio/messages/__init__.py
CHANGED
|
@@ -51,13 +51,13 @@ from velbusaio.messages.memo_text import MemoTextMessage
|
|
|
51
51
|
from velbusaio.messages.memory_data import MemoryDataMessage
|
|
52
52
|
from velbusaio.messages.memory_data_block import MemoryDataBlockMessage
|
|
53
53
|
from velbusaio.messages.memory_dump_request import MemoryDumpRequestMessage
|
|
54
|
-
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
55
54
|
from velbusaio.messages.module_status import ModuleStatusMessage, ModuleStatusMessage2
|
|
56
55
|
from velbusaio.messages.module_status_request import ModuleStatusRequestMessage
|
|
57
56
|
from velbusaio.messages.module_subtype import ModuleSubTypeMessage
|
|
58
|
-
from velbusaio.messages.module_type import
|
|
57
|
+
from velbusaio.messages.module_type import ModuleType2Message, ModuleTypeMessage
|
|
59
58
|
from velbusaio.messages.module_type_request import ModuleTypeRequestMessage
|
|
60
59
|
from velbusaio.messages.push_button_status import PushButtonStatusMessage
|
|
60
|
+
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
61
61
|
from velbusaio.messages.read_data_block_from_memory import (
|
|
62
62
|
ReadDataBlockFromMemoryMessage,
|
|
63
63
|
)
|
|
@@ -13,7 +13,7 @@ COMMAND_CODE = 0xEC
|
|
|
13
13
|
DSTATUS = {0: "off", 1: "up", 2: "down"}
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@register(COMMAND_CODE, ["VMB1BLE", "VMB2BLE", "VMB1BLS"])
|
|
16
|
+
@register(COMMAND_CODE, ["VMB1BLE", "VMB2BLE", "VMB1BLS", "VMB2BLE-10"])
|
|
17
17
|
class BlindStatusNgMessage(Message):
|
|
18
18
|
"""
|
|
19
19
|
sent by: VMB2BLE
|
velbusaio/messages/cover_down.py
CHANGED
velbusaio/messages/cover_off.py
CHANGED
velbusaio/messages/cover_up.py
CHANGED
velbusaio/module.py
CHANGED
|
@@ -4,14 +4,17 @@ This represents a velbus module
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import importlib.resources
|
|
8
|
+
import json
|
|
7
9
|
import logging
|
|
10
|
+
import os
|
|
8
11
|
import pathlib
|
|
9
12
|
import struct
|
|
10
13
|
import sys
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
14
|
from typing import Awaitable, Callable
|
|
14
15
|
|
|
16
|
+
from aiofile import async_open
|
|
17
|
+
|
|
15
18
|
from velbusaio.channels import (
|
|
16
19
|
Blind,
|
|
17
20
|
Button,
|
|
@@ -25,19 +28,20 @@ from velbusaio.channels import (
|
|
|
25
28
|
SelectedProgram,
|
|
26
29
|
Sensor,
|
|
27
30
|
SensorNumber,
|
|
28
|
-
Temperature,
|
|
29
|
-
ThermostatChannel,
|
|
30
31
|
)
|
|
32
|
+
from velbusaio.channels import Temperature
|
|
33
|
+
from velbusaio.channels import Temperature as TemperatureChannelType
|
|
34
|
+
from velbusaio.channels import ThermostatChannel
|
|
31
35
|
from velbusaio.command_registry import commandRegistry
|
|
32
36
|
from velbusaio.const import (
|
|
33
37
|
CHANNEL_LIGHT_VALUE,
|
|
34
38
|
CHANNEL_MEMO_TEXT,
|
|
35
39
|
CHANNEL_SELECTED_PROGRAM,
|
|
36
40
|
PRIORITY_LOW,
|
|
41
|
+
SCAN_MODULEINFO_TIMEOUT_INITIAL,
|
|
37
42
|
)
|
|
38
|
-
from velbusaio.helpers import handle_match, keys_exists
|
|
43
|
+
from velbusaio.helpers import h2, handle_match, keys_exists
|
|
39
44
|
from velbusaio.message import Message
|
|
40
|
-
from velbusaio.messages.dali_device_settings import DaliDeviceSettingMsg
|
|
41
45
|
from velbusaio.messages.blind_status import BlindStatusMessage, BlindStatusNgMessage
|
|
42
46
|
from velbusaio.messages.channel_name_part1 import (
|
|
43
47
|
ChannelNamePart1Message,
|
|
@@ -61,6 +65,7 @@ from velbusaio.messages.channel_name_request import ChannelNameRequestMessage
|
|
|
61
65
|
from velbusaio.messages.clear_led import ClearLedMessage
|
|
62
66
|
from velbusaio.messages.counter_status import CounterStatusMessage
|
|
63
67
|
from velbusaio.messages.counter_status_request import CounterStatusRequestMessage
|
|
68
|
+
from velbusaio.messages.dali_device_settings import DaliDeviceSettingMsg
|
|
64
69
|
from velbusaio.messages.dali_device_settings import DeviceType as DaliDeviceType
|
|
65
70
|
from velbusaio.messages.dali_device_settings import DeviceTypeMsg as DaliDeviceTypeMsg
|
|
66
71
|
from velbusaio.messages.dali_device_settings import MemberOfGroupMsg
|
|
@@ -73,7 +78,6 @@ from velbusaio.messages.dimmer_channel_status import DimmerChannelStatusMessage
|
|
|
73
78
|
from velbusaio.messages.dimmer_status import DimmerStatusMessage
|
|
74
79
|
from velbusaio.messages.fast_blinking_led import FastBlinkingLedMessage
|
|
75
80
|
from velbusaio.messages.memory_data import MemoryDataMessage
|
|
76
|
-
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
77
81
|
from velbusaio.messages.module_status import (
|
|
78
82
|
ModuleStatusGP4PirMessage,
|
|
79
83
|
ModuleStatusMessage,
|
|
@@ -82,6 +86,7 @@ from velbusaio.messages.module_status import (
|
|
|
82
86
|
)
|
|
83
87
|
from velbusaio.messages.module_status_request import ModuleStatusRequestMessage
|
|
84
88
|
from velbusaio.messages.push_button_status import PushButtonStatusMessage
|
|
89
|
+
from velbusaio.messages.raw import MeteoRawMessage, SensorRawMessage
|
|
85
90
|
from velbusaio.messages.read_data_from_memory import ReadDataFromMemoryMessage
|
|
86
91
|
from velbusaio.messages.relay_status import RelayStatusMessage, RelayStatusMessage2
|
|
87
92
|
from velbusaio.messages.sensor_temperature import SensorTemperatureMessage
|
|
@@ -90,7 +95,6 @@ from velbusaio.messages.slider_status import SliderStatusMessage
|
|
|
90
95
|
from velbusaio.messages.slow_blinking_led import SlowBlinkingLedMessage
|
|
91
96
|
from velbusaio.messages.temp_sensor_status import TempSensorStatusMessage
|
|
92
97
|
from velbusaio.messages.update_led_status import UpdateLedStatusMessage
|
|
93
|
-
from velbusaio.channels import Temperature as TemperatureChannelType
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
class Module:
|
|
@@ -103,7 +107,6 @@ class Module:
|
|
|
103
107
|
cls,
|
|
104
108
|
module_address: int,
|
|
105
109
|
module_type: int,
|
|
106
|
-
module_data: dict,
|
|
107
110
|
serial: int | None = None,
|
|
108
111
|
memorymap: int | None = None,
|
|
109
112
|
build_year: int | None = None,
|
|
@@ -114,7 +117,6 @@ class Module:
|
|
|
114
117
|
return VmbDali(
|
|
115
118
|
module_address,
|
|
116
119
|
module_type,
|
|
117
|
-
module_data,
|
|
118
120
|
serial,
|
|
119
121
|
memorymap,
|
|
120
122
|
build_year,
|
|
@@ -125,7 +127,6 @@ class Module:
|
|
|
125
127
|
return Module(
|
|
126
128
|
module_address,
|
|
127
129
|
module_type,
|
|
128
|
-
module_data,
|
|
129
130
|
serial,
|
|
130
131
|
memorymap,
|
|
131
132
|
build_year,
|
|
@@ -137,7 +138,6 @@ class Module:
|
|
|
137
138
|
self,
|
|
138
139
|
module_address: int,
|
|
139
140
|
module_type: int,
|
|
140
|
-
module_data: dict,
|
|
141
141
|
serial: int | None = None,
|
|
142
142
|
memorymap: int | None = None,
|
|
143
143
|
build_year: int | None = None,
|
|
@@ -145,8 +145,8 @@ class Module:
|
|
|
145
145
|
cache_dir: str | None = None,
|
|
146
146
|
) -> None:
|
|
147
147
|
self._address = module_address
|
|
148
|
-
self._type = module_type
|
|
149
|
-
self._data =
|
|
148
|
+
self._type = int(module_type)
|
|
149
|
+
self._data = {}
|
|
150
150
|
|
|
151
151
|
self._name = {}
|
|
152
152
|
self._sub_address = {}
|
|
@@ -159,8 +159,34 @@ class Module:
|
|
|
159
159
|
self._channels = {}
|
|
160
160
|
self.loaded = False
|
|
161
161
|
|
|
162
|
-
def
|
|
162
|
+
def get_initial_timeout(self) -> int:
|
|
163
|
+
return SCAN_MODULEINFO_TIMEOUT_INITIAL
|
|
164
|
+
|
|
165
|
+
async def initialize(self, writer: Callable[[Message], Awaitable[None]]) -> None:
|
|
163
166
|
self._log = logging.getLogger("velbus-module")
|
|
167
|
+
# load the protocol data
|
|
168
|
+
try:
|
|
169
|
+
if sys.version_info >= (3, 13):
|
|
170
|
+
with importlib.resources.path(
|
|
171
|
+
__name__, f"module_spec/{h2(self._type)}.json"
|
|
172
|
+
) as fspath:
|
|
173
|
+
async with async_open(fspath) as protocol_file:
|
|
174
|
+
self._data = json.loads(await protocol_file.read())
|
|
175
|
+
else:
|
|
176
|
+
async with async_open(
|
|
177
|
+
str(
|
|
178
|
+
importlib.resources.files(__name__.split(".")[0]).joinpath(
|
|
179
|
+
f"module_spec/{h2(self._type)}.json"
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
) as protocol_file:
|
|
183
|
+
self._data = json.loads(await protocol_file.read())
|
|
184
|
+
self._log.debug(f"Module spec {h2(self._type)} loaded")
|
|
185
|
+
except FileNotFoundError:
|
|
186
|
+
self._log.warning(f"No module spec for {h2(self._type)}")
|
|
187
|
+
self._data = {}
|
|
188
|
+
|
|
189
|
+
# set some params from the velbus controller
|
|
164
190
|
self._writer = writer
|
|
165
191
|
for chan in self._channels.values():
|
|
166
192
|
chan._writer = writer
|
|
@@ -185,10 +211,10 @@ class Module:
|
|
|
185
211
|
):
|
|
186
212
|
del self._channels[i]
|
|
187
213
|
|
|
188
|
-
def _cache(self) -> None:
|
|
214
|
+
async def _cache(self) -> None:
|
|
189
215
|
cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
|
|
190
|
-
with cfile
|
|
191
|
-
json.
|
|
216
|
+
async with async_open(cfile, "w") as fl:
|
|
217
|
+
await fl.write(json.dumps(self.to_cache(), indent=4))
|
|
192
218
|
|
|
193
219
|
def __getstate__(self) -> dict:
|
|
194
220
|
d = self.__dict__
|
|
@@ -265,6 +291,7 @@ class Module:
|
|
|
265
291
|
),
|
|
266
292
|
):
|
|
267
293
|
self._process_channel_name_message(1, message)
|
|
294
|
+
await self._cache()
|
|
268
295
|
elif isinstance(
|
|
269
296
|
message,
|
|
270
297
|
(
|
|
@@ -274,6 +301,7 @@ class Module:
|
|
|
274
301
|
),
|
|
275
302
|
):
|
|
276
303
|
self._process_channel_name_message(2, message)
|
|
304
|
+
await self._cache()
|
|
277
305
|
elif isinstance(
|
|
278
306
|
message,
|
|
279
307
|
(
|
|
@@ -283,6 +311,7 @@ class Module:
|
|
|
283
311
|
),
|
|
284
312
|
):
|
|
285
313
|
self._process_channel_name_message(3, message)
|
|
314
|
+
await self._cache()
|
|
286
315
|
elif isinstance(message, MemoryDataMessage):
|
|
287
316
|
await self._process_memory_data_message(message)
|
|
288
317
|
elif isinstance(message, (RelayStatusMessage, RelayStatusMessage2)):
|
|
@@ -511,8 +540,6 @@ class Module:
|
|
|
511
540
|
message.sensor, {"cur": message.value, "unit": message.unit}
|
|
512
541
|
)
|
|
513
542
|
|
|
514
|
-
self._cache()
|
|
515
|
-
|
|
516
543
|
async def _update_channel(self, channel: int, updates: dict):
|
|
517
544
|
try:
|
|
518
545
|
await self._channels[channel].update(updates)
|
|
@@ -533,8 +560,8 @@ class Module:
|
|
|
533
560
|
# see if we have a cache
|
|
534
561
|
try:
|
|
535
562
|
cfile = pathlib.Path(f"{self._cache_dir}/{self._address}.json")
|
|
536
|
-
with cfile
|
|
537
|
-
cache = json.
|
|
563
|
+
async with async_open(cfile, "r") as fl:
|
|
564
|
+
cache = json.loads(await fl.read())
|
|
538
565
|
except OSError:
|
|
539
566
|
cache = {}
|
|
540
567
|
# load default channels
|
|
@@ -551,6 +578,10 @@ class Module:
|
|
|
551
578
|
if "channels" in cache:
|
|
552
579
|
for num, chan in cache["channels"].items():
|
|
553
580
|
self._channels[int(num)]._name = chan["name"]
|
|
581
|
+
if "sub_device" in chan:
|
|
582
|
+
self._channels[int(num)]._sub_device = chan["sub_device"]
|
|
583
|
+
else:
|
|
584
|
+
self._channels[int(num)]._sub_device = False
|
|
554
585
|
if "Unit" in chan:
|
|
555
586
|
self._channels[int(num)]._Unit = chan["Unit"]
|
|
556
587
|
self._channels[int(num)]._is_loaded = True
|
|
@@ -640,7 +671,7 @@ class Module:
|
|
|
640
671
|
)
|
|
641
672
|
return int(channel)
|
|
642
673
|
|
|
643
|
-
def is_loaded(self) -> bool:
|
|
674
|
+
async def is_loaded(self) -> bool:
|
|
644
675
|
"""
|
|
645
676
|
Check if all name messages have been received
|
|
646
677
|
"""
|
|
@@ -658,7 +689,7 @@ class Module:
|
|
|
658
689
|
return False
|
|
659
690
|
# set that we finished the module loading
|
|
660
691
|
self.loaded = True
|
|
661
|
-
self._cache()
|
|
692
|
+
await self._cache()
|
|
662
693
|
return True
|
|
663
694
|
|
|
664
695
|
async def _request_module_status(self) -> None:
|
|
@@ -728,11 +759,20 @@ class Module:
|
|
|
728
759
|
|
|
729
760
|
for chan, chan_data in self._data["Channels"].items():
|
|
730
761
|
edit = True
|
|
762
|
+
sub = True
|
|
731
763
|
if "Editable" not in chan_data or chan_data["Editable"] != "yes":
|
|
732
764
|
edit = False
|
|
765
|
+
if "Subdevice" not in chan_data or chan_data["Subdevice"] != "yes":
|
|
766
|
+
sub = False
|
|
733
767
|
cls = getattr(sys.modules[__name__], chan_data["Type"])
|
|
734
768
|
self._channels[int(chan)] = cls(
|
|
735
|
-
self,
|
|
769
|
+
module=self,
|
|
770
|
+
num=int(chan),
|
|
771
|
+
name=chan_data["Name"],
|
|
772
|
+
nameEditable=edit,
|
|
773
|
+
subDevice=sub,
|
|
774
|
+
writer=self._writer,
|
|
775
|
+
address=self._address,
|
|
736
776
|
)
|
|
737
777
|
if chan_data["Type"] == "Temperature":
|
|
738
778
|
if "Thermostat" in self._data or (
|
|
@@ -751,7 +791,6 @@ class VmbDali(Module):
|
|
|
751
791
|
self,
|
|
752
792
|
module_address: int,
|
|
753
793
|
module_type: int,
|
|
754
|
-
module_data: dict,
|
|
755
794
|
serial: int | None = None,
|
|
756
795
|
memorymap: int | None = None,
|
|
757
796
|
build_year: int | None = None,
|
|
@@ -761,7 +800,6 @@ class VmbDali(Module):
|
|
|
761
800
|
super().__init__(
|
|
762
801
|
module_address,
|
|
763
802
|
module_type,
|
|
764
|
-
module_data,
|
|
765
803
|
serial,
|
|
766
804
|
memorymap,
|
|
767
805
|
build_year,
|
|
@@ -770,10 +808,19 @@ class VmbDali(Module):
|
|
|
770
808
|
)
|
|
771
809
|
self.group_members: dict[int, set[int]] = {}
|
|
772
810
|
|
|
811
|
+
def get_initial_timeout(self) -> int:
|
|
812
|
+
return 100000
|
|
813
|
+
|
|
773
814
|
async def _load_default_channels(self) -> None:
|
|
774
815
|
for chan in range(1, 64 + 1):
|
|
775
816
|
self._channels[chan] = Channel(
|
|
776
|
-
self,
|
|
817
|
+
module=self,
|
|
818
|
+
num=chan,
|
|
819
|
+
name="placeholder",
|
|
820
|
+
nameEditable=True,
|
|
821
|
+
subDevice=True,
|
|
822
|
+
writer=self._writer,
|
|
823
|
+
address=self._address,
|
|
777
824
|
)
|
|
778
825
|
# Placeholders will keep this module loading
|
|
779
826
|
# Until the DaliDeviceSettings messages either delete or replace these placeholder's
|
|
@@ -804,6 +851,7 @@ class VmbDali(Module):
|
|
|
804
851
|
message.channel,
|
|
805
852
|
None,
|
|
806
853
|
True,
|
|
854
|
+
True,
|
|
807
855
|
self._writer,
|
|
808
856
|
self._address,
|
|
809
857
|
slider_scale=254,
|
|
@@ -853,7 +901,7 @@ class VmbDali(Module):
|
|
|
853
901
|
else:
|
|
854
902
|
return await super().on_message(message)
|
|
855
903
|
|
|
856
|
-
self._cache()
|
|
904
|
+
await self._cache()
|
|
857
905
|
|
|
858
906
|
async def _request_channel_name(self) -> None:
|
|
859
907
|
# Channel names are requested after channel scan
|