conson-xp 1.39.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.
- {conson_xp-1.39.0.dist-info → conson_xp-1.41.0.dist-info}/METADATA +2 -1
- {conson_xp-1.39.0.dist-info → conson_xp-1.41.0.dist-info}/RECORD +18 -18
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +134 -15
- xp/models/actiontable/msactiontable_xp20.py +93 -3
- xp/models/actiontable/msactiontable_xp24.py +9 -10
- xp/models/actiontable/msactiontable_xp33.py +269 -2
- xp/models/config/conson_module_config.py +6 -2
- xp/services/actiontable/msactiontable_xp20_serializer.py +2 -2
- xp/services/actiontable/msactiontable_xp24_serializer.py +1 -1
- xp/services/actiontable/msactiontable_xp33_serializer.py +2 -2
- xp/services/conbus/msactiontable/msactiontable_download_service.py +4 -4
- xp/services/conbus/msactiontable/msactiontable_list_service.py +9 -1
- xp/services/conbus/msactiontable/msactiontable_upload_service.py +324 -0
- xp/utils/dependencies.py +16 -0
- xp/models/actiontable/msactiontable.py +0 -9
- {conson_xp-1.39.0.dist-info → conson_xp-1.41.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.39.0.dist-info → conson_xp-1.41.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.39.0.dist-info → conson_xp-1.41.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: conson-xp
|
|
3
|
-
Version: 1.
|
|
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.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
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=
|
|
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
|
|
@@ -58,10 +58,9 @@ xp/cli/utils/xp_module_type.py,sha256=qSFJBRceqPi_cUFPxAWtLUNq37-KwUEjo9ekYOj7kL
|
|
|
58
58
|
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
|
-
xp/models/actiontable/
|
|
62
|
-
xp/models/actiontable/
|
|
63
|
-
xp/models/actiontable/
|
|
64
|
-
xp/models/actiontable/msactiontable_xp33.py,sha256=K1noQe5TNoTFLWE58r0-ZOB8lYM3oXFqoNL7z7Uob5A,2945
|
|
61
|
+
xp/models/actiontable/msactiontable_xp20.py,sha256=zc9akPpuaW-pBk1vD9xn0JDWm_c2fiJYDuuk-DbvbGQ,5006
|
|
62
|
+
xp/models/actiontable/msactiontable_xp24.py,sha256=tpgYvlQwxhjo70Ucsg_rB9ox7-jlG2b-GBj-UXwP2Ic,9377
|
|
63
|
+
xp/models/actiontable/msactiontable_xp33.py,sha256=p_0HrvUmnqEEUlle7n0vpspGXFPrO5pXZeVF7n9K19g,11781
|
|
65
64
|
xp/models/conbus/__init__.py,sha256=VIusMWQdBtlwDgj7oSj06wQkklihTp4oWFShvP_JUgA,35
|
|
66
65
|
xp/models/conbus/conbus.py,sha256=mZQzKPfrdttT-qUnYUSyrEYyc_eHs8z301E5ejeiyvk,2689
|
|
67
66
|
xp/models/conbus/conbus_autoreport.py,sha256=lKotDfxRBb7h2Z1d4qI3KhhLJhFDwKqLbSdG5Makm8Y,2289
|
|
@@ -82,7 +81,7 @@ xp/models/conbus/conbus_raw.py,sha256=xqvYao6IE1SXum7JBgZpSuWXm9x_QZquS9N_3_r0Hj
|
|
|
82
81
|
xp/models/conbus/conbus_receive.py,sha256=-1u1qK-texfKCNZV-GYf_9KyLtJdIrx7HuZsKzu26Ow,1322
|
|
83
82
|
xp/models/conbus/conbus_writeconfig.py,sha256=z8fdJeFLyGJW7UMHcHxGrHIMS6gG1D3aXeYUkBuwnEg,2136
|
|
84
83
|
xp/models/config/__init__.py,sha256=gEZnX9eE3DjFtLtF32riEjJQLypqQRbyPauBI4Cowbs,36
|
|
85
|
-
xp/models/config/conson_module_config.py,sha256=
|
|
84
|
+
xp/models/config/conson_module_config.py,sha256=2uM1M4oKgQ3pkuEAEHHFMwZVGpWImJEe91TWgfS1ikU,2981
|
|
86
85
|
xp/models/homekit/__init__.py,sha256=5HDSOClCu0ArK3IICn3_LDMMLBAzLjBxUUSF73bxSSk,34
|
|
87
86
|
xp/models/homekit/homekit_accessory.py,sha256=NsHFhskuxIdJpF9-MvXHtjkLYvNHmSGFOy0GmQv3PY4,1038
|
|
88
87
|
xp/models/homekit/homekit_config.py,sha256=Y_k92PsKHFBnn3r1_RSZHJP5uLH27Gw8G7Bj5N8jvUE,2904
|
|
@@ -117,9 +116,9 @@ xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
|
|
|
117
116
|
xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
|
|
118
117
|
xp/services/actiontable/actiontable_serializer.py,sha256=U7bhd8lYMUJAsFydCt_Y5uOJoUODhSjRlUQPD6jsqMo,8517
|
|
119
118
|
xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
|
|
120
|
-
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=
|
|
121
|
-
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=
|
|
122
|
-
xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=
|
|
119
|
+
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=5e9L-xlFUSu2RJnhr0BQIbhIPZVRiOhp8kKvbWsU5BQ,6438
|
|
120
|
+
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=ku84HCCjYDM9XpRKIWx1HnW1_U0LyFDH57f9Gig-pgQ,4607
|
|
121
|
+
xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=MFjEwSsIa8l3RJt-ig828E6kyiFYYXMHFKe4Q0A3NvA,8781
|
|
123
122
|
xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
|
|
124
123
|
xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
|
|
125
124
|
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=C6cjNRRsl7_jjn94I6ycCDvoqIpivNv0cMVkR-CQBXk,7608
|
|
@@ -140,9 +139,10 @@ xp/services/conbus/conbus_raw_service.py,sha256=FmUaF9o2nFZVP8LpabKIwkg0P8coLCke
|
|
|
140
139
|
xp/services/conbus/conbus_receive_service.py,sha256=7wOaEDrdoXwZE9MeUM89eB3hobYpvtbYk_YLv3MVAtc,5352
|
|
141
140
|
xp/services/conbus/conbus_scan_service.py,sha256=QN7_x8BtNbHnqG7akcooAAcjz9Ex2y3VNDdhShKHUX8,6824
|
|
142
141
|
xp/services/conbus/msactiontable/__init__.py,sha256=rDYzumPSfcTjDADHxjE7bXQoeWtZTDGaYzFTYdVl_9g,42
|
|
143
|
-
xp/services/conbus/msactiontable/msactiontable_download_service.py,sha256=
|
|
144
|
-
xp/services/conbus/msactiontable/msactiontable_list_service.py,sha256=
|
|
142
|
+
xp/services/conbus/msactiontable/msactiontable_download_service.py,sha256=YAQeUAO04VkRTEvWwXBD_b6tdVjDYk55K4pZd7lxfE8,10049
|
|
143
|
+
xp/services/conbus/msactiontable/msactiontable_list_service.py,sha256=bTqUI2xs3Ie0MeZ_PYm-Bgx9A-Eewlpc8Tv6jhi1_kA,3127
|
|
145
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
|
|
146
146
|
xp/services/conbus/write_config_service.py,sha256=PQsN7rtTKHpwtAG8moLksUfRVqqE_0sxdE37meR1ZQ0,8935
|
|
147
147
|
xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
|
|
148
148
|
xp/services/homekit/homekit_cache_service.py,sha256=NdijyH5_iyhsTHBb-OyT8Y2xnNDj8F5MP8neoVQ26hY,11010
|
|
@@ -201,10 +201,10 @@ xp/term/widgets/protocol_log.py,sha256=CJUpckWj7GC1kVqixDadteyGnI4aHyzd4kkH-pSbz
|
|
|
201
201
|
xp/term/widgets/status_footer.py,sha256=bxrcqKzJ9V0aPSn_WwraVpJz7NxBUh3yIjA3fwv5nVA,3256
|
|
202
202
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
203
203
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
204
|
-
xp/utils/dependencies.py,sha256=
|
|
204
|
+
xp/utils/dependencies.py,sha256=zrvWx28N0f28JwRDRyqaf5Q9eV_yLwh9xDw9mYBUXEQ,25379
|
|
205
205
|
xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
|
|
206
206
|
xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
|
|
207
207
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
208
208
|
xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
|
|
209
209
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
210
|
-
conson_xp-1.
|
|
210
|
+
conson_xp-1.41.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -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")
|
|
@@ -55,29 +58,30 @@ def conbus_download_msactiontable(
|
|
|
55
58
|
click.echo(progress, nl=False)
|
|
56
59
|
|
|
57
60
|
def on_finish(
|
|
58
|
-
msaction_table: Union[
|
|
59
|
-
|
|
60
|
-
],
|
|
61
|
-
msaction_table_short: str,
|
|
61
|
+
msaction_table: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
|
|
62
|
+
msaction_table_short: list[str],
|
|
62
63
|
) -> None:
|
|
63
|
-
"""Handle successful completion of MS action table download.
|
|
64
|
+
"""Handle successful completion of XP24 MS action table download.
|
|
64
65
|
|
|
65
66
|
Args:
|
|
66
|
-
msaction_table: Downloaded MS action table object
|
|
67
|
-
msaction_table_short: Short version of MS action table
|
|
68
|
-
|
|
69
|
-
Raises:
|
|
70
|
-
Abort: If action table download failed.
|
|
67
|
+
msaction_table: Downloaded XP MS action table object.
|
|
68
|
+
msaction_table_short: Short version of XP24 MS action table.
|
|
71
69
|
"""
|
|
72
70
|
service.stop_reactor()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
|
|
72
|
+
# Format short representation based on module type
|
|
73
|
+
short_field_name = f"{xpmoduletype}_msaction_table"
|
|
74
|
+
# XP24 returns single-element list, XP20/XP33 return multi-line lists
|
|
75
|
+
short_value: Union[str, list[str]]
|
|
76
|
+
if len(msaction_table_short) == 1:
|
|
77
|
+
short_value = msaction_table_short[0]
|
|
78
|
+
else:
|
|
79
|
+
short_value = msaction_table_short
|
|
76
80
|
|
|
77
81
|
output = {
|
|
78
82
|
"serial_number": serial_number,
|
|
79
83
|
"xpmoduletype": xpmoduletype,
|
|
80
|
-
|
|
84
|
+
short_field_name: short_value,
|
|
81
85
|
"msaction_table": msaction_table.model_dump(),
|
|
82
86
|
}
|
|
83
87
|
click.echo(json.dumps(output, indent=2, default=str))
|
|
@@ -93,6 +97,8 @@ def conbus_download_msactiontable(
|
|
|
93
97
|
with service:
|
|
94
98
|
service.on_progress.connect(on_progress)
|
|
95
99
|
service.on_error.connect(on_error)
|
|
100
|
+
|
|
101
|
+
# Connect to the appropriate signal based on module type
|
|
96
102
|
service.on_finish.connect(on_finish)
|
|
97
103
|
service.start(
|
|
98
104
|
serial_number=serial_number,
|
|
@@ -155,9 +161,37 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
|
|
|
155
161
|
Args:
|
|
156
162
|
module: Dictionary containing module configuration.
|
|
157
163
|
"""
|
|
164
|
+
click.echo(f"\nModule: {module.name} ({module.serial_number})")
|
|
165
|
+
|
|
166
|
+
# Display short format if action table exists
|
|
167
|
+
if module.xp33_msaction_table:
|
|
168
|
+
click.echo("Short:")
|
|
169
|
+
for line in module.xp33_msaction_table:
|
|
170
|
+
click.echo(f" - {line}")
|
|
171
|
+
elif module.xp24_msaction_table:
|
|
172
|
+
click.echo("Short:")
|
|
173
|
+
for line in module.xp24_msaction_table:
|
|
174
|
+
click.echo(f" - {line}")
|
|
175
|
+
elif module.xp20_msaction_table:
|
|
176
|
+
click.echo("Short:")
|
|
177
|
+
for line in module.xp20_msaction_table:
|
|
178
|
+
click.echo(f" - {line}")
|
|
179
|
+
|
|
180
|
+
# Display full YAML format
|
|
181
|
+
click.echo("Full:")
|
|
158
182
|
module_data = module.model_dump()
|
|
159
183
|
module_data.pop("action_table", None)
|
|
160
|
-
|
|
184
|
+
|
|
185
|
+
# Show the action table in YAML format
|
|
186
|
+
if module.xp33_msaction_table:
|
|
187
|
+
yaml_dict = {"xp33_msaction_table": module_data}
|
|
188
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
189
|
+
elif module.xp24_msaction_table:
|
|
190
|
+
yaml_dict = {"xp24_msaction_table": module_data}
|
|
191
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
192
|
+
elif module.xp20_msaction_table:
|
|
193
|
+
yaml_dict = {"xp20_msaction_table": module_data}
|
|
194
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
161
195
|
|
|
162
196
|
def error_callback(error: str) -> None:
|
|
163
197
|
"""Handle errors during action table show.
|
|
@@ -173,3 +207,88 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
|
|
|
173
207
|
finish_callback=on_finish,
|
|
174
208
|
error_callback=error_callback,
|
|
175
209
|
)
|
|
210
|
+
|
|
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
|
+
|
|
271
|
+
def _format_yaml(data: dict, indent: int = 0) -> str:
|
|
272
|
+
"""Format a dictionary as YAML-like output.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
data: Dictionary to format.
|
|
276
|
+
indent: Current indentation level.
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
YAML-like formatted string.
|
|
280
|
+
"""
|
|
281
|
+
lines: list[str] = []
|
|
282
|
+
for key, value in data.items():
|
|
283
|
+
if isinstance(value, dict):
|
|
284
|
+
lines.extend((f"{' ' * indent}{key}:", _format_yaml(value, indent + 2)))
|
|
285
|
+
elif isinstance(value, list):
|
|
286
|
+
lines.append(f"{' ' * indent}{key}:")
|
|
287
|
+
for item in value:
|
|
288
|
+
if isinstance(item, dict):
|
|
289
|
+
lines.append(_format_yaml(item, indent + 2))
|
|
290
|
+
else:
|
|
291
|
+
lines.append(f"{' ' * (indent + 2)}- {item}")
|
|
292
|
+
else:
|
|
293
|
+
lines.append(f"{' ' * indent}{key}: {value}")
|
|
294
|
+
return "\n".join(lines)
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from xp.models.actiontable.msactiontable import MsActionTable
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
class InputChannel(BaseModel):
|
|
9
7
|
"""Configuration for a single input channel in XP20 action table.
|
|
@@ -25,7 +23,7 @@ class InputChannel(BaseModel):
|
|
|
25
23
|
ta_function: bool = False
|
|
26
24
|
|
|
27
25
|
|
|
28
|
-
class Xp20MsActionTable(
|
|
26
|
+
class Xp20MsActionTable(BaseModel):
|
|
29
27
|
"""XP20 Action Table for managing 8 input channels.
|
|
30
28
|
|
|
31
29
|
Contains configuration for 8 input channels (input1 through input8),
|
|
@@ -51,3 +49,95 @@ class Xp20MsActionTable(MsActionTable):
|
|
|
51
49
|
input6: InputChannel = Field(default_factory=InputChannel)
|
|
52
50
|
input7: InputChannel = Field(default_factory=InputChannel)
|
|
53
51
|
input8: InputChannel = Field(default_factory=InputChannel)
|
|
52
|
+
|
|
53
|
+
def to_short_format(self) -> list[str]:
|
|
54
|
+
"""Convert action table to short format string.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Short format string with each channel on a separate line.
|
|
58
|
+
Example:
|
|
59
|
+
CH1 I:0 S:0 G:0 AND:00000000 SA:0 TA:0
|
|
60
|
+
CH2 I:0 S:0 G:0 AND:00000000 SA:0 TA:0
|
|
61
|
+
...
|
|
62
|
+
"""
|
|
63
|
+
lines = []
|
|
64
|
+
for i in range(1, 9):
|
|
65
|
+
channel = getattr(self, f"input{i}")
|
|
66
|
+
# Convert and_functions list to binary string
|
|
67
|
+
and_bits = "".join("1" if bit else "0" for bit in channel.and_functions)
|
|
68
|
+
line = (
|
|
69
|
+
f"CH{i} "
|
|
70
|
+
f"I:{1 if channel.invert else 0} "
|
|
71
|
+
f"S:{1 if channel.short_long else 0} "
|
|
72
|
+
f"G:{1 if channel.group_on_off else 0} "
|
|
73
|
+
f"AND:{and_bits} "
|
|
74
|
+
f"SA:{1 if channel.sa_function else 0} "
|
|
75
|
+
f"TA:{1 if channel.ta_function else 0}"
|
|
76
|
+
)
|
|
77
|
+
lines.append(line)
|
|
78
|
+
return lines
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_short_format(cls, short_str: list[str]) -> "Xp20MsActionTable":
|
|
82
|
+
"""Parse short format string into action table.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
short_str: Short format string with 8 channel lines.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Xp20MsActionTable instance.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
ValueError: If format is invalid.
|
|
92
|
+
"""
|
|
93
|
+
import re
|
|
94
|
+
|
|
95
|
+
if len(short_str) != 8:
|
|
96
|
+
raise ValueError(f"Expected 8 channel lines, got {len(short_str)}")
|
|
97
|
+
|
|
98
|
+
pattern = re.compile(
|
|
99
|
+
r"^CH([1-8]) I:([01]) S:([01]) G:([01]) AND:([01]{8}) SA:([01]) TA:([01])$"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
channels = {}
|
|
103
|
+
for line in short_str:
|
|
104
|
+
line = line.strip()
|
|
105
|
+
match = pattern.match(line)
|
|
106
|
+
if not match:
|
|
107
|
+
raise ValueError(f"Invalid channel format: {line}")
|
|
108
|
+
|
|
109
|
+
ch_num = int(match.group(1))
|
|
110
|
+
invert = match.group(2) == "1"
|
|
111
|
+
short_long = match.group(3) == "1"
|
|
112
|
+
group_on_off = match.group(4) == "1"
|
|
113
|
+
and_bits = match.group(5)
|
|
114
|
+
sa_function = match.group(6) == "1"
|
|
115
|
+
ta_function = match.group(7) == "1"
|
|
116
|
+
|
|
117
|
+
# Convert binary string to list of bools
|
|
118
|
+
and_functions = [bit == "1" for bit in and_bits]
|
|
119
|
+
|
|
120
|
+
channels[ch_num] = InputChannel(
|
|
121
|
+
invert=invert,
|
|
122
|
+
short_long=short_long,
|
|
123
|
+
group_on_off=group_on_off,
|
|
124
|
+
and_functions=and_functions,
|
|
125
|
+
sa_function=sa_function,
|
|
126
|
+
ta_function=ta_function,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Verify all channels are present
|
|
130
|
+
for i in range(1, 9):
|
|
131
|
+
if i not in channels:
|
|
132
|
+
raise ValueError(f"Missing channel {i}")
|
|
133
|
+
|
|
134
|
+
return cls(
|
|
135
|
+
input1=channels[1],
|
|
136
|
+
input2=channels[2],
|
|
137
|
+
input3=channels[3],
|
|
138
|
+
input4=channels[4],
|
|
139
|
+
input5=channels[5],
|
|
140
|
+
input6=channels[6],
|
|
141
|
+
input7=channels[7],
|
|
142
|
+
input8=channels[8],
|
|
143
|
+
)
|
|
@@ -4,7 +4,6 @@ from typing import Any, ClassVar, Union
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
6
|
|
|
7
|
-
from xp.models.actiontable.msactiontable import MsActionTable
|
|
8
7
|
from xp.models.telegram.input_action_type import InputActionType
|
|
9
8
|
from xp.models.telegram.timeparam_type import TimeParam
|
|
10
9
|
|
|
@@ -82,7 +81,7 @@ class InputAction(BaseModel):
|
|
|
82
81
|
raise ValueError(f"Invalid type for TimeParam: {type(v)}")
|
|
83
82
|
|
|
84
83
|
|
|
85
|
-
class Xp24MsActionTable(
|
|
84
|
+
class Xp24MsActionTable(BaseModel):
|
|
86
85
|
"""XP24 Action Table for managing input actions and settings.
|
|
87
86
|
|
|
88
87
|
Each input has an action type (TOGGLE, ON, LEVELSET, etc.)
|
|
@@ -159,7 +158,7 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
159
158
|
curtain34: bool = False # Curtain setting for inputs 3-4
|
|
160
159
|
mutual_deadtime: int = MS300 # Master timing (MS300=12 or MS500=20)
|
|
161
160
|
|
|
162
|
-
def to_short_format(self) -> str:
|
|
161
|
+
def to_short_format(self) -> list[str]:
|
|
163
162
|
"""Convert action table to short format string.
|
|
164
163
|
|
|
165
164
|
Returns:
|
|
@@ -179,7 +178,7 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
179
178
|
param_value = action.param.value
|
|
180
179
|
action_parts.append(f"{short_code}:{param_value}")
|
|
181
180
|
|
|
182
|
-
result =
|
|
181
|
+
result = " ".join(action_parts)
|
|
183
182
|
|
|
184
183
|
# Add settings
|
|
185
184
|
settings = (
|
|
@@ -191,10 +190,10 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
191
190
|
)
|
|
192
191
|
result = f"{result} | {settings}"
|
|
193
192
|
|
|
194
|
-
return result
|
|
193
|
+
return [result]
|
|
195
194
|
|
|
196
195
|
@classmethod
|
|
197
|
-
def from_short_format(cls, short_str: str) -> "Xp24MsActionTable":
|
|
196
|
+
def from_short_format(cls, short_str: list[str]) -> "Xp24MsActionTable":
|
|
198
197
|
"""Parse short format string into action table.
|
|
199
198
|
|
|
200
199
|
Args:
|
|
@@ -207,20 +206,20 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
207
206
|
ValueError: If format is invalid.
|
|
208
207
|
"""
|
|
209
208
|
# Split by pipe to separate actions from settings
|
|
210
|
-
parts = short_str.split("|")
|
|
209
|
+
parts = short_str[0].split("|")
|
|
211
210
|
action_part = parts[0].strip()
|
|
212
211
|
settings_part = parts[1].strip()
|
|
213
212
|
|
|
214
213
|
# Parse action part
|
|
215
214
|
tokens = action_part.split()
|
|
216
|
-
if len(tokens) !=
|
|
215
|
+
if len(tokens) != 4:
|
|
217
216
|
raise ValueError(
|
|
218
|
-
f"Invalid short format: expected '
|
|
217
|
+
f"Invalid short format: expected '<a1> <a2> <a3> <a4>', got '{action_part}'"
|
|
219
218
|
)
|
|
220
219
|
|
|
221
220
|
# Parse input actions
|
|
222
221
|
input_actions = []
|
|
223
|
-
for i, token in enumerate(tokens[
|
|
222
|
+
for i, token in enumerate(tokens[0:4], 1):
|
|
224
223
|
if ":" not in token:
|
|
225
224
|
raise ValueError(f"Invalid action format at position {i}: '{token}'")
|
|
226
225
|
|
|
@@ -4,7 +4,6 @@ from typing import Union
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field, field_validator
|
|
6
6
|
|
|
7
|
-
from xp.models.actiontable.msactiontable import MsActionTable
|
|
8
7
|
from xp.models.telegram.timeparam_type import TimeParam
|
|
9
8
|
|
|
10
9
|
|
|
@@ -70,7 +69,7 @@ class Xp33Scene(BaseModel):
|
|
|
70
69
|
raise ValueError(f"Invalid type for TimeParam: {type(v)}")
|
|
71
70
|
|
|
72
71
|
|
|
73
|
-
class Xp33MsActionTable(
|
|
72
|
+
class Xp33MsActionTable(BaseModel):
|
|
74
73
|
"""XP33 Action Table for managing outputs and scenes.
|
|
75
74
|
|
|
76
75
|
Attributes:
|
|
@@ -91,3 +90,271 @@ class Xp33MsActionTable(MsActionTable):
|
|
|
91
90
|
scene2: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
92
91
|
scene3: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
93
92
|
scene4: Xp33Scene = Field(default_factory=Xp33Scene)
|
|
93
|
+
|
|
94
|
+
def to_short_format(self) -> list[str]:
|
|
95
|
+
"""Convert action table to short format string.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Short format string (multi-line format with OUT and SCENE lines).
|
|
99
|
+
"""
|
|
100
|
+
lines = []
|
|
101
|
+
|
|
102
|
+
# Format outputs
|
|
103
|
+
outputs = [
|
|
104
|
+
(1, self.output1),
|
|
105
|
+
(2, self.output2),
|
|
106
|
+
(3, self.output3),
|
|
107
|
+
]
|
|
108
|
+
for num, output in outputs:
|
|
109
|
+
lines.append(f"OUT{num} {self._format_output(output)}")
|
|
110
|
+
|
|
111
|
+
# Format scenes
|
|
112
|
+
scenes = [
|
|
113
|
+
(1, self.scene1),
|
|
114
|
+
(2, self.scene2),
|
|
115
|
+
(3, self.scene3),
|
|
116
|
+
(4, self.scene4),
|
|
117
|
+
]
|
|
118
|
+
for num, scene in scenes:
|
|
119
|
+
lines.append(f"SCENE{num} {self._format_scene(scene)}")
|
|
120
|
+
|
|
121
|
+
return lines
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_short_format(cls, short_str: list[str]) -> "Xp33MsActionTable":
|
|
125
|
+
"""Parse short format string into action table.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
short_str: Short format string (list of lines).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Xp33MsActionTable instance.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: If format is invalid.
|
|
135
|
+
"""
|
|
136
|
+
# Parse outputs and scenes from lines
|
|
137
|
+
outputs = {}
|
|
138
|
+
scenes = {}
|
|
139
|
+
|
|
140
|
+
for line in short_str:
|
|
141
|
+
line = line.strip()
|
|
142
|
+
if not line:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
if line.startswith("OUT"):
|
|
146
|
+
# Parse output line: OUT1 MIN:0 MAX:100 SO:0 SF:0 LE:0
|
|
147
|
+
parts = line.split(None, 1)
|
|
148
|
+
if len(parts) != 2:
|
|
149
|
+
raise ValueError(f"Invalid output line format: '{line}'")
|
|
150
|
+
|
|
151
|
+
out_key = parts[0] # OUT1, OUT2, OUT3
|
|
152
|
+
if not out_key.startswith("OUT"):
|
|
153
|
+
raise ValueError(f"Expected OUT prefix, got: '{out_key}'")
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
out_num = int(out_key[3:])
|
|
157
|
+
if out_num not in (1, 2, 3):
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"Invalid output number: {out_num}, expected 1-3"
|
|
160
|
+
)
|
|
161
|
+
except ValueError:
|
|
162
|
+
raise ValueError(f"Invalid output number in: '{out_key}'")
|
|
163
|
+
|
|
164
|
+
outputs[out_num] = cls._parse_output(parts[1])
|
|
165
|
+
|
|
166
|
+
elif line.startswith("SCENE"):
|
|
167
|
+
# Parse scene line: SCENE1 OUT1:0 OUT2:0 OUT3:0 T:NONE
|
|
168
|
+
parts = line.split(None, 1)
|
|
169
|
+
if len(parts) != 2:
|
|
170
|
+
raise ValueError(f"Invalid scene line format: '{line}'")
|
|
171
|
+
|
|
172
|
+
scene_key = parts[0] # SCENE1, SCENE2, etc.
|
|
173
|
+
if not scene_key.startswith("SCENE"):
|
|
174
|
+
raise ValueError(f"Expected SCENE prefix, got: '{scene_key}'")
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
scene_num = int(scene_key[5:])
|
|
178
|
+
if scene_num not in (1, 2, 3, 4):
|
|
179
|
+
raise ValueError(
|
|
180
|
+
f"Invalid scene number: {scene_num}, expected 1-4"
|
|
181
|
+
)
|
|
182
|
+
except ValueError:
|
|
183
|
+
raise ValueError(f"Invalid scene number in: '{scene_key}'")
|
|
184
|
+
|
|
185
|
+
scenes[scene_num] = cls._parse_scene(parts[1])
|
|
186
|
+
|
|
187
|
+
# Validate we have all required outputs and scenes
|
|
188
|
+
for i in (1, 2, 3):
|
|
189
|
+
if i not in outputs:
|
|
190
|
+
raise ValueError(f"Missing output{i} configuration")
|
|
191
|
+
|
|
192
|
+
for i in (1, 2, 3, 4):
|
|
193
|
+
if i not in scenes:
|
|
194
|
+
raise ValueError(f"Missing scene{i} configuration")
|
|
195
|
+
|
|
196
|
+
return cls(
|
|
197
|
+
output1=outputs[1],
|
|
198
|
+
output2=outputs[2],
|
|
199
|
+
output3=outputs[3],
|
|
200
|
+
scene1=scenes[1],
|
|
201
|
+
scene2=scenes[2],
|
|
202
|
+
scene3=scenes[3],
|
|
203
|
+
scene4=scenes[4],
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _format_output(output: Xp33Output) -> str:
|
|
208
|
+
"""Format output configuration to short string.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
output: Xp33Output instance.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Short string like "MIN:10 MAX:90 SO:1 SF:0 LE:1".
|
|
215
|
+
"""
|
|
216
|
+
return (
|
|
217
|
+
f"MIN:{output.min_level} "
|
|
218
|
+
f"MAX:{output.max_level} "
|
|
219
|
+
f"SO:{1 if output.scene_outputs else 0} "
|
|
220
|
+
f"SF:{1 if output.start_at_full else 0} "
|
|
221
|
+
f"LE:{1 if output.leading_edge else 0}"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _parse_output(output_str: str) -> Xp33Output:
|
|
226
|
+
"""Parse output configuration from short string.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
output_str: Short string like "MIN:10 MAX:90 SO:1 SF:0 LE:1".
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Xp33Output instance.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
ValueError: If format is invalid.
|
|
236
|
+
"""
|
|
237
|
+
# Parse key:value pairs
|
|
238
|
+
parts = output_str.split()
|
|
239
|
+
params = {}
|
|
240
|
+
|
|
241
|
+
for part in parts:
|
|
242
|
+
if ":" not in part:
|
|
243
|
+
raise ValueError(f"Invalid output parameter format: '{part}'")
|
|
244
|
+
|
|
245
|
+
key, value = part.split(":", 1)
|
|
246
|
+
params[key] = value
|
|
247
|
+
|
|
248
|
+
# Validate required keys
|
|
249
|
+
required_keys = ["MIN", "MAX", "SO", "SF", "LE"]
|
|
250
|
+
for key in required_keys:
|
|
251
|
+
if key not in params:
|
|
252
|
+
raise ValueError(f"Missing required parameter: {key}")
|
|
253
|
+
|
|
254
|
+
# Parse and validate values
|
|
255
|
+
try:
|
|
256
|
+
min_level = int(params["MIN"])
|
|
257
|
+
max_level = int(params["MAX"])
|
|
258
|
+
scene_outputs = params["SO"] == "1"
|
|
259
|
+
start_at_full = params["SF"] == "1"
|
|
260
|
+
leading_edge = params["LE"] == "1"
|
|
261
|
+
|
|
262
|
+
# Validate ranges
|
|
263
|
+
if not (0 <= min_level <= 100):
|
|
264
|
+
raise ValueError(f"MIN level out of range (0-100): {min_level}")
|
|
265
|
+
if not (0 <= max_level <= 100):
|
|
266
|
+
raise ValueError(f"MAX level out of range (0-100): {max_level}")
|
|
267
|
+
|
|
268
|
+
return Xp33Output(
|
|
269
|
+
min_level=min_level,
|
|
270
|
+
max_level=max_level,
|
|
271
|
+
scene_outputs=scene_outputs,
|
|
272
|
+
start_at_full=start_at_full,
|
|
273
|
+
leading_edge=leading_edge,
|
|
274
|
+
)
|
|
275
|
+
except ValueError as e:
|
|
276
|
+
raise ValueError(f"Invalid output parameter value: {e}")
|
|
277
|
+
|
|
278
|
+
@staticmethod
|
|
279
|
+
def _format_scene(scene: Xp33Scene) -> str:
|
|
280
|
+
"""Format scene configuration to short string.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
scene: Xp33Scene instance.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Short string like "OUT1:50 OUT2:60 OUT3:70 T:T5SEC".
|
|
287
|
+
"""
|
|
288
|
+
time_str = scene.time.name
|
|
289
|
+
return (
|
|
290
|
+
f"OUT1:{scene.output1_level} "
|
|
291
|
+
f"OUT2:{scene.output2_level} "
|
|
292
|
+
f"OUT3:{scene.output3_level} "
|
|
293
|
+
f"T:{time_str}"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _parse_scene(scene_str: str) -> Xp33Scene:
|
|
298
|
+
"""Parse scene configuration from short string.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
scene_str: Short string like "OUT1:50 OUT2:60 OUT3:70 T:T5SEC".
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Xp33Scene instance.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
ValueError: If format is invalid.
|
|
308
|
+
"""
|
|
309
|
+
# Parse key:value pairs
|
|
310
|
+
parts = scene_str.split()
|
|
311
|
+
params = {}
|
|
312
|
+
|
|
313
|
+
for part in parts:
|
|
314
|
+
if ":" not in part:
|
|
315
|
+
raise ValueError(f"Invalid scene parameter format: '{part}'")
|
|
316
|
+
|
|
317
|
+
key, value = part.split(":", 1)
|
|
318
|
+
params[key] = value
|
|
319
|
+
|
|
320
|
+
# Validate required keys
|
|
321
|
+
required_keys = ["OUT1", "OUT2", "OUT3", "T"]
|
|
322
|
+
for key in required_keys:
|
|
323
|
+
if key not in params:
|
|
324
|
+
raise ValueError(f"Missing required parameter: {key}")
|
|
325
|
+
|
|
326
|
+
# Parse and validate values
|
|
327
|
+
try:
|
|
328
|
+
output1_level = int(params["OUT1"])
|
|
329
|
+
output2_level = int(params["OUT2"])
|
|
330
|
+
output3_level = int(params["OUT3"])
|
|
331
|
+
|
|
332
|
+
# Validate ranges
|
|
333
|
+
if not (0 <= output1_level <= 100):
|
|
334
|
+
raise ValueError(f"OUT1 level out of range (0-100): {output1_level}")
|
|
335
|
+
if not (0 <= output2_level <= 100):
|
|
336
|
+
raise ValueError(f"OUT2 level out of range (0-100): {output2_level}")
|
|
337
|
+
if not (0 <= output3_level <= 100):
|
|
338
|
+
raise ValueError(f"OUT3 level out of range (0-100): {output3_level}")
|
|
339
|
+
|
|
340
|
+
# Parse time parameter - support both name and numeric value
|
|
341
|
+
time_str = params["T"]
|
|
342
|
+
try:
|
|
343
|
+
# Try parsing as enum name first
|
|
344
|
+
time_param = TimeParam[time_str]
|
|
345
|
+
except KeyError:
|
|
346
|
+
# Try parsing as numeric value
|
|
347
|
+
try:
|
|
348
|
+
time_value = int(time_str)
|
|
349
|
+
time_param = TimeParam(time_value)
|
|
350
|
+
except (ValueError, KeyError):
|
|
351
|
+
raise ValueError(f"Invalid TimeParam: '{time_str}'")
|
|
352
|
+
|
|
353
|
+
return Xp33Scene(
|
|
354
|
+
output1_level=output1_level,
|
|
355
|
+
output2_level=output2_level,
|
|
356
|
+
output3_level=output3_level,
|
|
357
|
+
time=time_param,
|
|
358
|
+
)
|
|
359
|
+
except ValueError as e:
|
|
360
|
+
raise ValueError(f"Invalid scene parameter value: {e}")
|
|
@@ -23,7 +23,9 @@ class ConsonModuleConfig(BaseModel):
|
|
|
23
23
|
sw_version: Optional software version.
|
|
24
24
|
hw_version: Optional hardware version.
|
|
25
25
|
action_table: Optional action table configuration.
|
|
26
|
-
|
|
26
|
+
xp20_msaction_table: Optional xp20 ms action table configuration.
|
|
27
|
+
xp24_msaction_table: Optional xp24 ms action table configuration.
|
|
28
|
+
xp33_msaction_table: Optional xp33 ms action table configuration.
|
|
27
29
|
auto_report_status: Optional auto report status.
|
|
28
30
|
"""
|
|
29
31
|
|
|
@@ -40,7 +42,9 @@ class ConsonModuleConfig(BaseModel):
|
|
|
40
42
|
hw_version: Optional[str] = None
|
|
41
43
|
auto_report_status: Optional[str] = None
|
|
42
44
|
action_table: Optional[List[str]] = None
|
|
43
|
-
|
|
45
|
+
xp20_msaction_table: Optional[List[str]] = None
|
|
46
|
+
xp24_msaction_table: Optional[List[str]] = None
|
|
47
|
+
xp33_msaction_table: Optional[List[str]] = None
|
|
44
48
|
|
|
45
49
|
|
|
46
50
|
class ConsonModuleListConfig(BaseModel):
|
|
@@ -16,7 +16,7 @@ class Xp20MsActionTableSerializer:
|
|
|
16
16
|
"""Handles serialization/deserialization of XP20 action tables to/from telegrams."""
|
|
17
17
|
|
|
18
18
|
@staticmethod
|
|
19
|
-
def format_decoded_output(action_table: Xp20MsActionTable) -> str:
|
|
19
|
+
def format_decoded_output(action_table: Xp20MsActionTable) -> list[str]:
|
|
20
20
|
"""Serialize XP20 action table to humane compact readable format.
|
|
21
21
|
|
|
22
22
|
Args:
|
|
@@ -25,7 +25,7 @@ class Xp20MsActionTableSerializer:
|
|
|
25
25
|
Returns:
|
|
26
26
|
Human-readable string describing XP20 action table
|
|
27
27
|
"""
|
|
28
|
-
return
|
|
28
|
+
return action_table.to_short_format()
|
|
29
29
|
|
|
30
30
|
@staticmethod
|
|
31
31
|
def to_data(action_table: Xp20MsActionTable) -> str:
|
|
@@ -10,7 +10,7 @@ class Xp24MsActionTableSerializer:
|
|
|
10
10
|
"""Handles serialization/deserialization of XP24 action tables to/from telegrams."""
|
|
11
11
|
|
|
12
12
|
@staticmethod
|
|
13
|
-
def format_decoded_output(action_table: Xp24MsActionTable) -> str:
|
|
13
|
+
def format_decoded_output(action_table: Xp24MsActionTable) -> list[str]:
|
|
14
14
|
"""Serialize XP24 action table to humane compact readable format.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
@@ -13,7 +13,7 @@ class Xp33MsActionTableSerializer:
|
|
|
13
13
|
"""Handles serialization/deserialization of XP33 action tables to/from telegrams."""
|
|
14
14
|
|
|
15
15
|
@staticmethod
|
|
16
|
-
def format_decoded_output(action_table: Xp33MsActionTable) -> str:
|
|
16
|
+
def format_decoded_output(action_table: Xp33MsActionTable) -> list[str]:
|
|
17
17
|
"""Serialize XP33 action table to humane compact readable format.
|
|
18
18
|
|
|
19
19
|
Args:
|
|
@@ -22,7 +22,7 @@ class Xp33MsActionTableSerializer:
|
|
|
22
22
|
Returns:
|
|
23
23
|
Human-readable string describing XP33 action table
|
|
24
24
|
"""
|
|
25
|
-
return
|
|
25
|
+
return action_table.to_short_format()
|
|
26
26
|
|
|
27
27
|
@staticmethod
|
|
28
28
|
def _percentage_to_byte(percentage: int) -> int:
|
|
@@ -5,7 +5,6 @@ from typing import Any, Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from psygnal import Signal
|
|
7
7
|
|
|
8
|
-
from xp.models.actiontable.msactiontable import MsActionTable
|
|
9
8
|
from xp.models.actiontable.msactiontable_xp20 import Xp20MsActionTable
|
|
10
9
|
from xp.models.actiontable.msactiontable_xp24 import Xp24MsActionTable
|
|
11
10
|
from xp.models.actiontable.msactiontable_xp33 import Xp33MsActionTable
|
|
@@ -42,12 +41,12 @@ class MsActionTableDownloadService:
|
|
|
42
41
|
conbus_protocol: Protocol instance for Conbus communication.
|
|
43
42
|
on_progress: Signal emitted for progress updates (str).
|
|
44
43
|
on_error: Signal emitted for errors (str).
|
|
45
|
-
on_finish: Signal emitted when download completes (
|
|
44
|
+
on_finish: Signal emitted when XP download completes (Xp20MsActionTable, str).
|
|
46
45
|
"""
|
|
47
46
|
|
|
48
47
|
on_progress: Signal = Signal(str)
|
|
49
48
|
on_error: Signal = Signal(str)
|
|
50
|
-
on_finish: Signal = Signal(
|
|
49
|
+
on_finish: Signal = Signal(object, list[str])
|
|
51
50
|
|
|
52
51
|
def __init__(
|
|
53
52
|
self,
|
|
@@ -188,7 +187,7 @@ class MsActionTableDownloadService:
|
|
|
188
187
|
def succeed(
|
|
189
188
|
self,
|
|
190
189
|
msactiontable: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
|
|
191
|
-
msactiontable_short: str,
|
|
190
|
+
msactiontable_short: list[str],
|
|
192
191
|
) -> None:
|
|
193
192
|
"""Handle succeed connection event.
|
|
194
193
|
|
|
@@ -196,6 +195,7 @@ class MsActionTableDownloadService:
|
|
|
196
195
|
msactiontable: result.
|
|
197
196
|
msactiontable_short: result in short form.
|
|
198
197
|
"""
|
|
198
|
+
# Emit to the appropriate signal based on module type
|
|
199
199
|
self.on_finish.emit(msactiontable, msactiontable_short)
|
|
200
200
|
|
|
201
201
|
def start(
|
|
@@ -72,7 +72,15 @@ class MsActionTableListService:
|
|
|
72
72
|
{
|
|
73
73
|
"serial_number": module.serial_number,
|
|
74
74
|
"module_type": module.module_type,
|
|
75
|
-
"msaction_table":
|
|
75
|
+
"msaction_table": (
|
|
76
|
+
1
|
|
77
|
+
if (
|
|
78
|
+
module.xp20_msaction_table
|
|
79
|
+
or module.xp24_msaction_table
|
|
80
|
+
or module.xp33_msaction_table
|
|
81
|
+
)
|
|
82
|
+
else 0
|
|
83
|
+
),
|
|
76
84
|
}
|
|
77
85
|
for module in config.root
|
|
78
86
|
]
|
|
@@ -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(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|