conson-xp 1.23.0__py3-none-any.whl → 1.25.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.23.0.dist-info → conson_xp-1.25.0.dist-info}/METADATA +1 -1
- {conson_xp-1.23.0.dist-info → conson_xp-1.25.0.dist-info}/RECORD +13 -13
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_discover_commands.py +7 -2
- xp/cli/commands/conbus/conbus_receive_commands.py +5 -2
- xp/services/conbus/conbus_discover_service.py +61 -53
- xp/services/conbus/conbus_receive_service.py +30 -25
- xp/services/protocol/conbus_event_protocol.py +14 -1
- xp/term/protocol.yml +14 -14
- xp/term/widgets/protocol_log.py +3 -49
- {conson_xp-1.23.0.dist-info → conson_xp-1.25.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.23.0.dist-info → conson_xp-1.25.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.23.0.dist-info → conson_xp-1.25.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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.25.0.dist-info/METADATA,sha256=SHMAjJSulwuqw_08c2vljlv4EeJ-Yk3O8l6rs5a_8Kk,10298
|
|
2
|
+
conson_xp-1.25.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.25.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.25.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=8oS2mib7fQyhvVyBRnvOzVSyN-c8IUOz8SSUi5u_gIM,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=noh8fdZAWq-ihJEboP8WugbIgq4LJ3jUWMRA7720xWE,4909
|
|
@@ -14,7 +14,7 @@ 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=MnTCzvERO5xerfs0fuuIBoo1O9h_0IfoJ6snLGVl0lA,1899
|
|
18
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
|
|
@@ -22,7 +22,7 @@ xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=L7-6y3rDllOjQ9g6Bk
|
|
|
22
22
|
xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=fb9MQ4O04H0Dinpt7vSF5GtfntTZHelQ5TuUmSBbCTg,2899
|
|
23
23
|
xp/cli/commands/conbus/conbus_output_commands.py,sha256=zdRVbHzVhMbZpG2x5WXtujc3wKTsoQUV4IgkVIbJbCc,5019
|
|
24
24
|
xp/cli/commands/conbus/conbus_raw_commands.py,sha256=8BKUarwvHgz-sxML7n99YVsb8B1HJNExjQpRsuY_tQw,1829
|
|
25
|
-
xp/cli/commands/conbus/conbus_receive_commands.py,sha256=
|
|
25
|
+
xp/cli/commands/conbus/conbus_receive_commands.py,sha256=_PsC-3xidmJBuOWUS60iDzhSHYYn5ZFmORXap-ljVGM,1902
|
|
26
26
|
xp/cli/commands/conbus/conbus_scan_commands.py,sha256=JfXucOwOadvLEKT_fW9fwvqWKHaEODOojLjnO8JV_00,1730
|
|
27
27
|
xp/cli/commands/file_commands.py,sha256=GV102X7FRZDUNKLlzvSsIGcoXAaofOzmjCp3HUpE9lw,5532
|
|
28
28
|
xp/cli/commands/homekit/__init__.py,sha256=qqwY8ulxTx1S74Mzpb6EKjBLT6fWTNdf9PQ3HKuERKY,50
|
|
@@ -128,12 +128,12 @@ xp/services/conbus/conbus_blink_service.py,sha256=x9uM-sLnIEV8wSNsvJgo08E042g-Hh
|
|
|
128
128
|
xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFxlQIdJBPQSa0enI,5826
|
|
129
129
|
xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=p9R02cVimhdJILHQ6BoeZj8Hog4oRpqBnMo3t4R8ecY,6816
|
|
130
130
|
xp/services/conbus/conbus_datapoint_service.py,sha256=SYhHj9RmTmaJ750tyZ1IW2kl7tgDQ1xm_EM1zUjk1aQ,6421
|
|
131
|
-
xp/services/conbus/conbus_discover_service.py,sha256=
|
|
131
|
+
xp/services/conbus/conbus_discover_service.py,sha256=ZwjYBlgP6FgpHBJk7pcKr4JHfH7WUHDxe4he4F_HblQ,12740
|
|
132
132
|
xp/services/conbus/conbus_event_list_service.py,sha256=0xyXXNU44epN5bFkU6oiZMyhxfUguul3evqClvPJDcA,3618
|
|
133
133
|
xp/services/conbus/conbus_event_raw_service.py,sha256=FZFu-LNLInrTKTpiGLyootozvyIF5Si5FMrxNk2ALD0,7000
|
|
134
134
|
xp/services/conbus/conbus_output_service.py,sha256=mHFOAPx2zo0TStZ3pokp6v94AQjIamcwZDeg5YH_-eo,7240
|
|
135
135
|
xp/services/conbus/conbus_raw_service.py,sha256=4yZLLTIAOxpgByUTWZXw1ihGa6Xtl98ckj9T7VfprDI,4335
|
|
136
|
-
xp/services/conbus/conbus_receive_service.py,sha256=
|
|
136
|
+
xp/services/conbus/conbus_receive_service.py,sha256=7wOaEDrdoXwZE9MeUM89eB3hobYpvtbYk_YLv3MVAtc,5352
|
|
137
137
|
xp/services/conbus/conbus_scan_service.py,sha256=tHJ5qaxcNXxAZb2D2F1v6IrzydfxjJOYllM6Txt1eBE,5176
|
|
138
138
|
xp/services/conbus/write_config_service.py,sha256=6feNdixI_Nli4MRLe15nea-7gTEXMUwZIvTqv_1OqHI,7157
|
|
139
139
|
xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
|
|
@@ -153,7 +153,7 @@ xp/services/homekit/homekit_service.py,sha256=0lW-hg40ETco3gDBEYkR_sX-UIYsLSKCD4
|
|
|
153
153
|
xp/services/log_file_service.py,sha256=fvPcZQj8XOKUA-4ep5R8n0PelvwvRlTLlVxvIWM5KR4,10506
|
|
154
154
|
xp/services/module_type_service.py,sha256=xWhr1EAZMykL5uNWHWdpa5T8yNruGKH43XRTOS8GwZg,7477
|
|
155
155
|
xp/services/protocol/__init__.py,sha256=qRufBmqRKGzpuzZ5bxBbmwf510TT00Ke8s5HcWGnqRY,818
|
|
156
|
-
xp/services/protocol/conbus_event_protocol.py,sha256=
|
|
156
|
+
xp/services/protocol/conbus_event_protocol.py,sha256=7u_Gv7vM53Ikzo56D5Vua4y8_0vsl6nUjfRJmrzgUhk,14936
|
|
157
157
|
xp/services/protocol/conbus_protocol.py,sha256=JO7yLkD_ohPT0ETjnAIx4CGpZyobf4LdbuozM_43btE,10276
|
|
158
158
|
xp/services/protocol/protocol_factory.py,sha256=PmjN9AtW9sxNo3voqUiNgQA-pTvX1RW4XXFlHKfFr5Q,2470
|
|
159
159
|
xp/services/protocol/telegram_protocol.py,sha256=Ki5DrXsKxiaqLcdP9WWUuhUI7cPu2DfwyZkh-Gv9Lb8,9496
|
|
@@ -180,10 +180,10 @@ xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R74
|
|
|
180
180
|
xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
|
|
181
181
|
xp/term/protocol.py,sha256=acYYsv7_4z5ePrnslSC1exKzqbKOE5ZGds4J33Q2XNs,4784
|
|
182
182
|
xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
|
|
183
|
-
xp/term/protocol.yml,sha256=
|
|
183
|
+
xp/term/protocol.yml,sha256=BI1dyWfYsINsJnbSR-z4fzFOsYcY27dS6it8eo7AVnU,2124
|
|
184
184
|
xp/term/widgets/__init__.py,sha256=ftWmN_fmjxy2E8Qfm-YSRmzKfgL0KTBCTpgvYWCPbUY,274
|
|
185
185
|
xp/term/widgets/help_menu.py,sha256=bdT5AYRdtKt_tvZTVbG7-DPMb1mj78kggtjjsa-95BA,1780
|
|
186
|
-
xp/term/widgets/protocol_log.py,sha256=
|
|
186
|
+
xp/term/widgets/protocol_log.py,sha256=X-2CBcfZ4LpWrUVLxxyYqd54lP10mg0t7oclKh3sLkI,11243
|
|
187
187
|
xp/term/widgets/status_footer.py,sha256=eRZHkrG5aZCMulibX56KfFyGHS8IgL-7psvr9f9S6FI,1992
|
|
188
188
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
189
189
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
@@ -193,4 +193,4 @@ xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
|
|
|
193
193
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
194
194
|
xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
|
|
195
195
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
196
|
-
conson_xp-1.
|
|
196
|
+
conson_xp-1.25.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -36,6 +36,7 @@ def send_discover_telegram(ctx: click.Context) -> None:
|
|
|
36
36
|
discovered_devices: Discover response with all found devices.
|
|
37
37
|
"""
|
|
38
38
|
click.echo(json.dumps(discovered_devices.to_dict(), indent=2))
|
|
39
|
+
service.stop_reactor()
|
|
39
40
|
|
|
40
41
|
def on_device_discovered(discovered_device: DiscoveredDevice) -> None:
|
|
41
42
|
"""Handle discovery of sa single module.
|
|
@@ -57,5 +58,9 @@ def send_discover_telegram(ctx: click.Context) -> None:
|
|
|
57
58
|
service: ConbusDiscoverService = (
|
|
58
59
|
ctx.obj.get("container").get_container().resolve(ConbusDiscoverService)
|
|
59
60
|
)
|
|
60
|
-
service
|
|
61
|
-
|
|
61
|
+
with service:
|
|
62
|
+
service.on_progress.connect(progress)
|
|
63
|
+
service.on_device_discovered.connect(on_device_discovered)
|
|
64
|
+
service.on_finish.connect(on_finish)
|
|
65
|
+
service.set_timeout(5)
|
|
66
|
+
service.start_reactor()
|
|
@@ -43,8 +43,9 @@ def receive_telegrams(ctx: Context, timeout: float) -> None:
|
|
|
43
43
|
response_received: Receive response object with telegrams.
|
|
44
44
|
"""
|
|
45
45
|
click.echo(json.dumps(response_received.to_dict(), indent=2))
|
|
46
|
+
service.stop_reactor()
|
|
46
47
|
|
|
47
|
-
def
|
|
48
|
+
def on_progress(telegram_received: str) -> None:
|
|
48
49
|
"""Handle progress updates during telegram receive operation.
|
|
49
50
|
|
|
50
51
|
Args:
|
|
@@ -56,5 +57,7 @@ def receive_telegrams(ctx: Context, timeout: float) -> None:
|
|
|
56
57
|
ctx.obj.get("container").get_container().resolve(ConbusReceiveService)
|
|
57
58
|
)
|
|
58
59
|
with service:
|
|
59
|
-
service.
|
|
60
|
+
service.on_progress.connect(on_progress)
|
|
61
|
+
service.on_finish.connect(on_finish)
|
|
62
|
+
service.set_timeout(timeout)
|
|
60
63
|
service.start_reactor()
|
|
@@ -4,8 +4,11 @@ This service implements a TCP client that connects to Conbus servers and sends
|
|
|
4
4
|
discover telegrams to find modules on the network.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
7
8
|
import logging
|
|
8
|
-
from typing import
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from psygnal import Signal
|
|
9
12
|
|
|
10
13
|
from xp.models import ConbusDiscoverResponse
|
|
11
14
|
from xp.models.conbus.conbus_discover import DiscoveredDevice
|
|
@@ -26,9 +29,15 @@ class ConbusDiscoverService:
|
|
|
26
29
|
|
|
27
30
|
Attributes:
|
|
28
31
|
conbus_protocol: Protocol instance for Conbus communication.
|
|
32
|
+
on_progress: Signal emitted when discovery progress is made (with serial number).
|
|
33
|
+
on_finish: Signal emitted when discovery finishes (with result).
|
|
34
|
+
on_device_discovered: Signal emitted when a device is discovered (with device info).
|
|
29
35
|
"""
|
|
30
36
|
|
|
31
37
|
conbus_protocol: ConbusEventProtocol
|
|
38
|
+
on_progress: Signal = Signal(str)
|
|
39
|
+
on_finish: Signal = Signal(DiscoveredDevice)
|
|
40
|
+
on_device_discovered: Signal = Signal(ConbusDiscoverResponse)
|
|
32
41
|
|
|
33
42
|
def __init__(self, conbus_protocol: ConbusEventProtocol) -> None:
|
|
34
43
|
"""Initialize the Conbus discover service.
|
|
@@ -36,12 +45,6 @@ class ConbusDiscoverService:
|
|
|
36
45
|
Args:
|
|
37
46
|
conbus_protocol: ConbusProtocol.
|
|
38
47
|
"""
|
|
39
|
-
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
40
|
-
self.device_discover_callback: Optional[Callable[[DiscoveredDevice], None]] = (
|
|
41
|
-
None
|
|
42
|
-
)
|
|
43
|
-
self.finish_callback: Optional[Callable[[ConbusDiscoverResponse], None]] = None
|
|
44
|
-
|
|
45
48
|
self.conbus_protocol: ConbusEventProtocol = conbus_protocol
|
|
46
49
|
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
47
50
|
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
@@ -135,9 +138,7 @@ class ConbusDiscoverService:
|
|
|
135
138
|
"module_type_name": None,
|
|
136
139
|
}
|
|
137
140
|
self.discovered_device_result.discovered_devices.append(device)
|
|
138
|
-
|
|
139
|
-
if self.device_discover_callback:
|
|
140
|
-
self.device_discover_callback(device)
|
|
141
|
+
self.on_device_discovered.emit(device)
|
|
141
142
|
|
|
142
143
|
# Send READ_DATAPOINT telegram to query module type
|
|
143
144
|
self.logger.debug(f"Sending module type query for {serial_number}")
|
|
@@ -147,8 +148,7 @@ class ConbusDiscoverService:
|
|
|
147
148
|
system_function=SystemFunction.READ_DATAPOINT,
|
|
148
149
|
data_value=DataPointType.MODULE_TYPE.value,
|
|
149
150
|
)
|
|
150
|
-
|
|
151
|
-
self.progress_callback(serial_number)
|
|
151
|
+
self.on_progress.emit(serial_number)
|
|
152
152
|
|
|
153
153
|
def handle_module_type_code_response(
|
|
154
154
|
self, serial_number: str, module_type_code: str
|
|
@@ -194,8 +194,7 @@ class ConbusDiscoverService:
|
|
|
194
194
|
device["module_type_code"] = code
|
|
195
195
|
device["module_type_name"] = module_type_name
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
self.device_discover_callback(device)
|
|
197
|
+
self.on_device_discovered.emit(device)
|
|
199
198
|
|
|
200
199
|
self.logger.debug(
|
|
201
200
|
f"Updated device {serial_number} with module_type {module_type_name}"
|
|
@@ -231,9 +230,7 @@ class ConbusDiscoverService:
|
|
|
231
230
|
self.logger.debug(
|
|
232
231
|
f"Updated device {serial_number} with module_type {module_type}"
|
|
233
232
|
)
|
|
234
|
-
|
|
235
|
-
self.device_discover_callback(device)
|
|
236
|
-
|
|
233
|
+
self.on_device_discovered.emit(device)
|
|
237
234
|
break
|
|
238
235
|
|
|
239
236
|
self.conbus_protocol.send_telegram(
|
|
@@ -249,10 +246,7 @@ class ConbusDiscoverService:
|
|
|
249
246
|
self.logger.info("Discovery stopped after: %ss", timeout)
|
|
250
247
|
self.discovered_device_result.success = False
|
|
251
248
|
self.discovered_device_result.error = "Discovered device timeout"
|
|
252
|
-
|
|
253
|
-
self.finish_callback(self.discovered_device_result)
|
|
254
|
-
|
|
255
|
-
self.stop_reactor()
|
|
249
|
+
self.on_finish.emit(self.discovered_device_result)
|
|
256
250
|
|
|
257
251
|
def failed(self, message: str) -> None:
|
|
258
252
|
"""Handle failed connection event.
|
|
@@ -263,50 +257,64 @@ class ConbusDiscoverService:
|
|
|
263
257
|
self.logger.debug(f"Failed: {message}")
|
|
264
258
|
self.discovered_device_result.success = False
|
|
265
259
|
self.discovered_device_result.error = message
|
|
266
|
-
|
|
267
|
-
self.finish_callback(self.discovered_device_result)
|
|
268
|
-
|
|
269
|
-
self.stop_reactor()
|
|
260
|
+
self.on_finish.emit(self.discovered_device_result)
|
|
270
261
|
|
|
271
262
|
def succeed(self) -> None:
|
|
272
263
|
"""Handle discovered device success event."""
|
|
273
264
|
self.logger.debug("Succeed")
|
|
274
265
|
self.discovered_device_result.success = True
|
|
275
266
|
self.discovered_device_result.error = None
|
|
276
|
-
|
|
277
|
-
self.finish_callback(self.discovered_device_result)
|
|
267
|
+
self.on_finish.emit(self.discovered_device_result)
|
|
278
268
|
|
|
279
|
-
|
|
269
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
270
|
+
"""Setup callbacks and timeout for receiving telegrams.
|
|
280
271
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
self.
|
|
285
|
-
|
|
286
|
-
def start_reactor(self) -> None:
|
|
287
|
-
"""Start reactor."""
|
|
288
|
-
self.logger.info("Starting reactor")
|
|
289
|
-
self.conbus_protocol.start_reactor()
|
|
272
|
+
Args:
|
|
273
|
+
timeout_seconds: Optional timeout in seconds.
|
|
274
|
+
"""
|
|
275
|
+
self.logger.debug("Set timeout")
|
|
276
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
290
277
|
|
|
291
|
-
def
|
|
278
|
+
def set_event_loop(
|
|
292
279
|
self,
|
|
293
|
-
|
|
294
|
-
device_discover_callback: Callable[[DiscoveredDevice], None],
|
|
295
|
-
finish_callback: Callable[[ConbusDiscoverResponse], None],
|
|
296
|
-
timeout_seconds: Optional[float] = None,
|
|
280
|
+
event_loop: asyncio.AbstractEventLoop,
|
|
297
281
|
) -> None:
|
|
298
|
-
"""
|
|
282
|
+
"""Setup callbacks and timeout for receiving telegrams.
|
|
299
283
|
|
|
300
284
|
Args:
|
|
301
|
-
|
|
302
|
-
device_discover_callback: Callback for each discovered device.
|
|
303
|
-
finish_callback: Callback when discovery completes.
|
|
304
|
-
timeout_seconds: Optional timeout in seconds.
|
|
285
|
+
event_loop: Optional event loop to use for async operations.
|
|
305
286
|
"""
|
|
306
|
-
self.logger.
|
|
287
|
+
self.logger.debug("Set eventloop")
|
|
288
|
+
self.conbus_protocol.set_event_loop(event_loop)
|
|
307
289
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
self.
|
|
311
|
-
|
|
312
|
-
|
|
290
|
+
def start_reactor(self) -> None:
|
|
291
|
+
"""Start the reactor."""
|
|
292
|
+
self.conbus_protocol.start_reactor()
|
|
293
|
+
|
|
294
|
+
def stop_reactor(self) -> None:
|
|
295
|
+
"""Start the reactor."""
|
|
296
|
+
self.conbus_protocol.stop_reactor()
|
|
297
|
+
|
|
298
|
+
def __enter__(self) -> "ConbusDiscoverService":
|
|
299
|
+
"""Enter context manager.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Self for context manager protocol.
|
|
303
|
+
"""
|
|
304
|
+
# Reset state for singleton reuse
|
|
305
|
+
self.receive_response = ConbusDiscoverResponse(success=True)
|
|
306
|
+
return self
|
|
307
|
+
|
|
308
|
+
def __exit__(
|
|
309
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
310
|
+
) -> None:
|
|
311
|
+
"""Exit context manager and disconnect signals."""
|
|
312
|
+
self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
|
|
313
|
+
self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
|
|
314
|
+
self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
|
|
315
|
+
self.conbus_protocol.on_timeout.disconnect(self.timeout)
|
|
316
|
+
self.conbus_protocol.on_failed.disconnect(self.failed)
|
|
317
|
+
self.on_device_discovered.disconnect()
|
|
318
|
+
self.on_progress.disconnect()
|
|
319
|
+
self.on_finish.disconnect()
|
|
320
|
+
self.stop_reactor()
|
|
@@ -6,7 +6,9 @@ allowing clients to receive waiting event telegrams using empty telegram sends.
|
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
8
|
import logging
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from psygnal import Signal
|
|
10
12
|
|
|
11
13
|
from xp.models.conbus.conbus_receive import ConbusReceiveResponse
|
|
12
14
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
@@ -22,9 +24,13 @@ class ConbusReceiveService:
|
|
|
22
24
|
|
|
23
25
|
Attributes:
|
|
24
26
|
conbus_protocol: Protocol instance for Conbus communication.
|
|
27
|
+
on_progress: Signal emitted when a telegram is received (with telegram frame).
|
|
28
|
+
on_finish: Signal emitted when receiving finishes (with result).
|
|
25
29
|
"""
|
|
26
30
|
|
|
27
31
|
conbus_protocol: ConbusEventProtocol
|
|
32
|
+
on_progress: Signal = Signal(str)
|
|
33
|
+
on_finish: Signal = Signal(ConbusReceiveResponse)
|
|
28
34
|
|
|
29
35
|
def __init__(self, conbus_protocol: ConbusEventProtocol) -> None:
|
|
30
36
|
"""Initialize the Conbus receive service.
|
|
@@ -32,8 +38,6 @@ class ConbusReceiveService:
|
|
|
32
38
|
Args:
|
|
33
39
|
conbus_protocol: ConbusEventProtocol instance.
|
|
34
40
|
"""
|
|
35
|
-
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
36
|
-
self.finish_callback: Optional[Callable[[ConbusReceiveResponse], None]] = None
|
|
37
41
|
self.receive_response: ConbusReceiveResponse = ConbusReceiveResponse(
|
|
38
42
|
success=True
|
|
39
43
|
)
|
|
@@ -67,8 +71,7 @@ class ConbusReceiveService:
|
|
|
67
71
|
telegram_received: The telegram received event.
|
|
68
72
|
"""
|
|
69
73
|
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
70
|
-
|
|
71
|
-
self.progress_callback(telegram_received.frame)
|
|
74
|
+
self.on_progress.emit(telegram_received.frame)
|
|
72
75
|
|
|
73
76
|
if not self.receive_response.received_telegrams:
|
|
74
77
|
self.receive_response.received_telegrams = []
|
|
@@ -79,8 +82,7 @@ class ConbusReceiveService:
|
|
|
79
82
|
timeout = self.conbus_protocol.timeout_seconds
|
|
80
83
|
self.logger.info("Receive stopped after: %ss", timeout)
|
|
81
84
|
self.receive_response.success = True
|
|
82
|
-
|
|
83
|
-
self.finish_callback(self.receive_response)
|
|
85
|
+
self.on_finish.emit(self.receive_response)
|
|
84
86
|
|
|
85
87
|
def failed(self, message: str) -> None:
|
|
86
88
|
"""Handle failed connection event.
|
|
@@ -91,37 +93,37 @@ class ConbusReceiveService:
|
|
|
91
93
|
self.logger.debug("Failed %s:", message)
|
|
92
94
|
self.receive_response.success = False
|
|
93
95
|
self.receive_response.error = message
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
self.on_finish.emit(self.receive_response)
|
|
97
|
+
|
|
98
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
99
|
+
"""Setup callbacks and timeout for receiving telegrams.
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
Args:
|
|
102
|
+
timeout_seconds: Optional timeout in seconds.
|
|
103
|
+
"""
|
|
104
|
+
self.logger.debug("Set timeout")
|
|
105
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
106
|
+
|
|
107
|
+
def set_event_loop(
|
|
98
108
|
self,
|
|
99
|
-
|
|
100
|
-
finish_callback: Callable[[ConbusReceiveResponse], None],
|
|
101
|
-
timeout_seconds: Optional[float] = None,
|
|
102
|
-
event_loop: Optional[asyncio.AbstractEventLoop] = None,
|
|
109
|
+
event_loop: asyncio.AbstractEventLoop,
|
|
103
110
|
) -> None:
|
|
104
111
|
"""Setup callbacks and timeout for receiving telegrams.
|
|
105
112
|
|
|
106
113
|
Args:
|
|
107
|
-
progress_callback: Callback for each received telegram.
|
|
108
|
-
finish_callback: Callback when receiving completes.
|
|
109
|
-
timeout_seconds: Optional timeout in seconds.
|
|
110
114
|
event_loop: Optional event loop to use for async operations.
|
|
111
115
|
"""
|
|
112
|
-
self.logger.
|
|
113
|
-
|
|
114
|
-
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
115
|
-
self.progress_callback = progress_callback
|
|
116
|
-
self.finish_callback = finish_callback
|
|
117
|
-
|
|
118
|
-
if event_loop:
|
|
119
|
-
self.conbus_protocol.set_event_loop(event_loop)
|
|
116
|
+
self.logger.debug("Set eventloop")
|
|
117
|
+
self.conbus_protocol.set_event_loop(event_loop)
|
|
120
118
|
|
|
121
119
|
def start_reactor(self) -> None:
|
|
122
120
|
"""Start the reactor."""
|
|
123
121
|
self.conbus_protocol.start_reactor()
|
|
124
122
|
|
|
123
|
+
def stop_reactor(self) -> None:
|
|
124
|
+
"""Start the reactor."""
|
|
125
|
+
self.conbus_protocol.stop_reactor()
|
|
126
|
+
|
|
125
127
|
def __enter__(self) -> "ConbusReceiveService":
|
|
126
128
|
"""Enter context manager.
|
|
127
129
|
|
|
@@ -141,3 +143,6 @@ class ConbusReceiveService:
|
|
|
141
143
|
self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
|
|
142
144
|
self.conbus_protocol.on_timeout.disconnect(self.timeout)
|
|
143
145
|
self.conbus_protocol.on_failed.disconnect(self.failed)
|
|
146
|
+
self.on_progress.disconnect()
|
|
147
|
+
self.on_finish.disconnect()
|
|
148
|
+
self.stop_reactor()
|
|
@@ -325,10 +325,23 @@ class ConbusEventProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
325
325
|
self._reactor.stop()
|
|
326
326
|
|
|
327
327
|
def connect(self) -> None:
|
|
328
|
-
"""Connect to TCP server.
|
|
328
|
+
"""Connect to TCP server.
|
|
329
|
+
|
|
330
|
+
Automatically detects and integrates with running asyncio event loop if present.
|
|
331
|
+
"""
|
|
329
332
|
self.logger.info(
|
|
330
333
|
f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
|
|
331
334
|
)
|
|
335
|
+
|
|
336
|
+
# Auto-detect and integrate with asyncio event loop if available
|
|
337
|
+
try:
|
|
338
|
+
event_loop = asyncio.get_running_loop()
|
|
339
|
+
self.logger.debug(f"Detected running event loop: {event_loop}")
|
|
340
|
+
self.set_event_loop(event_loop)
|
|
341
|
+
except RuntimeError:
|
|
342
|
+
# No running event loop - that's fine for non-async contexts
|
|
343
|
+
self.logger.debug("No running event loop detected - using reactor only")
|
|
344
|
+
|
|
332
345
|
self._reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
|
|
333
346
|
|
|
334
347
|
def disconnect(self) -> None:
|
xp/term/protocol.yml
CHANGED
|
@@ -63,8 +63,8 @@ protocol:
|
|
|
63
63
|
"e":
|
|
64
64
|
name: "Link 1 Off"
|
|
65
65
|
telegrams:
|
|
66
|
-
-
|
|
67
|
-
-
|
|
66
|
+
- E02L01I09M
|
|
67
|
+
- E02L01I09B
|
|
68
68
|
|
|
69
69
|
"f":
|
|
70
70
|
name: "Link 2 On"
|
|
@@ -75,8 +75,8 @@ protocol:
|
|
|
75
75
|
"g":
|
|
76
76
|
name: "Link 2 Off"
|
|
77
77
|
telegrams:
|
|
78
|
-
-
|
|
79
|
-
-
|
|
78
|
+
- E02L02I09M
|
|
79
|
+
- E02L02I09B
|
|
80
80
|
|
|
81
81
|
"h":
|
|
82
82
|
name: "Link 3 On"
|
|
@@ -87,8 +87,8 @@ protocol:
|
|
|
87
87
|
"i":
|
|
88
88
|
name: "Link 3 Off"
|
|
89
89
|
telegrams:
|
|
90
|
-
-
|
|
91
|
-
-
|
|
90
|
+
- E02L03I09M
|
|
91
|
+
- E02L03I09B
|
|
92
92
|
|
|
93
93
|
"j":
|
|
94
94
|
name: "Link 4 On"
|
|
@@ -99,8 +99,8 @@ protocol:
|
|
|
99
99
|
"k":
|
|
100
100
|
name: "Link 4 Off"
|
|
101
101
|
telegrams:
|
|
102
|
-
-
|
|
103
|
-
-
|
|
102
|
+
- E02L04I09M
|
|
103
|
+
- E02L04I09B
|
|
104
104
|
|
|
105
105
|
"l":
|
|
106
106
|
name: "Link 5 On"
|
|
@@ -111,8 +111,8 @@ protocol:
|
|
|
111
111
|
"m":
|
|
112
112
|
name: "Link 5 Off"
|
|
113
113
|
telegrams:
|
|
114
|
-
-
|
|
115
|
-
-
|
|
114
|
+
- E02L05I09M
|
|
115
|
+
- E02L05I09B
|
|
116
116
|
|
|
117
117
|
"n":
|
|
118
118
|
name: "Link 6 On"
|
|
@@ -123,8 +123,8 @@ protocol:
|
|
|
123
123
|
"o":
|
|
124
124
|
name: "Link 6 Off"
|
|
125
125
|
telegrams:
|
|
126
|
-
-
|
|
127
|
-
-
|
|
126
|
+
- E02L06I09M
|
|
127
|
+
- E02L06I09B
|
|
128
128
|
|
|
129
129
|
"p":
|
|
130
130
|
name: "Link 7 On"
|
|
@@ -135,5 +135,5 @@ protocol:
|
|
|
135
135
|
"q":
|
|
136
136
|
name: "Link 7 Off"
|
|
137
137
|
telegrams:
|
|
138
|
-
-
|
|
139
|
-
-
|
|
138
|
+
- E02L07I09M
|
|
139
|
+
- E02L07I09B
|
xp/term/widgets/protocol_log.py
CHANGED
|
@@ -12,7 +12,6 @@ from twisted.python.failure import Failure
|
|
|
12
12
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
13
13
|
from xp.models.term.connection_state import ConnectionState
|
|
14
14
|
from xp.models.term.status_message import StatusMessageChanged
|
|
15
|
-
from xp.services.conbus.conbus_receive_service import ConbusReceiveService
|
|
16
15
|
from xp.services.protocol import ConbusEventProtocol
|
|
17
16
|
|
|
18
17
|
|
|
@@ -26,7 +25,6 @@ class ProtocolLogWidget(Widget):
|
|
|
26
25
|
container: ServiceContainer for dependency injection.
|
|
27
26
|
connection_state: Current connection state (reactive).
|
|
28
27
|
protocol: Reference to ConbusEventProtocol (prevents duplicate connections).
|
|
29
|
-
service: ConbusReceiveService instance.
|
|
30
28
|
logger: Logger instance for this widget.
|
|
31
29
|
log_widget: RichLog widget for displaying messages.
|
|
32
30
|
"""
|
|
@@ -43,7 +41,6 @@ class ProtocolLogWidget(Widget):
|
|
|
43
41
|
self.border_title = "Protocol"
|
|
44
42
|
self.container = container
|
|
45
43
|
self.protocol: Optional[ConbusEventProtocol] = None
|
|
46
|
-
self.service: Optional[ConbusReceiveService] = None
|
|
47
44
|
self.logger = logging.getLogger(__name__)
|
|
48
45
|
self.log_widget: Optional[RichLog] = None
|
|
49
46
|
self._state_machine = ConnectionState.create_state_machine()
|
|
@@ -64,8 +61,7 @@ class ProtocolLogWidget(Widget):
|
|
|
64
61
|
Resolves ConbusReceiveService and connects signals.
|
|
65
62
|
"""
|
|
66
63
|
# Resolve service from container (singleton)
|
|
67
|
-
self.
|
|
68
|
-
self.protocol = self.service.conbus_protocol
|
|
64
|
+
self.protocol = self.container.resolve(ConbusEventProtocol)
|
|
69
65
|
|
|
70
66
|
# Connect psygnal signals
|
|
71
67
|
self.protocol.on_connection_made.connect(self._on_connection_made)
|
|
@@ -86,10 +82,6 @@ class ProtocolLogWidget(Widget):
|
|
|
86
82
|
Integrates Twisted reactor with Textual's asyncio loop cleanly.
|
|
87
83
|
"""
|
|
88
84
|
# Guard against duplicate connections (race condition)
|
|
89
|
-
if self.service is None:
|
|
90
|
-
self.logger.error("Service not initialized")
|
|
91
|
-
return
|
|
92
|
-
|
|
93
85
|
if self.protocol is None:
|
|
94
86
|
self.logger.error("Protocol not initialized")
|
|
95
87
|
return
|
|
@@ -109,46 +101,8 @@ class ProtocolLogWidget(Widget):
|
|
|
109
101
|
f"Connecting to {self.protocol.cli_config.ip}:{self.protocol.cli_config.port}..."
|
|
110
102
|
)
|
|
111
103
|
|
|
112
|
-
#
|
|
113
|
-
self.
|
|
114
|
-
self.logger.info(f"Reactor object: {self.protocol._reactor}")
|
|
115
|
-
self.logger.info(f"Reactor running: {self.protocol._reactor.running}")
|
|
116
|
-
|
|
117
|
-
# Setup service callbacks
|
|
118
|
-
def progress_callback(telegram: str) -> None:
|
|
119
|
-
"""Handle progress updates for telegram reception.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
telegram: Received telegram string.
|
|
123
|
-
"""
|
|
124
|
-
pass
|
|
125
|
-
|
|
126
|
-
def finish_callback(response: Any) -> None:
|
|
127
|
-
"""Handle completion of telegram reception.
|
|
128
|
-
|
|
129
|
-
Args:
|
|
130
|
-
response: Response object from telegram reception.
|
|
131
|
-
"""
|
|
132
|
-
pass
|
|
133
|
-
|
|
134
|
-
# Get the currently running asyncio event loop (Textual's loop)
|
|
135
|
-
event_loop = asyncio.get_running_loop()
|
|
136
|
-
self.logger.info(f"Current running loop: {event_loop}")
|
|
137
|
-
self.logger.info(f"Loop is running: {event_loop.is_running()}")
|
|
138
|
-
|
|
139
|
-
self.service.init(
|
|
140
|
-
progress_callback=progress_callback,
|
|
141
|
-
finish_callback=finish_callback,
|
|
142
|
-
timeout_seconds=None, # Continuous monitoring
|
|
143
|
-
event_loop=event_loop,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
reactor = self.service.conbus_protocol._reactor
|
|
147
|
-
reactor.connectTCP(
|
|
148
|
-
self.protocol.cli_config.ip,
|
|
149
|
-
self.protocol.cli_config.port,
|
|
150
|
-
self.protocol,
|
|
151
|
-
)
|
|
104
|
+
# Connect to server (auto-detects and integrates with asyncio event loop)
|
|
105
|
+
self.protocol.connect()
|
|
152
106
|
|
|
153
107
|
# Wait for connection to establish
|
|
154
108
|
await asyncio.sleep(1.0)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|