conson-xp 1.34.0__py3-none-any.whl → 1.36.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.34.0.dist-info → conson_xp-1.36.0.dist-info}/METADATA +1 -1
- {conson_xp-1.34.0.dist-info → conson_xp-1.36.0.dist-info}/RECORD +31 -31
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_actiontable_commands.py +34 -35
- xp/cli/commands/conbus/conbus_autoreport_commands.py +11 -10
- xp/cli/commands/conbus/conbus_custom_commands.py +6 -4
- xp/cli/commands/conbus/conbus_datapoint_commands.py +8 -6
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +19 -16
- xp/cli/commands/conbus/conbus_linknumber_commands.py +7 -6
- xp/cli/commands/conbus/conbus_modulenumber_commands.py +7 -6
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +7 -9
- xp/cli/commands/conbus/conbus_output_commands.py +9 -7
- xp/cli/commands/conbus/conbus_raw_commands.py +7 -2
- xp/cli/commands/conbus/conbus_scan_commands.py +4 -2
- xp/services/conbus/actiontable/actiontable_download_service.py +79 -37
- xp/services/conbus/actiontable/actiontable_list_service.py +17 -17
- xp/services/conbus/actiontable/actiontable_upload_service.py +78 -36
- xp/services/conbus/actiontable/msactiontable_service.py +88 -48
- xp/services/conbus/conbus_custom_service.py +81 -26
- xp/services/conbus/conbus_datapoint_queryall_service.py +90 -43
- xp/services/conbus/conbus_datapoint_service.py +76 -28
- xp/services/conbus/conbus_output_service.py +82 -22
- xp/services/conbus/conbus_raw_service.py +78 -37
- xp/services/conbus/conbus_scan_service.py +86 -42
- xp/services/conbus/write_config_service.py +76 -26
- xp/services/term/state_monitor_service.py +35 -15
- xp/term/widgets/modules_list.py +4 -2
- xp/utils/dependencies.py +10 -20
- {conson_xp-1.34.0.dist-info → conson_xp-1.36.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.34.0.dist-info → conson_xp-1.36.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.34.0.dist-info → conson_xp-1.36.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,9 +5,7 @@ import json
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
7
|
from xp.cli.commands.conbus.conbus import conbus_output
|
|
8
|
-
from xp.cli.utils.decorators import
|
|
9
|
-
connection_command,
|
|
10
|
-
)
|
|
8
|
+
from xp.cli.utils.decorators import connection_command
|
|
11
9
|
from xp.cli.utils.serial_number_type import SERIAL
|
|
12
10
|
from xp.models import ConbusDatapointResponse
|
|
13
11
|
from xp.models.conbus.conbus_output import ConbusOutputResponse
|
|
@@ -45,14 +43,16 @@ def xp_output_on(ctx: click.Context, serial_number: str, output_number: int) ->
|
|
|
45
43
|
response: Output response object.
|
|
46
44
|
"""
|
|
47
45
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
46
|
+
service.stop_reactor()
|
|
48
47
|
|
|
49
48
|
with service:
|
|
49
|
+
service.on_finish.connect(on_finish)
|
|
50
50
|
service.send_action(
|
|
51
51
|
serial_number=serial_number,
|
|
52
52
|
output_number=output_number,
|
|
53
53
|
action_type=ActionType.ON_RELEASE,
|
|
54
|
-
finish_callback=on_finish,
|
|
55
54
|
)
|
|
55
|
+
service.start_reactor()
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
@conbus_output.command("off")
|
|
@@ -83,14 +83,16 @@ def xp_output_off(ctx: click.Context, serial_number: str, output_number: int) ->
|
|
|
83
83
|
response: Output response object.
|
|
84
84
|
"""
|
|
85
85
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
86
|
+
service.stop_reactor()
|
|
86
87
|
|
|
87
88
|
with service:
|
|
89
|
+
service.on_finish.connect(on_finish)
|
|
88
90
|
service.send_action(
|
|
89
91
|
serial_number=serial_number,
|
|
90
92
|
output_number=output_number,
|
|
91
93
|
action_type=ActionType.OFF_PRESS,
|
|
92
|
-
finish_callback=on_finish,
|
|
93
94
|
)
|
|
95
|
+
service.start_reactor()
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
@conbus_output.command("status")
|
|
@@ -121,10 +123,10 @@ def xp_output_status(ctx: click.Context, serial_number: str) -> None:
|
|
|
121
123
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
122
124
|
|
|
123
125
|
with service:
|
|
126
|
+
service.on_finish.connect(on_finish)
|
|
124
127
|
service.query_datapoint(
|
|
125
128
|
serial_number=serial_number,
|
|
126
129
|
datapoint_type=DataPointType.MODULE_OUTPUT_STATE,
|
|
127
|
-
finish_callback=on_finish,
|
|
128
130
|
)
|
|
129
131
|
|
|
130
132
|
|
|
@@ -156,8 +158,8 @@ def xp_module_state(ctx: click.Context, serial_number: str) -> None:
|
|
|
156
158
|
click.echo(json.dumps(response.to_dict(), indent=2))
|
|
157
159
|
|
|
158
160
|
with service:
|
|
161
|
+
service.on_finish.connect(on_finish)
|
|
159
162
|
service.query_datapoint(
|
|
160
163
|
serial_number=serial_number,
|
|
161
164
|
datapoint_type=DataPointType.MODULE_STATE,
|
|
162
|
-
finish_callback=on_finish,
|
|
163
165
|
)
|
|
@@ -52,11 +52,16 @@ def send_raw_telegrams(ctx: Context, raw_telegrams: str) -> None:
|
|
|
52
52
|
service_response: Raw response object.
|
|
53
53
|
"""
|
|
54
54
|
click.echo(json.dumps(service_response.to_dict(), indent=2))
|
|
55
|
+
service.stop_reactor()
|
|
55
56
|
|
|
56
57
|
with service:
|
|
58
|
+
# Connect service signals
|
|
59
|
+
service.on_progress.connect(on_progress)
|
|
60
|
+
service.on_finish.connect(on_finish)
|
|
61
|
+
# Setup
|
|
57
62
|
service.send_raw_telegram(
|
|
58
63
|
raw_input=raw_telegrams,
|
|
59
|
-
progress_callback=on_progress,
|
|
60
|
-
finish_callback=on_finish,
|
|
61
64
|
timeout_seconds=5.0,
|
|
62
65
|
)
|
|
66
|
+
# Start (blocks until completion)
|
|
67
|
+
service.start_reactor()
|
|
@@ -48,11 +48,13 @@ def scan_module(ctx: Context, serial_number: str, function_code: str) -> None:
|
|
|
48
48
|
service_response: Scan response object.
|
|
49
49
|
"""
|
|
50
50
|
click.echo(json.dumps(service_response.to_dict(), indent=2))
|
|
51
|
+
service.stop_reactor()
|
|
51
52
|
|
|
52
53
|
with service:
|
|
54
|
+
service.on_progress.connect(on_progress)
|
|
55
|
+
service.on_finish.connect(on_finish)
|
|
53
56
|
service.scan_module(
|
|
54
57
|
serial_number=serial_number,
|
|
55
58
|
function_code=function_code,
|
|
56
|
-
progress_callback=on_progress,
|
|
57
|
-
finish_callback=on_finish,
|
|
58
59
|
)
|
|
60
|
+
service.start_reactor()
|
|
@@ -2,62 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from dataclasses import asdict
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from psygnal import Signal
|
|
8
8
|
|
|
9
|
-
from xp.models import ConbusClientConfig
|
|
10
|
-
from xp.models.actiontable.actiontable import ActionTable
|
|
11
9
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
12
10
|
from xp.models.telegram.system_function import SystemFunction
|
|
13
11
|
from xp.models.telegram.telegram_type import TelegramType
|
|
14
12
|
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
15
|
-
from xp.services.protocol import
|
|
13
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
16
14
|
from xp.services.telegram.telegram_service import TelegramService
|
|
17
15
|
|
|
18
16
|
|
|
19
|
-
class ActionTableService
|
|
17
|
+
class ActionTableService:
|
|
20
18
|
"""TCP client service for downloading action tables from Conbus modules.
|
|
21
19
|
|
|
22
20
|
Manages TCP socket connections, handles telegram generation and transmission,
|
|
23
21
|
and processes server responses for action table downloads.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
on_progress: Signal emitted with telegram frame when progress is made.
|
|
25
|
+
on_error: Signal emitted with error message string when an error occurs.
|
|
26
|
+
on_finish: Signal emitted with (ActionTable, Dict[str, Any], list[str]) when complete.
|
|
24
27
|
"""
|
|
25
28
|
|
|
29
|
+
on_progress: Signal = Signal(str)
|
|
30
|
+
on_error: Signal = Signal(str)
|
|
31
|
+
on_finish: Signal = Signal(object) # (ActionTable, Dict[str, Any], list[str])
|
|
32
|
+
|
|
26
33
|
def __init__(
|
|
27
34
|
self,
|
|
28
|
-
|
|
29
|
-
reactor: PosixReactorBase,
|
|
35
|
+
conbus_protocol: ConbusEventProtocol,
|
|
30
36
|
actiontable_serializer: ActionTableSerializer,
|
|
31
37
|
telegram_service: TelegramService,
|
|
32
38
|
) -> None:
|
|
33
39
|
"""Initialize the action table download service.
|
|
34
40
|
|
|
35
41
|
Args:
|
|
36
|
-
|
|
37
|
-
reactor: Twisted reactor instance.
|
|
42
|
+
conbus_protocol: ConbusEventProtocol instance.
|
|
38
43
|
actiontable_serializer: Action table serializer.
|
|
39
44
|
telegram_service: Telegram service for parsing.
|
|
40
45
|
"""
|
|
41
|
-
|
|
46
|
+
self.conbus_protocol = conbus_protocol
|
|
42
47
|
self.serializer = actiontable_serializer
|
|
43
48
|
self.telegram_service = telegram_service
|
|
44
49
|
self.serial_number: str = ""
|
|
45
|
-
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
46
|
-
self.error_callback: Optional[Callable[[str], None]] = None
|
|
47
|
-
self.finish_callback: Optional[
|
|
48
|
-
Callable[[ActionTable, Dict[str, Any], list[str]], None]
|
|
49
|
-
] = None
|
|
50
|
-
|
|
51
50
|
self.actiontable_data: list[str] = []
|
|
52
51
|
# Set up logging
|
|
53
52
|
self.logger = logging.getLogger(__name__)
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
# Connect protocol signals
|
|
55
|
+
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
56
|
+
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
57
|
+
self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
|
|
58
|
+
self.conbus_protocol.on_timeout.connect(self.timeout)
|
|
59
|
+
self.conbus_protocol.on_failed.connect(self.failed)
|
|
60
|
+
|
|
61
|
+
def connection_made(self) -> None:
|
|
56
62
|
"""Handle connection established event."""
|
|
57
63
|
self.logger.debug(
|
|
58
64
|
"Connection established, sending download actiontable telegram"
|
|
59
65
|
)
|
|
60
|
-
self.send_telegram(
|
|
66
|
+
self.conbus_protocol.send_telegram(
|
|
61
67
|
telegram_type=TelegramType.SYSTEM,
|
|
62
68
|
serial_number=self.serial_number,
|
|
63
69
|
system_function=SystemFunction.DOWNLOAD_ACTIONTABLE,
|
|
@@ -101,10 +107,9 @@ class ActionTableService(ConbusProtocol):
|
|
|
101
107
|
self.logger.debug("Saving actiontable response")
|
|
102
108
|
data_part = reply_telegram.data_value[2:]
|
|
103
109
|
self.actiontable_data.append(data_part)
|
|
104
|
-
|
|
105
|
-
self.progress_callback(".")
|
|
110
|
+
self.on_progress.emit(".")
|
|
106
111
|
|
|
107
|
-
self.send_telegram(
|
|
112
|
+
self.conbus_protocol.send_telegram(
|
|
108
113
|
telegram_type=TelegramType.SYSTEM,
|
|
109
114
|
serial_number=self.serial_number,
|
|
110
115
|
system_function=SystemFunction.ACK,
|
|
@@ -118,8 +123,12 @@ class ActionTableService(ConbusProtocol):
|
|
|
118
123
|
actiontable = self.serializer.from_encoded_string(all_data)
|
|
119
124
|
actiontable_dict = asdict(actiontable)
|
|
120
125
|
actiontable_short = self.serializer.format_decoded_output(actiontable)
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
self.on_finish.emit((actiontable, actiontable_dict, actiontable_short))
|
|
127
|
+
|
|
128
|
+
def timeout(self) -> None:
|
|
129
|
+
"""Handle timeout event."""
|
|
130
|
+
self.logger.debug("Timeout occurred")
|
|
131
|
+
self.failed("Timeout")
|
|
123
132
|
|
|
124
133
|
def failed(self, message: str) -> None:
|
|
125
134
|
"""Handle failed connection event.
|
|
@@ -128,31 +137,64 @@ class ActionTableService(ConbusProtocol):
|
|
|
128
137
|
message: Failure message.
|
|
129
138
|
"""
|
|
130
139
|
self.logger.debug(f"Failed: {message}")
|
|
131
|
-
|
|
132
|
-
self.error_callback(message)
|
|
140
|
+
self.on_error.emit(message)
|
|
133
141
|
|
|
134
142
|
def start(
|
|
135
143
|
self,
|
|
136
144
|
serial_number: str,
|
|
137
|
-
progress_callback: Callable[[str], None],
|
|
138
|
-
error_callback: Callable[[str], None],
|
|
139
|
-
finish_callback: Callable[[ActionTable, Dict[str, Any], list[str]], None],
|
|
140
145
|
timeout_seconds: Optional[float] = None,
|
|
141
146
|
) -> None:
|
|
142
147
|
"""Run reactor in dedicated thread with its own event loop.
|
|
143
148
|
|
|
144
149
|
Args:
|
|
145
150
|
serial_number: Module serial number.
|
|
146
|
-
progress_callback: Callback for progress updates.
|
|
147
|
-
error_callback: Callback for errors.
|
|
148
|
-
finish_callback: Callback when download completes.
|
|
149
151
|
timeout_seconds: Optional timeout in seconds.
|
|
150
152
|
"""
|
|
151
153
|
self.logger.info("Starting actiontable")
|
|
152
154
|
self.serial_number = serial_number
|
|
153
155
|
if timeout_seconds:
|
|
154
|
-
self.timeout_seconds = timeout_seconds
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
157
|
+
# Caller invokes start_reactor()
|
|
158
|
+
|
|
159
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
160
|
+
"""Set operation timeout.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
timeout_seconds: Timeout in seconds.
|
|
164
|
+
"""
|
|
165
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
166
|
+
|
|
167
|
+
def start_reactor(self) -> None:
|
|
168
|
+
"""Start the reactor."""
|
|
169
|
+
self.conbus_protocol.start_reactor()
|
|
170
|
+
|
|
171
|
+
def stop_reactor(self) -> None:
|
|
172
|
+
"""Stop the reactor."""
|
|
173
|
+
self.conbus_protocol.stop_reactor()
|
|
174
|
+
|
|
175
|
+
def __enter__(self) -> "ActionTableService":
|
|
176
|
+
"""Enter context manager - reset state for singleton reuse.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Self for context manager protocol.
|
|
180
|
+
"""
|
|
181
|
+
# Reset state for singleton reuse
|
|
182
|
+
self.actiontable_data = []
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
def __exit__(
|
|
186
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Exit context manager and disconnect signals."""
|
|
189
|
+
# Disconnect protocol signals
|
|
190
|
+
self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
|
|
191
|
+
self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
|
|
192
|
+
self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
|
|
193
|
+
self.conbus_protocol.on_timeout.disconnect(self.timeout)
|
|
194
|
+
self.conbus_protocol.on_failed.disconnect(self.failed)
|
|
195
|
+
# Disconnect service signals
|
|
196
|
+
self.on_progress.disconnect()
|
|
197
|
+
self.on_error.disconnect()
|
|
198
|
+
self.on_finish.disconnect()
|
|
199
|
+
# Stop reactor
|
|
200
|
+
self.stop_reactor()
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from psygnal import Signal
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class ActionTableListService:
|
|
@@ -10,13 +12,18 @@ class ActionTableListService:
|
|
|
10
12
|
|
|
11
13
|
Reads conson.yml and returns a list of all modules that have action table
|
|
12
14
|
configurations defined.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
on_finish: Signal emitted with dict[str, Any] when listing completes.
|
|
18
|
+
on_error: Signal emitted with error message string when an error occurs.
|
|
13
19
|
"""
|
|
14
20
|
|
|
21
|
+
on_finish: Signal = Signal(object) # dict[str, Any]
|
|
22
|
+
on_error: Signal = Signal(str)
|
|
23
|
+
|
|
15
24
|
def __init__(self) -> None:
|
|
16
25
|
"""Initialize the action table list service."""
|
|
17
26
|
self.logger = logging.getLogger(__name__)
|
|
18
|
-
self.finish_callback: Optional[Callable[[dict[str, Any]], None]] = None
|
|
19
|
-
self.error_callback: Optional[Callable[[str], None]] = None
|
|
20
27
|
|
|
21
28
|
def __enter__(self) -> "ActionTableListService":
|
|
22
29
|
"""Context manager entry.
|
|
@@ -28,24 +35,19 @@ class ActionTableListService:
|
|
|
28
35
|
|
|
29
36
|
def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
|
|
30
37
|
"""Context manager exit."""
|
|
31
|
-
|
|
38
|
+
# Disconnect service signals
|
|
39
|
+
self.on_finish.disconnect()
|
|
40
|
+
self.on_error.disconnect()
|
|
32
41
|
|
|
33
42
|
def start(
|
|
34
43
|
self,
|
|
35
|
-
finish_callback: Callable[[dict[str, Any]], None],
|
|
36
|
-
error_callback: Callable[[str], None],
|
|
37
44
|
config_path: Optional[Path] = None,
|
|
38
45
|
) -> None:
|
|
39
46
|
"""List all modules with action table configurations.
|
|
40
47
|
|
|
41
48
|
Args:
|
|
42
|
-
finish_callback: Callback to invoke with the module list.
|
|
43
|
-
error_callback: Callback to invoke on error.
|
|
44
49
|
config_path: Optional path to conson.yml. Defaults to current directory.
|
|
45
50
|
"""
|
|
46
|
-
self.finish_callback = finish_callback
|
|
47
|
-
self.error_callback = error_callback
|
|
48
|
-
|
|
49
51
|
# Default to current directory if not specified
|
|
50
52
|
if config_path is None:
|
|
51
53
|
config_path = Path.cwd() / "conson.yml"
|
|
@@ -77,15 +79,13 @@ class ActionTableListService:
|
|
|
77
79
|
# Prepare result
|
|
78
80
|
result = {"modules": modules_with_actiontable}
|
|
79
81
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
self.finish_callback(result)
|
|
82
|
+
# Emit finish signal
|
|
83
|
+
self.on_finish.emit(result)
|
|
83
84
|
|
|
84
85
|
def _handle_error(self, message: str) -> None:
|
|
85
|
-
"""Handle error and
|
|
86
|
+
"""Handle error and emit error signal.
|
|
86
87
|
|
|
87
88
|
Args:
|
|
88
89
|
message: Error message.
|
|
89
90
|
"""
|
|
90
|
-
|
|
91
|
-
self.error_callback(message)
|
|
91
|
+
self.on_error.emit(message)
|
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
"""Service for uploading ActionTable via Conbus protocol."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Optional
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from psygnal import Signal
|
|
7
7
|
|
|
8
|
-
from xp.models import ConbusClientConfig
|
|
9
8
|
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
10
9
|
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
11
10
|
from xp.models.telegram.system_function import SystemFunction
|
|
12
11
|
from xp.models.telegram.telegram_type import TelegramType
|
|
13
12
|
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
14
|
-
from xp.services.protocol import
|
|
13
|
+
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
15
14
|
from xp.services.telegram.telegram_service import TelegramService
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
class ActionTableUploadService
|
|
17
|
+
class ActionTableUploadService:
|
|
19
18
|
"""TCP client service for uploading action tables to Conbus modules.
|
|
20
19
|
|
|
21
20
|
Manages TCP socket connections, handles telegram generation and transmission,
|
|
22
21
|
and processes server responses for action table uploads.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
on_progress: Signal emitted with telegram frame when progress is made.
|
|
25
|
+
on_error: Signal emitted with error message string when an error occurs.
|
|
26
|
+
on_finish: Signal emitted with bool (True on success) when upload completes.
|
|
23
27
|
"""
|
|
24
28
|
|
|
29
|
+
on_progress: Signal = Signal(str)
|
|
30
|
+
on_error: Signal = Signal(str)
|
|
31
|
+
on_finish: Signal = Signal(bool) # True on success
|
|
32
|
+
|
|
25
33
|
def __init__(
|
|
26
34
|
self,
|
|
27
|
-
|
|
28
|
-
reactor: PosixReactorBase,
|
|
35
|
+
conbus_protocol: ConbusEventProtocol,
|
|
29
36
|
actiontable_serializer: ActionTableSerializer,
|
|
30
37
|
telegram_service: TelegramService,
|
|
31
38
|
conson_config: ConsonModuleListConfig,
|
|
@@ -33,20 +40,16 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
33
40
|
"""Initialize the action table upload service.
|
|
34
41
|
|
|
35
42
|
Args:
|
|
36
|
-
|
|
37
|
-
reactor: Twisted reactor instance.
|
|
43
|
+
conbus_protocol: ConbusEventProtocol for communication.
|
|
38
44
|
actiontable_serializer: Action table serializer.
|
|
39
45
|
telegram_service: Telegram service for parsing.
|
|
40
46
|
conson_config: Conson module list configuration.
|
|
41
47
|
"""
|
|
42
|
-
|
|
48
|
+
self.conbus_protocol = conbus_protocol
|
|
43
49
|
self.serializer = actiontable_serializer
|
|
44
50
|
self.telegram_service = telegram_service
|
|
45
51
|
self.conson_config = conson_config
|
|
46
52
|
self.serial_number: str = ""
|
|
47
|
-
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
48
|
-
self.error_callback: Optional[Callable[[str], None]] = None
|
|
49
|
-
self.success_callback: Optional[Callable[[], None]] = None
|
|
50
53
|
|
|
51
54
|
# Upload state
|
|
52
55
|
self.upload_data_chunks: list[str] = []
|
|
@@ -55,10 +58,17 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
55
58
|
# Set up logging
|
|
56
59
|
self.logger = logging.getLogger(__name__)
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
# Connect protocol signals
|
|
62
|
+
self.conbus_protocol.on_connection_made.connect(self.connection_made)
|
|
63
|
+
self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
|
|
64
|
+
self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
|
|
65
|
+
self.conbus_protocol.on_timeout.connect(self.timeout)
|
|
66
|
+
self.conbus_protocol.on_failed.connect(self.failed)
|
|
67
|
+
|
|
68
|
+
def connection_made(self) -> None:
|
|
59
69
|
"""Handle connection established event."""
|
|
60
70
|
self.logger.debug("Connection established, sending upload actiontable telegram")
|
|
61
|
-
self.send_telegram(
|
|
71
|
+
self.conbus_protocol.send_telegram(
|
|
62
72
|
telegram_type=TelegramType.SYSTEM,
|
|
63
73
|
serial_number=self.serial_number,
|
|
64
74
|
system_function=SystemFunction.UPLOAD_ACTIONTABLE,
|
|
@@ -112,33 +122,35 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
112
122
|
# Second character: 'A' + chunk_index (sequential counter A-O for 15 chunks)
|
|
113
123
|
prefix_hex = f"AAA{ord('A') + self.current_chunk_index:c}"
|
|
114
124
|
|
|
115
|
-
self.send_telegram(
|
|
125
|
+
self.conbus_protocol.send_telegram(
|
|
116
126
|
telegram_type=TelegramType.SYSTEM,
|
|
117
127
|
serial_number=self.serial_number,
|
|
118
128
|
system_function=SystemFunction.ACTIONTABLE,
|
|
119
129
|
data_value=f"{prefix_hex}{chunk}",
|
|
120
130
|
)
|
|
121
131
|
self.current_chunk_index += 1
|
|
122
|
-
|
|
123
|
-
self.progress_callback(".")
|
|
132
|
+
self.on_progress.emit(".")
|
|
124
133
|
else:
|
|
125
134
|
# All chunks sent, send EOF
|
|
126
135
|
self.logger.debug("All chunks sent, sending EOF")
|
|
127
|
-
self.send_telegram(
|
|
136
|
+
self.conbus_protocol.send_telegram(
|
|
128
137
|
telegram_type=TelegramType.SYSTEM,
|
|
129
138
|
serial_number=self.serial_number,
|
|
130
139
|
system_function=SystemFunction.EOF,
|
|
131
140
|
data_value="00",
|
|
132
141
|
)
|
|
133
|
-
|
|
134
|
-
self.success_callback()
|
|
135
|
-
self._stop_reactor()
|
|
142
|
+
self.on_finish.emit(True)
|
|
136
143
|
elif reply_telegram.system_function == SystemFunction.NAK:
|
|
137
144
|
self.logger.debug("Received NAK during upload")
|
|
138
145
|
self.failed("Upload failed: NAK received")
|
|
139
146
|
else:
|
|
140
147
|
self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
|
|
141
148
|
|
|
149
|
+
def timeout(self) -> None:
|
|
150
|
+
"""Handle timeout event."""
|
|
151
|
+
self.logger.debug("Upload timeout")
|
|
152
|
+
self.failed("Upload timeout")
|
|
153
|
+
|
|
142
154
|
def failed(self, message: str) -> None:
|
|
143
155
|
"""Handle failed connection event.
|
|
144
156
|
|
|
@@ -146,16 +158,11 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
146
158
|
message: Failure message.
|
|
147
159
|
"""
|
|
148
160
|
self.logger.debug(f"Failed: {message}")
|
|
149
|
-
|
|
150
|
-
self.error_callback(message)
|
|
151
|
-
self._stop_reactor()
|
|
161
|
+
self.on_error.emit(message)
|
|
152
162
|
|
|
153
163
|
def start(
|
|
154
164
|
self,
|
|
155
165
|
serial_number: str,
|
|
156
|
-
progress_callback: Callable[[str], None],
|
|
157
|
-
error_callback: Callable[[str], None],
|
|
158
|
-
success_callback: Callable[[], None],
|
|
159
166
|
timeout_seconds: Optional[float] = None,
|
|
160
167
|
) -> None:
|
|
161
168
|
"""Upload action table to module.
|
|
@@ -164,18 +171,12 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
164
171
|
|
|
165
172
|
Args:
|
|
166
173
|
serial_number: Module serial number.
|
|
167
|
-
progress_callback: Callback for progress updates.
|
|
168
|
-
error_callback: Callback for errors.
|
|
169
|
-
success_callback: Callback when upload completes successfully.
|
|
170
174
|
timeout_seconds: Optional timeout in seconds.
|
|
171
175
|
"""
|
|
172
176
|
self.logger.info("Starting actiontable upload")
|
|
173
177
|
self.serial_number = serial_number
|
|
174
178
|
if timeout_seconds:
|
|
175
|
-
self.timeout_seconds = timeout_seconds
|
|
176
|
-
self.progress_callback = progress_callback
|
|
177
|
-
self.error_callback = error_callback
|
|
178
|
-
self.success_callback = success_callback
|
|
179
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
179
180
|
|
|
180
181
|
# Find module
|
|
181
182
|
module = self.conson_config.find_module(serial_number)
|
|
@@ -208,4 +209,45 @@ class ActionTableUploadService(ConbusProtocol):
|
|
|
208
209
|
f"{len(self.upload_data_chunks)} chunks"
|
|
209
210
|
)
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
213
|
+
"""Set operation timeout.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
timeout_seconds: Timeout in seconds.
|
|
217
|
+
"""
|
|
218
|
+
self.conbus_protocol.timeout_seconds = timeout_seconds
|
|
219
|
+
|
|
220
|
+
def start_reactor(self) -> None:
|
|
221
|
+
"""Start the reactor."""
|
|
222
|
+
self.conbus_protocol.start_reactor()
|
|
223
|
+
|
|
224
|
+
def stop_reactor(self) -> None:
|
|
225
|
+
"""Stop the reactor."""
|
|
226
|
+
self.conbus_protocol.stop_reactor()
|
|
227
|
+
|
|
228
|
+
def __enter__(self) -> "ActionTableUploadService":
|
|
229
|
+
"""Enter context manager - reset state for singleton reuse.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Self for context manager protocol.
|
|
233
|
+
"""
|
|
234
|
+
# Reset state
|
|
235
|
+
self.upload_data_chunks = []
|
|
236
|
+
self.current_chunk_index = 0
|
|
237
|
+
self.serial_number = ""
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
|
|
241
|
+
"""Exit context manager - cleanup signals and reactor."""
|
|
242
|
+
# Disconnect protocol signals
|
|
243
|
+
self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
|
|
244
|
+
self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
|
|
245
|
+
self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
|
|
246
|
+
self.conbus_protocol.on_timeout.disconnect(self.timeout)
|
|
247
|
+
self.conbus_protocol.on_failed.disconnect(self.failed)
|
|
248
|
+
# Disconnect service signals
|
|
249
|
+
self.on_progress.disconnect()
|
|
250
|
+
self.on_error.disconnect()
|
|
251
|
+
self.on_finish.disconnect()
|
|
252
|
+
# Stop reactor
|
|
253
|
+
self.stop_reactor()
|