conson-xp 1.9.0__py3-none-any.whl → 1.11.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.9.0.dist-info → conson_xp-1.11.0.dist-info}/METADATA +4 -1
- {conson_xp-1.9.0.dist-info → conson_xp-1.11.0.dist-info}/RECORD +16 -13
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_actiontable_commands.py +158 -1
- xp/models/actiontable/actiontable.py +1 -1
- xp/models/actiontable/msactiontable_xp24.py +1 -1
- xp/models/telegram/input_action_type.py +4 -4
- xp/services/actiontable/actiontable_serializer.py +99 -4
- xp/services/conbus/actiontable/{actiontable_service.py → actiontable_download_service.py} +3 -4
- xp/services/conbus/actiontable/actiontable_list_service.py +91 -0
- xp/services/conbus/actiontable/actiontable_show_service.py +89 -0
- xp/services/conbus/actiontable/actiontable_upload_service.py +211 -0
- xp/utils/dependencies.py +36 -1
- {conson_xp-1.9.0.dist-info → conson_xp-1.11.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.9.0.dist-info → conson_xp-1.11.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.9.0.dist-info → conson_xp-1.11.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.11.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -276,6 +276,9 @@ xp conbus
|
|
|
276
276
|
|
|
277
277
|
xp conbus actiontable
|
|
278
278
|
xp conbus actiontable download
|
|
279
|
+
xp conbus actiontable list
|
|
280
|
+
xp conbus actiontable show
|
|
281
|
+
xp conbus actiontable upload
|
|
279
282
|
|
|
280
283
|
|
|
281
284
|
xp conbus autoreport
|
|
@@ -1,14 +1,14 @@
|
|
|
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.11.0.dist-info/METADATA,sha256=fGKwcGHNxOkx1fqKtt_yiGvb2qdCzbv3mABmqZVQvZ0,9358
|
|
2
|
+
conson_xp-1.11.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
conson_xp-1.11.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.11.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=XSIJ1JlixN5N_iTzjciH-3DCu1QnEoPIUXf79udFESc,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=02CbZoKmNX-fn5etX4Hdgg2lUt1MsLFPYx2VkXZyFJ8,4394
|
|
9
9
|
xp/cli/commands/conbus/__init__.py,sha256=gE3K5OEoXkkZX8UOc2v3nreQQzwkOQi7n0VZ-Z2juXA,495
|
|
10
10
|
xp/cli/commands/conbus/conbus.py,sha256=OTebWu-V-_1tOq2nWExPLtDuAeqy7fB7ltUqzHfgcY8,2705
|
|
11
|
-
xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=
|
|
11
|
+
xp/cli/commands/conbus/conbus_actiontable_commands.py,sha256=cdjLV9cnm7teEOlu5Jf1MS_aL7lNy8KiDIyjCQa5Nzw,7138
|
|
12
12
|
xp/cli/commands/conbus/conbus_autoreport_commands.py,sha256=oZgyUUFNsb4yf2WO81l2w1PrasNwdC__QwxNkJ2jCaU,3794
|
|
13
13
|
xp/cli/commands/conbus/conbus_blink_commands.py,sha256=UK-Ey4K0FvaPQ96U0gyMid236RlBmUhPNRes9y0SlkM,4848
|
|
14
14
|
xp/cli/commands/conbus/conbus_config_commands.py,sha256=BugIbgNX6_s4MySGvI6tWZkwguciajHUX2Xz8XBux7k,716
|
|
@@ -52,9 +52,9 @@ xp/connection/__init__.py,sha256=ClJsVWALYZgAGYZK_Jznd3YKLrHDu17kBfwugjuPfu0,209
|
|
|
52
52
|
xp/connection/exceptions.py,sha256=7CcRUzkyay5zA6Z9-5dIDRzua806v5N7pCcJazP_1dE,365
|
|
53
53
|
xp/models/__init__.py,sha256=wCyJNKBd8J2ziOm0g00eUZH4OeTaLO5vHuoQGd_AJbg,1111
|
|
54
54
|
xp/models/actiontable/__init__.py,sha256=6kVq1rTOlpc24sZxGGVWkY48tqR42YWHLQHqakWqlPc,43
|
|
55
|
-
xp/models/actiontable/actiontable.py,sha256=
|
|
55
|
+
xp/models/actiontable/actiontable.py,sha256=bIeluZhMsvukkSwy2neaewavU8YR6Pso3PIvJ8ENlGg,1251
|
|
56
56
|
xp/models/actiontable/msactiontable_xp20.py,sha256=C_lYYIQagEFap0S5S40_S7AhLO2UZG2EmXjjeem7uw8,1967
|
|
57
|
-
xp/models/actiontable/msactiontable_xp24.py,sha256=
|
|
57
|
+
xp/models/actiontable/msactiontable_xp24.py,sha256=ne1dC6CA-5GoTnsLCr6Faue_PmSwC7vtUnX4NSRrQ_Y,2089
|
|
58
58
|
xp/models/actiontable/msactiontable_xp33.py,sha256=2IEA0CBPvnatOueBPZiV0DPc7YFzTQIqIMqed8TKXeM,1932
|
|
59
59
|
xp/models/conbus/__init__.py,sha256=VIusMWQdBtlwDgj7oSj06wQkklihTp4oWFShvP_JUgA,35
|
|
60
60
|
xp/models/conbus/conbus.py,sha256=mZQzKPfrdttT-qUnYUSyrEYyc_eHs8z301E5ejeiyvk,2689
|
|
@@ -84,7 +84,7 @@ xp/models/telegram/action_type.py,sha256=vkw_chTgmsadksGXvZ9D_qYGpjOwCw-OgbEi8Bm
|
|
|
84
84
|
xp/models/telegram/datapoint_type.py,sha256=clmgqCsTNKuHmWN6ol2Hwj_71I10f36Oq-S5D5ZA9a8,2942
|
|
85
85
|
xp/models/telegram/event_telegram.py,sha256=FCCfyZXQEUPB6Uo1m7B9nvFCJ0Ipv2CApmImAZo_Xa4,4689
|
|
86
86
|
xp/models/telegram/event_type.py,sha256=VZhaDpey7KYWnmwN-gstj-r4Vd5hiGdzQuRazUdixB8,333
|
|
87
|
-
xp/models/telegram/input_action_type.py,sha256=
|
|
87
|
+
xp/models/telegram/input_action_type.py,sha256=EDYtE4uxByUyGsZTkXxwN9rQxCAejWk08_kv-7COurM,1852
|
|
88
88
|
xp/models/telegram/input_type.py,sha256=X3AcKKMNHswNZs5xgT_AnxeKQpSx_U7ctGnr6AYqNoU,491
|
|
89
89
|
xp/models/telegram/module_type.py,sha256=TdrcQC3UcdESzyUmS9PaVeJuF5VxH1WUtDWdf4QRA50,5223
|
|
90
90
|
xp/models/telegram/module_type_code.py,sha256=bg8Zi58yKs5DDnEF0bGnZ9vvpqzmIZzd1k9Wu4ufB-Y,8177
|
|
@@ -98,14 +98,17 @@ xp/models/telegram/timeparam_type.py,sha256=Ar8xvSfPmOAgR2g2Je0FgvP01SL7bPvZn5_H
|
|
|
98
98
|
xp/models/write_config_type.py,sha256=T2RaO52RpzoJ4782uMHE-fX7Ymx3CaIQAEwByydXq1M,881
|
|
99
99
|
xp/services/__init__.py,sha256=W9YZyrkh7vm--ZHhAXNQiOYQs5yhhmUHXP5I0Lf1XBg,782
|
|
100
100
|
xp/services/actiontable/__init__.py,sha256=z6js4EuJ6xKHaseTEhuEvKo1tr9K1XyQiruReJtBiPY,26
|
|
101
|
-
xp/services/actiontable/actiontable_serializer.py,sha256=
|
|
101
|
+
xp/services/actiontable/actiontable_serializer.py,sha256=U7bhd8lYMUJAsFydCt_Y5uOJoUODhSjRlUQPD6jsqMo,8517
|
|
102
102
|
xp/services/actiontable/msactiontable_serializer.py,sha256=RRL6TZ1gpSQw81kAiw2BV3jTqm4fCJC0pWIcO26Cmos,174
|
|
103
103
|
xp/services/actiontable/msactiontable_xp20_serializer.py,sha256=3Lz6t3uRYhoeMRhjDAO1XuWPJzH-ML13t05UQLFUW-s,6057
|
|
104
104
|
xp/services/actiontable/msactiontable_xp24_serializer.py,sha256=zdKzcrKqD41POqj_1c4B4why_Jp9mNXncajsnXXBtPw,4215
|
|
105
105
|
xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=xoZBA38pBSUPA9nn7HgaH1ZM5sR2heQbJ6JVlPVbzUY,8400
|
|
106
106
|
xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
|
|
107
107
|
xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
|
|
108
|
-
xp/services/conbus/actiontable/
|
|
108
|
+
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=x9k5VlVjvsAJi4McDGqErLaBE_dosV5uMSrNF_r6ic0,6013
|
|
109
|
+
xp/services/conbus/actiontable/actiontable_list_service.py,sha256=6izVZkM2hlWXUMUo1NkNzSMvPo0wFfDxYTADQBXQptU,3000
|
|
110
|
+
xp/services/conbus/actiontable/actiontable_show_service.py,sha256=jqNZ4UvZPHH66OYuryjnU1Km-a83OCwYvK0vc56oL8I,3017
|
|
111
|
+
xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=txhMumjcIHPI4TZk6CERhjyyTKUNhUb7fdSmaylYC48,8189
|
|
109
112
|
xp/services/conbus/actiontable/msactiontable_service.py,sha256=K0TiYL8g4ac8BS1tqS0UAIYJigOlNhxVLIb8ZFybnVE,8393
|
|
110
113
|
xp/services/conbus/conbus_blink_all_service.py,sha256=OaEg4b8AEiEruHSkZ5jDtaoI81vwwxLq4KWXO7zBdD0,6582
|
|
111
114
|
xp/services/conbus/conbus_blink_service.py,sha256=x9uM-sLnIEV8wSNsvJgo08E042g-Hh2ZF3rXkz-k_9s,5824
|
|
@@ -160,8 +163,8 @@ xp/services/telegram/telegram_service.py,sha256=XrP1CPi0ckxoKBaNwLA6lo-TogWxXgmX
|
|
|
160
163
|
xp/services/telegram/telegram_version_service.py,sha256=M5HdOTsLdcwo122FP-jW6R740ktLrtKf2TiMDVz23h8,10528
|
|
161
164
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
162
165
|
xp/utils/checksum.py,sha256=HDpiQxmdIedbCbZ4o_Box0i_Zig417BtCV_46ZyhiTk,1711
|
|
163
|
-
xp/utils/dependencies.py,sha256=
|
|
166
|
+
xp/utils/dependencies.py,sha256=xUmk4XWGArR5__kHdEG1Y4K_dHxsP5mkXhwon_SW6Eo,20110
|
|
164
167
|
xp/utils/event_helper.py,sha256=W-A_xmoXlpWZBbJH6qdaN50o3-XrwFsDgvAGMJDiAgo,1001
|
|
165
168
|
xp/utils/serialization.py,sha256=RWHHk86feaB4ZP7rjE4qOWK0900yg2joUBDkP76gfOY,4618
|
|
166
169
|
xp/utils/time_utils.py,sha256=dEyViDlAG9GWU-J3D_YVa-sGma6yiyyMTgN4h2x3PY4,3781
|
|
167
|
-
conson_xp-1.
|
|
170
|
+
conson_xp-1.11.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""ActionTable CLI commands."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
from typing import Any
|
|
5
7
|
|
|
6
8
|
import click
|
|
@@ -12,9 +14,28 @@ from xp.cli.utils.decorators import (
|
|
|
12
14
|
)
|
|
13
15
|
from xp.cli.utils.serial_number_type import SERIAL
|
|
14
16
|
from xp.models.actiontable.actiontable import ActionTable
|
|
15
|
-
from xp.
|
|
17
|
+
from xp.models.homekit.homekit_conson_config import (
|
|
18
|
+
ConsonModuleConfig,
|
|
19
|
+
ConsonModuleListConfig,
|
|
20
|
+
)
|
|
21
|
+
from xp.services.conbus.actiontable.actiontable_download_service import (
|
|
16
22
|
ActionTableService,
|
|
17
23
|
)
|
|
24
|
+
from xp.services.conbus.actiontable.actiontable_list_service import (
|
|
25
|
+
ActionTableListService,
|
|
26
|
+
)
|
|
27
|
+
from xp.services.conbus.actiontable.actiontable_show_service import (
|
|
28
|
+
ActionTableShowService,
|
|
29
|
+
)
|
|
30
|
+
from xp.services.conbus.actiontable.actiontable_upload_service import (
|
|
31
|
+
ActionTableUploadService,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ActionTableError(Exception):
|
|
36
|
+
"""Raised when ActionTable operations fail."""
|
|
37
|
+
|
|
38
|
+
pass
|
|
18
39
|
|
|
19
40
|
|
|
20
41
|
@conbus_actiontable.command("download", short_help="Download ActionTable")
|
|
@@ -74,3 +95,139 @@ def conbus_download_actiontable(ctx: Context, serial_number: str) -> None:
|
|
|
74
95
|
finish_callback=on_finish,
|
|
75
96
|
error_callback=error_callback,
|
|
76
97
|
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@conbus_actiontable.command("upload", short_help="Upload ActionTable")
|
|
101
|
+
@click.argument("serial_number", type=SERIAL)
|
|
102
|
+
@click.pass_context
|
|
103
|
+
@connection_command()
|
|
104
|
+
def conbus_upload_actiontable(ctx: Context, serial_number: str) -> None:
|
|
105
|
+
"""Upload action table from conson.yml to XP module.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
ctx: Click context object.
|
|
109
|
+
serial_number: 10-digit module serial number.
|
|
110
|
+
"""
|
|
111
|
+
service: ActionTableUploadService = (
|
|
112
|
+
ctx.obj.get("container").get_container().resolve(ActionTableUploadService)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
click.echo(f"Uploading action table to {serial_number}...")
|
|
116
|
+
|
|
117
|
+
# Track number of entries for success message
|
|
118
|
+
entries_count = 0
|
|
119
|
+
|
|
120
|
+
def progress_callback(progress: str) -> None:
|
|
121
|
+
"""Handle progress updates during action table upload.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
progress: Progress message string.
|
|
125
|
+
"""
|
|
126
|
+
click.echo(progress, nl=False)
|
|
127
|
+
|
|
128
|
+
def success_callback() -> None:
|
|
129
|
+
"""Handle successful completion of action table upload."""
|
|
130
|
+
click.echo("\nAction table uploaded successfully")
|
|
131
|
+
if entries_count > 0:
|
|
132
|
+
click.echo(f"{entries_count} entries written")
|
|
133
|
+
|
|
134
|
+
def error_callback(error: str) -> None:
|
|
135
|
+
"""Handle errors during action table upload.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
error: Error message string.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ActionTableError: Always raised with upload failure message.
|
|
142
|
+
"""
|
|
143
|
+
raise ActionTableError(f"Upload failed: {error}")
|
|
144
|
+
|
|
145
|
+
with service:
|
|
146
|
+
# Load config to get entry count for success message
|
|
147
|
+
config_path = Path.cwd() / "conson.yml"
|
|
148
|
+
if config_path.exists():
|
|
149
|
+
with suppress(Exception):
|
|
150
|
+
config = ConsonModuleListConfig.from_yaml(str(config_path))
|
|
151
|
+
module = config.find_module(serial_number)
|
|
152
|
+
if module and module.action_table:
|
|
153
|
+
entries_count = len(module.action_table)
|
|
154
|
+
|
|
155
|
+
service.start(
|
|
156
|
+
serial_number=serial_number,
|
|
157
|
+
progress_callback=progress_callback,
|
|
158
|
+
success_callback=success_callback,
|
|
159
|
+
error_callback=error_callback,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@conbus_actiontable.command("list", short_help="List modules with ActionTable")
|
|
164
|
+
@click.pass_context
|
|
165
|
+
def conbus_list_actiontable(ctx: Context) -> None:
|
|
166
|
+
"""List all modules with action table configurations from conson.yml.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
ctx: Click context object.
|
|
170
|
+
"""
|
|
171
|
+
service: ActionTableListService = (
|
|
172
|
+
ctx.obj.get("container").get_container().resolve(ActionTableListService)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def on_finish(module_list: dict) -> None:
|
|
176
|
+
"""Handle successful completion of action table list.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
module_list: Dictionary containing modules and total count.
|
|
180
|
+
"""
|
|
181
|
+
click.echo(json.dumps(module_list, indent=2, default=str))
|
|
182
|
+
|
|
183
|
+
def error_callback(error: str) -> None:
|
|
184
|
+
"""Handle errors during action table list.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
error: Error message string.
|
|
188
|
+
"""
|
|
189
|
+
click.echo(error)
|
|
190
|
+
|
|
191
|
+
with service:
|
|
192
|
+
service.start(
|
|
193
|
+
finish_callback=on_finish,
|
|
194
|
+
error_callback=error_callback,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@conbus_actiontable.command("show", short_help="Show ActionTable configuration")
|
|
199
|
+
@click.argument("serial_number", type=SERIAL)
|
|
200
|
+
@click.pass_context
|
|
201
|
+
def conbus_show_actiontable(ctx: Context, serial_number: str) -> None:
|
|
202
|
+
"""Show action table configuration for a specific module from conson.yml.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
ctx: Click context object.
|
|
206
|
+
serial_number: 10-digit module serial number.
|
|
207
|
+
"""
|
|
208
|
+
service: ActionTableShowService = (
|
|
209
|
+
ctx.obj.get("container").get_container().resolve(ActionTableShowService)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def on_finish(module: ConsonModuleConfig) -> None:
|
|
213
|
+
"""Handle successful completion of action table show.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
module: Dictionary containing module configuration.
|
|
217
|
+
"""
|
|
218
|
+
click.echo(json.dumps(module.model_dump(), indent=2, default=str))
|
|
219
|
+
|
|
220
|
+
def error_callback(error: str) -> None:
|
|
221
|
+
"""Handle errors during action table show.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
error: Error message string.
|
|
225
|
+
"""
|
|
226
|
+
click.echo(error)
|
|
227
|
+
|
|
228
|
+
with service:
|
|
229
|
+
service.start(
|
|
230
|
+
serial_number=serial_number,
|
|
231
|
+
finish_callback=on_finish,
|
|
232
|
+
error_callback=error_callback,
|
|
233
|
+
)
|
|
@@ -27,7 +27,7 @@ class ActionTableEntry:
|
|
|
27
27
|
link_number: int = 0
|
|
28
28
|
module_input: int = 0
|
|
29
29
|
module_output: int = 1
|
|
30
|
-
command: InputActionType = InputActionType.
|
|
30
|
+
command: InputActionType = InputActionType.OFF
|
|
31
31
|
parameter: TimeParam = TimeParam.NONE
|
|
32
32
|
inverted: bool = False
|
|
33
33
|
|
|
@@ -23,7 +23,7 @@ class InputAction:
|
|
|
23
23
|
class Xp24MsActionTable:
|
|
24
24
|
"""XP24 Action Table for managing input actions and settings.
|
|
25
25
|
|
|
26
|
-
Each input has an action type (TOGGLE,
|
|
26
|
+
Each input has an action type (TOGGLE, ON, LEVELSET, etc.)
|
|
27
27
|
with an optional parameter string.
|
|
28
28
|
|
|
29
29
|
Attributes:
|
|
@@ -8,8 +8,8 @@ class InputActionType(Enum):
|
|
|
8
8
|
|
|
9
9
|
Attributes:
|
|
10
10
|
VOID: No action.
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
ON: Turn on action.
|
|
12
|
+
OFF: Turn off action.
|
|
13
13
|
TOGGLE: Toggle action.
|
|
14
14
|
BLOCK: Block action.
|
|
15
15
|
AUXRELAY: Auxiliary relay action.
|
|
@@ -39,8 +39,8 @@ class InputActionType(Enum):
|
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
VOID = 0
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
ON = 1
|
|
43
|
+
OFF = 2
|
|
44
44
|
TOGGLE = 3
|
|
45
45
|
BLOCK = 4
|
|
46
46
|
AUXRELAY = 5
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Serializer for ActionTable telegram encoding/decoding."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
4
|
+
|
|
3
5
|
from xp.models import ModuleTypeCode
|
|
4
6
|
from xp.models.actiontable.actiontable import ActionTable, ActionTableEntry
|
|
5
7
|
from xp.models.telegram.input_action_type import InputActionType
|
|
@@ -18,7 +20,13 @@ from xp.utils.serialization import (
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ActionTableSerializer:
|
|
21
|
-
"""Handles serialization/deserialization of ActionTable to/from telegrams.
|
|
23
|
+
"""Handles serialization/deserialization of ActionTable to/from telegrams.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
MAX_ENTRIES: Maximum number of entries in an ActionTable (96).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
MAX_ENTRIES = 96 # ActionTable must always contain exactly 96 entries
|
|
22
30
|
|
|
23
31
|
@staticmethod
|
|
24
32
|
def from_data(data: bytes) -> ActionTable:
|
|
@@ -57,12 +65,12 @@ class ActionTableSerializer:
|
|
|
57
65
|
try:
|
|
58
66
|
module_type = ModuleTypeCode(module_type_raw)
|
|
59
67
|
except ValueError:
|
|
60
|
-
module_type = ModuleTypeCode.
|
|
68
|
+
module_type = ModuleTypeCode.NOMOD # Default fallback
|
|
61
69
|
|
|
62
70
|
try:
|
|
63
71
|
command = InputActionType(command_raw)
|
|
64
72
|
except ValueError:
|
|
65
|
-
command = InputActionType.
|
|
73
|
+
command = InputActionType.OFF # Default fallback
|
|
66
74
|
|
|
67
75
|
try:
|
|
68
76
|
parameter = TimeParam(parameter_raw)
|
|
@@ -91,7 +99,7 @@ class ActionTableSerializer:
|
|
|
91
99
|
action_table: ActionTable to serialize
|
|
92
100
|
|
|
93
101
|
Returns:
|
|
94
|
-
Raw byte data for telegram
|
|
102
|
+
Raw byte data for telegram (always 480 bytes for 96 entries)
|
|
95
103
|
"""
|
|
96
104
|
data = bytearray()
|
|
97
105
|
|
|
@@ -112,6 +120,14 @@ class ActionTableSerializer:
|
|
|
112
120
|
[type_byte, link_byte, input_byte, output_command_byte, parameter_byte]
|
|
113
121
|
)
|
|
114
122
|
|
|
123
|
+
# Pad to 96 entries with default NOMOD entries (00 00 00 00 00)
|
|
124
|
+
current_entries = len(action_table.entries)
|
|
125
|
+
if current_entries < ActionTableSerializer.MAX_ENTRIES:
|
|
126
|
+
# Default entry: NOMOD 0 0 > 0 OFF (all zeros)
|
|
127
|
+
padding_bytes = [0x00, 0x00, 0x00, 0x00, 0x00]
|
|
128
|
+
for _ in range(ActionTableSerializer.MAX_ENTRIES - current_entries):
|
|
129
|
+
data.extend(padding_bytes)
|
|
130
|
+
|
|
115
131
|
return bytes(data)
|
|
116
132
|
|
|
117
133
|
@staticmethod
|
|
@@ -176,3 +192,82 @@ class ActionTableSerializer:
|
|
|
176
192
|
lines.append(line)
|
|
177
193
|
|
|
178
194
|
return lines
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def parse_action_string(action_str: str) -> ActionTableEntry:
|
|
198
|
+
"""Parse action table entry from string format.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
action_str: String in format "CP20 0 0 > 1 OFF" or "CP20 0 1 > 1 ~ON"
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Parsed ActionTableEntry
|
|
205
|
+
|
|
206
|
+
Raises:
|
|
207
|
+
ValueError: If string format is invalid
|
|
208
|
+
"""
|
|
209
|
+
# Remove trailing semicolon if present
|
|
210
|
+
action_str = action_str.strip().rstrip(";")
|
|
211
|
+
|
|
212
|
+
# Pattern: <Type> <Link> <Input> > <Output> <Command> [Parameter]
|
|
213
|
+
pattern = r"^(\w+)\s+(\d+)\s+(\d+)\s+>\s+(\d+)\s+(~?)(\w+)(?:\s+(\d+))?$"
|
|
214
|
+
match = re.match(pattern, action_str)
|
|
215
|
+
|
|
216
|
+
if not match:
|
|
217
|
+
raise ValueError(f"Invalid action table format: {action_str}")
|
|
218
|
+
|
|
219
|
+
(
|
|
220
|
+
module_type_str,
|
|
221
|
+
link_str,
|
|
222
|
+
input_str,
|
|
223
|
+
output_str,
|
|
224
|
+
inverted_str,
|
|
225
|
+
command_str,
|
|
226
|
+
parameter_str,
|
|
227
|
+
) = match.groups()
|
|
228
|
+
|
|
229
|
+
# Parse module type
|
|
230
|
+
try:
|
|
231
|
+
module_type = ModuleTypeCode[module_type_str]
|
|
232
|
+
except KeyError:
|
|
233
|
+
raise ValueError(f"Invalid module type: {module_type_str}")
|
|
234
|
+
|
|
235
|
+
# Parse command
|
|
236
|
+
try:
|
|
237
|
+
command = InputActionType[command_str]
|
|
238
|
+
except KeyError:
|
|
239
|
+
raise ValueError(f"Invalid command: {command_str}")
|
|
240
|
+
|
|
241
|
+
# Parse parameter (default to NONE)
|
|
242
|
+
parameter = TimeParam.NONE
|
|
243
|
+
if parameter_str:
|
|
244
|
+
try:
|
|
245
|
+
parameter = TimeParam(int(parameter_str))
|
|
246
|
+
except ValueError:
|
|
247
|
+
raise ValueError(f"Invalid parameter: {parameter_str}")
|
|
248
|
+
|
|
249
|
+
return ActionTableEntry(
|
|
250
|
+
module_type=module_type,
|
|
251
|
+
link_number=int(link_str),
|
|
252
|
+
module_input=int(input_str),
|
|
253
|
+
module_output=int(output_str),
|
|
254
|
+
command=command,
|
|
255
|
+
parameter=parameter,
|
|
256
|
+
inverted=bool(inverted_str),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def parse_action_table(action_strings: list[str]) -> ActionTable:
|
|
261
|
+
"""Parse action table from list of string entries.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
action_strings: List of action strings from conson.yml
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Parsed ActionTable
|
|
268
|
+
"""
|
|
269
|
+
entries = [
|
|
270
|
+
ActionTableSerializer.parse_action_string(action_str)
|
|
271
|
+
for action_str in action_strings
|
|
272
|
+
]
|
|
273
|
+
return ActionTable(entries=entries)
|
|
@@ -17,11 +17,10 @@ from xp.services.telegram.telegram_service import TelegramService
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class ActionTableService(ConbusProtocol):
|
|
20
|
-
"""
|
|
21
|
-
TCP client service for sending telegrams to Conbus servers.
|
|
20
|
+
"""TCP client service for downloading action tables from Conbus modules.
|
|
22
21
|
|
|
23
22
|
Manages TCP socket connections, handles telegram generation and transmission,
|
|
24
|
-
and processes server responses.
|
|
23
|
+
and processes server responses for action table downloads.
|
|
25
24
|
"""
|
|
26
25
|
|
|
27
26
|
def __init__(
|
|
@@ -31,7 +30,7 @@ class ActionTableService(ConbusProtocol):
|
|
|
31
30
|
actiontable_serializer: ActionTableSerializer,
|
|
32
31
|
telegram_service: TelegramService,
|
|
33
32
|
) -> None:
|
|
34
|
-
"""Initialize the
|
|
33
|
+
"""Initialize the action table download service.
|
|
35
34
|
|
|
36
35
|
Args:
|
|
37
36
|
cli_config: Conbus client configuration.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Service for listing modules with action table configurations from conson.yml."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Callable, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActionTableListService:
|
|
9
|
+
"""Service for listing modules with action table configurations.
|
|
10
|
+
|
|
11
|
+
Reads conson.yml and returns a list of all modules that have action table
|
|
12
|
+
configurations defined.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
"""Initialize the action table list service."""
|
|
17
|
+
self.logger = logging.getLogger(__name__)
|
|
18
|
+
self.finish_callback: Optional[Callable[[dict[str, Any]], None]] = None
|
|
19
|
+
self.error_callback: Optional[Callable[[str], None]] = None
|
|
20
|
+
|
|
21
|
+
def __enter__(self) -> "ActionTableListService":
|
|
22
|
+
"""Context manager entry.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Self for context manager use.
|
|
26
|
+
"""
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
|
|
30
|
+
"""Context manager exit."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def start(
|
|
34
|
+
self,
|
|
35
|
+
finish_callback: Callable[[dict[str, Any]], None],
|
|
36
|
+
error_callback: Callable[[str], None],
|
|
37
|
+
config_path: Optional[Path] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""List all modules with action table configurations.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
finish_callback: Callback to invoke with the module list.
|
|
43
|
+
error_callback: Callback to invoke on error.
|
|
44
|
+
config_path: Optional path to conson.yml. Defaults to current directory.
|
|
45
|
+
"""
|
|
46
|
+
self.finish_callback = finish_callback
|
|
47
|
+
self.error_callback = error_callback
|
|
48
|
+
|
|
49
|
+
# Default to current directory if not specified
|
|
50
|
+
if config_path is None:
|
|
51
|
+
config_path = Path.cwd() / "conson.yml"
|
|
52
|
+
|
|
53
|
+
# Check if config file exists
|
|
54
|
+
if not config_path.exists():
|
|
55
|
+
self._handle_error("Error: conson.yml not found in current directory")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Load configuration
|
|
59
|
+
try:
|
|
60
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
61
|
+
|
|
62
|
+
config = ConsonModuleListConfig.from_yaml(str(config_path))
|
|
63
|
+
except Exception as e:
|
|
64
|
+
self.logger.error(f"Failed to load conson.yml: {e}")
|
|
65
|
+
self._handle_error(f"Error: Failed to load conson.yml: {e}")
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Filter modules that have action_table configured
|
|
69
|
+
modules_with_actiontable = [
|
|
70
|
+
{
|
|
71
|
+
"serial_number": module.serial_number,
|
|
72
|
+
"module_type": module.module_type,
|
|
73
|
+
}
|
|
74
|
+
for module in config.root
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Prepare result
|
|
78
|
+
result = {"modules": modules_with_actiontable}
|
|
79
|
+
|
|
80
|
+
# Invoke callback
|
|
81
|
+
if self.finish_callback is not None:
|
|
82
|
+
self.finish_callback(result)
|
|
83
|
+
|
|
84
|
+
def _handle_error(self, message: str) -> None:
|
|
85
|
+
"""Handle error and invoke error callback.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
message: Error message.
|
|
89
|
+
"""
|
|
90
|
+
if self.error_callback is not None:
|
|
91
|
+
self.error_callback(message)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Service for showing action table configuration for a specific module."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Callable, Optional
|
|
6
|
+
|
|
7
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleConfig
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ActionTableShowService:
|
|
11
|
+
"""Service for showing action table configuration for a specific module.
|
|
12
|
+
|
|
13
|
+
Reads conson.yml and returns the action table configuration for the specified
|
|
14
|
+
module serial number.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""Initialize the action table show service."""
|
|
19
|
+
self.logger = logging.getLogger(__name__)
|
|
20
|
+
self.finish_callback: Optional[Callable[[ConsonModuleConfig], None]] = None
|
|
21
|
+
self.error_callback: Optional[Callable[[str], None]] = None
|
|
22
|
+
|
|
23
|
+
def __enter__(self) -> "ActionTableShowService":
|
|
24
|
+
"""Context manager entry.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Self for context manager use.
|
|
28
|
+
"""
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
|
|
32
|
+
"""Context manager exit."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
def start(
|
|
36
|
+
self,
|
|
37
|
+
serial_number: str,
|
|
38
|
+
finish_callback: Callable[[ConsonModuleConfig], None],
|
|
39
|
+
error_callback: Callable[[str], None],
|
|
40
|
+
config_path: Optional[Path] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Show action table configuration for a specific module.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
serial_number: Module serial number.
|
|
46
|
+
finish_callback: Callback to invoke with the module configuration.
|
|
47
|
+
error_callback: Callback to invoke on error.
|
|
48
|
+
config_path: Optional path to conson.yml. Defaults to current directory.
|
|
49
|
+
"""
|
|
50
|
+
self.finish_callback = finish_callback
|
|
51
|
+
self.error_callback = error_callback
|
|
52
|
+
|
|
53
|
+
# Default to current directory if not specified
|
|
54
|
+
if config_path is None:
|
|
55
|
+
config_path = Path.cwd() / "conson.yml"
|
|
56
|
+
|
|
57
|
+
# Check if config file exists
|
|
58
|
+
if not config_path.exists():
|
|
59
|
+
self._handle_error("Error: conson.yml not found in current directory")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Load configuration
|
|
63
|
+
try:
|
|
64
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
65
|
+
|
|
66
|
+
config = ConsonModuleListConfig.from_yaml(str(config_path))
|
|
67
|
+
except Exception as e:
|
|
68
|
+
self.logger.error(f"Failed to load conson.yml: {e}")
|
|
69
|
+
self._handle_error(f"Error: Failed to load conson.yml: {e}")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# Find module
|
|
73
|
+
module = config.find_module(serial_number)
|
|
74
|
+
if not module:
|
|
75
|
+
self._handle_error(f"Error: Module {serial_number} not found in conson.yml")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Invoke callback
|
|
79
|
+
if self.finish_callback is not None:
|
|
80
|
+
self.finish_callback(module)
|
|
81
|
+
|
|
82
|
+
def _handle_error(self, message: str) -> None:
|
|
83
|
+
"""Handle error and invoke error callback.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
message: Error message.
|
|
87
|
+
"""
|
|
88
|
+
if self.error_callback is not None:
|
|
89
|
+
self.error_callback(message)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Service for uploading ActionTable via Conbus protocol."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from twisted.internet.posixbase import PosixReactorBase
|
|
7
|
+
|
|
8
|
+
from xp.models import ConbusClientConfig
|
|
9
|
+
from xp.models.homekit.homekit_conson_config import ConsonModuleListConfig
|
|
10
|
+
from xp.models.protocol.conbus_protocol import TelegramReceivedEvent
|
|
11
|
+
from xp.models.telegram.system_function import SystemFunction
|
|
12
|
+
from xp.models.telegram.telegram_type import TelegramType
|
|
13
|
+
from xp.services.actiontable.actiontable_serializer import ActionTableSerializer
|
|
14
|
+
from xp.services.protocol import ConbusProtocol
|
|
15
|
+
from xp.services.telegram.telegram_service import TelegramService
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ActionTableUploadService(ConbusProtocol):
|
|
19
|
+
"""TCP client service for uploading action tables to Conbus modules.
|
|
20
|
+
|
|
21
|
+
Manages TCP socket connections, handles telegram generation and transmission,
|
|
22
|
+
and processes server responses for action table uploads.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
cli_config: ConbusClientConfig,
|
|
28
|
+
reactor: PosixReactorBase,
|
|
29
|
+
actiontable_serializer: ActionTableSerializer,
|
|
30
|
+
telegram_service: TelegramService,
|
|
31
|
+
conson_config: ConsonModuleListConfig,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Initialize the action table upload service.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
cli_config: Conbus client configuration.
|
|
37
|
+
reactor: Twisted reactor instance.
|
|
38
|
+
actiontable_serializer: Action table serializer.
|
|
39
|
+
telegram_service: Telegram service for parsing.
|
|
40
|
+
conson_config: Conson module list configuration.
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(cli_config, reactor)
|
|
43
|
+
self.serializer = actiontable_serializer
|
|
44
|
+
self.telegram_service = telegram_service
|
|
45
|
+
self.conson_config = conson_config
|
|
46
|
+
self.serial_number: str = ""
|
|
47
|
+
self.progress_callback: Optional[Callable[[str], None]] = None
|
|
48
|
+
self.error_callback: Optional[Callable[[str], None]] = None
|
|
49
|
+
self.success_callback: Optional[Callable[[], None]] = None
|
|
50
|
+
|
|
51
|
+
# Upload state
|
|
52
|
+
self.upload_data_chunks: list[str] = []
|
|
53
|
+
self.current_chunk_index: int = 0
|
|
54
|
+
|
|
55
|
+
# Set up logging
|
|
56
|
+
self.logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
def connection_established(self) -> None:
|
|
59
|
+
"""Handle connection established event."""
|
|
60
|
+
self.logger.debug("Connection established, sending upload actiontable telegram")
|
|
61
|
+
self.send_telegram(
|
|
62
|
+
telegram_type=TelegramType.SYSTEM,
|
|
63
|
+
serial_number=self.serial_number,
|
|
64
|
+
system_function=SystemFunction.UPLOAD_ACTIONTABLE,
|
|
65
|
+
data_value="00",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def telegram_sent(self, telegram_sent: str) -> None:
|
|
69
|
+
"""Handle telegram sent event.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
telegram_sent: The telegram that was sent.
|
|
73
|
+
"""
|
|
74
|
+
self.logger.debug(f"Telegram sent: {telegram_sent}")
|
|
75
|
+
|
|
76
|
+
def telegram_received(self, telegram_received: TelegramReceivedEvent) -> None:
|
|
77
|
+
"""Handle telegram received event.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
telegram_received: The telegram received event.
|
|
81
|
+
"""
|
|
82
|
+
self.logger.debug(f"Telegram received: {telegram_received}")
|
|
83
|
+
if (
|
|
84
|
+
not telegram_received.checksum_valid
|
|
85
|
+
or telegram_received.telegram_type != TelegramType.REPLY.value
|
|
86
|
+
or telegram_received.serial_number != self.serial_number
|
|
87
|
+
):
|
|
88
|
+
self.logger.debug("Not a reply response")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
reply_telegram = self.telegram_service.parse_reply_telegram(
|
|
92
|
+
telegram_received.frame
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._handle_upload_response(reply_telegram)
|
|
96
|
+
|
|
97
|
+
def _handle_upload_response(self, reply_telegram: Any) -> None:
|
|
98
|
+
"""Handle telegram responses during upload.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
reply_telegram: Parsed reply telegram.
|
|
102
|
+
"""
|
|
103
|
+
if reply_telegram.system_function == SystemFunction.ACK:
|
|
104
|
+
self.logger.debug("Received ACK for upload")
|
|
105
|
+
# Send next chunk or EOF
|
|
106
|
+
if self.current_chunk_index < len(self.upload_data_chunks):
|
|
107
|
+
chunk = self.upload_data_chunks[self.current_chunk_index]
|
|
108
|
+
self.logger.debug(f"Sending chunk {self.current_chunk_index + 1}")
|
|
109
|
+
|
|
110
|
+
# Calculate prefix: AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO
|
|
111
|
+
# First character: 'A' (fixed)
|
|
112
|
+
# Second character: 'A' + chunk_index (sequential counter A-O for 15 chunks)
|
|
113
|
+
prefix_hex = f"AAA{ord('A') + self.current_chunk_index:c}"
|
|
114
|
+
|
|
115
|
+
self.send_telegram(
|
|
116
|
+
telegram_type=TelegramType.SYSTEM,
|
|
117
|
+
serial_number=self.serial_number,
|
|
118
|
+
system_function=SystemFunction.ACTIONTABLE,
|
|
119
|
+
data_value=f"{prefix_hex}{chunk}",
|
|
120
|
+
)
|
|
121
|
+
self.current_chunk_index += 1
|
|
122
|
+
if self.progress_callback:
|
|
123
|
+
self.progress_callback(".")
|
|
124
|
+
else:
|
|
125
|
+
# All chunks sent, send EOF
|
|
126
|
+
self.logger.debug("All chunks sent, sending EOF")
|
|
127
|
+
self.send_telegram(
|
|
128
|
+
telegram_type=TelegramType.SYSTEM,
|
|
129
|
+
serial_number=self.serial_number,
|
|
130
|
+
system_function=SystemFunction.EOF,
|
|
131
|
+
data_value="00",
|
|
132
|
+
)
|
|
133
|
+
if self.success_callback:
|
|
134
|
+
self.success_callback()
|
|
135
|
+
self._stop_reactor()
|
|
136
|
+
elif reply_telegram.system_function == SystemFunction.NAK:
|
|
137
|
+
self.logger.debug("Received NAK during upload")
|
|
138
|
+
self.failed("Upload failed: NAK received")
|
|
139
|
+
else:
|
|
140
|
+
self.logger.debug(f"Unexpected response during upload: {reply_telegram}")
|
|
141
|
+
|
|
142
|
+
def failed(self, message: str) -> None:
|
|
143
|
+
"""Handle failed connection event.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
message: Failure message.
|
|
147
|
+
"""
|
|
148
|
+
self.logger.debug(f"Failed: {message}")
|
|
149
|
+
if self.error_callback:
|
|
150
|
+
self.error_callback(message)
|
|
151
|
+
self._stop_reactor()
|
|
152
|
+
|
|
153
|
+
def start(
|
|
154
|
+
self,
|
|
155
|
+
serial_number: str,
|
|
156
|
+
progress_callback: Callable[[str], None],
|
|
157
|
+
error_callback: Callable[[str], None],
|
|
158
|
+
success_callback: Callable[[], None],
|
|
159
|
+
timeout_seconds: Optional[float] = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Upload action table to module.
|
|
162
|
+
|
|
163
|
+
Uploads the action table configuration to the specified module.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
serial_number: Module serial number.
|
|
167
|
+
progress_callback: Callback for progress updates.
|
|
168
|
+
error_callback: Callback for errors.
|
|
169
|
+
success_callback: Callback when upload completes successfully.
|
|
170
|
+
timeout_seconds: Optional timeout in seconds.
|
|
171
|
+
"""
|
|
172
|
+
self.logger.info("Starting actiontable upload")
|
|
173
|
+
self.serial_number = serial_number
|
|
174
|
+
if timeout_seconds:
|
|
175
|
+
self.timeout_seconds = timeout_seconds
|
|
176
|
+
self.progress_callback = progress_callback
|
|
177
|
+
self.error_callback = error_callback
|
|
178
|
+
self.success_callback = success_callback
|
|
179
|
+
|
|
180
|
+
# Find module
|
|
181
|
+
module = self.conson_config.find_module(serial_number)
|
|
182
|
+
if not module:
|
|
183
|
+
self.failed(f"Module {serial_number} not found in conson.yml")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Parse action table strings to ActionTable object
|
|
187
|
+
try:
|
|
188
|
+
module_action_table = module.action_table or []
|
|
189
|
+
action_table = self.serializer.parse_action_table(module_action_table)
|
|
190
|
+
except ValueError as e:
|
|
191
|
+
self.logger.error(f"Invalid action table format: {e}")
|
|
192
|
+
self.failed(f"Invalid action table format: {e}")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
# Encode action table to hex string
|
|
196
|
+
encoded_data = self.serializer.to_encoded_string(action_table)
|
|
197
|
+
|
|
198
|
+
# Chunk the data into 64 byte chunks
|
|
199
|
+
chunk_size = 64
|
|
200
|
+
self.upload_data_chunks = [
|
|
201
|
+
encoded_data[i : i + chunk_size]
|
|
202
|
+
for i in range(0, len(encoded_data), chunk_size)
|
|
203
|
+
]
|
|
204
|
+
self.current_chunk_index = 0
|
|
205
|
+
|
|
206
|
+
self.logger.debug(
|
|
207
|
+
f"Upload data encoded: {len(encoded_data)} chars, "
|
|
208
|
+
f"{len(self.upload_data_chunks)} chunks"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self.start_reactor()
|
xp/utils/dependencies.py
CHANGED
|
@@ -20,7 +20,18 @@ from xp.services.actiontable.msactiontable_xp24_serializer import (
|
|
|
20
20
|
from xp.services.actiontable.msactiontable_xp33_serializer import (
|
|
21
21
|
Xp33MsActionTableSerializer,
|
|
22
22
|
)
|
|
23
|
-
from xp.services.conbus.actiontable.
|
|
23
|
+
from xp.services.conbus.actiontable.actiontable_download_service import (
|
|
24
|
+
ActionTableService,
|
|
25
|
+
)
|
|
26
|
+
from xp.services.conbus.actiontable.actiontable_list_service import (
|
|
27
|
+
ActionTableListService,
|
|
28
|
+
)
|
|
29
|
+
from xp.services.conbus.actiontable.actiontable_show_service import (
|
|
30
|
+
ActionTableShowService,
|
|
31
|
+
)
|
|
32
|
+
from xp.services.conbus.actiontable.actiontable_upload_service import (
|
|
33
|
+
ActionTableUploadService,
|
|
34
|
+
)
|
|
24
35
|
from xp.services.conbus.actiontable.msactiontable_service import MsActionTableService
|
|
25
36
|
from xp.services.conbus.conbus_blink_all_service import ConbusBlinkAllService
|
|
26
37
|
from xp.services.conbus.conbus_blink_service import ConbusBlinkService
|
|
@@ -215,6 +226,30 @@ class ServiceContainer:
|
|
|
215
226
|
scope=punq.Scope.singleton,
|
|
216
227
|
)
|
|
217
228
|
|
|
229
|
+
self.container.register(
|
|
230
|
+
ActionTableUploadService,
|
|
231
|
+
factory=lambda: ActionTableUploadService(
|
|
232
|
+
cli_config=self.container.resolve(ConbusClientConfig),
|
|
233
|
+
reactor=self.container.resolve(PosixReactorBase),
|
|
234
|
+
actiontable_serializer=self.container.resolve(ActionTableSerializer),
|
|
235
|
+
telegram_service=self.container.resolve(TelegramService),
|
|
236
|
+
conson_config=self.container.resolve(ConsonModuleListConfig),
|
|
237
|
+
),
|
|
238
|
+
scope=punq.Scope.singleton,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
self.container.register(
|
|
242
|
+
ActionTableListService,
|
|
243
|
+
factory=ActionTableListService,
|
|
244
|
+
scope=punq.Scope.singleton,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
self.container.register(
|
|
248
|
+
ActionTableShowService,
|
|
249
|
+
factory=ActionTableShowService,
|
|
250
|
+
scope=punq.Scope.singleton,
|
|
251
|
+
)
|
|
252
|
+
|
|
218
253
|
self.container.register(
|
|
219
254
|
Xp20MsActionTableSerializer,
|
|
220
255
|
factory=lambda: Xp20MsActionTableSerializer,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|