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
|
@@ -16,7 +16,7 @@ from xp.models.response import Response
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class ReverseProxyError(Exception):
|
|
19
|
-
"""Raised when Conbus reverse proxy operations fail"""
|
|
19
|
+
"""Raised when Conbus reverse proxy operations fail."""
|
|
20
20
|
|
|
21
21
|
pass
|
|
22
22
|
|
|
@@ -28,6 +28,17 @@ class ReverseProxyService:
|
|
|
28
28
|
Accepts client connections on port 10001 and forwards all telegrams
|
|
29
29
|
to the target server configured in cli.yml. Monitors and prints all
|
|
30
30
|
bidirectional traffic with timestamps.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
logger: Logger instance for the service.
|
|
34
|
+
listen_port: Port to listen on for client connections.
|
|
35
|
+
server_socket: Main server socket for accepting connections.
|
|
36
|
+
is_running: Flag indicating if proxy is running.
|
|
37
|
+
active_connections: Dictionary of active connection information.
|
|
38
|
+
connection_counter: Counter for connection IDs.
|
|
39
|
+
cli_config: Conbus client configuration.
|
|
40
|
+
target_ip: Target server IP address.
|
|
41
|
+
target_port: Target server port number.
|
|
31
42
|
"""
|
|
32
43
|
|
|
33
44
|
def __init__(
|
|
@@ -35,7 +46,12 @@ class ReverseProxyService:
|
|
|
35
46
|
cli_config: ConbusClientConfig,
|
|
36
47
|
listen_port: int,
|
|
37
48
|
):
|
|
38
|
-
"""Initialize the Conbus reverse proxy service
|
|
49
|
+
"""Initialize the Conbus reverse proxy service.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
cli_config: Conbus client configuration.
|
|
53
|
+
listen_port: Port to listen on for client connections.
|
|
54
|
+
"""
|
|
39
55
|
# Set up logging first
|
|
40
56
|
self.logger = logging.getLogger(__name__)
|
|
41
57
|
|
|
@@ -50,16 +66,28 @@ class ReverseProxyService:
|
|
|
50
66
|
|
|
51
67
|
@property
|
|
52
68
|
def target_ip(self) -> str:
|
|
53
|
-
"""Get target server IP
|
|
69
|
+
"""Get target server IP.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Target server IP address.
|
|
73
|
+
"""
|
|
54
74
|
return self.cli_config.conbus.ip
|
|
55
75
|
|
|
56
76
|
@property
|
|
57
77
|
def target_port(self) -> int:
|
|
58
|
-
"""Get target server port
|
|
78
|
+
"""Get target server port.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Target server port number.
|
|
82
|
+
"""
|
|
59
83
|
return self.cli_config.conbus.port
|
|
60
84
|
|
|
61
85
|
def start_proxy(self) -> Response:
|
|
62
|
-
"""Start the reverse proxy server
|
|
86
|
+
"""Start the reverse proxy server.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Response object with success status and proxy details.
|
|
90
|
+
"""
|
|
63
91
|
if self.is_running:
|
|
64
92
|
return Response(
|
|
65
93
|
success=False, data=None, error="Reverse proxy is already running"
|
|
@@ -111,7 +139,11 @@ class ReverseProxyService:
|
|
|
111
139
|
)
|
|
112
140
|
|
|
113
141
|
def stop_proxy(self) -> Response:
|
|
114
|
-
"""Stop the reverse proxy server
|
|
142
|
+
"""Stop the reverse proxy server.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Response object with success status.
|
|
146
|
+
"""
|
|
115
147
|
if not self.is_running:
|
|
116
148
|
return Response(
|
|
117
149
|
success=False, data=None, error="Reverse proxy is not running"
|
|
@@ -139,7 +171,11 @@ class ReverseProxyService:
|
|
|
139
171
|
)
|
|
140
172
|
|
|
141
173
|
def get_status(self) -> Response:
|
|
142
|
-
"""Get current proxy status and active connections
|
|
174
|
+
"""Get current proxy status and active connections.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Response object with proxy status and connection details.
|
|
178
|
+
"""
|
|
143
179
|
return Response(
|
|
144
180
|
success=True,
|
|
145
181
|
data={
|
|
@@ -161,7 +197,7 @@ class ReverseProxyService:
|
|
|
161
197
|
)
|
|
162
198
|
|
|
163
199
|
def _accept_connections(self) -> None:
|
|
164
|
-
"""Accept and handle client connections"""
|
|
200
|
+
"""Accept and handle client connections."""
|
|
165
201
|
while self.is_running:
|
|
166
202
|
try:
|
|
167
203
|
# Accept connection
|
|
@@ -194,7 +230,13 @@ class ReverseProxyService:
|
|
|
194
230
|
def _handle_client(
|
|
195
231
|
self, client_socket: socket.socket, client_address: tuple, conn_id: str
|
|
196
232
|
) -> None:
|
|
197
|
-
"""Handle individual client connection with server relay
|
|
233
|
+
"""Handle individual client connection with server relay.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
client_socket: Client socket connection.
|
|
237
|
+
client_address: Client address tuple (ip, port).
|
|
238
|
+
conn_id: Connection identifier.
|
|
239
|
+
"""
|
|
198
240
|
try:
|
|
199
241
|
# Connect to target server
|
|
200
242
|
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
@@ -272,7 +314,15 @@ class ReverseProxyService:
|
|
|
272
314
|
dest_label: str,
|
|
273
315
|
conn_id: str,
|
|
274
316
|
) -> None:
|
|
275
|
-
"""Relay data between sockets with telegram monitoring
|
|
317
|
+
"""Relay data between sockets with telegram monitoring.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
source_socket: Source socket to receive from.
|
|
321
|
+
dest_socket: Destination socket to send to.
|
|
322
|
+
source_label: Label for source in logs.
|
|
323
|
+
dest_label: Label for destination in logs.
|
|
324
|
+
conn_id: Connection identifier.
|
|
325
|
+
"""
|
|
276
326
|
try:
|
|
277
327
|
while self.is_running:
|
|
278
328
|
# Receive data from source
|
|
@@ -316,7 +366,11 @@ class ReverseProxyService:
|
|
|
316
366
|
self.logger.error(f"Error in data relay: {e} [{conn_id}]")
|
|
317
367
|
|
|
318
368
|
def _close_connection_pair(self, conn_id: str) -> None:
|
|
319
|
-
"""Close both client and server sockets for a connection
|
|
369
|
+
"""Close both client and server sockets for a connection.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
conn_id: Connection identifier.
|
|
373
|
+
"""
|
|
320
374
|
if conn_id not in self.active_connections:
|
|
321
375
|
return
|
|
322
376
|
|
|
@@ -344,7 +398,10 @@ class ReverseProxyService:
|
|
|
344
398
|
f"Client {client_address} disconnected [{conn_id}] - {bytes_relayed} bytes relayed"
|
|
345
399
|
)
|
|
346
400
|
print(
|
|
347
|
-
f"{self.timestamp()} [DISCONNECTION]
|
|
401
|
+
f"{self.timestamp()} [DISCONNECTION] "
|
|
402
|
+
f"Client {client_address} "
|
|
403
|
+
f"disconnected [{conn_id}] - "
|
|
404
|
+
f"{bytes_relayed} bytes relayed"
|
|
348
405
|
)
|
|
349
406
|
|
|
350
407
|
# Remove from active connections
|
|
@@ -352,11 +409,19 @@ class ReverseProxyService:
|
|
|
352
409
|
|
|
353
410
|
@staticmethod
|
|
354
411
|
def timestamp() -> str:
|
|
355
|
-
"""Generate timestamp string for logging
|
|
412
|
+
"""Generate timestamp string for logging.
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Timestamp string in HH:MM:SS,mmm format.
|
|
416
|
+
"""
|
|
356
417
|
return datetime.now().strftime("%H:%M:%S,%f")[:-3]
|
|
357
418
|
|
|
358
419
|
def run_blocking(self) -> None:
|
|
359
|
-
"""Run the proxy in blocking mode (for CLI usage)
|
|
420
|
+
"""Run the proxy in blocking mode (for CLI usage).
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ReverseProxyError: If proxy fails to start.
|
|
424
|
+
"""
|
|
360
425
|
result = self.start_proxy()
|
|
361
426
|
if not result.success:
|
|
362
427
|
raise ReverseProxyError(result.error)
|
xp/services/server/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Server services for XP protocol variants."""
|
|
@@ -23,7 +23,11 @@ class BaseServerService(ABC):
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
def __init__(self, serial_number: str):
|
|
26
|
-
"""Initialize base server service
|
|
26
|
+
"""Initialize base server service.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
serial_number: The device serial number.
|
|
30
|
+
"""
|
|
27
31
|
self.serial_number = serial_number
|
|
28
32
|
self.logger = logging.getLogger(__name__)
|
|
29
33
|
|
|
@@ -40,7 +44,14 @@ class BaseServerService(ABC):
|
|
|
40
44
|
def generate_datapoint_type_response(
|
|
41
45
|
self, datapoint_type: DataPointType
|
|
42
46
|
) -> Optional[str]:
|
|
43
|
-
"""Generate datapoint_type response telegram
|
|
47
|
+
"""Generate datapoint_type response telegram.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
datapoint_type: The type of datapoint to query.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The response telegram string, or None if generation fails.
|
|
54
|
+
"""
|
|
44
55
|
datapoint_values = {
|
|
45
56
|
DataPointType.TEMPERATURE: self.temperature,
|
|
46
57
|
DataPointType.MODULE_TYPE_CODE: f"{self.module_type_code:02X}",
|
|
@@ -62,23 +73,46 @@ class BaseServerService(ABC):
|
|
|
62
73
|
return telegram
|
|
63
74
|
|
|
64
75
|
def _check_request_for_device(self, request: SystemTelegram) -> bool:
|
|
65
|
-
"""Check if request is for this device (including broadcast)
|
|
76
|
+
"""Check if request is for this device (including broadcast).
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
request: The system telegram request to check.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if request is for this device or broadcast, False otherwise.
|
|
83
|
+
"""
|
|
66
84
|
return request.serial_number in (self.serial_number, "0000000000")
|
|
67
85
|
|
|
68
86
|
@staticmethod
|
|
69
87
|
def _build_response_telegram(data_part: str) -> str:
|
|
70
|
-
"""Build a complete response telegram with checksum
|
|
88
|
+
"""Build a complete response telegram with checksum.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
data_part: The data part of the telegram without checksum.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The complete telegram with checksum enclosed in angle brackets.
|
|
95
|
+
"""
|
|
71
96
|
checksum = calculate_checksum(data_part)
|
|
72
97
|
return f"<{data_part}{checksum}>"
|
|
73
98
|
|
|
74
99
|
def _log_response(self, response_type: str, telegram: str) -> None:
|
|
75
|
-
"""Log response generation
|
|
100
|
+
"""Log response generation.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
response_type: The type of response being generated.
|
|
104
|
+
telegram: The telegram string being logged.
|
|
105
|
+
"""
|
|
76
106
|
self.logger.debug(
|
|
77
107
|
f"Generated {self.device_type} {response_type} response: {telegram}"
|
|
78
108
|
)
|
|
79
109
|
|
|
80
110
|
def generate_discover_response(self) -> str:
|
|
81
|
-
"""Generate discover response telegram
|
|
111
|
+
"""Generate discover response telegram.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
The discover response telegram string.
|
|
115
|
+
"""
|
|
82
116
|
data_part = f"R{self.serial_number}F01D"
|
|
83
117
|
telegram = self._build_response_telegram(data_part)
|
|
84
118
|
self._log_response("discover", telegram)
|
|
@@ -87,7 +121,15 @@ class BaseServerService(ABC):
|
|
|
87
121
|
def set_link_number(
|
|
88
122
|
self, request: SystemTelegram, new_link_number: int
|
|
89
123
|
) -> Optional[str]:
|
|
90
|
-
"""Set link number and generate ACK response
|
|
124
|
+
"""Set link number and generate ACK response.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
request: The system telegram request.
|
|
128
|
+
new_link_number: The new link number to set.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The ACK response telegram string, or None if request is invalid.
|
|
132
|
+
"""
|
|
91
133
|
if (
|
|
92
134
|
request.system_function == SystemFunction.WRITE_CONFIG
|
|
93
135
|
and request.datapoint_type == DataPointType.LINK_NUMBER
|
|
@@ -105,7 +147,14 @@ class BaseServerService(ABC):
|
|
|
105
147
|
return None
|
|
106
148
|
|
|
107
149
|
def process_system_telegram(self, request: SystemTelegram) -> Optional[str]:
|
|
108
|
-
"""Template method for processing system telegrams
|
|
150
|
+
"""Template method for processing system telegrams.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
request: The system telegram request to process.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
The response telegram string, or None if request cannot be handled.
|
|
157
|
+
"""
|
|
109
158
|
# Check if request is for this device
|
|
110
159
|
if not self._check_request_for_device(request):
|
|
111
160
|
return None
|
|
@@ -127,7 +176,14 @@ class BaseServerService(ABC):
|
|
|
127
176
|
return None
|
|
128
177
|
|
|
129
178
|
def _handle_return_data_request(self, request: SystemTelegram) -> Optional[str]:
|
|
130
|
-
"""Handle RETURN_DATA requests - can be overridden by subclasses
|
|
179
|
+
"""Handle RETURN_DATA requests - can be overridden by subclasses.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
request: The system telegram request.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The response telegram string, or None if request cannot be handled.
|
|
186
|
+
"""
|
|
131
187
|
self.logger.debug(
|
|
132
188
|
f"_handle_return_data_request {self.device_type} request: {request}"
|
|
133
189
|
)
|
|
@@ -140,27 +196,59 @@ class BaseServerService(ABC):
|
|
|
140
196
|
def _handle_device_specific_data_request(
|
|
141
197
|
self, request: SystemTelegram
|
|
142
198
|
) -> Optional[str]:
|
|
143
|
-
"""Override in subclasses for device-specific data requests
|
|
199
|
+
"""Override in subclasses for device-specific data requests.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
request: The system telegram request.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The response telegram string, or None if request cannot be handled.
|
|
206
|
+
"""
|
|
144
207
|
return None
|
|
145
208
|
|
|
146
209
|
def _handle_write_config_request(self, request: SystemTelegram) -> Optional[str]:
|
|
147
|
-
"""Handle WRITE_CONFIG requests
|
|
210
|
+
"""Handle WRITE_CONFIG requests.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
request: The system telegram request.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The response telegram string, or None if request cannot be handled.
|
|
217
|
+
"""
|
|
148
218
|
if request.datapoint_type == DataPointType.LINK_NUMBER:
|
|
149
219
|
return self.set_link_number(request, 1) # Default implementation
|
|
150
220
|
|
|
151
221
|
return self._handle_device_specific_config_request()
|
|
152
222
|
|
|
153
223
|
def _handle_action_request(self, request: SystemTelegram) -> Optional[str]:
|
|
154
|
-
"""Handle ACTION requests
|
|
224
|
+
"""Handle ACTION requests.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
request: The system telegram request.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
The response telegram string, or None if request cannot be handled.
|
|
231
|
+
"""
|
|
155
232
|
return self._handle_device_specific_action_request(request)
|
|
156
233
|
|
|
157
234
|
def _handle_device_specific_action_request(
|
|
158
235
|
self, request: SystemTelegram
|
|
159
236
|
) -> Optional[str]:
|
|
160
|
-
"""Override in subclasses for device-specific data requests
|
|
237
|
+
"""Override in subclasses for device-specific data requests.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
request: The system telegram request.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
The response telegram string, or None if request cannot be handled.
|
|
244
|
+
"""
|
|
161
245
|
return None
|
|
162
246
|
|
|
163
247
|
@staticmethod
|
|
164
248
|
def _handle_device_specific_config_request() -> Optional[str]:
|
|
165
|
-
"""Override in subclasses for device-specific config requests
|
|
249
|
+
"""Override in subclasses for device-specific config requests.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The response telegram string, or None if request cannot be handled.
|
|
253
|
+
"""
|
|
166
254
|
return None
|
|
@@ -11,7 +11,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class CP20ServerError(Exception):
|
|
14
|
-
"""Raised when CP20 server operations fail"""
|
|
14
|
+
"""Raised when CP20 server operations fail."""
|
|
15
15
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
@@ -25,7 +25,11 @@ class CP20ServerService(BaseServerService):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
def __init__(self, serial_number: str):
|
|
28
|
-
"""Initialize CP20 server service
|
|
28
|
+
"""Initialize CP20 server service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial_number: The device serial number.
|
|
32
|
+
"""
|
|
29
33
|
super().__init__(serial_number)
|
|
30
34
|
self.device_type = "CP20"
|
|
31
35
|
self.module_type_code = 2 # CP20 module type from registry
|
|
@@ -34,11 +38,15 @@ class CP20ServerService(BaseServerService):
|
|
|
34
38
|
def _handle_device_specific_data_request(
|
|
35
39
|
self, request: SystemTelegram
|
|
36
40
|
) -> Optional[str]:
|
|
37
|
-
"""Handle CP20-specific data requests"""
|
|
41
|
+
"""Handle CP20-specific data requests."""
|
|
38
42
|
return None
|
|
39
43
|
|
|
40
44
|
def get_device_info(self) -> Dict:
|
|
41
|
-
"""Get CP20 device information
|
|
45
|
+
"""Get CP20 device information.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dictionary containing device information.
|
|
49
|
+
"""
|
|
42
50
|
return {
|
|
43
51
|
"serial_number": self.serial_number,
|
|
44
52
|
"device_type": self.device_type,
|
|
@@ -26,7 +26,7 @@ from xp.services.telegram.telegram_service import TelegramService
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class ServerError(Exception):
|
|
29
|
-
"""Raised when Conbus server operations fail"""
|
|
29
|
+
"""Raised when Conbus server operations fail."""
|
|
30
30
|
|
|
31
31
|
pass
|
|
32
32
|
|
|
@@ -46,7 +46,14 @@ class ServerService:
|
|
|
46
46
|
config_path: str = "server.yml",
|
|
47
47
|
port: int = 10001,
|
|
48
48
|
):
|
|
49
|
-
"""Initialize the Conbus server service
|
|
49
|
+
"""Initialize the Conbus server service.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
telegram_service: Service for parsing system telegrams.
|
|
53
|
+
discover_service: Service for handling discover requests.
|
|
54
|
+
config_path: Path to the server configuration file.
|
|
55
|
+
port: TCP port to listen on.
|
|
56
|
+
"""
|
|
50
57
|
self.telegram_service = telegram_service
|
|
51
58
|
self.discover_service = discover_service
|
|
52
59
|
self.config_path = config_path
|
|
@@ -71,7 +78,7 @@ class ServerService:
|
|
|
71
78
|
self._load_device_config()
|
|
72
79
|
|
|
73
80
|
def _load_device_config(self) -> None:
|
|
74
|
-
"""Load device configurations from server.yml"""
|
|
81
|
+
"""Load device configurations from server.yml."""
|
|
75
82
|
try:
|
|
76
83
|
if Path(self.config_path).exists():
|
|
77
84
|
config = ConsonModuleListConfig.from_yaml(self.config_path)
|
|
@@ -90,7 +97,7 @@ class ServerService:
|
|
|
90
97
|
self.device_services = {}
|
|
91
98
|
|
|
92
99
|
def _create_device_services(self) -> None:
|
|
93
|
-
"""Create device service instances based on device configuration"""
|
|
100
|
+
"""Create device service instances based on device configuration."""
|
|
94
101
|
self.device_services = {}
|
|
95
102
|
|
|
96
103
|
for module in self.devices:
|
|
@@ -143,7 +150,11 @@ class ServerService:
|
|
|
143
150
|
)
|
|
144
151
|
|
|
145
152
|
def start_server(self) -> None:
|
|
146
|
-
"""Start the TCP server on port 10001
|
|
153
|
+
"""Start the TCP server on port 10001.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ServerError: If server is already running or fails to start.
|
|
157
|
+
"""
|
|
147
158
|
if self.is_running:
|
|
148
159
|
raise ServerError("Server is already running")
|
|
149
160
|
|
|
@@ -170,7 +181,7 @@ class ServerService:
|
|
|
170
181
|
raise ServerError(f"Failed to start server: {e}")
|
|
171
182
|
|
|
172
183
|
def stop_server(self) -> None:
|
|
173
|
-
"""Stop the TCP server"""
|
|
184
|
+
"""Stop the TCP server."""
|
|
174
185
|
if not self.is_running:
|
|
175
186
|
return
|
|
176
187
|
|
|
@@ -184,7 +195,7 @@ class ServerService:
|
|
|
184
195
|
self.logger.error(f"Error closing server socket: {e}")
|
|
185
196
|
|
|
186
197
|
def _accept_connections(self) -> None:
|
|
187
|
-
"""Accept and handle client connections"""
|
|
198
|
+
"""Accept and handle client connections."""
|
|
188
199
|
while self.is_running:
|
|
189
200
|
try:
|
|
190
201
|
# Accept connection
|
|
@@ -208,7 +219,7 @@ class ServerService:
|
|
|
208
219
|
def _handle_client(
|
|
209
220
|
self, client_socket: socket.socket, client_address: tuple[str, int]
|
|
210
221
|
) -> None:
|
|
211
|
-
"""Handle individual client connection"""
|
|
222
|
+
"""Handle individual client connection."""
|
|
212
223
|
try:
|
|
213
224
|
# Set timeout for idle connections (30 seconds as per spec)
|
|
214
225
|
client_socket.settimeout(300.0)
|
|
@@ -242,7 +253,7 @@ class ServerService:
|
|
|
242
253
|
self.logger.error(f"Error closing client socket: {e}")
|
|
243
254
|
|
|
244
255
|
def _process_request(self, message: str) -> List[str]:
|
|
245
|
-
"""Process incoming request and generate responses"""
|
|
256
|
+
"""Process incoming request and generate responses."""
|
|
246
257
|
responses: list[str] = []
|
|
247
258
|
|
|
248
259
|
try:
|
|
@@ -290,7 +301,11 @@ class ServerService:
|
|
|
290
301
|
return responses
|
|
291
302
|
|
|
292
303
|
def get_server_status(self) -> dict:
|
|
293
|
-
"""Get current server status
|
|
304
|
+
"""Get current server status.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dictionary containing server status information.
|
|
308
|
+
"""
|
|
294
309
|
return {
|
|
295
310
|
"running": self.is_running,
|
|
296
311
|
"port": self.port,
|
|
@@ -299,7 +314,7 @@ class ServerService:
|
|
|
299
314
|
}
|
|
300
315
|
|
|
301
316
|
def reload_config(self) -> None:
|
|
302
|
-
"""Reload device configuration from file"""
|
|
317
|
+
"""Reload device configuration from file."""
|
|
303
318
|
self._load_device_config()
|
|
304
319
|
self.logger.info(
|
|
305
320
|
f"Configuration reloaded: {len(self.devices)} devices, {len(self.device_services)} services"
|
|
@@ -11,7 +11,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class XP130ServerError(Exception):
|
|
14
|
-
"""Raised when XP130 server operations fail"""
|
|
14
|
+
"""Raised when XP130 server operations fail."""
|
|
15
15
|
|
|
16
16
|
pass
|
|
17
17
|
|
|
@@ -25,7 +25,11 @@ class XP130ServerService(BaseServerService):
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
def __init__(self, serial_number: str):
|
|
28
|
-
"""Initialize XP130 server service
|
|
28
|
+
"""Initialize XP130 server service.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
serial_number: The device serial number.
|
|
32
|
+
"""
|
|
29
33
|
super().__init__(serial_number)
|
|
30
34
|
self.device_type = "XP130"
|
|
31
35
|
self.module_type_code = 13 # XP130 module type from registry
|
|
@@ -37,7 +41,11 @@ class XP130ServerService(BaseServerService):
|
|
|
37
41
|
self.gateway = "192.168.1.1"
|
|
38
42
|
|
|
39
43
|
def get_device_info(self) -> Dict:
|
|
40
|
-
"""Get XP130 device information
|
|
44
|
+
"""Get XP130 device information.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Dictionary containing device information.
|
|
48
|
+
"""
|
|
41
49
|
return {
|
|
42
50
|
"serial_number": self.serial_number,
|
|
43
51
|
"device_type": self.device_type,
|
|
@@ -10,7 +10,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class XP20ServerError(Exception):
|
|
13
|
-
"""Raised when XP20 server operations fail"""
|
|
13
|
+
"""Raised when XP20 server operations fail."""
|
|
14
14
|
|
|
15
15
|
pass
|
|
16
16
|
|
|
@@ -24,14 +24,22 @@ class XP20ServerService(BaseServerService):
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
def __init__(self, serial_number: str):
|
|
27
|
-
"""Initialize XP20 server service
|
|
27
|
+
"""Initialize XP20 server service.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
serial_number: The device serial number.
|
|
31
|
+
"""
|
|
28
32
|
super().__init__(serial_number)
|
|
29
33
|
self.device_type = "XP20"
|
|
30
34
|
self.module_type_code = 33 # XP20 module type from registry
|
|
31
35
|
self.firmware_version = "XP20_V0.01.05"
|
|
32
36
|
|
|
33
37
|
def get_device_info(self) -> Dict:
|
|
34
|
-
"""Get XP20 device information
|
|
38
|
+
"""Get XP20 device information.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary containing device information.
|
|
42
|
+
"""
|
|
35
43
|
return {
|
|
36
44
|
"serial_number": self.serial_number,
|
|
37
45
|
"device_type": self.device_type,
|
|
@@ -10,7 +10,7 @@ from xp.services.server.base_server_service import BaseServerService
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class XP230ServerError(Exception):
|
|
13
|
-
"""Raised when XP230 server operations fail"""
|
|
13
|
+
"""Raised when XP230 server operations fail."""
|
|
14
14
|
|
|
15
15
|
pass
|
|
16
16
|
|
|
@@ -24,14 +24,22 @@ class XP230ServerService(BaseServerService):
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
def __init__(self, serial_number: str):
|
|
27
|
-
"""Initialize XP230 server service
|
|
27
|
+
"""Initialize XP230 server service.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
serial_number: The device serial number.
|
|
31
|
+
"""
|
|
28
32
|
super().__init__(serial_number)
|
|
29
33
|
self.device_type = "XP230"
|
|
30
34
|
self.module_type_code = 34 # XP230 module type from registry
|
|
31
35
|
self.firmware_version = "XP230_V1.00.04"
|
|
32
36
|
|
|
33
37
|
def get_device_info(self) -> Dict:
|
|
34
|
-
"""Get XP230 device information
|
|
38
|
+
"""Get XP230 device information.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Dictionary containing device information.
|
|
42
|
+
"""
|
|
35
43
|
return {
|
|
36
44
|
"serial_number": self.serial_number,
|
|
37
45
|
"device_type": self.device_type,
|