Tratando de lograr el el excel de comparacion de x7 funcione correctamente

This commit is contained in:
Miguel 2025-05-18 13:25:46 +02:00
parent 0f162377cd
commit 89451abd15
8 changed files with 514 additions and 425 deletions

View File

@ -1,15 +1,15 @@
--- Log de Ejecución: x4.py ---
Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:13:16
Fin: 2025-05-18 02:13:16
Duración: 0:00:00.162328
Inicio: 2025-05-18 13:15:28
Fin: 2025-05-18 13:15:28
Duración: 0:00:00.188819
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
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
Archivos JSON encontrados para procesar: 2
Archivos JSON encontrados para procesar: 3
--- Procesando archivo JSON: db1001_data.json ---
Archivo JSON 'db1001_data.json' cargado correctamente.
@ -21,6 +21,11 @@ Archivo JSON 'db1001_format.json' cargado correctamente.
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
--- Procesando archivo JSON: db1001_updated.json ---
Archivo JSON 'db1001_updated.json' cargado correctamente.
Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt
Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md
--- Proceso de generación de documentación completado ---
--- ERRORES (STDERR) ---

View File

@ -1,25 +1,30 @@
--- Log de Ejecución: x6.py ---
Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:20:21
Fin: 2025-05-18 02:20:22
Duración: 0:00:01.130771
Inicio: 2025-05-18 12:06:45
Fin: 2025-05-18 12:06:46
Duración: 0:00:00.564906
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
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
Archivos JSON encontrados para procesar: 2
Archivos JSON encontrados para procesar: 3
--- Procesando archivo JSON para Excel: db1001_data.json ---
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
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
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.xlsx
--- Procesando archivo JSON para Excel: db1001_format.json ---
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
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
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.xlsx
--- Procesando archivo JSON para Excel: db1001_updated.json ---
Archivo JSON 'db1001_updated.json' cargado correctamente.
Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_updated.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.json.xlsx
Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.json.xlsx
--- Proceso de generación de documentación Excel completado ---

View File

@ -1,9 +1,9 @@
--- Log de Ejecución: x7_value_updater.py ---
Grupo: S7_DB_Utils
Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
Inicio: 2025-05-18 02:56:24
Fin: 2025-05-18 02:56:25
Duración: 0:00:00.761362
Inicio: 2025-05-18 13:21:37
Fin: 2025-05-18 13:21:38
Duración: 0:00:01.043746
Estado: SUCCESS (Código de Salida: 0)
--- SALIDA ESTÁNDAR (STDOUT) ---
@ -22,7 +22,7 @@ Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data,
Los archivos son compatibles. Creando el archivo _updated...
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
Comparison Excel file generated: 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

View File

@ -30,19 +30,19 @@
"hidden": false
},
"x5.py": {
"display_name": "05: Generar Descripción MD del JSON",
"display_name": "05: Generar Descripción MD",
"short_description": "Genera documentación descriptiva de archivos JSON en Markdown.",
"long_description": "Crea un archivo Markdown que documenta la estructura interna de los archivos JSON (generados por x3.py). Detalla UDTs y DBs, incluyendo sus miembros, offsets, tipos de datos, y valores iniciales/actuales, facilitando la comprensión del contenido del JSON.",
"hidden": false
},
"x6.py": {
"display_name": "06: Generar Excel desde JSON",
"display_name": "06: Generar Excel",
"short_description": "Genera documentación de DBs en formato Excel (.xlsx) desde JSON.",
"long_description": "Procesa archivos JSON (generados por x3.py) y exporta la información de cada Bloque de Datos (DB) a un archivo Excel (.xlsx). La hoja de cálculo incluye detalles como direcciones, nombres de variables, tipos de datos, valores iniciales, valores actuales y comentarios.",
"hidden": false
},
"x7_value_updater.py": {
"display_name": "07: Actualizar Valores de DB (JSON)",
"display_name": "07: Actualizar Valores data+format->updated",
"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",
"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.",
"hidden": false

View File

@ -48,6 +48,7 @@ class VariableInfo:
children: List['VariableInfo'] = field(default_factory=list)
is_udt_expanded_member: bool = False
current_element_values: Optional[Dict[str, str]] = None
element_type: str = "SIMPLE_VAR" # New field with default value
@dataclass
class UdtInfo:
@ -191,6 +192,17 @@ class S7Parser:
data_type=clean_data_type,
byte_offset=0, size_in_bytes=0,
udt_source_name=udt_source_name_val)
# Set element_type based on what we know about the variable
if var_data['arraydims']:
var_info.element_type = "ARRAY"
elif clean_data_type.upper() == "STRUCT":
var_info.element_type = "STRUCT"
elif udt_source_name_val:
var_info.element_type = "UDT_INSTANCE"
else:
var_info.element_type = "SIMPLE_VAR"
if var_data.get('initval'): var_info.initial_value = var_data['initval'].strip()
if line_comment: var_info.comment = line_comment
num_array_elements = 1
@ -635,71 +647,84 @@ def calculate_array_element_offset(var: VariableInfo, indices_str: str) -> float
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).
Function that completely flattens a DB/UDT structure,
expanding all nested variables, UDTs, and array elements.
Ensures strict ordering by offset (byte.bit).
Returns:
List[Dict]: Lista de variables aplanadas con todos sus atributos
y un path completo, ordenada por offset estricto.
List[Dict]: Flattened list of variables with all attributes
and a complete path, strictly ordered by offset.
"""
flat_variables = []
processed_ids = set() # Para evitar duplicados
processed_ids = set() # To avoid duplicates
def process_variable(var: Dict[str, Any], path_prefix: str = "", is_expansion: bool = False):
# Identificador único para esta variable en este contexto
# Unique identifier for this variable in this context
var_id = f"{path_prefix}{var['name']}_{var['byte_offset']}"
# Evitar procesar duplicados (como miembros expandidos de UDTs)
# Avoid processing duplicates (like expanded UDT members)
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
# Create copy of the variable with complete path
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
flat_var["is_array_element"] = False # Default is not an array element
# Determinar si es array con valores específicos
# Preserve or infer element_type
if "element_type" not in flat_var:
# Infer type for backward compatibility
if var.get("array_dimensions"):
flat_var["element_type"] = "ARRAY"
elif var.get("children") and var["data_type"].upper() == "STRUCT":
flat_var["element_type"] = "STRUCT"
elif var.get("udt_source_name"):
flat_var["element_type"] = "UDT_INSTANCE"
else:
flat_var["element_type"] = "SIMPLE_VAR"
# Determine if it's an array with specific values
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 an array with specific values, add the base variable
if not has_array_values:
# Asegurarse de que el offset esté en el formato correcto
# Ensure the offset is in the correct format
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 it's an array with specific values, expand each element as individual variable
if has_array_values:
for idx, element_data in var.get("current_element_values", {}).items():
# Extraer valor y offset del elemento
# Extract value and offset of the element
if isinstance(element_data, dict) and "value" in element_data and "offset" in element_data:
# Nuevo formato con offset calculado
# New format with calculated offset
value = element_data["value"]
element_offset = element_data["offset"]
else:
# Compatibilidad con formato antiguo
# Compatibility with old format
value = element_data
element_offset = var["byte_offset"] # Offset base
element_offset = var["byte_offset"] # Base offset
# Crear una entrada por cada elemento del array
# Create an entry for each array element
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["byte_offset"] = element_offset # Use calculated offset
array_element["address_display"] = format_address_for_display(element_offset, var.get("bit_size", 0))
array_element["element_type"] = "ARRAY_ELEMENT"
# Eliminar current_element_values para evitar redundancia
# Remove current_element_values to avoid redundancy
if "current_element_values" in array_element:
del array_element["current_element_values"]
flat_variables.append(array_element)
# Procesar recursivamente todos los hijos
# Process all children recursively
if var.get("children"):
for child in var.get("children", []):
process_variable(
@ -708,11 +733,11 @@ def flatten_db_structure(db_info: Dict[str, Any]) -> List[Dict[str, Any]]:
is_expansion=bool(var.get("udt_source_name"))
)
# Procesar todos los miembros desde el nivel superior
# Process all members from the top level
for member in db_info.get("members", []):
process_variable(member)
# Ordenar estrictamente por offset byte.bit
# Sort strictly by offset byte.bit
flat_variables.sort(key=lambda x: (
int(x["byte_offset"]),
int(round((x["byte_offset"] - int(x["byte_offset"])) * 10))

View File

@ -147,7 +147,8 @@ def main():
if data_from_json.get("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")
## excel_output_filename = os.path.join(documentation_dir, f"{current_json_filename}_{db_to_document['name'].replace('"', '')}.xlsx")
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}")
try:

View File

@ -132,174 +132,6 @@ def compare_structures_by_offset(data_vars: List[Dict], format_vars: List[Dict])
return len(issues) == 0, issues
def create_updated_json(data_json: Dict, format_json: Dict) -> Dict:
"""
Crea JSON actualizado basado en la estructura de _format con valores de _data.
Utiliza offset como clave principal para encontrar variables correspondientes.
Reporta errores si no se encuentra un offset correspondiente.
"""
# Copia profunda de format_json para no modificar el original
updated_json = copy.deepcopy(format_json)
# Procesar cada DB
for db_idx, format_db in enumerate(format_json.get("dbs", [])):
# Buscar el DB correspondiente en data_json
data_db = next((db for db in data_json.get("dbs", []) if db["name"] == format_db["name"]), None)
if not data_db:
print(f"Error: No se encontró DB '{format_db['name']}' en data_json")
continue # No hay DB correspondiente en data_json
# Aplanar variables de ambos DBs
flat_data_vars = flatten_db_structure(data_db)
flat_format_vars = flatten_db_structure(format_db)
# Crear mapa de offset a variable para data
data_by_offset = {var["byte_offset"]: var for var in flat_data_vars}
# Para cada variable en format, buscar su correspondiente en data por offset
for format_var in flat_format_vars:
offset = format_var["byte_offset"]
path = format_var["full_path"]
# Buscar la variable correspondiente en data_json por offset
if offset in data_by_offset:
data_var = data_by_offset[offset]
# Encontrar la variable original en la estructura jerárquica
path_parts = format_var["full_path"].split('.')
current_node = updated_json["dbs"][db_idx]
# Variable para rastrear si se encontró la ruta
path_found = True
# Navegar la jerarquía hasta encontrar el nodo padre
for i in range(len(path_parts) - 1):
if "members" in current_node:
# Buscar el miembro correspondiente
member_name = path_parts[i]
matching_members = [m for m in current_node["members"] if m["name"] == member_name]
if matching_members:
current_node = matching_members[0]
else:
print(f"Error: No se encontró el miembro '{member_name}' en la ruta '{path}'")
path_found = False
break # No se encontró la ruta
elif "children" in current_node:
# Buscar el hijo correspondiente
child_name = path_parts[i]
matching_children = [c for c in current_node["children"] if c["name"] == child_name]
if matching_children:
current_node = matching_children[0]
else:
print(f"Error: No se encontró el hijo '{child_name}' en la ruta '{path}'")
path_found = False
break # No se encontró la ruta
else:
print(f"Error: No se puede navegar más en la ruta '{path}', nodo actual no tiene members ni children")
path_found = False
break # No se puede navegar más
# Si encontramos el nodo padre, actualizar el hijo
if path_found and ("members" in current_node or "children" in current_node):
target_list = current_node.get("members", current_node.get("children", []))
target_name = path_parts[-1]
# Si es un elemento de array, extraer el nombre base y el índice
if '[' in target_name and ']' in target_name:
base_name = target_name.split('[')[0]
index_str = target_name[target_name.find('[')+1:target_name.find(']')]
# Buscar el array base
array_var = next((var for var in target_list if var["name"] == base_name), None)
if array_var:
# Asegurarse que existe current_element_values
if "current_element_values" not in array_var:
array_var["current_element_values"] = {}
# Copiar el valor del elemento del array
if "current_value" in data_var:
array_var["current_element_values"][index_str] = {
"value": data_var["current_value"],
"offset": data_var["byte_offset"]
}
else:
# Buscar la variable a actualizar
target_var_found = False
for target_var in target_list:
if target_var["name"] == target_name:
target_var_found = True
# Limpiar y copiar initial_value si existe
if "initial_value" in target_var:
del target_var["initial_value"]
if "initial_value" in data_var and data_var["initial_value"] is not None:
target_var["initial_value"] = data_var["initial_value"]
# Limpiar y copiar current_value si existe
if "current_value" in target_var:
del target_var["current_value"]
if "current_value" in data_var and data_var["current_value"] is not None:
target_var["current_value"] = data_var["current_value"]
# Limpiar y copiar current_element_values si existe
if "current_element_values" in target_var:
del target_var["current_element_values"]
if "current_element_values" in data_var and data_var["current_element_values"]:
target_var["current_element_values"] = copy.deepcopy(data_var["current_element_values"])
break
if not target_var_found and not ('[' in target_name and ']' in target_name):
print(f"Error: No se encontró la variable '{target_name}' en la ruta '{path}'")
else:
# El offset no existe en data_json, reportar error
print(f"Error: Offset {offset} (para '{path}') no encontrado en los datos source (_data)")
# Eliminar valores si es una variable que no es elemento de array
if '[' not in path or ']' not in path:
# Encontrar la variable original en la estructura jerárquica
path_parts = path.split('.')
current_node = updated_json["dbs"][db_idx]
# Navegar hasta el nodo padre para limpiar valores
path_found = True
for i in range(len(path_parts) - 1):
if "members" in current_node:
member_name = path_parts[i]
matching_members = [m for m in current_node["members"] if m["name"] == member_name]
if matching_members:
current_node = matching_members[0]
else:
path_found = False
break
elif "children" in current_node:
child_name = path_parts[i]
matching_children = [c for c in current_node["children"] if c["name"] == child_name]
if matching_children:
current_node = matching_children[0]
else:
path_found = False
break
else:
path_found = False
break
if path_found and ("members" in current_node or "children" in current_node):
target_list = current_node.get("members", current_node.get("children", []))
target_name = path_parts[-1]
for target_var in target_list:
if target_var["name"] == target_name:
# Eliminar valores iniciales y actuales
if "initial_value" in target_var:
del target_var["initial_value"]
if "current_value" in target_var:
del target_var["current_value"]
if "current_element_values" in target_var:
del target_var["current_element_values"]
break
return updated_json
def process_updated_json(updated_json: Dict, updated_json_path: str, working_dir: str, documentation_dir: str, original_format_file: str):
"""
@ -347,212 +179,366 @@ def process_updated_json(updated_json: Dict, updated_json_path: str, working_dir
except Exception as e:
print(f"Error al generar archivo S7 para {base_name}: {e}")
def create_updated_json(data_json: Dict, format_json: Dict) -> Dict:
"""
Creates an updated JSON based on the structure of _format with values from _data.
Uses offset as the primary key for finding corresponding variables.
Reports errors if a corresponding offset is not found.
"""
# Deep copy of format_json to avoid modifying the original
updated_json = copy.deepcopy(format_json)
# Process each DB
for db_idx, format_db in enumerate(format_json.get("dbs", [])):
# Find corresponding DB in data_json
data_db = next((db for db in data_json.get("dbs", []) if db["name"] == format_db["name"]), None)
if not data_db:
print(f"Error: DB '{format_db['name']}' not found in data_json")
continue # No corresponding DB in data_json
# Flatten variables from both DBs
flat_data_vars = flatten_db_structure(data_db)
flat_format_vars = flatten_db_structure(format_db)
# Create offset to variable map for data - ONLY include usable variables (SIMPLE_VAR and ARRAY_ELEMENT)
# This is the key fix: filter by element_type to avoid matching STRUCT and other non-value types
data_by_offset = {
var["byte_offset"]: var for var in flat_data_vars
if var.get("element_type") in ["SIMPLE_VAR", "ARRAY_ELEMENT"]
}
# For each variable in format, find its corresponding in data by offset
for format_var in flat_format_vars:
# Only process variables and array elements, not structures or UDT instances
if format_var.get("element_type") not in ["SIMPLE_VAR", "ARRAY_ELEMENT"]:
continue
offset = format_var["byte_offset"]
path = format_var["full_path"]
# Find the corresponding variable in data_json by offset
if offset in data_by_offset:
data_var = data_by_offset[offset]
# Even though we've filtered the data variables, double-check element types
format_element_type = format_var.get("element_type")
data_element_type = data_var.get("element_type")
# Only copy values if element types are compatible
if format_element_type == data_element_type or (
format_element_type in ["SIMPLE_VAR", "ARRAY_ELEMENT"] and
data_element_type in ["SIMPLE_VAR", "ARRAY_ELEMENT"]
):
# Find the original variable in the hierarchical structure
path_parts = format_var["full_path"].split('.')
current_node = updated_json["dbs"][db_idx]
# Variable to track if the path was found
path_found = True
# Navigate the hierarchy to find the parent node
for i in range(len(path_parts) - 1):
if "members" in current_node:
# Find the corresponding member
member_name = path_parts[i]
matching_members = [m for m in current_node["members"] if m["name"] == member_name]
if matching_members:
current_node = matching_members[0]
else:
print(f"Error: Member '{member_name}' not found in path '{path}'")
path_found = False
break # Path not found
elif "children" in current_node:
# Find the corresponding child
child_name = path_parts[i]
matching_children = [c for c in current_node["children"] if c["name"] == child_name]
if matching_children:
current_node = matching_children[0]
else:
print(f"Error: Child '{child_name}' not found in path '{path}'")
path_found = False
break # Path not found
else:
print(f"Error: Cannot navigate further in path '{path}', current node has no members or children")
path_found = False
break # Cannot navigate further
# If parent node found, update the child
if path_found and ("members" in current_node or "children" in current_node):
target_list = current_node.get("members", current_node.get("children", []))
target_name = path_parts[-1]
# If it's an array element, extract the base name and index
if '[' in target_name and ']' in target_name:
base_name = target_name.split('[')[0]
index_str = target_name[target_name.find('[')+1:target_name.find(']')]
# Find the base array
array_var = next((var for var in target_list if var["name"] == base_name), None)
if array_var:
# Ensure current_element_values exists
if "current_element_values" not in array_var:
array_var["current_element_values"] = {}
# Copy the array element value
if "current_value" in data_var:
array_var["current_element_values"][index_str] = {
"value": data_var["current_value"],
"offset": data_var["byte_offset"]
}
else:
# Find the variable to update
target_var_found = False
for target_var in target_list:
if target_var["name"] == target_name:
target_var_found = True
# Clean and copy initial_value if exists
if "initial_value" in target_var:
del target_var["initial_value"]
if "initial_value" in data_var and data_var["initial_value"] is not None:
target_var["initial_value"] = data_var["initial_value"]
# Clean and copy current_value if exists
if "current_value" in target_var:
del target_var["current_value"]
if "current_value" in data_var and data_var["current_value"] is not None:
target_var["current_value"] = data_var["current_value"]
# Clean and copy current_element_values if exists
if "current_element_values" in target_var:
del target_var["current_element_values"]
if "current_element_values" in data_var and data_var["current_element_values"]:
target_var["current_element_values"] = copy.deepcopy(data_var["current_element_values"])
break
if not target_var_found and not ('[' in target_name and ']' in target_name):
print(f"Error: Variable '{target_name}' not found in path '{path}'")
else:
print(f"Warning: Element types don't match at offset {offset} for '{path}': {format_element_type} vs {data_element_type}")
else:
# Offset not found in data_json, report error
print(f"Error: Offset {offset} (for '{path}') not found in source data (_data)")
# Clear values if it's not an array element
if '[' not in path or ']' not in path:
# Find the original variable in the hierarchical structure
path_parts = path.split('.')
current_node = updated_json["dbs"][db_idx]
# Navigate to the parent node to clean values
path_found = True
for i in range(len(path_parts) - 1):
if "members" in current_node:
member_name = path_parts[i]
matching_members = [m for m in current_node["members"] if m["name"] == member_name]
if matching_members:
current_node = matching_members[0]
else:
path_found = False
break
elif "children" in current_node:
child_name = path_parts[i]
matching_children = [c for c in current_node["children"] if c["name"] == child_name]
if matching_children:
current_node = matching_children[0]
else:
path_found = False
break
else:
path_found = False
break
if path_found and ("members" in current_node or "children" in current_node):
target_list = current_node.get("members", current_node.get("children", []))
target_name = path_parts[-1]
for target_var in target_list:
if target_var["name"] == target_name:
# Remove initial and current values
if "initial_value" in target_var:
del target_var["initial_value"]
if "current_value" in target_var:
del target_var["current_value"]
if "current_element_values" in target_var:
del target_var["current_element_values"]
break
return updated_json
def generate_comparison_excel(format_json: Dict, data_json: Dict, updated_json: Dict, excel_filename: str):
"""
Genera un archivo Excel con dos hojas que comparan los valores iniciales y actuales
entre los archivos format_json, data_json y updated_json.
Filtra STRUCTs y solo compara variables con valores reales.
Generates a comprehensive Excel file comparing values between format, data and updated JSONs.
Uses flatten_db_structure and matches by offset, leveraging element_type for better filtering.
Args:
format_json: JSON con la estructura y nombres de formato
data_json: JSON con los datos source
updated_json: JSON con los datos actualizados
excel_filename: Ruta del archivo Excel a generar
format_json: JSON with the structure and names from format
data_json: JSON with the source data
updated_json: JSON with the updated data
excel_filename: Path to the Excel file to generate
"""
import openpyxl
from openpyxl.utils import get_column_letter
from openpyxl.styles import PatternFill, Font
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
# Crear un nuevo libro de Excel
# Create a new Excel workbook
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = "Value_Comparison"
# Definir estilos para resaltar diferencias
diff_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") # Amarillo
# Define styles
diff_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
type_mismatch_fill = PatternFill(start_color="FF9999", end_color="FF9999", fill_type="solid") # Light red
header_font = Font(bold=True)
header_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin'))
# Procesar cada DB
# Set up headers
headers = ["Address", "Name", "Type", "Element Type",
"Format Initial", "Data Initial", "Updated Initial",
"Format Current", "Data Current", "Updated Current",
"Type Match", "Value Differences"]
for col_num, header in enumerate(headers, 1):
cell = sheet.cell(row=1, column=col_num, value=header)
cell.font = header_font
cell.fill = header_fill
cell.border = thin_border
cell.alignment = Alignment(horizontal='center')
# Freeze top row
sheet.freeze_panes = "A2"
current_row = 2
# Process each DB
for db_idx, format_db in enumerate(format_json.get("dbs", [])):
# Buscar los DBs correspondientes
db_name = format_db["name"]
data_db = next((db for db in data_json.get("dbs", []) if db["name"] == db_name), None)
updated_db = next((db for db in updated_json.get("dbs", []) if db["name"] == db_name), None)
if not data_db or not updated_db:
print(f"Error: No se encontró el DB '{db_name}' en alguno de los archivos JSON")
print(f"Error: DB '{db_name}' not found in one of the JSON files")
continue
# Crear hojas para valores iniciales y actuales para este DB
initial_sheet = workbook.active if db_idx == 0 else workbook.create_sheet()
initial_sheet.title = f"{db_name}_Initial"[:31] # Limitar longitud del nombre de hoja
# Add DB name as section header with merged cells
sheet.merge_cells(f'A{current_row}:L{current_row}')
header_cell = sheet.cell(row=current_row, column=1, value=f"DB: {db_name}")
header_cell.font = Font(bold=True, size=12)
header_cell.fill = PatternFill(start_color="CCCCFF", end_color="CCCCFF", fill_type="solid") # Light blue
header_cell.alignment = Alignment(horizontal='center')
current_row += 1
current_sheet = workbook.create_sheet()
current_sheet.title = f"{db_name}_Current"[:31]
# Aplanar variables de los tres DBs
# Get flattened variables from all sources
flat_format_vars = flatten_db_structure(format_db)
flat_data_vars = flatten_db_structure(data_db)
flat_updated_vars = flatten_db_structure(updated_db)
# Filtrar STRUCTs - solo trabajamos con variables que tienen valores reales
flat_format_vars = [var for var in flat_format_vars
if var["data_type"].upper() != "STRUCT" and not var.get("children")]
# Crear mapas de offset a variable para búsqueda rápida
data_by_offset = {var["byte_offset"]: var for var in flat_data_vars
if var["data_type"].upper() != "STRUCT" and not var.get("children")}
updated_by_offset = {var["byte_offset"]: var for var in flat_updated_vars
if var["data_type"].upper() != "STRUCT" and not var.get("children")}
# Configurar encabezados para la hoja de valores iniciales
headers_initial = ["Address", "Name", "Type", "Format Initial", "Data Initial", "Updated Initial", "Difference"]
for col_num, header in enumerate(headers_initial, 1):
cell = initial_sheet.cell(row=1, column=col_num, value=header)
cell.font = header_font
# Configurar encabezados para la hoja de valores actuales
headers_current = ["Address", "Name", "Type", "Format Current", "Data Current", "Updated Current", "Difference"]
for col_num, header in enumerate(headers_current, 1):
cell = current_sheet.cell(row=1, column=col_num, value=header)
cell.font = header_font
# Llenar las hojas con datos
initial_row = 2
current_row = 2
# Create maps by offset for quick lookup
data_by_offset = {var["byte_offset"]: var for var in flat_data_vars}
updated_by_offset = {var["byte_offset"]: var for var in flat_updated_vars}
# Process each variable from format_json
for format_var in flat_format_vars:
# Skip certain types based on element_type
element_type = format_var.get("element_type", "UNKNOWN")
# Skip STRUCT types with no values, but include ARRAY and UDT_INSTANCE types
if element_type == "STRUCT" and not format_var.get("children"):
continue
offset = format_var["byte_offset"]
path = format_var["full_path"]
data_type = format_data_type_for_source(format_var)
address = format_var.get("address_display", format_address_for_display(offset, format_var.get("bit_size", 0)))
# Obtener variables correspondientes por offset
# Find corresponding variables by offset
data_var = data_by_offset.get(offset)
updated_var = updated_by_offset.get(offset)
# Procesar valores iniciales (solo si la variable puede tener initial_value)
format_initial = format_var.get("initial_value", "")
data_initial = data_var.get("initial_value", "") if data_var else ""
updated_initial = updated_var.get("initial_value", "") if updated_var else ""
# Compare element types
data_element_type = data_var.get("element_type", "MISSING") if data_var else "MISSING"
updated_element_type = updated_var.get("element_type", "MISSING") if updated_var else "MISSING"
# Solo incluir en la hoja de valores iniciales si al menos uno tiene valor inicial
if format_initial or data_initial or updated_initial:
# Determinar si hay diferencias en valores iniciales
# Determine type compatibility
type_match = "Yes"
if data_var and element_type != data_element_type:
# Check for compatible types
if (element_type in ["SIMPLE_VAR", "ARRAY_ELEMENT"] and
data_element_type in ["SIMPLE_VAR", "ARRAY_ELEMENT"]):
type_match = "Compatible"
else:
type_match = "No"
elif not data_var:
type_match = "Missing"
# Get values (with empty string defaults)
format_initial = str(format_var.get("initial_value", ""))
data_initial = str(data_var.get("initial_value", "")) if data_var else ""
updated_initial = str(updated_var.get("initial_value", "")) if updated_var else ""
format_current = str(format_var.get("current_value", ""))
data_current = str(data_var.get("current_value", "")) if data_var else ""
updated_current = str(updated_var.get("current_value", "")) if updated_var else ""
# Check for differences
has_initial_diff = (format_initial != data_initial or
format_initial != updated_initial or
data_initial != updated_initial)
# Escribir datos de valores iniciales
initial_sheet.cell(row=initial_row, column=1, value=address)
initial_sheet.cell(row=initial_row, column=2, value=path)
initial_sheet.cell(row=initial_row, column=3, value=data_type)
initial_sheet.cell(row=initial_row, column=4, value=str(format_initial))
initial_sheet.cell(row=initial_row, column=5, value=str(data_initial))
initial_sheet.cell(row=initial_row, column=6, value=str(updated_initial))
# Resaltar diferencias en valores iniciales
if has_initial_diff:
initial_sheet.cell(row=initial_row, column=7, value="")
for col in range(4, 7):
initial_sheet.cell(row=initial_row, column=col).fill = diff_fill
else:
initial_sheet.cell(row=initial_row, column=7, value="No")
initial_row += 1
# Procesar valores actuales
format_current = format_var.get("current_value", "")
data_current = data_var.get("current_value", "") if data_var else ""
updated_current = updated_var.get("current_value", "") if updated_var else ""
# Solo incluir en la hoja de valores actuales si al menos uno tiene valor actual
if format_current or data_current or updated_current:
# Determinar si hay diferencias en valores actuales
has_current_diff = (format_current != data_current or
format_current != updated_current or
data_current != updated_current)
# Escribir datos de valores actuales
current_sheet.cell(row=current_row, column=1, value=address)
current_sheet.cell(row=current_row, column=2, value=path)
current_sheet.cell(row=current_row, column=3, value=data_type)
current_sheet.cell(row=current_row, column=4, value=str(format_current))
current_sheet.cell(row=current_row, column=5, value=str(data_current))
current_sheet.cell(row=current_row, column=6, value=str(updated_current))
# Resaltar diferencias en valores actuales
# Create detailed difference description
diff_desc = []
if has_initial_diff:
diff_desc.append("Initial values differ")
if has_current_diff:
current_sheet.cell(row=current_row, column=7, value="")
for col in range(4, 7):
current_sheet.cell(row=current_row, column=col).fill = diff_fill
else:
current_sheet.cell(row=current_row, column=7, value="No")
diff_desc.append("Current values differ")
if not diff_desc:
diff_desc.append("None")
# Write data
sheet.cell(row=current_row, column=1, value=address)
sheet.cell(row=current_row, column=2, value=path)
sheet.cell(row=current_row, column=3, value=data_type)
sheet.cell(row=current_row, column=4, value=element_type)
sheet.cell(row=current_row, column=5, value=format_initial)
sheet.cell(row=current_row, column=6, value=data_initial)
sheet.cell(row=current_row, column=7, value=updated_initial)
sheet.cell(row=current_row, column=8, value=format_current)
sheet.cell(row=current_row, column=9, value=data_current)
sheet.cell(row=current_row, column=10, value=updated_current)
sheet.cell(row=current_row, column=11, value=type_match)
sheet.cell(row=current_row, column=12, value=", ".join(diff_desc))
# Add borders to all cells
for col in range(1, 13):
sheet.cell(row=current_row, column=col).border = thin_border
# Highlight differences
if has_initial_diff:
for col in range(5, 8):
sheet.cell(row=current_row, column=col).fill = diff_fill
if has_current_diff:
for col in range(8, 11):
sheet.cell(row=current_row, column=col).fill = diff_fill
# Highlight type mismatches
if type_match == "No" or type_match == "Missing":
sheet.cell(row=current_row, column=11).fill = type_mismatch_fill
current_row += 1
# Si es un array, procesamos también sus elementos
if format_var.get("current_element_values") or (data_var and data_var.get("current_element_values")) or (updated_var and updated_var.get("current_element_values")):
format_elements = format_var.get("current_element_values", {})
data_elements = data_var.get("current_element_values", {}) if data_var else {}
updated_elements = updated_var.get("current_element_values", {}) if updated_var else {}
# Add filter to headers
sheet.auto_filter.ref = f"A1:L{current_row-1}"
# Unir todos los índices disponibles
all_indices = set(list(format_elements.keys()) +
list(data_elements.keys()) +
list(updated_elements.keys()))
# Ordenar índices numéricamente
sorted_indices = sorted(all_indices, key=lambda x: [int(i) for i in x.split(',')]) if all_indices else []
for idx in sorted_indices:
elem_path = f"{path}[{idx}]"
# Valores actuales para elementos de array
format_elem_val = ""
if idx in format_elements:
if isinstance(format_elements[idx], dict) and "value" in format_elements[idx]:
format_elem_val = format_elements[idx]["value"]
else:
format_elem_val = format_elements[idx]
data_elem_val = ""
if idx in data_elements:
if isinstance(data_elements[idx], dict) and "value" in data_elements[idx]:
data_elem_val = data_elements[idx]["value"]
else:
data_elem_val = data_elements[idx]
updated_elem_val = ""
if idx in updated_elements:
if isinstance(updated_elements[idx], dict) and "value" in updated_elements[idx]:
updated_elem_val = updated_elements[idx]["value"]
else:
updated_elem_val = updated_elements[idx]
# Determinar si hay diferencias
has_elem_diff = (str(format_elem_val) != str(data_elem_val) or
str(format_elem_val) != str(updated_elem_val) or
str(data_elem_val) != str(updated_elem_val))
# Escribir datos de elementos de array (solo en hoja de valores actuales)
current_sheet.cell(row=current_row, column=1, value=address)
current_sheet.cell(row=current_row, column=2, value=elem_path)
current_sheet.cell(row=current_row, column=3, value=data_type.replace("ARRAY", "").strip())
current_sheet.cell(row=current_row, column=4, value=str(format_elem_val))
current_sheet.cell(row=current_row, column=5, value=str(data_elem_val))
current_sheet.cell(row=current_row, column=6, value=str(updated_elem_val))
# Resaltar diferencias
if has_elem_diff:
current_sheet.cell(row=current_row, column=7, value="")
for col in range(4, 7):
current_sheet.cell(row=current_row, column=col).fill = diff_fill
else:
current_sheet.cell(row=current_row, column=7, value="No")
current_row += 1
# Auto-ajustar anchos de columna
for sheet in [initial_sheet, current_sheet]:
# Auto-adjust column widths
for col_idx, column_cells in enumerate(sheet.columns, 1):
max_length = 0
column = get_column_letter(col_idx)
@ -562,15 +548,82 @@ def generate_comparison_excel(format_json: Dict, data_json: Dict, updated_json:
max_length = len(str(cell.value))
except:
pass
adjusted_width = min(max_length + 2, 100) # Limitar ancho máximo
adjusted_width = min(max_length + 2, 100) # Limit maximum width
sheet.column_dimensions[column].width = adjusted_width
# Guardar el archivo Excel
# Add a summary sheet
summary_sheet = workbook.create_sheet(title="Summary")
summary_sheet.column_dimensions['A'].width = 30
summary_sheet.column_dimensions['B'].width = 15
summary_sheet.column_dimensions['C'].width = 50
# Add header to summary
summary_headers = ["Database", "Item Count", "Notes"]
for col_num, header in enumerate(summary_headers, 1):
cell = summary_sheet.cell(row=1, column=col_num, value=header)
cell.font = header_font
cell.fill = header_fill
# Add summary data
summary_row = 2
for db_idx, format_db in enumerate(format_json.get("dbs", [])):
db_name = format_db["name"]
data_db = next((db for db in data_json.get("dbs", []) if db["name"] == db_name), None)
updated_db = next((db for db in updated_json.get("dbs", []) if db["name"] == db_name), None)
if not data_db or not updated_db:
continue
flat_format_vars = flatten_db_structure(format_db)
flat_data_vars = flatten_db_structure(data_db)
# Count by element type
format_type_counts = {}
for var in flat_format_vars:
element_type = var.get("element_type", "UNKNOWN")
format_type_counts[element_type] = format_type_counts.get(element_type, 0) + 1
# Count value differences
data_by_offset = {var["byte_offset"]: var for var in flat_data_vars}
diff_count = 0
type_mismatch_count = 0
for format_var in flat_format_vars:
offset = format_var["byte_offset"]
data_var = data_by_offset.get(offset)
if data_var:
# Check for type mismatch
if format_var.get("element_type") != data_var.get("element_type"):
type_mismatch_count += 1
# Check for value differences
format_initial = str(format_var.get("initial_value", ""))
data_initial = str(data_var.get("initial_value", ""))
format_current = str(format_var.get("current_value", ""))
data_current = str(data_var.get("current_value", ""))
if format_initial != data_initial or format_current != data_current:
diff_count += 1
# Write to summary
summary_sheet.cell(row=summary_row, column=1, value=db_name)
summary_sheet.cell(row=summary_row, column=2, value=len(flat_format_vars))
notes = []
for element_type, count in format_type_counts.items():
notes.append(f"{element_type}: {count}")
notes.append(f"Value differences: {diff_count}")
notes.append(f"Type mismatches: {type_mismatch_count}")
summary_sheet.cell(row=summary_row, column=3, value=", ".join(notes))
summary_row += 1
try:
workbook.save(excel_filename)
print(f"Archivo de comparación Excel generado: {excel_filename}")
print(f"Comparison Excel file generated: {excel_filename}")
except Exception as e:
print(f"Error al escribir el archivo Excel {excel_filename}: {e}")
print(f"Error writing Excel file {excel_filename}: {e}")
def main():
working_dir = find_working_directory()

View File

@ -1,21 +1,21 @@
[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...
[02:56:24] 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
[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
[02:56:24] Se encontraron 1 pares de archivos para procesar.
[02:56:24] --- Procesando par de archivos ---
[02:56:24] Data file: db1001_data.db
[02:56:24] Format file: db1001_format.db
[02:56:24] Parseando archivo data: db1001_data.db
[02:56:24] Parseando archivo format: db1001_format.db
[02:56:24] Archivos JSON generados: db1001_data.json y db1001_format.json
[02:56:24] Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format
[02:56:24] Los archivos son compatibles. Creando el archivo _updated...
[02:56:24] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json
[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
[02:56:25] Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md
[02:56:25] Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.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
[13:21:37] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001...
[13:21:37] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001
[13:21:37] Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json
[13:21:37] Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation
[13:21:37] Se encontraron 1 pares de archivos para procesar.
[13:21:37] --- Procesando par de archivos ---
[13:21:37] Data file: db1001_data.db
[13:21:37] Format file: db1001_format.db
[13:21:37] Parseando archivo data: db1001_data.db
[13:21:37] Parseando archivo format: db1001_format.db
[13:21:37] Archivos JSON generados: db1001_data.json y db1001_format.json
[13:21:37] Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format
[13:21:37] Los archivos son compatibles. Creando el archivo _updated...
[13:21:37] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json
[13:21:38] Comparison Excel file generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_comparison.xlsx
[13:21:38] Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md
[13:21:38] Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt
[13:21:38] Archivo S7 copiado a: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db
[13:21:38] --- Proceso completado ---
[13:21:38] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:01.043746.
[13:21:38] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt