ParamManagerScripts/backend/script_groups/ObtainIOFromProjectTia/x2.py

462 lines
19 KiB
Python

"""
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.")