Usando Sympy para simplificar los parentesis en las expresiones logicas

This commit is contained in:
Miguel 2025-04-20 01:32:05 +02:00
parent 98dbc7a5d7
commit 9b192a91fd
30 changed files with 3563 additions and 3345 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,8 @@ FUNCTION_BLOCK "BlenderCtrl__Main"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
VAR_RETURN
Ret_Val : Void;
END_VAR
VAR_TEMP
@ -43,13 +38,12 @@ BEGIN
// Network 4: Emergency Pressed (Original Language: LAD)
// Logic moved to Coil 26
"gEmergencyPressed" := NOT "gIN_VoltageOk" AND "M19000";
"M19000" := "gIN_VoltageOk"; // N_TRIG("gIN_VoltageOk")
"gEmergencyPressed" := "M19000" AND NOT "gIN_VoltageOk";
"M19000" := "gIN_VoltageOk"; // N_TRIG("gIN_VoltageOk") - Mem: "M19000"
// Network 5: Air and CO2 pressure ok and auxiliary ok (Original Language: LAD)
"gBlenderSuppliesOk" := (("gIN_LinePressCO2Ok" OR ("gWorkshopTest" AND (NOT "gWorkshop_Co2_Presence")) AND (NOT "gWorkshop_CIP_Signals")) AND "HMI_Digital"."_PAL_S11"."Filtered") OR ("gIN_LinePressCO2Ok" OR ("gWorkshopTest" AND (NOT "gWorkshop_Co2_Presence")) AND (NOT "gWorkshop_CIP_Signals")) AND (NOT "Disable_Bit") AND "gIN_VoltageOk";
"gBlenderSuppliesOk" := ("gIN_VoltageOk" AND "gIN_LinePressCO2Ok" AND "HMI_Digital"."_PAL_S11"."Filtered") OR ("gIN_VoltageOk" AND "gIN_LinePressCO2Ok" AND NOT "Disable_Bit") OR ("gIN_VoltageOk" AND "gWorkshopTest" AND "HMI_Digital"."_PAL_S11"."Filtered" AND NOT "gWorkshop_Co2_Presence" AND NOT "gWorkshop_CIP_Signals") OR ("gIN_VoltageOk" AND "gWorkshopTest" AND NOT "gWorkshop_Co2_Presence" AND NOT "gWorkshop_CIP_Signals" AND NOT "Disable_Bit");
// Network 6: Blender State Num (Original Language: LAD)
@ -57,7 +51,7 @@ BEGIN
// Network 7: Delay Power On (Original Language: LAD)
"mDelayPowerOnTmr"(IN := "FirstScan", PT := S5T#2S); // TODO: Declarar "mDelayPowerOnTmr" : TP; en VAR_STAT o VAR
"mDelayPowerOnTmr"(IN := "FirstScan", PT := S5T#2S); // TODO: Declarar "mDelayPowerOnTmr" : TP;
// Network 8: Production Mode (Original Language: LAD)
@ -65,56 +59,56 @@ BEGIN
// Network 9: CIp Mode (Original Language: LAD)
"gBlenderCIPMode" := (NOT "HMI_Variables_Status"."System"."Blender_Prod_CIP");
IF (NOT "HMI_Variables_Status"."System"."Blender_Prod_CIP") THEN
"gBlenderCIPMode" := NOT "HMI_Variables_Status"."System"."Blender_Prod_CIP";
IF NOT "HMI_Variables_Status"."System"."Blender_Prod_CIP" THEN
"HMI_Variables_Status"."Procedures"."BlenderStateNum" := 19;
END_IF;
// Network 10: Error Faults (Original Language: LAD)
IF (NOT "AUX FALSE") THEN
IF NOT "AUX FALSE" THEN
"HMI_Variables_Status"."Meters"."QTM3012_PRD_Fault" := FALSE;
END_IF;
IF (NOT "AUX FALSE") THEN
IF NOT "AUX FALSE" THEN
"gmPDS2000_Error_Fault" := FALSE;
END_IF;
IF (NOT "AUX FALSE") THEN
IF NOT "AUX FALSE" THEN
"HMI_Variables_Status"."Meters"."QTM3012_PRD_Run" := FALSE;
END_IF;
IF (NOT "AUX FALSE") THEN
IF NOT "AUX FALSE" THEN
"gNoFreezeProductMeter" := FALSE;
END_IF;
// Network 11: Filler Bottle Count Used to push Product (Original Language: LAD)
"System_RunOut_Variables"."ProdPipeRunOutFillerBott" := (NOT "System_RunOut_Variables"."ProdPipeRunOutWaterCount");
"System_RunOut_Variables"."ProdPipeRunOutFillerBott" := NOT "System_RunOut_Variables"."ProdPipeRunOutWaterCount";
// Network 12: Water Bypass Enable (Original Language: LAD)
"gStillWaterByPassEn" := (("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_StillWaterByPass" OR ("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_ByPassDeair" AND (NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_Deaireation"))) AND "Blender_Variables_Pers"."gWaterRecipe") AND (NOT "Blender_Variables_Pers"."gCarboStillRecipe");
"gStillWaterByPassEn" := ("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_StillWaterByPass" AND "Blender_Variables_Pers"."gWaterRecipe" AND NOT "Blender_Variables_Pers"."gCarboStillRecipe") OR ("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_ByPassDeair" AND "Blender_Variables_Pers"."gWaterRecipe" AND NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_Deaireation" AND NOT "Blender_Variables_Pers"."gCarboStillRecipe");
// Network 13: Still Water Bypass (Original Language: LAD)
"gBlendFiStillWaterByPass" := (("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_BlendFillSystem" AND "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_StillWaterByPass") AND "Blender_Variables_Pers"."gWaterRecipe") AND (NOT "Blender_Variables_Pers"."gCarboStillRecipe");
"gBlendFiStillWaterByPass" := "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_StillWaterByPass" AND "Blender_Variables_Pers"."gWaterRecipe" AND "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_BlendFillSystem" AND NOT "Blender_Variables_Pers"."gCarboStillRecipe";
// Network 14: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD)
"gHVP301_Open" := (("gSyrupRoomEn" AND (NOT "gIN_HVP301_Aux")) AND (NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_FastChangeOverEnabled") AND "Procedure_Variables"."FTP302Line_Preparation"."Done") AND (NOT "Procedure_Variables"."Syr_RunOut"."Done") OR (((("gSyrupRoomEn" AND (NOT "gIN_HVP301_Aux")) AND "gBlenderCIPMode") AND "gIN_CIP_CIPRunning") AND "Procedure_Variables"."Blender_Run"."Running");
"gHVP301_Open" := ("gSyrupRoomEn" AND "gBlenderCIPMode" AND "gIN_CIP_CIPRunning" AND "Procedure_Variables"."Blender_Run"."Running" AND NOT "gIN_HVP301_Aux") OR ("gSyrupRoomEn" AND "Procedure_Variables"."FTP302Line_Preparation"."Done" AND NOT "gIN_HVP301_Aux" AND NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_FastChangeOverEnabled" AND NOT "Procedure_Variables"."Syr_RunOut"."Done");
// Network 15: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD)
"mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON; en VAR_STAT o VAR
"mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON;
"gHVM302_Open" := "mHVM302_Dly".Q;
// Network 16: Maselli Control (Original Language: LAD)
IF "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType" = 6 THEN
IF Eq("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType", 6) THEN
Maselli_PA_Control();
END_IF;
// Network 17: mPDS Control (Original Language: LAD)
IF "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType" = 5 THEN
IF Eq("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType", 5) THEN
mPDS_PA_Control();
END_IF;
@ -129,7 +123,7 @@ BEGIN
// CALL "GetProdBrixCO2_FromAn"
// NOP 0
IF "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType" = 3 THEN
IF Eq("HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_MeterType", 3) THEN
GetProdBrixCO2_Anal_Inpt();
END_IF;
@ -157,24 +151,21 @@ BEGIN
// Network 25: Production ONS (Original Language: LAD)
// PBox Logic moved to consumer Coil
"gProductionONS" := ("gBlenderProdMode" AND NOT "M19001") AND (NOT "mDelayPowerOnTmr");
"gProductionONS" := "gBlenderProdMode" AND NOT "mDelayPowerOnTmr" AND NOT "M19001";
// Network 26: Blender Prod Mode Init (Original Language: LAD)
IF ("gProductionONS" OR "Procedure_Variables"."Blender_Rinse"."ONS_Done") AND (NOT "Blender_Variables_Pers"."gBlenderStarted") THEN
IF ("gProductionONS" AND NOT "Blender_Variables_Pers"."gBlenderStarted") OR ("Procedure_Variables"."Blender_Rinse"."ONS_Done" AND NOT "Blender_Variables_Pers"."gBlenderStarted") THEN
BlenderCtrl_ProdModeInit();
END_IF;
// Network 27: Rinse ONS (Original Language: LAD)
// PBox Logic moved to consumer Coil
"gRinseONS" := ("HMI_Variables_Status"."System"."Blender_Prod_CIP" AND NOT "M19002") AND (NOT "mDelayPowerOnTmr");
"gRinseONS" := "HMI_Variables_Status"."System"."Blender_Prod_CIP" AND NOT "mDelayPowerOnTmr" AND NOT "M19002";
// Network 28: CIP ONS (Original Language: LAD)
// PBox Logic moved to consumer Coil
"gCIPONS" := ("gBlenderCIPMode" AND NOT "M19003") AND (NOT "mDelayPowerOnTmr");
"gCIPONS" := "gBlenderCIPMode" AND NOT "mDelayPowerOnTmr" AND NOT "M19003";
// Network 29: CIp Mode Init (Original Language: LAD)
@ -228,7 +219,7 @@ BEGIN
// Network 41: Blend Procedure Data (Original Language: LAD)
IF (NOT "mDelayPowerOnTmr") THEN
IF NOT "mDelayPowerOnTmr" THEN
"Blender_Procedure Data"();
END_IF;
@ -268,11 +259,11 @@ BEGIN
// Network 50: ResetTotalizer (Original Language: LAD)
"mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP; en VAR_STAT o VAR
"mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP;
// Network 51: ResetWaterTot (Original Language: LAD)
"mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP; en VAR_STAT o VAR
"mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP;
"mResetWaterTot" := "mResetFTN301TotTmr".Q;
// Network 52: Water VFM Reset Totalizer (Original Language: LAD)
@ -283,8 +274,8 @@ BEGIN
// Network 53: ResetCO2Tot (Original Language: LAD)
"mResetFTP302TotTmr"(IN := "gFTP302_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP; en VAR_STAT o VAR
"mResetSyrupTot" := "mResetFTP302TotTmr".Q AND "gSyrupRoomEn";
"mResetFTP302TotTmr"(IN := "mResetTotalizerTmr" OR "gFTP302_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP;
"mResetSyrupTot" := "gSyrupRoomEn" AND "mResetFTP302TotTmr".Q;
// Network 54: Syrup MFM Reset Totalizer (Original Language: LAD)
@ -294,7 +285,7 @@ BEGIN
// Network 55: ResetProductTot (Original Language: LAD)
"mResetFTM303TotTmr"(IN := "gFTM303_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP; en VAR_STAT o VAR
"mResetFTM303TotTmr"(IN := "mResetTotalizerTmr" OR "gFTM303_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP;
"mResetCO2Tot" := "mResetFTM303TotTmr".Q;
// Network 56: CO2 MFM Reset Tot (Original Language: LAD)
@ -305,7 +296,7 @@ BEGIN
// Network 57: ResetCO2Tot (Original Language: LAD)
"mResetProductTotTmr"(IN := "gProductMFMResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP; en VAR_STAT o VAR
"mResetProductTotTmr"(IN := "mResetTotalizerTmr" OR "gProductMFMResetTot", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP;
"mResetProductTot" := "mResetProductTotTmr".Q;
// Network 58: Reset Totalizer (Original Language: LAD)
@ -322,7 +313,7 @@ BEGIN
// Network 60: Blender Ctrl Command (Original Language: LAD)
IF (NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_Simulation") THEN
IF NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_Simulation" THEN
BlenderCtrl_MFM_Command();
END_IF;
@ -340,8 +331,7 @@ BEGIN
// Network 64: All Auto (Original Language: LAD)
// NBox Logic moved to consumer Coil
IF NOT ("HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Command" AND "HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Enable") AND "M19011" THEN
IF ("M19011" AND NOT "HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Command") OR ("M19011" AND NOT "HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Enable") THEN
BlenderCtrl_All_Auto();
END_IF;
"HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Light" := "HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Command" AND "HMI_Variables_Cmd"."Commands_From_HMI"."F7_DeviceControl"."Enable";
@ -352,10 +342,10 @@ BEGIN
// Network 66: Mod Copy Recipe (Original Language: LAD)
"mAux_FP_M700_1" := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND (NOT "mFP_Recip_Main_Page");
"mAux_FP_M700_1" := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND NOT "mFP_Recip_Main_Page";
"mFP_Recip_Main_Page" := "HMI_Variables_Cmd"."Recipe"."Main_Page";
"T_Pulse_Recipe_Edit"(IN := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND "HMI_Variables_Cmd"."Recipe"."Edit", PT := S5T#500ms); // TODO: Declarar "T_Pulse_Recipe_Edit" : TP; en VAR_STAT o VAR
IF "T_Pulse_Recipe_Edit".Q AND "T_Pulse_Recipe_Edit" THEN
"T_Pulse_Recipe_Edit"(IN := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND "HMI_Variables_Cmd"."Recipe"."Edit", PT := S5T#500ms); // TODO: Declarar "T_Pulse_Recipe_Edit" : TP;
IF "T_Pulse_Recipe_Edit" AND "T_Pulse_Recipe_Edit".Q THEN
"HMI_Variables_Cmd"."Recipe"."Edit" := FALSE;
END_IF;
IF "mAux_FP_M700_1" THEN

View File

@ -251,17 +251,17 @@
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "37",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "34",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "37",
"source_pin": "out"
}
},
"outputs": {
@ -326,18 +326,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mHVM302_Dly\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "25",
"source_pin": "out"
},
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mHVM302_Dly\""
},
"tv": {
"uid": "23",
"scope": "TypedConstant",
@ -414,18 +414,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetTotalizerTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "24",
"source_pin": "out"
},
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetTotalizerTmr\""
},
"tv": {
"uid": "23",
"scope": "TypedConstant",
@ -501,17 +501,17 @@
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
@ -525,18 +525,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTN301TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTN301TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -636,17 +636,17 @@
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "28",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "28",
"source_pin": "out"
}
},
"outputs": {
@ -660,18 +660,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTP302TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "29",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTP302TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -795,17 +795,17 @@
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
@ -819,18 +819,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTM303TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTM303TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -930,17 +930,17 @@
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
@ -954,18 +954,18 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetProductTotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetProductTotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -1157,12 +1157,6 @@
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "27",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"T_Pulse_Recipe_Edit\""
},
"tv": {
"uid": "28",
"scope": "TypedConstant",
@ -1170,6 +1164,12 @@
"datatype": "TypedConstant",
"value": "S5T#500ms"
},
"timer": {
"uid": "27",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"T_Pulse_Recipe_Edit\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",

View File

@ -50,7 +50,7 @@
{
"instruction_uid": "30",
"uid": "30",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -67,12 +67,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gSyrupRoomEn\""
"scl": "// SymPy Contact: v0_"
},
{
"instruction_uid": "31",
"uid": "31",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {
"operand": true
@ -94,12 +94,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")"
"scl": "// SymPy Contact: v0_ & ~v1_"
},
{
"instruction_uid": "32",
"uid": "32",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {
"operand": true
@ -121,12 +121,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: (\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\")"
"scl": "// SymPy Contact: v0_ & ~v1_ & ~v2_"
},
{
"instruction_uid": "33",
"uid": "33",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -146,12 +146,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: (\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\") AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\""
"scl": "// SymPy Contact: v0_ & v3_ & ~v1_ & ~v2_"
},
{
"instruction_uid": "34",
"uid": "34",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {
"operand": true
@ -173,12 +173,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: ((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\") AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\") AND (NOT \"Procedure_Variables\".\"Syr_RunOut\".\"Done\")"
"scl": "// SymPy Contact: v0_ & v3_ & ~v1_ & ~v2_ & ~v4_"
},
{
"instruction_uid": "35",
"uid": "35",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -198,12 +198,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: (\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\""
"scl": "// SymPy Contact: v0_ & v5_ & ~v1_"
},
{
"instruction_uid": "36",
"uid": "36",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -223,12 +223,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: ((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\""
"scl": "// SymPy Contact: v0_ & v5_ & v6_ & ~v1_"
},
{
"instruction_uid": "37",
"uid": "37",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -248,39 +248,39 @@
"outputs": {
"out": []
},
"scl": "// RLO: (((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\") AND \"Procedure_Variables\".\"Blender_Run\".\"Running\""
"scl": "// SymPy Contact: v0_ & v5_ & v6_ & v7_ & ~v1_"
},
{
"instruction_uid": "38",
"uid": "38",
"type": "O_scl",
"type": "O_sympy_processed",
"template_values": {
"Card": "Cardinality"
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "37",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "34",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "37",
"source_pin": "out"
}
},
"outputs": {
"out": []
},
"scl": "// Logic O 38: ((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\") AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\") AND (NOT \"Procedure_Variables\".\"Syr_RunOut\".\"Done\") OR ((((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\") AND \"Procedure_Variables\".\"Blender_Run\".\"Running\")"
"scl": "// SymPy O: (v0_ & v5_ & v6_ & v7_ & ~v1_) | (v0_ & v3_ & ~v1_ & ~v2_ & ~v4_)"
},
{
"instruction_uid": "39",
"uid": "39",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -298,7 +298,7 @@
}
},
"outputs": {},
"scl": "\"gHVP301_Open\" := ((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\") AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\") AND (NOT \"Procedure_Variables\".\"Syr_RunOut\".\"Done\") OR ((((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\") AND \"Procedure_Variables\".\"Blender_Run\".\"Running\");"
"scl": "\"gHVP301_Open\" := (\"gSyrupRoomEn\" AND \"gBlenderCIPMode\" AND \"gIN_CIP_CIPRunning\" AND \"Procedure_Variables\".\"Blender_Run\".\"Running\" AND NOT \"gIN_HVP301_Aux\") OR (\"gSyrupRoomEn\" AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\" AND NOT \"gIN_HVP301_Aux\" AND NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\" AND NOT \"Procedure_Variables\".\"Syr_RunOut\".\"Done\");"
}
],
"language": "LAD"
@ -311,7 +311,7 @@
{
"instruction_uid": "25",
"uid": "25",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -328,27 +328,27 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gIN_HVM302_Aux\""
"scl": "// SymPy Contact: v8_"
},
{
"instruction_uid": "26",
"uid": "26",
"type": "Sd_scl",
"type": "Sd_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mHVM302_Dly\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "25",
"source_pin": "out"
},
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mHVM302_Dly\""
},
"tv": {
"uid": "23",
"scope": "TypedConstant",
@ -366,12 +366,12 @@
"outputs": {
"q": []
},
"scl": "\"mHVM302_Dly\"(IN := \"gIN_HVM302_Aux\", PT := S5T#1S); // TODO: Declarar \"mHVM302_Dly\" : TON; en VAR_STAT o VAR"
"scl": "\"mHVM302_Dly\"(IN := \"gIN_HVM302_Aux\", PT := S5T#1S); // TODO: Declarar \"mHVM302_Dly\" : TON;"
},
{
"instruction_uid": "27",
"uid": "27",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -402,7 +402,7 @@
{
"instruction_uid": "24",
"uid": "24",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -419,27 +419,27 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gBlendResetTotalizer\""
"scl": "// SymPy Contact: v9_"
},
{
"instruction_uid": "25",
"uid": "25",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetTotalizerTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "24",
"source_pin": "out"
},
"timer": {
"uid": "22",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetTotalizerTmr\""
},
"tv": {
"uid": "23",
"scope": "TypedConstant",
@ -455,7 +455,7 @@
}
},
"outputs": {},
"scl": "\"mResetTotalizerTmr\"(IN := \"gBlendResetTotalizer\", PT := S5T#2S); // TODO: Declarar \"mResetTotalizerTmr\" : TP; en VAR_STAT o VAR"
"scl": "\"mResetTotalizerTmr\"(IN := \"gBlendResetTotalizer\", PT := S5T#2S); // TODO: Declarar \"mResetTotalizerTmr\" : TP;"
}
],
"language": "LAD"
@ -468,7 +468,7 @@
{
"instruction_uid": "26",
"uid": "26",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -485,12 +485,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gFTN301_ResetTot\""
"scl": "// SymPy Contact: v10_"
},
{
"instruction_uid": "27",
"uid": "27",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -507,54 +507,54 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mResetTotalizerTmr\""
"scl": "// SymPy Contact: v11_"
},
{
"instruction_uid": "28",
"uid": "28",
"type": "O_scl",
"type": "O_sympy_processed",
"template_values": {
"Card": "Cardinality"
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
"out": []
},
"scl": "// Logic O 28: \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\""
"scl": "// SymPy O: v10_ | v11_"
},
{
"instruction_uid": "29",
"uid": "29",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTN301TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTN301TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -572,12 +572,12 @@
"outputs": {
"q": []
},
"scl": "\"mResetFTN301TotTmr\"(IN := \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTN301TotTmr\" : TP; en VAR_STAT o VAR"
"scl": "\"mResetFTN301TotTmr\"(IN := \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTN301TotTmr\" : TP;"
},
{
"instruction_uid": "30",
"uid": "30",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -608,7 +608,7 @@
{
"instruction_uid": "27",
"uid": "27",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -625,12 +625,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gFTP302_ResetTot\""
"scl": "// SymPy Contact: v12_"
},
{
"instruction_uid": "28",
"uid": "28",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -647,54 +647,54 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mResetTotalizerTmr\""
"scl": "// SymPy Contact: v11_"
},
{
"instruction_uid": "29",
"uid": "29",
"type": "O_scl",
"type": "O_sympy_processed",
"template_values": {
"Card": "Cardinality"
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "28",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "28",
"source_pin": "out"
}
},
"outputs": {
"out": []
},
"scl": "// Logic O 29: \"gFTP302_ResetTot\" OR \"mResetTotalizerTmr\""
"scl": "// SymPy O: v11_ | v12_"
},
{
"instruction_uid": "30",
"uid": "30",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTP302TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "29",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTP302TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -712,12 +712,12 @@
"outputs": {
"q": []
},
"scl": "\"mResetFTP302TotTmr\"(IN := \"gFTP302_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTP302TotTmr\" : TP; en VAR_STAT o VAR"
"scl": "\"mResetFTP302TotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gFTP302_ResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetFTP302TotTmr\" : TP;"
},
{
"instruction_uid": "31",
"uid": "31",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -737,12 +737,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mResetFTP302TotTmr\".Q AND \"gSyrupRoomEn\""
"scl": "// SymPy Contact: v0_ & v23_"
},
{
"instruction_uid": "32",
"uid": "32",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -760,7 +760,7 @@
}
},
"outputs": {},
"scl": "\"mResetSyrupTot\" := \"mResetFTP302TotTmr\".Q AND \"gSyrupRoomEn\";"
"scl": "\"mResetSyrupTot\" := \"gSyrupRoomEn\" AND \"mResetFTP302TotTmr\".Q;"
}
],
"language": "LAD"
@ -773,7 +773,7 @@
{
"instruction_uid": "26",
"uid": "26",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -790,12 +790,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gFTM303_ResetTot\""
"scl": "// SymPy Contact: v13_"
},
{
"instruction_uid": "27",
"uid": "27",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -812,54 +812,54 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mResetTotalizerTmr\""
"scl": "// SymPy Contact: v11_"
},
{
"instruction_uid": "28",
"uid": "28",
"type": "O_scl",
"type": "O_sympy_processed",
"template_values": {
"Card": "Cardinality"
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
"out": []
},
"scl": "// Logic O 28: \"gFTM303_ResetTot\" OR \"mResetTotalizerTmr\""
"scl": "// SymPy O: v11_ | v13_"
},
{
"instruction_uid": "29",
"uid": "29",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTM303TotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetFTM303TotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -877,12 +877,12 @@
"outputs": {
"q": []
},
"scl": "\"mResetFTM303TotTmr\"(IN := \"gFTM303_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTM303TotTmr\" : TP; en VAR_STAT o VAR"
"scl": "\"mResetFTM303TotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gFTM303_ResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetFTM303TotTmr\" : TP;"
},
{
"instruction_uid": "30",
"uid": "30",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -913,7 +913,7 @@
{
"instruction_uid": "26",
"uid": "26",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -930,12 +930,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"gProductMFMResetTot\""
"scl": "// SymPy Contact: v14_"
},
{
"instruction_uid": "27",
"uid": "27",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -952,54 +952,54 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mResetTotalizerTmr\""
"scl": "// SymPy Contact: v11_"
},
{
"instruction_uid": "28",
"uid": "28",
"type": "O_scl",
"type": "O_sympy_processed",
"template_values": {
"Card": "Cardinality"
},
"negated_pins": {},
"inputs": {
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
},
"in1": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "26",
"source_pin": "out"
},
"in2": {
"type": "connection",
"source_instruction_type": "Contact",
"source_instruction_uid": "27",
"source_pin": "out"
}
},
"outputs": {
"out": []
},
"scl": "// Logic O 28: \"gProductMFMResetTot\" OR \"mResetTotalizerTmr\""
"scl": "// SymPy O: v11_ | v14_"
},
{
"instruction_uid": "29",
"uid": "29",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetProductTotTmr\""
},
"s": {
"type": "connection",
"source_instruction_type": "O",
"source_instruction_uid": "28",
"source_pin": "out"
},
"timer": {
"uid": "23",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"mResetProductTotTmr\""
},
"tv": {
"uid": "24",
"scope": "TypedConstant",
@ -1017,12 +1017,12 @@
"outputs": {
"q": []
},
"scl": "\"mResetProductTotTmr\"(IN := \"gProductMFMResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetProductTotTmr\" : TP; en VAR_STAT o VAR"
"scl": "\"mResetProductTotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gProductMFMResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetProductTotTmr\" : TP;"
},
{
"instruction_uid": "30",
"uid": "30",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1053,7 +1053,7 @@
{
"instruction_uid": "33",
"uid": "33",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1070,12 +1070,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\""
"scl": "// SymPy Contact: v15_"
},
{
"instruction_uid": "34",
"uid": "34",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {
"operand": true
@ -1097,12 +1097,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND (NOT \"mFP_Recip_Main_Page\")"
"scl": "// SymPy Contact: v15_ & ~v16_"
},
{
"instruction_uid": "35",
"uid": "35",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1120,12 +1120,12 @@
}
},
"outputs": {},
"scl": "\"mAux_FP_M700_1\" := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND (NOT \"mFP_Recip_Main_Page\");"
"scl": "\"mAux_FP_M700_1\" := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND NOT \"mFP_Recip_Main_Page\";"
},
{
"instruction_uid": "36",
"uid": "36",
"type": "Coil_scl",
"type": "Coil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1148,7 +1148,7 @@
{
"instruction_uid": "37",
"uid": "37",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1165,12 +1165,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\""
"scl": "// SymPy Contact: v15_"
},
{
"instruction_uid": "38",
"uid": "38",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1190,21 +1190,15 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\""
"scl": "// SymPy Contact: v15_ & v17_"
},
{
"instruction_uid": "39",
"uid": "39",
"type": "Se_scl",
"type": "Se_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
"timer": {
"uid": "27",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"T_Pulse_Recipe_Edit\""
},
"tv": {
"uid": "28",
"scope": "TypedConstant",
@ -1212,6 +1206,12 @@
"datatype": "TypedConstant",
"value": "S5T#500ms"
},
"timer": {
"uid": "27",
"scope": "GlobalVariable",
"type": "variable",
"name": "\"T_Pulse_Recipe_Edit\""
},
"s": {
"type": "connection",
"source_instruction_type": "Contact",
@ -1228,12 +1228,12 @@
"outputs": {
"q": []
},
"scl": "\"T_Pulse_Recipe_Edit\"(IN := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\", PT := S5T#500ms); // TODO: Declarar \"T_Pulse_Recipe_Edit\" : TP; en VAR_STAT o VAR"
"scl": "\"T_Pulse_Recipe_Edit\"(IN := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\", PT := S5T#500ms); // TODO: Declarar \"T_Pulse_Recipe_Edit\" : TP;"
},
{
"instruction_uid": "40",
"uid": "40",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1253,12 +1253,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"T_Pulse_Recipe_Edit\".Q AND \"T_Pulse_Recipe_Edit\""
"scl": "// SymPy Contact: v18_ & v26_"
},
{
"instruction_uid": "41",
"uid": "41",
"type": "RCoil_scl",
"type": "RCoil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1276,12 +1276,12 @@
}
},
"outputs": {},
"scl": "IF \"T_Pulse_Recipe_Edit\".Q AND \"T_Pulse_Recipe_Edit\" THEN\n \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\" := FALSE;\nEND_IF;"
"scl": "IF \"T_Pulse_Recipe_Edit\" AND \"T_Pulse_Recipe_Edit\".Q THEN\n \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\" := FALSE;\nEND_IF;"
},
{
"instruction_uid": "42",
"uid": "42",
"type": "Contact_scl",
"type": "Contact_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {
@ -1298,12 +1298,12 @@
"outputs": {
"out": []
},
"scl": "// RLO: \"mAux_FP_M700_1\""
"scl": "// SymPy Contact: v19_"
},
{
"instruction_uid": "43",
"uid": "43",
"type": "SCoil_scl",
"type": "SCoil_sympy_processed",
"template_values": {},
"negated_pins": {},
"inputs": {

View File

@ -6,13 +6,8 @@ FUNCTION_BLOCK "TestLAD"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR_IN_OUT
VAR_RETURN
Ret_Val : Void;
END_VAR
VAR_TEMP
@ -29,43 +24,43 @@ BEGIN
// Network 1: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD)
"gHVP301_Open" := (("gSyrupRoomEn" AND (NOT "gIN_HVP301_Aux")) AND (NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_FastChangeOverEnabled") AND "Procedure_Variables"."FTP302Line_Preparation"."Done") AND (NOT "Procedure_Variables"."Syr_RunOut"."Done") OR (((("gSyrupRoomEn" AND (NOT "gIN_HVP301_Aux")) AND "gBlenderCIPMode") AND "gIN_CIP_CIPRunning") AND "Procedure_Variables"."Blender_Run"."Running");
"gHVP301_Open" := ("gSyrupRoomEn" AND "gBlenderCIPMode" AND "gIN_CIP_CIPRunning" AND "Procedure_Variables"."Blender_Run"."Running" AND NOT "gIN_HVP301_Aux") OR ("gSyrupRoomEn" AND "Procedure_Variables"."FTP302Line_Preparation"."Done" AND NOT "gIN_HVP301_Aux" AND NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_FastChangeOverEnabled" AND NOT "Procedure_Variables"."Syr_RunOut"."Done");
// Network 2: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD)
"mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON; en VAR_STAT o VAR
"mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON;
"gHVM302_Open" := "mHVM302_Dly".Q;
// Network 3: ResetTotalizer (Original Language: LAD)
"mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP; en VAR_STAT o VAR
"mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP;
// Network 4: ResetWaterTot (Original Language: LAD)
"mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP; en VAR_STAT o VAR
"mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP;
"mResetWaterTot" := "mResetFTN301TotTmr".Q;
// Network 5: ResetCO2Tot (Original Language: LAD)
"mResetFTP302TotTmr"(IN := "gFTP302_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP; en VAR_STAT o VAR
"mResetSyrupTot" := "mResetFTP302TotTmr".Q AND "gSyrupRoomEn";
"mResetFTP302TotTmr"(IN := "mResetTotalizerTmr" OR "gFTP302_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP;
"mResetSyrupTot" := "gSyrupRoomEn" AND "mResetFTP302TotTmr".Q;
// Network 6: ResetProductTot (Original Language: LAD)
"mResetFTM303TotTmr"(IN := "gFTM303_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP; en VAR_STAT o VAR
"mResetFTM303TotTmr"(IN := "mResetTotalizerTmr" OR "gFTM303_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP;
"mResetCO2Tot" := "mResetFTM303TotTmr".Q;
// Network 7: ResetCO2Tot (Original Language: LAD)
"mResetProductTotTmr"(IN := "gProductMFMResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP; en VAR_STAT o VAR
"mResetProductTotTmr"(IN := "mResetTotalizerTmr" OR "gProductMFMResetTot", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP;
"mResetProductTot" := "mResetProductTotTmr".Q;
// Network 8: Mod Copy Recipe (Original Language: LAD)
"mAux_FP_M700_1" := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND (NOT "mFP_Recip_Main_Page");
"mAux_FP_M700_1" := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND NOT "mFP_Recip_Main_Page";
"mFP_Recip_Main_Page" := "HMI_Variables_Cmd"."Recipe"."Main_Page";
"T_Pulse_Recipe_Edit"(IN := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND "HMI_Variables_Cmd"."Recipe"."Edit", PT := S5T#500ms); // TODO: Declarar "T_Pulse_Recipe_Edit" : TP; en VAR_STAT o VAR
IF "T_Pulse_Recipe_Edit".Q AND "T_Pulse_Recipe_Edit" THEN
"T_Pulse_Recipe_Edit"(IN := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND "HMI_Variables_Cmd"."Recipe"."Edit", PT := S5T#500ms); // TODO: Declarar "T_Pulse_Recipe_Edit" : TP;
IF "T_Pulse_Recipe_Edit" AND "T_Pulse_Recipe_Edit".Q THEN
"HMI_Variables_Cmd"."Recipe"."Edit" := FALSE;
END_IF;
IF "mAux_FP_M700_1" THEN

View File

@ -1,75 +1,87 @@
# processors/process_add.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re # Importar re si se usa para formateo
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
SCL_SUFFIX = "_sympy_processed" # Usar el nuevo sufijo
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_add(instruction, network_id, scl_map, access_map, data):
def process_add(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera SCL para Add, simplificando la condición EN."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Add")
current_type = instruction.get("type","")
if current_type.endswith(SCL_SUFFIX) or "_error" in current_type:
return False
# Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String)
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
in1_info = instruction["inputs"].get("in1")
in2_info = instruction["inputs"].get("in2")
in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map)
in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map)
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager)
op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager)
if en_scl is None or in1_scl is None or in2_scl is None:
# Obtener destino SCL
target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True)
# Verificar dependencias
if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None:
# print(f"DEBUG Add {instr_uid}: Dependency not ready")
return False
target_scl = get_target_scl_name(
instruction, "out", network_id, default_to_temp=True
)
if target_scl is None:
print(f"Error: Sin destino ADD {instr_uid}")
instruction["scl"] = f"// ERROR: Add {instr_uid} sin destino"
instruction["type"] += "_error"
return True
# Convertir operandos SymPy/Constante a SCL strings
op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager)
op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager)
# Formatear operandos si son variables
op1 = (
format_variable_name(in1_scl)
if in1_info and in1_info.get("type") == "variable"
else in1_scl
)
op2 = (
format_variable_name(in2_scl)
if in2_info and in2_info.get("type") == "variable"
else in2_scl
)
# Añadir paréntesis si contienen operadores (más seguro para SCL)
op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl
op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl
# Añadir paréntesis si es necesario
op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1
op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2
# Generar SCL Core
scl_core = f"{target_scl_name} := {op1_scl_formatted} + {op2_scl_formatted};"
scl_core = f"{target_scl} := {op1} + {op2};"
scl_final = (
f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
)
# Aplicar Condición EN (Simplificando EN)
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# Evitar IF TRUE THEN...
if en_condition_scl == "TRUE":
scl_final = scl_core
# Evitar IF FALSE THEN...
elif en_condition_scl == "FALSE":
scl_final = f"// {instr_type_original} {instr_uid} condition simplified to FALSE."
else:
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
# Actualizar instrucción y mapa
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy)
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = target_scl
sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino (string)
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
return True
sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO
# --- Function code ends ---
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el procesador Add."""
return {'type_name': 'add', 'processor_func': process_add, 'priority': 4}
# Asegurar que la clave coincida con el tipo en JSON ('add')
return {'type_name': 'add', 'processor_func': process_add, 'priority': 4}

View File

@ -1,112 +1,118 @@
# processors/process_blkmov.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
SCL_SUFFIX = "_sympy_processed" # Usar el nuevo sufijo
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_blkmov(instruction, network_id, scl_map, access_map, data):
def process_blkmov(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL usando BLKMOV directamente como nombre de función,
sin COUNT y con formato específico, según solicitud del usuario.
ADVERTENCIA: Es MUY PROBABLE que esto NO compile en TIA Portal estándar,
ya que BLKMOV no es una función SCL y MOVE_BLK requiere COUNT.
simplificando la condición EN.
ADVERTENCIA: Sintaxis BLKMOV probablemente no compile en TIA estándar.
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
return False # Ya procesado o con error
instr_type_original = instruction.get("type", "BlkMov") # Asegurar que el tipo base sea correcto
current_type = instruction.get("type","")
if current_type.endswith(SCL_SUFFIX) or "_error" in current_type:
return False
# --- Obtener Entradas ---
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
# Obtener EN como expresión SymPy
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
srcblk_info = instruction["inputs"].get("SRCBLK")
# ¡IMPORTANTE! Obtenemos el nombre RAW antes de formatearlo para usarlo como pide el usuario
# Obtener nombre RAW de SRCBLK (como se hacía antes, si es necesario para BLKMOV)
# Este nombre NO pasa por SymPy, se usa directo en el string SCL final
raw_srcblk_name = srcblk_info.get("name") if srcblk_info else None
# Verificar dependencias de entrada (solo necesitamos que EN esté resuelto)
if en_scl is None:
return False # Dependencia EN no lista
# Verificar dependencias (EN debe estar resuelto, SRCBLK debe tener nombre)
if sympy_en_expr is None:
# print(f"DEBUG BlkMov {instr_uid}: EN dependency not ready")
return False
if raw_srcblk_name is None:
print(f"Error: BLKMOV {instr_uid} sin información válida para SRCBLK.")
instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin SRCBLK válido."
instruction["type"] += "_error"
instruction["type"] = instr_type_original + "_error"
return True
# --- Obtener Destinos (Salidas) ---
# RET_VAL (Usamos get_target_scl_name para manejar variables temporales si es necesario)
retval_target_scl = get_target_scl_name(
instruction, "RET_VAL", network_id, default_to_temp=True
)
if retval_target_scl is None:
print(f"Error: BLKMOV {instr_uid} sin destino claro para RET_VAL.")
instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin destino RET_VAL"
instruction["type"] += "_error"
# RET_VAL (Obtener nombre SCL formateado)
retval_target_scl = get_target_scl_name(instruction, "RET_VAL", network_id, default_to_temp=True)
if retval_target_scl is None: # get_target_scl_name ya imprime error si falla y default_to_temp=True
instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} no pudo generar destino RET_VAL"
instruction["type"] = instr_type_original + "_error"
return True
# DSTBLK (Obtenemos el nombre RAW para usarlo como pide el usuario)
# DSTBLK (Obtener nombre RAW como antes, si se necesita)
raw_dstblk_name = None
dstblk_output_list = instruction.get("outputs", {}).get("DSTBLK", [])
if dstblk_output_list and isinstance(dstblk_output_list, list) and len(dstblk_output_list) == 1:
dest_access = dstblk_output_list[0]
if dest_access.get("type") == "variable":
raw_dstblk_name = dest_access.get("name") # Nombre raw del JSON
else:
print(f"Advertencia: Destino DSTBLK de BLKMOV {instr_uid} no es una variable (Tipo: {dest_access.get('type')}).")
else:
print(f"Error: No se encontró un destino único y válido para DSTBLK en BLKMOV {instr_uid}.")
raw_dstblk_name = dest_access.get("name")
# Manejar error si no se encuentra DSTBLK
if raw_dstblk_name is None:
print(f"Error: No se encontró un destino único y válido para DSTBLK en BLKMOV {instr_uid}.")
instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin destino DSTBLK válido."
instruction["type"] += "_error"
instruction["type"] = instr_type_original + "_error"
return True
# --- Formateo especial para SRCBLK/DSTBLK como pidió el usuario ---
# Asume formato "DB".Variable o "Struct".Variable del JSON y lo mantiene
# (Esto anula la limpieza normal de format_variable_name para estos parámetros)
srcblk_final_str = raw_srcblk_name if raw_srcblk_name else "_ERROR_SRC_"
dstblk_final_str = raw_dstblk_name if raw_dstblk_name else "_ERROR_DST_"
# --- Formateo especial (mantener nombres raw si es necesario para BLKMOV) ---
# Estos nombres van directo al string SCL, no necesitan pasar por SymPy
srcblk_final_str = raw_srcblk_name # Asumiendo que ya viene con comillas si las necesita
dstblk_final_str = raw_dstblk_name # Asumiendo que ya viene con comillas si las necesita
# --- Generar SCL Exacto Solicitado ---
# --- Generar SCL Core (Usando la sintaxis no estándar BLKMOV) ---
scl_core = (
f"{retval_target_scl} := BLKMOV(SRCBLK := {srcblk_final_str}, "
f"DSTBLK => {dstblk_final_str}); "
f"DSTBLK => {dstblk_final_str}); " # Usar => para Out/InOut
f"// ADVERTENCIA: BLKMOV usado directamente, probablemente no compile!"
)
# Añadir condición EN (usando la representación SCL obtenida para EN)
scl_final = (
f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
)
# --- Aplicar Condición EN (Simplificando EN) ---
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
# --- Actualizar Instrucción y Mapa SCL ---
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# Evitar IF TRUE/FALSE THEN...
if en_condition_scl == "TRUE":
scl_final = scl_core
elif en_condition_scl == "FALSE":
scl_final = f"// {instr_type_original} {instr_uid} condition simplified to FALSE."
else:
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
# Propagar ENO (igual que EN)
# --- Actualizar Instrucción y Mapa SymPy ---
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar ENO (expresión SymPy)
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
sympy_map[map_key_eno] = sympy_en_expr
# Propagar el valor de retorno (el contenido de la variable asignada a RET_VAL)
# Propagar el valor de retorno (nombre SCL string del destino de RET_VAL)
map_key_ret_val = (network_id, instr_uid, "RET_VAL")
scl_map[map_key_ret_val] = retval_target_scl # El valor es lo que sea que se asigne
sympy_map[map_key_ret_val] = retval_target_scl
return True
# ... (Asegúrate de que esta función está registrada en processor_map como antes) ...
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el procesador BLKMOV."""
# Asumiendo que 'BLKMOV' es el type en el JSON simplificado
return {'type_name': 'blkmov', 'processor_func': process_blkmov, 'priority': 6}
# Asegurarse que el type_name coincida con el JSON ('blkmov' parece probable)
return {'type_name': 'blkmov', 'processor_func': process_blkmov, 'priority': 6}

View File

@ -1,141 +1,131 @@
# processors/process_call.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Asumiendo que estas funciones ahora existen y están adaptadas
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name
from .symbol_manager import SymbolManager # Necesitamos pasar el symbol_manager
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# Definir sufijo globalmente o importar
SCL_SUFFIX = "_sympy_processed"
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_call(instruction, network_id, scl_map, access_map, data):
def process_call(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
instr_uid = instruction["instruction_uid"]
instr_type = instruction.get("type", "") # Usar get con default
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "") # Tipo antes de añadir sufijo
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
block_name = instruction.get("block_name", f"UnknownCall_{instr_uid}")
block_type = instruction.get("block_type") # FC, FB
instance_db = instruction.get("instance_db") # Nombre del DB de instancia (para FB)
# Formatear nombres
# Formatear nombres SCL (para la llamada final)
block_name_scl = format_variable_name(block_name)
instance_db_scl = format_variable_name(instance_db) if instance_db else None
# --- Manejo de EN ---
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
if en_scl is None:
return False # Dependencia EN no resuelta
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
# --- Procesar Parámetros de Entrada/Salida ---
# Necesitamos iterar sobre los pines definidos en la interfaz del bloque llamado.
# Esta información no está directamente en la instrucción 'Call' del JSON simplificado.
# ¡Limitación! Sin la interfaz del bloque llamado, solo podemos manejar EN/ENO
# y asumir una llamada sin parámetros o con parámetros conectados implícitamente.
if sympy_en_expr is None:
# print(f"DEBUG Call {instr_uid}: EN dependency not ready.")
return False # Dependencia EN no resuelta
# Solución temporal: Buscar conexiones en 'inputs' y 'outputs' que NO sean 'en'/'eno'
# y construir la llamada basándose en eso. Esto es muy heurístico.
# --- Procesar Parámetros de Entrada ---
scl_call_params = []
processed_inputs = {"en"} # Marcar 'en' como ya procesado
for pin_name, source_info in instruction.get("inputs", {}).items():
processed_inputs = {"en"}
dependencies_resolved = True
# Ordenar para consistencia
input_pin_names = sorted(instruction.get("inputs", {}).keys())
for pin_name in input_pin_names:
if pin_name not in processed_inputs:
param_scl = get_scl_representation(
source_info, network_id, scl_map, access_map
)
if param_scl is None:
# print(f"DEBUG: Call {instr_uid} esperando parámetro de entrada {pin_name}")
return False # Dependencia de parámetro no resuelta
# Formatear si es variable
param_scl_formatted = (
format_variable_name(param_scl)
if source_info.get("type") == "variable"
else param_scl
)
scl_call_params.append(
f"{format_variable_name(pin_name)} := {param_scl_formatted}"
)
source_info = instruction["inputs"][pin_name]
# Obtener la representación de la fuente (puede ser SymPy o Constante/String)
source_sympy_or_const = get_sympy_representation(source_info, network_id, sympy_map, symbol_manager)
if source_sympy_or_const is None:
# print(f"DEBUG Call {instr_uid}: Input param '{pin_name}' dependency not ready.")
dependencies_resolved = False
break # Salir si una dependencia no está lista
# Convertir la expresión/constante a SCL para la llamada
# Simplificar ANTES de convertir? Probablemente no necesario para parámetros de entrada
# a menos que queramos optimizar el valor pasado. Por ahora, convertir directo.
param_scl_value = sympy_expr_to_scl(source_sympy_or_const, symbol_manager)
# El nombre del pin SÍ necesita formateo
pin_name_scl = format_variable_name(pin_name)
scl_call_params.append(f"{pin_name_scl} := {param_scl_value}")
processed_inputs.add(pin_name)
# Procesar parámetros de salida (asignaciones después de la llamada o pasados como VAR_IN_OUT/VAR_OUTPUT)
# Esto es aún más complejo. SCL normalmente asigna salidas después o usa punteros/referencias.
# Simplificación: Asumir que las salidas se manejan por asignación posterior si es necesario,
# o que son VAR_OUTPUT y se acceden como instancia.salida.
# Por ahora, no generamos asignaciones explícitas para las salidas aquí.
if not dependencies_resolved:
return False
# --- Construcción de la Llamada SCL ---
# --- Construcción de la Llamada SCL (similar a antes) ---
scl_call_body = ""
param_string = ", ".join(scl_call_params)
if block_type == "FB":
if not instance_db_scl:
print(
f"Error: Llamada a FB '{block_name_scl}' (UID {instr_uid}) sin DB de instancia especificado."
)
print(f"Error: Call FB '{block_name_scl}' (UID {instr_uid}) sin instancia.")
instruction["scl"] = f"// ERROR: FB Call {block_name_scl} sin instancia"
instruction["type"] = "Call_FB_error"
return True # Procesado con error
# Llamada a FB con DB de instancia
instruction["type"] = f"Call_FB_error"
return True
scl_call_body = f"{instance_db_scl}({param_string});"
elif block_type == "FC":
# Llamada a FC
scl_call_body = f"{block_name_scl}({param_string});"
else:
print(
f"Advertencia: Tipo de bloque no soportado para Call UID {instr_uid}: {block_type}"
)
print(f"Advertencia: Tipo de bloque no soportado para Call UID {instr_uid}: {block_type}")
scl_call_body = f"// ERROR: Call a bloque tipo '{block_type}' no soportado: {block_name_scl}"
instruction["type"] = f"Call_{block_type}_error" # Marcar como error parcial
instruction["type"] = f"Call_{block_type}_error" # Marcar como error
# --- Aplicar Condición EN ---
# --- Aplicar Condición EN (usando la expresión SymPy EN) ---
scl_final = ""
if en_scl != "TRUE":
# Indentar la llamada dentro del IF
if sympy_en_expr != sympy.true:
# Simplificar la condición EN ANTES de convertirla a SCL
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for Call {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
indented_call = "\n".join([f" {line}" for line in scl_call_body.splitlines()])
scl_final = f"IF {en_scl} THEN\n{indented_call}\nEND_IF;"
scl_final = f"IF {en_condition_scl} THEN\n{indented_call}\nEND_IF;"
else:
scl_final = scl_call_body
# --- Actualizar JSON y Mapa SCL ---
instruction["scl"] = scl_final
instruction["type"] = (
f"Call_{block_type}_scl"
if "_error" not in instruction["type"]
else instruction["type"]
)
# --- Actualizar Instrucción y Mapa SymPy ---
instruction["scl"] = scl_final # Guardar el SCL final generado
instruction["type"] = (f"Call_{block_type}{SCL_SUFFIX}" if "_error" not in instruction["type"] else instruction["type"])
# Actualizar scl_map con el estado ENO (igual a EN para llamadas simples sin manejo explícito de ENO)
# Actualizar sympy_map con el estado ENO (es la expresión SymPy de EN)
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO
# Propagar valores de salida (si pudiéramos determinarlos)
# Ejemplo: Si supiéramos que hay una salida 'Out1' de tipo INT
# map_key_out1 = (network_id, instr_uid, "Out1")
# if block_type == "FB" and instance_db_scl:
# scl_map[map_key_out1] = f"{instance_db_scl}.Out1" # Acceso a salida de instancia
# else:
# # Para FCs, necesitaríamos una variable temporal o asignación explícita
# temp_out1 = generate_temp_var_name(network_id, instr_uid, "Out1")
# # Modificar scl_call_body para incluir la asignación: Out1 => temp_out1
# scl_map[map_key_out1] = temp_out1
# Propagar valores de salida (requiere info de interfaz o heurística)
# Si se sabe que hay una salida 'MyOutput', se podría añadir su SCL al mapa
# Ejemplo MUY simplificado:
# for pin_name, dest_list in instruction.get("outputs", {}).items():
# if pin_name != 'eno' and dest_list: # Asumir que hay un destino
# map_key_out = (network_id, instr_uid, pin_name)
# if block_type == "FB" and instance_db_scl:
# sympy_map[map_key_out] = f"{instance_db_scl}.{format_variable_name(pin_name)}" # Guardar el *string* de acceso SCL
# # Para FCs es más complejo, necesitaría asignación explícita a temp
# # else: # FC output -> necesita temp var
# # temp_var = generate_temp_var_name(...)
# # sympy_map[map_key_out] = temp_var
return True
# --- Procesador de Temporizadores (TON, TOF) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para las llamadas a FC y FB."""
# Esta función maneja tanto FC como FB. El despachador en x2_process.py
# usará 'call_fc' o 'call_fb' como clave basada en block_type.
return [
{'type_name': 'call_fc', 'processor_func': process_call, 'priority': 6},
{'type_name': 'call_fb', 'processor_func': process_call, 'priority': 6}
]
]

View File

@ -1,79 +1,82 @@
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
# processors/process_coil.py
import sympy
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_coil(instruction, network_id, scl_map, access_map, data):
"""Genera la asignación para Coil. Si la entrada viene de PBox/NBox,
añade la actualización de memoria del flanco DESPUÉS de la asignación."""
def process_coil(instruction, network_id, sympy_map, symbol_manager, data):
"""Genera la asignación SCL para Coil, simplificando la entrada SymPy."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Coil")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Get input expression from SymPy map
coil_input_info = instruction["inputs"].get("in")
operand_info = instruction["inputs"].get("operand")
sympy_expr_in = get_sympy_representation(coil_input_info, network_id, sympy_map, symbol_manager)
in_rlo_scl = get_scl_representation(coil_input_info, network_id, scl_map, access_map)
operand_scl = get_scl_representation(operand_info, network_id, scl_map, access_map)
# Get target variable SCL name
target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # Coil must have explicit target
if in_rlo_scl is None or operand_scl is None: return False
# Check dependencies
if sympy_expr_in is None:
# print(f"DEBUG Coil {instr_uid}: Input dependency not ready.")
return False
if target_scl_name is None:
print(f"Error: Coil {instr_uid} operando no es variable o falta info.")
instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no es variable."
instruction["type"] = instr_type_original + "_error"
return True # Processed with error
if not (operand_info and operand_info.get("type") == "variable"):
instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no es variable o falta info"
instruction["type"] = instr_type + "_error"
return True
# *** Perform Simplification ***
try:
#simplified_expr = sympy.simplify_logic(sympy_expr_in, force=False)
#simplified_expr = sympy_expr_in
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True)
except Exception as e:
print(f"Error during SymPy simplification for Coil {instr_uid}: {e}")
simplified_expr = sympy_expr_in # Fallback to original expression
operand_scl_formatted = format_variable_name(operand_scl)
if in_rlo_scl == "(TRUE)": in_rlo_scl = "TRUE"
elif in_rlo_scl == "(FALSE)": in_rlo_scl = "FALSE"
# *** Convert simplified expression back to SCL string ***
condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager)
# Generar la asignación SCL principal de la bobina
scl_assignment = f"{operand_scl_formatted} := {in_rlo_scl};"
scl_final = scl_assignment # Inicializar SCL final
# Generate the final SCL assignment
scl_assignment = f"{target_scl_name} := {condition_scl};"
scl_final = scl_assignment
# --- Lógica para añadir actualización de memoria de flancos ---
# --- Handle Edge Detector Memory Update (Logic similar to before) ---
# Check if input comes from PBox/NBox and append memory update
mem_update_scl_combined = None
if isinstance(coil_input_info, dict) and coil_input_info.get("type") == "connection":
source_uid = coil_input_info.get("source_instruction_uid")
source_pin = coil_input_info.get("source_pin")
# Buscar la instrucción fuente PBox/NBox
source_instruction = None
network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), [])
for instr in network_logic:
if instr.get("instruction_uid") == source_uid:
source_instruction = instr
break
if source_instruction:
source_type = source_instruction.get("type","").replace('_scl','').replace('_error','')
# Si la fuente es PBox o NBox y tiene el campo temporal con la actualización
if source_type in ["PBox", "NBox"] and '_edge_mem_update_scl' in source_instruction:
mem_update_scl_combined = source_instruction.get('_edge_mem_update_scl') # Obtener update+comment
# Check for the original type before suffix was added
orig_source_type = source_instruction.get("type", "").replace(SCL_SUFFIX, '').replace('_error', '')
if orig_source_type in ["PBox", "NBox"] and '_edge_mem_update_scl' in source_instruction:
mem_update_scl_combined = source_instruction.get('_edge_mem_update_scl')
if mem_update_scl_combined:
# Añadir la actualización DESPUÉS de la asignación de la bobina, USANDO \n
scl_final = f"{scl_assignment}\n{mem_update_scl_combined}"
# Marcar la instrucción PBox/NBox para que x3 no escriba su SCL (que ahora está vacío/comentario)
source_instruction['scl'] = f"// Logic moved to Coil {instr_uid}" # Actualizar PBox/NBox SCL
# Clear the source SCL?
source_instruction['scl'] = f"// Edge Logic handled by Coil {instr_uid}"
# Update instruction
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
instruction["type"] = instr_type_original + SCL_SUFFIX
# Coil typically doesn't output to scl_map
return True
# EN x2_process.py, junto a otras funciones process_xxx
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el procesador Coil."""
return {'type_name': 'coil', 'processor_func': process_coil, 'priority': 3}
return {'type_name': 'coil', 'processor_func': process_coil, 'priority': 3}

View File

@ -1,81 +1,87 @@
# processors/process_comparison.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
from .processor_utils import get_sympy_representation, format_variable_name # No necesita sympy_expr_to_scl aquí
from .symbol_manager import SymbolManager # Necesita acceso al manager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_comparison(instruction, network_id, scl_map, access_map, data):
def process_comparison(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera la expresión SCL para Comparadores (GT, LT, GE, LE, NE).
El resultado se propaga por scl_map['out'].
Genera la expresión SymPy para Comparadores (GT, LT, GE, LE, NE).
El resultado se propaga por sympy_map['out'].
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"] # GT, LT, GE, LE, NE
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "") # GT, LT, GE, LE, NE
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Mapa de tipos a operadores SCL
op_map = {"GT": ">", "LT": "<", "GE": ">=", "LE": "<=", "NE": "<>"}
scl_operator = op_map.get(instr_type)
if not scl_operator:
instruction["scl"] = f"// ERROR: Tipo de comparación no soportado: {instr_type}"
instruction["type"] += "_error"
# Mapa de tipos a funciones/clases SymPy Relational
# Nota: Asegúrate de que los tipos coincidan (ej. si son números o booleanos)
op_map = {
"GT": sympy.Gt, # Greater Than >
"LT": sympy.Lt, # Less Than <
"GE": sympy.Ge, # Greater or Equal >=
"LE": sympy.Le, # Less or Equal <=
"NE": sympy.Ne # Not Equal <> (sympy.Ne maneja esto)
}
sympy_relation_func = op_map.get(instr_type_original.upper())
if not sympy_relation_func:
instruction["scl"] = f"// ERROR: Tipo de comparación no soportado para SymPy: {instr_type_original}"
instruction["type"] = instr_type_original + "_error"
return True
# Obtener operandos
# Obtener operandos como expresiones SymPy o constantes/strings
in1_info = instruction["inputs"].get("in1")
in2_info = instruction["inputs"].get("in2")
in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map)
in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map)
op1_sympy = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager)
op2_sympy = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager)
if in1_scl is None or in2_scl is None:
return False # Dependencias no listas
# Obtener 'pre' (RLO anterior) como expresión SymPy
pre_input = instruction["inputs"].get("pre") # Asumiendo que 'pre' es la entrada RLO
sympy_pre_rlo = get_sympy_representation(pre_input, network_id, sympy_map, symbol_manager) if pre_input else sympy.true
# Formatear operandos si son variables
op1 = format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl
op2 = format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl
# Verificar dependencias
if op1_sympy is None or op2_sympy is None or sympy_pre_rlo is None:
# print(f"DEBUG Comparison {instr_uid}: Dependency not ready")
return False
# Añadir paréntesis si contienen espacios (poco probable tras formatear)
op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1
op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2
# Crear la expresión de comparación SymPy
try:
# Convertir constantes string a número si es posible (Sympy puede necesitarlo)
# Esto es heurístico y puede fallar. Mejor si los tipos son conocidos.
op1_eval = sympy.sympify(op1_sympy) if isinstance(op1_sympy, str) else op1_sympy
op2_eval = sympy.sympify(op2_sympy) if isinstance(op2_sympy, str) else op2_sympy
comparison_expr = sympy_relation_func(op1_eval, op2_eval)
except (SyntaxError, TypeError, ValueError) as e:
print(f"Error creating SymPy comparison for {instr_uid}: {e}")
instruction["scl"] = f"// ERROR creando expr SymPy Comparison {instr_uid}: {e}"
instruction["type"] = instr_type_original + "_error"
return True
comparison_scl = f"{op1} {scl_operator} {op2}"
# Guardar resultado en el mapa para 'out'
# Guardar resultado en el mapa para 'out' (es una expresión booleana SymPy)
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = f"({comparison_scl})" # Poner paréntesis por seguridad
# Manejar entrada 'pre'/RLO -> ENO (como en EQ)
pre_input = instruction["inputs"].get("pre") # Asumir 'pre' como en EQ
en_scl = get_scl_representation(pre_input, network_id, scl_map, access_map) if pre_input else "TRUE"
if en_scl is None:
return False # Dependencia 'pre'/'en' no lista
sympy_map[map_key_out] = comparison_expr
# Guardar el RLO de entrada ('pre') como ENO en el mapa SymPy
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
sympy_map[map_key_eno] = sympy_pre_rlo
instruction["scl"] = f"// Comparison {instr_type} {instr_uid}: {comparison_scl}"
instruction["type"] = instr_type + SCL_SUFFIX
# Marcar como procesado, SCL principal es solo comentario
instruction["scl"] = f"// SymPy Comparison {instr_type_original}: {comparison_expr}" # Comentario opcional
instruction["type"] = instr_type_original + SCL_SUFFIX
return True
# --- Procesador de Matemáticas (ADD ya existe, añadir otros) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para los comparadores (excepto EQ)."""
# Esta función maneja múltiples tipos de comparación.
"""Devuelve la información para los comparadores (excepto EQ, que debe ser similar)."""
return [
{'type_name': 'gt', 'processor_func': process_comparison, 'priority': 2}, # >
{'type_name': 'lt', 'processor_func': process_comparison, 'priority': 2}, # <
{'type_name': 'ge', 'processor_func': process_comparison, 'priority': 2}, # >=
{'type_name': 'le', 'processor_func': process_comparison, 'priority': 2}, # <=
{'type_name': 'ne', 'processor_func': process_comparison, 'priority': 2} # <>
]
{'type_name': 'gt', 'processor_func': process_comparison, 'priority': 2},
{'type_name': 'lt', 'processor_func': process_comparison, 'priority': 2},
{'type_name': 'ge', 'processor_func': process_comparison, 'priority': 2},
{'type_name': 'le', 'processor_func': process_comparison, 'priority': 2},
{'type_name': 'ne', 'processor_func': process_comparison, 'priority': 2}
# Asegúrate de tener también un procesador para 'eq' usando sympy.Eq
]

View File

@ -1,73 +1,60 @@
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
# processors/process_contact.py
import sympy
from .processor_utils import get_sympy_representation, format_variable_name # Use new util
from .symbol_manager import SymbolManager, extract_plc_variable_name # Need symbol manager access
# Define SCL_SUFFIX or import if needed globally
SCL_SUFFIX = "_sympy_processed" # Indicate processing type
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_contact(instruction, network_id, scl_map, access_map, data):
"""Traduce Contact (normal o negado) a una expresión booleana SCL."""
def process_contact(instruction, network_id, sympy_map, symbol_manager, data): # Pass symbol_manager
"""Genera la expresión SymPy para Contact (normal o negado)."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Contact")
# Check if already processed with the new method
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# --- INICIO LEER NEGACIÓN ---
# Verificar si el pin 'operand' está marcado como negado en el JSON
is_negated = instruction.get("negated_pins", {}).get("operand", False)
# --- FIN LEER NEGACIÓN ---
# print(f"DEBUG: Intentando procesar CONTACT{' (N)' if is_negated else ''} - UID: {instr_uid} en Red: {network_id}")
# Get incoming SymPy expression (RLO)
in_input = instruction["inputs"].get("in")
in_rlo_scl = (
"TRUE"
if in_input is None
else get_scl_representation(in_input, network_id, scl_map, access_map)
)
operand_scl = get_scl_representation(
instruction["inputs"].get("operand"), network_id, scl_map, access_map
)
sympy_expr_in = get_sympy_representation(in_input, network_id, sympy_map, symbol_manager)
if in_rlo_scl is None or operand_scl is None:
return False
# Get operand SymPy Symbol
operand_info = instruction["inputs"].get("operand")
operand_plc_name = extract_plc_variable_name(operand_info)
sympy_symbol_operand = symbol_manager.get_symbol(operand_plc_name) if operand_plc_name else None
# Usar is_negated para aplicar NOT
term = f"NOT {operand_scl}" if is_negated else operand_scl
if not (term.startswith('"') and term.endswith('"')):
# Añadir paréntesis si es NOT o si contiene espacios/operadores
if is_negated or (
" " in term and not (term.startswith("(") and term.endswith(")"))
):
term = f"({term})"
# Check dependencies
if sympy_expr_in is None or sympy_symbol_operand is None:
# print(f"DEBUG Contact {instr_uid}: Dependency not ready (In: {sympy_expr_in is not None}, Op: {sympy_symbol_operand is not None})")
return False # Dependencies not ready
new_rlo_scl = (
term
if in_rlo_scl == "TRUE"
else (
f"({in_rlo_scl}) AND {term}"
if ("AND" in in_rlo_scl or "OR" in in_rlo_scl)
and not (in_rlo_scl.startswith("(") and in_rlo_scl.endswith(")"))
else f"{in_rlo_scl} AND {term}"
)
)
# Apply negation using SymPy
current_term = sympy.Not(sympy_symbol_operand) if is_negated else sympy_symbol_operand
# Combine with previous RLO using SymPy
# Simplify common cases: TRUE AND X -> X
if sympy_expr_in == sympy.true:
sympy_expr_out = current_term
else:
# Could add FALSE AND X -> FALSE optimization here too
sympy_expr_out = sympy.And(sympy_expr_in, current_term)
# Store the resulting SymPy expression object in the map
map_key_out = (network_id, instr_uid, "out")
sympy_map[map_key_out] = sympy_expr_out
# Mark instruction as processed (SCL field is now less relevant here)
instruction["scl"] = f"// SymPy Contact: {sympy_expr_out}" # Optional debug comment
instruction["type"] = instr_type_original + SCL_SUFFIX # Use the new suffix
# Contact doesn't usually have ENO, it modifies the RLO ('out')
map_key = (network_id, instr_uid, "out")
scl_map[map_key] = new_rlo_scl
instruction["scl"] = f"// RLO: {new_rlo_scl}"
instruction["type"] = instr_type + SCL_SUFFIX
return True
# --- process_edge_detector MODIFICADA ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el procesador Contact."""
return {'type_name': 'contact', 'processor_func': process_contact, 'priority': 1}
# Ensure 'data' argument is added if needed by the processor function signature change
return {'type_name': 'contact', 'processor_func': process_contact, 'priority': 1}

View File

@ -1,81 +1,90 @@
# processors/process_convert.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_convert(instruction, network_id, scl_map, access_map, data):
def process_convert(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera SCL para Convert, tratando la conversión como una asignación."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Convert")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener EN y IN
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
in_info = instruction["inputs"].get("in")
in_scl = get_scl_representation(in_info, network_id, scl_map, access_map)
sympy_or_const_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
if en_scl is None or in_scl is None:
return False # Esperar si dependencias no listas
# Obtener destino SCL
target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True)
target_scl = get_target_scl_name(
instruction, "out", network_id, default_to_temp=True
)
if target_scl is None:
print(f"Error: Sin destino claro para CONVERT {instr_uid}")
instruction["scl"] = f"// ERROR: Convert {instr_uid} sin destino"
instruction["type"] += "_error"
return True # Procesado con error
# Verificar dependencias
if sympy_en_expr is None or sympy_or_const_in is None or target_scl_name is None:
return False
# Formatear entrada si es variable
in_scl_formatted = (
format_variable_name(in_scl)
if in_info and in_info.get("type") == "variable"
else in_scl
)
# Convertir la entrada (SymPy o Constante) a SCL
# La simplificación aquí no suele aplicar a la conversión en sí,
# pero sí podría aplicar a la condición EN.
input_scl = sympy_expr_to_scl(sympy_or_const_in, symbol_manager)
# Determinar el tipo de destino (simplificado, necesitaría info del XML original)
# Asumimos que el tipo está en TemplateValues o inferirlo del nombre/contexto
target_type = instruction.get("template_values", {}).get(
"destType", "VARIANT"
) # Ejemplo, ajustar según XML real
conversion_func = f"{target_type}_TO_" # Necesita el tipo de origen también
# Esta parte es compleja sin saber los tipos exactos. Usaremos una conversión genérica o MOVE.
# Para una conversión real, necesitaríamos algo como:
# conversion_expr = f"CONVERT(IN := {in_scl_formatted}, OUT => {target_scl})" # Sintaxis inventada
# O usar funciones específicas: INT_TO_REAL, etc.
# Determinar el tipo de destino (esto sigue siendo un desafío sin info completa)
# Usaremos funciones de conversión SCL explícitas si podemos inferirlas.
target_type_hint = instruction.get("template_values", {}).get("destType", "").upper() # Ejemplo
source_type_hint = "" # Necesitaríamos info del tipo de origen
conversion_func_name = None
# Simplificación: Usar asignación directa (MOVE implícito) o función genérica si existe
# Asumiremos asignación directa por ahora.
conversion_expr = in_scl_formatted
scl_core = f"{target_scl} := {conversion_expr};"
# Heurística MUY básica (necesita mejorar con info de tipos real)
if target_type_hint and source_type_hint and target_type_hint != source_type_hint:
conversion_func_name = f"{source_type_hint}_TO_{target_type_hint}"
# Añadir IF EN si es necesario
scl_final = (
f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
)
# Generar SCL Core
if conversion_func_name:
# Usar función explícita si la inferimos
scl_core = f"{target_scl_name} := {conversion_func_name}({input_scl});"
else:
# Asignación directa (MOVE implícito) si no hay conversión clara
# ADVERTENCIA: Esto puede causar errores de tipo en el PLC si los tipos no coinciden.
scl_core = f"{target_scl_name} := {input_scl};"
if target_type_hint: # Añadir comentario si al menos conocemos el destino
scl_core += f" // TODO: Verify implicit conversion to {target_type_hint}"
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# Aplicar Condición EN (Simplificando EN)
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for Convert {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
# Actualizar instrucción y mapa
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar valor de salida (el contenido del destino) y ENO
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = target_scl # El valor de salida es el contenido del destino
# Guardar el *nombre* SCL del destino en el mapa, ya que contiene el valor
# O podríamos crear un símbolo SymPy para ello si fuera necesario aguas abajo? Por ahora, string.
sympy_map[map_key_out] = target_scl_name
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl # ENO sigue a EN
return True
sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO
# --- Function code ends ---
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el procesador Convert."""
return {'type_name': 'convert', 'processor_func': process_convert, 'priority': 4}
return {'type_name': 'convert', 'processor_func': process_convert, 'priority': 4}

View File

@ -1,94 +1,110 @@
# processors/process_counter.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_counter(instruction, network_id, scl_map, access_map, data):
def process_counter(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL para Contadores (CTU, CTD, CTUD).
Requiere datos de instancia (DB o STAT).
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"] # CTU, CTD, CTUD
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "") # CTU, CTD, CTUD
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# 1. Obtener Inputs (varía según tipo)
params = []
resolved = True
# 1. Definir pines de entrada esperados
input_pins_map = {
"CTU": ["CU", "R", "PV"],
"CTD": ["CD", "LD", "PV"],
"CTUD": ["CU", "CD", "R", "LD", "PV"]
}
input_pins = input_pins_map.get(instr_type_original.upper())
if not input_pins:
instruction["scl"] = f"// ERROR: Tipo de contador no soportado: {instr_type_original}"
instruction["type"] = instr_type_original + "_error"
return True
input_pins = []
if instr_type == "CTU": input_pins = ["CU", "R", "PV"]
elif instr_type == "CTD": input_pins = ["CD", "LD", "PV"]
elif instr_type == "CTUD": input_pins = ["CU", "CD", "R", "LD", "PV"]
else:
instruction["scl"] = f"// ERROR: Tipo de contador no soportado: {instr_type}"
instruction["type"] += "_error"
return True # Procesado con error
# 2. Procesar Parámetros de Entrada
scl_call_params = []
dependencies_resolved = True
optional_pins = {"R", "LD"} # Estos pueden no estar conectados
for pin in input_pins:
pin_info = instruction["inputs"].get(pin)
if pin_info is None and pin not in ["R", "LD"]: # R y LD pueden no estar conectados
print(f"Error: Falta entrada requerida '{pin}' para {instr_type} UID {instr_uid}.")
# Permitir continuar si solo faltan R o LD opcionales? Por ahora no.
instruction["scl"] = f"// ERROR: Falta entrada requerida '{pin}' para {instr_type} UID {instr_uid}."
instruction["type"] += "_error"
return True # Error
elif pin_info: # Si el pin existe en el JSON
scl_pin = get_scl_representation(pin_info, network_id, scl_map, access_map)
if scl_pin is None:
resolved = False
break # Salir si una dependencia no está lista
scl_pin_formatted = format_variable_name(scl_pin) if pin_info.get("type") == "variable" else scl_pin
params.append(f"{pin} := {scl_pin_formatted}")
if pin_info: # Si el pin está definido en el JSON
source_sympy_or_const = get_sympy_representation(pin_info, network_id, sympy_map, symbol_manager)
if source_sympy_or_const is None:
# print(f"DEBUG Counter {instr_uid}: Input param '{pin}' dependency not ready.")
dependencies_resolved = False
break
# Convertir a SCL para la llamada (sin simplificar aquí)
param_scl_value = sympy_expr_to_scl(source_sympy_or_const, symbol_manager)
pin_name_scl = format_variable_name(pin) # Formatear nombre del parámetro
scl_call_params.append(f"{pin_name_scl} := {param_scl_value}")
elif pin not in optional_pins: # Si falta un pin requerido
print(f"Error: Falta entrada requerida '{pin}' para {instr_type_original} UID {instr_uid}.")
instruction["scl"] = f"// ERROR: Falta entrada requerida '{pin}' para {instr_type_original} UID {instr_uid}."
instruction["type"] = instr_type_original + "_error"
return True
if not resolved: return False
if not dependencies_resolved:
return False
# 2. Obtener Nombre de Instancia (NECESITA MEJORA EN x1.py)
instance_name = instruction.get("instance_db")
if not instance_name:
instance_name = f"#COUNTER_INSTANCE_{instr_uid}" # Placeholder
print(f"Advertencia: No se encontró instancia para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. Ajustar x1.py y declarar en x3.py.")
else:
instance_name = format_variable_name(instance_name)
# 3. Obtener Nombre de Instancia
# Asumiendo que x1 o una fase previa llena 'instance_db' si es un FB multi-instancia
instance_name_raw = instruction.get("instance_db")
if not instance_name_raw:
# Asumiendo que es STAT si no hay DB instancia explícito (requiere declaración en x3)
instance_name_raw = instruction.get("instance_name") # Buscar nombre directo si x1 lo provee
if not instance_name_raw:
instance_name_raw = f"#CTR_INSTANCE_{instr_uid}" # Placeholder final
print(f"Advertencia: No se encontró nombre/instancia para {instr_type_original} UID {instr_uid}. Usando placeholder '{instance_name_raw}'.")
instance_name_scl = format_variable_name(instance_name_raw)
# 3. Generar la llamada SCL
param_string = ", ".join(params)
scl_call = f"{instance_name}({param_string}); // TODO: Declarar {instance_name} : {instr_type}; en VAR_STAT o VAR"
# 4. Generar la llamada SCL
param_string = ", ".join(scl_call_params)
scl_call = f"{instance_name_scl}({param_string}); // TODO: Declarar {instance_name_scl} : {instr_type_original.upper()}; en VAR_STAT o VAR"
instruction["scl"] = scl_call
instruction["type"] = instr_type + SCL_SUFFIX
# Contadores no suelen tener EN/ENO explícito en LAD, se asume siempre habilitado
instruction["scl"] = scl_call # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# 4. Actualizar scl_map para las salidas (QU, QD, CV)
output_pins = []
if instr_type == "CTU": output_pins = ["QU", "CV"]
elif instr_type == "CTD": output_pins = ["QD", "CV"]
elif instr_type == "CTUD": output_pins = ["QU", "QD", "CV"]
# 4. Actualizar sympy_map para las salidas (QU, QD, CV)
output_pins_map = {
"CTU": ["QU", "CV"],
"CTD": ["QD", "CV"],
"CTUD": ["QU", "QD", "CV"]
}
output_pins = output_pins_map.get(instr_type_original.upper(), [])
for pin in output_pins:
map_key = (network_id, instr_uid, pin)
scl_map[map_key] = f"{instance_name}.{pin}"
# Contadores no tienen ENO estándar en LAD/FBD
map_key = (network_id, instr_uid, pin)
output_scl_access = f"{instance_name_scl}.{pin.upper()}"
if pin.upper() in ["QU", "QD"]: # These are boolean outputs
# *** Store SymPy Symbol for boolean outputs QU/QD ***
sympy_out_symbol = symbol_manager.get_symbol(output_scl_access)
if sympy_out_symbol:
sympy_map[map_key] = sympy_out_symbol # Store SYMBOL
else:
print(f"Error: Could not create symbol for {output_scl_access} in {instr_type_original} {instr_uid}")
sympy_map[map_key] = None
else:
# For non-boolean (like CV - count value), store SCL access string
sympy_map[map_key] = output_scl_access
return True
# --- Procesador de Comparadores (EQ ya existe, añadir otros) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para los contadores CTU, CTD, CTUD."""
# Asumiendo que los tipos en el JSON son CTU, CTD, CTUD
return [
{'type_name': 'ctu', 'processor_func': process_counter, 'priority': 5},
{'type_name': 'ctd', 'processor_func': process_counter, 'priority': 5},
{'type_name': 'ctud', 'processor_func': process_counter, 'priority': 5}
]
]

View File

@ -1,91 +1,85 @@
# processors/process_edge_detector.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_edge_detector(instruction, network_id, scl_map, access_map, data):
"""Genera SCL para PBox (P_TRIG) o NBox (N_TRIG).
Guarda la expresión del pulso en scl_map['out'] y la actualización
de memoria en un campo temporal '_edge_mem_update_scl'.
def process_edge_detector(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera la expresión SymPy para el pulso de PBox (P_TRIG) o NBox (N_TRIG).
Guarda la expresión SymPy del pulso en sympy_map['out'].
Genera y guarda el SCL para la actualización de memoria en '_edge_mem_update_scl'.
El campo 'scl' principal se deja casi vacío/comentario.
Usa el nombre de memoria original sin renombrar.
"""
instr_uid = instruction["instruction_uid"]
instr_type_original = instruction["type"] # PBox o NBox
instr_type_original = instruction.get("type", "") # PBox o NBox
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False # Ya procesado o error
return False
# 1. Obtener CLK y MemBit original
# 1. Obtener CLK (como SymPy expr) y MemBit (como SymPy Symbol)
clk_input = instruction["inputs"].get("in")
mem_bit_input = instruction["inputs"].get("bit")
clk_scl = get_scl_representation(clk_input, network_id, scl_map, access_map)
mem_bit_scl_original = get_scl_representation(mem_bit_input, network_id, scl_map, access_map) # Ej: "M19001"
# 2. Verificar dependencias y tipo de MemBit
if clk_scl is None: return False
if mem_bit_scl_original is None:
instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} MemBit no resuelto."
instruction["type"] = instr_type_original + "_error"
return True
if not (mem_bit_input and mem_bit_input.get("type") == "variable"):
instruction["scl"] = f"// ERROR: {instr_type_original} {instr_uid} 'bit' no es variable."
sympy_clk_expr = get_sympy_representation(clk_input, network_id, sympy_map, symbol_manager)
mem_bit_plc_name = extract_plc_variable_name(mem_bit_input)
sympy_mem_bit_symbol = symbol_manager.get_symbol(mem_bit_plc_name) if mem_bit_plc_name else None
# 2. Verificar dependencias
if sympy_clk_expr is None: return False
if sympy_mem_bit_symbol is None:
err_msg = f"MemBit no resuelto o no es variable para {instr_type_original} UID {instr_uid}"
print(f"Error: {err_msg}")
instruction["scl"] = f"// ERROR: {err_msg}"
instruction["type"] = instr_type_original + "_error"
return True
# 3. Formatear CLK (usa memoria original)
clk_scl_formatted = clk_scl
if clk_scl not in ["TRUE", "FALSE"] and \
(' ' in clk_scl or 'AND' in clk_scl or 'OR' in clk_scl or ':=' in clk_scl) and \
not (clk_scl.startswith('(') and clk_scl.endswith(')')):
clk_scl_formatted = f"({clk_scl})"
# 4. Generar Lógica SCL del *pulso*
result_pulse_expression = "FALSE"
scl_comment = ""
if instr_type_original == "PBox": # P_TRIG
result_pulse_expression = f"{clk_scl_formatted} AND NOT {mem_bit_scl_original}"
scl_comment = f"// P_TRIG({clk_scl_formatted})"
elif instr_type_original == "NBox": # N_TRIG
result_pulse_expression = f"NOT {clk_scl_formatted} AND {mem_bit_scl_original}"
scl_comment = f"// N_TRIG({clk_scl_formatted})"
# 3. Generar Lógica SymPy del *pulso*
result_pulse_sympy_expr = sympy.false # Default
scl_comment_prefix = ""
if instr_type_original.upper() == "PBOX": # P_TRIG
result_pulse_sympy_expr = sympy.And(sympy_clk_expr, sympy.Not(sympy_mem_bit_symbol))
scl_comment_prefix = "P_TRIG"
elif instr_type_original.upper() == "NBOX": # N_TRIG
result_pulse_sympy_expr = sympy.And(sympy.Not(sympy_clk_expr), sympy_mem_bit_symbol)
scl_comment_prefix = "N_TRIG"
else: # Error
instruction["scl"] = f"// ERROR: Tipo de flanco inesperado {instr_type_original}"
instruction["type"] = instr_type_original + "_error"
return True
# 5. Generar la actualización del bit de memoria
scl_mem_update = f"{mem_bit_scl_original} := {clk_scl_formatted};"
# 4. Generar el SCL para la actualización del bit de memoria
# Necesitamos la representación SCL de la entrada CLK
clk_scl_str = sympy_expr_to_scl(sympy_clk_expr, symbol_manager)
# Usamos el nombre PLC original formateado para el bit de memoria
mem_bit_scl_name = format_variable_name(mem_bit_plc_name)
scl_mem_update = f"{mem_bit_scl_name} := {clk_scl_str};"
scl_comment_for_update = f"// {scl_comment_prefix}({clk_scl_str}) - Mem: {mem_bit_scl_name}"
# 6. Almacenar Resultados
# 5. Almacenar Resultados
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = result_pulse_expression # Guardar EXPRESIÓN del pulso
sympy_map[map_key_out] = result_pulse_sympy_expr # Guardar EXPRESIÓN SymPy del pulso
instruction['_edge_mem_update_scl'] = f"{scl_mem_update} {scl_comment}" # Guardar UPDATE + Comentario en campo temporal
instruction['scl'] = f"// {instr_type_original} Logic moved to consumer Coil" # Dejar SCL principal vacío/comentario
# Guardar SCL de actualización + Comentario en campo temporal
instruction['_edge_mem_update_scl'] = f"{scl_mem_update} {scl_comment_for_update}"
# Marcar como procesado, SCL principal es solo comentario
instruction['scl'] = f"// {instr_type_original} SymPy processed, logic in consumer"
instruction["type"] = instr_type_original + SCL_SUFFIX
# 7. Propagar ENO
# 6. Propagar ENO (es la expresión SymPy de CLK)
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = clk_scl
sympy_map[map_key_eno] = sympy_clk_expr
return True
# --- process_coil MODIFICADA (con \n correcto) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para los detectores de flanco PBox (P_TRIG) y NBox (N_TRIG)."""
"""Devuelve la info para los detectores de flanco PBox y NBox."""
return [
{'type_name': 'pbox', 'processor_func': process_edge_detector, 'priority': 2}, # Flanco positivo
{'type_name': 'nbox', 'processor_func': process_edge_detector, 'priority': 2} # Flanco negativo
]
{'type_name': 'pbox', 'processor_func': process_edge_detector, 'priority': 2},
{'type_name': 'nbox', 'processor_func': process_edge_detector, 'priority': 2}
]

View File

@ -1,74 +1,64 @@
# processors/process_eq.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades de SymPy
from .processor_utils import get_sympy_representation, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_eq(instruction, network_id, scl_map, access_map, data):
def process_eq(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera la expresión SymPy para el comparador de igualdad (EQ).
El resultado se propaga por sympy_map['out'].
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Eq")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener operandos como expresiones SymPy o constantes/strings
in1_info = instruction["inputs"].get("in1")
in2_info = instruction["inputs"].get("in2")
in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map)
in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map)
op1_sympy = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager)
op2_sympy = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager)
if in1_scl is None or in2_scl is None:
return False # Dependencias no listas
# Obtener 'pre' (RLO anterior) como expresión SymPy
pre_input = instruction["inputs"].get("pre") # Asumir 'pre' como entrada RLO estándar
sympy_pre_rlo = get_sympy_representation(pre_input, network_id, sympy_map, symbol_manager) if pre_input else sympy.true
# Formatear operandos si son variables
op1 = (
format_variable_name(in1_scl)
if in1_info and in1_info.get("type") == "variable"
else in1_scl
)
op2 = (
format_variable_name(in2_scl)
if in2_info and in2_info.get("type") == "variable"
else in2_scl
)
# Verificar dependencias
if op1_sympy is None or op2_sympy is None or sympy_pre_rlo is None:
# print(f"DEBUG EQ {instr_uid}: Dependency not ready")
return False
# Añadir paréntesis si los operandos contienen espacios (poco probable después de formatear)
op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1
op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2
# Crear la expresión de igualdad SymPy
try:
# sympify puede ser necesario si los operandos son strings de constantes
op1_eval = sympy.sympify(op1_sympy) if isinstance(op1_sympy, str) else op1_sympy
op2_eval = sympy.sympify(op2_sympy) if isinstance(op2_sympy, str) else op2_sympy
comparison_expr = sympy.Eq(op1_eval, op2_eval) # Eq para igualdad
except (SyntaxError, TypeError, ValueError) as e:
print(f"Error creating SymPy equality for {instr_uid}: {e}")
instruction["scl"] = f"// ERROR creando expr SymPy EQ {instr_uid}: {e}"
instruction["type"] = instr_type_original + "_error"
return True
comparison_scl = f"{op1} = {op2}"
# Guardar el resultado booleano de la comparación en el mapa SCL para la salida 'out'
# Guardar resultado (Expresión SymPy booleana) en el mapa para 'out'
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = comparison_scl
sympy_map[map_key_out] = comparison_expr
# Procesar la entrada 'pre' (RLO anterior) para determinar ENO
pre_input = instruction["inputs"].get("pre")
pre_scl = (
"TRUE"
if pre_input is None
else get_scl_representation(pre_input, network_id, scl_map, access_map)
)
if pre_scl is None:
return False # Dependencia 'pre' no lista
# Guardar el estado de 'pre' como ENO
# Guardar el RLO de entrada ('pre') como ENO en el mapa SymPy
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = pre_scl
sympy_map[map_key_eno] = sympy_pre_rlo
# El SCL generado es solo un comentario indicando la condición,
# ya que la lógica se propaga a través de scl_map['out']
instruction["scl"] = f"// Comparison Eq {instr_uid}: {comparison_scl}"
instruction["type"] = instr_type + SCL_SUFFIX
# Marcar como procesado, SCL principal es solo comentario
instruction["scl"] = f"// SymPy EQ: {comparison_expr}" # Comentario opcional
instruction["type"] = instr_type_original + SCL_SUFFIX
return True
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el comparador de igualdad (EQ)."""
return {'type_name': 'eq', 'processor_func': process_eq, 'priority': 2}
return {'type_name': 'eq', 'processor_func': process_eq, 'priority': 2}

View File

@ -1,82 +1,90 @@
# processors/process_math.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_math(instruction, network_id, scl_map, access_map, data):
def process_math(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL para operaciones matemáticas (SUB, MUL, DIV).
Genera SCL para operaciones matemáticas (SUB, MUL, DIV), simplificando EN.
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"] # SUB, MUL, DIV
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "") # SUB, MUL, DIV
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Mapa de tipos a operadores SCL
# Mapa de tipos a operadores SCL string
op_map = {"SUB": "-", "MUL": "*", "DIV": "/"}
scl_operator = op_map.get(instr_type)
scl_operator = op_map.get(instr_type_original.upper())
if not scl_operator:
instruction["scl"] = f"// ERROR: Operación matemática no soportada: {instr_type}"
instruction["type"] += "_error"
instruction["scl"] = f"// ERROR: Operación matemática no soportada: {instr_type_original}"
instruction["type"] = instr_type_original + "_error"
return True
# Obtener EN, IN1, IN2
# Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String)
en_input = instruction["inputs"].get("en")
in1_info = instruction["inputs"].get("in1")
in2_info = instruction["inputs"].get("in2")
en_scl = get_scl_representation(en_input, network_id, scl_map, access_map) if en_input else "TRUE"
in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map)
in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map)
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager)
op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager)
if en_scl is None or in1_scl is None or in2_scl is None:
return False # Dependencias no listas
# Obtener destino SCL
target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True)
# Obtener destino 'out'
target_scl = get_target_scl_name(instruction, "out", network_id, default_to_temp=True)
if target_scl is None:
instruction["scl"] = f"// ERROR: {instr_type} {instr_uid} sin destino 'out'."
instruction["type"] += "_error"
return True
# Verificar dependencias
if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None:
return False
# Formatear operandos si son variables
op1 = format_variable_name(in1_scl) if in1_info and in1_info.get("type") == "variable" else in1_scl
op2 = format_variable_name(in2_scl) if in2_info and in2_info.get("type") == "variable" else in2_scl
# Convertir operandos SymPy/Constante a SCL strings
op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager)
op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager)
# Añadir paréntesis si es necesario (especialmente para expresiones)
op1 = f"({op1})" if (" " in op1 or "+" in op1 or "-" in op1 or "*" in op1 or "/" in op1) and not op1.startswith("(") else op1
op2 = f"({op2})" if (" " in op2 or "+" in op2 or "-" in op2 or "*" in op2 or "/" in op2) and not op2.startswith("(") else op2
# Añadir paréntesis si contienen operadores (más seguro)
# La función sympy_expr_to_scl debería idealmente manejar esto, pero doble chequeo simple:
op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl
op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl
# Generar SCL
scl_core = f"{target_scl} := {op1} {scl_operator} {op2};"
scl_final = f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
# Generar SCL Core
scl_core = f"{target_scl_name} := {op1_scl_formatted} {scl_operator} {op2_scl_formatted};"
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# Aplicar Condición EN (Simplificando EN)
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
# Actualizar mapa SCL
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
# Actualizar instrucción y mapa
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy)
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = target_scl
sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO
return True
# --- Procesador NOT ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para operaciones matemáticas (excepto ADD, MOD)."""
# Esta función maneja SUB, MUL, DIV.
"""Devuelve info para SUB, MUL, DIV."""
return [
{'type_name': 'sub', 'processor_func': process_math, 'priority': 4}, # Resta
{'type_name': 'mul', 'processor_func': process_math, 'priority': 4}, # Multiplicación
{'type_name': 'div', 'processor_func': process_math, 'priority': 4} # División
]
{'type_name': 'sub', 'processor_func': process_math, 'priority': 4},
{'type_name': 'mul', 'processor_func': process_math, 'priority': 4},
{'type_name': 'div', 'processor_func': process_math, 'priority': 4}
]

View File

@ -1,76 +1,75 @@
# processors/process_mod.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re # Importar re si no estaba
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_mod(instruction, network_id, scl_map, access_map, data):
def process_mod(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera SCL para Modulo (MOD), simplificando EN."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Mod")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String)
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
in1_info = instruction["inputs"].get("in1")
in2_info = instruction["inputs"].get("in2")
in1_scl = get_scl_representation(in1_info, network_id, scl_map, access_map)
in2_scl = get_scl_representation(in2_info, network_id, scl_map, access_map)
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager)
op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager)
if en_scl is None or in1_scl is None or in2_scl is None:
# Obtener destino SCL
target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True)
# Verificar dependencias
if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None:
return False
target_scl = get_target_scl_name(
instruction, "out", network_id, default_to_temp=True
)
if target_scl is None:
print(f"Error: Sin destino MOD {instr_uid}")
instruction["scl"] = f"// ERROR: Mod {instr_uid} sin destino"
instruction["type"] += "_error"
return True
# Convertir operandos SymPy/Constante a SCL strings
op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager)
op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager)
# Formatear operandos si son variables
op1 = (
format_variable_name(in1_scl)
if in1_info and in1_info.get("type") == "variable"
else in1_scl
)
op2 = (
format_variable_name(in2_scl)
if in2_info and in2_info.get("type") == "variable"
else in2_scl
)
# Añadir paréntesis si contienen operadores
op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl
op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl
# Añadir paréntesis si es necesario (poco probable tras formatear)
op1 = f"({op1})" if " " in op1 and not op1.startswith("(") else op1
op2 = f"({op2})" if " " in op2 and not op2.startswith("(") else op2
# Generar SCL Core
scl_core = f"{target_scl_name} := {op1_scl_formatted} MOD {op2_scl_formatted};"
scl_core = f"{target_scl} := {op1} MOD {op2};"
scl_final = (
f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
)
# Aplicar Condición EN (Simplificando EN)
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
# Actualizar instrucción y mapa
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy)
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = target_scl
sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
return True
sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO
# --- Function code ends ---
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la operación Modulo."""
return {'type_name': 'mod', 'processor_func': process_mod, 'priority': 4}
return {'type_name': 'mod', 'processor_func': process_mod, 'priority': 4}

View File

@ -1,83 +1,74 @@
# processors/process_move.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re # Importar re
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_move(instruction, network_id, scl_map, access_map, data):
def process_move(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera SCL para Move, simplificando la condición EN."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Move")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener EN (SymPy) e IN (SymPy o Constante/String)
en_input = instruction["inputs"].get("en")
en_scl = (
get_scl_representation(en_input, network_id, scl_map, access_map)
if en_input
else "TRUE"
)
in_info = instruction["inputs"].get("in")
in_scl = get_scl_representation(in_info, network_id, scl_map, access_map)
sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true
input_sympy_or_const = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
if en_scl is None or in_scl is None:
# Obtener destino SCL (requiere destino explícito para MOVE)
target_scl_name = get_target_scl_name(instruction, "out1", network_id, default_to_temp=False)
if target_scl_name is None:
target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=False)
# Verificar dependencias
if sympy_en_expr is None or input_sympy_or_const is None:
return False
if target_scl_name is None:
print(f"Error: MOVE {instr_uid} sin destino claro en 'out' o 'out1'.")
instruction["scl"] = f"// ERROR: MOVE {instr_uid} sin destino claro."
instruction["type"] = instr_type_original + "_error"
return True # Procesado con error
# Intentar obtener el destino de 'out1' (o 'out' si es el estándar)
target_scl = get_target_scl_name(
instruction, "out1", network_id, default_to_temp=False
)
if target_scl is None:
target_scl = get_target_scl_name(
instruction, "out", network_id, default_to_temp=False
)
# Convertir la entrada (SymPy o Constante) a SCL string
input_scl = sympy_expr_to_scl(input_sympy_or_const, symbol_manager)
if target_scl is None:
# Si no hay destino explícito, podríamos usar una temp si la lógica lo requiere,
# pero MOVE generalmente necesita un destino claro. Marcar como advertencia/error.
print(
f"Advertencia/Error: MOVE {instr_uid} sin destino claro en 'out' o 'out1'. Se requiere destino explícito."
)
# Decidir si generar error o simplemente no hacer nada. No hacer nada es más seguro.
# instruction["scl"] = f"// ERROR: MOVE {instr_uid} sin destino"
# instruction["type"] += "_error"
# return True
return False # No procesar si no hay destino claro
# Generar SCL Core
scl_core = f"{target_scl_name} := {input_scl};"
# Formatear entrada si es variable
in_scl_formatted = (
format_variable_name(in_scl)
if in_info and in_info.get("type") == "variable"
else in_scl
)
# Aplicar Condición EN (Simplificando EN)
scl_final = ""
if sympy_en_expr != sympy.true:
try:
#simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True)
simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True)
except Exception as e:
print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}")
simplified_en_expr = sympy_en_expr # Fallback
en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager)
scl_core = f"{target_scl} := {in_scl_formatted};"
scl_final = (
f"IF {en_scl} THEN\n {scl_core}\nEND_IF;" if en_scl != "TRUE" else scl_core
)
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
scl_final = scl_core
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# Actualizar instrucción y mapa
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# Propagar el valor movido a través de scl_map si se usa 'out' o 'out1' como fuente
map_key_out = (network_id, instr_uid, "out") # Asumir 'out' como estándar
scl_map[map_key_out] = target_scl # El valor es lo que está en el destino
map_key_out1 = (network_id, instr_uid, "out1") # Si existe out1
scl_map[map_key_out1] = target_scl
# Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy)
# Asumiendo que out y out1 deben propagar el mismo valor
sympy_map[(network_id, instr_uid, "out")] = target_scl_name
sympy_map[(network_id, instr_uid, "out1")] = target_scl_name
sympy_map[(network_id, instr_uid, "eno")] = sympy_en_expr
map_key_eno = (network_id, instr_uid, "eno")
scl_map[map_key_eno] = en_scl
return True
# EN x2_process.py
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la operación Move."""

View File

@ -1,47 +1,54 @@
# processors/process_not.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_not(instruction, network_id, scl_map, access_map, data):
"""Genera la expresión SCL para la inversión lógica NOT."""
def process_not(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera la expresión SymPy para la inversión lógica NOT."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"] # Not
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "Not")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener entrada como expresión SymPy
in_info = instruction["inputs"].get("in")
in_scl = get_scl_representation(in_info, network_id, scl_map, access_map)
sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
if in_scl is None:
return False # Dependencia no lista
# Verificar dependencias
if sympy_expr_in is None:
# print(f"DEBUG Not {instr_uid}: Dependency not ready")
return False
# Formatear entrada (añadir paréntesis si es complejo)
in_scl_formatted = in_scl
if (" " in in_scl or "AND" in in_scl or "OR" in in_scl) and not (in_scl.startswith("(") and in_scl.endswith(")")):
in_scl_formatted = f"({in_scl})"
# Crear la expresión NOT de SymPy
try:
not_expr = sympy.Not(sympy_expr_in)
# ¿Simplificar aquí? NOT(NOT A) -> A; NOT(TRUE) -> FALSE, etc.
# simplify_logic podría hacer esto, pero puede ser costoso en cada paso.
# SymPy podría manejar simplificaciones básicas automáticamente.
# Opcional: not_expr = sympy.simplify_logic(not_expr)
except Exception as e:
print(f"Error creating SymPy Not for {instr_uid}: {e}")
instruction["scl"] = f"// ERROR creando expr SymPy NOT {instr_uid}: {e}"
instruction["type"] = instr_type_original + "_error"
return True
result_scl = f"NOT {in_scl_formatted}"
# Guardar resultado en mapa para 'out'
# Guardar resultado (Expresión SymPy) en el mapa para 'out'
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = result_scl
sympy_map[map_key_out] = not_expr
# Marcar como procesado, SCL principal es solo comentario
instruction["scl"] = f"// SymPy NOT: {not_expr}" # Comentario opcional
instruction["type"] = instr_type_original + SCL_SUFFIX
# NOT no tiene EN/ENO explícito en LAD, modifica el RLO ('out')
instruction["scl"] = f"// Logic NOT {instr_uid}: {result_scl}"
instruction["type"] = instr_type + SCL_SUFFIX
# NOT no tiene EN/ENO explícito en LAD, modifica el RLO
return True
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la operación Not."""
return {'type_name': 'not', 'processor_func': process_not, 'priority': 1}
return {'type_name': 'not', 'processor_func': process_not, 'priority': 1}

View File

@ -1,80 +1,69 @@
# processors/process_o.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_o(instruction, network_id, scl_map, access_map, data):
def process_o(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera la expresión SymPy para la operación lógica O (OR)."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "O")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Buscar todas las entradas 'in', 'in1', 'in2', ...
input_pins = sorted([pin for pin in instruction["inputs"] if pin.startswith("in")])
input_pins = sorted([pin for pin in instruction.get("inputs", {}) if pin.startswith("in")])
if not input_pins:
print(f"Error: O {instr_uid} sin pines de entrada (inX).")
instruction["scl"] = f"// ERROR: O {instr_uid} sin pines inX"
instruction["type"] += "_error"
return True # Procesado con error
instruction["type"] = instr_type_original + "_error"
return True
scl_parts = []
sympy_parts = []
all_resolved = True
for pin in input_pins:
in_scl = get_scl_representation(
instruction["inputs"][pin], network_id, scl_map, access_map
)
if in_scl is None:
input_info = instruction["inputs"][pin]
sympy_expr = get_sympy_representation(input_info, network_id, sympy_map, symbol_manager)
if sympy_expr is None:
all_resolved = False
# print(f"DEBUG: O {instr_uid} esperando pin {pin}")
break # Salir del bucle for si una entrada no está lista
break # Salir si una dependencia no está lista
# Formatear término (añadir paréntesis si es necesario)
term = in_scl
if (" " in term or "AND" in term) and not (
term.startswith("(") and term.endswith(")")
):
term = f"({term})"
scl_parts.append(term)
# Optimización: No incluir FALSE en un OR
if sympy_expr != sympy.false:
sympy_parts.append(sympy_expr)
if not all_resolved:
return False # Esperar a que todas las entradas estén resueltas
return False # Esperar dependencias
# Construir la expresión OR
result_scl = "FALSE" # Valor por defecto si no hay entradas válidas (raro)
if scl_parts:
result_scl = " OR ".join(scl_parts)
# Simplificar si solo hay un término
if len(scl_parts) == 1:
result_scl = scl_parts[0]
# Quitar paréntesis redundantes si solo hay un término y está entre paréntesis
if result_scl.startswith("(") and result_scl.endswith(")"):
# Comprobar si los paréntesis son necesarios (contienen operadores de menor precedencia)
# Simplificación: quitar siempre si solo hay un término. Podría ser incorrecto en casos complejos.
# result_scl = result_scl[1:-1] # Comentado por seguridad
pass
# Construir la expresión OR de SymPy
result_sympy_expr = sympy.false # Valor por defecto si no hay entradas válidas o todas son FALSE
if sympy_parts:
# Usar sympy.Or para construir la expresión
result_sympy_expr = sympy.Or(*sympy_parts)
# Simplificar casos obvios como OR(X) -> X, OR(X, TRUE) -> TRUE
# simplify_logic aquí puede ser prematuro, mejor al final.
# Pero Or() podría simplificar automáticamente OR(X) -> X.
# Opcional: result_sympy_expr = sympy.simplify_logic(result_sympy_expr)
# Actualizar mapa SCL y la instrucción
# Guardar la expresión SymPy resultante en el mapa para 'out'
map_key_out = (network_id, instr_uid, "out")
scl_map[map_key_out] = result_scl
instruction["scl"] = (
f"// Logic O {instr_uid}: {result_scl}" # Comentario informativo
)
instruction["type"] = instr_type + SCL_SUFFIX
sympy_map[map_key_out] = result_sympy_expr
# Marcar como procesado, SCL principal es solo comentario
instruction["scl"] = f"// SymPy O: {result_sympy_expr}" # Comentario opcional
instruction["type"] = instr_type_original + SCL_SUFFIX
# La instrucción 'O' no tiene ENO propio, propaga el resultado por 'out'
return True
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la operación lógica O (OR)."""
return {'type_name': 'o', 'processor_func': process_o, 'priority': 1}
return {'type_name': 'o', 'processor_func': process_o, 'priority': 1}

View File

@ -1,59 +1,74 @@
# processors/process_rcoil.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_rcoil(instruction, network_id, scl_map, access_map, data ):
"""Genera SCL para Reset Coil (RCoil): IF condition THEN variable := FALSE; END_IF;"""
def process_rcoil(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data ):
"""Genera SCL para Reset Coil (RCoil), simplificando la condición."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
return False # Ya procesado o con error
instr_type_original = instruction.get("type", "RCoil")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener condición de entrada (RLO)
# Obtener condición de entrada (SymPy expr)
in_info = instruction["inputs"].get("in")
condition_scl = get_scl_representation(in_info, network_id, scl_map, access_map)
sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
# Obtener operando (variable a poner a FALSE)
operand_info = instruction["inputs"].get("operand")
variable_scl = get_scl_representation(operand_info, network_id, scl_map, access_map)
# Obtener operando (nombre SCL del destino)
target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # RCoil necesita destino explícito
# Verificar dependencias
if condition_scl is None or variable_scl is None:
return False # Dependencias no listas
# Verificar que el operando sea una variable
if not (operand_info and operand_info.get("type") == "variable"):
print(f"Error: RCoil {instr_uid} operando no es variable o falta info (Tipo: {operand_info.get('type')}).")
if sympy_expr_in is None: return False
if target_scl_name is None:
print(f"Error: RCoil {instr_uid} operando no es variable o falta info.")
instruction["scl"] = f"// ERROR: RCoil {instr_uid} operando no es variable."
instruction["type"] += "_error"
return True # Procesado con error
instruction["type"] = instr_type_original + "_error"
return True
# Formatear nombre de variable
variable_name_formatted = format_variable_name(variable_scl)
# No hacer nada si la condición es FALSE constante
if sympy_expr_in == sympy.false:
instruction["scl"] = f"// RCoil {instr_uid} con condición FALSE constante, optimizado."
instruction["type"] = instr_type_original + SCL_SUFFIX
return True
# Generar SCL
scl_core = f"{variable_name_formatted} := FALSE;"
scl_final = (
f"IF {condition_scl} THEN\n {scl_core}\nEND_IF;" if condition_scl != "TRUE" else scl_core
)
# Generar SCL Core (Reset)
scl_core = f"{target_scl_name} := FALSE;"
# Aplicar Condición IF si no es TRUE constante
scl_final = ""
if sympy_expr_in != sympy.true:
# Simplificar la condición ANTES de convertirla a SCL
try:
#simplified_expr = sympy.simplify_logic(sympy_expr_in, force=True)
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True)
except Exception as e:
print(f"Error simplifying condition for RCoil {instr_uid}: {e}")
simplified_expr = sympy_expr_in # Fallback
condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager)
# Evitar IF TRUE THEN...
if condition_scl == "TRUE":
scl_final = scl_core
else:
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
# Condición es TRUE constante
scl_final = scl_core
# Actualizar instrucción
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# RCoil no genera salida 'out' ni 'eno' significativas para propagar
return True
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# RCoil no tiene salida lógica para propagar en sympy_map
# --- Function code ends ---
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la bobina Reset (RCoil)."""
return {'type_name': 'rcoil', 'processor_func': process_rcoil, 'priority': 3}
return {'type_name': 'rcoil', 'processor_func': process_rcoil, 'priority': 3}

View File

@ -1,59 +1,74 @@
# processors/process_scoil.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
import re
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name
from .symbol_manager import SymbolManager
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_scoil(instruction, network_id, scl_map, access_map, data):
"""Genera SCL para Set Coil (SCoil): IF condition THEN variable := TRUE; END_IF;"""
def process_scoil(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""Genera SCL para Set Coil (SCoil), simplificando la condición."""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
return False # Ya procesado o con error
instr_type_original = instruction.get("type", "SCoil")
if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original:
return False
# Obtener condición de entrada (RLO)
# Obtener condición de entrada (SymPy expr)
in_info = instruction["inputs"].get("in")
condition_scl = get_scl_representation(in_info, network_id, scl_map, access_map)
sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
# Obtener operando (variable a poner a TRUE)
operand_info = instruction["inputs"].get("operand")
variable_scl = get_scl_representation(operand_info, network_id, scl_map, access_map)
# Obtener operando (nombre SCL del destino)
target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # SCoil necesita destino
# Verificar dependencias
if condition_scl is None or variable_scl is None:
return False # Dependencias no listas
# Verificar que el operando sea una variable
if not (operand_info and operand_info.get("type") == "variable"):
print(f"Error: SCoil {instr_uid} operando no es variable o falta info (Tipo: {operand_info.get('type')}).")
if sympy_expr_in is None: return False
if target_scl_name is None:
print(f"Error: SCoil {instr_uid} operando no es variable o falta info.")
instruction["scl"] = f"// ERROR: SCoil {instr_uid} operando no es variable."
instruction["type"] += "_error"
return True # Procesado con error
instruction["type"] = instr_type_original + "_error"
return True
# Formatear nombre de variable
variable_name_formatted = format_variable_name(variable_scl)
# No hacer nada si la condición es FALSE constante
if sympy_expr_in == sympy.false:
instruction["scl"] = f"// SCoil {instr_uid} con condición FALSE constante, optimizado."
instruction["type"] = instr_type_original + SCL_SUFFIX
return True
# Generar SCL
scl_core = f"{variable_name_formatted} := TRUE;"
scl_final = (
f"IF {condition_scl} THEN\n {scl_core}\nEND_IF;" if condition_scl != "TRUE" else scl_core
)
# Generar SCL Core (Set)
scl_core = f"{target_scl_name} := TRUE;"
# Aplicar Condición IF si no es TRUE constante
scl_final = ""
if sympy_expr_in != sympy.true:
# Simplificar la condición ANTES de convertirla a SCL
try:
#simplified_expr = sympy.simplify_logic(sympy_expr_in, force=True)
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True)
except Exception as e:
print(f"Error simplifying condition for SCoil {instr_uid}: {e}")
simplified_expr = sympy_expr_in # Fallback
condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager)
# Evitar IF TRUE THEN...
if condition_scl == "TRUE":
scl_final = scl_core
else:
indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()])
scl_final = f"IF {condition_scl} THEN\n{indented_core}\nEND_IF;"
else:
# Condición es TRUE constante
scl_final = scl_core
# Actualizar instrucción
instruction["scl"] = scl_final
instruction["type"] = instr_type + SCL_SUFFIX
# SCoil no genera salida 'out' ni 'eno' significativas para propagar
return True
instruction["scl"] = scl_final # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# SCoil no tiene salida lógica para propagar en sympy_map
# --- Function code ends ---
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para la bobina Set (SCoil)."""
return {'type_name': 'scoil', 'processor_func': process_scoil, 'priority': 3}
return {'type_name': 'scoil', 'processor_func': process_scoil, 'priority': 3}

View File

@ -1,73 +1,77 @@
# processors/process_sd.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_sd(instruction, network_id, scl_map, access_map, data):
def process_sd(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL para Temporizador On-Delay (Sd -> TON).
Requiere datos de instancia (DB o STAT/TEMP).
"""
instr_uid = instruction["instruction_uid"]
instr_type = "Sd" # Tipo original LAD
if instruction["type"].endswith(SCL_SUFFIX) or "_error" in instruction["type"]:
instr_type_original = "Sd" # Tipo original LAD
if instruction.get("type","").endswith(SCL_SUFFIX) or "_error" in instruction.get("type",""):
return False
# 1. Obtener Inputs: s (start), tv (time value)
# El pin 'r' (reset) no tiene equivalente directo en TON, se ignora aquí.
# 1. Obtener Inputs: s (start), tv (time value), timer (instance)
s_info = instruction["inputs"].get("s")
tv_info = instruction["inputs"].get("tv")
timer_instance_info = instruction["inputs"].get("timer") # Esperando que x1 lo extraiga
timer_instance_info = instruction["inputs"].get("timer")
scl_s = get_scl_representation(s_info, network_id, scl_map, access_map)
scl_tv = get_scl_representation(tv_info, network_id, scl_map, access_map)
scl_instance_name = get_scl_representation(timer_instance_info, network_id, scl_map, access_map)
sympy_s_expr = get_sympy_representation(s_info, network_id, sympy_map, symbol_manager)
# tv suele ser constante, pero lo obtenemos igual
sympy_or_const_tv = get_sympy_representation(tv_info, network_id, sympy_map, symbol_manager)
# Obtener el nombre de la INSTANCIA (no su valor)
instance_plc_name = extract_plc_variable_name(timer_instance_info)
if scl_s is None or scl_tv is None:
return False # Dependencias no listas
# Verificar dependencias
if sympy_s_expr is None or sympy_or_const_tv is None: return False
if instance_plc_name is None:
print(f"Error: Sd {instr_uid} sin variable de instancia 'timer'.")
instance_plc_name = f"#TON_INSTANCE_{instr_uid}" # Placeholder con error implícito
print(f"Advertencia: Usando placeholder '{instance_plc_name}'. ¡Declarar en SCL!")
# Podríamos marcar como error, pero intentamos generar algo
# instruction["type"] = instr_type_original + "_error"
# return True
# 2. Validar y obtener Nombre de Instancia
instance_name = None
if timer_instance_info and timer_instance_info.get("type") == "variable":
instance_name = scl_instance_name
elif timer_instance_info:
print(f"Advertencia: Pin 'timer' de {instr_type} UID {instr_uid} conectado a algo inesperado: {timer_instance_info.get('type')}")
instance_name = f"#TON_INSTANCE_{instr_uid}"
else:
instance_name = f"#TON_INSTANCE_{instr_uid}"
print(f"Advertencia: No se encontró conexión al pin 'timer' para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. ¡Revisar x1.py y XML!")
# Formatear nombre de instancia
instance_name_scl = format_variable_name(instance_plc_name)
# 3. Formatear entradas si son variables
scl_s_formatted = format_variable_name(scl_s) if s_info and s_info.get("type") == "variable" else scl_s
scl_tv_formatted = format_variable_name(scl_tv) if tv_info and tv_info.get("type") == "variable" else scl_tv
# Convertir entradas SymPy/Constante a SCL strings
s_scl = sympy_expr_to_scl(sympy_s_expr, symbol_manager)
tv_scl = sympy_expr_to_scl(sympy_or_const_tv, symbol_manager)
# 4. Generar la llamada SCL (TON usa IN, PT, Q, ET)
scl_call = f"{instance_name}(IN := {scl_s_formatted}, PT := {scl_tv_formatted}); // TODO: Declarar {instance_name} : TON; en VAR_STAT o VAR"
# Generar la llamada SCL (TON usa IN, PT)
# Ignoramos 'r' (reset) de Sd
scl_call = f"{instance_name_scl}(IN := {s_scl}, PT := {tv_scl}); // TODO: Declarar {instance_name_scl} : TON;"
instruction["scl"] = scl_call
instruction["type"] = instr_type + SCL_SUFFIX
# Actualizar instrucción
instruction["scl"] = scl_call # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# 5. Actualizar scl_map para las salidas Q y RT (mapeado a ET de TON)
# 7. Actualizar sympy_map para las salidas Q y RT
map_key_q = (network_id, instr_uid, "q")
scl_map[map_key_q] = f"{instance_name}.Q"
q_output_scl_access = f"{instance_name_scl}.Q" # SCL string to access output
# *** GET/CREATE AND STORE SYMBOL for boolean output Q ***
sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access)
if sympy_q_symbol:
sympy_map[map_key_q] = sympy_q_symbol # STORE THE SYMBOL OBJECT
else:
print(f"Error: Could not create symbol for {q_output_scl_access} in Sd {instr_uid}")
sympy_map[map_key_q] = None # Indicate error/unresolved
map_key_rt = (network_id, instr_uid, "rt")
scl_map[map_key_rt] = f"{instance_name}.ET"
# ET is TIME, store SCL access string
sympy_map[map_key_rt] = f"{instance_name_scl}.ET"
return True
# --- NUEVO: Procesador de Agrupación (Refinado) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el temporizador On-Delay (Sd -> TON)."""
# Asumiendo que el tipo en el JSON es 'Sd'
return {'type_name': 'sd', 'processor_func': process_sd, 'priority': 5}
return {'type_name': 'sd', 'processor_func': process_sd, 'priority': 5}

View File

@ -1,91 +1,112 @@
# processors/process_se.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_se(instruction, network_id, scl_map, access_map, data):
def process_se(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL para Temporizador de Pulso (Se -> TP).
Requiere datos de instancia (DB o STAT/TEMP).
Genera SCL para Temporizador de Pulso (Se -> TP) o SdCoil (-> TON).
Usa SymPy para entradas y almacena Symbol para salida Q.
"""
instr_uid = instruction["instruction_uid"]
instr_type = "Se" # Tipo original LAD
if instruction["type"].endswith(SCL_SUFFIX) or "_error" in instruction["type"]:
# Obtener tipo original (antes de añadir sufijo) para determinar comportamiento
instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX,"").replace("_error","") # Se o SdCoil
current_type = instruction.get("type","") # Tipo actual para chequeo inicial
if current_type.endswith(SCL_SUFFIX) or "_error" in current_type:
return False
# 1. Obtener Inputs: s (start), tv (time value)
# El pin 'r' (reset) no tiene equivalente directo en TP, se ignora aquí.
s_info = instruction["inputs"].get("s")
tv_info = instruction["inputs"].get("tv")
timer_instance_info = instruction["inputs"].get("timer") # Esperando que x1 lo extraiga
# Determinar el tipo de instrucción SCL y pines de entrada/salida correctos
scl_timer_type = "TP"
pin_in = "s" # Pin de entrada para Se
pin_time = "tv" # Pin de valor de tiempo para Se
pin_instance = "timer" # Pin donde se conecta la instancia para Se
pin_out_q = "q" # Pin de salida Q para Se
pin_out_time = "rt" # Pin de tiempo restante para Se -> TP.ET
scl_s = get_scl_representation(s_info, network_id, scl_map, access_map)
scl_tv = get_scl_representation(tv_info, network_id, scl_map, access_map)
# Obtenemos el nombre de la variable instancia, crucial!
scl_instance_name = get_scl_representation(timer_instance_info, network_id, scl_map, access_map)
# Ajustar pines si el tipo original era SdCoil
if instr_type_original == "SdCoil":
scl_timer_type = "TON" # SdCoil es funcionalmente un TON
pin_in = "in" # SdCoil usa 'in'
pin_time = "value" # SdCoil usa 'value'
pin_instance = "operand" # SdCoil usa 'operand' como instancia/variable de salida
pin_out_q = "out" # SdCoil usa 'out' como pin de salida Q
pin_out_time = None # SdCoil no tiene salida ET explícita
if scl_s is None or scl_tv is None:
return False # Dependencias no listas
# 1. Obtener Inputs usando los nombres de pin correctos
s_info = instruction["inputs"].get(pin_in)
tv_info = instruction["inputs"].get(pin_time)
timer_instance_info = instruction["inputs"].get(pin_instance)
# 2. Validar y obtener Nombre de Instancia
instance_name = None
if timer_instance_info and timer_instance_info.get("type") == "variable":
instance_name = scl_instance_name # Ya debería estar formateado por get_scl_repr
elif timer_instance_info: # Si está conectado pero no es variable directa? Raro.
print(f"Advertencia: Pin 'timer' de {instr_type} UID {instr_uid} conectado a algo inesperado: {timer_instance_info.get('type')}")
instance_name = f"#TP_INSTANCE_{instr_uid}" # Usar placeholder
else: # Si no hay pin 'timer' conectado (no debería pasar si x1 funciona)
instance_name = f"#TP_INSTANCE_{instr_uid}" # Usar placeholder
print(f"Advertencia: No se encontró conexión al pin 'timer' para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. ¡Revisar x1.py y XML!")
# Obtener representaciones (SymPy o Constante/String)
sympy_s_expr = get_sympy_representation(s_info, network_id, sympy_map, symbol_manager)
sympy_or_const_tv = get_sympy_representation(tv_info, network_id, sympy_map, symbol_manager)
# Obtener el nombre PLC original de la INSTANCIA
instance_plc_name = extract_plc_variable_name(timer_instance_info)
# 3. Formatear entradas si son variables (aunque get_scl_representation ya debería hacerlo)
scl_s_formatted = format_variable_name(scl_s) if s_info and s_info.get("type") == "variable" else scl_s
scl_tv_formatted = format_variable_name(scl_tv) if tv_info and tv_info.get("type") == "variable" else scl_tv
# 2. Verificar dependencias
if sympy_s_expr is None or sympy_or_const_tv is None:
# print(f"DEBUG {instr_type_original} {instr_uid}: Input/TV dependency not ready")
return False
if instance_plc_name is None:
print(f"Error: {instr_type_original} {instr_uid} sin variable de instancia en pin '{pin_instance}'.")
instance_plc_name = f"#{scl_timer_type}_INSTANCE_{instr_uid}" # Placeholder
print(f"Advertencia: Usando placeholder '{instance_plc_name}'. ¡Declarar en SCL!")
# 4. Generar la llamada SCL (TP usa IN, PT, Q, ET)
scl_call = f"{instance_name}(IN := {scl_s_formatted}, PT := {scl_tv_formatted}); // TODO: Declarar {instance_name} : TP; en VAR_STAT o VAR"
# 3. Formatear nombre de instancia para SCL
instance_name_scl = format_variable_name(instance_plc_name)
# 4. Convertir entradas SymPy/Constante a SCL strings (simplificando la entrada IN)
try:
# Simplificar la expresión de entrada booleana
simplified_s_expr = sympy.simplify_logic(sympy_s_expr, force=True)
simplified_s_expr = sympy.logic.boolalg.to_dnf(sympy_s_expr, simplify=True)
except Exception as e:
print(f"Error simplifying '{pin_in}' input for {instr_type_original} {instr_uid}: {e}")
simplified_s_expr = sympy_s_expr # Fallback
s_scl = sympy_expr_to_scl(simplified_s_expr, symbol_manager)
# tv normalmente es constante, sympy_expr_to_scl debería manejarlo
tv_scl = sympy_expr_to_scl(sympy_or_const_tv, symbol_manager)
# 5. Generar la llamada SCL
# Ignoramos 'r' (reset) de Se si existiera
scl_call = f"{instance_name_scl}(IN := {s_scl}, PT := {tv_scl}); // TODO: Declarar {instance_name_scl} : {scl_timer_type};"
# 6. Actualizar instrucción con el SCL final
instruction["scl"] = scl_call
instruction["type"] = instr_type + SCL_SUFFIX
instruction["type"] = instr_type_original + SCL_SUFFIX # Marcar como procesado
# 5. Actualizar scl_map usando los nombres de pin ORIGINALES mapeados si existen
output_pin_mapping_reverse = {v: k for k, v in instruction.get("_output_pin_mapping", {}).items()} # Necesitaríamos guardar el mapeo en x1
# 7. Actualizar sympy_map para las salidas (Q y ET si aplica)
# Usar los nombres de pin originales determinados al principio
map_key_q = (network_id, instr_uid, pin_out_q) # pin_out_q es 'q' o 'out'
q_output_scl_access = f"{instance_name_scl}.Q" # Siempre accedemos a .Q del FB SCL
# *** OBTENER/CREAR Y ALMACENAR SYMBOL para la salida booleana Q ***
sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access)
if sympy_q_symbol:
sympy_map[map_key_q] = sympy_q_symbol # Almacenar el OBJETO SYMBOL
else:
# Manejar error si no se pudo crear el símbolo
print(f"Error: No se pudo crear símbolo para {q_output_scl_access} en {instr_type_original} {instr_uid}")
sympy_map[map_key_q] = None # Indicar error/irresoluble
q_original_pin = "q" # Default
rt_original_pin = "rt" # Default
# Intentar encontrar los pines originales si x1 guardó el mapeo (MEJORA NECESARIA en x1)
# Por ahora, para SdCoil que mapeaba out->q, usaremos 'out' directamente
if instruction.get("type") == "Se_scl" and instruction.get("original_type") == "SdCoil": # Necesitamos guardar original_type en x1
q_original_pin = "out"
# rt no existe en SdCoil
map_key_q = (network_id, instr_uid, q_original_pin)
scl_map[map_key_q] = f"{instance_name}.Q"
if rt_original_pin: # Solo añadir rt si corresponde
map_key_rt = (network_id, instr_uid, rt_original_pin)
scl_map[map_key_rt] = f"{instance_name}.ET"
# Almacenar ET solo si corresponde (para Se, no para SdCoil)
if pin_out_time: # pin_out_time es 'rt' o None
map_key_rt = (network_id, instr_uid, pin_out_time)
# ET es TIME, no booleano. Almacenar el string SCL de acceso está bien.
sympy_map[map_key_rt] = f"{instance_name_scl}.ET" # Salida ET del FB SCL
return True
# --- Procesador para Sd (On-Delay Timer -> TON SCL) ---
# --- Function code ends ---
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para el temporizador de Pulso (Se -> TP) y maneja SdCoil."""
# Asumiendo que el tipo en el JSON es 'Se'
# Y que SdCoil también se mapea aquí según el análisis previo
"""Devuelve la info para Se (-> TP) y SdCoil (-> TON, manejado aquí)."""
return [
{'type_name': 'se', 'processor_func': process_se, 'priority': 5},
{'type_name': 'sdcoil', 'processor_func': process_se, 'priority': 5} # SdCoil también se procesa como TP
]
# Asegurarse que x1.py mapea SdCoil a este procesador o a uno específico
{'type_name': 'sdcoil', 'processor_func': process_se, 'priority': 5}
]

View File

@ -1,70 +1,85 @@
# processors/process_timer.py
# -*- coding: utf-8 -*-
from .processor_utils import get_scl_representation, format_variable_name,get_target_scl_name
import sympy
import traceback
# Usar las nuevas utilidades
from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name
from .symbol_manager import SymbolManager, extract_plc_variable_name
SCL_SUFFIX = "_sympy_processed"
# TODO: Import necessary functions from processor_utils
# Example: from .processor_utils import get_scl_representation, format_variable_name
# Or: import processors.processor_utils as utils
# TODO: Define constants if needed (e.g., SCL_SUFFIX) or import them
SCL_SUFFIX = "_scl"
# --- Function code starts ---
def process_timer(instruction, network_id, scl_map, access_map, data):
def process_timer(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data):
"""
Genera SCL para Temporizadores (TON, TOF).
Requiere datos de instancia (DB o STAT).
Genera SCL para Temporizadores (TON, TOF) directamente.
Requiere datos de instancia.
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"] # Será "TON" o "TOF"
if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type:
instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX,"").replace("_error","") # TON o TOF
if instruction.get("type","").endswith(SCL_SUFFIX) or "_error" in instruction.get("type",""):
return False
# 1. Obtener Inputs
in_info = instruction["inputs"].get("IN") # Entrada booleana
pt_info = instruction["inputs"].get("PT") # Preset Time (Tipo TIME)
scl_in = get_scl_representation(in_info, network_id, scl_map, access_map)
scl_pt = get_scl_representation(pt_info, network_id, scl_map, access_map)
scl_timer_type = instr_type_original.upper()
if scl_timer_type not in ["TON", "TOF"]:
instruction["scl"] = f"// ERROR: Tipo de temporizador directo no soportado: {instr_type_original}"
instruction["type"] = instr_type_original + "_error"
return True
if scl_in is None or scl_pt is None:
return False # Dependencias no listas
# 1. Obtener Inputs: IN, PT, y nombre de instancia (implícito o explícito)
in_info = instruction["inputs"].get("IN")
pt_info = instruction["inputs"].get("PT")
# Buscar instancia: ¿está en inputs? ¿o como instance_db?
instance_plc_name = instruction.get("instance_db") # Buscar primero aquí
if not instance_plc_name:
# Si no, buscar un input llamado 'timer' o similar? No estándar.
# Asumir que debe estar declarado como STAT si no hay instance_db
instance_plc_name = instruction.get("instance_name") # Nombre directo?
if not instance_plc_name:
instance_plc_name = f"#{scl_timer_type}_INSTANCE_{instr_uid}" # Placeholder final
print(f"Advertencia: No se encontró nombre/instancia para {instr_type_original} UID {instr_uid}. Usando placeholder '{instance_plc_name}'.")
# 2. Obtener Nombre de Instancia (NECESITA MEJORA EN x1.py)
instance_name = instruction.get("instance_db") # Reutilizar campo si x1 lo llena
if not instance_name:
# Generar placeholder si x1 no extrajo la instancia para Part
instance_name = f"#TIMER_INSTANCE_{instr_uid}" # Placeholder para VAR_TEMP o VAR_STAT
print(f"Advertencia: No se encontró instancia para {instr_type} UID {instr_uid}. Usando placeholder '{instance_name}'. Ajustar x1.py y declarar en x3.py.")
sympy_in_expr = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager)
sympy_or_const_pt = get_sympy_representation(pt_info, network_id, sympy_map, symbol_manager)
# Verificar dependencias
if sympy_in_expr is None or sympy_or_const_pt is None or instance_plc_name is None:
return False
# Formatear nombre de instancia
instance_name_scl = format_variable_name(instance_plc_name)
# Convertir entradas SymPy/Constante a SCL strings
in_scl = sympy_expr_to_scl(sympy_in_expr, symbol_manager)
pt_scl = sympy_expr_to_scl(sympy_or_const_pt, symbol_manager)
# Generar la llamada SCL
scl_call = f"{instance_name_scl}(IN := {in_scl}, PT := {pt_scl}); // TODO: Declarar {instance_name_scl} : {scl_timer_type};"
# Actualizar instrucción
instruction["scl"] = scl_call # SCL final generado
instruction["type"] = instr_type_original + SCL_SUFFIX
# 7. Actualizar sympy_map para las salidas Q y ET
map_key_q = (network_id, instr_uid, "Q") # Pin estándar SCL
# *** Store SymPy Symbol for boolean output Q ***
q_output_scl_access = f"{instance_name_scl}.Q" # String for SCL access
sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access) # Get/Create Symbol
if sympy_q_symbol:
sympy_map[map_key_q] = sympy_q_symbol # Store the SYMBOL
else:
instance_name = format_variable_name(instance_name) # Limpiar si viene de x1
print(f"Error: Could not create symbol for {q_output_scl_access} in {instr_type_original} {instr_uid}")
sympy_map[map_key_q] = None
# 3. Formatear entradas si son variables
scl_in_formatted = format_variable_name(scl_in) if in_info and in_info.get("type") == "variable" else scl_in
scl_pt_formatted = format_variable_name(scl_pt) if pt_info and pt_info.get("type") == "variable" else scl_pt
# 4. Generar la llamada SCL
# Nota: Las salidas Q y ET se acceden directamente desde la instancia.
scl_call = f"{instance_name}(IN := {scl_in_formatted}, PT := {scl_pt_formatted}); // TODO: Declarar {instance_name} : {instr_type}; en VAR_STAT o VAR"
instruction["scl"] = scl_call
instruction["type"] = instr_type + SCL_SUFFIX
# 5. Actualizar scl_map para las salidas Q y ET
map_key_q = (network_id, instr_uid, "Q")
scl_map[map_key_q] = f"{instance_name}.Q"
map_key_et = (network_id, instr_uid, "ET")
scl_map[map_key_et] = f"{instance_name}.ET"
# TON/TOF no tienen un pin ENO estándar en LAD/FBD que se mapee directamente
map_key_et = (network_id, instr_uid, "ET") # Pin estándar SCL
# ET is TIME, store SCL access string
sympy_map[map_key_et] = f"{instance_name_scl}.ET"
return True
# --- Processor Information Function ---
def get_processor_info():
"""Devuelve la información para los temporizadores TON y TOF (si se usan genéricamente)."""
# Esta función manejaría tipos TON/TOF si aparecieran directamente en el JSON
# y no fueran manejados por process_sd/process_se (que es lo más común desde LAD).
# Incluir por si acaso o si la conversión inicial genera TON/TOF directamente.
"""Devuelve info para TON y TOF directos."""
return [
{'type_name': 'ton', 'processor_func': process_timer, 'priority': 5},
{'type_name': 'tof', 'processor_func': process_timer, 'priority': 5}
]
]

View File

@ -1,15 +1,11 @@
# -*- coding: utf-8 -*-
# /processors/processor_utils.py
# Procesador de utilidades para el procesamiento de archivos XML de Simatic
# processors/processor_utils.py
import re
import sympy
from .symbol_manager import SymbolManager, extract_plc_variable_name
# --- Copia aquí las funciones auxiliares ---
# get_scl_representation, format_variable_name,
# generate_temp_var_name, get_target_scl_name
# Asegúrate de que no dependen de variables globales de x2_process.py
# (como 'data' o 'network_access_maps' - esas se pasarán como argumentos)
SCL_SUFFIX = "_sympy_processed" # <<< AÑADE ESTA LÍNEA
# Ejemplo de una función (asegúrate de copiar todas las necesarias)
def format_variable_name(name):
"""Limpia el nombre de la variable para SCL."""
if not name:
@ -25,8 +21,116 @@ def format_variable_name(name):
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
return prefix + name
# --- Helper Functions ---
# (get_scl_representation, format_variable_name, generate_temp_var_name, get_target_scl_name - sin cambios)
def get_sympy_representation(source_info, network_id, sympy_map, symbol_manager):
"""Gets the SymPy expression object representing the source."""
if not source_info:
print("Warning: get_sympy_representation called with None source_info.")
return None # Or raise error
# Handle lists (OR branches) - Recursively call and combine with sympy.Or
if isinstance(source_info, list):
sympy_parts = []
all_resolved = True
for sub_source in source_info:
sub_sympy = get_sympy_representation(sub_source, network_id, sympy_map, symbol_manager)
if sub_sympy is None:
all_resolved = False
break
sympy_parts.append(sub_sympy)
if not all_resolved:
return None
if not sympy_parts:
return sympy.false # Empty OR is false
# Return sympy.Or only if there are multiple parts
return sympy.Or(*sympy_parts) if len(sympy_parts) > 1 else sympy_parts[0]
# Handle single source dictionary
source_type = source_info.get("type")
if source_type == "powerrail":
return sympy.true
elif source_type == "variable":
plc_name = extract_plc_variable_name(source_info)
if plc_name:
return symbol_manager.get_symbol(plc_name)
else:
print(f"Error: Variable source without name: {source_info}")
return None # Error case
elif source_type == "constant":
# Represent constants directly if possible, otherwise maybe as symbols?
# For boolean simplification, only TRUE/FALSE matter significantly.
dtype = str(source_info.get("datatype", "")).upper()
value = source_info.get("value")
if dtype == "BOOL":
return sympy.true if str(value).upper() == "TRUE" else sympy.false
else:
# For simplification, treat non-boolean constants as opaque symbols?
# Or just return their string representation if they won't be simplified anyway?
# Let's return their string value for now, processors will handle it.
# This might need refinement if constants need symbolic handling.
return str(value) # Or maybe symbol_manager.get_symbol(str(value))?
elif source_type == "connection":
map_key = (
network_id,
source_info.get("source_instruction_uid"),
source_info.get("source_pin"),
)
# Return the SymPy object from the map
return sympy_map.get(map_key) # Returns None if not found (dependency not ready)
elif source_type == "unknown_source":
print(f"Warning: Referring to unknown source UID: {source_info.get('uid')}")
return None # Cannot resolve
else:
print(f"Warning: Unknown source type: {source_info}")
return None # Cannot resolve
def sympy_expr_to_scl(expr, symbol_manager, format_prec=5):
"""Converts a SymPy expression to an SCL string using the symbol map."""
if expr is None: return "/* ERROR: None expression */"
if expr == sympy.true: return "TRUE"
if expr == sympy.false: return "FALSE"
# Use sympy's string printer with custom settings if needed
# For boolean, standard printing might be okay, but need to substitute symbols
try:
# Get the inverse map (py_id -> plc_name)
inverse_map = symbol_manager.get_inverse_map()
# Substitute symbols back to their py_id strings first
# Need to handle the structure (And, Or, Not)
scl_str = sympy.sstr(expr, order=None) # Basic string representation
# Now, carefully replace py_id back to PLC names using regex
# Sort keys by length descending to replace longer IDs first
for py_id in sorted(inverse_map.keys(), key=len, reverse=True):
# Use word boundaries to avoid replacing parts of other IDs
scl_str = re.sub(r'\b' + re.escape(py_id) + r'\b', inverse_map[py_id], scl_str)
# Replace SymPy operators/functions with SCL equivalents
scl_str = scl_str.replace('&', ' AND ')
scl_str = scl_str.replace('|', ' OR ')
scl_str = scl_str.replace('^', ' XOR ') # If XOR is used
scl_str = scl_str.replace('~', 'NOT ')
# Add spaces around operators if needed after substitution
scl_str = re.sub(r'AND', ' AND ', scl_str)
scl_str = re.sub(r'OR', ' OR ', scl_str)
scl_str = re.sub(r'XOR', ' XOR ', scl_str)
scl_str = re.sub(r'NOT', 'NOT ', scl_str) # Space after NOT
# Clean up potential double spaces, etc.
scl_str = re.sub(r'\s+', ' ', scl_str).strip()
# Handle parentheses potentially added by sstr - maybe remove redundant ones?
# Be careful not to break operator precedence.
return scl_str
except Exception as e:
print(f"Error converting SymPy expr '{expr}' to SCL: {e}")
traceback.print_exc()
return f"/* ERROR converting SymPy: {expr} */"
def get_scl_representation(source_info, network_id, scl_map, access_map):
if not source_info:
return None
@ -154,56 +258,53 @@ def generate_temp_var_name(network_id, instr_uid, pin_name):
# Usar # para variables temporales SCL estándar
return f"#_temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}"
def get_target_scl_name(instruction, output_pin_name, network_id, default_to_temp=True):
def get_target_scl_name(instruction, pin_name, network_id, default_to_temp=True):
"""Gets the SCL formatted name for a target variable.
Handles instruction outputs AND specific inputs like Coil operand.
"""
instr_uid = instruction["instruction_uid"]
output_pin_data = instruction["outputs"].get(output_pin_name)
# Ahora SCL_SUFFIX está definido en este módulo
instr_type_upper = instruction.get("type", "").upper().replace(SCL_SUFFIX.upper(), "").replace("_ERROR", "") # Check original type
target_info = None
# Special handling for inputs that represent the target variable
if instr_type_upper in ["COIL", "SCOIL", "RCOIL"] and pin_name == "operand":
target_info = instruction.get("inputs", {}).get("operand")
# Add other instructions where input pin == target if necessary
# elif instr_type_upper == "XYZ" and pin_name == "some_input_target_pin":
# target_info = instruction.get("inputs", {}).get(pin_name)
else:
# Default: Assume pin_name refers to an output pin
output_pin_data = instruction.get("outputs", {}).get(pin_name)
# Check if it's a list and has one connection (standard case)
if (output_pin_data and isinstance(output_pin_data, list) and len(output_pin_data) == 1):
target_info = output_pin_data[0]
# Add handling for direct output assignment if your JSON structure supports it
target_scl = None
if (
output_pin_data
and isinstance(output_pin_data, list)
and len(output_pin_data) == 1
):
dest_access = output_pin_data[0]
if dest_access.get("type") == "variable":
target_scl = dest_access.get("name")
if target_scl:
target_scl = format_variable_name(target_scl) # Formatear nombre
if target_info:
if target_info.get("type") == "variable":
plc_name = target_info.get("name")
if plc_name:
target_scl = format_variable_name(plc_name) # Use existing util
else:
print(
f"Error: Var destino {instr_uid}.{output_pin_name} sin nombre (UID: {dest_access.get('uid')}). {'Usando temp.' if default_to_temp else 'Ignorando.'}"
)
target_scl = (
generate_temp_var_name(network_id, instr_uid, output_pin_name)
if default_to_temp
else None
)
elif dest_access.get("type") == "constant":
print(
f"Advertencia: Instr {instr_uid} escribe en const UID {dest_access.get('uid')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}"
)
target_scl = (
generate_temp_var_name(network_id, instr_uid, output_pin_name)
if default_to_temp
else None
)
else:
print(
f"Advertencia: Destino {instr_uid}.{output_pin_name} no es var/const: {dest_access.get('type')}. {'Usando temp.' if default_to_temp else 'Ignorando.'}"
)
target_scl = (
generate_temp_var_name(network_id, instr_uid, output_pin_name)
if default_to_temp
else None
)
print(f"Error: Target variable for {instr_uid}.{pin_name} has no name (UID: {target_info.get('uid')}).")
elif target_info.get("type") == "constant":
print(f"Advertencia: Attempt to write to constant target {instr_uid}.{pin_name} (UID: {target_info.get('uid')}).")
# else: # Handle other target types if needed
# print(f"Advertencia: Target {instr_uid}.{pin_name} is not a variable: {target_info.get('type')}.")
# else: # No target info found for the specified pin
# print(f"DEBUG: No target info found for {instr_uid}.{pin_name}")
pass
# Handle default_to_temp logic
if target_scl:
return target_scl
elif default_to_temp:
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name)
# Si target_scl sigue siendo None y no se debe usar temp, devolver None
if target_scl is None and not default_to_temp:
# Generate temp only if no explicit target was found AND default is allowed
print(f"INFO: Generating temp var for {instr_uid}.{pin_name}") # Be informative
return generate_temp_var_name(network_id, instr_uid, pin_name)
else:
# No target found and default temps not allowed
return None
# Si target_scl es None pero sí se permite temp, generar uno ahora
if target_scl is None and default_to_temp:
target_scl = generate_temp_var_name(network_id, instr_uid, output_pin_name)
return target_scl

View File

@ -0,0 +1,58 @@
# processors/symbol_manager.py
import sympy
import re
class SymbolManager:
def __init__(self):
# plc_name -> py_id (e.g., '"DB".Var' -> 'v0_')
self.plc_to_py_id = {}
# py_id -> Symbol object (e.g., 'v0_' -> sympy.Symbol('v0_'))
self.py_id_to_symbol = {}
# py_id -> plc_name (e.g., 'v0_' -> '"DB".Var') - Inverse mapping
self.py_id_to_plc = {}
self.counter = 0
# Pre-define common keywords/constants to avoid mapping them
self.reserved_names = {"TRUE", "FALSE"} # Add others if needed
def _generate_py_id(self):
py_id = f"v{self.counter}_"
self.counter += 1
# Extremely unlikely collision, but check anyway
while py_id in self.py_id_to_symbol:
py_id = f"v{self.counter}_"
self.counter += 1
return py_id
def get_symbol(self, plc_var_name):
"""Gets/Creates a SymPy Symbol for a PLC variable name."""
if plc_var_name is None:
print("Warning: Attempted to get symbol for None PLC name.")
return None # Or handle error appropriately
if plc_var_name.upper() in self.reserved_names:
print(f"Warning: Attempted to create symbol for reserved name: {plc_var_name}")
return None # Or handle differently (e.g., return sympy.true/false?)
if plc_var_name not in self.plc_to_py_id:
py_id = self._generate_py_id()
self.plc_to_py_id[plc_var_name] = py_id
self.py_id_to_plc[py_id] = plc_var_name
self.py_id_to_symbol[py_id] = sympy.symbols(py_id)
# print(f"DEBUG SymbolManager: Created {py_id} -> {plc_var_name}") # Debug
else:
py_id = self.plc_to_py_id[plc_var_name]
return self.py_id_to_symbol.get(py_id)
def get_plc_name(self, py_id):
"""Gets the original PLC name from a py_id."""
return self.py_id_to_plc.get(py_id)
def get_inverse_map(self):
"""Returns the map needed for postprocessing (py_id -> plc_name)."""
return self.py_id_to_plc.copy()
# Helper function to extract PLC variable name from JSON operand info
def extract_plc_variable_name(operand_info):
if operand_info and operand_info.get("type") == "variable":
return operand_info.get("name")
return None # Not a variable or info missing

View File

@ -7,167 +7,141 @@ import traceback
import re
import importlib
import sys
import sympy # Import sympy
# Import necessary components from processors directory
from processors.processor_utils import (
format_variable_name, # Keep if used outside processors
sympy_expr_to_scl, # Needed for IF grouping and maybe others
# get_target_scl_name might be used here? Unlikely.
)
from processors.symbol_manager import SymbolManager # Import the manager
# --- Constantes y Configuración ---
SCL_SUFFIX = "_scl"
# SCL_SUFFIX = "_scl" # Old suffix
SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method
GROUPED_COMMENT = "// Logic included in grouped IF"
SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful
# Global data variable
data = {}
def process_group_ifs(instruction, network_id, scl_map, access_map, data):
def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data):
"""
Busca instrucciones que generan condiciones (Contact, O, Eq, PBox, etc.) ya procesadas
y, si habilitan un grupo (>1) de bloques funcionales (Move, Add, Call, etc.),
construye el bloque IF agrupado.
Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map)
y, si habilitan un grupo (>1) de bloques funcionales (con SCL ya generado),
construye el bloque IF agrupado CON LA CONDICIÓN SIMPLIFICADA.
Modifica el campo 'scl' de la instrucción generadora de condición.
"""
instr_uid = instruction["instruction_uid"]
instr_type = instruction["type"]
instr_type_original = instr_type.replace("_scl", "").replace("_error", "")
instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "")
made_change = False
# Solo actuar sobre generadores de condición ya procesados (_scl)
# y que no sean ellos mismos errores o ya agrupados por otro IF
# Check if this instruction *could* generate a condition suitable for grouping
# It must have been processed by the new SymPy method
if (
not instr_type.endswith("_scl")
or "_error" in instr_type
not instruction.get("type", "").endswith(SCL_SUFFIX) # Check if processed by new method
or "_error" in instruction.get("type", "")
or instruction.get("grouped", False)
or instr_type_original
not in [
"Contact",
"O",
"Eq",
"Ne",
"Gt",
"Lt",
"Ge",
"Le",
"PBox",
"And",
"Xor",
or instr_type_original not in [ # Original types that produce boolean results
"Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "PBox", "NBox", "And", "Xor", "Not" # Add others like comparison
]
): # Añadir más si es necesario
):
return False
# Evitar reagrupar si ya se hizo (comprobando si SCL ya es un IF complejo)
# Avoid reagruping if SCL already contains a complex IF (less likely now)
current_scl = instruction.get("scl", "")
if current_scl.strip().startswith("IF") and "END_IF;" in current_scl:
# print(f"DEBUG Group: {instr_uid} ya tiene IF complejo, saltando agrupación.")
return False
# Ignorar comentarios simples que empiezan por IF
if current_scl.strip().startswith("//") and "IF" in current_scl:
if current_scl.strip().startswith("IF") and "END_IF;" in current_scl and GROUPED_COMMENT not in current_scl:
return False
# Obtener la condición generada por esta instrucción (debería estar en scl_map['out'])
# *** Get the SymPy expression for the condition ***
map_key_out = (network_id, instr_uid, "out")
condition_scl = scl_map.get(map_key_out)
sympy_condition_expr = sympy_map.get(map_key_out)
# No agrupar para condiciones triviales, no encontradas o ya agrupadas
if condition_scl is None or condition_scl in ["TRUE", "FALSE"]:
# No SymPy expression found or trivial conditions
if sympy_condition_expr is None or sympy_condition_expr in [sympy.true, sympy.false]:
return False
# Encontrar todos los bloques funcionales habilitados DIRECTAMENTE por esta condición
grouped_instructions_cores = [] # Lista de SCL 'core' de los consumidores
consumer_instr_list = [] # Lista de instrucciones consumidoras
network_logic = next(
(net["logic"] for net in data["networks"] if net["id"] == network_id), []
)
if not network_logic:
return False
# --- Find consumer instructions (logic similar to before) ---
grouped_instructions_cores = []
consumer_instr_list = []
network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), [])
if not network_logic: return False
# Identificar los tipos de instrucciones que queremos agrupar (bloques funcionales)
groupable_types = [
"Move",
"Add",
"Sub",
"Mul",
"Div",
"Mod",
"Convert",
"Call_FC",
"Call_FB",
] # Añadir más si es necesario
groupable_types = [ # Types whose *final SCL* we want to group
"Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert",
"Call_FC", "Call_FB", # Assuming these generate final SCL in their processors now
# SCoil/RCoil might also be groupable if their SCL is final assignment
"SCoil", "RCoil"
]
for consumer_instr in network_logic:
consumer_uid = consumer_instr["instruction_uid"]
# Saltar si ya está agrupado por otra condición o es él mismo
if consumer_instr.get("grouped", False) or consumer_uid == instr_uid:
continue
consumer_en = consumer_instr.get("inputs", {}).get("en")
consumer_type = consumer_instr.get("type", "") # Tipo actual (_scl o no)
consumer_type_original = consumer_type.replace("_scl", "").replace("_error", "")
consumer_type = consumer_instr.get("type", "") # Current type suffix matters
consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace("_error", "")
# ¿Está la entrada 'en' del consumidor conectada a nuestra salida 'out'?
is_enabled_by_us = False
if (
isinstance(consumer_en, dict)
and consumer_en.get("type") == "connection"
and consumer_en.get("source_instruction_uid") == instr_uid
and consumer_en.get("source_pin")
== "out" # Condición viene de 'out' del generador
):
if ( isinstance(consumer_en, dict) and consumer_en.get("type") == "connection" and
consumer_en.get("source_instruction_uid") == instr_uid and
consumer_en.get("source_pin") == "out"):
is_enabled_by_us = True
# ¿Es un tipo de instrucción agrupable y ya procesado (tiene SCL)?
if (
is_enabled_by_us
and consumer_type.endswith("_scl")
and consumer_type_original in groupable_types
):
# Check if consumer is groupable AND has its final SCL generated
# The suffix check needs adjustment based on how terminating processors set it.
# Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix.
if ( is_enabled_by_us and consumer_type.endswith(SCL_SUFFIX) and # Or a specific "final_scl" suffix
consumer_type_original in groupable_types ):
consumer_scl = consumer_instr.get("scl", "")
# Extraer el SCL core (la parte DENTRO del IF o la línea única si no había IF)
# Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now)
core_scl = None
if consumer_scl.strip().startswith("IF"):
# Extraer todo entre THEN y END_IF;
match = re.search(
r"IF\s+.*\s+THEN\s*(.*?)\s*END_IF;",
consumer_scl,
re.DOTALL | re.IGNORECASE,
)
if match:
core_scl = match.group(1).strip()
elif consumer_scl and not consumer_scl.strip().startswith("//"):
# Si no es IF y no es solo comentario, es el core
core_scl = consumer_scl.strip()
if consumer_scl:
# If consumer SCL itself is an IF generated by EN, take the body
if consumer_scl.strip().startswith("IF"):
match = re.search(r"THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE)
core_scl = match.group(1).strip() if match else None
elif not consumer_scl.strip().startswith("//"): # Otherwise, take the whole line if not comment
core_scl = consumer_scl.strip()
if core_scl:
grouped_instructions_cores.append(core_scl)
consumer_instr_list.append(consumer_instr) # Guardar referencia
# else: # Debug
# print(f"DEBUG Group: Consumidor {consumer_uid} ({consumer_type}) habilitado por {instr_uid} no tenía SCL core extraíble. SCL: '{consumer_scl}'")
consumer_instr_list.append(consumer_instr)
# Si encontramos más de un consumidor agrupable
# --- If groupable consumers found ---
if len(grouped_instructions_cores) > 1:
print(
f"INFO: Agrupando {len(grouped_instructions_cores)} instrucciones bajo condición de {instr_type_original} UID {instr_uid} (Cond: {condition_scl})"
)
print(f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}")
# Construir el bloque IF agrupado
scl_grouped = [f"IF {condition_scl} THEN"]
# *** Simplify the SymPy condition ***
try:
#simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True)
simplified_expr = sympy.logic.boolalg.to_dnf(sympy_condition_expr, simplify=True)
except Exception as e:
print(f"Error simplifying condition for grouping UID {instr_uid}: {e}")
simplified_expr = sympy_condition_expr # Fallback
# *** Convert simplified condition to SCL string ***
condition_scl_simplified = sympy_expr_to_scl(simplified_expr, symbol_manager)
# *** Build the grouped IF SCL ***
scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"]
for core_line in grouped_instructions_cores:
# Añadir indentación adecuada (2 espacios)
indented_core = "\n".join(
[f" {line.strip()}" for line in core_line.splitlines()]
)
scl_grouped.append(indented_core)
scl_grouped.append("END_IF;")
final_grouped_scl = "\n".join(scl_grouped)
indented_core = "\n".join([f" {line.strip()}" for line in core_line.splitlines()])
scl_grouped_lines.append(indented_core)
scl_grouped_lines.append("END_IF;")
final_grouped_scl = "\n".join(scl_grouped_lines)
# Sobrescribir 'scl' de la instrucción generadora de condición
# Update the generator instruction's SCL
instruction["scl"] = final_grouped_scl
# Marcar los consumidores como agrupados y limpiar su SCL original
# Mark consumers as grouped
for consumer_instr in consumer_instr_list:
consumer_instr["scl"] = f"{GROUPED_COMMENT} (by UID {instr_uid})"
consumer_instr["grouped"] = True # Marcar como agrupado
consumer_instr["grouped"] = True
made_change = True
# else: # Debug
# if len(grouped_instructions_cores) == 1:
# print(f"DEBUG Group: Solo 1 consumidor ({consumer_instr_list[0]['instruction_uid']}) para {instr_uid}. No se agrupa.")
# elif len(grouped_instructions_cores) == 0 and condition_scl not in ['TRUE', 'FALSE']:
# # Solo mostrar si la condición no era trivial
# pass # print(f"DEBUG Group: Ningún consumidor agrupable encontrado para {instr_uid} (Cond: {condition_scl}).")
return made_change
@ -254,30 +228,20 @@ def process_json_to_scl(json_filepath):
# como argumento (como ya se hace). Así que 'global data' aquí es probablemente innecesario.
# Eliminémoslo por ahora y aseguremos que data se pasa a process_group_ifs.
if not os.path.exists(json_filepath):
print(f"Error: JSON no encontrado: {json_filepath}")
return
if not os.path.exists(json_filepath): print(f"Error: JSON no encontrado: {json_filepath}"); return
print(f"Cargando JSON desde: {json_filepath}")
try:
# Cargar datos en una variable local de esta función
with open(json_filepath, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"Error al cargar JSON: {e}")
traceback.print_exc()
return
with open(json_filepath, "r", encoding="utf-8") as f: data = json.load(f)
except Exception as e: print(f"Error al cargar JSON: {e}"); traceback.print_exc(); return
# --- Carga dinámica de procesadores (Obtiene mapa y lista ordenada) ---
script_dir = os.path.dirname(__file__)
processors_dir_path = os.path.join(script_dir, 'processors')
# --- Carga dinámica de procesadores (sin cambios) ---
script_dir = os.path.dirname(__file__); processors_dir_path = os.path.join(script_dir, 'processors')
processor_map, sorted_processors = load_processors(processors_dir_path)
if not processor_map: print("Error crítico: No se cargaron procesadores. Abortando."); return
if not processor_map: # O verificar sorted_processors
print("Error crítico: No se cargaron procesadores. Abortando.")
return
# --- Crear mapas de acceso por red ---
# --- Crear mapas de acceso por red (sin cambios) ---
network_access_maps = {}
# ... (logic to populate network_access_maps remains the same) ...
for network in data.get("networks", []):
net_id = network["id"]
current_access_map = {}
@ -297,30 +261,35 @@ def process_json_to_scl(json_filepath):
current_access_map[dest["uid"]] = dest
network_access_maps[net_id] = current_access_map
# --- Inicializar mapa SCL y bucle ---
scl_map = {} # Mapa para resultados SCL intermedios
# --- Inicializar mapa SymPy y SymbolManager por red ---
# Cada red puede tener su propio contexto de símbolos si es necesario,
# pero un SymbolManager global suele ser suficiente si no hay colisiones graves.
# Usaremos uno global por simplicidad ahora.
symbol_manager = SymbolManager()
sympy_map = {} # Mapa para resultados SymPy intermedios (expresiones)
max_passes = 30
passes = 0
processing_complete = False
print("\n--- Iniciando Bucle de Procesamiento Iterativo (con prioridad) ---")
print("\n--- Iniciando Bucle de Procesamiento Iterativo (con SymPy y prioridad) ---")
while passes < max_passes and not processing_complete:
passes += 1
made_change_in_base_pass = False
made_change_in_base_pass = False # Renombrar: made_change_in_sympy_pass
made_change_in_group_pass = False
# made_change_in_simplify_pass = False # Ya no existe Fase 3
print(f"\n--- Pase {passes} ---")
num_processed_this_pass = 0
num_processed_this_pass = 0 # Renombrar: num_sympy_processed_this_pass
num_grouped_this_pass = 0
# num_simplified_this_pass = 0 # Ya no existe Fase 3
# --- FASE 1: Procesadores Base (Itera según la lista ordenada por prioridad) ---
print(f" Fase 1 (Base - Orden por Prioridad):")
for processor_info in sorted_processors: # Iterar sobre la lista ordenada por prioridad
# --- FASE 1: Procesadores Base (Ahora usan SymPy) ---
print(f" Fase 1 (SymPy Base - Orden por Prioridad):")
num_sympy_processed_this_pass = 0 # Contador específico
for processor_info in sorted_processors:
current_type_name = processor_info['type_name']
func_to_call = processor_info['func']
# Descomentar para depuración muy detallada:
# print(f" Intentando procesar tipo: {current_type_name} (Prio: {processor_info['priority']})")
# Buscar instrucciones de este tipo en todas las redes
for network in data.get("networks", []):
network_id = network["id"]
access_map = network_access_maps.get(network_id, {})
@ -330,82 +299,84 @@ def process_json_to_scl(json_filepath):
instr_uid = instruction.get("instruction_uid")
instr_type_original = instruction.get("type", "Unknown")
# Saltar si ya está procesado, es un error o ya fue agrupado por otro
if (instr_type_original.endswith(SCL_SUFFIX)
# Saltar si ya está procesado con el NUEVO método, es error, o agrupado
if (instr_type_original.endswith(SCL_SUFFIX) # Check new suffix
or "_error" in instr_type_original
or instruction.get("grouped", False)):
continue
# Determinar el tipo efectivo de la instrucción para la comparación
# (Manejo especial para 'Call')
# Determinar tipo efectivo (como antes)
lookup_key = instr_type_original.lower()
effective_type_name = lookup_key
if instr_type_original == "Call":
block_type = instruction.get("block_type", "").upper()
if block_type == "FC": effective_type_name = "call_fc"
elif block_type == "FB": effective_type_name = "call_fb"
# Nota: Si es un tipo de bloque desconocido, effective_type_name será 'call'
if instr_type_original == "Call": # ... (manejo Call FC/FB) ...
block_type = instruction.get("block_type", "").upper()
if block_type == "FC": effective_type_name = "call_fc"
elif block_type == "FB": effective_type_name = "call_fb"
# Si el tipo efectivo de la instrucción coincide con el tipo que estamos procesando en este ciclo...
if effective_type_name == current_type_name:
try:
# Llamar a la función del procesador, pasando 'data'
changed = func_to_call(instruction, network_id, scl_map, access_map, data) # Pasar data
# *** Llamar al procesador refactorizado ***
# Pasa sympy_map y symbol_manager, no scl_map
changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data) # Pasamos SymbolManager
if changed:
made_change_in_base_pass = True
num_processed_this_pass += 1
# La función llamada debe añadir _scl o _error al tipo de la instrucción
# Descomentar para depuración:
# print(f" Procesado: {instr_type_original} UID {instr_uid}")
num_sympy_processed_this_pass += 1
except Exception as e:
print(f"ERROR(Base) al procesar {instr_type_original} UID {instr_uid} con {func_to_call.__name__}: {e}")
print(f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}")
traceback.print_exc()
# Marcar como error para no reintentar
instruction["scl"] = f"// ERROR en procesador base: {e}"
instruction["scl"] = f"// ERROR en SymPy procesador base: {e}"
instruction["type"] = instr_type_original + "_error"
made_change_in_base_pass = True # Considerar error como cambio
made_change_in_base_pass = True
print(f" -> {num_sympy_processed_this_pass} instrucciones procesadas con SymPy.")
# --- FASE 2: Procesador de Agrupación (Se ejecuta después de toda la Fase 1) ---
# Ejecutar solo si hubo cambios en la fase base (o en el primer pase)
# --- FASE 2: Agrupación IF (Ahora usa SymPy para simplificar) ---
# Ejecutar si hubo cambios en base o es el primer pase
if made_change_in_base_pass or passes == 1:
print(f" Fase 2 (Agrupación IF):")
print(f" Fase 2 (Agrupación IF con Simplificación):")
num_grouped_this_pass = 0 # Reiniciar contador
for network in data.get("networks", []):
network_id = network["id"]
access_map = network_access_maps.get(network_id, {})
# access_map = network_access_maps.get(network_id, {}) # No usado directamente por group_ifs
network_logic = network.get("logic", [])
# Iterar sobre instrucciones ya procesadas que podrían generar condiciones
# Iterar sobre instrucciones que *pueden* generar condiciones booleanas
for instruction in network_logic:
# Intentar agrupar solo si está procesado (_scl) y no previamente agrupado
if instruction["type"].endswith("_scl") and not instruction.get("grouped", False):
try:
# Llamar a la función de agrupación, pasando 'data'
group_changed = process_group_ifs(instruction, network_id, scl_map, access_map, data)
if group_changed:
made_change_in_group_pass = True
num_grouped_this_pass += 1
except Exception as e:
print(f"ERROR(Group) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}")
traceback.print_exc()
# No marcamos la instrucción origen como error, solo falló la agrupación
# process_group_ifs ahora verifica internamente si la instr. fue procesada
try:
# *** Llamar a process_group_ifs adaptado ***
group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data)
if group_changed:
made_change_in_group_pass = True
num_grouped_this_pass += 1
except Exception as e:
print(f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}")
traceback.print_exc()
print(f" -> {num_grouped_this_pass} agrupaciones realizadas.")
# --- FASE 3 Eliminada ---
# --- Comprobar si se completó el procesamiento ---
# Solo considera Fase 1 (SymPy Base) y Fase 2 (Grouping)
if not made_change_in_base_pass and not made_change_in_group_pass:
print(f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---")
processing_complete = True
else:
print(f"--- Fin Pase {passes}: {num_processed_this_pass} procesados, {num_grouped_this_pass} agrupados. Continuando...")
# Mensaje de fin de pase actualizado
print(f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando...")
# --- Comprobar límite de pases ---
if passes == max_passes and not processing_complete:
print(f"\n--- ADVERTENCIA: Límite de {max_passes} pases alcanzado. Puede haber dependencias no resueltas. ---")
print(f"\n--- ADVERTENCIA: Límite de {max_passes} pases alcanzado...")
# --- FIN BUCLE ITERATIVO ---
# --- Verificación Final de Instrucciones No Procesadas ---
# --- Verificación Final ---
# La lógica aquí podría necesitar ajustes si el sufijo cambió o si el SCL final
# solo se genera en instrucciones terminales.
print("\n--- Verificación Final de Instrucciones No Procesadas ---")
unprocessed_count = 0
unprocessed_details = []
ignored_types = ['raw_scl_chunk', 'unsupported_lang'] # Tipos que esperamos no procesar
ignored_types = ['raw_scl_chunk', 'unsupported_lang']
for network in data.get("networks", []):
network_id = network.get("id", "Unknown ID")
@ -414,8 +385,11 @@ def process_json_to_scl(json_filepath):
instr_uid = instruction.get("instruction_uid", "Unknown UID")
instr_type = instruction.get("type", "Unknown Type")
is_grouped = instruction.get("grouped", False)
has_final_scl = bool(instruction.get("scl", "").strip()) and not instruction.get("scl", "").strip().startswith("//")
# Comprobar si NO está procesada, NO es error, NO está agrupada Y NO es un tipo ignorado
# Condición revisada: No tiene el sufijo nuevo Y no es error Y no está agrupada Y no es tipo ignorado
# Y ADEMÁS, ¿debería tener SCL final si es una instr. terminal?
# Simplificación: si no tiene sufijo, no es error, no agrupada, no ignorada -> problema
if (not instr_type.endswith(SCL_SUFFIX) and
"_error" not in instr_type and
not is_grouped and
@ -425,24 +399,21 @@ def process_json_to_scl(json_filepath):
f" - Red '{network_title}' (ID: {network_id}), "
f"Instrucción UID: {instr_uid}, Tipo Original: '{instr_type}'"
)
# Opcional: añadir si tiene SCL o no
# unprocessed_details[-1] += f" (Tiene SCL final: {has_final_scl})"
if unprocessed_count > 0:
print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones que no fueron procesadas (y no son tipos ignorados):")
if unprocessed_details:
for detail in unprocessed_details:
print(detail)
print(">>> Estos tipos podrían necesitar un procesador en el directorio 'processors' o tener dependencias irresolubles.")
# else: # No debería pasar si unprocessed_count > 0 y la lógica es correcta
# print("...")
print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones que no fueron procesadas:")
for detail in unprocessed_details: print(detail)
else:
print("INFO: Todas las instrucciones relevantes fueron procesadas a SCL, marcadas como error o agrupadas exitosamente.")
print("INFO: Todas las instrucciones relevantes parecen haber sido procesadas o agrupadas.")
# --- Guardar JSON Final ---
# --- Guardar JSON Final (sin cambios) ---
output_filename = json_filepath.replace("_simplified.json", "_simplified_processed.json")
print(f"\nGuardando JSON procesado en: {output_filename}")
try:
with open(output_filename, "w", encoding="utf-8") as f:
# Usamos la 'data' local que hemos estado modificando
json.dump(data, f, indent=4, ensure_ascii=False)
print("Guardado completado.")
except Exception as e:

View File

@ -1,28 +1,39 @@
# x3_generate_scl.py
# -*- coding: utf-8 -*-
import json
import os
import re
import argparse
import sys
import traceback # Importar traceback para errores
# --- Helper Functions ---
# --- Importar Utilidades y Constantes (Asumiendo ubicación) ---
try:
# Intenta importar desde el paquete de procesadores si está estructurado así
from processors.processor_utils import format_variable_name
# Definir SCL_SUFFIX aquí o importarlo si está centralizado
SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py
GROUPED_COMMENT = "// Logic included in grouped IF" # Opcional, si se usa para filtrar
except ImportError:
print("Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils.")
print("Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!).")
# Implementación local BÁSICA como fallback (MENOS RECOMENDADA)
def format_variable_name(name):
if not name: return "_INVALID_NAME_"
if name.startswith('"') and name.endswith('"'): return name # Mantener comillas
prefix = "#" if name.startswith("#") else ""
if prefix: name = name[1:]
if name and name[0].isdigit(): name = "_" + name
name = re.sub(r"[^a-zA-Z0-9_]", "_", name)
return prefix + name
SCL_SUFFIX = "_sympy_processed"
GROUPED_COMMENT = "// Logic included in grouped IF"
def format_variable_name(name):
"""Limpia el nombre de la variable quitando comillas y espacios."""
if not name:
return "_INVALID_NAME_"
# Quita comillas dobles iniciales/finales
name = name.strip('"')
# Reemplaza comillas dobles internas y puntos por guión bajo
name = name.replace('"."', '_').replace('.', '_')
# Quita comillas restantes (si las hubiera)
name = name.replace('"', '')
# Asegurarse de que no empiece con número (aunque raro con comillas iniciales)
if name and name[0].isdigit():
name = "_" + name
return name
# --- Función Principal de Generación SCL ---
def generate_scl(processed_json_filepath, output_scl_filepath):
"""Genera un archivo SCL a partir del JSON procesado."""
"""Genera un archivo SCL a partir del JSON procesado por x2_process (versión SymPy)."""
if not os.path.exists(processed_json_filepath):
print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'")
@ -34,117 +45,153 @@ def generate_scl(processed_json_filepath, output_scl_filepath):
data = json.load(f)
except Exception as e:
print(f"Error al cargar o parsear JSON: {e}")
traceback.print_exc()
return
# --- Extracción de Información del Bloque ---
block_name = data.get('block_name', 'UnknownBlock')
block_number = data.get('block_number')
block_lang = data.get('language', 'LAD') # Lenguaje original
block_lang_original = data.get('language', 'LAD') # Lenguaje original
# Determinar tipo de bloque SCL (Asumir FB si no se especifica)
# Idealmente, x1_to_json.py guardaría esto en data['block_type_scl'] = 'FC' o 'FB'
block_type_scl = data.get('block_type_scl', 'FUNCTION_BLOCK')
block_comment = data.get('block_comment', '')
# Limpiar nombre del bloque para usarlo en SCL
# Usar format_variable_name para el nombre del bloque en SCL
scl_block_name = format_variable_name(block_name)
print(f"Generando SCL para el bloque: {scl_block_name} (Original: {block_name})")
print(f"Generando SCL para {block_type_scl}: {scl_block_name} (Original: {block_name})")
# --- Identificación de Variables Temporales y Estáticas ---
# La detección basada en regex sobre el SCL final debería seguir funcionando
temp_vars = set()
stat_vars = set() # Para flancos, si se implementan completamente
# Usar regex para encontrar variables _temp_... y stat_...
temp_pattern = re.compile(r'"?(_temp_[a-zA-Z0-9_]+)"?')
stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?')
stat_vars = set()
# Regex mejorado para capturar variables temporales que empiezan con # o _temp_
# y estáticas (si usas un prefijo como 'stat_' o para bits de memoria de flanco)
temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?') # Captura con o sin #
stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') # Para memorias de flanco si usan prefijo 'stat_'
edge_memory_bits = set() # Para detectar bits de memoria de flanco por nombre
for network in data.get('networks', []):
for instruction in network.get('logic', []):
scl_code = instruction.get('scl', '')
if scl_code:
# Buscar temporales en el código SCL generado
found_temps = temp_pattern.findall(scl_code)
for temp_name in found_temps:
temp_vars.add(temp_name) # Añadir al set (evita duplicados)
# Buscar estáticas (para flancos)
found_stats = stat_pattern.findall(scl_code)
for stat_name in found_stats:
stat_vars.add(stat_name)
# Buscar también en _edge_mem_update_scl si existe
edge_update_code = instruction.get('_edge_mem_update_scl','')
code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '')
print(f"Variables temporales detectadas: {len(temp_vars)}")
# print(f"Variables estáticas detectadas (para flancos): {len(stat_vars)}")
if code_to_scan:
# Buscar #_temp_... o _temp_...
found_temps = temp_pattern.findall(code_to_scan)
for temp_tuple in found_temps:
# findall devuelve tuplas por los grupos de captura, tomar el no vacío
temp_name = next((t for t in temp_tuple if t), None)
if temp_name:
temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name) # Asegurar que empiece con #
# Buscar estáticas (ej: stat_...)
found_stats = stat_pattern.findall(code_to_scan)
stat_vars.update(found_stats)
# Identificar explícitamente bits de memoria usados por PBox/NBox
# Asumiendo que el nombre se guarda en el JSON (requiere ajuste en x1/x2)
# if instruction.get("type","").startswith(("PBox", "NBox")):
# mem_bit_info = instruction.get("inputs", {}).get("bit")
# if mem_bit_info and mem_bit_info.get("type") == "variable":
# edge_memory_bits.add(format_variable_name(mem_bit_info.get("name")))
print(f"Variables temporales (#_temp_...) detectadas: {len(temp_vars)}")
# Si se detectan memorias de flanco, añadirlas a stat_vars si no tienen prefijo 'stat_'
# stat_vars.update(edge_memory_bits - stat_vars) # Añadir solo las nuevas
print(f"Variables estáticas (stat_...) detectadas: {len(stat_vars)}")
# --- Construcción del String SCL ---
scl_output = []
# Cabecera del Bloque
scl_output.append(f"// Block Name (Original): {block_name}")
if block_number:
scl_output.append(f"// Block Number: {block_number}")
scl_output.append(f"// Original Language: {block_lang}")
if block_comment:
scl_output.append(f"// Block Comment: {block_comment}")
if block_number: scl_output.append(f"// Block Number: {block_number}")
scl_output.append(f"// Original Language: {block_lang_original}")
if block_comment: scl_output.append(f"// Block Comment: {block_comment}")
scl_output.append("")
scl_output.append(f"FUNCTION_BLOCK \"{scl_block_name}\"") # Asumir FB por variables Temp/Stat
scl_output.append("{ S7_Optimized_Access := 'TRUE' }") # Opcional, común
scl_output.append("VERSION : 0.1") # Opcional
scl_output.append(f"{block_type_scl} \"{scl_block_name}\"")
scl_output.append("{ S7_Optimized_Access := 'TRUE' }")
scl_output.append("VERSION : 0.1")
scl_output.append("")
# Declaraciones de Interfaz
scl_output.append("VAR_INPUT")
# Iterar sobre data['interface']['Input'] si existe
# for var in data.get('interface', {}).get('Input', []):
# scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};")
scl_output.append("END_VAR")
scl_output.append("")
# Declaraciones de Interfaz (Implementación básica)
interface_sections = ["Input", "Output", "InOut", "Static", "Temp", "Constant", "Return"]
interface_data = data.get('interface', {})
scl_output.append("VAR_OUTPUT")
# Iterar sobre data['interface']['Output'] si existe
# for var in data.get('interface', {}).get('Output', []):
# scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};")
scl_output.append("END_VAR")
scl_output.append("")
for section_name in interface_sections:
scl_section_name = section_name
# Ajustar nombres de sección para SCL (Static -> STAT, Temp -> TEMP)
if section_name == "Static": scl_section_name = "STAT"
if section_name == "Temp": scl_section_name = "TEMP" # Usar VAR_TEMP para variables #temp
scl_output.append("VAR_IN_OUT")
# Iterar sobre data['interface']['InOut'] si existe
# for var in data.get('interface', {}).get('InOut', []):
# scl_output.append(f" {format_variable_name(var['name'])} : {var['datatype']};")
scl_output.append("END_VAR")
scl_output.append("")
vars_in_section = interface_data.get(section_name, [])
# No declarar VAR_TEMP aquí, se hará después con las detectadas/originales
if section_name == "Temp": continue
# Declaraciones Estáticas (para flancos)
if stat_vars:
scl_output.append("VAR_STAT")
# Asumir Bool para flancos, se podría inferir mejor si PBox lo indicara
for var_name in sorted(list(stat_vars)):
scl_output.append(f" \"{var_name}\" : Bool; // Memory for edge detection")
scl_output.append("END_VAR")
scl_output.append("")
# No declarar VAR_STAT aquí si ya lo hacemos abajo con las detectadas
if section_name == "Static" and stat_vars: continue
# Declaraciones Temporales
# Incluir las variables de la sección Temp del JSON original
# y las generadas automáticamente (_temp_...)
if vars_in_section or (section_name == "Static" and stat_vars): # Incluir STAT si hay detectadas
# Usar VAR para Input/Output/InOut/Constant/Return
var_keyword = "VAR" if section_name != "Static" else "VAR_STAT"
scl_output.append(f"{var_keyword}_{section_name.upper()}")
for var in vars_in_section:
var_name = var.get('name')
var_dtype = var.get('datatype', 'VARIANT') # Default a VARIANT
if var_name:
# Usar format_variable_name CORRECTO
scl_name = format_variable_name(var_name)
scl_output.append(f" {scl_name} : {var_dtype};")
# Declarar stat_vars detectadas si esta es la sección STAT
if section_name == "Static" and stat_vars:
for var_name in sorted(list(stat_vars)):
# Asumir Bool para stat_, podría necesitar inferencia
scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
scl_output.append("END_VAR")
scl_output.append("")
# Declaraciones Estáticas (Si no estaban en la interfaz y se detectaron)
# Esto es redundante si la sección VAR_STAT ya se generó arriba
# if stat_vars and not interface_data.get("Static"):
# scl_output.append("VAR_STAT")
# for var_name in sorted(list(stat_vars)):
# scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT")
# scl_output.append("END_VAR")
# scl_output.append("")
# Declaraciones Temporales (Interfaz Temp + _temp_ detectadas)
scl_output.append("VAR_TEMP")
declared_temps = set()
interface_temps = data.get('interface', {}).get('Temp', [])
interface_temps = interface_data.get('Temp', [])
if interface_temps:
for var in interface_temps:
formatted_name = format_variable_name(var['name'])
# Añadir comillas si el nombre original las tenía o si contiene caracteres especiales
scl_name = f'"{formatted_name}"' if '"' in var['name'] or '.' in var['name'] else formatted_name
scl_output.append(f" {scl_name} : {var['datatype']};")
declared_temps.add(scl_name) # Marcar como declarada
var_name = var.get('name')
var_dtype = var.get('datatype', 'VARIANT')
if var_name:
scl_name = format_variable_name(var_name)
scl_output.append(f" {scl_name} : {var_dtype};")
declared_temps.add(scl_name) # Marcar como declarada
# Declarar las _temp_ generadas si no estaban ya en la interfaz Temp
if temp_vars:
# Intentar inferir tipo (difícil sin más info), por ahora usar Variant o Bool/DInt
for var_name in sorted(list(temp_vars)):
scl_name = f'"{var_name}"' # Asegurar comillas para _temp_
scl_name = format_variable_name(var_name) # #_temp_...
if scl_name not in declared_temps:
# Inferencia básica de tipo por nombre de pin (muy heurístico)
inferred_type = "Variant" # Tipo seguro por defecto
if var_name.endswith("_out"): # Salida de bloque lógico/comparación?
inferred_type = "Bool"
elif var_name.endswith("_out_num"): # Salida de bloque numérico?
inferred_type = "DInt" # O Real? O Int? Difícil saber
# Inferencia básica de tipo
inferred_type = "Bool" # Asumir Bool para la mayoría de temps de lógica
# Se podría mejorar si los procesadores añadieran info de tipo
scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary")
declared_temps.add(scl_name) # Marcar como declarada
declared_temps.add(scl_name)
scl_output.append("END_VAR")
scl_output.append("")
@ -156,75 +203,61 @@ def generate_scl(processed_json_filepath, output_scl_filepath):
for i, network in enumerate(data.get('networks', [])):
network_title = network.get('title', f'Network {network.get("id")}')
network_comment = network.get('comment', '')
# Obtener lenguaje original de la red, default a LAD si no está
network_lang = network.get('language', 'LAD')
network_lang = network.get('language', 'LAD') # O el lenguaje original
scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})")
if network_comment:
for line in network_comment.splitlines():
scl_output.append(f" // {line}")
scl_output.append("") # Línea en blanco antes del código de la red
scl_output.append("")
network_has_code = False
# Iterar sobre la 'logica' de la red
for instruction in network.get('logic', []):
instruction_type = instruction.get("type", "")
scl_code = instruction.get('scl')
scl_code = instruction.get('scl', "") # Obtener SCL generado por x2
# --- INICIO: Manejar RAW_SCL_CHUNK y otros tipos ---
if instruction_type == "RAW_SCL_CHUNK" and scl_code:
network_has_code = True
# Imprimir el SCL reconstruido directamente
for line in scl_code.splitlines():
scl_output.append(f" {line}") # Añadir indentación
elif instruction_type == "UNSUPPORTED_LANG" and scl_code:
network_has_code = True
# Imprimir comentario para lenguajes no soportados
for line in scl_code.splitlines():
scl_output.append(f" {line}")
elif instruction_type.endswith("_scl") and scl_code:
# Código SCL generado por x2_process.py para LAD/FBD
if instruction.get("grouped", False): # Ignorar si fue agrupado
continue
# Saltar instrucciones agrupadas
if instruction.get("grouped", False):
continue
# Lógica mejorada para omitir comentarios internos si no aportan
lines = scl_code.splitlines()
is_internal_comment_only = (len(lines) == 1 and
(lines[0].strip().startswith("// RLO") or
lines[0].strip().startswith("// Comparison") or
lines[0].strip().startswith("// Logic O") or
lines[0].strip().startswith("// Logic included in grouped IF") or # Usar GROUPED_COMMENT si lo importas
lines[0].strip().startswith("// P_TRIG") or
lines[0].strip().startswith("// N_TRIG") ))
# Escribir SCL si es un tipo procesado y tiene código relevante
# (Ignorar comentarios de depuración de SymPy)
if instruction_type.endswith(SCL_SUFFIX) and scl_code:
is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \
scl_code.strip().startswith("// PBox SymPy processed") or \
scl_code.strip().startswith("// NBox SymPy processed")
# O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL
is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines())
if not is_internal_comment_only:
# Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse)
if not is_only_comment or scl_code.strip().startswith("IF"):
network_has_code = True
for line in lines:
clean_line = line.strip()
# Omitir comentarios autogenerados específicos
if not (clean_line.startswith("// RLO:") or \
clean_line.startswith("// Comparison") or \
clean_line.startswith("// Logic O") or \
clean_line == "// Logic included in grouped IF" or \
clean_line.startswith("// P_TRIG(") or \
clean_line.startswith("// N_TRIG(")):
scl_output.append(f" {line}") # Indentación
# Ignorar instrucciones no procesadas (sin _scl) o con error
for line in scl_code.splitlines():
# Añadir indentación estándar
scl_output.append(f" {line}")
# Incluir también tipos especiales directamente
elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code:
network_has_code = True
for line in scl_code.splitlines():
scl_output.append(f" {line}") # Indentar
# Podríamos añadir comentarios para errores si se desea
# elif "_error" in instruction_type:
# scl_output.append(f" // Error processing instruction {instruction.get('instruction_uid')}: {scl_code}")
# network_has_code = True
# network_has_code = True
# scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}")
# --- FIN: Manejar RAW_SCL_CHUNK y otros tipos ---
if network_has_code:
scl_output.append("") # Añadir línea en blanco después del código de la red si hubo algo
scl_output.append("") # Línea en blanco después del código de la red
else:
# Añadir comentario si la red estaba vacía o no generó código imprimible
scl_output.append(f" // Network did not produce printable SCL code.")
scl_output.append("")
scl_output.append("END_FUNCTION_BLOCK")
# Fin del bloque
scl_output.append("END_FUNCTION_BLOCK") # O END_FUNCTION si es FC
# --- Escritura del Archivo SCL ---
print(f"Escribiendo archivo SCL en: {output_scl_filepath}")
@ -235,16 +268,12 @@ def generate_scl(processed_json_filepath, output_scl_filepath):
print("Generación de SCL completada.")
except Exception as e:
print(f"Error al escribir el archivo SCL: {e}")
traceback.print_exc()
# --- Ejecución ---
if __name__ == "__main__":
# Imports necesarios solo para la ejecución como script principal
import argparse
import os
import sys
parser = argparse.ArgumentParser(description="Generate final SCL file from processed JSON.")
# Acepta el nombre del XML original como referencia para derivar nombres
parser = argparse.ArgumentParser(description="Generate final SCL file from processed JSON (SymPy version).")
parser.add_argument(
"source_xml_filepath",
nargs="?",
@ -253,20 +282,17 @@ if __name__ == "__main__":
)
args = parser.parse_args()
# Derivar nombres de archivos de entrada y salida
xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0]
input_dir = os.path.dirname(args.source_xml_filepath) # Directorio del XML original
# Usar directorio del script si no hay ruta, o la ruta del XML si la tiene
xml_dir = os.path.dirname(args.source_xml_filepath)
base_dir = xml_dir if xml_dir else os.path.dirname(__file__)
# Nombre del JSON procesado (entrada para este script)
input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.json")
# Nombre del SCL final (salida de este script)
output_scl_file = os.path.join(input_dir, f"{xml_filename_base}_simplified_processed.scl")
input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json")
output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl")
# Verificar si el archivo JSON procesado de entrada existe
if not os.path.exists(input_json_file):
print(f"Error: Processed JSON file not found: '{input_json_file}'")
print(f"Ensure 'x2_process.py' ran successfully for '{args.source_xml_filepath}'.")
sys.exit(1) # Salir si el archivo de entrada no existe
sys.exit(1)
else:
# Llamar a la función principal con los nombres derivados
generate_scl(input_json_file, output_scl_file)