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:
-
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
.
- Crear un objeto
-
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 enknown_udts
yParsedData.udts
.
- Crear un nuevo objeto
- 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 enParsedData.dbs
.
- Crear un nuevo objeto
- 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 elSTRUCT
anónimo o nombrado. - Grabar el offset de inicio del
STRUCT
. - Hacer una llamada recursiva a
parse_struct_members
para los hijos delSTRUCT
, 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 delSTRUCT
. - Añadir el
VariableInfo
delSTRUCT
aparent_members_list
.
- El tamaño del
- Alinear
- Si la línea es
END_STRUCT
: Retornar (indicando el fin de este nivel deSTRUCT
). - 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 ytipo_base_array
. - Si es
STRING
: extraerlongitud_string
. - Si es un UDT (entre comillas):
nombre_udt_usado
. - Sino:
tipo_primitivo
.
- Si es
- Obtener
tamaño_unitario
yalineacion_requerida_bytes
para eltipo_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.
- Para UDT:
- Crear un
VariableInfo
. - Cálculo de Offset para la variable actual:
- Si el
tipo_base_final
esBOOL
: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 esBOOL
:- 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
ycurrent_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 eltamañ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.
- Si
- Si el
- Añadir el
VariableInfo
aparent_members_list
.
- Extraer
- Si la línea es
- Detección de Bloque UDT: Si la línea marca el inicio de un UDT (
- Para cada línea del archivo:
-
Post-Procesamiento:
- Una vez que todos los bloques han sido parseados, serializar el objeto
ParsedData
a JSON.
- Una vez que todos los bloques han sido parseados, serializar el objeto
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 paraSTRING[N]
.initval
: Valor inicial.comment
: Comentario.
- Esta regex es compleja y necesita ser probada y refinada cuidadosamente. Captura:
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": "''"
// ...
}
}
]
}