diff --git a/BlenderCtrl_ProdModeInit_simplified.json b/BlenderCtrl_ProdModeInit_simplified.json new file mode 100644 index 0000000..93e8516 --- /dev/null +++ b/BlenderCtrl_ProdModeInit_simplified.json @@ -0,0 +1,97 @@ +{ + "block_name": "BlenderCtrl_ProdModeInit", + "block_number": 2012, + "language": "LAD", + "block_comment": "", + "interface": { + "Return": [ + { + "name": "Ret_Val", + "datatype": "Void" + } + ] + }, + "networks": [ + { + "id": "9", + "title": "PID Reset Integral", + "comment": "", + "logic": [ + { + "instruction_uid": "21", + "uid": "21", + "type": "Call", + "block_name": "BlenderPID_PIDResInteg", + "block_type": "FC", + "inputs": { + "en": { + "type": "powerrail" + } + }, + "outputs": {} + } + ], + "language": "LAD" + }, + { + "id": "1A", + "title": "Ctrl Init Errors", + "comment": "", + "logic": [ + { + "instruction_uid": "21", + "uid": "21", + "type": "Call", + "block_name": "BlenderCtrl_InitErrors", + "block_type": "FC", + "inputs": { + "en": { + "type": "powerrail" + } + }, + "outputs": {} + } + ], + "language": "LAD" + }, + { + "id": "2B", + "title": "RunOut Counter", + "comment": "", + "logic": [ + { + "instruction_uid": "23", + "uid": "23", + "type": "Move", + "template_values": { + "Card": "Cardinality" + }, + "negated_pins": {}, + "inputs": { + "en": { + "type": "powerrail" + }, + "in": { + "uid": "21", + "scope": "LiteralConstant", + "type": "constant", + "datatype": "Real", + "value": 0.0 + } + }, + "outputs": { + "out1": [ + { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"HMI_Variables_Status\".\"Analog_Values\".\"TP301RunOutCount\"" + } + ] + } + } + ], + "language": "LAD" + } + ] +} \ No newline at end of file diff --git a/BlenderCtrl_ProdModeInit_simplified_processed.json b/BlenderCtrl_ProdModeInit_simplified_processed.json new file mode 100644 index 0000000..344db25 --- /dev/null +++ b/BlenderCtrl_ProdModeInit_simplified_processed.json @@ -0,0 +1,100 @@ +{ + "block_name": "BlenderCtrl_ProdModeInit", + "block_number": 2012, + "language": "LAD", + "block_comment": "", + "interface": { + "Return": [ + { + "name": "Ret_Val", + "datatype": "Void" + } + ] + }, + "networks": [ + { + "id": "9", + "title": "PID Reset Integral", + "comment": "", + "logic": [ + { + "instruction_uid": "21", + "uid": "21", + "type": "Call_FC_sympy_processed", + "block_name": "BlenderPID_PIDResInteg", + "block_type": "FC", + "inputs": { + "en": { + "type": "powerrail" + } + }, + "outputs": {}, + "scl": "BlenderPID_PIDResInteg();" + } + ], + "language": "LAD" + }, + { + "id": "1A", + "title": "Ctrl Init Errors", + "comment": "", + "logic": [ + { + "instruction_uid": "21", + "uid": "21", + "type": "Call_FC_sympy_processed", + "block_name": "BlenderCtrl_InitErrors", + "block_type": "FC", + "inputs": { + "en": { + "type": "powerrail" + } + }, + "outputs": {}, + "scl": "BlenderCtrl_InitErrors();" + } + ], + "language": "LAD" + }, + { + "id": "2B", + "title": "RunOut Counter", + "comment": "", + "logic": [ + { + "instruction_uid": "23", + "uid": "23", + "type": "Move_sympy_processed", + "template_values": { + "Card": "Cardinality" + }, + "negated_pins": {}, + "inputs": { + "en": { + "type": "powerrail" + }, + "in": { + "uid": "21", + "scope": "LiteralConstant", + "type": "constant", + "datatype": "Real", + "value": 0.0 + } + }, + "outputs": { + "out1": [ + { + "uid": "22", + "scope": "GlobalVariable", + "type": "variable", + "name": "\"HMI_Variables_Status\".\"Analog_Values\".\"TP301RunOutCount\"" + } + ] + }, + "scl": "\"HMI_Variables_Status\".\"Analog_Values\".\"TP301RunOutCount\" := 0.0;" + } + ], + "language": "LAD" + } + ] +} \ No newline at end of file diff --git a/BlenderCtrl_ProdModeInit_simplified_processed.scl b/BlenderCtrl_ProdModeInit_simplified_processed.scl new file mode 100644 index 0000000..7f1a373 --- /dev/null +++ b/BlenderCtrl_ProdModeInit_simplified_processed.scl @@ -0,0 +1,30 @@ +// Block Name (Original): BlenderCtrl_ProdModeInit +// Block Number: 2012 +// Original Language: LAD + +FUNCTION_BLOCK "BlenderCtrl_ProdModeInit" +{ S7_Optimized_Access := 'TRUE' } +VERSION : 0.1 + +VAR_RETURN + Ret_Val : Void; +END_VAR + +VAR_TEMP +END_VAR + +BEGIN + + // Network 1: PID Reset Integral (Original Language: LAD) + + BlenderPID_PIDResInteg(); + + // Network 2: Ctrl Init Errors (Original Language: LAD) + + BlenderCtrl_InitErrors(); + + // Network 3: RunOut Counter (Original Language: LAD) + + "HMI_Variables_Status"."Analog_Values"."TP301RunOutCount" := 0.0; + +END_FUNCTION_BLOCK diff --git a/TestLAD.xml b/TestLAD.xml index 3fe7fd2..0f704be 100644 --- a/TestLAD.xml +++ b/TestLAD.xml @@ -8,17 +8,18 @@
- - - - - - - + + + + + + + +
- +
Optimized @@ -77,162 +78,776 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD + + + + + + + + true + + + + + + + + + + + 0 + + + + + + + + + + true + + + + + + + + + + + + true + + + + + + + + + + + 100 + + + + + + + + + true + + + + + + + + + + + + + + + + true + + + + + + + + + + + 0 + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + 100 + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + 60 + + + + + + + + + + 100 + + + + + + + + + + true + + + + + + + + + + + + + + + + true + + + + + + + + + + + 0 + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + 100 + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + 60 + + + + + + + + 1000 + + + + + + + + + + 100 + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + 1 + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + SCL @@ -286,7 +901,7 @@ it-IT - Manual Syrup Drain Valve Open - Operator Alarm + @@ -329,1412 +944,45 @@ - - - - - - - - - - - - - - - - - S5T#1S - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - + - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - Manual Syrup Drain Valve Open - Operator Alarm - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - S5T#2S - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - ResetTotalizer - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S5T#2S - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - ResetWaterTot - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S5T#2S - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - ResetCO2Tot - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S5T#2S - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - ResetProductTot - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S5T#2S - - - - - - - - - - - 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - ResetCO2Tot - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S5T#500ms - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - LAD - - - - - - - it-IT - - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - it-IT - Mod Copy Recipe - - - - - de-DE - - - - - - en-US - - - - - - es-ES - - - - - - fr-FR - - - - - - zh-CN - - - - - - ja-JP - - - - - - - - - - + it-IT - + de-DE - + en-US - + es-ES - + fr-FR - + zh-CN - + ja-JP diff --git a/TestLAD_simplified.json b/TestLAD_simplified.json index 41de9f8..2ef1ac5 100644 --- a/TestLAD_simplified.json +++ b/TestLAD_simplified.json @@ -6,1278 +6,58 @@ "interface": { "Temp": [ { - "name": "All_Auto_RETVAL", - "datatype": "Int" + "name": "mWaterMaxFlow", + "datatype": "Real" }, { - "name": "Reset_SP_Word_RETVAL", - "datatype": "Int" + "name": "mWaterMinFlow", + "datatype": "Real" }, { - "name": "mResetWaterTot", - "datatype": "Bool" + "name": "mSyrupMaxFlow", + "datatype": "Real" }, { - "name": "mResetSyrupTot", - "datatype": "Bool" + "name": "mSyrupMinFlow", + "datatype": "Real" }, { - "name": "mResetCO2Tot", - "datatype": "Bool" + "name": "mMinRatio", + "datatype": "Real" }, { - "name": "mResetProductTot", - "datatype": "Bool" + "name": "mMaxRatio", + "datatype": "Real" }, { - "name": "Block_Move_Err", - "datatype": "Int" + "name": "mBevBrixMax", + "datatype": "Real" + }, + { + "name": "mBevBrixMin", + "datatype": "Real" } ], "Return": [ { "name": "Ret_Val", - "datatype": "Void" + "datatype": "Real" } ] }, "networks": [ { "id": "9", - "title": "Manual Syrup Drain Valve Open - Operator Alarm", + "title": "", "comment": "", + "language": "SCL", "logic": [ { - "instruction_uid": "30", - "uid": "30", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gSyrupRoomEn\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "31", - "uid": "31", - "type": "Contact", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_HVP301_Aux\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "30", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "32", - "uid": "32", - "type": "Contact", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "33", - "uid": "33", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "32", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "34", - "uid": "34", - "type": "Contact", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"Syr_RunOut\".\"Done\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "35", - "uid": "35", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gBlenderCIPMode\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "36", - "uid": "36", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "27", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_CIP_CIPRunning\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "35", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "37", - "uid": "37", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "28", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "36", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "38", - "uid": "38", - "type": "O", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "34", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "37", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "39", - "uid": "39", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gHVP301_Open\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "38", - "source_pin": "out" - } - }, - "outputs": {} + "instruction_uid": "SCL_9", + "type": "RAW_SCL_CHUNK", + "scl": "IF \"Blender_Variables\".gSP_H2O <> 0 THEN\n \"Blender_Variables\".gWaterVFMCalcError := \"Blender_Variables\".gWaterVFMMeasError / 100 * \"Blender_Variables\".gSP_H2O;\nEND_IF;\nIF \"Blender_Variables\".gSP_SYR <> 0 THEN\n \"Blender_Variables\".gSyrupMFMCalcError := (\"Blender_Variables\".gSyrupMFMMeasError / 100 + (\"Blender_Variables\".gSyrupMFMZeroStab / (\"Blender_Variables\".gSP_SYR * 60)) / 100) * \"Blender_Variables\".gSP_SYR;\nEND_IF;\nIF \"Blender_Variables\".gSP_CO2 <> 0 THEN\n \"Blender_Variables\".gCO2MFMCalcError := (\"Blender_Variables\".gCO2MFMMeasError / 100 + (\"Blender_Variables\".gCO2MFMZeroStab / (\"Blender_Variables\".gSP_CO2 * 60 / 1000)) / 100) * \"Blender_Variables\".gSP_CO2;\nEND_IF;\n\"mWaterMaxFlow\" := \"Blender_Variables\".gSP_H2O + \"Blender_Variables\".gWaterVFMCalcError;\n\"mWaterMinFlow\" := \"Blender_Variables\".gSP_H2O - \"Blender_Variables\".gWaterVFMCalcError;\nIF \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN\n \"mSyrupMaxFlow\" := (\"Blender_Variables\".gSP_SYR + \"Blender_Variables\".gSyrupMFMCalcError) / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity;\n \"mSyrupMinFlow\" := (\"Blender_Variables\".gSP_SYR - \"Blender_Variables\".gSyrupMFMCalcError) / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity;\nEND_IF;\nIF \"mSyrupMaxFlow\" <> 0 THEN\n \"mMinRatio\" := \"mWaterMinFlow\" / \"mSyrupMaxFlow\";\nEND_IF;\nIF \"mSyrupMinFlow\" <> 0 THEN\n \"mMaxRatio\" := \"mWaterMaxFlow\" / \"mSyrupMinFlow\";\nEND_IF;\nIF \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN\n \"mBevBrixMax\" := \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupBrix / ((\"mMinRatio\" / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity) + 1);\n \"mBevBrixMin\" := \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupBrix / ((\"mMaxRatio\" / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity) + 1);\nEND_IF;\n\"Blender_Variables\".gBlenderBlendMaxError := \"mBevBrixMax\" - \"mBevBrixMin\";\n\"TestLAD\" := \"Blender_Variables\".gBlenderBlendMaxError;" } - ], - "language": "LAD" - }, - { - "id": "1A", - "title": "Manual Syrup Drain Valve Open - Operator Alarm", - "comment": "", - "logic": [ - { - "instruction_uid": "25", - "uid": "25", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_HVM302_Aux\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "26", - "uid": "26", - "type": "Sd", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "25", - "source_pin": "out" - }, - "timer": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mHVM302_Dly\"" - }, - "tv": { - "uid": "23", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#1S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "25", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gHVM302_Open\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Sd", - "source_instruction_uid": "26", - "source_pin": "q" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "2B", - "title": "ResetTotalizer", - "comment": "", - "logic": [ - { - "instruction_uid": "24", - "uid": "24", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gBlendResetTotalizer\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "25", - "uid": "25", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "24", - "source_pin": "out" - }, - "timer": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "tv": { - "uid": "23", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "24", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "3C", - "title": "ResetWaterTot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTN301_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTN301TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetWaterTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "4D", - "title": "ResetCO2Tot", - "comment": "", - "logic": [ - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTP302_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "O", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "28", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "29", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTP302TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "29", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "31", - "uid": "31", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gSyrupRoomEn\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "30", - "source_pin": "q" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "32", - "uid": "32", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetSyrupTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "5E", - "title": "ResetProductTot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTM303_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTM303TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetCO2Tot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "6F", - "title": "ResetCO2Tot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gProductMFMResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetProductTotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetProductTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {} - } - ], - "language": "LAD" - }, - { - "id": "80", - "title": "Mod Copy Recipe", - "comment": "", - "logic": [ - { - "instruction_uid": "33", - "uid": "33", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "34", - "uid": "34", - "type": "Contact", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mFP_Recip_Main_Page\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "35", - "uid": "35", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mAux_FP_M700_1\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "34", - "source_pin": "out" - } - }, - "outputs": {} - }, - { - "instruction_uid": "36", - "uid": "36", - "type": "Coil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mFP_Recip_Main_Page\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": {} - }, - { - "instruction_uid": "37", - "uid": "37", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "38", - "uid": "38", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "37", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "39", - "uid": "39", - "type": "Se", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "tv": { - "uid": "28", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#500ms" - }, - "timer": { - "uid": "27", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"T_Pulse_Recipe_Edit\"" - }, - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "38", - "source_pin": "out" - }, - "en": { - "type": "connection", - "source_instruction_uid": "38", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - } - }, - { - "instruction_uid": "40", - "uid": "40", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"T_Pulse_Recipe_Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "SdCoil", - "source_instruction_uid": "39", - "source_pin": "q" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "41", - "uid": "41", - "type": "RCoil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "30", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "40", - "source_pin": "out" - } - }, - "outputs": {} - }, - { - "instruction_uid": "42", - "uid": "42", - "type": "Contact", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "31", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mAux_FP_M700_1\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - } - }, - { - "instruction_uid": "43", - "uid": "43", - "type": "SCoil", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "32", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "42", - "source_pin": "out" - } - }, - "outputs": {} - } - ], - "language": "LAD" + ] } ] } \ No newline at end of file diff --git a/TestLAD_simplified_processed.json b/TestLAD_simplified_processed.json index 449f7ff..2ef1ac5 100644 --- a/TestLAD_simplified_processed.json +++ b/TestLAD_simplified_processed.json @@ -6,1325 +6,58 @@ "interface": { "Temp": [ { - "name": "All_Auto_RETVAL", - "datatype": "Int" + "name": "mWaterMaxFlow", + "datatype": "Real" }, { - "name": "Reset_SP_Word_RETVAL", - "datatype": "Int" + "name": "mWaterMinFlow", + "datatype": "Real" }, { - "name": "mResetWaterTot", - "datatype": "Bool" + "name": "mSyrupMaxFlow", + "datatype": "Real" }, { - "name": "mResetSyrupTot", - "datatype": "Bool" + "name": "mSyrupMinFlow", + "datatype": "Real" }, { - "name": "mResetCO2Tot", - "datatype": "Bool" + "name": "mMinRatio", + "datatype": "Real" }, { - "name": "mResetProductTot", - "datatype": "Bool" + "name": "mMaxRatio", + "datatype": "Real" }, { - "name": "Block_Move_Err", - "datatype": "Int" + "name": "mBevBrixMax", + "datatype": "Real" + }, + { + "name": "mBevBrixMin", + "datatype": "Real" } ], "Return": [ { "name": "Ret_Val", - "datatype": "Void" + "datatype": "Real" } ] }, "networks": [ { "id": "9", - "title": "Manual Syrup Drain Valve Open - Operator Alarm", + "title": "", "comment": "", + "language": "SCL", "logic": [ { - "instruction_uid": "30", - "uid": "30", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gSyrupRoomEn\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_" - }, - { - "instruction_uid": "31", - "uid": "31", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_HVP301_Aux\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "30", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & ~v1_" - }, - { - "instruction_uid": "32", - "uid": "32", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & ~v1_ & ~v2_" - }, - { - "instruction_uid": "33", - "uid": "33", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "32", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v3_ & ~v1_ & ~v2_" - }, - { - "instruction_uid": "34", - "uid": "34", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"Syr_RunOut\".\"Done\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v3_ & ~v1_ & ~v2_ & ~v4_" - }, - { - "instruction_uid": "35", - "uid": "35", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gBlenderCIPMode\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v5_ & ~v1_" - }, - { - "instruction_uid": "36", - "uid": "36", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "27", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_CIP_CIPRunning\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "35", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v5_ & v6_ & ~v1_" - }, - { - "instruction_uid": "37", - "uid": "37", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "28", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"Procedure_Variables\".\"Blender_Run\".\"Running\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "36", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v5_ & v6_ & v7_ & ~v1_" - }, - { - "instruction_uid": "38", - "uid": "38", - "type": "O_sympy_processed", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "34", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "37", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy O: (v0_ & v5_ & v6_ & v7_ & ~v1_) | (v0_ & v3_ & ~v1_ & ~v2_ & ~v4_)" - }, - { - "instruction_uid": "39", - "uid": "39", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gHVP301_Open\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "38", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "\"gHVP301_Open\" := (\"gSyrupRoomEn\" AND \"gBlenderCIPMode\" AND \"gIN_CIP_CIPRunning\" AND \"Procedure_Variables\".\"Blender_Run\".\"Running\" AND NOT \"gIN_HVP301_Aux\") OR (\"gSyrupRoomEn\" AND \"Procedure_Variables\".\"FTP302Line_Preparation\".\"Done\" AND NOT \"gIN_HVP301_Aux\" AND NOT \"HMI_Blender_Parameters\".\"Processor_Options\".\"Blender_OPT\".\"_FastChangeOverEnabled\" AND NOT \"Procedure_Variables\".\"Syr_RunOut\".\"Done\");" + "instruction_uid": "SCL_9", + "type": "RAW_SCL_CHUNK", + "scl": "IF \"Blender_Variables\".gSP_H2O <> 0 THEN\n \"Blender_Variables\".gWaterVFMCalcError := \"Blender_Variables\".gWaterVFMMeasError / 100 * \"Blender_Variables\".gSP_H2O;\nEND_IF;\nIF \"Blender_Variables\".gSP_SYR <> 0 THEN\n \"Blender_Variables\".gSyrupMFMCalcError := (\"Blender_Variables\".gSyrupMFMMeasError / 100 + (\"Blender_Variables\".gSyrupMFMZeroStab / (\"Blender_Variables\".gSP_SYR * 60)) / 100) * \"Blender_Variables\".gSP_SYR;\nEND_IF;\nIF \"Blender_Variables\".gSP_CO2 <> 0 THEN\n \"Blender_Variables\".gCO2MFMCalcError := (\"Blender_Variables\".gCO2MFMMeasError / 100 + (\"Blender_Variables\".gCO2MFMZeroStab / (\"Blender_Variables\".gSP_CO2 * 60 / 1000)) / 100) * \"Blender_Variables\".gSP_CO2;\nEND_IF;\n\"mWaterMaxFlow\" := \"Blender_Variables\".gSP_H2O + \"Blender_Variables\".gWaterVFMCalcError;\n\"mWaterMinFlow\" := \"Blender_Variables\".gSP_H2O - \"Blender_Variables\".gWaterVFMCalcError;\nIF \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN\n \"mSyrupMaxFlow\" := (\"Blender_Variables\".gSP_SYR + \"Blender_Variables\".gSyrupMFMCalcError) / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity;\n \"mSyrupMinFlow\" := (\"Blender_Variables\".gSP_SYR - \"Blender_Variables\".gSyrupMFMCalcError) / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity;\nEND_IF;\nIF \"mSyrupMaxFlow\" <> 0 THEN\n \"mMinRatio\" := \"mWaterMinFlow\" / \"mSyrupMaxFlow\";\nEND_IF;\nIF \"mSyrupMinFlow\" <> 0 THEN\n \"mMaxRatio\" := \"mWaterMaxFlow\" / \"mSyrupMinFlow\";\nEND_IF;\nIF \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN\n \"mBevBrixMax\" := \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupBrix / ((\"mMinRatio\" / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity) + 1);\n \"mBevBrixMin\" := \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupBrix / ((\"mMaxRatio\" / \"HMI_Blender_Parameters\".Actual_Recipe_Parameters._SyrupDensity) + 1);\nEND_IF;\n\"Blender_Variables\".gBlenderBlendMaxError := \"mBevBrixMax\" - \"mBevBrixMin\";\n\"TestLAD\" := \"Blender_Variables\".gBlenderBlendMaxError;" } - ], - "language": "LAD" - }, - { - "id": "1A", - "title": "Manual Syrup Drain Valve Open - Operator Alarm", - "comment": "", - "logic": [ - { - "instruction_uid": "25", - "uid": "25", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gIN_HVM302_Aux\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v8_" - }, - { - "instruction_uid": "26", - "uid": "26", - "type": "Sd_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "25", - "source_pin": "out" - }, - "timer": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mHVM302_Dly\"" - }, - "tv": { - "uid": "23", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#1S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "25", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - }, - "scl": "\"mHVM302_Dly\"(IN := \"gIN_HVM302_Aux\", PT := S5T#1S); // TODO: Declarar \"mHVM302_Dly\" : TON;" - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gHVM302_Open\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Sd", - "source_instruction_uid": "26", - "source_pin": "q" - } - }, - "outputs": {}, - "scl": "\"gHVM302_Open\" := \"mHVM302_Dly\".Q;" - } - ], - "language": "LAD" - }, - { - "id": "2B", - "title": "ResetTotalizer", - "comment": "", - "logic": [ - { - "instruction_uid": "24", - "uid": "24", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gBlendResetTotalizer\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v9_" - }, - { - "instruction_uid": "25", - "uid": "25", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "24", - "source_pin": "out" - }, - "timer": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "tv": { - "uid": "23", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "24", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "\"mResetTotalizerTmr\"(IN := \"gBlendResetTotalizer\", PT := S5T#2S); // TODO: Declarar \"mResetTotalizerTmr\" : TP;" - } - ], - "language": "LAD" - }, - { - "id": "3C", - "title": "ResetWaterTot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTN301_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v10_" - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v11_" - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O_sympy_processed", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy O: v10_ | v11_" - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTN301TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - }, - "scl": "\"mResetFTN301TotTmr\"(IN := \"gFTN301_ResetTot\" OR \"mResetTotalizerTmr\", PT := S5T#2S); // TODO: Declarar \"mResetFTN301TotTmr\" : TP;" - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetWaterTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {}, - "scl": "\"mResetWaterTot\" := \"mResetFTN301TotTmr\".Q;" - } - ], - "language": "LAD" - }, - { - "id": "4D", - "title": "ResetCO2Tot", - "comment": "", - "logic": [ - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTP302_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v12_" - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v11_" - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "O_sympy_processed", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "28", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy O: v11_ | v12_" - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "29", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTP302TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "29", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - }, - "scl": "\"mResetFTP302TotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gFTP302_ResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetFTP302TotTmr\" : TP;" - }, - { - "instruction_uid": "31", - "uid": "31", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gSyrupRoomEn\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "30", - "source_pin": "q" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v0_ & v23_" - }, - { - "instruction_uid": "32", - "uid": "32", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetSyrupTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "31", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "\"mResetSyrupTot\" := \"gSyrupRoomEn\" AND \"mResetFTP302TotTmr\".Q;" - } - ], - "language": "LAD" - }, - { - "id": "5E", - "title": "ResetProductTot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gFTM303_ResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v13_" - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v11_" - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O_sympy_processed", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy O: v11_ | v13_" - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetFTM303TotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - }, - "scl": "\"mResetFTM303TotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gFTM303_ResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetFTM303TotTmr\" : TP;" - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetCO2Tot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {}, - "scl": "\"mResetCO2Tot\" := \"mResetFTM303TotTmr\".Q;" - } - ], - "language": "LAD" - }, - { - "id": "6F", - "title": "ResetCO2Tot", - "comment": "", - "logic": [ - { - "instruction_uid": "26", - "uid": "26", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"gProductMFMResetTot\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v14_" - }, - { - "instruction_uid": "27", - "uid": "27", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetTotalizerTmr\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v11_" - }, - { - "instruction_uid": "28", - "uid": "28", - "type": "O_sympy_processed", - "template_values": { - "Card": "Cardinality" - }, - "negated_pins": {}, - "inputs": { - "in1": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "26", - "source_pin": "out" - }, - "in2": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "27", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy O: v11_ | v14_" - }, - { - "instruction_uid": "29", - "uid": "29", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "s": { - "type": "connection", - "source_instruction_type": "O", - "source_instruction_uid": "28", - "source_pin": "out" - }, - "timer": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mResetProductTotTmr\"" - }, - "tv": { - "uid": "24", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#2S" - }, - "en": { - "type": "connection", - "source_instruction_uid": "28", - "source_instruction_type": "O", - "source_pin": "out" - } - }, - "outputs": { - "q": [] - }, - "scl": "\"mResetProductTotTmr\"(IN := \"mResetTotalizerTmr\" OR \"gProductMFMResetTot\", PT := S5T#2S); // TODO: Declarar \"mResetProductTotTmr\" : TP;" - }, - { - "instruction_uid": "30", - "uid": "30", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "LocalVariable", - "type": "variable", - "name": "\"mResetProductTot\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Se", - "source_instruction_uid": "29", - "source_pin": "q" - } - }, - "outputs": {}, - "scl": "\"mResetProductTot\" := \"mResetProductTotTmr\".Q;" - } - ], - "language": "LAD" - }, - { - "id": "80", - "title": "Mod Copy Recipe", - "comment": "", - "logic": [ - { - "instruction_uid": "33", - "uid": "33", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "21", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v15_" - }, - { - "instruction_uid": "34", - "uid": "34", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": { - "operand": true - }, - "inputs": { - "operand": { - "uid": "22", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mFP_Recip_Main_Page\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v15_ & ~v16_" - }, - { - "instruction_uid": "35", - "uid": "35", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "23", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mAux_FP_M700_1\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "34", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "\"mAux_FP_M700_1\" := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\" AND NOT \"mFP_Recip_Main_Page\";" - }, - { - "instruction_uid": "36", - "uid": "36", - "type": "Coil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "24", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mFP_Recip_Main_Page\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "33", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "\"mFP_Recip_Main_Page\" := \"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\";" - }, - { - "instruction_uid": "37", - "uid": "37", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "25", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Main_Page\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v15_" - }, - { - "instruction_uid": "38", - "uid": "38", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "26", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "37", - "source_pin": "out" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v15_ & v17_" - }, - { - "instruction_uid": "39", - "uid": "39", - "type": "Se_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "tv": { - "uid": "28", - "scope": "TypedConstant", - "type": "constant", - "datatype": "TypedConstant", - "value": "S5T#500ms" - }, - "timer": { - "uid": "27", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"T_Pulse_Recipe_Edit\"" - }, - "s": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "38", - "source_pin": "out" - }, - "en": { - "type": "connection", - "source_instruction_uid": "38", - "source_instruction_type": "Contact", - "source_pin": "out" - } - }, - "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;" - }, - { - "instruction_uid": "40", - "uid": "40", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "29", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"T_Pulse_Recipe_Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "SdCoil", - "source_instruction_uid": "39", - "source_pin": "q" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v18_ & v26_" - }, - { - "instruction_uid": "41", - "uid": "41", - "type": "RCoil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "30", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "40", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "IF \"T_Pulse_Recipe_Edit\" AND \"T_Pulse_Recipe_Edit\".Q THEN\n \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\" := FALSE;\nEND_IF;" - }, - { - "instruction_uid": "42", - "uid": "42", - "type": "Contact_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "31", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"mAux_FP_M700_1\"" - }, - "in": { - "type": "powerrail" - } - }, - "outputs": { - "out": [] - }, - "scl": "// SymPy Contact: v19_" - }, - { - "instruction_uid": "43", - "uid": "43", - "type": "SCoil_sympy_processed", - "template_values": {}, - "negated_pins": {}, - "inputs": { - "operand": { - "uid": "32", - "scope": "GlobalVariable", - "type": "variable", - "name": "\"HMI_Variables_Cmd\".\"Recipe\".\"Edit\"" - }, - "in": { - "type": "connection", - "source_instruction_type": "Contact", - "source_instruction_uid": "42", - "source_pin": "out" - } - }, - "outputs": {}, - "scl": "IF \"mAux_FP_M700_1\" THEN\n \"HMI_Variables_Cmd\".\"Recipe\".\"Edit\" := TRUE;\nEND_IF;" - } - ], - "language": "LAD" + ] } ] } \ No newline at end of file diff --git a/TestLAD_simplified_processed.scl b/TestLAD_simplified_processed.scl index 9dff9d7..08b6a4e 100644 --- a/TestLAD_simplified_processed.scl +++ b/TestLAD_simplified_processed.scl @@ -7,64 +7,50 @@ FUNCTION_BLOCK "TestLAD" VERSION : 0.1 VAR_RETURN - Ret_Val : Void; + Ret_Val : Real; END_VAR VAR_TEMP - All_Auto_RETVAL : Int; - Reset_SP_Word_RETVAL : Int; - mResetWaterTot : Bool; - mResetSyrupTot : Bool; - mResetCO2Tot : Bool; - mResetProductTot : Bool; - Block_Move_Err : Int; + mWaterMaxFlow : Real; + mWaterMinFlow : Real; + mSyrupMaxFlow : Real; + mSyrupMinFlow : Real; + mMinRatio : Real; + mMaxRatio : Real; + mBevBrixMax : Real; + mBevBrixMin : Real; END_VAR BEGIN - // Network 1: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD) + // Network 1: (Original Language: SCL) - "gHVP301_Open" := ("gSyrupRoomEn" AND "gBlenderCIPMode" AND "gIN_CIP_CIPRunning" AND "Procedure_Variables"."Blender_Run"."Running" AND NOT "gIN_HVP301_Aux") OR ("gSyrupRoomEn" AND "Procedure_Variables"."FTP302Line_Preparation"."Done" AND NOT "gIN_HVP301_Aux" AND NOT "HMI_Blender_Parameters"."Processor_Options"."Blender_OPT"."_FastChangeOverEnabled" AND NOT "Procedure_Variables"."Syr_RunOut"."Done"); - - // Network 2: Manual Syrup Drain Valve Open - Operator Alarm (Original Language: LAD) - - "mHVM302_Dly"(IN := "gIN_HVM302_Aux", PT := S5T#1S); // TODO: Declarar "mHVM302_Dly" : TON; - "gHVM302_Open" := "mHVM302_Dly".Q; - - // Network 3: ResetTotalizer (Original Language: LAD) - - "mResetTotalizerTmr"(IN := "gBlendResetTotalizer", PT := S5T#2S); // TODO: Declarar "mResetTotalizerTmr" : TP; - - // Network 4: ResetWaterTot (Original Language: LAD) - - "mResetFTN301TotTmr"(IN := "gFTN301_ResetTot" OR "mResetTotalizerTmr", PT := S5T#2S); // TODO: Declarar "mResetFTN301TotTmr" : TP; - "mResetWaterTot" := "mResetFTN301TotTmr".Q; - - // Network 5: ResetCO2Tot (Original Language: LAD) - - "mResetFTP302TotTmr"(IN := "mResetTotalizerTmr" OR "gFTP302_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTP302TotTmr" : TP; - "mResetSyrupTot" := "gSyrupRoomEn" AND "mResetFTP302TotTmr".Q; - - // Network 6: ResetProductTot (Original Language: LAD) - - "mResetFTM303TotTmr"(IN := "mResetTotalizerTmr" OR "gFTM303_ResetTot", PT := S5T#2S); // TODO: Declarar "mResetFTM303TotTmr" : TP; - "mResetCO2Tot" := "mResetFTM303TotTmr".Q; - - // Network 7: ResetCO2Tot (Original Language: LAD) - - "mResetProductTotTmr"(IN := "mResetTotalizerTmr" OR "gProductMFMResetTot", PT := S5T#2S); // TODO: Declarar "mResetProductTotTmr" : TP; - "mResetProductTot" := "mResetProductTotTmr".Q; - - // Network 8: Mod Copy Recipe (Original Language: LAD) - - "mAux_FP_M700_1" := "HMI_Variables_Cmd"."Recipe"."Main_Page" AND NOT "mFP_Recip_Main_Page"; - "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; - IF "T_Pulse_Recipe_Edit" AND "T_Pulse_Recipe_Edit".Q THEN - "HMI_Variables_Cmd"."Recipe"."Edit" := FALSE; + IF "Blender_Variables".gSP_H2O <> 0 THEN + "Blender_Variables".gWaterVFMCalcError := "Blender_Variables".gWaterVFMMeasError / 100 * "Blender_Variables".gSP_H2O; END_IF; - IF "mAux_FP_M700_1" THEN - "HMI_Variables_Cmd"."Recipe"."Edit" := TRUE; + IF "Blender_Variables".gSP_SYR <> 0 THEN + "Blender_Variables".gSyrupMFMCalcError := ("Blender_Variables".gSyrupMFMMeasError / 100 + ("Blender_Variables".gSyrupMFMZeroStab / ("Blender_Variables".gSP_SYR * 60)) / 100) * "Blender_Variables".gSP_SYR; END_IF; + IF "Blender_Variables".gSP_CO2 <> 0 THEN + "Blender_Variables".gCO2MFMCalcError := ("Blender_Variables".gCO2MFMMeasError / 100 + ("Blender_Variables".gCO2MFMZeroStab / ("Blender_Variables".gSP_CO2 * 60 / 1000)) / 100) * "Blender_Variables".gSP_CO2; + END_IF; + "mWaterMaxFlow" := "Blender_Variables".gSP_H2O + "Blender_Variables".gWaterVFMCalcError; + "mWaterMinFlow" := "Blender_Variables".gSP_H2O - "Blender_Variables".gWaterVFMCalcError; + IF "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN + "mSyrupMaxFlow" := ("Blender_Variables".gSP_SYR + "Blender_Variables".gSyrupMFMCalcError) / "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity; + "mSyrupMinFlow" := ("Blender_Variables".gSP_SYR - "Blender_Variables".gSyrupMFMCalcError) / "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity; + END_IF; + IF "mSyrupMaxFlow" <> 0 THEN + "mMinRatio" := "mWaterMinFlow" / "mSyrupMaxFlow"; + END_IF; + IF "mSyrupMinFlow" <> 0 THEN + "mMaxRatio" := "mWaterMaxFlow" / "mSyrupMinFlow"; + END_IF; + IF "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity <> 0 THEN + "mBevBrixMax" := "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupBrix / (("mMinRatio" / "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity) + 1); + "mBevBrixMin" := "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupBrix / (("mMaxRatio" / "HMI_Blender_Parameters".Actual_Recipe_Parameters._SyrupDensity) + 1); + END_IF; + "Blender_Variables".gBlenderBlendMaxError := "mBevBrixMax" - "mBevBrixMin"; + "TestLAD" := "Blender_Variables".gBlenderBlendMaxError; END_FUNCTION_BLOCK diff --git a/ToUpload/TestLAD.xml b/ToUpload/TestLAD.xml new file mode 100644 index 0000000..f8ea13d --- /dev/null +++ b/ToUpload/TestLAD.xml @@ -0,0 +1,763 @@ + + + + + + +
+
+
+
+ + + + + + + + + + + +
+
+
+ +
+ + Optimized + TestLAD + + 2 + LAD + false + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + + + + + + + + DInt + 3 + + + + + + + + STL + + + + + + + it-IT + DEVICE + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + Set manual active + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ARef + P#0.0 + + + + + + + + + + + + + + + + Int + 3 + + + + + + + + + + + + + + + + + + + ARef + P#0.0 + + + + + + + + + + + + + + + + + + + + + + + + + config + + + + + + + + + auto + + + + + + + + + + + + DInt + 3 + + + + + + + + + + + ARef + P#2.0 + + + + + + + + + + + + + + + + + + + + + + + + STL + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + + PID + + + + + + + + + + + + + + + + + + + + + + + + + + + ARef + P#0.0 + + + + + + + + + + + + + + + + Int + 3 + + + + + + + + + + + + + + + + + + + ARef + P#0.0 + + + + + + + + + + + + + + + + + + + + + + + + + config + + + + + + + + + + + + ManOut + + + + + + + + + LocalSP + + + + + + + + + + + + + + + DInt + 3 + + + + + + + + + + + ARef + P#76.0 + + + + + + + + + + + + + + + + + + + + + + + + STL + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + + + + + it-IT + + + + + + de-DE + + + + + + en-US + + + + + + es-ES + + + + + + fr-FR + + + + + + zh-CN + + + + + + ja-JP + + + + + + + + \ No newline at end of file diff --git a/ToUpload/TestLAD_simplified.json b/ToUpload/TestLAD_simplified.json new file mode 100644 index 0000000..9f52ad5 --- /dev/null +++ b/ToUpload/TestLAD_simplified.json @@ -0,0 +1,101 @@ +{ + "block_name": "TestLAD", + "block_number": 2, + "language": "LAD", + "block_comment": "", + "interface": { + "Temp": [ + { + "name": "All_Auto_RETVAL", + "datatype": "Int" + }, + { + "name": "Reset_SP_Word_RETVAL", + "datatype": "Int" + }, + { + "name": "mResetWaterTot", + "datatype": "Bool" + }, + { + "name": "mResetSyrupTot", + "datatype": "Bool" + }, + { + "name": "mResetCO2Tot", + "datatype": "Bool" + }, + { + "name": "mResetProductTot", + "datatype": "Bool" + }, + { + "name": "Block_Move_Err", + "datatype": "Int" + }, + { + "name": "Dim_HMI_Device", + "datatype": "Int" + }, + { + "name": "PDim_HMI_Device", + "datatype": "DWord" + }, + { + "name": "Dim_HMI_PID", + "datatype": "Int" + }, + { + "name": "PDim_HMI_PID", + "datatype": "DWord" + } + ], + "Return": [ + { + "name": "Ret_Val", + "datatype": "Void" + } + ] + }, + "networks": [ + { + "id": "9", + "title": "Set manual active", + "comment": "DEVICE", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_9", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 9 uses unsupported language: STL\n" + } + ] + }, + { + "id": "1A", + "title": "", + "comment": "", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_1A", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 1A uses unsupported language: STL\n" + } + ] + }, + { + "id": "2B", + "title": "", + "comment": "", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_2B", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 2B uses unsupported language: STL\n" + } + ] + } + ] +} \ No newline at end of file diff --git a/ToUpload/TestLAD_simplified_processed.json b/ToUpload/TestLAD_simplified_processed.json new file mode 100644 index 0000000..9f52ad5 --- /dev/null +++ b/ToUpload/TestLAD_simplified_processed.json @@ -0,0 +1,101 @@ +{ + "block_name": "TestLAD", + "block_number": 2, + "language": "LAD", + "block_comment": "", + "interface": { + "Temp": [ + { + "name": "All_Auto_RETVAL", + "datatype": "Int" + }, + { + "name": "Reset_SP_Word_RETVAL", + "datatype": "Int" + }, + { + "name": "mResetWaterTot", + "datatype": "Bool" + }, + { + "name": "mResetSyrupTot", + "datatype": "Bool" + }, + { + "name": "mResetCO2Tot", + "datatype": "Bool" + }, + { + "name": "mResetProductTot", + "datatype": "Bool" + }, + { + "name": "Block_Move_Err", + "datatype": "Int" + }, + { + "name": "Dim_HMI_Device", + "datatype": "Int" + }, + { + "name": "PDim_HMI_Device", + "datatype": "DWord" + }, + { + "name": "Dim_HMI_PID", + "datatype": "Int" + }, + { + "name": "PDim_HMI_PID", + "datatype": "DWord" + } + ], + "Return": [ + { + "name": "Ret_Val", + "datatype": "Void" + } + ] + }, + "networks": [ + { + "id": "9", + "title": "Set manual active", + "comment": "DEVICE", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_9", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 9 uses unsupported language: STL\n" + } + ] + }, + { + "id": "1A", + "title": "", + "comment": "", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_1A", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 1A uses unsupported language: STL\n" + } + ] + }, + { + "id": "2B", + "title": "", + "comment": "", + "language": "STL", + "logic": [ + { + "instruction_uid": "UNS_2B", + "type": "UNSUPPORTED_LANG", + "scl": "// Network 2B uses unsupported language: STL\n" + } + ] + } + ] +} \ No newline at end of file diff --git a/ToUpload/TestLAD_simplified_processed.scl.txt b/ToUpload/TestLAD_simplified_processed.scl.txt new file mode 100644 index 0000000..d096e71 --- /dev/null +++ b/ToUpload/TestLAD_simplified_processed.scl.txt @@ -0,0 +1,42 @@ +// Block Name (Original): TestLAD +// Block Number: 2 +// Original Language: LAD + +FUNCTION_BLOCK "TestLAD" +{ S7_Optimized_Access := 'TRUE' } +VERSION : 0.1 + +VAR_RETURN + Ret_Val : Void; +END_VAR + +VAR_TEMP + All_Auto_RETVAL : Int; + Reset_SP_Word_RETVAL : Int; + mResetWaterTot : Bool; + mResetSyrupTot : Bool; + mResetCO2Tot : Bool; + mResetProductTot : Bool; + Block_Move_Err : Int; + Dim_HMI_Device : Int; + PDim_HMI_Device : DWord; + Dim_HMI_PID : Int; + PDim_HMI_PID : DWord; +END_VAR + +BEGIN + + // Network 1: Set manual active (Original Language: STL) + // DEVICE + + // Network 9 uses unsupported language: STL + + // Network 2: (Original Language: STL) + + // Network 1A uses unsupported language: STL + + // Network 3: (Original Language: STL) + + // Network 2B uses unsupported language: STL + +END_FUNCTION_BLOCK diff --git a/ToUpload/XSD Schema Definition/SW.Common_v3.xsd.xml b/ToUpload/XSD Schema Definition/SW.Common_v3.xsd.xml new file mode 100644 index 0000000..19255f0 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.Common_v3.xsd.xml @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + A member attribute with a type restriction of boolean. + + + + + + + + + Exported only with ReadOnly option, ignored during import. + + + + + An attribute of attribute, denotes if it is defined by a user or the system itself. In V14, if exists it is always true. + + + + + + + + + Not allowed in STL + + + + + LAD/FBD: Only for Parts + + + + + + + + + + + + + + + For NumBLs in STL. NumBLs is the count of the blank spaces before the actual text in the Comment. This is informative. + + + + + + + Denotes if the comment is at the end of the line (using /*/) or inside the line (using (/* */) ) + + + + + Exported only with ReadOnly option, ignored during import. + + + + + + + + + + + + Exported only with ReadOnly option, ignored during import. + + + + + An attribute of attribute, denotes if it is defined by a user or the system itself. In V14, if exists it is always true. + + + + + + + + + + + + + + A member attribute with a type restriction of integer. + + + + + Not for LAD/FBD. + + + + + + + Exported only with ReadOnly option, ignored during import. + + + + + An attribute of attribute, denotes if it is defined by a user or the system itself. In V14, if exists it is always true. + + + + + + + + + + + + + + Not for LAD/FBD + + + + + + + For NumBLs in STL. NumBLs is the count of the blank spaces before the actual text in the LineComment. This is informative. + + + + + + + + + + Denotes if the comment is at the end of the line (using //) or inside the line (using /* */) + + + + + + + + + + + + + + + + + + + + + + A member attribute with a type restriction of real. + + + + + + + + + Exported only with ReadOnly option, ignored during import. + + + + + An attribute of attribute, denotes if it is defined by a user or the system itself. In V14, if exists it is always true. + + + + + + + + + + + + + + + + + + + + + + + + + + + + A member attribute with a type restriction of string. + + + + + + + + + Exported only with ReadOnly option, ignored during import. + + + + + An attribute of attribute, denotes if it is defined by a user or the system itself. In V14, if exists it is always true. + + + + + + + + + + + + + + + + + + + + + + + + + + For NumBLs. NumBLs is the count of the blank spaces at the start.This is informative. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.Interface.Snapshot.xsd.xml b/ToUpload/XSD Schema Definition/SW.Interface.Snapshot.xsd.xml new file mode 100644 index 0000000..cb59523 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.Interface.Snapshot.xsd.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ToUpload/XSD Schema Definition/SW.InterfaceSections_v5.xsd.xml b/ToUpload/XSD Schema Definition/SW.InterfaceSections_v5.xsd.xml new file mode 100644 index 0000000..7f0ba20 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.InterfaceSections_v5.xsd.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The version of the library type to use. Previous to this, the version was written inside the Datatype attribute itself, like "dtl:v1.0". Now, this is written in two separate attributes, to mitigate problems with weird names ("dtl:v1.0" could be a UDT name!). + + + + + + + + + + + Write acces only inside function + + + + + string: Member shares offset with another member in this structure + + + + + boolean: Member can be synchronized with work memory + + + + + boolean: Editor does not show the member + + + + + boolean: User cannot change member name + + + + + boolean: Editor does not allow to delete the member + + + + + boolean: No HMI access, no structure item + + + + + boolean: Filter to reduce the number of members shown in the first place + + + + + integer: + + + + + integer: + + + + + boolean: Hide assignement at call if matches with PredefinedAssignment + + + + + string: Input for the paramter used when call is placed + + + + + boolean: The user cannot change the predefined assignement at the call + + + + + + + + + + + + + + + + + Base Class + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.Access_v4.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.Access_v4.xsd.xml new file mode 100644 index 0000000..86c4cef --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.Access_v4.xsd.xml @@ -0,0 +1,593 @@ + + + + + + + + + + Byte * 8 + Bit + + + + + + + + + + + + + + + + + + + for NumBLs. NumBLs is informative. Not for LAD/FBD. + + + + + + + + call of a user block. Not in Graph ActionList. + + + + + call of an instruction. Not for LAD/FBD, Graph ActionList. + + + + + STL specific + + + + + + Only in SCL + + + + + SCL specific + + + + + + for absolute addresses + + + + + + + + + + + + + + + Not allowed in STL + + + + + + + + + + + + + + + + + + + + + for DB access + + + + + In general it is Byte * 8 + Bit. But if it is used for addressing a DB we will find the number of the DB here (e.g. "DB12" ->12). + + + + + if true, the import unnoted it + + + + + + + + + + + + + + + + + partly qualified access with DB register + + + + + partly qualified access with DI register + + + + + + + Classic Local Stack + + + + + + + + + + + + + + + + + + + Not for LAD/FBD. + + + + + for BlockNumber. BlockNumber is informative. + + + + + for ParameterModifiedTS. ParameterModifiedTS is informative + + + + + + + + + + + + + + + + + SCL + + + + + SCL + + + + + For the indices of an array + + + + + + + + + + If component has child AccessModifier is Array else AccessModifier is None + + + + + + + + + + + + for Format and FormatFlags. They are informative.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the DOT; only if separated. Not in Graph ActionList, not in LAD/FBD. + + + + + + + + + Not allowed in STL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SCL only + + + + + + + + + + + + + + for InterfaceFlags. InterfaceFlags is informative + The type of the value should be InterfaceFlags_TP + The default value is "S7_Visible" + + + + + + + + + + + + + for NumBLs. NumBLs is informative + + + + + for InterfaceFlags. InterfaceFlags is informative + The type of the value should be InterfaceFlags_TP + The default value is "S7_Visible" + + + + + + + + + + + + + + + + + + + + + + + + + + + + SCL + + + + + + + + + SCL + + + + + + + + + + + + + + + + + Symbols we do not know what they are + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only for S7-300/400/WinAC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SCL. + + + + + + + + Not allowed in STL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.CompileUnitCommon_v4.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.CompileUnitCommon_v4.xsd.xml new file mode 100644 index 0000000..4212279 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.CompileUnitCommon_v4.xsd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + for NumBLs. NumBLs is informative + + + + + + + + the COLON; only if separated + + + + + + + + Not allowed in STL + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.Graph_v5.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.Graph_v5.xsd.xml new file mode 100644 index 0000000..6f27866 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.Graph_v5.xsd.xml @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Temporary change for enable of empty alarm text because of the graph alarm handling reconstruction. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enabler token + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + For translated transiton names + + + + + + + + + For translated step names + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.InstanceSupervisions_v3.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.InstanceSupervisions_v3.xsd.xml new file mode 100644 index 0000000..a8371ae --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.InstanceSupervisions_v3.xsd.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.LADFBD_v4.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.LADFBD_v4.xsd.xml new file mode 100644 index 0000000..4cd4a14 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.LADFBD_v4.xsd.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The invisible pins of this part. + + + + + + The name of the invisible pin. + + + + + + + + + + + + + The name of the negated pin. + + + + + + The negated pins of this part. + + + + + + The name of the automatic chosen template parameter. Not for InstructionRef + + + + + + + + + + + + The equation of this part. This is only used for the Calculate box. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.SCL_v3.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.SCL_v3.xsd.xml new file mode 100644 index 0000000..65b5aad --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.SCL_v3.xsd.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.STL_v4.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.STL_v4.xsd.xml new file mode 100644 index 0000000..be6b647 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.STL_v4.xsd.xml @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + missing for empty lines + + + + + + + + + Not allowed in STL + + + + + + + + + for NumBLs. NumBLs is informative + + + + + + + e.g 0 1 for NOP 0, NOP 1; STW for L STW or DILG for L DILG; only if separated by comment + + + + + + + Not allowed in STL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SE, SV + + + + + + + SF, SA + + + + + SS + + + + + SD, SE + + + + + SP, SI + + + + + + + + + + AUF + + + + + AUF DI + + + + + + + + + + + + + + + + + + + + + + + SPA + + + + + SPB + + + + + SPO + + + + + SPZ + + + + + SPP + + + + + SPM + + + + + SPN + + + + + SPBN + + + + + SPBB + + + + + SPBNB + + + + + SPBI + + + + + SPBNI + + + + + SPS + + + + + SPU + + + + + SPMZ + + + + + SPZ + + + + + + + + + + + + SSD, SVD + + + + + SSW, SVW + + + + + + + + + + + + + + + + + + + + + + + + KEW, INV_F + + + + + KZW, NEG_F + + + + + KED + + + + + KZD + + + + + NEG_G, ND + + + + + ABS_G + + + + + + + + + + + + + + + + + DEF + + + + + DUF + + + + + DED + + + + + DUD + + + + + FDG + + + + + GFDN + + + + + GFDM + + + + + GFDP + + + + + + FD + + + + + TAW + + + + + TAD + + + + + + + +F + + + + + -F + + + + + xF + + + + + :F + + + + + +D + + + + + -D + + + + + xD + + + + + :D + + + + + + + + + + +G + + + + + -G + + + + + xG + + + + + :G + + + + + TAK + + + + + + + + + + + BEB + + + + + ) + + + + + + + + MCR( + + + + + MCR) + + + + + + + + + + + + TAR + + + + + TDB + + + + + + + + + + BEA + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.PlcBlocks.TypeSupervisions_v3.xsd.xml b/ToUpload/XSD Schema Definition/SW.PlcBlocks.TypeSupervisions_v3.xsd.xml new file mode 100644 index 0000000..3f9745f --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.PlcBlocks.TypeSupervisions_v3.xsd.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataAxis_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataAxis_v1.xsd.xml new file mode 100644 index 0000000..feffa1b --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataAxis_v1.xsd.xml @@ -0,0 +1,132 @@ + + + + + + + + + + Describes additional data, such as Connections, for Axis and ExternalEncoder TOs. + + + + + + + + + + + Describes a connection of a TO interface. + + + + Specifies the Interface of the TO that is connected. + + + + + Input bit address. + + + + + Output bit address. + + + + + Connect option used when the connection has been created. + + + + + Index of sensor in actor telegram if connected to same telegram. + + + + + Path to a DB member. + + + + + Name of a connected tag for analog connection. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains a list of master values for TO_SynchronousAxis. + + + + + Describes a reference to a master value TO that is coupled via set points. + + + + + Describes a reference to a master value TO that is coupled via actual values. + + + + + Describes a reference to a master value TO that is coupled via delayed values. + + + + + Describes a reference to a master value TO of type LeadingAxisProxy. + + + + + + + + + Describes a reference to a Technological Object. + + + + Specifies the name of the referenced Technological Object. + + + + + Specifies the type of the referenced Technological Object. + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataKinematics_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataKinematics_v1.xsd.xml new file mode 100644 index 0000000..568f342 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataKinematics_v1.xsd.xml @@ -0,0 +1,97 @@ + + + + + + + + + + Describes additional data, such as connected axes, for Kinematics TOs. + + + + + + + + + + + Describes a reference to a Technological Object. + + + + Specifies the name of the referenced Technological Object. + + + + + Specifies the name of the referenced Technological Object. + + + + + Specifies the type of the referenced Technological Object. + + + + + + + + + + + + + + + + Contains a list of leading values for conveyor tracking. + + + + + Describes a reference to a leading value TO that is coupled via set points. + + + + + Describes a reference to a leading value TO that is coupled via actual values. + + + + + Describes a reference to a leading value TO that is coupled via delayed values. + + + + + Describes a reference to a leading value TO of type LeadingAxisProxy. + + + + + + + + + Describes a reference to a Technological Object. + + + + Specifies the name of the referenced Technological Object. + + + + + Specifies the type of the referenced Technological Object. + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataMeasuringInput_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataMeasuringInput_v1.xsd.xml new file mode 100644 index 0000000..8d2b293 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataMeasuringInput_v1.xsd.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + Describes additional data, such as Connections, for MeasuringInput TOs. + + + + + + + + + + Describes a connection of a TO interface. + + + + Specifies the Interface of the TO that is connected. + + + + + Input bit address. + + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataOutputCam_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataOutputCam_v1.xsd.xml new file mode 100644 index 0000000..7629aa2 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_AdditionalDataOutputCam_v1.xsd.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + Describes additional data, such as Connections, for OutputCam and CamTrack TOs. + + + + + + + + + + Describes a connection of a TO interface. + + + + Specifies the Interface of the TO that is connected. + + + + + Output bit address. + + + + + Name of a connected tag. + + + + + + + + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_Parameters_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_Parameters_v1.xsd.xml new file mode 100644 index 0000000..17a4f01 --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_Parameters_v1.xsd.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + Describes a list of parameters. + + + + + + + + + + + Describes a single parameter, having Name and Value. If the Value is missing, the default value of the Parameter is used. + + + + Name of the Parameter + + + + + Value of the Parameter + + + + diff --git a/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_ProfileDataCam_v1.xsd.xml b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_ProfileDataCam_v1.xsd.xml new file mode 100644 index 0000000..20ee84f --- /dev/null +++ b/ToUpload/XSD Schema Definition/SW.TechnologicalObjects_ProfileDataCam_v1.xsd.xml @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ToUpload/processors/__init__.py b/ToUpload/processors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ToUpload/processors/process_add.py b/ToUpload/processors/process_add.py new file mode 100644 index 0000000..3c7fb6f --- /dev/null +++ b/ToUpload/processors/process_add.py @@ -0,0 +1,87 @@ +# processors/process_add.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re # Importar re si se usa para formateo +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" # Usar el nuevo sufijo + +def process_add(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera SCL para Add, simplificando la condición EN.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Add") + current_type = instruction.get("type","") + if current_type.endswith(SCL_SUFFIX) or "_error" in current_type: + return False + + # Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String) + en_input = instruction["inputs"].get("en") + in1_info = instruction["inputs"].get("in1") + in2_info = instruction["inputs"].get("in2") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager) + op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager) + + # Obtener destino SCL + target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) + + # Verificar dependencias + if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None: + # print(f"DEBUG Add {instr_uid}: Dependency not ready") + return False + + # Convertir operandos SymPy/Constante a SCL strings + op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager) + op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager) + + # Añadir paréntesis si contienen operadores (más seguro para SCL) + op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl + op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl + + # Generar SCL Core + scl_core = f"{target_scl_name} := {op1_scl_formatted} + {op2_scl_formatted};" + + # Aplicar Condición EN (Simplificando EN) + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + + except Exception as e: + print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + # Evitar IF TRUE THEN... + if en_condition_scl == "TRUE": + scl_final = scl_core + # Evitar IF FALSE THEN... + elif en_condition_scl == "FALSE": + scl_final = f"// {instr_type_original} {instr_uid} condition simplified to FALSE." + else: + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # Actualizar instrucción y mapa + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy) + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino (string) + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el procesador Add.""" + # Asegurar que la clave coincida con el tipo en JSON ('add') + return {'type_name': 'add', 'processor_func': process_add, 'priority': 4} \ No newline at end of file diff --git a/ToUpload/processors/process_blkmov.py b/ToUpload/processors/process_blkmov.py new file mode 100644 index 0000000..9d9ccd7 --- /dev/null +++ b/ToUpload/processors/process_blkmov.py @@ -0,0 +1,118 @@ +# processors/process_blkmov.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" # Usar el nuevo sufijo + +def process_blkmov(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL usando BLKMOV directamente como nombre de función, + simplificando la condición EN. + ADVERTENCIA: Sintaxis BLKMOV probablemente no compile en TIA estándar. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "BlkMov") # Asegurar que el tipo base sea correcto + current_type = instruction.get("type","") + if current_type.endswith(SCL_SUFFIX) or "_error" in current_type: + return False + + # --- Obtener Entradas --- + en_input = instruction["inputs"].get("en") + # Obtener EN como expresión SymPy + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + + srcblk_info = instruction["inputs"].get("SRCBLK") + # Obtener nombre RAW de SRCBLK (como se hacía antes, si es necesario para BLKMOV) + # Este nombre NO pasa por SymPy, se usa directo en el string SCL final + raw_srcblk_name = srcblk_info.get("name") if srcblk_info else None + + # Verificar dependencias (EN debe estar resuelto, SRCBLK debe tener nombre) + if sympy_en_expr is None: + # print(f"DEBUG BlkMov {instr_uid}: EN dependency not ready") + return False + if raw_srcblk_name is None: + print(f"Error: BLKMOV {instr_uid} sin información válida para SRCBLK.") + instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin SRCBLK válido." + instruction["type"] = instr_type_original + "_error" + return True + + # --- Obtener Destinos (Salidas) --- + # RET_VAL (Obtener nombre SCL formateado) + retval_target_scl = get_target_scl_name(instruction, "RET_VAL", network_id, default_to_temp=True) + if retval_target_scl is None: # get_target_scl_name ya imprime error si falla y default_to_temp=True + instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} no pudo generar destino RET_VAL" + instruction["type"] = instr_type_original + "_error" + return True + + # DSTBLK (Obtener nombre RAW como antes, si se necesita) + raw_dstblk_name = None + dstblk_output_list = instruction.get("outputs", {}).get("DSTBLK", []) + if dstblk_output_list and isinstance(dstblk_output_list, list) and len(dstblk_output_list) == 1: + dest_access = dstblk_output_list[0] + if dest_access.get("type") == "variable": + raw_dstblk_name = dest_access.get("name") + # Manejar error si no se encuentra DSTBLK + if raw_dstblk_name is None: + print(f"Error: No se encontró un destino único y válido para DSTBLK en BLKMOV {instr_uid}.") + instruction["scl"] = f"// ERROR: BLKMOV {instr_uid} sin destino DSTBLK válido." + instruction["type"] = instr_type_original + "_error" + return True + + # --- Formateo especial (mantener nombres raw si es necesario para BLKMOV) --- + # Estos nombres van directo al string SCL, no necesitan pasar por SymPy + srcblk_final_str = raw_srcblk_name # Asumiendo que ya viene con comillas si las necesita + dstblk_final_str = raw_dstblk_name # Asumiendo que ya viene con comillas si las necesita + + # --- Generar SCL Core (Usando la sintaxis no estándar BLKMOV) --- + scl_core = ( + f"{retval_target_scl} := BLKMOV(SRCBLK := {srcblk_final_str}, " + f"DSTBLK => {dstblk_final_str}); " # Usar => para Out/InOut + f"// ADVERTENCIA: BLKMOV usado directamente, probablemente no compile!" + ) + + # --- Aplicar Condición EN (Simplificando EN) --- + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + # Evitar IF TRUE/FALSE THEN... + if en_condition_scl == "TRUE": + scl_final = scl_core + elif en_condition_scl == "FALSE": + scl_final = f"// {instr_type_original} {instr_uid} condition simplified to FALSE." + else: + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # --- Actualizar Instrucción y Mapa SymPy --- + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar ENO (expresión SymPy) + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr + + # Propagar el valor de retorno (nombre SCL string del destino de RET_VAL) + map_key_ret_val = (network_id, instr_uid, "RET_VAL") + sympy_map[map_key_ret_val] = retval_target_scl + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el procesador BLKMOV.""" + # Asegurarse que el type_name coincida con el JSON ('blkmov' parece probable) + return {'type_name': 'blkmov', 'processor_func': process_blkmov, 'priority': 6} \ No newline at end of file diff --git a/ToUpload/processors/process_call.py b/ToUpload/processors/process_call.py new file mode 100644 index 0000000..7902697 --- /dev/null +++ b/ToUpload/processors/process_call.py @@ -0,0 +1,131 @@ +# processors/process_call.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Asumiendo que estas funciones ahora existen y están adaptadas +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name +from .symbol_manager import SymbolManager # Necesitamos pasar el symbol_manager + +# Definir sufijo globalmente o importar +SCL_SUFFIX = "_sympy_processed" + +def process_call(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "") # Tipo antes de añadir sufijo + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + block_name = instruction.get("block_name", f"UnknownCall_{instr_uid}") + block_type = instruction.get("block_type") # FC, FB + instance_db = instruction.get("instance_db") # Nombre del DB de instancia (para FB) + + # Formatear nombres SCL (para la llamada final) + block_name_scl = format_variable_name(block_name) + instance_db_scl = format_variable_name(instance_db) if instance_db else None + + # --- Manejo de EN --- + en_input = instruction["inputs"].get("en") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + + if sympy_en_expr is None: + # print(f"DEBUG Call {instr_uid}: EN dependency not ready.") + return False # Dependencia EN no resuelta + + # --- Procesar Parámetros de Entrada --- + scl_call_params = [] + processed_inputs = {"en"} + dependencies_resolved = True + + # Ordenar para consistencia + input_pin_names = sorted(instruction.get("inputs", {}).keys()) + + for pin_name in input_pin_names: + if pin_name not in processed_inputs: + source_info = instruction["inputs"][pin_name] + # Obtener la representación de la fuente (puede ser SymPy o Constante/String) + source_sympy_or_const = get_sympy_representation(source_info, network_id, sympy_map, symbol_manager) + + if source_sympy_or_const is None: + # print(f"DEBUG Call {instr_uid}: Input param '{pin_name}' dependency not ready.") + dependencies_resolved = False + break # Salir si una dependencia no está lista + + # Convertir la expresión/constante a SCL para la llamada + # Simplificar ANTES de convertir? Probablemente no necesario para parámetros de entrada + # a menos que queramos optimizar el valor pasado. Por ahora, convertir directo. + param_scl_value = sympy_expr_to_scl(source_sympy_or_const, symbol_manager) + + # El nombre del pin SÍ necesita formateo + pin_name_scl = format_variable_name(pin_name) + scl_call_params.append(f"{pin_name_scl} := {param_scl_value}") + processed_inputs.add(pin_name) + + if not dependencies_resolved: + return False + + # --- Construcción de la Llamada SCL (similar a antes) --- + scl_call_body = "" + param_string = ", ".join(scl_call_params) + + if block_type == "FB": + if not instance_db_scl: + print(f"Error: Call FB '{block_name_scl}' (UID {instr_uid}) sin instancia.") + instruction["scl"] = f"// ERROR: FB Call {block_name_scl} sin instancia" + instruction["type"] = f"Call_FB_error" + return True + scl_call_body = f"{instance_db_scl}({param_string});" + elif block_type == "FC": + scl_call_body = f"{block_name_scl}({param_string});" + else: + print(f"Advertencia: Tipo de bloque no soportado para Call UID {instr_uid}: {block_type}") + scl_call_body = f"// ERROR: Call a bloque tipo '{block_type}' no soportado: {block_name_scl}" + instruction["type"] = f"Call_{block_type}_error" # Marcar como error + + # --- Aplicar Condición EN (usando la expresión SymPy EN) --- + scl_final = "" + if sympy_en_expr != sympy.true: + # Simplificar la condición EN ANTES de convertirla a SCL + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for Call {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + indented_call = "\n".join([f" {line}" for line in scl_call_body.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_call}\nEND_IF;" + else: + scl_final = scl_call_body + + # --- Actualizar Instrucción y Mapa SymPy --- + instruction["scl"] = scl_final # Guardar el SCL final generado + instruction["type"] = (f"Call_{block_type}{SCL_SUFFIX}" if "_error" not in instruction["type"] else instruction["type"]) + + # Actualizar sympy_map con el estado ENO (es la expresión SymPy de EN) + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO + + # Propagar valores de salida (requiere info de interfaz o heurística) + # Si se sabe que hay una salida 'MyOutput', se podría añadir su SCL al mapa + # Ejemplo MUY simplificado: + # for pin_name, dest_list in instruction.get("outputs", {}).items(): + # if pin_name != 'eno' and dest_list: # Asumir que hay un destino + # map_key_out = (network_id, instr_uid, pin_name) + # if block_type == "FB" and instance_db_scl: + # sympy_map[map_key_out] = f"{instance_db_scl}.{format_variable_name(pin_name)}" # Guardar el *string* de acceso SCL + # # Para FCs es más complejo, necesitaría asignación explícita a temp + # # else: # FC output -> necesita temp var + # # temp_var = generate_temp_var_name(...) + # # sympy_map[map_key_out] = temp_var + + return True + + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para las llamadas a FC y FB.""" + return [ + {'type_name': 'call_fc', 'processor_func': process_call, 'priority': 6}, + {'type_name': 'call_fb', 'processor_func': process_call, 'priority': 6} + ] \ No newline at end of file diff --git a/ToUpload/processors/process_coil.py b/ToUpload/processors/process_coil.py new file mode 100644 index 0000000..3cb689c --- /dev/null +++ b/ToUpload/processors/process_coil.py @@ -0,0 +1,82 @@ +# processors/process_coil.py +import sympy +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" + +def process_coil(instruction, network_id, sympy_map, symbol_manager, data): + """Genera la asignación SCL para Coil, simplificando la entrada SymPy.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Coil") + + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Get input expression from SymPy map + coil_input_info = instruction["inputs"].get("in") + sympy_expr_in = get_sympy_representation(coil_input_info, network_id, sympy_map, symbol_manager) + + # Get target variable SCL name + target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # Coil must have explicit target + + # Check dependencies + if sympy_expr_in is None: + # print(f"DEBUG Coil {instr_uid}: Input dependency not ready.") + return False + if target_scl_name is None: + print(f"Error: Coil {instr_uid} operando no es variable o falta info.") + instruction["scl"] = f"// ERROR: Coil {instr_uid} operando no es variable." + instruction["type"] = instr_type_original + "_error" + return True # Processed with error + + # *** Perform Simplification *** + try: + #simplified_expr = sympy.simplify_logic(sympy_expr_in, force=False) + #simplified_expr = sympy_expr_in + simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True) + except Exception as e: + print(f"Error during SymPy simplification for Coil {instr_uid}: {e}") + simplified_expr = sympy_expr_in # Fallback to original expression + + # *** Convert simplified expression back to SCL string *** + condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager) + + # Generate the final SCL assignment + scl_assignment = f"{target_scl_name} := {condition_scl};" + scl_final = scl_assignment + + # --- Handle Edge Detector Memory Update (Logic similar to before) --- + # Check if input comes from PBox/NBox and append memory update + mem_update_scl_combined = None + if isinstance(coil_input_info, dict) and coil_input_info.get("type") == "connection": + source_uid = coil_input_info.get("source_instruction_uid") + source_pin = coil_input_info.get("source_pin") + source_instruction = None + network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), []) + for instr in network_logic: + if instr.get("instruction_uid") == source_uid: + source_instruction = instr + break + if source_instruction: + # Check for the original type before suffix was added + orig_source_type = source_instruction.get("type", "").replace(SCL_SUFFIX, '').replace('_error', '') + if orig_source_type in ["PBox", "NBox"] and '_edge_mem_update_scl' in source_instruction: + mem_update_scl_combined = source_instruction.get('_edge_mem_update_scl') + if mem_update_scl_combined: + scl_final = f"{scl_assignment}\n{mem_update_scl_combined}" + # Clear the source SCL? + source_instruction['scl'] = f"// Edge Logic handled by Coil {instr_uid}" + + + # Update instruction + instruction["scl"] = scl_final + instruction["type"] = instr_type_original + SCL_SUFFIX + # Coil typically doesn't output to scl_map + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el procesador Coil.""" + return {'type_name': 'coil', 'processor_func': process_coil, 'priority': 3} \ No newline at end of file diff --git a/ToUpload/processors/process_comparison.py b/ToUpload/processors/process_comparison.py new file mode 100644 index 0000000..cf0bfcd --- /dev/null +++ b/ToUpload/processors/process_comparison.py @@ -0,0 +1,87 @@ +# processors/process_comparison.py +# -*- coding: utf-8 -*- +import sympy +import traceback +from .processor_utils import get_sympy_representation, format_variable_name # No necesita sympy_expr_to_scl aquí +from .symbol_manager import SymbolManager # Necesita acceso al manager + +SCL_SUFFIX = "_sympy_processed" + +def process_comparison(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera la expresión SymPy para Comparadores (GT, LT, GE, LE, NE). + El resultado se propaga por sympy_map['out']. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "") # GT, LT, GE, LE, NE + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Mapa de tipos a funciones/clases SymPy Relational + # Nota: Asegúrate de que los tipos coincidan (ej. si son números o booleanos) + op_map = { + "GT": sympy.Gt, # Greater Than > + "LT": sympy.Lt, # Less Than < + "GE": sympy.Ge, # Greater or Equal >= + "LE": sympy.Le, # Less or Equal <= + "NE": sympy.Ne # Not Equal <> (sympy.Ne maneja esto) + } + sympy_relation_func = op_map.get(instr_type_original.upper()) + if not sympy_relation_func: + instruction["scl"] = f"// ERROR: Tipo de comparación no soportado para SymPy: {instr_type_original}" + instruction["type"] = instr_type_original + "_error" + return True + + # Obtener operandos como expresiones SymPy o constantes/strings + in1_info = instruction["inputs"].get("in1") + in2_info = instruction["inputs"].get("in2") + op1_sympy = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager) + op2_sympy = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager) + + # Obtener 'pre' (RLO anterior) como expresión SymPy + pre_input = instruction["inputs"].get("pre") # Asumiendo que 'pre' es la entrada RLO + sympy_pre_rlo = get_sympy_representation(pre_input, network_id, sympy_map, symbol_manager) if pre_input else sympy.true + + # Verificar dependencias + if op1_sympy is None or op2_sympy is None or sympy_pre_rlo is None: + # print(f"DEBUG Comparison {instr_uid}: Dependency not ready") + return False + + # Crear la expresión de comparación SymPy + try: + # Convertir constantes string a número si es posible (Sympy puede necesitarlo) + # Esto es heurístico y puede fallar. Mejor si los tipos son conocidos. + op1_eval = sympy.sympify(op1_sympy) if isinstance(op1_sympy, str) else op1_sympy + op2_eval = sympy.sympify(op2_sympy) if isinstance(op2_sympy, str) else op2_sympy + comparison_expr = sympy_relation_func(op1_eval, op2_eval) + except (SyntaxError, TypeError, ValueError) as e: + print(f"Error creating SymPy comparison for {instr_uid}: {e}") + instruction["scl"] = f"// ERROR creando expr SymPy Comparison {instr_uid}: {e}" + instruction["type"] = instr_type_original + "_error" + return True + + # Guardar resultado en el mapa para 'out' (es una expresión booleana SymPy) + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = comparison_expr + + # Guardar el RLO de entrada ('pre') como ENO en el mapa SymPy + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_pre_rlo + + # Marcar como procesado, SCL principal es solo comentario + instruction["scl"] = f"// SymPy Comparison {instr_type_original}: {comparison_expr}" # Comentario opcional + instruction["type"] = instr_type_original + SCL_SUFFIX + return True + + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para los comparadores (excepto EQ, que debe ser similar).""" + return [ + {'type_name': 'gt', 'processor_func': process_comparison, 'priority': 2}, + {'type_name': 'lt', 'processor_func': process_comparison, 'priority': 2}, + {'type_name': 'ge', 'processor_func': process_comparison, 'priority': 2}, + {'type_name': 'le', 'processor_func': process_comparison, 'priority': 2}, + {'type_name': 'ne', 'processor_func': process_comparison, 'priority': 2} + # Asegúrate de tener también un procesador para 'eq' usando sympy.Eq + ] \ No newline at end of file diff --git a/ToUpload/processors/process_contact.py b/ToUpload/processors/process_contact.py new file mode 100644 index 0000000..ec5b0ab --- /dev/null +++ b/ToUpload/processors/process_contact.py @@ -0,0 +1,60 @@ +# processors/process_contact.py +import sympy +from .processor_utils import get_sympy_representation, format_variable_name # Use new util +from .symbol_manager import SymbolManager, extract_plc_variable_name # Need symbol manager access + +# Define SCL_SUFFIX or import if needed globally +SCL_SUFFIX = "_sympy_processed" # Indicate processing type + +def process_contact(instruction, network_id, sympy_map, symbol_manager, data): # Pass symbol_manager + """Genera la expresión SymPy para Contact (normal o negado).""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Contact") + + # Check if already processed with the new method + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + is_negated = instruction.get("negated_pins", {}).get("operand", False) + + # Get incoming SymPy expression (RLO) + in_input = instruction["inputs"].get("in") + sympy_expr_in = get_sympy_representation(in_input, network_id, sympy_map, symbol_manager) + + # Get operand SymPy Symbol + operand_info = instruction["inputs"].get("operand") + operand_plc_name = extract_plc_variable_name(operand_info) + sympy_symbol_operand = symbol_manager.get_symbol(operand_plc_name) if operand_plc_name else None + + # Check dependencies + if sympy_expr_in is None or sympy_symbol_operand is None: + # print(f"DEBUG Contact {instr_uid}: Dependency not ready (In: {sympy_expr_in is not None}, Op: {sympy_symbol_operand is not None})") + return False # Dependencies not ready + + # Apply negation using SymPy + current_term = sympy.Not(sympy_symbol_operand) if is_negated else sympy_symbol_operand + + # Combine with previous RLO using SymPy + # Simplify common cases: TRUE AND X -> X + if sympy_expr_in == sympy.true: + sympy_expr_out = current_term + else: + # Could add FALSE AND X -> FALSE optimization here too + sympy_expr_out = sympy.And(sympy_expr_in, current_term) + + # Store the resulting SymPy expression object in the map + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = sympy_expr_out + + # Mark instruction as processed (SCL field is now less relevant here) + instruction["scl"] = f"// SymPy Contact: {sympy_expr_out}" # Optional debug comment + instruction["type"] = instr_type_original + SCL_SUFFIX # Use the new suffix + # Contact doesn't usually have ENO, it modifies the RLO ('out') + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el procesador Contact.""" + # Ensure 'data' argument is added if needed by the processor function signature change + return {'type_name': 'contact', 'processor_func': process_contact, 'priority': 1} \ No newline at end of file diff --git a/ToUpload/processors/process_convert.py b/ToUpload/processors/process_convert.py new file mode 100644 index 0000000..0825c21 --- /dev/null +++ b/ToUpload/processors/process_convert.py @@ -0,0 +1,90 @@ +# processors/process_convert.py +# -*- coding: utf-8 -*- +import sympy +import traceback +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_convert(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera SCL para Convert, tratando la conversión como una asignación.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Convert") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener EN y IN + en_input = instruction["inputs"].get("en") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + in_info = instruction["inputs"].get("in") + sympy_or_const_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + + # Obtener destino SCL + target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) + + # Verificar dependencias + if sympy_en_expr is None or sympy_or_const_in is None or target_scl_name is None: + return False + + # Convertir la entrada (SymPy o Constante) a SCL + # La simplificación aquí no suele aplicar a la conversión en sí, + # pero sí podría aplicar a la condición EN. + input_scl = sympy_expr_to_scl(sympy_or_const_in, symbol_manager) + + # Determinar el tipo de destino (esto sigue siendo un desafío sin info completa) + # Usaremos funciones de conversión SCL explícitas si podemos inferirlas. + target_type_hint = instruction.get("template_values", {}).get("destType", "").upper() # Ejemplo + source_type_hint = "" # Necesitaríamos info del tipo de origen + conversion_func_name = None + + # Heurística MUY básica (necesita mejorar con info de tipos real) + if target_type_hint and source_type_hint and target_type_hint != source_type_hint: + conversion_func_name = f"{source_type_hint}_TO_{target_type_hint}" + + # Generar SCL Core + if conversion_func_name: + # Usar función explícita si la inferimos + scl_core = f"{target_scl_name} := {conversion_func_name}({input_scl});" + else: + # Asignación directa (MOVE implícito) si no hay conversión clara + # ADVERTENCIA: Esto puede causar errores de tipo en el PLC si los tipos no coinciden. + scl_core = f"{target_scl_name} := {input_scl};" + if target_type_hint: # Añadir comentario si al menos conocemos el destino + scl_core += f" // TODO: Verify implicit conversion to {target_type_hint}" + + + # Aplicar Condición EN (Simplificando EN) + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for Convert {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # Actualizar instrucción y mapa + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar valor de salida (el contenido del destino) y ENO + map_key_out = (network_id, instr_uid, "out") + # Guardar el *nombre* SCL del destino en el mapa, ya que contiene el valor + # O podríamos crear un símbolo SymPy para ello si fuera necesario aguas abajo? Por ahora, string. + sympy_map[map_key_out] = target_scl_name + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el procesador Convert.""" + return {'type_name': 'convert', 'processor_func': process_convert, 'priority': 4} \ No newline at end of file diff --git a/ToUpload/processors/process_counter.py b/ToUpload/processors/process_counter.py new file mode 100644 index 0000000..693355a --- /dev/null +++ b/ToUpload/processors/process_counter.py @@ -0,0 +1,110 @@ +# processors/process_counter.py +# -*- coding: utf-8 -*- +import sympy +import traceback +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_counter(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL para Contadores (CTU, CTD, CTUD). + Requiere datos de instancia (DB o STAT). + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "") # CTU, CTD, CTUD + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # 1. Definir pines de entrada esperados + input_pins_map = { + "CTU": ["CU", "R", "PV"], + "CTD": ["CD", "LD", "PV"], + "CTUD": ["CU", "CD", "R", "LD", "PV"] + } + input_pins = input_pins_map.get(instr_type_original.upper()) + if not input_pins: + instruction["scl"] = f"// ERROR: Tipo de contador no soportado: {instr_type_original}" + instruction["type"] = instr_type_original + "_error" + return True + + # 2. Procesar Parámetros de Entrada + scl_call_params = [] + dependencies_resolved = True + optional_pins = {"R", "LD"} # Estos pueden no estar conectados + + for pin in input_pins: + pin_info = instruction["inputs"].get(pin) + if pin_info: # Si el pin está definido en el JSON + source_sympy_or_const = get_sympy_representation(pin_info, network_id, sympy_map, symbol_manager) + if source_sympy_or_const is None: + # print(f"DEBUG Counter {instr_uid}: Input param '{pin}' dependency not ready.") + dependencies_resolved = False + break + # Convertir a SCL para la llamada (sin simplificar aquí) + param_scl_value = sympy_expr_to_scl(source_sympy_or_const, symbol_manager) + pin_name_scl = format_variable_name(pin) # Formatear nombre del parámetro + scl_call_params.append(f"{pin_name_scl} := {param_scl_value}") + elif pin not in optional_pins: # Si falta un pin requerido + print(f"Error: Falta entrada requerida '{pin}' para {instr_type_original} UID {instr_uid}.") + instruction["scl"] = f"// ERROR: Falta entrada requerida '{pin}' para {instr_type_original} UID {instr_uid}." + instruction["type"] = instr_type_original + "_error" + return True + + if not dependencies_resolved: + return False + + # 3. Obtener Nombre de Instancia + # Asumiendo que x1 o una fase previa llena 'instance_db' si es un FB multi-instancia + instance_name_raw = instruction.get("instance_db") + if not instance_name_raw: + # Asumiendo que es STAT si no hay DB instancia explícito (requiere declaración en x3) + instance_name_raw = instruction.get("instance_name") # Buscar nombre directo si x1 lo provee + if not instance_name_raw: + instance_name_raw = f"#CTR_INSTANCE_{instr_uid}" # Placeholder final + print(f"Advertencia: No se encontró nombre/instancia para {instr_type_original} UID {instr_uid}. Usando placeholder '{instance_name_raw}'.") + instance_name_scl = format_variable_name(instance_name_raw) + + # 4. Generar la llamada SCL + param_string = ", ".join(scl_call_params) + scl_call = f"{instance_name_scl}({param_string}); // TODO: Declarar {instance_name_scl} : {instr_type_original.upper()}; en VAR_STAT o VAR" + + # Contadores no suelen tener EN/ENO explícito en LAD, se asume siempre habilitado + instruction["scl"] = scl_call # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # 4. Actualizar sympy_map para las salidas (QU, QD, CV) + output_pins_map = { + "CTU": ["QU", "CV"], + "CTD": ["QD", "CV"], + "CTUD": ["QU", "QD", "CV"] + } + output_pins = output_pins_map.get(instr_type_original.upper(), []) + + for pin in output_pins: + map_key = (network_id, instr_uid, pin) + output_scl_access = f"{instance_name_scl}.{pin.upper()}" + if pin.upper() in ["QU", "QD"]: # These are boolean outputs + # *** Store SymPy Symbol for boolean outputs QU/QD *** + sympy_out_symbol = symbol_manager.get_symbol(output_scl_access) + if sympy_out_symbol: + sympy_map[map_key] = sympy_out_symbol # Store SYMBOL + else: + print(f"Error: Could not create symbol for {output_scl_access} in {instr_type_original} {instr_uid}") + sympy_map[map_key] = None + else: + # For non-boolean (like CV - count value), store SCL access string + sympy_map[map_key] = output_scl_access + + return True + + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para los contadores CTU, CTD, CTUD.""" + return [ + {'type_name': 'ctu', 'processor_func': process_counter, 'priority': 5}, + {'type_name': 'ctd', 'processor_func': process_counter, 'priority': 5}, + {'type_name': 'ctud', 'processor_func': process_counter, 'priority': 5} + ] \ No newline at end of file diff --git a/ToUpload/processors/process_edge_detector.py b/ToUpload/processors/process_edge_detector.py new file mode 100644 index 0000000..6ff2fd3 --- /dev/null +++ b/ToUpload/processors/process_edge_detector.py @@ -0,0 +1,85 @@ +# processors/process_edge_detector.py +# -*- coding: utf-8 -*- +import sympy +import traceback +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" + +def process_edge_detector(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera la expresión SymPy para el pulso de PBox (P_TRIG) o NBox (N_TRIG). + Guarda la expresión SymPy del pulso en sympy_map['out']. + Genera y guarda el SCL para la actualización de memoria en '_edge_mem_update_scl'. + El campo 'scl' principal se deja casi vacío/comentario. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "") # PBox o NBox + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # 1. Obtener CLK (como SymPy expr) y MemBit (como SymPy Symbol) + clk_input = instruction["inputs"].get("in") + mem_bit_input = instruction["inputs"].get("bit") + + sympy_clk_expr = get_sympy_representation(clk_input, network_id, sympy_map, symbol_manager) + mem_bit_plc_name = extract_plc_variable_name(mem_bit_input) + sympy_mem_bit_symbol = symbol_manager.get_symbol(mem_bit_plc_name) if mem_bit_plc_name else None + + # 2. Verificar dependencias + if sympy_clk_expr is None: return False + if sympy_mem_bit_symbol is None: + err_msg = f"MemBit no resuelto o no es variable para {instr_type_original} UID {instr_uid}" + print(f"Error: {err_msg}") + instruction["scl"] = f"// ERROR: {err_msg}" + instruction["type"] = instr_type_original + "_error" + return True + + # 3. Generar Lógica SymPy del *pulso* + result_pulse_sympy_expr = sympy.false # Default + scl_comment_prefix = "" + if instr_type_original.upper() == "PBOX": # P_TRIG + result_pulse_sympy_expr = sympy.And(sympy_clk_expr, sympy.Not(sympy_mem_bit_symbol)) + scl_comment_prefix = "P_TRIG" + elif instr_type_original.upper() == "NBOX": # N_TRIG + result_pulse_sympy_expr = sympy.And(sympy.Not(sympy_clk_expr), sympy_mem_bit_symbol) + scl_comment_prefix = "N_TRIG" + else: # Error + instruction["scl"] = f"// ERROR: Tipo de flanco inesperado {instr_type_original}" + instruction["type"] = instr_type_original + "_error" + return True + + # 4. Generar el SCL para la actualización del bit de memoria + # Necesitamos la representación SCL de la entrada CLK + clk_scl_str = sympy_expr_to_scl(sympy_clk_expr, symbol_manager) + # Usamos el nombre PLC original formateado para el bit de memoria + mem_bit_scl_name = format_variable_name(mem_bit_plc_name) + scl_mem_update = f"{mem_bit_scl_name} := {clk_scl_str};" + scl_comment_for_update = f"// {scl_comment_prefix}({clk_scl_str}) - Mem: {mem_bit_scl_name}" + + # 5. Almacenar Resultados + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = result_pulse_sympy_expr # Guardar EXPRESIÓN SymPy del pulso + + # Guardar SCL de actualización + Comentario en campo temporal + instruction['_edge_mem_update_scl'] = f"{scl_mem_update} {scl_comment_for_update}" + + # Marcar como procesado, SCL principal es solo comentario + instruction['scl'] = f"// {instr_type_original} SymPy processed, logic in consumer" + instruction["type"] = instr_type_original + SCL_SUFFIX + + # 6. Propagar ENO (es la expresión SymPy de CLK) + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_clk_expr + + return True + + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la info para los detectores de flanco PBox y NBox.""" + return [ + {'type_name': 'pbox', 'processor_func': process_edge_detector, 'priority': 2}, + {'type_name': 'nbox', 'processor_func': process_edge_detector, 'priority': 2} + ] \ No newline at end of file diff --git a/ToUpload/processors/process_eq.py b/ToUpload/processors/process_eq.py new file mode 100644 index 0000000..3b16a32 --- /dev/null +++ b/ToUpload/processors/process_eq.py @@ -0,0 +1,64 @@ +# processors/process_eq.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades de SymPy +from .processor_utils import get_sympy_representation, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo + +def process_eq(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera la expresión SymPy para el comparador de igualdad (EQ). + El resultado se propaga por sympy_map['out']. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Eq") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener operandos como expresiones SymPy o constantes/strings + in1_info = instruction["inputs"].get("in1") + in2_info = instruction["inputs"].get("in2") + op1_sympy = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager) + op2_sympy = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager) + + # Obtener 'pre' (RLO anterior) como expresión SymPy + pre_input = instruction["inputs"].get("pre") # Asumir 'pre' como entrada RLO estándar + sympy_pre_rlo = get_sympy_representation(pre_input, network_id, sympy_map, symbol_manager) if pre_input else sympy.true + + # Verificar dependencias + if op1_sympy is None or op2_sympy is None or sympy_pre_rlo is None: + # print(f"DEBUG EQ {instr_uid}: Dependency not ready") + return False + + # Crear la expresión de igualdad SymPy + try: + # sympify puede ser necesario si los operandos son strings de constantes + op1_eval = sympy.sympify(op1_sympy) if isinstance(op1_sympy, str) else op1_sympy + op2_eval = sympy.sympify(op2_sympy) if isinstance(op2_sympy, str) else op2_sympy + comparison_expr = sympy.Eq(op1_eval, op2_eval) # Eq para igualdad + except (SyntaxError, TypeError, ValueError) as e: + print(f"Error creating SymPy equality for {instr_uid}: {e}") + instruction["scl"] = f"// ERROR creando expr SymPy EQ {instr_uid}: {e}" + instruction["type"] = instr_type_original + "_error" + return True + + # Guardar resultado (Expresión SymPy booleana) en el mapa para 'out' + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = comparison_expr + + # Guardar el RLO de entrada ('pre') como ENO en el mapa SymPy + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_pre_rlo + + # Marcar como procesado, SCL principal es solo comentario + instruction["scl"] = f"// SymPy EQ: {comparison_expr}" # Comentario opcional + instruction["type"] = instr_type_original + SCL_SUFFIX + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el comparador de igualdad (EQ).""" + return {'type_name': 'eq', 'processor_func': process_eq, 'priority': 2} \ No newline at end of file diff --git a/ToUpload/processors/process_math.py b/ToUpload/processors/process_math.py new file mode 100644 index 0000000..b181a12 --- /dev/null +++ b/ToUpload/processors/process_math.py @@ -0,0 +1,90 @@ +# processors/process_math.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_math(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL para operaciones matemáticas (SUB, MUL, DIV), simplificando EN. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "") # SUB, MUL, DIV + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Mapa de tipos a operadores SCL string + op_map = {"SUB": "-", "MUL": "*", "DIV": "/"} + scl_operator = op_map.get(instr_type_original.upper()) + if not scl_operator: + instruction["scl"] = f"// ERROR: Operación matemática no soportada: {instr_type_original}" + instruction["type"] = instr_type_original + "_error" + return True + + # Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String) + en_input = instruction["inputs"].get("en") + in1_info = instruction["inputs"].get("in1") + in2_info = instruction["inputs"].get("in2") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager) + op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager) + + # Obtener destino SCL + target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) + + # Verificar dependencias + if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None: + return False + + # Convertir operandos SymPy/Constante a SCL strings + op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager) + op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager) + + # Añadir paréntesis si contienen operadores (más seguro) + # La función sympy_expr_to_scl debería idealmente manejar esto, pero doble chequeo simple: + op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl + op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl + + # Generar SCL Core + scl_core = f"{target_scl_name} := {op1_scl_formatted} {scl_operator} {op2_scl_formatted};" + + # Aplicar Condición EN (Simplificando EN) + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # Actualizar instrucción y mapa + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy) + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve info para SUB, MUL, DIV.""" + return [ + {'type_name': 'sub', 'processor_func': process_math, 'priority': 4}, + {'type_name': 'mul', 'processor_func': process_math, 'priority': 4}, + {'type_name': 'div', 'processor_func': process_math, 'priority': 4} + ] \ No newline at end of file diff --git a/ToUpload/processors/process_mod.py b/ToUpload/processors/process_mod.py new file mode 100644 index 0000000..03c0ef1 --- /dev/null +++ b/ToUpload/processors/process_mod.py @@ -0,0 +1,75 @@ +# processors/process_mod.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re # Importar re si no estaba +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_mod(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera SCL para Modulo (MOD), simplificando EN.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Mod") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener EN (SymPy), IN1, IN2 (SymPy o Constante/String) + en_input = instruction["inputs"].get("en") + in1_info = instruction["inputs"].get("in1") + in2_info = instruction["inputs"].get("in2") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + op1_sympy_or_const = get_sympy_representation(in1_info, network_id, sympy_map, symbol_manager) + op2_sympy_or_const = get_sympy_representation(in2_info, network_id, sympy_map, symbol_manager) + + # Obtener destino SCL + target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=True) + + # Verificar dependencias + if sympy_en_expr is None or op1_sympy_or_const is None or op2_sympy_or_const is None or target_scl_name is None: + return False + + # Convertir operandos SymPy/Constante a SCL strings + op1_scl = sympy_expr_to_scl(op1_sympy_or_const, symbol_manager) + op2_scl = sympy_expr_to_scl(op2_sympy_or_const, symbol_manager) + + # Añadir paréntesis si contienen operadores + op1_scl_formatted = f"({op1_scl})" if re.search(r'[+\-*/ ]', op1_scl) else op1_scl + op2_scl_formatted = f"({op2_scl})" if re.search(r'[+\-*/ ]', op2_scl) else op2_scl + + # Generar SCL Core + scl_core = f"{target_scl_name} := {op1_scl_formatted} MOD {op2_scl_formatted};" + + # Aplicar Condición EN (Simplificando EN) + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # Actualizar instrucción y mapa + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy) + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = target_scl_name # Guardar nombre del destino + map_key_eno = (network_id, instr_uid, "eno") + sympy_map[map_key_eno] = sympy_en_expr # Guardar la expresión SymPy para ENO + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la operación Modulo.""" + return {'type_name': 'mod', 'processor_func': process_mod, 'priority': 4} \ No newline at end of file diff --git a/ToUpload/processors/process_move.py b/ToUpload/processors/process_move.py new file mode 100644 index 0000000..b950797 --- /dev/null +++ b/ToUpload/processors/process_move.py @@ -0,0 +1,75 @@ +# processors/process_move.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re # Importar re +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_move(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera SCL para Move, simplificando la condición EN.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Move") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener EN (SymPy) e IN (SymPy o Constante/String) + en_input = instruction["inputs"].get("en") + in_info = instruction["inputs"].get("in") + sympy_en_expr = get_sympy_representation(en_input, network_id, sympy_map, symbol_manager) if en_input else sympy.true + input_sympy_or_const = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + + # Obtener destino SCL (requiere destino explícito para MOVE) + target_scl_name = get_target_scl_name(instruction, "out1", network_id, default_to_temp=False) + if target_scl_name is None: + target_scl_name = get_target_scl_name(instruction, "out", network_id, default_to_temp=False) + + # Verificar dependencias + if sympy_en_expr is None or input_sympy_or_const is None: + return False + if target_scl_name is None: + print(f"Error: MOVE {instr_uid} sin destino claro en 'out' o 'out1'.") + instruction["scl"] = f"// ERROR: MOVE {instr_uid} sin destino claro." + instruction["type"] = instr_type_original + "_error" + return True # Procesado con error + + # Convertir la entrada (SymPy o Constante) a SCL string + input_scl = sympy_expr_to_scl(input_sympy_or_const, symbol_manager) + + # Generar SCL Core + scl_core = f"{target_scl_name} := {input_scl};" + + # Aplicar Condición EN (Simplificando EN) + scl_final = "" + if sympy_en_expr != sympy.true: + try: + #simplified_en_expr = sympy.simplify_logic(sympy_en_expr, force=True) + simplified_en_expr = sympy.logic.boolalg.to_dnf(sympy_en_expr, simplify=True) + except Exception as e: + print(f"Error simplifying EN for {instr_type_original} {instr_uid}: {e}") + simplified_en_expr = sympy_en_expr # Fallback + en_condition_scl = sympy_expr_to_scl(simplified_en_expr, symbol_manager) + + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {en_condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + scl_final = scl_core + + # Actualizar instrucción y mapa + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # Propagar valor de salida (nombre SCL del destino) y ENO (expresión SymPy) + # Asumiendo que out y out1 deben propagar el mismo valor + sympy_map[(network_id, instr_uid, "out")] = target_scl_name + sympy_map[(network_id, instr_uid, "out1")] = target_scl_name + sympy_map[(network_id, instr_uid, "eno")] = sympy_en_expr + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la operación Move.""" + return {'type_name': 'move', 'processor_func': process_move, 'priority': 3} \ No newline at end of file diff --git a/ToUpload/processors/process_not.py b/ToUpload/processors/process_not.py new file mode 100644 index 0000000..b721aee --- /dev/null +++ b/ToUpload/processors/process_not.py @@ -0,0 +1,54 @@ +# processors/process_not.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo + +def process_not(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera la expresión SymPy para la inversión lógica NOT.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "Not") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener entrada como expresión SymPy + in_info = instruction["inputs"].get("in") + sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + + # Verificar dependencias + if sympy_expr_in is None: + # print(f"DEBUG Not {instr_uid}: Dependency not ready") + return False + + # Crear la expresión NOT de SymPy + try: + not_expr = sympy.Not(sympy_expr_in) + # ¿Simplificar aquí? NOT(NOT A) -> A; NOT(TRUE) -> FALSE, etc. + # simplify_logic podría hacer esto, pero puede ser costoso en cada paso. + # SymPy podría manejar simplificaciones básicas automáticamente. + # Opcional: not_expr = sympy.simplify_logic(not_expr) + except Exception as e: + print(f"Error creating SymPy Not for {instr_uid}: {e}") + instruction["scl"] = f"// ERROR creando expr SymPy NOT {instr_uid}: {e}" + instruction["type"] = instr_type_original + "_error" + return True + + # Guardar resultado (Expresión SymPy) en el mapa para 'out' + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = not_expr + + # Marcar como procesado, SCL principal es solo comentario + instruction["scl"] = f"// SymPy NOT: {not_expr}" # Comentario opcional + instruction["type"] = instr_type_original + SCL_SUFFIX + # NOT no tiene EN/ENO explícito en LAD, modifica el RLO ('out') + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la operación Not.""" + return {'type_name': 'not', 'processor_func': process_not, 'priority': 1} \ No newline at end of file diff --git a/ToUpload/processors/process_o.py b/ToUpload/processors/process_o.py new file mode 100644 index 0000000..446817f --- /dev/null +++ b/ToUpload/processors/process_o.py @@ -0,0 +1,69 @@ +# processors/process_o.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" # Nuevo sufijo + +def process_o(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera la expresión SymPy para la operación lógica O (OR).""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "O") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Buscar todas las entradas 'in', 'in1', 'in2', ... + input_pins = sorted([pin for pin in instruction.get("inputs", {}) if pin.startswith("in")]) + + if not input_pins: + print(f"Error: O {instr_uid} sin pines de entrada (inX).") + instruction["scl"] = f"// ERROR: O {instr_uid} sin pines inX" + instruction["type"] = instr_type_original + "_error" + return True + + sympy_parts = [] + all_resolved = True + for pin in input_pins: + input_info = instruction["inputs"][pin] + sympy_expr = get_sympy_representation(input_info, network_id, sympy_map, symbol_manager) + + if sympy_expr is None: + all_resolved = False + # print(f"DEBUG: O {instr_uid} esperando pin {pin}") + break # Salir si una dependencia no está lista + + # Optimización: No incluir FALSE en un OR + if sympy_expr != sympy.false: + sympy_parts.append(sympy_expr) + + if not all_resolved: + return False # Esperar dependencias + + # Construir la expresión OR de SymPy + result_sympy_expr = sympy.false # Valor por defecto si no hay entradas válidas o todas son FALSE + if sympy_parts: + # Usar sympy.Or para construir la expresión + result_sympy_expr = sympy.Or(*sympy_parts) + # Simplificar casos obvios como OR(X) -> X, OR(X, TRUE) -> TRUE + # simplify_logic aquí puede ser prematuro, mejor al final. + # Pero Or() podría simplificar automáticamente OR(X) -> X. + # Opcional: result_sympy_expr = sympy.simplify_logic(result_sympy_expr) + + + # Guardar la expresión SymPy resultante en el mapa para 'out' + map_key_out = (network_id, instr_uid, "out") + sympy_map[map_key_out] = result_sympy_expr + + # Marcar como procesado, SCL principal es solo comentario + instruction["scl"] = f"// SymPy O: {result_sympy_expr}" # Comentario opcional + instruction["type"] = instr_type_original + SCL_SUFFIX + # La instrucción 'O' no tiene ENO propio, propaga el resultado por 'out' + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la operación lógica O (OR).""" + return {'type_name': 'o', 'processor_func': process_o, 'priority': 1} \ No newline at end of file diff --git a/ToUpload/processors/process_rcoil.py b/ToUpload/processors/process_rcoil.py new file mode 100644 index 0000000..9824b83 --- /dev/null +++ b/ToUpload/processors/process_rcoil.py @@ -0,0 +1,74 @@ +# processors/process_rcoil.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_rcoil(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data ): + """Genera SCL para Reset Coil (RCoil), simplificando la condición.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "RCoil") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener condición de entrada (SymPy expr) + in_info = instruction["inputs"].get("in") + sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + + # Obtener operando (nombre SCL del destino) + target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # RCoil necesita destino explícito + + # Verificar dependencias + if sympy_expr_in is None: return False + if target_scl_name is None: + print(f"Error: RCoil {instr_uid} operando no es variable o falta info.") + instruction["scl"] = f"// ERROR: RCoil {instr_uid} operando no es variable." + instruction["type"] = instr_type_original + "_error" + return True + + # No hacer nada si la condición es FALSE constante + if sympy_expr_in == sympy.false: + instruction["scl"] = f"// RCoil {instr_uid} con condición FALSE constante, optimizado." + instruction["type"] = instr_type_original + SCL_SUFFIX + return True + + # Generar SCL Core (Reset) + scl_core = f"{target_scl_name} := FALSE;" + + # Aplicar Condición IF si no es TRUE constante + scl_final = "" + if sympy_expr_in != sympy.true: + # Simplificar la condición ANTES de convertirla a SCL + try: + #simplified_expr = sympy.simplify_logic(sympy_expr_in, force=True) + simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True) + except Exception as e: + print(f"Error simplifying condition for RCoil {instr_uid}: {e}") + simplified_expr = sympy_expr_in # Fallback + condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager) + + # Evitar IF TRUE THEN... + if condition_scl == "TRUE": + scl_final = scl_core + else: + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + # Condición es TRUE constante + scl_final = scl_core + + # Actualizar instrucción + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + # RCoil no tiene salida lógica para propagar en sympy_map + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la bobina Reset (RCoil).""" + return {'type_name': 'rcoil', 'processor_func': process_rcoil, 'priority': 3} \ No newline at end of file diff --git a/ToUpload/processors/process_scoil.py b/ToUpload/processors/process_scoil.py new file mode 100644 index 0000000..4411f51 --- /dev/null +++ b/ToUpload/processors/process_scoil.py @@ -0,0 +1,74 @@ +# processors/process_scoil.py +# -*- coding: utf-8 -*- +import sympy +import traceback +import re +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, get_target_scl_name, format_variable_name +from .symbol_manager import SymbolManager + +SCL_SUFFIX = "_sympy_processed" + +def process_scoil(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """Genera SCL para Set Coil (SCoil), simplificando la condición.""" + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "SCoil") + if instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original: + return False + + # Obtener condición de entrada (SymPy expr) + in_info = instruction["inputs"].get("in") + sympy_expr_in = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + + # Obtener operando (nombre SCL del destino) + target_scl_name = get_target_scl_name(instruction, "operand", network_id, default_to_temp=False) # SCoil necesita destino + + # Verificar dependencias + if sympy_expr_in is None: return False + if target_scl_name is None: + print(f"Error: SCoil {instr_uid} operando no es variable o falta info.") + instruction["scl"] = f"// ERROR: SCoil {instr_uid} operando no es variable." + instruction["type"] = instr_type_original + "_error" + return True + + # No hacer nada si la condición es FALSE constante + if sympy_expr_in == sympy.false: + instruction["scl"] = f"// SCoil {instr_uid} con condición FALSE constante, optimizado." + instruction["type"] = instr_type_original + SCL_SUFFIX + return True + + # Generar SCL Core (Set) + scl_core = f"{target_scl_name} := TRUE;" + + # Aplicar Condición IF si no es TRUE constante + scl_final = "" + if sympy_expr_in != sympy.true: + # Simplificar la condición ANTES de convertirla a SCL + try: + #simplified_expr = sympy.simplify_logic(sympy_expr_in, force=True) + simplified_expr = sympy.logic.boolalg.to_dnf(sympy_expr_in, simplify=True) + except Exception as e: + print(f"Error simplifying condition for SCoil {instr_uid}: {e}") + simplified_expr = sympy_expr_in # Fallback + condition_scl = sympy_expr_to_scl(simplified_expr, symbol_manager) + + # Evitar IF TRUE THEN... + if condition_scl == "TRUE": + scl_final = scl_core + else: + indented_core = "\n".join([f" {line}" for line in scl_core.splitlines()]) + scl_final = f"IF {condition_scl} THEN\n{indented_core}\nEND_IF;" + else: + # Condición es TRUE constante + scl_final = scl_core + + # Actualizar instrucción + instruction["scl"] = scl_final # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + # SCoil no tiene salida lógica para propagar en sympy_map + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para la bobina Set (SCoil).""" + return {'type_name': 'scoil', 'processor_func': process_scoil, 'priority': 3} \ No newline at end of file diff --git a/ToUpload/processors/process_sd.py b/ToUpload/processors/process_sd.py new file mode 100644 index 0000000..72d51eb --- /dev/null +++ b/ToUpload/processors/process_sd.py @@ -0,0 +1,77 @@ +# processors/process_sd.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" + +def process_sd(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL para Temporizador On-Delay (Sd -> TON). + Requiere datos de instancia (DB o STAT/TEMP). + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = "Sd" # Tipo original LAD + if instruction.get("type","").endswith(SCL_SUFFIX) or "_error" in instruction.get("type",""): + return False + + # 1. Obtener Inputs: s (start), tv (time value), timer (instance) + s_info = instruction["inputs"].get("s") + tv_info = instruction["inputs"].get("tv") + timer_instance_info = instruction["inputs"].get("timer") + + sympy_s_expr = get_sympy_representation(s_info, network_id, sympy_map, symbol_manager) + # tv suele ser constante, pero lo obtenemos igual + sympy_or_const_tv = get_sympy_representation(tv_info, network_id, sympy_map, symbol_manager) + # Obtener el nombre de la INSTANCIA (no su valor) + instance_plc_name = extract_plc_variable_name(timer_instance_info) + + # Verificar dependencias + if sympy_s_expr is None or sympy_or_const_tv is None: return False + if instance_plc_name is None: + print(f"Error: Sd {instr_uid} sin variable de instancia 'timer'.") + instance_plc_name = f"#TON_INSTANCE_{instr_uid}" # Placeholder con error implícito + print(f"Advertencia: Usando placeholder '{instance_plc_name}'. ¡Declarar en SCL!") + # Podríamos marcar como error, pero intentamos generar algo + # instruction["type"] = instr_type_original + "_error" + # return True + + # Formatear nombre de instancia + instance_name_scl = format_variable_name(instance_plc_name) + + # Convertir entradas SymPy/Constante a SCL strings + s_scl = sympy_expr_to_scl(sympy_s_expr, symbol_manager) + tv_scl = sympy_expr_to_scl(sympy_or_const_tv, symbol_manager) + + # Generar la llamada SCL (TON usa IN, PT) + # Ignoramos 'r' (reset) de Sd + scl_call = f"{instance_name_scl}(IN := {s_scl}, PT := {tv_scl}); // TODO: Declarar {instance_name_scl} : TON;" + + # Actualizar instrucción + instruction["scl"] = scl_call # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # 7. Actualizar sympy_map para las salidas Q y RT + map_key_q = (network_id, instr_uid, "q") + q_output_scl_access = f"{instance_name_scl}.Q" # SCL string to access output + # *** GET/CREATE AND STORE SYMBOL for boolean output Q *** + sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access) + if sympy_q_symbol: + sympy_map[map_key_q] = sympy_q_symbol # STORE THE SYMBOL OBJECT + else: + print(f"Error: Could not create symbol for {q_output_scl_access} in Sd {instr_uid}") + sympy_map[map_key_q] = None # Indicate error/unresolved + + map_key_rt = (network_id, instr_uid, "rt") + # ET is TIME, store SCL access string + sympy_map[map_key_rt] = f"{instance_name_scl}.ET" + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la información para el temporizador On-Delay (Sd -> TON).""" + return {'type_name': 'sd', 'processor_func': process_sd, 'priority': 5} \ No newline at end of file diff --git a/ToUpload/processors/process_se.py b/ToUpload/processors/process_se.py new file mode 100644 index 0000000..cb44741 --- /dev/null +++ b/ToUpload/processors/process_se.py @@ -0,0 +1,112 @@ +# processors/process_se.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" + +def process_se(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL para Temporizador de Pulso (Se -> TP) o SdCoil (-> TON). + Usa SymPy para entradas y almacena Symbol para salida Q. + """ + instr_uid = instruction["instruction_uid"] + # Obtener tipo original (antes de añadir sufijo) para determinar comportamiento + instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX,"").replace("_error","") # Se o SdCoil + current_type = instruction.get("type","") # Tipo actual para chequeo inicial + if current_type.endswith(SCL_SUFFIX) or "_error" in current_type: + return False + + # Determinar el tipo de instrucción SCL y pines de entrada/salida correctos + scl_timer_type = "TP" + pin_in = "s" # Pin de entrada para Se + pin_time = "tv" # Pin de valor de tiempo para Se + pin_instance = "timer" # Pin donde se conecta la instancia para Se + pin_out_q = "q" # Pin de salida Q para Se + pin_out_time = "rt" # Pin de tiempo restante para Se -> TP.ET + + # Ajustar pines si el tipo original era SdCoil + if instr_type_original == "SdCoil": + scl_timer_type = "TON" # SdCoil es funcionalmente un TON + pin_in = "in" # SdCoil usa 'in' + pin_time = "value" # SdCoil usa 'value' + pin_instance = "operand" # SdCoil usa 'operand' como instancia/variable de salida + pin_out_q = "out" # SdCoil usa 'out' como pin de salida Q + pin_out_time = None # SdCoil no tiene salida ET explícita + + # 1. Obtener Inputs usando los nombres de pin correctos + s_info = instruction["inputs"].get(pin_in) + tv_info = instruction["inputs"].get(pin_time) + timer_instance_info = instruction["inputs"].get(pin_instance) + + # Obtener representaciones (SymPy o Constante/String) + sympy_s_expr = get_sympy_representation(s_info, network_id, sympy_map, symbol_manager) + sympy_or_const_tv = get_sympy_representation(tv_info, network_id, sympy_map, symbol_manager) + # Obtener el nombre PLC original de la INSTANCIA + instance_plc_name = extract_plc_variable_name(timer_instance_info) + + # 2. Verificar dependencias + if sympy_s_expr is None or sympy_or_const_tv is None: + # print(f"DEBUG {instr_type_original} {instr_uid}: Input/TV dependency not ready") + return False + if instance_plc_name is None: + print(f"Error: {instr_type_original} {instr_uid} sin variable de instancia en pin '{pin_instance}'.") + instance_plc_name = f"#{scl_timer_type}_INSTANCE_{instr_uid}" # Placeholder + print(f"Advertencia: Usando placeholder '{instance_plc_name}'. ¡Declarar en SCL!") + + # 3. Formatear nombre de instancia para SCL + instance_name_scl = format_variable_name(instance_plc_name) + + # 4. Convertir entradas SymPy/Constante a SCL strings (simplificando la entrada IN) + try: + # Simplificar la expresión de entrada booleana + simplified_s_expr = sympy.simplify_logic(sympy_s_expr, force=True) + simplified_s_expr = sympy.logic.boolalg.to_dnf(sympy_s_expr, simplify=True) + except Exception as e: + print(f"Error simplifying '{pin_in}' input for {instr_type_original} {instr_uid}: {e}") + simplified_s_expr = sympy_s_expr # Fallback + s_scl = sympy_expr_to_scl(simplified_s_expr, symbol_manager) + + # tv normalmente es constante, sympy_expr_to_scl debería manejarlo + tv_scl = sympy_expr_to_scl(sympy_or_const_tv, symbol_manager) + + # 5. Generar la llamada SCL + # Ignoramos 'r' (reset) de Se si existiera + scl_call = f"{instance_name_scl}(IN := {s_scl}, PT := {tv_scl}); // TODO: Declarar {instance_name_scl} : {scl_timer_type};" + + # 6. Actualizar instrucción con el SCL final + instruction["scl"] = scl_call + instruction["type"] = instr_type_original + SCL_SUFFIX # Marcar como procesado + + # 7. Actualizar sympy_map para las salidas (Q y ET si aplica) + # Usar los nombres de pin originales determinados al principio + map_key_q = (network_id, instr_uid, pin_out_q) # pin_out_q es 'q' o 'out' + q_output_scl_access = f"{instance_name_scl}.Q" # Siempre accedemos a .Q del FB SCL + # *** OBTENER/CREAR Y ALMACENAR SYMBOL para la salida booleana Q *** + sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access) + if sympy_q_symbol: + sympy_map[map_key_q] = sympy_q_symbol # Almacenar el OBJETO SYMBOL + else: + # Manejar error si no se pudo crear el símbolo + print(f"Error: No se pudo crear símbolo para {q_output_scl_access} en {instr_type_original} {instr_uid}") + sympy_map[map_key_q] = None # Indicar error/irresoluble + + # Almacenar ET solo si corresponde (para Se, no para SdCoil) + if pin_out_time: # pin_out_time es 'rt' o None + map_key_rt = (network_id, instr_uid, pin_out_time) + # ET es TIME, no booleano. Almacenar el string SCL de acceso está bien. + sympy_map[map_key_rt] = f"{instance_name_scl}.ET" # Salida ET del FB SCL + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve la info para Se (-> TP) y SdCoil (-> TON, manejado aquí).""" + return [ + {'type_name': 'se', 'processor_func': process_se, 'priority': 5}, + # Asegurarse que x1.py mapea SdCoil a este procesador o a uno específico + {'type_name': 'sdcoil', 'processor_func': process_se, 'priority': 5} + ] \ No newline at end of file diff --git a/ToUpload/processors/process_timer.py b/ToUpload/processors/process_timer.py new file mode 100644 index 0000000..f6b3d40 --- /dev/null +++ b/ToUpload/processors/process_timer.py @@ -0,0 +1,85 @@ +# processors/process_timer.py +# -*- coding: utf-8 -*- +import sympy +import traceback +# Usar las nuevas utilidades +from .processor_utils import get_sympy_representation, sympy_expr_to_scl, format_variable_name, get_target_scl_name +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" + +def process_timer(instruction, network_id, sympy_map, symbol_manager: SymbolManager, data): + """ + Genera SCL para Temporizadores (TON, TOF) directamente. + Requiere datos de instancia. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX,"").replace("_error","") # TON o TOF + if instruction.get("type","").endswith(SCL_SUFFIX) or "_error" in instruction.get("type",""): + return False + + scl_timer_type = instr_type_original.upper() + if scl_timer_type not in ["TON", "TOF"]: + instruction["scl"] = f"// ERROR: Tipo de temporizador directo no soportado: {instr_type_original}" + instruction["type"] = instr_type_original + "_error" + return True + + # 1. Obtener Inputs: IN, PT, y nombre de instancia (implícito o explícito) + in_info = instruction["inputs"].get("IN") + pt_info = instruction["inputs"].get("PT") + # Buscar instancia: ¿está en inputs? ¿o como instance_db? + instance_plc_name = instruction.get("instance_db") # Buscar primero aquí + if not instance_plc_name: + # Si no, buscar un input llamado 'timer' o similar? No estándar. + # Asumir que debe estar declarado como STAT si no hay instance_db + instance_plc_name = instruction.get("instance_name") # Nombre directo? + if not instance_plc_name: + instance_plc_name = f"#{scl_timer_type}_INSTANCE_{instr_uid}" # Placeholder final + print(f"Advertencia: No se encontró nombre/instancia para {instr_type_original} UID {instr_uid}. Usando placeholder '{instance_plc_name}'.") + + + sympy_in_expr = get_sympy_representation(in_info, network_id, sympy_map, symbol_manager) + sympy_or_const_pt = get_sympy_representation(pt_info, network_id, sympy_map, symbol_manager) + + # Verificar dependencias + if sympy_in_expr is None or sympy_or_const_pt is None or instance_plc_name is None: + return False + + # Formatear nombre de instancia + instance_name_scl = format_variable_name(instance_plc_name) + + # Convertir entradas SymPy/Constante a SCL strings + in_scl = sympy_expr_to_scl(sympy_in_expr, symbol_manager) + pt_scl = sympy_expr_to_scl(sympy_or_const_pt, symbol_manager) + + # Generar la llamada SCL + scl_call = f"{instance_name_scl}(IN := {in_scl}, PT := {pt_scl}); // TODO: Declarar {instance_name_scl} : {scl_timer_type};" + + # Actualizar instrucción + instruction["scl"] = scl_call # SCL final generado + instruction["type"] = instr_type_original + SCL_SUFFIX + + # 7. Actualizar sympy_map para las salidas Q y ET + map_key_q = (network_id, instr_uid, "Q") # Pin estándar SCL + # *** Store SymPy Symbol for boolean output Q *** + q_output_scl_access = f"{instance_name_scl}.Q" # String for SCL access + sympy_q_symbol = symbol_manager.get_symbol(q_output_scl_access) # Get/Create Symbol + if sympy_q_symbol: + sympy_map[map_key_q] = sympy_q_symbol # Store the SYMBOL + else: + print(f"Error: Could not create symbol for {q_output_scl_access} in {instr_type_original} {instr_uid}") + sympy_map[map_key_q] = None + + map_key_et = (network_id, instr_uid, "ET") # Pin estándar SCL + # ET is TIME, store SCL access string + sympy_map[map_key_et] = f"{instance_name_scl}.ET" + + return True + +# --- Processor Information Function --- +def get_processor_info(): + """Devuelve info para TON y TOF directos.""" + return [ + {'type_name': 'ton', 'processor_func': process_timer, 'priority': 5}, + {'type_name': 'tof', 'processor_func': process_timer, 'priority': 5} + ] \ No newline at end of file diff --git a/ToUpload/processors/processor_utils.py b/ToUpload/processors/processor_utils.py new file mode 100644 index 0000000..6d55415 --- /dev/null +++ b/ToUpload/processors/processor_utils.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +# processors/processor_utils.py +import re +import sympy +from .symbol_manager import SymbolManager, extract_plc_variable_name + +SCL_SUFFIX = "_sympy_processed" # <<< AÑADE ESTA LÍNEA + +def format_variable_name(name): + """Limpia el nombre de la variable para SCL.""" + if not name: + return "_INVALID_NAME_" + if name.startswith('"') and name.endswith('"'): + return name + prefix = "" + if name.startswith("#"): + prefix = "#" + name = name[1:] + if name and name[0].isdigit(): + name = "_" + name + name = re.sub(r"[^a-zA-Z0-9_]", "_", name) + return prefix + name + +def get_sympy_representation(source_info, network_id, sympy_map, symbol_manager): + """Gets the SymPy expression object representing the source.""" + if not source_info: + print("Warning: get_sympy_representation called with None source_info.") + return None # Or raise error + + # Handle lists (OR branches) - Recursively call and combine with sympy.Or + if isinstance(source_info, list): + sympy_parts = [] + all_resolved = True + for sub_source in source_info: + sub_sympy = get_sympy_representation(sub_source, network_id, sympy_map, symbol_manager) + if sub_sympy is None: + all_resolved = False + break + sympy_parts.append(sub_sympy) + + if not all_resolved: + return None + if not sympy_parts: + return sympy.false # Empty OR is false + # Return sympy.Or only if there are multiple parts + return sympy.Or(*sympy_parts) if len(sympy_parts) > 1 else sympy_parts[0] + + # Handle single source dictionary + source_type = source_info.get("type") + + if source_type == "powerrail": + return sympy.true + elif source_type == "variable": + plc_name = extract_plc_variable_name(source_info) + if plc_name: + return symbol_manager.get_symbol(plc_name) + else: + print(f"Error: Variable source without name: {source_info}") + return None # Error case + elif source_type == "constant": + # Represent constants directly if possible, otherwise maybe as symbols? + # For boolean simplification, only TRUE/FALSE matter significantly. + dtype = str(source_info.get("datatype", "")).upper() + value = source_info.get("value") + if dtype == "BOOL": + return sympy.true if str(value).upper() == "TRUE" else sympy.false + else: + # For simplification, treat non-boolean constants as opaque symbols? + # Or just return their string representation if they won't be simplified anyway? + # Let's return their string value for now, processors will handle it. + # This might need refinement if constants need symbolic handling. + return str(value) # Or maybe symbol_manager.get_symbol(str(value))? + + elif source_type == "connection": + map_key = ( + network_id, + source_info.get("source_instruction_uid"), + source_info.get("source_pin"), + ) + # Return the SymPy object from the map + return sympy_map.get(map_key) # Returns None if not found (dependency not ready) + elif source_type == "unknown_source": + print(f"Warning: Referring to unknown source UID: {source_info.get('uid')}") + return None # Cannot resolve + else: + print(f"Warning: Unknown source type: {source_info}") + return None # Cannot resolve + +def sympy_expr_to_scl(expr, symbol_manager, format_prec=5): + """Converts a SymPy expression to an SCL string using the symbol map.""" + if expr is None: return "/* ERROR: None expression */" + if expr == sympy.true: return "TRUE" + if expr == sympy.false: return "FALSE" + + # Use sympy's string printer with custom settings if needed + # For boolean, standard printing might be okay, but need to substitute symbols + try: + # Get the inverse map (py_id -> plc_name) + inverse_map = symbol_manager.get_inverse_map() + + # Substitute symbols back to their py_id strings first + # Need to handle the structure (And, Or, Not) + scl_str = sympy.sstr(expr, order=None) # Basic string representation + + # Now, carefully replace py_id back to PLC names using regex + # Sort keys by length descending to replace longer IDs first + for py_id in sorted(inverse_map.keys(), key=len, reverse=True): + # Use word boundaries to avoid replacing parts of other IDs + scl_str = re.sub(r'\b' + re.escape(py_id) + r'\b', inverse_map[py_id], scl_str) + + # Replace SymPy operators/functions with SCL equivalents + scl_str = scl_str.replace('&', ' AND ') + scl_str = scl_str.replace('|', ' OR ') + scl_str = scl_str.replace('^', ' XOR ') # If XOR is used + scl_str = scl_str.replace('~', 'NOT ') + # Add spaces around operators if needed after substitution + scl_str = re.sub(r'AND', ' AND ', scl_str) + scl_str = re.sub(r'OR', ' OR ', scl_str) + scl_str = re.sub(r'XOR', ' XOR ', scl_str) + scl_str = re.sub(r'NOT', 'NOT ', scl_str) # Space after NOT + + # Clean up potential double spaces, etc. + scl_str = re.sub(r'\s+', ' ', scl_str).strip() + # Handle parentheses potentially added by sstr - maybe remove redundant ones? + # Be careful not to break operator precedence. + + return scl_str + + except Exception as e: + print(f"Error converting SymPy expr '{expr}' to SCL: {e}") + traceback.print_exc() + return f"/* ERROR converting SymPy: {expr} */" + +def get_scl_representation(source_info, network_id, scl_map, access_map): + if not source_info: + return None + if isinstance(source_info, list): + scl_parts = [] + all_resolved = True + for sub_source in source_info: + sub_scl = get_scl_representation( + sub_source, network_id, scl_map, access_map + ) + if sub_scl is None: + all_resolved = False + break + if ( + sub_scl in ["TRUE", "FALSE"] + or (sub_scl.startswith('"') and sub_scl.endswith('"')) + or sub_scl.isdigit() + or (sub_scl.startswith("(") and sub_scl.endswith(")")) + ): + scl_parts.append(sub_scl) + else: + scl_parts.append(f"({sub_scl})") + return ( + " OR ".join(scl_parts) + if len(scl_parts) > 1 + else (scl_parts[0] if scl_parts else "FALSE") if all_resolved else None + ) + source_type = source_info.get("type") + if source_type == "powerrail": + return "TRUE" + elif source_type == "variable": + name = source_info.get("name") + # Asegurar que los nombres de variables se formatean correctamente aquí también + return ( + format_variable_name(name) + if name + else f"_ERR_VAR_NO_NAME_{source_info.get('uid')}_" + ) + elif source_type == "constant": + dtype = str(source_info.get("datatype", "")).upper() + value = source_info.get("value") + try: + if dtype == "BOOL": + return str(value).upper() + elif dtype in [ + "INT", + "DINT", + "SINT", + "USINT", + "UINT", + "UDINT", + "LINT", + "ULINT", + "WORD", + "DWORD", + "LWORD", + "BYTE", + ]: + return str(value) + elif dtype in ["REAL", "LREAL"]: + s_val = str(value) + return s_val if "." in s_val or "e" in s_val.lower() else s_val + ".0" + elif dtype == "STRING": + # Escapar comillas simples dentro del string si es necesario + str_val = str(value).replace("'", "''") + return f"'{str_val}'" + elif dtype == "TYPEDCONSTANT": + # Podría necesitar formateo específico basado en el tipo real + return str(value) + else: + # Otros tipos (TIME, DATE, etc.) - devolver como string por ahora + str_val = str(value).replace("'", "''") + return f"'{str_val}'" + except Exception as e: + print(f"Advertencia: Error formateando constante {source_info}: {e}") + return f"_ERR_CONST_FORMAT_{source_info.get('uid')}_" + elif source_type == "connection": + map_key = ( + network_id, + source_info.get("source_instruction_uid"), + source_info.get("source_pin"), + ) + return scl_map.get(map_key) + elif source_type == "unknown_source": + print( + f"Advertencia: Refiriendo a fuente desconocida UID: {source_info.get('uid')}" + ) + return f"_ERR_UNKNOWN_SRC_{source_info.get('uid')}_" + else: + print(f"Advertencia: Tipo de fuente desconocido: {source_info}") + return f"_ERR_INVALID_SRC_TYPE_" + +def format_variable_name(name): + """Limpia el nombre de la variable para SCL.""" + if not name: + return "_INVALID_NAME_" + + # Si ya está entre comillas dobles, asumimos que es un nombre complejo (ej. "DB"."Variable") + # y lo devolvemos tal cual para SCL. + if name.startswith('"') and name.endswith('"'): + # Podríamos añadir validación extra aquí si fuera necesario + return name + + # Si no tiene comillas, es un nombre simple (ej. Tag_1, #tempVar) + # Reemplazar caracteres no válidos (excepto '_') por '_' + # Permitir '#' al inicio para variables temporales + prefix = "" + if name.startswith("#"): + prefix = "#" + name = name[1:] + + # Permitir letras, números y guiones bajos. Reemplazar el resto. + # Asegurarse de que no empiece con número (después del # si existe) + if name and name[0].isdigit(): + name = "_" + name + # Reemplazar caracteres no válidos + name = re.sub(r"[^a-zA-Z0-9_]", "_", name) + + return prefix + name + +def generate_temp_var_name(network_id, instr_uid, pin_name): + net_id_clean = str(network_id).replace("-", "_") + instr_uid_clean = str(instr_uid).replace("-", "_") + pin_name_clean = str(pin_name).replace("-", "_").lower() + # Usar # para variables temporales SCL estándar + return f"#_temp_{net_id_clean}_{instr_uid_clean}_{pin_name_clean}" + +def get_target_scl_name(instruction, pin_name, network_id, default_to_temp=True): + """Gets the SCL formatted name for a target variable. + Handles instruction outputs AND specific inputs like Coil operand. + """ + instr_uid = instruction["instruction_uid"] + # Ahora SCL_SUFFIX está definido en este módulo + instr_type_upper = instruction.get("type", "").upper().replace(SCL_SUFFIX.upper(), "").replace("_ERROR", "") # Check original type + target_info = None + + # Special handling for inputs that represent the target variable + if instr_type_upper in ["COIL", "SCOIL", "RCOIL"] and pin_name == "operand": + target_info = instruction.get("inputs", {}).get("operand") + # Add other instructions where input pin == target if necessary + # elif instr_type_upper == "XYZ" and pin_name == "some_input_target_pin": + # target_info = instruction.get("inputs", {}).get(pin_name) + else: + # Default: Assume pin_name refers to an output pin + output_pin_data = instruction.get("outputs", {}).get(pin_name) + # Check if it's a list and has one connection (standard case) + if (output_pin_data and isinstance(output_pin_data, list) and len(output_pin_data) == 1): + target_info = output_pin_data[0] + # Add handling for direct output assignment if your JSON structure supports it + + target_scl = None + if target_info: + if target_info.get("type") == "variable": + plc_name = target_info.get("name") + if plc_name: + target_scl = format_variable_name(plc_name) # Use existing util + else: + print(f"Error: Target variable for {instr_uid}.{pin_name} has no name (UID: {target_info.get('uid')}).") + elif target_info.get("type") == "constant": + print(f"Advertencia: Attempt to write to constant target {instr_uid}.{pin_name} (UID: {target_info.get('uid')}).") + # else: # Handle other target types if needed + # print(f"Advertencia: Target {instr_uid}.{pin_name} is not a variable: {target_info.get('type')}.") + # else: # No target info found for the specified pin + # print(f"DEBUG: No target info found for {instr_uid}.{pin_name}") + pass + + + # Handle default_to_temp logic + if target_scl: + return target_scl + elif default_to_temp: + # Generate temp only if no explicit target was found AND default is allowed + print(f"INFO: Generating temp var for {instr_uid}.{pin_name}") # Be informative + return generate_temp_var_name(network_id, instr_uid, pin_name) + else: + # No target found and default temps not allowed + return None diff --git a/ToUpload/processors/symbol_manager.py b/ToUpload/processors/symbol_manager.py new file mode 100644 index 0000000..ed271d2 --- /dev/null +++ b/ToUpload/processors/symbol_manager.py @@ -0,0 +1,58 @@ +# processors/symbol_manager.py +import sympy +import re + +class SymbolManager: + def __init__(self): + # plc_name -> py_id (e.g., '"DB".Var' -> 'v0_') + self.plc_to_py_id = {} + # py_id -> Symbol object (e.g., 'v0_' -> sympy.Symbol('v0_')) + self.py_id_to_symbol = {} + # py_id -> plc_name (e.g., 'v0_' -> '"DB".Var') - Inverse mapping + self.py_id_to_plc = {} + self.counter = 0 + # Pre-define common keywords/constants to avoid mapping them + self.reserved_names = {"TRUE", "FALSE"} # Add others if needed + + def _generate_py_id(self): + py_id = f"v{self.counter}_" + self.counter += 1 + # Extremely unlikely collision, but check anyway + while py_id in self.py_id_to_symbol: + py_id = f"v{self.counter}_" + self.counter += 1 + return py_id + + def get_symbol(self, plc_var_name): + """Gets/Creates a SymPy Symbol for a PLC variable name.""" + if plc_var_name is None: + print("Warning: Attempted to get symbol for None PLC name.") + return None # Or handle error appropriately + if plc_var_name.upper() in self.reserved_names: + print(f"Warning: Attempted to create symbol for reserved name: {plc_var_name}") + return None # Or handle differently (e.g., return sympy.true/false?) + + if plc_var_name not in self.plc_to_py_id: + py_id = self._generate_py_id() + self.plc_to_py_id[plc_var_name] = py_id + self.py_id_to_plc[py_id] = plc_var_name + self.py_id_to_symbol[py_id] = sympy.symbols(py_id) + # print(f"DEBUG SymbolManager: Created {py_id} -> {plc_var_name}") # Debug + else: + py_id = self.plc_to_py_id[plc_var_name] + + return self.py_id_to_symbol.get(py_id) + + def get_plc_name(self, py_id): + """Gets the original PLC name from a py_id.""" + return self.py_id_to_plc.get(py_id) + + def get_inverse_map(self): + """Returns the map needed for postprocessing (py_id -> plc_name).""" + return self.py_id_to_plc.copy() + +# Helper function to extract PLC variable name from JSON operand info +def extract_plc_variable_name(operand_info): + if operand_info and operand_info.get("type") == "variable": + return operand_info.get("name") + return None # Not a variable or info missing \ No newline at end of file diff --git a/ToUpload/x0_main.py b/ToUpload/x0_main.py new file mode 100644 index 0000000..b82e9d2 --- /dev/null +++ b/ToUpload/x0_main.py @@ -0,0 +1,145 @@ +import argparse +import subprocess +import os +import sys +import locale +import glob # <--- Importar glob para buscar archivos + +# (Función get_console_encoding y variable CONSOLE_ENCODING como en la respuesta anterior) +def get_console_encoding(): + """Obtiene la codificación preferida de la consola, con fallback.""" + try: + return locale.getpreferredencoding(False) + except Exception: + return 'cp1252' + +CONSOLE_ENCODING = get_console_encoding() +# Descomenta la siguiente línea si quieres ver la codificación detectada: +# print(f"Detected console encoding: {CONSOLE_ENCODING}") + +# (Función run_script como en la respuesta anterior, usando CONSOLE_ENCODING) +def run_script(script_name, xml_arg): + """Runs a given script with the specified XML file argument.""" + script_path = os.path.join(os.path.dirname(__file__), script_name) + command = [sys.executable, script_path, xml_arg] + print(f"\n--- Running {script_name} with argument: {xml_arg} ---") + try: + result = subprocess.run(command, + check=True, + capture_output=True, + text=True, + encoding=CONSOLE_ENCODING, + errors='replace') # 'replace' para evitar errores + + # Imprimir stdout y stderr + # Eliminar saltos de línea extra al final si existen + stdout_clean = result.stdout.strip() + stderr_clean = result.stderr.strip() + if stdout_clean: + print(stdout_clean) + if stderr_clean: + print("--- Stderr ---") + print(stderr_clean) + print("--------------") + print(f"--- {script_name} finished successfully ---") + return True + except FileNotFoundError: + print(f"Error: Script '{script_path}' not found.") + return False + except subprocess.CalledProcessError as e: + print(f"Error running {script_name}:") + print(f"Return code: {e.returncode}") + stdout_decoded = e.stdout.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stdout, bytes) else (e.stdout or "").strip() + stderr_decoded = e.stderr.decode(CONSOLE_ENCODING, errors='replace').strip() if isinstance(e.stderr, bytes) else (e.stderr or "").strip() + if stdout_decoded: + print("--- Stdout ---") + print(stdout_decoded) + if stderr_decoded: + print("--- Stderr ---") + print(stderr_decoded) + print("--------------") + return False + except Exception as e: + print(f"An unexpected error occurred while running {script_name}: {e}") + return False + +# --- NUEVA FUNCIÓN PARA SELECCIONAR ARCHIVO --- +def select_xml_file(): + """Busca archivos .xml, los lista y pide al usuario que elija uno.""" + print("No XML file specified. Searching for XML files in current directory...") + # Buscar archivos .xml en el directorio actual (.) + xml_files = sorted(glob.glob('*.xml')) # sorted para orden alfabético + + if not xml_files: + print("Error: No .xml files found in the current directory.") + sys.exit(1) + + print("\nAvailable XML files:") + for i, filename in enumerate(xml_files, start=1): + print(f" {i}: {filename}") + + while True: + try: + choice = input(f"Enter the number of the file to process (1-{len(xml_files)}): ") + choice_num = int(choice) + if 1 <= choice_num <= len(xml_files): + selected_file = xml_files[choice_num - 1] + print(f"Selected: {selected_file}") + return selected_file + else: + print("Invalid choice. Please enter a number from the list.") + except ValueError: + print("Invalid input. Please enter a number.") + except EOFError: # Manejar si la entrada se cierra inesperadamente + print("\nSelection cancelled.") + sys.exit(1) +# --- FIN NUEVA FUNCIÓN --- + + +if __name__ == "__main__": + xml_filename = None + + # Comprobar si se pasó un argumento de línea de comandos + # sys.argv[0] es el nombre del script, sys.argv[1] sería el primer argumento + if len(sys.argv) > 1: + # Si hay argumentos, usar argparse para parsearlo (permite -h, etc.) + parser = argparse.ArgumentParser( + description="Run the Simatic XML processing pipeline." + ) + parser.add_argument( + "xml_file", + # Ya no necesitamos nargs='?' ni default aquí porque sabemos que hay un argumento + help="Path to the XML file to process.", + ) + # Parsear solo los argumentos conocidos, ignorar extras si los hubiera + args, unknown = parser.parse_known_args() + xml_filename = args.xml_file + print(f"XML file specified via argument: {xml_filename}") + else: + # Si no hay argumentos, llamar a la función interactiva + xml_filename = select_xml_file() + + # --- El resto del script continúa igual, usando xml_filename --- + + # Verificar si el archivo XML de entrada (seleccionado o pasado) existe + if not os.path.exists(xml_filename): + print(f"Error: Selected or specified XML file not found: {xml_filename}") + sys.exit(1) + + print(f"\nStarting pipeline for: {xml_filename}") + + # Run scripts sequentially (asegúrate que los nombres son correctos) + script1 = "x1_to_json.py" + script2 = "x2_process.py" + script3 = "x3_generate_scl.py" + + if run_script(script1, xml_filename): + if run_script(script2, xml_filename): + if run_script(script3, xml_filename): + print("\nPipeline completed successfully.") + else: + print("\nPipeline failed at script:", script3) + else: + print("\nPipeline failed at script:", script2) + else: + print("\nPipeline failed at script:", script1) \ No newline at end of file diff --git a/ToUpload/x1_to_json.py b/ToUpload/x1_to_json.py new file mode 100644 index 0000000..8b1a128 --- /dev/null +++ b/ToUpload/x1_to_json.py @@ -0,0 +1,1106 @@ +# -*- coding: utf-8 -*- +import json +import argparse +import os +from lxml import etree +import traceback +from collections import defaultdict + +# --- Namespaces --- +# Se añade el namespace 'st' para Structured Text +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 +} + + +# --- Helper Functions --- +def get_multilingual_text(element, default_lang="en-US", fallback_lang="it-IT"): + # (Sin cambios respecto a la versión anterior) + if element is None: + return "" + try: + xpath_expr = ( + f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{default_lang}']]" + f"/*[local-name()='AttributeList']/*[local-name()='Text']" + ) + text_items = element.xpath(xpath_expr) + if text_items and text_items[0].text is not None: + return text_items[0].text.strip() + xpath_expr = ( + f".//*[local-name()='MultilingualTextItem'][*[local-name()='AttributeList']/*[local-name()='Culture' and text()='{fallback_lang}']]" + f"/*[local-name()='AttributeList']/*[local-name()='Text']" + ) + text_items = element.xpath(xpath_expr) + if text_items and text_items[0].text is not None: + return text_items[0].text.strip() + xpath_expr = f".//*[local-name()='MultilingualTextItem']/*[local-name()='AttributeList']/*[local-name()='Text']" + text_items = element.xpath(xpath_expr) + if text_items and text_items[0].text is not None: + return text_items[0].text.strip() + return "" + except Exception as e: + 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: + return None + try: + components = symbol_element.xpath("./*[local-name()='Component']/@Name") + return ".".join(f'"{c}"' for c in components) if components else None + except Exception as e: + 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") + 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 + 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 + 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" + info["value"] = value_str + dtype_lower = info["datatype"].lower() + 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 + 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 + 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}") + 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, + } + + +def parse_call(call_element): + # (Mantiene la corrección para DB de instancia) + 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 + 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 + 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 + 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 + 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 + 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 + de un nodo . Es una aproximación y puede no ser perfecta. + """ + if st_node is None: + return "// Error: StructuredText node not found.\n" + + scl_parts = [] + # Obtener todos los elementos hijos directos en orden + children = st_node.xpath("./st:*", namespaces=ns) + + for elem in children: + tag = etree.QName(elem.tag).localname + + if tag == "Token": + scl_parts.append(elem.get("Text", "")) + elif tag == "Blank": + scl_parts.append(" " * int(elem.get("Num", 1))) + elif tag == "NewLine": + # Añadir un salto de línea real. strip() al final de la línea actual + # para evitar espacios extra antes del salto. + scl_parts.append("\n") + elif tag == "Access": + # 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 + ) + for sym_child in symbol_children: + sym_tag = etree.QName(sym_child.tag).localname + if sym_tag == "Component": + comp_name = sym_child.get("Name", "_ERR_") + # Comprobar si necesita comillas (podríamos necesitar parsear BooleanAttribute) + # 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" + ) + + # 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. + # if has_quotes or (len(symbol_parts) == 0 and '.' not in comp_name): # Asumir primer componente es DB + # symbol_parts.append(f'"{comp_name}"') + # else: + # symbol_parts.append(comp_name) + + # 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(".") + access_str = "".join(symbol_parts) + + # Manejar llamadas a funciones/FB dentro de Access Scope="Call" + if elem.get("Scope") == "Call": + instruction_elem = elem.xpath("./st:Instruction", namespaces=ns) + if instruction_elem: + instr_name = instruction_elem[0].get("Name", "_UNKNOWN_CALL_") + # 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 + + 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}") + else: + # Ignorar otros tipos de nodos por ahora o añadir manejo específico + pass + + # 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")] + # 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 + + +# --- 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): + """ + Parsea una red, extrae lógica y añade conexiones EN implícitas. + 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", + } + + network_id = network_element.get("ID") + + # --- Extracción Título/Comentario (sin cambios respecto a la última versión) --- + 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_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", + } + 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" + } + 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}" + ) + + # --- 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) + + 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 + + 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_uid is None: + continue # No se pudo determinar la 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 + + # 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) + + # 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) + + # Registrar conexiones que SALEN de un pin 'eno' + if source_pin == "eno" and source_uid in parts_and_calls_map: + if dest_info not in eno_outputs[source_uid]: + eno_outputs[source_uid].append(dest_info) + # else: # Debug opcional si un elemento no es destino válido + # print(f"Advertencia: Elemento en Wire {wire.get('UId')} no es destino válido: {etree.tostring(dest_elem)}") + # --- FIN MODIFICACIÓN Wire --- + + # 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", + ] + for instruction_uid, instruction_info in parts_and_calls_map.items(): + # 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 = [] + # ... (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: + 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 ( + 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 + ] + while something_changed and inference_passes < max_inference_passes: + 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 + ): + 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 + 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 + + # 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 + 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": + 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 + 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} + ) + interesting_eno_logic.append(target_info) + 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, + } + + +# --- 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 + 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 + 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_type_found = "FB" + 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("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" + if attribute_list_node: + attr_list = attribute_list_node[0] + 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()" + ) + 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}." + ) + 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": [], + } + print("Paso 4: Extrayendo la interfaz del bloque...") + if attribute_list_node: + 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 + 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.") + + print("Paso 5: Extrayendo y PROCESANDO lógica de redes (CompileUnits)...") + networks_processed_count = 0 + 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." + ) + + for network_elem in compile_units: + networks_processed_count += 1 + network_id = network_elem.get("ID") + if not network_id: + print(" Advertencia: Se encontró CompileUnit sin ID. Saltando.") + continue + + # --- 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 + + if attribute_list: + 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']" + ) + if network_source_list: + network_source_node = network_source_list[0] + + 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}" + ) + + comment_element = network_elem.xpath( + "./*[local-name()='ObjectList']/*[local-name()='MultilingualText'][@CompositionName='Comment']" + ) + 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 + ) + + 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" ... SCL reconstruido (parcial):\n{reconstructed_scl[:200]}...") # Preview opcional + else: + 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, + } + ], + } + + 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}" + ) + + else: + # Manejar otros lenguajes o casos inesperados + 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", + } + ], + } + + # Añadir la red procesada (si es válida) al resultado + if parsed_network_data: + result["networks"].append(parsed_network_data) + + # --- Fin del bucle for network_elem --- + + if networks_processed_count == 0: + 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.") + 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 ---") + + +# --- Punto de Entrada Principal --- +if __name__ == "__main__": + # Imports necesarios solo para la ejecución como script principal + import argparse + import os + import sys + + 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)", + ) + args = parser.parse_args() + + xml_input_file = args.xml_filepath + + # 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 + + # 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 + 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) diff --git a/ToUpload/x2_process.py b/ToUpload/x2_process.py new file mode 100644 index 0000000..d7a10a1 --- /dev/null +++ b/ToUpload/x2_process.py @@ -0,0 +1,447 @@ +# -*- coding: utf-8 -*- +import json +import argparse +import os +import copy +import traceback +import re +import importlib +import sys +import sympy # Import sympy + +# Import necessary components from processors directory +from processors.processor_utils import ( + format_variable_name, # Keep if used outside processors + sympy_expr_to_scl, # Needed for IF grouping and maybe others + # get_target_scl_name might be used here? Unlikely. +) +from processors.symbol_manager import SymbolManager # Import the manager + +# --- Constantes y Configuración --- +# SCL_SUFFIX = "_scl" # Old suffix +SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method +GROUPED_COMMENT = "// Logic included in grouped IF" +SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful + + +# Global data variable +data = {} + +def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data): + """ + Busca condiciones (ya procesadas -> tienen expr SymPy en sympy_map) + y, si habilitan un grupo (>1) de bloques funcionales (con SCL ya generado), + construye el bloque IF agrupado CON LA CONDICIÓN SIMPLIFICADA. + Modifica el campo 'scl' de la instrucción generadora de condición. + """ + instr_uid = instruction["instruction_uid"] + instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "") + made_change = False + + # Check if this instruction *could* generate a condition suitable for grouping + # It must have been processed by the new SymPy method + if ( + not instruction.get("type", "").endswith(SCL_SUFFIX) # Check if processed by new method + or "_error" in instruction.get("type", "") + or instruction.get("grouped", False) + or instr_type_original not in [ # Original types that produce boolean results + "Contact", "O", "Eq", "Ne", "Gt", "Lt", "Ge", "Le", "PBox", "NBox", "And", "Xor", "Not" # Add others like comparison + ] + ): + return False + + # Avoid reagruping if SCL already contains a complex IF (less likely now) + current_scl = instruction.get("scl", "") + if current_scl.strip().startswith("IF") and "END_IF;" in current_scl and GROUPED_COMMENT not in current_scl: + return False + + # *** Get the SymPy expression for the condition *** + map_key_out = (network_id, instr_uid, "out") + sympy_condition_expr = sympy_map.get(map_key_out) + + # No SymPy expression found or trivial conditions + if sympy_condition_expr is None or sympy_condition_expr in [sympy.true, sympy.false]: + return False + + # --- Find consumer instructions (logic similar to before) --- + grouped_instructions_cores = [] + consumer_instr_list = [] + network_logic = next((net["logic"] for net in data["networks"] if net["id"] == network_id), []) + if not network_logic: return False + + groupable_types = [ # Types whose *final SCL* we want to group + "Move", "Add", "Sub", "Mul", "Div", "Mod", "Convert", + "Call_FC", "Call_FB", # Assuming these generate final SCL in their processors now + # SCoil/RCoil might also be groupable if their SCL is final assignment + "SCoil", "RCoil" + ] + + for consumer_instr in network_logic: + consumer_uid = consumer_instr["instruction_uid"] + if consumer_instr.get("grouped", False) or consumer_uid == instr_uid: + continue + + consumer_en = consumer_instr.get("inputs", {}).get("en") + consumer_type = consumer_instr.get("type", "") # Current type suffix matters + consumer_type_original = consumer_type.replace(SCL_SUFFIX, "").replace("_error", "") + + is_enabled_by_us = False + if ( isinstance(consumer_en, dict) and consumer_en.get("type") == "connection" and + consumer_en.get("source_instruction_uid") == instr_uid and + consumer_en.get("source_pin") == "out"): + is_enabled_by_us = True + + # Check if consumer is groupable AND has its final SCL generated + # The suffix check needs adjustment based on how terminating processors set it. + # Assuming processors like Move, Add, Call, SCoil, RCoil NOW generate final SCL and add a suffix. + if ( is_enabled_by_us and consumer_type.endswith(SCL_SUFFIX) and # Or a specific "final_scl" suffix + consumer_type_original in groupable_types ): + + consumer_scl = consumer_instr.get("scl", "") + # Extract core SCL (logic is similar, maybe simpler if SCL is cleaner now) + core_scl = None + if consumer_scl: + # If consumer SCL itself is an IF generated by EN, take the body + if consumer_scl.strip().startswith("IF"): + match = re.search(r"THEN\s*(.*?)\s*END_IF;", consumer_scl, re.DOTALL | re.IGNORECASE) + core_scl = match.group(1).strip() if match else None + elif not consumer_scl.strip().startswith("//"): # Otherwise, take the whole line if not comment + core_scl = consumer_scl.strip() + + if core_scl: + grouped_instructions_cores.append(core_scl) + consumer_instr_list.append(consumer_instr) + + # --- If groupable consumers found --- + if len(grouped_instructions_cores) > 1: + print(f"INFO: Agrupando {len(grouped_instructions_cores)} instr. bajo condición de {instr_type_original} UID {instr_uid}") + + # *** Simplify the SymPy condition *** + try: + #simplified_expr = sympy.simplify_logic(sympy_condition_expr, force=True) + simplified_expr = sympy.logic.boolalg.to_dnf(sympy_condition_expr, simplify=True) + except Exception as e: + print(f"Error simplifying condition for grouping UID {instr_uid}: {e}") + simplified_expr = sympy_condition_expr # Fallback + + # *** Convert simplified condition to SCL string *** + condition_scl_simplified = sympy_expr_to_scl(simplified_expr, symbol_manager) + + # *** Build the grouped IF SCL *** + scl_grouped_lines = [f"IF {condition_scl_simplified} THEN"] + for core_line in grouped_instructions_cores: + indented_core = "\n".join([f" {line.strip()}" for line in core_line.splitlines()]) + scl_grouped_lines.append(indented_core) + scl_grouped_lines.append("END_IF;") + final_grouped_scl = "\n".join(scl_grouped_lines) + + # Update the generator instruction's SCL + instruction["scl"] = final_grouped_scl + # Mark consumers as grouped + for consumer_instr in consumer_instr_list: + consumer_instr["scl"] = f"{GROUPED_COMMENT} (by UID {instr_uid})" + consumer_instr["grouped"] = True + made_change = True + + return made_change + +def load_processors(processors_dir="processors"): + """ + Escanea el directorio, importa módulos, construye el mapa y una lista + ordenada por prioridad. + """ + processor_map = {} + processor_list_unsorted = [] # Lista para guardar (priority, type_name, func) + default_priority = 10 # Prioridad si no se define en get_processor_info + + if not os.path.isdir(processors_dir): + print(f"Error: Directorio de procesadores no encontrado: '{processors_dir}'") + return processor_map, [] # Devuelve mapa vacío y lista vacía + + print(f"Cargando procesadores desde: '{processors_dir}'") + processors_package = os.path.basename(processors_dir) + + for filename in os.listdir(processors_dir): + if filename.startswith("process_") and filename.endswith(".py"): + module_name_rel = filename[:-3] + full_module_name = f"{processors_package}.{module_name_rel}" + try: + module = importlib.import_module(full_module_name) + + if hasattr(module, 'get_processor_info') and callable(module.get_processor_info): + processor_info = module.get_processor_info() + info_list = [] + if isinstance(processor_info, dict): + info_list = [processor_info] + elif isinstance(processor_info, list): + info_list = processor_info + else: + print(f" Advertencia: get_processor_info en {full_module_name} devolvió tipo inesperado. Se ignora.") + continue + + for info in info_list: + if isinstance(info, dict) and 'type_name' in info and 'processor_func' in info: + type_name = info['type_name'].lower() + processor_func = info['processor_func'] + # Obtener prioridad, usar default si no existe + priority = info.get('priority', default_priority) + + if callable(processor_func): + if type_name in processor_map: + print(f" Advertencia: '{type_name}' en {full_module_name} sobrescribe definición anterior.") + processor_map[type_name] = processor_func + # Añadir a la lista para ordenar + processor_list_unsorted.append({'priority': priority, 'type_name': type_name, 'func': processor_func}) + print(f" - Cargado '{type_name}' (Prio: {priority}) desde {module_name_rel}.py") + else: + print(f" Advertencia: 'processor_func' para '{type_name}' en {full_module_name} no es callable.") + else: + print(f" Advertencia: Entrada inválida en {full_module_name}: {info}") + else: + print(f" Advertencia: Módulo {module_name_rel}.py no tiene 'get_processor_info'.") + + except ImportError as e: + print(f"Error importando {full_module_name}: {e}") + except Exception as e: + print(f"Error procesando {full_module_name}: {e}") + traceback.print_exc() + + # Ordenar la lista por prioridad (menor primero) + processor_list_sorted = sorted(processor_list_unsorted, key=lambda x: x['priority']) + + print(f"\nTotal de tipos de procesadores cargados: {len(processor_map)}") + print(f"Orden de procesamiento por prioridad: {[item['type_name'] for item in processor_list_sorted]}") + + # Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada + return processor_map, processor_list_sorted + +# --- Bucle Principal de Procesamiento (Modificado) --- +def process_json_to_scl(json_filepath): + """ + Lee el JSON simplificado, aplica los procesadores dinámicamente cargados + siguiendo un orden de prioridad, y guarda el JSON procesado. + """ + global data # Necesario si process_group_ifs (definido fuera) accede a data globalmente. + # Si process_group_ifs está definida DENTRO de process_json_to_scl, + # no necesitarías global, ya que accedería a la 'data' local. + # Lo más limpio es definir process_group_ifs fuera y pasarle 'data' + # como argumento (como ya se hace). Así que 'global data' aquí es probablemente innecesario. + # Eliminémoslo por ahora y aseguremos que data se pasa a process_group_ifs. + + if not os.path.exists(json_filepath): print(f"Error: JSON no encontrado: {json_filepath}"); return + print(f"Cargando JSON desde: {json_filepath}") + try: + with open(json_filepath, "r", encoding="utf-8") as f: data = json.load(f) + except Exception as e: print(f"Error al cargar JSON: {e}"); traceback.print_exc(); return + + # --- Carga dinámica de procesadores (sin cambios) --- + script_dir = os.path.dirname(__file__); processors_dir_path = os.path.join(script_dir, 'processors') + processor_map, sorted_processors = load_processors(processors_dir_path) + if not processor_map: print("Error crítico: No se cargaron procesadores. Abortando."); return + + # --- Crear mapas de acceso por red (sin cambios) --- + network_access_maps = {} + # ... (logic to populate network_access_maps remains the same) ... + for network in data.get("networks", []): + net_id = network["id"] + current_access_map = {} + # Extraer todos los 'Access' usados en esta red + for instr in network.get("logic", []): + # Revisar Inputs + for _, source in instr.get("inputs", {}).items(): + sources_to_check = (source if isinstance(source, list) else ([source] if isinstance(source, dict) else [])) + for src in sources_to_check: + if (isinstance(src, dict) and src.get("uid") and src.get("type") in ["variable", "constant"]): + current_access_map[src["uid"]] = src + # Revisar Outputs + for _, dest_list in instr.get("outputs", {}).items(): + if isinstance(dest_list, list): + for dest in dest_list: + if (isinstance(dest, dict) and dest.get("uid") and dest.get("type") in ["variable", "constant"]): + current_access_map[dest["uid"]] = dest + network_access_maps[net_id] = current_access_map + + # --- Inicializar mapa SymPy y SymbolManager por red --- + # Cada red puede tener su propio contexto de símbolos si es necesario, + # pero un SymbolManager global suele ser suficiente si no hay colisiones graves. + # Usaremos uno global por simplicidad ahora. + symbol_manager = SymbolManager() + sympy_map = {} # Mapa para resultados SymPy intermedios (expresiones) + + max_passes = 30 + passes = 0 + processing_complete = False + + print("\n--- Iniciando Bucle de Procesamiento Iterativo (con SymPy y prioridad) ---") + while passes < max_passes and not processing_complete: + passes += 1 + made_change_in_base_pass = False # Renombrar: made_change_in_sympy_pass + made_change_in_group_pass = False + # made_change_in_simplify_pass = False # Ya no existe Fase 3 + print(f"\n--- Pase {passes} ---") + num_processed_this_pass = 0 # Renombrar: num_sympy_processed_this_pass + num_grouped_this_pass = 0 + # num_simplified_this_pass = 0 # Ya no existe Fase 3 + + # --- FASE 1: Procesadores Base (Ahora usan SymPy) --- + print(f" Fase 1 (SymPy Base - Orden por Prioridad):") + num_sympy_processed_this_pass = 0 # Contador específico + for processor_info in sorted_processors: + current_type_name = processor_info['type_name'] + func_to_call = processor_info['func'] + + for network in data.get("networks", []): + network_id = network["id"] + access_map = network_access_maps.get(network_id, {}) + network_logic = network.get("logic", []) + + for instruction in network_logic: + instr_uid = instruction.get("instruction_uid") + instr_type_original = instruction.get("type", "Unknown") + + # Saltar si ya está procesado con el NUEVO método, es error, o agrupado + if (instr_type_original.endswith(SCL_SUFFIX) # Check new suffix + or "_error" in instr_type_original + or instruction.get("grouped", False)): + continue + + # Determinar tipo efectivo (como antes) + lookup_key = instr_type_original.lower() + effective_type_name = lookup_key + if instr_type_original == "Call": # ... (manejo Call FC/FB) ... + block_type = instruction.get("block_type", "").upper() + if block_type == "FC": effective_type_name = "call_fc" + elif block_type == "FB": effective_type_name = "call_fb" + + if effective_type_name == current_type_name: + try: + # *** Llamar al procesador refactorizado *** + # Pasa sympy_map y symbol_manager, no scl_map + changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data) # Pasamos SymbolManager + if changed: + made_change_in_base_pass = True + num_sympy_processed_this_pass += 1 + except Exception as e: + print(f"ERROR(SymPy Base) al procesar {instr_type_original} UID {instr_uid}: {e}") + traceback.print_exc() + instruction["scl"] = f"// ERROR en SymPy procesador base: {e}" + instruction["type"] = instr_type_original + "_error" + made_change_in_base_pass = True + print(f" -> {num_sympy_processed_this_pass} instrucciones procesadas con SymPy.") + + + # --- FASE 2: Agrupación IF (Ahora usa SymPy para simplificar) --- + # Ejecutar si hubo cambios en base o es el primer pase + if made_change_in_base_pass or passes == 1: + print(f" Fase 2 (Agrupación IF con Simplificación):") + num_grouped_this_pass = 0 # Reiniciar contador + for network in data.get("networks", []): + network_id = network["id"] + # access_map = network_access_maps.get(network_id, {}) # No usado directamente por group_ifs + network_logic = network.get("logic", []) + # Iterar sobre instrucciones que *pueden* generar condiciones booleanas + for instruction in network_logic: + # process_group_ifs ahora verifica internamente si la instr. fue procesada + try: + # *** Llamar a process_group_ifs adaptado *** + group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data) + if group_changed: + made_change_in_group_pass = True + num_grouped_this_pass += 1 + except Exception as e: + print(f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}") + traceback.print_exc() + print(f" -> {num_grouped_this_pass} agrupaciones realizadas.") + + # --- FASE 3 Eliminada --- + + # --- Comprobar si se completó el procesamiento --- + # Solo considera Fase 1 (SymPy Base) y Fase 2 (Grouping) + if not made_change_in_base_pass and not made_change_in_group_pass: + print(f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---") + processing_complete = True + else: + # Mensaje de fin de pase actualizado + print(f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando...") + + # --- Comprobar límite de pases --- + if passes == max_passes and not processing_complete: + print(f"\n--- ADVERTENCIA: Límite de {max_passes} pases alcanzado...") + + # --- FIN BUCLE ITERATIVO --- + + # --- Verificación Final --- + # La lógica aquí podría necesitar ajustes si el sufijo cambió o si el SCL final + # solo se genera en instrucciones terminales. + print("\n--- Verificación Final de Instrucciones No Procesadas ---") + unprocessed_count = 0 + unprocessed_details = [] + ignored_types = ['raw_scl_chunk', 'unsupported_lang'] + + for network in data.get("networks", []): + network_id = network.get("id", "Unknown ID") + network_title = network.get("title", f"Network {network_id}") + for instruction in network.get("logic", []): + instr_uid = instruction.get("instruction_uid", "Unknown UID") + instr_type = instruction.get("type", "Unknown Type") + is_grouped = instruction.get("grouped", False) + has_final_scl = bool(instruction.get("scl", "").strip()) and not instruction.get("scl", "").strip().startswith("//") + + # Condición revisada: No tiene el sufijo nuevo Y no es error Y no está agrupada Y no es tipo ignorado + # Y ADEMÁS, ¿debería tener SCL final si es una instr. terminal? + # Simplificación: si no tiene sufijo, no es error, no agrupada, no ignorada -> problema + if (not instr_type.endswith(SCL_SUFFIX) and + "_error" not in instr_type and + not is_grouped and + instr_type.lower() not in ignored_types): + unprocessed_count += 1 + unprocessed_details.append( + f" - Red '{network_title}' (ID: {network_id}), " + f"Instrucción UID: {instr_uid}, Tipo Original: '{instr_type}'" + ) + # Opcional: añadir si tiene SCL o no + # unprocessed_details[-1] += f" (Tiene SCL final: {has_final_scl})" + + + if unprocessed_count > 0: + print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones que no fueron procesadas:") + for detail in unprocessed_details: print(detail) + else: + print("INFO: Todas las instrucciones relevantes parecen haber sido procesadas o agrupadas.") + + # --- Guardar JSON Final (sin cambios) --- + output_filename = json_filepath.replace("_simplified.json", "_simplified_processed.json") + print(f"\nGuardando JSON procesado en: {output_filename}") + try: + with open(output_filename, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + print("Guardado completado.") + except Exception as e: + print(f"Error Crítico al guardar JSON procesado: {e}") + traceback.print_exc() + + +# --- Ejecución (igual que antes) --- +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic.") + parser.add_argument( + "source_xml_filepath", + nargs="?", + default="TestLAD.xml", + help="Path to the original source XML file (used to derive JSON input name, default: TestLAD.xml)" + ) + args = parser.parse_args() + + xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] + # Usar directorio del script actual si el XML no tiene ruta, o la ruta del XML si la tiene + xml_dir = os.path.dirname(args.source_xml_filepath) + input_dir = xml_dir if xml_dir else os.path.dirname(__file__) # Directorio de entrada/salida + + input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json") + + if not os.path.exists(input_json_file): + print(f"Error Fatal: El archivo de entrada JSON simplificado no existe: '{input_json_file}'") + print(f"Asegúrate de haber ejecutado 'x1_to_json.py' primero sobre '{args.source_xml_filepath}'.") + sys.exit(1) + else: + process_json_to_scl(input_json_file) \ No newline at end of file diff --git a/ToUpload/x3_generate_scl.py b/ToUpload/x3_generate_scl.py new file mode 100644 index 0000000..66207c4 --- /dev/null +++ b/ToUpload/x3_generate_scl.py @@ -0,0 +1,298 @@ +# x3_generate_scl.py +# -*- coding: utf-8 -*- +import json +import os +import re +import argparse +import sys +import traceback # Importar traceback para errores + +# --- Importar Utilidades y Constantes (Asumiendo ubicación) --- +try: + # Intenta importar desde el paquete de procesadores si está estructurado así + from processors.processor_utils import format_variable_name + # Definir SCL_SUFFIX aquí o importarlo si está centralizado + SCL_SUFFIX = "_sympy_processed" # Asegúrate que coincida con x2_process.py + GROUPED_COMMENT = "// Logic included in grouped IF" # Opcional, si se usa para filtrar +except ImportError: + print("Advertencia: No se pudo importar 'format_variable_name' desde processors.processor_utils.") + print("Usando una implementación local básica (¡PUEDE FALLAR CON NOMBRES COMPLEJOS!).") + # Implementación local BÁSICA como fallback (MENOS RECOMENDADA) + def format_variable_name(name): + if not name: return "_INVALID_NAME_" + if name.startswith('"') and name.endswith('"'): return name # Mantener comillas + prefix = "#" if name.startswith("#") else "" + if prefix: name = name[1:] + if name and name[0].isdigit(): name = "_" + name + name = re.sub(r"[^a-zA-Z0-9_]", "_", name) + return prefix + name + SCL_SUFFIX = "_sympy_processed" + GROUPED_COMMENT = "// Logic included in grouped IF" + + +# --- Función Principal de Generación SCL --- + +def generate_scl(processed_json_filepath, output_scl_filepath): + """Genera un archivo SCL a partir del JSON procesado por x2_process (versión SymPy).""" + + if not os.path.exists(processed_json_filepath): + print(f"Error: Archivo JSON procesado no encontrado en '{processed_json_filepath}'") + return + + print(f"Cargando JSON procesado desde: {processed_json_filepath}") + try: + with open(processed_json_filepath, 'r', encoding='utf-8') as f: + data = json.load(f) + except Exception as e: + print(f"Error al cargar o parsear JSON: {e}") + traceback.print_exc() + return + + # --- Extracción de Información del Bloque --- + block_name = data.get('block_name', 'UnknownBlock') + block_number = data.get('block_number') + block_lang_original = data.get('language', 'LAD') # Lenguaje original + # Determinar tipo de bloque SCL (Asumir FB si no se especifica) + # Idealmente, x1_to_json.py guardaría esto en data['block_type_scl'] = 'FC' o 'FB' + block_type_scl = data.get('block_type_scl', 'FUNCTION_BLOCK') + block_comment = data.get('block_comment', '') + + # Usar format_variable_name para el nombre del bloque en SCL + scl_block_name = format_variable_name(block_name) + print(f"Generando SCL para {block_type_scl}: {scl_block_name} (Original: {block_name})") + + # --- Identificación de Variables Temporales y Estáticas --- + # La detección basada en regex sobre el SCL final debería seguir funcionando + temp_vars = set() + stat_vars = set() + # Regex mejorado para capturar variables temporales que empiezan con # o _temp_ + # y estáticas (si usas un prefijo como 'stat_' o para bits de memoria de flanco) + temp_pattern = re.compile(r'"?#(_temp_[a-zA-Z0-9_]+)"?|"?(_temp_[a-zA-Z0-9_]+)"?') # Captura con o sin # + stat_pattern = re.compile(r'"?(stat_[a-zA-Z0-9_]+)"?') # Para memorias de flanco si usan prefijo 'stat_' + + edge_memory_bits = set() # Para detectar bits de memoria de flanco por nombre + + for network in data.get('networks', []): + for instruction in network.get('logic', []): + scl_code = instruction.get('scl', '') + # Buscar también en _edge_mem_update_scl si existe + edge_update_code = instruction.get('_edge_mem_update_scl','') + code_to_scan = (scl_code if scl_code else '') + '\n' + (edge_update_code if edge_update_code else '') + + if code_to_scan: + # Buscar #_temp_... o _temp_... + found_temps = temp_pattern.findall(code_to_scan) + for temp_tuple in found_temps: + # findall devuelve tuplas por los grupos de captura, tomar el no vacío + temp_name = next((t for t in temp_tuple if t), None) + if temp_name: + temp_vars.add("#"+temp_name if not temp_name.startswith("#") else temp_name) # Asegurar que empiece con # + + # Buscar estáticas (ej: stat_...) + found_stats = stat_pattern.findall(code_to_scan) + stat_vars.update(found_stats) + + # Identificar explícitamente bits de memoria usados por PBox/NBox + # Asumiendo que el nombre se guarda en el JSON (requiere ajuste en x1/x2) + # if instruction.get("type","").startswith(("PBox", "NBox")): + # mem_bit_info = instruction.get("inputs", {}).get("bit") + # if mem_bit_info and mem_bit_info.get("type") == "variable": + # edge_memory_bits.add(format_variable_name(mem_bit_info.get("name"))) + + + print(f"Variables temporales (#_temp_...) detectadas: {len(temp_vars)}") + # Si se detectan memorias de flanco, añadirlas a stat_vars si no tienen prefijo 'stat_' + # stat_vars.update(edge_memory_bits - stat_vars) # Añadir solo las nuevas + print(f"Variables estáticas (stat_...) detectadas: {len(stat_vars)}") + + # --- Construcción del String SCL --- + scl_output = [] + + # Cabecera del Bloque + scl_output.append(f"// Block Name (Original): {block_name}") + if block_number: scl_output.append(f"// Block Number: {block_number}") + scl_output.append(f"// Original Language: {block_lang_original}") + if block_comment: scl_output.append(f"// Block Comment: {block_comment}") + scl_output.append("") + scl_output.append(f"{block_type_scl} \"{scl_block_name}\"") + scl_output.append("{ S7_Optimized_Access := 'TRUE' }") + scl_output.append("VERSION : 0.1") + scl_output.append("") + + # Declaraciones de Interfaz (Implementación básica) + interface_sections = ["Input", "Output", "InOut", "Static", "Temp", "Constant", "Return"] + interface_data = data.get('interface', {}) + + for section_name in interface_sections: + scl_section_name = section_name + # Ajustar nombres de sección para SCL (Static -> STAT, Temp -> TEMP) + if section_name == "Static": scl_section_name = "STAT" + if section_name == "Temp": scl_section_name = "TEMP" # Usar VAR_TEMP para variables #temp + + vars_in_section = interface_data.get(section_name, []) + # No declarar VAR_TEMP aquí, se hará después con las detectadas/originales + if section_name == "Temp": continue + + # No declarar VAR_STAT aquí si ya lo hacemos abajo con las detectadas + if section_name == "Static" and stat_vars: continue + + + if vars_in_section or (section_name == "Static" and stat_vars): # Incluir STAT si hay detectadas + # Usar VAR para Input/Output/InOut/Constant/Return + var_keyword = "VAR" if section_name != "Static" else "VAR_STAT" + scl_output.append(f"{var_keyword}_{section_name.upper()}") + + for var in vars_in_section: + var_name = var.get('name') + var_dtype = var.get('datatype', 'VARIANT') # Default a VARIANT + if var_name: + # Usar format_variable_name CORRECTO + scl_name = format_variable_name(var_name) + scl_output.append(f" {scl_name} : {var_dtype};") + + # Declarar stat_vars detectadas si esta es la sección STAT + if section_name == "Static" and stat_vars: + for var_name in sorted(list(stat_vars)): + # Asumir Bool para stat_, podría necesitar inferencia + scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT") + + scl_output.append("END_VAR") + scl_output.append("") + + # Declaraciones Estáticas (Si no estaban en la interfaz y se detectaron) + # Esto es redundante si la sección VAR_STAT ya se generó arriba + # if stat_vars and not interface_data.get("Static"): + # scl_output.append("VAR_STAT") + # for var_name in sorted(list(stat_vars)): + # scl_output.append(f" {format_variable_name(var_name)} : Bool; // Auto-detected STAT") + # scl_output.append("END_VAR") + # scl_output.append("") + + + # Declaraciones Temporales (Interfaz Temp + _temp_ detectadas) + scl_output.append("VAR_TEMP") + declared_temps = set() + interface_temps = interface_data.get('Temp', []) + if interface_temps: + for var in interface_temps: + var_name = var.get('name') + var_dtype = var.get('datatype', 'VARIANT') + if var_name: + scl_name = format_variable_name(var_name) + scl_output.append(f" {scl_name} : {var_dtype};") + declared_temps.add(scl_name) # Marcar como declarada + + # Declarar las _temp_ generadas si no estaban ya en la interfaz Temp + if temp_vars: + for var_name in sorted(list(temp_vars)): + scl_name = format_variable_name(var_name) # #_temp_... + if scl_name not in declared_temps: + # Inferencia básica de tipo + inferred_type = "Bool" # Asumir Bool para la mayoría de temps de lógica + # Se podría mejorar si los procesadores añadieran info de tipo + scl_output.append(f" {scl_name} : {inferred_type}; // Auto-generated temporary") + declared_temps.add(scl_name) + scl_output.append("END_VAR") + scl_output.append("") + + # Cuerpo del Bloque + scl_output.append("BEGIN") + scl_output.append("") + + # Iterar por redes y lógica + for i, network in enumerate(data.get('networks', [])): + network_title = network.get('title', f'Network {network.get("id")}') + network_comment = network.get('comment', '') + network_lang = network.get('language', 'LAD') # O el lenguaje original + + scl_output.append(f" // Network {i+1}: {network_title} (Original Language: {network_lang})") + if network_comment: + for line in network_comment.splitlines(): + scl_output.append(f" // {line}") + scl_output.append("") + + network_has_code = False + # Iterar sobre la 'logica' de la red + for instruction in network.get('logic', []): + instruction_type = instruction.get("type", "") + scl_code = instruction.get('scl', "") # Obtener SCL generado por x2 + + # Saltar instrucciones agrupadas + if instruction.get("grouped", False): + continue + + # Escribir SCL si es un tipo procesado y tiene código relevante + # (Ignorar comentarios de depuración de SymPy) + if instruction_type.endswith(SCL_SUFFIX) and scl_code: + is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \ + scl_code.strip().startswith("// PBox SymPy processed") or \ + scl_code.strip().startswith("// NBox SymPy processed") + # O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL + is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines()) + + + # Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse) + if not is_only_comment or scl_code.strip().startswith("IF"): + network_has_code = True + for line in scl_code.splitlines(): + # Añadir indentación estándar + scl_output.append(f" {line}") + + # Incluir también tipos especiales directamente + elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code: + network_has_code = True + for line in scl_code.splitlines(): + scl_output.append(f" {line}") # Indentar + + # Podríamos añadir comentarios para errores si se desea + # elif "_error" in instruction_type: + # network_has_code = True + # scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}") + + + if network_has_code: + scl_output.append("") # Línea en blanco después del código de la red + else: + scl_output.append(f" // Network did not produce printable SCL code.") + scl_output.append("") + + # Fin del bloque + scl_output.append("END_FUNCTION_BLOCK") # O END_FUNCTION si es FC + + # --- Escritura del Archivo SCL --- + print(f"Escribiendo archivo SCL en: {output_scl_filepath}") + try: + with open(output_scl_filepath, 'w', encoding='utf-8') as f: + for line in scl_output: + f.write(line + '\n') + print("Generación de SCL completada.") + except Exception as e: + print(f"Error al escribir el archivo SCL: {e}") + traceback.print_exc() + + +# --- Ejecución --- +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate final SCL file from processed JSON (SymPy version).") + parser.add_argument( + "source_xml_filepath", + nargs="?", + default="TestLAD.xml", + help="Path to the original source XML file (used to derive input/output names, default: TestLAD.xml)" + ) + args = parser.parse_args() + + xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] + # Usar directorio del script si no hay ruta, o la ruta del XML si la tiene + xml_dir = os.path.dirname(args.source_xml_filepath) + base_dir = xml_dir if xml_dir else os.path.dirname(__file__) + + input_json_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.json") + output_scl_file = os.path.join(base_dir, f"{xml_filename_base}_simplified_processed.scl") + + if not os.path.exists(input_json_file): + print(f"Error: Processed JSON file not found: '{input_json_file}'") + print(f"Ensure 'x2_process.py' ran successfully for '{args.source_xml_filepath}'.") + sys.exit(1) + else: + generate_scl(input_json_file, output_scl_file) \ No newline at end of file diff --git a/x1_to_json.py b/x1_to_json.py index 8b1a128..f594e80 100644 --- a/x1_to_json.py +++ b/x1_to_json.py @@ -2,6 +2,7 @@ import json import argparse import os +import re from lxml import etree import traceback from collections import defaultdict @@ -11,7 +12,8 @@ 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", + "stl": "http://www.siemens.com/automation/Openness/SW/NetworkSource/StatementList/v4", } @@ -44,7 +46,6 @@ 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: @@ -56,7 +57,6 @@ 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: @@ -149,7 +149,6 @@ def parse_access(access_element): return info return info - def parse_part(part_element): # (Sin cambios respecto a la versión anterior) if part_element is None: @@ -185,7 +184,6 @@ def parse_part(part_element): "negated_pins": negated_pins, } - def parse_call(call_element): # (Mantiene la corrección para DB de instancia) if call_element is None: @@ -245,23 +243,24 @@ def parse_call(call_element): call_data["instance_scope"] = instance_scope return call_data - -# EN x1_to_json.py, junto a otras funciones auxiliares - +# SCL (Structured Text) Parser def reconstruct_scl_from_tokens(st_node): """ - Intenta reconstruir una cadena SCL a partir de los elementos hijos - de un nodo . Es una aproximación y puede no ser perfecta. + Reconstruye una cadena SCL a partir de los elementos hijos + de un nodo , manejando Tokens, Access (Variables y Constantes), + saltos de línea, espacios y comentarios. """ if st_node is None: return "// Error: StructuredText node not found.\n" scl_parts = [] # Obtener todos los elementos hijos directos en orden + # Usamos '*' para obtener todos los hijos y luego filtramos por tag children = st_node.xpath("./st:*", namespaces=ns) for elem in children: + # Obtener el nombre local de la etiqueta sin el namespace tag = etree.QName(elem.tag).localname if tag == "Token": @@ -269,84 +268,311 @@ def reconstruct_scl_from_tokens(st_node): elif tag == "Blank": scl_parts.append(" " * int(elem.get("Num", 1))) elif tag == "NewLine": - # Añadir un salto de línea real. strip() al final de la línea actual - # para evitar espacios extra antes del salto. scl_parts.append("\n") elif tag == "Access": - # 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 - ) - for sym_child in symbol_children: - sym_tag = etree.QName(sym_child.tag).localname - if sym_tag == "Component": - comp_name = sym_child.get("Name", "_ERR_") - # Comprobar si necesita comillas (podríamos necesitar parsear BooleanAttribute) - # 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" - ) + scope = elem.get("Scope") + access_str = f"_{scope}_?" # Fallback - # 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. - # if has_quotes or (len(symbol_parts) == 0 and '.' not in comp_name): # Asumir primer componente es DB - # symbol_parts.append(f'"{comp_name}"') - # else: - # symbol_parts.append(comp_name) + if scope == "GlobalVariable" or scope == "LocalVariable": + symbol_elem = elem.xpath("./st:Symbol", namespaces=ns) + if symbol_elem: + components = symbol_elem[0].xpath("./st:Component | ./st:Token[@Text='.']", namespaces=ns) + symbol_text_parts = [] + for comp in components: + comp_tag = etree.QName(comp.tag).localname + if comp_tag == "Component": + name = comp.get("Name", "_ERR_COMP_") + # Comprobar si necesita comillas (requiere BooleanAttribute) + # Simplificación: Añadir comillas si el nombre original parece tenerlas + has_quotes_elem = comp.xpath("../st:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns) + has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == "true" + + # Heurística: Usar comillas si HasQuotes=true o si es el primer componente y no es TEMP (#) + if has_quotes or (len(symbol_text_parts) == 0 and not name.startswith('#')): + symbol_text_parts.append(f'"{name}"') + else: + symbol_text_parts.append(name) - # Versión más simple: usar nombre tal cual del XML - symbol_parts.append(comp_name) + # Manejar índices de array (simplificado) + index_access = comp.xpath("./st:Access", namespaces=ns) + if index_access: + indices_text = [reconstruct_scl_from_tokens(idx_node) for idx_node in index_access] # Llamada recursiva + symbol_text_parts.append(f"[{','.join(indices_text)}]") - elif sym_tag == "Token": # Solo nos interesa el punto aquí - symbol_parts.append(".") - access_str = "".join(symbol_parts) + elif comp_tag == "Token": # Es un punto + # Asegurarse de no añadir puntos duplicados si ya están en las partes + if symbol_text_parts and symbol_text_parts[-1] != ".": + symbol_text_parts.append(".") + # Limpiar posibles puntos extra al inicio/final o dobles + access_str = "".join(symbol_text_parts).strip('.') + access_str = re.sub(r'\.+', '.', access_str) # Reemplazar múltiples puntos con uno solo - # Manejar llamadas a funciones/FB dentro de Access Scope="Call" - if elem.get("Scope") == "Call": - instruction_elem = elem.xpath("./st:Instruction", namespaces=ns) - if instruction_elem: - instr_name = instruction_elem[0].get("Name", "_UNKNOWN_CALL_") - # 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 + + elif scope == "LiteralConstant": + constant_elem = elem.xpath("./st:Constant", namespaces=ns) + if constant_elem: + val_elem = constant_elem[0].xpath("./st:ConstantValue/text()", namespaces=ns) + # **CORRECCIÓN CLAVE**: Extraer el valor y usarlo + access_str = val_elem[0] if val_elem else "_ERR_CONSTVAL_" + else: + access_str = "_ERR_NOCONST_" + # Añadir manejo para otros scopes si es necesario (Address, Call, etc.) + # elif scope == "Call": ... + # elif scope == "Address": ... 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}") - else: - # Ignorar otros tipos de nodos por ahora o añadir manejo específico - pass + # Manejo de comentarios (simplificado - asume texto directo) + comment_text = "".join(elem.xpath(".//text()")).strip() + if tag == "Comment": # Comentario tipo (* *) + scl_parts.append(f"(* {comment_text} *)") + else: # Comentario tipo // + scl_parts.append(f"// {comment_text}") + # else: # Ignorar otros tipos de nodos por ahora + # pass - # Unir todas las partes, limpiar espacios extra alrededor de saltos de línea + # Unir todas las partes full_scl = "".join(scl_parts) - # Limpieza básica de formato - lines = [line.rstrip() for line in full_scl.split("\n")] - # Re-ensamblar, asegurando que líneas vacías (solo espacios previos) se mantengan + # Limpieza básica de formato (puede necesitar ajustes) + lines = [line.rstrip() for line in full_scl.split('\n')] 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 +# STL (Statement List) Parser + +def get_access_text(access_element): + """Reconstruye una representación textual simple de un Access en STL.""" + if access_element is None: + return "_ERR_ACCESS_" + scope = access_element.get("Scope") + + # Intenta reconstruir el símbolo + # CORREGIDO: Añadido namespaces=ns + symbol_elem = access_element.xpath("./stl:Symbol", namespaces=ns) + if symbol_elem: + # CORREGIDO: Añadido namespaces=ns + components = symbol_elem[0].xpath("./stl:Component", namespaces=ns) + parts = [] + for comp in components: + name = comp.get("Name", "_ERR_COMP_") + # CORREGIDO: Añadido namespaces=ns + has_quotes_elem = comp.xpath("../stl:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns) + has_quotes = has_quotes_elem and has_quotes_elem[0].lower() == "true" + + # Usar nombre tal cual por ahora + parts.append(name) + + # Añadir índices si existen + # CORREGIDO: Añadido namespaces=ns + index_access = comp.xpath("./stl:Access", namespaces=ns) + if index_access: + indices = [get_access_text(ia) for ia in index_access] + parts.append(f"[{','.join(indices)}]") + + return ".".join(parts) + + # Intenta reconstruir constante + # CORREGIDO: Añadido namespaces=ns + constant_elem = access_element.xpath("./stl:Constant", namespaces=ns) + if constant_elem: + # CORREGIDO: Añadido namespaces=ns + val_elem = constant_elem[0].xpath("./stl:ConstantValue/text()", namespaces=ns) + type_elem = constant_elem[0].xpath("./stl:ConstantType/text()", namespaces=ns) # Obtener tipo para mejor formato + const_type = type_elem[0] if type_elem else "" + const_val = val_elem[0] if val_elem else "_ERR_CONST_" + # Añadir prefijo de tipo si es necesario (ej. T# , L#) - Simplificado + if const_type == "Time": return f"T#{const_val}" + if const_type == "ARef": return f"{const_val}" # No necesita prefijo + # Añadir más tipos si es necesario + return const_val # Valor directo para otros tipos + + # Intenta reconstruir etiqueta + # CORREGIDO: Añadido namespaces=ns + label_elem = access_element.xpath("./stl:Label", namespaces=ns) + if label_elem: + name = label_elem[0].get("Name", "_ERR_LABEL_") + return name + + # Intenta reconstruir acceso indirecto (simplificado) + # CORREGIDO: Añadido namespaces=ns + indirect_elem = access_element.xpath("./stl:Indirect", namespaces=ns) + if indirect_elem: + reg = indirect_elem[0].get("Register", "AR?") + offset_str = indirect_elem[0].get("BitOffset", "0") + area = indirect_elem[0].get("Area", "DB") + width = indirect_elem[0].get("Width", "X") + + # Convertir BitOffset a formato P#Byte.Bit + try: + bit_offset = int(offset_str) + byte_offset = bit_offset // 8 + bit_in_byte = bit_offset % 8 + p_format_offset = f"P#{byte_offset}.{bit_in_byte}" + except ValueError: + p_format_offset = "P#?.?" + + # Formatear ancho + width_map = {"Bit": "X", "Byte": "B", "Word": "W", "Double": "D"} + width_char = width_map.get(width, width[0] if width else "?") # Usa primera letra si no mapeado + + return f"{area}{width_char}[{reg},{p_format_offset}]" + + # Intenta reconstruir dirección absoluta + # CORREGIDO: Añadido namespaces=ns + address_elem = access_element.xpath("./stl:Address", namespaces=ns) + if address_elem: + area = address_elem[0].get("Area", "??") + bit_offset_str = address_elem[0].get("BitOffset", "0") + addr_type_str = address_elem[0].get("Type", "Bool") # Obtener tipo para ancho + try: + bit_offset = int(bit_offset_str) + byte_offset = bit_offset // 8 + bit_in_byte = bit_offset % 8 + # Determinar ancho basado en tipo (simplificación) + addr_width = "X" # Default a Bit + if addr_type_str == "Byte": addr_width = "B" + elif addr_type_str == "Word": addr_width = "W" + elif addr_type_str in ["DWord", "DInt"]: addr_width = "D" + # Añadir más tipos si es necesario (Real, etc.) + + # Mapear Area para STL estándar + area_map = { "Input": "I", "Output": "Q", "Memory": "M", + "PeripheryInput": "PI", "PeripheryOutput": "PQ", + "DB": "DB", "DI": "DI", "Local": "L", # L no siempre válido aquí + "Timer": "T", "Counter": "C" } + stl_area = area_map.get(area, area) + + # Manejar DB/DI que necesitan número de bloque + if stl_area in ["DB", "DI"]: + block_num = address_elem[0].get("BlockNumber") + if block_num: + return f"{stl_area}{block_num}.{stl_area}{addr_width}{byte_offset}.{bit_in_byte}" # Ej: DB1.DBX0.1 + else: # Acceso con registro DB/DI + return f"{stl_area}{addr_width}{byte_offset}.{bit_in_byte}" # Ej: DBX0.1 + elif stl_area in ["T", "C"]: + return f"{stl_area}{byte_offset}" # Los timers/contadores solo usan el número + else: # I, Q, M, L, PI, PQ + return f"{stl_area}{addr_width}{byte_offset}.{bit_in_byte}" # Ej: M10.1, I0.0 + + except ValueError: + return f"{area}?{bit_offset_str}?" + + return f"_{scope}_?" # Fallback + +def get_comment_text(comment_element): + """Extrae texto de un LineComment o Comment.""" + if comment_element is None: return "" + # Usar get_multilingual_text si los comentarios son multilingües + # Si no, extraer texto directamente + ml_texts = comment_element.xpath(".//mlt:MultilingualTextItem/mlt:AttributeList/mlt:Text/text()", + namespaces={'mlt': "http://www.siemens.com/automation/Openness/SW/Interface/v5"}) # Asumiendo ns + if ml_texts: + # Podrías intentar obtener un idioma específico o simplemente el primero + return ml_texts[0].strip() if ml_texts else "" + + # Fallback a texto directo si no hay estructura multilingüe + text_nodes = comment_element.xpath("./text()") + return "".join(text_nodes).strip() + +def reconstruct_stl_from_statementlist(statement_list_node): + """Reconstruye el código STL como una cadena de texto desde .""" + if statement_list_node is None: + return "// Error: StatementList node not found.\n" + + stl_lines = [] + # CORREGIDO: Añadido namespaces=ns + statements = statement_list_node.xpath("./stl:StlStatement", namespaces=ns) + + for stmt in statements: + line_parts = [] + line_comment = "" # Comentario al final de la línea + + # 1. Comentarios al inicio de la línea (como líneas separadas //) + # CORREGIDO: Añadido namespaces=ns + initial_comments = stmt.xpath("child::stl:Comment | child::stl:LineComment", namespaces=ns) + for comm in initial_comments: + comment_text = get_comment_text(comm) + if comment_text: + # Dividir comentarios multilínea en varias líneas // + for comment_line in comment_text.splitlines(): + stl_lines.append(f"// {comment_line}") + + # 2. Etiqueta (si existe) + # CORREGIDO: Añadido namespaces=ns + label_decl = stmt.xpath("./stl:LabelDeclaration", namespaces=ns) + label_str = "" + if label_decl: + # CORREGIDO: Añadido namespaces=ns + label_name_nodes = label_decl[0].xpath("./stl:Label/@Name", namespaces=ns) + if label_name_nodes: + label_str = f"{label_name_nodes[0]}:" + # Buscar comentarios DENTRO de LabelDeclaration pero después de Label + # CORREGIDO: Añadido namespaces=ns + label_comments = label_decl[0].xpath("./stl:Comment | ./stl:LineComment", namespaces=ns) + for lcomm in label_comments: + comment_text = get_comment_text(lcomm) + if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea + + # 3. Token de Instrucción STL + # CORREGIDO: Añadido namespaces=ns + instruction_token = stmt.xpath("./stl:StlToken", namespaces=ns) + instruction_str = "" + if instruction_token: + token_text = instruction_token[0].get("Text", "_ERR_TOKEN_") + instruction_str = token_text + # Comentarios asociados directamente al token + # CORREGIDO: Añadido namespaces=ns + token_comments = instruction_token[0].xpath("./stl:Comment | ./stl:LineComment", namespaces=ns) + for tcomm in token_comments: + comment_text = get_comment_text(tcomm) + if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea + + # 4. Acceso/Operando STL + # CORREGIDO: Añadido namespaces=ns + access_elem = stmt.xpath("./stl:Access", namespaces=ns) + access_str = "" + if access_elem: + access_text = get_access_text(access_elem[0]) + access_str = access_text + # Comentarios DENTRO del Access (pueden ser de línea o bloque) + # CORREGIDO: Añadido namespaces=ns + access_comments = access_elem[0].xpath("child::stl:LineComment | child::stl:Comment", namespaces=ns) + for acc_comm in access_comments: + comment_text = get_comment_text(acc_comm) + if comment_text: line_comment += f" // {comment_text}" # Añadir al comentario de línea + + # Construir la línea: Etiqueta (si hay) + Tab + Instrucción + Espacio + Operando (si hay) + Comentario(s) + current_line = "" + if label_str: + current_line += label_str + if instruction_str: + if current_line: # Si ya había etiqueta, añadir tabulador + current_line += "\t" + current_line += instruction_str + if access_str: + if current_line: # Si ya había algo, añadir espacio + current_line += " " + current_line += access_str + if line_comment: + # Añadir espacio antes del comentario si hay código en la línea + if current_line.strip(): + current_line += f" {line_comment}" + else: # Si la línea estaba vacía (solo comentarios iniciales), poner el comentario de línea + current_line = line_comment + + # Añadir la línea construida solo si no está vacía + if current_line.strip(): + stl_lines.append(current_line.rstrip()) # Eliminar espacios finales + + return "\n".join(stl_lines) + +# --- Main Parsing Function --- -# --- 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): """ Parsea una red, extrae lógica y añade conexiones EN implícitas. @@ -792,8 +1018,6 @@ def parse_network(network_element): "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): @@ -989,6 +1213,38 @@ def convert_xml_to_json(xml_filepath, json_filepath): ], } + # --- NUEVO MANEJO STL --- + elif programming_language == "STL": + statement_list_node = ( + network_source_node.xpath("./stl:StatementList", namespaces=ns) + if network_source_node is not None + else None + ) + + reconstructed_stl = f"// STL extraction failed for Network {network_id}: StatementList node not found.\n" + if statement_list_node: + print(f" Reconstruyendo STL desde StatementList para red {network_id}...") + # Llama a la nueva función de reconstrucción STL + reconstructed_stl = reconstruct_stl_from_statementlist(statement_list_node[0]) + # print(f" ... STL reconstruido (parcial):\n{reconstructed_stl[:200]}...") # Preview opcional + else: + print(f" Advertencia: No se encontró nodo para red STL {network_id}.") + + # Guardar como un chunk de texto crudo + parsed_network_data = { + "id": network_id, + "title": network_title, + "comment": network_comment, + "language": "STL", # Indicar que es STL + "logic": [ + { + "instruction_uid": f"STL_{network_id}", # UID inventado + "type": "RAW_STL_CHUNK", # Nuevo tipo para identificarlo + "stl": reconstructed_stl, # Guardar el texto reconstruido + } + ], + } + 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 @@ -1066,8 +1322,6 @@ def convert_xml_to_json(xml_filepath, json_filepath): traceback.print_exc() print("--- Fin Traceback ---") - -# --- Punto de Entrada Principal --- if __name__ == "__main__": # Imports necesarios solo para la ejecución como script principal import argparse diff --git a/x2_process.py b/x2_process.py index d7a10a1..bef6a4f 100644 --- a/x2_process.py +++ b/x2_process.py @@ -23,8 +23,9 @@ SCL_SUFFIX = "_sympy_processed" # New suffix to indicate processing method GROUPED_COMMENT = "// Logic included in grouped IF" SIMPLIFIED_IF_COMMENT = "// Simplified IF condition by script" # May still be useful - -# Global data variable +# Global data dictionary (consider passing 'data' as argument if needed elsewhere) +# It's currently used by process_group_ifs implicitly via the outer scope, +# which works but passing it explicitly might be cleaner. data = {} def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data): @@ -33,6 +34,7 @@ def process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data): y, si habilitan un grupo (>1) de bloques funcionales (con SCL ya generado), construye el bloque IF agrupado CON LA CONDICIÓN SIMPLIFICADA. Modifica el campo 'scl' de la instrucción generadora de condición. + (Esta es la implementación de la función como la tenías en el archivo original) """ instr_uid = instruction["instruction_uid"] instr_type_original = instruction.get("type", "").replace(SCL_SUFFIX, "").replace("_error", "") @@ -215,45 +217,47 @@ def load_processors(processors_dir="processors"): # Devolver el mapa (para lookup rápido si es necesario) y la lista ordenada return processor_map, processor_list_sorted -# --- Bucle Principal de Procesamiento (Modificado) --- +# --- Bucle Principal de Procesamiento (Modificado para STL) --- def process_json_to_scl(json_filepath): """ Lee el JSON simplificado, aplica los procesadores dinámicamente cargados - siguiendo un orden de prioridad, y guarda el JSON procesado. + siguiendo un orden de prioridad (ignorando redes STL), y guarda el JSON procesado. """ - global data # Necesario si process_group_ifs (definido fuera) accede a data globalmente. - # Si process_group_ifs está definida DENTRO de process_json_to_scl, - # no necesitarías global, ya que accedería a la 'data' local. - # Lo más limpio es definir process_group_ifs fuera y pasarle 'data' - # como argumento (como ya se hace). Así que 'global data' aquí es probablemente innecesario. - # Eliminémoslo por ahora y aseguremos que data se pasa a process_group_ifs. + global data # Necesario para que load_processors y process_group_ifs (definidas fuera) puedan acceder a ella. + # Considerar pasar 'data' como argumento si es posible refactorizar. - if not os.path.exists(json_filepath): print(f"Error: JSON no encontrado: {json_filepath}"); return + if not os.path.exists(json_filepath): + print(f"Error: JSON no encontrado: {json_filepath}") + return print(f"Cargando JSON desde: {json_filepath}") try: - with open(json_filepath, "r", encoding="utf-8") as f: data = json.load(f) - except Exception as e: print(f"Error al cargar JSON: {e}"); traceback.print_exc(); return + with open(json_filepath, "r", encoding="utf-8") as f: + data = json.load(f) # Carga en 'data' global + except Exception as e: + print(f"Error al cargar JSON: {e}") + traceback.print_exc() + return - # --- Carga dinámica de procesadores (sin cambios) --- - script_dir = os.path.dirname(__file__); processors_dir_path = os.path.join(script_dir, 'processors') + # --- Carga dinámica de procesadores --- + script_dir = os.path.dirname(__file__) + processors_dir_path = os.path.join(script_dir, 'processors') processor_map, sorted_processors = load_processors(processors_dir_path) - if not processor_map: print("Error crítico: No se cargaron procesadores. Abortando."); return + if not processor_map: + print("Error crítico: No se cargaron procesadores. Abortando.") + return - # --- Crear mapas de acceso por red (sin cambios) --- + # --- Crear mapas de acceso por red --- network_access_maps = {} - # ... (logic to populate network_access_maps remains the same) ... + # (La lógica para llenar network_access_maps no cambia, puedes copiarla de tu original) for network in data.get("networks", []): net_id = network["id"] current_access_map = {} - # Extraer todos los 'Access' usados en esta red for instr in network.get("logic", []): - # Revisar Inputs for _, source in instr.get("inputs", {}).items(): sources_to_check = (source if isinstance(source, list) else ([source] if isinstance(source, dict) else [])) for src in sources_to_check: if (isinstance(src, dict) and src.get("uid") and src.get("type") in ["variable", "constant"]): current_access_map[src["uid"]] = src - # Revisar Outputs for _, dest_list in instr.get("outputs", {}).items(): if isinstance(dest_list, list): for dest in dest_list: @@ -261,12 +265,9 @@ def process_json_to_scl(json_filepath): current_access_map[dest["uid"]] = dest network_access_maps[net_id] = current_access_map - # --- Inicializar mapa SymPy y SymbolManager por red --- - # Cada red puede tener su propio contexto de símbolos si es necesario, - # pero un SymbolManager global suele ser suficiente si no hay colisiones graves. - # Usaremos uno global por simplicidad ahora. + # --- Inicializar mapa SymPy y SymbolManager --- symbol_manager = SymbolManager() - sympy_map = {} # Mapa para resultados SymPy intermedios (expresiones) + sympy_map = {} max_passes = 30 passes = 0 @@ -275,23 +276,27 @@ def process_json_to_scl(json_filepath): print("\n--- Iniciando Bucle de Procesamiento Iterativo (con SymPy y prioridad) ---") while passes < max_passes and not processing_complete: passes += 1 - made_change_in_base_pass = False # Renombrar: made_change_in_sympy_pass + made_change_in_base_pass = False made_change_in_group_pass = False - # made_change_in_simplify_pass = False # Ya no existe Fase 3 print(f"\n--- Pase {passes} ---") - num_processed_this_pass = 0 # Renombrar: num_sympy_processed_this_pass + num_sympy_processed_this_pass = 0 num_grouped_this_pass = 0 - # num_simplified_this_pass = 0 # Ya no existe Fase 3 - # --- FASE 1: Procesadores Base (Ahora usan SymPy) --- + # --- FASE 1: Procesadores Base (Ignorando STL) --- print(f" Fase 1 (SymPy Base - Orden por Prioridad):") - num_sympy_processed_this_pass = 0 # Contador específico + num_sympy_processed_this_pass = 0 for processor_info in sorted_processors: current_type_name = processor_info['type_name'] func_to_call = processor_info['func'] for network in data.get("networks", []): network_id = network["id"] + network_lang = network.get("language", "LAD") # Obtener lenguaje de la red + + # *** IGNORAR REDES STL EN ESTA FASE *** + if network_lang == "STL": + continue # Saltar al siguiente network + access_map = network_access_maps.get(network_id, {}) network_logic = network.get("logic", []) @@ -299,25 +304,26 @@ def process_json_to_scl(json_filepath): instr_uid = instruction.get("instruction_uid") instr_type_original = instruction.get("type", "Unknown") - # Saltar si ya está procesado con el NUEVO método, es error, o agrupado - if (instr_type_original.endswith(SCL_SUFFIX) # Check new suffix + # Saltar si ya procesado, error, agrupado o es chunk STL/SCL/Unsupported + if (instr_type_original.endswith(SCL_SUFFIX) or "_error" in instr_type_original - or instruction.get("grouped", False)): + or instruction.get("grouped", False) + or instr_type_original in ["RAW_STL_CHUNK", "RAW_SCL_CHUNK", "UNSUPPORTED_LANG"]): continue # Determinar tipo efectivo (como antes) lookup_key = instr_type_original.lower() effective_type_name = lookup_key - if instr_type_original == "Call": # ... (manejo Call FC/FB) ... + if instr_type_original == "Call": block_type = instruction.get("block_type", "").upper() if block_type == "FC": effective_type_name = "call_fc" elif block_type == "FB": effective_type_name = "call_fb" + # Llamar al procesador si coincide el tipo if effective_type_name == current_type_name: try: - # *** Llamar al procesador refactorizado *** - # Pasa sympy_map y symbol_manager, no scl_map - changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data) # Pasamos SymbolManager + # Pasa sympy_map, symbol_manager y data + changed = func_to_call(instruction, network_id, sympy_map, symbol_manager, data) if changed: made_change_in_base_pass = True num_sympy_processed_this_pass += 1 @@ -326,24 +332,26 @@ def process_json_to_scl(json_filepath): traceback.print_exc() instruction["scl"] = f"// ERROR en SymPy procesador base: {e}" instruction["type"] = instr_type_original + "_error" - made_change_in_base_pass = True - print(f" -> {num_sympy_processed_this_pass} instrucciones procesadas con SymPy.") + made_change_in_base_pass = True # Marcar cambio aunque sea error + print(f" -> {num_sympy_processed_this_pass} instrucciones (no STL) procesadas con SymPy.") - # --- FASE 2: Agrupación IF (Ahora usa SymPy para simplificar) --- - # Ejecutar si hubo cambios en base o es el primer pase + # --- FASE 2: Agrupación IF (Ignorando STL) --- if made_change_in_base_pass or passes == 1: print(f" Fase 2 (Agrupación IF con Simplificación):") - num_grouped_this_pass = 0 # Reiniciar contador + num_grouped_this_pass = 0 for network in data.get("networks", []): network_id = network["id"] - # access_map = network_access_maps.get(network_id, {}) # No usado directamente por group_ifs + network_lang = network.get("language", "LAD") # Obtener lenguaje + + # *** IGNORAR REDES STL EN ESTA FASE *** + if network_lang == "STL": + continue # Saltar red STL + network_logic = network.get("logic", []) - # Iterar sobre instrucciones que *pueden* generar condiciones booleanas for instruction in network_logic: - # process_group_ifs ahora verifica internamente si la instr. fue procesada try: - # *** Llamar a process_group_ifs adaptado *** + # Llama a process_group_ifs (que necesita acceso a 'data' global o pasado) group_changed = process_group_ifs(instruction, network_id, sympy_map, symbol_manager, data) if group_changed: made_change_in_group_pass = True @@ -351,17 +359,14 @@ def process_json_to_scl(json_filepath): except Exception as e: print(f"ERROR(GroupLoop) al intentar agrupar desde UID {instruction.get('instruction_uid')}: {e}") traceback.print_exc() - print(f" -> {num_grouped_this_pass} agrupaciones realizadas.") + print(f" -> {num_grouped_this_pass} agrupaciones realizadas (en redes no STL).") - # --- FASE 3 Eliminada --- # --- Comprobar si se completó el procesamiento --- - # Solo considera Fase 1 (SymPy Base) y Fase 2 (Grouping) if not made_change_in_base_pass and not made_change_in_group_pass: print(f"\n--- No se hicieron más cambios en el pase {passes}. Proceso iterativo completado. ---") processing_complete = True else: - # Mensaje de fin de pase actualizado print(f"--- Fin Pase {passes}: {num_sympy_processed_this_pass} proc SymPy, {num_grouped_this_pass} agrup. Continuando...") # --- Comprobar límite de pases --- @@ -370,46 +375,45 @@ def process_json_to_scl(json_filepath): # --- FIN BUCLE ITERATIVO --- - # --- Verificación Final --- - # La lógica aquí podría necesitar ajustes si el sufijo cambió o si el SCL final - # solo se genera en instrucciones terminales. + # --- Verificación Final (Ajustada para RAW_STL_CHUNK) --- print("\n--- Verificación Final de Instrucciones No Procesadas ---") unprocessed_count = 0 unprocessed_details = [] - ignored_types = ['raw_scl_chunk', 'unsupported_lang'] + # Añadir RAW_STL_CHUNK a los tipos ignorados + ignored_types = ['raw_scl_chunk', 'unsupported_lang', 'raw_stl_chunk'] # Añadido raw_stl_chunk for network in data.get("networks", []): network_id = network.get("id", "Unknown ID") network_title = network.get("title", f"Network {network_id}") + network_lang = network.get("language", "LAD") # Obtener lenguaje + + # No verificar instrucciones dentro de redes STL, ya que no se procesan + if network_lang == "STL": + continue + for instruction in network.get("logic", []): instr_uid = instruction.get("instruction_uid", "Unknown UID") instr_type = instruction.get("type", "Unknown Type") is_grouped = instruction.get("grouped", False) - has_final_scl = bool(instruction.get("scl", "").strip()) and not instruction.get("scl", "").strip().startswith("//") - # Condición revisada: No tiene el sufijo nuevo Y no es error Y no está agrupada Y no es tipo ignorado - # Y ADEMÁS, ¿debería tener SCL final si es una instr. terminal? - # Simplificación: si no tiene sufijo, no es error, no agrupada, no ignorada -> problema + # Condición revisada para ignorar los chunks crudos if (not instr_type.endswith(SCL_SUFFIX) and "_error" not in instr_type and not is_grouped and - instr_type.lower() not in ignored_types): + instr_type.lower() not in ignored_types): # Verifica contra lista actualizada unprocessed_count += 1 unprocessed_details.append( - f" - Red '{network_title}' (ID: {network_id}), " - f"Instrucción UID: {instr_uid}, Tipo Original: '{instr_type}'" + f" - Red '{network_title}' (ID: {network_id}, Lang: {network_lang}), " + f"Instrucción UID: {instr_uid}, Tipo: '{instr_type}'" ) - # Opcional: añadir si tiene SCL o no - # unprocessed_details[-1] += f" (Tiene SCL final: {has_final_scl})" - if unprocessed_count > 0: - print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones que no fueron procesadas:") + print(f"ADVERTENCIA: Se encontraron {unprocessed_count} instrucciones (no STL) que parecen no haber sido procesadas:") for detail in unprocessed_details: print(detail) else: - print("INFO: Todas las instrucciones relevantes parecen haber sido procesadas o agrupadas.") + print("INFO: Todas las instrucciones relevantes (no STL) parecen haber sido procesadas o agrupadas.") - # --- Guardar JSON Final (sin cambios) --- + # --- Guardar JSON Final --- output_filename = json_filepath.replace("_simplified.json", "_simplified_processed.json") print(f"\nGuardando JSON procesado en: {output_filename}") try: @@ -420,8 +424,7 @@ def process_json_to_scl(json_filepath): print(f"Error Crítico al guardar JSON procesado: {e}") traceback.print_exc() - -# --- Ejecución (igual que antes) --- +# --- Ejecución (sin cambios) --- if __name__ == "__main__": parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic.") parser.add_argument( @@ -432,6 +435,48 @@ if __name__ == "__main__": ) args = parser.parse_args() + xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] + xml_dir = os.path.dirname(args.source_xml_filepath) + input_dir = xml_dir if xml_dir else os.path.dirname(__file__) + + input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json") + + if not os.path.exists(input_json_file): + print(f"Error Fatal: El archivo de entrada JSON simplificado no existe: '{input_json_file}'") + print(f"Asegúrate de haber ejecutado 'x1_to_json.py' primero sobre '{args.source_xml_filepath}'.") + sys.exit(1) + else: + process_json_to_scl(input_json_file) + parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic.") + parser.add_argument( + "source_xml_filepath", + nargs="?", + default="TestLAD.xml", + help="Path to the original source XML file (used to derive JSON input name, default: TestLAD.xml)" + ) + args = parser.parse_args() + + xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] + xml_dir = os.path.dirname(args.source_xml_filepath) + input_dir = xml_dir if xml_dir else os.path.dirname(__file__) + + input_json_file = os.path.join(input_dir, f"{xml_filename_base}_simplified.json") + + if not os.path.exists(input_json_file): + print(f"Error Fatal: El archivo de entrada JSON simplificado no existe: '{input_json_file}'") + print(f"Asegúrate de haber ejecutado 'x1_to_json.py' primero sobre '{args.source_xml_filepath}'.") + sys.exit(1) + else: + process_json_to_scl(input_json_file) + parser = argparse.ArgumentParser(description="Process simplified JSON to embed SCL logic.") + parser.add_argument( + "source_xml_filepath", + nargs="?", + default="TestLAD.xml", + help="Path to the original source XML file (used to derive JSON input name, default: TestLAD.xml)" + ) + args = parser.parse_args() + xml_filename_base = os.path.splitext(os.path.basename(args.source_xml_filepath))[0] # Usar directorio del script actual si el XML no tiene ruta, o la ruta del XML si la tiene xml_dir = os.path.dirname(args.source_xml_filepath) diff --git a/x3_generate_scl.py b/x3_generate_scl.py index 66207c4..edea599 100644 --- a/x3_generate_scl.py +++ b/x3_generate_scl.py @@ -212,49 +212,73 @@ def generate_scl(processed_json_filepath, output_scl_filepath): scl_output.append("") network_has_code = False - # Iterar sobre la 'logica' de la red - for instruction in network.get('logic', []): - instruction_type = instruction.get("type", "") - scl_code = instruction.get('scl', "") # Obtener SCL generado por x2 - # Saltar instrucciones agrupadas - if instruction.get("grouped", False): - continue + # --- NUEVO MANEJO STL con formato Markdown --- + if network_lang == "STL": + network_has_code = True # Marcar que la red tiene contenido + if network.get('logic') and isinstance(network['logic'], list) and len(network['logic']) > 0: + stl_chunk = network['logic'][0] + if stl_chunk.get("type") == "RAW_STL_CHUNK" and "stl" in stl_chunk: + raw_stl_code = stl_chunk["stl"] + # Añadir marcador de inicio (como comentario SCL para evitar errores) + scl_output.append(f" {'//'} ```STL") # Doble '//' para asegurar que sea comentario + # Escribir el código STL crudo, indentado + for stl_line in raw_stl_code.splitlines(): + # Añadir indentación estándar de SCL + scl_output.append(f" {stl_line}") # <-- STL sin comentar + # Añadir marcador de fin (como comentario SCL) + scl_output.append(f" {'//'} ```") + else: + scl_output.append(" // ERROR: Contenido STL inesperado en JSON.") + else: + scl_output.append(" // ERROR: No se encontró lógica STL en JSON para esta red.") + scl_output.append("") # Línea en blanco después de la red STL + # --- FIN NUEVO MANEJO STL con formato Markdown --- + else: - # Escribir SCL si es un tipo procesado y tiene código relevante - # (Ignorar comentarios de depuración de SymPy) - if instruction_type.endswith(SCL_SUFFIX) and scl_code: - is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \ - scl_code.strip().startswith("// PBox SymPy processed") or \ - scl_code.strip().startswith("// NBox SymPy processed") - # O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL - is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines()) + # Iterar sobre la 'logica' de la red + for instruction in network.get('logic', []): + instruction_type = instruction.get("type", "") + scl_code = instruction.get('scl', "") # Obtener SCL generado por x2 + + # Saltar instrucciones agrupadas + if instruction.get("grouped", False): + continue + + # Escribir SCL si es un tipo procesado y tiene código relevante + # (Ignorar comentarios de depuración de SymPy) + if instruction_type.endswith(SCL_SUFFIX) and scl_code: + is_internal_sympy_comment_only = scl_code.strip().startswith("// SymPy") or \ + scl_code.strip().startswith("// PBox SymPy processed") or \ + scl_code.strip().startswith("// NBox SymPy processed") + # O podría ser más genérico: ignorar cualquier línea que solo sea comentario SCL + is_only_comment = all(line.strip().startswith("//") for line in scl_code.splitlines()) - # Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse) - if not is_only_comment or scl_code.strip().startswith("IF"): + # Escribir solo si NO es un comentario interno de SymPy O si es un bloque IF (que sí debe escribirse) + if not is_only_comment or scl_code.strip().startswith("IF"): + network_has_code = True + for line in scl_code.splitlines(): + # Añadir indentación estándar + scl_output.append(f" {line}") + + # Incluir también tipos especiales directamente + elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code: network_has_code = True for line in scl_code.splitlines(): - # Añadir indentación estándar - scl_output.append(f" {line}") + scl_output.append(f" {line}") # Indentar - # Incluir también tipos especiales directamente - elif instruction_type in ["RAW_SCL_CHUNK", "UNSUPPORTED_LANG"] and scl_code: - network_has_code = True - for line in scl_code.splitlines(): - scl_output.append(f" {line}") # Indentar - - # Podríamos añadir comentarios para errores si se desea - # elif "_error" in instruction_type: - # network_has_code = True - # scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}") + # Podríamos añadir comentarios para errores si se desea + # elif "_error" in instruction_type: + # network_has_code = True + # scl_output.append(f" // ERROR processing instruction UID {instruction.get('instruction_uid')}: {instruction.get('scl', 'No details')}") - if network_has_code: - scl_output.append("") # Línea en blanco después del código de la red - else: - scl_output.append(f" // Network did not produce printable SCL code.") - scl_output.append("") + if network_has_code: + scl_output.append("") # Línea en blanco después del código de la red + else: + scl_output.append(f" // Network did not produce printable SCL code.") + scl_output.append("") # Fin del bloque scl_output.append("END_FUNCTION_BLOCK") # O END_FUNCTION si es FC