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.
Files changed (167) hide show
  1. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/METADATA +1 -1
  2. conson_xp-1.2.0.dist-info/RECORD +181 -0
  3. xp/__init__.py +4 -3
  4. xp/api/main.py +18 -3
  5. xp/api/models/api.py +13 -2
  6. xp/api/models/discover.py +12 -2
  7. xp/api/routers/conbus_blink.py +18 -6
  8. xp/api/routers/conbus_custom.py +11 -3
  9. xp/api/routers/conbus_datapoint.py +10 -3
  10. xp/api/routers/conbus_output.py +29 -9
  11. xp/api/routers/errors.py +6 -5
  12. xp/cli/__init__.py +1 -1
  13. xp/cli/commands/__init__.py +1 -0
  14. xp/cli/commands/api.py +1 -5
  15. xp/cli/commands/api_start_commands.py +14 -8
  16. xp/cli/commands/conbus/conbus.py +9 -37
  17. xp/cli/commands/conbus/conbus_actiontable_commands.py +21 -1
  18. xp/cli/commands/conbus/conbus_autoreport_commands.py +21 -11
  19. xp/cli/commands/conbus/conbus_blink_commands.py +53 -21
  20. xp/cli/commands/conbus/conbus_config_commands.py +7 -4
  21. xp/cli/commands/conbus/conbus_custom_commands.py +13 -4
  22. xp/cli/commands/conbus/conbus_datapoint_commands.py +28 -8
  23. xp/cli/commands/conbus/conbus_discover_commands.py +15 -4
  24. xp/cli/commands/conbus/conbus_lightlevel_commands.py +50 -11
  25. xp/cli/commands/conbus/conbus_linknumber_commands.py +21 -11
  26. xp/cli/commands/conbus/conbus_msactiontable_commands.py +25 -1
  27. xp/cli/commands/conbus/conbus_output_commands.py +46 -12
  28. xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
  29. xp/cli/commands/conbus/conbus_receive_commands.py +15 -7
  30. xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
  31. xp/cli/commands/file_commands.py +26 -15
  32. xp/cli/commands/homekit/homekit.py +14 -8
  33. xp/cli/commands/homekit/homekit_start_commands.py +5 -5
  34. xp/cli/commands/module_commands.py +26 -19
  35. xp/cli/commands/reverse_proxy_commands.py +24 -18
  36. xp/cli/commands/server/server_commands.py +18 -18
  37. xp/cli/commands/telegram/telegram.py +4 -12
  38. xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
  39. xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
  40. xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
  41. xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
  42. xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
  43. xp/cli/commands/telegram/telegram_version_commands.py +8 -4
  44. xp/cli/main.py +5 -1
  45. xp/cli/utils/click_tree.py +23 -3
  46. xp/cli/utils/datapoint_type_choice.py +20 -0
  47. xp/cli/utils/decorators.py +165 -14
  48. xp/cli/utils/error_handlers.py +49 -18
  49. xp/cli/utils/formatters.py +95 -10
  50. xp/cli/utils/serial_number_type.py +18 -0
  51. xp/cli/utils/system_function_choice.py +20 -0
  52. xp/cli/utils/xp_module_type.py +20 -0
  53. xp/connection/__init__.py +1 -1
  54. xp/connection/exceptions.py +5 -5
  55. xp/models/__init__.py +1 -1
  56. xp/models/actiontable/__init__.py +1 -0
  57. xp/models/actiontable/actiontable.py +17 -1
  58. xp/models/actiontable/msactiontable_xp20.py +10 -0
  59. xp/models/actiontable/msactiontable_xp24.py +20 -3
  60. xp/models/actiontable/msactiontable_xp33.py +27 -4
  61. xp/models/conbus/__init__.py +1 -0
  62. xp/models/conbus/conbus.py +34 -4
  63. xp/models/conbus/conbus_autoreport.py +20 -2
  64. xp/models/conbus/conbus_blink.py +22 -2
  65. xp/models/conbus/conbus_client_config.py +22 -1
  66. xp/models/conbus/conbus_connection_status.py +16 -2
  67. xp/models/conbus/conbus_custom.py +21 -2
  68. xp/models/conbus/conbus_datapoint.py +22 -2
  69. xp/models/conbus/conbus_discover.py +18 -2
  70. xp/models/conbus/conbus_lightlevel.py +20 -2
  71. xp/models/conbus/conbus_linknumber.py +20 -2
  72. xp/models/conbus/conbus_output.py +22 -2
  73. xp/models/conbus/conbus_raw.py +17 -2
  74. xp/models/conbus/conbus_receive.py +16 -2
  75. xp/models/homekit/__init__.py +1 -0
  76. xp/models/homekit/homekit_accessory.py +15 -1
  77. xp/models/homekit/homekit_config.py +52 -0
  78. xp/models/homekit/homekit_conson_config.py +32 -0
  79. xp/models/log_entry.py +49 -9
  80. xp/models/protocol/__init__.py +1 -0
  81. xp/models/protocol/conbus_protocol.py +130 -21
  82. xp/models/telegram/__init__.py +1 -0
  83. xp/models/telegram/action_type.py +16 -2
  84. xp/models/telegram/datapoint_type.py +36 -2
  85. xp/models/telegram/event_telegram.py +46 -10
  86. xp/models/telegram/event_type.py +8 -1
  87. xp/models/telegram/input_action_type.py +34 -2
  88. xp/models/telegram/input_type.py +9 -1
  89. xp/models/telegram/module_type.py +69 -19
  90. xp/models/telegram/module_type_code.py +43 -1
  91. xp/models/telegram/output_telegram.py +30 -6
  92. xp/models/telegram/reply_telegram.py +56 -11
  93. xp/models/telegram/system_function.py +35 -3
  94. xp/models/telegram/system_telegram.py +18 -4
  95. xp/models/telegram/telegram.py +12 -3
  96. xp/models/telegram/telegram_type.py +8 -1
  97. xp/models/telegram/timeparam_type.py +27 -0
  98. xp/models/write_config_type.py +17 -2
  99. xp/services/__init__.py +1 -1
  100. xp/services/conbus/__init__.py +1 -0
  101. xp/services/conbus/actiontable/__init__.py +1 -0
  102. xp/services/conbus/actiontable/actiontable_service.py +33 -2
  103. xp/services/conbus/actiontable/msactiontable_service.py +40 -3
  104. xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
  105. xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
  106. xp/services/conbus/conbus_autoreport_get_service.py +17 -8
  107. xp/services/conbus/conbus_autoreport_set_service.py +29 -16
  108. xp/services/conbus/conbus_blink_all_service.py +40 -21
  109. xp/services/conbus/conbus_blink_service.py +37 -13
  110. xp/services/conbus/conbus_custom_service.py +29 -13
  111. xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
  112. xp/services/conbus/conbus_datapoint_service.py +33 -12
  113. xp/services/conbus/conbus_discover_service.py +43 -7
  114. xp/services/conbus/conbus_lightlevel_get_service.py +22 -14
  115. xp/services/conbus/conbus_lightlevel_set_service.py +40 -20
  116. xp/services/conbus/conbus_linknumber_get_service.py +18 -10
  117. xp/services/conbus/conbus_linknumber_set_service.py +34 -8
  118. xp/services/conbus/conbus_output_service.py +33 -13
  119. xp/services/conbus/conbus_raw_service.py +36 -16
  120. xp/services/conbus/conbus_receive_service.py +38 -6
  121. xp/services/conbus/conbus_scan_service.py +44 -18
  122. xp/services/homekit/__init__.py +1 -0
  123. xp/services/homekit/homekit_cache_service.py +31 -6
  124. xp/services/homekit/homekit_conbus_service.py +33 -2
  125. xp/services/homekit/homekit_config_validator.py +97 -15
  126. xp/services/homekit/homekit_conson_validator.py +51 -7
  127. xp/services/homekit/homekit_dimminglight.py +47 -1
  128. xp/services/homekit/homekit_dimminglight_service.py +35 -1
  129. xp/services/homekit/homekit_hap_service.py +71 -18
  130. xp/services/homekit/homekit_lightbulb.py +35 -1
  131. xp/services/homekit/homekit_lightbulb_service.py +30 -2
  132. xp/services/homekit/homekit_module_service.py +23 -1
  133. xp/services/homekit/homekit_outlet.py +47 -1
  134. xp/services/homekit/homekit_outlet_service.py +44 -2
  135. xp/services/homekit/homekit_service.py +113 -19
  136. xp/services/log_file_service.py +37 -41
  137. xp/services/module_type_service.py +26 -5
  138. xp/services/protocol/__init__.py +1 -1
  139. xp/services/protocol/conbus_protocol.py +110 -16
  140. xp/services/protocol/protocol_factory.py +40 -0
  141. xp/services/protocol/telegram_protocol.py +38 -7
  142. xp/services/reverse_proxy_service.py +79 -14
  143. xp/services/server/__init__.py +1 -0
  144. xp/services/server/base_server_service.py +102 -14
  145. xp/services/server/cp20_server_service.py +12 -4
  146. xp/services/server/server_service.py +26 -11
  147. xp/services/server/xp130_server_service.py +11 -3
  148. xp/services/server/xp20_server_service.py +11 -3
  149. xp/services/server/xp230_server_service.py +11 -3
  150. xp/services/server/xp24_server_service.py +33 -6
  151. xp/services/server/xp33_server_service.py +41 -8
  152. xp/services/telegram/__init__.py +1 -0
  153. xp/services/telegram/telegram_blink_service.py +19 -31
  154. xp/services/telegram/telegram_checksum_service.py +10 -10
  155. xp/services/telegram/telegram_discover_service.py +58 -29
  156. xp/services/telegram/telegram_link_number_service.py +27 -40
  157. xp/services/telegram/telegram_output_service.py +46 -49
  158. xp/services/telegram/telegram_service.py +41 -41
  159. xp/services/telegram/telegram_version_service.py +4 -2
  160. xp/utils/__init__.py +1 -1
  161. xp/utils/dependencies.py +0 -1
  162. xp/utils/serialization.py +6 -0
  163. xp/utils/time_utils.py +6 -11
  164. conson_xp-1.1.0.dist-info/RECORD +0 -181
  165. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
  166. {conson_xp-1.1.0.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
  167. {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
- Twisted protocol for XP telegram communication.
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
- """Timeout callback, return True to continue waiting for next timeout, False to stop"""
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
- """Called when inactivity timeout expires"""
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
- """Context manager entry"""
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
- """Callback when async task completes"""
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
- """Sync callback from Twisted - delegates to async implementation"""
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
- """Sync callback from Twisted - delegates to async implementation"""
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] Client {client_address} disconnected [{conn_id}] - {bytes_relayed} bytes relayed"
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)
@@ -0,0 +1 @@
1
+ """Server services for XP protocol variants."""