Go to file
Miguel 31b9cd9701 Ningun Cambio 2025-04-21 00:24:58 +02:00
.vci
ToUpload Ningun Cambio 2025-04-21 00:24:58 +02:00
XSD Schema Definition
.gitignore Ningun Cambio 2025-04-21 00:24:58 +02:00
log.txt Ningun Cambio 2025-04-21 00:24:58 +02:00
paste.py Ningun Cambio 2025-04-21 00:24:58 +02:00
readme.md

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:

  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.

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.