From de5134920d73380834befb56b946a51db42b06b8 Mon Sep 17 00:00:00 2001 From: Miguel Date: Sat, 17 May 2025 14:31:37 +0200 Subject: [PATCH] Add initial implementation of Recipe Production and HMI Blender Parameters - Introduced a new data type "Recipe_Prod" with various parameters related to production settings, including syrup properties, CO2 factors, and production rates. - Created a data block "HMI_Blender_Parameters" to encapsulate processor options and settings for the blender, including various operational flags and parameters for different components. - Initialized default values for recipe parameters and process setup configurations to ensure proper operation of the production system. --- ...documentation_db_HMI_Blender_Parameters.md | 54 +- ...data_expanded.json => parsed_s7_data.json} | 327 ++++++++++++- ...rce_v2.txt => reconstructed_s7_source.txt} | 462 ++++++++++-------- backend/script_groups/S7_DB_Utils/x3.py | 456 ++++++++--------- backend/script_groups/S7_DB_Utils/x4.py | 266 +++++----- 5 files changed, 956 insertions(+), 609 deletions(-) rename backend/script_groups/S7_DB_Utils/{parsed_s7_data_expanded.json => parsed_s7_data.json} (86%) rename backend/script_groups/S7_DB_Utils/{reconstructed_s7_source_v2.txt => reconstructed_s7_source.txt} (97%) diff --git a/backend/script_groups/S7_DB_Utils/documentation_db_HMI_Blender_Parameters.md b/backend/script_groups/S7_DB_Utils/documentation_db_HMI_Blender_Parameters.md index 8b1dba3..71cc046 100644 --- a/backend/script_groups/S7_DB_Utils/documentation_db_HMI_Blender_Parameters.md +++ b/backend/script_groups/S7_DB_Utils/documentation_db_HMI_Blender_Parameters.md @@ -2,8 +2,6 @@ | Address | Name | Type | Initial Value | Actual Value | Comment | |---|---|---|---|---|---| -| 0.0 | Processor_Options | STRUCT | | | | -| 0.0 | Processor_Options.Blender_OPT | STRUCT | | | | | 0.0 | Processor_Options.Blender_OPT._ModelNum | INT | 6 | 6 | | | 2.0 | Processor_Options.Blender_OPT._CO2_Offset | REAL | 4.500000e-01 | 4.500000e-01 | | | 6.0 | Processor_Options.Blender_OPT._MaxSyrDeltaBrix | REAL | 8.000000e-01 | 8.000000e-01 | | @@ -78,12 +76,60 @@ | 44.0 | Spare1 | ARRAY [1..9] OF INT | | | | | 62.0 | _RVM301_DeadBand | REAL | 5.000000e-02 | 5.000000e-02 | | | 66.0 | _RVM301_Kp | REAL | 9.000000e+01 | 9.000000e+01 | | -| 70.0 | Actual_Recipe_Parameters | "Recipe_Prod" | | | | +| 70.0 | Actual_Recipe_Parameters._Name | STRING[32] | ' ' | '' | | +| 104.0 | Actual_Recipe_Parameters._EnProdTemp | BOOL | | TRUE | | +| 104.1 | Actual_Recipe_Parameters._SyrFlushing | BOOL | | FALSE | Ex_EnDeaireation --> DELETED - AVP320 VALVE OPEN | +| 104.2 | Actual_Recipe_Parameters._GAS2_Injection | BOOL | | FALSE | 0 = GAS2 not present; 1 = GAS2 present | +| 104.3 | Actual_Recipe_Parameters._Eq_Pression_Selected | BOOL | | FALSE | | +| 104.4 | Actual_Recipe_Parameters._DeoxStripEn | BOOL | | FALSE | ******Deairation with Strip Enable | +| 104.5 | Actual_Recipe_Parameters._DeoxVacuumEn | BOOL | | TRUE | ******Deairation with Vacuum | +| 104.6 | Actual_Recipe_Parameters._DeoxPreMixed | BOOL | | FALSE | ******Deairation of Premixed Product | +| 104.7 | Actual_Recipe_Parameters._EnBlowOffProdPipeCO2Fil | BOOL | | FALSE | | +| 105.0 | Actual_Recipe_Parameters._WaterSelection | BYTE | | B#16#0 | | +| 106.0 | Actual_Recipe_Parameters._FillerNextRecipeNum | BYTE | | B#16#0 | | +| 107.0 | Actual_Recipe_Parameters._BottleShape | BYTE | | B#16#0 | | +| 108.0 | Actual_Recipe_Parameters._Type | INT | 1 | 2 | 1= DIET; 2= REGULAR; 3= RATIO; 4= WATER | +| 110.0 | Actual_Recipe_Parameters._ProdMeterRecipeNum | INT | | 1 | | +| 112.0 | Actual_Recipe_Parameters._SyrupBrix | REAL | 5.000000e+01 | 4.625000e+01 | | +| 116.0 | Actual_Recipe_Parameters._SyrupDensity | REAL | 1.255800e+00 | 1.206908e+00 | | +| 120.0 | Actual_Recipe_Parameters._SyrupFactor | REAL | 1.000000e+00 | 1.000000e+00 | | +| 124.0 | Actual_Recipe_Parameters._ProductBrix | REAL | 1.045000e+01 | 1.000000e+01 | | +| 128.0 | Actual_Recipe_Parameters._ProductionRate | REAL | 9.000000e+02 | 3.800000e+02 | | +| 132.0 | Actual_Recipe_Parameters._Ratio | REAL | 2.000000e+01 | 4.238896e+00 | | +| 136.0 | Actual_Recipe_Parameters._ProdBrixOffset | REAL | | 2.500000e-01 | | +| 140.0 | Actual_Recipe_Parameters._CO2Vols | REAL | | 2.550000e+00 | | +| 144.0 | Actual_Recipe_Parameters._CO2Fact | REAL | 1.000000e+00 | 9.400000e-01 | | +| 148.0 | Actual_Recipe_Parameters._ProdTankPress | REAL | 1.000000e+00 | 4.400000e+00 | | +| 152.0 | Actual_Recipe_Parameters._SP_ProdTemp | REAL | 1.000000e+01 | 1.700000e+01 | | +| 156.0 | Actual_Recipe_Parameters._PrdTankMinLevel | REAL | 1.000000e+01 | 3.500000e+01 | | +| 160.0 | Actual_Recipe_Parameters._WaterValveSave | REAL | | 0.000000e+00 | | +| 164.0 | Actual_Recipe_Parameters._SyrupValveSave | REAL | | 0.000000e+00 | | +| 168.0 | Actual_Recipe_Parameters._CarboCO2ValveSave | REAL | | 0.000000e+00 | | +| 172.0 | Actual_Recipe_Parameters._ProdMeterHighBrix | REAL | | 1.030000e+01 | | +| 176.0 | Actual_Recipe_Parameters._ProdMeterLowBrix | REAL | | 9.830000e+00 | | +| 180.0 | Actual_Recipe_Parameters._ProdMeterHighCO2 | REAL | | 2.900000e+00 | | +| 184.0 | Actual_Recipe_Parameters._ProdMeterLowCO2 | REAL | | 2.300000e+00 | | +| 188.0 | Actual_Recipe_Parameters._ProdMeter_ZeroCO2 | REAL | | 0.000000e+00 | | +| 192.0 | Actual_Recipe_Parameters._ProdMeter_ZeroBrix | REAL | | 0.000000e+00 | | +| 196.0 | Actual_Recipe_Parameters._ProdHighCond | REAL | | 0.000000e+00 | | +| 200.0 | Actual_Recipe_Parameters._ProdLowCond | REAL | | 0.000000e+00 | | +| 204.0 | Actual_Recipe_Parameters._BottleSize | REAL | | 0.000000e+00 | | +| 208.0 | Actual_Recipe_Parameters._FillingValveHead_SP | REAL | | 0.000000e+00 | | +| 212.0 | Actual_Recipe_Parameters._SyrMeter_ZeroBrix | REAL | | 0.000000e+00 | | +| 216.0 | Actual_Recipe_Parameters._FirstProdExtraCO2Fact | REAL | 9.700000e-01 | 1.020000e+00 | | +| 220.0 | Actual_Recipe_Parameters._Gas2Vols | REAL | | 0.000000e+00 | | +| 224.0 | Actual_Recipe_Parameters._Gas2Fact | REAL | 1.000000e+00 | 0.000000e+00 | | +| 228.0 | Actual_Recipe_Parameters._SyrupPumpPressure | REAL | | 0.000000e+00 | ******Syrup Pump Pressure SP | +| 232.0 | Actual_Recipe_Parameters._WaterPumpPressure | REAL | | 0.000000e+00 | ******Water Pump Pressure SP | +| 236.0 | Actual_Recipe_Parameters._CO2_Air_N2_PressSelect | INT | | 0 | 1=CO2; 2=CO2+SterilAir; 3=CO2+N2 - Pressure Tank Selection | +| 238.0 | Actual_Recipe_Parameters._KFactRVM304BlowOff | REAL | | 0.000000e+00 | | +| 242.0 | Actual_Recipe_Parameters._ProdRecircPumpFreq | REAL | | 0.000000e+00 | | +| 246.0 | Actual_Recipe_Parameters._ProdBoosterPumpPress | REAL | | 0.000000e+00 | | +| 250.0 | Actual_Recipe_Parameters._ProdSendPumpFreq | REAL | | 0.000000e+00 | ******Product Sending Pump Frequency SP | | 254.0 | Spare2 | ARRAY [1..5] OF INT | | | | | 264.0 | Next_Recipe_Name | STRING[32] | ' ' | 'cambio 1$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00' | | | 298.0 | Next_Recipe_Number | INT | | 0 | | | 300.0 | Spare3 | ARRAY [1..18] OF INT | | | | -| 336.0 | ProcessSetup | STRUCT | | | | | 336.0 | ProcessSetup.Spare000 | REAL | | 0.000000e+00 | | | 340.0 | ProcessSetup.Spare040 | REAL | | 0.000000e+00 | | | 344.0 | ProcessSetup._KWaterLoss | REAL | 1.000000e-03 | 1.000000e-03 | Friction Loss Constant in Serpentine | diff --git a/backend/script_groups/S7_DB_Utils/parsed_s7_data_expanded.json b/backend/script_groups/S7_DB_Utils/parsed_s7_data.json similarity index 86% rename from backend/script_groups/S7_DB_Utils/parsed_s7_data_expanded.json rename to backend/script_groups/S7_DB_Utils/parsed_s7_data.json index 0cdf83c..36b5aea 100644 --- a/backend/script_groups/S7_DB_Utils/parsed_s7_data_expanded.json +++ b/backend/script_groups/S7_DB_Utils/parsed_s7_data.json @@ -1185,7 +1185,18 @@ "upper_bound": 9 } ], - "is_udt_expanded_member": false + "is_udt_expanded_member": false, + "current_element_values": { + "1": "0", + "2": "0", + "3": "0", + "4": "0", + "5": "0", + "6": "0", + "7": "0", + "8": "0", + "9": "0" + } }, { "name": "_RVM301_DeadBand", @@ -1705,7 +1716,14 @@ "upper_bound": 5 } ], - "is_udt_expanded_member": false + "is_udt_expanded_member": false, + "current_element_values": { + "1": "0", + "2": "0", + "3": "0", + "4": "0", + "5": "0" + } }, { "name": "Next_Recipe_Name", @@ -1739,7 +1757,27 @@ "upper_bound": 18 } ], - "is_udt_expanded_member": false + "is_udt_expanded_member": false, + "current_element_values": { + "1": "0", + "2": "0", + "3": "0", + "4": "0", + "5": "0", + "6": "0", + "7": "0", + "8": "0", + "9": "0", + "10": "0", + "11": "0", + "12": "0", + "13": "0", + "14": "0", + "15": "0", + "16": "0", + "17": "0", + "18": "0" + } }, { "name": "ProcessSetup", @@ -3050,7 +3088,288 @@ "is_udt_expanded_member": false } ], - "total_size_in_bytes": 784 + "total_size_in_bytes": 784, + "_initial_values_from_begin_block": { + "Processor_Options.Blender_OPT._ModelNum": "6", + "Processor_Options.Blender_OPT._CO2_Offset": "4.500000e-01", + "Processor_Options.Blender_OPT._MaxSyrDeltaBrix": "8.000000e-01", + "Processor_Options.Blender_OPT._BrixMeter": "TRUE", + "Processor_Options.Blender_OPT.Spare101": "FALSE", + "Processor_Options.Blender_OPT._TrackH2OEnable": "FALSE", + "Processor_Options.Blender_OPT._PAmPDSType": "FALSE", + "Processor_Options.Blender_OPT._HistoricalTrends": "TRUE", + "Processor_Options.Blender_OPT._PowerMeter": "FALSE", + "Processor_Options.Blender_OPT._Report": "TRUE", + "Processor_Options.Blender_OPT._Balaiage": "FALSE", + "Processor_Options.Blender_OPT._Valves_FullFeedback": "TRUE", + "Processor_Options.Blender_OPT._Valves_SingleFeedback": "FALSE", + "Processor_Options.Blender_OPT._PumpsSafetySwitches": "FALSE", + "Processor_Options.Blender_OPT._SurgeProtectionAct": "FALSE", + "Processor_Options.Blender_OPT._DBC_Type": "FALSE", + "Processor_Options.Blender_OPT._CO2InletMeter": "TRUE", + "Processor_Options.Blender_OPT._ProductO2Meter": "FALSE", + "Processor_Options.Blender_OPT._CopressedAirInletMeter": "FALSE", + "Processor_Options.Blender_OPT._MeterType": "6", + "Processor_Options.Blender_OPT._MeterReceiveOnly": "FALSE", + "Processor_Options.Blender_OPT._SyrBrixMeter": "FALSE", + "Processor_Options.Blender_OPT._Flooding_Start_Up": "FALSE", + "Processor_Options.Blender_OPT._FastChangeOverEnabled": "TRUE", + "Processor_Options.Blender_OPT._WaterInletMeter": "FALSE", + "Processor_Options.Blender_OPT._BlendFillSystem": "TRUE", + "Processor_Options.Blender_OPT._TrackFillerSpeed": "TRUE", + "Processor_Options.Blender_OPT._SignalExchange": "1", + "Processor_Options.Blender_OPT._CoolerPresent": "TRUE", + "Processor_Options.Blender_OPT._CoolerControl": "4", + "Processor_Options.Blender_OPT._CoolerType": "0", + "Processor_Options.Blender_OPT._LocalCIP": "FALSE", + "Processor_Options.Blender_OPT._ICS_CustomerHotWater": "FALSE", + "Processor_Options.Blender_OPT._ICS_CustomerChemRecov": "FALSE", + "Processor_Options.Blender_OPT._CIPSignalExchange": "FALSE", + "Processor_Options.Blender_OPT._ICS_CustomerChemicals": "FALSE", + "Processor_Options.Blender_OPT._CarboPresent": "TRUE", + "Processor_Options.Blender_OPT._InverterSyrupPumpPPP302": "FALSE", + "Processor_Options.Blender_OPT._InverterWaterPumpPPN301": "FALSE", + "Processor_Options.Blender_OPT._DoubleDeair": "TRUE", + "Processor_Options.Blender_OPT._DeairPreMixed": "FALSE", + "Processor_Options.Blender_OPT._Deaireation": "TRUE", + "Processor_Options.Blender_OPT._StillWaterByPass": "FALSE", + "Processor_Options.Blender_OPT._ManifoldSetting": "TRUE", + "Processor_Options.Blender_OPT._InverterProdPumpPPM303": "FALSE", + "Processor_Options.Blender_OPT._SidelCip": "FALSE", + "Processor_Options.Blender_OPT._EthernetCom_CpuPN_CP": "TRUE", + "Processor_Options.Blender_OPT._2ndOutlet": "0", + "Processor_Options.Blender_OPT._Promass": "2", + "Processor_Options.Blender_OPT._WaterPromass": "TRUE", + "Processor_Options.Blender_OPT._ProductConductimeter": "FALSE", + "Processor_Options.Blender_OPT._ICS_CustomerH2ORecov": "FALSE", + "Processor_Options.Blender_OPT.Spare303": "FALSE", + "Processor_Options.Blender_OPT._CO2_GAS2_Injection": "FALSE", + "Processor_Options.Blender_OPT._InverterVacuuPumpPPN304": "FALSE", + "Processor_Options.Blender_OPT._InverterBoostPumpPPM307": "FALSE", + "Processor_Options.Blender_OPT._RunOut_Water": "TRUE", + "Processor_Options.Blender_OPT._FlowMeterType": "TRUE", + "Processor_Options.Blender_OPT._SidelFiller": "FALSE", + "Processor_Options.Blender_OPT._Simulation": "FALSE", + "Processor_Options.Blender_OPT._ProductCoolingCTRL": "FALSE", + "Processor_Options.Blender_OPT._ChillerCTRL": "FALSE", + "Processor_Options.Blender_OPT._CO2_SterileFilter": "TRUE", + "Processor_Options.Blender_OPT._InverterRecirPumpPPM306": "FALSE", + "Processor_Options.Blender_OPT._ProdPressReleaseRVM304": "FALSE", + "Processor_Options.Blender_OPT._VacuumPump": "1", + "Processor_Options.Blender_OPT._GAS2InjectionType": "0", + "Processor_Options.Blender_OPT._InjectionPress_Ctrl": "1", + "Processor_Options.Blender_OPT._ProdPressureType": "0", + "Processor_Options.Blender_OPT._CIPHeatType": "0", + "Processor_Options.Blender_OPT._EHS_NrRes": "6", + "Spare1[1]": "0", + "Spare1[2]": "0", + "Spare1[3]": "0", + "Spare1[4]": "0", + "Spare1[5]": "0", + "Spare1[6]": "0", + "Spare1[7]": "0", + "Spare1[8]": "0", + "Spare1[9]": "0", + "_RVM301_DeadBand": "5.000000e-02", + "_RVM301_Kp": "9.000000e+01", + "Actual_Recipe_Parameters._Name": "''", + "Actual_Recipe_Parameters._EnProdTemp": "TRUE", + "Actual_Recipe_Parameters._SyrFlushing": "FALSE", + "Actual_Recipe_Parameters._GAS2_Injection": "FALSE", + "Actual_Recipe_Parameters._Eq_Pression_Selected": "FALSE", + "Actual_Recipe_Parameters._DeoxStripEn": "FALSE", + "Actual_Recipe_Parameters._DeoxVacuumEn": "TRUE", + "Actual_Recipe_Parameters._DeoxPreMixed": "FALSE", + "Actual_Recipe_Parameters._EnBlowOffProdPipeCO2Fil": "FALSE", + "Actual_Recipe_Parameters._WaterSelection": "B#16#0", + "Actual_Recipe_Parameters._FillerNextRecipeNum": "B#16#0", + "Actual_Recipe_Parameters._BottleShape": "B#16#0", + "Actual_Recipe_Parameters._Type": "2", + "Actual_Recipe_Parameters._ProdMeterRecipeNum": "1", + "Actual_Recipe_Parameters._SyrupBrix": "4.625000e+01", + "Actual_Recipe_Parameters._SyrupDensity": "1.206908e+00", + "Actual_Recipe_Parameters._SyrupFactor": "1.000000e+00", + "Actual_Recipe_Parameters._ProductBrix": "1.000000e+01", + "Actual_Recipe_Parameters._ProductionRate": "3.800000e+02", + "Actual_Recipe_Parameters._Ratio": "4.238896e+00", + "Actual_Recipe_Parameters._ProdBrixOffset": "2.500000e-01", + "Actual_Recipe_Parameters._CO2Vols": "2.550000e+00", + "Actual_Recipe_Parameters._CO2Fact": "9.400000e-01", + "Actual_Recipe_Parameters._ProdTankPress": "4.400000e+00", + "Actual_Recipe_Parameters._SP_ProdTemp": "1.700000e+01", + "Actual_Recipe_Parameters._PrdTankMinLevel": "3.500000e+01", + "Actual_Recipe_Parameters._WaterValveSave": "0.000000e+00", + "Actual_Recipe_Parameters._SyrupValveSave": "0.000000e+00", + "Actual_Recipe_Parameters._CarboCO2ValveSave": "0.000000e+00", + "Actual_Recipe_Parameters._ProdMeterHighBrix": "1.030000e+01", + "Actual_Recipe_Parameters._ProdMeterLowBrix": "9.830000e+00", + "Actual_Recipe_Parameters._ProdMeterHighCO2": "2.900000e+00", + "Actual_Recipe_Parameters._ProdMeterLowCO2": "2.300000e+00", + "Actual_Recipe_Parameters._ProdMeter_ZeroCO2": "0.000000e+00", + "Actual_Recipe_Parameters._ProdMeter_ZeroBrix": "0.000000e+00", + "Actual_Recipe_Parameters._ProdHighCond": "0.000000e+00", + "Actual_Recipe_Parameters._ProdLowCond": "0.000000e+00", + "Actual_Recipe_Parameters._BottleSize": "0.000000e+00", + "Actual_Recipe_Parameters._FillingValveHead_SP": "0.000000e+00", + "Actual_Recipe_Parameters._SyrMeter_ZeroBrix": "0.000000e+00", + "Actual_Recipe_Parameters._FirstProdExtraCO2Fact": "1.020000e+00", + "Actual_Recipe_Parameters._Gas2Vols": "0.000000e+00", + "Actual_Recipe_Parameters._Gas2Fact": "0.000000e+00", + "Actual_Recipe_Parameters._SyrupPumpPressure": "0.000000e+00", + "Actual_Recipe_Parameters._WaterPumpPressure": "0.000000e+00", + "Actual_Recipe_Parameters._CO2_Air_N2_PressSelect": "0", + "Actual_Recipe_Parameters._KFactRVM304BlowOff": "0.000000e+00", + "Actual_Recipe_Parameters._ProdRecircPumpFreq": "0.000000e+00", + "Actual_Recipe_Parameters._ProdBoosterPumpPress": "0.000000e+00", + "Actual_Recipe_Parameters._ProdSendPumpFreq": "0.000000e+00", + "Spare2[1]": "0", + "Spare2[2]": "0", + "Spare2[3]": "0", + "Spare2[4]": "0", + "Spare2[5]": "0", + "Next_Recipe_Name": "'cambio 1$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00'", + "Next_Recipe_Number": "0", + "Spare3[1]": "0", + "Spare3[2]": "0", + "Spare3[3]": "0", + "Spare3[4]": "0", + "Spare3[5]": "0", + "Spare3[6]": "0", + "Spare3[7]": "0", + "Spare3[8]": "0", + "Spare3[9]": "0", + "Spare3[10]": "0", + "Spare3[11]": "0", + "Spare3[12]": "0", + "Spare3[13]": "0", + "Spare3[14]": "0", + "Spare3[15]": "0", + "Spare3[16]": "0", + "Spare3[17]": "0", + "Spare3[18]": "0", + "ProcessSetup.Spare000": "0.000000e+00", + "ProcessSetup.Spare040": "0.000000e+00", + "ProcessSetup._KWaterLoss": "1.000000e-03", + "ProcessSetup._KSyrupLoss": "7.800000e-03", + "ProcessSetup._KProdLoss": "1.390000e-02", + "ProcessSetup._KPPM303": "5.700000e+00", + "ProcessSetup._BaialageRVM301OVMin": "2.000000e+00", + "ProcessSetup._SyrupLinePressure": "2.200000e+00", + "ProcessSetup._CIPRMM301OV": "1.000000e+01", + "ProcessSetup._CIPRMP302OV": "1.500000e+01", + "ProcessSetup._CIPTM301MinLevel": "3.500000e+01", + "ProcessSetup._CIPTM301MaxLevel": "5.500000e+01", + "ProcessSetup._CIPPPM303Freq": "5.000000e+01", + "ProcessSetup._CIPTP301MinLevel": "2.500000e+01", + "ProcessSetup._CIPTP301MaxLevel": "4.500000e+01", + "ProcessSetup._RinseRMM301OV": "1.000000e+01", + "ProcessSetup._RinseRMP302OV": "1.400000e+01", + "ProcessSetup._RinseTM301Press": "3.000000e-01", + "ProcessSetup._RinsePPM303Freq": "5.000000e+01", + "ProcessSetup._DrainTM301Press": "1.000000e+00", + "ProcessSetup._KRecBlendError": "2.000000e+00", + "ProcessSetup._KRecCarboCO2Error": "2.000000e+00", + "ProcessSetup._MaxBlendError": "1.000000e+02", + "ProcessSetup._MaxCarboCO2Error": "5.000000e+02", + "ProcessSetup._StartUpBrixExtraWater": "4.700000e+01", + "ProcessSetup._StartUpCO2ExtraWater": "8.000000e+00", + "ProcessSetup._StartUpPPM303Freq": "2.000000e+01", + "ProcessSetup._SyrupRoomTank": "1", + "ProcessSetup._SyrupRunOutLiters": "2.900000e+02", + "ProcessSetup._InjCO2Press_Offset": "5.000000e-01", + "ProcessSetup._InjCO2Press_MinFlow": "4.500000e+02", + "ProcessSetup._InjCO2Press_MaxFlow": "2.500000e+03", + "ProcessSetup._CarboCO2Pressure": "1.250000e+01", + "ProcessSetup._N2MinPressure": "1.000000e+00", + "ProcessSetup._DiffSensor_Height": "3.950000e+02", + "ProcessSetup._DiffSensor_DeltaHeight": "-2.500000e+01", + "ProcessSetup._DiffSensor_Offset": "3.618000e+01", + "ProcessSetup._FillingValveHeight": "1.400000e+03", + "ProcessSetup._FillerDiameter": "2.520000e+03", + "ProcessSetup._FillingValveNum": "91", + "ProcessSetup._FillerProdPipeDN": "1.000000e+02", + "ProcessSetup._FillerProdPipeMass": "1.600000e+02", + "ProcessSetup._FillingTime": "3.200000e+00", + "ProcessSetup._TM301Height_0": "1.050000e+03", + "ProcessSetup._TM301LevelPerc_2": "4.600000e+01", + "ProcessSetup._TM301Height_2": "1.625000e+03", + "ProcessSetup._RVN304Factor": "1.000000e+00", + "ProcessSetup._DrainTM301Flushing": "1.300000e+00", + "ProcessSetup._FirstProdExtraBrix": "5.000000e-02", + "ProcessSetup._FirstProdDietExtraSyr": "1.400000e-03", + "ProcessSetup._EndProdLastSyrlt": "0.000000e+00", + "ProcessSetup._TM301DrainSt0Time": "W#16#A", + "ProcessSetup._TM301DrainSt1Time": "W#16#50", + "ProcessSetup._ProdPipeRunOutSt0Time": "W#16#1", + "ProcessSetup._RMM301ProdPipeRunOu": "3.000000e+01", + "ProcessSetup._RMP302ProdPipeRunOu": "4.000000e+01", + "ProcessSetup._ProdPipeRunOutAmount": "3.000000e+01", + "ProcessSetup._TM301RunOutChiller": "5.000000e+00", + "ProcessSetup._MinSpeedNominalProd": "4.000000e-01", + "ProcessSetup._MinSpeedSlowProd": "3.000000e-01", + "ProcessSetup._FastChgOvrTM301DrnPrss": "9.000000e-01", + "ProcessSetup._CIPTN301MinLevel": "3.500000e+01", + "ProcessSetup._CIPTN301MaxLevel": "6.000000e+01", + "ProcessSetup._ProdPPN304Freq": "5.000000e+01", + "ProcessSetup._GAS2InjectionPress": "4.000000e+00", + "ProcessSetup._BaialageRVM301OVMax": "2.000000e+01", + "ProcessSetup._RinsePPN301Freq": "5.000000e+00", + "ProcessSetup._CIPPPN301Freq": "5.000000e+00", + "ProcessSetup._RinsePPP302Freq": "5.000000e+00", + "ProcessSetup._CIPPPP302Freq": "5.000000e+00", + "ProcessSetup._PercSyrupBrixSyrStarUp": "2.500000e+01", + "ProcessSetup._RefTempCoolingCTRL": "0.000000e+00", + "ProcessSetup._H2OSerpPrimingVolume": "1.500000e+02", + "ProcessSetup._AVN301_Nozzle_Kv": "1.650000e+02", + "ProcessSetup._AVN302_Nozzle_Kv": "2.600000e+02", + "ProcessSetup._AVN303_Nozzle_Kv": "1.650000e+02", + "ProcessSetup._DeoxSpryball_Kv": "6.700000e+01", + "ProcessSetup._PremixedLineDrainTime": "300", + "ProcessSetup._PPN301_H_MaxFlow": "9.000000e+01", + "ProcessSetup._PPN301_H_MinFlow": "8.700000e+01", + "ProcessSetup._PPN301_MaxFlow": "5.070000e+02", + "ProcessSetup._PPN301_MinFlow": "2.110000e+02", + "ProcessSetup._PPP302_H_MaxFlow": "8.600000e+01", + "ProcessSetup._PPP302_H_MinFlow": "8.500000e+01", + "ProcessSetup._PPP302_MaxFlow": "1.150000e+02", + "ProcessSetup._PPP302_MinFlow": "3.200000e+01", + "ProcessSetup._RinsePPM306Freq": "5.000000e+00", + "ProcessSetup._CIPPPM306Freq": "5.000000e+00", + "ProcessSetup._PPM307_H_MaxFlow": "0.000000e+00", + "ProcessSetup._PPM307_H_MinFlow": "0.000000e+00", + "ProcessSetup._PPM307_MaxFlow": "0.000000e+00", + "ProcessSetup._PPM307_MinFlow": "0.000000e+00", + "ProcessSetup._Temp0_VacuumCtrl": "1.800000e+01", + "ProcessSetup._Temp1_VacuumCtrl": "2.000000e+00", + "ProcessSetup._Temp2_VacuumCtrl": "2.000000e+00", + "ProcessSetup._Temp3_VacuumCtrl": "5.000000e+01", + "ProcessSetup._Temp4_VacuumCtrl": "5.000000e+01", + "ProcessSetup._T1_VacuumCtrl": "L#1500", + "ProcessSetup._T2_VacuumCtrl": "L#1500", + "ProcessSetup._T3_VacuumCtrl": "L#1000", + "ProcessSetup._T4_VacuumCtrl": "L#1000", + "ProcessSetup._ICS_VolDosWorkTimePAA": "30", + "ProcessSetup._ICS_VolPauseTimePAA": "30", + "ProcessSetup._ICS_PAAPulseWeight": "10", + "ProcessSetup._ICS_CausticPulseWeight": "10", + "ProcessSetup._ICS_AcidPulseWeight": "10", + "ProcessSetup._ICS_VolumeRestOfLine": "3.500000e+02", + "ProcessSetup._ICS_VolDosWorkTimeCaus": "30", + "ProcessSetup._ICS_VolDosPauseTimeCaus": "30", + "ProcessSetup._ICS_VolDosWorkTimeAcid": "30", + "ProcessSetup._ICS_VolDosPauseTimeAcid": "30", + "ProcessSetup._ICS_ConcDosWorkTimeCaus": "30", + "ProcessSetup._ICS_ConcDosPausTimeCaus": "30", + "ProcessSetup._ICS_ConcDosWorkTimeAcid": "30", + "ProcessSetup._ICS_ConcDosPausTimeAcid": "30", + "ProcessSetup._RinsePPM307Freq": "3.000000e+01", + "ProcessSetup._CIPPPM307Freq": "3.000000e+01", + "ProcessSetup._CIP2StepTN301Lvl": "0.000000e+00", + "ProcessSetup._CIP2StepTM301Lvl": "0.000000e+00", + "ProcessSetup._CIP2StepTP301Lvl": "0.000000e+00", + "ProcessSetup._PumpNominalFreq": "5.000000e+01", + "_SwitchOff_DensityOK": "FALSE" + } } ] } \ No newline at end of file diff --git a/backend/script_groups/S7_DB_Utils/reconstructed_s7_source_v2.txt b/backend/script_groups/S7_DB_Utils/reconstructed_s7_source.txt similarity index 97% rename from backend/script_groups/S7_DB_Utils/reconstructed_s7_source_v2.txt rename to backend/script_groups/S7_DB_Utils/reconstructed_s7_source.txt index 7ef2573..3bdcd6a 100644 --- a/backend/script_groups/S7_DB_Utils/reconstructed_s7_source_v2.txt +++ b/backend/script_groups/S7_DB_Utils/reconstructed_s7_source.txt @@ -61,8 +61,8 @@ DATA_BLOCK "HMI_Blender_Parameters" VERSION : 0.0; STRUCT - Processor_Options : STRUCT; - Blender_OPT : STRUCT; + Processor_Options : STRUCT + Blender_OPT : STRUCT _ModelNum : INT := 6; _CO2_Offset : REAL := 4.500000e-01; _MaxSyrDeltaBrix : REAL := 8.000000e-01; @@ -144,7 +144,7 @@ DATA_BLOCK "HMI_Blender_Parameters" Next_Recipe_Name : STRING[32] := ' '; Next_Recipe_Number : INT; Spare3 : ARRAY [1..18] OF INT; - ProcessSetup : STRUCT; + ProcessSetup : STRUCT Spare000 : REAL; Spare040 : REAL; _KWaterLoss : REAL := 1.000000e-03; // Friction Loss Constant in Serpentine @@ -270,209 +270,132 @@ DATA_BLOCK "HMI_Blender_Parameters" _SwitchOff_DensityOK : BOOL; END_STRUCT; BEGIN - Processor_Options.Blender_OPT._ModelNum := 6; - Processor_Options.Blender_OPT._CO2_Offset := 4.500000e-01; - Processor_Options.Blender_OPT._MaxSyrDeltaBrix := 8.000000e-01; - Processor_Options.Blender_OPT._BrixMeter := TRUE; - Processor_Options.Blender_OPT.Spare101 := FALSE; - Processor_Options.Blender_OPT._TrackH2OEnable := FALSE; - Processor_Options.Blender_OPT._PAmPDSType := FALSE; - Processor_Options.Blender_OPT._HistoricalTrends := TRUE; - Processor_Options.Blender_OPT._PowerMeter := FALSE; - Processor_Options.Blender_OPT._Report := TRUE; - Processor_Options.Blender_OPT._Balaiage := FALSE; - Processor_Options.Blender_OPT._Valves_FullFeedback := TRUE; - Processor_Options.Blender_OPT._Valves_SingleFeedback := FALSE; - Processor_Options.Blender_OPT._PumpsSafetySwitches := FALSE; - Processor_Options.Blender_OPT._SurgeProtectionAct := FALSE; - Processor_Options.Blender_OPT._DBC_Type := FALSE; - Processor_Options.Blender_OPT._CO2InletMeter := TRUE; - Processor_Options.Blender_OPT._ProductO2Meter := FALSE; - Processor_Options.Blender_OPT._CopressedAirInletMeter := FALSE; - Processor_Options.Blender_OPT._MeterType := 6; - Processor_Options.Blender_OPT._MeterReceiveOnly := FALSE; - Processor_Options.Blender_OPT._SyrBrixMeter := FALSE; - Processor_Options.Blender_OPT._Flooding_Start_Up := FALSE; - Processor_Options.Blender_OPT._FastChangeOverEnabled := TRUE; - Processor_Options.Blender_OPT._WaterInletMeter := FALSE; - Processor_Options.Blender_OPT._BlendFillSystem := TRUE; - Processor_Options.Blender_OPT._TrackFillerSpeed := TRUE; - Processor_Options.Blender_OPT._SignalExchange := 1; - Processor_Options.Blender_OPT._CoolerPresent := TRUE; - Processor_Options.Blender_OPT._CoolerControl := 4; - Processor_Options.Blender_OPT._CoolerType := 0; - Processor_Options.Blender_OPT._LocalCIP := FALSE; - Processor_Options.Blender_OPT._ICS_CustomerHotWater := FALSE; - Processor_Options.Blender_OPT._ICS_CustomerChemRecov := FALSE; - Processor_Options.Blender_OPT._CIPSignalExchange := FALSE; - Processor_Options.Blender_OPT._ICS_CustomerChemicals := FALSE; - Processor_Options.Blender_OPT._CarboPresent := TRUE; - Processor_Options.Blender_OPT._InverterSyrupPumpPPP302 := FALSE; - Processor_Options.Blender_OPT._InverterWaterPumpPPN301 := FALSE; - Processor_Options.Blender_OPT._DoubleDeair := TRUE; - Processor_Options.Blender_OPT._DeairPreMixed := FALSE; - Processor_Options.Blender_OPT._Deaireation := TRUE; - Processor_Options.Blender_OPT._StillWaterByPass := FALSE; - Processor_Options.Blender_OPT._ManifoldSetting := TRUE; - Processor_Options.Blender_OPT._InverterProdPumpPPM303 := FALSE; - Processor_Options.Blender_OPT._SidelCip := FALSE; - Processor_Options.Blender_OPT._EthernetCom_CpuPN_CP := TRUE; - Processor_Options.Blender_OPT._2ndOutlet := 0; - Processor_Options.Blender_OPT._Promass := 2; - Processor_Options.Blender_OPT._WaterPromass := TRUE; - Processor_Options.Blender_OPT._ProductConductimeter := FALSE; - Processor_Options.Blender_OPT._ICS_CustomerH2ORecov := FALSE; - Processor_Options.Blender_OPT.Spare303 := FALSE; - Processor_Options.Blender_OPT._CO2_GAS2_Injection := FALSE; - Processor_Options.Blender_OPT._InverterVacuuPumpPPN304 := FALSE; - Processor_Options.Blender_OPT._InverterBoostPumpPPM307 := FALSE; - Processor_Options.Blender_OPT._RunOut_Water := TRUE; - Processor_Options.Blender_OPT._FlowMeterType := TRUE; - Processor_Options.Blender_OPT._SidelFiller := FALSE; - Processor_Options.Blender_OPT._Simulation := FALSE; - Processor_Options.Blender_OPT._ProductCoolingCTRL := FALSE; - Processor_Options.Blender_OPT._ChillerCTRL := FALSE; - Processor_Options.Blender_OPT._CO2_SterileFilter := TRUE; - Processor_Options.Blender_OPT._InverterRecirPumpPPM306 := FALSE; - Processor_Options.Blender_OPT._ProdPressReleaseRVM304 := FALSE; - Processor_Options.Blender_OPT._VacuumPump := 1; - Processor_Options.Blender_OPT._GAS2InjectionType := 0; - Processor_Options.Blender_OPT._InjectionPress_Ctrl := 1; - Processor_Options.Blender_OPT._ProdPressureType := 0; - Processor_Options.Blender_OPT._CIPHeatType := 0; - Processor_Options.Blender_OPT._EHS_NrRes := 6; - _RVM301_DeadBand := 5.000000e-02; - _RVM301_Kp := 9.000000e+01; - Actual_Recipe_Parameters._Name := ''; - Actual_Recipe_Parameters._EnProdTemp := TRUE; - Actual_Recipe_Parameters._SyrFlushing := FALSE; - Actual_Recipe_Parameters._GAS2_Injection := FALSE; - Actual_Recipe_Parameters._Eq_Pression_Selected := FALSE; + Actual_Recipe_Parameters._BottleShape := B#16#0; + Actual_Recipe_Parameters._BottleSize := 0.000000e+00; + Actual_Recipe_Parameters._CO2Fact := 9.400000e-01; + Actual_Recipe_Parameters._CO2Vols := 2.550000e+00; + Actual_Recipe_Parameters._CO2_Air_N2_PressSelect := 0; + Actual_Recipe_Parameters._CarboCO2ValveSave := 0.000000e+00; + Actual_Recipe_Parameters._DeoxPreMixed := FALSE; Actual_Recipe_Parameters._DeoxStripEn := FALSE; Actual_Recipe_Parameters._DeoxVacuumEn := TRUE; - Actual_Recipe_Parameters._DeoxPreMixed := FALSE; Actual_Recipe_Parameters._EnBlowOffProdPipeCO2Fil := FALSE; - Actual_Recipe_Parameters._WaterSelection := B#16#0; + Actual_Recipe_Parameters._EnProdTemp := TRUE; + Actual_Recipe_Parameters._Eq_Pression_Selected := FALSE; Actual_Recipe_Parameters._FillerNextRecipeNum := B#16#0; - Actual_Recipe_Parameters._BottleShape := B#16#0; - Actual_Recipe_Parameters._Type := 2; + Actual_Recipe_Parameters._FillingValveHead_SP := 0.000000e+00; + Actual_Recipe_Parameters._FirstProdExtraCO2Fact := 1.020000e+00; + Actual_Recipe_Parameters._GAS2_Injection := FALSE; + Actual_Recipe_Parameters._Gas2Fact := 0.000000e+00; + Actual_Recipe_Parameters._Gas2Vols := 0.000000e+00; + Actual_Recipe_Parameters._KFactRVM304BlowOff := 0.000000e+00; + Actual_Recipe_Parameters._Name := ''; + Actual_Recipe_Parameters._PrdTankMinLevel := 3.500000e+01; + Actual_Recipe_Parameters._ProdBoosterPumpPress := 0.000000e+00; + Actual_Recipe_Parameters._ProdBrixOffset := 2.500000e-01; + Actual_Recipe_Parameters._ProdHighCond := 0.000000e+00; + Actual_Recipe_Parameters._ProdLowCond := 0.000000e+00; + Actual_Recipe_Parameters._ProdMeterHighBrix := 1.030000e+01; + Actual_Recipe_Parameters._ProdMeterHighCO2 := 2.900000e+00; + Actual_Recipe_Parameters._ProdMeterLowBrix := 9.830000e+00; + Actual_Recipe_Parameters._ProdMeterLowCO2 := 2.300000e+00; Actual_Recipe_Parameters._ProdMeterRecipeNum := 1; - Actual_Recipe_Parameters._SyrupBrix := 4.625000e+01; - Actual_Recipe_Parameters._SyrupDensity := 1.206908e+00; - Actual_Recipe_Parameters._SyrupFactor := 1.000000e+00; + Actual_Recipe_Parameters._ProdMeter_ZeroBrix := 0.000000e+00; + Actual_Recipe_Parameters._ProdMeter_ZeroCO2 := 0.000000e+00; + Actual_Recipe_Parameters._ProdRecircPumpFreq := 0.000000e+00; + Actual_Recipe_Parameters._ProdSendPumpFreq := 0.000000e+00; + Actual_Recipe_Parameters._ProdTankPress := 4.400000e+00; Actual_Recipe_Parameters._ProductBrix := 1.000000e+01; Actual_Recipe_Parameters._ProductionRate := 3.800000e+02; Actual_Recipe_Parameters._Ratio := 4.238896e+00; - Actual_Recipe_Parameters._ProdBrixOffset := 2.500000e-01; - Actual_Recipe_Parameters._CO2Vols := 2.550000e+00; - Actual_Recipe_Parameters._CO2Fact := 9.400000e-01; - Actual_Recipe_Parameters._ProdTankPress := 4.400000e+00; Actual_Recipe_Parameters._SP_ProdTemp := 1.700000e+01; - Actual_Recipe_Parameters._PrdTankMinLevel := 3.500000e+01; - Actual_Recipe_Parameters._WaterValveSave := 0.000000e+00; - Actual_Recipe_Parameters._SyrupValveSave := 0.000000e+00; - Actual_Recipe_Parameters._CarboCO2ValveSave := 0.000000e+00; - Actual_Recipe_Parameters._ProdMeterHighBrix := 1.030000e+01; - Actual_Recipe_Parameters._ProdMeterLowBrix := 9.830000e+00; - Actual_Recipe_Parameters._ProdMeterHighCO2 := 2.900000e+00; - Actual_Recipe_Parameters._ProdMeterLowCO2 := 2.300000e+00; - Actual_Recipe_Parameters._ProdMeter_ZeroCO2 := 0.000000e+00; - Actual_Recipe_Parameters._ProdMeter_ZeroBrix := 0.000000e+00; - Actual_Recipe_Parameters._ProdHighCond := 0.000000e+00; - Actual_Recipe_Parameters._ProdLowCond := 0.000000e+00; - Actual_Recipe_Parameters._BottleSize := 0.000000e+00; - Actual_Recipe_Parameters._FillingValveHead_SP := 0.000000e+00; + Actual_Recipe_Parameters._SyrFlushing := FALSE; Actual_Recipe_Parameters._SyrMeter_ZeroBrix := 0.000000e+00; - Actual_Recipe_Parameters._FirstProdExtraCO2Fact := 1.020000e+00; - Actual_Recipe_Parameters._Gas2Vols := 0.000000e+00; - Actual_Recipe_Parameters._Gas2Fact := 0.000000e+00; + Actual_Recipe_Parameters._SyrupBrix := 4.625000e+01; + Actual_Recipe_Parameters._SyrupDensity := 1.206908e+00; + Actual_Recipe_Parameters._SyrupFactor := 1.000000e+00; Actual_Recipe_Parameters._SyrupPumpPressure := 0.000000e+00; + Actual_Recipe_Parameters._SyrupValveSave := 0.000000e+00; + Actual_Recipe_Parameters._Type := 2; Actual_Recipe_Parameters._WaterPumpPressure := 0.000000e+00; - Actual_Recipe_Parameters._CO2_Air_N2_PressSelect := 0; - Actual_Recipe_Parameters._KFactRVM304BlowOff := 0.000000e+00; - Actual_Recipe_Parameters._ProdRecircPumpFreq := 0.000000e+00; - Actual_Recipe_Parameters._ProdBoosterPumpPress := 0.000000e+00; - Actual_Recipe_Parameters._ProdSendPumpFreq := 0.000000e+00; + Actual_Recipe_Parameters._WaterSelection := B#16#0; + Actual_Recipe_Parameters._WaterValveSave := 0.000000e+00; Next_Recipe_Name := 'cambio 1$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00$00'; Next_Recipe_Number := 0; ProcessSetup.Spare000 := 0.000000e+00; ProcessSetup.Spare040 := 0.000000e+00; - ProcessSetup._KWaterLoss := 1.000000e-03; - ProcessSetup._KSyrupLoss := 7.800000e-03; - ProcessSetup._KProdLoss := 1.390000e-02; - ProcessSetup._KPPM303 := 5.700000e+00; - ProcessSetup._BaialageRVM301OVMin := 2.000000e+00; - ProcessSetup._SyrupLinePressure := 2.200000e+00; - ProcessSetup._CIPRMM301OV := 1.000000e+01; - ProcessSetup._CIPRMP302OV := 1.500000e+01; - ProcessSetup._CIPTM301MinLevel := 3.500000e+01; - ProcessSetup._CIPTM301MaxLevel := 5.500000e+01; - ProcessSetup._CIPPPM303Freq := 5.000000e+01; - ProcessSetup._CIPTP301MinLevel := 2.500000e+01; - ProcessSetup._CIPTP301MaxLevel := 4.500000e+01; - ProcessSetup._RinseRMM301OV := 1.000000e+01; - ProcessSetup._RinseRMP302OV := 1.400000e+01; - ProcessSetup._RinseTM301Press := 3.000000e-01; - ProcessSetup._RinsePPM303Freq := 5.000000e+01; - ProcessSetup._DrainTM301Press := 1.000000e+00; - ProcessSetup._KRecBlendError := 2.000000e+00; - ProcessSetup._KRecCarboCO2Error := 2.000000e+00; - ProcessSetup._MaxBlendError := 1.000000e+02; - ProcessSetup._MaxCarboCO2Error := 5.000000e+02; - ProcessSetup._StartUpBrixExtraWater := 4.700000e+01; - ProcessSetup._StartUpCO2ExtraWater := 8.000000e+00; - ProcessSetup._StartUpPPM303Freq := 2.000000e+01; - ProcessSetup._SyrupRoomTank := 1; - ProcessSetup._SyrupRunOutLiters := 2.900000e+02; - ProcessSetup._InjCO2Press_Offset := 5.000000e-01; - ProcessSetup._InjCO2Press_MinFlow := 4.500000e+02; - ProcessSetup._InjCO2Press_MaxFlow := 2.500000e+03; - ProcessSetup._CarboCO2Pressure := 1.250000e+01; - ProcessSetup._N2MinPressure := 1.000000e+00; - ProcessSetup._DiffSensor_Height := 3.950000e+02; - ProcessSetup._DiffSensor_DeltaHeight := -2.500000e+01; - ProcessSetup._DiffSensor_Offset := 3.618000e+01; - ProcessSetup._FillingValveHeight := 1.400000e+03; - ProcessSetup._FillerDiameter := 2.520000e+03; - ProcessSetup._FillingValveNum := 91; - ProcessSetup._FillerProdPipeDN := 1.000000e+02; - ProcessSetup._FillerProdPipeMass := 1.600000e+02; - ProcessSetup._FillingTime := 3.200000e+00; - ProcessSetup._TM301Height_0 := 1.050000e+03; - ProcessSetup._TM301LevelPerc_2 := 4.600000e+01; - ProcessSetup._TM301Height_2 := 1.625000e+03; - ProcessSetup._RVN304Factor := 1.000000e+00; - ProcessSetup._DrainTM301Flushing := 1.300000e+00; - ProcessSetup._FirstProdExtraBrix := 5.000000e-02; - ProcessSetup._FirstProdDietExtraSyr := 1.400000e-03; - ProcessSetup._EndProdLastSyrlt := 0.000000e+00; - ProcessSetup._TM301DrainSt0Time := W#16#A; - ProcessSetup._TM301DrainSt1Time := W#16#50; - ProcessSetup._ProdPipeRunOutSt0Time := W#16#1; - ProcessSetup._RMM301ProdPipeRunOu := 3.000000e+01; - ProcessSetup._RMP302ProdPipeRunOu := 4.000000e+01; - ProcessSetup._ProdPipeRunOutAmount := 3.000000e+01; - ProcessSetup._TM301RunOutChiller := 5.000000e+00; - ProcessSetup._MinSpeedNominalProd := 4.000000e-01; - ProcessSetup._MinSpeedSlowProd := 3.000000e-01; - ProcessSetup._FastChgOvrTM301DrnPrss := 9.000000e-01; - ProcessSetup._CIPTN301MinLevel := 3.500000e+01; - ProcessSetup._CIPTN301MaxLevel := 6.000000e+01; - ProcessSetup._ProdPPN304Freq := 5.000000e+01; - ProcessSetup._GAS2InjectionPress := 4.000000e+00; - ProcessSetup._BaialageRVM301OVMax := 2.000000e+01; - ProcessSetup._RinsePPN301Freq := 5.000000e+00; - ProcessSetup._CIPPPN301Freq := 5.000000e+00; - ProcessSetup._RinsePPP302Freq := 5.000000e+00; - ProcessSetup._CIPPPP302Freq := 5.000000e+00; - ProcessSetup._PercSyrupBrixSyrStarUp := 2.500000e+01; - ProcessSetup._RefTempCoolingCTRL := 0.000000e+00; - ProcessSetup._H2OSerpPrimingVolume := 1.500000e+02; ProcessSetup._AVN301_Nozzle_Kv := 1.650000e+02; ProcessSetup._AVN302_Nozzle_Kv := 2.600000e+02; ProcessSetup._AVN303_Nozzle_Kv := 1.650000e+02; + ProcessSetup._BaialageRVM301OVMax := 2.000000e+01; + ProcessSetup._BaialageRVM301OVMin := 2.000000e+00; + ProcessSetup._CIP2StepTM301Lvl := 0.000000e+00; + ProcessSetup._CIP2StepTN301Lvl := 0.000000e+00; + ProcessSetup._CIP2StepTP301Lvl := 0.000000e+00; + ProcessSetup._CIPPPM303Freq := 5.000000e+01; + ProcessSetup._CIPPPM306Freq := 5.000000e+00; + ProcessSetup._CIPPPM307Freq := 3.000000e+01; + ProcessSetup._CIPPPN301Freq := 5.000000e+00; + ProcessSetup._CIPPPP302Freq := 5.000000e+00; + ProcessSetup._CIPRMM301OV := 1.000000e+01; + ProcessSetup._CIPRMP302OV := 1.500000e+01; + ProcessSetup._CIPTM301MaxLevel := 5.500000e+01; + ProcessSetup._CIPTM301MinLevel := 3.500000e+01; + ProcessSetup._CIPTN301MaxLevel := 6.000000e+01; + ProcessSetup._CIPTN301MinLevel := 3.500000e+01; + ProcessSetup._CIPTP301MaxLevel := 4.500000e+01; + ProcessSetup._CIPTP301MinLevel := 2.500000e+01; + ProcessSetup._CarboCO2Pressure := 1.250000e+01; ProcessSetup._DeoxSpryball_Kv := 6.700000e+01; - ProcessSetup._PremixedLineDrainTime := 300; + ProcessSetup._DiffSensor_DeltaHeight := -2.500000e+01; + ProcessSetup._DiffSensor_Height := 3.950000e+02; + ProcessSetup._DiffSensor_Offset := 3.618000e+01; + ProcessSetup._DrainTM301Flushing := 1.300000e+00; + ProcessSetup._DrainTM301Press := 1.000000e+00; + ProcessSetup._EndProdLastSyrlt := 0.000000e+00; + ProcessSetup._FastChgOvrTM301DrnPrss := 9.000000e-01; + ProcessSetup._FillerDiameter := 2.520000e+03; + ProcessSetup._FillerProdPipeDN := 1.000000e+02; + ProcessSetup._FillerProdPipeMass := 1.600000e+02; + ProcessSetup._FillingTime := 3.200000e+00; + ProcessSetup._FillingValveHeight := 1.400000e+03; + ProcessSetup._FillingValveNum := 91; + ProcessSetup._FirstProdDietExtraSyr := 1.400000e-03; + ProcessSetup._FirstProdExtraBrix := 5.000000e-02; + ProcessSetup._GAS2InjectionPress := 4.000000e+00; + ProcessSetup._H2OSerpPrimingVolume := 1.500000e+02; + ProcessSetup._ICS_AcidPulseWeight := 10; + ProcessSetup._ICS_CausticPulseWeight := 10; + ProcessSetup._ICS_ConcDosPausTimeAcid := 30; + ProcessSetup._ICS_ConcDosPausTimeCaus := 30; + ProcessSetup._ICS_ConcDosWorkTimeAcid := 30; + ProcessSetup._ICS_ConcDosWorkTimeCaus := 30; + ProcessSetup._ICS_PAAPulseWeight := 10; + ProcessSetup._ICS_VolDosPauseTimeAcid := 30; + ProcessSetup._ICS_VolDosPauseTimeCaus := 30; + ProcessSetup._ICS_VolDosWorkTimeAcid := 30; + ProcessSetup._ICS_VolDosWorkTimeCaus := 30; + ProcessSetup._ICS_VolDosWorkTimePAA := 30; + ProcessSetup._ICS_VolPauseTimePAA := 30; + ProcessSetup._ICS_VolumeRestOfLine := 3.500000e+02; + ProcessSetup._InjCO2Press_MaxFlow := 2.500000e+03; + ProcessSetup._InjCO2Press_MinFlow := 4.500000e+02; + ProcessSetup._InjCO2Press_Offset := 5.000000e-01; + ProcessSetup._KPPM303 := 5.700000e+00; + ProcessSetup._KProdLoss := 1.390000e-02; + ProcessSetup._KRecBlendError := 2.000000e+00; + ProcessSetup._KRecCarboCO2Error := 2.000000e+00; + ProcessSetup._KSyrupLoss := 7.800000e-03; + ProcessSetup._KWaterLoss := 1.000000e-03; + ProcessSetup._MaxBlendError := 1.000000e+02; + ProcessSetup._MaxCarboCO2Error := 5.000000e+02; + ProcessSetup._MinSpeedNominalProd := 4.000000e-01; + ProcessSetup._MinSpeedSlowProd := 3.000000e-01; + ProcessSetup._N2MinPressure := 1.000000e+00; + ProcessSetup._PPM307_H_MaxFlow := 0.000000e+00; + ProcessSetup._PPM307_H_MinFlow := 0.000000e+00; + ProcessSetup._PPM307_MaxFlow := 0.000000e+00; + ProcessSetup._PPM307_MinFlow := 0.000000e+00; ProcessSetup._PPN301_H_MaxFlow := 9.000000e+01; ProcessSetup._PPN301_H_MinFlow := 8.700000e+01; ProcessSetup._PPN301_MaxFlow := 5.070000e+02; @@ -481,41 +404,150 @@ BEGIN ProcessSetup._PPP302_H_MinFlow := 8.500000e+01; ProcessSetup._PPP302_MaxFlow := 1.150000e+02; ProcessSetup._PPP302_MinFlow := 3.200000e+01; + ProcessSetup._PercSyrupBrixSyrStarUp := 2.500000e+01; + ProcessSetup._PremixedLineDrainTime := 300; + ProcessSetup._ProdPPN304Freq := 5.000000e+01; + ProcessSetup._ProdPipeRunOutAmount := 3.000000e+01; + ProcessSetup._ProdPipeRunOutSt0Time := W#16#1; + ProcessSetup._PumpNominalFreq := 5.000000e+01; + ProcessSetup._RMM301ProdPipeRunOu := 3.000000e+01; + ProcessSetup._RMP302ProdPipeRunOu := 4.000000e+01; + ProcessSetup._RVN304Factor := 1.000000e+00; + ProcessSetup._RefTempCoolingCTRL := 0.000000e+00; + ProcessSetup._RinsePPM303Freq := 5.000000e+01; ProcessSetup._RinsePPM306Freq := 5.000000e+00; - ProcessSetup._CIPPPM306Freq := 5.000000e+00; - ProcessSetup._PPM307_H_MaxFlow := 0.000000e+00; - ProcessSetup._PPM307_H_MinFlow := 0.000000e+00; - ProcessSetup._PPM307_MaxFlow := 0.000000e+00; - ProcessSetup._PPM307_MinFlow := 0.000000e+00; + ProcessSetup._RinsePPM307Freq := 3.000000e+01; + ProcessSetup._RinsePPN301Freq := 5.000000e+00; + ProcessSetup._RinsePPP302Freq := 5.000000e+00; + ProcessSetup._RinseRMM301OV := 1.000000e+01; + ProcessSetup._RinseRMP302OV := 1.400000e+01; + ProcessSetup._RinseTM301Press := 3.000000e-01; + ProcessSetup._StartUpBrixExtraWater := 4.700000e+01; + ProcessSetup._StartUpCO2ExtraWater := 8.000000e+00; + ProcessSetup._StartUpPPM303Freq := 2.000000e+01; + ProcessSetup._SyrupLinePressure := 2.200000e+00; + ProcessSetup._SyrupRoomTank := 1; + ProcessSetup._SyrupRunOutLiters := 2.900000e+02; + ProcessSetup._T1_VacuumCtrl := L#1500; + ProcessSetup._T2_VacuumCtrl := L#1500; + ProcessSetup._T3_VacuumCtrl := L#1000; + ProcessSetup._T4_VacuumCtrl := L#1000; + ProcessSetup._TM301DrainSt0Time := W#16#A; + ProcessSetup._TM301DrainSt1Time := W#16#50; + ProcessSetup._TM301Height_0 := 1.050000e+03; + ProcessSetup._TM301Height_2 := 1.625000e+03; + ProcessSetup._TM301LevelPerc_2 := 4.600000e+01; + ProcessSetup._TM301RunOutChiller := 5.000000e+00; ProcessSetup._Temp0_VacuumCtrl := 1.800000e+01; ProcessSetup._Temp1_VacuumCtrl := 2.000000e+00; ProcessSetup._Temp2_VacuumCtrl := 2.000000e+00; ProcessSetup._Temp3_VacuumCtrl := 5.000000e+01; ProcessSetup._Temp4_VacuumCtrl := 5.000000e+01; - ProcessSetup._T1_VacuumCtrl := L#1500; - ProcessSetup._T2_VacuumCtrl := L#1500; - ProcessSetup._T3_VacuumCtrl := L#1000; - ProcessSetup._T4_VacuumCtrl := L#1000; - ProcessSetup._ICS_VolDosWorkTimePAA := 30; - ProcessSetup._ICS_VolPauseTimePAA := 30; - ProcessSetup._ICS_PAAPulseWeight := 10; - ProcessSetup._ICS_CausticPulseWeight := 10; - ProcessSetup._ICS_AcidPulseWeight := 10; - ProcessSetup._ICS_VolumeRestOfLine := 3.500000e+02; - ProcessSetup._ICS_VolDosWorkTimeCaus := 30; - ProcessSetup._ICS_VolDosPauseTimeCaus := 30; - ProcessSetup._ICS_VolDosWorkTimeAcid := 30; - ProcessSetup._ICS_VolDosPauseTimeAcid := 30; - ProcessSetup._ICS_ConcDosWorkTimeCaus := 30; - ProcessSetup._ICS_ConcDosPausTimeCaus := 30; - ProcessSetup._ICS_ConcDosWorkTimeAcid := 30; - ProcessSetup._ICS_ConcDosPausTimeAcid := 30; - ProcessSetup._RinsePPM307Freq := 3.000000e+01; - ProcessSetup._CIPPPM307Freq := 3.000000e+01; - ProcessSetup._CIP2StepTN301Lvl := 0.000000e+00; - ProcessSetup._CIP2StepTM301Lvl := 0.000000e+00; - ProcessSetup._CIP2StepTP301Lvl := 0.000000e+00; - ProcessSetup._PumpNominalFreq := 5.000000e+01; + Processor_Options.Blender_OPT.Spare101 := FALSE; + Processor_Options.Blender_OPT.Spare303 := FALSE; + Processor_Options.Blender_OPT._2ndOutlet := 0; + Processor_Options.Blender_OPT._Balaiage := FALSE; + Processor_Options.Blender_OPT._BlendFillSystem := TRUE; + Processor_Options.Blender_OPT._BrixMeter := TRUE; + Processor_Options.Blender_OPT._CIPHeatType := 0; + Processor_Options.Blender_OPT._CIPSignalExchange := FALSE; + Processor_Options.Blender_OPT._CO2InletMeter := TRUE; + Processor_Options.Blender_OPT._CO2_GAS2_Injection := FALSE; + Processor_Options.Blender_OPT._CO2_Offset := 4.500000e-01; + Processor_Options.Blender_OPT._CO2_SterileFilter := TRUE; + Processor_Options.Blender_OPT._CarboPresent := TRUE; + Processor_Options.Blender_OPT._ChillerCTRL := FALSE; + Processor_Options.Blender_OPT._CoolerControl := 4; + Processor_Options.Blender_OPT._CoolerPresent := TRUE; + Processor_Options.Blender_OPT._CoolerType := 0; + Processor_Options.Blender_OPT._CopressedAirInletMeter := FALSE; + Processor_Options.Blender_OPT._DBC_Type := FALSE; + Processor_Options.Blender_OPT._DeairPreMixed := FALSE; + Processor_Options.Blender_OPT._Deaireation := TRUE; + Processor_Options.Blender_OPT._DoubleDeair := TRUE; + Processor_Options.Blender_OPT._EHS_NrRes := 6; + Processor_Options.Blender_OPT._EthernetCom_CpuPN_CP := TRUE; + Processor_Options.Blender_OPT._FastChangeOverEnabled := TRUE; + Processor_Options.Blender_OPT._Flooding_Start_Up := FALSE; + Processor_Options.Blender_OPT._FlowMeterType := TRUE; + Processor_Options.Blender_OPT._GAS2InjectionType := 0; + Processor_Options.Blender_OPT._HistoricalTrends := TRUE; + Processor_Options.Blender_OPT._ICS_CustomerChemRecov := FALSE; + Processor_Options.Blender_OPT._ICS_CustomerChemicals := FALSE; + Processor_Options.Blender_OPT._ICS_CustomerH2ORecov := FALSE; + Processor_Options.Blender_OPT._ICS_CustomerHotWater := FALSE; + Processor_Options.Blender_OPT._InjectionPress_Ctrl := 1; + Processor_Options.Blender_OPT._InverterBoostPumpPPM307 := FALSE; + Processor_Options.Blender_OPT._InverterProdPumpPPM303 := FALSE; + Processor_Options.Blender_OPT._InverterRecirPumpPPM306 := FALSE; + Processor_Options.Blender_OPT._InverterSyrupPumpPPP302 := FALSE; + Processor_Options.Blender_OPT._InverterVacuuPumpPPN304 := FALSE; + Processor_Options.Blender_OPT._InverterWaterPumpPPN301 := FALSE; + Processor_Options.Blender_OPT._LocalCIP := FALSE; + Processor_Options.Blender_OPT._ManifoldSetting := TRUE; + Processor_Options.Blender_OPT._MaxSyrDeltaBrix := 8.000000e-01; + Processor_Options.Blender_OPT._MeterReceiveOnly := FALSE; + Processor_Options.Blender_OPT._MeterType := 6; + Processor_Options.Blender_OPT._ModelNum := 6; + Processor_Options.Blender_OPT._PAmPDSType := FALSE; + Processor_Options.Blender_OPT._PowerMeter := FALSE; + Processor_Options.Blender_OPT._ProdPressReleaseRVM304 := FALSE; + Processor_Options.Blender_OPT._ProdPressureType := 0; + Processor_Options.Blender_OPT._ProductConductimeter := FALSE; + Processor_Options.Blender_OPT._ProductCoolingCTRL := FALSE; + Processor_Options.Blender_OPT._ProductO2Meter := FALSE; + Processor_Options.Blender_OPT._Promass := 2; + Processor_Options.Blender_OPT._PumpsSafetySwitches := FALSE; + Processor_Options.Blender_OPT._Report := TRUE; + Processor_Options.Blender_OPT._RunOut_Water := TRUE; + Processor_Options.Blender_OPT._SidelCip := FALSE; + Processor_Options.Blender_OPT._SidelFiller := FALSE; + Processor_Options.Blender_OPT._SignalExchange := 1; + Processor_Options.Blender_OPT._Simulation := FALSE; + Processor_Options.Blender_OPT._StillWaterByPass := FALSE; + Processor_Options.Blender_OPT._SurgeProtectionAct := FALSE; + Processor_Options.Blender_OPT._SyrBrixMeter := FALSE; + Processor_Options.Blender_OPT._TrackFillerSpeed := TRUE; + Processor_Options.Blender_OPT._TrackH2OEnable := FALSE; + Processor_Options.Blender_OPT._VacuumPump := 1; + Processor_Options.Blender_OPT._Valves_FullFeedback := TRUE; + Processor_Options.Blender_OPT._Valves_SingleFeedback := FALSE; + Processor_Options.Blender_OPT._WaterInletMeter := FALSE; + Processor_Options.Blender_OPT._WaterPromass := TRUE; + Spare1[1] := 0; + Spare1[2] := 0; + Spare1[3] := 0; + Spare1[4] := 0; + Spare1[5] := 0; + Spare1[6] := 0; + Spare1[7] := 0; + Spare1[8] := 0; + Spare1[9] := 0; + Spare2[1] := 0; + Spare2[2] := 0; + Spare2[3] := 0; + Spare2[4] := 0; + Spare2[5] := 0; + Spare3[10] := 0; + Spare3[11] := 0; + Spare3[12] := 0; + Spare3[13] := 0; + Spare3[14] := 0; + Spare3[15] := 0; + Spare3[16] := 0; + Spare3[17] := 0; + Spare3[18] := 0; + Spare3[1] := 0; + Spare3[2] := 0; + Spare3[3] := 0; + Spare3[4] := 0; + Spare3[5] := 0; + Spare3[6] := 0; + Spare3[7] := 0; + Spare3[8] := 0; + Spare3[9] := 0; + _RVM301_DeadBand := 5.000000e-02; + _RVM301_Kp := 9.000000e+01; _SwitchOff_DensityOK := FALSE; END_DATA_BLOCK; diff --git a/backend/script_groups/S7_DB_Utils/x3.py b/backend/script_groups/S7_DB_Utils/x3.py index 6f2c219..2b620f3 100644 --- a/backend/script_groups/S7_DB_Utils/x3.py +++ b/backend/script_groups/S7_DB_Utils/x3.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from typing import List, Dict, Optional, Union, Tuple, Any import copy +# --- Estructuras de Datos --- @dataclass class ArrayDimension: lower_bound: int @@ -16,22 +17,24 @@ class ArrayDimension: @dataclass class VariableInfo: name: str - data_type: str + data_type: str # Tipo base limpio (ej. "INT", "REAL", nombre del UDT sin comillas) byte_offset: float - size_in_bytes: int # For BOOL arrays, this is the number of bytes spanned. For single BOOL, 0. - bit_size: int = 0 # For BOOL, this is 1. For others, usually 0 unless it's a bitfield type. - udt_source_name: Optional[str] = None + size_in_bytes: int # Para BOOL arrays, bytes que ocupa. Para BOOL individual, 0. + bit_size: int = 0 # Para BOOL, 1. Para otros, 0. + udt_source_name: Optional[str] = None # Nombre original del UDT con comillas, si es UDT string_length: Optional[int] = None array_dimensions: List[ArrayDimension] = field(default_factory=list) - initial_value: Optional[str] = None - current_value: Optional[str] = None + initial_value: Optional[str] = None # Valor de la declaración + current_value: Optional[str] = None # Valor efectivo (del bloque BEGIN o fallback a initial_value) comment: Optional[str] = None - children: List['VariableInfo'] = field(default_factory=list) + children: List['VariableInfo'] = field(default_factory=list) # Para STRUCTs o UDTs expandidos is_udt_expanded_member: bool = False + # Nuevo campo para almacenar valores de elementos de array del bloque BEGIN + current_element_values: Optional[Dict[str, str]] = None # Ej: {"1": "val1", "1,0":"val_multi"} @dataclass class UdtInfo: - name: str + name: str # Nombre del UDT (sin comillas) family: Optional[str] = None version: Optional[str] = None members: List[VariableInfo] = field(default_factory=list) @@ -39,12 +42,13 @@ class UdtInfo: @dataclass class DbInfo: - name: str + name: str # Nombre del DB (sin comillas) 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 + # Este diccionario almacena las asignaciones literales del bloque BEGIN _initial_values_from_begin_block: Dict[str, str] = field(default_factory=dict) @dataclass @@ -76,24 +80,23 @@ class OffsetContext: self.align_to_byte() if self.byte_offset % 2 != 0: self.byte_offset += 1 +# --- Fin Estructuras de Datos --- S7_PRIMITIVE_SIZES = { - # type_name: (size_in_bytes, alignment_in_bytes_for_start, is_bool_type) "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), "DATE": (2, 2, False), "DWORD": (4, 2, False), "DINT": (4, 2, False), "UDINT": (4, 2, False), "REAL": (4, 2, False), "TIME": (4, 2, False), - "TIME_OF_DAY": (4, 2, False), "TOD": (4, 2, False), # TOD is alias for TIME_OF_DAY + "TIME_OF_DAY": (4, 2, False), "TOD": (4, 2, False), "LREAL": (8, 2, False), "LINT": (8, 2, False), "ULINT": (8, 2, False), - "LWORD": (8, 2, False), "DATE_AND_TIME": (8, 2, False), "DT": (8, 2, False), # DT is alias for DATE_AND_TIME - # STRING is handled specially due to its length component + "LWORD": (8, 2, False), "DATE_AND_TIME": (8, 2, False), "DT": (8, 2, False), } class S7Parser: def __init__(self): self.parsed_data = ParsedData() - self.known_udts: Dict[str, UdtInfo] = {} + self.known_udts: Dict[str, UdtInfo] = {} # Clave es el nombre del UDT sin comillas self.type_start_regex = re.compile(r'^\s*TYPE\s+"([^"]+)"', re.IGNORECASE) self.db_start_regex = re.compile(r'^\s*DATA_BLOCK\s+"([^"]+)"', re.IGNORECASE) self.property_regex = re.compile(r'^\s*([A-Z_]+)\s*:\s*(.+?)(?:\s*;)?\s*(?://.*)?$', re.IGNORECASE) @@ -107,38 +110,45 @@ class S7Parser: r'^\s*(?P[a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*' r'(?P' r'(?:ARRAY\s*\[(?P[^\]]+?)\]\s*OF\s*)?' - r'(?P(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))' # UDTs in quotes, primitives without - r'(?:\s*\[\s*(?P\d+)\s*\])?' # Optional string length + r'(?P(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))' + r'(?:\s*\[\s*(?P\d+)\s*\])?' r')' - r'(?:\s*:=\s*(?P[^;]*?))??\s*' # Initial value: non-greedy, does not cross a semicolon - r';?\s*$', # Optional semicolon at the end of the declaration + r'(?:\s*:=\s*(?P[^;]*?))??\s*' + r';?\s*$', re.IGNORECASE ) self.array_dim_regex = re.compile(r'(\d+)\s*\.\.\s*(\d+)') + # Regex para capturar elementos de array en el bloque BEGIN, ej: MyArray[1] o MyArray[1,2] + self.array_element_assign_regex_template = r'^\s*{array_var_name_escaped}\[(?P[0-9,\s]+)\]\s*$' - def _get_type_details(self, type_name_raw: str) -> Tuple[int, int, bool, str]: - type_name_cleaned = type_name_raw.strip('"').upper() - udt_original_case_name = type_name_raw.strip('"') + def _get_type_details(self, type_name_raw_cleaned: str) -> Tuple[int, int, bool, str]: + """ + type_name_raw_cleaned: Nombre del tipo ya sin comillas y en mayúsculas para primitivos, + o en su caso original para UDTs. + Returns: (size_in_bytes, alignment_req_bytes, is_bool, effective_type_name_for_lookup) + """ + type_name_upper = type_name_raw_cleaned.upper() - if type_name_cleaned in S7_PRIMITIVE_SIZES: - size, align, is_bool = S7_PRIMITIVE_SIZES[type_name_cleaned] - return size, align, is_bool, type_name_cleaned - elif udt_original_case_name in self.known_udts: - udt = self.known_udts[udt_original_case_name] - return udt.total_size_in_bytes, 2, False, udt_original_case_name # UDTs align like structs (word) - elif type_name_cleaned == "STRUCT": # For explicit STRUCT members - return 0, 2, False, "STRUCT" # Size determined by members, aligns to word - raise ValueError(f"Unknown data type or UDT not defined: '{type_name_raw}' (Cleaned: '{type_name_cleaned}', Original UDT: '{udt_original_case_name}')") + if type_name_upper in S7_PRIMITIVE_SIZES: + size, align, is_bool = S7_PRIMITIVE_SIZES[type_name_upper] + return size, align, is_bool, type_name_upper + elif type_name_raw_cleaned in self.known_udts: # Búsqueda case-sensitive para UDTs + udt = self.known_udts[type_name_raw_cleaned] + return udt.total_size_in_bytes, 2, False, type_name_raw_cleaned + elif type_name_upper == "STRUCT": + return 0, 2, False, "STRUCT" + 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): for child in children: child.byte_offset += base_offset_add - if child.byte_offset == float(int(child.byte_offset)): + if child.byte_offset == float(int(child.byte_offset)): # Limpiar .0 innecesario child.byte_offset = float(int(child.byte_offset)) if child.children: - S7Parser._adjust_children_offsets(child.children, base_offset_add) # Pass the original base_offset_add + S7Parser._adjust_children_offsets(child.children, base_offset_add) + def _parse_struct_members(self, lines: List[str], current_line_idx: int, parent_members_list: List[VariableInfo], @@ -147,9 +157,8 @@ class S7Parser: idx_to_process = current_line_idx while idx_to_process < len(lines): - original_line_text_with_leading_space = lines[idx_to_process] - original_line_text = original_line_text_with_leading_space.strip() - + original_line_text = lines[idx_to_process].strip() + line_to_parse = original_line_text line_comment = None comment_marker_idx = original_line_text.find("//") @@ -157,332 +166,284 @@ class S7Parser: line_to_parse = original_line_text[:comment_marker_idx].strip() line_comment = original_line_text[comment_marker_idx + 2:].strip() - line_index_for_return = idx_to_process # Save index before incrementing - idx_to_process += 1 # Pre-increment for next loop or return + line_index_for_return = idx_to_process + idx_to_process += 1 - if not line_to_parse: # If line is empty after stripping comments, or was originally empty - continue + if not line_to_parse: continue - # Handle block endings (STRUCT, TYPE, DB, BEGIN) based on line_to_parse if self.end_struct_regex.match(line_to_parse): - if not is_top_level_struct_in_block: # End of a nested STRUCT member - active_context.align_to_byte() # Ensure current byte is finished - if active_context.byte_offset % 2 != 0: # Struct total size must be even - active_context.byte_offset += 1 - return idx_to_process # Return line number AFTER END_STRUCT - # If is_top_level_struct_in_block, END_STRUCT is for the main block, handled by END_TYPE/DB + if not is_top_level_struct_in_block: + active_context.align_to_byte() + if active_context.byte_offset % 2 != 0: active_context.byte_offset += 1 + return idx_to_process if is_top_level_struct_in_block and \ (self.end_type_regex.match(line_to_parse) or \ self.end_db_regex.match(line_to_parse) or \ self.begin_regex.match(line_to_parse)): - active_context.align_to_byte() # Finish current byte for the whole UDT/DB declaration part - if active_context.byte_offset % 2 != 0: # Total size must be even - active_context.byte_offset += 1 - return line_index_for_return # Return index OF the BEGIN/END_TYPE/END_DB line - - # Check for STRUCT start for a member - if self.struct_start_regex.match(line_to_parse): # This is a nested STRUCT member declaration - # This line should be matched by var_regex as a variable of type STRUCT - # If we are here, it means var_regex didn't match it, or it's an anonymous struct. - # For simplicity, we assume named structs are parsed by var_regex. - # This path might need review if anonymous structs are common. - # For now, we assume explicit STRUCT members are named and caught by var_regex - pass # Let var_regex handle it if it's a named struct - + active_context.align_to_byte() + if active_context.byte_offset % 2 != 0: active_context.byte_offset += 1 + return line_index_for_return + var_match = self.var_regex_simplified.match(line_to_parse) if var_match: var_data = var_match.groupdict() - var_info = VariableInfo(name=var_data['name'], data_type="", byte_offset=0, size_in_bytes=0) + + # data_type se refiere al tipo base limpio, udt_source_name al original con comillas + raw_base_type_from_regex = var_data['basetype'].strip() + clean_data_type = raw_base_type_from_regex.strip('"') + udt_source_name_val = raw_base_type_from_regex if raw_base_type_from_regex.startswith('"') else None - initial_val_str = var_data.get('initval') - if initial_val_str: - var_info.initial_value = initial_val_str.strip() + var_info = VariableInfo(name=var_data['name'], + data_type=clean_data_type, + byte_offset=0, size_in_bytes=0, + udt_source_name=udt_source_name_val) + + if var_data.get('initval'): + var_info.initial_value = var_data['initval'].strip() if line_comment: var_info.comment = line_comment - raw_base_type = var_data['basetype'].strip() - var_info.data_type = raw_base_type.strip('"') # Store clean type for logic - if raw_base_type.startswith('"') and raw_base_type.endswith('"'): - var_info.udt_source_name = raw_base_type # Store with quotes if UDT - num_array_elements = 1 if var_data['arraydims']: for dim_match in self.array_dim_regex.finditer(var_data['arraydims']): var_info.array_dimensions.append(ArrayDimension(int(dim_match.group(1)), int(dim_match.group(2)))) if var_info.array_dimensions: - for dim in var_info.array_dimensions: - num_array_elements *= dim.count + for dim in var_info.array_dimensions: num_array_elements *= dim.count - # --- Offset and Size Calculation --- - if var_info.data_type.upper() == "STRUCT": # Member is explicitly 'STRUCT' + if var_info.data_type.upper() == "STRUCT": # Un miembro definido como STRUCT explícitamente active_context.align_to_word() var_info.byte_offset = active_context.get_combined_offset() - nested_struct_context = OffsetContext() # Children offsets are relative to this new context - # We need to find the line "STRUCT" and parse from the line AFTER it - # The current idx_to_process is already advanced. - # The _parse_struct_members call will continue from where the var_match line was, - # which is not right for a struct definition that spans multiple lines. - # This means "STRUCT" members need to be handled by the parser finding "STRUCT" keyword, - # not just by var_regex matching "Variable : STRUCT". - # For now, assume if var_regex matched, it's a named struct. - # The call to _parse_struct_members for children should start from the *next line* in the input lines list. - + nested_struct_context = OffsetContext() idx_after_nested_struct = self._parse_struct_members( - lines, - idx_to_process, # Start parsing members of this struct from the next available line - var_info.children, - nested_struct_context, - is_top_level_struct_in_block=False # This is a nested struct + lines, idx_to_process, var_info.children, + nested_struct_context, is_top_level_struct_in_block=False ) - var_info.size_in_bytes = nested_struct_context.byte_offset # This is the calculated size of the nested struct - - # Adjust children offsets to be absolute - # The children's byte_offset are currently relative to the start of the nested_struct_context (0.0) - # They need to be relative to the DB/UDT. - # var_info.byte_offset is the absolute start of this STRUCT member. + var_info.size_in_bytes = nested_struct_context.byte_offset + + # Ajustar offsets de los hijos del struct anidado para que sean globales for child in var_info.children: child.byte_offset += var_info.byte_offset if child.byte_offset == float(int(child.byte_offset)): - child.byte_offset = float(int(child.byte_offset)) - if child.children: # If UDTs within struct had their own structs - S7Parser._adjust_children_offsets(child.children, var_info.byte_offset) + child.byte_offset = float(int(child.byte_offset)) + if child.children: + S7Parser._adjust_children_offsets(child.children, var_info.byte_offset) - active_context.byte_offset += var_info.size_in_bytes # Advance parent context - idx_to_process = idx_after_nested_struct # Update main loop's line index + active_context.byte_offset += var_info.size_in_bytes + idx_to_process = idx_after_nested_struct elif var_info.data_type.upper() == "STRING" and var_data['stringlength']: var_info.string_length = int(var_data['stringlength']) - unit_size = var_info.string_length + 2 - active_context.align_to_word() # STRINGs are word-aligned + unit_size = var_info.string_length + 2 # N chars + 2 header bytes + active_context.align_to_word() # STRING se alinea a palabra var_info.byte_offset = active_context.get_combined_offset() var_info.size_in_bytes = unit_size * num_array_elements active_context.byte_offset += var_info.size_in_bytes - - else: # Primitive or UDT instance - # Use var_info.data_type (cleaned name) for _get_type_details + + else: # Primitivo o instancia de UDT + # Usar var_info.data_type (limpio) para _get_type_details unit_size_bytes, unit_alignment_req, is_bool, type_name_for_udt_lookup = self._get_type_details(var_info.data_type) if is_bool: - var_info.bit_size = 1 # A single BOOL is 1 bit - # For an array of BOOLs, record offset of the first bit + var_info.bit_size = 1 var_info.byte_offset = active_context.get_combined_offset() - active_context.advance_bits(num_array_elements) # Advance context by total bits - - # Calculate effective byte span for the BOOL or BOOL array + active_context.advance_bits(num_array_elements) + start_byte_abs = int(var_info.byte_offset) start_bit_in_byte = int(round((var_info.byte_offset - start_byte_abs) * 10)) - if num_array_elements == 1: - var_info.size_in_bytes = 0 # Convention for single bit - else: # Array of BOOLs - bits_remaining = num_array_elements - bytes_spanned = 0 - if start_bit_in_byte > 0: # Starts mid-byte - bits_in_first_byte = 8 - start_bit_in_byte - if bits_remaining <= bits_in_first_byte: - bytes_spanned = 1 - else: - bytes_spanned = 1 - bits_remaining -= bits_in_first_byte - bytes_spanned += (bits_remaining + 7) // 8 # Ceiling division for remaining full bytes - else: # Starts on a byte boundary - bytes_spanned = (bits_remaining + 7) // 8 + var_info.size_in_bytes = 0 # Convención para un solo bit + else: # Array de BOOLs + bits_rem = num_array_elements; bytes_spanned = 0 + if start_bit_in_byte > 0: + bits_in_first = 8 - start_bit_in_byte + if bits_rem <= bits_in_first: bytes_spanned = 1 + else: bytes_spanned = 1; bits_rem -= bits_in_first; bytes_spanned += (bits_rem + 7) // 8 + else: bytes_spanned = (bits_rem + 7) // 8 var_info.size_in_bytes = bytes_spanned - else: # Non-BOOL primitive or UDT - active_context.align_to_byte() # Finish any pending bits - if unit_alignment_req == 2: # WORD, DWORD, REAL, UDT, etc. - active_context.align_to_word() + else: # No es BOOL (Primitivo > byte, o UDT) + active_context.align_to_byte() + if unit_alignment_req == 2: active_context.align_to_word() var_info.byte_offset = active_context.get_combined_offset() var_info.size_in_bytes = unit_size_bytes * num_array_elements active_context.byte_offset += var_info.size_in_bytes - # If it's a UDT instance, expand its members - if type_name_for_udt_lookup in self.known_udts and not is_bool: + if type_name_for_udt_lookup in self.known_udts and not is_bool: # Es una instancia de UDT udt_def = self.known_udts[type_name_for_udt_lookup] - udt_instance_absolute_start_offset = var_info.byte_offset + udt_instance_abs_start_offset = var_info.byte_offset for udt_member_template in udt_def.members: expanded_member = copy.deepcopy(udt_member_template) expanded_member.is_udt_expanded_member = True - # udt_member_template.byte_offset is relative to UDT start (0.0) - expanded_member.byte_offset += udt_instance_absolute_start_offset + expanded_member.byte_offset += udt_instance_abs_start_offset # Hacer absoluto if expanded_member.byte_offset == float(int(expanded_member.byte_offset)): expanded_member.byte_offset = float(int(expanded_member.byte_offset)) - # If the UDT member itself has children (e.g., a struct within the UDT) - # their offsets also need to be made absolute relative to the DB. - # The base_offset_add for _adjust_children_offsets should be the - # absolute start of the current UDT instance. - if expanded_member.children: - S7Parser._adjust_children_offsets(expanded_member.children, udt_instance_absolute_start_offset) + if expanded_member.children: # Si el miembro del UDT era un STRUCT + S7Parser._adjust_children_offsets(expanded_member.children, udt_instance_abs_start_offset) var_info.children.append(expanded_member) parent_members_list.append(var_info) - else: # Line not matched by var_regex - # Check if it's a STRUCT definition line that var_regex MISSED - # This is a fallback / debug for when 'STRUCT' starts a definition block for a member - struct_keyword_match = self.struct_start_regex.match(line_to_parse) - if struct_keyword_match and not var_match : # An unnamed struct or parsing issue - print(f"DEBUG: Found 'STRUCT' keyword on line but not parsed by var_regex: '{original_line_text}' | Processed='{line_to_parse}'") - # This case might need more robust handling if anonymous structs are used or if var_regex is too strict for named structs - elif line_to_parse and \ - not self.end_struct_regex.match(line_to_parse) and \ - not (is_top_level_struct_in_block and \ - (self.end_type_regex.match(line_to_parse) or \ - self.end_db_regex.match(line_to_parse) or \ - self.begin_regex.match(line_to_parse))): + else: # No es variable, ni fin de struct, ni fin de bloque principal + if line_to_parse and \ + not self.struct_start_regex.match(line_to_parse): # Si no es tampoco un "STRUCT" de inicio de miembro print(f"DEBUG: Line not parsed as variable or known keyword: Original='{original_line_text}' | Processed='{line_to_parse}'") - # This final padding should ideally be handled when END_STRUCT or END_TYPE/DB is detected - # For is_top_level_struct_in_block, it's handled by BEGIN/END_TYPE/DB detection. - # For nested structs, it's handled by END_STRUCT detection. + # El padding final del struct/bloque se maneja al detectar END_STRUCT o END_TYPE/DB/BEGIN return idx_to_process - def _parse_begin_block(self, lines: List[str], start_idx: int, db_info: DbInfo) -> int: idx = start_idx - # Regex for assignment: path := value ; - # Path can contain dots, array indices. Value can be complex. - assignment_regex = re.compile(r'^\s*(?P[a-zA-Z0-9_."\[\],\s]+?)\s*:=\s*(?P.+?)\s*;?\s*$', re.IGNORECASE) + assignment_regex = re.compile(r'^\s*(?P.+?)\s*:=\s*(?P.+?)\s*;?\s*$', re.IGNORECASE) + while idx < len(lines): original_line = lines[idx].strip() - line_to_parse = original_line - comment_marker_idx = original_line.find("//") - if comment_marker_idx != -1: - line_to_parse = original_line[:comment_marker_idx].strip() - # comment = original_line[comment_marker_idx+2:].strip() # Comment in BEGIN usually not stored + comment_marker = original_line.find("//") + if comment_marker != -1: + line_to_parse = original_line[:comment_marker].strip() + # Comentarios en líneas de asignación no se guardan estructuradamente por ahora - if self.end_db_regex.match(line_to_parse): # END_DATA_BLOCK terminates BEGIN section - return idx # Return index of END_DATA_BLOCK + if self.end_db_regex.match(line_to_parse): + return idx - idx += 1 # Advance to next line - - if not line_to_parse: continue # Skip empty lines + idx += 1 + if not line_to_parse: continue match = assignment_regex.match(line_to_parse) if match: path = match.group("path").strip() value = match.group("value").strip().rstrip(';').strip() db_info._initial_values_from_begin_block[path] = value - # else: # Optional: print lines in BEGIN that don't match assignment - # print(f"DEBUG: Line in BEGIN not matched as assignment: '{original_line}'") - - raise SyntaxError("Expected END_DATA_BLOCK after BEGIN section, but not found.") - + # else: + # print(f"DEBUG: Line in BEGIN not matched: '{original_line}'") + raise SyntaxError("Se esperaba END_DATA_BLOCK después de la sección BEGIN.") def _apply_current_values(self, members: List[VariableInfo], begin_values: Dict[str, str], current_path_prefix: str = ""): for var_info in members: - # Construct full path, handling array indices if necessary (simplification: not handling array element assignment here) - # For UDTs, the path in BEGIN block directly names the UDT member, e.g., "MyUdtVar._Name" full_member_path = f"{current_path_prefix}{var_info.name}" - if var_info.is_udt_expanded_member: # Path comes from the UDT parent - # This requires careful reconstruction if the assignment path is more complex - # For now, assume direct member access for expanded UDTs. - # Example: If parent is "Actual_Recipe", and child is "_Name", path is "Actual_Recipe._Name" - # current_path_prefix should be the name of the UDT variable instance. - pass # The full_member_path is already constructed above with current_path_prefix + if var_info.array_dimensions: + var_info.current_element_values = {} + # Lógica para buscar elementos de array en begin_values + # Asumimos que las claves en begin_values son como "ArrayName[idx]" o "Struct.ArrayName[idx,idx2]" + + # Construir un patrón para buscar elementos de este array específico + # Escapar el nombre del array por si contiene caracteres especiales de regex + # Y luego añadir el patrón para los corchetes e índices. + # Esto es una simplificación. Un parseo robusto de índices multidimensionales es más complejo. + # Ejemplo de clave en begin_values: "Spare1[1]", "Path.To.Array[5]" + prefix_for_search = full_member_path + "[" # ej. "Spare1[" + + for key_in_begin, val_in_begin in begin_values.items(): + if key_in_begin.startswith(prefix_for_search) and key_in_begin.endswith("]"): + # Extraer el contenido de los corchetes + 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 la clave del array: {key_in_begin}") + + if not var_info.current_element_values: + var_info.current_element_values = None # No guardar dict vacío + + # El current_value del array en sí mismo no se establece a menos que haya una asignación global + # como "ArrayName := ..." lo cual es raro para arrays complejos en BEGIN. + if full_member_path in begin_values: # Asignación global al array? + var_info.current_value = begin_values[full_member_path] - if full_member_path in begin_values: + + elif full_member_path in begin_values: # Variable simple var_info.current_value = begin_values[full_member_path] - elif var_info.initial_value is not None: # Fallback to declaration initial value + elif var_info.initial_value is not None: # Fallback a valor de declaración var_info.current_value = var_info.initial_value - # If this member itself has children (it's a parsed STRUCT or an expanded UDT that contained STRUCTs), - # recurse into them. - if var_info.children and not var_info.is_udt_expanded_member: # Recurse for normal structs + # Aplicar recursivamente para hijos de STRUCTs (no UDTs expandidos directamente aquí) + if var_info.children and not var_info.is_udt_expanded_member: # Es un STRUCT definido aquí self._apply_current_values(var_info.children, begin_values, f"{full_member_path}.") - # For expanded UDT members (is_udt_expanded_member = True), their values are set directly, - # and if THEY had children (structs within the UDT def), those are part of the UDT expansion. - # The BEGIN block paths would typically be like "MyUdtInstance.StructInUdt.Member". - # This simplified _apply_current_values might need enhancement for complex paths into UDTs. + # Para miembros expandidos de UDT (is_udt_expanded_member = True), + # sus current_values se establecen si su ruta completa (ej. "MyUdtInstance._Member") está en begin_values. + # Esto se maneja en la iteración principal si los hijos UDT son procesados. + # La llamada recursiva anterior para STRUCTs anidados maneja "MyStruct.MyUdtInstance._Member". + + # Si var_info es una instancia de UDT (tiene udt_source_name y children son sus miembros expandidos) + 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: try: - with open(filepath, 'r', encoding='utf-8-sig') as f: - lines = f.readlines() + with open(filepath, 'r', encoding='utf-8-sig') as f: lines = f.readlines() except Exception as e: - print(f"Error reading file {filepath}: {e}") - return self.parsed_data + print(f"Error al leer el archivo {filepath}: {e}"); return self.parsed_data current_block_handler: Optional[Union[UdtInfo, DbInfo]] = None active_block_context = OffsetContext() idx = 0 while idx < len(lines): - original_line = lines[idx].strip() + original_line_with_space = lines[idx] + original_line = original_line_with_space.strip() + line_to_parse = original_line - comment_marker_idx = original_line.find("//") - if comment_marker_idx != -1: - line_to_parse = original_line[:comment_marker_idx].strip() - # Top-level comments usually not stored with block definition + comment_marker = original_line.find("//") + if comment_marker != -1: + line_to_parse = original_line[:comment_marker].strip() type_match = self.type_start_regex.match(line_to_parse) db_match = self.db_start_regex.match(line_to_parse) if type_match: - if current_block_handler: print(f"Warning: Starting new TYPE block for '{type_match.group(1)}' before previous block '{current_block_handler.name}' ended.") udt_name = type_match.group(1) current_block_handler = UdtInfo(name=udt_name) self.parsed_data.udts.append(current_block_handler) active_block_context = OffsetContext() - idx += 1; continue + idx +=1; continue elif db_match: - if current_block_handler: print(f"Warning: Starting new DATA_BLOCK for '{db_match.group(1)}' before previous block '{current_block_handler.name}' ended.") db_name = db_match.group(1) current_block_handler = DbInfo(name=db_name) self.parsed_data.dbs.append(current_block_handler) active_block_context = OffsetContext() - idx += 1; continue + idx +=1; continue if not current_block_handler: - idx += 1; continue + idx +=1; continue - # Inside a UDT or DB block definition part (before BEGIN for DBs) - prop_match = self.property_regex.match(original_line) # Properties can have comments - struct_keyword_on_line = self.struct_start_regex.match(line_to_parse) # Check for "STRUCT" keyword line + prop_match = self.property_regex.match(original_line) # Las propiedades pueden tener comentarios + struct_keyword_match = self.struct_start_regex.match(line_to_parse) if prop_match: key, value = prop_match.group(1).upper(), prop_match.group(2).strip() - attr_name = key.lower() - if hasattr(current_block_handler, attr_name): - setattr(current_block_handler, attr_name, value) + attr = key.lower() + if hasattr(current_block_handler, attr): setattr(current_block_handler, attr, value) - elif struct_keyword_on_line and not current_block_handler.members: # Start of main STRUCT for UDT/DB - # The line 'STRUCT' itself is consumed. Parsing of members starts from the next line. - idx = self._parse_struct_members( - lines, idx + 1, # Start from line AFTER "STRUCT" - current_block_handler.members, - active_block_context, - is_top_level_struct_in_block=True - ) - # idx is now the line number of BEGIN, END_TYPE, or END_DB - continue # Let the main loop handle this new line index + elif struct_keyword_match and not current_block_handler.members: # Inicio del STRUCT principal del bloque + idx = self._parse_struct_members(lines, idx + 1, current_block_handler.members, + active_block_context, is_top_level_struct_in_block=True) + continue elif self.begin_regex.match(line_to_parse) and isinstance(current_block_handler, DbInfo): - # Finalize size from declaration part - current_block_handler.total_size_in_bytes = active_block_context.byte_offset - idx = self._parse_begin_block(lines, idx + 1, current_block_handler) # idx + 1 to start after BEGIN - # idx is now the line of END_DATA_BLOCK + # El tamaño total de la sección de declaración se finaliza aquí + current_block_handler.total_size_in_bytes = active_block_context.byte_offset + idx = self._parse_begin_block(lines, idx + 1, current_block_handler) continue elif self.end_type_regex.match(line_to_parse) and isinstance(current_block_handler, UdtInfo): - if not hasattr(current_block_handler, 'total_size_in_bytes') or current_block_handler.total_size_in_bytes == 0: - current_block_handler.total_size_in_bytes = active_block_context.byte_offset # Size from declarations + # Si total_size_in_bytes no fue establecido (ej. UDT vacío o error), calcularlo aquí + 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} bytes. Members: {len(current_block_handler.members)}") current_block_handler = None elif self.end_db_regex.match(line_to_parse) and isinstance(current_block_handler, DbInfo): - if not hasattr(current_block_handler, 'total_size_in_bytes') or current_block_handler.total_size_in_bytes == 0: # If no BEGIN block, size is from declarations + if current_block_handler.total_size_in_bytes == 0 : # Si no hubo sección BEGIN current_block_handler.total_size_in_bytes = active_block_context.byte_offset + # Aplicar valores del bloque BEGIN a los current_value de los miembros 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} bytes. Members: {len(current_block_handler.members)}") current_block_handler = None @@ -491,43 +452,48 @@ class S7Parser: return self.parsed_data +# --- Serializador JSON Personalizado --- def custom_json_serializer(obj: Any) -> Any: - if isinstance(obj, OffsetContext): return None # Don't serialize OffsetContext + if isinstance(obj, OffsetContext): return None # No serializar OffsetContext if hasattr(obj, '__dict__'): - # Filter out None values, empty lists, and specific private fields d = {k: v for k, v in obj.__dict__.items() if v is not None and \ - not (isinstance(v, list) and not v) and \ - k != '_initial_values_from_begin_block'} + not (isinstance(v, list) and not v)} # Ya no filtra _initial_values_from_begin_block - # Ensure 'is_udt_expanded_member' is present even if False (unless explicitly None) if isinstance(obj, VariableInfo): + # Asegurar que 'is_udt_expanded_member' esté presente si es False if 'is_udt_expanded_member' not in d and obj.is_udt_expanded_member is False: d['is_udt_expanded_member'] = False - # If it was True, it would already be in d or caught by the general v is not None - elif obj.is_udt_expanded_member is True: + elif obj.is_udt_expanded_member is True: # Asegurar que se incluya si es True d['is_udt_expanded_member'] = True - + + # Incluir current_element_values si existe y no está vacío + if hasattr(obj, 'current_element_values') and obj.current_element_values: + d['current_element_values'] = obj.current_element_values + # Eliminarlo si está presente pero vacío (ya que el default es None) + elif hasattr(obj, 'current_element_values') and 'current_element_values' in d and not d['current_element_values']: + del d['current_element_values'] return d raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable") +# --- Bloque Principal --- if __name__ == "__main__": parser = S7Parser() - # IMPORTANT: Ensure this filepath points to your actual source file. - # The filename was changed to .txt for upload, adjust if your local file is .db - filepath = "db1001_format.db.txt" # Or "db1001_format.db" if that's the actual name + # Asegúrate que este path es el correcto para tu archivo .db o .db.txt + filepath = "db1001_format.db.txt" - print(f"Attempting to parse: {filepath}") + print(f"Intentando parsear el archivo: {filepath}") parsed_result = parser.parse_file(filepath) - json_output_filename = "parsed_s7_data_expanded.json" # New output filename - print(f"\nParsing complete. Attempting to serialize to JSON.") + # Nuevo nombre para el archivo JSON de salida para esta versión + json_output_filename = "parsed_s7_data.json" + print(f"\nParseo completo. Intentando serializar a JSON.") try: json_output = json.dumps(parsed_result, default=custom_json_serializer, indent=2) - # print(json_output) # Optional: print to console for quick check + # print(json_output) # Descomentar para ver el JSON en la consola with open(json_output_filename, "w", encoding='utf-8') as f: f.write(json_output) - print(f"Result saved to {json_output_filename}") + print(f"Resultado guardado en: {json_output_filename}") except Exception as e: - print(f"Error during JSON serialization or file writing: {e}") \ No newline at end of file + print(f"Error durante la serialización JSON o escritura de archivo: {e}") \ No newline at end of file diff --git a/backend/script_groups/S7_DB_Utils/x4.py b/backend/script_groups/S7_DB_Utils/x4.py index 8e27a2e..ec2c7e4 100644 --- a/backend/script_groups/S7_DB_Utils/x4.py +++ b/backend/script_groups/S7_DB_Utils/x4.py @@ -1,10 +1,6 @@ import json from typing import List, Dict, Any, Union -# Se asume que las dataclasses (VariableInfo, UdtInfo, etc.) del script x3.py -# estarían disponibles si este script fuera parte de un paquete más grande. -# Para este script independiente, trabajaremos directamente con los diccionarios del JSON. - def format_data_type_for_source(var_info: Dict[str, Any]) -> str: """Formatea la declaración de tipo completa para la variable en S7 source.""" base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"] @@ -29,7 +25,6 @@ def generate_variable_declaration_for_source(var_info: Dict[str, Any], indent_le line = f'{indent_str}{var_info["name"]} : {type_declaration_str}' if var_info.get("initial_value") is not None: - # Asegurarse de que los booleanos se escriban como TRUE/FALSE initial_val = var_info["initial_value"] if isinstance(initial_val, bool): initial_val_str = "TRUE" if initial_val else "FALSE" @@ -37,7 +32,10 @@ def generate_variable_declaration_for_source(var_info: Dict[str, Any], indent_le initial_val_str = str(initial_val) line += f' := {initial_val_str}' - line += ';' + # No añadir ; si es una declaración de STRUCT que va a tener un bloque de miembros. + # El ; irá después de su END_STRUCT. + if not (var_info["data_type"].upper() == "STRUCT" and var_info.get("children") and not var_info.get("udt_source_name")): + line += ';' if var_info.get("comment"): line += f'\t// {var_info["comment"]}' @@ -48,213 +46,200 @@ def generate_struct_members_for_source(members: List[Dict[str, Any]], indent_lev """Genera recursivamente las declaraciones de miembros para STRUCTs/UDTs.""" lines = [] for var_info in members: - # No expandir UDTs anidados dentro de la sección de declaración de otro UDT o DB. - # Solo declarar la variable del tipo UDT. - # La expansión de miembros de UDT solo ocurre en el JSON para análisis, no para la reconstrucción de la fuente. - if var_info.get("is_udt_expanded_member"): # Estos no se declaran individualmente en el padre. + if var_info.get("is_udt_expanded_member"): # No declarar individualmente miembros expandidos de UDT en el padre continue - if var_info["data_type"].upper() == "STRUCT" and not var_info.get("udt_source_name"): # Es una definición de STRUCT anidada + # Si es una definición de STRUCT anidada (no una instancia de UDT) + if var_info["data_type"].upper() == "STRUCT" and 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;') - if var_info.get("children"): - lines.extend(generate_struct_members_for_source(var_info["children"], indent_level + 1)) - lines.append(f'{current_indent_str}END_STRUCT;') + # La declaración "Miembro : STRUCT" no lleva ; si es multilínea. + 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;') # ; después de END_STRUCT else: # Variable primitiva, String, Array, o instancia de UDT lines.append(generate_variable_declaration_for_source(var_info, indent_level)) return lines -def _generate_assignments_recursive(members: List[Dict[str, Any]], path_prefix: str, indent_str: str) -> List[str]: - """Ayudante recursivo para generar asignaciones del bloque BEGIN.""" +def _generate_assignments_recursive_from_current_values(members: List[Dict[str, Any]], path_prefix: str, indent_str: str) -> List[str]: + """ + Fallback recursivo para generar asignaciones del bloque BEGIN usando current_value y current_element_values. + Este se usa si _initial_values_from_begin_block no está en el JSON. + """ assignment_lines = [] for var_info in members: - # Construir la ruta actual para esta variable current_member_name = var_info['name'] current_full_path = f"{path_prefix}{current_member_name}" - # Si es una instancia de UDT, sus 'children' en el JSON son los miembros expandidos. - # Necesitamos iterar sobre estos 'children' para obtener sus 'current_value'. - if var_info.get("udt_source_name") and var_info.get("children"): - # Para la instancia de UDT, recursivamente generar asignaciones para sus miembros. - # El prefijo de ruta para los miembros del UDT será el nombre de la instancia UDT seguido de un punto. - assignment_lines.extend( - _generate_assignments_recursive(var_info["children"], f"{current_full_path}.", indent_str) - ) - # Si es un STRUCT definido inline (no una instancia de UDT) - elif var_info["data_type"].upper() == "STRUCT" and not var_info.get("udt_source_name") and var_info.get("children"): - assignment_lines.extend( - _generate_assignments_recursive(var_info["children"], f"{current_full_path}.", indent_str) - ) - # Si es un miembro primitivo (o array/string que tiene un current_value directo) - # y tiene un 'current_value'. Los miembros expandidos de UDT (is_udt_expanded_member=True) - # tendrán su current_value y su current_full_path ya incluirá el nombre de la instancia UDT. + if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict): + # Ordenar por índice para consistencia si los índices son numéricos simples + # La clave 'index_str' podría ser "1" o "1,0" etc. + try: + # Intenta convertir la clave a una tupla de enteros para un ordenamiento numérico robusto + sorted_indices = sorted( + var_info["current_element_values"].keys(), + key=lambda k: tuple(map(int, k.split(','))) + ) + except ValueError: # Si las claves no son puramente numéricas/separadas por comas + sorted_indices = sorted(var_info["current_element_values"].keys()) + + for index_str in sorted_indices: + val_str_el = var_info["current_element_values"][index_str] + el_path = f"{current_full_path}[{index_str}]" + + f_val_str_el = str(val_str_el) + if f_val_str_el.lower() == "true": f_val_str_el = "TRUE" + elif f_val_str_el.lower() == "false": f_val_str_el = "FALSE" + assignment_lines.append(f"{indent_str}{el_path} := {f_val_str_el};") + + elif var_info.get("udt_source_name") and var_info.get("children"): + assignment_lines.extend(_generate_assignments_recursive_from_current_values(var_info["children"], f"{current_full_path}.", indent_str)) + elif var_info.get("data_type", "").upper() == "STRUCT" and not var_info.get("udt_source_name") and var_info.get("children"): + assignment_lines.extend(_generate_assignments_recursive_from_current_values(var_info["children"], f"{current_full_path}.", indent_str)) elif var_info.get("current_value") is not None: - val_str = var_info["current_value"] - if isinstance(val_str, bool): # Convertir booleanos de JSON a TRUE/FALSE de S7 - val_str = "TRUE" if val_str else "FALSE" - - assignment_lines.append(f"{indent_str}{current_full_path} := {val_str};") - + val_str_cv = var_info["current_value"] + f_val_str_cv = str(val_str_cv) + if f_val_str_cv.lower() == "true": f_val_str_cv = "TRUE" + elif f_val_str_cv.lower() == "false": f_val_str_cv = "FALSE" + assignment_lines.append(f"{indent_str}{current_full_path} := {f_val_str_cv};") return assignment_lines -# En x4.py -def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int, parsed_json_udts: Dict[str, Dict[str, Any]]) -> List[str]: + +def generate_begin_block_assignments(db_info: Dict[str, Any], indent_level: int) -> List[str]: + """Genera las líneas de asignación para el bloque BEGIN de un DB.""" indent_str = " " * indent_level lines = [] - - # Utilizar directamente _initial_values_from_begin_block del JSON - # ¡ASEGÚRATE DE QUE x3.py INCLUYA ESTE CAMPO EN EL JSON! + + # Prioridad: Usar _initial_values_from_begin_block si está presente en el JSON + # ¡ASEGÚRATE DE QUE x3.py INCLUYA ESTE CAMPO EN EL JSON (modificando custom_json_serializer en x3.py)! begin_values_map = db_info.get("_initial_values_from_begin_block") if begin_values_map and isinstance(begin_values_map, dict): - # Intentar un ordenamiento simple por clave para una salida más consistente, - # aunque el orden original del bloque BEGIN no se garantiza. - for path, value_str in sorted(begin_values_map.items()): - # Aquí, value_str ya es una cadena. Si necesitáramos convertir booleanos - # necesitaríamos información del tipo del 'path', lo cual es complejo aquí. - # Asumimos que x3.py guardó los valores en el formato correcto (ej. TRUE/FALSE para bools). - # Si x3.py guardó Python bools (true/false), necesitamos convertir. - # Para ser seguro, si el valor es "true" o "false" (strings), convertir. - - # Esta conversión es una heurística. Sería mejor si x3.py ya los formateara. - final_value_str = str(value_str) # Asegurar que es string - if final_value_str.lower() == "true": - final_value_str = "TRUE" - elif final_value_str.lower() == "false": - final_value_str = "FALSE" - - lines.append(f"{indent_str}{path} := {final_value_str};") + print(f"INFO: Usando _initial_values_from_begin_block para DB '{db_info['name']}' en el bloque BEGIN.") + # Ordenar por clave para una salida más consistente. + # El orden original del bloque BEGIN en el archivo fuente es difícil de replicar sin información adicional. + for path, value_obj in sorted(begin_values_map.items()): + value_str = str(value_obj) # Asegurar que el valor es una cadena + # S7 convierte true/false de JSON a TRUE/FALSE en el fuente. + if value_str.lower() == "true": + value_str = "TRUE" + elif value_str.lower() == "false": + value_str = "FALSE" + # Otros tipos como B#16#XX, W#16#YYYY, L#... etc., deberían estar ya como strings. + lines.append(f"{indent_str}{path} := {value_str};") else: - # Fallback si _initial_values_from_begin_block no está o está mal formado. - # Este fallback ahora necesita ser más inteligente o se eliminará si el principal funciona. - # print(f"Advertencia: _initial_values_from_begin_block no encontrado o vacío para DB {db_info['name']}. Reconstrucción de BEGIN puede ser incompleta.") - # La función _generate_assignments_recursive anterior podría ser un fallback, - # pero depende de que los `current_value` de los elementos de array estén bien poblados. - # Si se implementa el `current_element_values` en `VariableInfo` en x3.py: - def generate_recursive_fallback(members, prefix, current_indent): - fallback_lines = [] - for v_info in members: - m_name = v_info['name'] - m_path = f"{prefix}{m_name}" - if v_info.get("current_element_values") and isinstance(v_info["current_element_values"], dict): - for index_str, val_str_el in sorted(v_info["current_element_values"].items()): - # index_str puede ser "1" o "1,2" etc. - el_path = f"{m_path}[{index_str}]" - - f_val_str_el = str(val_str_el) - if f_val_str_el.lower() == "true": f_val_str_el = "TRUE" - elif f_val_str_el.lower() == "false": f_val_str_el = "FALSE" - fallback_lines.append(f"{current_indent}{el_path} := {f_val_str_el};") - - elif v_info.get("udt_source_name") and v_info.get("children"): - fallback_lines.extend(generate_recursive_fallback(v_info["children"], f"{m_path}.", current_indent)) - elif v_info.get("data_type", "").upper() == "STRUCT" and not v_info.get("udt_source_name") and v_info.get("children"): - fallback_lines.extend(generate_recursive_fallback(v_info["children"], f"{m_path}.", current_indent)) - elif v_info.get("current_value") is not None: - val_str_cv = v_info["current_value"] - f_val_str_cv = str(val_str_cv) - if f_val_str_cv.lower() == "true": f_val_str_cv = "TRUE" - elif f_val_str_cv.lower() == "false": f_val_str_cv = "FALSE" - fallback_lines.append(f"{current_indent}{m_path} := {f_val_str_cv};") - return fallback_lines - - lines.extend(generate_recursive_fallback(db_info.get("members", []), "", indent_str)) - + # Fallback: si _initial_values_from_begin_block no está, intentar reconstruir desde current_value/current_element_values + # Esto depende de que x3.py haya poblado bien estos campos. + print(f"ADVERTENCIA: _initial_values_from_begin_block no encontrado o vacío para DB '{db_info['name']}'. " + "Intentando reconstruir el bloque BEGIN desde current_value/current_element_values.") + lines.extend(_generate_assignments_recursive_from_current_values(db_info.get("members", []), "", indent_str)) + return lines def generate_s7_source_code_lines(data: Dict[str, Any]) -> List[str]: """Genera el código fuente S7 completo (UDTs y DBs) a partir de los datos JSON.""" lines = [] + + parsed_json_udts_lookup = {udt['name']: udt for udt in data.get("udts", [])} # Necesario para algunas lógicas internas # Generar UDTs for udt in data.get("udts", []): lines.append(f'TYPE "{udt["name"]}"') if udt.get("family"): lines.append(f' FAMILY : {udt["family"]};') if udt.get("version"): lines.append(f' VERSION : {udt["version"]};') - lines.append("") # Línea en blanco - lines.append(" STRUCT") - # Los miembros del UDT están directamente bajo 'members' - lines.extend(generate_struct_members_for_source(udt["members"], 2)) # Indentación 2 para miembros + lines.append("") + lines.append(" STRUCT") # El keyword STRUCT en sí no lleva ; + lines.extend(generate_struct_members_for_source(udt["members"], 2)) lines.append(" END_STRUCT;") lines.append(f'END_TYPE;') - lines.append("") # Línea en blanco después de cada UDT + lines.append("") # Generar DBs for db in data.get("dbs", []): lines.append(f'DATA_BLOCK "{db["name"]}"') - if db.get("title"): lines.append(f' TITLE = {db["title"]};') # Asumir que el título ya tiene el formato correcto + + # Reconstruir TITLE si existe en el JSON + if db.get("title"): + title_str = db["title"] + # El formato original es TITLE = y luego el valor, usualmente una estructura { S7_language... } + # No se añade punto y coma a esta línea específica. + lines.append(f' TITLE = {title_str}') + if db.get("family"): lines.append(f' FAMILY : {db["family"]};') if db.get("version"): lines.append(f' VERSION : {db["version"]};') - lines.append("") # Línea en blanco - lines.append(" STRUCT") - lines.extend(generate_struct_members_for_source(db["members"], 2)) # Indentación 2 para miembros + lines.append("") + lines.append(" STRUCT") # El keyword STRUCT en sí no lleva ; + lines.extend(generate_struct_members_for_source(db["members"], 2)) lines.append(" END_STRUCT;") - # Generar bloque BEGIN si hay valores actuales (implicando que hubo un bloque BEGIN) - # La forma más fiable es chequear si hay current_values en los miembros. - # O, si el parser x3.py guardara una bandera explícita "has_begin_block". - # Por ahora, generaremos BEGIN si hay miembros, ya que las asignaciones se basan en current_value. - if db.get("members"): # Asumimos que si hay miembros, puede haber un bloque BEGIN. + begin_assignments = generate_begin_block_assignments(db, 1) # Pasar parsed_json_udts_lookup si es necesario en el futuro + if begin_assignments: lines.append("BEGIN") - lines.extend(generate_begin_block_assignments(db, 1)) # Indentación 1 para asignaciones + lines.extend(begin_assignments) lines.append(f'END_DATA_BLOCK;') - lines.append("") # Línea en blanco después de cada DB + lines.append("") return lines +# --- generate_markdown_table (sin cambios significativos respecto a la v3, pero puedes revisarla) --- def generate_markdown_table(db_info: Dict[str, Any]) -> List[str]: - """Genera una tabla Markdown para la documentación de un DB.""" 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("|---|---|---|---|---|---|") - def flatten_members_for_markdown(members: List[Dict[str, Any]], prefix: str = "", base_offset: float = 0.0): - md_lines = [] - for var in members: - if var.get("is_udt_expanded_member"): # No listar miembros expandidos como filas separadas de alto nivel aquí - continue + processed_expanded_members = set() - name = f"{prefix}{var['name']}" + 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']}" - # El offset en el JSON ya debería ser absoluto para los miembros del DB. - # Para miembros dentro de STRUCTs anidados, el JSON también debería tener offsets absolutos. 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 # El formato X.Y ya está bien para bools - elif var.get("bit_size", 0) > 0 : # bool en X.0 + pass + elif var.get("bit_size", 0) > 0 : address = f"{int(var['byte_offset'])}.0" - - data_type_str = format_data_type_for_source(var) # Usar la misma función de formato + 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", "")) - # Reemplazar pipes en los valores para no romper la tabla Markdown initial_value = initial_value.replace("|", "\\|") actual_value = actual_value.replace("|", "\\|") comment = 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} |") - md_lines.append(f"| {address} | {name} | {data_type_str} | {initial_value} | {actual_value} | {comment} |") - - # Si es un STRUCT (no UDT) o un UDT, listar sus miembros constitutivos recursivamente para la documentación if var.get("children"): - # Si es una instancia de UDT, los hijos son los miembros expandidos. - # Si es un STRUCT, los hijos son los miembros directos del STRUCT. - # El prefijo para los hijos debe ser el nombre completo del padre (STRUCT/UDT). - md_lines.extend(flatten_members_for_markdown(var["children"], f"{name}.", var['byte_offset'])) + 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(): - json_input_filename = "parsed_s7_data_expanded.json" - s7_output_filename = "reconstructed_s7_source_v2.txt" # Nuevo nombre para la salida S7 + # Asegúrate que este es el JSON generado por la última versión de x3.py + # (la que incluye _initial_values_from_begin_block y current_element_values) + json_input_filename = "parsed_s7_data.json" + s7_output_filename = "reconstructed_s7_source.txt" # Nueva versión de salida try: with open(json_input_filename, 'r', encoding='utf-8') as f: @@ -271,8 +256,8 @@ def main(): print(f"Archivo JSON '{json_input_filename}' cargado correctamente.") - # 1. Generar el archivo S7 reconstruido - s7_code_lines = generate_s7_source_code_lines(data_from_json) + # CORRECCIÓN DEL ERROR: Pasar el argumento faltante + s7_code_lines = generate_s7_source_code_lines(data_from_json) # parsed_json_udts se crea dentro ahora try: with open(s7_output_filename, 'w', encoding='utf-8') as f: for line in s7_code_lines: @@ -281,11 +266,10 @@ def main(): except Exception as e: print(f"Error al escribir el archivo S7 {s7_output_filename}: {e}") - # 2. Generar la documentación Markdown (para cada DB encontrado) if data_from_json.get("dbs"): for db_to_document in data_from_json["dbs"]: - db_name_safe = db_to_document['name'].replace('"', '').replace(' ', '_') - md_filename_specific = f"documentation_db_{db_name_safe}.md" + db_name_safe = db_to_document['name'].replace('"', '').replace(' ', '_').replace('/','_') + md_filename_specific = f"documentation_db_{db_name_safe}.md" # Nuevo nombre print(f"\nGenerando documentación Markdown para DB: {db_to_document['name']}...") markdown_lines = generate_markdown_table(db_to_document)