ramses-rf 0.52.5__py3-none-any.whl → 0.53.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.
- ramses_cli/client.py +167 -54
- ramses_cli/py.typed +0 -0
- ramses_rf/__init__.py +2 -0
- ramses_rf/entity_base.py +3 -1
- ramses_rf/gateway.py +203 -27
- ramses_rf/schemas.py +1 -0
- ramses_rf/version.py +1 -1
- {ramses_rf-0.52.5.dist-info → ramses_rf-0.53.0.dist-info}/METADATA +1 -1
- {ramses_rf-0.52.5.dist-info → ramses_rf-0.53.0.dist-info}/RECORD +19 -18
- {ramses_rf-0.52.5.dist-info → ramses_rf-0.53.0.dist-info}/licenses/LICENSE +1 -1
- ramses_tx/command.py +1 -1
- ramses_tx/const.py +110 -23
- ramses_tx/protocol.py +22 -8
- ramses_tx/protocol_fsm.py +28 -10
- ramses_tx/schemas.py +2 -2
- ramses_tx/transport.py +499 -41
- ramses_tx/version.py +1 -1
- {ramses_rf-0.52.5.dist-info → ramses_rf-0.53.0.dist-info}/WHEEL +0 -0
- {ramses_rf-0.52.5.dist-info → ramses_rf-0.53.0.dist-info}/entry_points.txt +0 -0
ramses_cli/client.py
CHANGED
|
@@ -7,7 +7,8 @@ import asyncio
|
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
9
|
import sys
|
|
10
|
-
from
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Final, Literal
|
|
11
12
|
|
|
12
13
|
import click
|
|
13
14
|
from colorama import Fore, Style, init as colorama_init
|
|
@@ -22,7 +23,7 @@ from ramses_rf.schemas import (
|
|
|
22
23
|
SZ_ENABLE_EAVESDROP,
|
|
23
24
|
SZ_REDUCE_PROCESSING,
|
|
24
25
|
)
|
|
25
|
-
from ramses_tx import is_valid_dev_id
|
|
26
|
+
from ramses_tx import is_valid_dev_id, transport_factory
|
|
26
27
|
from ramses_tx.logger import CONSOLE_COLS, DEFAULT_DATEFMT, DEFAULT_FMT
|
|
27
28
|
from ramses_tx.schemas import (
|
|
28
29
|
SZ_DISABLE_QOS,
|
|
@@ -47,6 +48,10 @@ from ramses_rf.const import ( # noqa: F401, isort: skip, pylint: disable=unused
|
|
|
47
48
|
Code,
|
|
48
49
|
)
|
|
49
50
|
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
from _typeshed import SupportsRead
|
|
53
|
+
|
|
54
|
+
|
|
50
55
|
_PROFILE_LIBRARY = False # NOTE: for profiling of library
|
|
51
56
|
|
|
52
57
|
if _PROFILE_LIBRARY:
|
|
@@ -85,28 +90,46 @@ COLORS = {
|
|
|
85
90
|
W_: Style.BRIGHT + Fore.MAGENTA,
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
93
|
+
CONTEXT_SETTINGS: dict[str, Any] = dict(help_option_names=["-h", "--help"])
|
|
89
94
|
|
|
90
95
|
LIB_KEYS = tuple(SCH_GLOBAL_CONFIG({}).keys()) + (SZ_SERIAL_PORT,)
|
|
91
96
|
LIB_CFG_KEYS = tuple(SCH_GLOBAL_CONFIG({})[SZ_CONFIG].keys()) + (SZ_EVOFW_FLAG,)
|
|
92
97
|
|
|
93
98
|
|
|
94
|
-
def normalise_config(
|
|
95
|
-
|
|
99
|
+
def normalise_config(
|
|
100
|
+
lib_config: dict[str, dict[str, str | bool | None]],
|
|
101
|
+
) -> tuple[str | None, dict[str, Any] | None]:
|
|
102
|
+
"""Convert a HA config dict into the client library's own format.
|
|
103
|
+
|
|
104
|
+
:param lib_config: The configuration dictionary from Home Assistant.
|
|
105
|
+
:return: A tuple containing the serial port (if any) and the normalized configuration dictionary.
|
|
106
|
+
"""
|
|
96
107
|
|
|
97
108
|
serial_port = lib_config.pop(SZ_SERIAL_PORT, None)
|
|
98
109
|
|
|
99
110
|
# fix for: https://github.com/ramses-rf/ramses_rf/issues/96
|
|
100
|
-
packet_log = lib_config.get(
|
|
101
|
-
|
|
111
|
+
packet_log: str | Mapping[str, str | bool | None] | None = lib_config.get(
|
|
112
|
+
SZ_PACKET_LOG
|
|
113
|
+
)
|
|
114
|
+
if packet_log is None:
|
|
115
|
+
packet_log = {}
|
|
116
|
+
elif isinstance(packet_log, str):
|
|
102
117
|
packet_log = {SZ_FILE_NAME: packet_log}
|
|
118
|
+
assert isinstance(packet_log, dict)
|
|
103
119
|
lib_config[SZ_PACKET_LOG] = packet_log
|
|
104
120
|
|
|
105
|
-
return serial_port, lib_config
|
|
121
|
+
return serial_port, lib_config # type: ignore[return-value]
|
|
106
122
|
|
|
107
123
|
|
|
108
|
-
def split_kwargs(
|
|
109
|
-
|
|
124
|
+
def split_kwargs(
|
|
125
|
+
obj: tuple[dict[str, Any], dict[str, Any]], kwargs: dict[str, Any]
|
|
126
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
127
|
+
"""Split kwargs into cli/library kwargs.
|
|
128
|
+
|
|
129
|
+
:param obj: A tuple containing the current CLI and library configuration dictionaries.
|
|
130
|
+
:param kwargs: The keyword arguments to split.
|
|
131
|
+
:return: A tuple containing the updated CLI and library configuration dictionaries.
|
|
132
|
+
"""
|
|
110
133
|
cli_kwargs, lib_kwargs = obj
|
|
111
134
|
|
|
112
135
|
cli_kwargs.update(
|
|
@@ -119,9 +142,19 @@ def split_kwargs(obj: tuple[dict, dict], kwargs: dict) -> tuple[dict, dict]:
|
|
|
119
142
|
|
|
120
143
|
|
|
121
144
|
class DeviceIdParamType(click.ParamType):
|
|
145
|
+
"""A Click parameter type for Device IDs."""
|
|
146
|
+
|
|
122
147
|
name = "device_id"
|
|
123
148
|
|
|
124
|
-
def convert(self, value: str, param, ctx):
|
|
149
|
+
def convert(self, value: str, param: Any, ctx: click.Context | None) -> str:
|
|
150
|
+
"""Convert the value to a Device ID.
|
|
151
|
+
|
|
152
|
+
:param value: The value to convert.
|
|
153
|
+
:param param: The parameter being converted.
|
|
154
|
+
:param ctx: The Click context.
|
|
155
|
+
:return: The converted Device ID.
|
|
156
|
+
:raises click.BadParameter: If the value is not a valid Device ID.
|
|
157
|
+
"""
|
|
125
158
|
if is_valid_dev_id(value):
|
|
126
159
|
return value.upper()
|
|
127
160
|
self.fail(f"{value!r} is not a valid device_id", param, ctx)
|
|
@@ -176,8 +209,20 @@ class DeviceIdParamType(click.ParamType):
|
|
|
176
209
|
help="display crazy things",
|
|
177
210
|
)
|
|
178
211
|
@click.pass_context
|
|
179
|
-
def cli(
|
|
180
|
-
|
|
212
|
+
def cli(
|
|
213
|
+
ctx: click.Context,
|
|
214
|
+
/,
|
|
215
|
+
config_file: SupportsRead[str | bytes] | None = None,
|
|
216
|
+
eavesdrop: None | bool = None,
|
|
217
|
+
**kwargs: Any,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""A CLI for the ramses_rf library.
|
|
220
|
+
|
|
221
|
+
:param ctx: The Click context.
|
|
222
|
+
:param config_file: An optional configuration file to load.
|
|
223
|
+
:param eavesdrop: Whether to enable eavesdropping mode.
|
|
224
|
+
:param kwargs: Additional keyword arguments.
|
|
225
|
+
"""
|
|
181
226
|
|
|
182
227
|
if kwargs[SZ_DBG_MODE] > 0: # Do first
|
|
183
228
|
start_debugging(kwargs[SZ_DBG_MODE] == 1)
|
|
@@ -187,7 +232,7 @@ def cli(ctx, config_file=None, eavesdrop: None | bool = None, **kwargs: Any) ->
|
|
|
187
232
|
if eavesdrop is not None:
|
|
188
233
|
lib_kwargs[SZ_CONFIG][SZ_ENABLE_EAVESDROP] = eavesdrop
|
|
189
234
|
|
|
190
|
-
if config_file: # TODO: validate with voluptuous, use YAML
|
|
235
|
+
if config_file: # TODO: validate file with voluptuous, use YAML
|
|
191
236
|
lib_kwargs = deep_merge(
|
|
192
237
|
lib_kwargs, json.load(config_file)
|
|
193
238
|
) # CLI takes precedence
|
|
@@ -197,6 +242,8 @@ def cli(ctx, config_file=None, eavesdrop: None | bool = None, **kwargs: Any) ->
|
|
|
197
242
|
|
|
198
243
|
# Args/Params for packet log only
|
|
199
244
|
class FileCommand(click.Command): # client.py parse <file>
|
|
245
|
+
"""A Click Command class for file-based operations."""
|
|
246
|
+
|
|
200
247
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
201
248
|
super().__init__(*args, **kwargs)
|
|
202
249
|
self.params.insert( # input_file name/path only
|
|
@@ -216,6 +263,8 @@ class FileCommand(click.Command): # client.py parse <file>
|
|
|
216
263
|
class PortCommand(
|
|
217
264
|
click.Command
|
|
218
265
|
): # client.py <command> <port> --packet-log xxx --evofw3-flag xxx
|
|
266
|
+
"""A Click Command class for serial port operations."""
|
|
267
|
+
|
|
219
268
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
220
269
|
super().__init__(*args, **kwargs)
|
|
221
270
|
self.params.insert(0, click.Argument(("serial-port",)))
|
|
@@ -251,8 +300,15 @@ class PortCommand(
|
|
|
251
300
|
# 1/4: PARSE (a file, +/- eavesdrop)
|
|
252
301
|
@click.command(cls=FileCommand) # parse a packet log file, then stop
|
|
253
302
|
@click.pass_obj
|
|
254
|
-
def parse(
|
|
255
|
-
|
|
303
|
+
def parse(
|
|
304
|
+
obj: Any, /, **kwargs: Any
|
|
305
|
+
) -> tuple[Literal["parse"], dict[str, str], dict[str, str]]:
|
|
306
|
+
"""Command to parse a log file containing messages/packets.
|
|
307
|
+
|
|
308
|
+
:param obj: The context object containing configuration.
|
|
309
|
+
:param kwargs: Additional keyword arguments.
|
|
310
|
+
:return: A tuple containing the command name, library configuration, and CLI configuration.
|
|
311
|
+
"""
|
|
256
312
|
config, lib_config = split_kwargs(obj, kwargs)
|
|
257
313
|
|
|
258
314
|
lib_config[SZ_INPUT_FILE] = config.pop(SZ_INPUT_FILE) # just the file path
|
|
@@ -277,8 +333,16 @@ def parse(obj, **kwargs: Any):
|
|
|
277
333
|
"--poll-devices", type=click.STRING, help="e.g. 'device_id, device_id, ...'"
|
|
278
334
|
)
|
|
279
335
|
@click.pass_obj
|
|
280
|
-
def monitor(
|
|
281
|
-
|
|
336
|
+
def monitor(
|
|
337
|
+
obj: Any, /, discover: None | bool = None, **kwargs: Any
|
|
338
|
+
) -> tuple[Literal["monitor"], dict[str, str], dict[str, str]]:
|
|
339
|
+
"""Monitor (eavesdrop and/or probe) a serial port for messages/packets.
|
|
340
|
+
|
|
341
|
+
:param obj: The context object containing configuration.
|
|
342
|
+
:param discover: Whether to enable discovery. If None, inferred from other arguments.
|
|
343
|
+
:param kwargs: Additional keyword arguments.
|
|
344
|
+
:return: A tuple containing the command name, library configuration, and CLI configuration.
|
|
345
|
+
"""
|
|
282
346
|
config, lib_config = split_kwargs(obj, kwargs)
|
|
283
347
|
|
|
284
348
|
if discover is None:
|
|
@@ -315,10 +379,16 @@ def monitor(obj, discover: None | bool = None, **kwargs: Any):
|
|
|
315
379
|
help="controller_id, filename.json",
|
|
316
380
|
)
|
|
317
381
|
@click.pass_obj
|
|
318
|
-
def execute(
|
|
382
|
+
def execute(
|
|
383
|
+
obj: Any, /, **kwargs: Any
|
|
384
|
+
) -> tuple[Literal["execute"], dict[str | None, str | dict[str, Any]], dict[str, str]]:
|
|
319
385
|
"""Execute any specified scripts, return the results, then quit.
|
|
320
386
|
|
|
321
387
|
Disables discovery, and enforces a strict allow_list.
|
|
388
|
+
|
|
389
|
+
:param obj: A tuple containing the CLI and library configuration dictionaries.
|
|
390
|
+
:param kwargs: Additional arguments passed to the command.
|
|
391
|
+
:return: A tuple containing the command string, library config, and CLI config.
|
|
322
392
|
"""
|
|
323
393
|
config, lib_config = split_kwargs(obj, kwargs)
|
|
324
394
|
|
|
@@ -326,29 +396,37 @@ def execute(obj, **kwargs: Any):
|
|
|
326
396
|
lib_config[SZ_CONFIG][SZ_DISABLE_DISCOVERY] = True
|
|
327
397
|
lib_config[SZ_CONFIG][SZ_DISABLE_QOS] = False
|
|
328
398
|
|
|
399
|
+
known_list: dict[str | None, dict[str, Any]] = {}
|
|
329
400
|
if kwargs[GET_FAULTS]:
|
|
330
401
|
known_list = {kwargs[GET_FAULTS]: {}}
|
|
331
402
|
elif kwargs[GET_SCHED][0]:
|
|
332
403
|
known_list = {kwargs[GET_SCHED][0]: {}}
|
|
333
404
|
elif kwargs[SET_SCHED][0]:
|
|
334
405
|
known_list = {kwargs[SET_SCHED][0]: {}}
|
|
335
|
-
else:
|
|
336
|
-
known_list = {}
|
|
337
406
|
|
|
338
407
|
if known_list:
|
|
339
408
|
print(" - known list is force-configured/enforced")
|
|
340
409
|
lib_config[SZ_KNOWN_LIST] = known_list
|
|
341
410
|
lib_config[SZ_CONFIG][SZ_ENFORCE_KNOWN_LIST] = True
|
|
342
411
|
|
|
343
|
-
return EXECUTE, lib_config, config
|
|
412
|
+
return EXECUTE, lib_config, config # type: ignore[return-value]
|
|
344
413
|
|
|
345
414
|
|
|
346
415
|
#
|
|
347
416
|
# 4/4: LISTEN (to RF, +/- eavesdrop - NO sending/discovery)
|
|
348
417
|
@click.command(cls=PortCommand) # (optionally) execute a command, then listen
|
|
349
418
|
@click.pass_obj
|
|
350
|
-
def listen(
|
|
351
|
-
|
|
419
|
+
def listen(
|
|
420
|
+
obj: Any, /, **kwargs: Any
|
|
421
|
+
) -> tuple[
|
|
422
|
+
Literal["listen"], dict[str, str | dict[str, str | None] | None], dict[str, Any]
|
|
423
|
+
]:
|
|
424
|
+
"""Listen to (eavesdrop only) a serial port for messages/packets.
|
|
425
|
+
|
|
426
|
+
:param obj: The context object containing configuration.
|
|
427
|
+
:param kwargs: Additional keyword arguments.
|
|
428
|
+
:return: A tuple containing the command name, library configuration, and CLI configuration.
|
|
429
|
+
"""
|
|
352
430
|
config, lib_config = split_kwargs(obj, kwargs)
|
|
353
431
|
|
|
354
432
|
print(" - sending is force-disabled")
|
|
@@ -358,20 +436,27 @@ def listen(obj, **kwargs: Any):
|
|
|
358
436
|
|
|
359
437
|
|
|
360
438
|
def print_results(gwy: Gateway, **kwargs: Any) -> None:
|
|
439
|
+
"""Print the results of execution commands (faults, schedules).
|
|
440
|
+
|
|
441
|
+
:param gwy: The gateway instance.
|
|
442
|
+
:param kwargs: The command arguments.
|
|
443
|
+
"""
|
|
361
444
|
if kwargs[GET_FAULTS]:
|
|
362
445
|
fault_log = gwy.system_by_id[kwargs[GET_FAULTS]]._faultlog.faultlog
|
|
363
446
|
|
|
364
|
-
if fault_log
|
|
365
|
-
print("No fault log, or failed to get the fault log.")
|
|
366
|
-
else:
|
|
447
|
+
if fault_log:
|
|
367
448
|
[print(f"{k:02X}", v) for k, v in fault_log.items()]
|
|
449
|
+
else:
|
|
450
|
+
print("No fault log, or failed to get the fault log.")
|
|
368
451
|
|
|
369
452
|
if kwargs[GET_SCHED][0]:
|
|
370
453
|
system_id, zone_idx = kwargs[GET_SCHED]
|
|
371
454
|
if zone_idx == "HW":
|
|
372
|
-
|
|
455
|
+
dhw = gwy.system_by_id[system_id].dhw
|
|
456
|
+
zone: Any = dhw
|
|
373
457
|
else:
|
|
374
458
|
zone = gwy.system_by_id[system_id].zone_by_idx[zone_idx]
|
|
459
|
+
assert zone
|
|
375
460
|
schedule = zone.schedule
|
|
376
461
|
|
|
377
462
|
if schedule is None:
|
|
@@ -387,6 +472,10 @@ def print_results(gwy: Gateway, **kwargs: Any) -> None:
|
|
|
387
472
|
|
|
388
473
|
|
|
389
474
|
def _save_state(gwy: Gateway) -> None:
|
|
475
|
+
"""Save the gateway state to files.
|
|
476
|
+
|
|
477
|
+
:param gwy: The gateway instance.
|
|
478
|
+
"""
|
|
390
479
|
schema, msgs = gwy.get_state()
|
|
391
480
|
|
|
392
481
|
with open("state_msgs.log", "w") as f:
|
|
@@ -397,6 +486,11 @@ def _save_state(gwy: Gateway) -> None:
|
|
|
397
486
|
|
|
398
487
|
|
|
399
488
|
def _print_engine_state(gwy: Gateway, **kwargs: Any) -> None:
|
|
489
|
+
"""Print the current engine state (schema and packets).
|
|
490
|
+
|
|
491
|
+
:param gwy: The gateway instance.
|
|
492
|
+
:param kwargs: Command arguments to determine verbosity.
|
|
493
|
+
"""
|
|
400
494
|
(schema, packets) = gwy.get_state(include_expired=True)
|
|
401
495
|
|
|
402
496
|
if kwargs["print_state"] > 0:
|
|
@@ -406,6 +500,11 @@ def _print_engine_state(gwy: Gateway, **kwargs: Any) -> None:
|
|
|
406
500
|
|
|
407
501
|
|
|
408
502
|
def print_summary(gwy: Gateway, **kwargs: Any) -> None:
|
|
503
|
+
"""Print a summary of the system state, schema, params, and status.
|
|
504
|
+
|
|
505
|
+
:param gwy: The gateway instance.
|
|
506
|
+
:param kwargs: Command arguments to determine what to display.
|
|
507
|
+
"""
|
|
409
508
|
entity = gwy.tcs or gwy
|
|
410
509
|
|
|
411
510
|
if kwargs.get("show_schema"):
|
|
@@ -445,8 +544,8 @@ def print_summary(gwy: Gateway, **kwargs: Any) -> None:
|
|
|
445
544
|
print(f"{msg._pkt}")
|
|
446
545
|
else: # TODO(eb): replace next block by
|
|
447
546
|
# raise NotImplementedError
|
|
448
|
-
for
|
|
449
|
-
if
|
|
547
|
+
for msg_code, verbs in device._msgz.items():
|
|
548
|
+
if msg_code in (Code._0005, Code._000C):
|
|
450
549
|
for verb in verbs.values():
|
|
451
550
|
for pkt in verb.values():
|
|
452
551
|
print(f"{pkt}")
|
|
@@ -455,19 +554,24 @@ def print_summary(gwy: Gateway, **kwargs: Any) -> None:
|
|
|
455
554
|
if gwy.msg_db:
|
|
456
555
|
for msg in gwy.msg_db.get(device=device.id):
|
|
457
556
|
print(f"{msg._pkt}")
|
|
458
|
-
else: # TODO(eb): replace next block by
|
|
557
|
+
else: # TODO(eb): Q1 2026 replace next legacy block by
|
|
459
558
|
# raise NotImplementedError
|
|
460
|
-
for
|
|
461
|
-
for verb in
|
|
559
|
+
for cd in device._msgz.values():
|
|
560
|
+
for verb in cd.values():
|
|
462
561
|
for pkt in verb.values():
|
|
463
562
|
print(f"{pkt}")
|
|
464
563
|
print()
|
|
465
564
|
|
|
466
565
|
|
|
467
|
-
async def async_main(command: str, lib_kwargs: dict, **kwargs: Any) -> None:
|
|
468
|
-
"""
|
|
566
|
+
async def async_main(command: str, lib_kwargs: dict[str, Any], **kwargs: Any) -> None:
|
|
567
|
+
"""Execute the main asynchronous logic for the CLI.
|
|
469
568
|
|
|
470
|
-
|
|
569
|
+
:param command: The command to execute (e.g., "execute", "monitor", "listen", "parse").
|
|
570
|
+
:param lib_kwargs: Configuration arguments for the library.
|
|
571
|
+
:param kwargs: Additional CLI arguments.
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
def handle_msg(_msg: Message) -> None:
|
|
471
575
|
"""Process the message as it arrives (a callback).
|
|
472
576
|
|
|
473
577
|
In this case, the message is merely printed.
|
|
@@ -475,39 +579,43 @@ async def async_main(command: str, lib_kwargs: dict, **kwargs: Any) -> None:
|
|
|
475
579
|
|
|
476
580
|
if kwargs["long_format"]: # HACK for test/dev
|
|
477
581
|
print(
|
|
478
|
-
f"{
|
|
479
|
-
f" # {
|
|
582
|
+
f"{_msg.dtm.isoformat(timespec='microseconds')} ... {_msg!r}"
|
|
583
|
+
f" # {_msg.payload}" # or f' # ("{msg.src!r}", "{msg.dst!r}")'
|
|
480
584
|
)
|
|
481
585
|
return
|
|
482
586
|
|
|
483
|
-
dtm = f"{
|
|
587
|
+
dtm = f"{_msg.dtm:%H:%M:%S.%f}"[:-3]
|
|
484
588
|
con_cols = CONSOLE_COLS
|
|
485
589
|
|
|
486
|
-
if
|
|
487
|
-
print(f"{Style.BRIGHT}{Fore.YELLOW}{dtm} {
|
|
488
|
-
elif
|
|
489
|
-
print(f"{Style.BRIGHT}{COLORS.get(
|
|
490
|
-
elif
|
|
491
|
-
print(f"{Fore.YELLOW}{dtm} {
|
|
492
|
-
elif
|
|
493
|
-
print(f"{Fore.YELLOW}{dtm} {
|
|
590
|
+
if _msg.code == Code._PUZZ:
|
|
591
|
+
print(f"{Style.BRIGHT}{Fore.YELLOW}{dtm} {_msg}"[:con_cols])
|
|
592
|
+
elif _msg.src and _msg.src.type == DEV_TYPE_MAP.HGI:
|
|
593
|
+
print(f"{Style.BRIGHT}{COLORS.get(_msg.verb)}{dtm} {_msg}"[:con_cols])
|
|
594
|
+
elif _msg.code == Code._1F09 and _msg.verb == I_:
|
|
595
|
+
print(f"{Fore.YELLOW}{dtm} {_msg}"[:con_cols])
|
|
596
|
+
elif _msg.code in (Code._000A, Code._2309, Code._30C9) and _msg._has_array:
|
|
597
|
+
print(f"{Fore.YELLOW}{dtm} {_msg}"[:con_cols])
|
|
494
598
|
else:
|
|
495
|
-
print(f"{COLORS.get(
|
|
599
|
+
print(f"{COLORS.get(_msg.verb)}{dtm} {_msg}"[:con_cols])
|
|
496
600
|
|
|
497
|
-
serial_port, lib_kwargs = normalise_config(lib_kwargs)
|
|
601
|
+
serial_port, lib_kwargs = normalise_config(lib_kwargs) # type: ignore[assignment]
|
|
498
602
|
|
|
499
603
|
if kwargs["restore_schema"]:
|
|
500
604
|
print(" - restoring client schema from a HA cache...")
|
|
501
|
-
state = json.load(kwargs["restore_schema"])["data"][
|
|
605
|
+
state: dict[str, Any] = json.load(kwargs["restore_schema"])["data"][
|
|
606
|
+
"client_state"
|
|
607
|
+
]
|
|
502
608
|
lib_kwargs = lib_kwargs | state["schema"]
|
|
503
609
|
|
|
504
610
|
# if serial_port == "/dev/ttyMOCK":
|
|
505
611
|
# from tests.deprecated.mocked_rf import MockGateway # FIXME: for test/dev
|
|
506
612
|
# gwy = MockGateway(serial_port, **lib_kwargs)
|
|
507
613
|
# else:
|
|
508
|
-
gwy = Gateway(
|
|
614
|
+
gwy = Gateway(
|
|
615
|
+
serial_port, transport_constructor=transport_factory, **lib_kwargs
|
|
616
|
+
) # passes action to gateway
|
|
509
617
|
|
|
510
|
-
if lib_kwargs[SZ_CONFIG][SZ_REDUCE_PROCESSING] < DONT_CREATE_MESSAGES:
|
|
618
|
+
if int(lib_kwargs[SZ_CONFIG][SZ_REDUCE_PROCESSING]) < DONT_CREATE_MESSAGES:
|
|
511
619
|
# library will not send MSGs to STDOUT, so we'll send PKTs instead
|
|
512
620
|
colorama_init(autoreset=True) # WIP: remove strip=True
|
|
513
621
|
gwy.add_msg_handler(handle_msg)
|
|
@@ -532,10 +640,10 @@ async def async_main(command: str, lib_kwargs: dict, **kwargs: Any) -> None:
|
|
|
532
640
|
|
|
533
641
|
elif command == MONITOR:
|
|
534
642
|
_ = spawn_scripts(gwy, **kwargs)
|
|
535
|
-
await gwy._protocol._wait_connection_lost
|
|
643
|
+
await asyncio.wait_for(gwy._protocol._wait_connection_lost, 1.0) # type: ignore[arg-type]
|
|
536
644
|
|
|
537
645
|
elif command in (LISTEN, PARSE):
|
|
538
|
-
await gwy._protocol._wait_connection_lost
|
|
646
|
+
await asyncio.wait_for(gwy._protocol._wait_connection_lost, 1.0) # type: ignore[arg-type]
|
|
539
647
|
|
|
540
648
|
except asyncio.CancelledError:
|
|
541
649
|
msg = "ended via: CancelledError (e.g. SIGINT)"
|
|
@@ -571,6 +679,11 @@ cli.add_command(listen)
|
|
|
571
679
|
|
|
572
680
|
|
|
573
681
|
def main() -> None:
|
|
682
|
+
"""Entry point for the CLI.
|
|
683
|
+
|
|
684
|
+
Parses arguments, sets up the event loop (including Windows-specific policies),
|
|
685
|
+
and runs the main asynchronous loop (optionally with profiling).
|
|
686
|
+
"""
|
|
574
687
|
print("\r\nclient.py: Starting ramses_rf...")
|
|
575
688
|
|
|
576
689
|
try:
|
ramses_cli/py.typed
ADDED
|
File without changes
|
ramses_rf/__init__.py
CHANGED
ramses_rf/entity_base.py
CHANGED
|
@@ -1048,7 +1048,9 @@ class _Discovery(_MessageDB):
|
|
|
1048
1048
|
f"No msg found for hdr {hdr}, task code {task[_SZ_COMMAND].code}"
|
|
1049
1049
|
)
|
|
1050
1050
|
else: # TODO(eb) remove next Q1 2026
|
|
1051
|
-
|
|
1051
|
+
# CRITICAL FIX: self.tcs might be None during early discovery
|
|
1052
|
+
if self.tcs:
|
|
1053
|
+
msgs += [self.tcs._msgz[task[_SZ_COMMAND].code][I_][True]]
|
|
1052
1054
|
# raise NotImplementedError
|
|
1053
1055
|
except KeyError:
|
|
1054
1056
|
pass
|