// ============================================================================== // PROGRAMMA: PRG_ProcessIdecon (FIX BUFFER OVERFLOW) // ============================================================================== VAR Internal // Variabili di stato interne iRcvState : INT := 0; iTestState : INT := 0; sCurrentRecipe : STRING[64]; sTestRecipe : STRING[64]; sTempParse : STRING[255]; prevHmiSend : BOOL; // Variabili ponte tmpIpAddress : STRING[256]; tmpPort : UINT; // Variabili temporanee tmpWeightData : Idecon_WeightData; tmpStatPData : Idecon_StatPData; // Logica newMsg : BOOL; newWeightMsg : BOOL; sendTrigger : BOOL; // Timer Riconnessione tonReconnect : TON; bEnableConnect : BOOL; // FIX: Trigger per impulso validità dati rTrigValid : R_TRIG; // Altre Variabili usate nel codice precedente prevPollSVQ : BOOL; prevPollPQ : BOOL; prevTxOutValid : BOOL; prevWeightMsg : BOOL; prevLastMessage : STRING[512]; totalRejected : DINT; END_VAR // --- 1. RESET INIZIALE --- IF NOT Enable THEN Connected := FALSE; iRcvState := 0; iTestState := 0; RxChunkLen := 0; RxChunkValid := FALSE; TxConsumed := FALSE; Command := ''; CommandTrigger := FALSE; // Reset Trigger Comandi Esterni G_Idecon_HMI_Cmd.SendTrigger := FALSE; G_Test_Ejector_Start := FALSE; G_Test_Ejector_Restore := FALSE; prevHmiSend := FALSE; tonReconnect(IN := FALSE); RETURN; END_IF; // --- 2. GESTIONE CONNESSIONE TCP --- tmpIpAddress := Config.IpAdr; tmpPort := Config.PortNo; // Se c'è errore di connessione, attiva timer 5s prima di riprovare tonReconnect(IN := (NOT Connected AND bConnectError), PT := T#5S); // Prova a connettere se: Abilitato E Non Connesso E (Nessun Errore O Timer Scaduto) bEnableConnect := (Enable AND NOT Connected AND NOT bConnectError) OR tonReconnect.Q; fbTcpConnect( Execute := bEnableConnect, DstAdr := tmpIpAddress, DstTcpPort := tmpPort, SrcTcpPort := 0, Socket => SocketID_Int, Done => bConnectDone, Busy => bConnectBusy, Error => bConnectError, ErrorID => wConnectErrorID ); IF bConnectDone THEN Connected := TRUE; END_IF; IF bConnectError THEN Connected := FALSE; END_IF; // --- 3. MACCHINA A STATI RICEZIONE --- IF NOT Connected THEN iRcvState := 0; bExecuteRcv := FALSE; iTestState := 0; END_IF; CASE iRcvState OF 0: // IDLE IF Connected AND NOT bRcvBusy AND NOT bRcvError THEN bExecuteRcv := TRUE; iRcvState := 10; END_IF; 10: // WAIT IF bRcvDone THEN iRcvState := 20; ELSIF bRcvError THEN iRcvState := 20; END_IF; 20: // RESET bExecuteRcv := FALSE; IF NOT bRcvDone AND NOT bRcvBusy THEN iRcvState := 0; END_IF; END_CASE; fbSocketRcv( Execute := bExecuteRcv, Socket := SocketID_Int, TimeOut := 0, // Timeout 0 per massima velocità (Non bloccante) Size := UINT#512, RcvDat := RxChunk[0], Done => bRcvDone, Busy => bRcvBusy, Error => bRcvError, ErrorID => wRcvErrorID, RcvSize => RxChunkLen ); // --- FIX CRITICO BUFFER OVERFLOW --- // Usiamo un Trigger (R_TRIG) per generare un IMPULSO singolo di validità. // Senza questo, RxChunkValid rimarrebbe TRUE per più cicli mentre siamo nello stato 20, // causando l'inserimento multiplo dello stesso pacchetto nel buffer della FB Idecon. rTrigValid(CLK := bRcvDone AND (RxChunkLen > 0) AND NOT bRcvError); RxChunkValid := rTrigValid.Q; // --- 4. GESTIONE PRIORITÀ COMANDI --- CommandTrigger := FALSE; // Logica per recuperare ricetta corrente IF G_Idecon_LastWeight.RecipeName <> '' THEN sCurrentRecipe := G_Idecon_LastWeight.RecipeName; END_IF; IF sCurrentRecipe = '' THEN sCurrentRecipe := 'Dummy'; END_IF; G_Test_Ejector_State := iTestState; CASE iTestState OF 0: // IDLE IF G_Test_Ejector_Start THEN sTestRecipe := sCurrentRecipe; // Passo 1: Leggi config attuale Command := CONCAT('GETFROMRECIPE=', CONCAT(sTestRecipe, '|EJECTOR_1')); CommandTrigger := TRUE; pollResetSV := TRUE; pollResetP := TRUE; tonPollingSTATSV(IN := FALSE); tonPollingSTATP(IN := FALSE); iTestState := 10; G_Test_Ejector_Start := FALSE; END_IF; 10: // WAIT READ (Gestito in Parsing) 15: // READ OK -> DISABILITA Command := CONCAT('ALTERRECIPE=', CONCAT(sTestRecipe, '|EJECTOR_1|0|0')); CommandTrigger := TRUE; iTestState := 20; 20: // TESTING IF G_Test_Ejector_Restore THEN // Passo 3: Ripristina valori salvati Command := CONCAT('ALTERRECIPE=', CONCAT(sTestRecipe, '|EJECTOR_1|')); Command := CONCAT(Command, G_Test_Saved_Offset); Command := CONCAT(Command, '|'); Command := CONCAT(Command, G_Test_Saved_Duration); CommandTrigger := TRUE; G_Test_Ejector_Restore := FALSE; iTestState := 0; END_IF; END_CASE; IF (iTestState = 0) OR (iTestState = 20) THEN // A. COMANDO MANUALE IF G_Idecon_HMI_Cmd.SendTrigger AND NOT prevHmiSend AND NOT CommandTrigger THEN Command := G_Idecon_HMI_Cmd.CmdString; CommandTrigger := TRUE; pollResetSV := TRUE; pollResetP := TRUE; tonPollingSTATSV(IN := FALSE); tonPollingSTATP(IN := FALSE); G_Idecon_HMI_Cmd.SendTrigger := FALSE; END_IF; prevHmiSend := G_Idecon_HMI_Cmd.SendTrigger; // B. POLLING IF NOT CommandTrigger AND (iTestState = 0) AND NOT G_Idecon_HMI_Cmd.SendTrigger THEN tonPollingSTATSV(IN := (Enable AND Connected AND NOT pollResetSV), PT := T#2S); tonPollingSTATP(IN := (Enable AND Connected AND NOT pollResetP), PT := T#5S); pollResetSV := FALSE; pollResetP := FALSE; IF tonPollingSTATSV.Q AND NOT prevPollSVQ THEN Command := 'STATSV'; CommandTrigger := TRUE; pollResetSV := TRUE; ELSIF tonPollingSTATP.Q AND NOT prevPollPQ THEN Command := 'STATREQ'; CommandTrigger := TRUE; pollResetP := TRUE; END_IF; prevPollSVQ := tonPollingSTATSV.Q; prevPollPQ := tonPollingSTATP.Q; END_IF; END_IF; // --- 5. CHIAMATA DRIVER --- Idecon( Enable := Enable, MsgFilter := MsgFilter, RxIn := RxChunk, RxInLen := RxChunkLen, RxInValid := RxChunkValid, // Ora è un impulso sicuro TxConsumed := TxConsumed, Command := Command, CommandTrigger := CommandTrigger ); // --- 6. PARSING RISPOSTE --- newMsg := (Idecon.LastMessage <> prevLastMessage); prevLastMessage := Idecon.LastMessage; IF newMsg THEN sTempParse := Idecon.LastMessage; IF (iTestState = 10) AND IDECON_StartsWith(sTempParse, 'GETFROMRECIPE=') THEN sTempParse := DELETE(sTempParse, 14, 1); IF (IDECON_FieldAt(sTempParse, 1, '|') = sTestRecipe) AND (IDECON_FieldAt(sTempParse, 2, '|') = 'EJECTOR_1') THEN G_Test_Saved_Offset := IDECON_FieldAt(sTempParse, 3, '|'); G_Test_Saved_Duration := IDECON_FieldAt(sTempParse, 4, '|'); iTestState := 15; END_IF; END_IF; newWeightMsg := IDECON_StartsWith(Idecon.LastMessage, 'WEIGHT='); IF newWeightMsg AND NOT prevWeightMsg THEN G_ProgressiveID := G_ProgressiveID + 1; END_IF; prevWeightMsg := newWeightMsg; END_IF; // --- 7. MAPPATURA OUTPUT --- tmpWeightData := Idecon.Weight; tmpStatPData := Idecon.StatP; G_Idecon_LastSTATSV := Idecon.LastSTATSV; IF tmpWeightData.Valid THEN G_Idecon_LastWeight := tmpWeightData; G_Idecon_LastWeight.Valid := TRUE; END_IF; IF tmpStatPData.Valid THEN G_Idecon_StatP := tmpStatPData; G_Idecon_StatP.Valid := TRUE; END_IF; // --- 8. ERRORI E OPC UA (REINSERITO IL PEZZO MANCANTE) --- G_System_Error := bConnectError OR bSendError OR bRcvError OR Idecon.Error; IF G_Idecon_StatP.Valid AND (G_Idecon_StatP.TotalProducts >= G_Idecon_StatP.TotalAccepted) THEN totalRejected := G_Idecon_StatP.TotalProducts - G_Idecon_StatP.TotalAccepted; ELSE totalRejected := 0; END_IF; // Mappatura Struttura OPC UA Completa G_OPCUA_Idecon.Connected := Connected; G_OPCUA_Idecon.WorkMode := G_Idecon_WorkMode; G_OPCUA_Idecon.LastSTATSV := G_Idecon_LastSTATSV; G_OPCUA_Idecon.BatchCode := G_Idecon_BatchCode; G_OPCUA_Idecon.ProductionOrder := G_Idecon_ProductionOrder; G_OPCUA_Idecon.Error := G_System_Error; G_OPCUA_Idecon.ErrorId := UINT_TO_UDINT(WORD_TO_UINT(wConnectErrorID)); // Mappatura Pesata G_OPCUA_Idecon.LastWeight.ProgressiveId := G_ProgressiveID; G_OPCUA_Idecon.LastWeight.TimestampText := G_Idecon_LastWeight.DateTimeText; G_OPCUA_Idecon.LastWeight.ProductionOrder := G_Idecon_LastWeight.ProductionOrder; G_OPCUA_Idecon.LastWeight.BatchCode := G_Idecon_LastWeight.BatchCode; G_OPCUA_Idecon.LastWeight.RecipeName := G_Idecon_LastWeight.RecipeName; G_OPCUA_Idecon.LastWeight.LineCode := G_Idecon_LastWeight.LineCode; G_OPCUA_Idecon.LastWeight.SerialNumber := G_Idecon_LastWeight.SerialNumber; G_OPCUA_Idecon.LastWeight.Weight_g := DINT_TO_LREAL(G_Idecon_LastWeight.WeightMg) / LREAL#1000.0; G_OPCUA_Idecon.LastWeight.Delta_g := DINT_TO_LREAL(G_Idecon_LastWeight.DeltaToNominalMg) / LREAL#1000.0; G_OPCUA_Idecon.LastWeight.Classification := G_Idecon_LastWeight.Classification; G_OPCUA_Idecon.LastWeight.Expelled := FALSE; // Da mappare se c'è un campo espulsione specifico G_OPCUA_Idecon.LastWeight.Valid := G_Idecon_LastWeight.Valid; // Mappatura Statistiche G_OPCUA_Idecon.Stats.TotalProducts := G_Idecon_StatP.TotalProducts; G_OPCUA_Idecon.Stats.TotalAccepted := G_Idecon_StatP.TotalAccepted; G_OPCUA_Idecon.Stats.TotalRejected := totalRejected; G_OPCUA_Idecon.Stats.RejectedPlusPlus := G_Idecon_StatP.RejectedPlusPlus; G_OPCUA_Idecon.Stats.CannotBeWeighed := G_Idecon_StatP.CannotBeWeighed; G_OPCUA_Idecon.Stats.MetalCategory := G_Idecon_StatP.MetalCategory; G_OPCUA_Idecon.Stats.Valid := G_Idecon_StatP.Valid; // --- 9. INVIO SOCKET --- sendTrigger := (Idecon.TxOutValid AND NOT prevTxOutValid); prevTxOutValid := Idecon.TxOutValid; IF sendTrigger THEN TxBuffer := Idecon.TxOut; END_IF; fbSocketSend( Execute := (Enable AND Connected AND sendTrigger), Socket := SocketID_Int, SendDat := TxBuffer[0], Size := Idecon.TxOutLen, Done => bSendDone, Busy => bSendBusy, Error => bSendError, ErrorID => wSendErrorID ); TxConsumed := bSendDone; // Gestione Reset su errore invio (opzionale) IF bSendError THEN // Non disconnettere brutalmente, ma resetta trigger TxConsumed := TRUE; // Sblocca la FB interna END_IF; fbClose(Execute := (Enable AND NOT Connected), Socket := SocketID_Int);