ai-station/workspaces/admin/Idecon_Process new.st

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);