309 lines
11 KiB
Smalltalk
309 lines
11 KiB
Smalltalk
// ==============================================================================
|
|
// 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);
|