ParamManagerScripts/backend/script_groups/TwinCat/lad_to_pseudocode_converter.py

391 lines
13 KiB
Python

#!/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 <archivo_entrada.EXP> <archivo_salida.txt>")
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()