conson-xp 1.38.0__py3-none-any.whl → 1.40.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.38.0.dist-info → conson_xp-1.40.0.dist-info}/METADATA +1 -1
- {conson_xp-1.38.0.dist-info → conson_xp-1.40.0.dist-info}/RECORD +16 -19
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +72 -15
- xp/models/actiontable/msactiontable_xp20.py +93 -3
- xp/models/actiontable/msactiontable_xp24.py +33 -39
- 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/connection/__init__.py +0 -13
- xp/connection/exceptions.py +0 -22
- xp/models/actiontable/msactiontable.py +0 -9
- {conson_xp-1.38.0.dist-info → conson_xp-1.40.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.38.0.dist-info → conson_xp-1.40.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.38.0.dist-info → conson_xp-1.40.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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.40.0.dist-info/METADATA,sha256=FxnffMc3lu49w-4RKgg8x5f8wH3rl7rdjqCvr-BqhLI,11330
|
|
2
|
+
conson_xp-1.40.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.40.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.40.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=J-7i4_8TyVn5z8NeO8OcuNsvrQlrsttTuKcPk6MvDMo,181
|
|
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=9JNZRemAgUUaNkKPzDUgIY5jnv-hCK8-bM5XjLYUIjc,7808
|
|
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
|
|
@@ -55,15 +55,12 @@ xp/cli/utils/module_type_choice.py,sha256=TPIEDsO0fNDu2HOQQ16WCJ-a7o2f58j3IKd8B0
|
|
|
55
55
|
xp/cli/utils/serial_number_type.py,sha256=GUs3jtVI6EVulvt6fCDN6H6vxhiJwdMmdIvLjDlGGZ4,1466
|
|
56
56
|
xp/cli/utils/system_function_choice.py,sha256=0J02EMgAQcsrE-9rEkv6YHelBoBkZ73T8VLBSm6YO5k,1623
|
|
57
57
|
xp/cli/utils/xp_module_type.py,sha256=qSFJBRceqPi_cUFPxAWtLUNq37-KwUEjo9ekYOj7kLQ,1471
|
|
58
|
-
xp/connection/__init__.py,sha256=ClJsVWALYZgAGYZK_Jznd3YKLrHDu17kBfwugjuPfu0,209
|
|
59
|
-
xp/connection/exceptions.py,sha256=7CcRUzkyay5zA6Z9-5dIDRzua806v5N7pCcJazP_1dE,365
|
|
60
58
|
xp/models/__init__.py,sha256=lROqr559DGd8WpJJUtfPT95VERCwMZHpBDEc96QSxQ0,1312
|
|
61
59
|
xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
|
|
62
60
|
xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
|
|
63
|
-
xp/models/actiontable/
|
|
64
|
-
xp/models/actiontable/
|
|
65
|
-
xp/models/actiontable/
|
|
66
|
-
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=ePuw5sAwmnUWZoti_uadvG1E-d7XGmucPm3WW2dQP0c,9415
|
|
63
|
+
xp/models/actiontable/msactiontable_xp33.py,sha256=p_0HrvUmnqEEUlle7n0vpspGXFPrO5pXZeVF7n9K19g,11781
|
|
67
64
|
xp/models/conbus/__init__.py,sha256=VIusMWQdBtlwDgj7oSj06wQkklihTp4oWFShvP_JUgA,35
|
|
68
65
|
xp/models/conbus/conbus.py,sha256=mZQzKPfrdttT-qUnYUSyrEYyc_eHs8z301E5ejeiyvk,2689
|
|
69
66
|
xp/models/conbus/conbus_autoreport.py,sha256=lKotDfxRBb7h2Z1d4qI3KhhLJhFDwKqLbSdG5Makm8Y,2289
|
|
@@ -84,7 +81,7 @@ xp/models/conbus/conbus_raw.py,sha256=xqvYao6IE1SXum7JBgZpSuWXm9x_QZquS9N_3_r0Hj
|
|
|
84
81
|
xp/models/conbus/conbus_receive.py,sha256=-1u1qK-texfKCNZV-GYf_9KyLtJdIrx7HuZsKzu26Ow,1322
|
|
85
82
|
xp/models/conbus/conbus_writeconfig.py,sha256=z8fdJeFLyGJW7UMHcHxGrHIMS6gG1D3aXeYUkBuwnEg,2136
|
|
86
83
|
xp/models/config/__init__.py,sha256=gEZnX9eE3DjFtLtF32riEjJQLypqQRbyPauBI4Cowbs,36
|
|
87
|
-
xp/models/config/conson_module_config.py,sha256=
|
|
84
|
+
xp/models/config/conson_module_config.py,sha256=2uM1M4oKgQ3pkuEAEHHFMwZVGpWImJEe91TWgfS1ikU,2981
|
|
88
85
|
xp/models/homekit/__init__.py,sha256=5HDSOClCu0ArK3IICn3_LDMMLBAzLjBxUUSF73bxSSk,34
|
|
89
86
|
xp/models/homekit/homekit_accessory.py,sha256=NsHFhskuxIdJpF9-MvXHtjkLYvNHmSGFOy0GmQv3PY4,1038
|
|
90
87
|
xp/models/homekit/homekit_config.py,sha256=Y_k92PsKHFBnn3r1_RSZHJP5uLH27Gw8G7Bj5N8jvUE,2904
|
|
@@ -119,9 +116,9 @@ xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
|
|
|
119
116
|
xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
|
|
120
117
|
xp/services/actiontable/actiontable_serializer.py,sha256=U7bhd8lYMUJAsFydCt_Y5uOJoUODhSjRlUQPD6jsqMo,8517
|
|
121
118
|
xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
|
|
122
|
-
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=
|
|
123
|
-
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=
|
|
124
|
-
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
|
|
125
122
|
xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
|
|
126
123
|
xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
|
|
127
124
|
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=C6cjNRRsl7_jjn94I6ycCDvoqIpivNv0cMVkR-CQBXk,7608
|
|
@@ -142,8 +139,8 @@ xp/services/conbus/conbus_raw_service.py,sha256=FmUaF9o2nFZVP8LpabKIwkg0P8coLCke
|
|
|
142
139
|
xp/services/conbus/conbus_receive_service.py,sha256=7wOaEDrdoXwZE9MeUM89eB3hobYpvtbYk_YLv3MVAtc,5352
|
|
143
140
|
xp/services/conbus/conbus_scan_service.py,sha256=QN7_x8BtNbHnqG7akcooAAcjz9Ex2y3VNDdhShKHUX8,6824
|
|
144
141
|
xp/services/conbus/msactiontable/__init__.py,sha256=rDYzumPSfcTjDADHxjE7bXQoeWtZTDGaYzFTYdVl_9g,42
|
|
145
|
-
xp/services/conbus/msactiontable/msactiontable_download_service.py,sha256=
|
|
146
|
-
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
|
|
147
144
|
xp/services/conbus/msactiontable/msactiontable_show_service.py,sha256=pyoB5xN1bDh0E8kity4k0UnYpc7-YWhs8oIMvAeC9Xk,3023
|
|
148
145
|
xp/services/conbus/write_config_service.py,sha256=PQsN7rtTKHpwtAG8moLksUfRVqqE_0sxdE37meR1ZQ0,8935
|
|
149
146
|
xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
|
|
@@ -209,4 +206,4 @@ xp/utils/logging.py,sha256=rZDXwlBrYK8A6MPq5StsMNpgsRowzJXM6fvROPwJdGM,3750
|
|
|
209
206
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
210
207
|
xp/utils/state_machine.py,sha256=Oe2sLwCh9z_vr1tF6X0ZRGTeuckRQAGzmef7xc9CNdc,2413
|
|
211
208
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
212
|
-
conson_xp-1.
|
|
209
|
+
conson_xp-1.40.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -55,29 +55,30 @@ def conbus_download_msactiontable(
|
|
|
55
55
|
click.echo(progress, nl=False)
|
|
56
56
|
|
|
57
57
|
def on_finish(
|
|
58
|
-
msaction_table: Union[
|
|
59
|
-
|
|
60
|
-
],
|
|
61
|
-
msaction_table_short: str,
|
|
58
|
+
msaction_table: Union[Xp20MsActionTable, Xp24MsActionTable, Xp33MsActionTable],
|
|
59
|
+
msaction_table_short: list[str],
|
|
62
60
|
) -> None:
|
|
63
|
-
"""Handle successful completion of MS action table download.
|
|
61
|
+
"""Handle successful completion of XP24 MS action table download.
|
|
64
62
|
|
|
65
63
|
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.
|
|
64
|
+
msaction_table: Downloaded XP MS action table object.
|
|
65
|
+
msaction_table_short: Short version of XP24 MS action table.
|
|
71
66
|
"""
|
|
72
67
|
service.stop_reactor()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
|
|
69
|
+
# Format short representation based on module type
|
|
70
|
+
short_field_name = f"{xpmoduletype}_msaction_table"
|
|
71
|
+
# XP24 returns single-element list, XP20/XP33 return multi-line lists
|
|
72
|
+
short_value: Union[str, list[str]]
|
|
73
|
+
if len(msaction_table_short) == 1:
|
|
74
|
+
short_value = msaction_table_short[0]
|
|
75
|
+
else:
|
|
76
|
+
short_value = msaction_table_short
|
|
76
77
|
|
|
77
78
|
output = {
|
|
78
79
|
"serial_number": serial_number,
|
|
79
80
|
"xpmoduletype": xpmoduletype,
|
|
80
|
-
|
|
81
|
+
short_field_name: short_value,
|
|
81
82
|
"msaction_table": msaction_table.model_dump(),
|
|
82
83
|
}
|
|
83
84
|
click.echo(json.dumps(output, indent=2, default=str))
|
|
@@ -93,6 +94,8 @@ def conbus_download_msactiontable(
|
|
|
93
94
|
with service:
|
|
94
95
|
service.on_progress.connect(on_progress)
|
|
95
96
|
service.on_error.connect(on_error)
|
|
97
|
+
|
|
98
|
+
# Connect to the appropriate signal based on module type
|
|
96
99
|
service.on_finish.connect(on_finish)
|
|
97
100
|
service.start(
|
|
98
101
|
serial_number=serial_number,
|
|
@@ -155,9 +158,37 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
|
|
|
155
158
|
Args:
|
|
156
159
|
module: Dictionary containing module configuration.
|
|
157
160
|
"""
|
|
161
|
+
click.echo(f"\nModule: {module.name} ({module.serial_number})")
|
|
162
|
+
|
|
163
|
+
# Display short format if action table exists
|
|
164
|
+
if module.xp33_msaction_table:
|
|
165
|
+
click.echo("Short:")
|
|
166
|
+
for line in module.xp33_msaction_table:
|
|
167
|
+
click.echo(f" - {line}")
|
|
168
|
+
elif module.xp24_msaction_table:
|
|
169
|
+
click.echo("Short:")
|
|
170
|
+
for line in module.xp24_msaction_table:
|
|
171
|
+
click.echo(f" - {line}")
|
|
172
|
+
elif module.xp20_msaction_table:
|
|
173
|
+
click.echo("Short:")
|
|
174
|
+
for line in module.xp20_msaction_table:
|
|
175
|
+
click.echo(f" - {line}")
|
|
176
|
+
|
|
177
|
+
# Display full YAML format
|
|
178
|
+
click.echo("Full:")
|
|
158
179
|
module_data = module.model_dump()
|
|
159
180
|
module_data.pop("action_table", None)
|
|
160
|
-
|
|
181
|
+
|
|
182
|
+
# Show the action table in YAML format
|
|
183
|
+
if module.xp33_msaction_table:
|
|
184
|
+
yaml_dict = {"xp33_msaction_table": module_data}
|
|
185
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
186
|
+
elif module.xp24_msaction_table:
|
|
187
|
+
yaml_dict = {"xp24_msaction_table": module_data}
|
|
188
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
189
|
+
elif module.xp20_msaction_table:
|
|
190
|
+
yaml_dict = {"xp20_msaction_table": module_data}
|
|
191
|
+
click.echo(_format_yaml(yaml_dict, indent=2))
|
|
161
192
|
|
|
162
193
|
def error_callback(error: str) -> None:
|
|
163
194
|
"""Handle errors during action table show.
|
|
@@ -173,3 +204,29 @@ def conbus_show_msactiontable(ctx: Context, serial_number: str) -> None:
|
|
|
173
204
|
finish_callback=on_finish,
|
|
174
205
|
error_callback=error_callback,
|
|
175
206
|
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _format_yaml(data: dict, indent: int = 0) -> str:
|
|
210
|
+
"""Format a dictionary as YAML-like output.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
data: Dictionary to format.
|
|
214
|
+
indent: Current indentation level.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
YAML-like formatted string.
|
|
218
|
+
"""
|
|
219
|
+
lines: list[str] = []
|
|
220
|
+
for key, value in data.items():
|
|
221
|
+
if isinstance(value, dict):
|
|
222
|
+
lines.extend((f"{' ' * indent}{key}:", _format_yaml(value, indent + 2)))
|
|
223
|
+
elif isinstance(value, list):
|
|
224
|
+
lines.append(f"{' ' * indent}{key}:")
|
|
225
|
+
for item in value:
|
|
226
|
+
if isinstance(item, dict):
|
|
227
|
+
lines.append(_format_yaml(item, indent + 2))
|
|
228
|
+
else:
|
|
229
|
+
lines.append(f"{' ' * (indent + 2)}- {item}")
|
|
230
|
+
else:
|
|
231
|
+
lines.append(f"{' ' * indent}{key}: {value}")
|
|
232
|
+
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,14 +158,11 @@ 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
|
|
161
|
+
def to_short_format(self) -> list[str]:
|
|
163
162
|
"""Convert action table to short format string.
|
|
164
163
|
|
|
165
|
-
Args:
|
|
166
|
-
include_settings: Include settings after pipe separator.
|
|
167
|
-
|
|
168
164
|
Returns:
|
|
169
|
-
Short format string (e.g., "XP24 T:1 T:2 T:0 T:0").
|
|
165
|
+
Short format string with settings (e.g., "XP24 T:1 T:2 T:0 T:0 | M12:0 M34:0 C12:0 C34:0 DT:12").
|
|
170
166
|
"""
|
|
171
167
|
# Format input actions
|
|
172
168
|
actions = [
|
|
@@ -184,21 +180,20 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
184
180
|
|
|
185
181
|
result = f"XP24 {' '.join(action_parts)}"
|
|
186
182
|
|
|
187
|
-
# Add settings
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
result = f"{result} | {settings}"
|
|
183
|
+
# Add settings
|
|
184
|
+
settings = (
|
|
185
|
+
f"M12:{1 if self.mutex12 else 0} "
|
|
186
|
+
f"M34:{1 if self.mutex34 else 0} "
|
|
187
|
+
f"C12:{1 if self.curtain12 else 0} "
|
|
188
|
+
f"C34:{1 if self.curtain34 else 0} "
|
|
189
|
+
f"DT:{self.mutual_deadtime}"
|
|
190
|
+
)
|
|
191
|
+
result = f"{result} | {settings}"
|
|
197
192
|
|
|
198
|
-
return result
|
|
193
|
+
return [result]
|
|
199
194
|
|
|
200
195
|
@classmethod
|
|
201
|
-
def from_short_format(cls, short_str: str) -> "Xp24MsActionTable":
|
|
196
|
+
def from_short_format(cls, short_str: list[str]) -> "Xp24MsActionTable":
|
|
202
197
|
"""Parse short format string into action table.
|
|
203
198
|
|
|
204
199
|
Args:
|
|
@@ -211,9 +206,9 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
211
206
|
ValueError: If format is invalid.
|
|
212
207
|
"""
|
|
213
208
|
# Split by pipe to separate actions from settings
|
|
214
|
-
parts = short_str.split("|")
|
|
209
|
+
parts = short_str[0].split("|")
|
|
215
210
|
action_part = parts[0].strip()
|
|
216
|
-
settings_part = parts[1].strip()
|
|
211
|
+
settings_part = parts[1].strip()
|
|
217
212
|
|
|
218
213
|
# Parse action part
|
|
219
214
|
tokens = action_part.split()
|
|
@@ -253,23 +248,22 @@ class Xp24MsActionTable(MsActionTable):
|
|
|
253
248
|
"input4_action": input_actions[3],
|
|
254
249
|
}
|
|
255
250
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
kwargs["mutual_deadtime"] = int(value)
|
|
251
|
+
# Parse settings: M12:0 M34:1 C12:0 C34:1 DT:12
|
|
252
|
+
for setting in settings_part.split():
|
|
253
|
+
if ":" not in setting:
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
key, value = setting.split(":", 1)
|
|
257
|
+
|
|
258
|
+
if key == "M12":
|
|
259
|
+
kwargs["mutex12"] = value == "1"
|
|
260
|
+
elif key == "M34":
|
|
261
|
+
kwargs["mutex34"] = value == "1"
|
|
262
|
+
elif key == "C12":
|
|
263
|
+
kwargs["curtain12"] = value == "1"
|
|
264
|
+
elif key == "C34":
|
|
265
|
+
kwargs["curtain34"] = value == "1"
|
|
266
|
+
elif key == "DT":
|
|
267
|
+
kwargs["mutual_deadtime"] = int(value)
|
|
274
268
|
|
|
275
269
|
return cls(**kwargs)
|
|
@@ -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
|
]
|
xp/connection/__init__.py
DELETED
xp/connection/exceptions.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Connection-related exceptions for XP CLI tool.
|
|
2
|
-
|
|
3
|
-
Following the architecture requirement for structured error handling.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class XPError(Exception):
|
|
8
|
-
"""Base exception for XP CLI tool."""
|
|
9
|
-
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class ProtocolError(XPError):
|
|
14
|
-
"""Console bus protocol errors."""
|
|
15
|
-
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ValidationError(XPError):
|
|
20
|
-
"""Input validation errors."""
|
|
21
|
-
|
|
22
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|