""" export_simatic_sd_tia20 : Script para exportar bloques de PLC desde TIA Portal 20 en formato SIMATIC SD. Basado en la nueva funcionalidad de exportación SIMATIC SD disponible en TIA Portal V20. """ import tkinter as tk from tkinter import filedialog import os import sys import traceback 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 --- TIA_PORTAL_VERSION = "20.0" # Target TIA Portal version for SIMATIC SD support 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 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=[ ( f"TIA Portal V{TIA_PORTAL_VERSION} Projects", f"*.ap{TIA_PORTAL_VERSION.split('.')[0]}", ) ], # e.g. *.ap20 ) root.destroy() if not file_path: print("No project file selected. Exiting.") 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 check_simatic_sd_support(): """Verifies if SIMATIC SD format is available in the current TIA Scripting version.""" try: # Check if SimaticSD is available in ExportFormats enum simatic_sd_format = ts.Enums.ExportFormats.SimaticSD print(f"✓ SIMATIC SD format supported (enum value: {simatic_sd_format})") return True except AttributeError: print("✗ ERROR: SIMATIC SD format not available in this TIA Scripting version.") print("Please ensure you are using TIA Portal V20 or later with compatible TIA Scripting.") return False def export_plc_data_simatic_sd(plc, export_base_dir): """Exports Blocks, UDTs, and Tag Tables from a given PLC in SIMATIC SD format.""" plc_name = plc.get_name() print(f"\n--- Processing PLC: {plc_name} (SIMATIC SD Export) ---") # Define base export path for this PLC with timestamp to avoid conflicts import datetime timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") plc_export_dir = os.path.join(export_base_dir, f"{plc_name}_SimaticSD_{timestamp}") os.makedirs(plc_export_dir, exist_ok=True) # --- Export Program Blocks in SIMATIC SD Format --- blocks_exported = 0 blocks_skipped = 0 print(f"\n[PLC: {plc_name}] Exporting Program Blocks (SIMATIC SD)...") sd_blocks_path = os.path.join(plc_export_dir, "01_ProgramBlocks_SD") os.makedirs(sd_blocks_path, exist_ok=True) print(f" SIMATIC SD Target: {sd_blocks_path}") # Also create a separate SimaticML export for comparison xml_blocks_path = os.path.join(plc_export_dir, "02_ProgramBlocks_XML_Compare") os.makedirs(xml_blocks_path, exist_ok=True) try: program_blocks = plc.get_program_blocks() print(f" Found {len(program_blocks)} program blocks.") for block in program_blocks: block_name = block.get_name() print(f" Processing block: {block_name}...") try: if not block.is_consistent(): print(f" Compiling block {block_name}...") block.compile() if not block.is_consistent(): print( f" WARNING: Block {block_name} inconsistent after compile. Skipping." ) blocks_skipped += 1 continue print(f" Exporting {block_name} as SIMATIC SD...") try: block.export( target_directory_path=sd_blocks_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticSD, # New SIMATIC SD format keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) blocks_exported += 1 print(f" ✓ Successfully exported {block_name} in SIMATIC SD") # Also export same block in XML for comparison print(f" Exporting {block_name} as XML for comparison...") block.export( target_directory_path=xml_blocks_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, # Traditional XML format keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) print(f" + Also exported {block_name} in XML for comparison") except Exception as export_ex: print(f" ERROR during export: {export_ex}") # Try to export only in XML if SD fails try: print(f" Attempting fallback XML export for {block_name}...") block.export( target_directory_path=xml_blocks_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) print(f" ✓ Fallback XML export successful for {block_name}") blocks_exported += 1 except Exception as fallback_ex: print(f" ERROR: Both SD and XML export failed: {fallback_ex}") blocks_skipped += 1 except Exception as block_ex: print(f" ERROR exporting block {block_name}: {block_ex}") blocks_skipped += 1 print( f" Program Blocks Export Summary: Exported={blocks_exported}, Skipped/Errors={blocks_skipped}" ) except Exception as e: print(f" ERROR processing Program Blocks: {e}") traceback.print_exc() # --- Export PLC Data Types (UDTs) in SIMATIC SD Format --- udts_exported = 0 udts_skipped = 0 print(f"\n[PLC: {plc_name}] Exporting PLC Data Types - UDTs (SIMATIC SD)...") udt_sd_export_path = os.path.join(plc_export_dir, "03_PlcDataTypes_SD") udt_xml_export_path = os.path.join(plc_export_dir, "04_PlcDataTypes_XML_Compare") os.makedirs(udt_sd_export_path, exist_ok=True) os.makedirs(udt_xml_export_path, exist_ok=True) print(f" SIMATIC SD Target: {udt_sd_export_path}") print(f" XML Compare Target: {udt_xml_export_path}") try: udts = plc.get_user_data_types() print(f" Found {len(udts)} UDTs.") for udt in udts: udt_name = udt.get_name() print(f" Processing UDT: {udt_name}...") try: if not udt.is_consistent(): print(f" Compiling UDT {udt_name}...") udt.compile() if not udt.is_consistent(): print( f" WARNING: UDT {udt_name} inconsistent after compile. Skipping." ) udts_skipped += 1 continue print(f" Exporting {udt_name} as SIMATIC SD...") try: udt.export( target_directory_path=udt_sd_export_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format for UDTs keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) # Also export in XML for comparison udt.export( target_directory_path=udt_xml_export_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) udts_exported += 1 print(f" ✓ Successfully exported {udt_name} (SD + XML)") except Exception as udt_export_ex: print(f" ERROR during UDT export: {udt_export_ex}") udts_skipped += 1 except Exception as udt_ex: print(f" ERROR exporting UDT {udt_name}: {udt_ex}") udts_skipped += 1 print( f" UDT Export Summary: Exported={udts_exported}, Skipped/Errors={udts_skipped}" ) except Exception as e: print(f" ERROR processing UDTs: {e}") traceback.print_exc() # --- Export PLC Tag Tables in SIMATIC SD Format --- tags_exported = 0 tags_skipped = 0 print(f"\n[PLC: {plc_name}] Exporting PLC Tag Tables (SIMATIC SD)...") tags_sd_export_path = os.path.join(plc_export_dir, "05_PlcTags_SD") tags_xml_export_path = os.path.join(plc_export_dir, "06_PlcTags_XML_Compare") os.makedirs(tags_sd_export_path, exist_ok=True) os.makedirs(tags_xml_export_path, exist_ok=True) print(f" SIMATIC SD Target: {tags_sd_export_path}") print(f" XML Compare Target: {tags_xml_export_path}") try: tag_tables = plc.get_plc_tag_tables() print(f" Found {len(tag_tables)} Tag Tables.") for table in tag_tables: table_name = table.get_name() print(f" Processing Tag Table: {table_name}...") try: print(f" Exporting {table_name} as SIMATIC SD...") try: table.export( target_directory_path=tags_sd_export_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format for Tag Tables keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) # Also export in XML for comparison table.export( target_directory_path=tags_xml_export_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) tags_exported += 1 print(f" ✓ Successfully exported {table_name} (SD + XML)") except Exception as tag_export_ex: print(f" ERROR during Tag Table export: {tag_export_ex}") tags_skipped += 1 except Exception as table_ex: print(f" ERROR exporting Tag Table {table_name}: {table_ex}") tags_skipped += 1 print( f" Tag Table Export Summary: Exported={tags_exported}, Skipped/Errors={tags_skipped}" ) except Exception as e: print(f" ERROR processing Tag Tables: {e}") traceback.print_exc() print(f"\n--- Finished processing PLC: {plc_name} ---") def export_additional_formats(plc, export_base_dir): """Optional: Export in traditional formats alongside SIMATIC SD for comparison.""" plc_name = plc.get_name() print(f"\n[Optional] Exporting traditional formats for comparison - PLC: {plc_name}") # Create comparison directory comparison_dir = os.path.join(export_base_dir, plc_name, "Comparison_Formats") os.makedirs(comparison_dir, exist_ok=True) # Export a few blocks in SimaticML for comparison xml_comparison_path = os.path.join(comparison_dir, "SimaticML_Sample") os.makedirs(xml_comparison_path, exist_ok=True) try: program_blocks = plc.get_program_blocks() # Export first 3 blocks in SimaticML for comparison for i, block in enumerate(program_blocks[:3]): if block.is_consistent(): block_name = block.get_name() print(f" Exporting {block_name} in SimaticML for comparison...") block.export( target_directory_path=xml_comparison_path, export_options=EXPORT_OPTIONS, export_format=ts.Enums.ExportFormats.SimaticML, keep_folder_structure=KEEP_FOLDER_STRUCTURE, ) except Exception as e: print(f" Warning: Could not export comparison formats: {e}") # --- Main Script --- if __name__ == "__main__": configs = load_configuration() working_directory = configs.get("working_directory") print("--- TIA Portal 20 SIMATIC SD Exporter ---") print("Exporting Blocks, UDTs, and Tags in SIMATIC SD Format") # Check SIMATIC SD support first if not check_simatic_sd_support(): sys.exit(1) # Validate working directory if not working_directory or not os.path.isdir(working_directory): print("ERROR: Working directory not set or invalid in configuration.") print("Please configure the working directory using the main application.") 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 print(f"\nSelected Project: {project_file}") print(f"Using Export Directory (Working Directory): {export_dir}") portal_instance = None project_object = None try: # 2. Connect to TIA Portal V20 print(f"\nConnecting to TIA Portal V{TIA_PORTAL_VERSION}...") portal_instance = ts.open_portal( version=TIA_PORTAL_VERSION, portal_mode=ts.Enums.PortalMode.WithGraphicalUserInterface, ) print("Connected to TIA Portal V20.") print(f"Portal Process ID: {portal_instance.get_process_id()}") # 3. Open Project print(f"Opening project: {os.path.basename(project_file)}...") project_object = portal_instance.open_project(project_file_path=project_file) if project_object is None: print("Project might already be open, attempting to get handle...") project_object = portal_instance.get_project() if project_object is None: raise Exception("Failed to open or get the specified project.") print("Project opened successfully.") # 4. Get PLCs plcs = project_object.get_plcs() if not plcs: print("No PLC devices found in the project.") else: print(f"Found {len(plcs)} PLC(s). Starting SIMATIC SD export process...") # 5. Iterate and Export Data for each PLC in SIMATIC SD format import datetime # Add this import for timestamp for plc_device in plcs: export_plc_data_simatic_sd( plc=plc_device, export_base_dir=export_dir ) print("\n🎉 SIMATIC SD Export process completed successfully!") print("\nExported files structure:") print("├── [PLC_Name]_SimaticSD_[timestamp]/") print("│ ├── 01_ProgramBlocks_SD/ # SIMATIC SD format") print("│ ├── 02_ProgramBlocks_XML_Compare/ # Traditional XML for comparison") print("│ ├── 03_PlcDataTypes_SD/") print("│ ├── 04_PlcDataTypes_XML_Compare/") print("│ ├── 05_PlcTags_SD/") print("│ └── 06_PlcTags_XML_Compare/") print("\nNow you can compare the differences between SIMATIC SD and traditional XML formats!") # Add file analysis print("\n=== FILE ANALYSIS ===") for plc_device in plcs: plc_name = plc_device.get_name() timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") plc_export_dir = os.path.join(export_dir, f"{plc_name}_SimaticSD_{timestamp}") print(f"\nAnalyzing exported files for PLC: {plc_name}") folders_to_check = [ ("01_ProgramBlocks_SD", "SIMATIC SD Blocks"), ("02_ProgramBlocks_XML_Compare", "XML Blocks"), ("03_PlcDataTypes_SD", "SIMATIC SD UDTs"), ("04_PlcDataTypes_XML_Compare", "XML UDTs"), ("05_PlcTags_SD", "SIMATIC SD Tags"), ("06_PlcTags_XML_Compare", "XML Tags") ] for folder_name, description in folders_to_check: folder_path = os.path.join(plc_export_dir, folder_name) if os.path.exists(folder_path): files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] print(f" {description}: {len(files)} files") if files: extensions = set(os.path.splitext(f)[1].lower() for f in files) print(f" File extensions: {', '.join(extensions) if extensions else 'No extensions'}") else: print(f" {description}: Folder not found") except ts.TiaException as tia_ex: print(f"\nTIA Portal Openness Error: {tia_ex}") traceback.print_exc() except FileNotFoundError: print(f"\nERROR: Project file not found at {project_file}") except Exception as e: print(f"\nAn unexpected error occurred: {e}") traceback.print_exc() finally: # 6. Cleanup if portal_instance: try: print("\nClosing TIA Portal...") portal_instance.close_portal() print("TIA Portal closed.") except Exception as close_ex: print(f"Error during TIA Portal cleanup: {close_ex}") print("\nScript finished.")