Add path validation and sanitization tests
- Implemented `test_path_validation.py` to test filename sanitization, path sanitization, and export path validation functions. - Added comprehensive test cases for various problematic block names and paths to ensure proper handling of invalid characters and whitespace. - Created `test_sanitization.py` to specifically address problematic block names with updated sanitization logic, including special cases for "I/O access error" and "Time error interrupt". - Enhanced filename sanitization to replace specific problematic characters and patterns, ensuring consistent output for known issues.
This commit is contained in:
parent
586e3cc9b3
commit
48e25282d6
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,49 +0,0 @@
|
||||||
--- Log de Ejecución: x3.py ---
|
|
||||||
Grupo: ObtainIOFromProjectTia
|
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
|
||||||
Inicio: 2025-05-12 14:24:35
|
|
||||||
Fin: 2025-05-12 14:24:39
|
|
||||||
Duración: 0:00:04.165462
|
|
||||||
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) ---
|
|
||||||
Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
|
||||||
Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
|
||||||
Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
|
||||||
Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
|
||||||
Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
|
||||||
Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
|
||||||
Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
|
||||||
Pass 2: Identifying PLCs and Networks (Refined v2)...
|
|
||||||
Identified Network: PROFIBUS_1 (d645659a-3704-4cd6-b2c8-6165ceeed6ee) Type: Profibus
|
|
||||||
Identified Network: ETHERNET_1 (f0b1c852-7dc9-4748-888e-34c60b519a75) Type: Ethernet/Profinet
|
|
||||||
Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
|
||||||
Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
|
||||||
Found 116 InternalLink(s).
|
|
||||||
Mapping Device/Node 'E1' (NodeID:439930b8-1bbc-4cb2-a93b-2eed931f4b12, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
|
||||||
--> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
|
||||||
Mapping Device/Node 'P1' (NodeID:904bb0f7-df2d-4c1d-ab65-f45480449db1, Addr:1) to Network 'PROFIBUS_1'
|
|
||||||
--> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:2784bae8-9807-475f-89bd-bcf44282f5f4, Addr:12) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:e9c5f60a-1da2-4c9b-979e-7d03a5b58a44, Addr:20) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:dd7201c2-e127-4a9d-b6ae-7a74a4ffe418, Addr:21) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:d8825919-3a6c-4f95-aef0-62c782cfdb51, Addr:22) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:27d0e31d-46dc-4fdd-ab82-cfb91899a27c, Addr:10) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:d91d5905-aa1a-485e-b4eb-8333cc2133c2, Addr:8) to Network 'PROFIBUS_1'
|
|
||||||
Mapping Device/Node 'PB1' (NodeID:0c5dfe06-786d-4ab6-b57c-8dfede56c2aa, Addr:40) to Network 'PROFIBUS_1'
|
|
||||||
Data extraction and structuring complete.
|
|
||||||
Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
|
||||||
JSON data written successfully.
|
|
||||||
|
|
||||||
IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
|
||||||
|
|
||||||
Found 1 PLC(s). Generating individual hardware trees...
|
|
||||||
Generating Hardware Tree for PLC 'PLC' (ID: a48e038f-0bcc-4b48-8373-033da316c62b) at: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
|
||||||
Markdown summary (including table) written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\PLC\Documentation\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
|
||||||
|
|
||||||
Script finished.
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
Ninguno
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -1,65 +0,0 @@
|
||||||
--- Log de Ejecución: x4.py ---
|
|
||||||
Grupo: ObtainIOFromProjectTia
|
|
||||||
Directorio de Trabajo: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Source
|
|
||||||
Inicio: 2025-06-19 19:05:36
|
|
||||||
Fin: 2025-06-19 19:06:33
|
|
||||||
Duración: 0:00:57.281042
|
|
||||||
Estado: SUCCESS (Código de Salida: 0)
|
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
|
||||||
--- Exportador de Referencias Cruzadas de TIA Portal ---
|
|
||||||
Versión de TIA Portal detectada: 19.0 (de la extensión .ap19)
|
|
||||||
|
|
||||||
Proyecto seleccionado: D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_11/98050_PLC_11.ap19
|
|
||||||
Usando directorio base de exportación: D:\Trabajo\VM\44 - 98050 - Fiera\Reporte\ExportsTia\Source
|
|
||||||
|
|
||||||
Conectando a TIA Portal V19.0...
|
|
||||||
2025-06-19 19:05:42,182 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
|
||||||
2025-06-19 19:05:42,202 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
|
|
||||||
Conectado a TIA Portal.
|
|
||||||
2025-06-19 19:05:52,371 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal GetProcessId - Process id: 24972
|
|
||||||
ID del proceso del Portal: 24972
|
|
||||||
2025-06-19 19:05:52,710 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... D:/Trabajo/VM/44 - 98050 - Fiera/InLavoro/PLC/98050_PLC_11/98050_PLC_11.ap19
|
|
||||||
|
|
||||||
Ocurrió un error inesperado: OpennessAccessException: Error when calling method 'OpenWithUpgrade' of type 'Siemens.Engineering.ProjectComposition'.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Unable to open the project under path 'D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19'.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An error occurred while opening the project
|
|
||||||
|
|
||||||
The project/library D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19 cannot be accessed. It has already been opened by user Miguel on computer CSANUC. Note: If the application was not correctly closed, the open projects and libraries can only be opened again after a 2 minute delay.
|
|
||||||
|
|
||||||
Script finalizado.
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
2025-06-19 19:05:53,136 [1] ERROR Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject -
|
|
||||||
Siemens.TiaPortal.OpennessContracts.OpennessAccessException: Error when calling method 'OpenWithUpgrade' of type 'Siemens.Engineering.ProjectComposition'.
|
|
||||||
|
|
||||||
Unable to open the project under path 'D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19'.
|
|
||||||
|
|
||||||
An error occurred while opening the project
|
|
||||||
The project/library D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19 cannot be accessed. It has already been opened by user Miguel on computer CSANUC. Note: If the application was not correctly closed, the open projects and libraries can only be opened again after a 2 minute delay.
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 455, in <module>
|
|
||||||
portal_instance, project_object = open_portal_and_project(tia_version, project_file)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\x4.py", line 413, in open_portal_and_project
|
|
||||||
project_obj = portal.open_project(project_file_path=str(project_file_path))
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
ValueError: OpennessAccessException: Error when calling method 'OpenWithUpgrade' of type 'Siemens.Engineering.ProjectComposition'.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Unable to open the project under path 'D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19'.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An error occurred while opening the project
|
|
||||||
|
|
||||||
The project/library D:\Trabajo\VM\44 - 98050 - Fiera\InLavoro\PLC\98050_PLC_11\98050_PLC_11.ap19 cannot be accessed. It has already been opened by user Miguel on computer CSANUC. Note: If the application was not correctly closed, the open projects and libraries can only be opened again after a 2 minute delay.
|
|
||||||
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -1,81 +0,0 @@
|
||||||
--- Log de Ejecución: xTest.py ---
|
|
||||||
Grupo: ObtainIOFromProjectTia
|
|
||||||
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\SourceDoc\SourcdSD
|
|
||||||
Inicio: 2025-05-22 11:17:27
|
|
||||||
Fin: 2025-05-22 11:18:44
|
|
||||||
Duración: 0:01:16.758340
|
|
||||||
Estado: ERROR (Código de Salida: 1)
|
|
||||||
|
|
||||||
--- SALIDA ESTÁNDAR (STDOUT) ---
|
|
||||||
============================================================
|
|
||||||
PRUEBA DE EXPORTACIÓN SIMATIC SD - TIA PORTAL V20
|
|
||||||
============================================================
|
|
||||||
|
|
||||||
Project: C:/Trabajo/SIDEL/09 - SAE452 - Diet as Regular - San Giovanni in Bosco/Reporte/SourceDoc/Migration/SAE452_V20/SAE452_V20.ap20
|
|
||||||
Export Directory: C:/Users/migue/Downloads/Nueva carpeta (18)\SIMATIC_SD_Test
|
|
||||||
|
|
||||||
Connecting to TIA Portal V20...
|
|
||||||
2025-05-22 11:17:49,266 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - Start TIA Portal, please acknowledge the security dialog.
|
|
||||||
2025-05-22 11:17:49,283 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Global OpenPortal - With user interface
|
|
||||||
Connected successfully.
|
|
||||||
Opening project...
|
|
||||||
2025-05-22 11:18:05,562 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal OpenProject - Open project... C:/Trabajo/SIDEL/09 - SAE452 - Diet as Regular - San Giovanni in Bosco/Reporte/SourceDoc/Migration/SAE452_V20/SAE452_V20.ap20
|
|
||||||
Project opened successfully.
|
|
||||||
2025-05-22 11:18:20,088 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Project GetPlcs - Found plc CPU 315F-2 PN/DP with parent name _SSAE0452
|
|
||||||
Found 1 PLC(s)
|
|
||||||
|
|
||||||
Testing with PLC: CPU 315F-2 PN/DP
|
|
||||||
Found 410 program blocks
|
|
||||||
|
|
||||||
--- Testing Block 1/3: ISOonTCP_or_TCP_Protocol ---
|
|
||||||
Programming Language: STL
|
|
||||||
Available methods on block:
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
✗ ExportAsDocuments method NOT found
|
|
||||||
Available methods containing 'export':
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
|
|
||||||
--- Testing Block 2/3: PIDControl ---
|
|
||||||
Compiling block...
|
|
||||||
2025-05-22 11:18:24,970 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.ProgramBlock Compile - Compile the PLC program block PIDControl. Result:
|
|
||||||
2025-05-22 11:18:31,184 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.ProgramBlock Compile - Warning: CPU 315F-2 PN/DP > General warnings > Inputs or outputs are used that do not exist in the configured hardware.
|
|
||||||
2025-05-22 11:18:31,185 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.ProgramBlock Compile - Warning: CPU 315F-2 PN/DP > Compiling finished (errors: 0; warnings: 1)
|
|
||||||
Programming Language: LAD
|
|
||||||
Available methods on block:
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
✗ ExportAsDocuments method NOT found
|
|
||||||
Available methods containing 'export':
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
|
|
||||||
--- Testing Block 3/3: DETAIL_DP_DIAG ---
|
|
||||||
Programming Language: STL
|
|
||||||
Available methods on block:
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
✗ ExportAsDocuments method NOT found
|
|
||||||
Available methods containing 'export':
|
|
||||||
- export
|
|
||||||
- export_cross_references
|
|
||||||
|
|
||||||
============================================================
|
|
||||||
PRUEBA COMPLETADA
|
|
||||||
============================================================
|
|
||||||
|
|
||||||
No se crearon archivos en C:/Users/migue/Downloads/Nueva carpeta (18)\SIMATIC_SD_Test
|
|
||||||
|
|
||||||
Closing TIA Portal...
|
|
||||||
2025-05-22 11:18:31,209 [1] INFO Siemens.TiaPortal.OpennessApi19.Implementations.Portal ClosePortal - Close TIA Portal
|
|
||||||
|
|
||||||
Press Enter to exit...
|
|
||||||
|
|
||||||
--- ERRORES (STDERR) ---
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\xTest.py", line 215, in <module>
|
|
||||||
input("\nPress Enter to exit...")
|
|
||||||
EOFError: EOF when reading a line
|
|
||||||
|
|
||||||
--- FIN DEL LOG ---
|
|
|
@ -22,5 +22,11 @@
|
||||||
"short_description": "Test específico para exportación SIMATIC SD usando ExportAsDocuments()",
|
"short_description": "Test específico para exportación SIMATIC SD usando ExportAsDocuments()",
|
||||||
"long_description": "Script de prueba experimental para validar la funcionalidad de exportación SIMATIC SD utilizando el método ExportAsDocuments() de la API de TIA Portal Openness.\n***\n**Propósito:**\n\n1. **Validación de API:** Prueba diferentes métodos de exportación SIMATIC SD\n2. **Comparación de métodos:** Evalúa ExportAsDocuments() vs Export() estándar\n3. **Debugging:** Identifica problemas y limitaciones en la exportación SD\n4. **Desarrollo:** Base para mejoras en scripts de producción\n\n**Estado:** Script experimental - usar solo para pruebas y desarrollo\n\n**Nota:** Este script es parte del proceso de desarrollo y optimización de los métodos de exportación SIMATIC SD.",
|
"long_description": "Script de prueba experimental para validar la funcionalidad de exportación SIMATIC SD utilizando el método ExportAsDocuments() de la API de TIA Portal Openness.\n***\n**Propósito:**\n\n1. **Validación de API:** Prueba diferentes métodos de exportación SIMATIC SD\n2. **Comparación de métodos:** Evalúa ExportAsDocuments() vs Export() estándar\n3. **Debugging:** Identifica problemas y limitaciones en la exportación SD\n4. **Desarrollo:** Base para mejoras en scripts de producción\n\n**Estado:** Script experimental - usar solo para pruebas y desarrollo\n\n**Nota:** Este script es parte del proceso de desarrollo y optimización de los métodos de exportación SIMATIC SD.",
|
||||||
"hidden": false
|
"hidden": false
|
||||||
|
},
|
||||||
|
"test_simatic_sd_compatibility.py": {
|
||||||
|
"display_name": "test_simatic_sd_compatibility",
|
||||||
|
"short_description": "Test script to verify SIMATIC SD compatibility detection",
|
||||||
|
"long_description": "",
|
||||||
|
"hidden": false
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
"""
|
||||||
|
Test script for path validation and sanitization functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
"""Sanitizes a filename by removing/replacing invalid characters and whitespace."""
|
||||||
|
# Replace spaces and other problematic characters with underscores
|
||||||
|
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", name)
|
||||||
|
# Remove leading/trailing underscores and dots
|
||||||
|
sanitized = sanitized.strip("_.")
|
||||||
|
# Ensure it's not empty
|
||||||
|
if not sanitized:
|
||||||
|
sanitized = "unknown"
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_path(path):
|
||||||
|
"""Sanitizes a path by ensuring it doesn't contain problematic whitespace."""
|
||||||
|
# Normalize the path and remove any trailing/leading whitespace
|
||||||
|
normalized = os.path.normpath(path.strip())
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def validate_export_path(path):
|
||||||
|
"""Validates that an export path is suitable for TIA Portal."""
|
||||||
|
if not path:
|
||||||
|
return False, "La ruta está vacía"
|
||||||
|
|
||||||
|
# Check for problematic characters or patterns
|
||||||
|
if any(char in path for char in '<>"|?*'):
|
||||||
|
return False, f"La ruta contiene caracteres no válidos: {path}"
|
||||||
|
|
||||||
|
# Check for excessive whitespace
|
||||||
|
if path != path.strip():
|
||||||
|
return False, f"La ruta contiene espacios al inicio o final: '{path}'"
|
||||||
|
|
||||||
|
# Check for multiple consecutive spaces
|
||||||
|
if " " in path:
|
||||||
|
return False, f"La ruta contiene espacios múltiples consecutivos: '{path}'"
|
||||||
|
|
||||||
|
# Check path length (Windows limitation)
|
||||||
|
if len(path) > 250:
|
||||||
|
return False, f"La ruta es demasiado larga ({len(path)} caracteres): {path}"
|
||||||
|
|
||||||
|
return True, "OK"
|
||||||
|
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
test_names = [
|
||||||
|
"IO access error",
|
||||||
|
"CreatesAnyPointer",
|
||||||
|
"WriteMemArea_G",
|
||||||
|
"Block with spaces",
|
||||||
|
"Block<>with:invalid|chars",
|
||||||
|
" Block with leading and trailing spaces ",
|
||||||
|
"Block with multiple spaces",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
test_paths = [
|
||||||
|
"C:\\normal\\path",
|
||||||
|
"C:\\path with spaces\\subdir",
|
||||||
|
" C:\\path with leading space",
|
||||||
|
"C:\\path with trailing space ",
|
||||||
|
"C:\\path with multiple spaces\\subdir",
|
||||||
|
"C:\\path<with>invalid:chars|in\\subdir",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=== Testing sanitize_filename ===")
|
||||||
|
for name in test_names:
|
||||||
|
sanitized = sanitize_filename(name)
|
||||||
|
print(f"'{name}' -> '{sanitized}'")
|
||||||
|
|
||||||
|
print("\n=== Testing sanitize_path ===")
|
||||||
|
for path in test_paths:
|
||||||
|
sanitized = sanitize_path(path)
|
||||||
|
print(f"'{path}' -> '{sanitized}'")
|
||||||
|
|
||||||
|
print("\n=== Testing validate_export_path ===")
|
||||||
|
for path in test_paths:
|
||||||
|
sanitized = sanitize_path(path)
|
||||||
|
is_valid, msg = validate_export_path(sanitized)
|
||||||
|
print(f"'{sanitized}' -> Valid: {is_valid}, Message: {msg}")
|
||||||
|
|
||||||
|
print("\n=== Test specific problematic block names ===")
|
||||||
|
problematic_blocks = ["IO access error", "CreatesAnyPointer", "WriteMemArea_G"]
|
||||||
|
base_path = "D:\\Export\\Test"
|
||||||
|
|
||||||
|
for block_name in problematic_blocks:
|
||||||
|
sanitized_name = sanitize_filename(block_name)
|
||||||
|
full_path = sanitize_path(
|
||||||
|
os.path.join(base_path, sanitized_name, "ProgramBlocks_XML")
|
||||||
|
)
|
||||||
|
is_valid, msg = validate_export_path(full_path)
|
||||||
|
print(f"Block: '{block_name}' -> '{sanitized_name}'")
|
||||||
|
print(f" Full path: '{full_path}'")
|
||||||
|
print(f" Valid: {is_valid}, Message: {msg}")
|
||||||
|
print()
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""
|
||||||
|
Test script for updated sanitization function
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
"""Sanitizes a filename by removing/replacing invalid characters and whitespace."""
|
||||||
|
# Handle specific problematic cases first
|
||||||
|
if name == "I/O access error":
|
||||||
|
return "IO_access_error"
|
||||||
|
elif name == "Time error interrupt":
|
||||||
|
return "Time_error_interrupt"
|
||||||
|
elif name.startswith("I/O_"):
|
||||||
|
return name.replace("I/O_", "IO_").replace("/", "_")
|
||||||
|
|
||||||
|
# Replace spaces and other problematic characters with underscores
|
||||||
|
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", name)
|
||||||
|
# Remove leading/trailing underscores and dots
|
||||||
|
sanitized = sanitized.strip("_.")
|
||||||
|
# Ensure it's not empty
|
||||||
|
if not sanitized:
|
||||||
|
sanitized = "unknown"
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
# Test the problematic block names from the log
|
||||||
|
problematic_blocks = [
|
||||||
|
"IO access error",
|
||||||
|
"Time error interrupt",
|
||||||
|
"I/O_FLT1",
|
||||||
|
"I/O_FLT2",
|
||||||
|
"CreatesAnyPointer",
|
||||||
|
"WriteMemArea_G",
|
||||||
|
"ComGetPut_G",
|
||||||
|
"Sys_Plc_G",
|
||||||
|
"Sys_PLC_D",
|
||||||
|
"RACK_FLT",
|
||||||
|
"Startup",
|
||||||
|
"PROG_ERR",
|
||||||
|
]
|
||||||
|
|
||||||
|
print("=== Testing updated sanitize_filename function ===")
|
||||||
|
for block_name in problematic_blocks:
|
||||||
|
sanitized = sanitize_filename(block_name)
|
||||||
|
has_spaces = " " in block_name
|
||||||
|
has_slash = "/" in block_name
|
||||||
|
print(f"'{block_name}' -> '{sanitized}' [Spaces: {has_spaces}, Slash: {has_slash}]")
|
|
@ -1,70 +0,0 @@
|
||||||
"""
|
|
||||||
Test script to verify SIMATIC SD compatibility detection
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# --- TIA Scripting Import Handling ---
|
|
||||||
if os.getenv("TIA_SCRIPTING"):
|
|
||||||
sys.path.append(os.getenv("TIA_SCRIPTING"))
|
|
||||||
|
|
||||||
try:
|
|
||||||
import siemens_tia_scripting as ts
|
|
||||||
|
|
||||||
print("✓ TIA Scripting import successful")
|
|
||||||
print(
|
|
||||||
f"Available programming languages: {[lang for lang in dir(ts.Enums.ProgrammingLanguage) if not lang.startswith('_')]}"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Available export formats: {[fmt for fmt in dir(ts.Enums.ExportFormats) if not fmt.startswith('_')]}"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Available block types: {[bt for bt in dir(ts.Enums.BlockType) if not bt.startswith('_')]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if SIMATIC SD is available
|
|
||||||
try:
|
|
||||||
simatic_sd_format = ts.Enums.ExportFormats.SimaticSD
|
|
||||||
print(f"✓ SIMATIC SD format available: {simatic_sd_format}")
|
|
||||||
except AttributeError:
|
|
||||||
print("✗ SIMATIC SD format NOT available in this TIA Scripting version")
|
|
||||||
|
|
||||||
except ImportError as e:
|
|
||||||
print(f"✗ Failed to import TIA Scripting: {e}")
|
|
||||||
print(
|
|
||||||
"This is expected if TIA Portal is not installed or TIA_SCRIPTING env var not set"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_simatic_sd_requirements():
|
|
||||||
"""Analyze and display SIMATIC SD requirements"""
|
|
||||||
print("\n=== SIMATIC SD FORMAT REQUIREMENTS ===")
|
|
||||||
print("Based on official Siemens documentation:")
|
|
||||||
print()
|
|
||||||
print("✓ SUPPORTED:")
|
|
||||||
print(" • Programming Language: LAD (Ladder) ONLY")
|
|
||||||
print(
|
|
||||||
" • Block Types: FB (Function Block), FC (Function), OB (Organization Block)"
|
|
||||||
)
|
|
||||||
print(" • TIA Portal Version: V20 or later")
|
|
||||||
print(" • Target PLCs: S7-1200, S7-1500")
|
|
||||||
print()
|
|
||||||
print("✗ NOT SUPPORTED:")
|
|
||||||
print(" • SCL (Structured Control Language)")
|
|
||||||
print(" • STL (Statement List)")
|
|
||||||
print(" • FBD (Function Block Diagram)")
|
|
||||||
print(" • Graph programming")
|
|
||||||
print(" • CFC (Continuous Function Chart)")
|
|
||||||
print(" • Complex LAD elements (some advanced functions)")
|
|
||||||
print()
|
|
||||||
print("📋 COMMON CAUSES FOR XML-ONLY EXPORT:")
|
|
||||||
print(" 1. Block programmed in SCL/STL/FBD instead of LAD")
|
|
||||||
print(" 2. Block contains unsupported LAD elements")
|
|
||||||
print(" 3. Block is not compiled/consistent")
|
|
||||||
print(" 4. TIA Portal version < V20")
|
|
||||||
print(" 5. Wrong block type (not FB/FC/OB)")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
analyze_simatic_sd_requirements()
|
|
|
@ -7,6 +7,8 @@ from tkinter import filedialog
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
from pathlib import Path # Import Path
|
from pathlib import Path # Import Path
|
||||||
|
|
||||||
script_root = os.path.dirname(
|
script_root = os.path.dirname(
|
||||||
|
@ -17,14 +19,12 @@ from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- Configuration ---
|
# --- Configuration ---
|
||||||
# Supported TIA Portal versions mapping (extension -> version)
|
# Supported TIA Portal versions mapping (extension -> version)
|
||||||
SUPPORTED_TIA_VERSIONS = {
|
SUPPORTED_TIA_VERSIONS = {".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0"}
|
||||||
".ap18": "18.0",
|
|
||||||
".ap19": "19.0",
|
|
||||||
".ap20": "20.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPORT_OPTIONS = None # Use default export options
|
EXPORT_OPTIONS = None # Use default export options
|
||||||
KEEP_FOLDER_STRUCTURE = True # Replicate TIA project folder structure in export directory
|
KEEP_FOLDER_STRUCTURE = (
|
||||||
|
True # Replicate TIA project folder structure in export directory
|
||||||
|
)
|
||||||
|
|
||||||
# --- TIA Scripting Import Handling ---
|
# --- TIA Scripting Import Handling ---
|
||||||
# Check if the TIA_SCRIPTING environment variable is set
|
# Check if the TIA_SCRIPTING environment variable is set
|
||||||
|
@ -46,7 +46,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("ERROR: Failed to import 'siemens_tia_scripting'.")
|
print("ERROR: Failed to import 'siemens_tia_scripting'.")
|
||||||
print("Ensure:")
|
print("Ensure:")
|
||||||
print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.")
|
print("1. TIA Portal Openness is installed.")
|
||||||
print(
|
print(
|
||||||
"2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or"
|
"2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or"
|
||||||
)
|
)
|
||||||
|
@ -64,41 +64,156 @@ except Exception as e:
|
||||||
|
|
||||||
# --- Functions ---
|
# --- Functions ---
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(name):
|
||||||
|
"""Sanitizes a filename by removing/replacing invalid characters and whitespace."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Handle specific problematic cases first
|
||||||
|
if name == "I/O access error":
|
||||||
|
return "IO_access_error"
|
||||||
|
elif name == "Time error interrupt":
|
||||||
|
return "Time_error_interrupt"
|
||||||
|
elif name.startswith("I/O_"):
|
||||||
|
return name.replace("I/O_", "IO_").replace("/", "_")
|
||||||
|
|
||||||
|
# Replace spaces and other problematic characters with underscores
|
||||||
|
sanitized = re.sub(r'[<>:"/\\|?*\s]+', "_", name)
|
||||||
|
# Remove leading/trailing underscores and dots
|
||||||
|
sanitized = sanitized.strip("_.")
|
||||||
|
# Ensure it's not empty
|
||||||
|
if not sanitized:
|
||||||
|
sanitized = "unknown"
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_path(path):
|
||||||
|
"""Sanitizes a path by ensuring it doesn't contain problematic whitespace."""
|
||||||
|
# Normalize the path and remove any trailing/leading whitespace
|
||||||
|
normalized = os.path.normpath(path.strip())
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def validate_export_path(path):
|
||||||
|
"""Validates that an export path is suitable for TIA Portal."""
|
||||||
|
if not path:
|
||||||
|
return False, "La ruta está vacía"
|
||||||
|
|
||||||
|
# Check for problematic characters or patterns
|
||||||
|
if any(char in path for char in '<>"|?*'):
|
||||||
|
return False, f"La ruta contiene caracteres no válidos: {path}"
|
||||||
|
|
||||||
|
# Check for excessive whitespace
|
||||||
|
if path != path.strip():
|
||||||
|
return False, f"La ruta contiene espacios al inicio o final: '{path}'"
|
||||||
|
|
||||||
|
# Check for multiple consecutive spaces
|
||||||
|
if " " in path:
|
||||||
|
return False, f"La ruta contiene espacios múltiples consecutivos: '{path}'"
|
||||||
|
|
||||||
|
# Check path length (Windows limitation)
|
||||||
|
if len(path) > 250:
|
||||||
|
return False, f"La ruta es demasiado larga ({len(path)} caracteres): {path}"
|
||||||
|
|
||||||
|
return True, "OK"
|
||||||
|
|
||||||
|
|
||||||
|
def create_temp_export_dir():
|
||||||
|
"""Creates a temporary directory for export that doesn't contain spaces."""
|
||||||
|
# Create a temporary directory with a safe name
|
||||||
|
temp_base = tempfile.gettempdir()
|
||||||
|
temp_export = os.path.join(temp_base, "TIA_Export_Temp")
|
||||||
|
|
||||||
|
# Ensure the temp directory exists and is clean
|
||||||
|
if os.path.exists(temp_export):
|
||||||
|
shutil.rmtree(temp_export)
|
||||||
|
os.makedirs(temp_export, exist_ok=True)
|
||||||
|
|
||||||
|
return temp_export
|
||||||
|
|
||||||
|
|
||||||
|
def copy_temp_to_final(temp_dir, final_dir):
|
||||||
|
"""Copies files from temporary directory to final destination."""
|
||||||
|
try:
|
||||||
|
print(f"\nCopiando archivos exportados desde directorio temporal...")
|
||||||
|
print(f" Origen: {temp_dir}")
|
||||||
|
print(f" Destino: {final_dir}")
|
||||||
|
|
||||||
|
# Ensure final directory exists
|
||||||
|
os.makedirs(final_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy all contents from temp to final directory
|
||||||
|
for item in os.listdir(temp_dir):
|
||||||
|
src_path = os.path.join(temp_dir, item)
|
||||||
|
dst_path = os.path.join(final_dir, item)
|
||||||
|
|
||||||
|
if os.path.isdir(src_path):
|
||||||
|
if os.path.exists(dst_path):
|
||||||
|
shutil.rmtree(dst_path)
|
||||||
|
shutil.copytree(src_path, dst_path)
|
||||||
|
print(f" Directorio copiado: {item}")
|
||||||
|
else:
|
||||||
|
shutil.copy2(src_path, dst_path)
|
||||||
|
print(f" Archivo copiado: {item}")
|
||||||
|
|
||||||
|
print(" Copia completada exitosamente.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ERROR durante la copia: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_temp_dir(temp_dir):
|
||||||
|
"""Cleans up the temporary directory."""
|
||||||
|
try:
|
||||||
|
if os.path.exists(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir)
|
||||||
|
print(f"Directorio temporal limpiado: {temp_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ADVERTENCIA: No se pudo limpiar el directorio temporal {temp_dir}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def get_supported_filetypes():
|
def get_supported_filetypes():
|
||||||
"""Returns the supported file types for TIA Portal projects."""
|
"""Returns the supported file types for TIA Portal projects."""
|
||||||
filetypes = []
|
filetypes = []
|
||||||
for ext, version in SUPPORTED_TIA_VERSIONS.items():
|
for ext, version in SUPPORTED_TIA_VERSIONS.items():
|
||||||
version_major = version.split('.')[0]
|
version_major = version.split(".")[0]
|
||||||
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
|
filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}"))
|
||||||
|
|
||||||
# Add option to show all supported files
|
# Add option to show all supported files
|
||||||
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
|
all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()])
|
||||||
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
|
filetypes.insert(0, ("All TIA Portal Projects", all_extensions))
|
||||||
|
|
||||||
return filetypes
|
return filetypes
|
||||||
|
|
||||||
|
|
||||||
def detect_tia_version(project_file_path):
|
def detect_tia_version(project_file_path):
|
||||||
"""Detects TIA Portal version based on file extension."""
|
"""Detects TIA Portal version based on file extension."""
|
||||||
file_path = Path(project_file_path)
|
file_path = Path(project_file_path)
|
||||||
file_extension = file_path.suffix.lower()
|
file_extension = file_path.suffix.lower()
|
||||||
|
|
||||||
if file_extension in SUPPORTED_TIA_VERSIONS:
|
if file_extension in SUPPORTED_TIA_VERSIONS:
|
||||||
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
|
detected_version = SUPPORTED_TIA_VERSIONS[file_extension]
|
||||||
print(f"Versión de TIA Portal detectada: {detected_version} (de la extensión {file_extension})")
|
print(
|
||||||
|
f"Versión de TIA Portal detectada: {detected_version} (de la extensión {file_extension})"
|
||||||
|
)
|
||||||
return detected_version
|
return detected_version
|
||||||
else:
|
else:
|
||||||
print(f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}")
|
print(
|
||||||
|
f"ADVERTENCIA: Extensión de archivo no reconocida '{file_extension}'. Extensiones soportadas: {list(SUPPORTED_TIA_VERSIONS.keys())}"
|
||||||
|
)
|
||||||
# Default to version 18.0 for backward compatibility
|
# Default to version 18.0 for backward compatibility
|
||||||
print("Usando por defecto TIA Portal V18.0")
|
print("Usando por defecto TIA Portal V18.0")
|
||||||
return "18.0"
|
return "18.0"
|
||||||
|
|
||||||
|
|
||||||
def select_project_file():
|
def select_project_file():
|
||||||
"""Opens a dialog to select a TIA Portal project file."""
|
"""Opens a dialog to select a TIA Portal project file."""
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
root.withdraw() # Hide the main tkinter window
|
root.withdraw() # Hide the main tkinter window
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
title="Select TIA Portal Project File",
|
title="Select TIA Portal Project File", filetypes=get_supported_filetypes()
|
||||||
filetypes=get_supported_filetypes()
|
|
||||||
)
|
)
|
||||||
root.destroy()
|
root.destroy()
|
||||||
if not file_path:
|
if not file_path:
|
||||||
|
@ -122,18 +237,40 @@ def select_export_directory():
|
||||||
def export_plc_data(plc, export_base_dir):
|
def export_plc_data(plc, export_base_dir):
|
||||||
"""Exports Blocks, UDTs, and Tag Tables from a given PLC."""
|
"""Exports Blocks, UDTs, and Tag Tables from a given PLC."""
|
||||||
plc_name = plc.get_name()
|
plc_name = plc.get_name()
|
||||||
|
plc_name_sanitized = sanitize_filename(plc_name)
|
||||||
print(f"\n--- Procesando PLC: {plc_name} ---")
|
print(f"\n--- Procesando PLC: {plc_name} ---")
|
||||||
|
if plc_name != plc_name_sanitized:
|
||||||
|
print(f" Nombre sanitizado para directorios: {plc_name_sanitized}")
|
||||||
|
|
||||||
# Define base export path for this PLC
|
# Define base export path for this PLC
|
||||||
plc_export_dir = os.path.join(export_base_dir, plc_name)
|
plc_export_dir = sanitize_path(os.path.join(export_base_dir, plc_name_sanitized))
|
||||||
|
|
||||||
|
# Validate PLC export directory
|
||||||
|
is_valid, validation_msg = validate_export_path(plc_export_dir)
|
||||||
|
if not is_valid:
|
||||||
|
print(f"ERROR: Directorio de exportación del PLC no válido - {validation_msg}")
|
||||||
|
return
|
||||||
|
|
||||||
os.makedirs(plc_export_dir, exist_ok=True)
|
os.makedirs(plc_export_dir, exist_ok=True)
|
||||||
|
|
||||||
# --- Export Program Blocks ---
|
# --- Export Program Blocks ---
|
||||||
blocks_exported = 0
|
blocks_exported = 0
|
||||||
blocks_skipped = 0
|
blocks_skipped = 0
|
||||||
print(f"\n[PLC: {plc_name}] Exportando bloques de programa...")
|
print(f"\n[PLC: {plc_name}] Exportando bloques de programa...")
|
||||||
xml_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_XML")
|
xml_blocks_path = sanitize_path(os.path.join(plc_export_dir, "ProgramBlocks_XML"))
|
||||||
scl_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_SCL")
|
scl_blocks_path = sanitize_path(os.path.join(plc_export_dir, "ProgramBlocks_SCL"))
|
||||||
|
|
||||||
|
# Validate block export paths
|
||||||
|
xml_valid, xml_msg = validate_export_path(xml_blocks_path)
|
||||||
|
scl_valid, scl_msg = validate_export_path(scl_blocks_path)
|
||||||
|
|
||||||
|
if not xml_valid:
|
||||||
|
print(f" ERROR: Ruta XML no válida - {xml_msg}")
|
||||||
|
return
|
||||||
|
if not scl_valid:
|
||||||
|
print(f" ERROR: Ruta SCL no válida - {scl_msg}")
|
||||||
|
return
|
||||||
|
|
||||||
os.makedirs(xml_blocks_path, exist_ok=True)
|
os.makedirs(xml_blocks_path, exist_ok=True)
|
||||||
os.makedirs(scl_blocks_path, exist_ok=True)
|
os.makedirs(scl_blocks_path, exist_ok=True)
|
||||||
print(f" Destino XML: {xml_blocks_path}")
|
print(f" Destino XML: {xml_blocks_path}")
|
||||||
|
@ -157,23 +294,152 @@ def export_plc_data(plc, export_base_dir):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f" Exportando {block_name} como XML...")
|
print(f" Exportando {block_name} como XML...")
|
||||||
block.export(
|
try:
|
||||||
target_directory_path=xml_blocks_path,
|
print(f" Destino: {xml_blocks_path}")
|
||||||
export_options=EXPORT_OPTIONS,
|
|
||||||
export_format=ts.Enums.ExportFormats.SimaticML,
|
|
||||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# Check if this is a system block that might need special handling
|
||||||
|
is_system_block = any(
|
||||||
|
keyword in block_name.lower()
|
||||||
|
for keyword in [
|
||||||
|
"interrupt",
|
||||||
|
"error",
|
||||||
|
"startup",
|
||||||
|
"i/o",
|
||||||
|
"rack_flt",
|
||||||
|
"prog_err",
|
||||||
|
"time error",
|
||||||
|
"io access",
|
||||||
|
"createsan",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Try creating a sanitized filename for problematic blocks
|
||||||
|
if is_system_block or " " in block_name or "/" in block_name:
|
||||||
|
print(
|
||||||
|
f" Detectado bloque con nombre problemático: '{block_name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a temporary export directory with sanitized name
|
||||||
|
sanitized_block_name = sanitize_filename(block_name)
|
||||||
|
temp_block_dir = os.path.join(
|
||||||
|
xml_blocks_path, sanitized_block_name
|
||||||
|
)
|
||||||
|
os.makedirs(temp_block_dir, exist_ok=True)
|
||||||
|
|
||||||
|
print(f" Usando directorio sanitizado: {temp_block_dir}")
|
||||||
|
|
||||||
|
block.export(
|
||||||
|
target_directory_path=temp_block_dir,
|
||||||
|
export_options=EXPORT_OPTIONS,
|
||||||
|
export_format=ts.Enums.ExportFormats.SimaticML,
|
||||||
|
keep_folder_structure=False, # Disable folder structure for problematic blocks
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rename files to use original block name in metadata
|
||||||
|
for file in os.listdir(temp_block_dir):
|
||||||
|
if file.endswith(".xml"):
|
||||||
|
original_path = os.path.join(temp_block_dir, file)
|
||||||
|
# Move file to main directory with original name preserved in content
|
||||||
|
target_path = os.path.join(xml_blocks_path, file)
|
||||||
|
if os.path.exists(target_path):
|
||||||
|
os.remove(target_path)
|
||||||
|
shutil.move(original_path, target_path)
|
||||||
|
|
||||||
|
# Remove temporary directory
|
||||||
|
if os.path.exists(temp_block_dir):
|
||||||
|
os.rmdir(temp_block_dir)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Normal export for regular blocks
|
||||||
|
block.export(
|
||||||
|
target_directory_path=xml_blocks_path,
|
||||||
|
export_options=EXPORT_OPTIONS,
|
||||||
|
export_format=ts.Enums.ExportFormats.SimaticML,
|
||||||
|
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as xml_ex:
|
||||||
|
print(
|
||||||
|
f" ERROR en exportación XML para {block_name}: {xml_ex}"
|
||||||
|
)
|
||||||
|
print(f" Ruta problemática: '{xml_blocks_path}'")
|
||||||
|
print(f" Tipo de bloque: {type(block).__name__}")
|
||||||
|
|
||||||
|
# Skip this block and continue with others
|
||||||
|
blocks_skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we get here, XML export was successful
|
||||||
|
# Now try SCL export if applicable
|
||||||
try:
|
try:
|
||||||
prog_language = block.get_property(name="ProgrammingLanguage")
|
prog_language = block.get_property(name="ProgrammingLanguage")
|
||||||
if prog_language == "SCL":
|
if prog_language == "SCL":
|
||||||
print(f" Exportando {block_name} como SCL...")
|
print(f" Exportando {block_name} como SCL...")
|
||||||
block.export(
|
try:
|
||||||
target_directory_path=scl_blocks_path,
|
print(f" Destino: {scl_blocks_path}")
|
||||||
export_options=EXPORT_OPTIONS,
|
|
||||||
export_format=ts.Enums.ExportFormats.ExternalSource,
|
# Use same logic for SCL export
|
||||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
is_system_block = any(
|
||||||
)
|
keyword in block_name.lower()
|
||||||
|
for keyword in [
|
||||||
|
"interrupt",
|
||||||
|
"error",
|
||||||
|
"startup",
|
||||||
|
"i/o",
|
||||||
|
"rack_flt",
|
||||||
|
"prog_err",
|
||||||
|
"time error",
|
||||||
|
"io access",
|
||||||
|
"createsan",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
is_system_block
|
||||||
|
or " " in block_name
|
||||||
|
or "/" in block_name
|
||||||
|
):
|
||||||
|
sanitized_block_name = sanitize_filename(block_name)
|
||||||
|
temp_block_dir = os.path.join(
|
||||||
|
scl_blocks_path, sanitized_block_name
|
||||||
|
)
|
||||||
|
os.makedirs(temp_block_dir, exist_ok=True)
|
||||||
|
|
||||||
|
block.export(
|
||||||
|
target_directory_path=temp_block_dir,
|
||||||
|
export_options=EXPORT_OPTIONS,
|
||||||
|
export_format=ts.Enums.ExportFormats.ExternalSource,
|
||||||
|
keep_folder_structure=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Move files to main directory
|
||||||
|
for file in os.listdir(temp_block_dir):
|
||||||
|
if file.endswith(".scl"):
|
||||||
|
original_path = os.path.join(
|
||||||
|
temp_block_dir, file
|
||||||
|
)
|
||||||
|
target_path = os.path.join(
|
||||||
|
scl_blocks_path, file
|
||||||
|
)
|
||||||
|
if os.path.exists(target_path):
|
||||||
|
os.remove(target_path)
|
||||||
|
shutil.move(original_path, target_path)
|
||||||
|
|
||||||
|
if os.path.exists(temp_block_dir):
|
||||||
|
os.rmdir(temp_block_dir)
|
||||||
|
else:
|
||||||
|
block.export(
|
||||||
|
target_directory_path=scl_blocks_path,
|
||||||
|
export_options=EXPORT_OPTIONS,
|
||||||
|
export_format=ts.Enums.ExportFormats.ExternalSource,
|
||||||
|
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||||
|
)
|
||||||
|
except Exception as scl_ex:
|
||||||
|
print(
|
||||||
|
f" ERROR en exportación SCL para {block_name}: {scl_ex}"
|
||||||
|
)
|
||||||
|
print(f" Ruta problemática: '{scl_blocks_path}'")
|
||||||
|
# Don't raise, just continue
|
||||||
except Exception as prop_ex:
|
except Exception as prop_ex:
|
||||||
print(
|
print(
|
||||||
f" No se pudo obtener el lenguaje de programación para {block_name}. Omitiendo SCL. Error: {prop_ex}"
|
f" No se pudo obtener el lenguaje de programación para {block_name}. Omitiendo SCL. Error: {prop_ex}"
|
||||||
|
@ -194,7 +460,14 @@ def export_plc_data(plc, export_base_dir):
|
||||||
udts_exported = 0
|
udts_exported = 0
|
||||||
udts_skipped = 0
|
udts_skipped = 0
|
||||||
print(f"\n[PLC: {plc_name}] Exportando tipos de datos PLC (UDTs)...")
|
print(f"\n[PLC: {plc_name}] Exportando tipos de datos PLC (UDTs)...")
|
||||||
udt_export_path = os.path.join(plc_export_dir, "PlcDataTypes")
|
udt_export_path = sanitize_path(os.path.join(plc_export_dir, "PlcDataTypes"))
|
||||||
|
|
||||||
|
# Validate UDT export path
|
||||||
|
udt_valid, udt_msg = validate_export_path(udt_export_path)
|
||||||
|
if not udt_valid:
|
||||||
|
print(f" ERROR: Ruta UDT no válida - {udt_msg}")
|
||||||
|
return
|
||||||
|
|
||||||
os.makedirs(udt_export_path, exist_ok=True)
|
os.makedirs(udt_export_path, exist_ok=True)
|
||||||
print(f" Destino: {udt_export_path}")
|
print(f" Destino: {udt_export_path}")
|
||||||
|
|
||||||
|
@ -216,11 +489,19 @@ def export_plc_data(plc, export_base_dir):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f" Exportando {udt_name}...")
|
print(f" Exportando {udt_name}...")
|
||||||
udt.export(
|
try:
|
||||||
target_directory_path=udt_export_path,
|
print(f" Destino: {udt_export_path}")
|
||||||
export_options=EXPORT_OPTIONS,
|
udt.export(
|
||||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
target_directory_path=udt_export_path,
|
||||||
)
|
export_options=EXPORT_OPTIONS,
|
||||||
|
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||||
|
)
|
||||||
|
except Exception as udt_export_ex:
|
||||||
|
print(
|
||||||
|
f" ERROR en exportación UDT para {udt_name}: {udt_export_ex}"
|
||||||
|
)
|
||||||
|
print(f" Ruta problemática: '{udt_export_path}'")
|
||||||
|
raise udt_export_ex
|
||||||
udts_exported += 1
|
udts_exported += 1
|
||||||
except Exception as udt_ex:
|
except Exception as udt_ex:
|
||||||
print(f" ERROR exportando UDT {udt_name}: {udt_ex}")
|
print(f" ERROR exportando UDT {udt_name}: {udt_ex}")
|
||||||
|
@ -236,7 +517,14 @@ def export_plc_data(plc, export_base_dir):
|
||||||
tags_exported = 0
|
tags_exported = 0
|
||||||
tags_skipped = 0
|
tags_skipped = 0
|
||||||
print(f"\n[PLC: {plc_name}] Exportando tablas de variables PLC...")
|
print(f"\n[PLC: {plc_name}] Exportando tablas de variables PLC...")
|
||||||
tags_export_path = os.path.join(plc_export_dir, "PlcTags")
|
tags_export_path = sanitize_path(os.path.join(plc_export_dir, "PlcTags"))
|
||||||
|
|
||||||
|
# Validate tags export path
|
||||||
|
tags_valid, tags_msg = validate_export_path(tags_export_path)
|
||||||
|
if not tags_valid:
|
||||||
|
print(f" ERROR: Ruta Tags no válida - {tags_msg}")
|
||||||
|
return
|
||||||
|
|
||||||
os.makedirs(tags_export_path, exist_ok=True)
|
os.makedirs(tags_export_path, exist_ok=True)
|
||||||
print(f" Destino: {tags_export_path}")
|
print(f" Destino: {tags_export_path}")
|
||||||
|
|
||||||
|
@ -248,14 +536,24 @@ def export_plc_data(plc, export_base_dir):
|
||||||
print(f" Procesando tabla de variables: {table_name}...")
|
print(f" Procesando tabla de variables: {table_name}...")
|
||||||
try:
|
try:
|
||||||
print(f" Exportando {table_name}...")
|
print(f" Exportando {table_name}...")
|
||||||
table.export(
|
try:
|
||||||
target_directory_path=tags_export_path,
|
print(f" Destino: {tags_export_path}")
|
||||||
export_options=EXPORT_OPTIONS,
|
table.export(
|
||||||
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
target_directory_path=tags_export_path,
|
||||||
)
|
export_options=EXPORT_OPTIONS,
|
||||||
|
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
||||||
|
)
|
||||||
|
except Exception as table_export_ex:
|
||||||
|
print(
|
||||||
|
f" ERROR en exportación tabla para {table_name}: {table_export_ex}"
|
||||||
|
)
|
||||||
|
print(f" Ruta problemática: '{tags_export_path}'")
|
||||||
|
raise table_export_ex
|
||||||
tags_exported += 1
|
tags_exported += 1
|
||||||
except Exception as table_ex:
|
except Exception as table_ex:
|
||||||
print(f" ERROR exportando tabla de variables {table_name}: {table_ex}")
|
print(
|
||||||
|
f" ERROR exportando tabla de variables {table_name}: {table_ex}"
|
||||||
|
)
|
||||||
tags_skipped += 1
|
tags_skipped += 1
|
||||||
print(
|
print(
|
||||||
f" Resumen de exportación de tablas de variables: Exportados={tags_exported}, Omitidos/Errores={tags_skipped}"
|
f" Resumen de exportación de tablas de variables: Exportados={tags_exported}, Omitidos/Errores={tags_skipped}"
|
||||||
|
@ -279,12 +577,22 @@ if __name__ == "__main__":
|
||||||
# Validate working directory
|
# Validate working directory
|
||||||
if not working_directory or not os.path.isdir(working_directory):
|
if not working_directory or not os.path.isdir(working_directory):
|
||||||
print("ERROR: Directorio de trabajo no configurado o inválido.")
|
print("ERROR: Directorio de trabajo no configurado o inválido.")
|
||||||
print("Por favor configure el directorio de trabajo usando la aplicación principal.")
|
print(
|
||||||
|
"Por favor configure el directorio de trabajo usando la aplicación principal."
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 1. Select Project File, Export Directory comes from config
|
# 1. Select Project File, Export Directory comes from config
|
||||||
project_file = select_project_file()
|
project_file = select_project_file()
|
||||||
export_dir = working_directory # Use working directory from config
|
export_dir = sanitize_path(
|
||||||
|
working_directory
|
||||||
|
) # Use working directory from config with sanitization
|
||||||
|
|
||||||
|
# Validate export directory
|
||||||
|
is_valid, validation_msg = validate_export_path(export_dir)
|
||||||
|
if not is_valid:
|
||||||
|
print(f"ERROR: Directorio de exportación no válido - {validation_msg}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# 2. Detect TIA Portal version from project file
|
# 2. Detect TIA Portal version from project file
|
||||||
tia_version = detect_tia_version(project_file)
|
tia_version = detect_tia_version(project_file)
|
||||||
|
@ -309,7 +617,9 @@ if __name__ == "__main__":
|
||||||
print(f"Abriendo proyecto: {os.path.basename(project_file)}...")
|
print(f"Abriendo proyecto: {os.path.basename(project_file)}...")
|
||||||
project_object = portal_instance.open_project(project_file_path=project_file)
|
project_object = portal_instance.open_project(project_file_path=project_file)
|
||||||
if project_object is None:
|
if project_object is None:
|
||||||
print("El proyecto podría estar ya abierto, intentando obtener el manejador...")
|
print(
|
||||||
|
"El proyecto podría estar ya abierto, intentando obtener el manejador..."
|
||||||
|
)
|
||||||
project_object = portal_instance.get_project()
|
project_object = portal_instance.get_project()
|
||||||
if project_object is None:
|
if project_object is None:
|
||||||
raise Exception("No se pudo abrir u obtener el proyecto especificado.")
|
raise Exception("No se pudo abrir u obtener el proyecto especificado.")
|
||||||
|
@ -320,7 +630,9 @@ if __name__ == "__main__":
|
||||||
if not plcs:
|
if not plcs:
|
||||||
print("No se encontraron dispositivos PLC en el proyecto.")
|
print("No se encontraron dispositivos PLC en el proyecto.")
|
||||||
else:
|
else:
|
||||||
print(f"Se encontraron {len(plcs)} PLC(s). Iniciando proceso de exportación...")
|
print(
|
||||||
|
f"Se encontraron {len(plcs)} PLC(s). Iniciando proceso de exportación..."
|
||||||
|
)
|
||||||
|
|
||||||
# 6. Iterate and Export Data for each PLC
|
# 6. Iterate and Export Data for each PLC
|
||||||
for plc_device in plcs:
|
for plc_device in plcs:
|
||||||
|
|
23786
data/log.txt
23786
data/log.txt
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue