conson-xp 1.50.0__py3-none-any.whl → 1.51.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.50.0.dist-info → conson_xp-1.51.0.dist-info}/METADATA +2 -1
- {conson_xp-1.50.0.dist-info → conson_xp-1.51.0.dist-info}/RECORD +11 -10
- xp/__init__.py +1 -1
- xp/cli/commands/conbus/conbus_export_commands.py +92 -0
- xp/services/conbus/actiontable/actiontable_download_service.py +2 -0
- xp/services/conbus/conbus_export_actiontable_service.py +328 -0
- xp/services/conbus/conbus_export_service.py +5 -2
- xp/utils/dependencies.py +14 -1
- {conson_xp-1.50.0.dist-info → conson_xp-1.51.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.50.0.dist-info → conson_xp-1.51.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.50.0.dist-info → conson_xp-1.51.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.51.0
|
|
4
4
|
Summary: XP Protocol Communication Tools
|
|
5
5
|
Author-Email: ldvchosal <ldvchosal@github.com>
|
|
6
6
|
License: MIT License
|
|
@@ -353,6 +353,7 @@ xp conbus event raw
|
|
|
353
353
|
|
|
354
354
|
|
|
355
355
|
xp conbus export
|
|
356
|
+
xp conbus export actiontable
|
|
356
357
|
xp conbus export config
|
|
357
358
|
|
|
358
359
|
|
|
@@ -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.51.0.dist-info/METADATA,sha256=8rmZ2V68vB3gZpx_sScMBXxKXqAei3GeqOOUHGe92mo,11432
|
|
2
|
+
conson_xp-1.51.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
conson_xp-1.51.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
|
|
4
|
+
conson_xp-1.51.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
|
|
5
|
+
xp/__init__.py,sha256=IbYggn-GNpmY-NkggNp_7SLbVaF6zPk0wENVaZeOIeE,182
|
|
6
6
|
xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
|
|
7
7
|
xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
|
|
8
8
|
xp/cli/commands/__init__.py,sha256=G7A1KFRSV0CEeDTqr_khu-K9_sc01CTI2KSfkFcaBRM,4949
|
|
@@ -16,7 +16,7 @@ xp/cli/commands/conbus/conbus_custom_commands.py,sha256=TqWo45yqEp_ezmKZ0tymQ0C4
|
|
|
16
16
|
xp/cli/commands/conbus/conbus_datapoint_commands.py,sha256=uYsvi37vl2od2XJB0semAF0sH3UrRWTt_qHnBmUBR-w,3788
|
|
17
17
|
xp/cli/commands/conbus/conbus_discover_commands.py,sha256=gSK5Y4lespNKFNgGtp7VkqStGe4fWxDNNfyPPuJiwXQ,1931
|
|
18
18
|
xp/cli/commands/conbus/conbus_event_commands.py,sha256=faaSGRNtJCttk-0VO5Z4m4Zz37aOsSgmbuzWbTOUZIQ,3103
|
|
19
|
-
xp/cli/commands/conbus/conbus_export_commands.py,sha256
|
|
19
|
+
xp/cli/commands/conbus/conbus_export_commands.py,sha256=pWZlS0MMcc8sZAfXx8xMSn53NT5cg3HpF-msBOU7whI,5992
|
|
20
20
|
xp/cli/commands/conbus/conbus_lightlevel_commands.py,sha256=WH4ZQHxSbquPlAqd0uRxq8VR-sO4_AnfseMqaoPoGoA,7092
|
|
21
21
|
xp/cli/commands/conbus/conbus_linknumber_commands.py,sha256=3bkik8nHXY89XfzeUnKWmvKA70r4qJJ79j8FfLL4sL0,3556
|
|
22
22
|
xp/cli/commands/conbus/conbus_modulenumber_commands.py,sha256=fOIO0f85iDJBfGnRCEN8zK6j4i43uuF_9YKQc_nQ39A,3628
|
|
@@ -123,7 +123,7 @@ xp/services/actiontable/msactiontable_xp33_serializer.py,sha256=9JzG9dOrfamu8Ujs
|
|
|
123
123
|
xp/services/actiontable/serializer_protocol.py,sha256=PMsZbPwPQD1MJYo_KpZSgpnVQCtXFXSfzXFpCiA6Xi8,2002
|
|
124
124
|
xp/services/conbus/__init__.py,sha256=Hi35sMKu9o6LpYoi2tQDaQoMb8M5sOt_-LUTxxaCU_0,28
|
|
125
125
|
xp/services/conbus/actiontable/__init__.py,sha256=oD6vRk_Ye-eZ9s_hldAgtRJFu4mfAnODqpkJUGHHszk,40
|
|
126
|
-
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=
|
|
126
|
+
xp/services/conbus/actiontable/actiontable_download_service.py,sha256=41Hr1753IBpUeHQqO57uS7qxOB0rJt8qCpznzKlUPOM,15028
|
|
127
127
|
xp/services/conbus/actiontable/actiontable_list_service.py,sha256=oTDSpBkp-MJeaF5bhRnwkSy3na55xqQ4e2ykJzbMCUo,3236
|
|
128
128
|
xp/services/conbus/actiontable/actiontable_show_service.py,sha256=WISY2VsmSlceGa5_9lpFO-gs5TnTjv6YidQksUjCapk,3058
|
|
129
129
|
xp/services/conbus/actiontable/actiontable_upload_service.py,sha256=FaQzOSg8s2zUL5xz9qZY9fvzrdDosc3CoxkVDvNg2SU,13252
|
|
@@ -135,7 +135,8 @@ xp/services/conbus/conbus_datapoint_service.py,sha256=WBQC42-6xuPWhMKKRtHtRzwEmV
|
|
|
135
135
|
xp/services/conbus/conbus_discover_service.py,sha256=mvqjHFMmEkQjHD9YDIk9gE8MowPMkOIJRmyjX96G5pw,12868
|
|
136
136
|
xp/services/conbus/conbus_event_list_service.py,sha256=-jl3WHpyidbh-h4NMK2gERqu48mTNFD6rpPo2EyGxeg,3641
|
|
137
137
|
xp/services/conbus/conbus_event_raw_service.py,sha256=viXuEXw165-RytdqC76wQShJLD7Yd0rtURxWZZ8hyKA,7060
|
|
138
|
-
xp/services/conbus/
|
|
138
|
+
xp/services/conbus/conbus_export_actiontable_service.py,sha256=d6Y2RDiVOH-jTMWOIE_gY_WBJnHFrbJGguk-WZWbNPs,11818
|
|
139
|
+
xp/services/conbus/conbus_export_service.py,sha256=RP8nADTIs4FGUf_BFLRZMtEJZdXV94zg3QrlWaDnhKA,17536
|
|
139
140
|
xp/services/conbus/conbus_output_service.py,sha256=e57bRkLgPnJuB8hkllNh0kgGkjPt9IK75tuBxd_bOkE,9361
|
|
140
141
|
xp/services/conbus/conbus_raw_service.py,sha256=OQuV521VOQraf2PGF2B9868vh7sDgmfc19YebrkZnyw,5844
|
|
141
142
|
xp/services/conbus/conbus_receive_service.py,sha256=TFf3W65brGsy6QZICpIs0Xy9bgqyL1vgQuhS_eHuIZs,5416
|
|
@@ -197,10 +198,10 @@ xp/term/widgets/protocol_log.py,sha256=E68QmSMpOFrvrPTo_gOQVfyiDqY5c_y8fkNKnQw6V
|
|
|
197
198
|
xp/term/widgets/status_footer.py,sha256=YYAT0431p6jmrzzpVgaPhu7yGkRroWGv4e99t2XlkHI,3297
|
|
198
199
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
199
200
|
xp/utils/checksum.py,sha256=Px1S3dFGA-_plavBxrq3IqmprNlgtNDunE3whg6Otwg,1722
|
|
200
|
-
xp/utils/dependencies.py,sha256=
|
|
201
|
+
xp/utils/dependencies.py,sha256=I8Z89_iTCeR-7adpae3GVQUGZ-1gY6wg0OGKmdJGg3w,24536
|
|
201
202
|
xp/utils/event_helper.py,sha256=zD0K3TPfGEThU9vUNlDtglTai3Cmm30727iwjDZy6Dk,1007
|
|
202
203
|
xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
|
|
203
204
|
xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
|
|
204
205
|
xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
|
|
205
206
|
xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
|
|
206
|
-
conson_xp-1.
|
|
207
|
+
conson_xp-1.51.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -6,8 +6,12 @@ import click
|
|
|
6
6
|
|
|
7
7
|
from xp.cli.commands.conbus.conbus import conbus_export
|
|
8
8
|
from xp.cli.utils.decorators import connection_command
|
|
9
|
+
from xp.models.actiontable.actiontable_type import ActionTableType
|
|
9
10
|
from xp.models.conbus.conbus_export import ConbusExportResponse
|
|
10
11
|
from xp.models.config.conson_module_config import ConsonModuleConfig
|
|
12
|
+
from xp.services.conbus.conbus_export_actiontable_service import (
|
|
13
|
+
ConbusActiontableExportService,
|
|
14
|
+
)
|
|
11
15
|
from xp.services.conbus.conbus_export_service import ConbusExportService
|
|
12
16
|
|
|
13
17
|
|
|
@@ -91,3 +95,91 @@ def export_conbus_config(ctx: click.Context) -> None:
|
|
|
91
95
|
service.on_finish.connect(on_finish)
|
|
92
96
|
service.set_timeout(5)
|
|
93
97
|
service.start_reactor()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@conbus_export.command("actiontable")
|
|
101
|
+
@click.pass_context
|
|
102
|
+
@connection_command()
|
|
103
|
+
def export_conbus_actiontable(ctx: click.Context) -> None:
|
|
104
|
+
r"""
|
|
105
|
+
Export Conbus device actiontable to YAML file.
|
|
106
|
+
|
|
107
|
+
Read device list from conson.yml
|
|
108
|
+
Export export.yml file in conson.yml format.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
ctx: Click context object.
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
\b
|
|
115
|
+
# Export device metadata to export.yml
|
|
116
|
+
xp conbus export
|
|
117
|
+
xp conbus export actiontable
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def on_progress(
|
|
121
|
+
serial_number: str, actiontable_type: str, current: int, total: int
|
|
122
|
+
) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Handle progress updates during export.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
serial_number: Serial number of discovered device.
|
|
128
|
+
actiontable_type: Type of action table being exported.
|
|
129
|
+
current: Current device number.
|
|
130
|
+
total: Total devices discovered.
|
|
131
|
+
"""
|
|
132
|
+
click.echo(
|
|
133
|
+
f"Querying device {current}/{total}: {serial_number} / {actiontable_type}."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def on_device_actiontable_exported(
|
|
137
|
+
module: ConsonModuleConfig,
|
|
138
|
+
actiontable_type: ActionTableType,
|
|
139
|
+
actiontable_short: str,
|
|
140
|
+
) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Handle device export completion.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
module: Exported module configuration.
|
|
146
|
+
actiontable_type: Type of action table exported.
|
|
147
|
+
actiontable_short: Short representation of the action table.
|
|
148
|
+
"""
|
|
149
|
+
serial_number = module.serial_number or "UNKNOWN"
|
|
150
|
+
click.echo(f" ✓ Module: {serial_number})")
|
|
151
|
+
click.echo(f" ✓ Action type: {actiontable_type}")
|
|
152
|
+
click.echo(f" ✓ Action table: {actiontable_short}")
|
|
153
|
+
|
|
154
|
+
def on_finish(result: ConbusExportResponse) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Handle export completion.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
result: Export result.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ClickException: When export fails with error message from result.
|
|
163
|
+
"""
|
|
164
|
+
# Try to stop reactor (may already be stopped)
|
|
165
|
+
with suppress(Exception):
|
|
166
|
+
service.stop_reactor()
|
|
167
|
+
|
|
168
|
+
if result.success:
|
|
169
|
+
click.echo(
|
|
170
|
+
f"\nExport complete: {result.output_file} ({result.device_count} devices)"
|
|
171
|
+
)
|
|
172
|
+
else:
|
|
173
|
+
click.echo(f"Error: {result.error}", err=True)
|
|
174
|
+
raise click.ClickException(result.error or "Export failed")
|
|
175
|
+
|
|
176
|
+
service: ConbusActiontableExportService = (
|
|
177
|
+
ctx.obj.get("container").get_container().resolve(ConbusActiontableExportService)
|
|
178
|
+
)
|
|
179
|
+
with service:
|
|
180
|
+
service.on_progress.connect(on_progress)
|
|
181
|
+
service.on_device_actiontable_exported.connect(on_device_actiontable_exported)
|
|
182
|
+
service.on_finish.connect(on_finish)
|
|
183
|
+
service.set_timeout(5)
|
|
184
|
+
service.configure()
|
|
185
|
+
service.start_reactor()
|
|
@@ -292,6 +292,8 @@ class ActionTableDownloadService(DownloadStateMachine):
|
|
|
292
292
|
raise RuntimeError("Cannot configure while download in progress")
|
|
293
293
|
self.logger.info("Configuring actiontable download")
|
|
294
294
|
self.serial_number = serial_number
|
|
295
|
+
self.actiontable_data = []
|
|
296
|
+
|
|
295
297
|
if actiontable_type == ActionTableType.ACTIONTABLE:
|
|
296
298
|
self.serializer = self.actiontable_serializer
|
|
297
299
|
elif actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Conbus export service for exporting device configurations."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from queue import Empty, SimpleQueue
|
|
7
|
+
from typing import Any, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
from psygnal import Signal
|
|
11
|
+
|
|
12
|
+
from xp.models.actiontable.actiontable_type import ActionTableType, ActionTableType2
|
|
13
|
+
from xp.models.conbus.conbus_export import ConbusExportResponse
|
|
14
|
+
from xp.models.config.conson_module_config import (
|
|
15
|
+
ConsonModuleConfig,
|
|
16
|
+
ConsonModuleListConfig,
|
|
17
|
+
)
|
|
18
|
+
from xp.services.conbus.actiontable.actiontable_download_service import (
|
|
19
|
+
ActionTableDownloadService,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConbusActiontableExportService:
|
|
24
|
+
"""
|
|
25
|
+
Service for exporting Conbus device configurations.
|
|
26
|
+
|
|
27
|
+
Discovers all devices on the Conbus network and queries their configuration
|
|
28
|
+
datapoints to generate a structured export file compatible with conson.yml format.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
download_service: Download service for exporting device configurations.
|
|
32
|
+
export_result: Final export result.
|
|
33
|
+
export_status: Export status (OK, FAILED_TIMEOUT, etc.).
|
|
34
|
+
on_progress: Signal emitted on device discovery (serial, current, total).
|
|
35
|
+
on_device_actiontable_exported: Signal emitted when device export completes.
|
|
36
|
+
on_finish: Signal emitted when export finishes.
|
|
37
|
+
ACTIONTABLE_SEQUENCE: Sequence of actiontable to query for each device.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# Signals (class attributes)
|
|
41
|
+
on_progress: Signal = Signal(str, str, int, int) # serial, current, total
|
|
42
|
+
on_device_actiontable_exported: Signal = Signal(
|
|
43
|
+
ConsonModuleConfig, ActionTableType, str
|
|
44
|
+
)
|
|
45
|
+
on_finish: Signal = Signal(ConbusExportResponse)
|
|
46
|
+
|
|
47
|
+
ACTIONTABLE_SEQUENCE = [
|
|
48
|
+
ActionTableType2.ACTIONTABLE,
|
|
49
|
+
ActionTableType2.MSACTIONTABLE,
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
download_service: ActionTableDownloadService,
|
|
55
|
+
module_list: ConsonModuleListConfig,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Initialize the Conbus export service.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
download_service: Protocol for downloading actiontables.
|
|
62
|
+
module_list: module to export.
|
|
63
|
+
"""
|
|
64
|
+
self.logger = logging.getLogger(__name__)
|
|
65
|
+
self.download_service = download_service
|
|
66
|
+
self._module_list: ConsonModuleListConfig = module_list
|
|
67
|
+
self._module_dic: dict[str, ConsonModuleConfig] = {
|
|
68
|
+
module.serial_number: module for module in module_list.root
|
|
69
|
+
}
|
|
70
|
+
# State management
|
|
71
|
+
self.device_queue: SimpleQueue[Tuple[str, ActionTableType]] = (
|
|
72
|
+
SimpleQueue()
|
|
73
|
+
) # FIFO
|
|
74
|
+
for module in self._module_list.root:
|
|
75
|
+
self.logger.info("Export module %s", module)
|
|
76
|
+
if module.module_type.lower() == "xp20":
|
|
77
|
+
self.device_queue.put(
|
|
78
|
+
(module.serial_number, ActionTableType.MSACTIONTABLE_XP20)
|
|
79
|
+
)
|
|
80
|
+
if module.module_type.lower() == "xp24":
|
|
81
|
+
self.device_queue.put(
|
|
82
|
+
(module.serial_number, ActionTableType.MSACTIONTABLE_XP24)
|
|
83
|
+
)
|
|
84
|
+
if module.module_type.lower() == "xp33":
|
|
85
|
+
self.device_queue.put(
|
|
86
|
+
(module.serial_number, ActionTableType.MSACTIONTABLE_XP33)
|
|
87
|
+
)
|
|
88
|
+
self.device_queue.put((module.serial_number, ActionTableType.ACTIONTABLE))
|
|
89
|
+
|
|
90
|
+
self.logger.info("Export module %s", self.device_queue.qsize())
|
|
91
|
+
|
|
92
|
+
self.current_module: Optional[ConsonModuleConfig] = None
|
|
93
|
+
self.current_actiontable_type: Optional[ActionTableType] = None
|
|
94
|
+
self.export_result = ConbusExportResponse(success=False)
|
|
95
|
+
self.export_status = "OK"
|
|
96
|
+
|
|
97
|
+
def on_module_actiontable_received(
|
|
98
|
+
self, actiontable: Any, short_actiontable: list[str]
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Handle actiontable received event.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
actiontable: Full actiontable data.
|
|
105
|
+
short_actiontable: Short representation of the actiontable.
|
|
106
|
+
"""
|
|
107
|
+
if not self.current_actiontable_type:
|
|
108
|
+
self._fail("Invalid state (curent_actiontable_type)")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
if not self.current_module:
|
|
112
|
+
self._fail("Invalid state (current_module)")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
if self.current_actiontable_type == ActionTableType.ACTIONTABLE:
|
|
116
|
+
self.current_module.action_table = short_actiontable
|
|
117
|
+
elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP20:
|
|
118
|
+
self.current_module.xp20_msaction_table = short_actiontable
|
|
119
|
+
elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP24:
|
|
120
|
+
self.current_module.xp24_msaction_table = short_actiontable
|
|
121
|
+
elif self.current_actiontable_type == ActionTableType.MSACTIONTABLE_XP33:
|
|
122
|
+
self.current_module.xp33_msaction_table = short_actiontable
|
|
123
|
+
|
|
124
|
+
self.on_device_actiontable_exported.emit(
|
|
125
|
+
self.current_module, self.current_actiontable_type, short_actiontable
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def on_module_finish(self) -> None:
|
|
129
|
+
"""Handle module export completion."""
|
|
130
|
+
self._save_action_table()
|
|
131
|
+
has_next_module = self.configure()
|
|
132
|
+
if not has_next_module:
|
|
133
|
+
self._succeed()
|
|
134
|
+
|
|
135
|
+
def on_module_progress(self) -> None:
|
|
136
|
+
"""Handle module progress event and emit progress signal."""
|
|
137
|
+
serial_number = (
|
|
138
|
+
self.current_module.serial_number if self.current_module else "UNKNOWN"
|
|
139
|
+
)
|
|
140
|
+
current_actiontable_type = self.current_actiontable_type or "UNKNOWN"
|
|
141
|
+
total_modules = len(self._module_list.root)
|
|
142
|
+
current_index = total_modules - self.device_queue.qsize()
|
|
143
|
+
|
|
144
|
+
self.on_progress.emit(
|
|
145
|
+
serial_number, current_actiontable_type, current_index, total_modules
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
def on_module_error(self, error_message: str) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Handle module error event.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
error_message: Error message from module.
|
|
154
|
+
"""
|
|
155
|
+
self._fail(error_message)
|
|
156
|
+
|
|
157
|
+
def _save_action_table(self) -> None:
|
|
158
|
+
"""Write export to YAML file."""
|
|
159
|
+
self.logger.info("Saving action table")
|
|
160
|
+
|
|
161
|
+
if not self._module_list:
|
|
162
|
+
self._fail("FAILED_NO_DEVICES")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Write to file
|
|
167
|
+
path = "export.yml"
|
|
168
|
+
output_path = Path(path)
|
|
169
|
+
|
|
170
|
+
# Use Pydantic's model_dump to serialize, excluding only internal fields
|
|
171
|
+
data = self._module_list.model_dump(
|
|
172
|
+
exclude={
|
|
173
|
+
"root": {
|
|
174
|
+
"__all__": {
|
|
175
|
+
"enabled",
|
|
176
|
+
"conbus_ip",
|
|
177
|
+
"conbus_port",
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
exclude_none=True,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Export as list at root level (not wrapped in 'root:' key)
|
|
185
|
+
modules_list = data.get("root", [])
|
|
186
|
+
|
|
187
|
+
with output_path.open("w") as f:
|
|
188
|
+
# Dump each module separately with blank lines between them
|
|
189
|
+
for i, module in enumerate(modules_list):
|
|
190
|
+
# Add blank line before each module except the first
|
|
191
|
+
if i > 0:
|
|
192
|
+
f.write("\n")
|
|
193
|
+
|
|
194
|
+
# Dump single item as list element
|
|
195
|
+
yaml_str = yaml.safe_dump(
|
|
196
|
+
[module],
|
|
197
|
+
default_flow_style=False,
|
|
198
|
+
sort_keys=False,
|
|
199
|
+
allow_unicode=True,
|
|
200
|
+
)
|
|
201
|
+
# Remove the trailing newline and write
|
|
202
|
+
f.write(yaml_str.rstrip("\n") + "\n")
|
|
203
|
+
|
|
204
|
+
self.logger.info(f"Export written to {path}")
|
|
205
|
+
self.export_result.output_file = path
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self._fail(f"Failed to create export: {e}")
|
|
209
|
+
|
|
210
|
+
def configure(self) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Configure export service.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
True if there is a module to export, False otherwise.
|
|
216
|
+
"""
|
|
217
|
+
self.download_service.reset()
|
|
218
|
+
try:
|
|
219
|
+
(current_serial_number, self.current_actiontable_type) = (
|
|
220
|
+
self.device_queue.get_nowait()
|
|
221
|
+
)
|
|
222
|
+
except Empty:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
self.current_module = self._module_dic[current_serial_number]
|
|
226
|
+
if not (self.current_module or self.current_actiontable_type):
|
|
227
|
+
self.logger.error("No module to export")
|
|
228
|
+
return False
|
|
229
|
+
|
|
230
|
+
self.logger.info(
|
|
231
|
+
f"Downloading {self.current_module.serial_number} / {self.current_actiontable_type}"
|
|
232
|
+
)
|
|
233
|
+
self.download_service.configure(
|
|
234
|
+
self.current_module.serial_number,
|
|
235
|
+
self.current_actiontable_type,
|
|
236
|
+
)
|
|
237
|
+
self.download_service.do_connect()
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
def set_event_loop(self, event_loop: asyncio.AbstractEventLoop) -> None:
|
|
241
|
+
"""
|
|
242
|
+
Set event loop for async operations.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
event_loop: Event loop to use.
|
|
246
|
+
"""
|
|
247
|
+
self.logger.debug("Set event loop")
|
|
248
|
+
self.download_service.set_event_loop(event_loop)
|
|
249
|
+
|
|
250
|
+
def set_timeout(self, timeout_seconds: float) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Set timeout.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
timeout_seconds: Timeout in seconds.
|
|
256
|
+
"""
|
|
257
|
+
self.download_service.set_timeout(timeout_seconds)
|
|
258
|
+
|
|
259
|
+
def start_reactor(self) -> None:
|
|
260
|
+
"""Start the reactor."""
|
|
261
|
+
self.download_service.start_reactor()
|
|
262
|
+
|
|
263
|
+
def stop_reactor(self) -> None:
|
|
264
|
+
"""Stop the reactor."""
|
|
265
|
+
self.download_service.stop_reactor()
|
|
266
|
+
|
|
267
|
+
def __enter__(self) -> "ConbusActiontableExportService":
|
|
268
|
+
"""
|
|
269
|
+
Enter context manager.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Self for context manager protocol.
|
|
273
|
+
"""
|
|
274
|
+
# Reset state for reuse
|
|
275
|
+
self.export_result = ConbusExportResponse(success=False)
|
|
276
|
+
self.export_status = "OK"
|
|
277
|
+
self._connect_signals()
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
def __exit__(
|
|
281
|
+
self, _exc_type: Optional[type], _exc_val: Optional[Exception], _exc_tb: Any
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Exit context manager and disconnect signals."""
|
|
284
|
+
self._disconnect_signals()
|
|
285
|
+
self.stop_reactor()
|
|
286
|
+
|
|
287
|
+
def _connect_signals(self) -> None:
|
|
288
|
+
"""Connect download service signals to handlers."""
|
|
289
|
+
self.download_service.on_actiontable_received.connect(
|
|
290
|
+
self.on_module_actiontable_received
|
|
291
|
+
)
|
|
292
|
+
self.download_service.on_finish.connect(self.on_module_finish)
|
|
293
|
+
self.download_service.on_progress.connect(self.on_module_progress)
|
|
294
|
+
self.download_service.on_error.connect(self.on_module_error)
|
|
295
|
+
|
|
296
|
+
def _disconnect_signals(self) -> None:
|
|
297
|
+
"""Disconnect download service signals from handlers."""
|
|
298
|
+
self.download_service.on_actiontable_received.disconnect(
|
|
299
|
+
self.on_module_actiontable_received
|
|
300
|
+
)
|
|
301
|
+
self.download_service.on_finish.disconnect(self.on_module_finish)
|
|
302
|
+
self.download_service.on_progress.disconnect(self.on_module_progress)
|
|
303
|
+
self.download_service.on_error.disconnect(self.on_module_error)
|
|
304
|
+
|
|
305
|
+
self.on_progress.disconnect()
|
|
306
|
+
self.on_device_actiontable_exported.disconnect()
|
|
307
|
+
self.on_finish.disconnect()
|
|
308
|
+
|
|
309
|
+
def _fail(self, error: str) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Handle export failure.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
error: Error message.
|
|
315
|
+
"""
|
|
316
|
+
self.logger.error(error)
|
|
317
|
+
self.export_result.success = False
|
|
318
|
+
self.export_result.error = error
|
|
319
|
+
self.export_result.export_status = "FAILED"
|
|
320
|
+
self.on_finish.emit(self.export_result)
|
|
321
|
+
|
|
322
|
+
def _succeed(self) -> None:
|
|
323
|
+
"""Handle export success."""
|
|
324
|
+
self.logger.info("Export succeed")
|
|
325
|
+
self.export_result.success = True
|
|
326
|
+
self.export_result.error = None
|
|
327
|
+
self.export_result.export_status = "OK"
|
|
328
|
+
self.on_finish.emit(self.export_result)
|
|
@@ -57,16 +57,19 @@ class ConbusExportService:
|
|
|
57
57
|
DataPointType.AUTO_REPORT_STATUS,
|
|
58
58
|
]
|
|
59
59
|
|
|
60
|
-
def __init__(
|
|
60
|
+
def __init__(
|
|
61
|
+
self, conbus_protocol: ConbusEventProtocol, telegram_service: TelegramService
|
|
62
|
+
) -> None:
|
|
61
63
|
"""
|
|
62
64
|
Initialize the Conbus export service.
|
|
63
65
|
|
|
64
66
|
Args:
|
|
65
67
|
conbus_protocol: Protocol for Conbus communication.
|
|
68
|
+
telegram_service: TelegramService for telegram parsing.
|
|
66
69
|
"""
|
|
67
70
|
self.logger = logging.getLogger(__name__)
|
|
68
71
|
self.conbus_protocol = conbus_protocol
|
|
69
|
-
self.telegram_service =
|
|
72
|
+
self.telegram_service = telegram_service
|
|
70
73
|
|
|
71
74
|
# State management
|
|
72
75
|
self.discovered_devices: list[str] = []
|
xp/utils/dependencies.py
CHANGED
|
@@ -46,6 +46,9 @@ from xp.services.conbus.conbus_datapoint_service import (
|
|
|
46
46
|
from xp.services.conbus.conbus_discover_service import ConbusDiscoverService
|
|
47
47
|
from xp.services.conbus.conbus_event_list_service import ConbusEventListService
|
|
48
48
|
from xp.services.conbus.conbus_event_raw_service import ConbusEventRawService
|
|
49
|
+
from xp.services.conbus.conbus_export_actiontable_service import (
|
|
50
|
+
ConbusActiontableExportService,
|
|
51
|
+
)
|
|
49
52
|
from xp.services.conbus.conbus_export_service import ConbusExportService
|
|
50
53
|
from xp.services.conbus.conbus_output_service import ConbusOutputService
|
|
51
54
|
from xp.services.conbus.conbus_raw_service import ConbusRawService
|
|
@@ -211,7 +214,17 @@ class ServiceContainer:
|
|
|
211
214
|
self.container.register(
|
|
212
215
|
ConbusExportService,
|
|
213
216
|
factory=lambda: ConbusExportService(
|
|
214
|
-
conbus_protocol=self.container.resolve(ConbusEventProtocol)
|
|
217
|
+
conbus_protocol=self.container.resolve(ConbusEventProtocol),
|
|
218
|
+
telegram_service=self.container.resolve(TelegramService),
|
|
219
|
+
),
|
|
220
|
+
scope=punq.Scope.singleton,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
self.container.register(
|
|
224
|
+
ConbusActiontableExportService,
|
|
225
|
+
factory=lambda: ConbusActiontableExportService(
|
|
226
|
+
download_service=self.container.resolve(ActionTableDownloadService),
|
|
227
|
+
module_list=self.container.resolve(ConsonModuleListConfig),
|
|
215
228
|
),
|
|
216
229
|
scope=punq.Scope.singleton,
|
|
217
230
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|