Compare commits
No commits in common. "0f162377cd4de42e00aaaece90fd61ed703f7fa1" and "00f3b6d2ec162ce98df12a5c58d9e577a77f40cd" have entirely different histories.
0f162377cd
...
00f3b6d2ec
|
@ -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) ---
|
||||||
|
|
|
@ -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) ---
|
||||||
|
|
|
@ -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) ---
|
||||||
|
|
|
@ -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 ---
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
del d['current_element_values']
|
||||||
if 'current_element_values' in d:
|
if isinstance(obj, DbInfo): # Asegurar que las listas vacías no se omitan si el campo existe
|
||||||
if not d['current_element_values']:
|
if '_begin_block_assignments_ordered' not in d and obj._begin_block_assignments_ordered == []:
|
||||||
del d['current_element_values']
|
d['_begin_block_assignments_ordered'] = [] # Mantener lista vacía si es el caso
|
||||||
else:
|
if '_initial_values_from_begin_block' not in d and obj._initial_values_from_begin_block == {}:
|
||||||
# Asegurar que current_element_values se serializa correctamente
|
d['_initial_values_from_begin_block'] = {} # Mantener dict vacío si es el caso
|
||||||
element_values = d['current_element_values']
|
|
||||||
if isinstance(element_values, dict):
|
|
||||||
# Preservar el formato {índice: {value, offset}}
|
|
||||||
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} ---")
|
||||||
|
|
||||||
|
|
|
@ -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
|
else:
|
||||||
lines.append(f"{indent_str}{var['full_path']} := {value_str};")
|
print(f"ADVERTENCIA: '_begin_block_assignments_ordered' no encontrado para DB '{db_info['name']}'. "
|
||||||
|
"El bloque BEGIN puede estar incompleto o desordenado si se usa el fallback.")
|
||||||
return lines
|
# (Aquí podría ir el fallback a _generate_assignments_recursive_from_current_values si se desea)
|
||||||
|
# fallback_lines = _generate_assignments_recursive_from_current_values(db_info.get("members", []), "", indent_str)
|
||||||
def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]:
|
# if fallback_lines: lines.extend(fallback_lines)
|
||||||
"""
|
|
||||||
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:
|
|
||||||
data_type_str = var["data_type"]
|
|
||||||
else:
|
|
||||||
# Para variables normales, mostrar tipo completo
|
|
||||||
data_type_str = format_data_type_for_source(var)
|
|
||||||
|
|
||||||
# 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}")
|
||||||
|
|
|
@ -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):
|
||||||
else:
|
# Mostrar un resumen o un placeholder para arrays complejos en la tabla principal
|
||||||
row = f"| {name_display} | {data_type_display} | {address} | {size_bytes_display} | {bit_size_display} | {initial_value_display} | {comment_display} |"
|
# 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.
|
||||||
|
|
||||||
lines.append(row)
|
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:
|
||||||
|
current_value_display = ""
|
||||||
|
|
||||||
return lines
|
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}` |"
|
||||||
|
|
||||||
def generate_begin_block_documentation(db_info: Dict[str, Any]) -> List[str]:
|
row += f" {comment_display} |"
|
||||||
"""
|
md_lines.append(row)
|
||||||
Genera documentación para el bloque BEGIN utilizando flatten_db_structure.
|
|
||||||
"""
|
|
||||||
lines = []
|
|
||||||
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 son ordenadas por offset:")
|
|
||||||
lines.append("")
|
|
||||||
|
|
||||||
# Usar la función de aplanamiento importada de x3
|
# Recursión para hijos de STRUCTs o miembros expandidos de UDTs
|
||||||
flat_vars = flatten_db_structure(db_info)
|
# `is_udt_expanded_member` en el JSON nos dice si los 'children' son la expansión de un UDT.
|
||||||
|
if var_info.get("children"):
|
||||||
|
# El prefijo para los hijos es el nombre completo del padre actual.
|
||||||
|
# Si el hijo es un miembro expandido de UDT, su propio nombre en 'children' ya es el nombre final del miembro.
|
||||||
|
# Si el hijo es parte de un STRUCT anidado, su nombre es relativo al STRUCT.
|
||||||
|
md_lines.extend(generate_members_table_md(
|
||||||
|
var_info["children"],
|
||||||
|
f"{name_display}.",
|
||||||
|
is_udt_definition,
|
||||||
|
include_current_value
|
||||||
|
))
|
||||||
|
|
||||||
# Filtrar solo variables con valores actuales
|
return md_lines
|
||||||
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
|
||||||
|
|
|
@ -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,90 +21,90 @@ 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")
|
for col_idx, column_cells in enumerate(sheet.columns, 1):
|
||||||
begin_headers = ["Address", "Path", "Value"]
|
max_length = 0
|
||||||
for col_num, header in enumerate(begin_headers, 1):
|
column = get_column_letter(col_idx)
|
||||||
cell = begin_sheet.cell(row=1, column=col_num, value=header)
|
for cell in column_cells:
|
||||||
cell.font = openpyxl.styles.Font(bold=True)
|
try:
|
||||||
|
if len(str(cell.value)) > max_length:
|
||||||
# Filtrar solo variables con valores actuales para la hoja BEGIN
|
max_length = len(str(cell.value))
|
||||||
vars_with_values = [var for var in flat_vars if var.get("current_value") is not None]
|
except:
|
||||||
|
pass
|
||||||
# Poblar la hoja BEGIN
|
adjusted_width = (max_length + 2)
|
||||||
for row_num, var in enumerate(vars_with_values, 2):
|
sheet.column_dimensions[column].width = adjusted_width
|
||||||
# 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):
|
|
||||||
max_length = 0
|
|
||||||
column = get_column_letter(col_idx)
|
|
||||||
for cell in column_cells:
|
|
||||||
try:
|
|
||||||
if len(str(cell.value)) > max_length:
|
|
||||||
max_length = len(str(cell.value))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
adjusted_width = min(max_length + 2, 100) # Limitar a 100 para anchos extremos
|
|
||||||
sheet.column_dimensions[column].width = adjusted_width
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
workbook.save(excel_filename)
|
workbook.save(excel_filename)
|
||||||
|
@ -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
38
data/log.txt
38
data/log.txt
|
@ -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
|
|
||||||
|
|
Loading…
Reference in New Issue