conson-xp 1.16.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.16.0
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
@@ -305,6 +305,10 @@ xp conbus datapoint query
305
305
 
306
306
  xp conbus discover
307
307
 
308
+ xp conbus event
309
+ xp conbus event raw
310
+
311
+
308
312
  xp conbus lightlevel
309
313
  xp conbus lightlevel get
310
314
  xp conbus lightlevel off
@@ -1,11 +1,11 @@
1
- conson_xp-1.16.0.dist-info/METADATA,sha256=vy5t4nVHJcDwnzs6v6zmBlGsEOz-fu5AS9Xr7XfcIGo,9468
2
- conson_xp-1.16.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
3
- conson_xp-1.16.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.16.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=VssXZod82LgI2ba-rx7RfBTkJ4tXhhfY1_DlC0xf4sU,181
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=EGDWTEH_pCKIMWelnjhK4_PxBsvIH24rO9fz1nF1pKA,4638
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
@@ -15,6 +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=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
@@ -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=wCyJNKBd8J2ziOm0g00eUZH4OeTaLO5vHuoQGd_AJbg,1111
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
@@ -117,6 +119,7 @@ xp/services/conbus/conbus_custom_service.py,sha256=4aneYdPObiZOGxPFYg5Wr70cl_xFx
117
119
  xp/services/conbus/conbus_datapoint_queryall_service.py,sha256=p9R02cVimhdJILHQ6BoeZj8Hog4oRpqBnMo3t4R8ecY,6816
118
120
  xp/services/conbus/conbus_datapoint_service.py,sha256=SYhHj9RmTmaJ750tyZ1IW2kl7tgDQ1xm_EM1zUjk1aQ,6421
119
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
@@ -165,8 +168,8 @@ xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmX
165
168
  xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
166
169
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
167
170
  xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
168
- xp/utils/dependencies.py,sha256=yU5BcD9DnByEL1dL9gdTEaJfxlyBEkomIg_AXGnzDk0,20591
171
+ xp/utils/dependencies.py,sha256=QsZfPDMdlrQK01YQ4PRQ8Q59ZF9w22h1evWX3J4xCjE,20930
169
172
  xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
170
173
  xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
171
174
  xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
172
- conson_xp-1.16.0.dist-info/RECORD,,
175
+ conson_xp-1.17.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.16.0"
6
+ __version__ = "1.17.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -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",
@@ -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/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
+ }
@@ -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
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
@@ -180,6 +181,14 @@ class ServiceContainer:
180
181
  scope=punq.Scope.singleton,
181
182
  )
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
+
183
192
  self.container.register(
184
193
  ConbusBlinkService,
185
194
  factory=lambda: ConbusBlinkService(