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.
This commit is contained in:
Miguel 2025-05-17 14:31:37 +02:00
parent a82ef44fb8
commit de5134920d
5 changed files with 956 additions and 609 deletions

View File

@ -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 |

View File

@ -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"
}
}
]
}

View File

@ -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;

View File

@ -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<name>[a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*'
r'(?P<typefull>'
r'(?:ARRAY\s*\[(?P<arraydims>[^\]]+?)\]\s*OF\s*)?'
r'(?P<basetype>(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))' # UDTs in quotes, primitives without
r'(?:\s*\[\s*(?P<stringlength>\d+)\s*\])?' # Optional string length
r'(?P<basetype>(?:"[^"]+"|[a-zA-Z_][a-zA-Z0-9_]*))'
r'(?:\s*\[\s*(?P<stringlength>\d+)\s*\])?'
r')'
r'(?:\s*:=\s*(?P<initval>[^;]*?))??\s*' # Initial value: non-greedy, does not cross a semicolon
r';?\s*$', # Optional semicolon at the end of the declaration
r'(?:\s*:=\s*(?P<initval>[^;]*?))??\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<indices>[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<path>[a-zA-Z0-9_."\[\],\s]+?)\s*:=\s*(?P<value>.+?)\s*;?\s*$', re.IGNORECASE)
assignment_regex = re.compile(r'^\s*(?P<path>.+?)\s*:=\s*(?P<value>.+?)\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}")
print(f"Error durante la serialización JSON o escritura de archivo: {e}")

View File

@ -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)