# 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 y las conexiones explícitas, e **infiere conexiones implícitas** (especialmente las habilitaciones EN) para crear un archivo JSON detallado.
2.  **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.
3.  **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.

### Directory structure

<working_directory>/
├── <PLC1_Name>/
│   ├── ProgramBlocks_XML/
│   │   └── ... (archivos XML de bloques)
│   ├── ProgramBlocks_SCL/
│   │   └── ... (archivos SCL de bloques)
│   ├── ProgramBlocks_CR/
│   │   └── ... (archivos XML de referencias cruzadas de bloques)
│   ├── PlcTags/
│   │   └── ... (archivos XML de tablas de tags)
│   ├── PlcTags_CR/
│   │   └── ... (archivos XML de referencias cruzadas de tablas de tags)
│   ├── PlcDataTypes_CR/
│   │   └── ... (archivos XML de referencias cruzadas de UDTs)
│   ├── SystemBlocks_CR/
│   │   └── ...
│   └── SoftwareUnits_CR/
│       └── ...
│   └── Documentation/
│       └── Source
│          └── ... (archivos md de bloques de programa)
│       └── JSON
│          └── ... (archivos JSON temporales)
│       └── xref_calls_tree.md
│       └── xref_db_usage_summary.md
│       └── xref_plc_tags_summary.md
│       └── full_project_representation.md
│       └── SAE196_c0.2_CAx_Export_Hardware_Tree.md

├── <PLC2_Name>/
│   ├── ProgramBlocks_XML/
│   │   └── ...
│   └── ...
└── ...


## 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.
    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`).
                *   `Part`: Identifica instrucciones estándar LAD como `Contact`, `Coil`, `Move`, `Add`, etc. **Importante:** Detecta pines negados (`<Negated>`) y los almacena en `negated_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` y `parts_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 mapa `src_uid -> [(dest_uid, dest_pin), ...]` para conexiones que *salen* de un pin `eno`.
            *   **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`, etc.) que *no* tienen una entrada `en` 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 como `Contact`/`O`/`Eq` 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.
            *   **Adición de Lógica ENO Interesante:** Identifica las conexiones que salen de un pin `eno` y *no* van directamente al pin `en` de la siguiente instrucción, almacenándolas en el campo `eno_logic`.
            *   **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 (`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:**
    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.
        *   **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`.
            *   **`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`.
                *   Si alguna dependencia no está resuelta (devuelve `None`), el procesador retorna `False`.
                *   Si las dependencias están resueltas:
                    *   **Generadores RLO (`Contact`, `O`, `Eq`, `PBox`):** Calculan la expresión booleana SCL resultante y la almacenan en `scl_map` bajo la clave `(network_id, instr_uid, 'out')`. También almacenan el estado `EN` en `scl_map` para `eno` si aplica. Guardan un comentario en `instruction['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;`** si `en_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` para `out`/`out1`.
                        *   Almacenan el `en_scl` en `scl_map` para `eno`.
                    *   **Bobinas (`Coil`):** Obtienen el RLO de entrada (`in`), obtienen el operando destino, generan la asignación `destino := rlo;` y la guardan 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`, etc.).
            *   Obtiene la `condition_scl` de `scl_map`.
            *   Busca todos los bloques funcionales (`Move_scl`, `Add_scl`, etc.) cuyo pin `EN` 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 bloque `IF` agrupado.
                *   Marca cada instrucción consumidora con `grouped = True` y cambia su `scl` a un comentario (`GROUPED_COMMENT`) para evitar que `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.  **Escritura JSON:** Guarda el JSON modificado (con SCL embebido y marcas de agrupación) en el archivo `_simplified_scl_processed.json`.

### 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:**
    1.  **Carga de JSON:** Lee el archivo JSON procesado.
    2.  **Generación de Cabecera:** Escribe el encabezado del bloque (`FUNCTION_BLOCK name`, `VERSION`, etc.).
    3.  **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 usar `Variant`).
    4.  **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`:** Si `instruction.get('grouped', False)` es `True`, **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 depurar `process.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.
        *   Añade líneas en blanco entre redes si contienen código.
    5.  **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:**

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_network`):**
    *   **Parseo:** Asegúrate de que `parse_part` o `parse_call` (o una nueva función si es muy diferente) capture correctamente la nueva instrucción y sus atributos específicos. Añade su `type` al `parts_and_calls_map`.
    *   **Clasificación:** Decide si la nueva instrucción es un `functional_block_type` (tiene EN/ENO implícito/explícito) o un `rlo_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ógica `eno_outputs` si tiene `ENO`.

3.  **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én `en_scl`. Si `en_scl` no es `"TRUE"`, envuelve el SCL core en `IF 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 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` 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.

4.  **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`.

5.  **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 FB `FP`/`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.