790 lines
32 KiB
Python
790 lines
32 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 verify_export_format(export_path, expected_format="SIMATIC_SD"):
|
|
"""
|
|
Verifies what format was actually exported by examining file extensions.
|
|
Returns (actual_format, file_extensions, file_count)
|
|
"""
|
|
if not os.path.exists(export_path):
|
|
return "NO_FILES", [], 0
|
|
|
|
files = [
|
|
f
|
|
for f in os.listdir(export_path)
|
|
if os.path.isfile(os.path.join(export_path, f))
|
|
]
|
|
if not files:
|
|
return "EMPTY_FOLDER", [], 0
|
|
|
|
extensions = [os.path.splitext(f)[1].lower() for f in files]
|
|
extension_counts = {}
|
|
for ext in extensions:
|
|
extension_counts[ext] = extension_counts.get(ext, 0) + 1
|
|
|
|
# Determine actual format based on file extensions
|
|
if all(ext == ".xml" for ext in extensions):
|
|
actual_format = "XML_ONLY"
|
|
elif any(ext in [".sd", ".simatic"] for ext in extensions):
|
|
actual_format = "SIMATIC_SD"
|
|
elif ".xml" in extensions and len(set(extensions)) > 1:
|
|
actual_format = "MIXED"
|
|
else:
|
|
actual_format = "UNKNOWN"
|
|
|
|
return actual_format, extension_counts, len(files)
|
|
|
|
|
|
def check_simatic_sd_block_compatibility(block):
|
|
"""
|
|
Checks if a block is compatible with SIMATIC SD export format.
|
|
Returns (is_compatible, reason)
|
|
"""
|
|
try:
|
|
# Check if block is consistent
|
|
if not block.is_consistent():
|
|
return False, "Block is not consistent/compiled"
|
|
|
|
# Check programming language - SIMATIC SD only supports LAD
|
|
try:
|
|
prog_lang = block.get_programming_language()
|
|
if prog_lang != ts.Enums.ProgrammingLanguage.LAD:
|
|
return (
|
|
False,
|
|
f"Language {prog_lang} not supported (SIMATIC SD requires LAD)",
|
|
)
|
|
except Exception:
|
|
return False, "Could not determine programming language"
|
|
|
|
# Check block type - SIMATIC SD typically supports FB, FC, OB
|
|
try:
|
|
block_type = block.get_block_type()
|
|
supported_types = [
|
|
ts.Enums.BlockType.FB, # Function Block
|
|
ts.Enums.BlockType.FC, # Function
|
|
ts.Enums.BlockType.OB, # Organization Block
|
|
]
|
|
if block_type not in supported_types:
|
|
return False, f"Block type {block_type} may not be supported"
|
|
except Exception:
|
|
# If we can't determine type, assume it might work
|
|
pass
|
|
|
|
return True, "Block appears compatible with SIMATIC SD"
|
|
|
|
except Exception as e:
|
|
return False, f"Error checking compatibility: {e}"
|
|
|
|
|
|
def verify_export_format(export_path, expected_format="SimaticSD"):
|
|
"""
|
|
Verifies if the exported files are actually in the expected format.
|
|
For SIMATIC SD, looks for specific keywords like RUNG, END_RUNG, wire#
|
|
Returns (is_correct_format, file_count, sample_files, format_details)
|
|
"""
|
|
if not os.path.exists(export_path):
|
|
return False, 0, [], "Directory does not exist"
|
|
|
|
files = [
|
|
f
|
|
for f in os.listdir(export_path)
|
|
if os.path.isfile(os.path.join(export_path, f))
|
|
]
|
|
if not files:
|
|
return False, 0, [], "No files found"
|
|
|
|
# Check first few files for format
|
|
sample_files = files[:3]
|
|
format_details = []
|
|
|
|
for file_name in sample_files:
|
|
file_path = os.path.join(export_path, file_name)
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
content = f.read(2000) # Read first 2KB
|
|
|
|
file_info = {"file": file_name, "size": len(content)}
|
|
|
|
if expected_format == "SimaticSD":
|
|
# SIMATIC SD specific keywords and structure
|
|
sd_keywords = ["RUNG", "END_RUNG", "wire#", "NETWORK", "TITLE", "LAD"]
|
|
xml_indicators = ["<?xml", "<Document", "<SW.Blocks"]
|
|
|
|
found_sd_keywords = [kw for kw in sd_keywords if kw in content]
|
|
found_xml_indicators = [xi for xi in xml_indicators if xi in content]
|
|
|
|
file_info["sd_keywords"] = found_sd_keywords
|
|
file_info["xml_indicators"] = found_xml_indicators
|
|
file_info["is_xml"] = len(found_xml_indicators) > 0
|
|
file_info["is_simatic_sd"] = (
|
|
len(found_sd_keywords) > 0 and not file_info["is_xml"]
|
|
)
|
|
file_info["first_100_chars"] = (
|
|
content[:100].replace("\n", " ").replace("\r", "")
|
|
)
|
|
|
|
else: # XML format
|
|
file_info["is_xml"] = (
|
|
content.strip().startswith("<?xml") or "<Document" in content
|
|
)
|
|
file_info["first_100_chars"] = (
|
|
content[:100].replace("\n", " ").replace("\r", "")
|
|
)
|
|
|
|
format_details.append(file_info)
|
|
|
|
except Exception as e:
|
|
format_details.append({"file": file_name, "error": str(e)})
|
|
|
|
# Determine overall result for SIMATIC SD
|
|
if expected_format == "SimaticSD":
|
|
is_correct_format = any(
|
|
f.get("is_simatic_sd", False) for f in format_details if "error" not in f
|
|
)
|
|
else:
|
|
is_correct_format = any(
|
|
f.get("is_xml", False) for f in format_details if "error" not in f
|
|
)
|
|
|
|
return is_correct_format, len(files), sample_files, format_details
|
|
|
|
|
|
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 enum found (value: {simatic_sd_format})")
|
|
|
|
# Try to get more information about available formats
|
|
try:
|
|
all_formats = [
|
|
attr for attr in dir(ts.Enums.ExportFormats) if not attr.startswith("_")
|
|
]
|
|
print(f" Available export formats: {all_formats}")
|
|
except Exception:
|
|
pass
|
|
|
|
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 check_tia_portal_version():
|
|
"""Check TIA Portal version and compatibility."""
|
|
print("\n=== TIA PORTAL VERSION CHECK ===")
|
|
try:
|
|
# This will be filled when we connect to TIA Portal
|
|
print("TIA Portal version check will be performed after connection...")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Could not check TIA Portal version: {e}")
|
|
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_sd = 0
|
|
blocks_exported_xml = 0
|
|
blocks_skipped = 0
|
|
blocks_not_lad = 0
|
|
print(f"\n[PLC: {plc_name}] Exporting Program Blocks (SIMATIC SD)...")
|
|
print(" NOTE: SIMATIC SD format only supports LAD (Ladder) programming language!")
|
|
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
|
|
|
|
# Check programming language - CRITICAL for SIMATIC SD
|
|
is_compatible, compatibility_reason = (
|
|
check_simatic_sd_block_compatibility(block)
|
|
)
|
|
print(f" Compatibility check: {compatibility_reason}")
|
|
|
|
if not is_compatible:
|
|
print(f" Block {block_name} not compatible with SIMATIC SD.")
|
|
print(f" Exporting XML only: {compatibility_reason}")
|
|
blocks_not_lad += 1
|
|
|
|
# Export only in XML for incompatible 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,
|
|
)
|
|
blocks_exported_xml += 1
|
|
print(
|
|
f" ✓ Exported {block_name} in XML (incompatible with SIMATIC SD)"
|
|
)
|
|
continue
|
|
|
|
# Try SIMATIC SD export for LAD blocks
|
|
print(f" Exporting LAD block {block_name} as SIMATIC SD...")
|
|
try:
|
|
block.export(
|
|
target_directory_path=sd_blocks_path,
|
|
export_options=EXPORT_OPTIONS,
|
|
export_format=ts.Enums.ExportFormats.SimaticSD, # SIMATIC SD format
|
|
keep_folder_structure=KEEP_FOLDER_STRUCTURE,
|
|
)
|
|
|
|
# Verify if the export was actually in SIMATIC SD format
|
|
print(f" Verifying SIMATIC SD format for {block_name}...")
|
|
is_sd, file_count, sample_files, format_details = (
|
|
verify_export_format(sd_blocks_path, "SimaticSD")
|
|
)
|
|
|
|
if is_sd:
|
|
blocks_exported_sd += 1
|
|
print(
|
|
f" ✓ Successfully exported {block_name} in REAL SIMATIC SD format"
|
|
)
|
|
# Show sample of SD content
|
|
for detail in format_details:
|
|
if detail.get("is_simatic_sd") and detail.get(
|
|
"sd_keywords"
|
|
):
|
|
print(
|
|
f" 🎯 SD Keywords found: {', '.join(detail['sd_keywords'])}"
|
|
)
|
|
break
|
|
else:
|
|
print(
|
|
f" ❌ FAILED: Export claimed SD but files are actually XML!"
|
|
)
|
|
print(f" 📋 Format analysis:")
|
|
for detail in format_details:
|
|
if "error" not in detail:
|
|
print(f" File: {detail['file']}")
|
|
print(
|
|
f" SIMATIC SD: {detail.get('is_simatic_sd', False)}"
|
|
)
|
|
print(
|
|
f" XML format: {detail.get('is_xml', False)}"
|
|
)
|
|
if detail.get("sd_keywords"):
|
|
print(
|
|
f" SD keywords: {detail['sd_keywords']}"
|
|
)
|
|
if detail.get("xml_indicators"):
|
|
print(
|
|
f" XML indicators: {detail['xml_indicators']}"
|
|
)
|
|
print(
|
|
f" Content start: {detail.get('first_100_chars', '')[:50]}..."
|
|
)
|
|
|
|
# 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,
|
|
)
|
|
blocks_exported_xml += 1
|
|
print(f" + Also exported {block_name} in XML for comparison")
|
|
|
|
except Exception as export_ex:
|
|
print(f" ERROR during SIMATIC SD export: {export_ex}")
|
|
print(
|
|
f" This is likely because SIMATIC SD has specific requirements:"
|
|
)
|
|
print(f" - Block must be in LAD (Ladder) format")
|
|
print(
|
|
f" - Block must be compatible with SIMATIC SD specification"
|
|
)
|
|
# 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_xml += 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: SIMATIC SD={blocks_exported_sd}, XML={blocks_exported_xml}, Non-LAD={blocks_not_lad}, 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()}")
|
|
|
|
# Get TIA Portal version information
|
|
try:
|
|
portal_version = portal_instance.get_version()
|
|
print(f"TIA Portal Version: {portal_version}")
|
|
|
|
# Check if this version really supports SIMATIC SD
|
|
version_parts = portal_version.split(".")
|
|
major_version = int(version_parts[0]) if version_parts else 0
|
|
if major_version < 20:
|
|
print(
|
|
f"⚠️ WARNING: TIA Portal V{major_version} may not fully support SIMATIC SD (requires V20+)"
|
|
)
|
|
else:
|
|
print(f"✓ TIA Portal V{major_version} should support SIMATIC SD")
|
|
|
|
except Exception as ver_ex:
|
|
print(f"Could not get TIA Portal version: {ver_ex}")
|
|
|
|
# 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 (LAD blocks only)"
|
|
)
|
|
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("\n📋 IMPORTANT SIMATIC SD LIMITATIONS:")
|
|
print(" • SIMATIC SD format ONLY supports LAD (Ladder) programming language")
|
|
print(" • SCL, STL, FBD blocks are exported as XML only")
|
|
print(" • Only FB, FC, OB block types are typically supported")
|
|
print(" • Complex LAD elements may still fall back to XML")
|
|
print(
|
|
"\nNow you can compare the differences between SIMATIC SD and traditional XML formats!"
|
|
)
|
|
|
|
# Add file analysis
|
|
print("\n=== FILE FORMAT ANALYSIS ===")
|
|
for plc_device in plcs:
|
|
plc_name = plc_device.get_name()
|
|
import datetime
|
|
|
|
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"),
|
|
]
|
|
|
|
simatic_sd_working = False
|
|
total_sd_files = 0
|
|
total_xml_fallbacks = 0
|
|
|
|
for folder_name, description in folders_to_check:
|
|
folder_path = os.path.join(plc_export_dir, folder_name)
|
|
if os.path.exists(folder_path):
|
|
actual_format, extensions, file_count = verify_export_format(
|
|
folder_path
|
|
)
|
|
print(f" {description}: {file_count} files")
|
|
print(f" Format detected: {actual_format}")
|
|
print(f" File extensions: {extensions}")
|
|
|
|
if "SD" in folder_name: # This should be SIMATIC SD folder
|
|
if actual_format == "XML_ONLY":
|
|
print(
|
|
f" ⚠️ WARNING: Expected SIMATIC SD but got XML only!"
|
|
)
|
|
total_xml_fallbacks += file_count
|
|
elif actual_format == "SIMATIC_SD":
|
|
simatic_sd_working = True
|
|
total_sd_files += file_count
|
|
elif actual_format == "UNKNOWN" and file_count > 0:
|
|
print(f" 🔍 UNKNOWN format - needs manual inspection")
|
|
|
|
else:
|
|
print(f" {description}: Folder not found")
|
|
|
|
print(f"\n🔍 SIMATIC SD DIAGNOSIS FOR {plc_name}:")
|
|
if simatic_sd_working:
|
|
print(
|
|
f" ✅ SIMATIC SD is working: {total_sd_files} files in true SD format"
|
|
)
|
|
else:
|
|
print(f" ❌ SIMATIC SD NOT working: All 'SD' exports are actually XML")
|
|
print(f" 📊 Total XML fallbacks: {total_xml_fallbacks}")
|
|
|
|
if total_xml_fallbacks > 0:
|
|
print(f"\n 💡 POSSIBLE CAUSES:")
|
|
print(f" • TIA Portal version doesn't fully support SIMATIC SD")
|
|
print(f" • TIA Scripting version incompatible with SIMATIC SD")
|
|
print(f" • Project blocks contain unsupported LAD elements")
|
|
print(
|
|
f" • SIMATIC SD enum exists but falls back to XML silently"
|
|
)
|
|
|
|
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.")
|