conson-xp 1.1.0__py3-none-any.whl → 1.3.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 (174) hide show
  1. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
  2. conson_xp-1.3.0.dist-info/RECORD +164 -0
  3. xp/__init__.py +4 -3
  4. xp/cli/__init__.py +1 -1
  5. xp/cli/commands/__init__.py +1 -2
  6. xp/cli/commands/conbus/conbus.py +9 -37
  7. xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
  8. xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
  9. xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
  10. xp/cli/commands/conbus/conbus_config_commands.py +10 -5
  11. xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
  12. xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
  13. xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
  14. xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
  15. xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
  16. xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
  17. xp/cli/commands/conbus/conbus_output_commands.py +52 -14
  18. xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
  19. xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
  20. xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
  21. xp/cli/commands/file_commands.py +35 -18
  22. xp/cli/commands/homekit/homekit.py +14 -8
  23. xp/cli/commands/homekit/homekit_start_commands.py +8 -6
  24. xp/cli/commands/module_commands.py +38 -23
  25. xp/cli/commands/reverse_proxy_commands.py +27 -19
  26. xp/cli/commands/server/server_commands.py +18 -18
  27. xp/cli/commands/telegram/telegram.py +4 -12
  28. xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
  29. xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
  30. xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
  31. xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
  32. xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
  33. xp/cli/commands/telegram/telegram_version_commands.py +8 -4
  34. xp/cli/main.py +5 -3
  35. xp/cli/utils/click_tree.py +23 -3
  36. xp/cli/utils/datapoint_type_choice.py +20 -0
  37. xp/cli/utils/decorators.py +165 -14
  38. xp/cli/utils/error_handlers.py +49 -18
  39. xp/cli/utils/formatters.py +95 -10
  40. xp/cli/utils/serial_number_type.py +18 -0
  41. xp/cli/utils/system_function_choice.py +20 -0
  42. xp/cli/utils/xp_module_type.py +20 -0
  43. xp/connection/__init__.py +1 -1
  44. xp/connection/exceptions.py +5 -5
  45. xp/models/__init__.py +1 -1
  46. xp/models/actiontable/__init__.py +1 -0
  47. xp/models/actiontable/actiontable.py +17 -1
  48. xp/models/actiontable/msactiontable_xp20.py +10 -0
  49. xp/models/actiontable/msactiontable_xp24.py +20 -3
  50. xp/models/actiontable/msactiontable_xp33.py +27 -4
  51. xp/models/conbus/__init__.py +1 -0
  52. xp/models/conbus/conbus.py +34 -4
  53. xp/models/conbus/conbus_autoreport.py +20 -2
  54. xp/models/conbus/conbus_blink.py +22 -2
  55. xp/models/conbus/conbus_client_config.py +22 -1
  56. xp/models/conbus/conbus_connection_status.py +16 -2
  57. xp/models/conbus/conbus_custom.py +21 -2
  58. xp/models/conbus/conbus_datapoint.py +25 -2
  59. xp/models/conbus/conbus_discover.py +18 -2
  60. xp/models/conbus/conbus_lightlevel.py +20 -2
  61. xp/models/conbus/conbus_linknumber.py +20 -2
  62. xp/models/conbus/conbus_output.py +22 -2
  63. xp/models/conbus/conbus_raw.py +17 -2
  64. xp/models/conbus/conbus_receive.py +16 -2
  65. xp/models/conbus/conbus_writeconfig.py +60 -0
  66. xp/models/homekit/__init__.py +1 -0
  67. xp/models/homekit/homekit_accessory.py +15 -1
  68. xp/models/homekit/homekit_config.py +52 -0
  69. xp/models/homekit/homekit_conson_config.py +32 -0
  70. xp/models/log_entry.py +49 -9
  71. xp/models/protocol/__init__.py +1 -0
  72. xp/models/protocol/conbus_protocol.py +130 -21
  73. xp/models/telegram/__init__.py +1 -0
  74. xp/models/telegram/action_type.py +16 -2
  75. xp/models/telegram/datapoint_type.py +36 -2
  76. xp/models/telegram/event_telegram.py +46 -10
  77. xp/models/telegram/event_type.py +8 -1
  78. xp/models/telegram/input_action_type.py +34 -2
  79. xp/models/telegram/input_type.py +9 -1
  80. xp/models/telegram/module_type.py +69 -19
  81. xp/models/telegram/module_type_code.py +43 -1
  82. xp/models/telegram/output_telegram.py +30 -6
  83. xp/models/telegram/reply_telegram.py +56 -11
  84. xp/models/telegram/system_function.py +35 -3
  85. xp/models/telegram/system_telegram.py +18 -4
  86. xp/models/telegram/telegram.py +12 -3
  87. xp/models/telegram/telegram_type.py +8 -1
  88. xp/models/telegram/timeparam_type.py +27 -0
  89. xp/models/write_config_type.py +17 -2
  90. xp/services/__init__.py +1 -1
  91. xp/services/conbus/__init__.py +1 -0
  92. xp/services/conbus/actiontable/__init__.py +1 -0
  93. xp/services/conbus/actiontable/actiontable_service.py +33 -2
  94. xp/services/conbus/actiontable/msactiontable_service.py +40 -3
  95. xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
  96. xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
  97. xp/services/conbus/conbus_blink_all_service.py +40 -21
  98. xp/services/conbus/conbus_blink_service.py +37 -13
  99. xp/services/conbus/conbus_custom_service.py +29 -13
  100. xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
  101. xp/services/conbus/conbus_datapoint_service.py +42 -18
  102. xp/services/conbus/conbus_discover_service.py +43 -7
  103. xp/services/conbus/conbus_output_service.py +33 -13
  104. xp/services/conbus/conbus_raw_service.py +36 -16
  105. xp/services/conbus/conbus_receive_service.py +38 -6
  106. xp/services/conbus/conbus_scan_service.py +44 -18
  107. xp/services/conbus/write_config_service.py +193 -0
  108. xp/services/homekit/__init__.py +1 -0
  109. xp/services/homekit/homekit_cache_service.py +31 -6
  110. xp/services/homekit/homekit_conbus_service.py +33 -2
  111. xp/services/homekit/homekit_config_validator.py +97 -15
  112. xp/services/homekit/homekit_conson_validator.py +51 -7
  113. xp/services/homekit/homekit_dimminglight.py +47 -1
  114. xp/services/homekit/homekit_dimminglight_service.py +35 -1
  115. xp/services/homekit/homekit_hap_service.py +71 -18
  116. xp/services/homekit/homekit_lightbulb.py +35 -1
  117. xp/services/homekit/homekit_lightbulb_service.py +30 -2
  118. xp/services/homekit/homekit_module_service.py +23 -1
  119. xp/services/homekit/homekit_outlet.py +47 -1
  120. xp/services/homekit/homekit_outlet_service.py +44 -2
  121. xp/services/homekit/homekit_service.py +113 -19
  122. xp/services/log_file_service.py +37 -41
  123. xp/services/module_type_service.py +26 -5
  124. xp/services/protocol/__init__.py +1 -1
  125. xp/services/protocol/conbus_protocol.py +110 -16
  126. xp/services/protocol/protocol_factory.py +40 -0
  127. xp/services/protocol/telegram_protocol.py +38 -7
  128. xp/services/reverse_proxy_service.py +79 -14
  129. xp/services/server/__init__.py +1 -0
  130. xp/services/server/base_server_service.py +102 -14
  131. xp/services/server/cp20_server_service.py +12 -4
  132. xp/services/server/server_service.py +26 -11
  133. xp/services/server/xp130_server_service.py +11 -3
  134. xp/services/server/xp20_server_service.py +11 -3
  135. xp/services/server/xp230_server_service.py +11 -3
  136. xp/services/server/xp24_server_service.py +33 -6
  137. xp/services/server/xp33_server_service.py +41 -8
  138. xp/services/telegram/__init__.py +1 -0
  139. xp/services/telegram/telegram_blink_service.py +19 -31
  140. xp/services/telegram/telegram_checksum_service.py +10 -10
  141. xp/services/telegram/telegram_datapoint_service.py +70 -0
  142. xp/services/telegram/telegram_discover_service.py +58 -29
  143. xp/services/telegram/telegram_link_number_service.py +27 -40
  144. xp/services/telegram/telegram_output_service.py +46 -49
  145. xp/services/telegram/telegram_service.py +41 -41
  146. xp/services/telegram/telegram_version_service.py +4 -2
  147. xp/utils/__init__.py +1 -1
  148. xp/utils/dependencies.py +4 -47
  149. xp/utils/serialization.py +6 -0
  150. xp/utils/time_utils.py +6 -11
  151. conson_xp-1.1.0.dist-info/RECORD +0 -181
  152. xp/api/__init__.py +0 -1
  153. xp/api/main.py +0 -110
  154. xp/api/models/__init__.py +0 -1
  155. xp/api/models/api.py +0 -20
  156. xp/api/models/discover.py +0 -21
  157. xp/api/routers/__init__.py +0 -17
  158. xp/api/routers/conbus.py +0 -5
  159. xp/api/routers/conbus_blink.py +0 -105
  160. xp/api/routers/conbus_custom.py +0 -63
  161. xp/api/routers/conbus_datapoint.py +0 -67
  162. xp/api/routers/conbus_output.py +0 -147
  163. xp/api/routers/errors.py +0 -37
  164. xp/cli/commands/api.py +0 -16
  165. xp/cli/commands/api_start_commands.py +0 -126
  166. xp/services/conbus/conbus_autoreport_get_service.py +0 -85
  167. xp/services/conbus/conbus_autoreport_set_service.py +0 -128
  168. xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
  169. xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
  170. xp/services/conbus/conbus_linknumber_get_service.py +0 -86
  171. xp/services/conbus/conbus_linknumber_set_service.py +0 -155
  172. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
  173. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
  174. {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -15,7 +15,7 @@ from xp.utils.time_utils import (
15
15
 
16
16
 
17
17
  class LogFileParsingError(Exception):
18
- """Raised when log file parsing fails"""
18
+ """Raised when log file parsing fails."""
19
19
 
20
20
  pass
21
21
 
@@ -26,6 +26,10 @@ class LogFileService:
26
26
 
27
27
  Handles parsing of log files containing timestamped telegram transmissions
28
28
  and receptions with automatic telegram parsing and validation.
29
+
30
+ Attributes:
31
+ telegram_service: Telegram service for parsing telegrams.
32
+ LOG_LINE_PATTERN: Regex pattern for log line format.
29
33
  """
30
34
 
31
35
  # Regex pattern for log line format: HH:MM:SS,mmm [TX/RX] <telegram>
@@ -34,29 +38,27 @@ class LogFileService:
34
38
  )
35
39
 
36
40
  def __init__(self, telegram_service: TelegramService):
37
- """
38
- Initialize the log file service
41
+ """Initialize the log file service.
39
42
 
40
43
  Args:
41
- telegram_service: Telegram service for parsing telegrams
44
+ telegram_service: Telegram service for parsing telegrams.
42
45
  """
43
46
  self.telegram_service = telegram_service
44
47
 
45
48
  def parse_log_file(
46
49
  self, file_path: str, base_date: Optional[datetime] = None
47
50
  ) -> List[LogEntry]:
48
- """
49
- Parse a console bus log file into LogEntry objects
51
+ """Parse a console bus log file into LogEntry objects.
50
52
 
51
53
  Args:
52
- file_path: Path to the log file
53
- base_date: Base date for timestamps (defaults to today)
54
+ file_path: Path to the log file.
55
+ base_date: Base date for timestamps (defaults to today).
54
56
 
55
57
  Returns:
56
- List of parsed LogEntry objects
58
+ List of parsed LogEntry objects.
57
59
 
58
60
  Raises:
59
- LogFileParsingError: If file cannot be read or parsed
61
+ LogFileParsingError: If file cannot be read or parsed.
60
62
  """
61
63
  try:
62
64
  path = Path(file_path)
@@ -77,15 +79,14 @@ class LogFileService:
77
79
  def parse_log_lines(
78
80
  self, lines: List[str], base_date: Optional[datetime] = None
79
81
  ) -> List[LogEntry]:
80
- """
81
- Parse log lines into LogEntry objects
82
+ """Parse log lines into LogEntry objects.
82
83
 
83
84
  Args:
84
- lines: List of log lines to parse
85
- base_date: Base date for timestamps
85
+ lines: List of log lines to parse.
86
+ base_date: Base date for timestamps.
86
87
 
87
88
  Returns:
88
- List of parsed LogEntry objects
89
+ List of parsed LogEntry objects.
89
90
  """
90
91
  entries = []
91
92
 
@@ -114,16 +115,15 @@ class LogFileService:
114
115
  def _parse_log_line(
115
116
  self, line: str, line_number: int, base_date: Optional[datetime] = None
116
117
  ) -> Optional[LogEntry]:
117
- """
118
- Parse a single log line into a LogEntry
118
+ """Parse a single log line into a LogEntry.
119
119
 
120
120
  Args:
121
- line: Log line to parse
122
- line_number: Line number in the file
123
- base_date: Base date for timestamp
121
+ line: Log line to parse.
122
+ line_number: Line number in the file.
123
+ base_date: Base date for timestamp.
124
124
 
125
125
  Returns:
126
- LogEntry object or None if line format is invalid
126
+ LogEntry object or None if line format is invalid.
127
127
  """
128
128
  match = self.LOG_LINE_PATTERN.match(line)
129
129
  if not match:
@@ -157,14 +157,13 @@ class LogFileService:
157
157
  return entry
158
158
 
159
159
  def validate_log_format(self, file_path: str) -> bool:
160
- """
161
- Validate that a file follows the expected log format
160
+ """Validate that a file follows the expected log format.
162
161
 
163
162
  Args:
164
- file_path: Path to the log file
163
+ file_path: Path to the log file.
165
164
 
166
165
  Returns:
167
- True if format is valid, False otherwise
166
+ True if format is valid, False otherwise.
168
167
  """
169
168
  try:
170
169
  entries = self.parse_log_file(file_path)
@@ -175,28 +174,26 @@ class LogFileService:
175
174
  return False
176
175
 
177
176
  def extract_telegrams(self, file_path: str) -> List[str]:
178
- """
179
- Extract all telegram strings from a log file
177
+ """Extract all telegram strings from a log file.
180
178
 
181
179
  Args:
182
- file_path: Path to the log file
180
+ file_path: Path to the log file.
183
181
 
184
182
  Returns:
185
- List of telegram strings
183
+ List of telegram strings.
186
184
  """
187
185
  entries = self.parse_log_file(file_path)
188
186
  return [entry.raw_telegram for entry in entries]
189
187
 
190
188
  @staticmethod
191
189
  def get_file_statistics(entries: List[LogEntry]) -> Dict[str, Any]:
192
- """
193
- Generate statistics for a list of log entries
190
+ """Generate statistics for a list of log entries.
194
191
 
195
192
  Args:
196
- entries: List of LogEntry objects
193
+ entries: List of LogEntry objects.
197
194
 
198
195
  Returns:
199
- Dictionary containing statistics
196
+ Dictionary containing statistics.
200
197
  """
201
198
  if not entries:
202
199
  return {"total_entries": 0}
@@ -283,18 +280,17 @@ class LogFileService:
283
280
  start_time: Optional[datetime] = None,
284
281
  end_time: Optional[datetime] = None,
285
282
  ) -> List[LogEntry]:
286
- """
287
- Filter log entries based on criteria
283
+ """Filter log entries based on criteria.
288
284
 
289
285
  Args:
290
- entries: List of LogEntry objects to filter
291
- telegram_type: Filter by telegram type (event, system, reply)
292
- direction: Filter by direction (TX, RX)
293
- start_time: Filter entries after this time
294
- end_time: Filter entries before this time
286
+ entries: List of LogEntry objects to filter.
287
+ telegram_type: Filter by telegram type (event, system, reply).
288
+ direction: Filter by direction (TX, RX).
289
+ start_time: Filter entries after this time.
290
+ end_time: Filter entries before this time.
295
291
 
296
292
  Returns:
297
- Filtered list of LogEntry objects
293
+ Filtered list of LogEntry objects.
298
294
  """
299
295
  filtered = entries.copy()
300
296
 
@@ -1,3 +1,8 @@
1
+ """Module Type Service for XP module management.
2
+
3
+ This module provides lookup, validation, and search functionality for XP system module types.
4
+ """
5
+
1
6
  from typing import Dict, List, Optional, Union
2
7
 
3
8
  from xp.models.telegram.module_type import (
@@ -9,7 +14,7 @@ from xp.models.telegram.module_type import (
9
14
 
10
15
 
11
16
  class ModuleTypeNotFoundError(Exception):
12
- """Raised when a module type cannot be found"""
17
+ """Raised when a module type cannot be found."""
13
18
 
14
19
  pass
15
20
 
@@ -17,11 +22,12 @@ class ModuleTypeNotFoundError(Exception):
17
22
  class ModuleTypeService:
18
23
  """
19
24
  Service for managing module type operations.
25
+
20
26
  Provides lookup, validation, and search functionality for XP system module types.
21
27
  """
22
28
 
23
29
  def __init__(self) -> None:
24
- """Initialize the module type service"""
30
+ """Initialize the module type service."""
25
31
  pass
26
32
 
27
33
  @staticmethod
@@ -193,7 +199,14 @@ class ModuleTypeService:
193
199
 
194
200
  @staticmethod
195
201
  def _format_module_summary(module_type: ModuleType) -> str:
196
- """Format a single module type for display"""
202
+ """Format a single module type for display.
203
+
204
+ Args:
205
+ module_type: The module type to format.
206
+
207
+ Returns:
208
+ Formatted string with module information.
209
+ """
197
210
  summary = f"Module: {module_type.name} (Code {module_type.code})\n"
198
211
  summary += f"Description: {module_type.description}\n"
199
212
  summary += f"Category: {module_type.category}\n"
@@ -213,7 +226,11 @@ class ModuleTypeService:
213
226
 
214
227
  @staticmethod
215
228
  def _format_all_modules() -> str:
216
- """Format all modules in a simple list"""
229
+ """Format all modules in a simple list.
230
+
231
+ Returns:
232
+ Formatted string with all modules.
233
+ """
217
234
  modules = get_all_module_types()
218
235
  lines = ["Code | Name | Description", "-" * 60]
219
236
 
@@ -224,7 +241,11 @@ class ModuleTypeService:
224
241
 
225
242
  @staticmethod
226
243
  def _format_modules_by_category() -> str:
227
- """Format modules grouped by category"""
244
+ """Format modules grouped by category.
245
+
246
+ Returns:
247
+ Formatted string with modules grouped by category.
248
+ """
228
249
  categories = get_module_types_by_category()
229
250
  lines = []
230
251
 
@@ -1,4 +1,4 @@
1
- """Protocol layer services for XP"""
1
+ """Protocol layer services for XP."""
2
2
 
3
3
  from xp.models.protocol.conbus_protocol import (
4
4
  ConnectionMadeEvent,
@@ -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