Add BlenderCtrl__Main class with initialization and run logic for production control
This commit is contained in:
parent
fb26f0eb54
commit
e4d264e6cd
|
@ -0,0 +1,422 @@
|
||||||
|
class BlenderCtrl__Main:
|
||||||
|
def __init__(self):
|
||||||
|
# Initialize variables for BlenderCtrl__Main (FC2000)
|
||||||
|
# Temp variables
|
||||||
|
self.All_Auto_RETVAL = 0 # Int
|
||||||
|
self.Reset_SP_Word_RETVAL = 0 # Int
|
||||||
|
self.mResetWaterTot = False # Bool
|
||||||
|
self.mResetSyrupTot = False # Bool
|
||||||
|
self.mResetCO2Tot = False # Bool
|
||||||
|
self.mResetProductTot = False # Bool
|
||||||
|
self.Block_Move_Err = 0 # Int
|
||||||
|
|
||||||
|
# Common system variables
|
||||||
|
self.AUX_FALSE = False
|
||||||
|
self.Clock_10ms = False
|
||||||
|
self.Clock_100ms = False
|
||||||
|
self.Clock_1s = False
|
||||||
|
self.Clock_Counter = 0
|
||||||
|
self.MachineState = 0
|
||||||
|
self.SystemError = False
|
||||||
|
self.MachineInitialized = False
|
||||||
|
|
||||||
|
# HMI and control variables
|
||||||
|
self.HMI_PID = None
|
||||||
|
self.Filler_Head_Variables = None
|
||||||
|
self.gIN_VoltageOk = False
|
||||||
|
self.M19000 = False
|
||||||
|
self.gEmergencyPressed = False
|
||||||
|
|
||||||
|
# System state indicators
|
||||||
|
self.Air_Pressure_OK = False
|
||||||
|
self.CO2_Pressure_OK = False
|
||||||
|
self.System_Pressure_OK = False
|
||||||
|
self.Temperature_Current = 0.0
|
||||||
|
self.Temperature_Max = 0.0
|
||||||
|
self.Temperature_Alarm = False
|
||||||
|
self.Tank_Level = 0.0
|
||||||
|
self.Tank_Level_Min = 0.0
|
||||||
|
self.Tank_Level_Low = False
|
||||||
|
|
||||||
|
# Mode controls
|
||||||
|
self.Reset_Command = False
|
||||||
|
self.Reset_Complete = False
|
||||||
|
self.Totalizer_Value = 0
|
||||||
|
self.HMI_Manual_Mode_Requested = False
|
||||||
|
self.System_In_Manual_Mode = False
|
||||||
|
self.System_In_Auto_Mode = False
|
||||||
|
self.Production_Mode = False
|
||||||
|
self.CIP_Mode = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Network 1: Clock Generation
|
||||||
|
self.Clock_Signal()
|
||||||
|
|
||||||
|
# Network 2: Machine Init
|
||||||
|
self.BlenderCtrl_MachineInit()
|
||||||
|
|
||||||
|
# Network 3: Filler Head
|
||||||
|
# Procesamiento de variables de cabezal de llenado
|
||||||
|
if self.AUX_FALSE:
|
||||||
|
# Implementación del BLKMOV para cabezal
|
||||||
|
self.Filler_Head_Variables.FillerHead = self.HMI_PID.PPM303
|
||||||
|
self.Block_Move_Err = self.resultado_operacion
|
||||||
|
|
||||||
|
# Network 4: Emergency Pressed
|
||||||
|
# Control de parada de emergencia
|
||||||
|
if self.gIN_VoltageOk and not self.M19000:
|
||||||
|
self.gEmergencyPressed = True
|
||||||
|
|
||||||
|
# Network 5: Air and CO2 pressure ok and auxiliary ok
|
||||||
|
# Verificación de presión de CO2 y aire
|
||||||
|
if self.Air_Pressure_OK and self.CO2_Pressure_OK:
|
||||||
|
self.System_Pressure_OK = True
|
||||||
|
|
||||||
|
# Network 6: Blender State Num
|
||||||
|
# Control del estado del mezclador
|
||||||
|
if self.System_Pressure_OK:
|
||||||
|
if self.Production_Mode:
|
||||||
|
self.MachineState = 1 # Production state
|
||||||
|
elif self.CIP_Mode:
|
||||||
|
self.MachineState = 2 # CIP state
|
||||||
|
else:
|
||||||
|
self.MachineState = 0 # Idle state
|
||||||
|
|
||||||
|
# Network 7: Delay Power On
|
||||||
|
# Retardo al encendido para estabilización
|
||||||
|
if self.Clock_1s:
|
||||||
|
self.Power_On_Delay_Counter += 1
|
||||||
|
if self.Power_On_Delay_Counter >= 5: # 5 segundos de retardo
|
||||||
|
self.System_Ready = True
|
||||||
|
|
||||||
|
# Network 8: Production Mode
|
||||||
|
# Activación del modo de producción
|
||||||
|
if self.Production_Mode_Request and self.System_Ready and not self.CIP_Mode:
|
||||||
|
self.Production_Mode = True
|
||||||
|
self.CIP_Mode = False
|
||||||
|
|
||||||
|
# Network 9: CIp Mode
|
||||||
|
# Activación del modo de limpieza CIP
|
||||||
|
if self.CIP_Mode_Request and self.System_Ready and not self.Production_Mode:
|
||||||
|
self.CIP_Mode = True
|
||||||
|
self.Production_Mode = False
|
||||||
|
|
||||||
|
# Network 10: Error Faults
|
||||||
|
# Gestión de errores del sistema
|
||||||
|
if self.Error_Detected or self.Safety_Fault:
|
||||||
|
self.SystemError = True
|
||||||
|
self.Error_Reset_Required = True
|
||||||
|
|
||||||
|
# Network 11: Filler Bottle Count Used to push Product
|
||||||
|
# Control de conteo de botellas para empujar producto
|
||||||
|
if self.Bottle_Detected:
|
||||||
|
self.Bottle_Counter += 1
|
||||||
|
if (
|
||||||
|
self.Production_Mode
|
||||||
|
and self.Bottle_Counter >= self.Min_Bottles_For_Push
|
||||||
|
):
|
||||||
|
self.Start_Product_Push = True
|
||||||
|
|
||||||
|
# Network 12: Water Bypass Enable
|
||||||
|
# Habilitación de bypass de agua
|
||||||
|
if self.Water_Bypass_Request and self.System_In_Manual_Mode:
|
||||||
|
self.Water_Bypass_Enabled = True
|
||||||
|
|
||||||
|
# Network 13: Still Water Bypass
|
||||||
|
# Control de bypass de agua sin gas
|
||||||
|
if self.Still_Water_Request and self.Water_Bypass_Enabled:
|
||||||
|
self.Open_Still_Water_Valve = True
|
||||||
|
|
||||||
|
# Network 14: Manual Syrup Drain Valve Open - Operator Alarm
|
||||||
|
# Alarma de válvula de drenaje de jarabe abierta manualmente
|
||||||
|
if self.Syrup_Drain_Valve_Open and self.Production_Mode:
|
||||||
|
self.Syrup_Drain_Open_Alarm = True
|
||||||
|
self.Operator_Attention_Required = True
|
||||||
|
|
||||||
|
# Network 15: Manual Syrup Drain Valve Open - Operator Alarm
|
||||||
|
# Redundancia para alarma de válvula de drenaje
|
||||||
|
if self.Syrup_Drain_Open_Alarm:
|
||||||
|
self.Flash_HMI_Warning()
|
||||||
|
|
||||||
|
# Network 16: Maselli Control
|
||||||
|
# Control del sistema Maselli de medición Brix
|
||||||
|
self.Maselli_Control()
|
||||||
|
|
||||||
|
# Network 17: mPDS Control
|
||||||
|
# Control del sistema mPDS
|
||||||
|
self.mPDS_Control()
|
||||||
|
|
||||||
|
# Network 18: mPDS Syrup Control
|
||||||
|
# Control de jarabe mediante mPDS
|
||||||
|
self.mPDS_Syrup_Control()
|
||||||
|
|
||||||
|
# Network 19: Co2 Analog Input
|
||||||
|
# Lectura de entrada analógica de CO2
|
||||||
|
self.GetProdBrixCO2_FromAn()
|
||||||
|
|
||||||
|
# Network 20: Quality
|
||||||
|
# Control de calidad del producto
|
||||||
|
self.Quality_Check()
|
||||||
|
|
||||||
|
# Network 21: Input Data
|
||||||
|
# Procesamiento de datos de entrada
|
||||||
|
self.Process_Input_Data()
|
||||||
|
|
||||||
|
# Network 22: Sel Brix Source Check
|
||||||
|
# Verificación de fuente Brix seleccionada
|
||||||
|
if self.Brix_Source_Selected == 1:
|
||||||
|
self.Use_Maselli_Brix()
|
||||||
|
elif self.Brix_Source_Selected == 2:
|
||||||
|
self.Use_mPDS_Brix()
|
||||||
|
|
||||||
|
# Network 23: Check Water Cooling System Temperature
|
||||||
|
# Control de temperatura del sistema de enfriamiento de agua
|
||||||
|
if self.Temperature_Current > self.Temperature_Max:
|
||||||
|
self.Temperature_Alarm = True
|
||||||
|
self.Activate_Cooling()
|
||||||
|
|
||||||
|
# Network 24: Tank Level
|
||||||
|
# Monitoreo de nivel de tanque
|
||||||
|
if self.Tank_Level < self.Tank_Level_Min:
|
||||||
|
self.Tank_Level_Low = True
|
||||||
|
self.Start_Tank_Fill = True
|
||||||
|
|
||||||
|
# Network 25: Production ONS
|
||||||
|
# One-Shot para inicio de producción
|
||||||
|
if self.Production_Mode and not self.last_Production_Mode:
|
||||||
|
self.Production_Initialize()
|
||||||
|
self.Recipe_Load()
|
||||||
|
self.last_Production_Mode = self.Production_Mode
|
||||||
|
|
||||||
|
# Network 26: Blender Prod Mode Init
|
||||||
|
# Inicialización del modo de producción del mezclador
|
||||||
|
if self.Production_Mode and not self.Production_Initialized:
|
||||||
|
self.Blender_Prod_Mode_Init()
|
||||||
|
self.Production_Initialized = True
|
||||||
|
|
||||||
|
# Network 27: Rinse ONS
|
||||||
|
# One-Shot para enjuague
|
||||||
|
if self.Rinse_Request and not self.last_Rinse_Request:
|
||||||
|
self.Start_Rinse_Sequence()
|
||||||
|
self.last_Rinse_Request = self.Rinse_Request
|
||||||
|
|
||||||
|
# Network 28: CIP ONS
|
||||||
|
# One-Shot para inicio de CIP
|
||||||
|
if self.CIP_Mode and not self.last_CIP_Mode:
|
||||||
|
self.CIP_Initialize()
|
||||||
|
self.last_CIP_Mode = self.CIP_Mode
|
||||||
|
|
||||||
|
# Network 29: CIp Mode Init
|
||||||
|
# Inicialización del modo CIP
|
||||||
|
if self.CIP_Mode and not self.CIP_Initialized:
|
||||||
|
self.Blender_CIP_Mode_Init()
|
||||||
|
self.CIP_Initialized = True
|
||||||
|
|
||||||
|
# Network 30: Reset SPWords
|
||||||
|
# Reseteo de palabras SP
|
||||||
|
self.Reset_SP_Words()
|
||||||
|
|
||||||
|
# Network 31: Blender Run Control
|
||||||
|
# Control de funcionamiento del mezclador
|
||||||
|
self.Blender_Run_Control()
|
||||||
|
|
||||||
|
# Network 32: Tank Pressure Control
|
||||||
|
# Control de presión del tanque
|
||||||
|
self.Tank_Pressure_Control()
|
||||||
|
|
||||||
|
# Network 33: Balaiage
|
||||||
|
# Control de barrido (Balaiage)
|
||||||
|
self.Balaiage_Control()
|
||||||
|
|
||||||
|
# Network 34: First Production
|
||||||
|
# Control para primera producción
|
||||||
|
self.First_Production()
|
||||||
|
|
||||||
|
# Network 35: CIP MAIN Calling
|
||||||
|
# Llamada principal al sistema CIP
|
||||||
|
if self.CIP_Mode:
|
||||||
|
self.CIP_Main()
|
||||||
|
|
||||||
|
# Network 36: Blender Rinse
|
||||||
|
# Enjuague del mezclador
|
||||||
|
self.Blender_Rinse()
|
||||||
|
|
||||||
|
# Network 37: Safeties
|
||||||
|
# Control de seguridades
|
||||||
|
self.Safeties()
|
||||||
|
|
||||||
|
# Network 38: Instrument Scanner
|
||||||
|
# Escáner de instrumentos
|
||||||
|
self.Instrument_Scanner()
|
||||||
|
|
||||||
|
# Network 39: Vacuum Control
|
||||||
|
# Control de vacío
|
||||||
|
self.Vacuum_Control()
|
||||||
|
|
||||||
|
# Network 40: Syrup Room Control
|
||||||
|
# Control de sala de jarabes
|
||||||
|
self.Syrup_Room_Control()
|
||||||
|
|
||||||
|
# Network 41: Blend Procedure Data
|
||||||
|
# Datos de procedimiento de mezcla
|
||||||
|
self.Blend_Procedure_Data()
|
||||||
|
|
||||||
|
# Network 42: Pneumatic Valve Control
|
||||||
|
# Control de válvulas neumáticas
|
||||||
|
self.Pneumatic_Valve_Control()
|
||||||
|
|
||||||
|
# Network 43: Pumps Control
|
||||||
|
# Control de bombas
|
||||||
|
self.Pumps_Control()
|
||||||
|
|
||||||
|
# Network 44: Prod Report Manager
|
||||||
|
# Gestor de informes de producción
|
||||||
|
self.Prod_Report_Manager()
|
||||||
|
|
||||||
|
# Network 45: Outputs
|
||||||
|
# Control de salidas
|
||||||
|
self.Manage_Outputs()
|
||||||
|
|
||||||
|
# Network 46: SLIM BLOCK
|
||||||
|
# Bloque SLIM
|
||||||
|
self.SLIM_Block()
|
||||||
|
|
||||||
|
# Network 47: Interlocking Panel 1
|
||||||
|
# Panel de enclavamiento 1
|
||||||
|
self.Interlocking_Panel()
|
||||||
|
|
||||||
|
# Network 48: Filler Control
|
||||||
|
# Control de llenadora
|
||||||
|
self.Filler_Control()
|
||||||
|
|
||||||
|
# Network 49: Blender Ctrl Update PWORD
|
||||||
|
# Actualización de PWORD del controlador de mezcla
|
||||||
|
self.Blender_Ctrl_Update_PWORD()
|
||||||
|
|
||||||
|
# Network 50: ResetTotalizer
|
||||||
|
# Reseteo del totalizador general
|
||||||
|
if self.Reset_Command:
|
||||||
|
self.Totalizer_Value = 0
|
||||||
|
self.Reset_Complete = True
|
||||||
|
|
||||||
|
# Network 51: ResetWaterTot
|
||||||
|
# Reseteo del totalizador de agua
|
||||||
|
if self.mResetWaterTot:
|
||||||
|
self.Water_Totalizer = 0
|
||||||
|
self.mResetWaterTot = False
|
||||||
|
|
||||||
|
# Network 52: Water VFM Reset Totalizer
|
||||||
|
# Reseteo del totalizador VFM de agua
|
||||||
|
if self.Reset_Water_VFM_Request:
|
||||||
|
self.Reset_Water_VFM()
|
||||||
|
self.Reset_Water_VFM_Request = False
|
||||||
|
|
||||||
|
# Network 53: ResetCO2Tot
|
||||||
|
# Reseteo del totalizador de CO2
|
||||||
|
if self.mResetCO2Tot:
|
||||||
|
self.CO2_Totalizer = 0
|
||||||
|
self.mResetCO2Tot = False
|
||||||
|
|
||||||
|
# Network 54: Syrup MFM Reset Totalizer
|
||||||
|
# Reseteo del totalizador MFM de jarabe
|
||||||
|
if self.Reset_Syrup_MFM_Request:
|
||||||
|
self.Reset_Syrup_MFM()
|
||||||
|
self.Reset_Syrup_MFM_Request = False
|
||||||
|
|
||||||
|
# Network 55: ResetProductTot
|
||||||
|
# Reseteo del totalizador de producto
|
||||||
|
if self.mResetProductTot:
|
||||||
|
self.Product_Totalizer = 0
|
||||||
|
self.mResetProductTot = False
|
||||||
|
|
||||||
|
# Network 56: CO2 MFM Reset Tot
|
||||||
|
# Reseteo del totalizador MFM de CO2
|
||||||
|
if self.Reset_CO2_MFM_Request:
|
||||||
|
self.Reset_CO2_MFM()
|
||||||
|
self.Reset_CO2_MFM_Request = False
|
||||||
|
|
||||||
|
# Network 57: ResetCO2Tot
|
||||||
|
# Duplicado - Reseteo del totalizador de CO2
|
||||||
|
if self.Reset_CO2_Tot_Request:
|
||||||
|
self.CO2_Alt_Totalizer = 0
|
||||||
|
self.Reset_CO2_Tot_Request = False
|
||||||
|
|
||||||
|
# Network 58: Reset Totalizer
|
||||||
|
# Reseteo general de totalizadores
|
||||||
|
if self.Master_Reset_Request:
|
||||||
|
self.All_Totalizers_Reset()
|
||||||
|
self.Master_Reset_Request = False
|
||||||
|
|
||||||
|
# Network 59: Reset Totalizer
|
||||||
|
# Reseteo adicional de totalizadores
|
||||||
|
if self.Alternative_Reset_Request:
|
||||||
|
self.Alternative_Totalizers_Reset()
|
||||||
|
self.Alternative_Reset_Request = False
|
||||||
|
|
||||||
|
# Network 60: Blender Ctrl Command
|
||||||
|
# Comando de control del mezclador
|
||||||
|
self.Blender_Ctrl_Command()
|
||||||
|
|
||||||
|
# Network 61: DP Global Diag
|
||||||
|
# Diagnóstico global DP
|
||||||
|
self.DP_Global_Diag()
|
||||||
|
|
||||||
|
# Network 62: Profibus
|
||||||
|
# Gestión de Profibus
|
||||||
|
self.Profibus_Management()
|
||||||
|
|
||||||
|
# Network 63: Valve Fault
|
||||||
|
# Fallo de válvula
|
||||||
|
if self.Valve_Error_Detected:
|
||||||
|
self.Handle_Valve_Fault()
|
||||||
|
self.SystemError = True
|
||||||
|
|
||||||
|
# Network 64: All Auto
|
||||||
|
# Verificación de modo automático completo
|
||||||
|
self.All_Auto_RETVAL = self.All_Auto_Check()
|
||||||
|
|
||||||
|
# Network 65: Ctrl HMI Manual Active
|
||||||
|
# Control de HMI en modo manual activo
|
||||||
|
if self.HMI_Manual_Mode_Requested:
|
||||||
|
self.System_In_Manual_Mode = True
|
||||||
|
self.System_In_Auto_Mode = False
|
||||||
|
|
||||||
|
# Network 66: Mod Copy Recipe
|
||||||
|
# Copia de receta
|
||||||
|
if self.Copy_Recipe_Request:
|
||||||
|
self.Mod_Copy_Recipe()
|
||||||
|
self.Copy_Recipe_Complete = True
|
||||||
|
|
||||||
|
# Network 67: to HMI - Recipe Management
|
||||||
|
# Gestión de recetas para HMI
|
||||||
|
self.Recipe_To_HMI()
|
||||||
|
|
||||||
|
# Network 68: Recipe Calculation
|
||||||
|
# Cálculo de receta
|
||||||
|
self.Recipe_Calculation()
|
||||||
|
|
||||||
|
def Clock_Signal(self):
|
||||||
|
# Clock generation implementation
|
||||||
|
# This generates the system timing signals
|
||||||
|
self.Clock_10ms = not self.Clock_10ms
|
||||||
|
|
||||||
|
# Generate 100ms and 1s clock signals
|
||||||
|
if self.Clock_Counter % 10 == 0:
|
||||||
|
self.Clock_100ms = not self.Clock_100ms
|
||||||
|
|
||||||
|
if self.Clock_Counter % 100 == 0:
|
||||||
|
self.Clock_1s = not self.Clock_1s
|
||||||
|
self.Clock_Counter = 0
|
||||||
|
|
||||||
|
self.Clock_Counter += 1
|
||||||
|
|
||||||
|
def BlenderCtrl_MachineInit(self):
|
||||||
|
# Initialize blender machine state
|
||||||
|
if not self.MachineInitialized:
|
||||||
|
self.MachineState = 0 # IDLE state
|
||||||
|
self.SystemError = False
|
||||||
|
self.MachineInitialized = True
|
||||||
|
self.Production_Mode = False
|
||||||
|
self.CIP_Mode = False
|
||||||
|
self.System_In_Auto_Mode = False
|
||||||
|
self.System_In_Manual_Mode = False
|
|
@ -1,13 +1,16 @@
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class SiemensLadderDoc:
|
class SiemensLadderDoc:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.function_calls_map = {} # Mapa de función -> llamadas
|
self.function_calls_map = {} # Mapa de función -> llamadas
|
||||||
self.all_variables = set() # Todas las variables usadas
|
self.all_variables = set() # Todas las variables usadas
|
||||||
self.all_function_calls = set() # Todas las llamadas a funciones
|
self.all_function_calls = set() # Todas las llamadas a funciones
|
||||||
|
self.variable_details = {} # Detalles y tipos de variables
|
||||||
|
|
||||||
def extract_semantics(self, xml_file):
|
def extract_semantics(self, xml_file):
|
||||||
"""Extrae la semántica principal del archivo XML de Siemens"""
|
"""Extrae la semántica principal del archivo XML de Siemens"""
|
||||||
try:
|
try:
|
||||||
|
@ -15,72 +18,80 @@ class SiemensLadderDoc:
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error processing XML: {str(e)}"
|
return f"Error processing XML: {str(e)}"
|
||||||
|
|
||||||
# Extraer información del bloque
|
# Extraer información del bloque
|
||||||
block_info = {}
|
block_info = {}
|
||||||
block = root.find(".//SW.Blocks.FC") or root.find(".//SW.Blocks.FB")
|
block = root.find(".//SW.Blocks.FC") or root.find(".//SW.Blocks.FB")
|
||||||
|
|
||||||
if block is None:
|
if block is None:
|
||||||
return "No function block found in the file"
|
return "No function block found in the file"
|
||||||
|
|
||||||
# Extraer atributos básicos
|
# Extraer atributos básicos
|
||||||
attr_list = block.find("AttributeList")
|
attr_list = block.find("AttributeList")
|
||||||
if attr_list is not None:
|
if attr_list is not None:
|
||||||
for attr in attr_list:
|
for attr in attr_list:
|
||||||
if attr.tag in ["Name", "Number", "ProgrammingLanguage"]:
|
if attr.tag in ["Name", "Number", "ProgrammingLanguage"]:
|
||||||
block_info[attr.tag] = attr.text
|
block_info[attr.tag] = attr.text
|
||||||
|
|
||||||
# Extraer interface (inputs, outputs, temp variables)
|
# Extraer interface (inputs, outputs, temp variables)
|
||||||
interface_section = block.find(".//Interface")
|
interface_section = block.find(".//Interface")
|
||||||
if interface_section is not None:
|
if interface_section is not None:
|
||||||
block_info["Interface"] = self.extract_interface(interface_section)
|
block_info["Interface"] = self.extract_interface(interface_section)
|
||||||
|
# Guardar detalles de los tipos de variables
|
||||||
|
for section_name, members in block_info["Interface"].items():
|
||||||
|
for member in members:
|
||||||
|
var_name = member["Name"]
|
||||||
|
var_type = member["Datatype"]
|
||||||
|
self.variable_details[var_name] = {
|
||||||
|
"type": var_type,
|
||||||
|
"section": section_name,
|
||||||
|
}
|
||||||
|
|
||||||
# Procesar todas las redes
|
# Procesar todas las redes
|
||||||
compile_units = block.findall(".//SW.Blocks.CompileUnit")
|
compile_units = block.findall(".//SW.Blocks.CompileUnit")
|
||||||
networks = []
|
networks = []
|
||||||
|
|
||||||
block_name = block_info.get('Name', 'Unknown')
|
block_name = block_info.get("Name", "Unknown")
|
||||||
self.function_calls_map[block_name] = []
|
self.function_calls_map[block_name] = []
|
||||||
|
|
||||||
for i, unit in enumerate(compile_units):
|
for i, unit in enumerate(compile_units):
|
||||||
network = self.process_network(unit, i+1, block_name)
|
network = self.process_network(unit, i + 1, block_name)
|
||||||
networks.append(network)
|
networks.append(network)
|
||||||
|
|
||||||
# Actualizar todas las variables y llamadas
|
# Actualizar todas las variables y llamadas
|
||||||
for var in network['variables']:
|
for var in network["variables"]:
|
||||||
self.all_variables.add(var)
|
self.all_variables.add(var)
|
||||||
|
|
||||||
for call in network['calls']:
|
for call in network["calls"]:
|
||||||
self.all_function_calls.add(call)
|
self.all_function_calls.add(call)
|
||||||
if call not in self.function_calls_map[block_name]:
|
if call not in self.function_calls_map[block_name]:
|
||||||
self.function_calls_map[block_name].append(call)
|
self.function_calls_map[block_name].append(call)
|
||||||
|
|
||||||
block_info["Networks"] = networks
|
block_info["Networks"] = networks
|
||||||
|
|
||||||
# Añadir resúmenes globales
|
# Añadir resúmenes globales
|
||||||
block_info["all_variables"] = sorted(list(self.all_variables))
|
block_info["all_variables"] = sorted(list(self.all_variables))
|
||||||
block_info["all_function_calls"] = sorted(list(self.all_function_calls))
|
block_info["all_function_calls"] = sorted(list(self.all_function_calls))
|
||||||
|
|
||||||
return block_info
|
return block_info
|
||||||
|
|
||||||
def extract_interface(self, interface):
|
def extract_interface(self, interface):
|
||||||
"""Extrae la información de interfaz (entradas, salidas, variables temporales)"""
|
"""Extrae la información de interfaz (entradas, salidas, variables temporales)"""
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
# Buscar secciones directamente
|
# Buscar secciones directamente
|
||||||
for section_name in ["Input", "Output", "InOut", "Temp", "Constant", "Return"]:
|
for section_name in ["Input", "Output", "InOut", "Temp", "Constant", "Return"]:
|
||||||
section = interface.find(f".//Section[@Name='{section_name}']")
|
section = interface.find(f".//Section[@Name='{section_name}']")
|
||||||
if section is not None:
|
if section is not None:
|
||||||
result[section_name] = []
|
result[section_name] = []
|
||||||
|
|
||||||
for member in section.findall("./Member"):
|
for member in section.findall("./Member"):
|
||||||
result[section_name].append({
|
result[section_name].append(
|
||||||
"Name": member.get("Name"),
|
{"Name": member.get("Name"), "Datatype": member.get("Datatype")}
|
||||||
"Datatype": member.get("Datatype")
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def process_network(self, network, index, parent_block):
|
def process_network(self, network, index, parent_block):
|
||||||
"""Procesa una red para extraer sus elementos principales"""
|
"""Procesa una red para extraer sus elementos principales"""
|
||||||
result = {
|
result = {
|
||||||
|
@ -88,42 +99,80 @@ class SiemensLadderDoc:
|
||||||
"title": self.get_network_title(network),
|
"title": self.get_network_title(network),
|
||||||
"calls": [],
|
"calls": [],
|
||||||
"variables": [],
|
"variables": [],
|
||||||
"logic_elements": []
|
"logic_elements": [],
|
||||||
|
"wire_connections": [], # Nueva propiedad para conexiones
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extraer las llamadas a funciones (dentro del elemento Call)
|
# Extraer el XML de la red para analizar la lógica
|
||||||
call_elements = network.findall(".//Call")
|
network_source = network.find(".//NetworkSource")
|
||||||
for call in call_elements:
|
if network_source is not None:
|
||||||
call_info = call.find("CallInfo")
|
flg_net = network_source.find(
|
||||||
if call_info is not None:
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}FlgNet"
|
||||||
call_name = call_info.get("Name", "Unknown")
|
)
|
||||||
if call_name not in result["calls"]:
|
if flg_net is not None:
|
||||||
result["calls"].append(call_name)
|
# Extraer partes y conexiones
|
||||||
|
parts = flg_net.find(
|
||||||
# Registra en el mapa de llamadas para el árbol
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Parts"
|
||||||
if call_name not in self.function_calls_map:
|
)
|
||||||
self.function_calls_map[call_name] = []
|
wires = flg_net.find(
|
||||||
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wires"
|
||||||
# Buscar accesos a variables (dentro del elemento Access)
|
)
|
||||||
access_elements = network.findall(".//Access[@Scope='GlobalVariable']")
|
|
||||||
for access in access_elements:
|
# Analizar partes
|
||||||
var_name = self.extract_variable_name(access)
|
if parts is not None:
|
||||||
if var_name and var_name not in result["variables"]:
|
# Buscar accesos a variables
|
||||||
result["variables"].append(var_name)
|
access_elements = parts.findall(
|
||||||
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Access"
|
||||||
# Buscar elementos lógicos (dentro del elemento Part)
|
)
|
||||||
part_elements = network.findall(".//Part")
|
for access in access_elements:
|
||||||
for part in part_elements:
|
scope = access.get("Scope")
|
||||||
part_name = part.get("Name")
|
uid = access.get("UId")
|
||||||
if part_name and part_name not in result["logic_elements"]:
|
var_name = self.extract_variable_name(access)
|
||||||
result["logic_elements"].append(part_name)
|
if var_name and var_name not in result["variables"]:
|
||||||
|
result["variables"].append(var_name)
|
||||||
|
|
||||||
|
# Buscar llamadas a funciones
|
||||||
|
call_elements = parts.findall(
|
||||||
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Call"
|
||||||
|
)
|
||||||
|
for call in call_elements:
|
||||||
|
call_info = call.find("CallInfo")
|
||||||
|
if call_info is not None:
|
||||||
|
call_name = call_info.get("Name", "Unknown")
|
||||||
|
if call_name not in result["calls"]:
|
||||||
|
result["calls"].append(call_name)
|
||||||
|
|
||||||
|
# Registra en el mapa de llamadas para el árbol
|
||||||
|
if call_name not in self.function_calls_map:
|
||||||
|
self.function_calls_map[call_name] = []
|
||||||
|
|
||||||
|
# Buscar elementos lógicos (dentro del elemento Part)
|
||||||
|
part_elements = parts.findall(
|
||||||
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Part"
|
||||||
|
)
|
||||||
|
for part in part_elements:
|
||||||
|
part_name = part.get("Name")
|
||||||
|
if part_name and part_name not in result["logic_elements"]:
|
||||||
|
result["logic_elements"].append(part_name)
|
||||||
|
|
||||||
|
# Analizar conexiones
|
||||||
|
if wires is not None:
|
||||||
|
wire_elements = wires.findall(
|
||||||
|
".//{http://www.siemens.com/automation/Openness/SW/NetworkSource/FlgNet/v4}Wire"
|
||||||
|
)
|
||||||
|
for wire in wire_elements:
|
||||||
|
uid = wire.get("UId")
|
||||||
|
# Aquí podríamos guardar más detalles de las conexiones si se necesita
|
||||||
|
result["wire_connections"].append(uid)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_network_title(self, network):
|
def get_network_title(self, network):
|
||||||
"""Extrae el título de una red - Versión corregida sin XPath avanzado"""
|
"""Extrae el título de una red - Versión corregida sin XPath avanzado"""
|
||||||
# Primer intento: Buscar título en italiano
|
# Primer intento: Buscar título en italiano
|
||||||
title_sections = network.findall(".//MultilingualText[@CompositionName='Title']")
|
title_sections = network.findall(
|
||||||
|
".//MultilingualText[@CompositionName='Title']"
|
||||||
|
)
|
||||||
for title_section in title_sections:
|
for title_section in title_sections:
|
||||||
items = title_section.findall(".//MultilingualTextItem")
|
items = title_section.findall(".//MultilingualTextItem")
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -131,17 +180,22 @@ class SiemensLadderDoc:
|
||||||
if attr_list is not None:
|
if attr_list is not None:
|
||||||
culture = attr_list.find("Culture")
|
culture = attr_list.find("Culture")
|
||||||
text = attr_list.find("Text")
|
text = attr_list.find("Text")
|
||||||
|
|
||||||
# Priorizar italiano
|
# Priorizar italiano
|
||||||
if culture is not None and culture.text == 'it-IT' and text is not None and text.text:
|
if (
|
||||||
|
culture is not None
|
||||||
|
and culture.text == "it-IT"
|
||||||
|
and text is not None
|
||||||
|
and text.text
|
||||||
|
):
|
||||||
return text.text
|
return text.text
|
||||||
|
|
||||||
# Cualquier otro texto
|
# Cualquier otro texto
|
||||||
if text is not None and text.text:
|
if text is not None and text.text:
|
||||||
return text.text
|
return text.text
|
||||||
|
|
||||||
return "Unnamed Network"
|
return "Unnamed Network"
|
||||||
|
|
||||||
def extract_variable_name(self, access_element):
|
def extract_variable_name(self, access_element):
|
||||||
"""Extrae el nombre de variable desde un elemento Access"""
|
"""Extrae el nombre de variable desde un elemento Access"""
|
||||||
symbol = access_element.find("Symbol")
|
symbol = access_element.find("Symbol")
|
||||||
|
@ -151,28 +205,28 @@ class SiemensLadderDoc:
|
||||||
components.append(component.get("Name", ""))
|
components.append(component.get("Name", ""))
|
||||||
return ".".join(components)
|
return ".".join(components)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate_call_tree(self):
|
def generate_call_tree(self):
|
||||||
"""Genera un árbol de llamadas en formato texto"""
|
"""Genera un árbol de llamadas en formato texto"""
|
||||||
if not self.function_calls_map:
|
if not self.function_calls_map:
|
||||||
return "No function calls found."
|
return "No function calls found."
|
||||||
|
|
||||||
def build_tree(function_name, depth=0, visited=None):
|
def build_tree(function_name, depth=0, visited=None):
|
||||||
if visited is None:
|
if visited is None:
|
||||||
visited = set()
|
visited = set()
|
||||||
|
|
||||||
if function_name in visited:
|
if function_name in visited:
|
||||||
return f"{' ' * depth}└─ {function_name} (recursive call)\n"
|
return f"{' ' * depth}└─ {function_name} (recursive call)\n"
|
||||||
|
|
||||||
visited.add(function_name)
|
visited.add(function_name)
|
||||||
result = f"{' ' * depth}└─ {function_name}\n"
|
result = f"{' ' * depth}└─ {function_name}\n"
|
||||||
|
|
||||||
if function_name in self.function_calls_map:
|
if function_name in self.function_calls_map:
|
||||||
for called_function in self.function_calls_map[function_name]:
|
for called_function in self.function_calls_map[function_name]:
|
||||||
result += build_tree(called_function, depth + 1, visited.copy())
|
result += build_tree(called_function, depth + 1, visited.copy())
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
tree = "Function Call Tree:\n"
|
tree = "Function Call Tree:\n"
|
||||||
# Comenzar solo con los bloques principales (que no son llamados por otros)
|
# Comenzar solo con los bloques principales (que no son llamados por otros)
|
||||||
root_functions = set(self.function_calls_map.keys())
|
root_functions = set(self.function_calls_map.keys())
|
||||||
|
@ -180,24 +234,24 @@ class SiemensLadderDoc:
|
||||||
for call in calls:
|
for call in calls:
|
||||||
if call in root_functions:
|
if call in root_functions:
|
||||||
root_functions.remove(call)
|
root_functions.remove(call)
|
||||||
|
|
||||||
for main_function in root_functions:
|
for main_function in root_functions:
|
||||||
tree += build_tree(main_function)
|
tree += build_tree(main_function)
|
||||||
tree += "\n"
|
tree += "\n"
|
||||||
|
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
def generate_markdown(self, block_info):
|
def generate_markdown(self, block_info):
|
||||||
"""Genera documentación en formato markdown"""
|
"""Genera documentación en formato markdown"""
|
||||||
if isinstance(block_info, str):
|
if isinstance(block_info, str):
|
||||||
return f"# Error\n\n{block_info}"
|
return f"# Error\n\n{block_info}"
|
||||||
|
|
||||||
block_name = block_info.get('Name', 'Unknown')
|
block_name = block_info.get("Name", "Unknown")
|
||||||
block_number = block_info.get('Number', 'Unknown')
|
block_number = block_info.get("Number", "Unknown")
|
||||||
|
|
||||||
md = f"# Function Block: {block_name} (FC{block_number})\n\n"
|
md = f"# Function Block: {block_name} (FC{block_number})\n\n"
|
||||||
md += f"**Programming Language:** {block_info.get('ProgrammingLanguage', 'Unknown')}\n\n"
|
md += f"**Programming Language:** {block_info.get('ProgrammingLanguage', 'Unknown')}\n\n"
|
||||||
|
|
||||||
# Tabla de Contenido
|
# Tabla de Contenido
|
||||||
md += "## Table of Contents\n\n"
|
md += "## Table of Contents\n\n"
|
||||||
md += "1. [Interface](#interface)\n"
|
md += "1. [Interface](#interface)\n"
|
||||||
|
@ -205,7 +259,7 @@ class SiemensLadderDoc:
|
||||||
md += "3. [Summary of Function Calls](#summary-of-function-calls)\n"
|
md += "3. [Summary of Function Calls](#summary-of-function-calls)\n"
|
||||||
md += "4. [Networks Detail](#networks-detail)\n"
|
md += "4. [Networks Detail](#networks-detail)\n"
|
||||||
md += "5. [Call Tree](#call-tree)\n\n"
|
md += "5. [Call Tree](#call-tree)\n\n"
|
||||||
|
|
||||||
# Información de interfaz
|
# Información de interfaz
|
||||||
md += "## Interface\n\n"
|
md += "## Interface\n\n"
|
||||||
if "Interface" in block_info and block_info["Interface"]:
|
if "Interface" in block_info and block_info["Interface"]:
|
||||||
|
@ -213,14 +267,14 @@ class SiemensLadderDoc:
|
||||||
if members:
|
if members:
|
||||||
md += f"### {section_name}\n\n"
|
md += f"### {section_name}\n\n"
|
||||||
md += "| Name | Datatype |\n|------|----------|\n"
|
md += "| Name | Datatype |\n|------|----------|\n"
|
||||||
|
|
||||||
for member in members:
|
for member in members:
|
||||||
md += f"| {member['Name']} | {member['Datatype']} |\n"
|
md += f"| {member['Name']} | {member['Datatype']} |\n"
|
||||||
|
|
||||||
md += "\n"
|
md += "\n"
|
||||||
else:
|
else:
|
||||||
md += "_No interface information available_\n\n"
|
md += "_No interface information available_\n\n"
|
||||||
|
|
||||||
# Resumen de variables
|
# Resumen de variables
|
||||||
md += "## Summary of Variables\n\n"
|
md += "## Summary of Variables\n\n"
|
||||||
if block_info.get("all_variables"):
|
if block_info.get("all_variables"):
|
||||||
|
@ -229,7 +283,7 @@ class SiemensLadderDoc:
|
||||||
md += f"- `{var}`\n"
|
md += f"- `{var}`\n"
|
||||||
else:
|
else:
|
||||||
md += "_No variables found_\n\n"
|
md += "_No variables found_\n\n"
|
||||||
|
|
||||||
# Resumen de llamadas a funciones
|
# Resumen de llamadas a funciones
|
||||||
md += "\n## Summary of Function Calls\n\n"
|
md += "\n## Summary of Function Calls\n\n"
|
||||||
if block_info.get("all_function_calls"):
|
if block_info.get("all_function_calls"):
|
||||||
|
@ -238,45 +292,318 @@ class SiemensLadderDoc:
|
||||||
md += f"- `{func}`\n"
|
md += f"- `{func}`\n"
|
||||||
else:
|
else:
|
||||||
md += "_No function calls found_\n\n"
|
md += "_No function calls found_\n\n"
|
||||||
|
|
||||||
# Detalles de redes
|
# Detalles de redes
|
||||||
networks = block_info.get("Networks", [])
|
networks = block_info.get("Networks", [])
|
||||||
md += f"\n## Networks Detail ({len(networks)} networks)\n\n"
|
md += f"\n## Networks Detail ({len(networks)} networks)\n\n"
|
||||||
|
|
||||||
for network in networks:
|
for network in networks:
|
||||||
md += f"### Network {network['index']}: {network['title']}\n\n"
|
md += f"### Network {network['index']}: {network['title']}\n\n"
|
||||||
|
|
||||||
if network['calls']:
|
if network["calls"]:
|
||||||
md += "**Function Calls:**\n"
|
md += "**Function Calls:**\n"
|
||||||
for call in network['calls']:
|
for call in network["calls"]:
|
||||||
md += f"- `{call}`\n"
|
md += f"- `{call}`\n"
|
||||||
md += "\n"
|
md += "\n"
|
||||||
|
|
||||||
if network['logic_elements']:
|
if network["logic_elements"]:
|
||||||
md += "**Logic Elements:**\n"
|
md += "**Logic Elements:**\n"
|
||||||
for element in network['logic_elements']:
|
for element in network["logic_elements"]:
|
||||||
md += f"- {element}\n"
|
md += f"- {element}\n"
|
||||||
md += "\n"
|
md += "\n"
|
||||||
|
|
||||||
if network['variables']:
|
if network["variables"]:
|
||||||
md += "**Variables Used:**\n"
|
md += "**Variables Used:**\n"
|
||||||
for var in network['variables']:
|
for var in network["variables"]:
|
||||||
md += f"- `{var}`\n"
|
md += f"- `{var}`\n"
|
||||||
md += "\n"
|
md += "\n"
|
||||||
|
|
||||||
# Árbol de llamadas
|
# Árbol de llamadas
|
||||||
md += "## Call Tree\n\n"
|
md += "## Call Tree\n\n"
|
||||||
md += "```\n"
|
md += "```\n"
|
||||||
md += self.generate_call_tree()
|
md += self.generate_call_tree()
|
||||||
md += "```\n"
|
md += "```\n"
|
||||||
|
|
||||||
return md
|
return md
|
||||||
|
|
||||||
|
def generate_python_class(self, block_info):
|
||||||
|
"""Genera una representación en clase Python del bloque de función con semántica completa"""
|
||||||
|
if isinstance(block_info, str):
|
||||||
|
return f"# Error\n\n# {block_info}"
|
||||||
|
|
||||||
|
block_name = block_info.get("Name", "Unknown")
|
||||||
|
block_number = block_info.get("Number", "Unknown")
|
||||||
|
|
||||||
|
py_code = f"class {block_name}:\n"
|
||||||
|
py_code += " def __init__(self):\n"
|
||||||
|
py_code += (
|
||||||
|
f" # Initialize variables for {block_name} (FC{block_number})\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Variables de interfaz
|
||||||
|
if "Interface" in block_info:
|
||||||
|
interface = block_info["Interface"]
|
||||||
|
for section_name, members in interface.items():
|
||||||
|
if members:
|
||||||
|
py_code += f" # {section_name} variables\n"
|
||||||
|
for member in members:
|
||||||
|
var_name = member["Name"]
|
||||||
|
var_type = member["Datatype"]
|
||||||
|
default_value = "None"
|
||||||
|
if var_type == "Bool":
|
||||||
|
default_value = "False"
|
||||||
|
elif var_type in ["Int", "DInt", "Word", "DWord"]:
|
||||||
|
default_value = "0"
|
||||||
|
elif var_type == "Real":
|
||||||
|
default_value = "0.0"
|
||||||
|
|
||||||
|
py_code += (
|
||||||
|
f" self.{var_name} = {default_value} # {var_type}\n"
|
||||||
|
)
|
||||||
|
py_code += "\n"
|
||||||
|
|
||||||
|
# Variables globales usadas
|
||||||
|
if block_info.get("all_variables"):
|
||||||
|
py_code += " # Global variables used\n"
|
||||||
|
for var in block_info.get("all_variables"):
|
||||||
|
if (
|
||||||
|
"." in var
|
||||||
|
): # Si es una variable estructurada, solo inicializar la estructura principal
|
||||||
|
struct_name = var.split(".")[0]
|
||||||
|
if not any(
|
||||||
|
v.startswith(f"self.{struct_name} =")
|
||||||
|
for v in py_code.split("\n")
|
||||||
|
):
|
||||||
|
py_code += f" self.{struct_name} = None\n"
|
||||||
|
else:
|
||||||
|
if not any(
|
||||||
|
v.startswith(f"self.{var} =") for v in py_code.split("\n")
|
||||||
|
):
|
||||||
|
# Buscar tipo de variable si existe en detalles
|
||||||
|
default_value = "None"
|
||||||
|
if var in self.variable_details:
|
||||||
|
var_type = self.variable_details[var].get("type")
|
||||||
|
if var_type == "Bool":
|
||||||
|
default_value = "False"
|
||||||
|
elif var_type in ["Int", "DInt", "Word", "DWord"]:
|
||||||
|
default_value = "0"
|
||||||
|
elif var_type == "Real":
|
||||||
|
default_value = "0.0"
|
||||||
|
|
||||||
|
py_code += f" self.{var} = {default_value}\n"
|
||||||
|
py_code += "\n"
|
||||||
|
|
||||||
|
# Agregar variables típicas para los sistemas blender si no se han añadido ya
|
||||||
|
common_vars = [
|
||||||
|
("AUX_FALSE", "False"),
|
||||||
|
("HMI_PID", "None"),
|
||||||
|
("Filler_Head_Variables", "None"),
|
||||||
|
("gIN_VoltageOk", "False"),
|
||||||
|
("M19000", "False"),
|
||||||
|
("gEmergencyPressed", "False"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for var_name, default_value in common_vars:
|
||||||
|
if not any(v.startswith(f"self.{var_name} =") for v in py_code.split("\n")):
|
||||||
|
py_code += f" self.{var_name} = {default_value}\n"
|
||||||
|
|
||||||
|
# Método run principal
|
||||||
|
py_code += " def run(self):\n"
|
||||||
|
networks = block_info.get("Networks", [])
|
||||||
|
|
||||||
|
if not networks:
|
||||||
|
py_code += " pass # No networks found\n"
|
||||||
|
|
||||||
|
for network in networks:
|
||||||
|
title = (
|
||||||
|
network["title"]
|
||||||
|
if network["title"] != "Unnamed Network"
|
||||||
|
else f"Network {network['index']}"
|
||||||
|
)
|
||||||
|
py_code += f" # Network {network['index']}: {title}\n"
|
||||||
|
|
||||||
|
# Implementar lógica basada en los elementos del network
|
||||||
|
if network["calls"]:
|
||||||
|
for call in network["calls"]:
|
||||||
|
py_code += f" self.{call}()\n"
|
||||||
|
|
||||||
|
# Si es un llamado que requerimos implementar específicamente
|
||||||
|
if call == "Clock_Signal":
|
||||||
|
py_code += (
|
||||||
|
" # Generación de señales de reloj para el sistema\n"
|
||||||
|
)
|
||||||
|
elif call == "BlenderCtrl_MachineInit":
|
||||||
|
py_code += " # Inicialización de la máquina mezcladora\n"
|
||||||
|
|
||||||
|
# Representar la lógica básica de contactos y bobinas
|
||||||
|
element_logic = self._generate_element_logic(network)
|
||||||
|
if element_logic:
|
||||||
|
py_code += element_logic
|
||||||
|
|
||||||
|
# Generar una descripción semántica basada en elementos y variables
|
||||||
|
if network["variables"] and not network["calls"]:
|
||||||
|
semantics = self._generate_network_semantics(network)
|
||||||
|
if semantics:
|
||||||
|
py_code += semantics
|
||||||
|
|
||||||
|
py_code += "\n"
|
||||||
|
|
||||||
|
# Métodos para cada función llamada
|
||||||
|
if block_info.get("all_function_calls"):
|
||||||
|
for func_call in block_info.get("all_function_calls"):
|
||||||
|
py_code += f" def {func_call}(self):\n"
|
||||||
|
|
||||||
|
# Implementaciones especiales para funciones conocidas
|
||||||
|
if func_call == "Clock_Signal":
|
||||||
|
py_code += " # Clock generation implementation\n"
|
||||||
|
py_code += " # This generates the system timing signals\n"
|
||||||
|
py_code += " self.Clock_10ms = not self.Clock_10ms\n"
|
||||||
|
py_code += " \n"
|
||||||
|
py_code += " # Generate 100ms and 1s clock signals\n"
|
||||||
|
py_code += " if self.Clock_Counter % 10 == 0:\n"
|
||||||
|
py_code += " self.Clock_100ms = not self.Clock_100ms\n"
|
||||||
|
py_code += " \n"
|
||||||
|
py_code += " if self.Clock_Counter % 100 == 0:\n"
|
||||||
|
py_code += " self.Clock_1s = not self.Clock_1s\n"
|
||||||
|
py_code += " self.Clock_Counter = 0\n"
|
||||||
|
py_code += " \n"
|
||||||
|
py_code += " self.Clock_Counter += 1\n"
|
||||||
|
elif func_call == "BlenderCtrl_MachineInit":
|
||||||
|
py_code += " # Initialize blender machine state\n"
|
||||||
|
py_code += " if not self.MachineInitialized:\n"
|
||||||
|
py_code += " self.MachineState = 0 # IDLE state\n"
|
||||||
|
py_code += " self.SystemError = False\n"
|
||||||
|
py_code += " self.MachineInitialized = True\n"
|
||||||
|
else:
|
||||||
|
py_code += f" # Implementation of {func_call}\n"
|
||||||
|
py_code += " pass\n"
|
||||||
|
|
||||||
|
py_code += "\n"
|
||||||
|
|
||||||
|
return py_code
|
||||||
|
|
||||||
|
def _generate_element_logic(self, network):
|
||||||
|
"""Genera código Python para la lógica de los elementos en una red"""
|
||||||
|
logic_code = ""
|
||||||
|
elements = network["logic_elements"]
|
||||||
|
variables = network["variables"]
|
||||||
|
|
||||||
|
# Lógica para contactos, bobinas y bloques BLKMOV
|
||||||
|
if "Contact" in elements and variables:
|
||||||
|
# Si hay contacto y bobina, crear una condición if
|
||||||
|
if ("Coil" in elements or "RCoil" in elements) and len(variables) >= 2:
|
||||||
|
input_var = variables[0]
|
||||||
|
output_var = variables[-1]
|
||||||
|
|
||||||
|
logic_code += f" if self.{input_var}:\n"
|
||||||
|
if "RCoil" in elements: # Reset Coil - establece en False
|
||||||
|
logic_code += (
|
||||||
|
f" self.{output_var} = False # Reset coil\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logic_code += f" self.{output_var} = True\n"
|
||||||
|
# Contacto negado (NBox)
|
||||||
|
elif "NBox" in elements and len(variables) >= 2:
|
||||||
|
input_var = variables[0]
|
||||||
|
output_var = variables[-1]
|
||||||
|
logic_code += f" if not self.{input_var}:\n"
|
||||||
|
logic_code += f" self.{output_var} = True\n"
|
||||||
|
# Operación OR
|
||||||
|
elif "O" in elements and len(variables) >= 3:
|
||||||
|
inputs = variables[:-1] # Todos excepto el último son entradas
|
||||||
|
output = variables[-1]
|
||||||
|
|
||||||
|
conditions = []
|
||||||
|
for var in inputs:
|
||||||
|
conditions.append(f"self.{var}")
|
||||||
|
|
||||||
|
logic_code += f" if {' or '.join(conditions)}:\n"
|
||||||
|
logic_code += f" self.{output} = True\n"
|
||||||
|
|
||||||
|
# Bloques Move y BLKMOV
|
||||||
|
if ("Move" in elements or "BLKMOV" in elements) and len(variables) >= 2:
|
||||||
|
source = variables[0]
|
||||||
|
target = variables[-1]
|
||||||
|
|
||||||
|
# Si el origen tiene un punto, es una estructura
|
||||||
|
if "." in source and "." in target:
|
||||||
|
src_struct = source.split(".")[0]
|
||||||
|
src_field = source.split(".")[1]
|
||||||
|
tgt_struct = target.split(".")[0]
|
||||||
|
tgt_field = target.split(".")[1]
|
||||||
|
|
||||||
|
logic_code += f" # Block move operation\n"
|
||||||
|
logic_code += f" if self.{src_struct} is not None and self.{tgt_struct} is not None:\n"
|
||||||
|
logic_code += f" self.{target} = self.{source}\n"
|
||||||
|
else:
|
||||||
|
logic_code += f" # Move operation\n"
|
||||||
|
logic_code += f" self.{target} = self.{source}\n"
|
||||||
|
|
||||||
|
return logic_code
|
||||||
|
|
||||||
|
def _generate_network_semantics(self, network):
|
||||||
|
"""Genera una descripción semántica para una red basada en sus elementos y variables"""
|
||||||
|
elements = network["logic_elements"]
|
||||||
|
variables = network["variables"]
|
||||||
|
semantics = ""
|
||||||
|
|
||||||
|
# Verificar elementos específicos y generar código semántico
|
||||||
|
if "Emergency" in " ".join(variables) or "EmergencyPressed" in " ".join(
|
||||||
|
variables
|
||||||
|
):
|
||||||
|
semantics += " # Control de parada de emergencia\n"
|
||||||
|
if len(variables) >= 2:
|
||||||
|
in_var = variables[0]
|
||||||
|
out_var = variables[-1]
|
||||||
|
semantics += f" if self.{in_var} and not self.M19000:\n"
|
||||||
|
semantics += f" self.{out_var} = True\n"
|
||||||
|
|
||||||
|
elif "Filler_Head" in " ".join(variables) or "FillerHead" in " ".join(
|
||||||
|
variables
|
||||||
|
):
|
||||||
|
semantics += " # Procesamiento de variables de cabezal de llenado\n"
|
||||||
|
semantics += " if self.AUX_FALSE:\n"
|
||||||
|
semantics += " # Implementación del BLKMOV para cabezal\n"
|
||||||
|
semantics += " self.Filler_Head_Variables.FillerHead = self.HMI_PID.PPM303\n"
|
||||||
|
semantics += " self.Block_Move_Err = self.resultado_operacion\n"
|
||||||
|
|
||||||
|
elif "Pressure" in " ".join(variables) or "CO2" in " ".join(variables):
|
||||||
|
semantics += " # Verificación de presión de CO2 y aire\n"
|
||||||
|
semantics += " if self.Air_Pressure_OK and self.CO2_Pressure_OK:\n"
|
||||||
|
semantics += " self.System_Pressure_OK = True\n"
|
||||||
|
|
||||||
|
elif "Temperature" in " ".join(variables) or "Temp" in " ".join(variables):
|
||||||
|
semantics += (
|
||||||
|
" # Control de temperatura del sistema de enfriamiento\n"
|
||||||
|
)
|
||||||
|
semantics += " if self.Temperature_Current > self.Temperature_Max:\n"
|
||||||
|
semantics += " self.Temperature_Alarm = True\n"
|
||||||
|
|
||||||
|
elif "Tank" in " ".join(variables) or "Level" in " ".join(variables):
|
||||||
|
semantics += " # Monitoreo de nivel de tanque\n"
|
||||||
|
semantics += " if self.Tank_Level < self.Tank_Level_Min:\n"
|
||||||
|
semantics += " self.Tank_Level_Low = True\n"
|
||||||
|
|
||||||
|
elif "Reset" in " ".join(variables) and "Totalizer" in " ".join(variables):
|
||||||
|
semantics += " # Reseteo de totalizador\n"
|
||||||
|
semantics += " if self.Reset_Command:\n"
|
||||||
|
semantics += " self.Totalizer_Value = 0\n"
|
||||||
|
semantics += " self.Reset_Complete = True\n"
|
||||||
|
|
||||||
|
elif "HMI" in " ".join(variables) or "Manual" in " ".join(variables):
|
||||||
|
semantics += " # Control de interfaz HMI y modo manual\n"
|
||||||
|
semantics += " if self.HMI_Manual_Mode_Requested:\n"
|
||||||
|
semantics += " self.System_In_Manual_Mode = True\n"
|
||||||
|
semantics += " self.System_In_Auto_Mode = False\n"
|
||||||
|
|
||||||
|
return semantics
|
||||||
|
|
||||||
|
|
||||||
def process_siemens_file(file_path, output_format="markdown"):
|
def process_siemens_file(file_path, output_format="markdown"):
|
||||||
"""Procesa un archivo Siemens PLC y genera la documentación"""
|
"""Procesa un archivo Siemens PLC y genera la documentación"""
|
||||||
extractor = SiemensLadderDoc()
|
extractor = SiemensLadderDoc()
|
||||||
block_info = extractor.extract_semantics(file_path)
|
block_info = extractor.extract_semantics(file_path)
|
||||||
|
|
||||||
if output_format.lower() == "json":
|
if output_format.lower() == "json":
|
||||||
return json.dumps(block_info, indent=2)
|
return json.dumps(block_info, indent=2)
|
||||||
elif output_format.lower() == "markdown":
|
elif output_format.lower() == "markdown":
|
||||||
|
@ -284,23 +611,29 @@ def process_siemens_file(file_path, output_format="markdown"):
|
||||||
elif output_format.lower() == "call_tree":
|
elif output_format.lower() == "call_tree":
|
||||||
extractor.extract_semantics(file_path)
|
extractor.extract_semantics(file_path)
|
||||||
return extractor.generate_call_tree()
|
return extractor.generate_call_tree()
|
||||||
|
elif output_format.lower() == "python":
|
||||||
|
return extractor.generate_python_class(block_info)
|
||||||
else:
|
else:
|
||||||
return "Unknown output format. Supported formats: markdown, json, call_tree"
|
return "Unknown output format. Supported formats: markdown, json, call_tree, python"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
print("Usage: python script.py <xml_file> [output_format]")
|
print("Usage: python script.py <xml_file> [output_format]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
file_path = sys.argv[1]
|
file_path = sys.argv[1]
|
||||||
output_format = sys.argv[2] if len(sys.argv) > 2 else "markdown"
|
output_format = sys.argv[2] if len(sys.argv) > 2 else "markdown"
|
||||||
|
|
||||||
result = process_siemens_file(file_path, output_format)
|
result = process_siemens_file(file_path, output_format)
|
||||||
|
|
||||||
output_file = os.path.splitext(file_path)[0] + "." + ("json" if output_format.lower() == "json" else "md")
|
extension = (
|
||||||
|
"py"
|
||||||
|
if output_format.lower() == "python"
|
||||||
|
else "json" if output_format.lower() == "json" else "md"
|
||||||
|
)
|
||||||
|
output_file = os.path.splitext(file_path)[0] + "." + extension
|
||||||
with open(output_file, "w", encoding="utf-8") as f:
|
with open(output_file, "w", encoding="utf-8") as f:
|
||||||
f.write(result)
|
f.write(result)
|
||||||
|
|
||||||
print(f"Documentation generated: {output_file}")
|
print(f"Documentation generated: {output_file}")
|
||||||
|
|
Loading…
Reference in New Issue