conson-xp 1.1.0__py3-none-any.whl → 1.2.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.1.0.dist-info → conson_xp-1.2.0.dist-info}/METADATA +1 -1
- conson_xp-1.2.0.dist-info/RECORD +181 -0
- xp/__init__.py +4 -3
- xp/api/main.py +18 -3
- xp/api/models/api.py +13 -2
- xp/api/models/discover.py +12 -2
- xp/api/routers/conbus_blink.py +18 -6
- xp/api/routers/conbus_custom.py +11 -3
- xp/api/routers/conbus_datapoint.py +10 -3
- xp/api/routers/conbus_output.py +29 -9
- xp/api/routers/errors.py +6 -5
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -0
- xp/cli/commands/api.py +1 -5
- xp/cli/commands/api_start_commands.py +14 -8
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +21 -1
- xp/cli/commands/conbus/conbus_autoreport_commands.py +21 -11
- xp/cli/commands/conbus/conbus_blink_commands.py +53 -21
- xp/cli/commands/conbus/conbus_config_commands.py +7 -4
- xp/cli/commands/conbus/conbus_custom_commands.py +13 -4
- xp/cli/commands/conbus/conbus_datapoint_commands.py +28 -8
- xp/cli/commands/conbus/conbus_discover_commands.py +15 -4
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +50 -11
- xp/cli/commands/conbus/conbus_linknumber_commands.py +21 -11
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +25 -1
- xp/cli/commands/conbus/conbus_output_commands.py +46 -12
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +15 -7
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +26 -15
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +5 -5
- xp/cli/commands/module_commands.py +26 -19
- xp/cli/commands/reverse_proxy_commands.py +24 -18
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -1
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +22 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_autoreport_get_service.py +17 -8
- xp/services/conbus/conbus_autoreport_set_service.py +29 -16
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +33 -12
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_lightlevel_get_service.py +22 -14
- xp/services/conbus/conbus_lightlevel_set_service.py +40 -20
- xp/services/conbus/conbus_linknumber_get_service.py +18 -10
- xp/services/conbus/conbus_linknumber_set_service.py +34 -8
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +44 -18
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +110 -16
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +0 -1
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Conbus Protocol for XP telegram communication.
|
|
2
|
+
|
|
3
|
+
This module implements the Twisted protocol for Conbus communication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
from typing import Any, Optional
|
|
3
8
|
|
|
@@ -17,8 +22,15 @@ from xp.utils import calculate_checksum
|
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
20
|
-
"""
|
|
21
|
-
|
|
25
|
+
"""Twisted protocol for XP telegram communication.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
buffer: Buffer for incoming telegram data.
|
|
29
|
+
logger: Logger instance for this protocol.
|
|
30
|
+
cli_config: Conbus configuration settings.
|
|
31
|
+
reactor: Twisted reactor instance.
|
|
32
|
+
timeout_seconds: Timeout duration in seconds.
|
|
33
|
+
timeout_call: Delayed call handle for timeout management.
|
|
22
34
|
"""
|
|
23
35
|
|
|
24
36
|
buffer: bytes
|
|
@@ -28,6 +40,12 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
28
40
|
cli_config: ConbusClientConfig,
|
|
29
41
|
reactor: PosixReactorBase,
|
|
30
42
|
) -> None:
|
|
43
|
+
"""Initialize ConbusProtocol.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
cli_config: Configuration for Conbus client connection.
|
|
47
|
+
reactor: Twisted reactor for event handling.
|
|
48
|
+
"""
|
|
31
49
|
self.buffer = b""
|
|
32
50
|
self.logger = logging.getLogger(__name__)
|
|
33
51
|
self.cli_config = cli_config.conbus
|
|
@@ -36,12 +54,24 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
36
54
|
self.timeout_call: Optional[DelayedCall] = None
|
|
37
55
|
|
|
38
56
|
def connectionMade(self) -> None:
|
|
57
|
+
"""Handle connection established event.
|
|
58
|
+
|
|
59
|
+
Called when TCP connection is successfully established.
|
|
60
|
+
Starts inactivity timeout monitoring.
|
|
61
|
+
"""
|
|
39
62
|
self.logger.debug("connectionMade")
|
|
40
63
|
self.connection_established()
|
|
41
64
|
# Start inactivity timeout
|
|
42
65
|
self._reset_timeout()
|
|
43
66
|
|
|
44
67
|
def dataReceived(self, data: bytes) -> None:
|
|
68
|
+
"""Handle received data from TCP connection.
|
|
69
|
+
|
|
70
|
+
Parses incoming telegram frames and dispatches events.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
data: Raw bytes received from connection.
|
|
74
|
+
"""
|
|
45
75
|
self.logger.debug("dataReceived")
|
|
46
76
|
self.buffer += data
|
|
47
77
|
|
|
@@ -94,11 +124,13 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
94
124
|
self.telegram_received(telegram_received)
|
|
95
125
|
|
|
96
126
|
def sendFrame(self, data: bytes) -> None:
|
|
97
|
-
"""
|
|
98
|
-
Send telegram frame
|
|
127
|
+
"""Send telegram frame.
|
|
99
128
|
|
|
100
129
|
Args:
|
|
101
|
-
data: Raw telegram payload (without checksum/framing)
|
|
130
|
+
data: Raw telegram payload (without checksum/framing).
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
IOError: If transport is not open.
|
|
102
134
|
"""
|
|
103
135
|
# Calculate full frame (add checksum and brackets)
|
|
104
136
|
checksum = calculate_checksum(data.decode())
|
|
@@ -121,6 +153,14 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
121
153
|
system_function: SystemFunction,
|
|
122
154
|
data_value: str,
|
|
123
155
|
) -> None:
|
|
156
|
+
"""Send telegram with specified parameters.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
telegram_type: Type of telegram to send.
|
|
160
|
+
serial_number: Device serial number.
|
|
161
|
+
system_function: System function code.
|
|
162
|
+
data_value: Data value to send.
|
|
163
|
+
"""
|
|
124
164
|
payload = (
|
|
125
165
|
f"{telegram_type.value}"
|
|
126
166
|
f"{serial_number}"
|
|
@@ -130,33 +170,62 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
130
170
|
self.sendFrame(payload.encode())
|
|
131
171
|
|
|
132
172
|
def buildProtocol(self, addr: IAddress) -> protocol.Protocol:
|
|
173
|
+
"""Build protocol instance for connection.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
addr: Address of the connection.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Protocol instance for this connection.
|
|
180
|
+
"""
|
|
133
181
|
self.logger.debug(f"buildProtocol: {addr}")
|
|
134
182
|
return self
|
|
135
183
|
|
|
136
184
|
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
185
|
+
"""Handle client connection failure.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
connector: Connection connector instance.
|
|
189
|
+
reason: Failure reason details.
|
|
190
|
+
"""
|
|
137
191
|
self.logger.debug(f"clientConnectionFailed: {reason}")
|
|
138
192
|
self.connection_failed(reason)
|
|
139
193
|
self._cancel_timeout()
|
|
140
194
|
self._stop_reactor()
|
|
141
195
|
|
|
142
196
|
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
197
|
+
"""Handle client connection lost event.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
connector: Connection connector instance.
|
|
201
|
+
reason: Reason for connection loss.
|
|
202
|
+
"""
|
|
143
203
|
self.logger.debug(f"clientConnectionLost: {reason}")
|
|
144
204
|
self.connection_lost(reason)
|
|
145
205
|
self._cancel_timeout()
|
|
146
206
|
self._stop_reactor()
|
|
147
207
|
|
|
148
208
|
def timeout(self) -> bool:
|
|
149
|
-
"""
|
|
209
|
+
"""Handle timeout event.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
True to continue waiting for next timeout, False to stop.
|
|
213
|
+
"""
|
|
150
214
|
self.logger.info("Timeout after: %ss", self.timeout_seconds)
|
|
151
215
|
self.failed(f"Timeout after: {self.timeout_seconds}s")
|
|
152
216
|
return False
|
|
153
217
|
|
|
154
218
|
def connection_failed(self, reason: Failure) -> None:
|
|
219
|
+
"""Handle connection failure.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
reason: Failure reason details.
|
|
223
|
+
"""
|
|
155
224
|
self.logger.debug(f"Client connection failed: {reason}")
|
|
156
225
|
self.failed(reason.getErrorMessage())
|
|
157
226
|
|
|
158
227
|
def _reset_timeout(self) -> None:
|
|
159
|
-
"""Reset the inactivity timeout"""
|
|
228
|
+
"""Reset the inactivity timeout."""
|
|
160
229
|
self._cancel_timeout()
|
|
161
230
|
self.timeout_call = self.reactor.callLater(
|
|
162
231
|
self.timeout_seconds, self._on_timeout
|
|
@@ -164,26 +233,26 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
164
233
|
self.logger.debug(f"Timeout set for {self.timeout_seconds} seconds")
|
|
165
234
|
|
|
166
235
|
def _cancel_timeout(self) -> None:
|
|
167
|
-
"""Cancel the inactivity timeout"""
|
|
236
|
+
"""Cancel the inactivity timeout."""
|
|
168
237
|
if self.timeout_call and self.timeout_call.active():
|
|
169
238
|
self.timeout_call.cancel()
|
|
170
239
|
self.logger.debug("Timeout cancelled")
|
|
171
240
|
|
|
172
241
|
def _on_timeout(self) -> None:
|
|
173
|
-
"""
|
|
242
|
+
"""Handle inactivity timeout expiration."""
|
|
174
243
|
self.logger.debug(f"Conbus timeout after {self.timeout_seconds} seconds")
|
|
175
244
|
continue_work = self.timeout()
|
|
176
245
|
if not continue_work:
|
|
177
246
|
self._stop_reactor()
|
|
178
247
|
|
|
179
248
|
def _stop_reactor(self) -> None:
|
|
180
|
-
"""Stop the reactor if it's running"""
|
|
249
|
+
"""Stop the reactor if it's running."""
|
|
181
250
|
if self.reactor.running:
|
|
182
251
|
self.logger.info("Stopping reactor")
|
|
183
252
|
self.reactor.stop()
|
|
184
253
|
|
|
185
254
|
def start_reactor(self) -> None:
|
|
186
|
-
"""Start the reactor if it's running"""
|
|
255
|
+
"""Start the reactor if it's running."""
|
|
187
256
|
# Connect to TCP server
|
|
188
257
|
self.logger.info(
|
|
189
258
|
f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
|
|
@@ -191,11 +260,15 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
191
260
|
self.reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
|
|
192
261
|
|
|
193
262
|
# Run the reactor (which now uses asyncio underneath)
|
|
194
|
-
self.logger.info("Starting reactor event loop
|
|
263
|
+
self.logger.info("Starting reactor event loop.")
|
|
195
264
|
self.reactor.run()
|
|
196
265
|
|
|
197
266
|
def __enter__(self) -> "ConbusProtocol":
|
|
198
|
-
"""
|
|
267
|
+
"""Enter context manager.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Self for context management.
|
|
271
|
+
"""
|
|
199
272
|
return self
|
|
200
273
|
|
|
201
274
|
def __exit__(
|
|
@@ -204,23 +277,44 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
204
277
|
_exc_val: Optional[BaseException],
|
|
205
278
|
_exc_tb: Optional[Any],
|
|
206
279
|
) -> None:
|
|
207
|
-
"""Context manager exit - ensure connection is closed"""
|
|
208
|
-
self.logger.debug("Exiting the event loop
|
|
280
|
+
"""Context manager exit - ensure connection is closed."""
|
|
281
|
+
self.logger.debug("Exiting the event loop.")
|
|
209
282
|
self._stop_reactor()
|
|
210
283
|
|
|
211
|
-
"""Override methods"""
|
|
284
|
+
"""Override methods."""
|
|
212
285
|
|
|
213
286
|
def telegram_sent(self, telegram_sent: str) -> None:
|
|
287
|
+
"""Override callback when telegram has been sent.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
telegram_sent: The telegram that was sent.
|
|
291
|
+
"""
|
|
214
292
|
pass
|
|
215
293
|
|
|
216
294
|
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
295
|
+
"""Override callback when telegram is received.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
telegram_received: Event containing received telegram details.
|
|
299
|
+
"""
|
|
217
300
|
pass
|
|
218
301
|
|
|
219
302
|
def connection_established(self) -> None:
|
|
303
|
+
"""Override callback when connection established."""
|
|
220
304
|
pass
|
|
221
305
|
|
|
222
306
|
def connection_lost(self, reason: Failure) -> None:
|
|
307
|
+
"""Override callback when connection is lost.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
reason: Reason for connection loss.
|
|
311
|
+
"""
|
|
223
312
|
pass
|
|
224
313
|
|
|
225
314
|
def failed(self, message: str) -> None:
|
|
315
|
+
"""Override callback when connection failed.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
message: Error message describing the failure.
|
|
319
|
+
"""
|
|
226
320
|
pass
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Protocol Factory for Twisted protocol creation.
|
|
2
|
+
|
|
3
|
+
This module provides factory classes for protocol instantiation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
|
|
3
8
|
from bubus import EventBus
|
|
@@ -13,26 +18,61 @@ from xp.services.protocol import TelegramProtocol
|
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class TelegramFactory(protocol.ClientFactory):
|
|
21
|
+
"""Factory for creating Telegram protocol instances.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
event_bus: Event bus for dispatching protocol events.
|
|
25
|
+
telegram_protocol: Protocol instance to use.
|
|
26
|
+
connector: Connection connector instance.
|
|
27
|
+
logger: Logger instance for this factory.
|
|
28
|
+
"""
|
|
29
|
+
|
|
16
30
|
def __init__(
|
|
17
31
|
self,
|
|
18
32
|
event_bus: EventBus,
|
|
19
33
|
telegram_protocol: TelegramProtocol,
|
|
20
34
|
connector: IConnector,
|
|
21
35
|
) -> None:
|
|
36
|
+
"""Initialize TelegramFactory.
|
|
22
37
|
|
|
38
|
+
Args:
|
|
39
|
+
event_bus: Event bus for protocol events.
|
|
40
|
+
telegram_protocol: Protocol instance to use for connections.
|
|
41
|
+
connector: Connection connector for managing connections.
|
|
42
|
+
"""
|
|
23
43
|
self.event_bus = event_bus
|
|
24
44
|
self.telegram_protocol = telegram_protocol
|
|
25
45
|
self.connector = connector
|
|
26
46
|
self.logger = logging.getLogger(__name__)
|
|
27
47
|
|
|
28
48
|
def buildProtocol(self, addr: IAddress) -> TelegramProtocol:
|
|
49
|
+
"""Build protocol instance for connection.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
addr: Address of the connection.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Telegram protocol instance for this connection.
|
|
56
|
+
"""
|
|
29
57
|
self.logger.debug(f"buildProtocol: {addr}")
|
|
30
58
|
return self.telegram_protocol
|
|
31
59
|
|
|
32
60
|
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
61
|
+
"""Handle connection failure event.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
connector: Connection connector instance.
|
|
65
|
+
reason: Failure reason details.
|
|
66
|
+
"""
|
|
33
67
|
self.event_bus.dispatch(ConnectionFailedEvent(reason=str(reason)))
|
|
34
68
|
self.connector.stop()
|
|
35
69
|
|
|
36
70
|
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
71
|
+
"""Handle connection lost event.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
connector: Connection connector instance.
|
|
75
|
+
reason: Reason for connection loss.
|
|
76
|
+
"""
|
|
37
77
|
self.event_bus.dispatch(ConnectionLostEvent(reason=str(reason)))
|
|
38
78
|
self.connector.stop()
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Telegram Protocol for XP telegram communication.
|
|
2
|
+
|
|
3
|
+
This module provides the protocol implementation for telegram-based communication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import asyncio
|
|
2
7
|
import logging
|
|
3
8
|
import time
|
|
@@ -15,17 +20,30 @@ from xp.utils import calculate_checksum
|
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
class TelegramProtocol(protocol.Protocol):
|
|
18
|
-
"""
|
|
19
|
-
Twisted protocol for XP telegram communication with built-in debouncing.
|
|
23
|
+
"""Twisted protocol for XP telegram communication with built-in debouncing.
|
|
20
24
|
|
|
21
25
|
Automatically deduplicates identical telegram frames sent within a
|
|
22
26
|
configurable time window (default 50ms).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
buffer: Buffer for incoming telegram data.
|
|
30
|
+
event_bus: Event bus for dispatching protocol events.
|
|
31
|
+
debounce_ms: Debounce time window in milliseconds.
|
|
32
|
+
logger: Logger instance for this protocol.
|
|
33
|
+
send_queue: Dictionary tracking frame send timestamps.
|
|
34
|
+
timer_handle: Handle for cleanup timer.
|
|
23
35
|
"""
|
|
24
36
|
|
|
25
37
|
buffer: bytes
|
|
26
38
|
event_bus: EventBus
|
|
27
39
|
|
|
28
40
|
def __init__(self, event_bus: EventBus, debounce_ms: int = 50) -> None:
|
|
41
|
+
"""Initialize TelegramProtocol.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
event_bus: Event bus for dispatching protocol events.
|
|
45
|
+
debounce_ms: Debounce time window in milliseconds.
|
|
46
|
+
"""
|
|
29
47
|
self.buffer = b""
|
|
30
48
|
self.event_bus = event_bus
|
|
31
49
|
self.debounce_ms = debounce_ms
|
|
@@ -36,6 +54,7 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
36
54
|
self.timer_handle: Optional[asyncio.TimerHandle] = None
|
|
37
55
|
|
|
38
56
|
def connectionMade(self) -> None:
|
|
57
|
+
"""Handle connection established event."""
|
|
39
58
|
self.logger.debug("connectionMade")
|
|
40
59
|
try:
|
|
41
60
|
self.logger.debug("Scheduling async connection handler")
|
|
@@ -45,7 +64,11 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
45
64
|
self.logger.error(f"Error scheduling async handler: {e}", exc_info=True)
|
|
46
65
|
|
|
47
66
|
def _on_task_done(self, task: asyncio.Task) -> None:
|
|
48
|
-
"""
|
|
67
|
+
"""Handle async task completion.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
task: Completed async task.
|
|
71
|
+
"""
|
|
49
72
|
try:
|
|
50
73
|
if task.exception():
|
|
51
74
|
self.logger.error(
|
|
@@ -57,7 +80,7 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
57
80
|
self.logger.error(f"Error in task done callback: {e}", exc_info=True)
|
|
58
81
|
|
|
59
82
|
async def _async_connection_made(self) -> None:
|
|
60
|
-
"""Async handler for connection made"""
|
|
83
|
+
"""Async handler for connection made."""
|
|
61
84
|
self.logger.debug("_async_connectionMade starting")
|
|
62
85
|
self.logger.info("Dispatching ConnectionMadeEvent")
|
|
63
86
|
try:
|
|
@@ -69,12 +92,16 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
69
92
|
)
|
|
70
93
|
|
|
71
94
|
def dataReceived(self, data: bytes) -> None:
|
|
72
|
-
"""
|
|
95
|
+
"""Handle received data from Twisted.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
data: Raw bytes received from connection.
|
|
99
|
+
"""
|
|
73
100
|
task = asyncio.create_task(self._async_dataReceived(data))
|
|
74
101
|
task.add_done_callback(self._on_task_done)
|
|
75
102
|
|
|
76
103
|
async def _async_dataReceived(self, data: bytes) -> None:
|
|
77
|
-
"""Async handler for received data"""
|
|
104
|
+
"""Async handler for received data."""
|
|
78
105
|
self.logger.debug("dataReceived")
|
|
79
106
|
self.buffer += data
|
|
80
107
|
|
|
@@ -137,7 +164,11 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
137
164
|
)
|
|
138
165
|
|
|
139
166
|
def sendFrame(self, data: bytes) -> None:
|
|
140
|
-
"""
|
|
167
|
+
"""Send telegram frame.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
data: Raw telegram payload (without checksum/framing).
|
|
171
|
+
"""
|
|
141
172
|
task = asyncio.create_task(self._async_sendFrame(data))
|
|
142
173
|
task.add_done_callback(self._on_task_done)
|
|
143
174
|
|
|
@@ -16,7 +16,7 @@ from xp.models.response import Response
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class ReverseProxyError(Exception):
|
|
19
|
-
"""Raised when Conbus reverse proxy operations fail"""
|
|
19
|
+
"""Raised when Conbus reverse proxy operations fail."""
|
|
20
20
|
|
|
21
21
|
pass
|
|
22
22
|
|
|
@@ -28,6 +28,17 @@ class ReverseProxyService:
|
|
|
28
28
|
Accepts client connections on port 10001 and forwards all telegrams
|
|
29
29
|
to the target server configured in cli.yml. Monitors and prints all
|
|
30
30
|
bidirectional traffic with timestamps.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
logger: Logger instance for the service.
|
|
34
|
+
listen_port: Port to listen on for client connections.
|
|
35
|
+
server_socket: Main server socket for accepting connections.
|
|
36
|
+
is_running: Flag indicating if proxy is running.
|
|
37
|
+
active_connections: Dictionary of active connection information.
|
|
38
|
+
connection_counter: Counter for connection IDs.
|
|
39
|
+
cli_config: Conbus client configuration.
|
|
40
|
+
target_ip: Target server IP address.
|
|
41
|
+
target_port: Target server port number.
|
|
31
42
|
"""
|
|
32
43
|
|
|
33
44
|
def __init__(
|
|
@@ -35,7 +46,12 @@ class ReverseProxyService:
|
|
|
35
46
|
cli_config: ConbusClientConfig,
|
|
36
47
|
listen_port: int,
|
|
37
48
|
):
|
|
38
|
-
"""Initialize the Conbus reverse proxy service
|
|
49
|
+
"""Initialize the Conbus reverse proxy service.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
cli_config: Conbus client configuration.
|
|
53
|
+
listen_port: Port to listen on for client connections.
|
|
54
|
+
"""
|
|
39
55
|
# Set up logging first
|
|
40
56
|
self.logger = logging.getLogger(__name__)
|
|
41
57
|
|
|
@@ -50,16 +66,28 @@ class ReverseProxyService:
|
|
|
50
66
|
|
|
51
67
|
@property
|
|
52
68
|
def target_ip(self) -> str:
|
|
53
|
-
"""Get target server IP
|
|
69
|
+
"""Get target server IP.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Target server IP address.
|
|
73
|
+
"""
|
|
54
74
|
return self.cli_config.conbus.ip
|
|
55
75
|
|
|
56
76
|
@property
|
|
57
77
|
def target_port(self) -> int:
|
|
58
|
-
"""Get target server port
|
|
78
|
+
"""Get target server port.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Target server port number.
|
|
82
|
+
"""
|
|
59
83
|
return self.cli_config.conbus.port
|
|
60
84
|
|
|
61
85
|
def start_proxy(self) -> Response:
|
|
62
|
-
"""Start the reverse proxy server
|
|
86
|
+
"""Start the reverse proxy server.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Response object with success status and proxy details.
|
|
90
|
+
"""
|
|
63
91
|
if self.is_running:
|
|
64
92
|
return Response(
|
|
65
93
|
success=False, data=None, error="Reverse proxy is already running"
|
|
@@ -111,7 +139,11 @@ class ReverseProxyService:
|
|
|
111
139
|
)
|
|
112
140
|
|
|
113
141
|
def stop_proxy(self) -> Response:
|
|
114
|
-
"""Stop the reverse proxy server
|
|
142
|
+
"""Stop the reverse proxy server.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Response object with success status.
|
|
146
|
+
"""
|
|
115
147
|
if not self.is_running:
|
|
116
148
|
return Response(
|
|
117
149
|
success=False, data=None, error="Reverse proxy is not running"
|
|
@@ -139,7 +171,11 @@ class ReverseProxyService:
|
|
|
139
171
|
)
|
|
140
172
|
|
|
141
173
|
def get_status(self) -> Response:
|
|
142
|
-
"""Get current proxy status and active connections
|
|
174
|
+
"""Get current proxy status and active connections.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Response object with proxy status and connection details.
|
|
178
|
+
"""
|
|
143
179
|
return Response(
|
|
144
180
|
success=True,
|
|
145
181
|
data={
|
|
@@ -161,7 +197,7 @@ class ReverseProxyService:
|
|
|
161
197
|
)
|
|
162
198
|
|
|
163
199
|
def _accept_connections(self) -> None:
|
|
164
|
-
"""Accept and handle client connections"""
|
|
200
|
+
"""Accept and handle client connections."""
|
|
165
201
|
while self.is_running:
|
|
166
202
|
try:
|
|
167
203
|
# Accept connection
|
|
@@ -194,7 +230,13 @@ class ReverseProxyService:
|
|
|
194
230
|
def _handle_client(
|
|
195
231
|
self, client_socket: socket.socket, client_address: tuple, conn_id: str
|
|
196
232
|
) -> None:
|
|
197
|
-
"""Handle individual client connection with server relay
|
|
233
|
+
"""Handle individual client connection with server relay.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
client_socket: Client socket connection.
|
|
237
|
+
client_address: Client address tuple (ip, port).
|
|
238
|
+
conn_id: Connection identifier.
|
|
239
|
+
"""
|
|
198
240
|
try:
|
|
199
241
|
# Connect to target server
|
|
200
242
|
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
@@ -272,7 +314,15 @@ class ReverseProxyService:
|
|
|
272
314
|
dest_label: str,
|
|
273
315
|
conn_id: str,
|
|
274
316
|
) -> None:
|
|
275
|
-
"""Relay data between sockets with telegram monitoring
|
|
317
|
+
"""Relay data between sockets with telegram monitoring.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
source_socket: Source socket to receive from.
|
|
321
|
+
dest_socket: Destination socket to send to.
|
|
322
|
+
source_label: Label for source in logs.
|
|
323
|
+
dest_label: Label for destination in logs.
|
|
324
|
+
conn_id: Connection identifier.
|
|
325
|
+
"""
|
|
276
326
|
try:
|
|
277
327
|
while self.is_running:
|
|
278
328
|
# Receive data from source
|
|
@@ -316,7 +366,11 @@ class ReverseProxyService:
|
|
|
316
366
|
self.logger.error(f"Error in data relay: {e} [{conn_id}]")
|
|
317
367
|
|
|
318
368
|
def _close_connection_pair(self, conn_id: str) -> None:
|
|
319
|
-
"""Close both client and server sockets for a connection
|
|
369
|
+
"""Close both client and server sockets for a connection.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
conn_id: Connection identifier.
|
|
373
|
+
"""
|
|
320
374
|
if conn_id not in self.active_connections:
|
|
321
375
|
return
|
|
322
376
|
|
|
@@ -344,7 +398,10 @@ class ReverseProxyService:
|
|
|
344
398
|
f"Client {client_address} disconnected [{conn_id}] - {bytes_relayed} bytes relayed"
|
|
345
399
|
)
|
|
346
400
|
print(
|
|
347
|
-
f"{self.timestamp()} [DISCONNECTION]
|
|
401
|
+
f"{self.timestamp()} [DISCONNECTION] "
|
|
402
|
+
f"Client {client_address} "
|
|
403
|
+
f"disconnected [{conn_id}] - "
|
|
404
|
+
f"{bytes_relayed} bytes relayed"
|
|
348
405
|
)
|
|
349
406
|
|
|
350
407
|
# Remove from active connections
|
|
@@ -352,11 +409,19 @@ class ReverseProxyService:
|
|
|
352
409
|
|
|
353
410
|
@staticmethod
|
|
354
411
|
def timestamp() -> str:
|
|
355
|
-
"""Generate timestamp string for logging
|
|
412
|
+
"""Generate timestamp string for logging.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Timestamp string in HH:MM:SS,mmm format.
|
|
416
|
+
"""
|
|
356
417
|
return datetime.now().strftime("%H:%M:%S,%f")[:-3]
|
|
357
418
|
|
|
358
419
|
def run_blocking(self) -> None:
|
|
359
|
-
"""Run the proxy in blocking mode (for CLI usage)
|
|
420
|
+
"""Run the proxy in blocking mode (for CLI usage).
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ReverseProxyError: If proxy fails to start.
|
|
424
|
+
"""
|
|
360
425
|
result = self.start_proxy()
|
|
361
426
|
if not result.success:
|
|
362
427
|
raise ReverseProxyError(result.error)
|
xp/services/server/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Server services for XP protocol variants."""
|