#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Convertidor de código LAD (Ladder Diagram) de TwinCAT a pseudocódigo estructurado Compatible con IEC61131-3 Autor: Asistente AI Fecha: 2024 """ import re from typing import List, Dict, Any, Optional from dataclasses import dataclass from enum import Enum class ElementType(Enum): CONTACT = "CONTACT" COIL = "COIL" FUNCTION_BLOCK = "FUNCTION_BLOCK" OPERATOR = "OPERATOR" EXPRESSION = "EXPRESSION" ASSIGN = "ASSIGN" @dataclass class LadderElement: """Representa un elemento de la escalera LAD""" element_type: ElementType name: str expression: str = "" positive: bool = True enabled: bool = True parameters: List[str] = None outputs: List[str] = None def __post_init__(self): if self.parameters is None: self.parameters = [] if self.outputs is None: self.outputs = [] @dataclass class LadderNetwork: """Representa una red completa de LAD""" network_id: int comment: str = "" elements: List[LadderElement] = None def __post_init__(self): if self.elements is None: self.elements = [] class LadToStructuredConverter: """Conversor principal de LAD a código estructurado""" def __init__(self): self.networks: List[LadderNetwork] = [] self.current_network: Optional[LadderNetwork] = None self.output_lines: List[str] = [] def parse_file(self, file_path: str) -> None: """Parse el archivo LAD completo""" with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Encontrar la sección LAD lad_start = content.find('_LD_BODY') if lad_start == -1: raise ValueError("No se encontró sección _LD_BODY en el archivo") lad_content = content[lad_start:] self._parse_lad_content(lad_content) def _parse_lad_content(self, content: str) -> None: """Parse el contenido LAD línea por línea""" lines = content.split('\n') i = 0 while i < len(lines): line = lines[i].strip() if line == '_NETWORK': i = self._parse_network(lines, i) else: i += 1 def _parse_network(self, lines: List[str], start_idx: int) -> int: """Parse una red individual""" network_id = len(self.networks) + 1 network = LadderNetwork(network_id=network_id) self.current_network = network i = start_idx + 1 while i < len(lines) and not lines[i].strip().startswith('_NETWORK'): line = lines[i].strip() if line == '_COMMENT': i, comment = self._parse_comment(lines, i) network.comment = comment elif line == '_LD_ASSIGN': i = self._parse_assignment(lines, i) elif line == '_LD_CONTACT': i = self._parse_contact(lines, i) elif line == '_LD_AND' or line == '_LD_OR': i = self._parse_logic_operation(lines, i, line) elif line.startswith('_FUNCTIONBLOCK'): i = self._parse_function_block(lines, i) elif line.startswith('_OPERATOR'): i = self._parse_operator(lines, i) else: i += 1 self.networks.append(network) return i def _parse_comment(self, lines: List[str], start_idx: int) -> tuple[int, str]: """Parse un comentario""" i = start_idx + 1 comment_lines = [] while i < len(lines): line = lines[i].strip() if line == '_END_COMMENT': break if line and not line.startswith('_'): comment_lines.append(line) i += 1 return i + 1, '\n'.join(comment_lines) def _parse_assignment(self, lines: List[str], start_idx: int) -> int: """Parse una asignación""" i = start_idx + 1 # Buscar el elemento a asignar target_var = "" source_expr = "" while i < len(lines): line = lines[i].strip() if line == '_EMPTY': i += 1 continue elif line == '_EXPRESSION': i += 1 continue elif line == '_POSITIV' or line == '_NEGATIV': i += 1 continue elif line.startswith('_OUTPUT'): # Siguiente línea debería ser la variable de salida i += 2 # Saltar _POSITIV/_NEGATIV i += 1 # Saltar _NO_SET o _SET if i < len(lines): target_var = lines[i].strip() break elif line == 'ENABLELIST_END': break elif not line.startswith('_') and not line.startswith('ENABLELIST'): if not target_var: source_expr = line i += 1 if target_var: element = LadderElement( element_type=ElementType.ASSIGN, name=target_var, expression=source_expr ) self.current_network.elements.append(element) return i + 1 def _parse_contact(self, lines: List[str], start_idx: int) -> int: """Parse un contacto""" i = start_idx + 1 contact_name = "" is_positive = True if i < len(lines): contact_name = lines[i].strip() i += 1 if i < len(lines): polarity = lines[i].strip() is_positive = polarity == '_POSITIV' i += 1 element = LadderElement( element_type=ElementType.CONTACT, name=contact_name, positive=is_positive ) self.current_network.elements.append(element) return i def _parse_logic_operation(self, lines: List[str], start_idx: int, operation: str) -> int: """Parse operaciones lógicas AND/OR""" i = start_idx + 1 # Extraer el tipo de operación op_type = operation.replace('_LD_', '') element = LadderElement( element_type=ElementType.OPERATOR, name=op_type ) self.current_network.elements.append(element) return i def _parse_function_block(self, lines: List[str], start_idx: int) -> int: """Parse un bloque de función""" i = start_idx + 1 fb_name = "" parameters = [] if i < len(lines): fb_name = lines[i].strip() i += 1 # Buscar parámetros while i < len(lines): line = lines[i].strip() if line.startswith('_OPERAND'): i += 2 # Saltar _EXPRESSION y _POSITIV if i < len(lines): param = lines[i].strip() parameters.append(param) elif line == '_OUTPUTS': break i += 1 element = LadderElement( element_type=ElementType.FUNCTION_BLOCK, name=fb_name, parameters=parameters ) self.current_network.elements.append(element) return i def _parse_operator(self, lines: List[str], start_idx: int) -> int: """Parse un operador""" i = start_idx + 1 operator_name = "" operands = [] while i < len(lines): line = lines[i].strip() if line.startswith('_OPERAND'): i += 2 # Saltar _EXPRESSION y _POSITIV if i < len(lines): operand = lines[i].strip() operands.append(operand) elif line.startswith('_EXPRESSION') and i + 1 < len(lines): next_line = lines[i + 1].strip() if not next_line.startswith('_'): operator_name = next_line i += 1 elif line == '_OUTPUTS': break i += 1 element = LadderElement( element_type=ElementType.OPERATOR, name=operator_name, parameters=operands ) self.current_network.elements.append(element) return i def convert_to_structured(self) -> str: """Convierte las redes LAD a código pseudo estructurado""" self.output_lines = [] self.output_lines.append("// Código pseudo estructurado generado desde LAD") self.output_lines.append("// Compatible con IEC61131-3") self.output_lines.append("") for network in self.networks: self._convert_network_to_structured(network) self.output_lines.append("") return '\n'.join(self.output_lines) def _convert_network_to_structured(self, network: LadderNetwork) -> None: """Convierte una red a código estructurado""" self.output_lines.append(f"// Red {network.network_id}") if network.comment: comment_lines = network.comment.split('\n') for line in comment_lines: if line.strip(): self.output_lines.append(f"// {line.strip()}") # Procesar elementos de la red conditions = [] assignments = [] for element in network.elements: if element.element_type == ElementType.CONTACT: condition = element.name if not element.positive: condition = f"NOT {condition}" conditions.append(condition) elif element.element_type == ElementType.ASSIGN: if conditions: condition_str = " AND ".join(conditions) self.output_lines.append(f"IF {condition_str} THEN") self.output_lines.append(f" {element.name} := {element.expression};") self.output_lines.append("END_IF;") else: self.output_lines.append(f"{element.name} := {element.expression};") elif element.element_type == ElementType.FUNCTION_BLOCK: params_str = ", ".join(element.parameters) if element.parameters else "" if conditions: condition_str = " AND ".join(conditions) self.output_lines.append(f"IF {condition_str} THEN") self.output_lines.append(f" {element.name}({params_str});") self.output_lines.append("END_IF;") else: self.output_lines.append(f"{element.name}({params_str});") elif element.element_type == ElementType.OPERATOR: if element.name == "AND": # AND lógico - ya manejado en condiciones pass elif element.name == "OR": # OR lógico - cambiar estrategia de condiciones if len(conditions) > 1: last_condition = conditions.pop() conditions[-1] = f"({conditions[-1]} OR {last_condition})" else: # Otros operadores matemáticos if element.parameters and len(element.parameters) >= 2: expr = f"{element.parameters[0]} {element.name} {element.parameters[1]}" if conditions: condition_str = " AND ".join(conditions) self.output_lines.append(f"IF {condition_str} THEN") self.output_lines.append(f" // Resultado := {expr};") self.output_lines.append("END_IF;") else: self.output_lines.append(f"// Resultado := {expr};") def save_to_file(self, output_path: str) -> None: """Guarda el código estructurado a un archivo""" structured_code = self.convert_to_structured() with open(output_path, 'w', encoding='utf-8') as f: f.write(structured_code) print(f"Código pseudo estructurado guardado en: {output_path}") def main(): """Función principal""" import sys if len(sys.argv) != 3: print("Uso: python lad_to_pseudocode_converter.py ") sys.exit(1) input_file = sys.argv[1] output_file = sys.argv[2] try: converter = LadToStructuredConverter() converter.parse_file(input_file) converter.save_to_file(output_file) print(f"Conversión completada exitosamente!") print(f"Redes procesadas: {len(converter.networks)}") except Exception as e: print(f"Error durante la conversión: {e}") sys.exit(1) if __name__ == "__main__": main()