Compare commits

..

No commits in common. "0f162377cd4de42e00aaaece90fd61ed703f7fa1" and "00f3b6d2ec162ce98df12a5c58d9e577a77f40cd" have entirely different histories.

12 changed files with 795 additions and 1351 deletions

View File

@ -1,15 +1,15 @@
--- Log de Ejecución: x3.py --- --- Log de Ejecución: x3.py ---
Grupo: S7_DB_Utils Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:09:01 Inicio: 2025-05-17 21:31:24
Fin: 2025-05-18 02:09:01 Fin: 2025-05-17 21:31:25
Duración: 0:00:00.154928 Duración: 0:00:00.136451
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Los archivos JSON de salida se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json Los archivos JSON de salida se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json
Archivos encontrados para procesar: 2 Archivos encontrados para procesar: 3
--- Procesando archivo: db1001_data.db --- --- Procesando archivo: db1001_data.db ---
Parseo completo. Intentando serializar a JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data.json Parseo completo. Intentando serializar a JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data.json
@ -19,6 +19,10 @@ Resultado guardado en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giov
Parseo completo. Intentando serializar a JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format.json Parseo completo. Intentando serializar a JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format.json
Resultado guardado en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format.json Resultado guardado en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format.json
--- Procesando archivo: db1001_format_updated.db ---
Parseo completo. Intentando serializar a JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_updated.json
Resultado guardado en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_updated.json
--- Proceso completado --- --- Proceso completado ---
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---

View File

@ -1,26 +1,34 @@
--- Log de Ejecución: x4.py --- --- Log de Ejecución: x4.py ---
Grupo: S7_DB_Utils Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:13:16 Inicio: 2025-05-17 21:37:42
Fin: 2025-05-18 02:13:16 Fin: 2025-05-17 21:37:42
Duración: 0:00:00.162328 Duración: 0:00:00.131741
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Los archivos de documentación generados se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation Los archivos de documentación generados se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation
Archivos JSON encontrados para procesar: 2 Archivos JSON encontrados para procesar: 3
--- Procesando archivo JSON: db1001_data.json --- --- Procesando archivo JSON: db1001_data.json ---
Archivo JSON 'db1001_data.json' cargado correctamente. Archivo JSON 'db1001_data.json' cargado correctamente.
INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'.
Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.txt Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.txt
Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.md Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.md
--- Procesando archivo JSON: db1001_format.json --- --- Procesando archivo JSON: db1001_format.json ---
Archivo JSON 'db1001_format.json' cargado correctamente. Archivo JSON 'db1001_format.json' cargado correctamente.
INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'.
Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.txt Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.txt
Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.md Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.md
--- Procesando archivo JSON: db1001_format_updated.json ---
Archivo JSON 'db1001_format_updated.json' cargado correctamente.
INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'.
Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_updated.txt
Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_updated.md
--- Proceso de generación de documentación completado --- --- Proceso de generación de documentación completado ---
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---

View File

@ -1,15 +1,15 @@
--- Log de Ejecución: x5.py --- --- Log de Ejecución: x5.py ---
Grupo: S7_DB_Utils Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:19:47 Inicio: 2025-05-17 21:51:25
Fin: 2025-05-18 02:19:47 Fin: 2025-05-17 21:51:25
Duración: 0:00:00.125156 Duración: 0:00:00.099104
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Los archivos Markdown de descripción se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation Los archivos Markdown de descripción se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation
Archivos JSON encontrados para procesar: 2 Archivos JSON encontrados para procesar: 3
--- Procesando archivo JSON para descripción: db1001_data.json --- --- Procesando archivo JSON para descripción: db1001_data.json ---
Archivo JSON 'db1001_data.json' cargado correctamente. Archivo JSON 'db1001_data.json' cargado correctamente.
@ -19,6 +19,10 @@ Documentación Markdown completa generada: C:\Trabajo\SIDEL\09 - SAE452 - Diet a
Archivo JSON 'db1001_format.json' cargado correctamente. Archivo JSON 'db1001_format.json' cargado correctamente.
Documentación Markdown completa generada: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_description.md Documentación Markdown completa generada: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_description.md
--- Procesando archivo JSON para descripción: db1001_format_updated.json ---
Archivo JSON 'db1001_format_updated.json' cargado correctamente.
Documentación Markdown completa generada: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_updated_description.md
--- Proceso de generación de descripciones Markdown completado --- --- Proceso de generación de descripciones Markdown completado ---
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---

View File

@ -1,25 +1,30 @@
--- Log de Ejecución: x6.py --- --- Log de Ejecución: x6.py ---
Grupo: S7_DB_Utils Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:20:21 Inicio: 2025-05-17 22:05:32
Fin: 2025-05-18 02:20:22 Fin: 2025-05-17 22:05:33
Duración: 0:00:01.130771 Duración: 0:00:00.614471
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Los archivos Excel de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation Los archivos Excel de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation
Archivos JSON encontrados para procesar: 2 Archivos JSON encontrados para procesar: 3
--- Procesando archivo JSON para Excel: db1001_data.json --- --- Procesando archivo JSON para Excel: db1001_data.json ---
Archivo JSON 'db1001_data.json' cargado correctamente. Archivo JSON 'db1001_data.json' cargado correctamente.
Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_data.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json_HMI_Blender_Parameters.xlsx Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_data.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json.xlsx
Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json_HMI_Blender_Parameters.xlsx Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json.xlsx
--- Procesando archivo JSON para Excel: db1001_format.json --- --- Procesando archivo JSON para Excel: db1001_format.json ---
Archivo JSON 'db1001_format.json' cargado correctamente. Archivo JSON 'db1001_format.json' cargado correctamente.
Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_format.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json_HMI_Blender_Parameters.xlsx Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_format.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json.xlsx
Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json_HMI_Blender_Parameters.xlsx Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json.xlsx
--- Procesando archivo JSON para Excel: db1001_format_updated.json ---
Archivo JSON 'db1001_format_updated.json' cargado correctamente.
Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_format_updated.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_updated.json.xlsx
Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format_updated.json.xlsx
--- Proceso de generación de documentación Excel completado --- --- Proceso de generación de documentación Excel completado ---

View File

@ -1,33 +1,28 @@
--- Log de Ejecución: x7_value_updater.py --- --- Log de Ejecución: x7_value_updater.py ---
Grupo: S7_DB_Utils Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:56:24 Inicio: 2025-05-17 23:48:43
Fin: 2025-05-18 02:56:25 Fin: 2025-05-17 23:48:43
Duración: 0:00:00.761362 Duración: 0:00:00.106052
Estado: SUCCESS (Código de Salida: 0) Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) --- --- SALIDA ESTÁNDAR (STDOUT) ---
Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json Found _data file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_data.db
Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation Found _format file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_format.db
Se encontraron 1 pares de archivos para procesar. Parsing S7 file: db1001_data.db...
Serializing to JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data_data.json
JSON saved: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data_data.json
Parsing S7 file: db1001_format.db...
Serializing to JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_format.json
JSON saved: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_format.json
Comparing structure of DB: HMI_Blender_Parameters
La estructura del DB 'HMI_Blender_Parameters' es compatible.
--- Procesando par de archivos --- All DB structures are compatible. Proceeding to generate _updated file.
Data file: db1001_data.db INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'.
Format file: db1001_format.db
Parseando archivo data: db1001_data.db
Parseando archivo format: db1001_format.db
Archivos JSON generados: db1001_data.json y db1001_format.json
Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format
Los archivos son compatibles. Creando el archivo _updated... Successfully generated _updated S7 file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db
Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json
Archivo de comparación Excel generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_comparison.xlsx
Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md
Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt
Archivo S7 copiado a: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db
--- Proceso completado ---
--- ERRORES (STDERR) --- --- ERRORES (STDERR) ---
Ninguno Ninguno

View File

@ -42,9 +42,9 @@
"hidden": false "hidden": false
}, },
"x7_value_updater.py": { "x7_value_updater.py": {
"display_name": "07: Actualizar Valores de DB (JSON)", "display_name": "x7_value_updater",
"short_description": "Busca archivos .db o .awl con la terminacion _data y _format. Si los encuentra y son compatibles usa los datos de _data para generar un _updated con los nombres de las variables de _format", "short_description": "Sin descripción corta.",
"long_description": "Procesa pares de archivos a JSON (_data.json y _format.json, generados por x3.py). Compara sus estructuras por offset para asegurar compatibilidad. Si son compatibles, crea un nuevo archivo _updated.json que combina la estructura del _format.json con los valores actuales del _data.json.", "long_description": "",
"hidden": false "hidden": false
} }
} }

View File

@ -1,10 +1,10 @@
# --- x3_refactored.py --- # --- x3.py (Modificaciones v_final_4 - Incluye 'count' para ArrayDimension y ajuste debug) ---
import re import re
import json import json
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Dict, Optional, Union, Tuple, Any from typing import List, Dict, Optional, Union, Tuple, Any
import os import os # Asegurarse de que os está importado
import glob import glob # Para buscar archivos
import copy import copy
import sys import sys
@ -29,11 +29,11 @@ class ArrayDimension:
upper_bound: int upper_bound: int
@property @property
def count(self) -> int: def count(self) -> int: # La propiedad 'count' se calculará
return self.upper_bound - self.lower_bound + 1 return self.upper_bound - self.lower_bound + 1
@dataclass @dataclass
class VariableInfo: class VariableInfo: # Sin cambios respecto a v_final_3
name: str name: str
data_type: str data_type: str
byte_offset: float byte_offset: float
@ -50,7 +50,7 @@ class VariableInfo:
current_element_values: Optional[Dict[str, str]] = None current_element_values: Optional[Dict[str, str]] = None
@dataclass @dataclass
class UdtInfo: class UdtInfo: # Sin cambios respecto a v_final_3
name: str name: str
family: Optional[str] = None family: Optional[str] = None
version: Optional[str] = None version: Optional[str] = None
@ -58,23 +58,23 @@ class UdtInfo:
total_size_in_bytes: int = 0 total_size_in_bytes: int = 0
@dataclass @dataclass
class DbInfo: class DbInfo: # Sin cambios respecto a v_final_3
name: str name: str
title: Optional[str] = None title: Optional[str] = None
family: Optional[str] = None family: Optional[str] = None
version: Optional[str] = None version: Optional[str] = None
members: List[VariableInfo] = field(default_factory=list) members: List[VariableInfo] = field(default_factory=list)
total_size_in_bytes: int = 0 total_size_in_bytes: int = 0
# Eliminamos los campos redundantes: _begin_block_assignments_ordered: List[Tuple[str, str]] = field(default_factory=list)
# _begin_block_assignments_ordered y _initial_values_from_begin_block _initial_values_from_begin_block: Dict[str, str] = field(default_factory=dict)
@dataclass @dataclass
class ParsedData: class ParsedData: # Sin cambios
udts: List[UdtInfo] = field(default_factory=list) udts: List[UdtInfo] = field(default_factory=list)
dbs: List[DbInfo] = field(default_factory=list) dbs: List[DbInfo] = field(default_factory=list)
@dataclass @dataclass
class OffsetContext: class OffsetContext: # Sin cambios
byte_offset: int = 0 byte_offset: int = 0
bit_offset: int = 0 bit_offset: int = 0
def get_combined_offset(self) -> float: def get_combined_offset(self) -> float:
@ -89,7 +89,7 @@ class OffsetContext:
if self.byte_offset % 2 != 0: self.byte_offset += 1 if self.byte_offset % 2 != 0: self.byte_offset += 1
# --- Fin Estructuras de Datos --- # --- Fin Estructuras de Datos ---
S7_PRIMITIVE_SIZES = { S7_PRIMITIVE_SIZES = { # Sin cambios
"BOOL": (0, 1, True), "BYTE": (1, 1, False), "CHAR": (1, 1, False), "BOOL": (0, 1, True), "BYTE": (1, 1, False), "CHAR": (1, 1, False),
"SINT": (1, 1, False), "USINT": (1, 1, False), "WORD": (2, 2, False), "SINT": (1, 1, False), "USINT": (1, 1, False), "WORD": (2, 2, False),
"INT": (2, 2, False), "UINT": (2, 2, False), "S5TIME": (2, 2, False), "INT": (2, 2, False), "UINT": (2, 2, False), "S5TIME": (2, 2, False),
@ -100,7 +100,7 @@ S7_PRIMITIVE_SIZES = {
"LWORD": (8, 2, False), "DATE_AND_TIME": (8, 2, False), "DT": (8, 2, False), "LWORD": (8, 2, False), "DATE_AND_TIME": (8, 2, False), "DT": (8, 2, False),
} }
class S7Parser: class S7Parser: # Sin cambios en __init__ respecto a v_final_3
def __init__(self): def __init__(self):
self.parsed_data = ParsedData() self.parsed_data = ParsedData()
self.known_udts: Dict[str, UdtInfo] = {} self.known_udts: Dict[str, UdtInfo] = {}
@ -125,7 +125,7 @@ class S7Parser:
) )
self.array_dim_regex = re.compile(r'(\d+)\s*\.\.\s*(\d+)') self.array_dim_regex = re.compile(r'(\d+)\s*\.\.\s*(\d+)')
def _get_type_details(self, type_name_raw_cleaned: str) -> Tuple[int, int, bool, str]: def _get_type_details(self, type_name_raw_cleaned: str) -> Tuple[int, int, bool, str]: # Sin cambios
type_name_upper = type_name_raw_cleaned.upper() type_name_upper = type_name_raw_cleaned.upper()
if type_name_upper in S7_PRIMITIVE_SIZES: if type_name_upper in S7_PRIMITIVE_SIZES:
size, align, is_bool = S7_PRIMITIVE_SIZES[type_name_upper] size, align, is_bool = S7_PRIMITIVE_SIZES[type_name_upper]
@ -138,7 +138,7 @@ class S7Parser:
raise ValueError(f"Tipo de dato desconocido o UDT no definido: '{type_name_raw_cleaned}'") raise ValueError(f"Tipo de dato desconocido o UDT no definido: '{type_name_raw_cleaned}'")
@staticmethod @staticmethod
def _adjust_children_offsets(children: List[VariableInfo], base_offset_add: float): def _adjust_children_offsets(children: List[VariableInfo], base_offset_add: float): # Sin cambios
for child in children: for child in children:
child.byte_offset += base_offset_add child.byte_offset += base_offset_add
if child.byte_offset == float(int(child.byte_offset)): if child.byte_offset == float(int(child.byte_offset)):
@ -149,7 +149,7 @@ class S7Parser:
def _parse_struct_members(self, lines: List[str], current_line_idx: int, def _parse_struct_members(self, lines: List[str], current_line_idx: int,
parent_members_list: List[VariableInfo], parent_members_list: List[VariableInfo],
active_context: OffsetContext, active_context: OffsetContext,
is_top_level_struct_in_block: bool = False) -> int: is_top_level_struct_in_block: bool = False) -> int: # Ajuste en depuración
idx_to_process = current_line_idx idx_to_process = current_line_idx
while idx_to_process < len(lines): while idx_to_process < len(lines):
original_line_text = lines[idx_to_process].strip() original_line_text = lines[idx_to_process].strip()
@ -178,11 +178,11 @@ class S7Parser:
active_context.align_to_byte() active_context.align_to_byte()
if active_context.byte_offset % 2 != 0: active_context.byte_offset += 1 if active_context.byte_offset % 2 != 0: active_context.byte_offset += 1
return line_index_for_return return line_index_for_return
if is_main_block_end_struct: if is_main_block_end_struct: # Simplemente lo ignoramos aquí, será manejado por END_TYPE/DB
pass pass
var_match = self.var_regex_simplified.match(line_to_parse) var_match = self.var_regex_simplified.match(line_to_parse)
if var_match: if var_match: # Lógica de var_match sin cambios respecto a v_final_3
var_data = var_match.groupdict() var_data = var_match.groupdict()
raw_base_type_from_regex = var_data['basetype'].strip() raw_base_type_from_regex = var_data['basetype'].strip()
clean_data_type = raw_base_type_from_regex.strip('"') clean_data_type = raw_base_type_from_regex.strip('"')
@ -244,186 +244,54 @@ class S7Parser:
if expanded_member.children: S7Parser._adjust_children_offsets(expanded_member.children, udt_instance_abs_start_offset) if expanded_member.children: S7Parser._adjust_children_offsets(expanded_member.children, udt_instance_abs_start_offset)
var_info.children.append(expanded_member) var_info.children.append(expanded_member)
parent_members_list.append(var_info) parent_members_list.append(var_info)
# Ajuste de la condición del mensaje de depuración
elif line_to_parse and \ elif line_to_parse and \
not self.struct_start_regex.match(line_to_parse) and \ not self.struct_start_regex.match(line_to_parse) and \
not is_main_block_end_struct and \ not is_main_block_end_struct and \
not is_nested_end_struct and \ not is_nested_end_struct and \
not is_block_terminator : not is_block_terminator : # Solo imprimir si no es un terminador conocido
print(f"DEBUG (_parse_struct_members): Line not parsed: Original='{original_line_text}' | Processed='{line_to_parse}'") print(f"DEBUG (_parse_struct_members): Line not parsed: Original='{original_line_text}' | Processed='{line_to_parse}'")
return idx_to_process return idx_to_process
def _parse_begin_block(self, lines: List[str], start_idx: int, db_info: DbInfo) -> int: def _parse_begin_block(self, lines: List[str], start_idx: int, db_info: DbInfo) -> int: # Sin cambios
"""
Parsea el bloque BEGIN y aplica directamente los valores a las variables
correspondientes, calculando también offsets para elementos de arrays.
"""
idx = start_idx idx = start_idx
assignment_regex = re.compile(r'^\s*(?P<path>.+?)\s*:=\s*(?P<value>.+?)\s*;?\s*$', re.IGNORECASE) assignment_regex = re.compile(r'^\s*(?P<path>.+?)\s*:=\s*(?P<value>.+?)\s*;?\s*$', re.IGNORECASE)
# Diccionario temporal para mapear rutas a variables
path_to_var_map = {}
# Función para calcular offset de elemento de array
def calculate_array_element_offset(var: VariableInfo, indices_str: str) -> float:
# Parsear los índices (pueden ser múltiples para arrays multidimensionales)
indices = [int(idx.strip()) for idx in indices_str.split(',')]
# Obtener dimensiones del array
dimensions = var.array_dimensions
if not dimensions or len(indices) != len(dimensions):
return var.byte_offset # No podemos calcular, devolver offset base
# Determinar tamaño de cada elemento base
element_size = 0
is_bit_array = False
if var.data_type.upper() == "BOOL":
is_bit_array = True
element_size = 0.1 # 0.1 byte = 1 bit (representación decimal)
elif var.data_type.upper() == "STRING" and var.string_length is not None:
element_size = var.string_length + 2
else:
# Para tipos primitivos y UDTs
data_type_upper = var.data_type.upper()
if data_type_upper in S7_PRIMITIVE_SIZES:
element_size = S7_PRIMITIVE_SIZES[data_type_upper][0]
elif var.data_type in self.known_udts:
element_size = self.known_udts[var.data_type].total_size_in_bytes
else:
# Si no podemos determinar tamaño, usar tamaño total / elementos
total_elements = 1
for dim in dimensions:
total_elements *= dim.count
if total_elements > 0 and var.size_in_bytes > 0:
element_size = var.size_in_bytes / total_elements
# Calcular offset para arrays multidimensionales
# Necesitamos calcular el índice lineal basado en índices multidimensionales
linear_index = 0
dimension_multiplier = 1
# Calcular desde la dimensión más interna a la más externa
# Los índices en S7 comienzan en las dimensiones a la izquierda
for i in range(len(indices)-1, -1, -1):
# Ajustar por el índice inicial de cada dimensión
adjusted_index = indices[i] - dimensions[i].lower_bound
linear_index += adjusted_index * dimension_multiplier
# Multiplicador para la siguiente dimensión
if i > 0: # No es necesario para la última iteración
dimension_multiplier *= dimensions[i].count
# Para arrays de bits, tenemos que calcular bit por bit
if is_bit_array:
base_byte = int(var.byte_offset)
base_bit = int(round((var.byte_offset - base_byte) * 10))
# Calcular nuevo bit y byte
new_bit = base_bit + linear_index
new_byte = base_byte + (new_bit // 8)
new_bit_position = new_bit % 8
return float(new_byte) + (float(new_bit_position) / 10.0)
else:
# Para tipos regulares, simplemente sumamos el offset lineal
return var.byte_offset + (linear_index * element_size)
# Construir mapa de rutas a variables
def build_path_map(members: List[VariableInfo], prefix: str = ""):
for var in members:
var_path = f"{prefix}{var.name}"
path_to_var_map[var_path] = var
# Para arrays, inicializar diccionario de elementos si es necesario
if var.array_dimensions:
var.current_element_values = {}
# Para variables con hijos, procesar recursivamente
if var.children:
build_path_map(var.children, f"{var_path}.")
# Construir el mapa antes de procesar el bloque BEGIN
build_path_map(db_info.members)
# Ahora procesar el bloque BEGIN
while idx < len(lines): while idx < len(lines):
original_line = lines[idx].strip() original_line = lines[idx].strip(); line_to_parse = original_line
line_to_parse = original_line
comment_marker = original_line.find("//") comment_marker = original_line.find("//")
if comment_marker != -1: if comment_marker != -1: line_to_parse = original_line[:comment_marker].strip()
line_to_parse = original_line[:comment_marker].strip() if self.end_db_regex.match(line_to_parse): return idx
if self.end_db_regex.match(line_to_parse):
break
idx += 1 idx += 1
if not line_to_parse: if not line_to_parse: continue
continue
match = assignment_regex.match(line_to_parse) match = assignment_regex.match(line_to_parse)
if match: if match:
path, value = match.group("path").strip(), match.group("value").strip().rstrip(';').strip() path, value = match.group("path").strip(), match.group("value").strip().rstrip(';').strip()
db_info._begin_block_assignments_ordered.append((path, value))
db_info._initial_values_from_begin_block[path] = value
raise SyntaxError("Se esperaba END_DATA_BLOCK después de la sección BEGIN.")
# Distinguir entre asignación a elemento de array y variable normal def _apply_current_values(self, members: List[VariableInfo], begin_values: Dict[str, str], current_path_prefix: str = ""): # Sin cambios
if '[' in path and ']' in path: for var_info in members:
# Es un elemento de array full_member_path = f"{current_path_prefix}{var_info.name}"
array_path = path[:path.find('[')] if var_info.array_dimensions:
indices = path[path.find('[')+1:path.find(']')] var_info.current_element_values = {}
prefix_for_search = full_member_path + "["
for key_in_begin, val_in_begin in begin_values.items():
if key_in_begin.startswith(prefix_for_search) and key_in_begin.endswith("]"):
try:
indices_str = key_in_begin[len(prefix_for_search):-1]
var_info.current_element_values[indices_str] = val_in_begin
except: print(f"Advertencia: No se pudo parsear el índice para: {key_in_begin}")
if not var_info.current_element_values: var_info.current_element_values = None
if full_member_path in begin_values: var_info.current_value = begin_values[full_member_path]
elif full_member_path in begin_values: var_info.current_value = begin_values[full_member_path]
elif var_info.initial_value is not None: var_info.current_value = var_info.initial_value
if var_info.children and not var_info.is_udt_expanded_member:
self._apply_current_values(var_info.children, begin_values, f"{full_member_path}.")
elif var_info.udt_source_name and var_info.children:
self._apply_current_values(var_info.children, begin_values, f"{full_member_path}.")
if array_path in path_to_var_map: def parse_file(self, filepath: str) -> ParsedData: # Sin cambios respecto a v_final_3
var = path_to_var_map[array_path]
if var.current_element_values is None:
var.current_element_values = {}
# Calcular y guardar el offset real del elemento
element_offset = calculate_array_element_offset(var, indices)
# Guardar como un objeto con valor y offset
var.current_element_values[indices] = {
"value": value,
"offset": element_offset
}
elif path in path_to_var_map:
# Es una variable normal (o array completo)
var = path_to_var_map[path]
var.current_value = value
# También manejar rutas jerárquicas (e.g., MyStruct.MyField)
if '.' in path and '[' not in path: # Para simplificar, excluimos arrays con path jerárquico
parts = path.split('.')
current_path = ""
current_var = None
# Navegar por la jerarquía
for i, part in enumerate(parts):
if current_path:
current_path += f".{part}"
else:
current_path = part
if current_path in path_to_var_map:
current_var = path_to_var_map[current_path]
# Si es el último componente, asignar valor
if i == len(parts) - 1 and current_var:
current_var.current_value = value
# Propagar valores iniciales a variables sin asignación explícita
def propagate_initial_values(members: List[VariableInfo]):
for var in members:
# Si no tiene current_value pero tiene initial_value, copiar
if var.current_value is None and var.initial_value is not None:
var.current_value = var.initial_value
# Recursión para hijos
if var.children:
propagate_initial_values(var.children)
# Propagar valores iniciales
propagate_initial_values(db_info.members)
return idx
def parse_file(self, filepath: str) -> ParsedData:
try: try:
with open(filepath, 'r', encoding='utf-8-sig') as f: lines = f.readlines() with open(filepath, 'r', encoding='utf-8-sig') as f: lines = f.readlines()
except Exception as e: print(f"Error al leer el archivo {filepath}: {e}"); return self.parsed_data except Exception as e: print(f"Error al leer el archivo {filepath}: {e}"); return self.parsed_data
@ -466,260 +334,42 @@ class S7Parser:
elif self.end_type_regex.match(line_to_parse) and isinstance(current_block_handler, UdtInfo): elif self.end_type_regex.match(line_to_parse) and isinstance(current_block_handler, UdtInfo):
if current_block_handler.total_size_in_bytes == 0: current_block_handler.total_size_in_bytes = active_block_context.byte_offset if current_block_handler.total_size_in_bytes == 0: current_block_handler.total_size_in_bytes = active_block_context.byte_offset
self.known_udts[current_block_handler.name] = current_block_handler self.known_udts[current_block_handler.name] = current_block_handler
# print(f"Parsed UDT: {current_block_handler.name}, Size: {current_block_handler.total_size_in_bytes}b, Members: {len(current_block_handler.members)}")
current_block_handler = None; parsing_title_value_next_line = False current_block_handler = None; parsing_title_value_next_line = False
elif self.end_db_regex.match(line_to_parse) and isinstance(current_block_handler, DbInfo): elif self.end_db_regex.match(line_to_parse) and isinstance(current_block_handler, DbInfo):
if current_block_handler.total_size_in_bytes == 0 : current_block_handler.total_size_in_bytes = active_block_context.byte_offset if current_block_handler.total_size_in_bytes == 0 : current_block_handler.total_size_in_bytes = active_block_context.byte_offset
# Ya no necesitamos aplicar valores, porque se aplican directamente en _parse_begin_block self._apply_current_values(current_block_handler.members, current_block_handler._initial_values_from_begin_block)
# print(f"Parsed DB: {current_block_handler.name}, Decl.Size: {current_block_handler.total_size_in_bytes}b, Members: {len(current_block_handler.members)}, BEGIN assigns: {len(current_block_handler._begin_block_assignments_ordered)}")
current_block_handler = None; parsing_title_value_next_line = False current_block_handler = None; parsing_title_value_next_line = False
idx += 1 idx += 1
return self.parsed_data return self.parsed_data
def custom_json_serializer(obj: Any) -> Any: def custom_json_serializer(obj: Any) -> Any:
if isinstance(obj, OffsetContext): return None if isinstance(obj, OffsetContext): return None
# Manejar ArrayDimension explícitamente para incluir 'count'
if isinstance(obj, ArrayDimension): if isinstance(obj, ArrayDimension):
return { return {
'lower_bound': obj.lower_bound, 'lower_bound': obj.lower_bound,
'upper_bound': obj.upper_bound, 'upper_bound': obj.upper_bound,
'count': obj.count 'count': obj.count # La propiedad se calcula y se añade aquí
} }
if hasattr(obj, '__dict__'): if hasattr(obj, '__dict__'):
d = {k: v for k, v in obj.__dict__.items() d = {k: v for k, v in obj.__dict__.items()
if not (v is None or (isinstance(v, list) and not v))} if not (v is None or (isinstance(v, list) and not v))} # No filtrar _initial_values_from_begin_block
if isinstance(obj, VariableInfo): if isinstance(obj, VariableInfo):
if not obj.is_udt_expanded_member and 'is_udt_expanded_member' not in d: if not obj.is_udt_expanded_member and 'is_udt_expanded_member' not in d :
d['is_udt_expanded_member'] = False d['is_udt_expanded_member'] = False
if not obj.current_element_values and 'current_element_values' in d:
# Manejar current_element_values con format especial para offsets
if 'current_element_values' in d:
if not d['current_element_values']:
del d['current_element_values'] del d['current_element_values']
else: if isinstance(obj, DbInfo): # Asegurar que las listas vacías no se omitan si el campo existe
# Asegurar que current_element_values se serializa correctamente if '_begin_block_assignments_ordered' not in d and obj._begin_block_assignments_ordered == []:
element_values = d['current_element_values'] d['_begin_block_assignments_ordered'] = [] # Mantener lista vacía si es el caso
if isinstance(element_values, dict): if '_initial_values_from_begin_block' not in d and obj._initial_values_from_begin_block == {}:
# Preservar el formato {índice: {value, offset}} d['_initial_values_from_begin_block'] = {} # Mantener dict vacío si es el caso
d['current_element_values'] = element_values
return d return d
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable: {type(obj)}") raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable: {type(obj)}")
def format_address_for_display(byte_offset: float, bit_size: int = 0) -> str:
"""
Formatea correctamente la dirección para mostrar, preservando el índice del bit para BOOLs.
Args:
byte_offset: El offset en bytes (con parte decimal para bits)
bit_size: Tamaño en bits (>0 para BOOLs)
Returns:
String formateado como "X.Y" para bits o "X" para bytes completos
"""
if bit_size > 0:
# Para BOOL, extraer y mostrar el byte y bit exactos
byte_part = int(byte_offset)
# Multiplicamos por 10 y tomamos el entero para obtener el índice correcto del bit
bit_part = int(round((byte_offset - byte_part) * 10))
return f"{byte_part}.{bit_part}"
else:
# Para otros tipos, mostrar como entero si es un byte completo
if byte_offset == float(int(byte_offset)):
return str(int(byte_offset))
return f"{byte_offset:.1f}"
def compare_offsets(offset1: float, offset2: float) -> int:
"""
Compara dos offsets considerando tanto la parte del byte como la del bit.
Returns:
-1 si offset1 < offset2, 0 si son iguales, 1 si offset1 > offset2
"""
# Extraer partes de byte y bit
byte1 = int(offset1)
bit1 = int(round((offset1 - byte1) * 10))
byte2 = int(offset2)
bit2 = int(round((offset2 - byte2) * 10))
# Comparar primero por byte
if byte1 < byte2:
return -1
elif byte1 > byte2:
return 1
# Si bytes son iguales, comparar por bit
if bit1 < bit2:
return -1
elif bit1 > bit2:
return 1
# Son exactamente iguales
return 0
def calculate_array_element_offset(var: VariableInfo, indices_str: str) -> float:
"""
Calcula el offset exacto para un elemento de array basado en sus índices.
Maneja correctamente arrays de bits y multidimensionales.
Args:
var: Variable información del array
indices_str: String de índices (e.g. "1,2" para array bidimensional)
Returns:
Offset calculado como float, con parte decimal para bits
"""
# Parsear los índices (pueden ser múltiples para arrays multidimensionales)
indices = [int(idx.strip()) for idx in indices_str.split(',')]
# Obtener dimensiones del array
dimensions = var.array_dimensions
if not dimensions or len(indices) != len(dimensions):
return var.byte_offset # No podemos calcular, devolver offset base
# Determinar tamaño de cada elemento base
element_size = 0
is_bit_array = False
if var.data_type.upper() == "BOOL":
is_bit_array = True
element_size = 0.1 # 0.1 byte = 1 bit (representación decimal)
elif var.data_type.upper() == "STRING" and var.string_length is not None:
element_size = var.string_length + 2 # Para strings, sumar 2 bytes de cabecera
else:
# Para tipos primitivos y UDTs
data_type_upper = var.data_type.upper()
if data_type_upper in S7_PRIMITIVE_SIZES:
element_size = S7_PRIMITIVE_SIZES[data_type_upper][0]
elif var.data_type in self.known_udts:
element_size = self.known_udts[var.data_type].total_size_in_bytes
else:
# Si no podemos determinar tamaño, usar tamaño total / elementos
total_elements = 1
for dim in dimensions:
total_elements *= dim.count
if total_elements > 0 and var.size_in_bytes > 0:
element_size = var.size_in_bytes / total_elements
# Calcular offset para arrays multidimensionales
# En S7, los arrays se almacenan en orden Row-Major (la última dimensión varía más rápido)
linear_index = 0
dimension_multiplier = 1
# Calcular desde la dimensión más interna a la más externa
# Para S7, procesamos desde la última dimensión hacia la primera
for i in range(len(indices)-1, -1, -1):
# Ajustar por el índice inicial de cada dimensión
adjusted_index = indices[i] - dimensions[i].lower_bound
linear_index += adjusted_index * dimension_multiplier
# Multiplicador para la siguiente dimensión
if i > 0: # No es necesario para la última iteración
dimension_multiplier *= dimensions[i].count
# Calcular offset según tipo
if is_bit_array:
# Para arrays de bits, calcular bit por bit
base_byte = int(var.byte_offset)
base_bit = int(round((var.byte_offset - base_byte) * 10))
# Calcular nuevo bit y byte
new_bit = base_bit + linear_index
new_byte = base_byte + (new_bit // 8)
new_bit_position = new_bit % 8
# Formato S7: byte.bit con bit de 0-7
return float(new_byte) + (float(new_bit_position) / 10.0)
else:
# Para tipos regulares, simplemente sumar el offset lineal * tamaño elemento
return var.byte_offset + (linear_index * element_size)
def flatten_db_structure(db_info: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Función genérica que aplana completamente una estructura de DB/UDT,
expandiendo todas las variables anidadas, UDTs y elementos de array.
Garantiza ordenamiento estricto por offset (byte.bit).
Returns:
List[Dict]: Lista de variables aplanadas con todos sus atributos
y un path completo, ordenada por offset estricto.
"""
flat_variables = []
processed_ids = set() # Para evitar duplicados
def process_variable(var: Dict[str, Any], path_prefix: str = "", is_expansion: bool = False):
# Identificador único para esta variable en este contexto
var_id = f"{path_prefix}{var['name']}_{var['byte_offset']}"
# Evitar procesar duplicados (como miembros expandidos de UDTs)
if is_expansion and var_id in processed_ids:
return
if is_expansion:
processed_ids.add(var_id)
# Crear copia de la variable con path completo
flat_var = var.copy()
flat_var["full_path"] = f"{path_prefix}{var['name']}"
flat_var["is_array_element"] = False # Por defecto no es elemento de array
# Determinar si es array con valores específicos
is_array = bool(var.get("array_dimensions"))
has_array_values = is_array and var.get("current_element_values")
# Si no es un array con valores específicos, agregar la variable base
if not has_array_values:
# Asegurarse de que el offset esté en el formato correcto
flat_var["address_display"] = format_address_for_display(var["byte_offset"], var.get("bit_size", 0))
flat_variables.append(flat_var)
# Si es array con valores específicos, expandir cada elemento como variable individual
if has_array_values:
for idx, element_data in var.get("current_element_values", {}).items():
# Extraer valor y offset del elemento
if isinstance(element_data, dict) and "value" in element_data and "offset" in element_data:
# Nuevo formato con offset calculado
value = element_data["value"]
element_offset = element_data["offset"]
else:
# Compatibilidad con formato antiguo
value = element_data
element_offset = var["byte_offset"] # Offset base
# Crear una entrada por cada elemento del array
array_element = var.copy()
array_element["full_path"] = f"{path_prefix}{var['name']}[{idx}]"
array_element["is_array_element"] = True
array_element["array_index"] = idx
array_element["current_value"] = value
array_element["byte_offset"] = element_offset # Usar offset calculado
array_element["address_display"] = format_address_for_display(element_offset, var.get("bit_size", 0))
# Eliminar current_element_values para evitar redundancia
if "current_element_values" in array_element:
del array_element["current_element_values"]
flat_variables.append(array_element)
# Procesar recursivamente todos los hijos
if var.get("children"):
for child in var.get("children", []):
process_variable(
child,
f"{path_prefix}{var['name']}.",
is_expansion=bool(var.get("udt_source_name"))
)
# Procesar todos los miembros desde el nivel superior
for member in db_info.get("members", []):
process_variable(member)
# Ordenar estrictamente por offset byte.bit
flat_variables.sort(key=lambda x: (
int(x["byte_offset"]),
int(round((x["byte_offset"] - int(x["byte_offset"])) * 10))
))
return flat_variables
if __name__ == "__main__": if __name__ == "__main__":
working_dir = find_working_directory() working_dir = find_working_directory()
print(f"Using working directory: {working_dir}") print(f"Using working directory: {working_dir}")
@ -738,7 +388,7 @@ if __name__ == "__main__":
print(f"Archivos encontrados para procesar: {len(all_source_files)}") print(f"Archivos encontrados para procesar: {len(all_source_files)}")
for filepath in all_source_files: for filepath in all_source_files:
parser = S7Parser() parser = S7Parser() # Nueva instancia para cada archivo para evitar estados residuales
filename = os.path.basename(filepath) filename = os.path.basename(filepath)
print(f"\n--- Procesando archivo: {filename} ---") print(f"\n--- Procesando archivo: {filename} ---")

View File

@ -1,10 +1,9 @@
# --- x4_refactored.py --- # --- x4.py (Modificaciones v_final_2) ---
import json import json
from typing import List, Dict, Any from typing import List, Dict, Any
import sys import sys
import os import os
import glob import glob # Para buscar archivos JSON
from x3 import flatten_db_structure
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -20,6 +19,7 @@ def find_working_directory():
sys.exit(1) sys.exit(1)
return working_directory return working_directory
# format_data_type_for_source (sin cambios respecto a la v5 que te di antes)
def format_data_type_for_source(var_info: Dict[str, Any]) -> str: def format_data_type_for_source(var_info: Dict[str, Any]) -> str:
base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"] base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"]
type_str = "" type_str = ""
@ -45,7 +45,7 @@ def generate_variable_declaration_for_source(var_info: Dict[str, Any], indent_le
is_multiline_struct_def = (var_info["data_type"].upper() == "STRUCT" and \ is_multiline_struct_def = (var_info["data_type"].upper() == "STRUCT" and \
not var_info.get("udt_source_name") and \ not var_info.get("udt_source_name") and \
var_info.get("children")) var_info.get("children"))
if not is_multiline_struct_def: if not is_multiline_struct_def: # Solo añadir ; si no es una cabecera de STRUCT multilínea
line += ';' line += ';'
if var_info.get("comment"): if var_info.get("comment"):
@ -60,76 +60,32 @@ def generate_struct_members_for_source(members: List[Dict[str, Any]], indent_lev
not var_info.get("udt_source_name") and \ not var_info.get("udt_source_name") and \
var_info.get("children"): var_info.get("children"):
current_indent_str = " " * indent_level current_indent_str = " " * indent_level
lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT') lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT') # SIN ;
lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1)) lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1))
lines.append(f'{current_indent_str}END_STRUCT;') lines.append(f'{current_indent_str}END_STRUCT;') # CON ;
else: else:
lines.append(generate_variable_declaration_for_source(var_info, indent_level)) lines.append(generate_variable_declaration_for_source(var_info, indent_level))
return lines return lines
def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int) -> List[str]: def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int) -> List[str]:
"""
Genera asignaciones del bloque BEGIN para todas las variables con valores actuales,
ordenadas estrictamente por offset (byte.bit).
"""
indent_str = " " * indent_level indent_str = " " * indent_level
lines = [] lines = []
# Usar la lista ordenada de asignaciones del JSON, que x3.py ahora debería poblar
ordered_assignments = db_info.get("_begin_block_assignments_ordered")
# Obtener todas las variables aplanadas y ordenadas if ordered_assignments and isinstance(ordered_assignments, list):
flat_vars = flatten_db_structure(db_info) print(f"INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB '{db_info['name']}'.")
for path, value_obj in ordered_assignments:
# Para cada variable en el orden correcto, generar la asignación value_str = str(value_obj)
for var in flat_vars:
# Verificar que tenga un valor actual para asignar
if var.get("current_value") is not None:
value_str = str(var["current_value"])
# Convertir valores booleanos a TRUE/FALSE según estándar S7
if value_str.lower() == "true": value_str = "TRUE" if value_str.lower() == "true": value_str = "TRUE"
elif value_str.lower() == "false": value_str = "FALSE" elif value_str.lower() == "false": value_str = "FALSE"
lines.append(f"{indent_str}{path} := {value_str};") # Asignaciones siempre con ;
# Generar la línea de asignación
lines.append(f"{indent_str}{var['full_path']} := {value_str};")
return lines
def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]:
"""
Genera una tabla markdown completa con offsets de bits correctos.
"""
lines = []
lines.append(f"## Documentación para DB: {db_info['name']}")
lines.append("")
lines.append("| Address | Name | Type | Initial Value | Actual Value | Comment |")
lines.append("|---|---|---|---|---|---|")
# Obtener todas las variables aplanadas (ya ordenadas por offset)
flat_vars = flatten_db_structure(db_info)
# Mostrar todas las variables, incluyendo elementos de array
for var in flat_vars:
# Usar el address_display pre-calculado
address = var["address_display"]
name_for_display = var["full_path"]
# Formatear tipo adecuadamente según sea variable normal o elemento de array
if var.get("is_array_element"):
# Para elementos de array, mostrar solo el tipo base
if "array_dimensions" in var:
# Si todavía tenemos información de array, eliminar la parte ARRAY[..]
base_type = var["data_type"]
data_type_str = base_type
else: else:
data_type_str = var["data_type"] print(f"ADVERTENCIA: '_begin_block_assignments_ordered' no encontrado para DB '{db_info['name']}'. "
else: "El bloque BEGIN puede estar incompleto o desordenado si se usa el fallback.")
# Para variables normales, mostrar tipo completo # (Aquí podría ir el fallback a _generate_assignments_recursive_from_current_values si se desea)
data_type_str = format_data_type_for_source(var) # fallback_lines = _generate_assignments_recursive_from_current_values(db_info.get("members", []), "", indent_str)
# if fallback_lines: lines.extend(fallback_lines)
# Formatear valores para la tabla
initial_value = str(var.get("initial_value", "")).replace("|", "\\|").replace("\n", " ")
actual_value = str(var.get("current_value", "")).replace("|", "\\|").replace("\n", " ")
comment = str(var.get("comment", "")).replace("|", "\\|").replace("\n", " ")
lines.append(f"| {address} | {name_for_display} | {data_type_str} | {initial_value} | {actual_value} | {comment} |")
return lines return lines
@ -137,35 +93,70 @@ def generate_s7_source_code_lines(data: Dict[str, Any]) -> List[str]:
lines = [] lines = []
for udt in data.get("udts", []): for udt in data.get("udts", []):
lines.append(f'TYPE "{udt["name"]}"') lines.append(f'TYPE "{udt["name"]}"')
if udt.get("family"): lines.append(f' FAMILY : {udt["family"]}') if udt.get("family"): lines.append(f' FAMILY : {udt["family"]}') # SIN ;
if udt.get("version"): lines.append(f' VERSION : {udt["version"]}') if udt.get("version"): lines.append(f' VERSION : {udt["version"]}') # SIN ;
lines.append("") lines.append("")
lines.append(" STRUCT") lines.append(" STRUCT") # SIN ;
lines.extend(generate_struct_members_for_source(udt["members"], 2)) lines.extend(generate_struct_members_for_source(udt["members"], 2))
lines.append(" END_STRUCT;") lines.append(" END_STRUCT;") # CON ;
lines.append(f'END_TYPE') lines.append(f'END_TYPE') # SIN ; según tu último comentario
lines.append("") lines.append("")
for db in data.get("dbs", []): for db in data.get("dbs", []):
lines.append(f'DATA_BLOCK "{db["name"]}"') lines.append(f'DATA_BLOCK "{db["name"]}"')
if db.get("title"): if db.get("title"): # TITLE = { ... } va tal cual y SIN ;
lines.append(f' TITLE = {db["title"]}') lines.append(f' TITLE = {db["title"]}')
if db.get("family"): lines.append(f' FAMILY : {db["family"]}') if db.get("family"): lines.append(f' FAMILY : {db["family"]}') # SIN ;
if db.get("version"): lines.append(f' VERSION : {db["version"]}') if db.get("version"): lines.append(f' VERSION : {db["version"]}') # SIN ;
lines.append("") lines.append("")
lines.append(" STRUCT") lines.append(" STRUCT") # SIN ;
lines.extend(generate_struct_members_for_source(db["members"], 2)) lines.extend(generate_struct_members_for_source(db["members"], 2))
lines.append(" END_STRUCT;") lines.append(" END_STRUCT;") # CON ;
begin_assignments = generate_begin_block_assignments(db, 1) begin_assignments = generate_begin_block_assignments(db, 1) # Indentación 1 para las asignaciones
if begin_assignments: if begin_assignments:
lines.append("BEGIN") lines.append("BEGIN") # SIN ;
lines.extend(begin_assignments) lines.extend(begin_assignments)
lines.append(f'END_DATA_BLOCK') lines.append(f'END_DATA_BLOCK') # SIN ; según tu último comentario
lines.append("") lines.append("")
return lines return lines
# generate_markdown_table (sin cambios respecto a la v5)
def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]:
lines = []
lines.append(f"## Documentación para DB: {db_info['name']}") # Cambiado a H2 para múltiples DBs por archivo
lines.append("")
lines.append("| Address | Name | Type | Initial Value | Actual Value | Comment |")
lines.append("|---|---|---|---|---|---|")
processed_expanded_members = set()
def flatten_members_for_markdown(members: List[Dict[str, Any]], prefix: str = "", base_offset: float = 0.0, is_expansion: bool = False):
md_lines = []
for var_idx, var in enumerate(members):
member_id = f"{prefix}{var['name']}_{var_idx}"
if is_expansion and member_id in processed_expanded_members: continue
if is_expansion: processed_expanded_members.add(member_id)
name_for_display = f"{prefix}{var['name']}"
address = f"{var['byte_offset']:.1f}" if isinstance(var['byte_offset'], float) else str(var['byte_offset'])
if var.get("bit_size", 0) > 0 and isinstance(var['byte_offset'], float) and var['byte_offset'] != int(var['byte_offset']): pass
elif var.get("bit_size", 0) > 0 : address = f"{int(var['byte_offset'])}.0"
data_type_str = format_data_type_for_source(var)
initial_value = str(var.get("initial_value", "")).replace("|", "\\|").replace("\n", " ")
actual_value = str(var.get("current_value", "")).replace("|", "\\|").replace("\n", " ")
comment = str(var.get("comment", "")).replace("|", "\\|").replace("\n", " ")
is_struct_container = var["data_type"].upper() == "STRUCT" and not var.get("udt_source_name") and var.get("children")
is_udt_instance_container = bool(var.get("udt_source_name")) and var.get("children")
if not is_struct_container and not is_udt_instance_container or var.get("is_udt_expanded_member"):
md_lines.append(f"| {address} | {name_for_display} | {data_type_str} | {initial_value} | {actual_value} | {comment} |")
if var.get("children"):
md_lines.extend(flatten_members_for_markdown(var["children"],
f"{name_for_display}.",
var['byte_offset'],
is_expansion=bool(var.get("udt_source_name"))))
return md_lines
lines.extend(flatten_members_for_markdown(db_info.get("members", [])))
return lines
def main(): def main():
working_dir = find_working_directory() working_dir = find_working_directory()
print(f"Using working directory: {working_dir}") print(f"Using working directory: {working_dir}")
@ -197,7 +188,7 @@ def main():
print(f"Archivo JSON '{current_json_filename}' cargado correctamente.") print(f"Archivo JSON '{current_json_filename}' cargado correctamente.")
except Exception as e: except Exception as e:
print(f"Error al cargar/leer {current_json_filename}: {e}") print(f"Error al cargar/leer {current_json_filename}: {e}")
continue continue # Saltar al siguiente archivo JSON
# Generar archivo S7 (.txt) # Generar archivo S7 (.txt)
s7_code_lines = generate_s7_source_code_lines(data_from_json) s7_code_lines = generate_s7_source_code_lines(data_from_json)
@ -218,11 +209,11 @@ def main():
for db_index, db_to_document in enumerate(data_from_json["dbs"]): for db_index, db_to_document in enumerate(data_from_json["dbs"]):
if db_index > 0: if db_index > 0:
all_db_markdown_lines.append("\n---\n") all_db_markdown_lines.append("\n---\n") # Separador visual entre DBs
markdown_lines_for_one_db = generate_markdown_table(db_to_document) markdown_lines_for_one_db = generate_markdown_table(db_to_document)
all_db_markdown_lines.extend(markdown_lines_for_one_db) all_db_markdown_lines.extend(markdown_lines_for_one_db)
all_db_markdown_lines.append("") all_db_markdown_lines.append("") # Espacio después de cada tabla de DB
try: try:
with open(md_output_filename, 'w', encoding='utf-8') as f: with open(md_output_filename, 'w', encoding='utf-8') as f:
@ -233,6 +224,7 @@ def main():
print(f"Error al escribir el archivo Markdown {md_output_filename}: {e}") print(f"Error al escribir el archivo Markdown {md_output_filename}: {e}")
else: else:
print(f"No se encontraron DBs en {current_json_filename} para generar documentación Markdown.") print(f"No se encontraron DBs en {current_json_filename} para generar documentación Markdown.")
# Opcionalmente, crear un archivo MD con un mensaje
with open(md_output_filename, 'w', encoding='utf-8') as f: with open(md_output_filename, 'w', encoding='utf-8') as f:
f.write(f"# Documentación S7 para {json_filename_base}\n\n_Fuente JSON: {current_json_filename}_\n\nNo se encontraron Bloques de Datos (DBs) en este archivo JSON.\n") f.write(f"# Documentación S7 para {json_filename_base}\n\n_Fuente JSON: {current_json_filename}_\n\nNo se encontraron Bloques de Datos (DBs) en este archivo JSON.\n")
print(f"Archivo Markdown generado (sin DBs): {md_output_filename}") print(f"Archivo Markdown generado (sin DBs): {md_output_filename}")

View File

@ -1,10 +1,9 @@
# --- x5_refactored.py ---
import json import json
from typing import List, Dict, Any, Optional from typing import List, Dict, Any, Optional
import sys import sys
import os import os
import glob import glob # Para buscar archivos JSON
from datetime import datetime from datetime import datetime # Mover import al inicio
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -12,10 +11,6 @@ script_root = os.path.dirname(
sys.path.append(script_root) sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# Importar funciones comunes desde x3
from x3 import flatten_db_structure, format_address_for_display
from x4 import format_data_type_for_source
def find_working_directory(): def find_working_directory():
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
@ -24,85 +19,94 @@ def find_working_directory():
sys.exit(1) sys.exit(1)
return working_directory return working_directory
def format_data_type_for_display(var_info: Dict[str, Any]) -> str:
"""Formatea la declaración de tipo para visualización en Markdown."""
base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"]
type_str = ""
if var_info.get("array_dimensions"):
dims_str = ",".join([f"{d['lower_bound']}..{d['upper_bound']}" for d in var_info["array_dimensions"]])
type_str += f"ARRAY [{dims_str}] OF "
type_str += base_type
if var_info["data_type"].upper() == "STRING" and var_info.get("string_length") is not None:
type_str += f"[{var_info['string_length']}]"
return type_str
def format_offset_for_display(byte_offset: float) -> str:
"""Formatea el offset como X.Y o solo X si es .0."""
if byte_offset == float(int(byte_offset)):
return str(int(byte_offset))
return f"{byte_offset:.1f}"
def generate_members_table_md( def generate_members_table_md(
db_info: Dict[str, Any], members: List[Dict[str, Any]],
path_prefix: str = "",
is_udt_definition: bool = False, is_udt_definition: bool = False,
include_current_value: bool = True include_current_value: bool = False
) -> List[str]: ) -> List[str]:
""" """Genera líneas de tabla Markdown para una lista de miembros."""
Genera tabla markdown para todos los miembros usando las funciones de aplanamiento de x3. md_lines = []
""" for var_info in members:
lines = [] name_display = f"{path_prefix}{var_info['name']}"
# Definir columnas de la tabla # Para miembros expandidos de un UDT, su nombre ya está completo en la jerarquía del JSON.
if include_current_value: # La recursión ya habrá construido el path_prefix.
header = "| Nombre Miembro (Ruta) | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial (Decl.) | Valor Actual (Efectivo) | Comentario |" # No necesitamos hacer nada especial aquí si `is_udt_expanded_member` es true,
separator = "|---|---|---|---|---|---|---|---|" # ya que esta función se llama recursivamente sobre `children`.
else:
header = "| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |"
separator = "|---|---|---|---|---|---|---|"
lines.append(header) data_type_display = format_data_type_for_display(var_info)
lines.append(separator) offset_display = format_offset_for_display(var_info['byte_offset'])
size_bytes_display = str(var_info['size_in_bytes'])
bit_size_display = str(var_info.get('bit_size', '0')) if var_info.get('bit_size', 0) > 0 else ""
# Usar la función de aplanamiento importada initial_value_display = str(var_info.get('initial_value', '')).replace("|", "\\|").replace("\n", " ")
flat_vars = flatten_db_structure(db_info) comment_display = str(var_info.get('comment', '')).replace("|", "\\|").replace("\n", " ")
# Generar filas para cada variable row = f"| `{name_display}` | `{data_type_display}` | {offset_display} | {size_bytes_display} | {bit_size_display} | `{initial_value_display}` |"
for var in flat_vars:
# Usar la dirección formateada desde flatten_db_structure
address = var.get("address_display", format_address_for_display(var["byte_offset"], var.get("bit_size", 0)))
name_display = f"`{var['full_path']}`"
data_type_display = f"`{format_data_type_for_source(var)}`"
size_bytes_display = str(var.get('size_in_bytes', '0'))
bit_size_display = str(var.get('bit_size', '0')) if var.get('bit_size', 0) > 0 else ""
initial_value = str(var.get('initial_value', '')).replace("|", "\\|").replace("\n", " ")
initial_value_display = f"`{initial_value}`" if initial_value else ""
comment_display = str(var.get('comment', '')).replace("|", "\\|").replace("\n", " ")
if include_current_value: if include_current_value:
current_value = str(var.get('current_value', '')).replace("|", "\\|").replace("\n", " ") current_value_display = ""
current_value_display = f"`{current_value}`" if current_value else "" # Si es un array y tiene current_element_values
row = f"| {name_display} | {data_type_display} | {address} | {size_bytes_display} | {bit_size_display} | {initial_value_display} | {current_value_display} | {comment_display} |" if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict):
# Mostrar un resumen o un placeholder para arrays complejos en la tabla principal
# Los valores detallados del array se listarán en la sección BEGIN.
num_elements = sum(dim['count'] for dim in var_info.get('array_dimensions', [])) if var_info.get('array_dimensions') else 1
if num_elements == 0 and var_info.get("array_dimensions"): # Caso de ARRAY [x..y] donde x > y (raro, pero posible)
num_elements = 1 # Para evitar división por cero o lógica extraña.
assigned_elements = len(var_info["current_element_values"])
if assigned_elements > 0:
current_value_display = f"{assigned_elements} elemento(s) asignado(s) en BEGIN"
elif var_info.get("current_value") is not None: # Para arrays con una asignación global (raro en BEGIN)
current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ")
else: else:
row = f"| {name_display} | {data_type_display} | {address} | {size_bytes_display} | {bit_size_display} | {initial_value_display} | {comment_display} |" current_value_display = ""
lines.append(row) elif var_info.get("current_value") is not None:
current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ")
row += f" `{current_value_display}` |"
return lines row += f" {comment_display} |"
md_lines.append(row)
def generate_begin_block_documentation(db_info: Dict[str, Any]) -> List[str]: # Recursión para hijos de STRUCTs o miembros expandidos de UDTs
""" # `is_udt_expanded_member` en el JSON nos dice si los 'children' son la expansión de un UDT.
Genera documentación para el bloque BEGIN utilizando flatten_db_structure. if var_info.get("children"):
""" # El prefijo para los hijos es el nombre completo del padre actual.
lines = [] # Si el hijo es un miembro expandido de UDT, su propio nombre en 'children' ya es el nombre final del miembro.
lines.append("#### Contenido del Bloque `BEGIN` (Valores Actuales Asignados):") # Si el hijo es parte de un STRUCT anidado, su nombre es relativo al STRUCT.
lines.append("El bloque `BEGIN` define los valores actuales de las variables en el DB. Las siguientes asignaciones son ordenadas por offset:") md_lines.extend(generate_members_table_md(
lines.append("") var_info["children"],
f"{name_display}.",
is_udt_definition,
include_current_value
))
# Usar la función de aplanamiento importada de x3 return md_lines
flat_vars = flatten_db_structure(db_info)
# Filtrar solo variables con valores actuales
vars_with_values = [var for var in flat_vars if var.get("current_value") is not None]
if vars_with_values:
lines.append("```scl")
for var in vars_with_values:
value_str = str(var["current_value"])
if value_str.lower() == "true": value_str = "TRUE"
elif value_str.lower() == "false": value_str = "FALSE"
lines.append(f" {var['full_path']} := {value_str};")
lines.append("```")
lines.append("")
else:
lines.append("No se encontraron asignaciones de valores actuales.")
lines.append("")
return lines
def generate_json_documentation(data: Dict[str, Any], output_filename: str): def generate_json_documentation(data: Dict[str, Any], output_filename: str):
"""Genera la documentación Markdown completa para el archivo JSON parseado.""" """Genera la documentación Markdown completa para el archivo JSON parseado."""
@ -125,10 +129,9 @@ def generate_json_documentation(data: Dict[str, Any], output_filename: str):
lines.append(f"- **Tamaño Total**: {udt['total_size_in_bytes']} bytes") lines.append(f"- **Tamaño Total**: {udt['total_size_in_bytes']} bytes")
lines.append("") lines.append("")
lines.append("#### Miembros del UDT:") lines.append("#### Miembros del UDT:")
lines.append("| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |")
# Usar la función optimizada para generar tabla lines.append("|---|---|---|---|---|---|---|")
udt_member_lines = generate_members_table_md(udt, is_udt_definition=True, include_current_value=False) lines.extend(generate_members_table_md(udt.get("members", []), is_udt_definition=True, include_current_value=False))
lines.extend(udt_member_lines)
lines.append("") lines.append("")
else: else:
lines.append("No se encontraron UDTs en el archivo JSON.") lines.append("No se encontraron UDTs en el archivo JSON.")
@ -147,14 +150,28 @@ def generate_json_documentation(data: Dict[str, Any], output_filename: str):
lines.append("") lines.append("")
lines.append("#### Miembros del DB (Sección de Declaración):") lines.append("#### Miembros del DB (Sección de Declaración):")
db_member_lines = generate_members_table_md(db, include_current_value=True) lines.append("| Nombre Miembro (Ruta) | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial (Decl.) | Valor Actual (Efectivo) | Comentario |")
lines.extend(db_member_lines) lines.append("|---|---|---|---|---|---|---|---|")
lines.extend(generate_members_table_md(db.get("members", []), include_current_value=True))
lines.append("") lines.append("")
# Generar sección BEGIN usando la función optimizada # Sección BEGIN
begin_lines = generate_begin_block_documentation(db) ordered_assignments = db.get("_begin_block_assignments_ordered")
lines.extend(begin_lines) if ordered_assignments:
lines.append("#### Contenido del Bloque `BEGIN` (Valores Actuales Asignados):")
lines.append("El bloque `BEGIN` define los valores actuales de las variables en el DB. Las siguientes asignaciones fueron encontradas, en orden:")
lines.append("")
lines.append("```scl") # Usar SCL para syntax highlighting si el visualizador Markdown lo soporta
for path, value in ordered_assignments:
val_str = str(value)
if val_str.lower() == "true": val_str = "TRUE"
elif val_str.lower() == "false": val_str = "FALSE"
lines.append(f" {path} := {val_str};")
lines.append("```")
lines.append("")
else:
lines.append("No se encontraron asignaciones en el bloque `BEGIN` (o no fue parseado).")
lines.append("")
else: else:
lines.append("No se encontraron DBs en el archivo JSON.") lines.append("No se encontraron DBs en el archivo JSON.")
@ -167,6 +184,7 @@ def generate_json_documentation(data: Dict[str, Any], output_filename: str):
except Exception as e: except Exception as e:
print(f"Error al escribir el archivo Markdown de documentación {output_filename}: {e}") print(f"Error al escribir el archivo Markdown de documentación {output_filename}: {e}")
# --- Main ---
if __name__ == "__main__": if __name__ == "__main__":
working_dir = find_working_directory() working_dir = find_working_directory()
print(f"Using working directory: {working_dir}") print(f"Using working directory: {working_dir}")
@ -181,6 +199,7 @@ if __name__ == "__main__":
if not json_files_to_process: if not json_files_to_process:
print(f"No se encontraron archivos .json en {input_json_dir}") print(f"No se encontraron archivos .json en {input_json_dir}")
else: else:
print(f"Archivos JSON encontrados para procesar: {len(json_files_to_process)}") print(f"Archivos JSON encontrados para procesar: {len(json_files_to_process)}")
for json_input_filepath in json_files_to_process: for json_input_filepath in json_files_to_process:
@ -194,6 +213,12 @@ if __name__ == "__main__":
with open(json_input_filepath, 'r', encoding='utf-8') as f: with open(json_input_filepath, 'r', encoding='utf-8') as f:
data_from_json = json.load(f) data_from_json = json.load(f)
print(f"Archivo JSON '{current_json_filename}' cargado correctamente.") print(f"Archivo JSON '{current_json_filename}' cargado correctamente.")
except FileNotFoundError:
print(f"Error: No se encontró el archivo JSON de entrada: {json_input_filepath}")
continue
except json.JSONDecodeError:
print(f"Error: El archivo JSON de entrada no es válido: {json_input_filepath}")
continue
except Exception as e: except Exception as e:
print(f"Error al leer el archivo JSON {json_input_filepath}: {e}") print(f"Error al leer el archivo JSON {json_input_filepath}: {e}")
continue continue

View File

@ -1,11 +1,11 @@
# --- x6_refactored.py --- # --- x6.py ---
import json import json
from typing import List, Dict, Any from typing import List, Dict, Any
import openpyxl import openpyxl # For Excel export
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
import sys import sys
import os import os
import glob import glob # Para buscar archivos JSON
script_root = os.path.dirname( script_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))) os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
@ -13,10 +13,6 @@ script_root = os.path.dirname(
sys.path.append(script_root) sys.path.append(script_root)
from backend.script_utils import load_configuration from backend.script_utils import load_configuration
# Importar funciones comunes desde x3
from x3 import flatten_db_structure, format_address_for_display
from x4 import format_data_type_for_source
def find_working_directory(): def find_working_directory():
configs = load_configuration() configs = load_configuration()
working_directory = configs.get("working_directory") working_directory = configs.get("working_directory")
@ -25,79 +21,79 @@ def find_working_directory():
sys.exit(1) sys.exit(1)
return working_directory return working_directory
# format_data_type_for_source (copied from x4.py as it's needed)
def format_data_type_for_source(var_info: Dict[str, Any]) -> str:
base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"]
type_str = ""
if var_info.get("array_dimensions"):
dims_str = ",".join([f"{d['lower_bound']}..{d['upper_bound']}" for d in var_info["array_dimensions"]])
type_str += f"ARRAY [{dims_str}] OF "
type_str += base_type
if var_info["data_type"].upper() == "STRING" and var_info.get("string_length") is not None:
type_str += f"[{var_info['string_length']}]"
return type_str
def generate_excel_table(db_info: Dict[str, Any], excel_filename: str): def generate_excel_table(db_info: Dict[str, Any], excel_filename: str):
""" """
Genera un archivo Excel con documentación del DB usando flatten_db_structure. Generates an Excel file with DB documentation.
""" """
workbook = openpyxl.Workbook() workbook = openpyxl.Workbook()
sheet = workbook.active sheet = workbook.active
db_name_safe = db_info['name'].replace('"', '').replace(' ', '_').replace('/','_') db_name_safe = db_info['name'].replace('"', '').replace(' ', '_').replace('/','_')
sheet.title = f"DB_{db_name_safe}"[:31] # Sheet names tienen límite de longitud sheet.title = f"DB_{db_name_safe}"[:31] # Sheet names have a length limit
# Definir encabezados headers = ["Address", "Name", "Type", "Initial Value", "Actual Value", "Comment"]
headers = ["Address", "Name", "Type", "Size (Bytes)", "Bit Size", "Initial Value", "Actual Value", "Comment"]
for col_num, header in enumerate(headers, 1): for col_num, header in enumerate(headers, 1):
cell = sheet.cell(row=1, column=col_num, value=header) cell = sheet.cell(row=1, column=col_num, value=header)
cell.font = openpyxl.styles.Font(bold=True) cell.font = openpyxl.styles.Font(bold=True)
# Usar flatten_db_structure importado de x3 current_row = 2
flat_vars = flatten_db_structure(db_info) processed_expanded_members = set() # To handle expanded UDT members correctly
# Poblar filas con los datos def flatten_members_for_excel(members: List[Dict[str, Any]], prefix: str = "", base_offset: float = 0.0, is_expansion: bool = False):
for row_num, var in enumerate(flat_vars, 2): nonlocal current_row
# Columna 1: Address for var_idx, var in enumerate(members):
address = var.get("address_display", format_address_for_display(var["byte_offset"], var.get("bit_size", 0))) member_id = f"{prefix}{var['name']}_{var_idx}" # Unique ID for processed check
sheet.cell(row=row_num, column=1, value=address) if is_expansion and member_id in processed_expanded_members:
continue
if is_expansion:
processed_expanded_members.add(member_id)
# Columna 2: Name name_for_display = f"{prefix}{var['name']}"
sheet.cell(row=row_num, column=2, value=var["full_path"])
# Columna 3: Type address = f"{var['byte_offset']:.1f}" if isinstance(var['byte_offset'], float) else str(var['byte_offset'])
data_type = format_data_type_for_source(var) # Adjust address formatting for bits as in markdown generation
sheet.cell(row=row_num, column=3, value=data_type) if var.get("bit_size", 0) > 0 and isinstance(var['byte_offset'], float) and var['byte_offset'] != int(var['byte_offset']):
pass # Already formatted like X.Y
elif var.get("bit_size", 0) > 0 :
address = f"{int(var['byte_offset'])}.0" # Ensure X.0 for bits at the start of a byte
# Columna 4: Size (Bytes) data_type_str = format_data_type_for_source(var)
sheet.cell(row=row_num, column=4, value=var.get("size_in_bytes", 0)) initial_value = str(var.get("initial_value", ""))
actual_value = str(var.get("current_value", ""))
comment = str(var.get("comment", ""))
# Columna 5: Bit Size is_struct_container = var["data_type"].upper() == "STRUCT" and \
sheet.cell(row=row_num, column=5, value=var.get("bit_size", 0) if var.get("bit_size", 0) > 0 else None) not var.get("udt_source_name") and \
var.get("children")
is_udt_instance_container = bool(var.get("udt_source_name")) and var.get("children")
# Columna 6: Initial Value if not is_struct_container and not is_udt_instance_container or var.get("is_udt_expanded_member"):
sheet.cell(row=row_num, column=6, value=var.get("initial_value", "")) row_data = [address, name_for_display, data_type_str, initial_value, actual_value, comment]
for col_num, value in enumerate(row_data, 1):
sheet.cell(row=current_row, column=col_num, value=value)
current_row += 1
# Columna 7: Actual Value if var.get("children"):
sheet.cell(row=row_num, column=7, value=var.get("current_value", "")) flatten_members_for_excel(var["children"],
f"{name_for_display}.",
var['byte_offset'], # Pass the parent's offset
is_expansion=bool(var.get("udt_source_name"))) # Mark if we are expanding a UDT
# Columna 8: Comment flatten_members_for_excel(db_info.get("members", []))
sheet.cell(row=row_num, column=8, value=var.get("comment", ""))
# Crear una segunda hoja para el bloque BEGIN # Auto-size columns for better readability
begin_sheet = workbook.create_sheet(title="BEGIN_Values")
begin_headers = ["Address", "Path", "Value"]
for col_num, header in enumerate(begin_headers, 1):
cell = begin_sheet.cell(row=1, column=col_num, value=header)
cell.font = openpyxl.styles.Font(bold=True)
# Filtrar solo variables con valores actuales para la hoja BEGIN
vars_with_values = [var for var in flat_vars if var.get("current_value") is not None]
# Poblar la hoja BEGIN
for row_num, var in enumerate(vars_with_values, 2):
# Columna 1: Address
begin_sheet.cell(row=row_num, column=1, value=var.get("address_display"))
# Columna 2: Path
begin_sheet.cell(row=row_num, column=2, value=var["full_path"])
# Columna 3: Value
value_str = str(var["current_value"])
if value_str.lower() == "true": value_str = "TRUE"
elif value_str.lower() == "false": value_str = "FALSE"
begin_sheet.cell(row=row_num, column=3, value=value_str)
# Auto-ajustar columnas para mejor legibilidad
for sheet in workbook.worksheets:
for col_idx, column_cells in enumerate(sheet.columns, 1): for col_idx, column_cells in enumerate(sheet.columns, 1):
max_length = 0 max_length = 0
column = get_column_letter(col_idx) column = get_column_letter(col_idx)
@ -107,7 +103,7 @@ def generate_excel_table(db_info: Dict[str, Any], excel_filename: str):
max_length = len(str(cell.value)) max_length = len(str(cell.value))
except: except:
pass pass
adjusted_width = min(max_length + 2, 100) # Limitar a 100 para anchos extremos adjusted_width = (max_length + 2)
sheet.column_dimensions[column].width = adjusted_width sheet.column_dimensions[column].width = adjusted_width
try: try:
@ -141,13 +137,20 @@ def main():
with open(json_input_filepath, 'r', encoding='utf-8') as f: with open(json_input_filepath, 'r', encoding='utf-8') as f:
data_from_json = json.load(f) data_from_json = json.load(f)
print(f"Archivo JSON '{current_json_filename}' cargado correctamente.") print(f"Archivo JSON '{current_json_filename}' cargado correctamente.")
except FileNotFoundError:
print(f"Error: El archivo JSON de entrada '{current_json_filename}' no fue encontrado en {json_input_filepath}.")
continue
except json.JSONDecodeError:
print(f"Error: El archivo JSON '{current_json_filename}' no tiene un formato JSON válido.")
continue
except Exception as e: except Exception as e:
print(f"Error al cargar/leer {current_json_filename}: {e}") print(f"Error al cargar/leer {current_json_filename}: {e}")
continue continue
if data_from_json.get("dbs"): if data_from_json.get("dbs"):
for db_to_document in data_from_json["dbs"]: for db_to_document in data_from_json["dbs"]:
excel_output_filename = os.path.join(documentation_dir, f"{current_json_filename}_{db_to_document['name'].replace('"', '')}.xlsx") # Construir el path completo para el archivo Excel de salida
excel_output_filename = os.path.join(documentation_dir, f"{current_json_filename}.xlsx")
print(f"Generando documentación Excel para DB: '{db_to_document['name']}' (desde {current_json_filename}) -> {excel_output_filename}") print(f"Generando documentación Excel para DB: '{db_to_document['name']}' (desde {current_json_filename}) -> {excel_output_filename}")
try: try:

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,17 @@
[02:56:24] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001... [23:48:43] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001...
[02:56:24] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 [23:48:43] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
[02:56:24] Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json [23:48:43] Found _data file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_data.db
[02:56:24] Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation [23:48:43] Found _format file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_format.db
[02:56:24] Se encontraron 1 pares de archivos para procesar. [23:48:43] Parsing S7 file: db1001_data.db...
[02:56:24] --- Procesando par de archivos --- [23:48:43] Serializing to JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data_data.json
[02:56:24] Data file: db1001_data.db [23:48:43] JSON saved: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_data_data.json
[02:56:24] Format file: db1001_format.db [23:48:43] Parsing S7 file: db1001_format.db...
[02:56:24] Parseando archivo data: db1001_data.db [23:48:43] Serializing to JSON: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_format.json
[02:56:24] Parseando archivo format: db1001_format.db [23:48:43] JSON saved: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_format_format.json
[02:56:24] Archivos JSON generados: db1001_data.json y db1001_format.json [23:48:43] Comparing structure of DB: HMI_Blender_Parameters
[02:56:24] Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format [23:48:43] La estructura del DB 'HMI_Blender_Parameters' es compatible.
[02:56:24] Los archivos son compatibles. Creando el archivo _updated... [23:48:43] All DB structures are compatible. Proceeding to generate _updated file.
[02:56:24] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json [23:48:43] INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'.
[02:56:25] Archivo de comparación Excel generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_comparison.xlsx [23:48:43] Successfully generated _updated S7 file: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db
[02:56:25] Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md [23:48:43] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:00.106052.
[02:56:25] Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt [23:48:43] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt
[02:56:25] Archivo S7 copiado a: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db
[02:56:25] --- Proceso completado ---
[02:56:25] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:00.761362.
[02:56:25] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt