conson-xp 1.15.0__py3-none-any.whl → 1.17.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.15.0.dist-info → conson_xp-1.17.0.dist-info}/METADATA +6 -1
- {conson_xp-1.15.0.dist-info → conson_xp-1.17.0.dist-info}/RECORD +20 -16
- xp/__init__.py +1 -1
- xp/cli/commands/__init__.py +3 -0
- xp/cli/commands/conbus/conbus_discover_commands.py +11 -2
- xp/cli/commands/conbus/conbus_event_commands.py +115 -0
- xp/cli/main.py +1 -1
- xp/models/__init__.py +2 -0
- xp/models/conbus/conbus_event_raw.py +47 -0
- xp/models/protocol/conbus_protocol.py +2 -1
- xp/services/conbus/conbus_datapoint_service.py +1 -0
- xp/services/conbus/conbus_discover_service.py +91 -38
- xp/services/conbus/conbus_event_raw_service.py +185 -0
- xp/services/homekit/homekit_outlet.py +11 -12
- xp/services/protocol/__init__.py +2 -1
- xp/services/protocol/conbus_event_protocol.py +342 -0
- xp/utils/dependencies.py +20 -2
- {conson_xp-1.15.0.dist-info → conson_xp-1.17.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.15.0.dist-info → conson_xp-1.17.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.15.0.dist-info → conson_xp-1.17.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.17.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,6 +47,7 @@ Requires-Dist: HAP-python[QRCode]>=5.0.0
|
|
|
47
47
|
Requires-Dist: punq>=0.7.0
|
|
48
48
|
Requires-Dist: twisted>=25.5.0
|
|
49
49
|
Requires-Dist: bubus>=1.5.6
|
|
50
|
+
Requires-Dist: psygnal>=0.15.0
|
|
50
51
|
Description-Content-Type: text/markdown
|
|
51
52
|
|
|
52
53
|
# 🔌 XP Protocol Communication Tool
|
|
@@ -304,6 +305,10 @@ xp conbus datapoint query
|
|
|
304
305
|
|
|
305
306
|
xp conbus discover
|
|
306
307
|
|
|
308
|
+
xp conbus event
|
|
309
|
+
xp conbus event raw
|
|
310
|
+
|
|
311
|
+
|
|
307
312
|
xp conbus lightlevel
|
|
308
313
|
xp conbus lightlevel get
|
|
309
314
|
xp conbus lightlevel off
|
|
@@ -1,11 +1,11 @@
|
|
|
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.17.0.dist-info/METADATA,sha256=Ib3pQUP44vhlduSotZu5juw2A86rHXgnO1e3fHrhuLg,9506
|
|
2
|
+
conson_xp-1.17.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
conson_xp-1.17.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.17.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=3Amcz5pSjBDLeWf7aPWXM1DtmHeDt9T4cbNlNM6t964,181
|
|
6
6
|
xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
|
|
7
7
|
xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
|
|
8
|
-
xp/cli/commands/__init__.py,sha256=
|
|
8
|
+
xp/cli/commands/__init__.py,sha256=wvo9Z5viwpjvO2432E7YP5HWjLLiW1IFpyXLc5puuGY,4766
|
|
9
9
|
xp/cli/commands/conbus/__init__.py,sha256=gE3K5OEoXkkZX8UOc2v3nreQQzwkOQi7n0VZ-Z2juXA,495
|
|
10
10
|
xp/cli/commands/conbus/conbus.py,sha256=eqdY8ArapvD08Z4p7Xk7eh4z0dESHuMSw7PKtwTJRYU,3021
|
|
11
11
|
xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=cdjLV9cnm7teEOlu5Jf1MS_aL7lNy8KiDIyjCQa5Nzw,7138
|
|
@@ -14,7 +14,8 @@ xp/cli/commands/conbus/conbus_blink_commands.py,sha256=UK-Ey4K0FvaPQ96U0gyMid236
|
|
|
14
14
|
xp/cli/commands/conbus/conbus_config_commands.py,sha256=BugIbgNX6_s4MySGvI6tWZkwguciajHUX2Xz8XBux7k,716
|
|
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
|
-
xp/cli/commands/conbus/conbus_discover_commands.py,sha256
|
|
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=8IjQfX9vXlTRprb1oGkMRHRDPmxb02ZnmVbv3ltCqGk,3369
|
|
18
19
|
xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=FpCwogdxa7yFUjlrxM7e8Q2Ut32tKAHabngQQChvtJI,6763
|
|
19
20
|
xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=KitaGDM5HpwVUz8rLpO8VZUypUTcAg3Bzl0DVm6gnSk,3391
|
|
20
21
|
xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=L7-6y3rDllOjQ9g6Bk_RiTKIhAOHVPLdxWif9exkngs,3463
|
|
@@ -39,7 +40,7 @@ xp/cli/commands/telegram/telegram_discover_commands.py,sha256=0UArJinw1eWFbee5EG
|
|
|
39
40
|
xp/cli/commands/telegram/telegram_linknumber_commands.py,sha256=7j0-E5Moqqga4NrKDch82C6glaFDFMQn5_3hMwie7BQ,2511
|
|
40
41
|
xp/cli/commands/telegram/telegram_parse_commands.py,sha256=_OYOso1hS4f_ox96qlkYL2SuFnmimpAvqqdYlLzX9yo,2232
|
|
41
42
|
xp/cli/commands/telegram/telegram_version_commands.py,sha256=WQyx1-B9yJ8V9WrFyBpOvULJ-jq12GoZZDDoRbM7eyw,1553
|
|
42
|
-
xp/cli/main.py,sha256=
|
|
43
|
+
xp/cli/main.py,sha256=3TY4wZoKMK8kQBgOn0WshTsag4J4ofoGoGPgg12wueM,2810
|
|
43
44
|
xp/cli/utils/__init__.py,sha256=gTGIj60Uai0iE7sr9_TtEpl04fD7krtTzbbigXUsUVU,46
|
|
44
45
|
xp/cli/utils/click_tree.py,sha256=ilmM2IMa_c-TqUMsv2alrZXuS0BNhvVlrBlSfyN8lzM,1670
|
|
45
46
|
xp/cli/utils/datapoint_type_choice.py,sha256=HcydhlqxZ7YyorEeTjFGkypF2JnYNPvOzkl1rhZ93Fc,1666
|
|
@@ -51,7 +52,7 @@ xp/cli/utils/system_function_choice.py,sha256=0J02EMgAQcsrE-9rEkv6YHelBoBkZ73T8V
|
|
|
51
52
|
xp/cli/utils/xp_module_type.py,sha256=qSFJBRceqPi_cUFPxAWtLUNq37-KwUEjo9ekYOj7kLQ,1471
|
|
52
53
|
xp/connection/__init__.py,sha256=ClJsVWALYZgAGYZK_Jznd3YKLrHDu17kBfwugjuPfu0,209
|
|
53
54
|
xp/connection/exceptions.py,sha256=7CcRUzkyay5zA6Z9-5dIDRzua806v5N7pCcJazP_1dE,365
|
|
54
|
-
xp/models/__init__.py,sha256=
|
|
55
|
+
xp/models/__init__.py,sha256=UaUiuvWevneh9gPzKNaVsuy6rxM7YlZg4mi8VlEJpfg,1210
|
|
55
56
|
xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
|
|
56
57
|
xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
|
|
57
58
|
xp/models/actiontable/msactiontable_xp20.py,sha256=C_lYYIQagEFap0S5S40_S7AhLO2UZG2EmXjjeem7uw8,1967
|
|
@@ -66,6 +67,7 @@ xp/models/conbus/conbus_connection_status.py,sha256=iGbmtBaAMwV6UD7XG3H3tnB0fl2M
|
|
|
66
67
|
xp/models/conbus/conbus_custom.py,sha256=8H2sPR6_LIlksuOvL7-8bPkzAJLR0rpYiiwfYYFVjEo,1965
|
|
67
68
|
xp/models/conbus/conbus_datapoint.py,sha256=4ncR-vB2lRzRBAA30rYn8eguyTxsZoOKrrXtjGmPpWg,3396
|
|
68
69
|
xp/models/conbus/conbus_discover.py,sha256=nxxUEKfEsH1kd0BF8ovMs7zLujRhrq1oL9ZJtysPr5o,2238
|
|
70
|
+
xp/models/conbus/conbus_event_raw.py,sha256=i5gc7z-0yeunWOZ4rw3AiBt4MANezmhBQKjOOQk3oDc,1567
|
|
69
71
|
xp/models/conbus/conbus_lightlevel.py,sha256=GQGhzrCBEJROosNHInXIzBy6MD2AskEIMoFEGgZ60-0,1695
|
|
70
72
|
xp/models/conbus/conbus_linknumber.py,sha256=uFzKzfB06oIzZEKCb5X2JEI80JjMPFuYglsT1W1k8j4,1815
|
|
71
73
|
xp/models/conbus/conbus_output.py,sha256=q7QKsD_CWT7YOk-V3otKWD1VM7qThrSLIUOunntMrMc,1953
|
|
@@ -78,7 +80,7 @@ xp/models/homekit/homekit_config.py,sha256=Y_k92PsKHFBnn3r1_RSZHJP5uLH27Gw8G7Bj5
|
|
|
78
80
|
xp/models/homekit/homekit_conson_config.py,sha256=NML644Ij7abstMbud-TUPcxraGY4vQwKrkJOwftv2pM,2603
|
|
79
81
|
xp/models/log_entry.py,sha256=kPcYuAirCXugfL3YkOK9cQVlkNWxG_8a4AVuhsykHL0,4355
|
|
80
82
|
xp/models/protocol/__init__.py,sha256=TJ_CJKchA-xgQiv5vCo_ndBBZjrcaTmjT74bR0T-5Cw,38
|
|
81
|
-
xp/models/protocol/conbus_protocol.py,sha256=
|
|
83
|
+
xp/models/protocol/conbus_protocol.py,sha256=3uWYE_t_-mp_2wPEgbDHbZoeQSEv48IdRcQpQyemEY0,9141
|
|
82
84
|
xp/models/response.py,sha256=h6_B1k_v6IrWhgNWBohEGQGRCp5TcVhgQ3RJS8gTkhY,1230
|
|
83
85
|
xp/models/telegram/__init__.py,sha256=-_exhjlRLbBNuPxHC4tLA2SAgf8M0yHJMeyEoQIk9PI,53
|
|
84
86
|
xp/models/telegram/action_type.py,sha256=vkw_chTgmsadksGXvZ9D_qYGpjOwCw-OgbEi8Bml17g,783
|
|
@@ -115,8 +117,9 @@ xp/services/conbus/conbus_blink_all_service.py,sha256=OaEg4b8AEiEruHSkZ5jDtaoI81
|
|
|
115
117
|
xp/services/conbus/conbus_blink_service.py,sha256=x9uM-sLnIEV8wSNsvJgo08E042g-Hh2ZF3rXkz-k_9s,5824
|
|
116
118
|
xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFxlQIdJBPQSa0enI,5826
|
|
117
119
|
xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=p9R02cVimhdJILHQ6BoeZj8Hog4oRpqBnMo3t4R8ecY,6816
|
|
118
|
-
xp/services/conbus/conbus_datapoint_service.py,sha256=
|
|
119
|
-
xp/services/conbus/conbus_discover_service.py,sha256=
|
|
120
|
+
xp/services/conbus/conbus_datapoint_service.py,sha256=SYhHj9RmTmaJ750tyZ1IW2kl7tgDQ1xm_EM1zUjk1aQ,6421
|
|
121
|
+
xp/services/conbus/conbus_discover_service.py,sha256=sSCSDNWWGtx5QOShwJfcbG54WCYH-BxWvgE10ghibN4,12326
|
|
122
|
+
xp/services/conbus/conbus_event_raw_service.py,sha256=zNY7GxT4R6ROsT1dDhoOoJkGtGbv2_AIBgOlLxZJl1A,7068
|
|
120
123
|
xp/services/conbus/conbus_output_service.py,sha256=mHFOAPx2zo0TStZ3pokp6v94AQjIamcwZDeg5YH_-eo,7240
|
|
121
124
|
xp/services/conbus/conbus_raw_service.py,sha256=4yZLLTIAOxpgByUTWZXw1ihGa6Xtl98ckj9T7VfprDI,4335
|
|
122
125
|
xp/services/conbus/conbus_receive_service.py,sha256=frXrS0OyKKvYYQTWdma21Kd0BKw5aSuHn3ZXTTqOaj0,3953
|
|
@@ -133,12 +136,13 @@ xp/services/homekit/homekit_hap_service.py,sha256=YrFe10XPBf6EC2SRnWmcCbjdVkrHjx
|
|
|
133
136
|
xp/services/homekit/homekit_lightbulb.py,sha256=7HGMIPwEcmvSs89ENcDhdb8g0R9WMq7115gYuwcskAs,3661
|
|
134
137
|
xp/services/homekit/homekit_lightbulb_service.py,sha256=G_ummBFiBurhQ2ZVwJ9l_aZ2MQgl5Uq-oi3KjIrdb-Y,2752
|
|
135
138
|
xp/services/homekit/homekit_module_service.py,sha256=7lanEinxAfTdn28ZHV-O5YyTqq_3v8FIyP2FI0jsEQM,1526
|
|
136
|
-
xp/services/homekit/homekit_outlet.py,sha256=
|
|
139
|
+
xp/services/homekit/homekit_outlet.py,sha256=TtrOwVF3BkEvDcTOkNJIWT64zhtPLFkDgtyzW6u_4yQ,5209
|
|
137
140
|
xp/services/homekit/homekit_outlet_service.py,sha256=y7DbWbbvihWwF1Gyl0l9Hup1JHin6PTlDEHdoIqTfEQ,3798
|
|
138
141
|
xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4NWruLXUM8,14031
|
|
139
142
|
xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
|
|
140
143
|
xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
|
|
141
|
-
xp/services/protocol/__init__.py,sha256=
|
|
144
|
+
xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
|
|
145
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=btWLGM-onWXVIvL5atD7HgQKNcx6F8dNqTZf2CSquiE,12272
|
|
142
146
|
xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
|
|
143
147
|
xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
|
|
144
148
|
xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
|
|
@@ -164,8 +168,8 @@ xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmX
|
|
|
164
168
|
xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
|
|
165
169
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
166
170
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
167
|
-
xp/utils/dependencies.py,sha256=
|
|
171
|
+
xp/utils/dependencies.py,sha256=QsZfPDMdlrQK01YQ4PRQ8Q59ZF9w22h1evWX3J4xCjE,20930
|
|
168
172
|
xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
|
|
169
173
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
170
174
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
171
|
-
conson_xp-1.
|
|
175
|
+
conson_xp-1.17.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
xp/cli/commands/__init__.py
CHANGED
|
@@ -37,6 +37,7 @@ from xp.cli.commands.conbus.conbus_datapoint_commands import (
|
|
|
37
37
|
query_datapoint,
|
|
38
38
|
)
|
|
39
39
|
from xp.cli.commands.conbus.conbus_discover_commands import send_discover_telegram
|
|
40
|
+
from xp.cli.commands.conbus.conbus_event_commands import conbus_event, send_event_raw
|
|
40
41
|
from xp.cli.commands.conbus.conbus_lightlevel_commands import (
|
|
41
42
|
xp_lightlevel_get,
|
|
42
43
|
xp_lightlevel_off,
|
|
@@ -97,6 +98,7 @@ __all__ = [
|
|
|
97
98
|
"conbus_lightlevel",
|
|
98
99
|
"conbus_msactiontable",
|
|
99
100
|
"conbus_actiontable",
|
|
101
|
+
"conbus_event",
|
|
100
102
|
"file",
|
|
101
103
|
"module",
|
|
102
104
|
"reverse_proxy",
|
|
@@ -118,6 +120,7 @@ __all__ = [
|
|
|
118
120
|
"show_config",
|
|
119
121
|
"send_custom_telegram",
|
|
120
122
|
"send_discover_telegram",
|
|
123
|
+
"send_event_raw",
|
|
121
124
|
"xp_output_on",
|
|
122
125
|
"xp_output_off",
|
|
123
126
|
"xp_output_status",
|
|
@@ -9,6 +9,7 @@ from xp.cli.utils.decorators import (
|
|
|
9
9
|
connection_command,
|
|
10
10
|
)
|
|
11
11
|
from xp.models import ConbusDiscoverResponse
|
|
12
|
+
from xp.models.conbus.conbus_discover import DiscoveredDevice
|
|
12
13
|
from xp.services.conbus.conbus_discover_service import (
|
|
13
14
|
ConbusDiscoverService,
|
|
14
15
|
)
|
|
@@ -36,6 +37,14 @@ def send_discover_telegram(ctx: click.Context) -> None:
|
|
|
36
37
|
"""
|
|
37
38
|
click.echo(json.dumps(discovered_devices.to_dict(), indent=2))
|
|
38
39
|
|
|
40
|
+
def on_device_discovered(discovered_device: DiscoveredDevice) -> None:
|
|
41
|
+
"""Handle discovery of sa single module.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
discovered_device: Discover device.
|
|
45
|
+
"""
|
|
46
|
+
click.echo(json.dumps(discovered_device, indent=2))
|
|
47
|
+
|
|
39
48
|
def progress(_serial_number: str) -> None:
|
|
40
49
|
"""Handle progress updates during device discovery.
|
|
41
50
|
|
|
@@ -48,5 +57,5 @@ def send_discover_telegram(ctx: click.Context) -> None:
|
|
|
48
57
|
service: ConbusDiscoverService = (
|
|
49
58
|
ctx.obj.get("container").get_container().resolve(ConbusDiscoverService)
|
|
50
59
|
)
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
service.run(progress, on_device_discovered, on_finish, 5)
|
|
61
|
+
service.start_reactor()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Conbus event operations CLI commands."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from xp.cli.commands.conbus.conbus import conbus
|
|
8
|
+
from xp.cli.utils.decorators import connection_command
|
|
9
|
+
from xp.models import ConbusEventRawResponse
|
|
10
|
+
from xp.models.telegram.module_type_code import ModuleTypeCode
|
|
11
|
+
from xp.services.conbus.conbus_event_raw_service import ConbusEventRawService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group(name="event")
|
|
15
|
+
def conbus_event() -> None:
|
|
16
|
+
"""Send event telegrams to Conbus modules."""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@conbus_event.command("raw")
|
|
21
|
+
@click.argument("module_type", type=str)
|
|
22
|
+
@click.argument("link_number", type=int)
|
|
23
|
+
@click.argument("input_number", type=int)
|
|
24
|
+
@click.argument("time_ms", type=int, default=1000)
|
|
25
|
+
@click.pass_context
|
|
26
|
+
@connection_command()
|
|
27
|
+
def send_event_raw(
|
|
28
|
+
ctx: click.Context,
|
|
29
|
+
module_type: str,
|
|
30
|
+
link_number: int,
|
|
31
|
+
input_number: int,
|
|
32
|
+
time_ms: int,
|
|
33
|
+
) -> None:
|
|
34
|
+
r"""Send raw event telegrams to simulate button presses.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
ctx: Click context object.
|
|
38
|
+
module_type: Module type code (e.g., CP20, XP33).
|
|
39
|
+
link_number: Link number (0-99).
|
|
40
|
+
input_number: Input number (0-9).
|
|
41
|
+
time_ms: Delay between MAKE/BREAK events in milliseconds (default: 1000).
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
\b
|
|
45
|
+
xp conbus event raw CP20 00 00
|
|
46
|
+
xp conbus event raw XP33 00 00 500
|
|
47
|
+
"""
|
|
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
|
+
|
|
83
|
+
def on_finish(response: ConbusEventRawResponse) -> None:
|
|
84
|
+
"""Handle successful completion of event raw operation.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
response: Event raw response with sent and received telegrams.
|
|
88
|
+
"""
|
|
89
|
+
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
90
|
+
|
|
91
|
+
def on_progress(telegram: str) -> None:
|
|
92
|
+
"""Handle progress updates during event operation.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
telegram: Received telegram.
|
|
96
|
+
"""
|
|
97
|
+
click.echo(json.dumps({"telegram": telegram}))
|
|
98
|
+
|
|
99
|
+
service: ConbusEventRawService = (
|
|
100
|
+
ctx.obj.get("container").get_container().resolve(ConbusEventRawService)
|
|
101
|
+
)
|
|
102
|
+
service.run(
|
|
103
|
+
module_type_code=module_type_code,
|
|
104
|
+
link_number=link_number,
|
|
105
|
+
input_number=input_number,
|
|
106
|
+
time_ms=time_ms,
|
|
107
|
+
progress_callback=on_progress,
|
|
108
|
+
finish_callback=on_finish,
|
|
109
|
+
timeout_seconds=5,
|
|
110
|
+
)
|
|
111
|
+
service.start_reactor()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Register the event command group with conbus
|
|
115
|
+
conbus.add_command(conbus_event)
|
xp/cli/main.py
CHANGED
|
@@ -56,7 +56,7 @@ def cli(ctx: click.Context) -> None:
|
|
|
56
56
|
|
|
57
57
|
# xp
|
|
58
58
|
logging.getLogger("xp").setLevel(logging.DEBUG)
|
|
59
|
-
logging.getLogger("xp.services.homekit").setLevel(logging.
|
|
59
|
+
logging.getLogger("xp.services.homekit").setLevel(logging.DEBUG)
|
|
60
60
|
|
|
61
61
|
# pyhap
|
|
62
62
|
logging.getLogger("pyhap").setLevel(logging.WARNING)
|
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_raw import ConbusEventRawResponse
|
|
8
9
|
from xp.models.log_entry import LogEntry
|
|
9
10
|
from xp.models.telegram.event_telegram import EventTelegram
|
|
10
11
|
from xp.models.telegram.event_type import EventType
|
|
@@ -30,5 +31,6 @@ __all__ = [
|
|
|
30
31
|
"ConbusResponse",
|
|
31
32
|
"ConbusDatapointResponse",
|
|
32
33
|
"ConbusDiscoverResponse",
|
|
34
|
+
"ConbusEventRawResponse",
|
|
33
35
|
"ConbusConnectionStatus",
|
|
34
36
|
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Conbus event raw 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 ConbusEventRawResponse:
|
|
10
|
+
"""Represents a response from Conbus event raw operation.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
success: Whether the operation was successful.
|
|
14
|
+
sent_telegrams: List of event telegrams sent (MAKE and BREAK).
|
|
15
|
+
received_telegrams: List of all telegrams received.
|
|
16
|
+
error: Error message if operation failed.
|
|
17
|
+
timestamp: Timestamp of the response.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
success: bool
|
|
21
|
+
sent_telegrams: Optional[list[str]] = None
|
|
22
|
+
received_telegrams: Optional[list[str]] = None
|
|
23
|
+
error: Optional[str] = None
|
|
24
|
+
timestamp: Optional[datetime] = None
|
|
25
|
+
|
|
26
|
+
def __post_init__(self) -> None:
|
|
27
|
+
"""Initialize timestamp and telegram lists if not provided."""
|
|
28
|
+
if self.timestamp is None:
|
|
29
|
+
self.timestamp = datetime.now()
|
|
30
|
+
if self.sent_telegrams is None:
|
|
31
|
+
self.sent_telegrams = []
|
|
32
|
+
if self.received_telegrams is None:
|
|
33
|
+
self.received_telegrams = []
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert to dictionary for JSON serialization.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Dictionary representation of the response.
|
|
40
|
+
"""
|
|
41
|
+
return {
|
|
42
|
+
"success": self.success,
|
|
43
|
+
"sent_telegrams": self.sent_telegrams,
|
|
44
|
+
"received_telegrams": self.received_telegrams,
|
|
45
|
+
"error": self.error,
|
|
46
|
+
"timestamp": self.timestamp.isoformat() if self.timestamp else None,
|
|
47
|
+
}
|
|
@@ -12,6 +12,7 @@ from xp.models.homekit.homekit_conson_config import ConsonModuleConfig
|
|
|
12
12
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
15
16
|
from xp.services.protocol.conbus_protocol import ConbusProtocol
|
|
16
17
|
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
17
18
|
|
|
@@ -249,7 +250,7 @@ class TelegramEvent(BaseEvent):
|
|
|
249
250
|
checksum_valid: Checksum valid true or false.
|
|
250
251
|
"""
|
|
251
252
|
|
|
252
|
-
protocol: Union[TelegramProtocol, ConbusProtocol] = Field(
|
|
253
|
+
protocol: Union[TelegramProtocol, ConbusProtocol, ConbusEventProtocol] = Field(
|
|
253
254
|
description="TelegramProtocol instance"
|
|
254
255
|
)
|
|
255
256
|
frame: str = Field(description="Frame <S0123450001F02D12FK>")
|
|
@@ -130,6 +130,7 @@ class ConbusDatapointService(ConbusProtocol):
|
|
|
130
130
|
self.service_response.data_value = datapoint_telegram.data_value
|
|
131
131
|
if self.datapoint_finished_callback:
|
|
132
132
|
self.datapoint_finished_callback(self.service_response)
|
|
133
|
+
self._stop_reactor()
|
|
133
134
|
|
|
134
135
|
def failed(self, message: str) -> None:
|
|
135
136
|
"""Handle failed connection event.
|
|
@@ -7,49 +7,57 @@ discover telegrams to find modules on the network.
|
|
|
7
7
|
import logging
|
|
8
8
|
from typing import Callable, Optional
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
from xp.models import ConbusClientConfig, ConbusDiscoverResponse
|
|
10
|
+
from xp.models import ConbusDiscoverResponse
|
|
13
11
|
from xp.models.conbus.conbus_discover import DiscoveredDevice
|
|
14
12
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
15
13
|
from xp.models.telegram.datapoint_type import DataPointType
|
|
16
14
|
from xp.models.telegram.module_type_code import MODULE_TYPE_REGISTRY
|
|
17
15
|
from xp.models.telegram.system_function import SystemFunction
|
|
18
16
|
from xp.models.telegram.telegram_type import TelegramType
|
|
19
|
-
from xp.services.protocol.
|
|
17
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
20
18
|
|
|
21
19
|
|
|
22
|
-
class ConbusDiscoverService
|
|
20
|
+
class ConbusDiscoverService:
|
|
23
21
|
"""
|
|
24
22
|
Service for discovering modules on Conbus servers.
|
|
25
23
|
|
|
26
24
|
Uses ConbusProtocol to provide discovery functionality for finding
|
|
27
25
|
modules connected to the Conbus network.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
conbus_protocol: Protocol instance for Conbus communication.
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
reactor: PosixReactorBase,
|
|
34
|
-
) -> None:
|
|
31
|
+
conbus_protocol: ConbusEventProtocol
|
|
32
|
+
|
|
33
|
+
def __init__(self, conbus_protocol: ConbusEventProtocol) -> None:
|
|
35
34
|
"""Initialize the Conbus discover service.
|
|
36
35
|
|
|
37
36
|
Args:
|
|
38
|
-
|
|
39
|
-
reactor: Twisted reactor instance.
|
|
37
|
+
conbus_protocol: ConbusProtocol.
|
|
40
38
|
"""
|
|
41
|
-
super().__init__(cli_config, reactor)
|
|
42
39
|
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
40
|
+
self.device_discover_callback: Optional[Callable[[DiscoveredDevice], None]] = (
|
|
41
|
+
None
|
|
42
|
+
)
|
|
43
43
|
self.finish_callback: Optional[Callable[[ConbusDiscoverResponse], None]] = None
|
|
44
44
|
|
|
45
|
+
self.conbus_protocol: ConbusEventProtocol = conbus_protocol
|
|
46
|
+
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
47
|
+
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
48
|
+
self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
|
|
49
|
+
self.conbus_protocol.on_timeout.connect(self.timeout)
|
|
50
|
+
self.conbus_protocol.on_failed.connect(self.failed)
|
|
51
|
+
|
|
45
52
|
self.discovered_device_result = ConbusDiscoverResponse(success=False)
|
|
46
53
|
# Set up logging
|
|
47
54
|
self.logger = logging.getLogger(__name__)
|
|
48
55
|
|
|
49
|
-
def
|
|
56
|
+
def connection_made(self) -> None:
|
|
50
57
|
"""Handle connection established event."""
|
|
51
|
-
self.logger.debug("Connection established
|
|
52
|
-
self.
|
|
58
|
+
self.logger.debug("Connection established")
|
|
59
|
+
self.logger.debug("Sending discover telegram")
|
|
60
|
+
self.conbus_protocol.send_telegram(
|
|
53
61
|
telegram_type=TelegramType.SYSTEM,
|
|
54
62
|
serial_number="0000000000",
|
|
55
63
|
system_function=SystemFunction.DISCOVERY,
|
|
@@ -83,7 +91,7 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
83
91
|
and telegram_received.payload[11:16] == "F01D"
|
|
84
92
|
and len(telegram_received.payload) == 15
|
|
85
93
|
):
|
|
86
|
-
self.
|
|
94
|
+
self.handle_discovered_device(telegram_received.serial_number)
|
|
87
95
|
|
|
88
96
|
# Check for module type response (F02D07)
|
|
89
97
|
elif (
|
|
@@ -109,7 +117,7 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
109
117
|
else:
|
|
110
118
|
self.logger.debug("Not a discover or module type response")
|
|
111
119
|
|
|
112
|
-
def
|
|
120
|
+
def handle_discovered_device(self, serial_number: str) -> None:
|
|
113
121
|
"""Handle discovered device event.
|
|
114
122
|
|
|
115
123
|
Args:
|
|
@@ -128,22 +136,17 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
128
136
|
}
|
|
129
137
|
self.discovered_device_result.discovered_devices.append(device)
|
|
130
138
|
|
|
139
|
+
if self.device_discover_callback:
|
|
140
|
+
self.device_discover_callback(device)
|
|
141
|
+
|
|
131
142
|
# Send READ_DATAPOINT telegram to query module type
|
|
132
143
|
self.logger.debug(f"Sending module type query for {serial_number}")
|
|
133
|
-
self.send_telegram(
|
|
144
|
+
self.conbus_protocol.send_telegram(
|
|
134
145
|
telegram_type=TelegramType.SYSTEM,
|
|
135
146
|
serial_number=serial_number,
|
|
136
147
|
system_function=SystemFunction.READ_DATAPOINT,
|
|
137
148
|
data_value=DataPointType.MODULE_TYPE.value,
|
|
138
149
|
)
|
|
139
|
-
|
|
140
|
-
self.send_telegram(
|
|
141
|
-
telegram_type=TelegramType.SYSTEM,
|
|
142
|
-
serial_number=serial_number,
|
|
143
|
-
system_function=SystemFunction.READ_DATAPOINT,
|
|
144
|
-
data_value=DataPointType.MODULE_TYPE_CODE.value,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
150
|
if self.progress_callback:
|
|
148
151
|
self.progress_callback(serial_number)
|
|
149
152
|
|
|
@@ -190,11 +193,27 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
190
193
|
if device["serial_number"] == serial_number:
|
|
191
194
|
device["module_type_code"] = code
|
|
192
195
|
device["module_type_name"] = module_type_name
|
|
196
|
+
|
|
197
|
+
if self.device_discover_callback:
|
|
198
|
+
self.device_discover_callback(device)
|
|
199
|
+
|
|
193
200
|
self.logger.debug(
|
|
194
201
|
f"Updated device {serial_number} with module_type {module_type_name}"
|
|
195
202
|
)
|
|
196
203
|
break
|
|
197
204
|
|
|
205
|
+
if self.discovered_device_result.discovered_devices:
|
|
206
|
+
for device in self.discovered_device_result.discovered_devices:
|
|
207
|
+
if not (
|
|
208
|
+
device["serial_number"]
|
|
209
|
+
and device["module_type"]
|
|
210
|
+
and device["module_type_code"]
|
|
211
|
+
and device["module_type_name"]
|
|
212
|
+
):
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
self.succeed()
|
|
216
|
+
|
|
198
217
|
def handle_module_type_response(self, serial_number: str, module_type: str) -> None:
|
|
199
218
|
"""Handle module type response and update discovered device.
|
|
200
219
|
|
|
@@ -212,19 +231,28 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
212
231
|
self.logger.debug(
|
|
213
232
|
f"Updated device {serial_number} with module_type {module_type}"
|
|
214
233
|
)
|
|
234
|
+
if self.device_discover_callback:
|
|
235
|
+
self.device_discover_callback(device)
|
|
236
|
+
|
|
215
237
|
break
|
|
216
238
|
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
self.conbus_protocol.send_telegram(
|
|
240
|
+
telegram_type=TelegramType.SYSTEM,
|
|
241
|
+
serial_number=serial_number,
|
|
242
|
+
system_function=SystemFunction.READ_DATAPOINT,
|
|
243
|
+
data_value=DataPointType.MODULE_TYPE_CODE.value,
|
|
244
|
+
)
|
|
219
245
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
self.logger.info("Discovery stopped after: %ss",
|
|
224
|
-
self.discovered_device_result.success =
|
|
246
|
+
def timeout(self) -> None:
|
|
247
|
+
"""Handle timeout event to stop discovery."""
|
|
248
|
+
timeout = self.conbus_protocol.timeout_seconds
|
|
249
|
+
self.logger.info("Discovery stopped after: %ss", timeout)
|
|
250
|
+
self.discovered_device_result.success = False
|
|
251
|
+
self.discovered_device_result.error = "Discovered device timeout"
|
|
225
252
|
if self.finish_callback:
|
|
226
253
|
self.finish_callback(self.discovered_device_result)
|
|
227
|
-
|
|
254
|
+
|
|
255
|
+
self.stop_reactor()
|
|
228
256
|
|
|
229
257
|
def failed(self, message: str) -> None:
|
|
230
258
|
"""Handle failed connection event.
|
|
@@ -238,9 +266,32 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
238
266
|
if self.finish_callback:
|
|
239
267
|
self.finish_callback(self.discovered_device_result)
|
|
240
268
|
|
|
241
|
-
|
|
269
|
+
self.stop_reactor()
|
|
270
|
+
|
|
271
|
+
def succeed(self) -> None:
|
|
272
|
+
"""Handle discovered device success event."""
|
|
273
|
+
self.logger.debug("Succeed")
|
|
274
|
+
self.discovered_device_result.success = True
|
|
275
|
+
self.discovered_device_result.error = None
|
|
276
|
+
if self.finish_callback:
|
|
277
|
+
self.finish_callback(self.discovered_device_result)
|
|
278
|
+
|
|
279
|
+
self.stop_reactor()
|
|
280
|
+
|
|
281
|
+
def stop_reactor(self) -> None:
|
|
282
|
+
"""Stop reactor."""
|
|
283
|
+
self.logger.info("Stopping reactor")
|
|
284
|
+
self.conbus_protocol.stop_reactor()
|
|
285
|
+
|
|
286
|
+
def start_reactor(self) -> None:
|
|
287
|
+
"""Start reactor."""
|
|
288
|
+
self.logger.info("Starting reactor")
|
|
289
|
+
self.conbus_protocol.start_reactor()
|
|
290
|
+
|
|
291
|
+
def run(
|
|
242
292
|
self,
|
|
243
293
|
progress_callback: Callable[[str], None],
|
|
294
|
+
device_discover_callback: Callable[[DiscoveredDevice], None],
|
|
244
295
|
finish_callback: Callable[[ConbusDiscoverResponse], None],
|
|
245
296
|
timeout_seconds: Optional[float] = None,
|
|
246
297
|
) -> None:
|
|
@@ -248,12 +299,14 @@ class ConbusDiscoverService(ConbusProtocol):
|
|
|
248
299
|
|
|
249
300
|
Args:
|
|
250
301
|
progress_callback: Callback for each discovered device.
|
|
302
|
+
device_discover_callback: Callback for each discovered device.
|
|
251
303
|
finish_callback: Callback when discovery completes.
|
|
252
304
|
timeout_seconds: Optional timeout in seconds.
|
|
253
305
|
"""
|
|
254
306
|
self.logger.info("Starting discovery")
|
|
307
|
+
|
|
255
308
|
if timeout_seconds:
|
|
256
|
-
self.timeout_seconds = timeout_seconds
|
|
309
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
257
310
|
self.progress_callback = progress_callback
|
|
311
|
+
self.device_discover_callback = device_discover_callback
|
|
258
312
|
self.finish_callback = finish_callback
|
|
259
|
-
self.start_reactor()
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Conbus Event Raw Service for sending raw event telegrams.
|
|
2
|
+
|
|
3
|
+
This service implements a TCP client that connects to Conbus servers and sends
|
|
4
|
+
raw event telegrams to simulate button presses on Conbus modules.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Callable, Optional
|
|
9
|
+
|
|
10
|
+
from twisted.internet.base import DelayedCall
|
|
11
|
+
|
|
12
|
+
from xp.models import ConbusEventRawResponse
|
|
13
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
14
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConbusEventRawService:
|
|
18
|
+
"""Service for sending raw event telegrams to Conbus servers.
|
|
19
|
+
|
|
20
|
+
Uses ConbusEventProtocol to send MAKE/BREAK event sequences to
|
|
21
|
+
simulate button presses on Conbus modules.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
conbus_protocol: Protocol instance for Conbus communication.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
conbus_protocol: ConbusEventProtocol
|
|
28
|
+
|
|
29
|
+
def __init__(self, conbus_protocol: ConbusEventProtocol) -> None:
|
|
30
|
+
"""Initialize the Conbus event raw service.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
conbus_protocol: ConbusEventProtocol instance.
|
|
34
|
+
"""
|
|
35
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
36
|
+
self.finish_callback: Optional[Callable[[ConbusEventRawResponse], None]] = None
|
|
37
|
+
|
|
38
|
+
self.conbus_protocol: ConbusEventProtocol = conbus_protocol
|
|
39
|
+
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
40
|
+
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
41
|
+
self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
|
|
42
|
+
self.conbus_protocol.on_timeout.connect(self.timeout)
|
|
43
|
+
self.conbus_protocol.on_failed.connect(self.failed)
|
|
44
|
+
|
|
45
|
+
self.event_result = ConbusEventRawResponse(success=False)
|
|
46
|
+
self.logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
# Event parameters
|
|
49
|
+
self.module_type_code: int = 0
|
|
50
|
+
self.link_number: int = 0
|
|
51
|
+
self.input_number: int = 0
|
|
52
|
+
self.time_ms: int = 1000
|
|
53
|
+
self.break_event_call: Optional[DelayedCall] = None
|
|
54
|
+
|
|
55
|
+
def connection_made(self) -> None:
|
|
56
|
+
"""Handle connection established event."""
|
|
57
|
+
self.logger.debug("Connection established")
|
|
58
|
+
self.logger.debug("Sending MAKE event telegram")
|
|
59
|
+
self._send_make_event()
|
|
60
|
+
|
|
61
|
+
def _send_make_event(self) -> None:
|
|
62
|
+
"""Send MAKE event telegram."""
|
|
63
|
+
payload = f"E{self.module_type_code:02d}L{self.link_number:02d}I{self.input_number:02d}M"
|
|
64
|
+
self.logger.debug(f"Sending MAKE event: {payload}")
|
|
65
|
+
self.conbus_protocol.telegram_queue.put_nowait(payload.encode())
|
|
66
|
+
self.conbus_protocol._reactor.callLater(
|
|
67
|
+
0.0, self.conbus_protocol.start_queue_manager
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Schedule BREAK event after delay
|
|
71
|
+
delay_seconds = self.time_ms / 1000.0
|
|
72
|
+
self.break_event_call = self.conbus_protocol._reactor.callLater(
|
|
73
|
+
delay_seconds, self._send_break_event
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _send_break_event(self) -> None:
|
|
77
|
+
"""Send BREAK event telegram."""
|
|
78
|
+
payload = f"E{self.module_type_code:02d}L{self.link_number:02d}I{self.input_number:02d}B"
|
|
79
|
+
self.logger.debug(f"Sending BREAK event: {payload}")
|
|
80
|
+
self.conbus_protocol.telegram_queue.put_nowait(payload.encode())
|
|
81
|
+
self.conbus_protocol._reactor.callLater(
|
|
82
|
+
0.0, self.conbus_protocol.start_queue_manager
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
86
|
+
"""Handle telegram sent event.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
telegram_sent: The telegram that was sent.
|
|
90
|
+
"""
|
|
91
|
+
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
92
|
+
if self.event_result.sent_telegrams is None:
|
|
93
|
+
self.event_result.sent_telegrams = []
|
|
94
|
+
self.event_result.sent_telegrams.append(telegram_sent)
|
|
95
|
+
|
|
96
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
97
|
+
"""Handle telegram received event.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
telegram_received: The telegram received event.
|
|
101
|
+
"""
|
|
102
|
+
self.logger.debug(f"Telegram received: {telegram_received.frame}")
|
|
103
|
+
if self.event_result.received_telegrams is None:
|
|
104
|
+
self.event_result.received_telegrams = []
|
|
105
|
+
self.event_result.received_telegrams.append(telegram_received.frame)
|
|
106
|
+
|
|
107
|
+
# Display progress - show ALL received telegrams
|
|
108
|
+
if self.progress_callback:
|
|
109
|
+
self.progress_callback(telegram_received.frame)
|
|
110
|
+
|
|
111
|
+
def timeout(self) -> None:
|
|
112
|
+
"""Handle timeout event.
|
|
113
|
+
|
|
114
|
+
Timeout is the normal/expected way to finish this service.
|
|
115
|
+
"""
|
|
116
|
+
timeout_seconds = self.conbus_protocol.timeout_seconds
|
|
117
|
+
self.logger.info("Event raw finished after timeout: %ss", timeout_seconds)
|
|
118
|
+
self.event_result.success = True
|
|
119
|
+
self.event_result.error = None
|
|
120
|
+
if self.finish_callback:
|
|
121
|
+
self.finish_callback(self.event_result)
|
|
122
|
+
|
|
123
|
+
self.stop_reactor()
|
|
124
|
+
|
|
125
|
+
def failed(self, message: str) -> None:
|
|
126
|
+
"""Handle failed connection event.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
message: Failure message.
|
|
130
|
+
"""
|
|
131
|
+
self.logger.debug(f"Failed: {message}")
|
|
132
|
+
self.event_result.success = False
|
|
133
|
+
self.event_result.error = message
|
|
134
|
+
if self.finish_callback:
|
|
135
|
+
self.finish_callback(self.event_result)
|
|
136
|
+
|
|
137
|
+
self.stop_reactor()
|
|
138
|
+
|
|
139
|
+
def stop_reactor(self) -> None:
|
|
140
|
+
"""Stop reactor."""
|
|
141
|
+
self.logger.info("Stopping reactor")
|
|
142
|
+
# Cancel break event call if it's still pending
|
|
143
|
+
if self.break_event_call and self.break_event_call.active():
|
|
144
|
+
self.break_event_call.cancel()
|
|
145
|
+
self.conbus_protocol.stop_reactor()
|
|
146
|
+
|
|
147
|
+
def start_reactor(self) -> None:
|
|
148
|
+
"""Start reactor."""
|
|
149
|
+
self.logger.info("Starting reactor")
|
|
150
|
+
self.conbus_protocol.start_reactor()
|
|
151
|
+
|
|
152
|
+
def run(
|
|
153
|
+
self,
|
|
154
|
+
module_type_code: int,
|
|
155
|
+
link_number: int,
|
|
156
|
+
input_number: int,
|
|
157
|
+
time_ms: int,
|
|
158
|
+
progress_callback: Optional[Callable[[str], None]],
|
|
159
|
+
finish_callback: Callable[[ConbusEventRawResponse], None],
|
|
160
|
+
timeout_seconds: int = 5,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Run reactor in dedicated thread with its own event loop.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
module_type_code: Module type code (numeric, e.g., 2 for CP20, 33 for XP33).
|
|
166
|
+
link_number: Link number (0-99).
|
|
167
|
+
input_number: Input number (0-9).
|
|
168
|
+
time_ms: Delay in milliseconds between MAKE and BREAK events.
|
|
169
|
+
progress_callback: Callback for progress updates (received telegrams).
|
|
170
|
+
finish_callback: Callback when operation completes.
|
|
171
|
+
timeout_seconds: Timeout in seconds (default: 5).
|
|
172
|
+
"""
|
|
173
|
+
self.logger.info(
|
|
174
|
+
f"Starting event raw: module={module_type_code}, "
|
|
175
|
+
f"link={link_number}, input={input_number}, time={time_ms}ms"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self.module_type_code = module_type_code
|
|
179
|
+
self.link_number = link_number
|
|
180
|
+
self.input_number = input_number
|
|
181
|
+
self.time_ms = time_ms
|
|
182
|
+
|
|
183
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
184
|
+
self.progress_callback = progress_callback
|
|
185
|
+
self.finish_callback = finish_callback
|
|
@@ -134,19 +134,18 @@ class Outlet(Accessory):
|
|
|
134
134
|
value: True to turn on, False to turn off.
|
|
135
135
|
"""
|
|
136
136
|
# Emit set event
|
|
137
|
-
self.logger.debug(f"set_on {value}")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
value=value,
|
|
148
|
-
)
|
|
137
|
+
self.logger.debug(f"set_on {value} {self.is_on}")
|
|
138
|
+
|
|
139
|
+
self.is_on = value
|
|
140
|
+
self.event_bus.dispatch(
|
|
141
|
+
OutletSetOnEvent(
|
|
142
|
+
serial_number=self.accessory.serial_number,
|
|
143
|
+
output_number=self.accessory.output_number,
|
|
144
|
+
module=self.module,
|
|
145
|
+
accessory=self.accessory,
|
|
146
|
+
value=value,
|
|
149
147
|
)
|
|
148
|
+
)
|
|
150
149
|
|
|
151
150
|
def get_on(self) -> bool:
|
|
152
151
|
"""Get the on/off state of the outlet.
|
xp/services/protocol/__init__.py
CHANGED
|
@@ -7,10 +7,11 @@ from xp.models.protocol.conbus_protocol import (
|
|
|
7
7
|
ModuleDiscoveredEvent,
|
|
8
8
|
TelegramReceivedEvent,
|
|
9
9
|
)
|
|
10
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
10
11
|
from xp.services.protocol.conbus_protocol import ConbusProtocol
|
|
11
12
|
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
12
13
|
|
|
13
|
-
__all__ = ["TelegramProtocol", "ConbusProtocol"]
|
|
14
|
+
__all__ = ["TelegramProtocol", "ConbusProtocol", "ConbusEventProtocol"]
|
|
14
15
|
|
|
15
16
|
# Rebuild models after TelegramProtocol and ConbusProtocol are imported to resolve forward references
|
|
16
17
|
ConnectionMadeEvent.model_rebuild()
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""Conbus Event Protocol for XP telegram communication.
|
|
2
|
+
|
|
3
|
+
This module implements the Twisted protocol for Conbus communication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from queue import SimpleQueue
|
|
8
|
+
from random import randint
|
|
9
|
+
from threading import Lock
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from psygnal import Signal
|
|
13
|
+
from twisted.internet import protocol
|
|
14
|
+
from twisted.internet.base import DelayedCall
|
|
15
|
+
from twisted.internet.interfaces import IAddress, IConnector
|
|
16
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
17
|
+
from twisted.python.failure import Failure
|
|
18
|
+
|
|
19
|
+
from xp.models import ConbusClientConfig
|
|
20
|
+
from xp.models.protocol.conbus_protocol import (
|
|
21
|
+
TelegramReceivedEvent,
|
|
22
|
+
)
|
|
23
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
24
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
25
|
+
from xp.utils import calculate_checksum
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
29
|
+
"""Twisted protocol for XP telegram communication.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
buffer: Buffer for incoming telegram data.
|
|
33
|
+
logger: Logger instance for this protocol.
|
|
34
|
+
cli_config: Conbus configuration settings.
|
|
35
|
+
timeout_seconds: Timeout duration in seconds.
|
|
36
|
+
timeout_call: Delayed call handle for timeout management.
|
|
37
|
+
telegram_queue: FIFO queue for outgoing telegrams.
|
|
38
|
+
queue_manager_running: Flag indicating if queue manager is active.
|
|
39
|
+
queue_manager_lock: Lock for thread-safe queue manager access.
|
|
40
|
+
on_connection_made: Signal emitted when connection is established.
|
|
41
|
+
on_connection_lost: Signal emitted when connection is lost.
|
|
42
|
+
on_connection_failed: Signal emitted when connection fails.
|
|
43
|
+
on_client_connection_failed: Signal emitted when client connection fails.
|
|
44
|
+
on_client_connection_lost: Signal emitted when client connection is lost.
|
|
45
|
+
on_send_frame: Signal emitted when a frame is sent.
|
|
46
|
+
on_telegram_sent: Signal emitted when a telegram is sent.
|
|
47
|
+
on_data_received: Signal emitted when data is received.
|
|
48
|
+
on_telegram_received: Signal emitted when a telegram is received.
|
|
49
|
+
on_timeout: Signal emitted when timeout occurs.
|
|
50
|
+
on_failed: Signal emitted when operation fails.
|
|
51
|
+
on_start_reactor: Signal emitted when reactor starts.
|
|
52
|
+
on_stop_reactor: Signal emitted when reactor stops.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
buffer: bytes
|
|
56
|
+
|
|
57
|
+
telegram_queue: SimpleQueue[bytes] = SimpleQueue() # FIFO
|
|
58
|
+
queue_manager_running: bool = False
|
|
59
|
+
queue_manager_lock: Lock = Lock()
|
|
60
|
+
|
|
61
|
+
on_connection_made: Signal = Signal()
|
|
62
|
+
on_connection_lost: Signal = Signal()
|
|
63
|
+
on_connection_failed: Signal = Signal(Failure)
|
|
64
|
+
on_client_connection_failed: Signal = Signal(Failure)
|
|
65
|
+
on_client_connection_lost: Signal = Signal(Failure)
|
|
66
|
+
on_send_frame: Signal = Signal(bytes)
|
|
67
|
+
on_telegram_sent: Signal = Signal(bytes)
|
|
68
|
+
on_data_received: Signal = Signal(bytes)
|
|
69
|
+
on_telegram_received: Signal = Signal(TelegramReceivedEvent)
|
|
70
|
+
on_timeout: Signal = Signal()
|
|
71
|
+
on_failed: Signal = Signal(str)
|
|
72
|
+
on_start_reactor: Signal = Signal()
|
|
73
|
+
on_stop_reactor: Signal = Signal()
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
cli_config: ConbusClientConfig,
|
|
78
|
+
reactor: PosixReactorBase,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Initialize ConbusProtocol.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
cli_config: Configuration for Conbus client connection.
|
|
84
|
+
reactor: Twisted reactor for event handling.
|
|
85
|
+
"""
|
|
86
|
+
self.buffer = b""
|
|
87
|
+
self.logger = logging.getLogger(__name__)
|
|
88
|
+
self.cli_config = cli_config.conbus
|
|
89
|
+
self._reactor = reactor
|
|
90
|
+
self.timeout_seconds = self.cli_config.timeout
|
|
91
|
+
self.timeout_call: Optional[DelayedCall] = None
|
|
92
|
+
|
|
93
|
+
def connectionMade(self) -> None:
|
|
94
|
+
"""Handle connection established event.
|
|
95
|
+
|
|
96
|
+
Called when TCP connection is successfully established.
|
|
97
|
+
Starts inactivity timeout monitoring.
|
|
98
|
+
"""
|
|
99
|
+
self.logger.debug("connectionMade")
|
|
100
|
+
self.on_connection_made.emit()
|
|
101
|
+
|
|
102
|
+
# Start inactivity timeout
|
|
103
|
+
self._reset_timeout()
|
|
104
|
+
|
|
105
|
+
def dataReceived(self, data: bytes) -> None:
|
|
106
|
+
"""Handle received data from TCP connection.
|
|
107
|
+
|
|
108
|
+
Parses incoming telegram frames and dispatches events.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
data: Raw bytes received from connection.
|
|
112
|
+
"""
|
|
113
|
+
self.logger.debug("dataReceived")
|
|
114
|
+
self.on_data_received.emit(data)
|
|
115
|
+
self.buffer += data
|
|
116
|
+
|
|
117
|
+
while True:
|
|
118
|
+
start = self.buffer.find(b"<")
|
|
119
|
+
if start == -1:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
end = self.buffer.find(b">", start)
|
|
123
|
+
if end == -1:
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
# <S0123450001F02D12FK>
|
|
127
|
+
# <R0123450001F02D12FK>
|
|
128
|
+
# <E12L01I08MAK>
|
|
129
|
+
frame = self.buffer[start : end + 1] # <S0123450001F02D12FK>
|
|
130
|
+
self.buffer = self.buffer[end + 1 :]
|
|
131
|
+
telegram = frame[1:-1] # S0123450001F02D12FK
|
|
132
|
+
telegram_type = telegram[0:1].decode() # S
|
|
133
|
+
payload = telegram[:-2] # S0123450001F02D12
|
|
134
|
+
checksum = telegram[-2:].decode() # FK
|
|
135
|
+
serial_number = (
|
|
136
|
+
telegram[1:11] if telegram_type in ("S", "R") else b""
|
|
137
|
+
) # 0123450001
|
|
138
|
+
calculated_checksum = calculate_checksum(payload.decode(encoding="latin-1"))
|
|
139
|
+
|
|
140
|
+
checksum_valid = checksum == calculated_checksum
|
|
141
|
+
if not checksum_valid:
|
|
142
|
+
self.logger.debug(
|
|
143
|
+
f"Invalid checksum: {checksum}, calculated: {calculated_checksum}"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.logger.debug(
|
|
147
|
+
f"frameReceived payload: {payload.decode('latin-1')}, checksum: {checksum}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Reset timeout on activity
|
|
151
|
+
self._reset_timeout()
|
|
152
|
+
|
|
153
|
+
telegram_received = TelegramReceivedEvent(
|
|
154
|
+
protocol=self,
|
|
155
|
+
frame=frame.decode("latin-1"),
|
|
156
|
+
telegram=telegram.decode("latin-1"),
|
|
157
|
+
payload=payload.decode("latin-1"),
|
|
158
|
+
telegram_type=telegram_type,
|
|
159
|
+
serial_number=serial_number,
|
|
160
|
+
checksum=checksum,
|
|
161
|
+
checksum_valid=checksum_valid,
|
|
162
|
+
)
|
|
163
|
+
self.on_telegram_received.emit(telegram_received)
|
|
164
|
+
|
|
165
|
+
def sendFrame(self, data: bytes) -> None:
|
|
166
|
+
"""Send telegram frame.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
data: Raw telegram payload (without checksum/framing).
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
IOError: If transport is not open.
|
|
173
|
+
"""
|
|
174
|
+
self.on_send_frame.emit(data)
|
|
175
|
+
|
|
176
|
+
# Calculate full frame (add checksum and brackets)
|
|
177
|
+
checksum = calculate_checksum(data.decode())
|
|
178
|
+
frame_data = data.decode() + checksum
|
|
179
|
+
frame = b"<" + frame_data.encode() + b">"
|
|
180
|
+
|
|
181
|
+
if not self.transport:
|
|
182
|
+
self.logger.info("Invalid transport")
|
|
183
|
+
raise IOError("Transport is not open")
|
|
184
|
+
|
|
185
|
+
self.logger.debug(f"Sending frame: {frame.decode()}")
|
|
186
|
+
self.transport.write(frame) # type: ignore
|
|
187
|
+
self.on_telegram_sent.emit(frame.decode())
|
|
188
|
+
self._reset_timeout()
|
|
189
|
+
|
|
190
|
+
def send_telegram(
|
|
191
|
+
self,
|
|
192
|
+
telegram_type: TelegramType,
|
|
193
|
+
serial_number: str,
|
|
194
|
+
system_function: SystemFunction,
|
|
195
|
+
data_value: str,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Send telegram with specified parameters.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
telegram_type: Type of telegram to send.
|
|
201
|
+
serial_number: Device serial number.
|
|
202
|
+
system_function: System function code.
|
|
203
|
+
data_value: Data value to send.
|
|
204
|
+
"""
|
|
205
|
+
payload = (
|
|
206
|
+
f"{telegram_type.value}"
|
|
207
|
+
f"{serial_number}"
|
|
208
|
+
f"F{system_function.value}"
|
|
209
|
+
f"D{data_value}"
|
|
210
|
+
)
|
|
211
|
+
self.telegram_queue.put_nowait(payload.encode())
|
|
212
|
+
self._reactor.callLater(0.0, self.start_queue_manager)
|
|
213
|
+
|
|
214
|
+
def buildProtocol(self, addr: IAddress) -> protocol.Protocol:
|
|
215
|
+
"""Build protocol instance for connection.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
addr: Address of the connection.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
Protocol instance for this connection.
|
|
222
|
+
"""
|
|
223
|
+
self.logger.debug(f"buildProtocol: {addr}")
|
|
224
|
+
return self
|
|
225
|
+
|
|
226
|
+
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
227
|
+
"""Handle client connection failure.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
connector: Connection connector instance.
|
|
231
|
+
reason: Failure reason details.
|
|
232
|
+
"""
|
|
233
|
+
self.logger.debug(f"clientConnectionFailed: {reason}")
|
|
234
|
+
self.on_client_connection_failed.emit(reason)
|
|
235
|
+
self.connection_failed(reason)
|
|
236
|
+
self._cancel_timeout()
|
|
237
|
+
|
|
238
|
+
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
239
|
+
"""Handle client connection lost event.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
connector: Connection connector instance.
|
|
243
|
+
reason: Reason for connection loss.
|
|
244
|
+
"""
|
|
245
|
+
self.logger.debug(f"clientConnectionLost: {reason}")
|
|
246
|
+
self.on_connection_lost.emit(reason)
|
|
247
|
+
self._cancel_timeout()
|
|
248
|
+
|
|
249
|
+
def timeout(self) -> None:
|
|
250
|
+
"""Handle timeout event."""
|
|
251
|
+
self.logger.info("Timeout after: %ss", self.timeout_seconds)
|
|
252
|
+
self.on_timeout.emit()
|
|
253
|
+
|
|
254
|
+
def connection_failed(self, reason: Failure) -> None:
|
|
255
|
+
"""Handle connection failure.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
reason: Failure reason details.
|
|
259
|
+
"""
|
|
260
|
+
self.logger.debug(f"Client connection failed: {reason}")
|
|
261
|
+
self.on_connection_failed.emit(reason)
|
|
262
|
+
self.on_failed.emit(reason.getErrorMessage())
|
|
263
|
+
|
|
264
|
+
def _reset_timeout(self) -> None:
|
|
265
|
+
"""Reset the inactivity timeout."""
|
|
266
|
+
self._cancel_timeout()
|
|
267
|
+
self.timeout_call = self._reactor.callLater(
|
|
268
|
+
self.timeout_seconds, self._on_timeout
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def _cancel_timeout(self) -> None:
|
|
272
|
+
"""Cancel the inactivity timeout."""
|
|
273
|
+
if self.timeout_call and self.timeout_call.active():
|
|
274
|
+
self.timeout_call.cancel()
|
|
275
|
+
|
|
276
|
+
def _on_timeout(self) -> None:
|
|
277
|
+
"""Handle inactivity timeout expiration."""
|
|
278
|
+
self.timeout()
|
|
279
|
+
self.logger.debug(f"Conbus timeout after {self.timeout_seconds} seconds")
|
|
280
|
+
|
|
281
|
+
def stop_reactor(self) -> None:
|
|
282
|
+
"""Stop the reactor if it's running."""
|
|
283
|
+
if self._reactor.running:
|
|
284
|
+
self.logger.info("Stopping reactor")
|
|
285
|
+
self._reactor.stop()
|
|
286
|
+
|
|
287
|
+
def start_reactor(self) -> None:
|
|
288
|
+
"""Start the reactor if it's running."""
|
|
289
|
+
# Connect to TCP server
|
|
290
|
+
self.logger.info(
|
|
291
|
+
f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
|
|
292
|
+
)
|
|
293
|
+
self._reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
|
|
294
|
+
|
|
295
|
+
# Run the reactor (which now uses asyncio underneath)
|
|
296
|
+
self.logger.info("Starting reactor event loop.")
|
|
297
|
+
self._reactor.run()
|
|
298
|
+
|
|
299
|
+
def start_queue_manager(self) -> None:
|
|
300
|
+
"""Start the queue manager if it's not running."""
|
|
301
|
+
with self.queue_manager_lock:
|
|
302
|
+
if self.queue_manager_running:
|
|
303
|
+
return
|
|
304
|
+
self.logger.debug("Queue manager: starting")
|
|
305
|
+
self.queue_manager_running = True
|
|
306
|
+
self.process_telegram_queue()
|
|
307
|
+
|
|
308
|
+
def process_telegram_queue(self) -> None:
|
|
309
|
+
"""Start the queue manager if it's not running."""
|
|
310
|
+
self.logger.debug(
|
|
311
|
+
f"Queue manager: processing (remaining: {self.telegram_queue.qsize()})"
|
|
312
|
+
)
|
|
313
|
+
if self.telegram_queue.empty():
|
|
314
|
+
with self.queue_manager_lock:
|
|
315
|
+
self.logger.debug("Queue manager: stopping")
|
|
316
|
+
self.queue_manager_running = False
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
self.logger.debug("Queue manager: event loop")
|
|
320
|
+
telegram = self.telegram_queue.get_nowait()
|
|
321
|
+
self.sendFrame(telegram)
|
|
322
|
+
later = randint(10, 80) / 100
|
|
323
|
+
self._reactor.callLater(later, self.process_telegram_queue)
|
|
324
|
+
|
|
325
|
+
def __enter__(self) -> "ConbusEventProtocol":
|
|
326
|
+
"""Enter context manager.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Self for context management.
|
|
330
|
+
"""
|
|
331
|
+
self.logger.debug("Entering the event loop.")
|
|
332
|
+
return self
|
|
333
|
+
|
|
334
|
+
def __exit__(
|
|
335
|
+
self,
|
|
336
|
+
_exc_type: Optional[type],
|
|
337
|
+
_exc_val: Optional[BaseException],
|
|
338
|
+
_exc_tb: Optional[Any],
|
|
339
|
+
) -> None:
|
|
340
|
+
"""Context manager exit - ensure connection is closed."""
|
|
341
|
+
self.logger.debug("Exiting the event loop.")
|
|
342
|
+
self.stop_reactor()
|
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_raw_service import ConbusEventRawService
|
|
46
47
|
from xp.services.conbus.conbus_output_service import ConbusOutputService
|
|
47
48
|
from xp.services.conbus.conbus_raw_service import ConbusRawService
|
|
48
49
|
from xp.services.conbus.conbus_receive_service import ConbusReceiveService
|
|
@@ -58,6 +59,7 @@ from xp.services.homekit.homekit_outlet_service import HomeKitOutletService
|
|
|
58
59
|
from xp.services.homekit.homekit_service import HomeKitService
|
|
59
60
|
from xp.services.log_file_service import LogFileService
|
|
60
61
|
from xp.services.module_type_service import ModuleTypeService
|
|
62
|
+
from xp.services.protocol import ConbusEventProtocol
|
|
61
63
|
from xp.services.protocol.protocol_factory import TelegramFactory
|
|
62
64
|
from xp.services.protocol.telegram_protocol import TelegramProtocol
|
|
63
65
|
from xp.services.reverse_proxy_service import ReverseProxyService
|
|
@@ -163,14 +165,30 @@ class ServiceContainer:
|
|
|
163
165
|
)
|
|
164
166
|
|
|
165
167
|
self.container.register(
|
|
166
|
-
|
|
167
|
-
factory=lambda:
|
|
168
|
+
ConbusEventProtocol,
|
|
169
|
+
factory=lambda: ConbusEventProtocol(
|
|
168
170
|
cli_config=self.container.resolve(ConbusClientConfig),
|
|
169
171
|
reactor=self.container.resolve(PosixReactorBase),
|
|
170
172
|
),
|
|
171
173
|
scope=punq.Scope.singleton,
|
|
172
174
|
)
|
|
173
175
|
|
|
176
|
+
self.container.register(
|
|
177
|
+
ConbusDiscoverService,
|
|
178
|
+
factory=lambda: ConbusDiscoverService(
|
|
179
|
+
conbus_protocol=self.container.resolve(ConbusEventProtocol)
|
|
180
|
+
),
|
|
181
|
+
scope=punq.Scope.singleton,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self.container.register(
|
|
185
|
+
ConbusEventRawService,
|
|
186
|
+
factory=lambda: ConbusEventRawService(
|
|
187
|
+
conbus_protocol=self.container.resolve(ConbusEventProtocol)
|
|
188
|
+
),
|
|
189
|
+
scope=punq.Scope.singleton,
|
|
190
|
+
)
|
|
191
|
+
|
|
174
192
|
self.container.register(
|
|
175
193
|
ConbusBlinkService,
|
|
176
194
|
factory=lambda: ConbusBlinkService(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|