diff --git a/backend/script_groups/S7_DB_Utils/log_x3.txt b/backend/script_groups/S7_DB_Utils/log_x3.txt index faad557..e7a4983 100644 --- a/backend/script_groups/S7_DB_Utils/log_x3.txt +++ b/backend/script_groups/S7_DB_Utils/log_x3.txt @@ -1,15 +1,15 @@ --- Log de Ejecución: x3.py --- Grupo: S7_DB_Utils Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -Inicio: 2025-05-17 21:31:24 -Fin: 2025-05-17 21:31:25 -Duración: 0:00:00.136451 +Inicio: 2025-05-18 02:09:01 +Fin: 2025-05-18 02:09:01 +Duración: 0:00:00.154928 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Los archivos 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: 3 +Archivos encontrados para procesar: 2 --- 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 @@ -19,10 +19,6 @@ 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 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 --- --- ERRORES (STDERR) --- diff --git a/backend/script_groups/S7_DB_Utils/log_x4.txt b/backend/script_groups/S7_DB_Utils/log_x4.txt index b521b85..a1dbbba 100644 --- a/backend/script_groups/S7_DB_Utils/log_x4.txt +++ b/backend/script_groups/S7_DB_Utils/log_x4.txt @@ -1,34 +1,26 @@ --- Log de Ejecución: x4.py --- Grupo: S7_DB_Utils Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -Inicio: 2025-05-18 00:51:35 -Fin: 2025-05-18 00:51:35 -Duración: 0:00:00.110751 +Inicio: 2025-05-18 02:13:16 +Fin: 2025-05-18 02:13:16 +Duración: 0:00:00.162328 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Los archivos de documentación generados se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation -Archivos JSON encontrados para procesar: 3 +Archivos JSON encontrados para procesar: 2 --- Procesando archivo JSON: db1001_data.json --- 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 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 --- 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 Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.md ---- Procesando archivo JSON: db1001_updated.json --- -Archivo JSON 'db1001_updated.json' cargado correctamente. -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_updated.txt -Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md - --- Proceso de generación de documentación completado --- --- ERRORES (STDERR) --- diff --git a/backend/script_groups/S7_DB_Utils/log_x5.txt b/backend/script_groups/S7_DB_Utils/log_x5.txt index 64c738e..f76676a 100644 --- a/backend/script_groups/S7_DB_Utils/log_x5.txt +++ b/backend/script_groups/S7_DB_Utils/log_x5.txt @@ -1,15 +1,15 @@ --- Log de Ejecución: x5.py --- Grupo: S7_DB_Utils Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -Inicio: 2025-05-17 21:51:25 -Fin: 2025-05-17 21:51:25 -Duración: 0:00:00.099104 +Inicio: 2025-05-18 02:19:47 +Fin: 2025-05-18 02:19:47 +Duración: 0:00:00.125156 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Los archivos 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: 3 +Archivos JSON encontrados para procesar: 2 --- Procesando archivo JSON para descripción: db1001_data.json --- Archivo JSON 'db1001_data.json' cargado correctamente. @@ -19,10 +19,6 @@ Documentación Markdown completa generada: C:\Trabajo\SIDEL\09 - SAE452 - Diet a 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 ---- 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 --- --- ERRORES (STDERR) --- diff --git a/backend/script_groups/S7_DB_Utils/log_x6.txt b/backend/script_groups/S7_DB_Utils/log_x6.txt index 0a8ebc7..6977baa 100644 --- a/backend/script_groups/S7_DB_Utils/log_x6.txt +++ b/backend/script_groups/S7_DB_Utils/log_x6.txt @@ -1,30 +1,25 @@ --- Log de Ejecución: x6.py --- Grupo: S7_DB_Utils Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -Inicio: 2025-05-17 22:05:32 -Fin: 2025-05-17 22:05:33 -Duración: 0:00:00.614471 +Inicio: 2025-05-18 02:20:21 +Fin: 2025-05-18 02:20:22 +Duración: 0:00:01.130771 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Los archivos Excel de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation -Archivos JSON encontrados para procesar: 3 +Archivos JSON encontrados para procesar: 2 --- Procesando archivo JSON para Excel: db1001_data.json --- Archivo JSON 'db1001_data.json' cargado correctamente. -Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_data.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json.xlsx -Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json.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_HMI_Blender_Parameters.xlsx +Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.json_HMI_Blender_Parameters.xlsx --- Procesando archivo JSON para Excel: db1001_format.json --- Archivo JSON 'db1001_format.json' cargado correctamente. -Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_format.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json.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 +Generando documentación Excel para DB: 'HMI_Blender_Parameters' (desde db1001_format.json) -> C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json_HMI_Blender_Parameters.xlsx +Excel documentation generated: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.json_HMI_Blender_Parameters.xlsx --- Proceso de generación de documentación Excel completado --- diff --git a/backend/script_groups/S7_DB_Utils/log_x7_value_updater.txt b/backend/script_groups/S7_DB_Utils/log_x7_value_updater.txt index dec3bb5..1941584 100644 --- a/backend/script_groups/S7_DB_Utils/log_x7_value_updater.txt +++ b/backend/script_groups/S7_DB_Utils/log_x7_value_updater.txt @@ -1,14 +1,15 @@ --- Log de Ejecución: x7_value_updater.py --- Grupo: S7_DB_Utils Directorio de Trabajo: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -Inicio: 2025-05-18 00:51:17 -Fin: 2025-05-18 00:51:18 -Duración: 0:00:00.223903 +Inicio: 2025-05-18 02:56:24 +Fin: 2025-05-18 02:56:25 +Duración: 0:00:00.761362 Estado: SUCCESS (Código de Salida: 0) --- SALIDA ESTÁNDAR (STDOUT) --- Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json +Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation Se encontraron 1 pares de archivos para procesar. --- Procesando par de archivos --- @@ -17,291 +18,14 @@ 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 -Aplanando variables por offset... -Comparando estructuras: 251 variables en _data, 251 variables en _format +Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format Los archivos son compatibles. Creando el archivo _updated... -Mapeando STAT0.STAT1.STAT2 -> Processor_Options.Blender_OPT._ModelNum (offset 0.0) -Mapeando STAT0.STAT1.STAT3 -> Processor_Options.Blender_OPT._CO2_Offset (offset 2.0) -Mapeando STAT0.STAT1.STAT4 -> Processor_Options.Blender_OPT._MaxSyrDeltaBrix (offset 6.0) -Mapeando STAT0.STAT1.STAT5 -> Processor_Options.Blender_OPT._BrixMeter (offset 10.0) -Mapeando STAT0.STAT1.STAT6 -> Processor_Options.Blender_OPT.Spare101 (offset 10.1) -Mapeando STAT0.STAT1.STAT7 -> Processor_Options.Blender_OPT._TrackH2OEnable (offset 10.2) -Mapeando STAT0.STAT1.STAT8 -> Processor_Options.Blender_OPT._PAmPDSType (offset 10.3) -Mapeando STAT0.STAT1.STAT9 -> Processor_Options.Blender_OPT._HistoricalTrends (offset 10.4) -Mapeando STAT0.STAT1.STAT10 -> Processor_Options.Blender_OPT._PowerMeter (offset 10.5) -Mapeando STAT0.STAT1.STAT11 -> Processor_Options.Blender_OPT._Report (offset 10.6) -Mapeando STAT0.STAT1.STAT12 -> Processor_Options.Blender_OPT._Balaiage (offset 10.7) -Mapeando STAT0.STAT1.STAT13 -> Processor_Options.Blender_OPT._Valves_FullFeedback (offset 11.0) -Mapeando STAT0.STAT1.STAT14 -> Processor_Options.Blender_OPT._Valves_SingleFeedback (offset 11.1) -Mapeando STAT0.STAT1.STAT15 -> Processor_Options.Blender_OPT._PumpsSafetySwitches (offset 11.2) -Mapeando STAT0.STAT1.STAT16 -> Processor_Options.Blender_OPT._SurgeProtectionAct (offset 11.3) -Mapeando STAT0.STAT1.STAT17 -> Processor_Options.Blender_OPT._DBC_Type (offset 11.4) -Mapeando STAT0.STAT1.STAT18 -> Processor_Options.Blender_OPT._CO2InletMeter (offset 11.5) -Mapeando STAT0.STAT1.STAT19 -> Processor_Options.Blender_OPT._ProductO2Meter (offset 11.6) -Mapeando STAT0.STAT1.STAT20 -> Processor_Options.Blender_OPT._CopressedAirInletMeter (offset 11.7) -Mapeando STAT0.STAT1.STAT21 -> Processor_Options.Blender_OPT._MeterType (offset 12.0) -Mapeando STAT0.STAT1.STAT22 -> Processor_Options.Blender_OPT._MeterReceiveOnly (offset 14.0) -Mapeando STAT0.STAT1.STAT23 -> Processor_Options.Blender_OPT._SyrBrixMeter (offset 14.1) -Mapeando STAT0.STAT1.STAT24 -> Processor_Options.Blender_OPT._Flooding_Start_Up (offset 14.2) -Mapeando STAT0.STAT1.STAT25 -> Processor_Options.Blender_OPT._FastChangeOverEnabled (offset 14.3) -Mapeando STAT0.STAT1.STAT26 -> Processor_Options.Blender_OPT._WaterInletMeter (offset 14.4) -Mapeando STAT0.STAT1.STAT27 -> Processor_Options.Blender_OPT._BlendFillSystem (offset 14.5) -Mapeando STAT0.STAT1.STAT28 -> Processor_Options.Blender_OPT._TrackFillerSpeed (offset 14.6) -Mapeando STAT0.STAT1.STAT29 -> Processor_Options.Blender_OPT._SignalExchange (offset 16.0) -Mapeando STAT0.STAT1.STAT30 -> Processor_Options.Blender_OPT._CoolerPresent (offset 18.0) -Mapeando STAT0.STAT1.STAT31 -> Processor_Options.Blender_OPT._CoolerControl (offset 20.0) -Mapeando STAT0.STAT1.STAT32 -> Processor_Options.Blender_OPT._CoolerType (offset 22.0) -Mapeando STAT0.STAT1.STAT33 -> Processor_Options.Blender_OPT._LocalCIP (offset 24.0) -Mapeando STAT0.STAT1.STAT34 -> Processor_Options.Blender_OPT._ICS_CustomerHotWater (offset 24.1) -Mapeando STAT0.STAT1.STAT35 -> Processor_Options.Blender_OPT._ICS_CustomerChemRecov (offset 24.2) -Mapeando STAT0.STAT1.STAT36 -> Processor_Options.Blender_OPT._CIPSignalExchange (offset 24.3) -Mapeando STAT0.STAT1.STAT37 -> Processor_Options.Blender_OPT._ICS_CustomerChemicals (offset 24.4) -Mapeando STAT0.STAT1.STAT38 -> Processor_Options.Blender_OPT._CarboPresent (offset 24.5) -Mapeando STAT0.STAT1.STAT39 -> Processor_Options.Blender_OPT._InverterSyrupPumpPPP302 (offset 24.6) -Mapeando STAT0.STAT1.STAT40 -> Processor_Options.Blender_OPT._InverterWaterPumpPPN301 (offset 24.7) -Mapeando STAT0.STAT1.STAT41 -> Processor_Options.Blender_OPT._DoubleDeair (offset 25.0) -Mapeando STAT0.STAT1.STAT42 -> Processor_Options.Blender_OPT._DeairPreMixed (offset 25.1) -Mapeando STAT0.STAT1.STAT43 -> Processor_Options.Blender_OPT._Deaireation (offset 25.2) -Mapeando STAT0.STAT1.STAT44 -> Processor_Options.Blender_OPT._StillWaterByPass (offset 25.3) -Mapeando STAT0.STAT1.STAT45 -> Processor_Options.Blender_OPT._ManifoldSetting (offset 25.4) -Mapeando STAT0.STAT1.STAT46 -> Processor_Options.Blender_OPT._InverterProdPumpPPM303 (offset 25.5) -Mapeando STAT0.STAT1.STAT47 -> Processor_Options.Blender_OPT._SidelCip (offset 25.6) -Mapeando STAT0.STAT1.STAT48 -> Processor_Options.Blender_OPT._EthernetCom_CpuPN_CP (offset 25.7) -Mapeando STAT0.STAT1.STAT49 -> Processor_Options.Blender_OPT._2ndOutlet (offset 26.0) -Mapeando STAT0.STAT1.STAT50 -> Processor_Options.Blender_OPT._Promass (offset 28.0) -Mapeando STAT0.STAT1.STAT51 -> Processor_Options.Blender_OPT._WaterPromass (offset 30.0) -Mapeando STAT0.STAT1.STAT52 -> Processor_Options.Blender_OPT._ProductConductimeter (offset 30.1) -Mapeando STAT0.STAT1.STAT53 -> Processor_Options.Blender_OPT._ICS_CustomerH2ORecov (offset 30.2) -Mapeando STAT0.STAT1.STAT54 -> Processor_Options.Blender_OPT.Spare303 (offset 30.3) -Mapeando STAT0.STAT1.STAT55 -> Processor_Options.Blender_OPT._CO2_GAS2_Injection (offset 30.4) -Mapeando STAT0.STAT1.STAT56 -> Processor_Options.Blender_OPT._InverterVacuuPumpPPN304 (offset 30.5) -Mapeando STAT0.STAT1.STAT57 -> Processor_Options.Blender_OPT._InverterBoostPumpPPM307 (offset 30.6) -Mapeando STAT0.STAT1.STAT58 -> Processor_Options.Blender_OPT._RunOut_Water (offset 30.7) -Mapeando STAT0.STAT1.STAT59 -> Processor_Options.Blender_OPT._FlowMeterType (offset 31.0) -Mapeando STAT0.STAT1.STAT60 -> Processor_Options.Blender_OPT._SidelFiller (offset 31.1) -Mapeando STAT0.STAT1.STAT61 -> Processor_Options.Blender_OPT._Simulation (offset 31.2) -Mapeando STAT0.STAT1.STAT62 -> Processor_Options.Blender_OPT._ProductCoolingCTRL (offset 31.3) -Mapeando STAT0.STAT1.STAT63 -> Processor_Options.Blender_OPT._ChillerCTRL (offset 31.4) -Mapeando STAT0.STAT1.STAT64 -> Processor_Options.Blender_OPT._CO2_SterileFilter (offset 31.5) -Mapeando STAT0.STAT1.STAT65 -> Processor_Options.Blender_OPT._InverterRecirPumpPPM306 (offset 31.6) -Mapeando STAT0.STAT1.STAT66 -> Processor_Options.Blender_OPT._ProdPressReleaseRVM304 (offset 31.7) -Mapeando STAT0.STAT1.STAT67 -> Processor_Options.Blender_OPT._VacuumPump (offset 32.0) -Mapeando STAT0.STAT1.STAT68 -> Processor_Options.Blender_OPT._GAS2InjectionType (offset 34.0) -Mapeando STAT0.STAT1.STAT69 -> Processor_Options.Blender_OPT._InjectionPress_Ctrl (offset 36.0) -Mapeando STAT0.STAT1.STAT70 -> Processor_Options.Blender_OPT._ProdPressureType (offset 38.0) -Mapeando STAT0.STAT1.STAT71 -> Processor_Options.Blender_OPT._CIPHeatType (offset 40.0) -Mapeando STAT0.STAT1.STAT72 -> Processor_Options.Blender_OPT._EHS_NrRes (offset 42.0) -Mapeando STAT73[1] -> Spare1 (offset 44.0) -Mapeando STAT73[2] -> Spare1 (offset 44.0) -Mapeando STAT73[3] -> Spare1 (offset 44.0) -Mapeando STAT73[4] -> Spare1 (offset 44.0) -Mapeando STAT73[5] -> Spare1 (offset 44.0) -Mapeando STAT73[6] -> Spare1 (offset 44.0) -Mapeando STAT73[7] -> Spare1 (offset 44.0) -Mapeando STAT73[8] -> Spare1 (offset 44.0) -Mapeando STAT73[9] -> Spare1 (offset 44.0) -Mapeando STAT74 -> _RVM301_DeadBand (offset 62.0) -Mapeando STAT75 -> _RVM301_Kp (offset 66.0) -Mapeando STAT76.STAT77 -> Actual_Recipe_Parameters._Name (offset 70.0) -Mapeando STAT76.STAT78 -> Actual_Recipe_Parameters._EnProdTemp (offset 104.0) -Mapeando STAT76.STAT79 -> Actual_Recipe_Parameters._SyrFlushing (offset 104.1) -Mapeando STAT76.STAT80 -> Actual_Recipe_Parameters._GAS2_Injection (offset 104.2) -Mapeando STAT76.STAT81 -> Actual_Recipe_Parameters._Eq_Pression_Selected (offset 104.3) -Mapeando STAT76.STAT82 -> Actual_Recipe_Parameters._DeoxStripEn (offset 104.4) -Mapeando STAT76.STAT83 -> Actual_Recipe_Parameters._DeoxVacuumEn (offset 104.5) -Mapeando STAT76.STAT84 -> Actual_Recipe_Parameters._DeoxPreMixed (offset 104.6) -Mapeando STAT76.STAT85 -> Actual_Recipe_Parameters._EnBlowOffProdPipeCO2Fil (offset 104.7) -Mapeando STAT76.STAT86 -> Actual_Recipe_Parameters._WaterSelection (offset 105.0) -Mapeando STAT76.STAT87 -> Actual_Recipe_Parameters._FillerNextRecipeNum (offset 106.0) -Mapeando STAT76.STAT88 -> Actual_Recipe_Parameters._BottleShape (offset 107.0) -Mapeando STAT76.STAT89 -> Actual_Recipe_Parameters._Type (offset 108.0) -Mapeando STAT76.STAT90 -> Actual_Recipe_Parameters._ProdMeterRecipeNum (offset 110.0) -Mapeando STAT76.STAT91 -> Actual_Recipe_Parameters._SyrupBrix (offset 112.0) -Mapeando STAT76.STAT92 -> Actual_Recipe_Parameters._SyrupDensity (offset 116.0) -Mapeando STAT76.STAT93 -> Actual_Recipe_Parameters._SyrupFactor (offset 120.0) -Mapeando STAT76.STAT94 -> Actual_Recipe_Parameters._ProductBrix (offset 124.0) -Mapeando STAT76.STAT95 -> Actual_Recipe_Parameters._ProductionRate (offset 128.0) -Mapeando STAT76.STAT96 -> Actual_Recipe_Parameters._Ratio (offset 132.0) -Mapeando STAT76.STAT97 -> Actual_Recipe_Parameters._ProdBrixOffset (offset 136.0) -Mapeando STAT76.STAT98 -> Actual_Recipe_Parameters._CO2Vols (offset 140.0) -Mapeando STAT76.STAT99 -> Actual_Recipe_Parameters._CO2Fact (offset 144.0) -Mapeando STAT76.STAT100 -> Actual_Recipe_Parameters._ProdTankPress (offset 148.0) -Mapeando STAT76.STAT101 -> Actual_Recipe_Parameters._SP_ProdTemp (offset 152.0) -Mapeando STAT76.STAT102 -> Actual_Recipe_Parameters._PrdTankMinLevel (offset 156.0) -Mapeando STAT76.STAT103 -> Actual_Recipe_Parameters._WaterValveSave (offset 160.0) -Mapeando STAT76.STAT104 -> Actual_Recipe_Parameters._SyrupValveSave (offset 164.0) -Mapeando STAT76.STAT105 -> Actual_Recipe_Parameters._CarboCO2ValveSave (offset 168.0) -Mapeando STAT76.STAT106 -> Actual_Recipe_Parameters._ProdMeterHighBrix (offset 172.0) -Mapeando STAT76.STAT107 -> Actual_Recipe_Parameters._ProdMeterLowBrix (offset 176.0) -Mapeando STAT76.STAT108 -> Actual_Recipe_Parameters._ProdMeterHighCO2 (offset 180.0) -Mapeando STAT76.STAT109 -> Actual_Recipe_Parameters._ProdMeterLowCO2 (offset 184.0) -Mapeando STAT76.STAT110 -> Actual_Recipe_Parameters._ProdMeter_ZeroCO2 (offset 188.0) -Mapeando STAT76.STAT111 -> Actual_Recipe_Parameters._ProdMeter_ZeroBrix (offset 192.0) -Mapeando STAT76.STAT112 -> Actual_Recipe_Parameters._ProdHighCond (offset 196.0) -Mapeando STAT76.STAT113 -> Actual_Recipe_Parameters._ProdLowCond (offset 200.0) -Mapeando STAT76.STAT114 -> Actual_Recipe_Parameters._BottleSize (offset 204.0) -Mapeando STAT76.STAT115 -> Actual_Recipe_Parameters._FillingValveHead_SP (offset 208.0) -Mapeando STAT76.STAT116 -> Actual_Recipe_Parameters._SyrMeter_ZeroBrix (offset 212.0) -Mapeando STAT76.STAT117 -> Actual_Recipe_Parameters._FirstProdExtraCO2Fact (offset 216.0) -Mapeando STAT76.STAT118 -> Actual_Recipe_Parameters._Gas2Vols (offset 220.0) -Mapeando STAT76.STAT119 -> Actual_Recipe_Parameters._Gas2Fact (offset 224.0) -Mapeando STAT76.STAT120 -> Actual_Recipe_Parameters._SyrupPumpPressure (offset 228.0) -Mapeando STAT76.STAT121 -> Actual_Recipe_Parameters._WaterPumpPressure (offset 232.0) -Mapeando STAT76.STAT122 -> Actual_Recipe_Parameters._CO2_Air_N2_PressSelect (offset 236.0) -Mapeando STAT76.STAT123 -> Actual_Recipe_Parameters._KFactRVM304BlowOff (offset 238.0) -Mapeando STAT76.STAT124 -> Actual_Recipe_Parameters._ProdRecircPumpFreq (offset 242.0) -Mapeando STAT76.STAT125 -> Actual_Recipe_Parameters._ProdBoosterPumpPress (offset 246.0) -Mapeando STAT76.STAT126 -> Actual_Recipe_Parameters._ProdSendPumpFreq (offset 250.0) -Mapeando STAT127[1] -> Spare2 (offset 254.0) -Mapeando STAT127[2] -> Spare2 (offset 254.0) -Mapeando STAT127[3] -> Spare2 (offset 254.0) -Mapeando STAT127[4] -> Spare2 (offset 254.0) -Mapeando STAT127[5] -> Spare2 (offset 254.0) -Mapeando STAT128 -> Next_Recipe_Name (offset 264.0) -Mapeando STAT129 -> Next_Recipe_Number (offset 298.0) -Mapeando STAT130[1] -> Spare3 (offset 300.0) -Mapeando STAT130[2] -> Spare3 (offset 300.0) -Mapeando STAT130[3] -> Spare3 (offset 300.0) -Mapeando STAT130[4] -> Spare3 (offset 300.0) -Mapeando STAT130[5] -> Spare3 (offset 300.0) -Mapeando STAT130[6] -> Spare3 (offset 300.0) -Mapeando STAT130[7] -> Spare3 (offset 300.0) -Mapeando STAT130[8] -> Spare3 (offset 300.0) -Mapeando STAT130[9] -> Spare3 (offset 300.0) -Mapeando STAT130[10] -> Spare3 (offset 300.0) -Mapeando STAT130[11] -> Spare3 (offset 300.0) -Mapeando STAT130[12] -> Spare3 (offset 300.0) -Mapeando STAT130[13] -> Spare3 (offset 300.0) -Mapeando STAT130[14] -> Spare3 (offset 300.0) -Mapeando STAT130[15] -> Spare3 (offset 300.0) -Mapeando STAT130[16] -> Spare3 (offset 300.0) -Mapeando STAT130[17] -> Spare3 (offset 300.0) -Mapeando STAT130[18] -> Spare3 (offset 300.0) -Mapeando STAT131.STAT132 -> ProcessSetup.Spare000 (offset 336.0) -Mapeando STAT131.STAT133 -> ProcessSetup.Spare040 (offset 340.0) -Mapeando STAT131.STAT134 -> ProcessSetup._KWaterLoss (offset 344.0) -Mapeando STAT131.STAT135 -> ProcessSetup._KSyrupLoss (offset 348.0) -Mapeando STAT131.STAT136 -> ProcessSetup._KProdLoss (offset 352.0) -Mapeando STAT131.STAT137 -> ProcessSetup._KPPM303 (offset 356.0) -Mapeando STAT131.STAT138 -> ProcessSetup._BaialageRVM301OVMin (offset 360.0) -Mapeando STAT131.STAT139 -> ProcessSetup._SyrupLinePressure (offset 364.0) -Mapeando STAT131.STAT140 -> ProcessSetup._CIPRMM301OV (offset 368.0) -Mapeando STAT131.STAT141 -> ProcessSetup._CIPRMP302OV (offset 372.0) -Mapeando STAT131.STAT142 -> ProcessSetup._CIPTM301MinLevel (offset 376.0) -Mapeando STAT131.STAT143 -> ProcessSetup._CIPTM301MaxLevel (offset 380.0) -Mapeando STAT131.STAT144 -> ProcessSetup._CIPPPM303Freq (offset 384.0) -Mapeando STAT131.STAT145 -> ProcessSetup._CIPTP301MinLevel (offset 388.0) -Mapeando STAT131.STAT146 -> ProcessSetup._CIPTP301MaxLevel (offset 392.0) -Mapeando STAT131.STAT147 -> ProcessSetup._RinseRMM301OV (offset 396.0) -Mapeando STAT131.STAT148 -> ProcessSetup._RinseRMP302OV (offset 400.0) -Mapeando STAT131.STAT149 -> ProcessSetup._RinseTM301Press (offset 404.0) -Mapeando STAT131.STAT150 -> ProcessSetup._RinsePPM303Freq (offset 408.0) -Mapeando STAT131.STAT151 -> ProcessSetup._DrainTM301Press (offset 412.0) -Mapeando STAT131.STAT152 -> ProcessSetup._KRecBlendError (offset 416.0) -Mapeando STAT131.STAT153 -> ProcessSetup._KRecCarboCO2Error (offset 420.0) -Mapeando STAT131.STAT154 -> ProcessSetup._MaxBlendError (offset 424.0) -Mapeando STAT131.STAT155 -> ProcessSetup._MaxCarboCO2Error (offset 428.0) -Mapeando STAT131.STAT156 -> ProcessSetup._StartUpBrixExtraWater (offset 432.0) -Mapeando STAT131.STAT157 -> ProcessSetup._StartUpCO2ExtraWater (offset 436.0) -Mapeando STAT131.STAT158 -> ProcessSetup._StartUpPPM303Freq (offset 440.0) -Mapeando STAT131.STAT159 -> ProcessSetup._SyrupRoomTank (offset 444.0) -Mapeando STAT131.STAT160 -> ProcessSetup._SyrupRunOutLiters (offset 446.0) -Mapeando STAT131.STAT161 -> ProcessSetup._InjCO2Press_Offset (offset 450.0) -Mapeando STAT131.STAT162 -> ProcessSetup._InjCO2Press_MinFlow (offset 454.0) -Mapeando STAT131.STAT163 -> ProcessSetup._InjCO2Press_MaxFlow (offset 458.0) -Mapeando STAT131.STAT164 -> ProcessSetup._CarboCO2Pressure (offset 462.0) -Mapeando STAT131.STAT165 -> ProcessSetup._N2MinPressure (offset 466.0) -Mapeando STAT131.STAT166 -> ProcessSetup._DiffSensor_Height (offset 470.0) -Mapeando STAT131.STAT167 -> ProcessSetup._DiffSensor_DeltaHeight (offset 474.0) -Mapeando STAT131.STAT168 -> ProcessSetup._DiffSensor_Offset (offset 478.0) -Mapeando STAT131.STAT169 -> ProcessSetup._FillingValveHeight (offset 482.0) -Mapeando STAT131.STAT170 -> ProcessSetup._FillerDiameter (offset 486.0) -Mapeando STAT131.STAT171 -> ProcessSetup._FillingValveNum (offset 490.0) -Mapeando STAT131.STAT172 -> ProcessSetup._FillerProdPipeDN (offset 492.0) -Mapeando STAT131.STAT173 -> ProcessSetup._FillerProdPipeMass (offset 496.0) -Mapeando STAT131.STAT174 -> ProcessSetup._FillingTime (offset 500.0) -Mapeando STAT131.STAT175 -> ProcessSetup._TM301Height_0 (offset 504.0) -Mapeando STAT131.STAT176 -> ProcessSetup._TM301LevelPerc_2 (offset 508.0) -Mapeando STAT131.STAT177 -> ProcessSetup._TM301Height_2 (offset 512.0) -Mapeando STAT131.STAT178 -> ProcessSetup._RVN304Factor (offset 516.0) -Mapeando STAT131.STAT179 -> ProcessSetup._DrainTM301Flushing (offset 520.0) -Mapeando STAT131.STAT180 -> ProcessSetup._FirstProdExtraBrix (offset 524.0) -Mapeando STAT131.STAT181 -> ProcessSetup._FirstProdDietExtraSyr (offset 528.0) -Mapeando STAT131.STAT182 -> ProcessSetup._EndProdLastSyrlt (offset 532.0) -Mapeando STAT131.STAT183 -> ProcessSetup._TM301DrainSt0Time (offset 536.0) -Mapeando STAT131.STAT184 -> ProcessSetup._TM301DrainSt1Time (offset 538.0) -Mapeando STAT131.STAT185 -> ProcessSetup._ProdPipeRunOutSt0Time (offset 540.0) -Mapeando STAT131.STAT186 -> ProcessSetup._RMM301ProdPipeRunOu (offset 542.0) -Mapeando STAT131.STAT187 -> ProcessSetup._RMP302ProdPipeRunOu (offset 546.0) -Mapeando STAT131.STAT188 -> ProcessSetup._ProdPipeRunOutAmount (offset 550.0) -Mapeando STAT131.STAT189 -> ProcessSetup._TM301RunOutChiller (offset 554.0) -Mapeando STAT131.STAT190 -> ProcessSetup._MinSpeedNominalProd (offset 558.0) -Mapeando STAT131.STAT191 -> ProcessSetup._MinSpeedSlowProd (offset 562.0) -Mapeando STAT131.STAT192 -> ProcessSetup._FastChgOvrTM301DrnPrss (offset 566.0) -Mapeando STAT131.STAT193 -> ProcessSetup._CIPTN301MinLevel (offset 570.0) -Mapeando STAT131.STAT194 -> ProcessSetup._CIPTN301MaxLevel (offset 574.0) -Mapeando STAT131.STAT195 -> ProcessSetup._ProdPPN304Freq (offset 578.0) -Mapeando STAT131.STAT196 -> ProcessSetup._GAS2InjectionPress (offset 582.0) -Mapeando STAT131.STAT197 -> ProcessSetup._BaialageRVM301OVMax (offset 586.0) -Mapeando STAT131.STAT198 -> ProcessSetup._RinsePPN301Freq (offset 590.0) -Mapeando STAT131.STAT199 -> ProcessSetup._CIPPPN301Freq (offset 594.0) -Mapeando STAT131.STAT200 -> ProcessSetup._RinsePPP302Freq (offset 598.0) -Mapeando STAT131.STAT201 -> ProcessSetup._CIPPPP302Freq (offset 602.0) -Mapeando STAT131.STAT202 -> ProcessSetup._PercSyrupBrixSyrStarUp (offset 606.0) -Mapeando STAT131.STAT203 -> ProcessSetup._RefTempCoolingCTRL (offset 610.0) -Mapeando STAT131.STAT204 -> ProcessSetup._H2OSerpPrimingVolume (offset 614.0) -Mapeando STAT131.STAT205 -> ProcessSetup._AVN301_Nozzle_Kv (offset 618.0) -Mapeando STAT131.STAT206 -> ProcessSetup._AVN302_Nozzle_Kv (offset 622.0) -Mapeando STAT131.STAT207 -> ProcessSetup._AVN303_Nozzle_Kv (offset 626.0) -Mapeando STAT131.STAT208 -> ProcessSetup._DeoxSpryball_Kv (offset 630.0) -Mapeando STAT131.STAT209 -> ProcessSetup._PremixedLineDrainTime (offset 634.0) -Mapeando STAT131.STAT210 -> ProcessSetup._PPN301_H_MaxFlow (offset 636.0) -Mapeando STAT131.STAT211 -> ProcessSetup._PPN301_H_MinFlow (offset 640.0) -Mapeando STAT131.STAT212 -> ProcessSetup._PPN301_MaxFlow (offset 644.0) -Mapeando STAT131.STAT213 -> ProcessSetup._PPN301_MinFlow (offset 648.0) -Mapeando STAT131.STAT214 -> ProcessSetup._PPP302_H_MaxFlow (offset 652.0) -Mapeando STAT131.STAT215 -> ProcessSetup._PPP302_H_MinFlow (offset 656.0) -Mapeando STAT131.STAT216 -> ProcessSetup._PPP302_MaxFlow (offset 660.0) -Mapeando STAT131.STAT217 -> ProcessSetup._PPP302_MinFlow (offset 664.0) -Mapeando STAT131.STAT218 -> ProcessSetup._RinsePPM306Freq (offset 668.0) -Mapeando STAT131.STAT219 -> ProcessSetup._CIPPPM306Freq (offset 672.0) -Mapeando STAT131.STAT220 -> ProcessSetup._PPM307_H_MaxFlow (offset 676.0) -Mapeando STAT131.STAT221 -> ProcessSetup._PPM307_H_MinFlow (offset 680.0) -Mapeando STAT131.STAT222 -> ProcessSetup._PPM307_MaxFlow (offset 684.0) -Mapeando STAT131.STAT223 -> ProcessSetup._PPM307_MinFlow (offset 688.0) -Mapeando STAT131.STAT224 -> ProcessSetup._Temp0_VacuumCtrl (offset 692.0) -Mapeando STAT131.STAT225 -> ProcessSetup._Temp1_VacuumCtrl (offset 696.0) -Mapeando STAT131.STAT226 -> ProcessSetup._Temp2_VacuumCtrl (offset 700.0) -Mapeando STAT131.STAT227 -> ProcessSetup._Temp3_VacuumCtrl (offset 704.0) -Mapeando STAT131.STAT228 -> ProcessSetup._Temp4_VacuumCtrl (offset 708.0) -Mapeando STAT131.STAT229 -> ProcessSetup._T1_VacuumCtrl (offset 712.0) -Mapeando STAT131.STAT230 -> ProcessSetup._T2_VacuumCtrl (offset 716.0) -Mapeando STAT131.STAT231 -> ProcessSetup._T3_VacuumCtrl (offset 720.0) -Mapeando STAT131.STAT232 -> ProcessSetup._T4_VacuumCtrl (offset 724.0) -Mapeando STAT131.STAT233 -> ProcessSetup._ICS_VolDosWorkTimePAA (offset 728.0) -Mapeando STAT131.STAT234 -> ProcessSetup._ICS_VolPauseTimePAA (offset 730.0) -Mapeando STAT131.STAT235 -> ProcessSetup._ICS_PAAPulseWeight (offset 732.0) -Mapeando STAT131.STAT236 -> ProcessSetup._ICS_CausticPulseWeight (offset 734.0) -Mapeando STAT131.STAT237 -> ProcessSetup._ICS_AcidPulseWeight (offset 736.0) -Mapeando STAT131.STAT238 -> ProcessSetup._ICS_VolumeRestOfLine (offset 738.0) -Mapeando STAT131.STAT239 -> ProcessSetup._ICS_VolDosWorkTimeCaus (offset 742.0) -Mapeando STAT131.STAT240 -> ProcessSetup._ICS_VolDosPauseTimeCaus (offset 744.0) -Mapeando STAT131.STAT241 -> ProcessSetup._ICS_VolDosWorkTimeAcid (offset 746.0) -Mapeando STAT131.STAT242 -> ProcessSetup._ICS_VolDosPauseTimeAcid (offset 748.0) -Mapeando STAT131.STAT243 -> ProcessSetup._ICS_ConcDosWorkTimeCaus (offset 750.0) -Mapeando STAT131.STAT244 -> ProcessSetup._ICS_ConcDosPausTimeCaus (offset 752.0) -Mapeando STAT131.STAT245 -> ProcessSetup._ICS_ConcDosWorkTimeAcid (offset 754.0) -Mapeando STAT131.STAT246 -> ProcessSetup._ICS_ConcDosPausTimeAcid (offset 756.0) -Mapeando STAT131.STAT247 -> ProcessSetup._RinsePPM307Freq (offset 758.0) -Mapeando STAT131.STAT248 -> ProcessSetup._CIPPPM307Freq (offset 762.0) -Mapeando STAT131.STAT249 -> ProcessSetup._CIP2StepTN301Lvl (offset 766.0) -Mapeando STAT131.STAT250 -> ProcessSetup._CIP2StepTM301Lvl (offset 770.0) -Mapeando STAT131.STAT251 -> ProcessSetup._CIP2StepTP301Lvl (offset 774.0) -Mapeando STAT131.STAT252 -> ProcessSetup._PumpNominalFreq (offset 778.0) -Mapeando STAT253 -> _SwitchOff_DensityOK (offset 782.0) -Mapeando STAT254 -> STAT254 (offset 784.0) 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 --- diff --git a/backend/script_groups/S7_DB_Utils/scripts_description.json b/backend/script_groups/S7_DB_Utils/scripts_description.json index e7ee3ec..2040fda 100644 --- a/backend/script_groups/S7_DB_Utils/scripts_description.json +++ b/backend/script_groups/S7_DB_Utils/scripts_description.json @@ -42,9 +42,9 @@ "hidden": false }, "x7_value_updater.py": { - "display_name": "x7_value_updater", - "short_description": "Sin descripción corta.", - "long_description": "", + "display_name": "07: Actualizar Valores de DB (JSON)", + "short_description": "Busca archivos .db o .awl con la terminacion _data y _format. Si los encuentra y son compatibles usa los datos de _data para generar un _updated con los nombres de las variables de _format", + "long_description": "Procesa pares de archivos a JSON (_data.json y _format.json, generados por x3.py). Compara sus estructuras por offset para asegurar compatibilidad. Si son compatibles, crea un nuevo archivo _updated.json que combina la estructura del _format.json con los valores actuales del _data.json.", "hidden": false } } \ No newline at end of file diff --git a/backend/script_groups/S7_DB_Utils/x3.py b/backend/script_groups/S7_DB_Utils/x3.py index d2001b7..63eaf00 100644 --- a/backend/script_groups/S7_DB_Utils/x3.py +++ b/backend/script_groups/S7_DB_Utils/x3.py @@ -1,10 +1,10 @@ -# --- x3.py (Modificaciones v_final_4 - Incluye 'count' para ArrayDimension y ajuste debug) --- +# --- x3_refactored.py --- import re import json from dataclasses import dataclass, field from typing import List, Dict, Optional, Union, Tuple, Any -import os # Asegurarse de que os está importado -import glob # Para buscar archivos +import os +import glob import copy import sys @@ -29,11 +29,11 @@ class ArrayDimension: upper_bound: int @property - def count(self) -> int: # La propiedad 'count' se calculará + def count(self) -> int: return self.upper_bound - self.lower_bound + 1 @dataclass -class VariableInfo: # Sin cambios respecto a v_final_3 +class VariableInfo: name: str data_type: str byte_offset: float @@ -50,7 +50,7 @@ class VariableInfo: # Sin cambios respecto a v_final_3 current_element_values: Optional[Dict[str, str]] = None @dataclass -class UdtInfo: # Sin cambios respecto a v_final_3 +class UdtInfo: name: str family: Optional[str] = None version: Optional[str] = None @@ -58,23 +58,23 @@ class UdtInfo: # Sin cambios respecto a v_final_3 total_size_in_bytes: int = 0 @dataclass -class DbInfo: # Sin cambios respecto a v_final_3 +class DbInfo: name: str title: Optional[str] = None family: Optional[str] = None version: Optional[str] = None members: List[VariableInfo] = field(default_factory=list) total_size_in_bytes: int = 0 - _begin_block_assignments_ordered: List[Tuple[str, str]] = field(default_factory=list) - _initial_values_from_begin_block: Dict[str, str] = field(default_factory=dict) + # Eliminamos los campos redundantes: + # _begin_block_assignments_ordered y _initial_values_from_begin_block @dataclass -class ParsedData: # Sin cambios +class ParsedData: udts: List[UdtInfo] = field(default_factory=list) dbs: List[DbInfo] = field(default_factory=list) @dataclass -class OffsetContext: # Sin cambios +class OffsetContext: byte_offset: int = 0 bit_offset: int = 0 def get_combined_offset(self) -> float: @@ -89,7 +89,7 @@ class OffsetContext: # Sin cambios if self.byte_offset % 2 != 0: self.byte_offset += 1 # --- Fin Estructuras de Datos --- -S7_PRIMITIVE_SIZES = { # Sin cambios +S7_PRIMITIVE_SIZES = { "BOOL": (0, 1, True), "BYTE": (1, 1, False), "CHAR": (1, 1, 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), @@ -100,7 +100,7 @@ S7_PRIMITIVE_SIZES = { # Sin cambios "LWORD": (8, 2, False), "DATE_AND_TIME": (8, 2, False), "DT": (8, 2, False), } -class S7Parser: # Sin cambios en __init__ respecto a v_final_3 +class S7Parser: def __init__(self): self.parsed_data = ParsedData() self.known_udts: Dict[str, UdtInfo] = {} @@ -125,7 +125,7 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 ) 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]: # Sin cambios + def _get_type_details(self, type_name_raw_cleaned: str) -> Tuple[int, int, bool, str]: type_name_upper = type_name_raw_cleaned.upper() if type_name_upper in S7_PRIMITIVE_SIZES: size, align, is_bool = S7_PRIMITIVE_SIZES[type_name_upper] @@ -138,7 +138,7 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 raise ValueError(f"Tipo de dato desconocido o UDT no definido: '{type_name_raw_cleaned}'") @staticmethod - def _adjust_children_offsets(children: List[VariableInfo], base_offset_add: float): # Sin cambios + def _adjust_children_offsets(children: List[VariableInfo], base_offset_add: float): for child in children: child.byte_offset += base_offset_add if child.byte_offset == float(int(child.byte_offset)): @@ -149,7 +149,7 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 def _parse_struct_members(self, lines: List[str], current_line_idx: int, parent_members_list: List[VariableInfo], active_context: OffsetContext, - is_top_level_struct_in_block: bool = False) -> int: # Ajuste en depuración + is_top_level_struct_in_block: bool = False) -> int: idx_to_process = current_line_idx while idx_to_process < len(lines): original_line_text = lines[idx_to_process].strip() @@ -178,11 +178,11 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 active_context.align_to_byte() if active_context.byte_offset % 2 != 0: active_context.byte_offset += 1 return line_index_for_return - if is_main_block_end_struct: # Simplemente lo ignoramos aquí, será manejado por END_TYPE/DB + if is_main_block_end_struct: pass var_match = self.var_regex_simplified.match(line_to_parse) - if var_match: # Lógica de var_match sin cambios respecto a v_final_3 + if var_match: var_data = var_match.groupdict() raw_base_type_from_regex = var_data['basetype'].strip() clean_data_type = raw_base_type_from_regex.strip('"') @@ -244,54 +244,186 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 if expanded_member.children: S7Parser._adjust_children_offsets(expanded_member.children, udt_instance_abs_start_offset) var_info.children.append(expanded_member) parent_members_list.append(var_info) - # Ajuste de la condición del mensaje de depuración elif line_to_parse and \ not self.struct_start_regex.match(line_to_parse) and \ not is_main_block_end_struct and \ not is_nested_end_struct and \ - not is_block_terminator : # Solo imprimir si no es un terminador conocido + not is_block_terminator : print(f"DEBUG (_parse_struct_members): Line not parsed: Original='{original_line_text}' | Processed='{line_to_parse}'") return idx_to_process - def _parse_begin_block(self, lines: List[str], start_idx: int, db_info: DbInfo) -> int: # Sin cambios + def _parse_begin_block(self, lines: List[str], start_idx: int, db_info: DbInfo) -> int: + """ + Parsea el bloque BEGIN y aplica directamente los valores a las variables + correspondientes, calculando también offsets para elementos de arrays. + """ idx = start_idx assignment_regex = re.compile(r'^\s*(?P.+?)\s*:=\s*(?P.+?)\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): - original_line = lines[idx].strip(); line_to_parse = original_line + original_line = lines[idx].strip() + line_to_parse = original_line comment_marker = original_line.find("//") - if comment_marker != -1: line_to_parse = original_line[:comment_marker].strip() - if self.end_db_regex.match(line_to_parse): return idx + if comment_marker != -1: + line_to_parse = original_line[:comment_marker].strip() + + if self.end_db_regex.match(line_to_parse): + break + idx += 1 - if not line_to_parse: continue + if not line_to_parse: + continue + match = assignment_regex.match(line_to_parse) if match: 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 + if '[' in path and ']' in path: + # Es un elemento de array + array_path = path[:path.find('[')] + indices = path[path.find('[')+1:path.find(']')] + + if array_path in path_to_var_map: + 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 _apply_current_values(self, members: List[VariableInfo], begin_values: Dict[str, str], current_path_prefix: str = ""): # Sin cambios - for var_info in members: - full_member_path = f"{current_path_prefix}{var_info.name}" - if var_info.array_dimensions: - 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}.") - - def parse_file(self, filepath: str) -> ParsedData: # Sin cambios respecto a v_final_3 + def parse_file(self, filepath: str) -> ParsedData: try: 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 @@ -334,42 +466,260 @@ class S7Parser: # Sin cambios en __init__ respecto a v_final_3 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 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 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 - 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)}") + # Ya no necesitamos aplicar valores, porque se aplican directamente en _parse_begin_block current_block_handler = None; parsing_title_value_next_line = False idx += 1 return self.parsed_data def custom_json_serializer(obj: Any) -> Any: if isinstance(obj, OffsetContext): return None - # Manejar ArrayDimension explícitamente para incluir 'count' if isinstance(obj, ArrayDimension): return { 'lower_bound': obj.lower_bound, 'upper_bound': obj.upper_bound, - 'count': obj.count # La propiedad se calcula y se añade aquí + 'count': obj.count } if hasattr(obj, '__dict__'): d = {k: v for k, v in obj.__dict__.items() - if not (v is None or (isinstance(v, list) and not v))} # No filtrar _initial_values_from_begin_block + if not (v is None or (isinstance(v, list) and not v))} 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 - if not obj.current_element_values and 'current_element_values' in d: - del d['current_element_values'] - if isinstance(obj, DbInfo): # Asegurar que las listas vacías no se omitan si el campo existe - if '_begin_block_assignments_ordered' not in d and obj._begin_block_assignments_ordered == []: - d['_begin_block_assignments_ordered'] = [] # Mantener lista vacía si es el caso - if '_initial_values_from_begin_block' not in d and obj._initial_values_from_begin_block == {}: - d['_initial_values_from_begin_block'] = {} # Mantener dict vacío si es el caso + + # Manejar current_element_values con format especial para offsets + if 'current_element_values' in d: + if not d['current_element_values']: + del d['current_element_values'] + else: + # Asegurar que current_element_values se serializa correctamente + 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 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__": working_dir = find_working_directory() print(f"Using working directory: {working_dir}") @@ -388,7 +738,7 @@ if __name__ == "__main__": print(f"Archivos encontrados para procesar: {len(all_source_files)}") for filepath in all_source_files: - parser = S7Parser() # Nueva instancia para cada archivo para evitar estados residuales + parser = S7Parser() filename = os.path.basename(filepath) print(f"\n--- Procesando archivo: {filename} ---") diff --git a/backend/script_groups/S7_DB_Utils/x4.py b/backend/script_groups/S7_DB_Utils/x4.py index d42fc66..f08cc24 100644 --- a/backend/script_groups/S7_DB_Utils/x4.py +++ b/backend/script_groups/S7_DB_Utils/x4.py @@ -1,9 +1,10 @@ -# --- x4.py (Modificaciones v_final_2) --- +# --- x4_refactored.py --- import json from typing import List, Dict, Any import sys import os -import glob # Para buscar archivos JSON +import glob +from x3 import flatten_db_structure script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -19,7 +20,6 @@ def find_working_directory(): sys.exit(1) 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: base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"] 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 \ not var_info.get("udt_source_name") and \ var_info.get("children")) - if not is_multiline_struct_def: # Solo añadir ; si no es una cabecera de STRUCT multilínea + if not is_multiline_struct_def: line += ';' if var_info.get("comment"): @@ -60,103 +60,112 @@ def generate_struct_members_for_source(members: List[Dict[str, Any]], indent_lev not var_info.get("udt_source_name") and \ var_info.get("children"): current_indent_str = " " * indent_level - lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT') # SIN ; + lines.append(f'{current_indent_str}{var_info["name"]} : STRUCT') lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1)) - lines.append(f'{current_indent_str}END_STRUCT;') # CON ; + lines.append(f'{current_indent_str}END_STRUCT;') else: lines.append(generate_variable_declaration_for_source(var_info, indent_level)) return lines 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 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") - - if ordered_assignments and isinstance(ordered_assignments, list): - print(f"INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB '{db_info['name']}'.") - for path, value_obj in ordered_assignments: - value_str = str(value_obj) + + # Obtener todas las variables aplanadas y ordenadas + flat_vars = flatten_db_structure(db_info) + + # Para cada variable en el orden correcto, generar la asignación + 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" elif value_str.lower() == "false": value_str = "FALSE" - lines.append(f"{indent_str}{path} := {value_str};") # Asignaciones siempre con ; - else: - 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.") - # (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) - # if fallback_lines: lines.extend(fallback_lines) + + # Generar la línea de asignación + lines.append(f"{indent_str}{var['full_path']} := {value_str};") + + return lines +def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]: + """ + Genera una tabla markdown completa con offsets de bits correctos. + """ + lines = [] + lines.append(f"## Documentación para DB: {db_info['name']}") + lines.append("") + lines.append("| Address | Name | Type | Initial Value | Actual Value | Comment |") + lines.append("|---|---|---|---|---|---|") + + # Obtener todas las variables aplanadas (ya ordenadas por offset) + flat_vars = flatten_db_structure(db_info) + + # Mostrar todas las variables, incluyendo elementos de array + for var in flat_vars: + # Usar el address_display pre-calculado + address = var["address_display"] + name_for_display = var["full_path"] + + # Formatear tipo adecuadamente según sea variable normal o elemento de array + if var.get("is_array_element"): + # Para elementos de array, mostrar solo el tipo base + if "array_dimensions" in var: + # Si todavía tenemos información de array, eliminar la parte ARRAY[..] + base_type = var["data_type"] + data_type_str = base_type + else: + 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 def generate_s7_source_code_lines(data: Dict[str, Any]) -> List[str]: lines = [] for udt in data.get("udts", []): lines.append(f'TYPE "{udt["name"]}"') - if udt.get("family"): lines.append(f' FAMILY : {udt["family"]}') # SIN ; - if udt.get("version"): lines.append(f' VERSION : {udt["version"]}') # SIN ; + if udt.get("family"): lines.append(f' FAMILY : {udt["family"]}') + if udt.get("version"): lines.append(f' VERSION : {udt["version"]}') lines.append("") - lines.append(" STRUCT") # SIN ; + lines.append(" STRUCT") lines.extend(generate_struct_members_for_source(udt["members"], 2)) - lines.append(" END_STRUCT;") # CON ; - lines.append(f'END_TYPE') # SIN ; según tu último comentario + lines.append(" END_STRUCT;") + lines.append(f'END_TYPE') lines.append("") for db in data.get("dbs", []): lines.append(f'DATA_BLOCK "{db["name"]}"') - if db.get("title"): # TITLE = { ... } va tal cual y SIN ; + if db.get("title"): lines.append(f' TITLE = {db["title"]}') - if db.get("family"): lines.append(f' FAMILY : {db["family"]}') # SIN ; - if db.get("version"): lines.append(f' VERSION : {db["version"]}') # SIN ; + if db.get("family"): lines.append(f' FAMILY : {db["family"]}') + if db.get("version"): lines.append(f' VERSION : {db["version"]}') lines.append("") - lines.append(" STRUCT") # SIN ; + lines.append(" STRUCT") lines.extend(generate_struct_members_for_source(db["members"], 2)) - lines.append(" END_STRUCT;") # CON ; + lines.append(" END_STRUCT;") - begin_assignments = generate_begin_block_assignments(db, 1) # Indentación 1 para las asignaciones + begin_assignments = generate_begin_block_assignments(db, 1) if begin_assignments: - lines.append("BEGIN") # SIN ; + lines.append("BEGIN") lines.extend(begin_assignments) - lines.append(f'END_DATA_BLOCK') # SIN ; según tu último comentario + lines.append(f'END_DATA_BLOCK') lines.append("") 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(): working_dir = find_working_directory() print(f"Using working directory: {working_dir}") @@ -188,7 +197,7 @@ def main(): print(f"Archivo JSON '{current_json_filename}' cargado correctamente.") except Exception as e: print(f"Error al cargar/leer {current_json_filename}: {e}") - continue # Saltar al siguiente archivo JSON + continue # Generar archivo S7 (.txt) s7_code_lines = generate_s7_source_code_lines(data_from_json) @@ -209,11 +218,11 @@ def main(): for db_index, db_to_document in enumerate(data_from_json["dbs"]): if db_index > 0: - all_db_markdown_lines.append("\n---\n") # Separador visual entre DBs + all_db_markdown_lines.append("\n---\n") 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.append("") # Espacio después de cada tabla de DB + all_db_markdown_lines.append("") try: with open(md_output_filename, 'w', encoding='utf-8') as f: @@ -224,7 +233,6 @@ def main(): print(f"Error al escribir el archivo Markdown {md_output_filename}: {e}") else: 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: 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}") diff --git a/backend/script_groups/S7_DB_Utils/x5.py b/backend/script_groups/S7_DB_Utils/x5.py index 767abaa..2451f88 100644 --- a/backend/script_groups/S7_DB_Utils/x5.py +++ b/backend/script_groups/S7_DB_Utils/x5.py @@ -1,9 +1,10 @@ +# --- x5_refactored.py --- import json from typing import List, Dict, Any, Optional import sys import os -import glob # Para buscar archivos JSON -from datetime import datetime # Mover import al inicio +import glob +from datetime import datetime script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -11,6 +12,10 @@ script_root = os.path.dirname( sys.path.append(script_root) 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(): configs = load_configuration() working_directory = configs.get("working_directory") @@ -19,94 +24,85 @@ def find_working_directory(): sys.exit(1) 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( - members: List[Dict[str, Any]], - path_prefix: str = "", + db_info: Dict[str, Any], is_udt_definition: bool = False, - include_current_value: bool = False + include_current_value: bool = True ) -> List[str]: - """Genera líneas de tabla Markdown para una lista de miembros.""" - md_lines = [] - for var_info in members: - name_display = f"{path_prefix}{var_info['name']}" + """ + Genera tabla markdown para todos los miembros usando las funciones de aplanamiento de x3. + """ + lines = [] + + # Definir columnas de la tabla + if include_current_value: + header = "| Nombre Miembro (Ruta) | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial (Decl.) | Valor Actual (Efectivo) | Comentario |" + separator = "|---|---|---|---|---|---|---|---|" + else: + header = "| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |" + separator = "|---|---|---|---|---|---|---|" + + lines.append(header) + lines.append(separator) + + # Usar la función de aplanamiento importada + flat_vars = flatten_db_structure(db_info) + + # Generar filas para cada variable + 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 "" - # Para miembros expandidos de un UDT, su nombre ya está completo en la jerarquía del JSON. - # La recursión ya habrá construido el path_prefix. - # No necesitamos hacer nada especial aquí si `is_udt_expanded_member` es true, - # ya que esta función se llama recursivamente sobre `children`. - - data_type_display = format_data_type_for_display(var_info) - 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 "" - - initial_value_display = str(var_info.get('initial_value', '')).replace("|", "\\|").replace("\n", " ") - comment_display = str(var_info.get('comment', '')).replace("|", "\\|").replace("\n", " ") - - row = f"| `{name_display}` | `{data_type_display}` | {offset_display} | {size_bytes_display} | {bit_size_display} | `{initial_value_display}` |" + 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: - current_value_display = "" - # Si es un array y tiene current_element_values - if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict): - # Mostrar un resumen o un placeholder para arrays complejos en la tabla principal - # Los valores detallados del array se listarán en la sección BEGIN. - num_elements = sum(dim['count'] for dim in var_info.get('array_dimensions', [])) if var_info.get('array_dimensions') else 1 - if num_elements == 0 and var_info.get("array_dimensions"): # Caso de ARRAY [x..y] donde x > y (raro, pero posible) - num_elements = 1 # Para evitar división por cero o lógica extraña. - - assigned_elements = len(var_info["current_element_values"]) - if assigned_elements > 0: - current_value_display = f"{assigned_elements} elemento(s) asignado(s) en BEGIN" - elif var_info.get("current_value") is not None: # Para arrays con una asignación global (raro en BEGIN) - current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ") - else: - current_value_display = "" + current_value = str(var.get('current_value', '')).replace("|", "\\|").replace("\n", " ") + current_value_display = f"`{current_value}`" if current_value else "" + row = f"| {name_display} | {data_type_display} | {address} | {size_bytes_display} | {bit_size_display} | {initial_value_display} | {current_value_display} | {comment_display} |" + else: + row = f"| {name_display} | {data_type_display} | {address} | {size_bytes_display} | {bit_size_display} | {initial_value_display} | {comment_display} |" + + lines.append(row) + + 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}` |" - - row += f" {comment_display} |" - md_lines.append(row) - - # Recursión para hijos de STRUCTs o miembros expandidos de UDTs - # `is_udt_expanded_member` en el JSON nos dice si los 'children' son la expansión de un UDT. - 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 - )) +def generate_begin_block_documentation(db_info: Dict[str, Any]) -> List[str]: + """ + 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 + flat_vars = flatten_db_structure(db_info) + + # Filtrar solo variables con valores actuales + vars_with_values = [var for var in flat_vars if var.get("current_value") is not None] + + if vars_with_values: + lines.append("```scl") + for var in vars_with_values: + value_str = str(var["current_value"]) + if value_str.lower() == "true": value_str = "TRUE" + elif value_str.lower() == "false": value_str = "FALSE" - return md_lines - + 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): """Genera la documentación Markdown completa para el archivo JSON parseado.""" @@ -129,9 +125,10 @@ 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("") lines.append("#### Miembros del UDT:") - lines.append("| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |") - lines.append("|---|---|---|---|---|---|---|") - lines.extend(generate_members_table_md(udt.get("members", []), is_udt_definition=True, include_current_value=False)) + + # Usar la función optimizada para generar tabla + udt_member_lines = generate_members_table_md(udt, is_udt_definition=True, include_current_value=False) + lines.extend(udt_member_lines) lines.append("") else: lines.append("No se encontraron UDTs en el archivo JSON.") @@ -150,28 +147,14 @@ def generate_json_documentation(data: Dict[str, Any], output_filename: str): lines.append("") lines.append("#### Miembros del DB (Sección de Declaración):") - 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.append("|---|---|---|---|---|---|---|---|") - lines.extend(generate_members_table_md(db.get("members", []), include_current_value=True)) + db_member_lines = generate_members_table_md(db, include_current_value=True) + lines.extend(db_member_lines) lines.append("") - # Sección BEGIN - ordered_assignments = db.get("_begin_block_assignments_ordered") - 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("") + # Generar sección BEGIN usando la función optimizada + begin_lines = generate_begin_block_documentation(db) + lines.extend(begin_lines) + else: lines.append("No se encontraron DBs en el archivo JSON.") @@ -184,7 +167,6 @@ def generate_json_documentation(data: Dict[str, Any], output_filename: str): except Exception as e: print(f"Error al escribir el archivo Markdown de documentación {output_filename}: {e}") -# --- Main --- if __name__ == "__main__": working_dir = find_working_directory() print(f"Using working directory: {working_dir}") @@ -199,7 +181,6 @@ if __name__ == "__main__": if not json_files_to_process: print(f"No se encontraron archivos .json en {input_json_dir}") else: - print(f"Archivos JSON encontrados para procesar: {len(json_files_to_process)}") for json_input_filepath in json_files_to_process: @@ -213,12 +194,6 @@ if __name__ == "__main__": with open(json_input_filepath, 'r', encoding='utf-8') as f: data_from_json = json.load(f) 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: print(f"Error al leer el archivo JSON {json_input_filepath}: {e}") continue @@ -228,4 +203,4 @@ if __name__ == "__main__": except Exception as e: print(f"Error al generar la documentación para {current_json_filename}: {e}") - print("\n--- Proceso de generación de descripciones Markdown completado ---") + print("\n--- Proceso de generación de descripciones Markdown completado ---") \ No newline at end of file diff --git a/backend/script_groups/S7_DB_Utils/x6.py b/backend/script_groups/S7_DB_Utils/x6.py index 15a8c3d..8e6cccd 100644 --- a/backend/script_groups/S7_DB_Utils/x6.py +++ b/backend/script_groups/S7_DB_Utils/x6.py @@ -1,11 +1,11 @@ -# --- x6.py --- +# --- x6_refactored.py --- import json from typing import List, Dict, Any -import openpyxl # For Excel export +import openpyxl from openpyxl.utils import get_column_letter import sys import os -import glob # Para buscar archivos JSON +import glob script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -13,6 +13,10 @@ script_root = os.path.dirname( sys.path.append(script_root) 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(): configs = load_configuration() working_directory = configs.get("working_directory") @@ -21,90 +25,90 @@ def find_working_directory(): sys.exit(1) 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): """ - Generates an Excel file with DB documentation. + Genera un archivo Excel con documentación del DB usando flatten_db_structure. """ workbook = openpyxl.Workbook() sheet = workbook.active db_name_safe = db_info['name'].replace('"', '').replace(' ', '_').replace('/','_') - sheet.title = f"DB_{db_name_safe}"[:31] # Sheet names have a length limit + sheet.title = f"DB_{db_name_safe}"[:31] # Sheet names tienen límite de longitud - headers = ["Address", "Name", "Type", "Initial Value", "Actual Value", "Comment"] + # Definir encabezados + headers = ["Address", "Name", "Type", "Size (Bytes)", "Bit Size", "Initial Value", "Actual Value", "Comment"] for col_num, header in enumerate(headers, 1): cell = sheet.cell(row=1, column=col_num, value=header) cell.font = openpyxl.styles.Font(bold=True) - current_row = 2 - processed_expanded_members = set() # To handle expanded UDT members correctly + # Usar flatten_db_structure importado de x3 + flat_vars = flatten_db_structure(db_info) + + # Poblar filas con los datos + for row_num, var in enumerate(flat_vars, 2): + # Columna 1: Address + address = var.get("address_display", format_address_for_display(var["byte_offset"], var.get("bit_size", 0))) + sheet.cell(row=row_num, column=1, value=address) + + # Columna 2: Name + sheet.cell(row=row_num, column=2, value=var["full_path"]) + + # Columna 3: Type + data_type = format_data_type_for_source(var) + sheet.cell(row=row_num, column=3, value=data_type) + + # Columna 4: Size (Bytes) + sheet.cell(row=row_num, column=4, value=var.get("size_in_bytes", 0)) + + # Columna 5: Bit Size + sheet.cell(row=row_num, column=5, value=var.get("bit_size", 0) if var.get("bit_size", 0) > 0 else None) + + # Columna 6: Initial Value + sheet.cell(row=row_num, column=6, value=var.get("initial_value", "")) + + # Columna 7: Actual Value + sheet.cell(row=row_num, column=7, value=var.get("current_value", "")) + + # Columna 8: Comment + sheet.cell(row=row_num, column=8, value=var.get("comment", "")) - def flatten_members_for_excel(members: List[Dict[str, Any]], prefix: str = "", base_offset: float = 0.0, is_expansion: bool = False): - nonlocal current_row - for var_idx, var in enumerate(members): - member_id = f"{prefix}{var['name']}_{var_idx}" # Unique ID for processed check - 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']) - # Adjust address formatting for bits as in markdown generation - 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 - - data_type_str = format_data_type_for_source(var) - initial_value = str(var.get("initial_value", "")) - actual_value = str(var.get("current_value", "")) - comment = str(var.get("comment", "")) - - 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"): - 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 - - if var.get("children"): - 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 - - flatten_members_for_excel(db_info.get("members", [])) - - # Auto-size columns for better readability - 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 = (max_length + 2) - sheet.column_dimensions[column].width = adjusted_width + # Crear una segunda hoja para el bloque BEGIN + begin_sheet = workbook.create_sheet(title="BEGIN_Values") + begin_headers = ["Address", "Path", "Value"] + for col_num, header in enumerate(begin_headers, 1): + cell = begin_sheet.cell(row=1, column=col_num, value=header) + cell.font = openpyxl.styles.Font(bold=True) + + # Filtrar solo variables con valores actuales para la hoja BEGIN + vars_with_values = [var for var in flat_vars if var.get("current_value") is not None] + + # Poblar la hoja BEGIN + for row_num, var in enumerate(vars_with_values, 2): + # Columna 1: Address + begin_sheet.cell(row=row_num, column=1, value=var.get("address_display")) + + # Columna 2: Path + begin_sheet.cell(row=row_num, column=2, value=var["full_path"]) + + # Columna 3: Value + value_str = str(var["current_value"]) + if value_str.lower() == "true": value_str = "TRUE" + elif value_str.lower() == "false": value_str = "FALSE" + begin_sheet.cell(row=row_num, column=3, value=value_str) + + # Auto-ajustar columnas para mejor legibilidad + for sheet in workbook.worksheets: + for col_idx, column_cells in enumerate(sheet.columns, 1): + 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: workbook.save(excel_filename) @@ -137,20 +141,13 @@ def main(): with open(json_input_filepath, 'r', encoding='utf-8') as f: data_from_json = json.load(f) 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: print(f"Error al cargar/leer {current_json_filename}: {e}") continue if data_from_json.get("dbs"): for db_to_document in data_from_json["dbs"]: - # Construir el path completo para el archivo Excel de salida - excel_output_filename = os.path.join(documentation_dir, f"{current_json_filename}.xlsx") + excel_output_filename = os.path.join(documentation_dir, f"{current_json_filename}_{db_to_document['name'].replace('"', '')}.xlsx") print(f"Generando documentación Excel para DB: '{db_to_document['name']}' (desde {current_json_filename}) -> {excel_output_filename}") try: diff --git a/backend/script_groups/S7_DB_Utils/x7_value_updater.py b/backend/script_groups/S7_DB_Utils/x7_value_updater.py index 0e75f39..1b7c465 100644 --- a/backend/script_groups/S7_DB_Utils/x7_value_updater.py +++ b/backend/script_groups/S7_DB_Utils/x7_value_updater.py @@ -1,37 +1,39 @@ -# --- x7.py --- +# --- x7_refactored.py --- import json import os import glob import sys import copy +import shutil # Para copiar archivos from typing import Dict, List, Tuple, Any, Optional -# Importar load_configuration desde backend.script_utils +# Importar para el path script_root = os.path.dirname( os.path.dirname(os.path.dirname(os.path.dirname(__file__))) ) sys.path.append(script_root) from backend.script_utils import load_configuration -# Importar lo necesario desde x3.py -sys.path.append(os.path.dirname(__file__)) -from x3 import S7Parser, find_working_directory, custom_json_serializer, ParsedData +# Importar desde x3 +from x3 import S7Parser, find_working_directory, custom_json_serializer, flatten_db_structure, format_address_for_display +from x4 import format_data_type_for_source + +# Importar desde x4 para generar archivos +from x4 import generate_s7_source_code_lines, generate_markdown_table def find_matching_files(working_dir: str) -> List[Tuple[str, str]]: """ Busca pares de archivos _data y _format con extensión .db o .awl. """ - # Buscar archivos _data + # [Código existente] data_files_db = glob.glob(os.path.join(working_dir, "*_data.db")) data_files_awl = glob.glob(os.path.join(working_dir, "*_data.awl")) all_data_files = data_files_db + data_files_awl - # Buscar archivos _format format_files_db = glob.glob(os.path.join(working_dir, "*_format.db")) format_files_awl = glob.glob(os.path.join(working_dir, "*_format.awl")) all_format_files = format_files_db + format_files_awl - # Emparejar archivos _data y _format matched_pairs = [] for data_file in all_data_files: base_name = os.path.basename(data_file).replace("_data", "").split('.')[0] @@ -41,11 +43,12 @@ def find_matching_files(working_dir: str) -> List[Tuple[str, str]]: return matched_pairs +# [Otras funciones existentes: parse_files_to_json, compare_structures_by_offset, update_values_recursive, create_updated_json] + def parse_files_to_json(data_file: str, format_file: str, json_dir: str) -> Tuple[Dict, Dict]: """ Parsea los archivos _data y _format usando S7Parser y guarda los resultados como JSON. """ - # Instancias separadas del parser para cada archivo data_parser = S7Parser() format_parser = S7Parser() @@ -55,14 +58,12 @@ def parse_files_to_json(data_file: str, format_file: str, json_dir: str) -> Tupl print(f"Parseando archivo format: {os.path.basename(format_file)}") format_result = format_parser.parse_file(format_file) - # Guardar resultados como JSON data_base = os.path.splitext(os.path.basename(data_file))[0] format_base = os.path.splitext(os.path.basename(format_file))[0] data_json_path = os.path.join(json_dir, f"{data_base}.json") format_json_path = os.path.join(json_dir, f"{format_base}.json") - # Serializar y guardar como JSON data_json = json.dumps(data_result, default=custom_json_serializer, indent=2) format_json = json.dumps(format_result, default=custom_json_serializer, indent=2) @@ -74,273 +75,514 @@ def parse_files_to_json(data_file: str, format_file: str, json_dir: str) -> Tupl print(f"Archivos JSON generados: {os.path.basename(data_json_path)} y {os.path.basename(format_json_path)}") - # Cargar de nuevo como objetos para procesamiento data_obj = json.loads(data_json) format_obj = json.loads(format_json) return data_obj, format_obj -def create_offset_path_map(members: List[Dict], path_prefix: str = "") -> Dict[float, str]: - """ - Crea un mapa que asocia cada offset con la ruta completa de la variable. - Esto será usado para actualizar las asignaciones del bloque BEGIN. - """ - offset_to_path = {} - - def process_member(member: Dict, current_path_prefix: str): - offset = member["byte_offset"] - full_path = f"{current_path_prefix}{member['name']}" - - # Mapear offset a ruta completa - offset_to_path[offset] = full_path - - # Procesar hijos recursivamente - if "children" in member and member["children"]: - for child in member["children"]: - process_member(child, f"{full_path}.") - - # Procesar todos los miembros - for member in members: - process_member(member, path_prefix) - - return offset_to_path - -def flatten_variables_by_offset(data: Dict) -> Dict[float, Dict]: - """ - Aplana completamente todas las variables por offset, similar a flatten_members_for_markdown. - Incluye UDTs expandidos, estructuras anidadas, etc. - """ - offset_map = {} - processed_expanded_members = set() - - def process_members(members: List[Dict], prefix: str = "", is_expansion: bool = False): - for var_idx, var in enumerate(members): - # Control para miembros UDT expandidos (evitar duplicados) - 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) - - # Extraer offset e información de la variable - offset = var["byte_offset"] - var_info = { - "path": f"{prefix}{var['name']}", - "data_type": var["data_type"], - "size_in_bytes": var["size_in_bytes"], - "bit_size": var.get("bit_size", 0), - "initial_value": var.get("initial_value"), - "current_value": var.get("current_value"), - "current_element_values": var.get("current_element_values") - } - - # Guardar en mapa por offset - offset_map[offset] = var_info - - # Procesar recursivamente los hijos - if "children" in var and var["children"]: - process_members( - var["children"], - f"{prefix}{var['name']}.", - is_expansion=bool(var.get("udt_source_name")) - ) - - # Procesar todos los DBs - for db in data.get("dbs", []): - process_members(db.get("members", [])) - - return offset_map - -def create_path_to_offset_map(members: List[Dict], path_prefix: str = "") -> Dict[str, float]: - """ - Crea un mapa que asocia cada ruta (path) completa con su offset. - Esto será usado para actualizar las asignaciones del bloque BEGIN. - """ - path_to_offset = {} - processed_expanded_members = set() - - def process_member(member: Dict, current_path_prefix: str, is_expansion: bool = False): - member_id = f"{current_path_prefix}{member['name']}" - - # Evitar duplicados para miembros expandidos de UDTs - if is_expansion and member_id in processed_expanded_members: - return - if is_expansion: - processed_expanded_members.add(member_id) - - offset = member["byte_offset"] - path = f"{current_path_prefix}{member['name']}" - - # Mapear ruta a offset - path_to_offset[path] = offset - - # Para arrays, también mapear rutas con índices si hay valores iniciales - if member.get("array_dimensions") and member.get("current_element_values"): - for index in member["current_element_values"].keys(): - array_path = f"{path}[{index}]" - path_to_offset[array_path] = offset - - # Procesar hijos recursivamente - if "children" in member and member["children"]: - for child in member["children"]: - process_member( - child, - f"{path}.", - is_expansion=bool(member.get("udt_source_name")) - ) - - # Procesar todos los miembros - for member in members: - process_member(member, path_prefix) - - return path_to_offset - -def compare_structures_by_offset(data_vars: Dict[float, Dict], format_vars: Dict[float, Dict]) -> Tuple[bool, List[str]]: +def compare_structures_by_offset(data_vars: List[Dict], format_vars: List[Dict]) -> Tuple[bool, List[str]]: """ Compara variables por offset, verificando compatibilidad. + Usa las listas aplanadas de flatten_db_structure. """ issues = [] + # Crear diccionarios para búsqueda rápida por offset + data_by_offset = {var["byte_offset"]: var for var in data_vars} + format_by_offset = {var["byte_offset"]: var for var in format_vars} + # Recopilar todos los offsets únicos de ambos conjuntos - all_offsets = sorted(set(list(data_vars.keys()) + list(format_vars.keys()))) + all_offsets = sorted(set(list(data_by_offset.keys()) + list(format_by_offset.keys()))) # Verificar que todos los offsets existan en ambos conjuntos for offset in all_offsets: - if offset not in data_vars: + if offset not in data_by_offset: issues.append(f"Offset {offset} existe en _format pero no en _data") continue - if offset not in format_vars: + if offset not in format_by_offset: issues.append(f"Offset {offset} existe en _data pero no en _format") continue + # Obtener las variables para comparar + data_var = data_by_offset[offset] + format_var = format_by_offset[offset] + # Verificar coincidencia de tipos - data_type = data_vars[offset]["data_type"].upper() - format_type = format_vars[offset]["data_type"].upper() + data_type = data_var["data_type"].upper() + format_type = format_var["data_type"].upper() if data_type != format_type: - issues.append(f"Tipo de dato diferente en offset {offset}: {data_type} ({data_vars[offset]['path']}) vs {format_type} ({format_vars[offset]['path']})") + issues.append(f"Tipo de dato diferente en offset {offset}: {data_type} ({data_var['full_path']}) vs {format_type} ({format_var['full_path']})") # Verificar tamaño - data_size = data_vars[offset]["size_in_bytes"] - format_size = format_vars[offset]["size_in_bytes"] + data_size = data_var["size_in_bytes"] + format_size = format_var["size_in_bytes"] if data_size != format_size: - issues.append(f"Tamaño diferente en offset {offset}: {data_size} bytes ({data_vars[offset]['path']}) vs {format_size} bytes ({format_vars[offset]['path']})") + issues.append(f"Tamaño diferente en offset {offset}: {data_size} bytes ({data_var['full_path']}) vs {format_size} bytes ({format_var['full_path']})") # Verificar tamaño en bits para BOOLs - data_bit_size = data_vars[offset]["bit_size"] - format_bit_size = format_vars[offset]["bit_size"] + data_bit_size = data_var.get("bit_size", 0) + format_bit_size = format_var.get("bit_size", 0) if data_bit_size != format_bit_size: - issues.append(f"Tamaño en bits diferente en offset {offset}: {data_bit_size} ({data_vars[offset]['path']}) vs {format_bit_size} ({format_vars[offset]['path']})") + issues.append(f"Tamaño en bits diferente en offset {offset}: {data_bit_size} ({data_var['full_path']}) vs {format_bit_size} ({format_var['full_path']})") return len(issues) == 0, issues -def update_values_recursive(target_member: Dict, data_offset_map: Dict[float, Dict]): - """ - Actualiza los valores de target_member con valores de data_offset_map basado en offset. - """ - offset = target_member["byte_offset"] - - # Si encontramos una variable con el mismo offset en _data, tomar sus valores - if offset in data_offset_map: - data_var = data_offset_map[offset] - - # Actualizar initial_value - if "initial_value" in data_var and data_var["initial_value"] is not None: - target_member["initial_value"] = data_var["initial_value"] - - # Actualizar current_value - if "current_value" in data_var and data_var["current_value"] is not None: - target_member["current_value"] = data_var["current_value"] - - # Actualizar current_element_values (para arrays) - if "current_element_values" in data_var and data_var["current_element_values"]: - target_member["current_element_values"] = data_var["current_element_values"] - - # Actualizar recursivamente los hijos - if "children" in target_member and target_member["children"]: - for child in target_member["children"]: - update_values_recursive(child, data_offset_map) def create_updated_json(data_json: Dict, format_json: Dict) -> Dict: """ Crea JSON actualizado basado en la estructura de _format con valores de _data. + Utiliza offset como clave principal para encontrar variables correspondientes. + Reporta errores si no se encuentra un offset correspondiente. """ # Copia profunda de format_json para no modificar el original updated_json = copy.deepcopy(format_json) - # Extraer todas las variables flat por offset - data_offset_map = flatten_variables_by_offset(data_json) - - # Crear mapas de offsets y rutas para cada BD - db_maps = {} - for db_idx, db in enumerate(format_json.get("dbs", [])): - db_name = db["name"] - db_maps[db_name] = { - "offset_to_path": create_offset_path_map(db.get("members", [])), - "path_to_offset": create_path_to_offset_map(db.get("members", [])) - } - - # Actualizar valores de variables en la estructura de formato - for db_idx, db in enumerate(updated_json.get("dbs", [])): - for member in db.get("members", []): - update_values_recursive(member, data_offset_map) + # Procesar cada DB + for db_idx, format_db in enumerate(format_json.get("dbs", [])): + # Buscar el DB correspondiente en data_json + data_db = next((db for db in data_json.get("dbs", []) if db["name"] == format_db["name"]), None) + if not data_db: + print(f"Error: No se encontró DB '{format_db['name']}' en data_json") + continue # No hay DB correspondiente en data_json - # Actualizar también asignaciones del bloque BEGIN - db_name = db["name"] - data_db = next((d for d in data_json.get("dbs", []) if d["name"] == db_name), None) + # Aplanar variables de ambos DBs + flat_data_vars = flatten_db_structure(data_db) + flat_format_vars = flatten_db_structure(format_db) - if data_db and "_begin_block_assignments_ordered" in data_db: - # Obtener los mapas para este DB - offset_to_path = db_maps[db_name]["offset_to_path"] + # Crear mapa de offset a variable para data + data_by_offset = {var["byte_offset"]: var for var in flat_data_vars} + + # Para cada variable en format, buscar su correspondiente en data por offset + for format_var in flat_format_vars: + offset = format_var["byte_offset"] + path = format_var["full_path"] - # Crear una nueva lista de asignaciones con las rutas correctas - updated_assignments = [] - - # Para cada asignación en los datos de origen - for path, value in data_db["_begin_block_assignments_ordered"]: - # Búsqueda por offset si es posible - data_db_path_to_offset = create_path_to_offset_map(data_db.get("members", [])) + # Buscar la variable correspondiente en data_json por offset + if offset in data_by_offset: + data_var = data_by_offset[offset] - if path in data_db_path_to_offset: - # Obtener el offset de la ruta original - offset = data_db_path_to_offset[path] - - # Buscar la ruta correspondiente en el formato usando el offset - if offset in offset_to_path: - new_path = offset_to_path[offset] - updated_assignments.append([new_path, value]) - print(f"Mapeando {path} -> {new_path} (offset {offset})") + # Encontrar la variable original en la estructura jerárquica + path_parts = format_var["full_path"].split('.') + current_node = updated_json["dbs"][db_idx] + + # Variable para rastrear si se encontró la ruta + path_found = True + + # Navegar la jerarquía hasta encontrar el nodo padre + for i in range(len(path_parts) - 1): + if "members" in current_node: + # Buscar el miembro correspondiente + member_name = path_parts[i] + matching_members = [m for m in current_node["members"] if m["name"] == member_name] + if matching_members: + current_node = matching_members[0] + else: + print(f"Error: No se encontró el miembro '{member_name}' en la ruta '{path}'") + path_found = False + break # No se encontró la ruta + elif "children" in current_node: + # Buscar el hijo correspondiente + child_name = path_parts[i] + matching_children = [c for c in current_node["children"] if c["name"] == child_name] + if matching_children: + current_node = matching_children[0] + else: + print(f"Error: No se encontró el hijo '{child_name}' en la ruta '{path}'") + path_found = False + break # No se encontró la ruta else: - print(f"Advertencia: No se encontró un mapeo para el offset {offset} ({path})") - else: - print(f"Advertencia: No se pudo determinar el offset para la ruta {path}") - - # Actualizar asignaciones en el JSON actualizado - db["_begin_block_assignments_ordered"] = updated_assignments - - # También actualizar el diccionario _initial_values_from_begin_block - if "_initial_values_from_begin_block" in data_db: - updated_values = {} - for path, value in updated_assignments: - updated_values[path] = value - db["_initial_values_from_begin_block"] = updated_values + print(f"Error: No se puede navegar más en la ruta '{path}', nodo actual no tiene members ni children") + path_found = False + break # No se puede navegar más + + # Si encontramos el nodo padre, actualizar el hijo + if path_found and ("members" in current_node or "children" in current_node): + target_list = current_node.get("members", current_node.get("children", [])) + target_name = path_parts[-1] + + # Si es un elemento de array, extraer el nombre base y el índice + if '[' in target_name and ']' in target_name: + base_name = target_name.split('[')[0] + index_str = target_name[target_name.find('[')+1:target_name.find(']')] + + # Buscar el array base + array_var = next((var for var in target_list if var["name"] == base_name), None) + if array_var: + # Asegurarse que existe current_element_values + if "current_element_values" not in array_var: + array_var["current_element_values"] = {} + + # Copiar el valor del elemento del array + if "current_value" in data_var: + array_var["current_element_values"][index_str] = { + "value": data_var["current_value"], + "offset": data_var["byte_offset"] + } + else: + # Buscar la variable a actualizar + target_var_found = False + for target_var in target_list: + if target_var["name"] == target_name: + target_var_found = True + + # Limpiar y copiar initial_value si existe + if "initial_value" in target_var: + del target_var["initial_value"] + if "initial_value" in data_var and data_var["initial_value"] is not None: + target_var["initial_value"] = data_var["initial_value"] + + # Limpiar y copiar current_value si existe + if "current_value" in target_var: + del target_var["current_value"] + if "current_value" in data_var and data_var["current_value"] is not None: + target_var["current_value"] = data_var["current_value"] + + # Limpiar y copiar current_element_values si existe + if "current_element_values" in target_var: + del target_var["current_element_values"] + if "current_element_values" in data_var and data_var["current_element_values"]: + target_var["current_element_values"] = copy.deepcopy(data_var["current_element_values"]) + + break + + if not target_var_found and not ('[' in target_name and ']' in target_name): + print(f"Error: No se encontró la variable '{target_name}' en la ruta '{path}'") + else: + # El offset no existe en data_json, reportar error + print(f"Error: Offset {offset} (para '{path}') no encontrado en los datos source (_data)") + + # Eliminar valores si es una variable que no es elemento de array + if '[' not in path or ']' not in path: + # Encontrar la variable original en la estructura jerárquica + path_parts = path.split('.') + current_node = updated_json["dbs"][db_idx] + + # Navegar hasta el nodo padre para limpiar valores + path_found = True + for i in range(len(path_parts) - 1): + if "members" in current_node: + member_name = path_parts[i] + matching_members = [m for m in current_node["members"] if m["name"] == member_name] + if matching_members: + current_node = matching_members[0] + else: + path_found = False + break + elif "children" in current_node: + child_name = path_parts[i] + matching_children = [c for c in current_node["children"] if c["name"] == child_name] + if matching_children: + current_node = matching_children[0] + else: + path_found = False + break + else: + path_found = False + break + + if path_found and ("members" in current_node or "children" in current_node): + target_list = current_node.get("members", current_node.get("children", [])) + target_name = path_parts[-1] + + for target_var in target_list: + if target_var["name"] == target_name: + # Eliminar valores iniciales y actuales + if "initial_value" in target_var: + del target_var["initial_value"] + if "current_value" in target_var: + del target_var["current_value"] + if "current_element_values" in target_var: + del target_var["current_element_values"] + break return updated_json +def process_updated_json(updated_json: Dict, updated_json_path: str, working_dir: str, documentation_dir: str, original_format_file: str): + """ + Genera los archivos markdown y S7 a partir del JSON actualizado, y copia el archivo S7 + al directorio de trabajo con la extensión correcta. + """ + # Obtener nombre base y extensión original + format_file_name = os.path.basename(original_format_file) + base_name = format_file_name.replace("_format", "_updated").split('.')[0] + original_extension = os.path.splitext(format_file_name)[1] # .db o .awl + + # Generar archivo markdown para documentación + for db in updated_json.get("dbs", []): + md_output_filename = os.path.join(documentation_dir, f"{base_name}.md") + try: + md_lines = [] + md_lines.append(f"# Documentación S7 para {base_name}") + md_lines.append(f"_Fuente JSON: {os.path.basename(updated_json_path)}_") + md_lines.append("") + + # Generar tabla markdown usando generate_markdown_table importado de x4 + db_md_lines = generate_markdown_table(db) + md_lines.extend(db_md_lines) + + with open(md_output_filename, 'w', encoding='utf-8') as f: + for line in md_lines: + f.write(line + "\n") + print(f"Archivo Markdown generado: {md_output_filename}") + except Exception as e: + print(f"Error al generar Markdown para {base_name}: {e}") + + # Generar archivo de código fuente S7 + s7_txt_filename = os.path.join(documentation_dir, f"{base_name}.txt") + try: + s7_lines = generate_s7_source_code_lines(updated_json) + with open(s7_txt_filename, 'w', encoding='utf-8') as f: + for line in s7_lines: + f.write(line + "\n") + print(f"Archivo S7 generado: {s7_txt_filename}") + + # Copiar al directorio de trabajo con la extensión original + s7_output_filename = os.path.join(working_dir, f"{base_name}{original_extension}") + shutil.copy2(s7_txt_filename, s7_output_filename) + print(f"Archivo S7 copiado a: {s7_output_filename}") + except Exception as e: + print(f"Error al generar archivo S7 para {base_name}: {e}") + +def generate_comparison_excel(format_json: Dict, data_json: Dict, updated_json: Dict, excel_filename: str): + """ + Genera un archivo Excel con dos hojas que comparan los valores iniciales y actuales + entre los archivos format_json, data_json y updated_json. + Filtra STRUCTs y solo compara variables con valores reales. + + Args: + format_json: JSON con la estructura y nombres de formato + data_json: JSON con los datos source + updated_json: JSON con los datos actualizados + excel_filename: Ruta del archivo Excel a generar + """ + import openpyxl + from openpyxl.utils import get_column_letter + from openpyxl.styles import PatternFill, Font + + # Crear un nuevo libro de Excel + workbook = openpyxl.Workbook() + + # Definir estilos para resaltar diferencias + diff_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") # Amarillo + header_font = Font(bold=True) + + # Procesar cada DB + for db_idx, format_db in enumerate(format_json.get("dbs", [])): + # Buscar los DBs correspondientes + db_name = format_db["name"] + data_db = next((db for db in data_json.get("dbs", []) if db["name"] == db_name), None) + updated_db = next((db for db in updated_json.get("dbs", []) if db["name"] == db_name), None) + + if not data_db or not updated_db: + print(f"Error: No se encontró el DB '{db_name}' en alguno de los archivos JSON") + continue + + # Crear hojas para valores iniciales y actuales para este DB + initial_sheet = workbook.active if db_idx == 0 else workbook.create_sheet() + initial_sheet.title = f"{db_name}_Initial"[:31] # Limitar longitud del nombre de hoja + + current_sheet = workbook.create_sheet() + current_sheet.title = f"{db_name}_Current"[:31] + + # Aplanar variables de los tres DBs + flat_format_vars = flatten_db_structure(format_db) + flat_data_vars = flatten_db_structure(data_db) + flat_updated_vars = flatten_db_structure(updated_db) + + # Filtrar STRUCTs - solo trabajamos con variables que tienen valores reales + flat_format_vars = [var for var in flat_format_vars + if var["data_type"].upper() != "STRUCT" and not var.get("children")] + + # Crear mapas de offset a variable para búsqueda rápida + data_by_offset = {var["byte_offset"]: var for var in flat_data_vars + if var["data_type"].upper() != "STRUCT" and not var.get("children")} + updated_by_offset = {var["byte_offset"]: var for var in flat_updated_vars + if var["data_type"].upper() != "STRUCT" and not var.get("children")} + + # Configurar encabezados para la hoja de valores iniciales + headers_initial = ["Address", "Name", "Type", "Format Initial", "Data Initial", "Updated Initial", "Difference"] + for col_num, header in enumerate(headers_initial, 1): + cell = initial_sheet.cell(row=1, column=col_num, value=header) + cell.font = header_font + + # Configurar encabezados para la hoja de valores actuales + headers_current = ["Address", "Name", "Type", "Format Current", "Data Current", "Updated Current", "Difference"] + for col_num, header in enumerate(headers_current, 1): + cell = current_sheet.cell(row=1, column=col_num, value=header) + cell.font = header_font + + # Llenar las hojas con datos + initial_row = 2 + current_row = 2 + + for format_var in flat_format_vars: + offset = format_var["byte_offset"] + path = format_var["full_path"] + data_type = format_data_type_for_source(format_var) + address = format_var.get("address_display", format_address_for_display(offset, format_var.get("bit_size", 0))) + + # Obtener variables correspondientes por offset + data_var = data_by_offset.get(offset) + updated_var = updated_by_offset.get(offset) + + # Procesar valores iniciales (solo si la variable puede tener initial_value) + format_initial = format_var.get("initial_value", "") + data_initial = data_var.get("initial_value", "") if data_var else "" + updated_initial = updated_var.get("initial_value", "") if updated_var else "" + + # Solo incluir en la hoja de valores iniciales si al menos uno tiene valor inicial + if format_initial or data_initial or updated_initial: + # Determinar si hay diferencias en valores iniciales + has_initial_diff = (format_initial != data_initial or + format_initial != updated_initial or + data_initial != updated_initial) + + # Escribir datos de valores iniciales + initial_sheet.cell(row=initial_row, column=1, value=address) + initial_sheet.cell(row=initial_row, column=2, value=path) + initial_sheet.cell(row=initial_row, column=3, value=data_type) + initial_sheet.cell(row=initial_row, column=4, value=str(format_initial)) + initial_sheet.cell(row=initial_row, column=5, value=str(data_initial)) + initial_sheet.cell(row=initial_row, column=6, value=str(updated_initial)) + + # Resaltar diferencias en valores iniciales + if has_initial_diff: + initial_sheet.cell(row=initial_row, column=7, value="Sí") + for col in range(4, 7): + initial_sheet.cell(row=initial_row, column=col).fill = diff_fill + else: + initial_sheet.cell(row=initial_row, column=7, value="No") + + initial_row += 1 + + # Procesar valores actuales + format_current = format_var.get("current_value", "") + data_current = data_var.get("current_value", "") if data_var else "" + updated_current = updated_var.get("current_value", "") if updated_var else "" + + # Solo incluir en la hoja de valores actuales si al menos uno tiene valor actual + if format_current or data_current or updated_current: + # Determinar si hay diferencias en valores actuales + has_current_diff = (format_current != data_current or + format_current != updated_current or + data_current != updated_current) + + # Escribir datos de valores actuales + current_sheet.cell(row=current_row, column=1, value=address) + current_sheet.cell(row=current_row, column=2, value=path) + current_sheet.cell(row=current_row, column=3, value=data_type) + current_sheet.cell(row=current_row, column=4, value=str(format_current)) + current_sheet.cell(row=current_row, column=5, value=str(data_current)) + current_sheet.cell(row=current_row, column=6, value=str(updated_current)) + + # Resaltar diferencias en valores actuales + if has_current_diff: + current_sheet.cell(row=current_row, column=7, value="Sí") + for col in range(4, 7): + current_sheet.cell(row=current_row, column=col).fill = diff_fill + else: + current_sheet.cell(row=current_row, column=7, value="No") + + current_row += 1 + + # Si es un array, procesamos también sus elementos + if format_var.get("current_element_values") or (data_var and data_var.get("current_element_values")) or (updated_var and updated_var.get("current_element_values")): + format_elements = format_var.get("current_element_values", {}) + data_elements = data_var.get("current_element_values", {}) if data_var else {} + updated_elements = updated_var.get("current_element_values", {}) if updated_var else {} + + # Unir todos los índices disponibles + all_indices = set(list(format_elements.keys()) + + list(data_elements.keys()) + + list(updated_elements.keys())) + + # Ordenar índices numéricamente + sorted_indices = sorted(all_indices, key=lambda x: [int(i) for i in x.split(',')]) if all_indices else [] + + for idx in sorted_indices: + elem_path = f"{path}[{idx}]" + + # Valores actuales para elementos de array + format_elem_val = "" + if idx in format_elements: + if isinstance(format_elements[idx], dict) and "value" in format_elements[idx]: + format_elem_val = format_elements[idx]["value"] + else: + format_elem_val = format_elements[idx] + + data_elem_val = "" + if idx in data_elements: + if isinstance(data_elements[idx], dict) and "value" in data_elements[idx]: + data_elem_val = data_elements[idx]["value"] + else: + data_elem_val = data_elements[idx] + + updated_elem_val = "" + if idx in updated_elements: + if isinstance(updated_elements[idx], dict) and "value" in updated_elements[idx]: + updated_elem_val = updated_elements[idx]["value"] + else: + updated_elem_val = updated_elements[idx] + + # Determinar si hay diferencias + has_elem_diff = (str(format_elem_val) != str(data_elem_val) or + str(format_elem_val) != str(updated_elem_val) or + str(data_elem_val) != str(updated_elem_val)) + + # Escribir datos de elementos de array (solo en hoja de valores actuales) + current_sheet.cell(row=current_row, column=1, value=address) + current_sheet.cell(row=current_row, column=2, value=elem_path) + current_sheet.cell(row=current_row, column=3, value=data_type.replace("ARRAY", "").strip()) + current_sheet.cell(row=current_row, column=4, value=str(format_elem_val)) + current_sheet.cell(row=current_row, column=5, value=str(data_elem_val)) + current_sheet.cell(row=current_row, column=6, value=str(updated_elem_val)) + + # Resaltar diferencias + if has_elem_diff: + current_sheet.cell(row=current_row, column=7, value="Sí") + for col in range(4, 7): + current_sheet.cell(row=current_row, column=col).fill = diff_fill + else: + current_sheet.cell(row=current_row, column=7, value="No") + + current_row += 1 + + # Auto-ajustar anchos de columna + for sheet in [initial_sheet, current_sheet]: + 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 ancho máximo + sheet.column_dimensions[column].width = adjusted_width + + # Guardar el archivo Excel + try: + workbook.save(excel_filename) + print(f"Archivo de comparación Excel generado: {excel_filename}") + except Exception as e: + print(f"Error al escribir el archivo Excel {excel_filename}: {e}") + def main(): - # Obtener directorio de trabajo working_dir = find_working_directory() print(f"Using working directory: {working_dir}") - # Crear directorio para JSON si no existe output_json_dir = os.path.join(working_dir, "json") + documentation_dir = os.path.join(working_dir, "documentation") os.makedirs(output_json_dir, exist_ok=True) + os.makedirs(documentation_dir, exist_ok=True) print(f"Los archivos JSON se guardarán en: {output_json_dir}") + print(f"Los archivos de documentación se guardarán en: {documentation_dir}") - # Buscar pares de archivos _data y _format matched_pairs = find_matching_files(working_dir) if not matched_pairs: @@ -357,35 +599,51 @@ def main(): # Parsear archivos a JSON data_json, format_json = parse_files_to_json(data_file, format_file, output_json_dir) - # Aplanar variables por offset - print("Aplanando variables por offset...") - data_offset_map = flatten_variables_by_offset(data_json) - format_offset_map = flatten_variables_by_offset(format_json) + # Verificar compatibilidad usando listas aplanadas + all_compatible = True + for db_idx, format_db in enumerate(format_json.get("dbs", [])): + # Buscar el DB correspondiente en data_json + data_db = next((db for db in data_json.get("dbs", []) if db["name"] == format_db["name"]), None) + if not data_db: + print(f"Error: No se encontró DB '{format_db['name']}' en el archivo data") + all_compatible = False + continue + + # Aplanar variables de ambos DBs + flat_data_vars = flatten_db_structure(data_db) + flat_format_vars = flatten_db_structure(format_db) + + print(f"Comparando estructuras para DB '{format_db['name']}': {len(flat_data_vars)} variables en _data, {len(flat_format_vars)} variables en _format") + compatible, issues = compare_structures_by_offset(flat_data_vars, flat_format_vars) + + if not compatible: + all_compatible = False + print(f"\nSe encontraron problemas de compatibilidad en DB '{format_db['name']}':") + for issue in issues: + print(f" - {issue}") + print(f"Abortando el proceso para este DB.") - # Comparar estructuras usando offset como clave - print(f"Comparando estructuras: {len(data_offset_map)} variables en _data, {len(format_offset_map)} variables en _format") - compatible, issues = compare_structures_by_offset(data_offset_map, format_offset_map) - - if not compatible: - print("\nSe encontraron problemas de compatibilidad entre los archivos:") - for issue in issues: - print(f" - {issue}") - print("\nAbortando el proceso para este par de archivos.") - continue - - print("\nLos archivos son compatibles. Creando el archivo _updated...") - - # Crear JSON actualizado usando el mapa de offsets de _data - updated_json = create_updated_json(data_json, format_json) - - # Guardar la versión actualizada - base_name = os.path.basename(format_file).replace("_format", "").split('.')[0] - updated_json_path = os.path.join(output_json_dir, f"{base_name}_updated.json") - - with open(updated_json_path, "w", encoding='utf-8') as f: - json.dump(updated_json, f, default=custom_json_serializer, indent=2) - - print(f"Archivo _updated generado: {updated_json_path}") + if all_compatible: + print("\nLos archivos son compatibles. Creando el archivo _updated...") + + # Crear JSON actualizado + updated_json = create_updated_json(data_json, format_json) + + # Guardar la versión actualizada + base_name = os.path.basename(format_file).replace("_format", "").split('.')[0] + updated_json_path = os.path.join(output_json_dir, f"{base_name}_updated.json") + + with open(updated_json_path, "w", encoding='utf-8') as f: + json.dump(updated_json, f, default=custom_json_serializer, indent=2) + + print(f"Archivo _updated generado: {updated_json_path}") + + # Generar archivo de comparación Excel + comparison_excel_path = os.path.join(documentation_dir, f"{base_name}_comparison.xlsx") + generate_comparison_excel(format_json, data_json, updated_json, comparison_excel_path) + + # Procesar el JSON actualizado para generar archivos Markdown y S7 + process_updated_json(updated_json, updated_json_path, working_dir, documentation_dir, format_file) print("\n--- Proceso completado ---") diff --git a/data/log.txt b/data/log.txt index fdcd5d5..af5532f 100644 --- a/data/log.txt +++ b/data/log.txt @@ -1,319 +1,21 @@ -[00:51:17] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001... -[00:51:18] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -[00:51:18] Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json -[00:51:18] Se encontraron 1 pares de archivos para procesar. -[00:51:18] --- Procesando par de archivos --- -[00:51:18] Data file: db1001_data.db -[00:51:18] Format file: db1001_format.db -[00:51:18] Parseando archivo data: db1001_data.db -[00:51:18] Parseando archivo format: db1001_format.db -[00:51:18] Archivos JSON generados: db1001_data.json y db1001_format.json -[00:51:18] Aplanando variables por offset... -[00:51:18] Comparando estructuras: 251 variables en _data, 251 variables en _format -[00:51:18] Los archivos son compatibles. Creando el archivo _updated... -[00:51:18] Mapeando STAT0.STAT1.STAT2 -> Processor_Options.Blender_OPT._ModelNum (offset 0.0) -[00:51:18] Mapeando STAT0.STAT1.STAT3 -> Processor_Options.Blender_OPT._CO2_Offset (offset 2.0) -[00:51:18] Mapeando STAT0.STAT1.STAT4 -> Processor_Options.Blender_OPT._MaxSyrDeltaBrix (offset 6.0) -[00:51:18] Mapeando STAT0.STAT1.STAT5 -> Processor_Options.Blender_OPT._BrixMeter (offset 10.0) -[00:51:18] Mapeando STAT0.STAT1.STAT6 -> Processor_Options.Blender_OPT.Spare101 (offset 10.1) -[00:51:18] Mapeando STAT0.STAT1.STAT7 -> Processor_Options.Blender_OPT._TrackH2OEnable (offset 10.2) -[00:51:18] Mapeando STAT0.STAT1.STAT8 -> Processor_Options.Blender_OPT._PAmPDSType (offset 10.3) -[00:51:18] Mapeando STAT0.STAT1.STAT9 -> Processor_Options.Blender_OPT._HistoricalTrends (offset 10.4) -[00:51:18] Mapeando STAT0.STAT1.STAT10 -> Processor_Options.Blender_OPT._PowerMeter (offset 10.5) -[00:51:18] Mapeando STAT0.STAT1.STAT11 -> Processor_Options.Blender_OPT._Report (offset 10.6) -[00:51:18] Mapeando STAT0.STAT1.STAT12 -> Processor_Options.Blender_OPT._Balaiage (offset 10.7) -[00:51:18] Mapeando STAT0.STAT1.STAT13 -> Processor_Options.Blender_OPT._Valves_FullFeedback (offset 11.0) -[00:51:18] Mapeando STAT0.STAT1.STAT14 -> Processor_Options.Blender_OPT._Valves_SingleFeedback (offset 11.1) -[00:51:18] Mapeando STAT0.STAT1.STAT15 -> Processor_Options.Blender_OPT._PumpsSafetySwitches (offset 11.2) -[00:51:18] Mapeando STAT0.STAT1.STAT16 -> Processor_Options.Blender_OPT._SurgeProtectionAct (offset 11.3) -[00:51:18] Mapeando STAT0.STAT1.STAT17 -> Processor_Options.Blender_OPT._DBC_Type (offset 11.4) -[00:51:18] Mapeando STAT0.STAT1.STAT18 -> Processor_Options.Blender_OPT._CO2InletMeter (offset 11.5) -[00:51:18] Mapeando STAT0.STAT1.STAT19 -> Processor_Options.Blender_OPT._ProductO2Meter (offset 11.6) -[00:51:18] Mapeando STAT0.STAT1.STAT20 -> Processor_Options.Blender_OPT._CopressedAirInletMeter (offset 11.7) -[00:51:18] Mapeando STAT0.STAT1.STAT21 -> Processor_Options.Blender_OPT._MeterType (offset 12.0) -[00:51:18] Mapeando STAT0.STAT1.STAT22 -> Processor_Options.Blender_OPT._MeterReceiveOnly (offset 14.0) -[00:51:18] Mapeando STAT0.STAT1.STAT23 -> Processor_Options.Blender_OPT._SyrBrixMeter (offset 14.1) -[00:51:18] Mapeando STAT0.STAT1.STAT24 -> Processor_Options.Blender_OPT._Flooding_Start_Up (offset 14.2) -[00:51:18] Mapeando STAT0.STAT1.STAT25 -> Processor_Options.Blender_OPT._FastChangeOverEnabled (offset 14.3) -[00:51:18] Mapeando STAT0.STAT1.STAT26 -> Processor_Options.Blender_OPT._WaterInletMeter (offset 14.4) -[00:51:18] Mapeando STAT0.STAT1.STAT27 -> Processor_Options.Blender_OPT._BlendFillSystem (offset 14.5) -[00:51:18] Mapeando STAT0.STAT1.STAT28 -> Processor_Options.Blender_OPT._TrackFillerSpeed (offset 14.6) -[00:51:18] Mapeando STAT0.STAT1.STAT29 -> Processor_Options.Blender_OPT._SignalExchange (offset 16.0) -[00:51:18] Mapeando STAT0.STAT1.STAT30 -> Processor_Options.Blender_OPT._CoolerPresent (offset 18.0) -[00:51:18] Mapeando STAT0.STAT1.STAT31 -> Processor_Options.Blender_OPT._CoolerControl (offset 20.0) -[00:51:18] Mapeando STAT0.STAT1.STAT32 -> Processor_Options.Blender_OPT._CoolerType (offset 22.0) -[00:51:18] Mapeando STAT0.STAT1.STAT33 -> Processor_Options.Blender_OPT._LocalCIP (offset 24.0) -[00:51:18] Mapeando STAT0.STAT1.STAT34 -> Processor_Options.Blender_OPT._ICS_CustomerHotWater (offset 24.1) -[00:51:18] Mapeando STAT0.STAT1.STAT35 -> Processor_Options.Blender_OPT._ICS_CustomerChemRecov (offset 24.2) -[00:51:18] Mapeando STAT0.STAT1.STAT36 -> Processor_Options.Blender_OPT._CIPSignalExchange (offset 24.3) -[00:51:18] Mapeando STAT0.STAT1.STAT37 -> Processor_Options.Blender_OPT._ICS_CustomerChemicals (offset 24.4) -[00:51:18] Mapeando STAT0.STAT1.STAT38 -> Processor_Options.Blender_OPT._CarboPresent (offset 24.5) -[00:51:18] Mapeando STAT0.STAT1.STAT39 -> Processor_Options.Blender_OPT._InverterSyrupPumpPPP302 (offset 24.6) -[00:51:18] Mapeando STAT0.STAT1.STAT40 -> Processor_Options.Blender_OPT._InverterWaterPumpPPN301 (offset 24.7) -[00:51:18] Mapeando STAT0.STAT1.STAT41 -> Processor_Options.Blender_OPT._DoubleDeair (offset 25.0) -[00:51:18] Mapeando STAT0.STAT1.STAT42 -> Processor_Options.Blender_OPT._DeairPreMixed (offset 25.1) -[00:51:18] Mapeando STAT0.STAT1.STAT43 -> Processor_Options.Blender_OPT._Deaireation (offset 25.2) -[00:51:18] Mapeando STAT0.STAT1.STAT44 -> Processor_Options.Blender_OPT._StillWaterByPass (offset 25.3) -[00:51:18] Mapeando STAT0.STAT1.STAT45 -> Processor_Options.Blender_OPT._ManifoldSetting (offset 25.4) -[00:51:18] Mapeando STAT0.STAT1.STAT46 -> Processor_Options.Blender_OPT._InverterProdPumpPPM303 (offset 25.5) -[00:51:18] Mapeando STAT0.STAT1.STAT47 -> Processor_Options.Blender_OPT._SidelCip (offset 25.6) -[00:51:18] Mapeando STAT0.STAT1.STAT48 -> Processor_Options.Blender_OPT._EthernetCom_CpuPN_CP (offset 25.7) -[00:51:18] Mapeando STAT0.STAT1.STAT49 -> Processor_Options.Blender_OPT._2ndOutlet (offset 26.0) -[00:51:18] Mapeando STAT0.STAT1.STAT50 -> Processor_Options.Blender_OPT._Promass (offset 28.0) -[00:51:18] Mapeando STAT0.STAT1.STAT51 -> Processor_Options.Blender_OPT._WaterPromass (offset 30.0) -[00:51:18] Mapeando STAT0.STAT1.STAT52 -> Processor_Options.Blender_OPT._ProductConductimeter (offset 30.1) -[00:51:18] Mapeando STAT0.STAT1.STAT53 -> Processor_Options.Blender_OPT._ICS_CustomerH2ORecov (offset 30.2) -[00:51:18] Mapeando STAT0.STAT1.STAT54 -> Processor_Options.Blender_OPT.Spare303 (offset 30.3) -[00:51:18] Mapeando STAT0.STAT1.STAT55 -> Processor_Options.Blender_OPT._CO2_GAS2_Injection (offset 30.4) -[00:51:18] Mapeando STAT0.STAT1.STAT56 -> Processor_Options.Blender_OPT._InverterVacuuPumpPPN304 (offset 30.5) -[00:51:18] Mapeando STAT0.STAT1.STAT57 -> Processor_Options.Blender_OPT._InverterBoostPumpPPM307 (offset 30.6) -[00:51:18] Mapeando STAT0.STAT1.STAT58 -> Processor_Options.Blender_OPT._RunOut_Water (offset 30.7) -[00:51:18] Mapeando STAT0.STAT1.STAT59 -> Processor_Options.Blender_OPT._FlowMeterType (offset 31.0) -[00:51:18] Mapeando STAT0.STAT1.STAT60 -> Processor_Options.Blender_OPT._SidelFiller (offset 31.1) -[00:51:18] Mapeando STAT0.STAT1.STAT61 -> Processor_Options.Blender_OPT._Simulation (offset 31.2) -[00:51:18] Mapeando STAT0.STAT1.STAT62 -> Processor_Options.Blender_OPT._ProductCoolingCTRL (offset 31.3) -[00:51:18] Mapeando STAT0.STAT1.STAT63 -> Processor_Options.Blender_OPT._ChillerCTRL (offset 31.4) -[00:51:18] Mapeando STAT0.STAT1.STAT64 -> Processor_Options.Blender_OPT._CO2_SterileFilter (offset 31.5) -[00:51:18] Mapeando STAT0.STAT1.STAT65 -> Processor_Options.Blender_OPT._InverterRecirPumpPPM306 (offset 31.6) -[00:51:18] Mapeando STAT0.STAT1.STAT66 -> Processor_Options.Blender_OPT._ProdPressReleaseRVM304 (offset 31.7) -[00:51:18] Mapeando STAT0.STAT1.STAT67 -> Processor_Options.Blender_OPT._VacuumPump (offset 32.0) -[00:51:18] Mapeando STAT0.STAT1.STAT68 -> Processor_Options.Blender_OPT._GAS2InjectionType (offset 34.0) -[00:51:18] Mapeando STAT0.STAT1.STAT69 -> Processor_Options.Blender_OPT._InjectionPress_Ctrl (offset 36.0) -[00:51:18] Mapeando STAT0.STAT1.STAT70 -> Processor_Options.Blender_OPT._ProdPressureType (offset 38.0) -[00:51:18] Mapeando STAT0.STAT1.STAT71 -> Processor_Options.Blender_OPT._CIPHeatType (offset 40.0) -[00:51:18] Mapeando STAT0.STAT1.STAT72 -> Processor_Options.Blender_OPT._EHS_NrRes (offset 42.0) -[00:51:18] Mapeando STAT73[1] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[2] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[3] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[4] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[5] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[6] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[7] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[8] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT73[9] -> Spare1 (offset 44.0) -[00:51:18] Mapeando STAT74 -> _RVM301_DeadBand (offset 62.0) -[00:51:18] Mapeando STAT75 -> _RVM301_Kp (offset 66.0) -[00:51:18] Mapeando STAT76.STAT77 -> Actual_Recipe_Parameters._Name (offset 70.0) -[00:51:18] Mapeando STAT76.STAT78 -> Actual_Recipe_Parameters._EnProdTemp (offset 104.0) -[00:51:18] Mapeando STAT76.STAT79 -> Actual_Recipe_Parameters._SyrFlushing (offset 104.1) -[00:51:18] Mapeando STAT76.STAT80 -> Actual_Recipe_Parameters._GAS2_Injection (offset 104.2) -[00:51:18] Mapeando STAT76.STAT81 -> Actual_Recipe_Parameters._Eq_Pression_Selected (offset 104.3) -[00:51:18] Mapeando STAT76.STAT82 -> Actual_Recipe_Parameters._DeoxStripEn (offset 104.4) -[00:51:18] Mapeando STAT76.STAT83 -> Actual_Recipe_Parameters._DeoxVacuumEn (offset 104.5) -[00:51:18] Mapeando STAT76.STAT84 -> Actual_Recipe_Parameters._DeoxPreMixed (offset 104.6) -[00:51:18] Mapeando STAT76.STAT85 -> Actual_Recipe_Parameters._EnBlowOffProdPipeCO2Fil (offset 104.7) -[00:51:18] Mapeando STAT76.STAT86 -> Actual_Recipe_Parameters._WaterSelection (offset 105.0) -[00:51:18] Mapeando STAT76.STAT87 -> Actual_Recipe_Parameters._FillerNextRecipeNum (offset 106.0) -[00:51:18] Mapeando STAT76.STAT88 -> Actual_Recipe_Parameters._BottleShape (offset 107.0) -[00:51:18] Mapeando STAT76.STAT89 -> Actual_Recipe_Parameters._Type (offset 108.0) -[00:51:18] Mapeando STAT76.STAT90 -> Actual_Recipe_Parameters._ProdMeterRecipeNum (offset 110.0) -[00:51:18] Mapeando STAT76.STAT91 -> Actual_Recipe_Parameters._SyrupBrix (offset 112.0) -[00:51:18] Mapeando STAT76.STAT92 -> Actual_Recipe_Parameters._SyrupDensity (offset 116.0) -[00:51:18] Mapeando STAT76.STAT93 -> Actual_Recipe_Parameters._SyrupFactor (offset 120.0) -[00:51:18] Mapeando STAT76.STAT94 -> Actual_Recipe_Parameters._ProductBrix (offset 124.0) -[00:51:18] Mapeando STAT76.STAT95 -> Actual_Recipe_Parameters._ProductionRate (offset 128.0) -[00:51:18] Mapeando STAT76.STAT96 -> Actual_Recipe_Parameters._Ratio (offset 132.0) -[00:51:18] Mapeando STAT76.STAT97 -> Actual_Recipe_Parameters._ProdBrixOffset (offset 136.0) -[00:51:18] Mapeando STAT76.STAT98 -> Actual_Recipe_Parameters._CO2Vols (offset 140.0) -[00:51:18] Mapeando STAT76.STAT99 -> Actual_Recipe_Parameters._CO2Fact (offset 144.0) -[00:51:18] Mapeando STAT76.STAT100 -> Actual_Recipe_Parameters._ProdTankPress (offset 148.0) -[00:51:18] Mapeando STAT76.STAT101 -> Actual_Recipe_Parameters._SP_ProdTemp (offset 152.0) -[00:51:18] Mapeando STAT76.STAT102 -> Actual_Recipe_Parameters._PrdTankMinLevel (offset 156.0) -[00:51:18] Mapeando STAT76.STAT103 -> Actual_Recipe_Parameters._WaterValveSave (offset 160.0) -[00:51:18] Mapeando STAT76.STAT104 -> Actual_Recipe_Parameters._SyrupValveSave (offset 164.0) -[00:51:18] Mapeando STAT76.STAT105 -> Actual_Recipe_Parameters._CarboCO2ValveSave (offset 168.0) -[00:51:18] Mapeando STAT76.STAT106 -> Actual_Recipe_Parameters._ProdMeterHighBrix (offset 172.0) -[00:51:18] Mapeando STAT76.STAT107 -> Actual_Recipe_Parameters._ProdMeterLowBrix (offset 176.0) -[00:51:18] Mapeando STAT76.STAT108 -> Actual_Recipe_Parameters._ProdMeterHighCO2 (offset 180.0) -[00:51:18] Mapeando STAT76.STAT109 -> Actual_Recipe_Parameters._ProdMeterLowCO2 (offset 184.0) -[00:51:18] Mapeando STAT76.STAT110 -> Actual_Recipe_Parameters._ProdMeter_ZeroCO2 (offset 188.0) -[00:51:18] Mapeando STAT76.STAT111 -> Actual_Recipe_Parameters._ProdMeter_ZeroBrix (offset 192.0) -[00:51:18] Mapeando STAT76.STAT112 -> Actual_Recipe_Parameters._ProdHighCond (offset 196.0) -[00:51:18] Mapeando STAT76.STAT113 -> Actual_Recipe_Parameters._ProdLowCond (offset 200.0) -[00:51:18] Mapeando STAT76.STAT114 -> Actual_Recipe_Parameters._BottleSize (offset 204.0) -[00:51:18] Mapeando STAT76.STAT115 -> Actual_Recipe_Parameters._FillingValveHead_SP (offset 208.0) -[00:51:18] Mapeando STAT76.STAT116 -> Actual_Recipe_Parameters._SyrMeter_ZeroBrix (offset 212.0) -[00:51:18] Mapeando STAT76.STAT117 -> Actual_Recipe_Parameters._FirstProdExtraCO2Fact (offset 216.0) -[00:51:18] Mapeando STAT76.STAT118 -> Actual_Recipe_Parameters._Gas2Vols (offset 220.0) -[00:51:18] Mapeando STAT76.STAT119 -> Actual_Recipe_Parameters._Gas2Fact (offset 224.0) -[00:51:18] Mapeando STAT76.STAT120 -> Actual_Recipe_Parameters._SyrupPumpPressure (offset 228.0) -[00:51:18] Mapeando STAT76.STAT121 -> Actual_Recipe_Parameters._WaterPumpPressure (offset 232.0) -[00:51:18] Mapeando STAT76.STAT122 -> Actual_Recipe_Parameters._CO2_Air_N2_PressSelect (offset 236.0) -[00:51:18] Mapeando STAT76.STAT123 -> Actual_Recipe_Parameters._KFactRVM304BlowOff (offset 238.0) -[00:51:18] Mapeando STAT76.STAT124 -> Actual_Recipe_Parameters._ProdRecircPumpFreq (offset 242.0) -[00:51:18] Mapeando STAT76.STAT125 -> Actual_Recipe_Parameters._ProdBoosterPumpPress (offset 246.0) -[00:51:18] Mapeando STAT76.STAT126 -> Actual_Recipe_Parameters._ProdSendPumpFreq (offset 250.0) -[00:51:18] Mapeando STAT127[1] -> Spare2 (offset 254.0) -[00:51:18] Mapeando STAT127[2] -> Spare2 (offset 254.0) -[00:51:18] Mapeando STAT127[3] -> Spare2 (offset 254.0) -[00:51:18] Mapeando STAT127[4] -> Spare2 (offset 254.0) -[00:51:18] Mapeando STAT127[5] -> Spare2 (offset 254.0) -[00:51:18] Mapeando STAT128 -> Next_Recipe_Name (offset 264.0) -[00:51:18] Mapeando STAT129 -> Next_Recipe_Number (offset 298.0) -[00:51:18] Mapeando STAT130[1] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[2] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[3] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[4] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[5] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[6] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[7] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[8] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[9] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[10] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[11] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[12] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[13] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[14] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[15] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[16] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[17] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT130[18] -> Spare3 (offset 300.0) -[00:51:18] Mapeando STAT131.STAT132 -> ProcessSetup.Spare000 (offset 336.0) -[00:51:18] Mapeando STAT131.STAT133 -> ProcessSetup.Spare040 (offset 340.0) -[00:51:18] Mapeando STAT131.STAT134 -> ProcessSetup._KWaterLoss (offset 344.0) -[00:51:18] Mapeando STAT131.STAT135 -> ProcessSetup._KSyrupLoss (offset 348.0) -[00:51:18] Mapeando STAT131.STAT136 -> ProcessSetup._KProdLoss (offset 352.0) -[00:51:18] Mapeando STAT131.STAT137 -> ProcessSetup._KPPM303 (offset 356.0) -[00:51:18] Mapeando STAT131.STAT138 -> ProcessSetup._BaialageRVM301OVMin (offset 360.0) -[00:51:18] Mapeando STAT131.STAT139 -> ProcessSetup._SyrupLinePressure (offset 364.0) -[00:51:18] Mapeando STAT131.STAT140 -> ProcessSetup._CIPRMM301OV (offset 368.0) -[00:51:18] Mapeando STAT131.STAT141 -> ProcessSetup._CIPRMP302OV (offset 372.0) -[00:51:18] Mapeando STAT131.STAT142 -> ProcessSetup._CIPTM301MinLevel (offset 376.0) -[00:51:18] Mapeando STAT131.STAT143 -> ProcessSetup._CIPTM301MaxLevel (offset 380.0) -[00:51:18] Mapeando STAT131.STAT144 -> ProcessSetup._CIPPPM303Freq (offset 384.0) -[00:51:18] Mapeando STAT131.STAT145 -> ProcessSetup._CIPTP301MinLevel (offset 388.0) -[00:51:18] Mapeando STAT131.STAT146 -> ProcessSetup._CIPTP301MaxLevel (offset 392.0) -[00:51:18] Mapeando STAT131.STAT147 -> ProcessSetup._RinseRMM301OV (offset 396.0) -[00:51:18] Mapeando STAT131.STAT148 -> ProcessSetup._RinseRMP302OV (offset 400.0) -[00:51:18] Mapeando STAT131.STAT149 -> ProcessSetup._RinseTM301Press (offset 404.0) -[00:51:18] Mapeando STAT131.STAT150 -> ProcessSetup._RinsePPM303Freq (offset 408.0) -[00:51:18] Mapeando STAT131.STAT151 -> ProcessSetup._DrainTM301Press (offset 412.0) -[00:51:18] Mapeando STAT131.STAT152 -> ProcessSetup._KRecBlendError (offset 416.0) -[00:51:18] Mapeando STAT131.STAT153 -> ProcessSetup._KRecCarboCO2Error (offset 420.0) -[00:51:18] Mapeando STAT131.STAT154 -> ProcessSetup._MaxBlendError (offset 424.0) -[00:51:18] Mapeando STAT131.STAT155 -> ProcessSetup._MaxCarboCO2Error (offset 428.0) -[00:51:18] Mapeando STAT131.STAT156 -> ProcessSetup._StartUpBrixExtraWater (offset 432.0) -[00:51:18] Mapeando STAT131.STAT157 -> ProcessSetup._StartUpCO2ExtraWater (offset 436.0) -[00:51:18] Mapeando STAT131.STAT158 -> ProcessSetup._StartUpPPM303Freq (offset 440.0) -[00:51:18] Mapeando STAT131.STAT159 -> ProcessSetup._SyrupRoomTank (offset 444.0) -[00:51:18] Mapeando STAT131.STAT160 -> ProcessSetup._SyrupRunOutLiters (offset 446.0) -[00:51:18] Mapeando STAT131.STAT161 -> ProcessSetup._InjCO2Press_Offset (offset 450.0) -[00:51:18] Mapeando STAT131.STAT162 -> ProcessSetup._InjCO2Press_MinFlow (offset 454.0) -[00:51:18] Mapeando STAT131.STAT163 -> ProcessSetup._InjCO2Press_MaxFlow (offset 458.0) -[00:51:18] Mapeando STAT131.STAT164 -> ProcessSetup._CarboCO2Pressure (offset 462.0) -[00:51:18] Mapeando STAT131.STAT165 -> ProcessSetup._N2MinPressure (offset 466.0) -[00:51:18] Mapeando STAT131.STAT166 -> ProcessSetup._DiffSensor_Height (offset 470.0) -[00:51:18] Mapeando STAT131.STAT167 -> ProcessSetup._DiffSensor_DeltaHeight (offset 474.0) -[00:51:18] Mapeando STAT131.STAT168 -> ProcessSetup._DiffSensor_Offset (offset 478.0) -[00:51:18] Mapeando STAT131.STAT169 -> ProcessSetup._FillingValveHeight (offset 482.0) -[00:51:18] Mapeando STAT131.STAT170 -> ProcessSetup._FillerDiameter (offset 486.0) -[00:51:18] Mapeando STAT131.STAT171 -> ProcessSetup._FillingValveNum (offset 490.0) -[00:51:18] Mapeando STAT131.STAT172 -> ProcessSetup._FillerProdPipeDN (offset 492.0) -[00:51:18] Mapeando STAT131.STAT173 -> ProcessSetup._FillerProdPipeMass (offset 496.0) -[00:51:18] Mapeando STAT131.STAT174 -> ProcessSetup._FillingTime (offset 500.0) -[00:51:18] Mapeando STAT131.STAT175 -> ProcessSetup._TM301Height_0 (offset 504.0) -[00:51:18] Mapeando STAT131.STAT176 -> ProcessSetup._TM301LevelPerc_2 (offset 508.0) -[00:51:18] Mapeando STAT131.STAT177 -> ProcessSetup._TM301Height_2 (offset 512.0) -[00:51:18] Mapeando STAT131.STAT178 -> ProcessSetup._RVN304Factor (offset 516.0) -[00:51:18] Mapeando STAT131.STAT179 -> ProcessSetup._DrainTM301Flushing (offset 520.0) -[00:51:18] Mapeando STAT131.STAT180 -> ProcessSetup._FirstProdExtraBrix (offset 524.0) -[00:51:18] Mapeando STAT131.STAT181 -> ProcessSetup._FirstProdDietExtraSyr (offset 528.0) -[00:51:18] Mapeando STAT131.STAT182 -> ProcessSetup._EndProdLastSyrlt (offset 532.0) -[00:51:18] Mapeando STAT131.STAT183 -> ProcessSetup._TM301DrainSt0Time (offset 536.0) -[00:51:18] Mapeando STAT131.STAT184 -> ProcessSetup._TM301DrainSt1Time (offset 538.0) -[00:51:18] Mapeando STAT131.STAT185 -> ProcessSetup._ProdPipeRunOutSt0Time (offset 540.0) -[00:51:18] Mapeando STAT131.STAT186 -> ProcessSetup._RMM301ProdPipeRunOu (offset 542.0) -[00:51:18] Mapeando STAT131.STAT187 -> ProcessSetup._RMP302ProdPipeRunOu (offset 546.0) -[00:51:18] Mapeando STAT131.STAT188 -> ProcessSetup._ProdPipeRunOutAmount (offset 550.0) -[00:51:18] Mapeando STAT131.STAT189 -> ProcessSetup._TM301RunOutChiller (offset 554.0) -[00:51:18] Mapeando STAT131.STAT190 -> ProcessSetup._MinSpeedNominalProd (offset 558.0) -[00:51:18] Mapeando STAT131.STAT191 -> ProcessSetup._MinSpeedSlowProd (offset 562.0) -[00:51:18] Mapeando STAT131.STAT192 -> ProcessSetup._FastChgOvrTM301DrnPrss (offset 566.0) -[00:51:18] Mapeando STAT131.STAT193 -> ProcessSetup._CIPTN301MinLevel (offset 570.0) -[00:51:18] Mapeando STAT131.STAT194 -> ProcessSetup._CIPTN301MaxLevel (offset 574.0) -[00:51:18] Mapeando STAT131.STAT195 -> ProcessSetup._ProdPPN304Freq (offset 578.0) -[00:51:18] Mapeando STAT131.STAT196 -> ProcessSetup._GAS2InjectionPress (offset 582.0) -[00:51:18] Mapeando STAT131.STAT197 -> ProcessSetup._BaialageRVM301OVMax (offset 586.0) -[00:51:18] Mapeando STAT131.STAT198 -> ProcessSetup._RinsePPN301Freq (offset 590.0) -[00:51:18] Mapeando STAT131.STAT199 -> ProcessSetup._CIPPPN301Freq (offset 594.0) -[00:51:18] Mapeando STAT131.STAT200 -> ProcessSetup._RinsePPP302Freq (offset 598.0) -[00:51:18] Mapeando STAT131.STAT201 -> ProcessSetup._CIPPPP302Freq (offset 602.0) -[00:51:18] Mapeando STAT131.STAT202 -> ProcessSetup._PercSyrupBrixSyrStarUp (offset 606.0) -[00:51:18] Mapeando STAT131.STAT203 -> ProcessSetup._RefTempCoolingCTRL (offset 610.0) -[00:51:18] Mapeando STAT131.STAT204 -> ProcessSetup._H2OSerpPrimingVolume (offset 614.0) -[00:51:18] Mapeando STAT131.STAT205 -> ProcessSetup._AVN301_Nozzle_Kv (offset 618.0) -[00:51:18] Mapeando STAT131.STAT206 -> ProcessSetup._AVN302_Nozzle_Kv (offset 622.0) -[00:51:18] Mapeando STAT131.STAT207 -> ProcessSetup._AVN303_Nozzle_Kv (offset 626.0) -[00:51:18] Mapeando STAT131.STAT208 -> ProcessSetup._DeoxSpryball_Kv (offset 630.0) -[00:51:18] Mapeando STAT131.STAT209 -> ProcessSetup._PremixedLineDrainTime (offset 634.0) -[00:51:18] Mapeando STAT131.STAT210 -> ProcessSetup._PPN301_H_MaxFlow (offset 636.0) -[00:51:18] Mapeando STAT131.STAT211 -> ProcessSetup._PPN301_H_MinFlow (offset 640.0) -[00:51:18] Mapeando STAT131.STAT212 -> ProcessSetup._PPN301_MaxFlow (offset 644.0) -[00:51:18] Mapeando STAT131.STAT213 -> ProcessSetup._PPN301_MinFlow (offset 648.0) -[00:51:18] Mapeando STAT131.STAT214 -> ProcessSetup._PPP302_H_MaxFlow (offset 652.0) -[00:51:18] Mapeando STAT131.STAT215 -> ProcessSetup._PPP302_H_MinFlow (offset 656.0) -[00:51:18] Mapeando STAT131.STAT216 -> ProcessSetup._PPP302_MaxFlow (offset 660.0) -[00:51:18] Mapeando STAT131.STAT217 -> ProcessSetup._PPP302_MinFlow (offset 664.0) -[00:51:18] Mapeando STAT131.STAT218 -> ProcessSetup._RinsePPM306Freq (offset 668.0) -[00:51:18] Mapeando STAT131.STAT219 -> ProcessSetup._CIPPPM306Freq (offset 672.0) -[00:51:18] Mapeando STAT131.STAT220 -> ProcessSetup._PPM307_H_MaxFlow (offset 676.0) -[00:51:18] Mapeando STAT131.STAT221 -> ProcessSetup._PPM307_H_MinFlow (offset 680.0) -[00:51:18] Mapeando STAT131.STAT222 -> ProcessSetup._PPM307_MaxFlow (offset 684.0) -[00:51:18] Mapeando STAT131.STAT223 -> ProcessSetup._PPM307_MinFlow (offset 688.0) -[00:51:18] Mapeando STAT131.STAT224 -> ProcessSetup._Temp0_VacuumCtrl (offset 692.0) -[00:51:18] Mapeando STAT131.STAT225 -> ProcessSetup._Temp1_VacuumCtrl (offset 696.0) -[00:51:18] Mapeando STAT131.STAT226 -> ProcessSetup._Temp2_VacuumCtrl (offset 700.0) -[00:51:18] Mapeando STAT131.STAT227 -> ProcessSetup._Temp3_VacuumCtrl (offset 704.0) -[00:51:18] Mapeando STAT131.STAT228 -> ProcessSetup._Temp4_VacuumCtrl (offset 708.0) -[00:51:18] Mapeando STAT131.STAT229 -> ProcessSetup._T1_VacuumCtrl (offset 712.0) -[00:51:18] Mapeando STAT131.STAT230 -> ProcessSetup._T2_VacuumCtrl (offset 716.0) -[00:51:18] Mapeando STAT131.STAT231 -> ProcessSetup._T3_VacuumCtrl (offset 720.0) -[00:51:18] Mapeando STAT131.STAT232 -> ProcessSetup._T4_VacuumCtrl (offset 724.0) -[00:51:18] Mapeando STAT131.STAT233 -> ProcessSetup._ICS_VolDosWorkTimePAA (offset 728.0) -[00:51:18] Mapeando STAT131.STAT234 -> ProcessSetup._ICS_VolPauseTimePAA (offset 730.0) -[00:51:18] Mapeando STAT131.STAT235 -> ProcessSetup._ICS_PAAPulseWeight (offset 732.0) -[00:51:18] Mapeando STAT131.STAT236 -> ProcessSetup._ICS_CausticPulseWeight (offset 734.0) -[00:51:18] Mapeando STAT131.STAT237 -> ProcessSetup._ICS_AcidPulseWeight (offset 736.0) -[00:51:18] Mapeando STAT131.STAT238 -> ProcessSetup._ICS_VolumeRestOfLine (offset 738.0) -[00:51:18] Mapeando STAT131.STAT239 -> ProcessSetup._ICS_VolDosWorkTimeCaus (offset 742.0) -[00:51:18] Mapeando STAT131.STAT240 -> ProcessSetup._ICS_VolDosPauseTimeCaus (offset 744.0) -[00:51:18] Mapeando STAT131.STAT241 -> ProcessSetup._ICS_VolDosWorkTimeAcid (offset 746.0) -[00:51:18] Mapeando STAT131.STAT242 -> ProcessSetup._ICS_VolDosPauseTimeAcid (offset 748.0) -[00:51:18] Mapeando STAT131.STAT243 -> ProcessSetup._ICS_ConcDosWorkTimeCaus (offset 750.0) -[00:51:18] Mapeando STAT131.STAT244 -> ProcessSetup._ICS_ConcDosPausTimeCaus (offset 752.0) -[00:51:18] Mapeando STAT131.STAT245 -> ProcessSetup._ICS_ConcDosWorkTimeAcid (offset 754.0) -[00:51:18] Mapeando STAT131.STAT246 -> ProcessSetup._ICS_ConcDosPausTimeAcid (offset 756.0) -[00:51:18] Mapeando STAT131.STAT247 -> ProcessSetup._RinsePPM307Freq (offset 758.0) -[00:51:18] Mapeando STAT131.STAT248 -> ProcessSetup._CIPPPM307Freq (offset 762.0) -[00:51:18] Mapeando STAT131.STAT249 -> ProcessSetup._CIP2StepTN301Lvl (offset 766.0) -[00:51:18] Mapeando STAT131.STAT250 -> ProcessSetup._CIP2StepTM301Lvl (offset 770.0) -[00:51:18] Mapeando STAT131.STAT251 -> ProcessSetup._CIP2StepTP301Lvl (offset 774.0) -[00:51:18] Mapeando STAT131.STAT252 -> ProcessSetup._PumpNominalFreq (offset 778.0) -[00:51:18] Mapeando STAT253 -> _SwitchOff_DensityOK (offset 782.0) -[00:51:18] Mapeando STAT254 -> STAT254 (offset 784.0) -[00:51:18] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json -[00:51:18] --- Proceso completado --- -[00:51:18] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:00.223903. -[00:51:18] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt -[00:51:35] Iniciando ejecución de x4.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001... -[00:51:35] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 -[00:51:35] 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 -[00:51:35] Archivos JSON encontrados para procesar: 3 -[00:51:35] --- Procesando archivo JSON: db1001_data.json --- -[00:51:35] Archivo JSON 'db1001_data.json' cargado correctamente. -[00:51:35] INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'. -[00:51:35] Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.txt -[00:51:35] Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_data.md -[00:51:35] --- Procesando archivo JSON: db1001_format.json --- -[00:51:35] Archivo JSON 'db1001_format.json' cargado correctamente. -[00:51:35] INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'. -[00:51:35] Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.txt -[00:51:35] Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_format.md -[00:51:35] --- Procesando archivo JSON: db1001_updated.json --- -[00:51:35] Archivo JSON 'db1001_updated.json' cargado correctamente. -[00:51:35] INFO: Usando '_begin_block_assignments_ordered' para generar bloque BEGIN de DB 'HMI_Blender_Parameters'. -[00:51:35] Archivo S7 reconstruido generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt -[00:51:35] Archivo Markdown de documentación generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md -[00:51:35] --- Proceso de generación de documentación completado --- -[00:51:35] Ejecución de x4.py finalizada (success). Duración: 0:00:00.110751. -[00:51:35] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x4.txt +[02:56:24] Iniciando ejecución de x7_value_updater.py en C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001... +[02:56:24] Using working directory: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001 +[02:56:24] Los archivos JSON se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json +[02:56:24] Los archivos de documentación se guardarán en: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation +[02:56:24] Se encontraron 1 pares de archivos para procesar. +[02:56:24] --- Procesando par de archivos --- +[02:56:24] Data file: db1001_data.db +[02:56:24] Format file: db1001_format.db +[02:56:24] Parseando archivo data: db1001_data.db +[02:56:24] Parseando archivo format: db1001_format.db +[02:56:24] Archivos JSON generados: db1001_data.json y db1001_format.json +[02:56:24] Comparando estructuras para DB 'HMI_Blender_Parameters': 284 variables en _data, 284 variables en _format +[02:56:24] Los archivos son compatibles. Creando el archivo _updated... +[02:56:24] Archivo _updated generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\json\db1001_updated.json +[02:56:25] Archivo de comparación Excel generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_comparison.xlsx +[02:56:25] Archivo Markdown generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.md +[02:56:25] Archivo S7 generado: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\documentation\db1001_updated.txt +[02:56:25] Archivo S7 copiado a: C:\Trabajo\SIDEL\09 - SAE452 - Diet as Regular - San Giovanni in Bosco\Reporte\DB1001\db1001_updated.db +[02:56:25] --- Proceso completado --- +[02:56:25] Ejecución de x7_value_updater.py finalizada (success). Duración: 0:00:00.761362. +[02:56:25] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\S7_DB_Utils\log_x7_value_updater.txt