From 0f1d00da1b85544d089cd967b4d231c7b74db035 Mon Sep 17 00:00:00 2001 From: Miguel Date: Tue, 15 Apr 2025 16:28:18 +0200 Subject: [PATCH] Funcionamiento basico --- BlenderCtrl_ProdModeInit.xml | 477 +++++++++++++ BlenderCtrl__Main.json | 486 ------------- BlenderCtrl__Main.md | 171 ----- LadderToPython.py | 1280 ++++++++++++++++++---------------- debug_xml.py | 0 5 files changed, 1141 insertions(+), 1273 deletions(-) create mode 100644 BlenderCtrl_ProdModeInit.xml delete mode 100644 BlenderCtrl__Main.json delete mode 100644 BlenderCtrl__Main.md delete mode 100644 debug_xml.py diff --git a/BlenderCtrl_ProdModeInit.xml b/BlenderCtrl_ProdModeInit.xml new file mode 100644 index 0000000..2485d76 --- /dev/null +++ b/BlenderCtrl_ProdModeInit.xml @@ -0,0 +1,477 @@ + + + + + + false + TASK2 + +
+
+
+
+
+
+ +
+ + Standard + BlenderCtrl_ProdModeInit + + 2012 + LAD + false + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + + + + + + + + + LAD + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + PID Reset Integral + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + + + + + + + + + + + LAD + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + Ctrl Init Errors + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + + Real + 0.0 + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + LAD + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + RunOut Counter + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + it-IT + Prode Mode Init + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + \ No newline at end of file diff --git a/BlenderCtrl__Main.json b/BlenderCtrl__Main.json deleted file mode 100644 index 06dd991..0000000 --- a/BlenderCtrl__Main.json +++ /dev/null @@ -1,486 +0,0 @@ -{ - "Name": "BlenderCtrl__Main", - "Number": "2000", - "ProgrammingLanguage": "LAD", - "Interface": {}, - "Networks": [ - { - "index": 1, - "title": "Clock Generation", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 2, - "title": "Machine Init", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 3, - "title": "Filler Head", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 4, - "title": "Emergency Pressed", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 5, - "title": "Air and CO2 pressure ok and auxiliary ok", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 6, - "title": "Blender State Num", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 7, - "title": "Delay Power On", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 8, - "title": "Production Mode", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 9, - "title": "CIp Mode", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 10, - "title": "Error Faults", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 11, - "title": "Filler Bottle Count Used to push Product", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 12, - "title": "Water Bypass Enable", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 13, - "title": "Still Water Bypass", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 14, - "title": "Manual Syrup Drain Valve Open - Operator Alarm", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 15, - "title": "Manual Syrup Drain Valve Open - Operator Alarm", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 16, - "title": "Maselli Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 17, - "title": "mPDS Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 18, - "title": "mPDS Syrup Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 19, - "title": "Co2 Analog Input", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 20, - "title": "Quality", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 21, - "title": "Input Data", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 22, - "title": "Sel Brix Source Check", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 23, - "title": "Check Water Cooling System Temperature", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 24, - "title": "Tank Level", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 25, - "title": "Production ONS", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 26, - "title": "Blender Prod Mode Init", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 27, - "title": "Rinse ONS", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 28, - "title": "CIP ONS", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 29, - "title": "CIp Mode Init", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 30, - "title": "Reset SPWords ", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 31, - "title": "Blender Run Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 32, - "title": "Tank Pressure Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 33, - "title": "Balaiage", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 34, - "title": "First Production", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 35, - "title": "CIP MAIN Calling ", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 36, - "title": "Blender Rinse", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 37, - "title": "Safeties", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 38, - "title": "Instrument Scanner", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 39, - "title": "Vacuum Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 40, - "title": "Syrup Room Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 41, - "title": "Blend Procedure Data", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 42, - "title": "Pneumatic Valve Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 43, - "title": "Pumps Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 44, - "title": "Prod Report Manager", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 45, - "title": "Outputs", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 46, - "title": "SLIM BLOCK", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 47, - "title": "Interlocking Panel 1", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 48, - "title": "Filler Control", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 49, - "title": "Blender Ctrl Update PWORD", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 50, - "title": "ResetTotalizer", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 51, - "title": "ResetWaterTot", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 52, - "title": "Water VFM Reset Totalizer", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 53, - "title": "ResetCO2Tot", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 54, - "title": "Syrup MFM Reset Totalizer", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 55, - "title": "ResetProductTot", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 56, - "title": "CO2 MFM Reset Tot", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 57, - "title": "ResetCO2Tot", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 58, - "title": "Reset Totalizer", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 59, - "title": "Reset Totalizer", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 60, - "title": "Blender Ctrl Command", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 61, - "title": "DP Global Diag", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 62, - "title": "Profibus", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 63, - "title": "Valve Fault", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 64, - "title": "All Auto", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 65, - "title": "Ctrl HMI Manual Active", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 66, - "title": "Mod Copy Recipe", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 67, - "title": "to HMI - Recipe Management", - "calls": [], - "variables": [], - "logic_elements": [] - }, - { - "index": 68, - "title": "Recipe Calculation", - "calls": [], - "variables": [], - "logic_elements": [] - } - ], - "all_variables": [], - "all_function_calls": [] -} \ No newline at end of file diff --git a/BlenderCtrl__Main.md b/BlenderCtrl__Main.md deleted file mode 100644 index c3ae2a0..0000000 --- a/BlenderCtrl__Main.md +++ /dev/null @@ -1,171 +0,0 @@ -# Function Block: BlenderCtrl__Main (FC2000) - -**Programming Language:** LAD - -## Table of Contents - -1. [Interface](#interface) -2. [Summary of Variables](#summary-of-variables) -3. [Summary of Function Calls](#summary-of-function-calls) -4. [Networks Detail](#networks-detail) -5. [Call Tree](#call-tree) - -## Interface - -_No interface information available_ - -## Summary of Variables - -_No variables found_ - - -## Summary of Function Calls - -_No function calls found_ - - -## Networks Detail (68 networks) - -### Network 1: Clock Generation - -### Network 2: Machine Init - -### Network 3: Filler Head - -### Network 4: Emergency Pressed - -### Network 5: Air and CO2 pressure ok and auxiliary ok - -### Network 6: Blender State Num - -### Network 7: Delay Power On - -### Network 8: Production Mode - -### Network 9: CIp Mode - -### Network 10: Error Faults - -### Network 11: Filler Bottle Count Used to push Product - -### Network 12: Water Bypass Enable - -### Network 13: Still Water Bypass - -### Network 14: Manual Syrup Drain Valve Open - Operator Alarm - -### Network 15: Manual Syrup Drain Valve Open - Operator Alarm - -### Network 16: Maselli Control - -### Network 17: mPDS Control - -### Network 18: mPDS Syrup Control - -### Network 19: Co2 Analog Input - -### Network 20: Quality - -### Network 21: Input Data - -### Network 22: Sel Brix Source Check - -### Network 23: Check Water Cooling System Temperature - -### Network 24: Tank Level - -### Network 25: Production ONS - -### Network 26: Blender Prod Mode Init - -### Network 27: Rinse ONS - -### Network 28: CIP ONS - -### Network 29: CIp Mode Init - -### Network 30: Reset SPWords - -### Network 31: Blender Run Control - -### Network 32: Tank Pressure Control - -### Network 33: Balaiage - -### Network 34: First Production - -### Network 35: CIP MAIN Calling - -### Network 36: Blender Rinse - -### Network 37: Safeties - -### Network 38: Instrument Scanner - -### Network 39: Vacuum Control - -### Network 40: Syrup Room Control - -### Network 41: Blend Procedure Data - -### Network 42: Pneumatic Valve Control - -### Network 43: Pumps Control - -### Network 44: Prod Report Manager - -### Network 45: Outputs - -### Network 46: SLIM BLOCK - -### Network 47: Interlocking Panel 1 - -### Network 48: Filler Control - -### Network 49: Blender Ctrl Update PWORD - -### Network 50: ResetTotalizer - -### Network 51: ResetWaterTot - -### Network 52: Water VFM Reset Totalizer - -### Network 53: ResetCO2Tot - -### Network 54: Syrup MFM Reset Totalizer - -### Network 55: ResetProductTot - -### Network 56: CO2 MFM Reset Tot - -### Network 57: ResetCO2Tot - -### Network 58: Reset Totalizer - -### Network 59: Reset Totalizer - -### Network 60: Blender Ctrl Command - -### Network 61: DP Global Diag - -### Network 62: Profibus - -### Network 63: Valve Fault - -### Network 64: All Auto - -### Network 65: Ctrl HMI Manual Active - -### Network 66: Mod Copy Recipe - -### Network 67: to HMI - Recipe Management - -### Network 68: Recipe Calculation - -## Call Tree - -``` -Function Call Tree: -└─ BlenderCtrl__Main - -``` diff --git a/LadderToPython.py b/LadderToPython.py index f95e7b0..ba823b1 100644 --- a/LadderToPython.py +++ b/LadderToPython.py @@ -1,639 +1,687 @@ -import xml.etree.ElementTree as ET -import json -import os import sys +import os +from lxml import etree -class SiemensLadderDoc: - def __init__(self): - self.function_calls_map = {} # Mapa de función -> llamadas - self.all_variables = set() # Todas las variables usadas - self.all_function_calls = set() # Todas las llamadas a funciones - self.variable_details = {} # Detalles y tipos de variables +def debug_print(message, enabled=True): + """Print debug messages if enabled.""" + if enabled: + print(f"DEBUG: {message}") - def extract_semantics(self, xml_file): - """Extrae la semántica principal del archivo XML de Siemens""" - try: - tree = ET.parse(xml_file) - root = tree.getroot() - except Exception as e: - return f"Error processing XML: {str(e)}" - # Extraer información del bloque - block_info = {} - block = root.find(".//SW.Blocks.FC") or root.find(".//SW.Blocks.FB") +def parse_siemens_lad_to_scl(xml_file, debug=True): + """ + Parse a Siemens LAD/FUP XML file and convert to SCL using lxml. + + Args: + xml_file: Path to the Siemens XML file + debug: Enable debug output + + Returns: + String containing the SCL equivalent code + """ + try: + # Parse the XML file with lxml + parser = etree.XMLParser(remove_blank_text=True) + tree = etree.parse(xml_file, parser) + root = tree.getroot() + + # Extract namespaces + nsmap = root.nsmap + debug_print(f"Namespaces: {nsmap}", debug) + + # Create namespace dictionary for XPath queries + ns = {} + default_ns = nsmap.get(None, "") + if default_ns: + ns["d"] = default_ns + + # Add other namespaces + for prefix, uri in nsmap.items(): + if prefix is not None: + ns[prefix] = uri + + debug_print(f"Namespace dictionary: {ns}", debug) + + # Find the block type (FC, FB, OB) + block = None + block_type = None + for block_tag in ["SW.Blocks.FC", "SW.Blocks.FB", "SW.Blocks.OB"]: + # Try with and without namespace + block = root.find(f".//{block_tag}") + if block is None and "d" in ns: + block = root.find(f".//d:{block_tag}", namespaces=ns) + + if block is not None: + block_type = block_tag.split(".")[-1] + debug_print(f"Found block of type {block_type}", debug) + break if block is None: - return "No function block found in the file" + return "Error: No supported block (FC, FB, OB) found in the XML file." - # Extraer atributos básicos - attr_list = block.find("AttributeList") - if attr_list is not None: - for attr in attr_list: - if attr.tag in ["Name", "Number", "ProgrammingLanguage"]: - block_info[attr.tag] = attr.text + # Extract block information + block_name = block.find(".//Name").text + block_number = block.find(".//Number").text + programming_language = block.find(".//ProgrammingLanguage").text - # Extraer interface (inputs, outputs, temp variables) - interface_section = block.find(".//Interface") - if interface_section is not None: - block_info["Interface"] = self.extract_interface(interface_section) - # Guardar detalles de los tipos de variables - for section_name, members in block_info["Interface"].items(): - for member in members: - var_name = member["Name"] - var_type = member["Datatype"] - self.variable_details[var_name] = { - "type": var_type, - "section": section_name, - } - - # Procesar todas las redes - compile_units = block.findall(".//SW.Blocks.CompileUnit") - networks = [] - - block_name = block_info.get("Name", "Unknown") - self.function_calls_map[block_name] = [] - - for i, unit in enumerate(compile_units): - network = self.process_network(unit, i + 1, block_name) - networks.append(network) - - # Actualizar todas las variables y llamadas - for var in network["variables"]: - self.all_variables.add(var) - - for call in network["calls"]: - self.all_function_calls.add(call) - if call not in self.function_calls_map[block_name]: - self.function_calls_map[block_name].append(call) - - block_info["Networks"] = networks - - # Añadir resúmenes globales - block_info["all_variables"] = sorted(list(self.all_variables)) - block_info["all_function_calls"] = sorted(list(self.all_function_calls)) - - return block_info - - def extract_interface(self, interface): - """Extrae la información de interfaz (entradas, salidas, variables temporales)""" - result = {} - - # Buscar secciones directamente - for section_name in ["Input", "Output", "InOut", "Temp", "Constant", "Return"]: - section = interface.find(f".//Section[@Name='{section_name}']") - if section is not None: - result[section_name] = [] - - for member in section.findall("./Member"): - result[section_name].append( - {"Name": member.get("Name"), "Datatype": member.get("Datatype")} - ) - - return result - - def process_network(self, network, index, parent_block): - """Procesa una red para extraer sus elementos principales""" - result = { - "index": index, - "title": self.get_network_title(network), - "calls": [], - "variables": [], - "logic_elements": [], - "wire_connections": [], # Nueva propiedad para conexiones - } - - # Extraer el XML de la red para analizar la lógica - network_source = network.find(".//NetworkSource") - if network_source is not None: - flg_net = network_source.find( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}FlgNet" - ) - if flg_net is not None: - # Extraer partes y conexiones - parts = flg_net.find( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Parts" - ) - wires = flg_net.find( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wires" - ) - - # Analizar partes - if parts is not None: - # Buscar accesos a variables - access_elements = parts.findall( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Access" - ) - for access in access_elements: - scope = access.get("Scope") - uid = access.get("UId") - var_name = self.extract_variable_name(access) - if var_name and var_name not in result["variables"]: - result["variables"].append(var_name) - - # Buscar llamadas a funciones - call_elements = parts.findall( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Call" - ) - for call in call_elements: - call_info = call.find("CallInfo") - if call_info is not None: - call_name = call_info.get("Name", "Unknown") - if call_name not in result["calls"]: - result["calls"].append(call_name) - - # Registra en el mapa de llamadas para el árbol - if call_name not in self.function_calls_map: - self.function_calls_map[call_name] = [] - - # Buscar elementos lógicos (dentro del elemento Part) - part_elements = parts.findall( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Part" - ) - for part in part_elements: - part_name = part.get("Name") - if part_name and part_name not in result["logic_elements"]: - result["logic_elements"].append(part_name) - - # Analizar conexiones - if wires is not None: - wire_elements = wires.findall( - ".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wire" - ) - for wire in wire_elements: - uid = wire.get("UId") - # Aquí podríamos guardar más detalles de las conexiones si se necesita - result["wire_connections"].append(uid) - - return result - - def get_network_title(self, network): - """Extrae el título de una red - Versión corregida sin XPath avanzado""" - # Primer intento: Buscar título en italiano - title_sections = network.findall( - ".//MultilingualText[@CompositionName='Title']" - ) - for title_section in title_sections: - items = title_section.findall(".//MultilingualTextItem") - for item in items: - attr_list = item.find("AttributeList") - if attr_list is not None: - culture = attr_list.find("Culture") - text = attr_list.find("Text") - - # Priorizar italiano - if ( - culture is not None - and culture.text == "it-IT" - and text is not None - and text.text - ): - return text.text - - # Cualquier otro texto - if text is not None and text.text: - return text.text - - return "Unnamed Network" - - def extract_variable_name(self, access_element): - """Extrae el nombre de variable desde un elemento Access""" - symbol = access_element.find("Symbol") - if symbol is not None: - components = [] - for component in symbol.findall("Component"): - components.append(component.get("Name", "")) - return ".".join(components) - return None - - def generate_call_tree(self): - """Genera un árbol de llamadas en formato texto""" - if not self.function_calls_map: - return "No function calls found." - - def build_tree(function_name, depth=0, visited=None): - if visited is None: - visited = set() - - if function_name in visited: - return f"{' ' * depth}└─ {function_name} (recursive call)\n" - - visited.add(function_name) - result = f"{' ' * depth}└─ {function_name}\n" - - if function_name in self.function_calls_map: - for called_function in self.function_calls_map[function_name]: - result += build_tree(called_function, depth + 1, visited.copy()) - - return result - - tree = "Function Call Tree:\n" - # Comenzar solo con los bloques principales (que no son llamados por otros) - root_functions = set(self.function_calls_map.keys()) - for func, calls in self.function_calls_map.items(): - for call in calls: - if call in root_functions: - root_functions.remove(call) - - for main_function in root_functions: - tree += build_tree(main_function) - tree += "\n" - - return tree - - def generate_markdown(self, block_info): - """Genera documentación en formato markdown""" - if isinstance(block_info, str): - return f"# Error\n\n{block_info}" - - block_name = block_info.get("Name", "Unknown") - block_number = block_info.get("Number", "Unknown") - - md = f"# Function Block: {block_name} (FC{block_number})\n\n" - md += f"**Programming Language:** {block_info.get('ProgrammingLanguage', 'Unknown')}\n\n" - - # Tabla de Contenido - md += "## Table of Contents\n\n" - md += "1. [Interface](#interface)\n" - md += "2. [Summary of Variables](#summary-of-variables)\n" - md += "3. [Summary of Function Calls](#summary-of-function-calls)\n" - md += "4. [Networks Detail](#networks-detail)\n" - md += "5. [Call Tree](#call-tree)\n\n" - - # Información de interfaz - md += "## Interface\n\n" - if "Interface" in block_info and block_info["Interface"]: - for section_name, members in block_info["Interface"].items(): - if members: - md += f"### {section_name}\n\n" - md += "| Name | Datatype |\n|------|----------|\n" - - for member in members: - md += f"| {member['Name']} | {member['Datatype']} |\n" - - md += "\n" - else: - md += "_No interface information available_\n\n" - - # Resumen de variables - md += "## Summary of Variables\n\n" - if block_info.get("all_variables"): - md += "All global variables used in this function block:\n\n" - for var in block_info.get("all_variables"): - md += f"- `{var}`\n" - else: - md += "_No variables found_\n\n" - - # Resumen de llamadas a funciones - md += "\n## Summary of Function Calls\n\n" - if block_info.get("all_function_calls"): - md += "All functions called by this function block:\n\n" - for func in block_info.get("all_function_calls"): - md += f"- `{func}`\n" - else: - md += "_No function calls found_\n\n" - - # Detalles de redes - networks = block_info.get("Networks", []) - md += f"\n## Networks Detail ({len(networks)} networks)\n\n" - - for network in networks: - md += f"### Network {network['index']}: {network['title']}\n\n" - - if network["calls"]: - md += "**Function Calls:**\n" - for call in network["calls"]: - md += f"- `{call}`\n" - md += "\n" - - if network["logic_elements"]: - md += "**Logic Elements:**\n" - for element in network["logic_elements"]: - md += f"- {element}\n" - md += "\n" - - if network["variables"]: - md += "**Variables Used:**\n" - for var in network["variables"]: - md += f"- `{var}`\n" - md += "\n" - - # Árbol de llamadas - md += "## Call Tree\n\n" - md += "```\n" - md += self.generate_call_tree() - md += "```\n" - - return md - - def generate_python_class(self, block_info): - """Genera una representación en clase Python del bloque de función con semántica completa""" - if isinstance(block_info, str): - return f"# Error\n\n# {block_info}" - - block_name = block_info.get("Name", "Unknown") - block_number = block_info.get("Number", "Unknown") - - py_code = f"class {block_name}:\n" - py_code += " def __init__(self):\n" - py_code += ( - f" # Initialize variables for {block_name} (FC{block_number})\n" + debug_print( + f"Block name: {block_name}, Number: {block_number}, Language: {programming_language}", + debug, ) - # Variables de interfaz - if "Interface" in block_info: - interface = block_info["Interface"] - for section_name, members in interface.items(): - if members: - py_code += f" # {section_name} variables\n" - for member in members: - var_name = member["Name"] - var_type = member["Datatype"] - default_value = "None" - if var_type == "Bool": - default_value = "False" - elif var_type in ["Int", "DInt", "Word", "DWord"]: - default_value = "0" - elif var_type == "Real": - default_value = "0.0" - - py_code += ( - f" self.{var_name} = {default_value} # {var_type}\n" - ) - py_code += "\n" - - # Variables globales usadas - if block_info.get("all_variables"): - py_code += " # Global variables used\n" - for var in block_info.get("all_variables"): - if ( - "." in var - ): # Si es una variable estructurada, solo inicializar la estructura principal - struct_name = var.split(".")[0] - if not any( - v.startswith(f"self.{struct_name} =") - for v in py_code.split("\n") - ): - py_code += f" self.{struct_name} = None\n" - else: - if not any( - v.startswith(f"self.{var} =") for v in py_code.split("\n") - ): - # Buscar tipo de variable si existe en detalles - default_value = "None" - if var in self.variable_details: - var_type = self.variable_details[var].get("type") - if var_type == "Bool": - default_value = "False" - elif var_type in ["Int", "DInt", "Word", "DWord"]: - default_value = "0" - elif var_type == "Real": - default_value = "0.0" - - py_code += f" self.{var} = {default_value}\n" - py_code += "\n" - - # Agregar variables típicas para los sistemas blender si no se han añadido ya - common_vars = [ - ("AUX_FALSE", "False"), - ("HMI_PID", "None"), - ("Filler_Head_Variables", "None"), - ("gIN_VoltageOk", "False"), - ("M19000", "False"), - ("gEmergencyPressed", "False"), + # Start SCL code generation + scl_code = [ + f"// SCL equivalent of {programming_language} block: {block_name} ({block_type} {block_number})" ] - for var_name, default_value in common_vars: - if not any(v.startswith(f"self.{var_name} =") for v in py_code.split("\n")): - py_code += f" self.{var_name} = {default_value}\n" + # Extract block title + title_element = block.find(".//MultilingualText[@CompositionName='Title']") + if title_element is not None: + # Try to find title in any available language + for text_item in title_element.findall(".//MultilingualTextItem"): + culture_elem = text_item.find(".//Culture") + text_elem = text_item.find(".//Text") - # Método run principal - py_code += " def run(self):\n" - networks = block_info.get("Networks", []) - - if not networks: - py_code += " pass # No networks found\n" - - for network in networks: - title = ( - network["title"] - if network["title"] != "Unnamed Network" - else f"Network {network['index']}" - ) - py_code += f" # Network {network['index']}: {title}\n" - - # Implementar lógica basada en los elementos del network - if network["calls"]: - for call in network["calls"]: - py_code += f" self.{call}()\n" - - # Si es un llamado que requerimos implementar específicamente - if call == "Clock_Signal": - py_code += ( - " # Generación de señales de reloj para el sistema\n" - ) - elif call == "BlenderCtrl_MachineInit": - py_code += " # Inicialización de la máquina mezcladora\n" - - # Representar la lógica básica de contactos y bobinas - element_logic = self._generate_element_logic(network) - if element_logic: - py_code += element_logic - - # Generar una descripción semántica basada en elementos y variables - if network["variables"] and not network["calls"]: - semantics = self._generate_network_semantics(network) - if semantics: - py_code += semantics - - py_code += "\n" - - # Métodos para cada función llamada - if block_info.get("all_function_calls"): - for func_call in block_info.get("all_function_calls"): - py_code += f" def {func_call}(self):\n" - - # Implementaciones especiales para funciones conocidas - if func_call == "Clock_Signal": - py_code += " # Clock generation implementation\n" - py_code += " # This generates the system timing signals\n" - py_code += " self.Clock_10ms = not self.Clock_10ms\n" - py_code += " \n" - py_code += " # Generate 100ms and 1s clock signals\n" - py_code += " if self.Clock_Counter % 10 == 0:\n" - py_code += " self.Clock_100ms = not self.Clock_100ms\n" - py_code += " \n" - py_code += " if self.Clock_Counter % 100 == 0:\n" - py_code += " self.Clock_1s = not self.Clock_1s\n" - py_code += " self.Clock_Counter = 0\n" - py_code += " \n" - py_code += " self.Clock_Counter += 1\n" - elif func_call == "BlenderCtrl_MachineInit": - py_code += " # Initialize blender machine state\n" - py_code += " if not self.MachineInitialized:\n" - py_code += " self.MachineState = 0 # IDLE state\n" - py_code += " self.SystemError = False\n" - py_code += " self.MachineInitialized = True\n" - else: - py_code += f" # Implementation of {func_call}\n" - py_code += " pass\n" - - py_code += "\n" - - return py_code - - def _generate_element_logic(self, network): - """Genera código Python para la lógica de los elementos en una red""" - logic_code = "" - elements = network["logic_elements"] - variables = network["variables"] - - # Lógica para contactos, bobinas y bloques BLKMOV - if "Contact" in elements and variables: - # Si hay contacto y bobina, crear una condición if - if ("Coil" in elements or "RCoil" in elements) and len(variables) >= 2: - input_var = variables[0] - output_var = variables[-1] - - logic_code += f" if self.{input_var}:\n" - if "RCoil" in elements: # Reset Coil - establece en False - logic_code += ( - f" self.{output_var} = False # Reset coil\n" + if text_elem is not None and text_elem.text: + culture = ( + culture_elem.text if culture_elem is not None else "unknown" ) + debug_print(f"Found title in {culture}: {text_elem.text}", debug) + scl_code.append(f"// Title: {text_elem.text}") + break + + # Generate block header + if block_type == "FC": + scl_code.append(f'FUNCTION "{block_name}" : VOID') + elif block_type == "FB": + scl_code.append(f'FUNCTION_BLOCK "{block_name}"') + elif block_type == "OB": + scl_code.append(f'ORGANIZATION_BLOCK "{block_name}"') + + # Add BEGIN marker + scl_code.append("BEGIN") + + # Process each network + compile_units = block.findall(".//SW.Blocks.CompileUnit") + debug_print(f"Found {len(compile_units)} networks", debug) + + for i, unit in enumerate(compile_units): + debug_print(f"Processing network {i+1}", debug) + + # Get network title + title_element = unit.find(".//MultilingualText[@CompositionName='Title']") + network_title = f"Network {i+1}" + + if title_element is not None: + for text_item in title_element.findall(".//MultilingualTextItem"): + culture_elem = text_item.find(".//Culture") + text_elem = text_item.find(".//Text") + + if text_elem is not None and text_elem.text: + culture = ( + culture_elem.text if culture_elem is not None else "unknown" + ) + network_title = f"Network {i+1}: {text_elem.text}" + debug_print( + f"Network title ({culture}): {network_title}", debug + ) + break + + scl_code.append(f" // {network_title}") + + # Get network source + network_source_elem = unit.find(".//NetworkSource") + if network_source_elem is None: + debug_print(f"No NetworkSource found for network {i+1}", debug) + continue + + # Look for FlgNet - the container for LAD/FUP network logic + flg_net = None + + # Try direct search + flg_net = network_source_elem.find("FlgNet") + + # Try with namespace + if flg_net is None and "d" in ns: + flg_net = network_source_elem.find("d:FlgNet", namespaces=ns) + + # Try using full XPath with any namespace + if flg_net is None: + for elem in network_source_elem: + if elem.tag.endswith("FlgNet"): + flg_net = elem + break + + if flg_net is None: + debug_print(f"No FlgNet found for network {i+1}", debug) + continue + + # Get FlgNet namespace - THIS IS THE KEY FIX + flgnet_ns = {} + if flg_net.nsmap: + debug_print(f"FlgNet has its own namespace: {flg_net.nsmap}", debug) + # Add namespace with 'flg' prefix + for prefix, uri in flg_net.nsmap.items(): + if prefix is None: + flgnet_ns["flg"] = uri + else: + flgnet_ns[prefix] = uri + + # Extract Parts and Wires sections - try with proper namespace handling + parts = None + wires = None + + # Try different methods to find Parts and Wires + # Method 1: Direct access + parts = flg_net.find("Parts") + wires = flg_net.find("Wires") + + # Method 2: With FlgNet namespace + if (parts is None or wires is None) and flgnet_ns: + parts = parts or flg_net.find("flg:Parts", namespaces=flgnet_ns) + wires = wires or flg_net.find("flg:Wires", namespaces=flgnet_ns) + + # Method 3: Try XPath with any namespace + if parts is None: + for child in flg_net: + if child.tag.endswith("Parts"): + parts = child + break + + if wires is None: + for child in flg_net: + if child.tag.endswith("Wires"): + wires = child + break + + if parts is None: + debug_print("No Parts element found - check XML namespace", debug) + debug_print( + f"FlgNet children: {[child.tag for child in flg_net]}", debug + ) + continue + + if wires is None: + debug_print("No Wires element found - check XML namespace", debug) + continue + + debug_print(f"Successfully found Parts and Wires elements", debug) + + # Process function calls (CALL instructions) + call_elements = [] + # Try both direct and with namespace + call_elements.extend(parts.findall("Call")) + if flgnet_ns: + call_elements.extend(parts.findall("flg:Call", namespaces=flgnet_ns)) + + # Try XPath with any namespace + if not call_elements: + for child in parts: + if child.tag.endswith("Call"): + call_elements.append(child) + + debug_print(f"Found {len(call_elements)} function calls", debug) + + for call in call_elements: + call_uid = call.get("UId") + # Try both direct and with namespace for CallInfo + call_info = call.find("CallInfo") or ( + call.find("flg:CallInfo", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace if still not found + if call_info is None: + for child in call: + if child.tag.endswith("CallInfo"): + call_info = child + break + + if call_info is None: + debug_print( + f"No CallInfo found for call with UId {call_uid}", debug + ) + continue + + function_name = call_info.get("Name") + block_type = call_info.get("BlockType") + debug_print(f"Found call to {function_name} ({block_type})", debug) + + # Check if this call is connected to a powerrail (unconditional execution) + is_enabled = False + + wire_elements = [] + wire_elements.extend(wires.findall("Wire")) + if flgnet_ns: + wire_elements.extend( + wires.findall("flg:Wire", namespaces=flgnet_ns) + ) + + # Try XPath with any namespace + if not wire_elements: + for child in wires: + if child.tag.endswith("Wire"): + wire_elements.append(child) + + for wire in wire_elements: + # Check if wire has a powerrail source + powerrail = wire.find("Powerrail") or ( + wire.find("flg:Powerrail", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if powerrail is None: + for child in wire: + if child.tag.endswith("Powerrail"): + powerrail = child + break + + if powerrail is None: + continue + + # Check if wire connects to this call's enable input + name_con = wire.find(f"NameCon[@UId='{call_uid}'][@Name='en']") or ( + wire.find( + f"flg:NameCon[@UId='{call_uid}'][@Name='en']", + namespaces=flgnet_ns, + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if name_con is None: + for child in wire: + if ( + child.tag.endswith("NameCon") + and child.get("UId") == call_uid + and child.get("Name") == "en" + ): + name_con = child + break + + if name_con is not None: + is_enabled = True + debug_print( + f"Call to {function_name} is directly enabled by powerrail", + debug, + ) + break + + if is_enabled: + # Generate code for function call + scl_code.append(f" {function_name}();") + + # Process MOVE operations + move_elements = [] + move_elements.extend(parts.findall("Part[@Name='Move']")) + if flgnet_ns: + move_elements.extend( + parts.findall("flg:Part[@Name='Move']", namespaces=flgnet_ns) + ) + + # Try XPath with any namespace + if not move_elements: + for child in parts: + if child.tag.endswith("Part") and child.get("Name") == "Move": + move_elements.append(child) + + debug_print(f"Found {len(move_elements)} MOVE operations", debug) + + for move in move_elements: + move_uid = move.get("UId") + debug_print(f"Processing MOVE with UId {move_uid}", debug) + + # Check if MOVE is directly enabled + is_enabled = False + for wire in wire_elements: + powerrail = wire.find("Powerrail") or ( + wire.find("flg:Powerrail", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if powerrail is None: + for child in wire: + if child.tag.endswith("Powerrail"): + powerrail = child + break + + if powerrail is None: + continue + + name_con = wire.find(f"NameCon[@UId='{move_uid}'][@Name='en']") or ( + wire.find( + f"flg:NameCon[@UId='{move_uid}'][@Name='en']", + namespaces=flgnet_ns, + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if name_con is None: + for child in wire: + if ( + child.tag.endswith("NameCon") + and child.get("UId") == move_uid + and child.get("Name") == "en" + ): + name_con = child + break + + if name_con is not None: + is_enabled = True + debug_print("MOVE is directly enabled by powerrail", debug) + break + + if not is_enabled: + debug_print("MOVE is not directly enabled, skipping", debug) + continue + + # Find source value for MOVE + source_uid = None + for wire in wire_elements: + # Find wire that connects to this MOVE's input + name_con = wire.find(f"NameCon[@UId='{move_uid}'][@Name='in']") or ( + wire.find( + f"flg:NameCon[@UId='{move_uid}'][@Name='in']", + namespaces=flgnet_ns, + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if name_con is None: + for child in wire: + if ( + child.tag.endswith("NameCon") + and child.get("UId") == move_uid + and child.get("Name") == "in" + ): + name_con = child + break + + if name_con is None: + continue + + # Get source identifier from wire + ident_con = wire.find("IdentCon") or ( + wire.find("flg:IdentCon", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if ident_con is None: + for child in wire: + if child.tag.endswith("IdentCon"): + ident_con = child + break + + if ident_con is not None: + source_uid = ident_con.get("UId") + debug_print(f"MOVE input connected to UId {source_uid}", debug) + break + + # Find source value + source_value = None + if source_uid: + source_access = parts.find(f"Access[@UId='{source_uid}']") or ( + parts.find( + f"flg:Access[@UId='{source_uid}']", namespaces=flgnet_ns + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if source_access is None: + for child in parts: + if ( + child.tag.endswith("Access") + and child.get("UId") == source_uid + ): + source_access = child + break + + if source_access is not None: + scope = source_access.get("Scope") + debug_print(f"Source access scope: {scope}", debug) + + if scope == "LiteralConstant": + # For literal constants - try with namespace + constant = source_access.find(".//Constant") or ( + source_access.find( + ".//flg:Constant", namespaces=flgnet_ns + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if constant is None: + for elem in source_access.iter(): + if elem.tag.endswith("Constant"): + constant = elem + break + + if constant is not None: + # Try with namespace for child elements + const_type = constant.find("ConstantType") or ( + constant.find( + "flg:ConstantType", namespaces=flgnet_ns + ) + if flgnet_ns + else None + ) + const_value = constant.find("ConstantValue") or ( + constant.find( + "flg:ConstantValue", namespaces=flgnet_ns + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if const_value is None: + for child in constant: + if child.tag.endswith("ConstantValue"): + const_value = child + break + + if const_value is not None: + source_value = const_value.text + debug_print( + f"Source value is constant: {source_value}", + debug, + ) + + elif scope == "GlobalVariable": + # For variables - try with namespace + symbol = source_access.find("Symbol") or ( + source_access.find("flg:Symbol", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if symbol is None: + for child in source_access: + if child.tag.endswith("Symbol"): + symbol = child + break + + if symbol is not None: + # Try with namespace for components + components = symbol.findall("Component") or ( + symbol.findall( + "flg:Component", namespaces=flgnet_ns + ) + if flgnet_ns + else [] + ) + + # Try XPath with any namespace + if not components: + for child in symbol: + if child.tag.endswith("Component"): + components.append(child) + + if components: + source_value = ".".join( + [comp.get("Name") for comp in components] + ) + debug_print( + f"Source value is variable: {source_value}", + debug, + ) + + # Find destination for MOVE + dest_uid = None + for wire in wire_elements: + # Find wire that connects from this MOVE's output + name_con = wire.find( + f"NameCon[@UId='{move_uid}'][@Name='out1']" + ) or ( + wire.find( + f"flg:NameCon[@UId='{move_uid}'][@Name='out1']", + namespaces=flgnet_ns, + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if name_con is None: + for child in wire: + if ( + child.tag.endswith("NameCon") + and child.get("UId") == move_uid + and child.get("Name") == "out1" + ): + name_con = child + break + + if name_con is None: + continue + + # Get destination identifier from wire + ident_con = wire.find("IdentCon") or ( + wire.find("flg:IdentCon", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if ident_con is None: + for child in wire: + if child.tag.endswith("IdentCon"): + ident_con = child + break + + if ident_con is not None: + dest_uid = ident_con.get("UId") + debug_print(f"MOVE output connected to UId {dest_uid}", debug) + break + + # Find destination value + dest_value = None + if dest_uid: + dest_access = parts.find(f"Access[@UId='{dest_uid}']") or ( + parts.find( + f"flg:Access[@UId='{dest_uid}']", namespaces=flgnet_ns + ) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if dest_access is None: + for child in parts: + if ( + child.tag.endswith("Access") + and child.get("UId") == dest_uid + ): + dest_access = child + break + + if ( + dest_access is not None + and dest_access.get("Scope") == "GlobalVariable" + ): + symbol = dest_access.find("Symbol") or ( + dest_access.find("flg:Symbol", namespaces=flgnet_ns) + if flgnet_ns + else None + ) + + # Try XPath with any namespace + if symbol is None: + for child in dest_access: + if child.tag.endswith("Symbol"): + symbol = child + break + + if symbol is not None: + components = symbol.findall("Component") or ( + symbol.findall("flg:Component", namespaces=flgnet_ns) + if flgnet_ns + else [] + ) + + # Try XPath with any namespace + if not components: + for child in symbol: + if child.tag.endswith("Component"): + components.append(child) + + if components: + dest_value = ".".join( + [comp.get("Name") for comp in components] + ) + debug_print( + f"Destination value is: {dest_value}", debug + ) + + # Generate SCL for assignment + if source_value is not None and dest_value is not None: + scl_code.append(f" {dest_value} := {source_value};") else: - logic_code += f" self.{output_var} = True\n" - # Contacto negado (NBox) - elif "NBox" in elements and len(variables) >= 2: - input_var = variables[0] - output_var = variables[-1] - logic_code += f" if not self.{input_var}:\n" - logic_code += f" self.{output_var} = True\n" - # Operación OR - elif "O" in elements and len(variables) >= 3: - inputs = variables[:-1] # Todos excepto el último son entradas - output = variables[-1] + debug_print("Could not resolve complete MOVE operation", debug) - conditions = [] - for var in inputs: - conditions.append(f"self.{var}") + # Close the block + if block_type == "FC": + scl_code.append("END_FUNCTION;") + elif block_type == "FB": + scl_code.append("END_FUNCTION_BLOCK;") + elif block_type == "OB": + scl_code.append("END_ORGANIZATION_BLOCK;") - logic_code += f" if {' or '.join(conditions)}:\n" - logic_code += f" self.{output} = True\n" + return "\n".join(scl_code) - # Bloques Move y BLKMOV - if ("Move" in elements or "BLKMOV" in elements) and len(variables) >= 2: - source = variables[0] - target = variables[-1] + except Exception as e: + import traceback - # Si el origen tiene un punto, es una estructura - if "." in source and "." in target: - src_struct = source.split(".")[0] - src_field = source.split(".")[1] - tgt_struct = target.split(".")[0] - tgt_field = target.split(".")[1] - - logic_code += f" # Block move operation\n" - logic_code += f" if self.{src_struct} is not None and self.{tgt_struct} is not None:\n" - logic_code += f" self.{target} = self.{source}\n" - else: - logic_code += f" # Move operation\n" - logic_code += f" self.{target} = self.{source}\n" - - return logic_code - - def _generate_network_semantics(self, network): - """Genera una descripción semántica para una red basada en sus elementos y variables""" - elements = network["logic_elements"] - variables = network["variables"] - semantics = "" - - # Verificar elementos específicos y generar código semántico - if "Emergency" in " ".join(variables) or "EmergencyPressed" in " ".join( - variables - ): - semantics += " # Control de parada de emergencia\n" - if len(variables) >= 2: - in_var = variables[0] - out_var = variables[-1] - semantics += f" if self.{in_var} and not self.M19000:\n" - semantics += f" self.{out_var} = True\n" - - elif "Filler_Head" in " ".join(variables) or "FillerHead" in " ".join( - variables - ): - semantics += " # Procesamiento de variables de cabezal de llenado\n" - semantics += " if self.AUX_FALSE:\n" - semantics += " # Implementación del BLKMOV para cabezal\n" - semantics += " self.Filler_Head_Variables.FillerHead = self.HMI_PID.PPM303\n" - semantics += " self.Block_Move_Err = self.resultado_operacion\n" - - elif "Pressure" in " ".join(variables) or "CO2" in " ".join(variables): - semantics += " # Verificación de presión de CO2 y aire\n" - semantics += " if self.Air_Pressure_OK and self.CO2_Pressure_OK:\n" - semantics += " self.System_Pressure_OK = True\n" - - elif "Temperature" in " ".join(variables) or "Temp" in " ".join(variables): - semantics += ( - " # Control de temperatura del sistema de enfriamiento\n" - ) - semantics += " if self.Temperature_Current > self.Temperature_Max:\n" - semantics += " self.Temperature_Alarm = True\n" - - elif "Tank" in " ".join(variables) or "Level" in " ".join(variables): - semantics += " # Monitoreo de nivel de tanque\n" - semantics += " if self.Tank_Level < self.Tank_Level_Min:\n" - semantics += " self.Tank_Level_Low = True\n" - - elif "Reset" in " ".join(variables) and "Totalizer" in " ".join(variables): - semantics += " # Reseteo de totalizador\n" - semantics += " if self.Reset_Command:\n" - semantics += " self.Totalizer_Value = 0\n" - semantics += " self.Reset_Complete = True\n" - - elif "HMI" in " ".join(variables) or "Manual" in " ".join(variables): - semantics += " # Control de interfaz HMI y modo manual\n" - semantics += " if self.HMI_Manual_Mode_Requested:\n" - semantics += " self.System_In_Manual_Mode = True\n" - semantics += " self.System_In_Auto_Mode = False\n" - - return semantics - - -def process_siemens_file(file_path, output_format="markdown"): - """Procesa un archivo Siemens PLC y genera la documentación""" - extractor = SiemensLadderDoc() - block_info = extractor.extract_semantics(file_path) - - if output_format.lower() == "json": - return json.dumps(block_info, indent=2) - elif output_format.lower() == "markdown": - return extractor.generate_markdown(block_info) - elif output_format.lower() == "call_tree": - extractor.extract_semantics(file_path) - return extractor.generate_call_tree() - elif output_format.lower() == "python": - return extractor.generate_python_class(block_info) - else: - return "Unknown output format. Supported formats: markdown, json, call_tree, python" + debug_print(f"Exception occurred: {str(e)}", debug) + debug_print(traceback.format_exc(), debug) + return f"Error converting to SCL: {str(e)}" +# Main execution if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python script.py [output_format]") - sys.exit(1) - - file_path = sys.argv[1] - output_format = sys.argv[2] if len(sys.argv) > 2 else "markdown" - - result = process_siemens_file(file_path, output_format) - - extension = ( - "py" - if output_format.lower() == "python" - else "json" if output_format.lower() == "json" else "md" - ) - output_file = os.path.splitext(file_path)[0] + "." + extension - with open(output_file, "w", encoding="utf-8") as f: - f.write(result) - - print(f"Documentation generated: {output_file}") + if len(sys.argv) > 1: + input_file = sys.argv[1] + if os.path.exists(input_file): + scl = parse_siemens_lad_to_scl(input_file) + print(scl) + else: + print(f"Error: File {input_file} not found") + else: + print("Usage: python script.py ") diff --git a/debug_xml.py b/debug_xml.py deleted file mode 100644 index e69de29..0000000