conson-xp 1.0.1__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.0.1.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 +35 -102
  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 +45 -19
  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 +115 -20
  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.0.1.dist-info/RECORD +0 -181
  165. {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/WHEEL +0 -0
  166. {conson_xp-1.0.1.dist-info → conson_xp-1.2.0.dist-info}/entry_points.txt +0 -0
  167. {conson_xp-1.0.1.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
 
@@ -75,7 +105,7 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
75
105
  )
76
106
 
77
107
  self.logger.debug(
78
- f"frameReceived payload: {payload.decode()}, checksum: {checksum}"
108
+ f"frameReceived payload: {payload.decode('latin-1')}, checksum: {checksum}"
79
109
  )
80
110
 
81
111
  # Reset timeout on activity
@@ -83,9 +113,9 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
83
113
 
84
114
  telegram_received = TelegramReceivedEvent(
85
115
  protocol=self,
86
- frame=frame.decode(),
87
- telegram=telegram.decode(),
88
- payload=payload.decode(),
116
+ frame=frame.decode("latin-1"),
117
+ telegram=telegram.decode("latin-1"),
118
+ payload=payload.decode("latin-1"),
89
119
  telegram_type=telegram_type,
90
120
  serial_number=serial_number,
91
121
  checksum=checksum,
@@ -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())
@@ -112,6 +144,7 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
112
144
  self.logger.debug(f"Sending frame: {frame.decode()}")
113
145
  self.transport.write(frame) # type: ignore
114
146
  self.telegram_sent(frame.decode())
147
+ self._reset_timeout()
115
148
 
116
149
  def send_telegram(
117
150
  self,
@@ -120,6 +153,14 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
120
153
  system_function: SystemFunction,
121
154
  data_value: str,
122
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
+ """
123
164
  payload = (
124
165
  f"{telegram_type.value}"
125
166
  f"{serial_number}"
@@ -129,33 +170,62 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
129
170
  self.sendFrame(payload.encode())
130
171
 
131
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
+ """
132
181
  self.logger.debug(f"buildProtocol: {addr}")
133
182
  return self
134
183
 
135
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
+ """
136
191
  self.logger.debug(f"clientConnectionFailed: {reason}")
137
192
  self.connection_failed(reason)
138
193
  self._cancel_timeout()
139
194
  self._stop_reactor()
140
195
 
141
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
+ """
142
203
  self.logger.debug(f"clientConnectionLost: {reason}")
143
204
  self.connection_lost(reason)
144
205
  self._cancel_timeout()
145
206
  self._stop_reactor()
146
207
 
147
208
  def timeout(self) -> bool:
148
- """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
+ """
149
214
  self.logger.info("Timeout after: %ss", self.timeout_seconds)
150
215
  self.failed(f"Timeout after: {self.timeout_seconds}s")
151
216
  return False
152
217
 
153
218
  def connection_failed(self, reason: Failure) -> None:
219
+ """Handle connection failure.
220
+
221
+ Args:
222
+ reason: Failure reason details.
223
+ """
154
224
  self.logger.debug(f"Client connection failed: {reason}")
155
225
  self.failed(reason.getErrorMessage())
156
226
 
157
227
  def _reset_timeout(self) -> None:
158
- """Reset the inactivity timeout"""
228
+ """Reset the inactivity timeout."""
159
229
  self._cancel_timeout()
160
230
  self.timeout_call = self.reactor.callLater(
161
231
  self.timeout_seconds, self._on_timeout
@@ -163,26 +233,26 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
163
233
  self.logger.debug(f"Timeout set for {self.timeout_seconds} seconds")
164
234
 
165
235
  def _cancel_timeout(self) -> None:
166
- """Cancel the inactivity timeout"""
236
+ """Cancel the inactivity timeout."""
167
237
  if self.timeout_call and self.timeout_call.active():
168
238
  self.timeout_call.cancel()
169
239
  self.logger.debug("Timeout cancelled")
170
240
 
171
241
  def _on_timeout(self) -> None:
172
- """Called when inactivity timeout expires"""
242
+ """Handle inactivity timeout expiration."""
173
243
  self.logger.debug(f"Conbus timeout after {self.timeout_seconds} seconds")
174
244
  continue_work = self.timeout()
175
245
  if not continue_work:
176
246
  self._stop_reactor()
177
247
 
178
248
  def _stop_reactor(self) -> None:
179
- """Stop the reactor if it's running"""
249
+ """Stop the reactor if it's running."""
180
250
  if self.reactor.running:
181
251
  self.logger.info("Stopping reactor")
182
252
  self.reactor.stop()
183
253
 
184
254
  def start_reactor(self) -> None:
185
- """Start the reactor if it's running"""
255
+ """Start the reactor if it's running."""
186
256
  # Connect to TCP server
187
257
  self.logger.info(
188
258
  f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
@@ -190,11 +260,15 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
190
260
  self.reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
191
261
 
192
262
  # Run the reactor (which now uses asyncio underneath)
193
- self.logger.info("Starting reactor event loop...")
263
+ self.logger.info("Starting reactor event loop.")
194
264
  self.reactor.run()
195
265
 
196
266
  def __enter__(self) -> "ConbusProtocol":
197
- """Context manager entry"""
267
+ """Enter context manager.
268
+
269
+ Returns:
270
+ Self for context management.
271
+ """
198
272
  return self
199
273
 
200
274
  def __exit__(
@@ -203,23 +277,44 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
203
277
  _exc_val: Optional[BaseException],
204
278
  _exc_tb: Optional[Any],
205
279
  ) -> None:
206
- """Context manager exit - ensure connection is closed"""
207
- self.logger.debug("Exiting the event loop...")
280
+ """Context manager exit - ensure connection is closed."""
281
+ self.logger.debug("Exiting the event loop.")
208
282
  self._stop_reactor()
209
283
 
210
- """Override methods"""
284
+ """Override methods."""
211
285
 
212
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
+ """
213
292
  pass
214
293
 
215
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
+ """
216
300
  pass
217
301
 
218
302
  def connection_established(self) -> None:
303
+ """Override callback when connection established."""
219
304
  pass
220
305
 
221
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
+ """
222
312
  pass
223
313
 
224
314
  def failed(self, message: str) -> None:
315
+ """Override callback when connection failed.
316
+
317
+ Args:
318
+ message: Error message describing the failure.
319
+ """
225
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."""