conson-xp 1.49.0__py3-none-any.whl → 1.50.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.49.0
3
+ Version: 1.50.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -1,14 +1,14 @@
1
- conson_xp-1.49.0.dist-info/METADATA,sha256=TynMPgKlwxBap1KLaYahYSNDF4YOS_iO1kNhUFR78io,11403
2
- conson_xp-1.49.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-1.49.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.49.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=KJJ4ZcIuwzCmqcxnfXWmH4RNucpuxrNrvbjN0cEnmaw,182
1
+ conson_xp-1.50.0.dist-info/METADATA,sha256=9CLWQiRR8QJj0Ne3zXyV2XzXqkmv3uL7NfVaGkaogHk,11403
2
+ conson_xp-1.50.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-1.50.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.50.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=W-aER6IUlDyRVKHOLg5oh4E-KxDbSmRYOT9XO1035Ac,182
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
9
9
  xp/cli/commands/conbus/__init__.py,sha256=HYaX2__XxwD3Xaw4CzflvL8CwoUa4yR6wOyH8wwyofM,535
10
10
  xp/cli/commands/conbus/conbus.py,sha256=GpWq1QS3mKfQFG77nTED-_hsvcnXb3ZIrC6DhfyhzBE,3538
11
- xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=9kLP-ISIDWXVUtELCjTAs0JIT3DNGcYGBRnZEo-tp3g,7743
11
+ xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=EpyLG_6RxhIQY4BWxDXTyo8yzpBwbHfZDOgmRl--R5w,7378
12
12
  xp/cli/commands/conbus/conbus_autoreport_commands.py,sha256=dO3rxgII_xZVhbkxlMqTJCarrUveQCs_7-qf4li0UkQ,3962
13
13
  xp/cli/commands/conbus/conbus_blink_commands.py,sha256=1zLiXV8Nn4XSDI-z7F3f2RN2IO-yxeMkGQ1PCknAkWY,5398
14
14
  xp/cli/commands/conbus/conbus_config_commands.py,sha256=jeZi7J973DrDCyP59Qrvk6tonduj97KnV4fq9zbei2k,721
@@ -20,7 +20,7 @@ xp/cli/commands/conbus/conbus_export_commands.py,sha256=-fA4vzQF3XsDvRD4BjbE7KRq
20
20
  xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=WH4ZQHxSbquPlAqd0uRxq8VR-sO4_AnfseMqaoPoGoA,7092
21
21
  xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=3bkik8nHXY89XfzeUnKWmvKA70r4qJJ79j8FfLL4sL0,3556
22
22
  xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=fOIO0f85iDJBfGnRCEN8zK6j4i43uuF_9YKQc_nQ39A,3628
23
- xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=cTH6reU7oyqlOah-0-MWI2aWDlSJ-hnSf_0_aWk97uI,10419
23
+ xp/cli/commands/conbus/conbus_msactiontable_commands.py,sha256=oMHfrxDR9yj0Pvx92ZHnwYU3CZqwNNvfAfEoLuo_jhQ,11585
24
24
  xp/cli/commands/conbus/conbus_output_commands.py,sha256=XMEMb5tM0ul7lwhwoo4QRgWprXZ31qswXBno8KCeFMo,5342
25
25
  xp/cli/commands/conbus/conbus_raw_commands.py,sha256=o1dVZqiRw7J3_8Bge7DJucFyAqt7GNP-Azf-f3ir3SU,2019
26
26
  xp/cli/commands/conbus/conbus_receive_commands.py,sha256=LdxoOUdM5atkC8fFlp-GK7HQ7oxTnjrSFDx-lQ3nme0,1925
@@ -54,11 +54,10 @@ xp/cli/utils/formatters.py,sha256=SN_kUkL4M7csay8eftnxoIPS_VvQDlqZueARNElyZZs,98
54
54
  xp/cli/utils/module_type_choice.py,sha256=Hs0VFrssKWWGqfqBtSJPP7DDCzLyv-BfyeNIPC1WxyU,1672
55
55
  xp/cli/utils/serial_number_type.py,sha256=-6MlAAPbpqaGx1Ds3vPPT3PU9hYJG4_C2SmytXV3VyI,1480
56
56
  xp/cli/utils/system_function_choice.py,sha256=3YZrTtBHCw_f6KqqT41wJUiiPBVLAQufyPnrMkesdd0,1637
57
- xp/cli/utils/xp_module_type.py,sha256=rCafAi8k0BNRqtQryoai2WW4zr51lCEY-VRS7asW7VA,1485
58
57
  xp/models/__init__.py,sha256=lROqr559DGd8WpJJUtfPT95VERCwMZHpBDEc96QSxQ0,1312
59
58
  xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
60
59
  xp/models/actiontable/actiontable.py,sha256=IAV3KhA5B4jNOHFvVQmhJQV3vbX7EOug2UNAJuTslvk,1258
61
- xp/models/actiontable/actiontable_type.py,sha256=wfJXnXMz9IeU4RIM21BfXqAzJJ9I1Ak7cEPyJ8c6j94,610
60
+ xp/models/actiontable/actiontable_type.py,sha256=HjQ9Z-akdhMJaVx6fgyjqqFCAdFwQp0vO1YmSznlW1M,887
62
61
  xp/models/actiontable/msactiontable_xp20.py,sha256=E2JQpZzWuAb_ZdFApzx80tqfF1qFjiO4zp-PtrRBYeQ,5034
63
62
  xp/models/actiontable/msactiontable_xp24.py,sha256=WNtUmJVizyUs0RmG9EQW21tgiUB9ofqVQxrzEVpOjdk,9423
64
63
  xp/models/actiontable/msactiontable_xp33.py,sha256=nM4v7jICY3woXV50ji02rBbMxAu6CNjydoh9QNAMsF4,11859
@@ -127,7 +126,7 @@ xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAn
127
126
  xp/services/conbus/actiontable/actiontable_download_service.py,sha256=ZTQCC1D9TLmyDu3b5GAO4VxUVGSr6HuqZI9fxHB1qW8,14992
128
127
  xp/services/conbus/actiontable/actiontable_list_service.py,sha256=oTDSpBkp-MJeaF5bhRnwkSy3na55xqQ4e2ykJzbMCUo,3236
129
128
  xp/services/conbus/actiontable/actiontable_show_service.py,sha256=WISY2VsmSlceGa5_9lpFO-gs5TnTjv6YidQksUjCapk,3058
130
- xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=4crX4OZeTLYC_9ZjhV1Cqqc-WOhhBlXDmEHToLS6N9g,9772
129
+ xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=FaQzOSg8s2zUL5xz9qZY9fvzrdDosc3CoxkVDvNg2SU,13252
131
130
  xp/services/conbus/conbus_blink_all_service.py,sha256=toDIZDXBGBYnEishcdnJrVzkmfPi7g5nCDXuyA_wFCs,8536
132
131
  xp/services/conbus/conbus_blink_service.py,sha256=ggLuzeq_UsgCoxRxg2bsNs9p8Lw_shjsj-niRzb5dKk,7953
133
132
  xp/services/conbus/conbus_custom_service.py,sha256=9OIRC2CG_rN96vbv_EZXf7BrX_abhqi5MZx0Se8fEhU,7826
@@ -141,8 +140,6 @@ xp/services/conbus/conbus_output_service.py,sha256=e57bRkLgPnJuB8hkllNh0kgGkjPt9
141
140
  xp/services/conbus/conbus_raw_service.py,sha256=OQuV521VOQraf2PGF2B9868vh7sDgmfc19YebrkZnyw,5844
142
141
  xp/services/conbus/conbus_receive_service.py,sha256=TFf3W65brGsy6QZICpIs0Xy9bgqyL1vgQuhS_eHuIZs,5416
143
142
  xp/services/conbus/conbus_scan_service.py,sha256=_Ka0OUDNYhDgZIR49Q0P5GTxJq6RcAAX2DVqEDdtb5U,6888
144
- xp/services/conbus/msactiontable/__init__.py,sha256=rDYzumPSfcTjDADHxjE7bXQoeWtZTDGaYzFTYdVl_9g,42
145
- xp/services/conbus/msactiontable/msactiontable_upload_service.py,sha256=LB9pv0VOUDmyTHKUcY894fmBAqINO7qpM8mW1E3s8aU,12423
146
143
  xp/services/conbus/write_config_service.py,sha256=BCfmLNPRDpwSwRMRYJvx2FXA8IZsdgmyeTXIYvmb4ys,9004
147
144
  xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
148
145
  xp/services/homekit/homekit_cache_service.py,sha256=z1TB6icEqd1paoilVTewuFL0lXVCQbvrOJkJvvQECJY,11060
@@ -200,10 +197,10 @@ xp/term/widgets/protocol_log.py,sha256=E68QmSMpOFrvrPTo_gOQVfyiDqY5c_y8fkNKnQw6V
200
197
  xp/term/widgets/status_footer.py,sha256=YYAT0431p6jmrzzpVgaPhu7yGkRroWGv4e99t2XlkHI,3297
201
198
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
202
199
  xp/utils/checksum.py,sha256=Px1S3dFGA-_plavBxrq3IqmprNlgtNDunE3whg6Otwg,1722
203
- xp/utils/dependencies.py,sha256=b3BdBOiTuX7WU734MyZXDE854RDlJgJlQY6oBj2AABQ,25295
200
+ xp/utils/dependencies.py,sha256=tmqgooFjkZ8W_CekzJdPlaQas3IJGnVXbfBZdUimwZU,23992
204
201
  xp/utils/event_helper.py,sha256=zD0K3TPfGEThU9vUNlDtglTai3Cmm30727iwjDZy6Dk,1007
205
202
  xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
206
203
  xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
207
204
  xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
208
205
  xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
209
- conson_xp-1.49.0.dist-info/RECORD,,
206
+ conson_xp-1.50.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -4,7 +4,7 @@ XP CLI tool for remote console bus operations.
4
4
  conson-xp package.
5
5
  """
6
6
 
7
- __version__ = "1.49.0"
7
+ __version__ = "1.50.0"
8
8
  __manufacturer__ = "salchichon"
9
9
  __model__ = "xp.cli"
10
10
  __serial__ = "2025.09.23.000"
@@ -1,8 +1,6 @@
1
1
  """ActionTable CLI commands."""
2
2
 
3
3
  import json
4
- from contextlib import suppress
5
- from pathlib import Path
6
4
 
7
5
  import click
8
6
  from click import Context
@@ -13,10 +11,9 @@ from xp.cli.utils.decorators import (
13
11
  )
14
12
  from xp.cli.utils.serial_number_type import SERIAL
15
13
  from xp.models.actiontable.actiontable import ActionTable
16
- from xp.models.actiontable.actiontable_type import ActionTableType
14
+ from xp.models.actiontable.actiontable_type import ActionTableType, ActionTableType2
17
15
  from xp.models.config.conson_module_config import (
18
16
  ConsonModuleConfig,
19
- ConsonModuleListConfig,
20
17
  )
21
18
  from xp.services.conbus.actiontable.actiontable_download_service import (
22
19
  ActionTableDownloadService,
@@ -164,18 +161,12 @@ def conbus_upload_actiontable(ctx: Context, serial_number: str) -> None:
164
161
 
165
162
  with service:
166
163
  # Load config to get entry count for success message
167
- config_path = Path.cwd() / "conson.yml"
168
- if config_path.exists():
169
- with suppress(Exception):
170
- config = ConsonModuleListConfig.from_yaml(str(config_path))
171
- module = config.find_module(serial_number)
172
- if module and module.action_table:
173
- entries_count = len(module.action_table)
174
-
175
164
  service.on_progress.connect(progress_callback)
176
165
  service.on_finish.connect(on_finish)
177
166
  service.on_error.connect(on_error)
178
- service.start(serial_number=serial_number)
167
+ service.start(
168
+ serial_number=serial_number, actiontable_type=ActionTableType2.ACTIONTABLE
169
+ )
179
170
  service.start_reactor()
180
171
 
181
172
 
@@ -1,7 +1,7 @@
1
1
  """XP24 Action Table CLI commands."""
2
2
 
3
3
  import json
4
- from typing import Any, Union
4
+ from typing import Any, Optional, Union
5
5
 
6
6
  import click
7
7
  from click import Context
@@ -11,8 +11,7 @@ from xp.cli.utils.decorators import (
11
11
  connection_command,
12
12
  )
13
13
  from xp.cli.utils.serial_number_type import SERIAL
14
- from xp.cli.utils.xp_module_type import XP_MODULE_TYPE
15
- from xp.models.actiontable.actiontable_type import ActionTableType
14
+ from xp.models.actiontable.actiontable_type import ActionTableType, ActionTableType2
16
15
  from xp.models.config.conson_module_config import ConsonModuleConfig
17
16
  from xp.services.conbus.actiontable.actiontable_download_service import (
18
17
  ActionTableDownloadService,
@@ -23,11 +22,59 @@ from xp.services.conbus.actiontable.actiontable_list_service import (
23
22
  from xp.services.conbus.actiontable.actiontable_show_service import (
24
23
  ActionTableShowService,
25
24
  )
26
- from xp.services.conbus.msactiontable.msactiontable_upload_service import (
27
- MsActionTableUploadService,
25
+ from xp.services.conbus.actiontable.actiontable_upload_service import (
26
+ ActionTableUploadService,
28
27
  )
29
28
 
30
29
 
30
+ class XpModuleTypeChoice(click.ParamType):
31
+ """
32
+ Click parameter type for validating XP module types.
33
+
34
+ Attributes:
35
+ name: The parameter type name.
36
+ choices: List of valid module type strings.
37
+ """
38
+
39
+ name = "xpmoduletype"
40
+
41
+ def __init__(self) -> None:
42
+ """Initialize the XpModuleTypeChoice parameter type."""
43
+ self.choices = ["xp20", "xp24", "xp31", "xp33"]
44
+
45
+ def convert(
46
+ self,
47
+ value: Any,
48
+ param: Optional[click.Parameter],
49
+ ctx: Optional[click.Context],
50
+ ) -> Any:
51
+ """
52
+ Convert and validate XP module type input.
53
+
54
+ Args:
55
+ value: The input value to convert.
56
+ param: The Click parameter.
57
+ ctx: The Click context.
58
+
59
+ Returns:
60
+ Lowercase module type string if valid, None if input is None.
61
+ """
62
+ if value is None:
63
+ return value
64
+ normalized_value = value.lower()
65
+ if normalized_value in self.choices:
66
+ return normalized_value
67
+ choices_list = "\n".join(f" - {choice}" for choice in sorted(self.choices))
68
+ self.fail(
69
+ f"{value!r} is not a valid choice. Choose from:\n{choices_list}",
70
+ param,
71
+ ctx,
72
+ )
73
+
74
+
75
+ XP_MODULE_TYPE = XpModuleTypeChoice()
76
+
77
+
31
78
  def _get_actiontable_type(xpmoduletype: str) -> ActionTableType:
32
79
  """
33
80
  Map xpmoduletype string to ActionTableType enum.
@@ -244,22 +291,18 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
244
291
 
245
292
  @conbus_msactiontable.command("upload", short_help="Upload MSActionTable")
246
293
  @click.argument("serial_number", type=SERIAL)
247
- @click.argument("xpmoduletype", type=XP_MODULE_TYPE)
248
294
  @click.pass_context
249
295
  @connection_command()
250
- def conbus_upload_msactiontable(
251
- ctx: Context, serial_number: str, xpmoduletype: str
252
- ) -> None:
296
+ def conbus_upload_msactiontable(ctx: Context, serial_number: str) -> None:
253
297
  """
254
298
  Upload MS action table from conson.yml to XP module.
255
299
 
256
300
  Args:
257
301
  ctx: Click context object.
258
302
  serial_number: 10-digit module serial number.
259
- xpmoduletype: XP module type.
260
303
  """
261
- service: MsActionTableUploadService = (
262
- ctx.obj.get("container").get_container().resolve(MsActionTableUploadService)
304
+ service: ActionTableUploadService = (
305
+ ctx.obj.get("container").get_container().resolve(ActionTableUploadService)
263
306
  )
264
307
 
265
308
  def on_progress(progress: str) -> None:
@@ -300,7 +343,7 @@ def conbus_upload_msactiontable(
300
343
  service.on_finish.connect(on_finish)
301
344
  service.start(
302
345
  serial_number=serial_number,
303
- xpmoduletype=xpmoduletype,
346
+ actiontable_type=ActionTableType2.MSACTIONTABLE,
304
347
  )
305
348
  service.start_reactor()
306
349
 
@@ -18,3 +18,16 @@ class ActionTableType(str, Enum):
18
18
  MSACTIONTABLE_XP20 = "msactiontable_xp20"
19
19
  MSACTIONTABLE_XP24 = "msactiontable_xp24"
20
20
  MSACTIONTABLE_XP33 = "msactiontable_xp33"
21
+
22
+
23
+ class ActionTableType2(str, Enum):
24
+ """
25
+ ActionTable types for download/upload operations.
26
+
27
+ Attributes:
28
+ ACTIONTABLE: Standard action table.
29
+ MSACTIONTABLE: MS action table.
30
+ """
31
+
32
+ ACTIONTABLE = "actiontable"
33
+ MSACTIONTABLE = "msactiontable"
@@ -5,18 +5,32 @@ from typing import Any, Optional
5
5
 
6
6
  from psygnal import Signal
7
7
 
8
- from xp.models.config.conson_module_config import ConsonModuleListConfig
8
+ from xp.models.actiontable.actiontable_type import ActionTableType2
9
+ from xp.models.config.conson_module_config import (
10
+ ConsonModuleConfig,
11
+ ConsonModuleListConfig,
12
+ )
9
13
  from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
10
14
  from xp.models.telegram.system_function import SystemFunction
11
15
  from xp.models.telegram.telegram_type import TelegramType
12
16
  from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
17
+ from xp.services.actiontable.msactiontable_xp20_serializer import (
18
+ Xp20MsActionTableSerializer,
19
+ )
20
+ from xp.services.actiontable.msactiontable_xp24_serializer import (
21
+ Xp24MsActionTableSerializer,
22
+ )
23
+ from xp.services.actiontable.msactiontable_xp33_serializer import (
24
+ Xp33MsActionTableSerializer,
25
+ )
13
26
  from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
14
27
  from xp.services.telegram.telegram_service import TelegramService
15
28
 
16
29
 
17
30
  class ActionTableUploadService:
18
31
  """
19
- TCP client service for uploading action tables to Conbus modules.
32
+ TCP client service for uploading action tables and MS action tables to Conbus
33
+ modules.
20
34
 
21
35
  Manages TCP socket connections, handles telegram generation and transmission,
22
36
  and processes server responses for action table uploads.
@@ -35,6 +49,9 @@ class ActionTableUploadService:
35
49
  self,
36
50
  conbus_protocol: ConbusEventProtocol,
37
51
  actiontable_serializer: ActionTableSerializer,
52
+ xp20ms_serializer: Xp20MsActionTableSerializer,
53
+ xp24ms_serializer: Xp24MsActionTableSerializer,
54
+ xp33ms_serializer: Xp33MsActionTableSerializer,
38
55
  telegram_service: TelegramService,
39
56
  conson_config: ConsonModuleListConfig,
40
57
  ) -> None:
@@ -44,14 +61,22 @@ class ActionTableUploadService:
44
61
  Args:
45
62
  conbus_protocol: ConbusEventProtocol for communication.
46
63
  actiontable_serializer: Action table serializer.
64
+ xp20ms_serializer: XP20 MS action table serializer.
65
+ xp24ms_serializer: XP24 MS action table serializer.
66
+ xp33ms_serializer: XP33 MS action table serializer.
47
67
  telegram_service: Telegram service for parsing.
48
68
  conson_config: Conson module list configuration.
49
69
  """
50
70
  self.conbus_protocol = conbus_protocol
51
- self.serializer = actiontable_serializer
71
+ self.actiontable_serializer = actiontable_serializer
72
+ self.xp20ms_serializer = xp20ms_serializer
73
+ self.xp24ms_serializer = xp24ms_serializer
74
+ self.xp33ms_serializer = xp33ms_serializer
52
75
  self.telegram_service = telegram_service
53
76
  self.conson_config = conson_config
54
77
  self.serial_number: str = ""
78
+ self.xpmoduletype: str = ""
79
+ self.actiontable_type: ActionTableType2 = ActionTableType2.ACTIONTABLE
55
80
 
56
81
  # Upload state
57
82
  self.upload_data_chunks: list[str] = []
@@ -70,10 +95,17 @@ class ActionTableUploadService:
70
95
  def connection_made(self) -> None:
71
96
  """Handle connection established event."""
72
97
  self.logger.debug("Connection established, sending upload actiontable telegram")
98
+
99
+ system_function = (
100
+ SystemFunction.UPLOAD_ACTIONTABLE
101
+ if self.actiontable_type == ActionTableType2.ACTIONTABLE
102
+ else SystemFunction.UPLOAD_MSACTIONTABLE
103
+ )
104
+
73
105
  self.conbus_protocol.send_telegram(
74
106
  telegram_type=TelegramType.SYSTEM,
75
107
  serial_number=self.serial_number,
76
- system_function=SystemFunction.UPLOAD_ACTIONTABLE,
108
+ system_function=system_function,
77
109
  data_value="00",
78
110
  )
79
111
 
@@ -127,10 +159,16 @@ class ActionTableUploadService:
127
159
  # Second character: 'A' + chunk_index (sequential counter A-O for 15 chunks)
128
160
  prefix_hex = f"AAA{ord('A') + self.current_chunk_index:c}"
129
161
 
162
+ system_function = (
163
+ SystemFunction.ACTIONTABLE
164
+ if self.actiontable_type == ActionTableType2.ACTIONTABLE
165
+ else SystemFunction.MSACTIONTABLE
166
+ )
167
+
130
168
  self.conbus_protocol.send_telegram(
131
169
  telegram_type=TelegramType.SYSTEM,
132
170
  serial_number=self.serial_number,
133
- system_function=SystemFunction.ACTIONTABLE,
171
+ system_function=system_function,
134
172
  data_value=f"{prefix_hex}{chunk}",
135
173
  )
136
174
  self.current_chunk_index += 1
@@ -169,19 +207,24 @@ class ActionTableUploadService:
169
207
  def start(
170
208
  self,
171
209
  serial_number: str,
210
+ actiontable_type: ActionTableType2,
172
211
  timeout_seconds: Optional[float] = None,
173
212
  ) -> None:
174
213
  """
175
- Upload action table to module.
214
+ Upload action table or MS action table to module.
176
215
 
177
216
  Uploads the action table configuration to the specified module.
217
+ Module type will decide which actiontable to use.
178
218
 
179
219
  Args:
180
220
  serial_number: Module serial number.
221
+ actiontable_type: True if actionTable false for MS action table.
181
222
  timeout_seconds: Optional timeout in seconds.
182
223
  """
183
224
  self.logger.info("Starting actiontable upload")
184
225
  self.serial_number = serial_number
226
+ self.actiontable_type = actiontable_type
227
+
185
228
  if timeout_seconds:
186
229
  self.conbus_protocol.timeout_seconds = timeout_seconds
187
230
 
@@ -193,29 +236,65 @@ class ActionTableUploadService:
193
236
 
194
237
  # Parse action table strings to ActionTable object
195
238
  try:
196
- module_action_table = module.action_table or []
197
- action_table = self.serializer.from_short_string(module_action_table)
239
+ encoded_data = self.get_encoded_action_table(module)
240
+ # Chunk the data into 64 byte chunks
241
+ chunk_size = 64
242
+ self.upload_data_chunks = [
243
+ encoded_data[i : i + chunk_size]
244
+ for i in range(0, len(encoded_data), chunk_size)
245
+ ]
246
+ self.current_chunk_index = 0
247
+
198
248
  except ValueError as e:
199
249
  self.logger.error(f"Invalid action table format: {e}")
200
250
  self.failed(f"Invalid action table format: {e}")
201
251
  return
202
252
 
203
- # Encode action table to hex string
204
- encoded_data = self.serializer.to_encoded_string(action_table)
205
-
206
- # Chunk the data into 64 byte chunks
207
- chunk_size = 64
208
- self.upload_data_chunks = [
209
- encoded_data[i : i + chunk_size]
210
- for i in range(0, len(encoded_data), chunk_size)
211
- ]
212
- self.current_chunk_index = 0
213
-
214
253
  self.logger.debug(
215
254
  f"Upload data encoded: {len(encoded_data)} chars, "
216
255
  f"{len(self.upload_data_chunks)} chunks"
217
256
  )
218
257
 
258
+ def get_encoded_action_table(self, module: ConsonModuleConfig) -> str:
259
+ """
260
+ Get encoded action table string for upload.
261
+
262
+ Args:
263
+ module: Module configuration containing action table data.
264
+
265
+ Returns:
266
+ Hex-encoded action table string ready for transmission.
267
+ """
268
+ msactiontable = (
269
+ True if self.actiontable_type == ActionTableType2.MSACTIONTABLE else False
270
+ )
271
+ # Parse MS action table from short format (first element)
272
+ if msactiontable and module.module_type.lower() == "xp20":
273
+ xp20_short_table = module.xp20_msaction_table or []
274
+ xp20_actiontable = self.xp20ms_serializer.from_short_string(
275
+ xp20_short_table
276
+ )
277
+ encoded_string = self.xp20ms_serializer.to_encoded_string(xp20_actiontable)
278
+ elif msactiontable and module.module_type.lower() == "xp24":
279
+ xp24_short_table = module.xp24_msaction_table or []
280
+ xp24_actiontable = self.xp24ms_serializer.from_short_string(
281
+ xp24_short_table
282
+ )
283
+ encoded_string = self.xp24ms_serializer.to_encoded_string(xp24_actiontable)
284
+ elif msactiontable and module.module_type.lower() == "xp33":
285
+ xp33_short_table = module.xp33_msaction_table or []
286
+ xp33_actiontable = self.xp33ms_serializer.from_short_string(
287
+ xp33_short_table
288
+ )
289
+ encoded_string = self.xp33ms_serializer.to_encoded_string(xp33_actiontable)
290
+ else:
291
+ short_table = module.action_table or []
292
+ actiontable = self.actiontable_serializer.from_short_string(short_table)
293
+ encoded_string = self.actiontable_serializer.to_encoded_string(actiontable)
294
+
295
+ # Serialize to telegram data (64 characters: AAAA + 64 data chars)
296
+ return encoded_string
297
+
219
298
  def set_timeout(self, timeout_seconds: float) -> None:
220
299
  """
221
300
  Set operation timeout.
@@ -243,6 +322,7 @@ class ActionTableUploadService:
243
322
  self.upload_data_chunks = []
244
323
  self.current_chunk_index = 0
245
324
  self.serial_number = ""
325
+ self.xpmoduletype = ""
246
326
  return self
247
327
 
248
328
  def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
xp/utils/dependencies.py CHANGED
@@ -51,9 +51,6 @@ from xp.services.conbus.conbus_output_service import ConbusOutputService
51
51
  from xp.services.conbus.conbus_raw_service import ConbusRawService
52
52
  from xp.services.conbus.conbus_receive_service import ConbusReceiveService
53
53
  from xp.services.conbus.conbus_scan_service import ConbusScanService
54
- from xp.services.conbus.msactiontable.msactiontable_upload_service import (
55
- MsActionTableUploadService,
56
- )
57
54
  from xp.services.conbus.write_config_service import WriteConfigService
58
55
  from xp.services.homekit.homekit_cache_service import HomeKitCacheService
59
56
  from xp.services.homekit.homekit_conbus_service import HomeKitConbusService
@@ -336,6 +333,9 @@ class ServiceContainer:
336
333
  factory=lambda: ActionTableUploadService(
337
334
  conbus_protocol=self.container.resolve(ConbusEventProtocol),
338
335
  actiontable_serializer=self.container.resolve(ActionTableSerializer),
336
+ xp20ms_serializer=self.container.resolve(Xp20MsActionTableSerializer),
337
+ xp24ms_serializer=self.container.resolve(Xp24MsActionTableSerializer),
338
+ xp33ms_serializer=self.container.resolve(Xp33MsActionTableSerializer),
339
339
  telegram_service=self.container.resolve(TelegramService),
340
340
  conson_config=self.container.resolve(ConsonModuleListConfig),
341
341
  ),
@@ -372,37 +372,6 @@ class ServiceContainer:
372
372
  scope=punq.Scope.singleton,
373
373
  )
374
374
 
375
- self.container.register(
376
- ActionTableDownloadService,
377
- factory=lambda: ActionTableDownloadService(
378
- conbus_protocol=self.container.resolve(ConbusEventProtocol),
379
- actiontable_serializer=self.container.resolve(ActionTableSerializer),
380
- msactiontable_serializer_xp20=self.container.resolve(
381
- Xp20MsActionTableSerializer
382
- ),
383
- msactiontable_serializer_xp24=self.container.resolve(
384
- Xp24MsActionTableSerializer
385
- ),
386
- msactiontable_serializer_xp33=self.container.resolve(
387
- Xp33MsActionTableSerializer
388
- ),
389
- ),
390
- scope=punq.Scope.singleton,
391
- )
392
-
393
- self.container.register(
394
- MsActionTableUploadService,
395
- factory=lambda: MsActionTableUploadService(
396
- conbus_protocol=self.container.resolve(ConbusEventProtocol),
397
- xp20ms_serializer=self.container.resolve(Xp20MsActionTableSerializer),
398
- xp24ms_serializer=self.container.resolve(Xp24MsActionTableSerializer),
399
- xp33ms_serializer=self.container.resolve(Xp33MsActionTableSerializer),
400
- telegram_service=self.container.resolve(TelegramService),
401
- conson_config=self.container.resolve(ConsonModuleListConfig),
402
- ),
403
- scope=punq.Scope.singleton,
404
- )
405
-
406
375
  self.container.register(
407
376
  ConbusCustomService,
408
377
  factory=lambda: ConbusCustomService(
@@ -1,55 +0,0 @@
1
- """Click parameter type for XP module type validation."""
2
-
3
- from typing import Any, Optional
4
-
5
- import click
6
-
7
-
8
- class XpModuleTypeChoice(click.ParamType):
9
- """
10
- Click parameter type for validating XP module types.
11
-
12
- Attributes:
13
- name: The parameter type name.
14
- choices: List of valid module type strings.
15
- """
16
-
17
- name = "xpmoduletype"
18
-
19
- def __init__(self) -> None:
20
- """Initialize the XpModuleTypeChoice parameter type."""
21
- self.choices = ["xp20", "xp24", "xp31", "xp33"]
22
-
23
- def convert(
24
- self, value: Any, param: Optional[click.Parameter], ctx: Optional[click.Context]
25
- ) -> Any:
26
- """
27
- Convert and validate XP module type input.
28
-
29
- Args:
30
- value: The input value to convert.
31
- param: The Click parameter.
32
- ctx: The Click context.
33
-
34
- Returns:
35
- Lowercase module type string if valid, None if input is None.
36
- """
37
- if value is None:
38
- return value
39
-
40
- # Convert to lower for comparison
41
- normalized_value = value.lower()
42
-
43
- if normalized_value in self.choices:
44
- return normalized_value
45
-
46
- # If not found, show error with available choices
47
- choices_list = "\n".join(f" - {choice}" for choice in sorted(self.choices))
48
- self.fail(
49
- f"{value!r} is not a valid choice. " f"Choose from:\n{choices_list}",
50
- param,
51
- ctx,
52
- )
53
-
54
-
55
- XP_MODULE_TYPE = XpModuleTypeChoice()
@@ -1 +0,0 @@
1
- """MsAction table services for Conbus."""
@@ -1,332 +0,0 @@
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
- """
33
- TCP client service for uploading MS action tables to Conbus modules.
34
-
35
- Manages TCP socket connections, handles telegram generation and transmission,
36
- and processes server responses for MS action table uploads.
37
-
38
- Attributes:
39
- on_progress: Signal emitted with telegram frame when progress is made.
40
- on_error: Signal emitted with error message string when an error occurs.
41
- on_finish: Signal emitted with bool (True on success) when upload completes.
42
- """
43
-
44
- on_progress: Signal = Signal(str)
45
- on_error: Signal = Signal(str)
46
- on_finish: Signal = Signal(bool) # True on success
47
-
48
- def __init__(
49
- self,
50
- conbus_protocol: ConbusEventProtocol,
51
- xp20ms_serializer: Xp20MsActionTableSerializer,
52
- xp24ms_serializer: Xp24MsActionTableSerializer,
53
- xp33ms_serializer: Xp33MsActionTableSerializer,
54
- telegram_service: TelegramService,
55
- conson_config: ConsonModuleListConfig,
56
- ) -> None:
57
- """
58
- Initialize the MS action table upload service.
59
-
60
- Args:
61
- conbus_protocol: ConbusEventProtocol for communication.
62
- xp20ms_serializer: XP20 MS action table serializer.
63
- xp24ms_serializer: XP24 MS action table serializer.
64
- xp33ms_serializer: XP33 MS action table serializer.
65
- telegram_service: Telegram service for parsing.
66
- conson_config: Conson module list configuration.
67
- """
68
- self.conbus_protocol = conbus_protocol
69
- self.xp20ms_serializer = xp20ms_serializer
70
- self.xp24ms_serializer = xp24ms_serializer
71
- self.xp33ms_serializer = xp33ms_serializer
72
- self.serializer: Union[
73
- Xp20MsActionTableSerializer,
74
- Xp24MsActionTableSerializer,
75
- Xp33MsActionTableSerializer,
76
- ] = xp20ms_serializer
77
- self.telegram_service = telegram_service
78
- self.conson_config = conson_config
79
- self.serial_number: str = ""
80
- self.xpmoduletype: str = ""
81
-
82
- # Upload state
83
- self.upload_data: str = ""
84
- self.upload_initiated: bool = False
85
-
86
- # Set up logging
87
- self.logger = logging.getLogger(__name__)
88
-
89
- # Connect protocol signals
90
- self.conbus_protocol.on_connection_made.connect(self.connection_made)
91
- self.conbus_protocol.on_telegram_sent.connect(self.telegram_sent)
92
- self.conbus_protocol.on_telegram_received.connect(self.telegram_received)
93
- self.conbus_protocol.on_timeout.connect(self.timeout)
94
- self.conbus_protocol.on_failed.connect(self.failed)
95
-
96
- def connection_made(self) -> None:
97
- """Handle connection established event."""
98
- self.logger.debug(
99
- "Connection established, sending upload msactiontable telegram"
100
- )
101
- self.conbus_protocol.send_telegram(
102
- telegram_type=TelegramType.SYSTEM,
103
- serial_number=self.serial_number,
104
- system_function=SystemFunction.UPLOAD_MSACTIONTABLE,
105
- data_value="00",
106
- )
107
-
108
- def telegram_sent(self, telegram_sent: str) -> None:
109
- """
110
- Handle telegram sent event.
111
-
112
- Args:
113
- telegram_sent: The telegram that was sent.
114
- """
115
- self.logger.debug(f"Telegram sent: {telegram_sent}")
116
-
117
- def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
118
- """
119
- Handle telegram received event.
120
-
121
- Args:
122
- telegram_received: The telegram received event.
123
- """
124
- self.logger.debug(f"Telegram received: {telegram_received}")
125
- if (
126
- not telegram_received.checksum_valid
127
- or telegram_received.telegram_type != TelegramType.REPLY.value
128
- or telegram_received.serial_number != self.serial_number
129
- ):
130
- self.logger.debug("Not a reply response")
131
- return
132
-
133
- reply_telegram = self.telegram_service.parse_reply_telegram(
134
- telegram_received.frame
135
- )
136
-
137
- self._handle_upload_response(reply_telegram)
138
-
139
- def _handle_upload_response(self, reply_telegram: Any) -> None:
140
- """
141
- Handle telegram responses during upload.
142
-
143
- Args:
144
- reply_telegram: Parsed reply telegram.
145
- """
146
- if reply_telegram.system_function == SystemFunction.ACK:
147
- self.logger.debug("Received ACK for upload")
148
-
149
- if not self.upload_initiated:
150
- # First ACK - send data chunk
151
- self.logger.debug("Sending msactiontable data")
152
- self.conbus_protocol.send_telegram(
153
- telegram_type=TelegramType.SYSTEM,
154
- serial_number=self.serial_number,
155
- system_function=SystemFunction.MSACTIONTABLE,
156
- data_value=self.upload_data,
157
- )
158
- self.upload_initiated = True
159
- self.on_progress.emit(".")
160
- else:
161
- # Second ACK - send EOF
162
- self.logger.debug("Data sent, sending EOF")
163
- self.conbus_protocol.send_telegram(
164
- telegram_type=TelegramType.SYSTEM,
165
- serial_number=self.serial_number,
166
- system_function=SystemFunction.EOF,
167
- data_value="00",
168
- )
169
- self.on_finish.emit(True)
170
- elif reply_telegram.system_function == SystemFunction.NAK:
171
- self.logger.debug("Received NAK during upload")
172
- self.failed("Upload failed: NAK received")
173
- else:
174
- self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
175
-
176
- def timeout(self) -> None:
177
- """Handle timeout event."""
178
- self.logger.debug("Upload timeout")
179
- self.failed("Upload timeout")
180
-
181
- def failed(self, message: str) -> None:
182
- """
183
- Handle failed connection event.
184
-
185
- Args:
186
- message: Failure message.
187
- """
188
- self.logger.debug(f"Failed: {message}")
189
- self.on_error.emit(message)
190
-
191
- def start(
192
- self,
193
- serial_number: str,
194
- xpmoduletype: str,
195
- timeout_seconds: Optional[float] = None,
196
- ) -> None:
197
- """
198
- Upload MS action table to module.
199
-
200
- Uploads the MS action table configuration to the specified module.
201
-
202
- Args:
203
- serial_number: Module serial number.
204
- xpmoduletype: XP module type (xp20, xp24, xp33).
205
- timeout_seconds: Optional timeout in seconds.
206
-
207
- Raises:
208
- MsActionTableUploadError: If configuration or validation errors occur.
209
- """
210
- self.logger.info("Starting msactiontable upload")
211
- self.serial_number = serial_number
212
- self.xpmoduletype = xpmoduletype
213
-
214
- # Select serializer based on module type
215
- if xpmoduletype == "xp20":
216
- self.serializer = self.xp20ms_serializer
217
- config_field = "xp20_msaction_table"
218
- elif xpmoduletype == "xp24":
219
- self.serializer = self.xp24ms_serializer
220
- config_field = "xp24_msaction_table"
221
- elif xpmoduletype == "xp33":
222
- self.serializer = self.xp33ms_serializer
223
- config_field = "xp33_msaction_table"
224
- else:
225
- raise MsActionTableUploadError(f"Unsupported module type: {xpmoduletype}")
226
-
227
- if timeout_seconds:
228
- self.conbus_protocol.timeout_seconds = timeout_seconds
229
-
230
- # Find module
231
- module = self.conson_config.find_module(serial_number)
232
- if not module:
233
- self.failed(f"Module {serial_number} not found in conson.yml")
234
- return
235
-
236
- # Validate module type matches
237
- if module.module_type.lower() != xpmoduletype.lower():
238
- self.failed(
239
- f"Module type mismatch: module has type {module.module_type}, "
240
- f"but {xpmoduletype} was specified"
241
- )
242
- return
243
-
244
- # Get msactiontable config for the module type
245
- msactiontable_config = getattr(module, config_field, None)
246
-
247
- if not msactiontable_config:
248
- self.failed(
249
- f"Module {serial_number} does not have {config_field} configured in conson.yml"
250
- )
251
- return
252
-
253
- if not isinstance(msactiontable_config, list) or len(msactiontable_config) == 0:
254
- self.failed(
255
- f"Module {serial_number} has empty {config_field} list in conson.yml"
256
- )
257
- return
258
-
259
- # Parse MS action table from short format (first element)
260
- try:
261
- short_format = msactiontable_config
262
- msactiontable: Union[
263
- "Xp20MsActionTable", "Xp24MsActionTable", "Xp33MsActionTable"
264
- ]
265
- if xpmoduletype == "xp20":
266
- from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
267
-
268
- msactiontable = Xp20MsActionTable.from_short_format(short_format)
269
- elif xpmoduletype == "xp24":
270
- from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
271
-
272
- msactiontable = Xp24MsActionTable.from_short_format(short_format)
273
- elif xpmoduletype == "xp33":
274
- from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
275
-
276
- msactiontable = Xp33MsActionTable.from_short_format(short_format)
277
- except (ValueError, AttributeError) as e:
278
- self.logger.error(f"Invalid msactiontable format: {e}")
279
- self.failed(f"Invalid msactiontable format: {e}")
280
- return
281
-
282
- # Serialize to telegram data (64 characters: AAAA + 64 data chars)
283
- self.upload_data = "AAAA" + self.serializer.to_encoded_string(msactiontable) # type: ignore[arg-type]
284
-
285
- self.logger.debug(
286
- f"Upload data encoded: {len(self.upload_data)} chars (single chunk)"
287
- )
288
-
289
- def set_timeout(self, timeout_seconds: float) -> None:
290
- """
291
- Set operation timeout.
292
-
293
- Args:
294
- timeout_seconds: Timeout in seconds.
295
- """
296
- self.conbus_protocol.timeout_seconds = timeout_seconds
297
-
298
- def start_reactor(self) -> None:
299
- """Start the reactor."""
300
- self.conbus_protocol.start_reactor()
301
-
302
- def stop_reactor(self) -> None:
303
- """Stop the reactor."""
304
- self.conbus_protocol.stop_reactor()
305
-
306
- def __enter__(self) -> "MsActionTableUploadService":
307
- """Enter context manager - reset state for singleton reuse.
308
-
309
- Returns:
310
- Self for context manager protocol.
311
- """
312
- # Reset state
313
- self.upload_data = ""
314
- self.upload_initiated = False
315
- self.serial_number = ""
316
- self.xpmoduletype = ""
317
- return self
318
-
319
- def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
320
- """Exit context manager - cleanup signals and reactor."""
321
- # Disconnect protocol signals
322
- self.conbus_protocol.on_connection_made.disconnect(self.connection_made)
323
- self.conbus_protocol.on_telegram_sent.disconnect(self.telegram_sent)
324
- self.conbus_protocol.on_telegram_received.disconnect(self.telegram_received)
325
- self.conbus_protocol.on_timeout.disconnect(self.timeout)
326
- self.conbus_protocol.on_failed.disconnect(self.failed)
327
- # Disconnect service signals
328
- self.on_progress.disconnect()
329
- self.on_error.disconnect()
330
- self.on_finish.disconnect()
331
- # Stop reactor
332
- self.stop_reactor()