conson-xp 1.17.0__py3-none-any.whl → 1.19.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.
- {conson_xp-1.17.0.dist-info → conson_xp-1.19.0.dist-info}/METADATA +2 -1
- {conson_xp-1.17.0.dist-info → conson_xp-1.19.0.dist-info}/RECORD +14 -11
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_event_commands.py +32 -41
- xp/cli/utils/module_type_choice.py +56 -0
- xp/models/__init__.py +2 -0
- xp/models/conbus/conbus_event_list.py +34 -0
- xp/services/conbus/conbus_event_list_service.py +91 -0
- xp/services/conbus/conbus_event_raw_service.py +3 -7
- xp/services/protocol/conbus_event_protocol.py +24 -6
- xp/utils/dependencies.py +9 -0
- {conson_xp-1.17.0.dist-info → conson_xp-1.19.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.17.0.dist-info → conson_xp-1.19.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.17.0.dist-info → conson_xp-1.19.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: conson-xp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -306,6 +306,7 @@ xp conbus datapoint query
|
|
|
306
306
|
xp conbus discover
|
|
307
307
|
|
|
308
308
|
xp conbus event
|
|
309
|
+
xp conbus event list
|
|
309
310
|
xp conbus event raw
|
|
310
311
|
|
|
311
312
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
conson_xp-1.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
1
|
+
conson_xp-1.19.0.dist-info/METADATA,sha256=-VtLm8xePli914-pZt50momoSfpi6BARWkhyla_IiYA,9527
|
|
2
|
+
conson_xp-1.19.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
conson_xp-1.19.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.19.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=xqDwwDvx5lt_aNSuHKANp3WXQCP4x1a9OzyP8CniQnE,181
|
|
6
6
|
xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
|
|
7
7
|
xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
|
|
8
8
|
xp/cli/commands/__init__.py,sha256=wvo9Z5viwpjvO2432E7YP5HWjLLiW1IFpyXLc5puuGY,4766
|
|
@@ -15,7 +15,7 @@ xp/cli/commands/conbus/conbus_config_commands.py,sha256=BugIbgNX6_s4MySGvI6tWZkw
|
|
|
15
15
|
xp/cli/commands/conbus/conbus_custom_commands.py,sha256=lICT93ijMdhVRm8KjNMLo7kQ2BLlnOZvMPbR3SxSmZ4,1692
|
|
16
16
|
xp/cli/commands/conbus/conbus_datapoint_commands.py,sha256=r36OuTjREtbGKL-bskAGa0-WLw7x06td6woZn3GYJNA,3630
|
|
17
17
|
xp/cli/commands/conbus/conbus_discover_commands.py,sha256=-y3TDgOnw1_cjvxvgyfQ1GQE2_WmYq-l8Md7DsdTXmo,1719
|
|
18
|
-
xp/cli/commands/conbus/conbus_event_commands.py,sha256=
|
|
18
|
+
xp/cli/commands/conbus/conbus_event_commands.py,sha256=7URf-2u8Kzcy0chLYShbZfCbKawf--i-8U88AjhxleQ,3177
|
|
19
19
|
xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=FpCwogdxa7yFUjlrxM7e8Q2Ut32tKAHabngQQChvtJI,6763
|
|
20
20
|
xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=KitaGDM5HpwVUz8rLpO8VZUypUTcAg3Bzl0DVm6gnSk,3391
|
|
21
21
|
xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=L7-6y3rDllOjQ9g6Bk_RiTKIhAOHVPLdxWif9exkngs,3463
|
|
@@ -47,12 +47,13 @@ xp/cli/utils/datapoint_type_choice.py,sha256=HcydhlqxZ7YyorEeTjFGkypF2JnYNPvOzkl
|
|
|
47
47
|
xp/cli/utils/decorators.py,sha256=iAWm75VK_opqjX2h7ATZXlMzPINhF1FeeACzukm7ldQ,10149
|
|
48
48
|
xp/cli/utils/error_handlers.py,sha256=zL4f6996Is0lWl8uITNqiLyPNaeW6uUbkew7BRCFi8Y,6581
|
|
49
49
|
xp/cli/utils/formatters.py,sha256=fl7UmX6yLypqc0_QWevgPO2L6XA2Kd_0d_GSLUV5U30,9776
|
|
50
|
+
xp/cli/utils/module_type_choice.py,sha256=TPIEDsO0fNDu2HOQQ16WCJ-a7o2f58j3IKd8B0XsBDQ,1658
|
|
50
51
|
xp/cli/utils/serial_number_type.py,sha256=GUs3jtVI6EVulvt6fCDN6H6vxhiJwdMmdIvLjDlGGZ4,1466
|
|
51
52
|
xp/cli/utils/system_function_choice.py,sha256=0J02EMgAQcsrE-9rEkv6YHelBoBkZ73T8VLBSm6YO5k,1623
|
|
52
53
|
xp/cli/utils/xp_module_type.py,sha256=qSFJBRceqPi_cUFPxAWtLUNq37-KwUEjo9ekYOj7kLQ,1471
|
|
53
54
|
xp/connection/__init__.py,sha256=ClJsVWALYZgAGYZK_Jznd3YKLrHDu17kBfwugjuPfu0,209
|
|
54
55
|
xp/connection/exceptions.py,sha256=7CcRUzkyay5zA6Z9-5dIDRzua806v5N7pCcJazP_1dE,365
|
|
55
|
-
xp/models/__init__.py,sha256=
|
|
56
|
+
xp/models/__init__.py,sha256=lROqr559DGd8WpJJUtfPT95VERCwMZHpBDEc96QSxQ0,1312
|
|
56
57
|
xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
|
|
57
58
|
xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
|
|
58
59
|
xp/models/actiontable/msactiontable_xp20.py,sha256=C_lYYIQagEFap0S5S40_S7AhLO2UZG2EmXjjeem7uw8,1967
|
|
@@ -67,6 +68,7 @@ xp/models/conbus/conbus_connection_status.py,sha256=iGbmtBaAMwV6UD7XG3H3tnB0fl2M
|
|
|
67
68
|
xp/models/conbus/conbus_custom.py,sha256=8H2sPR6_LIlksuOvL7-8bPkzAJLR0rpYiiwfYYFVjEo,1965
|
|
68
69
|
xp/models/conbus/conbus_datapoint.py,sha256=4ncR-vB2lRzRBAA30rYn8eguyTxsZoOKrrXtjGmPpWg,3396
|
|
69
70
|
xp/models/conbus/conbus_discover.py,sha256=nxxUEKfEsH1kd0BF8ovMs7zLujRhrq1oL9ZJtysPr5o,2238
|
|
71
|
+
xp/models/conbus/conbus_event_list.py,sha256=M8aHRHVB5VDIjqMzjO86xlERt7AMdfjIjt1b70RF52Y,958
|
|
70
72
|
xp/models/conbus/conbus_event_raw.py,sha256=i5gc7z-0yeunWOZ4rw3AiBt4MANezmhBQKjOOQk3oDc,1567
|
|
71
73
|
xp/models/conbus/conbus_lightlevel.py,sha256=GQGhzrCBEJROosNHInXIzBy6MD2AskEIMoFEGgZ60-0,1695
|
|
72
74
|
xp/models/conbus/conbus_linknumber.py,sha256=uFzKzfB06oIzZEKCb5X2JEI80JjMPFuYglsT1W1k8j4,1815
|
|
@@ -119,7 +121,8 @@ xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFx
|
|
|
119
121
|
xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=p9R02cVimhdJILHQ6BoeZj8Hog4oRpqBnMo3t4R8ecY,6816
|
|
120
122
|
xp/services/conbus/conbus_datapoint_service.py,sha256=SYhHj9RmTmaJ750tyZ1IW2kl7tgDQ1xm_EM1zUjk1aQ,6421
|
|
121
123
|
xp/services/conbus/conbus_discover_service.py,sha256=sSCSDNWWGtx5QOShwJfcbG54WCYH-BxWvgE10ghibN4,12326
|
|
122
|
-
xp/services/conbus/
|
|
124
|
+
xp/services/conbus/conbus_event_list_service.py,sha256=0xyXXNU44epN5bFkU6oiZMyhxfUguul3evqClvPJDcA,3618
|
|
125
|
+
xp/services/conbus/conbus_event_raw_service.py,sha256=FZFu-LNLInrTKTpiGLyootozvyIF5Si5FMrxNk2ALD0,7000
|
|
123
126
|
xp/services/conbus/conbus_output_service.py,sha256=mHFOAPx2zo0TStZ3pokp6v94AQjIamcwZDeg5YH_-eo,7240
|
|
124
127
|
xp/services/conbus/conbus_raw_service.py,sha256=4yZLLTIAOxpgByUTWZXw1ihGa6Xtl98ckj9T7VfprDI,4335
|
|
125
128
|
xp/services/conbus/conbus_receive_service.py,sha256=frXrS0OyKKvYYQTWdma21Kd0BKw5aSuHn3ZXTTqOaj0,3953
|
|
@@ -142,7 +145,7 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
|
|
|
142
145
|
xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
|
|
143
146
|
xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
|
|
144
147
|
xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
|
|
145
|
-
xp/services/protocol/conbus_event_protocol.py,sha256=
|
|
148
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=6ihDsWj5k08Hb3OpYd3xBZCS-yPa16FfWtFSxJknIwo,12852
|
|
146
149
|
xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
|
|
147
150
|
xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
|
|
148
151
|
xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
|
|
@@ -168,8 +171,8 @@ xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmX
|
|
|
168
171
|
xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
|
|
169
172
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
170
173
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
171
|
-
xp/utils/dependencies.py,sha256=
|
|
174
|
+
xp/utils/dependencies.py,sha256=PYe-RvmfGBRXWnLKX62nXGMDFN7PQW3deoGCkIVEG4s,21274
|
|
172
175
|
xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
|
|
173
176
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
174
177
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
175
|
-
conson_xp-1.
|
|
178
|
+
conson_xp-1.19.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -6,8 +6,9 @@ import click
|
|
|
6
6
|
|
|
7
7
|
from xp.cli.commands.conbus.conbus import conbus
|
|
8
8
|
from xp.cli.utils.decorators import connection_command
|
|
9
|
+
from xp.cli.utils.module_type_choice import MODULE_TYPE
|
|
9
10
|
from xp.models import ConbusEventRawResponse
|
|
10
|
-
from xp.
|
|
11
|
+
from xp.services.conbus.conbus_event_list_service import ConbusEventListService
|
|
11
12
|
from xp.services.conbus.conbus_event_raw_service import ConbusEventRawService
|
|
12
13
|
|
|
13
14
|
|
|
@@ -17,16 +18,40 @@ def conbus_event() -> None:
|
|
|
17
18
|
pass
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
@conbus_event.command("list")
|
|
22
|
+
@click.pass_context
|
|
23
|
+
def list_events(ctx: click.Context) -> None:
|
|
24
|
+
r"""List configured event telegrams from module action tables.
|
|
25
|
+
|
|
26
|
+
Reads conson.yml configuration, parses action tables, and groups
|
|
27
|
+
modules by their event keys to show which modules are assigned to
|
|
28
|
+
each event (button configuration).
|
|
29
|
+
|
|
30
|
+
Output is sorted by module count (most frequently used events first).
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ctx: Click context object.
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
\b
|
|
37
|
+
xp conbus event list
|
|
38
|
+
"""
|
|
39
|
+
service: ConbusEventListService = (
|
|
40
|
+
ctx.obj.get("container").get_container().resolve(ConbusEventListService)
|
|
41
|
+
)
|
|
42
|
+
click.echo(json.dumps(service.list_events().to_dict(), indent=2))
|
|
43
|
+
|
|
44
|
+
|
|
20
45
|
@conbus_event.command("raw")
|
|
21
|
-
@click.argument("module_type", type=
|
|
22
|
-
@click.argument("link_number", type=
|
|
23
|
-
@click.argument("input_number", type=
|
|
24
|
-
@click.argument("time_ms", type=
|
|
46
|
+
@click.argument("module_type", type=MODULE_TYPE)
|
|
47
|
+
@click.argument("link_number", type=click.IntRange(0, 99))
|
|
48
|
+
@click.argument("input_number", type=click.IntRange(0, 9))
|
|
49
|
+
@click.argument("time_ms", type=click.IntRange(min=1), default=1000)
|
|
25
50
|
@click.pass_context
|
|
26
51
|
@connection_command()
|
|
27
52
|
def send_event_raw(
|
|
28
53
|
ctx: click.Context,
|
|
29
|
-
module_type:
|
|
54
|
+
module_type: int,
|
|
30
55
|
link_number: int,
|
|
31
56
|
input_number: int,
|
|
32
57
|
time_ms: int,
|
|
@@ -45,40 +70,6 @@ def send_event_raw(
|
|
|
45
70
|
xp conbus event raw CP20 00 00
|
|
46
71
|
xp conbus event raw XP33 00 00 500
|
|
47
72
|
"""
|
|
48
|
-
# Validate parameters
|
|
49
|
-
if link_number < 0 or link_number > 99:
|
|
50
|
-
click.echo(
|
|
51
|
-
json.dumps({"error": "Link number must be between 0 and 99"}, indent=2)
|
|
52
|
-
)
|
|
53
|
-
return
|
|
54
|
-
|
|
55
|
-
if input_number < 0 or input_number > 9:
|
|
56
|
-
click.echo(
|
|
57
|
-
json.dumps({"error": "Input number must be between 0 and 9"}, indent=2)
|
|
58
|
-
)
|
|
59
|
-
return
|
|
60
|
-
|
|
61
|
-
if time_ms <= 0:
|
|
62
|
-
click.echo(json.dumps({"error": "Time must be greater than 0"}, indent=2))
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
# Resolve module type to numeric code
|
|
66
|
-
module_type_code: int = 0
|
|
67
|
-
try:
|
|
68
|
-
# Try to get the enum value by name
|
|
69
|
-
module_type_enum = ModuleTypeCode[module_type.upper()]
|
|
70
|
-
module_type_code = module_type_enum.value
|
|
71
|
-
except KeyError:
|
|
72
|
-
# Module type not found
|
|
73
|
-
click.echo(
|
|
74
|
-
json.dumps(
|
|
75
|
-
{
|
|
76
|
-
"error": f"Unknown module type: {module_type}. Use module types like CP20, XP33, XP24, etc."
|
|
77
|
-
},
|
|
78
|
-
indent=2,
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
return
|
|
82
73
|
|
|
83
74
|
def on_finish(response: ConbusEventRawResponse) -> None:
|
|
84
75
|
"""Handle successful completion of event raw operation.
|
|
@@ -100,7 +91,7 @@ def send_event_raw(
|
|
|
100
91
|
ctx.obj.get("container").get_container().resolve(ConbusEventRawService)
|
|
101
92
|
)
|
|
102
93
|
service.run(
|
|
103
|
-
module_type_code=
|
|
94
|
+
module_type_code=module_type,
|
|
104
95
|
link_number=link_number,
|
|
105
96
|
input_number=input_number,
|
|
106
97
|
time_ms=time_ms,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Click parameter type for ModuleTypeCode enum validation."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from xp.models.telegram.module_type_code import ModuleTypeCode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModuleTypeChoice(click.ParamType):
|
|
11
|
+
"""Click parameter type for validating ModuleTypeCode enum values.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
name: The parameter type name.
|
|
15
|
+
choices: List of valid choice strings.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name = "module_type"
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
"""Initialize the ModuleTypeChoice parameter type."""
|
|
22
|
+
self.choices = [key for key in ModuleTypeCode.__members__.keys()]
|
|
23
|
+
|
|
24
|
+
def convert(
|
|
25
|
+
self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
|
|
26
|
+
) -> int:
|
|
27
|
+
"""Convert and validate input to ModuleTypeCode value.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
value: The input value to convert.
|
|
31
|
+
param: The Click parameter.
|
|
32
|
+
ctx: The Click context.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Module type code integer value if valid.
|
|
36
|
+
"""
|
|
37
|
+
if value is None:
|
|
38
|
+
self.fail("Module type is required", param, ctx)
|
|
39
|
+
|
|
40
|
+
# Convert to upper for comparison
|
|
41
|
+
normalized_value = value.upper()
|
|
42
|
+
|
|
43
|
+
if normalized_value in self.choices:
|
|
44
|
+
# Return the actual enum value (integer)
|
|
45
|
+
return ModuleTypeCode[normalized_value].value
|
|
46
|
+
|
|
47
|
+
# If not found, show error with available choices
|
|
48
|
+
choices_list = "\n".join(f" - {choice}" for choice in sorted(self.choices))
|
|
49
|
+
self.fail(
|
|
50
|
+
f"{value!r} is not a valid module type. " f"Choose from:\n{choices_list}",
|
|
51
|
+
param,
|
|
52
|
+
ctx,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
MODULE_TYPE = ModuleTypeChoice()
|
xp/models/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from xp.models.conbus.conbus_client_config import ConbusClientConfig
|
|
|
5
5
|
from xp.models.conbus.conbus_connection_status import ConbusConnectionStatus
|
|
6
6
|
from xp.models.conbus.conbus_datapoint import ConbusDatapointResponse
|
|
7
7
|
from xp.models.conbus.conbus_discover import ConbusDiscoverResponse
|
|
8
|
+
from xp.models.conbus.conbus_event_list import ConbusEventListResponse
|
|
8
9
|
from xp.models.conbus.conbus_event_raw import ConbusEventRawResponse
|
|
9
10
|
from xp.models.log_entry import LogEntry
|
|
10
11
|
from xp.models.telegram.event_telegram import EventTelegram
|
|
@@ -31,6 +32,7 @@ __all__ = [
|
|
|
31
32
|
"ConbusResponse",
|
|
32
33
|
"ConbusDatapointResponse",
|
|
33
34
|
"ConbusDiscoverResponse",
|
|
35
|
+
"ConbusEventListResponse",
|
|
34
36
|
"ConbusEventRawResponse",
|
|
35
37
|
"ConbusConnectionStatus",
|
|
36
38
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Conbus event list response model."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ConbusEventListResponse:
|
|
10
|
+
"""Represents a response from Conbus event list operation.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
events: Dict mapping event keys to list of module names.
|
|
14
|
+
timestamp: Timestamp of the response.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
events: Dict[str, list[str]]
|
|
18
|
+
timestamp: Optional[datetime] = None
|
|
19
|
+
|
|
20
|
+
def __post_init__(self) -> None:
|
|
21
|
+
"""Initialize timestamp if not provided."""
|
|
22
|
+
if self.timestamp is None:
|
|
23
|
+
self.timestamp = datetime.now()
|
|
24
|
+
|
|
25
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
26
|
+
"""Convert to dictionary for JSON serialization.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Dictionary representation of the response.
|
|
30
|
+
"""
|
|
31
|
+
return {
|
|
32
|
+
"events": self.events,
|
|
33
|
+
"timestamp": self.timestamp.isoformat() if self.timestamp else None,
|
|
34
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Conbus Event List Service for listing configured event telegrams.
|
|
2
|
+
|
|
3
|
+
This service parses action tables from conson.yml and groups events
|
|
4
|
+
by button configuration to show which modules are assigned to each event.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from typing import Dict, List
|
|
10
|
+
|
|
11
|
+
from xp.models import ConbusEventListResponse
|
|
12
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
13
|
+
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConbusEventListService:
|
|
17
|
+
"""Service for listing configured event telegrams from action tables.
|
|
18
|
+
|
|
19
|
+
Parses action tables from conson.yml configuration and groups modules
|
|
20
|
+
by their event keys to identify common button configurations.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
conson_config: Configuration containing module action tables.
|
|
24
|
+
logger: Logger instance for the service.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, conson_config: ConsonModuleListConfig) -> None:
|
|
28
|
+
"""Initialize the Conbus event list service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
conson_config: ConsonModuleListConfig instance with module action tables.
|
|
32
|
+
"""
|
|
33
|
+
self.conson_config = conson_config
|
|
34
|
+
self.logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
def list_events(self) -> ConbusEventListResponse:
|
|
37
|
+
"""List all configured events from module action tables.
|
|
38
|
+
|
|
39
|
+
Parses action tables, extracts event information (module_type, link, input),
|
|
40
|
+
groups modules by event key, and sorts by usage count.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
ConbusEventListResponse with events dict mapping event keys to module names.
|
|
44
|
+
"""
|
|
45
|
+
# Dict to track which modules are assigned to each event
|
|
46
|
+
# event_key -> set of module names (using set for automatic deduplication)
|
|
47
|
+
event_modules: Dict[str, set[str]] = defaultdict(set)
|
|
48
|
+
|
|
49
|
+
# Process each module's action table
|
|
50
|
+
for module in self.conson_config.root:
|
|
51
|
+
# Skip modules without action table
|
|
52
|
+
if not module.action_table:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# Process each action in the module's action table
|
|
56
|
+
for action in module.action_table:
|
|
57
|
+
try:
|
|
58
|
+
# Use existing ActionTableSerializer to parse action
|
|
59
|
+
entry = ActionTableSerializer.parse_action_string(action)
|
|
60
|
+
|
|
61
|
+
# Extract event data from parsed entry
|
|
62
|
+
module_type_name = entry.module_type.name
|
|
63
|
+
link = entry.link_number
|
|
64
|
+
input_num = entry.module_input
|
|
65
|
+
|
|
66
|
+
# Create event key (space-separated format)
|
|
67
|
+
event_key = f"{module_type_name} {link:02d} {input_num:02d}"
|
|
68
|
+
|
|
69
|
+
# Add this module to the event (set automatically deduplicates)
|
|
70
|
+
event_modules[event_key].add(
|
|
71
|
+
f"{module.serial_number}:{entry.module_output}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
except ValueError as e:
|
|
75
|
+
# Invalid action format - log warning and skip
|
|
76
|
+
self.logger.warning(
|
|
77
|
+
f"Invalid action '{action}' in module '{module.serial_number}': {e}"
|
|
78
|
+
)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# Convert sets to sorted lists and sort events by module count (descending)
|
|
82
|
+
events_dict: Dict[str, List[str]] = {
|
|
83
|
+
event_key: sorted(list(modules))
|
|
84
|
+
for event_key, modules in sorted(
|
|
85
|
+
event_modules.items(),
|
|
86
|
+
key=lambda item: len(item[1]),
|
|
87
|
+
reverse=True,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return ConbusEventListResponse(events=events_dict)
|
|
@@ -63,13 +63,11 @@ class ConbusEventRawService:
|
|
|
63
63
|
payload = f"E{self.module_type_code:02d}L{self.link_number:02d}I{self.input_number:02d}M"
|
|
64
64
|
self.logger.debug(f"Sending MAKE event: {payload}")
|
|
65
65
|
self.conbus_protocol.telegram_queue.put_nowait(payload.encode())
|
|
66
|
-
self.conbus_protocol.
|
|
67
|
-
0.0, self.conbus_protocol.start_queue_manager
|
|
68
|
-
)
|
|
66
|
+
self.conbus_protocol.call_later(0.0, self.conbus_protocol.start_queue_manager)
|
|
69
67
|
|
|
70
68
|
# Schedule BREAK event after delay
|
|
71
69
|
delay_seconds = self.time_ms / 1000.0
|
|
72
|
-
self.break_event_call = self.conbus_protocol.
|
|
70
|
+
self.break_event_call = self.conbus_protocol.call_later(
|
|
73
71
|
delay_seconds, self._send_break_event
|
|
74
72
|
)
|
|
75
73
|
|
|
@@ -78,9 +76,7 @@ class ConbusEventRawService:
|
|
|
78
76
|
payload = f"E{self.module_type_code:02d}L{self.link_number:02d}I{self.input_number:02d}B"
|
|
79
77
|
self.logger.debug(f"Sending BREAK event: {payload}")
|
|
80
78
|
self.conbus_protocol.telegram_queue.put_nowait(payload.encode())
|
|
81
|
-
self.conbus_protocol.
|
|
82
|
-
0.0, self.conbus_protocol.start_queue_manager
|
|
83
|
-
)
|
|
79
|
+
self.conbus_protocol.call_later(0.0, self.conbus_protocol.start_queue_manager)
|
|
84
80
|
|
|
85
81
|
def telegram_sent(self, telegram_sent: str) -> None:
|
|
86
82
|
"""Handle telegram sent event.
|
|
@@ -7,7 +7,7 @@ import logging
|
|
|
7
7
|
from queue import SimpleQueue
|
|
8
8
|
from random import randint
|
|
9
9
|
from threading import Lock
|
|
10
|
-
from typing import Any, Optional
|
|
10
|
+
from typing import Any, Callable, Optional
|
|
11
11
|
|
|
12
12
|
from psygnal import Signal
|
|
13
13
|
from twisted.internet import protocol
|
|
@@ -209,7 +209,27 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
209
209
|
f"D{data_value}"
|
|
210
210
|
)
|
|
211
211
|
self.telegram_queue.put_nowait(payload.encode())
|
|
212
|
-
self.
|
|
212
|
+
self.call_later(0.0, self.start_queue_manager)
|
|
213
|
+
|
|
214
|
+
def call_later(
|
|
215
|
+
self,
|
|
216
|
+
delay: float,
|
|
217
|
+
callable_action: Callable[..., Any],
|
|
218
|
+
*args: object,
|
|
219
|
+
**kw: object,
|
|
220
|
+
) -> DelayedCall:
|
|
221
|
+
"""Schedule a callable to be called later.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
delay: Delay in seconds before calling.
|
|
225
|
+
callable_action: The callable to execute.
|
|
226
|
+
args: Positional arguments to pass to callable.
|
|
227
|
+
kw: Keyword arguments to pass to callable.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
DelayedCall object that can be cancelled.
|
|
231
|
+
"""
|
|
232
|
+
return self._reactor.callLater(delay, callable_action, *args, **kw)
|
|
213
233
|
|
|
214
234
|
def buildProtocol(self, addr: IAddress) -> protocol.Protocol:
|
|
215
235
|
"""Build protocol instance for connection.
|
|
@@ -264,9 +284,7 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
264
284
|
def _reset_timeout(self) -> None:
|
|
265
285
|
"""Reset the inactivity timeout."""
|
|
266
286
|
self._cancel_timeout()
|
|
267
|
-
self.timeout_call = self.
|
|
268
|
-
self.timeout_seconds, self._on_timeout
|
|
269
|
-
)
|
|
287
|
+
self.timeout_call = self.call_later(self.timeout_seconds, self._on_timeout)
|
|
270
288
|
|
|
271
289
|
def _cancel_timeout(self) -> None:
|
|
272
290
|
"""Cancel the inactivity timeout."""
|
|
@@ -320,7 +338,7 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
320
338
|
telegram = self.telegram_queue.get_nowait()
|
|
321
339
|
self.sendFrame(telegram)
|
|
322
340
|
later = randint(10, 80) / 100
|
|
323
|
-
self.
|
|
341
|
+
self.call_later(later, self.process_telegram_queue)
|
|
324
342
|
|
|
325
343
|
def __enter__(self) -> "ConbusEventProtocol":
|
|
326
344
|
"""Enter context manager.
|
xp/utils/dependencies.py
CHANGED
|
@@ -43,6 +43,7 @@ from xp.services.conbus.conbus_datapoint_service import (
|
|
|
43
43
|
ConbusDatapointService,
|
|
44
44
|
)
|
|
45
45
|
from xp.services.conbus.conbus_discover_service import ConbusDiscoverService
|
|
46
|
+
from xp.services.conbus.conbus_event_list_service import ConbusEventListService
|
|
46
47
|
from xp.services.conbus.conbus_event_raw_service import ConbusEventRawService
|
|
47
48
|
from xp.services.conbus.conbus_output_service import ConbusOutputService
|
|
48
49
|
from xp.services.conbus.conbus_raw_service import ConbusRawService
|
|
@@ -189,6 +190,14 @@ class ServiceContainer:
|
|
|
189
190
|
scope=punq.Scope.singleton,
|
|
190
191
|
)
|
|
191
192
|
|
|
193
|
+
self.container.register(
|
|
194
|
+
ConbusEventListService,
|
|
195
|
+
factory=lambda: ConbusEventListService(
|
|
196
|
+
conson_config=self.container.resolve(ConsonModuleListConfig)
|
|
197
|
+
),
|
|
198
|
+
scope=punq.Scope.singleton,
|
|
199
|
+
)
|
|
200
|
+
|
|
192
201
|
self.container.register(
|
|
193
202
|
ConbusBlinkService,
|
|
194
203
|
factory=lambda: ConbusBlinkService(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|