""" export_logic_from_tia : Script para exportar el software de un PLC desde TIA Portal en archivos XML y SCL. """ import tkinter as tk from tkinter import filedialog import os import sys import traceback from pathlib import Path # Import Path 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 # --- Configuration --- # Supported TIA Portal versions mapping (extension -> version) SUPPORTED_TIA_VERSIONS = { ".ap18": "18.0", ".ap19": "19.0", ".ap20": "20.0" } EXPORT_OPTIONS = None # Use default export options KEEP_FOLDER_STRUCTURE = True # Replicate TIA project folder structure in export directory # --- TIA Scripting Import Handling --- # Check if the TIA_SCRIPTING environment variable is set if os.getenv("TIA_SCRIPTING"): sys.path.append(os.getenv("TIA_SCRIPTING")) else: # Optional: Define a fallback path if the environment variable isn't set # fallback_path = "C:\\path\\to\\your\\TIA_Scripting_binaries" # if os.path.exists(fallback_path): # sys.path.append(fallback_path) pass # Allow import to fail if not found try: import siemens_tia_scripting as ts EXPORT_OPTIONS = ( ts.Enums.ExportOptions.WithDefaults ) # Set default options now that 'ts' is imported except ImportError: print("ERROR: Failed to import 'siemens_tia_scripting'.") print("Ensure:") print(f"1. TIA Portal Openness for V{TIA_PORTAL_VERSION} is installed.") print( "2. The 'siemens_tia_scripting' Python module is installed (pip install ...) or" ) print( " the path to its binaries is set in the 'TIA_SCRIPTING' environment variable." ) print( "3. You are using a compatible Python version (e.g., 3.12.X as per documentation)." ) sys.exit(1) except Exception as e: print(f"An unexpected error occurred during import: {e}") traceback.print_exc() sys.exit(1) # --- Functions --- def get_supported_filetypes(): """Returns the supported file types for TIA Portal projects.""" filetypes = [] for ext, version in SUPPORTED_TIA_VERSIONS.items(): version_major = version.split('.')[0] filetypes.append((f"TIA Portal V{version_major} Projects", f"*{ext}")) # Add option to show all supported files all_extensions = " ".join([f"*{ext}" for ext in SUPPORTED_TIA_VERSIONS.keys()]) filetypes.insert(0, ("All TIA Portal Projects", all_extensions)) return filetypes def detect_tia_version(project_file_path): """Detects TIA Portal version based on file extension.""" file_path = Path(project_file_path) file_extension = file_path.suffix.lower() if file_extension in SUPPORTED_TIA_VERSIONS: detected_version = SUPPORTED_TIA_VERSIONS[file_extension] print(f"Versión de TIA Portal detectada: {detected_version} (de la extensión {file_extension})") return detected_version else: 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 print("Usando por defecto TIA Portal V18.0") return "18.0" def select_project_file(): """Opens a dialog to select a TIA Portal project file.""" root = tk.Tk() root.withdraw() # Hide the main tkinter window file_path = filedialog.askopenfilename( title="Select TIA Portal Project File", filetypes=get_supported_filetypes() ) root.destroy() if not file_path: print("No se seleccionó ningún archivo de proyecto. Saliendo.") sys.exit(0) return file_path def select_export_directory(): """Opens a dialog to select the export directory.""" root = tk.Tk() root.withdraw() # Hide the main tkinter window dir_path = filedialog.askdirectory(title="Select Export Directory") root.destroy() if not dir_path: print("No export directory selected. Exiting.") sys.exit(0) return dir_path def export_plc_data(plc, export_base_dir): """Exports Blocks, UDTs, and Tag Tables from a given PLC.""" plc_name = plc.get_name() print(f"\n--- Procesando PLC: {plc_name} ---") # Define base export path for this PLC plc_export_dir = os.path.join(export_base_dir, plc_name) os.makedirs(plc_export_dir, exist_ok=True) # --- Export Program Blocks --- blocks_exported = 0 blocks_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando bloques de programa...") xml_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_XML") scl_blocks_path = os.path.join(plc_export_dir, "ProgramBlocks_SCL") os.makedirs(xml_blocks_path, exist_ok=True) os.makedirs(scl_blocks_path, exist_ok=True) print(f" Destino XML: {xml_blocks_path}") print(f" Destino SCL: {scl_blocks_path}") try: program_blocks = plc.get_program_blocks() print(f" Se encontraron {len(program_blocks)} bloques de programa.") for block in program_blocks: block_name = block.get_name() print(f" Procesando bloque: {block_name}...") try: if not block.is_consistent(): print(f" Compilando bloque {block_name}...") block.compile() if not block.is_consistent(): print( f" ADVERTENCIA: Bloque {block_name} inconsistente después de compilar. Omitiendo." ) blocks_skipped += 1 continue print(f" Exportando {block_name} como XML...") block.export( target_directory_path=xml_blocks_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) try: prog_language = block.get_property(name="ProgrammingLanguage") if prog_language == "SCL": print(f" Exportando {block_name} como SCL...") 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 prop_ex: print( f" No se pudo obtener el lenguaje de programación para {block_name}. Omitiendo SCL. Error: {prop_ex}" ) blocks_exported += 1 except Exception as block_ex: print(f" ERROR exportando bloque {block_name}: {block_ex}") blocks_skipped += 1 print( f" Resumen de exportación de bloques: Exportados={blocks_exported}, Omitidos/Errores={blocks_skipped}" ) except Exception as e: print(f" ERROR procesando bloques de programa: {e}") traceback.print_exc() # --- Export PLC Data Types (UDTs) --- udts_exported = 0 udts_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando tipos de datos PLC (UDTs)...") udt_export_path = os.path.join(plc_export_dir, "PlcDataTypes") os.makedirs(udt_export_path, exist_ok=True) print(f" Destino: {udt_export_path}") try: udts = plc.get_user_data_types() print(f" Se encontraron {len(udts)} UDTs.") for udt in udts: udt_name = udt.get_name() print(f" Procesando UDT: {udt_name}...") try: if not udt.is_consistent(): print(f" Compilando UDT {udt_name}...") udt.compile() if not udt.is_consistent(): print( f" ADVERTENCIA: UDT {udt_name} inconsistente después de compilar. Omitiendo." ) udts_skipped += 1 continue print(f" Exportando {udt_name}...") udt.export( target_directory_path=udt_export_path, export_options=EXPORT_OPTIONS, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) udts_exported += 1 except Exception as udt_ex: print(f" ERROR exportando UDT {udt_name}: {udt_ex}") udts_skipped += 1 print( f" Resumen de exportación de UDTs: Exportados={udts_exported}, Omitidos/Errores={udts_skipped}" ) except Exception as e: print(f" ERROR procesando UDTs: {e}") traceback.print_exc() # --- Export PLC Tag Tables --- tags_exported = 0 tags_skipped = 0 print(f"\n[PLC: {plc_name}] Exportando tablas de variables PLC...") tags_export_path = os.path.join(plc_export_dir, "PlcTags") os.makedirs(tags_export_path, exist_ok=True) print(f" Destino: {tags_export_path}") try: tag_tables = plc.get_plc_tag_tables() print(f" Se encontraron {len(tag_tables)} tablas de variables.") for table in tag_tables: table_name = table.get_name() print(f" Procesando tabla de variables: {table_name}...") try: print(f" Exportando {table_name}...") table.export( target_directory_path=tags_export_path, export_options=EXPORT_OPTIONS, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) tags_exported += 1 except Exception as table_ex: print(f" ERROR exportando tabla de variables {table_name}: {table_ex}") tags_skipped += 1 print( f" Resumen de exportación de tablas de variables: Exportados={tags_exported}, Omitidos/Errores={tags_skipped}" ) except Exception as e: print(f" ERROR procesando tablas de variables: {e}") traceback.print_exc() print(f"\n--- Finalizado el procesamiento del PLC: {plc_name} ---") # --- Main Script --- if __name__ == "__main__": configs = load_configuration() working_directory = configs.get("working_directory") print("--- Exportador de datos TIA Portal (Bloques, UDTs, Variables) ---") # Validate working directory if not working_directory or not os.path.isdir(working_directory): print("ERROR: Directorio de trabajo no configurado o inválido.") print("Por favor configure el directorio de trabajo usando la aplicación principal.") sys.exit(1) # 1. Select Project File, Export Directory comes from config project_file = select_project_file() export_dir = working_directory # Use working directory from config # 2. Detect TIA Portal version from project file tia_version = detect_tia_version(project_file) print(f"\nProyecto seleccionado: {project_file}") print(f"Usando directorio de exportación (Directorio de trabajo): {export_dir}") portal_instance = None project_object = None try: # 3. Connect to TIA Portal with detected version print(f"\nConectando a TIA Portal V{tia_version}...") portal_instance = ts.open_portal( version=tia_version, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, ) print("Conectado a TIA Portal.") print(f"ID del proceso del Portal: {portal_instance.get_process_id()}") # 4. Open Project print(f"Abriendo proyecto: {os.path.basename(project_file)}...") project_object = portal_instance.open_project(project_file_path=project_file) if project_object is None: print("El proyecto podría estar ya abierto, intentando obtener el manejador...") project_object = portal_instance.get_project() if project_object is None: raise Exception("No se pudo abrir u obtener el proyecto especificado.") print("Proyecto abierto exitosamente.") # 5. Get PLCs plcs = project_object.get_plcs() if not plcs: print("No se encontraron dispositivos PLC en el proyecto.") else: print(f"Se encontraron {len(plcs)} PLC(s). Iniciando proceso de exportación...") # 6. Iterate and Export Data for each PLC for plc_device in plcs: export_plc_data(plc=plc_device, export_base_dir=export_dir) print("\nProceso de exportación completado.") except ts.TiaException as tia_ex: print(f"\nError de TIA Portal Openness: {tia_ex}") traceback.print_exc() except FileNotFoundError: print(f"\nERROR: Archivo de proyecto no encontrado en {project_file}") except Exception as e: print(f"\nOcurrió un error inesperado: {e}") traceback.print_exc() finally: # 7. Cleanup if portal_instance: try: print("\nCerrando TIA Portal...") portal_instance.close_portal() print("TIA Portal cerrado.") except Exception as close_ex: print(f"Error durante la limpieza de TIA Portal: {close_ex}") print("\nScript finalizado.")