diff --git a/TestLAD_simplified.json b/TestLAD_simplified.json index 5116ea7..31dea3a 100644 --- a/TestLAD_simplified.json +++ b/TestLAD_simplified.json @@ -64,7 +64,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "31", @@ -88,7 +90,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "32", @@ -112,7 +116,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "33", @@ -134,7 +140,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "34", @@ -158,7 +166,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "35", @@ -180,7 +190,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "36", @@ -202,7 +214,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "37", @@ -224,7 +238,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "38", @@ -235,20 +251,22 @@ }, "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": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "39", @@ -297,7 +315,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "26", @@ -312,6 +332,12 @@ "source_instruction_uid": "25", "source_pin": "out" }, + "timer": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mHVM302_Dly\"" + }, "tv": { "uid": "23", "scope": "TypedConstant", @@ -326,7 +352,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "27", @@ -375,7 +403,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "25", @@ -390,6 +420,12 @@ "source_instruction_uid": "24", "source_pin": "out" }, + "timer": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetTotalizerTmr\"" + }, "tv": { "uid": "23", "scope": "TypedConstant", @@ -431,7 +467,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "27", @@ -450,7 +488,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "28", @@ -461,20 +501,22 @@ }, "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": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "29", @@ -489,6 +531,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTN301TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -503,7 +551,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "30", @@ -552,7 +602,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "28", @@ -571,7 +623,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "29", @@ -582,20 +636,22 @@ }, "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": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "30", @@ -610,6 +666,12 @@ "source_instruction_uid": "29", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTP302TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -624,7 +686,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "31", @@ -646,7 +710,9 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "32", @@ -695,7 +761,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "27", @@ -714,7 +782,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "28", @@ -725,20 +795,22 @@ }, "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": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "29", @@ -753,6 +825,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTM303TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -767,7 +845,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "30", @@ -816,7 +896,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "27", @@ -835,7 +917,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "28", @@ -846,20 +930,22 @@ }, "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": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "29", @@ -874,6 +960,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetProductTotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -888,7 +980,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "30", @@ -937,7 +1031,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "34", @@ -961,7 +1057,9 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "35", @@ -1024,7 +1122,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "38", @@ -1046,29 +1146,46 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "39", "uid": "39", - "type": "SdCoil", + "type": "Se", "template_values": {}, "negated_pins": {}, "inputs": { - "operand": { + "timer": { "uid": "27", "scope": "GlobalVariable", "type": "variable", "name": "\"T_Pulse_Recipe_Edit\"" }, - "in": { + "s": { "type": "connection", "source_instruction_type": "Contact", "source_instruction_uid": "38", "source_pin": "out" + }, + "tv": { + "uid": "28", + "scope": "TypedConstant", + "type": "constant", + "datatype": "TypedConstant", + "value": "S5T#500ms" + }, + "en": { + "type": "connection", + "source_instruction_uid": "38", + "source_instruction_type": "Contact", + "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + } }, { "instruction_uid": "40", @@ -1087,10 +1204,12 @@ "type": "connection", "source_instruction_type": "SdCoil", "source_instruction_uid": "39", - "source_pin": "out" + "source_pin": "q" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "41", @@ -1131,7 +1250,9 @@ "type": "powerrail" } }, - "outputs": {} + "outputs": { + "out": [] + } }, { "instruction_uid": "43", diff --git a/TestLAD_simplified_processed.json b/TestLAD_simplified_processed.json index 4680364..093c89a 100644 --- a/TestLAD_simplified_processed.json +++ b/TestLAD_simplified_processed.json @@ -64,7 +64,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gSyrupRoomEn\"" }, { @@ -89,7 +91,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")" }, { @@ -114,7 +118,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: (\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND (NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\")" }, { @@ -137,7 +143,9 @@ "source_pin": "out" } }, - "outputs": {}, + "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\"" }, { @@ -162,7 +170,9 @@ "source_pin": "out" } }, - "outputs": {}, + "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\")" }, { @@ -185,7 +195,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: (\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\"" }, { @@ -208,7 +220,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: ((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\"" }, { @@ -231,7 +245,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: (((\"gSyrupRoomEn\" AND (NOT \"gIN_HVP301_Aux\")) AND \"gBlenderCIPMode\") AND \"gIN_CIP_CIPRunning\") AND \"Procedure_Variables\".\"Blender_Run\".\"Running\"" }, { @@ -243,20 +259,22 @@ }, "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": {}, + "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\")" }, { @@ -307,13 +325,15 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gIN_HVM302_Aux\"" }, { "instruction_uid": "26", "uid": "26", - "type": "Sd", + "type": "Sd_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -323,6 +343,12 @@ "source_instruction_uid": "25", "source_pin": "out" }, + "timer": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mHVM302_Dly\"" + }, "tv": { "uid": "23", "scope": "TypedConstant", @@ -337,12 +363,15 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + }, + "scl": "\"mHVM302_Dly\"(IN := \"gIN_HVM302_Aux\", PT := S5T#1S); // TODO: Declarar \"mHVM302_Dly\" : TON; en VAR_STAT o VAR" }, { "instruction_uid": "27", "uid": "27", - "type": "Coil", + "type": "Coil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -359,7 +388,8 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": {}, + "scl": "\"gHVM302_Open\" := \"mHVM302_Dly\".Q;" } ], "language": "LAD" @@ -386,13 +416,15 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gBlendResetTotalizer\"" }, { "instruction_uid": "25", "uid": "25", - "type": "Se", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -402,6 +434,12 @@ "source_instruction_uid": "24", "source_pin": "out" }, + "timer": { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetTotalizerTmr\"" + }, "tv": { "uid": "23", "scope": "TypedConstant", @@ -416,7 +454,8 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": {}, + "scl": "\"mResetTotalizerTmr\"(IN := \"gBlendResetTotalizer\", PT := S5T#2S); // TODO: Declarar \"mResetTotalizerTmr\" : TP; en VAR_STAT o VAR" } ], "language": "LAD" @@ -443,7 +482,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gFTN301_ResetTot\"" }, { @@ -463,7 +504,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"mResetTotalizerTmr\"" }, { @@ -475,26 +518,28 @@ }, "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": {}, + "outputs": { + "out": [] + }, "scl": "// Logic O 28: \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\"" }, { "instruction_uid": "29", "uid": "29", - "type": "Se", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -504,6 +549,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTN301TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -518,12 +569,15 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + }, + "scl": "\"mResetFTN301TotTmr\"(IN := \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTN301TotTmr\" : TP; en VAR_STAT o VAR" }, { "instruction_uid": "30", "uid": "30", - "type": "Coil", + "type": "Coil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -540,7 +594,8 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": {}, + "scl": "\"mResetWaterTot\" := \"mResetFTN301TotTmr\".Q;" } ], "language": "LAD" @@ -567,7 +622,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gFTP302_ResetTot\"" }, { @@ -587,7 +644,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"mResetTotalizerTmr\"" }, { @@ -599,26 +658,28 @@ }, "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": {}, + "outputs": { + "out": [] + }, "scl": "// Logic O 29: \"gFTP302_ResetTot\" OR \"mResetTotalizerTmr\"" }, { "instruction_uid": "30", "uid": "30", - "type": "Se", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -628,6 +689,12 @@ "source_instruction_uid": "29", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTP302TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -642,12 +709,15 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + }, + "scl": "\"mResetFTP302TotTmr\"(IN := \"gFTP302_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTP302TotTmr\" : TP; en VAR_STAT o VAR" }, { "instruction_uid": "31", "uid": "31", - "type": "Contact", + "type": "Contact_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -664,12 +734,15 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": { + "out": [] + }, + "scl": "// RLO: \"mResetFTP302TotTmr\".Q AND \"gSyrupRoomEn\"" }, { "instruction_uid": "32", "uid": "32", - "type": "Coil", + "type": "Coil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -686,7 +759,8 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": {}, + "scl": "\"mResetSyrupTot\" := \"mResetFTP302TotTmr\".Q AND \"gSyrupRoomEn\";" } ], "language": "LAD" @@ -713,7 +787,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gFTM303_ResetTot\"" }, { @@ -733,7 +809,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"mResetTotalizerTmr\"" }, { @@ -745,26 +823,28 @@ }, "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": {}, + "outputs": { + "out": [] + }, "scl": "// Logic O 28: \"gFTM303_ResetTot\" OR \"mResetTotalizerTmr\"" }, { "instruction_uid": "29", "uid": "29", - "type": "Se", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -774,6 +854,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetFTM303TotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -788,12 +874,15 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + }, + "scl": "\"mResetFTM303TotTmr\"(IN := \"gFTM303_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTM303TotTmr\" : TP; en VAR_STAT o VAR" }, { "instruction_uid": "30", "uid": "30", - "type": "Coil", + "type": "Coil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -810,7 +899,8 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": {}, + "scl": "\"mResetCO2Tot\" := \"mResetFTM303TotTmr\".Q;" } ], "language": "LAD" @@ -837,7 +927,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"gProductMFMResetTot\"" }, { @@ -857,7 +949,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"mResetTotalizerTmr\"" }, { @@ -869,26 +963,28 @@ }, "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": {}, + "outputs": { + "out": [] + }, "scl": "// Logic O 28: \"gProductMFMResetTot\" OR \"mResetTotalizerTmr\"" }, { "instruction_uid": "29", "uid": "29", - "type": "Se", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -898,6 +994,12 @@ "source_instruction_uid": "28", "source_pin": "out" }, + "timer": { + "uid": "23", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"mResetProductTotTmr\"" + }, "tv": { "uid": "24", "scope": "TypedConstant", @@ -912,12 +1014,15 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": { + "q": [] + }, + "scl": "\"mResetProductTotTmr\"(IN := \"gProductMFMResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetProductTotTmr\" : TP; en VAR_STAT o VAR" }, { "instruction_uid": "30", "uid": "30", - "type": "Coil", + "type": "Coil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -934,7 +1039,8 @@ "source_pin": "q" } }, - "outputs": {} + "outputs": {}, + "scl": "\"mResetProductTot\" := \"mResetProductTotTmr\".Q;" } ], "language": "LAD" @@ -961,7 +1067,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" }, { @@ -986,7 +1094,9 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND (NOT \"mFP_Recip_Main_Page\")" }, { @@ -1052,7 +1162,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" }, { @@ -1075,35 +1187,53 @@ "source_pin": "out" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" }, { "instruction_uid": "39", "uid": "39", - "type": "SdCoil", + "type": "Se_scl", "template_values": {}, "negated_pins": {}, "inputs": { - "operand": { + "timer": { "uid": "27", "scope": "GlobalVariable", "type": "variable", "name": "\"T_Pulse_Recipe_Edit\"" }, - "in": { + "s": { "type": "connection", "source_instruction_type": "Contact", "source_instruction_uid": "38", "source_pin": "out" + }, + "tv": { + "uid": "28", + "scope": "TypedConstant", + "type": "constant", + "datatype": "TypedConstant", + "value": "S5T#500ms" + }, + "en": { + "type": "connection", + "source_instruction_uid": "38", + "source_instruction_type": "Contact", + "source_pin": "out" } }, - "outputs": {} + "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" }, { "instruction_uid": "40", "uid": "40", - "type": "Contact", + "type": "Contact_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -1117,15 +1247,18 @@ "type": "connection", "source_instruction_type": "SdCoil", "source_instruction_uid": "39", - "source_pin": "out" + "source_pin": "q" } }, - "outputs": {} + "outputs": { + "out": [] + }, + "scl": "// RLO: \"T_Pulse_Recipe_Edit\".Q AND \"T_Pulse_Recipe_Edit\"" }, { "instruction_uid": "41", "uid": "41", - "type": "RCoil", + "type": "RCoil_scl", "template_values": {}, "negated_pins": {}, "inputs": { @@ -1142,7 +1275,8 @@ "source_pin": "out" } }, - "outputs": {} + "outputs": {}, + "scl": "IF \"T_Pulse_Recipe_Edit\".Q AND \"T_Pulse_Recipe_Edit\" THEN\n \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\" := FALSE;\nEND_IF;" }, { "instruction_uid": "42", @@ -1161,7 +1295,9 @@ "type": "powerrail" } }, - "outputs": {}, + "outputs": { + "out": [] + }, "scl": "// RLO: \"mAux_FP_M700_1\"" }, { diff --git a/TestLAD_simplified_processed.scl b/TestLAD_simplified_processed.scl index c4dc244..4f05b74 100644 --- a/TestLAD_simplified_processed.scl +++ b/TestLAD_simplified_processed.scl @@ -33,32 +33,41 @@ BEGIN // Network 2: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD) - // Network did not produce printable SCL code. + "mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON; en VAR_STAT o VAR + "gHVM302_Open" := "mHVM302_Dly".Q; // Network 3: ResetTotalizer (Original Language: LAD) - // Network did not produce printable SCL code. + "mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP; en VAR_STAT o VAR // Network 4: ResetWaterTot (Original Language: LAD) - // Network did not produce printable SCL code. + "mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP; en VAR_STAT o VAR + "mResetWaterTot" := "mResetFTN301TotTmr".Q; // Network 5: ResetCO2Tot (Original Language: LAD) - // Network did not produce printable SCL code. + "mResetFTP302TotTmr"(IN := "gFTP302_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP; en VAR_STAT o VAR + "mResetSyrupTot" := "mResetFTP302TotTmr".Q AND "gSyrupRoomEn"; // Network 6: ResetProductTot (Original Language: LAD) - // Network did not produce printable SCL code. + "mResetFTM303TotTmr"(IN := "gFTM303_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP; en VAR_STAT o VAR + "mResetCO2Tot" := "mResetFTM303TotTmr".Q; // Network 7: ResetCO2Tot (Original Language: LAD) - // Network did not produce printable SCL code. + "mResetProductTotTmr"(IN := "gProductMFMResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP; en VAR_STAT o VAR + "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"); "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 + "HMI_Variables_Cmd"."Recipe"."Edit" := FALSE; + END_IF; IF "mAux_FP_M700_1" THEN "HMI_Variables_Cmd"."Recipe"."Edit" := TRUE; END_IF; diff --git a/x1_to_json.py b/x1_to_json.py index 1e99f63..8b1a128 100644 --- a/x1_to_json.py +++ b/x1_to_json.py @@ -11,7 +11,7 @@ from collections import defaultdict ns = { "iface": "http://www.siemens.com/automation/Openness/SW/Interface/v5", "flg": "http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4", - "st": "http://www.siemens.com/automation/Openness/SW/NetworkSource/StructuredText/v3" # <--- Added SCL namespace + "st": "http://www.siemens.com/automation/Openness/SW/NetworkSource/StructuredText/v3", # <--- Added SCL namespace } @@ -44,6 +44,7 @@ def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"): print(f"Advertencia: Error extrayendo MultilingualText: {e}") return "" + def get_symbol_name(symbol_element): # (Sin cambios respecto a la versión anterior) if symbol_element is None: @@ -55,96 +56,199 @@ def get_symbol_name(symbol_element): print(f"Advertencia: Excepción en get_symbol_name: {e}") return None + def parse_access(access_element): # (Sin cambios respecto a la versión anterior) - if access_element is None: return None - uid = access_element.get("UId"); scope = access_element.get("Scope") + if access_element is None: + return None + uid = access_element.get("UId") + scope = access_element.get("Scope") info = {"uid": uid, "scope": scope, "type": "unknown"} symbol = access_element.xpath("./*[local-name()='Symbol']") constant = access_element.xpath("./*[local-name()='Constant']") if symbol: info["type"] = "variable" info["name"] = get_symbol_name(symbol[0]) - if info["name"] is None: info["type"] = "error_parsing_symbol"; print(f"Error: No se pudo parsear nombre símbolo Access UID={uid}"); return info + if info["name"] is None: + info["type"] = "error_parsing_symbol" + print(f"Error: No se pudo parsear nombre símbolo Access UID={uid}") + return info elif constant: info["type"] = "constant" const_type_elem = constant[0].xpath("./*[local-name()='ConstantType']") const_val_elem = constant[0].xpath("./*[local-name()='ConstantValue']") - info["datatype"] = const_type_elem[0].text if const_type_elem and const_type_elem[0].text is not None else "Unknown" - value_str = const_val_elem[0].text if const_val_elem and const_val_elem[0].text is not None else None - if value_str is None: info["type"] = "error_parsing_constant"; info["value"] = None; print(f"Error: Constante sin valor Access UID={uid}"); return info + info["datatype"] = ( + const_type_elem[0].text + if const_type_elem and const_type_elem[0].text is not None + else "Unknown" + ) + value_str = ( + const_val_elem[0].text + if const_val_elem and const_val_elem[0].text is not None + else None + ) + if value_str is None: + info["type"] = "error_parsing_constant" + info["value"] = None + print(f"Error: Constante sin valor Access UID={uid}") + return info if info["datatype"] == "Unknown": val_lower = value_str.lower() - if val_lower in ["true", "false"]: info["datatype"] = "Bool" - elif value_str.isdigit() or (value_str.startswith('-') and value_str[1:].isdigit()): info["datatype"] = "Int" - elif '.' in value_str: - try: float(value_str); info["datatype"] = "Real" - except ValueError: pass - elif '#' in value_str: info["datatype"] = "TypedConstant" + if val_lower in ["true", "false"]: + info["datatype"] = "Bool" + elif value_str.isdigit() or ( + value_str.startswith("-") and value_str[1:].isdigit() + ): + info["datatype"] = "Int" + elif "." in value_str: + try: + float(value_str) + info["datatype"] = "Real" + except ValueError: + pass + elif "#" in value_str: + info["datatype"] = "TypedConstant" info["value"] = value_str dtype_lower = info["datatype"].lower() - val_str_processed = value_str.split('#')[-1] if '#' in value_str else value_str + val_str_processed = value_str.split("#")[-1] if "#" in value_str else value_str try: - if dtype_lower in ['int', 'dint', 'udint', 'sint', 'usint', 'lint', 'ulint', 'word', 'dword', 'lword', 'byte']: info["value"] = int(val_str_processed) - elif dtype_lower == 'bool': info["value"] = (val_str_processed.lower() == 'true' or val_str_processed == '1') - elif dtype_lower in ['real', 'lreal']: info["value"] = float(val_str_processed) - elif dtype_lower == 'typedconstant': info["value"] = value_str - except (ValueError, TypeError) as e: print(f"Advertencia: No se pudo convertir valor '{val_str_processed}' a {dtype_lower} UID={uid}. Error: {e}"); info["value"] = value_str - else: info["type"] = "unknown_structure"; print(f"Advertencia: Access UID={uid} no es Symbol ni Constant."); return info - if info['type'] == 'variable' and info.get('name') is None: print(f"Error Interno: parse_access var sin nombre UID {uid}."); info['type'] = "error_no_name"; return info + if dtype_lower in [ + "int", + "dint", + "udint", + "sint", + "usint", + "lint", + "ulint", + "word", + "dword", + "lword", + "byte", + ]: + info["value"] = int(val_str_processed) + elif dtype_lower == "bool": + info["value"] = ( + val_str_processed.lower() == "true" or val_str_processed == "1" + ) + elif dtype_lower in ["real", "lreal"]: + info["value"] = float(val_str_processed) + elif dtype_lower == "typedconstant": + info["value"] = value_str + except (ValueError, TypeError) as e: + print( + f"Advertencia: No se pudo convertir valor '{val_str_processed}' a {dtype_lower} UID={uid}. Error: {e}" + ) + info["value"] = value_str + else: + info["type"] = "unknown_structure" + print(f"Advertencia: Access UID={uid} no es Symbol ni Constant.") + return info + if info["type"] == "variable" and info.get("name") is None: + print(f"Error Interno: parse_access var sin nombre UID {uid}.") + info["type"] = "error_no_name" + return info return info + def parse_part(part_element): # (Sin cambios respecto a la versión anterior) - if part_element is None: return None - uid = part_element.get('UId'); name = part_element.get('Name') - if not uid or not name: print(f"Error: Part sin UID o Name: {etree.tostring(part_element, encoding='unicode')}"); return None + if part_element is None: + return None + uid = part_element.get("UId") + name = part_element.get("Name") + if not uid or not name: + print( + f"Error: Part sin UID o Name: {etree.tostring(part_element, encoding='unicode')}" + ) + return None template_values = {} try: for tv in part_element.xpath("./*[local-name()='TemplateValue']"): - tv_name = tv.get('Name'); tv_type = tv.get('Type') - if tv_name and tv_type: template_values[tv_name] = tv_type - except Exception as e: print(f"Advertencia: Error extrayendo TemplateValues Part UID={uid}: {e}") + tv_name = tv.get("Name") + tv_type = tv.get("Type") + if tv_name and tv_type: + template_values[tv_name] = tv_type + except Exception as e: + print(f"Advertencia: Error extrayendo TemplateValues Part UID={uid}: {e}") negated_pins = {} try: for negated_elem in part_element.xpath("./*[local-name()='Negated']"): - negated_pin_name = negated_elem.get('Name') - if negated_pin_name: negated_pins[negated_pin_name] = True - except Exception as e: print(f"Advertencia: Error extrayendo Negated Pins Part UID={uid}: {e}") - return {'uid': uid, 'type': name, 'template_values': template_values, 'negated_pins': negated_pins} + negated_pin_name = negated_elem.get("Name") + if negated_pin_name: + negated_pins[negated_pin_name] = True + except Exception as e: + print(f"Advertencia: Error extrayendo Negated Pins Part UID={uid}: {e}") + return { + "uid": uid, + "type": name, + "template_values": template_values, + "negated_pins": negated_pins, + } + def parse_call(call_element): # (Mantiene la corrección para DB de instancia) - if call_element is None: return None + if call_element is None: + return None uid = call_element.get("UId") - if not uid: print(f"Error: Call encontrado sin UID: {etree.tostring(call_element, encoding='unicode')}"); return None + if not uid: + print( + f"Error: Call encontrado sin UID: {etree.tostring(call_element, encoding='unicode')}" + ) + return None call_info_elem = call_element.xpath("./*[local-name()='CallInfo']") - if not call_info_elem: print(f"Error: Call UID {uid} sin elemento CallInfo."); return None + if not call_info_elem: + print(f"Error: Call UID {uid} sin elemento CallInfo.") + return None call_info = call_info_elem[0] block_name = call_info.get("Name") block_type = call_info.get("BlockType") - instance_name = None; instance_scope = None - if not block_name or not block_type: print(f"Error: CallInfo para UID {uid} sin Name o BlockType."); return None + instance_name = None + instance_scope = None + if not block_name or not block_type: + print(f"Error: CallInfo para UID {uid} sin Name o BlockType.") + return None if block_type == "FB": instance_elem_list = call_info.xpath("./*[local-name()='Instance']") if instance_elem_list: instance_elem = instance_elem_list[0] instance_scope = instance_elem.get("Scope") - component_elem_list = instance_elem.xpath("./*[local-name()='Component']") # Busca Component directo + component_elem_list = instance_elem.xpath( + "./*[local-name()='Component']" + ) # Busca Component directo if component_elem_list: component_elem = component_elem_list[0] - db_name_raw = component_elem.get('Name') - if db_name_raw: instance_name = f'"{db_name_raw}"' # Añade comillas - else: print(f"Advertencia: dentro de para FB Call UID {uid} no tiene atributo 'Name'.") - else: print(f"Advertencia: No se encontró dentro de para FB Call UID {uid}. No se pudo obtener el nombre del DB.") - else: print(f"Advertencia: FB Call '{block_name}' UID {uid} no tiene elemento .") - call_data = {"uid": uid, "type": "Call", "block_name": block_name, "block_type": block_type} - if instance_name: call_data["instance_db"] = instance_name - if instance_scope: call_data["instance_scope"] = instance_scope + db_name_raw = component_elem.get("Name") + if db_name_raw: + instance_name = f'"{db_name_raw}"' # Añade comillas + else: + print( + f"Advertencia: dentro de para FB Call UID {uid} no tiene atributo 'Name'." + ) + else: + print( + f"Advertencia: No se encontró dentro de para FB Call UID {uid}. No se pudo obtener el nombre del DB." + ) + else: + print( + f"Advertencia: FB Call '{block_name}' UID {uid} no tiene elemento ." + ) + call_data = { + "uid": uid, + "type": "Call", + "block_name": block_name, + "block_type": block_type, + } + if instance_name: + call_data["instance_db"] = instance_name + if instance_scope: + call_data["instance_scope"] = instance_scope return call_data + # EN x1_to_json.py, junto a otras funciones auxiliares + def reconstruct_scl_from_tokens(st_node): """ Intenta reconstruir una cadena SCL a partir de los elementos hijos @@ -172,7 +276,9 @@ def reconstruct_scl_from_tokens(st_node): # Reconstruir acceso (simplificado) symbol_parts = [] # Buscar componentes y puntos dentro del símbolo de este acceso - symbol_children = elem.xpath(".//st:Component | .//st:Token[@Text='.']", namespaces=ns) + symbol_children = elem.xpath( + ".//st:Component | .//st:Token[@Text='.']", namespaces=ns + ) for sym_child in symbol_children: sym_tag = etree.QName(sym_child.tag).localname if sym_tag == "Component": @@ -181,8 +287,13 @@ def reconstruct_scl_from_tokens(st_node): # Simplificación: Añadir comillas si el nombre original parece tenerlas (o siempre para DBs?) # Aquí usamos el nombre tal cual viene en el XML por ahora # Si el XML tiene true podríamos usarlo - has_quotes_elem = sym_child.xpath("../st:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns) - has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == 'true' + has_quotes_elem = sym_child.xpath( + "../st:BooleanAttribute[@Name='HasQuotes']/text()", + namespaces=ns, + ) + has_quotes = ( + has_quotes_elem and has_quotes_elem[0].lower() == "true" + ) # Reconstrucción básica: poner comillas si HasQuotes es true o si es el primer componente (posible DB) # Esto es heurístico y puede fallar. @@ -194,8 +305,8 @@ def reconstruct_scl_from_tokens(st_node): # Versión más simple: usar nombre tal cual del XML symbol_parts.append(comp_name) - elif sym_tag == "Token": # Solo nos interesa el punto aquí - symbol_parts.append(".") + elif sym_tag == "Token": # Solo nos interesa el punto aquí + symbol_parts.append(".") access_str = "".join(symbol_parts) # Manejar llamadas a funciones/FB dentro de Access Scope="Call" @@ -206,17 +317,17 @@ def reconstruct_scl_from_tokens(st_node): # Reconstrucción básica de parámetros (muy simplificada) # Necesitaría parsear Parameters, Tokens '(', ')', ':=', '=>', etc. # Por ahora, solo añadimos el nombre y paréntesis vacíos - access_str = f"{instr_name}()" # Placeholder muy básico + access_str = f"{instr_name}()" # Placeholder muy básico scl_parts.append(access_str) elif tag == "Comment" or tag == "LineComment": - # Añadir comentarios - comment_text = elem.text if elem.text else "" - if tag == "Comment" and "\n" in comment_text: # Comentario multi-línea - scl_parts.append(f"(*{comment_text}*)") - else: # Comentario de una línea - scl_parts.append(f"// {comment_text}") + # Añadir comentarios + comment_text = elem.text if elem.text else "" + if tag == "Comment" and "\n" in comment_text: # Comentario multi-línea + scl_parts.append(f"(*{comment_text}*)") + else: # Comentario de una línea + scl_parts.append(f"// {comment_text}") else: # Ignorar otros tipos de nodos por ahora o añadir manejo específico pass @@ -224,14 +335,16 @@ def reconstruct_scl_from_tokens(st_node): # Unir todas las partes, limpiar espacios extra alrededor de saltos de línea full_scl = "".join(scl_parts) # Limpieza básica de formato - lines = [line.rstrip() for line in full_scl.split('\n')] + lines = [line.rstrip() for line in full_scl.split("\n")] # Re-ensamblar, asegurando que líneas vacías (solo espacios previos) se mantengan cleaned_scl = "\n".join(lines) # Eliminar múltiples líneas vacías consecutivas (opcional) # import re # cleaned_scl = re.sub(r'\n\s*\n', '\n\n', cleaned_scl) - return cleaned_scl.strip() # Quitar espacios/saltos al inicio/final + return cleaned_scl.strip() # Quitar espacios/saltos al inicio/final + + # --- Función parse_network con XPath corregido para Title/Comment --- # --- Función parse_network MODIFICADA (maneja multi-destino en Wire) --- def parse_network(network_element): @@ -240,7 +353,13 @@ def parse_network(network_element): Maneja wires con múltiples destinos. """ if network_element is None: - return {"id": "ERROR", "title": "Invalid Network Element", "comment": "", "logic": [], "error": "Input element was None"} + return { + "id": "ERROR", + "title": "Invalid Network Element", + "comment": "", + "logic": [], + "error": "Input element was None", + } network_id = network_element.get("ID") @@ -248,76 +367,123 @@ def parse_network(network_element): title_element = network_element.xpath( ".//*[local-name()='MultilingualText'][@CompositionName='Title']" ) - network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" - comment_element = network_element.xpath( - "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + network_title = ( + get_multilingual_text(title_element[0]) + if title_element + else f"Network {network_id}" + ) + comment_element = network_element.xpath( + "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + ) + network_comment = ( + get_multilingual_text(comment_element[0]) if comment_element else "" ) - network_comment = get_multilingual_text(comment_element[0]) if comment_element else "" flgnet_list = network_element.xpath(".//flg:FlgNet", namespaces=ns) if not flgnet_list: # print(f"Advertencia: FlgNet no encontrado en Red ID={network_id}. Puede estar vacía o ser comentario.") - return {"id": network_id, "title": network_title, "comment": network_comment, "logic": [], "error": "FlgNet not found"} + return { + "id": network_id, + "title": network_title, + "comment": network_comment, + "logic": [], + "error": "FlgNet not found", + } flgnet = flgnet_list[0] # 1. Parsear Access, Parts y Calls (sin cambios) - access_map = {acc_info["uid"]: acc_info for acc in flgnet.xpath(".//flg:Access", namespaces=ns) if (acc_info := parse_access(acc)) and acc_info['type'] != 'unknown'} + access_map = { + acc_info["uid"]: acc_info + for acc in flgnet.xpath(".//flg:Access", namespaces=ns) + if (acc_info := parse_access(acc)) and acc_info["type"] != "unknown" + } parts_and_calls_map = {} instruction_elements = flgnet.xpath(".//flg:Part | .//flg:Call", namespaces=ns) for element in instruction_elements: parsed_info = None - tag_name = etree.QName(element.tag).localname # Obtener nombre local de la etiqueta - if tag_name == "Part": parsed_info = parse_part(element) - elif tag_name == "Call": parsed_info = parse_call(element) - if parsed_info and "uid" in parsed_info: parts_and_calls_map[parsed_info["uid"]] = parsed_info - else: print(f"Advertencia: Se ignoró un Part/Call inválido en la red {network_id}") + tag_name = etree.QName( + element.tag + ).localname # Obtener nombre local de la etiqueta + if tag_name == "Part": + parsed_info = parse_part(element) + elif tag_name == "Call": + parsed_info = parse_call(element) + if parsed_info and "uid" in parsed_info: + parts_and_calls_map[parsed_info["uid"]] = parsed_info + else: + print( + f"Advertencia: Se ignoró un Part/Call inválido en la red {network_id}" + ) # --- 2. Parsear Wires (MODIFICADO para multi-destino) --- - wire_connections = defaultdict(list) # (dest_uid, dest_pin) -> [(src_uid, src_pin), ...] - source_connections = defaultdict(list) # (src_uid, src_pin) -> [(dest_uid, dest_pin), ...] - eno_outputs = defaultdict(list) # src_uid -> [(dest_uid, dest_pin), ...] (conexiones DESDE eno) + wire_connections = defaultdict( + list + ) # (dest_uid, dest_pin) -> [(src_uid, src_pin), ...] + source_connections = defaultdict( + list + ) # (src_uid, src_pin) -> [(dest_uid, dest_pin), ...] + eno_outputs = defaultdict( + list + ) # src_uid -> [(dest_uid, dest_pin), ...] (conexiones DESDE eno) - flg_ns_uri = ns["flg"] # Cache namespace URI + flg_ns_uri = ns["flg"] # Cache namespace URI qname_powerrail = etree.QName(flg_ns_uri, "Powerrail") qname_identcon = etree.QName(flg_ns_uri, "IdentCon") qname_namecon = etree.QName(flg_ns_uri, "NameCon") for wire in flgnet.xpath(".//flg:Wire", namespaces=ns): children = wire.getchildren() - if len(children) < 2: continue # Ignorar wires sin fuente y al menos un destino + if len(children) < 2: + continue # Ignorar wires sin fuente y al menos un destino source_elem = children[0] source_uid, source_pin = None, None # Determinar fuente - if source_elem.tag == qname_powerrail: source_uid, source_pin = "POWERRAIL", "out" - elif source_elem.tag == qname_identcon: source_uid, source_pin = source_elem.get("UId"), "value" # Acceso a variable/constante - elif source_elem.tag == qname_namecon: source_uid, source_pin = source_elem.get("UId"), source_elem.get("Name") # Salida de instrucción + if source_elem.tag == qname_powerrail: + source_uid, source_pin = "POWERRAIL", "out" + elif source_elem.tag == qname_identcon: + source_uid, source_pin = ( + source_elem.get("UId"), + "value", + ) # Acceso a variable/constante + elif source_elem.tag == qname_namecon: + source_uid, source_pin = source_elem.get("UId"), source_elem.get( + "Name" + ) # Salida de instrucción - if source_uid is None: continue # No se pudo determinar la fuente + if source_uid is None: + continue # No se pudo determinar la fuente - source_info = (source_uid, source_pin) # Par de fuente + source_info = (source_uid, source_pin) # Par de fuente # Iterar sobre TODOS los posibles destinos (desde el segundo hijo en adelante) for dest_elem in children[1:]: dest_uid, dest_pin = None, None # Determinar destino - if dest_elem.tag == qname_identcon: dest_uid, dest_pin = dest_elem.get("UId"), "value" # Entrada a variable/constante (Coil, etc.) - elif dest_elem.tag == qname_namecon: dest_uid, dest_pin = dest_elem.get("UId"), dest_elem.get("Name") # Entrada a instrucción + if dest_elem.tag == qname_identcon: + dest_uid, dest_pin = ( + dest_elem.get("UId"), + "value", + ) # Entrada a variable/constante (Coil, etc.) + elif dest_elem.tag == qname_namecon: + dest_uid, dest_pin = dest_elem.get("UId"), dest_elem.get( + "Name" + ) # Entrada a instrucción # Guardar conexiones si son válidas if dest_uid is not None and dest_pin is not None: # Mapa de Conexiones (Destino -> [Fuentes]) dest_key = (dest_uid, dest_pin) if source_info not in wire_connections[dest_key]: - wire_connections[dest_key].append(source_info) + wire_connections[dest_key].append(source_info) # Mapa de Fuentes (Fuente -> [Destinos]) source_key = (source_uid, source_pin) dest_info = (dest_uid, dest_pin) if dest_info not in source_connections[source_key]: - source_connections[source_key].append(dest_info) + source_connections[source_key].append(dest_info) # Registrar conexiones que SALEN de un pin 'eno' if source_pin == "eno" and source_uid in parts_and_calls_map: @@ -329,97 +495,335 @@ def parse_network(network_element): # 3. Construcción Lógica Inicial (sin cambios) all_logic_steps = {} - functional_block_types = ['Move', 'Add', 'Sub', 'Mul', 'Div', 'Mod', 'Convert', 'Call', 'Se', 'Sd', 'BLKMOV'] - rlo_generators = ['Contact', 'O', 'Eq', 'Ne', 'Gt', 'Lt', 'Ge', 'Le', 'And', 'Xor', 'PBox', 'NBox'] + functional_block_types = [ + "Move", + "Add", + "Sub", + "Mul", + "Div", + "Mod", + "Convert", + "Call", + "Se", + "Sd", + "BLKMOV", + ] + rlo_generators = [ + "Contact", + "O", + "Eq", + "Ne", + "Gt", + "Lt", + "Ge", + "Le", + "And", + "Xor", + "PBox", + "NBox", + ] for instruction_uid, instruction_info in parts_and_calls_map.items(): - instruction_repr = {"instruction_uid": instruction_uid, **instruction_info}; instruction_repr["inputs"] = {}; instruction_repr["outputs"] = {} - possible_input_pins = set(['en', 'in', 'in1', 'in2', 'in3', 'in4', 's', 'r', 'clk', 'cu', 'cd', 'ld', 'pv', 'tv', 'bit', 'operand', 'pre', 'SRCBLK']) - for dest_pin_name in possible_input_pins: - dest_key = (instruction_uid, dest_pin_name) + # Copiar info básica + instruction_repr = {"instruction_uid": instruction_uid, **instruction_info} + instruction_repr["inputs"] = {} + instruction_repr["outputs"] = {} + + # --- INICIO: Manejo Especial SdCoil y otros Timers --- + original_type = instruction_info["type"] + current_type = original_type + input_pin_mapping = {} # Mapa XML pin -> JSON pin + output_pin_mapping = {} # Mapa XML pin -> JSON pin + + if original_type == "SdCoil": + print( + f" Advertencia: Reinterpretando 'SdCoil' (UID: {instruction_uid}) como 'Se' (Pulse Timer)." + ) + current_type = "Se" # Tratarlo como Se (TP) + input_pin_mapping = { + "in": "s", # Pin XML 'in' mapea a JSON 's' (Start) + "operand": "timer", # Pin XML 'operand' mapea a JSON 'timer' (Instance) + "value": "tv", # Pin XML 'value' mapea a JSON 'tv' (Time Value) + } + output_pin_mapping = {"out": "q"} # Pin XML 'out' mapea a JSON 'q' (Output) + elif original_type in ["Se", "Sd"]: + # Mapear pines estándar de Se/Sd para consistencia con TP/TON + input_pin_mapping = {"s": "s", "tv": "tv", "r": "r", "timer": "timer"} + output_pin_mapping = { + "q": "q", + "rt": "rt", # "rtbcd": "rtbcd" (ignorar BCD) + } + # Añadir otros mapeos si son necesarios para otros bloques (ej. Contadores) + # elif original_type == "CTU": + # input_pin_mapping = {"cu": "cu", "r": "r", "pv": "pv", "counter": "counter"} # 'counter' es inventado para instancia + # output_pin_mapping = {"qu": "qu", "cv": "cv"} + # --- FIN Manejo Especial --- + + instruction_repr["type"] = current_type # Actualizar el tipo si se cambió + + # Mapear Entradas usando el mapeo de pines + possible_input_pins = set( + [ + "en", + "in", + "in1", + "in2", + "s", + "r", + "tv", + "value", + "operand", + "timer", + "bit", + "clk", + "pv", + "cu", + "cd", + "ld", + "pre", + "SRCBLK", + ] + ) # Ampliar con pines conocidos + for xml_pin_name in possible_input_pins: + dest_key = (instruction_uid, xml_pin_name) if dest_key in wire_connections: - sources_list = wire_connections[dest_key]; input_sources_repr = [] + sources_list = wire_connections[dest_key] + input_sources_repr = [] + # ... (lógica existente para obtener input_sources_repr de sources_list) ... for source_uid, source_pin in sources_list: - if source_uid == "POWERRAIL": input_sources_repr.append({"type": "powerrail"}) - elif source_uid in access_map: input_sources_repr.append(access_map[source_uid]) - elif source_uid in parts_and_calls_map: input_sources_repr.append({"type": "connection", "source_instruction_type": parts_and_calls_map[source_uid]["type"], "source_instruction_uid": source_uid, "source_pin": source_pin}) - else: input_sources_repr.append({"type": "unknown_source", "uid": source_uid}) - if len(input_sources_repr) == 1: instruction_repr["inputs"][dest_pin_name] = input_sources_repr[0] - elif len(input_sources_repr) > 1: instruction_repr["inputs"][dest_pin_name] = input_sources_repr - possible_output_pins = set(['out', 'out1', 'Q', 'eno', 'RET_VAL', 'DSTBLK', 'q', 'rt', 'rtbcd', 'cv', 'cvbcd']) - for source_pin_name in possible_output_pins: - source_key = (instruction_uid, source_pin_name) + if source_uid == "POWERRAIL": + input_sources_repr.append({"type": "powerrail"}) + elif source_uid in access_map: + input_sources_repr.append(access_map[source_uid]) + elif source_uid in parts_and_calls_map: + source_instr_info = parts_and_calls_map[source_uid] + source_original_type = source_instr_info["type"] + # Obtener el mapeo de salida para el tipo de la fuente (si existe) + source_output_mapping = {} + if source_original_type == "SdCoil": + source_output_mapping = {"out": "q"} + elif source_original_type in ["Se", "Sd"]: + source_output_mapping = {"q": "q", "rt": "rt"} + + # Usar el pin mapeado si existe, sino el original + mapped_source_pin = source_output_mapping.get(source_pin, source_pin) + + input_sources_repr.append({ + "type": "connection", + "source_instruction_type": source_original_type, # Guardar tipo original puede ser útil + "source_instruction_uid": source_uid, + "source_pin": mapped_source_pin # <-- USAR PIN MAPEADO + }) + else: + input_sources_repr.append( + {"type": "unknown_source", "uid": source_uid} + ) + + # Usar el nombre de pin mapeado para el JSON + json_pin_name = input_pin_mapping.get(xml_pin_name, xml_pin_name) + + if len(input_sources_repr) == 1: + instruction_repr["inputs"][json_pin_name] = input_sources_repr[0] + elif len(input_sources_repr) > 1: + instruction_repr["inputs"][json_pin_name] = input_sources_repr + + # Mapear Salidas usando el mapeo de pines + possible_output_pins = set( + [ + "out", + "out1", + "Q", + "q", + "eno", + "RET_VAL", + "DSTBLK", + "rt", + "rtbcd", + "cv", + "cvbcd", + "QU", + "QD", + ] + ) + for xml_pin_name in possible_output_pins: + source_key = (instruction_uid, xml_pin_name) if source_key in source_connections: + # Usar el nombre de pin mapeado para el JSON + json_pin_name = output_pin_mapping.get(xml_pin_name, xml_pin_name) + + if json_pin_name not in instruction_repr["outputs"]: + instruction_repr["outputs"][json_pin_name] = [] + for dest_uid, dest_pin in source_connections[source_key]: if dest_uid in access_map: - if source_pin_name not in instruction_repr["outputs"]: instruction_repr["outputs"][source_pin_name] = [] - if access_map[dest_uid] not in instruction_repr["outputs"][source_pin_name]: instruction_repr["outputs"][source_pin_name].append(access_map[dest_uid]) + if ( + access_map[dest_uid] + not in instruction_repr["outputs"][json_pin_name] + ): + instruction_repr["outputs"][json_pin_name].append( + access_map[dest_uid] + ) + all_logic_steps[instruction_uid] = instruction_repr # 4. Inferencia EN (sin cambios) - processed_blocks_en_inference = set(); something_changed = True; inference_passes = 0; max_inference_passes = len(all_logic_steps) + 5 - try: sorted_uids_for_en = sorted(all_logic_steps.keys(), key=lambda x: int(x) if x.isdigit() else float('inf')) - except ValueError: sorted_uids_for_en = sorted(all_logic_steps.keys()) - ordered_logic_list_for_en = [all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps] + processed_blocks_en_inference = set() + something_changed = True + inference_passes = 0 + max_inference_passes = len(all_logic_steps) + 5 + try: + sorted_uids_for_en = sorted( + all_logic_steps.keys(), + key=lambda x: int(x) if x.isdigit() else float("inf"), + ) + except ValueError: + sorted_uids_for_en = sorted(all_logic_steps.keys()) + ordered_logic_list_for_en = [ + all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps + ] while something_changed and inference_passes < max_inference_passes: - something_changed = False; inference_passes += 1 + something_changed = False + inference_passes += 1 for i, instruction in enumerate(ordered_logic_list_for_en): - part_uid = instruction["instruction_uid"]; part_type_original = instruction["type"].replace('_scl', '').replace('_error', '') - if (part_type_original in functional_block_types and "en" not in instruction["inputs"] and part_uid not in processed_blocks_en_inference): + part_uid = instruction["instruction_uid"] + part_type_original = ( + instruction["type"].replace("_scl", "").replace("_error", "") + ) + if ( + part_type_original in functional_block_types + and "en" not in instruction["inputs"] + and part_uid not in processed_blocks_en_inference + ): inferred_en_source = None if i > 0: for j in range(i - 1, -1, -1): - prev_instr = ordered_logic_list_for_en[j]; prev_uid = prev_instr["instruction_uid"]; prev_type_original = prev_instr["type"].replace('_scl', '').replace('_error', '') - if prev_type_original in rlo_generators: inferred_en_source = {"type": "connection", "source_instruction_uid": prev_uid, "source_instruction_type": prev_type_original, "source_pin": "out"}; break + prev_instr = ordered_logic_list_for_en[j] + prev_uid = prev_instr["instruction_uid"] + prev_type_original = ( + prev_instr["type"].replace("_scl", "").replace("_error", "") + ) + if prev_type_original in rlo_generators: + inferred_en_source = { + "type": "connection", + "source_instruction_uid": prev_uid, + "source_instruction_type": prev_type_original, + "source_pin": "out", + } + break elif prev_type_original in functional_block_types: source_key_eno = (prev_uid, "eno") - if source_key_eno in source_connections: inferred_en_source = {"type": "connection", "source_instruction_uid": prev_uid, "source_instruction_type": prev_type_original, "source_pin": "eno"}; break - else: continue - elif prev_type_original in ['Coil', 'SCoil', 'RCoil', 'SetCoil', 'ResetCoil', 'SdCoil']: break - if inferred_en_source: all_logic_steps[part_uid]["inputs"]["en"] = inferred_en_source; processed_blocks_en_inference.add(part_uid); something_changed = True + if source_key_eno in source_connections: + inferred_en_source = { + "type": "connection", + "source_instruction_uid": prev_uid, + "source_instruction_type": prev_type_original, + "source_pin": "eno", + } + break + else: + continue + elif prev_type_original in [ + "Coil", + "SCoil", + "RCoil", + "SetCoil", + "ResetCoil", + "SdCoil", + ]: + break + if inferred_en_source: + all_logic_steps[part_uid]["inputs"]["en"] = inferred_en_source + processed_blocks_en_inference.add(part_uid) + something_changed = True # 5. Añadir lógica ENO interesante (sin cambios) for source_instr_uid, eno_destinations in eno_outputs.items(): - if source_instr_uid not in all_logic_steps: continue + if source_instr_uid not in all_logic_steps: + continue interesting_eno_logic = [] for dest_uid, dest_pin in eno_destinations: is_direct_en_connection = False - if dest_uid in parts_and_calls_map and dest_pin == 'en': + if dest_uid in parts_and_calls_map and dest_pin == "en": try: - source_idx = sorted_uids_for_en.index(source_instr_uid); dest_idx = sorted_uids_for_en.index(dest_uid) - if dest_idx == source_idx + 1 and parts_and_calls_map[dest_uid]['type'] in functional_block_types: is_direct_en_connection = True - except ValueError: pass + source_idx = sorted_uids_for_en.index(source_instr_uid) + dest_idx = sorted_uids_for_en.index(dest_uid) + if ( + dest_idx == source_idx + 1 + and parts_and_calls_map[dest_uid]["type"] + in functional_block_types + ): + is_direct_en_connection = True + except ValueError: + pass if not is_direct_en_connection: target_info = {"target_pin": dest_pin} - if dest_uid in parts_and_calls_map: target_info.update({"target_type": "instruction", "target_uid": dest_uid, "target_name": parts_and_calls_map[dest_uid].get("name", parts_and_calls_map[dest_uid].get("type"))}) - elif dest_uid in access_map: target_info.update({"target_type": "operand", "target_details": access_map[dest_uid]}) - else: target_info.update({"target_type": "unknown", "target_uid": dest_uid}) + if dest_uid in parts_and_calls_map: + target_info.update( + { + "target_type": "instruction", + "target_uid": dest_uid, + "target_name": parts_and_calls_map[dest_uid].get( + "name", parts_and_calls_map[dest_uid].get("type") + ), + } + ) + elif dest_uid in access_map: + target_info.update( + { + "target_type": "operand", + "target_details": access_map[dest_uid], + } + ) + else: + target_info.update( + {"target_type": "unknown", "target_uid": dest_uid} + ) interesting_eno_logic.append(target_info) - if interesting_eno_logic: all_logic_steps[source_instr_uid]["eno_logic"] = interesting_eno_logic + if interesting_eno_logic: + all_logic_steps[source_instr_uid]["eno_logic"] = interesting_eno_logic # 6. Ordenar y Devolver (sin cambios) - network_logic_final = [all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps] - return {"id": network_id, "title": network_title, "comment": network_comment, "logic": network_logic_final} + network_logic_final = [ + all_logic_steps[uid] for uid in sorted_uids_for_en if uid in all_logic_steps + ] + return { + "id": network_id, + "title": network_title, + "comment": network_comment, + "logic": network_logic_final, + } + # --- Función Principal convert_xml_to_json (sin cambios en su flujo general) --- def convert_xml_to_json(xml_filepath, json_filepath): print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...") - if not os.path.exists(xml_filepath): print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'"); return + if not os.path.exists(xml_filepath): + print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'") + return try: print("Paso 1: Parseando archivo XML...") parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(xml_filepath, parser) root = tree.getroot() print("Paso 1: Parseo XML completado.") - print("Paso 2: Buscando el bloque SW.Blocks.FC...") # Asume FC primero + print("Paso 2: Buscando el bloque SW.Blocks.FC...") # Asume FC primero block_list = root.xpath("//*[local-name()='SW.Blocks.FC']") block_type_found = "FC" if not block_list: - block_list = root.xpath("//*[local-name()='SW.Blocks.FB']") # Busca FB si no hay FC + block_list = root.xpath( + "//*[local-name()='SW.Blocks.FB']" + ) # Busca FB si no hay FC block_type_found = "FB" - if not block_list: print("Error Crítico: No se encontró ni ."); return - else: print("Advertencia: Se encontró en lugar de .") + if not block_list: + print("Error Crítico: No se encontró ni .") + return + else: + print( + "Advertencia: Se encontró en lugar de ." + ) the_block = block_list[0] - print(f"Paso 2: Bloque SW.Blocks.{block_type_found} encontrado (ID={the_block.get('ID')}).") + print( + f"Paso 2: Bloque SW.Blocks.{block_type_found} encontrado (ID={the_block.get('ID')})." + ) print("Paso 3: Extrayendo atributos del bloque...") attribute_list_node = the_block.xpath("./*[local-name()='AttributeList']") block_name_val, block_number_val, block_lang_val = "Unknown", None, "Unknown" @@ -428,42 +832,79 @@ def convert_xml_to_json(xml_filepath, json_filepath): name_node = attr_list.xpath("./*[local-name()='Name']/text()") block_name_val = name_node[0].strip() if name_node else block_name_val num_node = attr_list.xpath("./*[local-name()='Number']/text()") - try: block_number_val = int(num_node[0]) if num_node else None - except ValueError: block_number_val = None - lang_node = attr_list.xpath("./*[local-name()='ProgrammingLanguage']/text()") + try: + block_number_val = int(num_node[0]) if num_node else None + except ValueError: + block_number_val = None + lang_node = attr_list.xpath( + "./*[local-name()='ProgrammingLanguage']/text()" + ) block_lang_val = lang_node[0].strip() if lang_node else block_lang_val - print(f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'") - else: print(f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}.") + print( + f"Paso 3: Atributos: Nombre='{block_name_val}', Número={block_number_val}, Lenguaje='{block_lang_val}'" + ) + else: + print( + f"Advertencia: No se encontró AttributeList para el bloque {block_type_found}." + ) block_comment_val = "" - comment_node_list = the_block.xpath("./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']") - if comment_node_list: block_comment_val = get_multilingual_text(comment_node_list[0]); print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'") - result = {"block_name": block_name_val, "block_number": block_number_val, "language": block_lang_val, "block_comment": block_comment_val, "interface": {}, "networks": []} + comment_node_list = the_block.xpath( + "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + ) + if comment_node_list: + block_comment_val = get_multilingual_text(comment_node_list[0]) + print(f"Paso 3b: Comentario bloque: '{block_comment_val[:50]}...'") + result = { + "block_name": block_name_val, + "block_number": block_number_val, + "language": block_lang_val, + "block_comment": block_comment_val, + "interface": {}, + "networks": [], + } print("Paso 4: Extrayendo la interfaz del bloque...") if attribute_list_node: - interface_node_list = attribute_list_node[0].xpath(".//*[local-name()='Interface']") + interface_node_list = attribute_list_node[0].xpath( + ".//*[local-name()='Interface']" + ) if interface_node_list: interface_node = interface_node_list[0] print("Paso 4: Nodo Interface encontrado.") for section in interface_node.xpath(".//iface:Section", namespaces=ns): - section_name = section.get("Name"); - if not section_name: continue + section_name = section.get("Name") + if not section_name: + continue members = [] for member in section.xpath("./iface:Member", namespaces=ns): - member_name = member.get("Name"); member_dtype = member.get("Datatype") - if member_name and member_dtype: members.append({"name": member_name, "datatype": member_dtype}) - if members: result["interface"][section_name] = members - if not result["interface"]: print("Advertencia: Interface sin secciones iface:Section válidas.") - else: print("Advertencia: No se encontró dentro de .") - if not result["interface"]: print("Advertencia: No se pudo extraer información de la interfaz.") + member_name = member.get("Name") + member_dtype = member.get("Datatype") + if member_name and member_dtype: + members.append( + {"name": member_name, "datatype": member_dtype} + ) + if members: + result["interface"][section_name] = members + if not result["interface"]: + print("Advertencia: Interface sin secciones iface:Section válidas.") + else: + print( + "Advertencia: No se encontró dentro de ." + ) + if not result["interface"]: + print("Advertencia: No se pudo extraer información de la interfaz.") print("Paso 5: Extrayendo y PROCESANDO lógica de redes (CompileUnits)...") networks_processed_count = 0 - result["networks"] = [] # Initialize networks list here + result["networks"] = [] # Initialize networks list here object_list_node = the_block.xpath("./*[local-name()='ObjectList']") if object_list_node: - compile_units = object_list_node[0].xpath("./*[local-name()='SW.Blocks.CompileUnit']") - print(f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit.") + compile_units = object_list_node[0].xpath( + "./*[local-name()='SW.Blocks.CompileUnit']" + ) + print( + f"Paso 5: Se encontraron {len(compile_units)} elementos SW.Blocks.CompileUnit." + ) for network_elem in compile_units: networks_processed_count += 1 @@ -474,78 +915,116 @@ def convert_xml_to_json(xml_filepath, json_filepath): # --- Detectar lenguaje de la red --- attribute_list = network_elem.xpath("./*[local-name()='AttributeList']") - programming_language = "LAD" # Default a LAD si no se especifica - network_source_node = None # Nodo + programming_language = "LAD" # Default a LAD si no se especifica + network_source_node = None # Nodo if attribute_list: - lang_node = attribute_list[0].xpath("./*[local-name()='ProgrammingLanguage']/text()") + lang_node = attribute_list[0].xpath( + "./*[local-name()='ProgrammingLanguage']/text()" + ) if lang_node: programming_language = lang_node[0].strip() # Obtener el nodo NetworkSource para pasarlo a los parsers - network_source_list = attribute_list[0].xpath("./*[local-name()='NetworkSource']") + network_source_list = attribute_list[0].xpath( + "./*[local-name()='NetworkSource']" + ) if network_source_list: network_source_node = network_source_list[0] - print(f" - Procesando Red ID={network_id}, Lenguaje={programming_language}") + print( + f" - Procesando Red ID={network_id}, Lenguaje={programming_language}" + ) # --- Extraer título y comentario (común) --- title_element = network_elem.xpath( ".//*[local-name()='MultilingualText'][@CompositionName='Title']" ) - network_title = get_multilingual_text(title_element[0]) if title_element else f"Network {network_id}" + network_title = ( + get_multilingual_text(title_element[0]) + if title_element + else f"Network {network_id}" + ) comment_element = network_elem.xpath( - "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + ) + network_comment = ( + get_multilingual_text(comment_element[0]) if comment_element else "" ) - network_comment = get_multilingual_text(comment_element[0]) if comment_element else "" # --- Procesar según el lenguaje --- parsed_network_data = None if programming_language == "SCL": - structured_text_node = network_source_node.xpath("./st:StructuredText", namespaces=ns) if network_source_node is not None else None + structured_text_node = ( + network_source_node.xpath("./st:StructuredText", namespaces=ns) + if network_source_node is not None + else None + ) reconstructed_scl = f"// SCL extraction failed for Network {network_id}: StructuredText node not found.\n" if structured_text_node: - print(f" Reconstruyendo SCL desde tokens para red {network_id}...") - reconstructed_scl = reconstruct_scl_from_tokens(structured_text_node[0]) + print( + f" Reconstruyendo SCL desde tokens para red {network_id}..." + ) + reconstructed_scl = reconstruct_scl_from_tokens( + structured_text_node[0] + ) # print(f" ... SCL reconstruido (parcial):\n{reconstructed_scl[:200]}...") # Preview opcional else: - print(f" Advertencia: No se encontró nodo para red SCL {network_id}.") + print( + f" Advertencia: No se encontró nodo para red SCL {network_id}." + ) parsed_network_data = { "id": network_id, "title": network_title, "comment": network_comment, "language": "SCL", - "logic": [{ - "instruction_uid": f"SCL_{network_id}", # UID inventado - "type": "RAW_SCL_CHUNK", - "scl": reconstructed_scl - }] + "logic": [ + { + "instruction_uid": f"SCL_{network_id}", # UID inventado + "type": "RAW_SCL_CHUNK", + "scl": reconstructed_scl, + } + ], } elif programming_language in ["LAD", "FBD"]: - # Para LAD/FBD, llamar a parse_network (que espera FlgNet dentro de NetworkSource) - # parse_network ya maneja su propio título/comentario si es necesario, pero podemos pasar los extraídos - # Nota: parse_network espera el *CompileUnit* element, no el NetworkSource - parsed_network_data = parse_network(network_elem) - if parsed_network_data: - parsed_network_data["language"] = programming_language # Asegurar que el lenguaje se guarda - if parsed_network_data.get("error"): - print(f" Error al parsear red {programming_language} ID={network_id}: {parsed_network_data['error']}") - # parsed_network_data = None # Descomentar para omitir redes con error - else: - print(f" Error: parse_network devolvió None para red {programming_language} ID={network_id}") + # Para LAD/FBD, llamar a parse_network (que espera FlgNet dentro de NetworkSource) + # parse_network ya maneja su propio título/comentario si es necesario, pero podemos pasar los extraídos + # Nota: parse_network espera el *CompileUnit* element, no el NetworkSource + parsed_network_data = parse_network(network_elem) + if parsed_network_data: + parsed_network_data["language"] = ( + programming_language # Asegurar que el lenguaje se guarda + ) + if parsed_network_data.get("error"): + print( + f" Error al parsear red {programming_language} ID={network_id}: {parsed_network_data['error']}" + ) + # parsed_network_data = None # Descomentar para omitir redes con error + else: + print( + f" Error: parse_network devolvió None para red {programming_language} ID={network_id}" + ) else: # Manejar otros lenguajes o casos inesperados - print(f" Advertencia: Lenguaje no soportado '{programming_language}' en red ID={network_id}. Creando placeholder.") + print( + f" Advertencia: Lenguaje no soportado '{programming_language}' en red ID={network_id}. Creando placeholder." + ) parsed_network_data = { "id": network_id, "title": network_title, "comment": network_comment, "language": programming_language, - "logic": [{"instruction_uid": f"UNS_{network_id}", "type": "UNSUPPORTED_LANG", "scl": f"// Network {network_id} uses unsupported language: {programming_language}\n"}] + "logic": [ + { + "instruction_uid": f"UNS_{network_id}", + "type": "UNSUPPORTED_LANG", + "scl": f"// Network {network_id} uses unsupported language: {programming_language}\n", + } + ], } # Añadir la red procesada (si es válida) al resultado @@ -555,20 +1034,38 @@ def convert_xml_to_json(xml_filepath, json_filepath): # --- Fin del bucle for network_elem --- if networks_processed_count == 0: - print("Advertencia: ObjectList no contenía elementos SW.Blocks.CompileUnit.") + print( + "Advertencia: ObjectList no contenía elementos SW.Blocks.CompileUnit." + ) else: print("Advertencia: No se encontró ObjectList para el bloque.") print("Paso 6: Escribiendo el resultado en el archivo JSON...") - if not result["interface"]: print("ADVERTENCIA FINAL: 'interface' está vacía.") - if not result["networks"]: print("ADVERTENCIA FINAL: 'networks' está vacía.") + if not result["interface"]: + print("ADVERTENCIA FINAL: 'interface' está vacía.") + if not result["networks"]: + print("ADVERTENCIA FINAL: 'networks' está vacía.") try: - with open(json_filepath, "w", encoding="utf-8") as f: json.dump(result, f, indent=4, ensure_ascii=False) - print("Paso 6: Escritura completada."); print(f"Conversión finalizada. JSON guardado en: '{json_filepath}'") - except IOError as e: print(f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}") - except TypeError as e: print(f"Error Crítico: Problema al serializar a JSON. Error: {e}") - except etree.XMLSyntaxError as e: print(f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}") - except Exception as e: print(f"Error Crítico: Error inesperado durante la conversión: {e}"); print("--- Traceback ---"); traceback.print_exc(); print("--- Fin Traceback ---") + with open(json_filepath, "w", encoding="utf-8") as f: + json.dump(result, f, indent=4, ensure_ascii=False) + print("Paso 6: Escritura completada.") + print(f"Conversión finalizada. JSON guardado en: '{json_filepath}'") + except IOError as e: + print( + f"Error Crítico: No se pudo escribir JSON en '{json_filepath}'. Error: {e}" + ) + except TypeError as e: + print(f"Error Crítico: Problema al serializar a JSON. Error: {e}") + except etree.XMLSyntaxError as e: + print( + f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}" + ) + except Exception as e: + print(f"Error Crítico: Error inesperado durante la conversión: {e}") + print("--- Traceback ---") + traceback.print_exc() + print("--- Fin Traceback ---") + # --- Punto de Entrada Principal --- if __name__ == "__main__": @@ -577,12 +1074,14 @@ if __name__ == "__main__": import os import sys - parser = argparse.ArgumentParser(description="Convert Simatic XML LAD/FBD to simplified JSON.") + parser = argparse.ArgumentParser( + description="Convert Simatic XML LAD/FBD to simplified JSON." + ) parser.add_argument( "xml_filepath", nargs="?", # Argumento opcional default="TestLAD.xml", # Valor por defecto si se ejecuta sin argumentos - help="Path to the input XML file (default: TestLAD.xml)" + help="Path to the input XML file (default: TestLAD.xml)", ) args = parser.parse_args() @@ -591,15 +1090,17 @@ if __name__ == "__main__": # Verificar si el archivo de entrada existe if not os.path.exists(xml_input_file): print(f"Error Crítico: Archivo XML no encontrado: '{xml_input_file}'") - sys.exit(1) # Salir si el archivo no existe + sys.exit(1) # Salir si el archivo no existe # Derivar nombre base para archivo de salida JSON # os.path.basename obtiene el nombre del archivo de la ruta # os.path.splitext divide el nombre y la extensión xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0] # Construir la ruta de salida en el mismo directorio que el script o el XML - output_dir = os.path.dirname(xml_input_file) # O usar os.path.dirname(__file__) para el directorio del script + output_dir = os.path.dirname( + xml_input_file + ) # O usar os.path.dirname(__file__) para el directorio del script json_output_file = os.path.join(output_dir, f"{xml_filename_base}_simplified.json") # Llamar a la función principal con los nombres de archivo derivados - convert_xml_to_json(xml_input_file, json_output_file) \ No newline at end of file + convert_xml_to_json(xml_input_file, json_output_file) diff --git a/x2_process.py b/x2_process.py index 505447c..ced7953 100644 --- a/x2_process.py +++ b/x2_process.py @@ -1132,6 +1132,390 @@ def process_call(instruction, network_id, scl_map, access_map): return True +# --- Procesador de Temporizadores (TON, TOF) --- +def process_timer(instruction, network_id, scl_map, access_map): + """ + Genera SCL para Temporizadores (TON, TOF). + Requiere datos de instancia (DB o STAT). + """ + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] # Será "TON" o "TOF" + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_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) + + if scl_in is None or scl_pt is None: + return False # Dependencias no listas + + # 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.") + else: + instance_name = format_variable_name(instance_name) # Limpiar si viene de x1 + + # 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 + + return True + +# --- Procesador de Contadores (CTU, CTD, CTUD) --- +def process_counter(instruction, network_id, scl_map, access_map): + """ + 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: + return False + + # 1. Obtener Inputs (varía según tipo) + params = [] + resolved = 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 + + 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 not 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. 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" + + instruction["scl"] = scl_call + instruction["type"] = instr_type + 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"] + + 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 + + return True + +# --- Procesador de Comparadores (EQ ya existe, añadir otros) --- +def process_comparison(instruction, network_id, scl_map, access_map): + """ + Genera la expresión SCL para Comparadores (GT, LT, GE, LE, NE). + El resultado se propaga por scl_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: + 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" + return True + + # Obtener operandos + 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) + + if in1_scl is None or in2_scl is None: + return False # Dependencias no listas + + # 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 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 + + comparison_scl = f"{op1} {scl_operator} {op2}" + + # Guardar resultado en el mapa para 'out' + 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 + + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = en_scl + + instruction["scl"] = f"// Comparison {instr_type} {instr_uid}: {comparison_scl}" + instruction["type"] = instr_type + SCL_SUFFIX + return True + +# --- Procesador de Matemáticas (ADD ya existe, añadir otros) --- +def process_math(instruction, network_id, scl_map, access_map): + """ + Genera SCL para operaciones matemáticas (SUB, MUL, DIV). + """ + instr_uid = instruction["instruction_uid"] + instr_type = instruction["type"] # SUB, MUL, DIV + if instr_type.endswith(SCL_SUFFIX) or "_error" in instr_type: + return False + + # Mapa de tipos a operadores SCL + op_map = {"SUB": "-", "MUL": "*", "DIV": "/"} + scl_operator = op_map.get(instr_type) + if not scl_operator: + instruction["scl"] = f"// ERROR: Operación matemática no soportada: {instr_type}" + instruction["type"] += "_error" + return True + + # Obtener EN, IN1, IN2 + 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) + + if en_scl is None or in1_scl is None or in2_scl is None: + return False # Dependencias no listas + + # 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 + + # 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 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 + + # 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 + + instruction["scl"] = scl_final + instruction["type"] = instr_type + SCL_SUFFIX + + # Actualizar mapa SCL + map_key_out = (network_id, instr_uid, "out") + scl_map[map_key_out] = target_scl + map_key_eno = (network_id, instr_uid, "eno") + scl_map[map_key_eno] = en_scl + return True + +# --- Procesador NOT --- +def process_not(instruction, network_id, scl_map, access_map): + """Genera la expresión SCL 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: + return False + + in_info = instruction["inputs"].get("in") + in_scl = get_scl_representation(in_info, network_id, scl_map, access_map) + + if in_scl is None: + return False # Dependencia no lista + + # 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})" + + result_scl = f"NOT {in_scl_formatted}" + + # Guardar resultado en mapa para 'out' + map_key_out = (network_id, instr_uid, "out") + scl_map[map_key_out] = result_scl + + 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 + +# EN x2_process.py, junto a otros procesadores + +# --- Procesador para Se (Timer Pulse -> TP SCL) --- +def process_se(instruction, network_id, scl_map, access_map): + """ + Genera SCL para Temporizador de Pulso (Se -> TP). + Requiere datos de instancia (DB o STAT/TEMP). + """ + instr_uid = instruction["instruction_uid"] + instr_type = "Se" # Tipo original LAD + if instruction["type"].endswith(SCL_SUFFIX) or "_error" in instruction["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 + + 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) + + if scl_s is None or scl_tv is None: + return False # Dependencias no listas + + # 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!") + + # 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 + + # 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" + + instruction["scl"] = scl_call + instruction["type"] = instr_type + SCL_SUFFIX + + # 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 + + 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" + + return True + +# --- Procesador para Sd (On-Delay Timer -> TON SCL) --- +def process_sd(instruction, network_id, scl_map, access_map): + """ + 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"]: + return False + + # 1. Obtener Inputs: s (start), tv (time value) + # El pin 'r' (reset) no tiene equivalente directo en TON, 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 + + 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) + + if scl_s is None or scl_tv is None: + return False # Dependencias no listas + + # 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!") + + # 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 + + # 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" + + instruction["scl"] = scl_call + instruction["type"] = instr_type + SCL_SUFFIX + + # 5. Actualizar scl_map para las salidas Q y RT (mapeado a ET de TON) + map_key_q = (network_id, instr_uid, "q") + scl_map[map_key_q] = f"{instance_name}.Q" + map_key_rt = (network_id, instr_uid, "rt") + scl_map[map_key_rt] = f"{instance_name}.ET" + + return True + # --- NUEVO: Procesador de Agrupación (Refinado) --- def process_group_ifs(instruction, network_id, scl_map, access_map): """ @@ -1289,7 +1673,6 @@ def process_group_ifs(instruction, network_id, scl_map, access_map): return made_change - # --- Bucle Principal de Procesamiento --- def process_json_to_scl(json_filepath): """Lee el JSON, aplica los procesadores iterativamente y guarda el resultado.""" @@ -1356,21 +1739,27 @@ def process_json_to_scl(json_filepath): # Lista y mapa de procesadores base base_processors = [ - process_convert, - process_mod, - process_eq, - process_contact, - process_o, - process_edge_detector, - process_add, - process_move, - process_call, - process_coil, - process_scoil, # <--- Añadir aquí - process_rcoil, # <--- Añadir aquí - process_blkmov, # El que añadimos antes - # ... otros procesadores base ... - ] + process_convert, + process_mod, + process_eq, + process_contact, + process_o, + process_not, # <-- Nuevo + process_edge_detector, + process_comparison, # <-- Nuevo (para GT, LT, etc.) + process_add, + process_math, # <-- Nuevo (para SUB, MUL, DIV) + process_move, + process_timer, # <-- Nuevo + process_se, # <-- Añadido + process_sd, # <-- Añadido + process_counter, # <-- Nuevo + process_call, + process_coil, + process_scoil, + process_rcoil, + process_blkmov, + ] # Crear mapa por nombre de tipo original (en minúsculas) processor_map = {} for func in base_processors: @@ -1389,9 +1778,35 @@ def process_json_to_scl(json_filepath): elif type_name == "scoil": processor_map[type_name] = func elif type_name == "rcoil": - processor_map[type_name] = func - else: processor_map[type_name] = func + elif type_name == "se": + processor_map["se"] = func + processor_map["sdcoil"] = func # Mapear SdCoil a process_se basado en análisis + elif type_name == "sd": + processor_map["sd"] = func + elif type_name == "timer": + processor_map["ton"] = func # Mapear TON al procesador de timer + processor_map["tof"] = func # Mapear TOF al procesador de timer + elif type_name == "counter": + processor_map["ctu"] = func + processor_map["ctd"] = func + processor_map["ctud"] = func + elif type_name == "comparison": + processor_map["gt"] = func + processor_map["lt"] = func + processor_map["ge"] = func + processor_map["le"] = func + processor_map["ne"] = func + # EQ ya tiene su propio procesador (process_eq), así que no lo añadimos aquí. + elif type_name == "math": + processor_map["sub"] = func + processor_map["mul"] = func + processor_map["div"] = func + # ADD ya tiene su propio procesador (process_add) + elif type_name == "not": + processor_map["not"] = func # Mapear 'not' + elif type_name not in processor_map: + processor_map[type_name] = func print("\n--- Iniciando Bucle de Procesamiento Iterativo ---") while passes < max_passes and not processing_complete: