16 KiB
LAD-to-SCL Conversion Pipeline: Documentación de Referencia
1. Visión General
Este documento describe un pipeline de scripts de Python diseñado para convertir bloques de función o funciones (FC/FB) escritos en Ladder Logic (LAD) desde archivos XML de TIA Portal Openness a un código SCL (Structured Control Language) semánticamente equivalente.
El proceso se divide en tres etapas principales, cada una manejada por un script específico:
- XML a JSON Enriquecido (
x1_to_json.py
): Parsea el XML de Openness, extrae la estructura lógica y las conexiones explícitas, e infiere conexiones implícitas (especialmente las habilitaciones EN) para crear un archivo JSON detallado. - Procesamiento Semántico (
process.py
): Lee el JSON enriquecido y, de forma iterativa, traduce cada instrucción LAD a su equivalente SCL, manejando dependencias, propagando el estado lógico (RLO), y agrupando lógica paralela. El SCL generado se almacena dentro del propio JSON. - Generación de SCL Final (
generate_scl.py
): Lee el JSON completamente procesado y ensambla el código SCL final en un archivo.scl
formateado, incluyendo declaraciones de variables y el cuerpo del programa.
2. Etapas del Pipeline
Etapa 1: XML a JSON Enriquecido (x1_to_json.py
)
- Propósito: Transformar la compleja y a veces ambigua estructura XML de Openness en un formato JSON estructurado y más fácil de procesar, añadiendo información clave que está implícita en el LAD visual pero no siempre explícita en el XML.
- Entrada: Archivo
.xml
exportado desde TIA Portal Openness para un FC o FB. - Salida: Archivo
_simplified.json
. - Proceso Clave:
- Parseo XML: Utiliza
lxml
para leer el archivo XML. - Extracción de Metadatos: Obtiene nombre del bloque, número, lenguaje original, comentario del bloque.
- Extracción de Interfaz: Parsea las secciones
Input
,Output
,InOut
,Temp
,Constant
,Return
para obtener la declaración de variables. - Parseo de Redes (
CompileUnit
): Itera sobre cada red lógica.parse_network
:- Parseo de Componentes:
Access
: Identifica variables globales/locales, constantes literales y tipadas (parse_access
).Part
: Identifica instrucciones estándar LAD comoContact
,Coil
,Move
,Add
, etc. Importante: Detecta pines negados (<Negated>
) y los almacena ennegated_pins
(parse_part
).Call
: Identifica llamadas a otros FCs o FBs, extrayendo el nombre del bloque llamado, tipo (FC/FB) e información de instancia DB si aplica (parse_call
).- Crea
access_map
yparts_and_calls_map
para referencia rápida.
- Parseo de Conexiones (
Wire
):- Construye
wire_connections
: Un mapa(dest_uid, dest_pin) -> [(src_uid, src_pin), ...]
. - Construye
source_connections
: Un mapa(src_uid, src_pin) -> [(dest_uid, dest_pin), ...]
. - Construye
eno_outputs
: Un mapasrc_uid -> [(dest_uid, dest_pin), ...]
para conexiones que salen de un pineno
.
- Construye
- Construcción Lógica Inicial: Crea una lista de diccionarios (
all_logic_steps
), uno por cadaPart
oCall
, rellenandoinputs
youtputs
solo con las conexiones explícitas encontradas en losWire
. - Inferencia de Conexión
EN
(¡Paso Crucial!):- Itera sobre los bloques funcionales (
Move
,Add
,Call
, etc.) que no tienen una entradaen
explícita definida después del paso anterior. - Utiliza una heurística (buscar hacia atrás en la lista ordenada por UID) para encontrar la fuente de RLO más probable que precede a este bloque (la salida
out
de un bloque lógico comoContact
/O
/Eq
o la salidaeno
de un bloque funcional anterior). - Si se encuentra una fuente inferida, añade la conexión
en
al diccionarioinputs
del bloque funcional actual. Esto enriquece el JSON con la dependencia lógica implícita.
- Itera sobre los bloques funcionales (
- Adición de Lógica ENO Interesante: Identifica las conexiones que salen de un pin
eno
y no van directamente al pinen
de la siguiente instrucción, almacenándolas en el campoeno_logic
. - Ordenamiento: Ordena las instrucciones en la lista
logic
final (generalmente por UID).
- Parseo de Componentes:
- Escritura JSON: Guarda la estructura de datos completa en el archivo
_simplified.json
.
- Parseo XML: Utiliza
Etapa 2: Procesamiento Semántico (process.py
)
- Propósito: Traducir la lógica LAD (representada en el JSON enriquecido) a código SCL embebido, resolviendo dependencias entre instrucciones y aplicando optimizaciones de agrupación.
- Entrada: Archivo
_simplified.json
(generado por la Etapa 1). - Salida: Archivo
_simplified_scl_processed.json
. - Proceso Clave:
- Carga de JSON y Preparación: Lee el JSON de entrada y reconstruye mapas de acceso por red. Inicializa el
scl_map
global (o por red). - Bucle Iterativo: Repite los siguientes pasos hasta que no se realicen cambios en un pase completo o se alcance un límite máximo de pases.
- Fase 1: Procesadores Base:
- Itera sobre cada instrucción en cada red.
- Si la instrucción no ha sido procesada (
_scl
o_error
) ni agrupada (grouped
), busca el procesador adecuado (process_xxx
) basado en sutype
. process_xxx
(Ejecución):- Llama a
get_scl_representation
para obtener el SCL de sus pines de entrada, buscándolos enscl_map
o directamente enaccess_map
. - Si alguna dependencia no está resuelta (devuelve
None
), el procesador retornaFalse
. - Si las dependencias están resueltas:
- Generadores RLO (
Contact
,O
,Eq
,PBox
): Calculan la expresión booleana SCL resultante y la almacenan enscl_map
bajo la clave(network_id, instr_uid, 'out')
. También almacenan el estadoEN
enscl_map
paraeno
si aplica. Guardan un comentario eninstruction['scl']
. - Bloques Funcionales (
Move
,Add
,Convert
,Mod
,Call
):- Obtienen la condición
EN
(explícita o inferida por el JSON enriquecido). - Generan el código SCL core (la asignación o cálculo).
- Generan el SCL final, siempre incluyendo
IF en_scl THEN ... END_IF;
sien_scl
no es exactamente"TRUE"
. - Almacenan el SCL final en
instruction['scl']
. - Almacenan el nombre de la variable de salida (o temporal) en
scl_map
paraout
/out1
. - Almacenan el
en_scl
enscl_map
paraeno
.
- Obtienen la condición
- Bobinas (
Coil
): Obtienen el RLO de entrada (in
), obtienen el operando destino, generan la asignacióndestino := rlo;
y la guardan eninstruction['scl']
.
- Generadores RLO (
- Marcan la instrucción como procesada añadiendo
_scl
ainstruction['type']
. - Retornan
True
.
- Llama a
- Se registra si hubo algún cambio en esta fase (
made_change_in_base_pass
).
- Fase 2: Agrupación de IFs (
process_group_ifs
):- Itera sobre las instrucciones ya procesadas (
_scl
) que son generadoras de condición (Contact
,O
,Eq
, etc.). - Obtiene la
condition_scl
descl_map
. - Busca todos los bloques funcionales (
Move_scl
,Add_scl
, etc.) cuyo pinEN
esté conectado a la salida de esta instrucción generadora. - Verifica si el SCL de estos consumidores ya contiene un
IF condition_scl THEN...
. - Si encuentra más de uno con la misma condición
IF
:- Extrae el código core (lo que está dentro del
IF...END_IF;
) de cada consumidor. - Construye un único bloque
IF condition_scl THEN ... END_IF;
que contiene todos los cores extraídos. - Sobrescribe el campo
scl
de la instrucción generadora de condición con este nuevo bloqueIF
agrupado. - Marca cada instrucción consumidora con
grouped = True
y cambia suscl
a un comentario (GROUPED_COMMENT
) para evitar quegenerate_scl.py
lo use. - Se registra si hubo algún cambio en esta fase (
made_change_in_group_pass
).
- Extrae el código core (lo que está dentro del
- Itera sobre las instrucciones ya procesadas (
- Condición de Salida: El bucle termina si
made_change_in_base_pass
ymade_change_in_group_pass
son ambosFalse
.
- Fase 1: Procesadores Base:
- Escritura JSON: Guarda el JSON modificado (con SCL embebido y marcas de agrupación) en el archivo
_simplified_scl_processed.json
.
- Carga de JSON y Preparación: Lee el JSON de entrada y reconstruye mapas de acceso por red. Inicializa el
Etapa 3: Generación de SCL Final (generate_scl.py
)
- Propósito: Ensamblar un archivo
.scl
completo y formateado a partir del JSON procesado. - Entrada: Archivo
_simplified_scl_processed.json
(generado por la Etapa 2). - Salida: Archivo
.scl
. - Proceso Clave:
- Carga de JSON: Lee el archivo JSON procesado.
- Generación de Cabecera: Escribe el encabezado del bloque (
FUNCTION_BLOCK name
,VERSION
, etc.). - Generación de Declaraciones VAR:
- Escribe las secciones
VAR_INPUT
,VAR_OUTPUT
,VAR_IN_OUT
,VAR_TEMP
,VAR_STAT
. - Rellena cada sección con las variables definidas en la
interface
del JSON. - Detecta y declara variables temporales (
_temp_...
) y estáticas (stat_...
) encontradas en el SCL generado (requiere inferencia de tipo básica o usarVariant
).
- Escribe las secciones
- Generación del Cuerpo (
BEGIN
/END_FUNCTION_BLOCK
):- Itera sobre las
networks
en el JSON. - Añade comentarios de red.
- Itera sobre la
logic
de cada red. - Para cada
instruction
:- Verifica el flag
grouped
: Siinstruction.get('grouped', False)
esTrue
, ignora esta instrucción (su lógica ya está en otro lugar). - Si no está agrupada, obtiene el valor del campo
scl
. - Limpia comentarios internos: Opcionalmente, elimina comentarios como
// RLO:
o// Cond:
que fueron útiles para depurarprocess.py
pero no son necesarios en el SCL final (excepto quizás los de PBox/flancos). - Indenta y añade las líneas del SCL al output.
- Verifica el flag
- Añade líneas en blanco entre redes si contienen código.
- Itera sobre las
- Escritura de Archivo: Escribe el string SCL completo al archivo
.scl
.
3. Cómo Extender para Nuevas Instrucciones LAD/FBD
Añadir soporte para un nuevo tipo de instrucción (p.ej., un temporizador TON
, un comparador GT
, o una función matemática SQRT
) requiere modificar los scripts x1_to_json.py
y process.py
.
Pasos:
-
Analizar el XML: Exporta un bloque simple que use la nueva instrucción y examina el XML de Openness. Identifica:
- Si se representa como
<Part Name="NombreInstruccion">
o<Call Name="NombreInstruccion">
. - Sus pines de entrada y salida (
NameCon
/IdentCon
en losWire
). - Cualquier atributo o
TemplateValue
relevante. - Si tiene pines negados (
<Negated>
).
- Si se representa como
-
Modificar
x1_to_json.py
(parse_network
):- Parseo: Asegúrate de que
parse_part
oparse_call
(o una nueva función si es muy diferente) capture correctamente la nueva instrucción y sus atributos específicos. Añade sutype
alparts_and_calls_map
. - Clasificación: Decide si la nueva instrucción es un
functional_block_type
(tiene EN/ENO implícito/explícito) o unrlo_generator
(modifica el flujo lógico principal). Actualiza estas listas si es necesario. - Inferencia EN: Si es un bloque funcional, revisa si la lógica de inferencia EN necesita ajustes para manejar este nuevo tipo correctamente (aunque si tiene un
EN
explícito en el XML, la inferencia no debería ser necesaria). - Salidas: Asegúrate de que sus pines de salida relevantes (
out
,Q
, etc.) sean considerados por otros bloques y por la lógicaeno_outputs
si tieneENO
.
- Parseo: Asegúrate de que
-
Modificar
process.py
:- Crear Procesador: Escribe una nueva función
process_nombre_instruccion(instruction, network_id, scl_map, access_map, ...)
. - Lógica SCL: Implementa la traducción:
- Obtén las representaciones SCL de las entradas necesarias usando
get_scl_representation
. - Verifica que todas las dependencias estén resueltas.
- Genera el SCL core equivalente.
- Maneja la habilitación
EN
: Obténen_scl
. Sien_scl
no es"TRUE"
, envuelve el SCL core enIF en_scl THEN ... END_IF;
. - Almacena el SCL final en
instruction['scl']
. - Actualiza
instruction['type']
con el sufijo_scl
. - Actualiza
scl_map
: Añade entradas para los pines de salida (out
,Q
, etc.) con su representación SCL (puede ser un nombre de variable temporal o el resultado de una expresión). Añade la entrada paraeno
si el bloque lo tiene (generalmentescl_map[key_eno] = en_scl
). - Retorna
True
.
- Obtén las representaciones SCL de las entradas necesarias usando
- Añadir a la Lista: Inserta la nueva función
process_nombre_instruccion
en la listabase_processors
en el orden de prioridad correcto (generalmente, generadores de valores/condiciones antes que consumidores). - Agrupación: Considera si este nuevo bloque debería ser parte de la lógica de agrupación (¿es un bloque funcional que puede ser habilitado en paralelo?). Si es así, no necesita cambios especiales aquí si se usa el enfoque de
process_group_ifs
. Si se usa el enfoque integrado, el generador de condición necesitaría saber cómo obtener el SCL core de este nuevo tipo.
- Crear Procesador: Escribe una nueva función
-
Modificar
generate_scl.py
(Menos Frecuente):- Declaraciones: Si la nueva instrucción introduce la necesidad de tipos específicos de variables temporales o estáticas, ajusta la lógica de detección y declaración en
generate_scl.py
. - Limpieza de Comentarios: Si el nuevo procesador genera comentarios específicos que no quieres en el SCL final, ajusta el filtro en
generate_scl.py
.
- Declaraciones: Si la nueva instrucción introduce la necesidad de tipos específicos de variables temporales o estáticas, ajusta la lógica de detección y declaración en
-
Probar: Ejecuta el pipeline completo con un XML que contenga la nueva instrucción y verifica los JSON intermedios y el SCL final.
4. Futuras Mejoras y Consideraciones
- Manejo de Tipos de Datos: Implementar un seguimiento y conversión de tipos más robusto. Inferir tipos para variables temporales. Usar funciones de conversión SCL explícitas (
INT_TO_DINT
, etc.). - Lógica de Flancos Completa: Implementar correctamente la lógica de variables estáticas para
P_TRIG
/N_TRIG
(o generar instancias de FBFP
/FN
). - Soporte para FBs: Manejar correctamente las llamadas a FBs con sus DBs de instancia y parámetros complejos.
- Optimización SCL: Además de agrupar IFs, implementar otras optimizaciones (eliminación de código muerto, simplificación de expresiones).
- Estructuras LAD Complejas: Mejorar la inferencia de EN para manejar bifurcaciones y uniones más complejas, o idealmente, rediseñar
x1_to_json.py
para un análisis topológico completo. Manejar saltos (JMP
). - Manejo de Errores: Mejorar el reporte de errores y la resiliencia ante XMLs inesperados.
- Interfaz Gráfica/Configuración: Crear una interfaz más amigable o archivos de configuración para gestionar el proceso.
- Soporte para otros lenguajes (FBD/SCL): Extender el parser para manejar otros lenguajes de origen.
5. Conclusión
Este pipeline proporciona una base sólida para la conversión automática de LAD a SCL. Siguiendo los pasos de extensión, se puede añadir soporte para más instrucciones y mejorar la calidad y completitud de la conversión. La clave es el enfoque iterativo y la separación de responsabilidades entre el parseo/enriquecimiento del JSON y el procesamiento semántico/generación de SCL.