Simatic_XML_Parser_to_SCL/VersionNoFuncionante
Miguel b2d3f0f5bf Iniciando segunda etapa 2025-04-18 23:47:18 +02:00
..
readme.md Iniciando segunda etapa 2025-04-18 23:47:18 +02:00
x1_to_json.py Iniciando segunda etapa 2025-04-18 23:47:18 +02:00
x2_process.py Iniciando segunda etapa 2025-04-18 23:47:18 +02:00
x3_generate_scl.py Iniciando segunda etapa 2025-04-18 23:47:18 +02:00

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 (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.