|
||
---|---|---|
.. | ||
readme.md | ||
x1_to_json.py | ||
x2_process.py | ||
x3_generate_scl.py |
readme.md
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 (incluyendo llamadas a FC/FB y temporizadores S5), conexiones explícitas e infiere conexiones implícitas (especialmente las habilitaciones EN) para crear un archivo JSON detallado. Mapea tipos de instrucción LAD/FBD (p.ej.,Se
,NBox
) a nombres internos consistentes (p.ej.,TON_S5
,N_TRIG
). - Procesamiento Semántico (
x2_process.py
): Lee el JSON enriquecido y, de forma iterativa, traduce cada instrucción (usando los tipos mapeados) a su equivalente SCL, manejando dependencias, propagando el estado lógico (RLO), traduciendo temporizadores S5 a IEC, generando lógica de flancos y agrupando lógica paralela bajo bloquesIF
. El SCL generado se almacena dentro del propio JSON. - Generación de SCL Final (
x3_generate_scl.py
): Lee el JSON completamente procesado y ensambla el código SCL final en un archivo.scl
formateado, incluyendo declaraciones de variables (Input, Output, InOut, Temp, Stat - incluyendo instancias de temporizadores y bits de memoria de flancos) y el cuerpo del programa (FUNCTION
oFUNCTION_BLOCK
).
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. Detecta si es
SW.Blocks.FC
oSW.Blocks.FB
. - 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
). Utilizaget_symbol_name
para formatear nombres simbólicos con comillas ("DB"."Var"
).Part
: Identifica instrucciones estándar LAD/FBD (parse_part
). Importante: Detecta pines negados (<Negated>
) y los almacena ennegated_pins
. Mapea nombres XML (Se
,Sd
,PBox
,NBox
,RCoil
,SCoil
,SdCoil
) a tipos internos (TON_S5
,TONR_S5
,P_TRIG
,N_TRIG
,R
,S
,SR
).Call
: Identifica llamadas a otros FCs o FBs (parse_call
), extrayendo el nombre del bloque llamado, tipo (FC/FB) e información de instancia DB (instance_db
) si aplica (formateado con comillas).- Crea
access_map
yparts_and_calls_map
para referencia rápida por UID.
- 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
- 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
,BLKMOV
, etc.) que no tienen una entradaen
explícita definida después del paso anterior. - Utiliza una heurística de búsqueda lineal hacia atrás 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
/P_TRIG
/N_TRIG
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 (
- 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 (x2_process.py
)
- Propósito: Traducir la lógica LAD/FBD (representada en el JSON enriquecido) a código SCL embebido, resolviendo dependencias entre instrucciones, generando lógica SCL equivalente (incluyendo manejo de flancos y temporizadores), y aplicando optimizaciones de agrupación
IF
. - Entrada: Archivo
_simplified.json
(generado por la Etapa 1). - Salida: Archivo
_simplified_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 (
max_passes
).- 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
(usando los tipos mapeados comoTON_S5
,P_TRIG
, etc.). 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
.get_scl_representation
ahora convierte constantes S5T# a T#. - Si alguna dependencia no está resuelta (devuelve
None
), el procesador retornaFalse
. - Si las dependencias están resueltas:
- Generadores RLO (
Contact
,O
,Eq
,P_TRIG
,N_TRIG
): Calculan la expresión booleana SCL resultante y la almacenan enscl_map
bajo la clave(network_id, instr_uid, 'out')
.P_TRIG
/N_TRIG
generan lógica explícita de flancos y la llamada para actualizar el bit de memoria (stat_... := CLK;
). Guardan comentarios/lógica eninstruction['scl']
. - Bloques Funcionales (
Move
,Add
,Convert
,Mod
,BLKMOV
,Call
):- Obtienen la condición
EN
(explícita o inferida). - Generan el código SCL core (la asignación, cálculo o llamada).
process_call
ahora usa correctamenteinstance_db
para FBs.process_blkmov
traduce aDST := SRC;
(con advertencia sobre tipos). - Generan el SCL final, envolviendo el core en
IF en_scl THEN ... END_IF;
sien_scl
no es"TRUE"
. - Almacenan el SCL final en
instruction['scl']
. - Almacenan el nombre de la variable de salida/temporal (prefijado con
#
si es temporal) enscl_map
paraout
/out1
/RET_VAL
. - Almacenan el
en_scl
enscl_map
paraeno
.
- Obtienen la condición
- Bobinas (
Coil
,R
,S
,SR
): Obtienen el RLO de entrada (in
, oS
/R1
paraSR
), obtienen el operando destino, generan la asignación (:= TRUE
/:= FALSE
) o lógicaIF/ELSIF
envuelta enIF RLO THEN ...
si aplica, y la guardan eninstruction['scl']
. - Temporizadores (
TON_S5
,TONR_S5
): Generan una llamada a un bloque IEC (TON
/TONR
) usando un nombre de instancia (stat_timer_...
). Parsean el valorPT
(T#...). MapeanQ
yET
a la instancia (Instance.Q
,Instance.ET
) enscl_map
. Almacenan la llamada 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
,P_TRIG
,N_TRIG
, etc.). - Obtiene la
condition_scl
descl_map
. - Busca todos los bloques funcionales, bobinas o temporizadores (
Move_scl
,Add_scl
,Coil_scl
,TON_S5_scl
, etc.) cuyo pin de habilitación (en
,in
,s
) esté conectado a la salidaout
de esta instrucción generadora. - Si encuentra más de uno:
- Extrae el código core (lo que está dentro del
IF...END_IF;
si existe, o el código completo si no) de cada consumidor. - Construye un único bloque
IF condition_scl THEN ... END_IF;
que contiene todos los cores extraídos, indentados. - 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 quex3_generate_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:
- Verificación Final: Comprueba si quedaron instrucciones sin procesar, sin agrupar y sin errores, e informa al usuario.
- Escritura JSON: Guarda el JSON modificado (con SCL embebido y marcas de agrupación) en el archivo
_simplified_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 (x3_generate_scl.py
)
- Propósito: Ensamblar un archivo
.scl
completo y formateado a partir del JSON procesado. - Entrada: Archivo
_simplified_processed.json
(generado por la Etapa 2). - Salida: Archivo
.scl
. - Proceso Clave:
- Carga de JSON: Lee el archivo JSON procesado.
- Detección de Variables y Tipo de Bloque:
- Escanea el SCL generado en busca de variables
#_temp_...
ystat_...
. - Identifica los tipos de las variables
stat_...
(Bool, TON, TONR) según sus nombres (stat_nbox_mem...
,stat_timer_Se...
, etc.). - Determina si el bloque debe ser
FUNCTION_BLOCK
(si hay variablesSTAT
oTEMP
) oFUNCTION
.
- Escanea el SCL generado en busca de variables
- Generación de Cabecera: Escribe el encabezado del bloque (
FUNCTION_BLOCK name
oFUNCTION name
,VERSION
, etc.). Utilizaformat_variable_name
(la versión corregida) para el nombre del bloque. - Generación de Declaraciones VAR:
- Escribe las secciones
VAR_INPUT
,VAR_OUTPUT
,VAR_IN_OUT
usandoformat_variable_name
para los nombres de las variables de la interfaz. - Escribe
VAR_STAT
: Declara las variablesstat_...
detectadas con sus tipos inferidos (Bool, TON, TONR) y nombres entre comillas. - Escribe
VAR_TEMP
: Declara las variables#_temp_...
detectadas (declaradas como"_temp_..."
sin el#
pero con comillas) y las variables de la secciónTemp
de la interfaz. Utiliza inferencia de tipo básica para las variables#_temp_...
.
- Escribe las secciones
- Generación del Cuerpo (
BEGIN
/END_FUNCTION_BLOCK
oEND_FUNCTION
):- 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. - Si no está agrupada, obtiene el valor del campo
scl
. - Limpia comentarios internos: Elimina comentarios informativos específicos (
// RLO:
,// Comparison:
,// Logic O:
,// N/P_TRIG Output Logic:
) si son la única parte de la línea, pero conserva los comentarios de actualización de memoria de flancos, errores y agrupación. - Indenta y añade las líneas del SCL resultante 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 comparador GT
, una función matemática SQRT
, o un temporizador IEC TP
) requiere modificar los scripts x1_to_json.py
y x2_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_part
oparse_call
yparse_network
):- Parseo: Asegúrate de que
parse_part
oparse_call
capture correctamente la nueva instrucción. - Mapeo de Tipo: Si el nombre XML no es ideal (como
Se
), mapéalo a un nombre interno consistente enparse_part
(p.ej., si fuera un temporizador IEC TP, podrías mapearTPartName
->TP_IEC
). - Clasificación: Decide si la nueva instrucción es un
functional_block_type
(necesita inferencia EN?) o unrlo_generator
. Actualiza estas listas enparse_network
si es necesario. - Inferencia EN: Revisa si la lógica de inferencia EN en
parse_network
necesita ajustes. - Pines: Asegúrate de que sus pines de entrada/salida esperados estén en
possible_input_pins
/possible_output_pins
enparse_network
.
- Parseo: Asegúrate de que
-
Modificar
x2_process.py
(continuación):- Lógica SCL (continuación):
- ...
- 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 prefijado con#
, el resultado de una expresión, o el acceso a un miembro de instancia como"Instance".Q
). Añade la entrada paraeno
si el bloque lo tiene (generalmentescl_map[key_eno] = en_scl
). - Retorna
True
.
- Añadir a la Lista: Inserta la nueva función
process_nombre_instruccion
en la listabase_processors_list
en el orden de prioridad correcto (generalmente, generadores de valores/condiciones antes que consumidores, y las llamadas (process_call
) al final o cerca del final). Actualiza elprocessor_map
si es necesario (especialmente si manejas tipos comoCall_FC
,Call_FB
). - Agrupación: Considera si este nuevo bloque debería ser parte de la lógica de agrupación (¿es un bloque funcional, bobina o temporizador que puede ser habilitado en paralelo?). Si es así, añádelo a la lista
groupable_types_original
enprocess_group_ifs
. El código de agrupación existente debería funcionar si el procesador del nuevo bloque genera correctamente la estructuraIF EN THEN ... END_IF;
(o código simple si EN=TRUE).
- Lógica SCL (continuación):
-
Modificar
x3_generate_scl.py
:- Declaraciones STAT: Si la nueva instrucción introduce la necesidad de un nuevo tipo de variable estática (p.ej., un tipo de datos específico para un contador, o un nuevo tipo de instancia de FB IEC), necesitarás:
- Añadir una nueva expresión regular (
stat_pattern_xxx
) para detectar el nombre de la instancia/variable estática generada por tu nuevo procesador enx2_process.py
. - Actualizar el bucle de detección para que use esta nueva regex y almacene el nombre y el tipo SCL correcto (
MyCounterType
,IEC_Counter
, etc.) en el diccionariostat_vars
. - La lógica existente en
generate_scl
que escribe la secciónVAR_STAT
usará esta información para declarar la variable correctamente.
- Añadir una nueva expresión regular (
- Declaraciones TEMP: Si la nueva instrucción genera variables temporales con un patrón específico o requiere un tipo de dato específico que pueda ser inferido, puedes mejorar la lógica de inferencia de tipo en la sección
VAR_TEMP
. - Limpieza de Comentarios: Si el nuevo procesador genera comentarios internos específicos que no quieres en el SCL final, ajusta la lógica de filtrado en la sección de generación del cuerpo del bloque.
- Declaraciones STAT: Si la nueva instrucción introduce la necesidad de un nuevo tipo de variable estática (p.ej., un tipo de datos específico para un contador, o un nuevo tipo de instancia de FB IEC), necesitarás:
-
Probar:
- Crea un archivo XML de prueba simple que use la nueva instrucción en diferentes contextos (con entradas/salidas conectadas, con EN explícito/implícito, negaciones si aplica).
- Ejecuta el pipeline completo (
x1_to_json.py
,x2_process.py
,x3_generate_scl.py
). - Verifica
_simplified.json
: Asegúrate de quex1
parseó correctamente la instrucción, mapeó su tipo, identificó sus conexiones y realizó la inferencia EN si era necesario. - Verifica
_simplified_processed.json
: Comprueba quex2
ejecutó tu nuevo procesador, generó el SCL esperado en el camposcl
, marcó el tipo con_scl
, y actualizó elscl_map
correctamente para sus salidas. Verifica si la agrupaciónIF
funcionó como se esperaba si la instrucción era parte de lógica paralela. - Verifica el archivo
.scl
: Confirma quex3
generó el SCL final correctamente, incluyendo las declaraciones necesarias (STAT/TEMP) y el código SCL de la instrucción (indentado y sin comentarios no deseados). Asegúrate de que no haya errores de sintaxis SCL obvios. - Importar en TIA Portal (Opcional pero Recomendado): Intenta importar el archivo SCL generado en un proyecto de TIA Portal para validar la sintaxis y la estructura del bloque.
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 de forma más precisa. Usar funciones de conversión SCL explícitas (
INT_TO_DINT
, etc.) donde sea necesario, posiblemente requiriendo información de tipo adicional en el JSON. - Lógica de Flancos/Temporizadores: Asegurar que la traducción de temporizadores S5 y la lógica de flancos generada sea completamente compatible con los bloques IEC estándar (
TON
,TOF
,TP
,TONR
,R_TRIG
,F_TRIG
). Considerar la necesidad de declarar instancias deR_TRIG
/F_TRIG
enVAR_STAT
en lugar de generar lógica explícita. - Soporte para FBs: Mejorar el manejo de parámetros InOut y Return para llamadas a FC/FB. Potencialmente, requerir información de la interfaz del bloque llamado (parseando su propio XML o desde una biblioteca) para generar llamadas SCL más precisas.
- Optimización SCL: Explorar más optimizaciones SCL más allá de la agrupación de IFs (p.ej., simplificación de expresiones booleanas complejas, propagación de constantes).
- Estructuras LAD Complejas: La inferencia de EN actual (búsqueda lineal hacia atrás) es simple. Para manejar bifurcaciones (
Branch
), uniones (Merge
) y saltos (JMP
) complejos de forma robusta,x1_to_json.py
necesitaría realizar un análisis de flujo de datos/control más sofisticado para determinar las dependencias lógicas correctas antes de generar el JSON. - Manejo de Errores: Mejorar el reporte de errores con más contexto (nombre de red, UID, tipo de instrucción). Añadir más validaciones en cada etapa.
- Interfaz Gráfica/Configuración: Crear una interfaz más amigable o archivos de configuración para gestionar el proceso, seleccionar archivos y configurar opciones (p.ej., idioma por defecto, nivel de log).
- Soporte para otros lenguajes (FBD/STL): Extender el parser
x1_to_json.py
y los procesadores enx2_process.py
para manejar otros lenguajes de origen, lo cual requeriría entender sus respectivas representaciones XML en Openness.
5. Conclusión
Este pipeline proporciona una base funcional para la conversión automática de LAD a SCL desde archivos TIA Portal Openness XML. La clave de su funcionamiento es la separación de responsabilidades: x1
enriquece el modelo de datos con información implícita, x2
realiza la traducción semántica iterativa y la optimización de agrupación, y x3
ensambla el archivo SCL final. Al añadir procesadores específicos para cada tipo de instrucción LAD/FBD y refinar la lógica de inferencia y generación, la cobertura y calidad de la conversión pueden ser extendidas significativamente. La estructura modular facilita la incorporación de soporte para nuevas instrucciones y futuras mejoras.