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.
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
- conson_xp-1.3.0.dist-info/RECORD +164 -0
- xp/__init__.py +4 -3
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -2
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
- xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
- xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
- xp/cli/commands/conbus/conbus_config_commands.py +10 -5
- xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
- xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
- xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
- xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
- xp/cli/commands/conbus/conbus_output_commands.py +52 -14
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +35 -18
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +8 -6
- xp/cli/commands/module_commands.py +38 -23
- xp/cli/commands/reverse_proxy_commands.py +27 -19
- xp/cli/commands/server/server_commands.py +18 -18
- xp/cli/commands/telegram/telegram.py +4 -12
- xp/cli/commands/telegram/telegram_blink_commands.py +10 -8
- xp/cli/commands/telegram/telegram_checksum_commands.py +19 -8
- xp/cli/commands/telegram/telegram_discover_commands.py +2 -4
- xp/cli/commands/telegram/telegram_linknumber_commands.py +11 -8
- xp/cli/commands/telegram/telegram_parse_commands.py +10 -9
- xp/cli/commands/telegram/telegram_version_commands.py +8 -4
- xp/cli/main.py +5 -3
- xp/cli/utils/click_tree.py +23 -3
- xp/cli/utils/datapoint_type_choice.py +20 -0
- xp/cli/utils/decorators.py +165 -14
- xp/cli/utils/error_handlers.py +49 -18
- xp/cli/utils/formatters.py +95 -10
- xp/cli/utils/serial_number_type.py +18 -0
- xp/cli/utils/system_function_choice.py +20 -0
- xp/cli/utils/xp_module_type.py +20 -0
- xp/connection/__init__.py +1 -1
- xp/connection/exceptions.py +5 -5
- xp/models/__init__.py +1 -1
- xp/models/actiontable/__init__.py +1 -0
- xp/models/actiontable/actiontable.py +17 -1
- xp/models/actiontable/msactiontable_xp20.py +10 -0
- xp/models/actiontable/msactiontable_xp24.py +20 -3
- xp/models/actiontable/msactiontable_xp33.py +27 -4
- xp/models/conbus/__init__.py +1 -0
- xp/models/conbus/conbus.py +34 -4
- xp/models/conbus/conbus_autoreport.py +20 -2
- xp/models/conbus/conbus_blink.py +22 -2
- xp/models/conbus/conbus_client_config.py +22 -1
- xp/models/conbus/conbus_connection_status.py +16 -2
- xp/models/conbus/conbus_custom.py +21 -2
- xp/models/conbus/conbus_datapoint.py +25 -2
- xp/models/conbus/conbus_discover.py +18 -2
- xp/models/conbus/conbus_lightlevel.py +20 -2
- xp/models/conbus/conbus_linknumber.py +20 -2
- xp/models/conbus/conbus_output.py +22 -2
- xp/models/conbus/conbus_raw.py +17 -2
- xp/models/conbus/conbus_receive.py +16 -2
- xp/models/conbus/conbus_writeconfig.py +60 -0
- xp/models/homekit/__init__.py +1 -0
- xp/models/homekit/homekit_accessory.py +15 -1
- xp/models/homekit/homekit_config.py +52 -0
- xp/models/homekit/homekit_conson_config.py +32 -0
- xp/models/log_entry.py +49 -9
- xp/models/protocol/__init__.py +1 -0
- xp/models/protocol/conbus_protocol.py +130 -21
- xp/models/telegram/__init__.py +1 -0
- xp/models/telegram/action_type.py +16 -2
- xp/models/telegram/datapoint_type.py +36 -2
- xp/models/telegram/event_telegram.py +46 -10
- xp/models/telegram/event_type.py +8 -1
- xp/models/telegram/input_action_type.py +34 -2
- xp/models/telegram/input_type.py +9 -1
- xp/models/telegram/module_type.py +69 -19
- xp/models/telegram/module_type_code.py +43 -1
- xp/models/telegram/output_telegram.py +30 -6
- xp/models/telegram/reply_telegram.py +56 -11
- xp/models/telegram/system_function.py +35 -3
- xp/models/telegram/system_telegram.py +18 -4
- xp/models/telegram/telegram.py +12 -3
- xp/models/telegram/telegram_type.py +8 -1
- xp/models/telegram/timeparam_type.py +27 -0
- xp/models/write_config_type.py +17 -2
- xp/services/__init__.py +1 -1
- xp/services/conbus/__init__.py +1 -0
- xp/services/conbus/actiontable/__init__.py +1 -0
- xp/services/conbus/actiontable/actiontable_service.py +33 -2
- xp/services/conbus/actiontable/msactiontable_service.py +40 -3
- xp/services/conbus/actiontable/msactiontable_xp24_serializer.py +36 -4
- xp/services/conbus/actiontable/msactiontable_xp33_serializer.py +45 -5
- xp/services/conbus/conbus_blink_all_service.py +40 -21
- xp/services/conbus/conbus_blink_service.py +37 -13
- xp/services/conbus/conbus_custom_service.py +29 -13
- xp/services/conbus/conbus_datapoint_queryall_service.py +40 -16
- xp/services/conbus/conbus_datapoint_service.py +42 -18
- xp/services/conbus/conbus_discover_service.py +43 -7
- xp/services/conbus/conbus_output_service.py +33 -13
- xp/services/conbus/conbus_raw_service.py +36 -16
- xp/services/conbus/conbus_receive_service.py +38 -6
- xp/services/conbus/conbus_scan_service.py +44 -18
- xp/services/conbus/write_config_service.py +193 -0
- xp/services/homekit/__init__.py +1 -0
- xp/services/homekit/homekit_cache_service.py +31 -6
- xp/services/homekit/homekit_conbus_service.py +33 -2
- xp/services/homekit/homekit_config_validator.py +97 -15
- xp/services/homekit/homekit_conson_validator.py +51 -7
- xp/services/homekit/homekit_dimminglight.py +47 -1
- xp/services/homekit/homekit_dimminglight_service.py +35 -1
- xp/services/homekit/homekit_hap_service.py +71 -18
- xp/services/homekit/homekit_lightbulb.py +35 -1
- xp/services/homekit/homekit_lightbulb_service.py +30 -2
- xp/services/homekit/homekit_module_service.py +23 -1
- xp/services/homekit/homekit_outlet.py +47 -1
- xp/services/homekit/homekit_outlet_service.py +44 -2
- xp/services/homekit/homekit_service.py +113 -19
- xp/services/log_file_service.py +37 -41
- xp/services/module_type_service.py +26 -5
- xp/services/protocol/__init__.py +1 -1
- xp/services/protocol/conbus_protocol.py +110 -16
- xp/services/protocol/protocol_factory.py +40 -0
- xp/services/protocol/telegram_protocol.py +38 -7
- xp/services/reverse_proxy_service.py +79 -14
- xp/services/server/__init__.py +1 -0
- xp/services/server/base_server_service.py +102 -14
- xp/services/server/cp20_server_service.py +12 -4
- xp/services/server/server_service.py +26 -11
- xp/services/server/xp130_server_service.py +11 -3
- xp/services/server/xp20_server_service.py +11 -3
- xp/services/server/xp230_server_service.py +11 -3
- xp/services/server/xp24_server_service.py +33 -6
- xp/services/server/xp33_server_service.py +41 -8
- xp/services/telegram/__init__.py +1 -0
- xp/services/telegram/telegram_blink_service.py +19 -31
- xp/services/telegram/telegram_checksum_service.py +10 -10
- xp/services/telegram/telegram_datapoint_service.py +70 -0
- xp/services/telegram/telegram_discover_service.py +58 -29
- xp/services/telegram/telegram_link_number_service.py +27 -40
- xp/services/telegram/telegram_output_service.py +46 -49
- xp/services/telegram/telegram_service.py +41 -41
- xp/services/telegram/telegram_version_service.py +4 -2
- xp/utils/__init__.py +1 -1
- xp/utils/dependencies.py +4 -47
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- xp/api/__init__.py +0 -1
- xp/api/main.py +0 -110
- xp/api/models/__init__.py +0 -1
- xp/api/models/api.py +0 -20
- xp/api/models/discover.py +0 -21
- xp/api/routers/__init__.py +0 -17
- xp/api/routers/conbus.py +0 -5
- xp/api/routers/conbus_blink.py +0 -105
- xp/api/routers/conbus_custom.py +0 -63
- xp/api/routers/conbus_datapoint.py +0 -67
- xp/api/routers/conbus_output.py +0 -147
- xp/api/routers/errors.py +0 -37
- xp/cli/commands/api.py +0 -16
- xp/cli/commands/api_start_commands.py +0 -126
- xp/services/conbus/conbus_autoreport_get_service.py +0 -85
- xp/services/conbus/conbus_autoreport_set_service.py +0 -128
- xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
- xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
- xp/services/conbus/conbus_linknumber_get_service.py +0 -86
- xp/services/conbus/conbus_linknumber_set_service.py +0 -155
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
xp/services/log_file_service.py
CHANGED
|
@@ -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
|
|
xp/services/protocol/__init__.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Conbus Protocol for XP telegram communication.
|
|
2
|
+
|
|
3
|
+
This module implements the Twisted protocol for Conbus communication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
from typing import Any, Optional
|
|
3
8
|
|
|
@@ -17,8 +22,15 @@ from xp.utils import calculate_checksum
|
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
20
|
-
"""
|
|
21
|
-
|
|
25
|
+
"""Twisted protocol for XP telegram communication.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
buffer: Buffer for incoming telegram data.
|
|
29
|
+
logger: Logger instance for this protocol.
|
|
30
|
+
cli_config: Conbus configuration settings.
|
|
31
|
+
reactor: Twisted reactor instance.
|
|
32
|
+
timeout_seconds: Timeout duration in seconds.
|
|
33
|
+
timeout_call: Delayed call handle for timeout management.
|
|
22
34
|
"""
|
|
23
35
|
|
|
24
36
|
buffer: bytes
|
|
@@ -28,6 +40,12 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
28
40
|
cli_config: ConbusClientConfig,
|
|
29
41
|
reactor: PosixReactorBase,
|
|
30
42
|
) -> None:
|
|
43
|
+
"""Initialize ConbusProtocol.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
cli_config: Configuration for Conbus client connection.
|
|
47
|
+
reactor: Twisted reactor for event handling.
|
|
48
|
+
"""
|
|
31
49
|
self.buffer = b""
|
|
32
50
|
self.logger = logging.getLogger(__name__)
|
|
33
51
|
self.cli_config = cli_config.conbus
|
|
@@ -36,12 +54,24 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
36
54
|
self.timeout_call: Optional[DelayedCall] = None
|
|
37
55
|
|
|
38
56
|
def connectionMade(self) -> None:
|
|
57
|
+
"""Handle connection established event.
|
|
58
|
+
|
|
59
|
+
Called when TCP connection is successfully established.
|
|
60
|
+
Starts inactivity timeout monitoring.
|
|
61
|
+
"""
|
|
39
62
|
self.logger.debug("connectionMade")
|
|
40
63
|
self.connection_established()
|
|
41
64
|
# Start inactivity timeout
|
|
42
65
|
self._reset_timeout()
|
|
43
66
|
|
|
44
67
|
def dataReceived(self, data: bytes) -> None:
|
|
68
|
+
"""Handle received data from TCP connection.
|
|
69
|
+
|
|
70
|
+
Parses incoming telegram frames and dispatches events.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
data: Raw bytes received from connection.
|
|
74
|
+
"""
|
|
45
75
|
self.logger.debug("dataReceived")
|
|
46
76
|
self.buffer += data
|
|
47
77
|
|
|
@@ -94,11 +124,13 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
94
124
|
self.telegram_received(telegram_received)
|
|
95
125
|
|
|
96
126
|
def sendFrame(self, data: bytes) -> None:
|
|
97
|
-
"""
|
|
98
|
-
Send telegram frame
|
|
127
|
+
"""Send telegram frame.
|
|
99
128
|
|
|
100
129
|
Args:
|
|
101
|
-
data: Raw telegram payload (without checksum/framing)
|
|
130
|
+
data: Raw telegram payload (without checksum/framing).
|
|
131
|
+
|
|
132
|
+
Raises:
|
|
133
|
+
IOError: If transport is not open.
|
|
102
134
|
"""
|
|
103
135
|
# Calculate full frame (add checksum and brackets)
|
|
104
136
|
checksum = calculate_checksum(data.decode())
|
|
@@ -121,6 +153,14 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
121
153
|
system_function: SystemFunction,
|
|
122
154
|
data_value: str,
|
|
123
155
|
) -> None:
|
|
156
|
+
"""Send telegram with specified parameters.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
telegram_type: Type of telegram to send.
|
|
160
|
+
serial_number: Device serial number.
|
|
161
|
+
system_function: System function code.
|
|
162
|
+
data_value: Data value to send.
|
|
163
|
+
"""
|
|
124
164
|
payload = (
|
|
125
165
|
f"{telegram_type.value}"
|
|
126
166
|
f"{serial_number}"
|
|
@@ -130,33 +170,62 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
130
170
|
self.sendFrame(payload.encode())
|
|
131
171
|
|
|
132
172
|
def buildProtocol(self, addr: IAddress) -> protocol.Protocol:
|
|
173
|
+
"""Build protocol instance for connection.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
addr: Address of the connection.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Protocol instance for this connection.
|
|
180
|
+
"""
|
|
133
181
|
self.logger.debug(f"buildProtocol: {addr}")
|
|
134
182
|
return self
|
|
135
183
|
|
|
136
184
|
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
185
|
+
"""Handle client connection failure.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
connector: Connection connector instance.
|
|
189
|
+
reason: Failure reason details.
|
|
190
|
+
"""
|
|
137
191
|
self.logger.debug(f"clientConnectionFailed: {reason}")
|
|
138
192
|
self.connection_failed(reason)
|
|
139
193
|
self._cancel_timeout()
|
|
140
194
|
self._stop_reactor()
|
|
141
195
|
|
|
142
196
|
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
197
|
+
"""Handle client connection lost event.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
connector: Connection connector instance.
|
|
201
|
+
reason: Reason for connection loss.
|
|
202
|
+
"""
|
|
143
203
|
self.logger.debug(f"clientConnectionLost: {reason}")
|
|
144
204
|
self.connection_lost(reason)
|
|
145
205
|
self._cancel_timeout()
|
|
146
206
|
self._stop_reactor()
|
|
147
207
|
|
|
148
208
|
def timeout(self) -> bool:
|
|
149
|
-
"""
|
|
209
|
+
"""Handle timeout event.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
True to continue waiting for next timeout, False to stop.
|
|
213
|
+
"""
|
|
150
214
|
self.logger.info("Timeout after: %ss", self.timeout_seconds)
|
|
151
215
|
self.failed(f"Timeout after: {self.timeout_seconds}s")
|
|
152
216
|
return False
|
|
153
217
|
|
|
154
218
|
def connection_failed(self, reason: Failure) -> None:
|
|
219
|
+
"""Handle connection failure.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
reason: Failure reason details.
|
|
223
|
+
"""
|
|
155
224
|
self.logger.debug(f"Client connection failed: {reason}")
|
|
156
225
|
self.failed(reason.getErrorMessage())
|
|
157
226
|
|
|
158
227
|
def _reset_timeout(self) -> None:
|
|
159
|
-
"""Reset the inactivity timeout"""
|
|
228
|
+
"""Reset the inactivity timeout."""
|
|
160
229
|
self._cancel_timeout()
|
|
161
230
|
self.timeout_call = self.reactor.callLater(
|
|
162
231
|
self.timeout_seconds, self._on_timeout
|
|
@@ -164,26 +233,26 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
164
233
|
self.logger.debug(f"Timeout set for {self.timeout_seconds} seconds")
|
|
165
234
|
|
|
166
235
|
def _cancel_timeout(self) -> None:
|
|
167
|
-
"""Cancel the inactivity timeout"""
|
|
236
|
+
"""Cancel the inactivity timeout."""
|
|
168
237
|
if self.timeout_call and self.timeout_call.active():
|
|
169
238
|
self.timeout_call.cancel()
|
|
170
239
|
self.logger.debug("Timeout cancelled")
|
|
171
240
|
|
|
172
241
|
def _on_timeout(self) -> None:
|
|
173
|
-
"""
|
|
242
|
+
"""Handle inactivity timeout expiration."""
|
|
174
243
|
self.logger.debug(f"Conbus timeout after {self.timeout_seconds} seconds")
|
|
175
244
|
continue_work = self.timeout()
|
|
176
245
|
if not continue_work:
|
|
177
246
|
self._stop_reactor()
|
|
178
247
|
|
|
179
248
|
def _stop_reactor(self) -> None:
|
|
180
|
-
"""Stop the reactor if it's running"""
|
|
249
|
+
"""Stop the reactor if it's running."""
|
|
181
250
|
if self.reactor.running:
|
|
182
251
|
self.logger.info("Stopping reactor")
|
|
183
252
|
self.reactor.stop()
|
|
184
253
|
|
|
185
254
|
def start_reactor(self) -> None:
|
|
186
|
-
"""Start the reactor if it's running"""
|
|
255
|
+
"""Start the reactor if it's running."""
|
|
187
256
|
# Connect to TCP server
|
|
188
257
|
self.logger.info(
|
|
189
258
|
f"Connecting to TCP server {self.cli_config.ip}:{self.cli_config.port}"
|
|
@@ -191,11 +260,15 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
191
260
|
self.reactor.connectTCP(self.cli_config.ip, self.cli_config.port, self)
|
|
192
261
|
|
|
193
262
|
# Run the reactor (which now uses asyncio underneath)
|
|
194
|
-
self.logger.info("Starting reactor event loop
|
|
263
|
+
self.logger.info("Starting reactor event loop.")
|
|
195
264
|
self.reactor.run()
|
|
196
265
|
|
|
197
266
|
def __enter__(self) -> "ConbusProtocol":
|
|
198
|
-
"""
|
|
267
|
+
"""Enter context manager.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Self for context management.
|
|
271
|
+
"""
|
|
199
272
|
return self
|
|
200
273
|
|
|
201
274
|
def __exit__(
|
|
@@ -204,23 +277,44 @@ class ConbusProtocol(protocol.Protocol, protocol.ClientFactory):
|
|
|
204
277
|
_exc_val: Optional[BaseException],
|
|
205
278
|
_exc_tb: Optional[Any],
|
|
206
279
|
) -> None:
|
|
207
|
-
"""Context manager exit - ensure connection is closed"""
|
|
208
|
-
self.logger.debug("Exiting the event loop
|
|
280
|
+
"""Context manager exit - ensure connection is closed."""
|
|
281
|
+
self.logger.debug("Exiting the event loop.")
|
|
209
282
|
self._stop_reactor()
|
|
210
283
|
|
|
211
|
-
"""Override methods"""
|
|
284
|
+
"""Override methods."""
|
|
212
285
|
|
|
213
286
|
def telegram_sent(self, telegram_sent: str) -> None:
|
|
287
|
+
"""Override callback when telegram has been sent.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
telegram_sent: The telegram that was sent.
|
|
291
|
+
"""
|
|
214
292
|
pass
|
|
215
293
|
|
|
216
294
|
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
295
|
+
"""Override callback when telegram is received.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
telegram_received: Event containing received telegram details.
|
|
299
|
+
"""
|
|
217
300
|
pass
|
|
218
301
|
|
|
219
302
|
def connection_established(self) -> None:
|
|
303
|
+
"""Override callback when connection established."""
|
|
220
304
|
pass
|
|
221
305
|
|
|
222
306
|
def connection_lost(self, reason: Failure) -> None:
|
|
307
|
+
"""Override callback when connection is lost.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
reason: Reason for connection loss.
|
|
311
|
+
"""
|
|
223
312
|
pass
|
|
224
313
|
|
|
225
314
|
def failed(self, message: str) -> None:
|
|
315
|
+
"""Override callback when connection failed.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
message: Error message describing the failure.
|
|
319
|
+
"""
|
|
226
320
|
pass
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Protocol Factory for Twisted protocol creation.
|
|
2
|
+
|
|
3
|
+
This module provides factory classes for protocol instantiation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
|
|
3
8
|
from bubus import EventBus
|
|
@@ -13,26 +18,61 @@ from xp.services.protocol import TelegramProtocol
|
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
class TelegramFactory(protocol.ClientFactory):
|
|
21
|
+
"""Factory for creating Telegram protocol instances.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
event_bus: Event bus for dispatching protocol events.
|
|
25
|
+
telegram_protocol: Protocol instance to use.
|
|
26
|
+
connector: Connection connector instance.
|
|
27
|
+
logger: Logger instance for this factory.
|
|
28
|
+
"""
|
|
29
|
+
|
|
16
30
|
def __init__(
|
|
17
31
|
self,
|
|
18
32
|
event_bus: EventBus,
|
|
19
33
|
telegram_protocol: TelegramProtocol,
|
|
20
34
|
connector: IConnector,
|
|
21
35
|
) -> None:
|
|
36
|
+
"""Initialize TelegramFactory.
|
|
22
37
|
|
|
38
|
+
Args:
|
|
39
|
+
event_bus: Event bus for protocol events.
|
|
40
|
+
telegram_protocol: Protocol instance to use for connections.
|
|
41
|
+
connector: Connection connector for managing connections.
|
|
42
|
+
"""
|
|
23
43
|
self.event_bus = event_bus
|
|
24
44
|
self.telegram_protocol = telegram_protocol
|
|
25
45
|
self.connector = connector
|
|
26
46
|
self.logger = logging.getLogger(__name__)
|
|
27
47
|
|
|
28
48
|
def buildProtocol(self, addr: IAddress) -> TelegramProtocol:
|
|
49
|
+
"""Build protocol instance for connection.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
addr: Address of the connection.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Telegram protocol instance for this connection.
|
|
56
|
+
"""
|
|
29
57
|
self.logger.debug(f"buildProtocol: {addr}")
|
|
30
58
|
return self.telegram_protocol
|
|
31
59
|
|
|
32
60
|
def clientConnectionFailed(self, connector: IConnector, reason: Failure) -> None:
|
|
61
|
+
"""Handle connection failure event.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
connector: Connection connector instance.
|
|
65
|
+
reason: Failure reason details.
|
|
66
|
+
"""
|
|
33
67
|
self.event_bus.dispatch(ConnectionFailedEvent(reason=str(reason)))
|
|
34
68
|
self.connector.stop()
|
|
35
69
|
|
|
36
70
|
def clientConnectionLost(self, connector: IConnector, reason: Failure) -> None:
|
|
71
|
+
"""Handle connection lost event.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
connector: Connection connector instance.
|
|
75
|
+
reason: Reason for connection loss.
|
|
76
|
+
"""
|
|
37
77
|
self.event_bus.dispatch(ConnectionLostEvent(reason=str(reason)))
|
|
38
78
|
self.connector.stop()
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Telegram Protocol for XP telegram communication.
|
|
2
|
+
|
|
3
|
+
This module provides the protocol implementation for telegram-based communication.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import asyncio
|
|
2
7
|
import logging
|
|
3
8
|
import time
|
|
@@ -15,17 +20,30 @@ from xp.utils import calculate_checksum
|
|
|
15
20
|
|
|
16
21
|
|
|
17
22
|
class TelegramProtocol(protocol.Protocol):
|
|
18
|
-
"""
|
|
19
|
-
Twisted protocol for XP telegram communication with built-in debouncing.
|
|
23
|
+
"""Twisted protocol for XP telegram communication with built-in debouncing.
|
|
20
24
|
|
|
21
25
|
Automatically deduplicates identical telegram frames sent within a
|
|
22
26
|
configurable time window (default 50ms).
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
buffer: Buffer for incoming telegram data.
|
|
30
|
+
event_bus: Event bus for dispatching protocol events.
|
|
31
|
+
debounce_ms: Debounce time window in milliseconds.
|
|
32
|
+
logger: Logger instance for this protocol.
|
|
33
|
+
send_queue: Dictionary tracking frame send timestamps.
|
|
34
|
+
timer_handle: Handle for cleanup timer.
|
|
23
35
|
"""
|
|
24
36
|
|
|
25
37
|
buffer: bytes
|
|
26
38
|
event_bus: EventBus
|
|
27
39
|
|
|
28
40
|
def __init__(self, event_bus: EventBus, debounce_ms: int = 50) -> None:
|
|
41
|
+
"""Initialize TelegramProtocol.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
event_bus: Event bus for dispatching protocol events.
|
|
45
|
+
debounce_ms: Debounce time window in milliseconds.
|
|
46
|
+
"""
|
|
29
47
|
self.buffer = b""
|
|
30
48
|
self.event_bus = event_bus
|
|
31
49
|
self.debounce_ms = debounce_ms
|
|
@@ -36,6 +54,7 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
36
54
|
self.timer_handle: Optional[asyncio.TimerHandle] = None
|
|
37
55
|
|
|
38
56
|
def connectionMade(self) -> None:
|
|
57
|
+
"""Handle connection established event."""
|
|
39
58
|
self.logger.debug("connectionMade")
|
|
40
59
|
try:
|
|
41
60
|
self.logger.debug("Scheduling async connection handler")
|
|
@@ -45,7 +64,11 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
45
64
|
self.logger.error(f"Error scheduling async handler: {e}", exc_info=True)
|
|
46
65
|
|
|
47
66
|
def _on_task_done(self, task: asyncio.Task) -> None:
|
|
48
|
-
"""
|
|
67
|
+
"""Handle async task completion.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
task: Completed async task.
|
|
71
|
+
"""
|
|
49
72
|
try:
|
|
50
73
|
if task.exception():
|
|
51
74
|
self.logger.error(
|
|
@@ -57,7 +80,7 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
57
80
|
self.logger.error(f"Error in task done callback: {e}", exc_info=True)
|
|
58
81
|
|
|
59
82
|
async def _async_connection_made(self) -> None:
|
|
60
|
-
"""Async handler for connection made"""
|
|
83
|
+
"""Async handler for connection made."""
|
|
61
84
|
self.logger.debug("_async_connectionMade starting")
|
|
62
85
|
self.logger.info("Dispatching ConnectionMadeEvent")
|
|
63
86
|
try:
|
|
@@ -69,12 +92,16 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
69
92
|
)
|
|
70
93
|
|
|
71
94
|
def dataReceived(self, data: bytes) -> None:
|
|
72
|
-
"""
|
|
95
|
+
"""Handle received data from Twisted.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
data: Raw bytes received from connection.
|
|
99
|
+
"""
|
|
73
100
|
task = asyncio.create_task(self._async_dataReceived(data))
|
|
74
101
|
task.add_done_callback(self._on_task_done)
|
|
75
102
|
|
|
76
103
|
async def _async_dataReceived(self, data: bytes) -> None:
|
|
77
|
-
"""Async handler for received data"""
|
|
104
|
+
"""Async handler for received data."""
|
|
78
105
|
self.logger.debug("dataReceived")
|
|
79
106
|
self.buffer += data
|
|
80
107
|
|
|
@@ -137,7 +164,11 @@ class TelegramProtocol(protocol.Protocol):
|
|
|
137
164
|
)
|
|
138
165
|
|
|
139
166
|
def sendFrame(self, data: bytes) -> None:
|
|
140
|
-
"""
|
|
167
|
+
"""Send telegram frame.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
data: Raw telegram payload (without checksum/framing).
|
|
171
|
+
"""
|
|
141
172
|
task = asyncio.create_task(self._async_sendFrame(data))
|
|
142
173
|
task.add_done_callback(self._on_task_done)
|
|
143
174
|
|