Actualización de scripts de adaptación IO y mejora en la gestión de directorios de trabajo

- Se modificaron los logs de ejecución para reflejar nuevas fechas y duraciones en los scripts x1 y x2.
- Se implementó un sistema de fallback para el directorio de trabajo, creando un directorio `.debug` en caso de que no se pueda cargar la configuración.
- Se añadieron nuevas funcionalidades en el script x2_process_CAx.py para generar informes en Excel con detalles de IOs por nodos del PLC.
- Se mejoró la documentación en el archivo readme.md, incluyendo detalles sobre el uso del directorio de debug y su estructura.
- Se actualizaron las descripciones de los scripts en scripts_description.json para reflejar cambios recientes y nuevas funcionalidades.
This commit is contained in:
Miguel 2025-06-07 15:40:30 +02:00
parent 88806ee4e4
commit 9ac769e2fc
10 changed files with 7986 additions and 172 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
# IO Summary Table for PLC: A40510
| Network | Type | Address | Device Name | Sub-Device | OrderNo | Type | IO Type | IO Address | Number of Bits |
|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| PN/IE_1 | Ethernet/Profinet | 10.1.30.31 | U30110-AxisX | DO SERVO_1 | N/A | DO SERVO | Input | `EW 100..119` | 160 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.31 | U30110-AxisX | DO SERVO_1 | N/A | DO SERVO | Output | `AW 100..119` | 160 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.32 | U30210-AxisY | DO SERVO_1 | N/A | DO SERVO | Input | `EW 120..139` | 160 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.32 | U30210-AxisY | DO SERVO_1 | N/A | DO SERVO | Output | `AW 120..139` | 160 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.33 | U30310 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 4..51` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.33 | U30310 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 3..50` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.34 | U30410 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 52..99` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.34 | U30410 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 51..98` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.35 | U30510 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 156..203` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.35 | U30510 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 144..191` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.36 | U30610 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Input | `EW 204..251` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.36 | U30610 | DO with manufacturer telegr. 102_1 | N/A | DO with manufacturer telegr. 102 | Output | `AW 192..239` | 384 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.37 | M30710 | DS402_Extend-A | N/A | N/A | Input | `EW 4066..4087` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.37 | M30710 | DS402_Extend-A | N/A | N/A | Output | `AW 4132..4175` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.40 | M31010 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 272..287` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.40 | M31010 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 272..287` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.41 | M31110 | DS402_Extend-A | N/A | N/A | Input | `EW 4000..4021` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.41 | M31110 | DS402_Extend-A | N/A | N/A | Output | `AW 4000..4043` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.42 | M31210 | DS402_Extend-A | N/A | N/A | Input | `EW 4022..4043` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.42 | M31210 | DS402_Extend-A | N/A | N/A | Output | `AW 4044..4087` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.43 | M31310 | DS402_Extend-A | N/A | N/A | Input | `EW 4044..4065` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.43 | M31310 | DS402_Extend-A | N/A | N/A | Output | `AW 4088..4131` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.44 | M31410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 288..303` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.44 | M31410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 288..303` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.45 | M31510 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 304..319` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.45 | M31510 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 304..319` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.46 | M31610 | DS402_Extend-A | N/A | N/A | Input | `EW 4088..4109` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.46 | M31610 | DS402_Extend-A | N/A | N/A | Output | `AW 4176..4219` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.47 | M31710 | DS402_Extend-A | N/A | N/A | Input | `EW 4110..4131` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.47 | M31710 | DS402_Extend-A | N/A | N/A | Output | `AW 4220..4263` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.48 | M31810 | DS402_Extend-A | N/A | N/A | Input | `EW 4132..4153` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.48 | M31810 | DS402_Extend-A | N/A | N/A | Output | `AW 4264..4307` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.49 | M31910 | DS402_Extend-A | N/A | N/A | Input | `EW 4154..4175` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.49 | M31910 | DS402_Extend-A | N/A | N/A | Output | `AW 4308..4351` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.70 | M34010 | DS402_Extend-A | N/A | N/A | Input | `EW 4176..4197` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.70 | M34010 | DS402_Extend-A | N/A | N/A | Output | `AW 4352..4395` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.71 | M34110 | DS402_Extend-A | N/A | N/A | Input | `EW 4198..4219` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.71 | M34110 | DS402_Extend-A | N/A | N/A | Output | `AW 4396..4439` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.72 | M34210 | DS402_Extend-A | N/A | N/A | Input | `EW 4220..4241` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.72 | M34210 | DS402_Extend-A | N/A | N/A | Output | `AW 4440..4483` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.73 | M34310 | DS402_Extend-A | N/A | N/A | Input | `EW 4242..4263` | 176 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.73 | M34310 | DS402_Extend-A | N/A | N/A | Output | `AW 4484..4527` | 352 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.74 | M34410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Input | `EW 362..377` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.74 | M34410 | Module I/O (08 words) | N/A | Module I/O (08 words) | Output | `AW 340..355` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.170 | E44010-Encoder | EO Encoder Multiturn V2.x_1 | N/A | N/A | Input | `EW 140..155` | 128 |
| PN/IE_1 | Ethernet/Profinet | 10.1.30.170 | E44010-Encoder | EO Encoder Multiturn V2.x_1 | N/A | N/A | Output | `AW 140..143` | 32 |

View File

@ -1,38 +1,14 @@
--- Log de Ejecución: x1_export_CAx.py ---
Grupo: IO_adaptation
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-06-06 16:30:18
Fin: 2025-06-06 16:33:55
Duración: 0:03:37.052535
Inicio: 2025-06-06 18:25:09
Fin: 2025-06-06 18:25:20
Duración: 0:00:11.673530
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
--- TIA Portal Project CAx Exporter and Analyzer ---
Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Detected TIA Portal version: 19.0 (from extension .ap19)
Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log
Connecting to TIA Portal V19.0...
2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
Connected.
Opening project: 98050_PLC_01.ap19...
2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19
Project opened.
Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml...
CAx data exported successfully.
Closing TIA Portal...
2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal
TIA Portal closed.
Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
Script finished.
No project file selected. Exiting.
--- ERRORES (STDERR) ---
Ninguno

View File

@ -1,67 +1,76 @@
--- Log de Ejecución: x2_process_CAx.py ---
Grupo: IO_adaptation
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Inicio: 2025-06-06 16:34:01
Fin: 2025-06-06 16:34:09
Duración: 0:00:08.316649
Inicio: 2025-06-07 14:29:10
Fin: 2025-06-07 14:29:16
Duración: 0:00:06.140835
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v32.2 - Simplified IO Address Format (Separate Start/End)) ---
Using configured working directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json
Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_IO_Upward_Debug.md
Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.aml
Pass 1: Found 363 InternalElement(s). Populating device dictionary...
Pass 2: Identifying PLCs and Networks (Refined v2)...
Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown
Identified Network: PN/IE_1 (6ce86626-0043-4a58-b675-cc13ac87121c) Type: Ethernet/Profinet
Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
Found 103 InternalLink(s).
Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1'
Mapping Device/Node 'E1' (NodeID:ab796923-4471-4a60-98f4-f8ea5920b3b9, Addr:10.1.30.11) to Network 'PN/IE_1'
--> Found PLC in children: A40510 (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8)
--> Associating Network 'PN/IE_1' with PLC 'A40510' (via Node 'E1' Addr: 10.1.30.11)
Mapping Device/Node 'IE1' (NodeID:c53ae31d-bee4-47ba-950d-15c31d2599b9, Addr:10.1.30.58) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1a8366c9-7d4c-4e49-b0a3-77d445eabc8b, Addr:10.1.30.59) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:deeda41b-54d0-4c86-82c3-a311f753021e, Addr:10.1.30.60) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8a916a9d-895e-4a12-9190-516ef6a8f191, Addr:10.1.30.61) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c51471e0-9621-4ef7-b050-09bf4d695ea1, Addr:10.1.30.62) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:77ca312c-3dd0-46f6-bd64-5da69a99cf6f, Addr:10.1.30.63) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:011019c6-5925-4544-87aa-27288c3aa70c, Addr:10.1.30.64) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5ea1e894-b51c-44f9-8aff-80c58c6cb7ef, Addr:10.1.30.65) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:612916a9-7a26-4712-9de2-d3c7894db862, Addr:10.1.30.66) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7be7cf9f-f7af-418c-8fe4-96b4a97a581d, Addr:10.1.30.31) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:135492a8-02ab-4236-92ce-7a5585538297, Addr:10.1.30.32) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b7a06147-6428-4d95-ba0d-834fad49a1ae, Addr:10.1.30.170) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:7ce83bdf-dc4d-40f0-85c8-a246dd2c44be, Addr:10.1.30.33) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e9a07506-2869-4c34-8541-ee021d1623f0, Addr:10.1.30.34) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:3dd0f886-c3d0-4628-804b-ae18cf5931e8, Addr:10.1.30.35) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:67c3fa72-a956-4363-9a4d-e3300d7d1429, Addr:10.1.30.36) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b35e73e8-a9ad-47e2-8ad5-00442c7a2df7, Addr:10.1.30.40) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:867d0580-06cd-4722-a66f-8c5559b624f5, Addr:10.1.30.44) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:6f1b4062-80ac-4d1a-b351-546c9d0157e2, Addr:10.1.30.41) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:c0016a06-48cc-47af-83aa-d4a0a6cb44f6, Addr:10.1.30.42) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:07fa5e8f-ffcf-4d81-9897-5ff9f73d6125, Addr:10.1.30.43) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:5e8a4449-c958-4397-8b71-877af262333b, Addr:10.1.30.37) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:79096325-dcb8-4650-bc44-2c9735a93f52, Addr:10.1.30.45) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:957e5975-eafe-477c-a682-bebf330a2868, Addr:10.1.30.46) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:379ccd79-27b4-4b53-a552-3da783bc5b25, Addr:10.1.30.47) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:b53dd719-e8af-431f-b837-a0903ffb3a76, Addr:10.1.30.48) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:0f28d88a-5208-4fc4-8ee1-f1bb33a947e8, Addr:10.1.30.49) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:d1f8ea18-50d2-410e-9966-136d8a79471d, Addr:10.1.30.70) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:1c86a627-5646-45e7-9c21-5d18d6544568, Addr:10.1.30.71) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:e80a9939-59d7-44e0-9a46-1fade44e1b78, Addr:10.1.30.72) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:8c51fa26-883a-468c-8c36-c0e1b31852e4, Addr:10.1.30.74) to Network 'PN/IE_1'
Mapping Device/Node 'IE1' (NodeID:17be2ccc-dede-4187-ba77-1ad8499a7349, Addr:10.1.30.73) to Network 'PN/IE_1'
Data extraction and structuring complete.
Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01.hierarchical.json
JSON data written successfully.
IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_IO_Upward_Debug.md
Found 1 PLC(s). Generating individual hardware trees...
Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_Hardware_Tree.md
Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_Hardware_Tree.md
IO summary table written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Hardware.md
IO summary table generated in separate file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Hardware.md
Generating Excel IO Report for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_IO_Report.xlsx
Generating Excel IO report for PLC: A40510
Excel IO report saved to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_IO_Report.xlsx
Total rows in report: 33
Script finished.

View File

@ -24,8 +24,29 @@ if __name__ == "__main__":
working_dir = configs.get("working_directory", "")
""""
configs = load_configuration()
working_directory = configs.get("working_directory")
try:
configs = load_configuration()
working_directory = configs.get("working_directory")
except Exception as e:
print(f"Warning: Could not load configuration (frontend not running): {e}")
configs = {}
working_directory = None
# Validate working directory with .debug fallback for standalone execution
if not working_directory or not os.path.isdir(working_directory):
print("Working directory not set or invalid in configuration.")
print("Using .debug directory as fallback for direct script execution.")
# Fallback to .debug directory under script location
script_dir = os.path.dirname(os.path.abspath(__file__))
debug_dir = os.path.join(script_dir, ".debug")
# Create .debug directory if it doesn't exist
os.makedirs(debug_dir, exist_ok=True)
working_directory = debug_dir
print(f"Using debug directory: {working_directory}")
else:
print(f"Using configured working directory: {working_directory}")
# Acceder a la configuración específica del grupo
group_config = configs.get("level2", {})
@ -38,6 +59,34 @@ if __name__ == "__main__":
### Debug Directory Fallback
Cuando los scripts se ejecutan directamente (no desde el frontend), utilizan un sistema de fallback para determinar el directorio de trabajo:
1. **Con Frontend**: Los scripts utilizan el `working_directory` configurado dinámicamente a través de `load_configuration()`
2. **Ejecución Directa (Debug)**: Si no hay configuración válida, los scripts crean automáticamente un directorio `.debug` en la ubicación del script
**Estructura del directorio de debug:**
```
IO_adaptation/
├── .debug/ # ← Directorio creado automáticamente para debug
│ ├── *.aml # Archivos AML procesados
│ ├── *.hierarchical.json # Datos estructurados extraídos
│ ├── *_IO_Upward_Debug.md # Archivo de debug de conexiones IO
│ ├── Hardware.md # Tabla resumen de IO
│ └── <PLC_Name>/
│ └── Documentation/
│ └── *_Hardware_Tree.md # Árbol de hardware por PLC
├── x2_process_CAx.py # Script principal
└── example/ # Archivos de ejemplo
```
**Ventajas del directorio .debug:**
- ✅ Separación clara entre archivos de debug y archivos de producción
- ✅ Fácil limpieza (se puede eliminar todo el directorio `.debug`)
- ✅ No interfiere con archivos del proyecto principal
- ✅ Creación automática sin configuración manual
### Directory structure for Tia Portal scripts
<working_directory>/

View File

@ -29,10 +29,46 @@
"long_description": "# Script de Adaptación IO para PLCTags\n\n## Descripción\n\nEste script automatiza la adaptación de tags PLC entre hardware físico Siemens TIA Portal y software master, actualizando las direcciones lógicas según un archivo de mapeo en formato Markdown.\n\n## Funcionamiento\n\n### Entradas\n- **Archivo Excel**: Exportación de tags desde TIA Portal\n- **Archivo Markdown**: Tabla de adaptación IO con mapeo entre hardware y master tags\n\n### Proceso\n1. **Análisis inicial**:\n - Lee la tabla Markdown de adaptación IO\n - Identifica las columnas de IO y Master Tag\n - Crea un diccionario de mapeo entre tags y direcciones IO\n\n2. **Procesamiento de tags**:\n - Filtra tags en los paths \"Inputs\", \"Outputs\", \"IO Not in Hardware\\InputsMaster\" y \"IO Not in Hardware\\OutputsMaster\"\n - Actualiza las direcciones lógicas de los tags encontrados en el mapeo\n - Reasigna paths según el tipo de señal (%E -> \"Inputs\", %A -> \"Outputs\")\n\n3. **Gestión de tags sin hardware**:\n - Asigna direcciones de memoria (%Mxxxx.x) para tags sin correspondencia\n - Tags de entrada: Asignados a \"IO Not in Hardware\\InputsMaster\" desde %M3600.0\n - Tags de salida: Asignados a \"IO Not in Hardware\\OutputsMaster\" desde %M3800.0\n\n4. **Manejo de direcciones**:\n - Convierte formatos (I0.0 → %E0.0, PEW100 → %EW100, etc.)\n - Gestiona bits incrementalmente (0.0, 0.1, ..., 0.7, luego 1.0)\n - Alinea words cada 2 bytes (%MW3600, %MW3602, etc.)\n\n### Salidas\n- **Archivo Excel actualizado**: Con las nuevas direcciones lógicas y paths\n- **Archivo de log**: Registro detallado del proceso con estadísticas\n\n## Uso\n1. Ejecute el script\n2. Seleccione el archivo Excel exportado de TIA Portal\n3. Seleccione el archivo Markdown con la tabla de adaptación\n4. El script generará automáticamente el archivo Excel actualizado\n\n## Detección de tipos\n- **Entradas**: Identificadas por prefijos (DI_, P_AI_, etc.)\n- **Salidas**: Identificadas por prefijos (DO_, P_AO_, etc.)\n- **Bits vs Words**: Determinados por el tipo de dato (Bool vs Word/Int)",
"hidden": false
},
"x7_update_CAx.py": {
"display_name": "7: Actualizar AML desde Excel modificado",
"short_description": "Actualiza un archivo AML original basándose en las modificaciones hechas en el archivo Excel de IOs.",
"long_description": "Este script permite hacer un 'round trip' de datos: extrae información del AML al Excel, permite ediciones manuales en el Excel, y luego aplica esos cambios de vuelta al AML original.\n***\n**Pipeline:**\n\n1. **Entrada:** Solicita el archivo AML original y el archivo Excel modificado (generado por x2_process_CAx.py)\n2. **Validación:** Genera un Excel de referencia desde el AML y compara con el Excel modificado para validar que la estructura base sea la misma (mismos nodos y nombres)\n3. **Detección de cambios:** Identifica modificaciones en:\n - Direcciones IP/nodos\n - Direcciones IO (inputs/outputs)\n - Tipos de dispositivos\n - Números de orden\n - Versiones de firmware\n4. **Aplicación:** Localiza los elementos correspondientes en el XML del AML y aplica los cambios detectados\n5. **Salida:** Genera un nuevo archivo AML con sufijo '_updated' que contiene todas las modificaciones\n\n**ID único:** Utiliza PLC+Device Name como identificador único para mapear cambios entre Excel y AML.\n\n**Restricciones:** Solo procede si el Excel modificado mantiene la misma cantidad de nodos y nombres que el Excel original.",
"hidden": false
},
"x0_documentation.py": {
"display_name": "0:Documentación",
"short_description": "Descripción del flujo de trabajo",
"long_description": "### Flujo de trabajo:\n***\n1. Se usa [x1] para exportar los datos de configuración desde el proyecto de Tia Portal, incluidos los IO configurados en el hardware. Esto genera un archivo AML.\n2. Con [x2] se procesa el archivo AML y se convierte en markdown. Si hay un solo PLC se genera el archivo [Hardware.md]\n3. Se deben procesar los IO **desde el esquema eléctrico y agregarlos** a [Hardware.md]\n4. Se debe exportar todos los tags como un archivo excel desde el Tia Portal.\n5. Con [x3] se convierte y se filtran los tags según los path definidos en [io_paths_config] y se genera un archivo Master IO Tags.md\n6. [x4] Genera el prompt a usar con Claude usando MCP para que pueda acceder a los archivos originales y evitar tener que hacer uploads.\n7. Una vez que se genera el archivo procesado por el LLM se puede usar [x5] que convierte las adaptaciones a un archivo que luego se puede importar en Tia Portal\n8. Importar en Tia Portal el archivo [PLCTags_Updated.xlsx]",
"long_description": "### Flujo de trabajo:\n***\n1. Se usa [x1] para exportar los datos de configuración desde el proyecto de Tia Portal, incluidos los IO configurados en el hardware. Esto genera un archivo AML.\n2. Con [x2] se procesa el archivo AML y se convierte en markdown. Si hay un solo PLC se genera el archivo [Hardware.md]. **Además genera un archivo Excel con los IOs por nodos del PLC.**\n3. Se deben procesar los IO **desde el esquema eléctrico y agregarlos** a [Hardware.md]\n4. Se debe exportar todos los tags como un archivo excel desde el Tia Portal.\n5. Con [x3] se convierte y se filtran los tags según los path definidos en [io_paths_config] y se genera un archivo Master IO Tags.md\n6. [x4] Genera el prompt a usar con Claude usando MCP para que pueda acceder a los archivos originales y evitar tener que hacer uploads.\n7. Una vez que se genera el archivo procesado por el LLM se puede usar [x5] que convierte las adaptaciones a un archivo que luego se puede importar en Tia Portal\n8. Importar en Tia Portal el archivo [PLCTags_Updated.xlsx]\n\n**Nuevo flujo alternativo con Excel:**\nDesp¹és del paso 2, se puede usar [x7] para:\n- Modificar manualmente el archivo Excel de IOs generado\n- Aplicar los cambios de vuelta al archivo AML original\n- Generar un AML actualizado con las modificaciones",
"hidden": false
},
"analyze_u32810_specific.py": {
"display_name": "analyze_u32810_specific",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"compare_u32810_telegrams.py": {
"display_name": "compare_u32810_telegrams",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"comprehensive_address_search.py": {
"display_name": "comprehensive_address_search",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"find_address_differences.py": {
"display_name": "find_address_differences",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
},
"find_u32810_addresses.py": {
"display_name": "find_u32810_addresses",
"short_description": "Sin descripción corta.",
"long_description": "",
"hidden": false
}
}

View File

@ -2,6 +2,7 @@
"path": "D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
"history": [
"D:\\Trabajo\\VM\\44 - 98050 - Fiera\\Reporte\\ExportsTia",
"D:\\Proyectos\\Scripts\\ParamManagerScripts\\backend\\script_groups\\IO_adaptation\\example",
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\TAGsIO\\v2"
]
}

View File

@ -12,6 +12,7 @@ import json
from pathlib import Path
import re
import math # Needed for ceil
import pandas as pd # For Excel generation
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -221,6 +222,7 @@ def extract_aml_data(root):
is_subnet_by_role = any("SUBNET" in rc.upper() for rc in role_classes)
if is_subnet_by_role:
is_network = True
# Check role classes first
for rc in role_classes:
rc_upper = rc.upper()
if "PROFINET" in rc_upper or "ETHERNET" in rc_upper:
@ -229,12 +231,23 @@ def extract_aml_data(root):
elif "PROFIBUS" in rc_upper:
net_type = "Profibus"
break
# If still unknown, check the Type attribute (crucial for PROFINET)
if net_type == "Unknown":
type_attr = device.get("attributes", {}).get("Type", "")
if type_attr.upper() == "ETHERNET":
net_type = "Ethernet/Profinet"
elif type_attr.upper() == "PROFIBUS":
net_type = "Profibus"
# Finally, check device name as fallback
if net_type == "Unknown":
if "PROFIBUS" in device["name"].upper():
net_type = "Profibus"
elif (
"ETHERNET" in device["name"].upper()
or "PROFINET" in device["name"].upper()
or "PN/IE" in device["name"].upper() # Add common PROFINET naming pattern
):
net_type = "Ethernet/Profinet"
if is_network:
@ -328,6 +341,33 @@ def extract_aml_data(root):
and interface_info["parent_id"] in data["plcs"]
):
potential_plc_id = interface_info["parent_id"]
# Enhanced PLC search: look for PLCs in the entire hierarchy
if not potential_plc_id:
# Search for PLCs in the hierarchy starting from linked_device_id
current_search_id = linked_device_id
search_depth = 0
max_search_depth = 10
while current_search_id and search_depth < max_search_depth:
# Check current device and all its children for PLCs
device_to_check = data["devices"].get(current_search_id)
if device_to_check:
# Check if current device has PLCs as children
for child_id in device_to_check.get("children_ids", []):
if child_id in data["plcs"]:
potential_plc_id = child_id
print(f" --> Found PLC in children: {data['plcs'][child_id].get('name', 'Unknown PLC')} (ID: {child_id})")
break
if potential_plc_id:
break
# Move up to parent
current_search_id = device_to_check.get("parent_id")
search_depth += 1
else:
break
if potential_plc_id:
plc_object = data["plcs"][potential_plc_id]
if "connected_networks" not in plc_object:
@ -497,6 +537,337 @@ def generate_io_summary_file(all_plc_io_for_table, md_file_path, plc_name, proje
return hardware_file_path
# --- generate_io_excel_report function ---
def generate_io_excel_report(project_data, excel_file_path, target_plc_id, output_root_path):
"""
Genera un archivo Excel con información detallada de IOs por nodos del PLC.
"""
plc_info = project_data.get("plcs", {}).get(target_plc_id)
if not plc_info:
print(f"PLC ID '{target_plc_id}' not found in project data.")
return
plc_name = plc_info.get('name', target_plc_id)
print(f"Generating Excel IO report for PLC: {plc_name}")
# Lista para almacenar todas las filas del Excel
excel_rows = []
# Procesar las redes conectadas al PLC
plc_networks = plc_info.get("connected_networks", {})
if not plc_networks:
# Si no hay redes, crear una fila básica del PLC
excel_rows.append({
'PLC Name': plc_name,
'Network Path': 'No networks connected',
'Network Type': 'N/A',
'Device Address': 'N/A',
'Device Name': plc_name,
'Device Type': plc_info.get("type_name", "N/A"),
'Order Number': plc_info.get("order_number", "N/A"),
'Firmware Version': plc_info.get("firmware_version", "N/A"),
'Position': plc_info.get("position", "N/A"),
'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A',
'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A',
'Total Input Bits': 0,
'Total Output Bits': 0,
'Module Name': 'N/A',
'Module Type': 'N/A',
'Module Order Number': 'N/A'
})
else:
# Procesar cada red conectada
for net_id, plc_addr_on_net in plc_networks.items():
net_info = project_data.get("networks", {}).get(net_id)
if not net_info:
continue
network_name = net_info.get('name', net_id)
network_type = net_info.get('type', 'Unknown')
devices_on_net = net_info.get("devices_on_net", {})
# Identificar nodos que pertenecen al PLC para excluirlos de la lista de dispositivos
plc_interface_and_node_ids = set()
for node in plc_info.get("network_nodes", []):
plc_interface_and_node_ids.add(node["id"])
interface_id_lookup = project_data["devices"].get(node["id"], {}).get("parent_id")
if interface_id_lookup:
plc_interface_and_node_ids.add(interface_id_lookup)
plc_interface_and_node_ids.add(target_plc_id)
# Filtrar dispositivos que no son interfaces del PLC
other_devices = [
(node_id, node_addr)
for node_id, node_addr in devices_on_net.items()
if node_id not in plc_interface_and_node_ids
]
if not other_devices:
# Si no hay otros dispositivos, crear fila solo para el PLC en esta red
excel_rows.append({
'PLC Name': plc_name,
'Network Path': f"{network_name} -> {plc_name}",
'Network Type': network_type,
'Device Address': plc_addr_on_net,
'Device Name': plc_name,
'Device Type': plc_info.get("type_name", "N/A"),
'Order Number': plc_info.get("order_number", "N/A"),
'Firmware Version': plc_info.get("firmware_version", "N/A"),
'Position': plc_info.get("position", "N/A"),
'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A',
'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A',
'Total Input Bits': 0,
'Total Output Bits': 0,
'Module Name': 'PLC Main Unit',
'Module Type': plc_info.get("type_name", "N/A"),
'Module Order Number': plc_info.get("order_number", "N/A")
})
else:
# Procesar cada dispositivo en la red
for node_id, node_addr in other_devices:
node_info = project_data.get("devices", {}).get(node_id)
if not node_info:
continue
# Determinar la estructura jerárquica del dispositivo
interface_id = node_info.get("parent_id")
interface_info = None
actual_device_id = None
actual_device_info = None
if interface_id:
interface_info = project_data.get("devices", {}).get(interface_id)
if interface_info:
actual_device_id = interface_info.get("parent_id")
if actual_device_id:
actual_device_info = project_data.get("devices", {}).get(actual_device_id)
# Determinar qué información mostrar
display_info = actual_device_info if actual_device_info else (interface_info if interface_info else node_info)
display_id = actual_device_id if actual_device_info else (interface_id if interface_info else node_id)
device_name = display_info.get("name", display_id)
device_type = display_info.get("type_name", "N/A")
device_order = display_info.get("order_number", "N/A")
device_position = display_info.get("position", "N/A")
firmware_version = display_info.get("firmware_version", "N/A")
# Construir el path de red
network_path = f"{network_name} ({network_type}) -> {device_name} @ {node_addr}"
# Buscar IOs recursivamente
io_search_root_id = display_id
io_search_root_info = project_data.get("devices", {}).get(io_search_root_id)
aggregated_io_addresses = []
# Buscar IOs en la estructura padre si existe
parent_structure_id = io_search_root_info.get("parent_id") if io_search_root_info else None
if parent_structure_id:
# Buscar IOs en dispositivos hermanos bajo la misma estructura padre
for dev_scan_id, dev_scan_info in project_data.get("devices", {}).items():
if dev_scan_info.get("parent_id") == parent_structure_id:
module_context = {
"id": dev_scan_id,
"name": dev_scan_info.get("name", dev_scan_id),
"order_number": dev_scan_info.get("order_number", "N/A"),
"type_name": dev_scan_info.get("type_name", "N/A")
}
io_from_sibling = find_io_recursively(dev_scan_id, project_data, module_context)
aggregated_io_addresses.extend(io_from_sibling)
elif io_search_root_id:
# Buscar IOs directamente en el dispositivo
module_context = {
"id": io_search_root_id,
"name": io_search_root_info.get("name", io_search_root_id),
"order_number": io_search_root_info.get("order_number", "N/A"),
"type_name": io_search_root_info.get("type_name", "N/A")
}
aggregated_io_addresses = find_io_recursively(io_search_root_id, project_data, module_context)
# Procesar IOs por módulo
if aggregated_io_addresses:
# Agrupar IOs por módulo
ios_by_module = {}
for addr_info in aggregated_io_addresses:
module_id = addr_info.get("module_id")
if module_id not in ios_by_module:
ios_by_module[module_id] = {
'module_info': {
'name': addr_info.get('module_name', '?'),
'type': addr_info.get('module_type_name', 'N/A'),
'order': addr_info.get('module_order_number', 'N/A'),
'position': addr_info.get('module_pos', 'N/A')
},
'inputs': [],
'outputs': []
}
# Clasificar IO como input u output
io_type = addr_info.get("type", "").lower()
if io_type == "input":
ios_by_module[module_id]['inputs'].append(addr_info)
elif io_type == "output":
ios_by_module[module_id]['outputs'].append(addr_info)
# Crear una fila por cada módulo con IOs
for module_id, module_data in ios_by_module.items():
module_info = module_data['module_info']
# Calcular direcciones de entrada - formato simplificado
input_start_addr = 'N/A'
input_end_addr = 'N/A'
total_input_bits = 0
for addr_info in module_data['inputs']:
start_str = addr_info.get("start", "?")
length_str = addr_info.get("length", "?")
try:
start_byte = int(start_str)
length_bits = int(length_str)
length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0:
length_bytes = 1
end_byte = start_byte + length_bytes - 1
# Para múltiples rangos, tomar el primer inicio y último fin
if input_start_addr == 'N/A':
input_start_addr = start_byte
input_end_addr = end_byte
else:
input_start_addr = min(input_start_addr, start_byte)
input_end_addr = max(input_end_addr, end_byte)
total_input_bits += length_bits
except:
# En caso de error, mantener N/A
pass
# Calcular direcciones de salida - formato simplificado
output_start_addr = 'N/A'
output_end_addr = 'N/A'
total_output_bits = 0
for addr_info in module_data['outputs']:
start_str = addr_info.get("start", "?")
length_str = addr_info.get("length", "?")
try:
start_byte = int(start_str)
length_bits = int(length_str)
length_bytes = math.ceil(length_bits / 8.0)
if length_bits > 0 and length_bytes == 0:
length_bytes = 1
end_byte = start_byte + length_bytes - 1
# Para múltiples rangos, tomar el primer inicio y último fin
if output_start_addr == 'N/A':
output_start_addr = start_byte
output_end_addr = end_byte
else:
output_start_addr = min(output_start_addr, start_byte)
output_end_addr = max(output_end_addr, end_byte)
total_output_bits += length_bits
except:
# En caso de error, mantener N/A
pass
excel_rows.append({
'PLC Name': plc_name,
'Network Path': network_path,
'Network Type': network_type,
'Device Address': node_addr,
'Device Name': device_name,
'Device Type': device_type,
'Order Number': device_order,
'Firmware Version': firmware_version,
'Position': device_position,
'IO Input Start Address': input_start_addr,
'IO Input End Address': input_end_addr,
'IO Output Start Address': output_start_addr,
'IO Output End Address': output_end_addr,
'Total Input Bits': total_input_bits,
'Total Output Bits': total_output_bits,
'Module Name': module_info['name'],
'Module Type': module_info['type'],
'Module Order Number': module_info['order']
})
else:
# Dispositivo sin IOs
excel_rows.append({
'PLC Name': plc_name,
'Network Path': network_path,
'Network Type': network_type,
'Device Address': node_addr,
'Device Name': device_name,
'Device Type': device_type,
'Order Number': device_order,
'Firmware Version': firmware_version,
'Position': device_position,
'IO Input Start Address': 'N/A',
'IO Input End Address': 'N/A',
'IO Output Start Address': 'N/A',
'IO Output End Address': 'N/A',
'Total Input Bits': 0,
'Total Output Bits': 0,
'Module Name': 'N/A',
'Module Type': 'N/A',
'Module Order Number': 'N/A'
})
# Crear DataFrame y guardar Excel
if excel_rows:
df = pd.DataFrame(excel_rows)
# Agregar columna de ID único para compatibilidad con x7_update_CAx
df['Unique_ID'] = df['PLC Name'] + "+" + df['Device Name']
# Reordenar columnas para mejor legibilidad
column_order = [
'PLC Name', 'Network Path', 'Network Type', 'Device Address', 'Device Name',
'Device Type', 'Order Number', 'Firmware Version', 'Position',
'Module Name', 'Module Type', 'Module Order Number',
'IO Input Start Address', 'IO Input End Address', 'IO Output Start Address', 'IO Output End Address',
'Total Input Bits', 'Total Output Bits', 'Unique_ID' # Agregar al final para compatibilidad
]
df = df.reindex(columns=column_order)
try:
# Guardar como Excel con formato
with pd.ExcelWriter(excel_file_path, engine='openpyxl') as writer:
df.to_excel(writer, sheet_name='IO Report', index=False)
# Ajustar ancho de columnas
worksheet = writer.sheets['IO Report']
for column in worksheet.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = min(max_length + 2, 50) # Máximo 50 caracteres
worksheet.column_dimensions[column_letter].width = adjusted_width
print(f"Excel IO report saved to: {excel_file_path}")
print(f"Total rows in report: {len(excel_rows)}")
except Exception as e:
print(f"ERROR saving Excel file {excel_file_path}: {e}")
traceback.print_exc()
else:
print("No data to write to Excel file.")
# --- generate_markdown_tree function ---
def generate_markdown_tree(project_data, md_file_path, target_plc_id, output_root_path):
"""(Modified) Generates hierarchical Markdown for a specific PLC."""
@ -1151,25 +1522,34 @@ def sanitize_filename(name):
# --- Main Execution ---
if __name__ == "__main__":
configs = load_configuration()
working_directory = configs.get("working_directory")
try:
configs = load_configuration()
working_directory = configs.get("working_directory")
except Exception as e:
print(f"Warning: Could not load configuration (frontend not running): {e}")
configs = {}
working_directory = None
script_version = "v31.1 - Corrected IO Summary Table Initialization" # Updated version
script_version = "v32.2 - Simplified IO Address Format (Separate Start/End)" # Updated version
print(
f"--- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter ({script_version}) ---"
)
# Validate working directory
# Validate working directory with .debug fallback
if not working_directory or not os.path.isdir(working_directory):
print("ERROR: Working directory not set or invalid in configuration.")
print("Attempting to use script's directory as fallback.")
# Fallback to script's directory or current directory if needed
working_directory = os.path.dirname(os.path.abspath(__file__))
if not os.path.isdir(working_directory):
working_directory = os.getcwd()
print(f"Using fallback directory: {working_directory}")
# Optionally, prompt user to select a working directory here if critical
# output_dir = select_output_directory() # Keep this if you want user selection on failure
print("Working directory not set or invalid in configuration.")
print("Using .debug directory as fallback for direct script execution.")
# Fallback to .debug directory under script location
script_dir = os.path.dirname(os.path.abspath(__file__))
debug_dir = os.path.join(script_dir, ".debug")
# Create .debug directory if it doesn't exist
os.makedirs(debug_dir, exist_ok=True)
working_directory = debug_dir
print(f"Using debug directory: {working_directory}")
else:
print(f"Using configured working directory: {working_directory}")
# Use working_directory as the output directory
output_dir = working_directory
@ -1227,6 +1607,12 @@ if __name__ == "__main__":
print(f" Generating Hardware Tree for PLC '{plc_name_original}' (ID: {plc_id}) at: {output_plc_md_file.resolve()}")
# Pass output_path as the root directory for Hardware.md placement
generate_markdown_tree(project_data, str(output_plc_md_file), plc_id, str(output_path))
# Generate Excel IO report for this PLC
excel_io_filename = f"{input_path.stem}_IO_Report.xlsx"
output_excel_file = plc_doc_dir / excel_io_filename
print(f" Generating Excel IO Report for PLC '{plc_name_original}' (ID: {plc_id}) at: {output_excel_file.resolve()}")
generate_io_excel_report(project_data, str(output_excel_file), plc_id, str(output_path))
else:
print("\nFailed to process AML data. Halting before generating PLC-specific trees.")

View File

@ -0,0 +1,619 @@
"""
x7_update_CAx.py : Script que actualiza un archivo AML original basándose en las modificaciones
hechas en el archivo Excel de IOs generado por x2_process_CAx.py
Pipeline:
1. Lee el AML original
2. Lee el Excel modificado
3. Genera Excel de referencia desde el AML para comparar
4. Valida que la estructura base sea la misma (mismos nodos, nombres)
5. Identifica cambios en IOs, direcciones IP, etc.
6. Aplica los cambios al AML original
7. Genera un nuevo archivo AML con sufijo "_updated"
"""
import os
import sys
import tkinter as tk
from tkinter import filedialog, messagebox
import traceback
from lxml import etree as ET
import pandas as pd
from pathlib import Path
import re
import math
import tempfile
script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
sys.path.append(script_root)
from backend.script_utils import load_configuration
# Import functions from x2_process_CAx
from x2_process_CAx import (
extract_aml_data,
generate_io_excel_report,
sanitize_filename
)
def select_aml_file(title="Select Original AML File", initial_dir=None):
"""Abre un diálogo para seleccionar un archivo AML."""
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
title=title,
filetypes=[("AML Files", "*.aml"), ("All Files", "*.*")],
initialdir=initial_dir
)
root.destroy()
if not file_path:
return None
return file_path
def select_excel_file(title="Select Modified Excel File", initial_dir=None):
"""Abre un diálogo para seleccionar un archivo Excel."""
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(
title=title,
filetypes=[("Excel Files", "*.xlsx"), ("Excel Files", "*.xls"), ("All Files", "*.*")],
initialdir=initial_dir
)
root.destroy()
if not file_path:
return None
return file_path
def generate_reference_excel_from_aml(aml_file_path, temp_dir):
"""
Genera un Excel de referencia desde el AML para comparar con el Excel modificado.
Retorna el path al Excel generado y los project_data.
"""
print("Generando Excel de referencia desde AML original...")
# Extraer datos del AML
try:
parser = ET.XMLParser(remove_blank_text=True, huge_tree=True)
tree = ET.parse(aml_file_path, parser)
root = tree.getroot()
project_data = extract_aml_data(root)
except Exception as e:
print(f"ERROR procesando archivo AML {aml_file_path}: {e}")
return None, None
if not project_data or not project_data.get("plcs"):
print("No se encontraron PLCs en el archivo AML.")
return None, None
# Generar Excel para cada PLC y combinar en uno solo
all_excel_rows = []
for plc_id, plc_data in project_data.get("plcs", {}).items():
# Crear archivo temporal para este PLC
temp_excel_path = os.path.join(temp_dir, f"temp_plc_{plc_id}.xlsx")
# Generar Excel para este PLC (reutilizamos la función existente)
generate_io_excel_report(project_data, temp_excel_path, plc_id, temp_dir)
# Leer el Excel generado y agregar a la lista combinada
if os.path.exists(temp_excel_path):
try:
df_plc = pd.read_excel(temp_excel_path, sheet_name='IO Report')
# Agregar columna de ID único
df_plc['Unique_ID'] = df_plc['PLC Name'] + "+" + df_plc['Device Name']
all_excel_rows.append(df_plc)
os.remove(temp_excel_path) # Limpiar archivo temporal
except Exception as e:
print(f"ERROR leyendo Excel temporal para PLC {plc_id}: {e}")
if not all_excel_rows:
print("No se pudieron generar datos de Excel de referencia.")
return None, None
# Combinar todos los DataFrames
combined_df = pd.concat(all_excel_rows, ignore_index=True)
# Guardar Excel de referencia
reference_excel_path = os.path.join(temp_dir, "reference_excel.xlsx")
combined_df.to_excel(reference_excel_path, sheet_name='IO Report', index=False)
return reference_excel_path, project_data
def compare_excel_files(reference_excel_path, modified_excel_path):
"""
Compara el Excel de referencia con el Excel modificado.
Retorna (is_valid, changes_dict) donde:
- is_valid: True si la estructura básica es la misma
- changes_dict: diccionario con los cambios detectados
"""
print("Comparando archivos Excel...")
try:
# Leer ambos archivos
df_ref = pd.read_excel(reference_excel_path, sheet_name='IO Report')
df_mod = pd.read_excel(modified_excel_path, sheet_name='IO Report')
# Agregar columna de ID único si no existe
if 'Unique_ID' not in df_ref.columns:
df_ref['Unique_ID'] = df_ref['PLC Name'] + "+" + df_ref['Device Name']
if 'Unique_ID' not in df_mod.columns:
df_mod['Unique_ID'] = df_mod['PLC Name'] + "+" + df_mod['Device Name']
except Exception as e:
print(f"ERROR leyendo archivos Excel: {e}")
return False, {}
# Validar estructura básica
if len(df_ref) != len(df_mod):
print(f"ERROR: Número de filas diferente. Referencia: {len(df_ref)}, Modificado: {len(df_mod)}")
return False, {}
# Verificar que todos los IDs únicos coincidan
ref_ids = set(df_ref['Unique_ID'].tolist())
mod_ids = set(df_mod['Unique_ID'].tolist())
if ref_ids != mod_ids:
missing_in_mod = ref_ids - mod_ids
extra_in_mod = mod_ids - ref_ids
print("ERROR: Los IDs únicos no coinciden entre archivos.")
if missing_in_mod:
print(f" Faltantes en modificado: {missing_in_mod}")
if extra_in_mod:
print(f" Extras en modificado: {extra_in_mod}")
return False, {}
print("Estructura básica validada correctamente.")
# Detectar cambios
changes = {}
columns_to_monitor = [
'Device Address', 'IO Input Start Address', 'IO Input End Address',
'IO Output Start Address', 'IO Output End Address',
'Network Type', 'Device Type', 'Order Number', 'Firmware Version'
]
for index, row_ref in df_ref.iterrows():
unique_id = row_ref['Unique_ID']
row_mod = df_mod[df_mod['Unique_ID'] == unique_id].iloc[0]
row_changes = {}
for col in columns_to_monitor:
if col in df_ref.columns and col in df_mod.columns:
ref_val = str(row_ref[col]).strip() if pd.notna(row_ref[col]) else 'N/A'
mod_val = str(row_mod[col]).strip() if pd.notna(row_mod[col]) else 'N/A'
if ref_val != mod_val:
row_changes[col] = {
'original': ref_val,
'modified': mod_val
}
if row_changes:
changes[unique_id] = {
'plc_name': row_ref['PLC Name'],
'device_name': row_ref['Device Name'],
'changes': row_changes
}
print(f"Detectados {len(changes)} dispositivos con cambios.")
for unique_id, change_info in changes.items():
print(f" {unique_id}: {list(change_info['changes'].keys())}")
return True, changes
def find_device_in_aml(project_data, plc_name, device_name):
"""
Encuentra el device_id correspondiente en los datos del AML basándose en PLC y device name.
"""
# Buscar el PLC por nombre
target_plc_id = None
for plc_id, plc_data in project_data.get("plcs", {}).items():
if plc_data.get('name', plc_id) == plc_name:
target_plc_id = plc_id
break
if not target_plc_id:
return None
# Buscar el dispositivo en las redes del PLC
plc_info = project_data["plcs"][target_plc_id]
plc_networks = plc_info.get("connected_networks", {})
for net_id, plc_addr_on_net in plc_networks.items():
net_info = project_data.get("networks", {}).get(net_id)
if not net_info:
continue
devices_on_net = net_info.get("devices_on_net", {})
# Identificar nodos que pertenecen al PLC
plc_interface_and_node_ids = set()
for node in plc_info.get("network_nodes", []):
plc_interface_and_node_ids.add(node["id"])
interface_id_lookup = project_data["devices"].get(node["id"], {}).get("parent_id")
if interface_id_lookup:
plc_interface_and_node_ids.add(interface_id_lookup)
plc_interface_and_node_ids.add(target_plc_id)
# Buscar en dispositivos de la red
for node_id, node_addr in devices_on_net.items():
if node_id in plc_interface_and_node_ids:
continue
node_info = project_data.get("devices", {}).get(node_id)
if not node_info:
continue
# Determinar información del dispositivo
interface_id = node_info.get("parent_id")
interface_info = None
actual_device_id = None
actual_device_info = None
if interface_id:
interface_info = project_data.get("devices", {}).get(interface_id)
if interface_info:
actual_device_id = interface_info.get("parent_id")
if actual_device_id:
actual_device_info = project_data.get("devices", {}).get(actual_device_id)
display_info = actual_device_info if actual_device_info else (interface_info if interface_info else node_info)
display_name = display_info.get("name", "Unknown")
if display_name == device_name:
return {
'node_id': node_id,
'display_id': actual_device_id if actual_device_info else (interface_id if interface_info else node_id),
'node_info': node_info,
'display_info': display_info,
'network_id': net_id
}
return None
def apply_changes_to_aml(aml_tree, project_data, changes_dict):
"""
Aplica los cambios detectados al árbol XML del AML.
"""
print("Aplicando cambios al archivo AML...")
root = aml_tree.getroot()
changes_applied = 0
for unique_id, change_info in changes_dict.items():
plc_name = change_info['plc_name']
device_name = change_info['device_name']
changes = change_info['changes']
print(f" Procesando cambios para: {unique_id}")
# Encontrar el dispositivo en los datos del AML
device_info = find_device_in_aml(project_data, plc_name, device_name)
if not device_info:
print(f" ERROR: No se pudo encontrar el dispositivo en el AML")
continue
# Encontrar el elemento XML correspondiente
device_id = device_info['node_id']
xml_element = root.xpath(f".//*[@ID='{device_id}']")
if not xml_element:
print(f" ERROR: No se pudo encontrar el elemento XML con ID {device_id}")
continue
device_element = xml_element[0]
# Aplicar cambios específicos
for field, change_data in changes.items():
original_val = change_data['original']
modified_val = change_data['modified']
if field == 'Device Address':
# Cambiar dirección de red del dispositivo
if apply_network_address_change(device_element, modified_val):
print(f" ✓ Dirección de red actualizada: {original_val} -> {modified_val}")
changes_applied += 1
else:
print(f" ✗ Error actualizando dirección de red")
elif field in ['IO Input Start Address', 'IO Input End Address', 'IO Output Start Address', 'IO Output End Address']:
# Cambiar direcciones IO específicas
if apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id):
print(f"{field} actualizada: {original_val} -> {modified_val}")
changes_applied += 1
else:
print(f" ✗ Error actualizando {field}")
elif field in ['Device Type', 'Order Number', 'Firmware Version']:
# Cambiar atributos del dispositivo
if apply_device_attribute_change(device_element, field, modified_val):
print(f"{field} actualizado: {original_val} -> {modified_val}")
changes_applied += 1
else:
print(f" ✗ Error actualizando {field}")
print(f"Total de cambios aplicados: {changes_applied}")
return changes_applied > 0
def apply_network_address_change(device_element, new_address):
"""
Aplica cambio de dirección de red a un elemento del dispositivo.
"""
try:
# Buscar atributo NetworkAddress
addr_attr = device_element.xpath("./*[local-name()='Attribute'][@Name='NetworkAddress']")
if addr_attr:
value_elem = addr_attr[0].xpath("./*[local-name()='Value']")
if value_elem:
value_elem[0].text = new_address
return True
# Si no existe, crear el atributo
attr_elem = ET.SubElement(device_element, "Attribute")
attr_elem.set("Name", "NetworkAddress")
value_elem = ET.SubElement(attr_elem, "Value")
value_elem.text = new_address
return True
except Exception as e:
print(f" Error aplicando cambio de dirección: {e}")
return False
def apply_device_attribute_change(device_element, field, new_value):
"""
Aplica cambio de atributo del dispositivo.
"""
try:
# Mapear campos a nombres de atributos XML
attr_mapping = {
'Device Type': 'TypeName',
'Order Number': 'OrderNumber',
'Firmware Version': 'FirmwareVersion'
}
attr_name = attr_mapping.get(field)
if not attr_name:
return False
# Buscar atributo existente
attr_elem = device_element.xpath(f"./*[local-name()='Attribute'][@Name='{attr_name}']")
if attr_elem:
value_elem = attr_elem[0].xpath("./*[local-name()='Value']")
if value_elem:
value_elem[0].text = new_value
return True
# Si no existe, crear el atributo
attr_elem = ET.SubElement(device_element, "Attribute")
attr_elem.set("Name", attr_name)
value_elem = ET.SubElement(attr_elem, "Value")
value_elem.text = new_value
return True
except Exception as e:
print(f" Error aplicando cambio de atributo {field}: {e}")
return False
def apply_io_address_change(device_element, field, original_val, modified_val, project_data, device_id):
"""
Aplica cambios a las direcciones IO individuales. Ahora es más simple
porque las direcciones están separadas en campos específicos.
"""
try:
# Validar que el nuevo valor sea numérico o N/A
if modified_val != 'N/A' and modified_val != '':
try:
new_addr_value = int(modified_val)
except ValueError:
print(f" Error: Valor de dirección no válido: {modified_val}")
return False
else:
# Si es N/A, no hay dirección IO para este tipo
return True
# Determinar tipo de IO y si es start o end
is_input = "Input" in field
is_start = "Start" in field
io_type = "Input" if is_input else "Output"
# Buscar la estructura IO en el dispositivo
# Esto requiere encontrar los elementos Address y sus sub-atributos
address_elements = device_element.xpath("./*[local-name()='Attribute'][@Name='Address']")
if not address_elements:
print(f" No se encontró elemento Address en el dispositivo")
return False
# Buscar dentro del Address el sub-atributo correcto
address_element = address_elements[0]
io_subelements = address_element.xpath(f"./*[local-name()='Attribute']")
target_io_element = None
for io_sub in io_subelements:
# Verificar si es el tipo correcto (Input/Output)
type_val = io_sub.xpath("./*[local-name()='Attribute'][@Name='IoType']/*[local-name()='Value']/text()")
if type_val and type_val[0].lower() == io_type.lower():
target_io_element = io_sub
break
if not target_io_element:
print(f" No se encontró elemento {io_type} en Address")
return False
# Actualizar StartAddress o calcular Length según el campo
if is_start:
# Actualizar StartAddress
start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']")
if start_attr:
value_elem = start_attr[0].xpath("./*[local-name()='Value']")
if value_elem:
value_elem[0].text = str(new_addr_value)
return True
else:
# Crear StartAddress si no existe
start_attr_elem = ET.SubElement(target_io_element, "Attribute")
start_attr_elem.set("Name", "StartAddress")
value_elem = ET.SubElement(start_attr_elem, "Value")
value_elem.text = str(new_addr_value)
return True
else:
# Es End Address - necesitamos calcular Length basándose en Start y End
# Primero obtener StartAddress
start_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='StartAddress']/*[local-name()='Value']/text()")
if start_attr:
try:
start_value = int(start_attr[0])
# Length en bytes = (end - start + 1)
length_bytes = new_addr_value - start_value + 1
if length_bytes > 0:
# Convertir a bits (asumiendo 8 bits por byte)
length_bits = length_bytes * 8
# Actualizar Length
length_attr = target_io_element.xpath("./*[local-name()='Attribute'][@Name='Length']")
if length_attr:
value_elem = length_attr[0].xpath("./*[local-name()='Value']")
if value_elem:
value_elem[0].text = str(length_bits)
return True
else:
# Crear Length si no existe
length_attr_elem = ET.SubElement(target_io_element, "Attribute")
length_attr_elem.set("Name", "Length")
value_elem = ET.SubElement(length_attr_elem, "Value")
value_elem.text = str(length_bits)
return True
else:
print(f" Error: End address ({new_addr_value}) debe ser mayor que start address ({start_value})")
return False
except ValueError:
print(f" Error: No se pudo convertir start address: {start_attr[0]}")
return False
else:
print(f" Error: No se encontró StartAddress para calcular Length")
return False
return False
except Exception as e:
print(f" Error aplicando cambio de dirección IO {field}: {e}")
traceback.print_exc()
return False
def save_updated_aml(aml_tree, original_aml_path):
"""
Guarda el árbol XML modificado como un nuevo archivo AML con sufijo "_updated".
"""
original_path = Path(original_aml_path)
updated_path = original_path.parent / f"{original_path.stem}_updated{original_path.suffix}"
try:
# Escribir el XML actualizado
aml_tree.write(
str(updated_path),
pretty_print=True,
xml_declaration=True,
encoding='utf-8'
)
print(f"Archivo AML actualizado guardado en: {updated_path}")
return str(updated_path)
except Exception as e:
print(f"ERROR guardando archivo AML actualizado: {e}")
return None
def main():
"""Función principal del script."""
try:
configs = load_configuration()
working_directory = configs.get("working_directory")
except Exception as e:
print(f"Warning: No se pudo cargar configuración: {e}")
configs = {}
working_directory = None
script_version = "v1.1 - Simplified IO Address Handling (Start/End Separated)"
print(f"--- Actualizador de AML desde Excel Modificado ({script_version}) ---")
# Validar directorio de trabajo
if not working_directory or not os.path.isdir(working_directory):
print("Directorio de trabajo no configurado. Usando directorio actual.")
working_directory = os.getcwd()
print(f"Directorio de trabajo: {working_directory}")
# 1. Seleccionar archivo AML original
print("\n1. Seleccione el archivo AML original:")
aml_file_path = select_aml_file(initial_dir=working_directory)
if not aml_file_path:
print("No se seleccionó archivo AML. Saliendo.")
return
# 2. Seleccionar archivo Excel modificado
print("\n2. Seleccione el archivo Excel modificado:")
excel_file_path = select_excel_file(initial_dir=working_directory)
if not excel_file_path:
print("No se seleccionó archivo Excel. Saliendo.")
return
print(f"\nArchivo AML original: {aml_file_path}")
print(f"Archivo Excel modificado: {excel_file_path}")
# 3. Crear directorio temporal para archivos intermedios
with tempfile.TemporaryDirectory() as temp_dir:
print(f"\nUsando directorio temporal: {temp_dir}")
# 4. Generar Excel de referencia desde AML
reference_excel_path, project_data = generate_reference_excel_from_aml(aml_file_path, temp_dir)
if not reference_excel_path or not project_data:
print("ERROR: No se pudo generar Excel de referencia.")
return
# 5. Comparar archivos Excel
is_valid, changes_dict = compare_excel_files(reference_excel_path, excel_file_path)
if not is_valid:
print("ERROR: El archivo Excel modificado no es compatible con el AML original.")
return
if not changes_dict:
print("No se detectaron cambios en el Excel. No hay nada que actualizar.")
return
# 6. Cargar y parsear AML original
print("\nCargando archivo AML original...")
try:
parser = ET.XMLParser(remove_blank_text=True, huge_tree=True)
aml_tree = ET.parse(aml_file_path, parser)
except Exception as e:
print(f"ERROR parseando archivo AML: {e}")
return
# 7. Aplicar cambios al AML
success = apply_changes_to_aml(aml_tree, project_data, changes_dict)
if not success:
print("No se pudieron aplicar cambios al AML.")
return
# 8. Guardar AML actualizado
updated_aml_path = save_updated_aml(aml_tree, aml_file_path)
if updated_aml_path:
print(f"\n¡Proceso completado exitosamente!")
print(f"Archivo AML actualizado: {updated_aml_path}")
else:
print("ERROR: No se pudo guardar el archivo AML actualizado.")
if __name__ == "__main__":
main()

View File

@ -1,82 +1,5 @@
[16:30:18] Iniciando ejecución de x1_export_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
[16:30:19] --- TIA Portal Project CAx Exporter and Analyzer ---
[16:30:24] Selected Project: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_01/98050_PLC_01.ap19
[16:30:24] Using Output Directory (Working Directory): D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[16:30:24] Detected TIA Portal version: 19.0 (from extension .ap19)
[16:30:24] Will export CAx data to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:30:24] Will generate summary to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
[16:30:24] Export log file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.log
[16:30:24] Connecting to TIA Portal V19.0...
[16:30:25] 2025-06-06 16:30:25,861 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
[16:30:25] 2025-06-06 16:30:25,895 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
[16:32:34] Connected.
[16:32:34] Opening project: 98050_PLC_01.ap19...
[16:32:34] 2025-06-06 16:32:34,404 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_01\98050_PLC_01.ap19
[16:33:05] Project opened.
[16:33:05] Exporting CAx data for the project to D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml...
[16:33:52] CAx data exported successfully.
[16:33:52] Closing TIA Portal...
[16:33:52] 2025-06-06 16:33:52,008 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal
[16:33:52] TIA Portal closed.
[16:33:52] Parsing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:33:52] Markdown summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Summary.md
[16:33:52] Script finished.
[16:33:55] Ejecución de x1_export_CAx.py finalizada (success). Duración: 0:03:37.052535.
[16:33:55] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x1_export_CAx.txt
[16:34:01] Iniciando ejecución de x2_process_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
[16:34:01] --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v31.1 - Corrected IO Summary Table Initialization) ---
[16:34:01] Using Working Directory for Output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[16:34:09] Input AML: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:34:09] Output Directory: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[16:34:09] Output JSON: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
[16:34:09] Output IO Debug Tree MD: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
[16:34:09] Processing AML file: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.aml
[16:34:09] Pass 1: Found 363 InternalElement(s). Populating device dictionary...
[16:34:09] Pass 2: Identifying PLCs and Networks (Refined v2)...
[16:34:09] Identified Network: PN/IE_1 (fcbafb48-c53d-4af5-8143-794df99be4d6) Type: Unknown
[16:34:09] Identified PLC: A40510 (fc0d3bac-267e-488a-8dcf-7dc8599d80e8) - Type: CPU 1514SP T-2 PN OrderNo: 6ES7 514-2VN03-0AB0
[16:34:09] Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
[16:34:09] Found 103 InternalLink(s).
[16:34:09] Mapping Device/Node 'E1' (NodeID:ffef8b7b-b6bf-46aa-b3b5-f5083c420563, Addr:10.1.30.11) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:221c248d-83d8-464c-9425-c949086b34e7, Addr:10.1.30.58) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:5c3bf795-ecec-47e7-a962-4d54c95b9887, Addr:10.1.30.59) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:4100c829-d889-489a-971c-a69207333e2f, Addr:10.1.30.60) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a6aed467-1a4c-4a5a-bf3d-e7ce46e94a9f, Addr:10.1.30.61) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b3d71b04-f99a-4af8-aa69-2ca02a30d391, Addr:10.1.30.62) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:eb59daf8-f59c-435e-bfe4-8e7afd0937bd, Addr:10.1.30.63) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:3e931f82-08a5-44de-89a5-b907f5aaeb24, Addr:10.1.30.64) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:f673d611-7a73-4a8e-912d-ea16c294d51b, Addr:10.1.30.65) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:09a2569a-4948-4830-9dd8-315ae638e391, Addr:10.1.30.66) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:719a9643-c903-454e-8325-0f03cf871c35, Addr:10.1.30.31) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ef8c3829-2363-43b3-ae20-7e903c2517a5, Addr:10.1.30.32) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:7cece978-67aa-4707-b23c-375ebddfc2bc, Addr:10.1.30.170) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad86d223-8f60-41c0-8208-c88d68e42154, Addr:10.1.30.33) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:fa86bb0c-6142-41ce-ba6c-565e1e1f810e, Addr:10.1.30.34) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:441a0b56-5830-4595-ab8d-b073b6db8545, Addr:10.1.30.35) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:8dbc0c6b-a8f5-491c-832b-4af0757c259d, Addr:10.1.30.36) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:d06cdd21-62c3-4cff-9389-12a4e21869a2, Addr:10.1.30.40) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:f63db18d-dc19-48b8-afaa-be557db4d13e, Addr:10.1.30.44) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:21589a71-d4dd-4534-b457-deb72f3f7660, Addr:10.1.30.41) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:230ddec2-fa03-44d9-8350-0c0eeb463400, Addr:10.1.30.42) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b2647d59-ed85-44c5-813f-167c41ea1ca1, Addr:10.1.30.43) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:9933a2a3-47db-448a-a724-ed583d5994bb, Addr:10.1.30.37) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:9b45edaa-0640-4e86-ba59-1b991c1a85ca, Addr:10.1.30.45) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a5fbeadb-a8ab-42e0-b58f-296e44633ce5, Addr:10.1.30.46) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:67d1d9ad-cf1d-43aa-9dc4-9e574cda5e5b, Addr:10.1.30.47) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:08689cb8-1b00-403f-bc20-c5911a20e655, Addr:10.1.30.48) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:a06a0e94-b651-4510-bbe0-8a0c3b717283, Addr:10.1.30.49) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:cf339d67-1758-4462-85f0-9e6a002d1c3c, Addr:10.1.30.70) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:d80a9fcf-5e0d-45a9-b617-362a9cb4121a, Addr:10.1.30.71) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:7c28d11a-9673-4957-931d-8840a589da85, Addr:10.1.30.72) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:ad792043-fdb8-4695-a10a-aa2cad996cf0, Addr:10.1.30.74) to Network 'PN/IE_1'
[16:34:09] Mapping Device/Node 'IE1' (NodeID:b30940b8-6cb5-4143-854f-9b569f081703, Addr:10.1.30.73) to Network 'PN/IE_1'
[16:34:09] Data extraction and structuring complete.
[16:34:09] Generating JSON output: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export.hierarchical.json
[16:34:09] JSON data written successfully.
[16:34:09] IO upward debug tree written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\98050_PLC_01_CAx_Export_IO_Upward_Debug.md
[16:34:09] Found 1 PLC(s). Generating individual hardware trees...
[16:34:09] Generating Hardware Tree for PLC 'A40510' (ID: fc0d3bac-267e-488a-8dcf-7dc8599d80e8) at: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
[16:34:09] Markdown tree summary written to: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\A40510\Documentation\98050_PLC_01_CAx_Export_Hardware_Tree.md
[16:34:09] Script finished.
[16:34:09] Ejecución de x2_process_CAx.py finalizada (success). Duración: 0:00:08.316649.
[16:34:09] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\IO_adaptation\log_x2_process_CAx.txt
[15:36:49] Iniciando ejecución de x7_update_CAx.py en D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia...
[15:36:49] --- Actualizador de AML desde Excel Modificado (v1.1 - Simplified IO Address Handling (Start/End Separated)) ---
[15:36:49] Directorio de trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia
[15:36:49] 1. Seleccione el archivo AML original:
[15:36:57] 2. Seleccione el archivo Excel modificado: