conson-xp 1.40.0__py3-none-any.whl → 1.41.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.40.0
3
+ Version: 1.41.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -376,6 +376,7 @@ xp conbus msactiontable
376
376
  xp conbus msactiontable download
377
377
  xp conbus msactiontable list
378
378
  xp conbus msactiontable show
379
+ xp conbus msactiontable upload
379
380
 
380
381
 
381
382
  xp conbus output
@@ -1,8 +1,8 @@
1
- conson_xp-1.40.0.dist-info/METADATA,sha256=FxnffMc3lu49w-4RKgg8x5f8wH3rl7rdjqCvr-BqhLI,11330
2
- conson_xp-1.40.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-1.40.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.40.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=J-7i4_8TyVn5z8NeO8OcuNsvrQlrsttTuKcPk6MvDMo,181
1
+ conson_xp-1.41.0.dist-info/METADATA,sha256=pML1FepvoeR1ktngrS69ieWa5rMO0j43m8BC22G_UW0,11361
2
+ conson_xp-1.41.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-1.41.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.41.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=fIc-tmd56J8tWGwB8NrHfsUhz6rBuPHOZmnmWfimedo,181
6
6
  xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
7
7
  xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
8
8
  xp/cli/commands/__init__.py,sha256=G7A1KFRSV0CEeDTqr_khu-K9_sc01CTI2KSfkFcaBRM,4949
@@ -20,7 +20,7 @@ xp/cli/commands/conbus/conbus_export_commands.py,sha256=KSeXZbD6tO0_BMgqmg20iVaE
20
20
  xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=AQhVneN5_rH6wd7D4KW80XIMh9MGjJv85gN57S206j4,7036
21
21
  xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=hVr1g6nDTa4MezW8joHPjPuZcMp2ttd9PfZaT9sQED4,3528
22
22
  xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=FjFWnLU_aUoAXQ2tKKLC-ichNiIb90_9OWpSdIUyHvc,3600
23
- xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=9JNZRemAgUUaNkKPzDUgIY5jnv-hCK8-bM5XjLYUIjc,7808
23
+ xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=2M4abf8i51HwLF3wmUAgoCN-veXKMA8Fd0nWThyNPdg,9706
24
24
  xp/cli/commands/conbus/conbus_output_commands.py,sha256=rJx8pfsl_ZeCNXhEelsY7mfYnaj_DHdz4TC-e8d5QGs,5286
25
25
  xp/cli/commands/conbus/conbus_raw_commands.py,sha256=892-S6wxp5xNPz6K86Le8KtQXNO4a0PQv20Hzx3vhiA,1996
26
26
  xp/cli/commands/conbus/conbus_receive_commands.py,sha256=_PsC-3xidmJBuOWUS60iDzhSHYYn5ZFmORXap-ljVGM,1902
@@ -59,7 +59,7 @@ xp/models/__init__.py,sha256=lROqr559DGd8WpJJUtfPT95VERCwMZHpBDEc96QSxQ0,1312
59
59
  xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
60
60
  xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
61
61
  xp/models/actiontable/msactiontable_xp20.py,sha256=zc9akPpuaW-pBk1vD9xn0JDWm_c2fiJYDuuk-DbvbGQ,5006
62
- xp/models/actiontable/msactiontable_xp24.py,sha256=ePuw5sAwmnUWZoti_uadvG1E-d7XGmucPm3WW2dQP0c,9415
62
+ xp/models/actiontable/msactiontable_xp24.py,sha256=tpgYvlQwxhjo70Ucsg_rB9ox7-jlG2b-GBj-UXwP2Ic,9377
63
63
  xp/models/actiontable/msactiontable_xp33.py,sha256=p_0HrvUmnqEEUlle7n0vpspGXFPrO5pXZeVF7n9K19g,11781
64
64
  xp/models/conbus/__init__.py,sha256=VIusMWQdBtlwDgj7oSj06wQkklihTp4oWFShvP_JUgA,35
65
65
  xp/models/conbus/conbus.py,sha256=mZQzKPfrdttT-qUnYUSyrEYyc_eHs8z301E5ejeiyvk,2689
@@ -142,6 +142,7 @@ xp/services/conbus/msactiontable/__init__.py,sha256=rDYzumPSfcTjDADHxjE7bXQoeWtZ
142
142
  xp/services/conbus/msactiontable/msactiontable_download_service.py,sha256=YAQeUAO04VkRTEvWwXBD_b6tdVjDYk55K4pZd7lxfE8,10049
143
143
  xp/services/conbus/msactiontable/msactiontable_list_service.py,sha256=bTqUI2xs3Ie0MeZ_PYm-Bgx9A-Eewlpc8Tv6jhi1_kA,3127
144
144
  xp/services/conbus/msactiontable/msactiontable_show_service.py,sha256=pyoB5xN1bDh0E8kity4k0UnYpc7-YWhs8oIMvAeC9Xk,3023
145
+ xp/services/conbus/msactiontable/msactiontable_upload_service.py,sha256=D_sWGFlABVCBxzqt-6y8eB--ZpywS5MjC6ER-JESq74,12336
145
146
  xp/services/conbus/write_config_service.py,sha256=PQsN7rtTKHpwtAG8moLksUfRVqqE_0sxdE37meR1ZQ0,8935
146
147
  xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
147
148
  xp/services/homekit/homekit_cache_service.py,sha256=NdijyH5_iyhsTHBb-OyT8Y2xnNDj8F5MP8neoVQ26hY,11010
@@ -200,10 +201,10 @@ xp/term/widgets/protocol_log.py,sha256=CJUpckWj7GC1kVqixDadteyGnI4aHyzd4kkH-pSbz
200
201
  xp/term/widgets/status_footer.py,sha256=bxrcqKzJ9V0aPSn_WwraVpJz7NxBUh3yIjA3fwv5nVA,3256
201
202
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
202
203
  xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
203
- xp/utils/dependencies.py,sha256=Z1vRSWr_dhmyNhD4dstJg-ZOlKVPPq_viGGNJ32GRNs,24584
204
+ xp/utils/dependencies.py,sha256=zrvWx28N0f28JwRDRyqaf5Q9eV_yLwh9xDw9mYBUXEQ,25379
204
205
  xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
205
206
  xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
206
207
  xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
207
208
  xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
208
209
  xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
209
- conson_xp-1.40.0.dist-info/RECORD,,
210
+ conson_xp-1.41.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  conson-xp package.
4
4
  """
5
5
 
6
- __version__ = "1.40.0"
6
+ __version__ = "1.41.0"
7
7
  __manufacturer__ = "salchichon"
8
8
  __model__ = "xp.cli"
9
9
  __serial__ = "2025.09.23.000"
@@ -25,6 +25,9 @@ from xp.services.conbus.msactiontable.msactiontable_list_service import (
25
25
  from xp.services.conbus.msactiontable.msactiontable_show_service import (
26
26
  MsActionTableShowService,
27
27
  )
28
+ from xp.services.conbus.msactiontable.msactiontable_upload_service import (
29
+ MsActionTableUploadService,
30
+ )
28
31
 
29
32
 
30
33
  @conbus_msactiontable.command("download", short_help="Download MSActionTable")
@@ -206,6 +209,65 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
206
209
  )
207
210
 
208
211
 
212
+ @conbus_msactiontable.command("upload", short_help="Upload MSActionTable")
213
+ @click.argument("serial_number", type=SERIAL)
214
+ @click.argument("xpmoduletype", type=XP_MODULE_TYPE)
215
+ @click.pass_context
216
+ @connection_command()
217
+ def conbus_upload_msactiontable(
218
+ ctx: Context, serial_number: str, xpmoduletype: str
219
+ ) -> None:
220
+ """Upload MS action table from conson.yml to XP module.
221
+
222
+ Args:
223
+ ctx: Click context object.
224
+ serial_number: 10-digit module serial number.
225
+ xpmoduletype: XP module type.
226
+ """
227
+ service: MsActionTableUploadService = (
228
+ ctx.obj.get("container").get_container().resolve(MsActionTableUploadService)
229
+ )
230
+
231
+ def on_progress(progress: str) -> None:
232
+ """Handle progress updates during MS action table upload.
233
+
234
+ Args:
235
+ progress: Progress message string.
236
+ """
237
+ click.echo(progress, nl=False)
238
+
239
+ def on_finish(success: bool) -> None:
240
+ """Handle successful completion of MS action table upload.
241
+
242
+ Args:
243
+ success: Whether upload was successful.
244
+ """
245
+ service.stop_reactor()
246
+ if success:
247
+ click.echo("\nMsactiontable uploaded successfully")
248
+
249
+ def on_error(error: str) -> None:
250
+ """Handle errors during MS action table upload.
251
+
252
+ Args:
253
+ error: Error message string.
254
+ """
255
+ service.stop_reactor()
256
+ click.echo(f"\nError: {error}")
257
+
258
+ click.echo(f"Uploading msactiontable to {serial_number}...")
259
+
260
+ with service:
261
+ service.on_progress.connect(on_progress)
262
+ service.on_error.connect(on_error)
263
+ service.on_finish.connect(on_finish)
264
+ service.start(
265
+ serial_number=serial_number,
266
+ xpmoduletype=xpmoduletype,
267
+ )
268
+ service.start_reactor()
269
+
270
+
209
271
  def _format_yaml(data: dict, indent: int = 0) -> str:
210
272
  """Format a dictionary as YAML-like output.
211
273
 
@@ -178,7 +178,7 @@ class Xp24MsActionTable(BaseModel):
178
178
  param_value = action.param.value
179
179
  action_parts.append(f"{short_code}:{param_value}")
180
180
 
181
- result = f"XP24 {' '.join(action_parts)}"
181
+ result = " ".join(action_parts)
182
182
 
183
183
  # Add settings
184
184
  settings = (
@@ -212,14 +212,14 @@ class Xp24MsActionTable(BaseModel):
212
212
 
213
213
  # Parse action part
214
214
  tokens = action_part.split()
215
- if len(tokens) != 5 or tokens[0] != "XP24":
215
+ if len(tokens) != 4:
216
216
  raise ValueError(
217
- f"Invalid short format: expected 'XP24 <a1> <a2> <a3> <a4>', got '{action_part}'"
217
+ f"Invalid short format: expected '<a1> <a2> <a3> <a4>', got '{action_part}'"
218
218
  )
219
219
 
220
220
  # Parse input actions
221
221
  input_actions = []
222
- for i, token in enumerate(tokens[1:5], 1):
222
+ for i, token in enumerate(tokens[0:4], 1):
223
223
  if ":" not in token:
224
224
  raise ValueError(f"Invalid action format at position {i}: '{token}'")
225
225
 
@@ -0,0 +1,324 @@
1
+ """Service for uploading MS action tables via Conbus protocol."""
2
+
3
+ import logging
4
+ from typing import Any, Optional, Union
5
+
6
+ from psygnal import Signal
7
+
8
+ from xp.models.config.conson_module_config import ConsonModuleListConfig
9
+ from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
10
+ from xp.models.telegram.system_function import SystemFunction
11
+ from xp.models.telegram.telegram_type import TelegramType
12
+ from xp.services.actiontable.msactiontable_xp20_serializer import (
13
+ Xp20MsActionTableSerializer,
14
+ )
15
+ from xp.services.actiontable.msactiontable_xp24_serializer import (
16
+ Xp24MsActionTableSerializer,
17
+ )
18
+ from xp.services.actiontable.msactiontable_xp33_serializer import (
19
+ Xp33MsActionTableSerializer,
20
+ )
21
+ from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
22
+ from xp.services.telegram.telegram_service import TelegramService
23
+
24
+
25
+ class MsActionTableUploadError(Exception):
26
+ """Raised when MS action table upload operations fail."""
27
+
28
+ pass
29
+
30
+
31
+ class MsActionTableUploadService:
32
+ """TCP client service for uploading MS action tables to Conbus modules.
33
+
34
+ Manages TCP socket connections, handles telegram generation and transmission,
35
+ and processes server responses for MS action table uploads.
36
+
37
+ Attributes:
38
+ on_progress: Signal emitted with telegram frame when progress is made.
39
+ on_error: Signal emitted with error message string when an error occurs.
40
+ on_finish: Signal emitted with bool (True on success) when upload completes.
41
+ """
42
+
43
+ on_progress: Signal = Signal(str)
44
+ on_error: Signal = Signal(str)
45
+ on_finish: Signal = Signal(bool) # True on success
46
+
47
+ def __init__(
48
+ self,
49
+ conbus_protocol: ConbusEventProtocol,
50
+ xp20ms_serializer: Xp20MsActionTableSerializer,
51
+ xp24ms_serializer: Xp24MsActionTableSerializer,
52
+ xp33ms_serializer: Xp33MsActionTableSerializer,
53
+ telegram_service: TelegramService,
54
+ conson_config: ConsonModuleListConfig,
55
+ ) -> None:
56
+ """Initialize the MS action table upload service.
57
+
58
+ Args:
59
+ conbus_protocol: ConbusEventProtocol for communication.
60
+ xp20ms_serializer: XP20 MS action table serializer.
61
+ xp24ms_serializer: XP24 MS action table serializer.
62
+ xp33ms_serializer: XP33 MS action table serializer.
63
+ telegram_service: Telegram service for parsing.
64
+ conson_config: Conson module list configuration.
65
+ """
66
+ self.conbus_protocol = conbus_protocol
67
+ self.xp20ms_serializer = xp20ms_serializer
68
+ self.xp24ms_serializer = xp24ms_serializer
69
+ self.xp33ms_serializer = xp33ms_serializer
70
+ self.serializer: Union[
71
+ Xp20MsActionTableSerializer,
72
+ Xp24MsActionTableSerializer,
73
+ Xp33MsActionTableSerializer,
74
+ ] = xp20ms_serializer
75
+ self.telegram_service = telegram_service
76
+ self.conson_config = conson_config
77
+ self.serial_number: str = ""
78
+ self.xpmoduletype: str = ""
79
+
80
+ # Upload state
81
+ self.upload_data: str = ""
82
+ self.upload_initiated: bool = False
83
+
84
+ # Set up logging
85
+ self.logger = logging.getLogger(__name__)
86
+
87
+ # Connect protocol signals
88
+ self.conbus_protocol.on_connection_made.connect(self.connection_made)
89
+ self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
90
+ self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
91
+ self.conbus_protocol.on_timeout.connect(self.timeout)
92
+ self.conbus_protocol.on_failed.connect(self.failed)
93
+
94
+ def connection_made(self) -> None:
95
+ """Handle connection established event."""
96
+ self.logger.debug(
97
+ "Connection established, sending upload msactiontable telegram"
98
+ )
99
+ self.conbus_protocol.send_telegram(
100
+ telegram_type=TelegramType.SYSTEM,
101
+ serial_number=self.serial_number,
102
+ system_function=SystemFunction.UPLOAD_MSACTIONTABLE,
103
+ data_value="00",
104
+ )
105
+
106
+ def telegram_sent(self, telegram_sent: str) -> None:
107
+ """Handle telegram sent event.
108
+
109
+ Args:
110
+ telegram_sent: The telegram that was sent.
111
+ """
112
+ self.logger.debug(f"Telegram sent: {telegram_sent}")
113
+
114
+ def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
115
+ """Handle telegram received event.
116
+
117
+ Args:
118
+ telegram_received: The telegram received event.
119
+ """
120
+ self.logger.debug(f"Telegram received: {telegram_received}")
121
+ if (
122
+ not telegram_received.checksum_valid
123
+ or telegram_received.telegram_type != TelegramType.REPLY.value
124
+ or telegram_received.serial_number != self.serial_number
125
+ ):
126
+ self.logger.debug("Not a reply response")
127
+ return
128
+
129
+ reply_telegram = self.telegram_service.parse_reply_telegram(
130
+ telegram_received.frame
131
+ )
132
+
133
+ self._handle_upload_response(reply_telegram)
134
+
135
+ def _handle_upload_response(self, reply_telegram: Any) -> None:
136
+ """Handle telegram responses during upload.
137
+
138
+ Args:
139
+ reply_telegram: Parsed reply telegram.
140
+ """
141
+ if reply_telegram.system_function == SystemFunction.ACK:
142
+ self.logger.debug("Received ACK for upload")
143
+
144
+ if not self.upload_initiated:
145
+ # First ACK - send data chunk
146
+ self.logger.debug("Sending msactiontable data")
147
+ self.conbus_protocol.send_telegram(
148
+ telegram_type=TelegramType.SYSTEM,
149
+ serial_number=self.serial_number,
150
+ system_function=SystemFunction.MSACTIONTABLE,
151
+ data_value=self.upload_data,
152
+ )
153
+ self.upload_initiated = True
154
+ self.on_progress.emit(".")
155
+ else:
156
+ # Second ACK - send EOF
157
+ self.logger.debug("Data sent, sending EOF")
158
+ self.conbus_protocol.send_telegram(
159
+ telegram_type=TelegramType.SYSTEM,
160
+ serial_number=self.serial_number,
161
+ system_function=SystemFunction.EOF,
162
+ data_value="00",
163
+ )
164
+ self.on_finish.emit(True)
165
+ elif reply_telegram.system_function == SystemFunction.NAK:
166
+ self.logger.debug("Received NAK during upload")
167
+ self.failed("Upload failed: NAK received")
168
+ else:
169
+ self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
170
+
171
+ def timeout(self) -> None:
172
+ """Handle timeout event."""
173
+ self.logger.debug("Upload timeout")
174
+ self.failed("Upload timeout")
175
+
176
+ def failed(self, message: str) -> None:
177
+ """Handle failed connection event.
178
+
179
+ Args:
180
+ message: Failure message.
181
+ """
182
+ self.logger.debug(f"Failed: {message}")
183
+ self.on_error.emit(message)
184
+
185
+ def start(
186
+ self,
187
+ serial_number: str,
188
+ xpmoduletype: str,
189
+ timeout_seconds: Optional[float] = None,
190
+ ) -> None:
191
+ """Upload MS action table to module.
192
+
193
+ Uploads the MS action table configuration to the specified module.
194
+
195
+ Args:
196
+ serial_number: Module serial number.
197
+ xpmoduletype: XP module type (xp20, xp24, xp33).
198
+ timeout_seconds: Optional timeout in seconds.
199
+
200
+ Raises:
201
+ MsActionTableUploadError: If configuration or validation errors occur.
202
+ """
203
+ self.logger.info("Starting msactiontable upload")
204
+ self.serial_number = serial_number
205
+ self.xpmoduletype = xpmoduletype
206
+
207
+ # Select serializer based on module type
208
+ if xpmoduletype == "xp20":
209
+ self.serializer = self.xp20ms_serializer
210
+ config_field = "xp20_msaction_table"
211
+ elif xpmoduletype == "xp24":
212
+ self.serializer = self.xp24ms_serializer
213
+ config_field = "xp24_msaction_table"
214
+ elif xpmoduletype == "xp33":
215
+ self.serializer = self.xp33ms_serializer
216
+ config_field = "xp33_msaction_table"
217
+ else:
218
+ raise MsActionTableUploadError(f"Unsupported module type: {xpmoduletype}")
219
+
220
+ if timeout_seconds:
221
+ self.conbus_protocol.timeout_seconds = timeout_seconds
222
+
223
+ # Find module
224
+ module = self.conson_config.find_module(serial_number)
225
+ if not module:
226
+ self.failed(f"Module {serial_number} not found in conson.yml")
227
+ return
228
+
229
+ # Validate module type matches
230
+ if module.module_type.lower() != xpmoduletype.lower():
231
+ self.failed(
232
+ f"Module type mismatch: module has type {module.module_type}, "
233
+ f"but {xpmoduletype} was specified"
234
+ )
235
+ return
236
+
237
+ # Get msactiontable config for the module type
238
+ msactiontable_config = getattr(module, config_field, None)
239
+
240
+ if not msactiontable_config:
241
+ self.failed(
242
+ f"Module {serial_number} does not have {config_field} configured in conson.yml"
243
+ )
244
+ return
245
+
246
+ if not isinstance(msactiontable_config, list) or len(msactiontable_config) == 0:
247
+ self.failed(
248
+ f"Module {serial_number} has empty {config_field} list in conson.yml"
249
+ )
250
+ return
251
+
252
+ # Parse MS action table from short format (first element)
253
+ try:
254
+ short_format = msactiontable_config
255
+ msactiontable: Union[
256
+ "Xp20MsActionTable", "Xp24MsActionTable", "Xp33MsActionTable"
257
+ ]
258
+ if xpmoduletype == "xp20":
259
+ from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
260
+
261
+ msactiontable = Xp20MsActionTable.from_short_format(short_format)
262
+ elif xpmoduletype == "xp24":
263
+ from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
264
+
265
+ msactiontable = Xp24MsActionTable.from_short_format(short_format)
266
+ elif xpmoduletype == "xp33":
267
+ from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
268
+
269
+ msactiontable = Xp33MsActionTable.from_short_format(short_format)
270
+ except (ValueError, AttributeError) as e:
271
+ self.logger.error(f"Invalid msactiontable format: {e}")
272
+ self.failed(f"Invalid msactiontable format: {e}")
273
+ return
274
+
275
+ # Serialize to telegram data (68 characters: AAAA + 64 data chars)
276
+ self.upload_data = self.serializer.to_data(msactiontable) # type: ignore[arg-type]
277
+
278
+ self.logger.debug(
279
+ f"Upload data encoded: {len(self.upload_data)} chars (single chunk)"
280
+ )
281
+
282
+ def set_timeout(self, timeout_seconds: float) -> None:
283
+ """Set operation timeout.
284
+
285
+ Args:
286
+ timeout_seconds: Timeout in seconds.
287
+ """
288
+ self.conbus_protocol.timeout_seconds = timeout_seconds
289
+
290
+ def start_reactor(self) -> None:
291
+ """Start the reactor."""
292
+ self.conbus_protocol.start_reactor()
293
+
294
+ def stop_reactor(self) -> None:
295
+ """Stop the reactor."""
296
+ self.conbus_protocol.stop_reactor()
297
+
298
+ def __enter__(self) -> "MsActionTableUploadService":
299
+ """Enter context manager - reset state for singleton reuse.
300
+
301
+ Returns:
302
+ Self for context manager protocol.
303
+ """
304
+ # Reset state
305
+ self.upload_data = ""
306
+ self.upload_initiated = False
307
+ self.serial_number = ""
308
+ self.xpmoduletype = ""
309
+ return self
310
+
311
+ def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
312
+ """Exit context manager - cleanup signals and reactor."""
313
+ # Disconnect protocol signals
314
+ self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
315
+ self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
316
+ self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
317
+ self.conbus_protocol.on_timeout.disconnect(self.timeout)
318
+ self.conbus_protocol.on_failed.disconnect(self.failed)
319
+ # Disconnect service signals
320
+ self.on_progress.disconnect()
321
+ self.on_error.disconnect()
322
+ self.on_finish.disconnect()
323
+ # Stop reactor
324
+ self.stop_reactor()
xp/utils/dependencies.py CHANGED
@@ -60,6 +60,9 @@ from xp.services.conbus.msactiontable.msactiontable_list_service import (
60
60
  from xp.services.conbus.msactiontable.msactiontable_show_service import (
61
61
  MsActionTableShowService,
62
62
  )
63
+ from xp.services.conbus.msactiontable.msactiontable_upload_service import (
64
+ MsActionTableUploadService,
65
+ )
63
66
  from xp.services.conbus.write_config_service import WriteConfigService
64
67
  from xp.services.homekit.homekit_cache_service import HomeKitCacheService
65
68
  from xp.services.homekit.homekit_conbus_service import HomeKitConbusService
@@ -381,6 +384,19 @@ class ServiceContainer:
381
384
  scope=punq.Scope.singleton,
382
385
  )
383
386
 
387
+ self.container.register(
388
+ MsActionTableUploadService,
389
+ factory=lambda: MsActionTableUploadService(
390
+ conbus_protocol=self.container.resolve(ConbusEventProtocol),
391
+ xp20ms_serializer=self.container.resolve(Xp20MsActionTableSerializer),
392
+ xp24ms_serializer=self.container.resolve(Xp24MsActionTableSerializer),
393
+ xp33ms_serializer=self.container.resolve(Xp33MsActionTableSerializer),
394
+ telegram_service=self.container.resolve(TelegramService),
395
+ conson_config=self.container.resolve(ConsonModuleListConfig),
396
+ ),
397
+ scope=punq.Scope.singleton,
398
+ )
399
+
384
400
  self.container.register(
385
401
  ConbusCustomService,
386
402
  factory=lambda: ConbusCustomService(