Obsidean_VM/01-Documentation/Siemens/Scripts/Calculo de Offsets de DBs.md

16 KiB

Parser de Archivos de Definición de DB y UDT de Step 7 (Formato Texto)

1. Introducción

Este documento describe el proceso y la arquitectura de un parser diseñado para analizar archivos de texto que contienen definiciones de Bloques de Datos (DB) y Tipos de Datos de Usuario (UDT) exportados desde Siemens Step 7 (o TIA Portal en formato AWL/STL). El objetivo principal del parser es extraer la estructura de estos bloques, incluyendo los nombres de las variables, tipos de datos, dimensiones de arrays, comentarios, y, crucialmente, calcular el offset (desplazamiento) absoluto en bytes y bits para cada variable dentro de su DB o UDT padre.

El resultado final del parsing es una representación estructurada (por ejemplo, en formato JSON) que puede ser utilizada para diversas aplicaciones, como la generación automática de documentación, la creación de interfaces HMI/SCADA, herramientas de diagnóstico, o la integración con otros sistemas.

2. Conceptos Clave de Step 7 Relevantes para el Parsing

Antes de detallar el proceso de parsing, es fundamental comprender algunos conceptos de Step 7:

2.1. Tipos de Datos Primitivos y Tamaños

El parser debe conocer el tamaño en bytes (y bits para BOOL) de los tipos de datos fundamentales de S7: * BOOL: 1 bit. * BYTE: 1 byte (8 bits). * CHAR: 1 byte (representación ASCII). * WORD: 2 bytes. * INT: 2 bytes (entero con signo). * UINT: 2 bytes (entero sin signo, TIA Portal). * DWORD: 4 bytes. * DINT: 4 bytes (entero con signo). * UDINT: 4 bytes (entero sin signo, TIA Portal). * REAL: 4 bytes (punto flotante, IEEE 754). * S5TIME: 2 bytes (formato de temporizador S5). * TIME: 4 bytes (formato de tiempo IEC). * TIME_OF_DAY (TOD): 4 bytes. * DATE: 2 bytes. * DATE_AND_TIME (DT): 8 bytes. * STRING[N]: N+2 bytes (1 byte max_len, 1 byte actual_len, N bytes para caracteres). * LREAL, LINT, ULINT, LWORD, LTIME, LTIME_OF_DAY (TIA Portal): Generalmente 8 bytes.

2.2. Tipos de Datos Compuestos

*   **STRUCT**: Una colección de variables (miembros) de diferentes tipos, agrupadas bajo un nombre. Los miembros se almacenan secuencialmente.
*   **UDT (User Defined Type)**: Un tipo de dato definido por el usuario, que internamente es un STRUCT. Permite reutilizar estructuras complejas.
*   **ARRAY [L..U] OF Type**: Una colección de elementos del mismo tipo (`Type`), indexados desde `L` (límite inferior) hasta `U` (límite superior).

2.3. Reglas de Alineación y Offsets

Siemens S7 tiene reglas estrictas para la alineación de datos en memoria, lo cual afecta el cálculo de offsets: * BOOL: Se empaquetan en bytes. Ocho BOOLs pueden ocupar un solo byte. * BYTE, CHAR: Pueden comenzar en cualquier offset de byte. * WORD (y tipos de 2 bytes): Deben comenzar en un offset de byte par (0, 2, 4,...). Si el offset actual es impar, se inserta un byte de relleno (padding) antes del WORD. * DWORD (y tipos de 4 bytes): También deben comenzar en un offset de byte par. * LWORD (y tipos de 8 bytes): También deben comenzar en un offset de byte par. * STRUCT / UDT: Como un todo, un STRUCT o UDT comienza en un offset de byte par. Su tamaño total también se redondea (padding) para que sea un número par de bytes. Los miembros internos siguen sus propias reglas de alineación relativas al inicio del STRUCT/UDT. * STRING: El tipo STRING (con sus 2 bytes de cabecera) se considera alineado a palabra (comienza en offset par). * ARRAY: Un array de tipos que requieren alineación (ej. ARRAY OF INT) comenzará en un offset alineado (par). Los elementos dentro del array se colocan consecutivamente según el tamaño del tipo base. El tamaño total de un array de tipos alineados a palabra también resultará en un tamaño par o se le añadirá padding al final si el siguiente elemento lo requiere.

2.4. Formato del Archivo de Texto

Los archivos .db o .udt exportados como "fuente" (source) tienen una sintaxis similar a AWL/STL: * Bloques TYPE "Nombre_UDT" ... END_TYPE para UDTs. * Bloques DATA_BLOCK "Nombre_DB" ... END_DATA_BLOCK para DBs. * Declaraciones de variables: NombreVar : TIPO_DATO [:= ValorInicial] [// Comentario] * Estructuras: STRUCT ... END_STRUCT. * Arrays: ARRAY [inferior..superior] OF TIPO_DATO. * Sección BEGIN ... END_DATA_BLOCK (para DBs) contiene los valores iniciales efectivos.

3. Arquitectura del Parser

El parser se estructura en varios componentes lógicos:

3.1. Lector de Archivo

*   Lee el archivo de entrada línea por línea.

3.2. Identificador de Bloques

*   Detecta el inicio y fin de bloques UDT (`TYPE ... END_TYPE`) y DB (`DATA_BLOCK ... END_DATA_BLOCK`).

3.3. Analizador de Declaraciones

*   Utiliza expresiones regulares (regex) para identificar y extraer información de:
    *   Propiedades del bloque (FAMILY, VERSION, TITLE).
    *   Declaraciones de variables (nombre, tipo, valor inicial, comentario).
    *   Inicio y fin de `STRUCT`.
    *   Declaraciones de `ARRAY`.
*   Maneja la recursividad de `STRUCT` anidados.

3.4. Calculadora de Offsets y Tamaños

*   Mantiene el `byte_offset` y `bit_offset` actual durante el parsing de un `STRUCT` (o el cuerpo principal de un DB/UDT).
*   Aplica las reglas de alineación de S7.
*   Consulta una tabla interna de tamaños de tipos de datos primitivos.
*   Para UDTs usados como tipo de dato, consulta la información de UDTs previamente parseados.
*   Calcula el tamaño total de `STRUCT`s, `UDT`s y `ARRAY`s.

3.5. Gestor de UDTs

*   Almacena las definiciones de UDTs parseadas en un diccionario o estructura similar.
*   Cuando una variable es de un tipo UDT, el parser consulta este gestor para obtener el tamaño y la estructura del UDT.

3.6. Analizador de la Sección BEGIN (para DBs)

*   Parsea las asignaciones de valores en la sección `BEGIN` para actualizar los valores iniciales de las variables del DB.

3.7. Estructuras de Datos Internas

*   Se utilizan clases o diccionarios para representar:
    *   `VariableInfo`: Nombre, tipo, offset, tamaño, sub-miembros (si es STRUCT/UDT), dimensiones de array, etc.
    *   `UdtInfo`: Nombre, lista de `VariableInfo` (miembros), tamaño total.
    *   `DbInfo`: Nombre, lista de `VariableInfo` (miembros), tamaño total, valores de la sección `BEGIN`.
    *   `ParsedData`: Contenedor raíz para todos los UDTs y DBs parseados.

3.8. Serializador de Salida

*   Convierte las estructuras de datos internas a formato JSON.

4. Proceso Detallado del Parsing

El proceso general sigue estos pasos:

  1. Inicialización:

    • Crear un objeto ParsedData para almacenar los resultados.
    • Crear un diccionario known_udts para almacenar las definiciones de UDTs a medida que se parsean.
    • Inicializar current_byte_offset = 0, current_bit_offset = 0.
  2. Lectura Línea por Línea:

    • Para cada línea del archivo:
      • Detección de Bloque UDT: Si la línea marca el inicio de un UDT (TYPE "Nombre"):
        • Crear un nuevo objeto UdtInfo.
        • Parsear las propiedades del UDT (FAMILY, VERSION).
        • Resetear current_byte_offset = 0, current_bit_offset = 0 para este UDT.
        • Llamar a una función recursiva/iterativa parse_struct_members para los miembros del UDT.
        • Al encontrar END_TYPE, finalizar el UDT, aplicar padding final si es necesario para que el tamaño total sea par, y almacenar el UDT en known_udts y ParsedData.udts.
      • Detección de Bloque DB: Si la línea marca el inicio de un DB (DATA_BLOCK "Nombre"):
        • Crear un nuevo objeto DbInfo.
        • Parsear las propiedades del DB (TITLE, FAMILY, VERSION).
        • Resetear current_byte_offset = 0, current_bit_offset = 0 para este DB.
        • Llamar a parse_struct_members para los miembros del DB.
        • Si se encuentra BEGIN:
          • Finalizar el cálculo de offsets para la sección de declaración (aplicar padding si el offset es impar).
          • Llamar a una función parse_begin_section para extraer los valores iniciales.
        • Al encontrar END_DATA_BLOCK, finalizar el DB, aplicar padding final al tamaño total de las declaraciones si es necesario, y almacenar el DB en ParsedData.dbs.
      • Dentro de parse_struct_members(parent_members_list, current_offsets):
        • Si la línea es STRUCT:
          • Alinear current_offsets a palabra (byte par).
          • Crear un VariableInfo para el STRUCT anónimo o nombrado.
          • Grabar el offset de inicio del STRUCT.
          • Hacer una llamada recursiva a parse_struct_members para los hijos del STRUCT, pasándole nuevos offsets relativos (0.0).
          • Al retornar de la llamada recursiva (al encontrar END_STRUCT):
            • El tamaño del STRUCT es el offset final retornado por la llamada recursiva (después de su propio padding final a par).
            • Actualizar current_offsets del nivel padre sumando el tamaño del STRUCT.
            • Añadir el VariableInfo del STRUCT a parent_members_list.
        • Si la línea es END_STRUCT: Retornar (indicando el fin de este nivel de STRUCT).
        • Si la línea es una declaración de variable (detectada por regex):
          • Extraer nombre_var, tipo_completo (ej. ARRAY [0..9] OF INT, STRING[32], "MiUDT"), valor_inicial, comentario.
          • Analizar tipo_completo para determinar:
            • Si es ARRAY: extraer dimensiones y tipo_base_array.
            • Si es STRING: extraer longitud_string.
            • Si es un UDT (entre comillas): nombre_udt_usado.
            • Sino: tipo_primitivo.
          • Obtener tamaño_unitario y alineacion_requerida_bytes para el tipo_base_final (primitivo, UDT, STRING):
            • Para UDT: tamaño_unitario = known_udts[nombre_udt_usado].total_size_in_bytes. alineacion_requerida_bytes = 2.
            • Para STRING[N]: tamaño_unitario = N + 2. alineacion_requerida_bytes = 2.
            • Para primitivos: obtener de una tabla interna.
          • Crear un VariableInfo.
          • Cálculo de Offset para la variable actual:
            • Si el tipo_base_final es BOOL:
              • VariableInfo.offset = current_offsets.byte + (current_offsets.bit / 10.0) (representación decimal).
              • current_offsets.bit += 1.
              • Si current_offsets.bit >= 8:
                • current_offsets.byte += 1.
                • current_offsets.bit = 0.
              • El tamaño para BOOL es 1 bit. Si es un array de BOOLs, este proceso se repite para cada elemento.
            • Si el tipo_base_final no es BOOL:
              • Si current_offsets.bit > 0 (se estaba empaquetando BOOLs):
                • current_offsets.byte += 1 (terminar el byte actual).
                • current_offsets.bit = 0.
              • Si alineacion_requerida_bytes == 2 y current_offsets.byte es impar:
                • current_offsets.byte += 1 (insertar byte de padding).
              • VariableInfo.offset = current_offsets.byte.
              • tamaño_total_var = tamaño_unitario.
              • Si es ARRAY: tamaño_total_var *= numero_total_elementos_array.
              • VariableInfo.size_in_bytes = tamaño_total_var.
              • current_offsets.byte += tamaño_total_var.
              • Si es un array de elementos alineados a palabra y tamaño_total_var es impar (esto es raro, usualmente el tamaño_unitario ya es par), S7 no suele añadir padding dentro del array, sino que el padding general del STRUCT/UDT se encarga. La alineación es para el inicio del array y de cada elemento si el tipo base lo requiere.
          • Añadir el VariableInfo a parent_members_list.
  3. Post-Procesamiento:

    • Una vez que todos los bloques han sido parseados, serializar el objeto ParsedData a JSON.

5. Expresiones Regulares Clave (Ejemplos Conceptuales)

  • Inicio UDT: ^\s*TYPE\s+"([^"]+)"
  • Inicio DB: ^\s*DATA_BLOCK\s+"([^"]+)"
  • Propiedad: ^\s*([A-Z_]+)\s*:\s*(.+?)\s*(?://.*)?$
  • Inicio STRUCT: ^\s*STRUCT
  • Fin STRUCT/TYPE/DB: ^\s*END_(STRUCT|TYPE|DATA_BLOCK)
  • Declaración de Variable (simplificada): ^\s*(?<name>[a-zA-Z_]\w*)\s*:\s*(?<typefull>(?:ARRAY\s*\[(?<arraydims>[^\]]+)\]\s*OF\s*)?(?<basetype>(?:"[^"]+"|\w+))(?:\s*\[\s*(?<strlen>\d+)\s*\])?)\s*(?::=\s*(?<initval>.+?))?\s*(?://\s*(?<comment>.*))?;?\s*$
    • Esta regex es compleja y necesita ser probada y refinada cuidadosamente. Captura:
      • name: Nombre de la variable.
      • typefull: La declaración de tipo completa.
      • arraydims: Dimensiones del array (ej. 1..5,0..2).
      • basetype: El tipo base (ej. INT, "MiUDT", STRING).
      • strlen: Longitud para STRING[N].
      • initval: Valor inicial.
      • comment: Comentario.

6. Estructura de Salida JSON (Ejemplo Conceptual)

{
  "udts": [
    {
      "name": "Recipe_Prod",
      "family": "DataType",
      "version": "0.1",
      "total_size_in_bytes": 218, // Ejemplo, calculado
      "members": [
        {
          "name": "_Name",
          "data_type": "STRING",
          "string_length": 32,
          "initial_value": "'                              '",
          "byte_offset": 0.0,
          "size_in_bytes": 34
        },
        {
          "name": "_EnProdTemp",
          "data_type": "BOOL",
          "byte_offset": 34.0, // Byte 34, bit 0
          "size_in_bytes": 0 // Representa 1 bit
        },
        // ... más miembros
        {
          "name": "_Type",
          "data_type": "INT",
          "initial_value": "1",
          "byte_offset": 40.0, // Ejemplo, después de padding y otros BOOLs/BYTEs
          "size_in_bytes": 2
        }
      ]
    }
  ],
  "dbs": [
    {
      "name": "HMI_Blender_Parameters",
      "title": "{ S7_language := '28(1) Albanese  15.06.2005  17:07:04' }",
      "family": "Resource",
      "version": "0.0",
      "total_size_in_bytes": 1024, // Ejemplo, calculado
      "members": [
        {
          "name": "Processor_Options",
          "data_type": "STRUCT",
          "byte_offset": 0.0,
          "size_in_bytes": 100, // Ejemplo
          "children": [
            {
              "name": "Blender_OPT",
              "data_type": "STRUCT",
              "byte_offset": 0.0, // Relativo a Processor_Options
              "size_in_bytes": 80, // Ejemplo
              "children": [
                {
                  "name": "_ModelNum",
                  "data_type": "INT",
                  "initial_value": "6",
                  "byte_offset": 0.0, // Relativo a Blender_OPT
                  "size_in_bytes": 2
                }
                // ...
              ]
            }
          ]
        },
        {
          "name": "Actual_Recipe_Parameters",
          "data_type": "UDT", // El parser identifica esto como UDT
          "udt_source_name": "Recipe_Prod", // Nombre del UDT original
          "byte_offset": 120.0, // Ejemplo
          "size_in_bytes": 218, // Tamaño del UDT "Recipe_Prod"
          "children": [] // Opcionalmente, se pueden expandir los miembros del UDT aquí
        }
        // ... más miembros
      ],
      "initial_values_from_begin_block": {
          "Processor_Options.Blender_OPT._ModelNum": "6",
          "Actual_Recipe_Parameters._Name": "''"
          // ...
      }
    }
  ]
}