Simatic_XML_Parser_to_SCL/VersionNoFuncionante/readme.md

170 lines
21 KiB
Markdown

# 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:
1. **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`).
2. **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 bloques `IF`. El SCL generado se almacena *dentro* del propio JSON.
3. **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` o `FUNCTION_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:**
1. **Parseo XML:** Utiliza `lxml` para leer el archivo XML.
2. **Extracción de Metadatos:** Obtiene nombre del bloque, número, lenguaje original, comentario del bloque. Detecta si es `SW.Blocks.FC` o `SW.Blocks.FB`.
3. **Extracción de Interfaz:** Parsea las secciones `Input`, `Output`, `InOut`, `Temp`, `Constant`, `Return` para obtener la declaración de variables.
4. **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`). Utiliza `get_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 en `negated_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` y `parts_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), ...]`.
* **Construcción Lógica Inicial:** Crea una lista de diccionarios (`all_logic_steps`), uno por cada `Part` o `Call`, rellenando `inputs` y `outputs` **solo con las conexiones explícitas** encontradas en los `Wire`.
* **Inferencia de Conexión `EN` (¡Paso Crucial!):**
* Itera sobre los bloques funcionales (`Move`, `Add`, `Call`, `BLKMOV`, etc.) que *no* tienen una entrada `en` 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 como `Contact`/`O`/`Eq`/`P_TRIG`/`N_TRIG` o la salida `eno` de un bloque funcional anterior).
* Si se encuentra una fuente inferida, **añade la conexión `en`** al diccionario `inputs` del bloque funcional actual. Esto enriquece el JSON con la dependencia lógica implícita.
* **Ordenamiento:** Ordena las instrucciones en la lista `logic` final (generalmente por UID).
5. **Escritura JSON:** Guarda la estructura de datos completa en el archivo `_simplified.json`.
### 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:**
1. **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).
2. **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 su `type` (usando los tipos mapeados como `TON_S5`, `P_TRIG`, etc.).
* **`process_xxx` (Ejecución):**
* Llama a `get_scl_representation` para obtener el SCL de sus pines de entrada, buscándolos en `scl_map` o directamente en `access_map`. `get_scl_representation` ahora **convierte constantes S5T# a T#**.
* Si alguna dependencia no está resuelta (devuelve `None`), el procesador retorna `False`.
* 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 en `scl_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 en `instruction['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 correctamente `instance_db` para FBs. `process_blkmov` traduce a `DST := SRC;` (con advertencia sobre tipos).
* Generan el SCL final, **envolviendo el core en `IF en_scl THEN ... END_IF;`** si `en_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) en `scl_map` para `out`/`out1`/`RET_VAL`.
* Almacenan el `en_scl` en `scl_map` para `eno`.
* **Bobinas (`Coil`, `R`, `S`, `SR`):** Obtienen el RLO de entrada (`in`, o `S`/`R1` para `SR`), obtienen el operando destino, generan la asignación (`:= TRUE`/`:= FALSE`) o lógica `IF/ELSIF` envuelta en `IF RLO THEN ...` si aplica, y la guardan en `instruction['scl']`.
* **Temporizadores (`TON_S5`, `TONR_S5`):** Generan una llamada a un bloque IEC (`TON`/`TONR`) usando un nombre de instancia (`stat_timer_...`). Parsean el valor `PT` (T#...). Mapean `Q` y `ET` a la instancia (`Instance.Q`, `Instance.ET`) en `scl_map`. Almacenan la llamada en `instruction['scl']`.
* Marcan la instrucción como procesada añadiendo `_scl` a `instruction['type']`.
* Retornan `True`.
* 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` de `scl_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 salida `out` 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 bloque `IF` agrupado.
* Marca cada instrucción consumidora con `grouped = True` y cambia su `scl` a un comentario (`GROUPED_COMMENT`) para evitar que `x3_generate_scl.py` lo use.
* Se registra si hubo algún cambio en esta fase (`made_change_in_group_pass`).
* **Condición de Salida:** El bucle termina si `made_change_in_base_pass` y `made_change_in_group_pass` son ambos `False`.
3. **Verificación Final:** Comprueba si quedaron instrucciones sin procesar, sin agrupar y sin errores, e informa al usuario.
4. **Escritura JSON:** Guarda el JSON modificado (con SCL embebido y marcas de agrupación) en el archivo `_simplified_processed.json`.
### 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:**
1. **Carga de JSON:** Lee el archivo JSON procesado.
2. **Detección de Variables y Tipo de Bloque:**
* Escanea el SCL generado en busca de variables `#_temp_...` y `stat_...`.
* 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 variables `STAT` o `TEMP`) o `FUNCTION`.
3. **Generación de Cabecera:** Escribe el encabezado del bloque (`FUNCTION_BLOCK name` o `FUNCTION name`, `VERSION`, etc.). Utiliza `format_variable_name` (la versión corregida) para el nombre del bloque.
4. **Generación de Declaraciones VAR:**
* Escribe las secciones `VAR_INPUT`, `VAR_OUTPUT`, `VAR_IN_OUT` usando `format_variable_name` para los nombres de las variables de la interfaz.
* **Escribe `VAR_STAT`:** Declara las variables `stat_...` 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ón `Temp` de la interfaz. Utiliza inferencia de tipo básica para las variables `#_temp_...`.
5. **Generación del Cuerpo (`BEGIN`/`END_FUNCTION_BLOCK` o `END_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`:** Si `instruction.get('grouped', False)` es `True`, **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.
* Añade líneas en blanco entre redes si contienen código.
6. **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:**
1. **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 los `Wire`).
* Cualquier atributo o `TemplateValue` relevante.
* Si tiene pines negados (`<Negated>`).
2. **Modificar `x1_to_json.py` (`parse_part` o `parse_call` y `parse_network`):**
* **Parseo:** Asegúrate de que `parse_part` o `parse_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 en `parse_part` (p.ej., si fuera un temporizador IEC TP, podrías mapear `TPartName` -> `TP_IEC`).
* **Clasificación:** Decide si la nueva instrucción es un `functional_block_type` (necesita inferencia EN?) o un `rlo_generator`. Actualiza estas listas en `parse_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` en `parse_network`.
3. **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 para `eno` si el bloque lo tiene (generalmente `scl_map[key_eno] = en_scl`).
* Retorna `True`.
* **Añadir a la Lista:** Inserta la nueva función `process_nombre_instruccion` en la lista `base_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 el `processor_map` si es necesario (especialmente si manejas tipos como `Call_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` en `process_group_ifs`. El código de agrupación existente debería funcionar si el procesador del nuevo bloque genera correctamente la estructura `IF EN THEN ... END_IF;` (o código simple si EN=TRUE).
4. **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 en `x2_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 diccionario `stat_vars`.
* La lógica existente en `generate_scl` que escribe la sección `VAR_STAT` usará esta información para declarar la variable correctamente.
* **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.
5. **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 que `x1` parseó correctamente la instrucción, mapeó su tipo, identificó sus conexiones y realizó la inferencia EN si era necesario.
* **Verifica `_simplified_processed.json`:** Comprueba que `x2` ejecutó tu nuevo procesador, generó el SCL esperado en el campo `scl`, marcó el tipo con `_scl`, y actualizó el `scl_map` correctamente para sus salidas. Verifica si la agrupación `IF` funcionó como se esperaba si la instrucción era parte de lógica paralela.
* **Verifica el archivo `.scl`:** Confirma que `x3` 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 de `R_TRIG`/`F_TRIG` en `VAR_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 en `x2_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.