Fix: RAg version

This commit is contained in:
DFFM-maker 2025-12-26 10:11:06 +01:00
parent f6952e980c
commit ed3708f42a
5 changed files with 669 additions and 50 deletions

View File

@ -0,0 +1,310 @@
VAR Internal
Idecon FB_IDECON_Client False False
Enable BOOL True False False
MsgFilter UINT 17 False False
fbTcpConnect SktTCPConnect False False
fbSocketSend SktTCPSend False False
fbSocketRcv SktTCPRcv False False
fbClose SktClose False False
Config _sSOCKET_ADDRESS (PortNo := 50000, IpAdr := '172.16.224.200') False False
SocketID_Int _sSOCKET False False
tmpIpAddress STRING[256] False False
tmpPort UINT False False
Connected BOOL False False
bConnectDone BOOL False False
bConnectBusy BOOL False False
bConnectError BOOL False False
wConnectErrorID WORD False False
bSendDone BOOL False False
bSendBusy BOOL False False
bSendError BOOL False False
wSendErrorID WORD False False
bRcvDone BOOL False False
bRcvBusy BOOL False False
bRcvError BOOL False False
wRcvErrorID WORD False False
RxChunk ARRAY[0..511] OF BYTE False False
RxChunkLen UINT False False
RxChunkValid BOOL False False
RcvTimeout UINT 50 False False
TxConsumed BOOL False False
prevTxOutValid BOOL False False
sendTrigger BOOL False False
Command STRING[255] False False
CommandTrigger BOOL False False
prevCommandTrigger BOOL False False
tonPollingSTATSV TON False False
tonPollingSTATP TON False False
prevPollSVQ BOOL False False
prevPollPQ BOOL False False
pollResetSV BOOL False False
pollResetP BOOL False False
prevLastMessage STRING[512] False False
newMsg BOOL False False
newWeightMsg BOOL False False
prevWeightMsg BOOL False False
totalRejected DINT False False
TxChunk ARRAY[0..511] OF BYTE False False
TxChunkLen UINT False False
iTx UINT False False
TxBuffer ARRAY[0..511] OF BYTE False False
tmpWeightData Idecon_WeightData False False
tmpStatPData Idecon_StatPData False False
rTrigRcv R_TRIG False False
bExecuteRcv BOOL False False
iRcvState INT False False
END VAR
VAR External
G_Idecon_Config False
G_System_Error False
G_ProgressiveID False
G_OPCUA_Idecon False
G_Idecon_StatP False
G_Idecon_LastWeight False
G_Idecon_LastSTATSV False
G_Idecon_WorkMode False
G_Idecon_BatchCode False
G_Idecon_ProductionOrder False
END VAR
// ---------- Program ProcessIdecon---------------//
IF NOT Enable THEN
Connected := FALSE;
RxChunkLen := 0;
RxChunkValid := FALSE;
TxConsumed := FALSE;
Command := '';
CommandTrigger := FALSE;
prevCommandTrigger := FALSE;
prevTxOutValid := FALSE;
pollResetSV := FALSE;
pollResetP := FALSE;
END_IF;
tmpIpAddress := Config.IpAdr;
tmpPort := Config.PortNo;
fbTcpConnect(
Execute := (Enable AND NOT Connected),
DstAdr := tmpIpAddress, // Passiamo la variabile semplice (FIX per "Member of structure")
DstTcpPort := tmpPort, // Passiamo la variabile semplice
SrcTcpPort := 0, // (Opzionale) 0 = Assegnazione automatica porta locale
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;
// ---------------------------------------------------------
// MACCHINA A STATI "INFINITY LOOP" (Per lettura continua)
// ---------------------------------------------------------
CASE iRcvState OF
0: // STATO IDLE: Pronti a ricevere?
// Se siamo connessi e il blocco non è occupato o in errore...
IF Connected AND NOT bRcvBusy AND NOT bRcvError THEN
bExecuteRcv := TRUE; // ALZA IL FRONTE DI SALITA
iRcvState := 10; // Passa in attesa
END_IF;
10: // STATO WAIT: Aspetta i dati
IF bRcvDone THEN
// Dati ricevuti! Andiamo a resettare
iRcvState := 20;
ELSIF bRcvError OR NOT Connected THEN
// Se cade la connessione o c'è errore socket
iRcvState := 99;
END_IF;
20: // STATO RESET (FONDAMENTALE PER LEGGERE IL SUCCESSIVO)
bExecuteRcv := FALSE; // Abbassa Execute
// Aspettiamo che il blocco confermi di essersi spento (Done deve tornare False)
// Senza questo check, il riarmo è troppo veloce e fallisce!
IF NOT bRcvDone AND NOT bRcvBusy THEN
iRcvState := 0; // Torna all'inizio per leggere il prossimo
END_IF;
99: // STATO ERRORE
bExecuteRcv := FALSE;
// Se l'errore sparisce o la connessione cade, resetta la macchina
IF NOT bRcvError THEN
iRcvState := 0;
END_IF;
END_CASE;
// --- Chiamata al Blocco (Fuori dal CASE) ---
fbSocketRcv(
Execute := bExecuteRcv,
Socket := SocketID_Int,
TimeOut := 1, //RcvTimeout, // Assicurati sia basso (es. 10 o 50 ms)
Size := UINT#512,
RcvDat := RxChunk[0],
Done => bRcvDone,
Busy => bRcvBusy,
Error => bRcvError,
ErrorID => wRcvErrorID,
RcvSize => RxChunkLen
);
rTrigRcv(CLK := bRcvDone);
RxChunkValid := rTrigRcv.Q AND (RxChunkLen > 0);
// Gestione disconnessione su errore grave
IF bRcvError THEN
Connected := FALSE;
iRcvState := 99;
END_IF;
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;
CommandTrigger := 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;
Idecon(
Enable := Enable,
MsgFilter := MsgFilter,
RxIn := RxChunk,
RxInLen := RxChunkLen,
RxInValid := RxChunkValid,
TxConsumed := TxConsumed,
Command := Command,
CommandTrigger := CommandTrigger
);
tmpWeightData := Idecon.Weight;
tmpStatPData := Idecon.StatP;
// Gestione Messaggi
newMsg := (Idecon.LastMessage <> prevLastMessage);
prevLastMessage := Idecon.LastMessage;
newWeightMsg := newMsg AND IDECON_StartsWith(Idecon.LastMessage, 'WEIGHT=');
IF newWeightMsg AND NOT prevWeightMsg THEN
G_ProgressiveID := G_ProgressiveID + 1;
END_IF;
prevWeightMsg := newWeightMsg;
G_Idecon_LastSTATSV := Idecon.LastSTATSV;
IF tmpWeightData.Valid THEN
G_Idecon_LastWeight.DateTimeText := tmpWeightData.DateTimeText;
G_Idecon_LastWeight.ProductionOrder := tmpWeightData.ProductionOrder;
G_Idecon_LastWeight.BatchCode := tmpWeightData.BatchCode;
G_Idecon_LastWeight.RecipeName := tmpWeightData.RecipeName;
G_Idecon_LastWeight.LineCode := tmpWeightData.LineCode;
G_Idecon_LastWeight.SerialNumber := tmpWeightData.SerialNumber;
G_Idecon_LastWeight.WeightMg := tmpWeightData.WeightMg;
G_Idecon_LastWeight.DeltaToNominalMg:= tmpWeightData.DeltaToNominalMg;
G_Idecon_LastWeight.Classification := tmpWeightData.Classification;
G_Idecon_LastWeight.Valid := TRUE;
END_IF;
// --- E anche qui usiamo tmpStatPData ---
IF tmpStatPData.Valid THEN
G_Idecon_StatP.TotalProducts := tmpStatPData.TotalProducts;
G_Idecon_StatP.TotalAccepted := tmpStatPData.TotalAccepted;
G_Idecon_StatP.RejectedMinus := tmpStatPData.RejectedMinus;
G_Idecon_StatP.RejectedMinusMinus := tmpStatPData.RejectedMinusMinus;
G_Idecon_StatP.RejectedPlus := tmpStatPData.RejectedPlus;
G_Idecon_StatP.RejectedPlusPlus := tmpStatPData.RejectedPlusPlus;
G_Idecon_StatP.CannotBeWeighed := tmpStatPData.CannotBeWeighed;
G_Idecon_StatP.MetalCategory := tmpStatPData.MetalCategory;
G_Idecon_StatP.Valid := TRUE;
END_IF;
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;
// OPC UA Mapping
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));
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;
G_OPCUA_Idecon.LastWeight.Valid := G_Idecon_LastWeight.Valid;
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;
// Invio Socket
sendTrigger := (Idecon.TxOutValid AND NOT prevTxOutValid);
prevTxOutValid := Idecon.TxOutValid;
IF sendTrigger THEN
// Copiamo i dati dall'output dell'FB al buffer LOCALE
// Questo evita l'errore "Member of function block instance variable"
TxBuffer := Idecon.TxOut;
END_IF;
fbSocketSend(
Execute := (Enable AND Connected AND sendTrigger),
Socket := SocketID_Int,
SendDat := TxBuffer[0], // --- CORREZIONE 2: Usa buffer locale ---
Size := Idecon.TxOutLen,
Done => bSendDone,
Busy => bSendBusy,
Error => bSendError,
ErrorID => wSendErrorID
);
TxConsumed := bSendDone;
IF bSendError THEN
Connected := FALSE;
END_IF;
fbClose(
Execute := (Enable AND NOT Connected),
Socket := SocketID_Int
);

View File

@ -0,0 +1,310 @@
VAR Internal
Idecon FB_IDECON_Client False False
Enable BOOL True False False
MsgFilter UINT 17 False False
fbTcpConnect SktTCPConnect False False
fbSocketSend SktTCPSend False False
fbSocketRcv SktTCPRcv False False
fbClose SktClose False False
Config _sSOCKET_ADDRESS (PortNo := 50000, IpAdr := '172.16.224.200') False False
SocketID_Int _sSOCKET False False
tmpIpAddress STRING[256] False False
tmpPort UINT False False
Connected BOOL False False
bConnectDone BOOL False False
bConnectBusy BOOL False False
bConnectError BOOL False False
wConnectErrorID WORD False False
bSendDone BOOL False False
bSendBusy BOOL False False
bSendError BOOL False False
wSendErrorID WORD False False
bRcvDone BOOL False False
bRcvBusy BOOL False False
bRcvError BOOL False False
wRcvErrorID WORD False False
RxChunk ARRAY[0..511] OF BYTE False False
RxChunkLen UINT False False
RxChunkValid BOOL False False
RcvTimeout UINT 50 False False
TxConsumed BOOL False False
prevTxOutValid BOOL False False
sendTrigger BOOL False False
Command STRING[255] False False
CommandTrigger BOOL False False
prevCommandTrigger BOOL False False
tonPollingSTATSV TON False False
tonPollingSTATP TON False False
prevPollSVQ BOOL False False
prevPollPQ BOOL False False
pollResetSV BOOL False False
pollResetP BOOL False False
prevLastMessage STRING[512] False False
newMsg BOOL False False
newWeightMsg BOOL False False
prevWeightMsg BOOL False False
totalRejected DINT False False
TxChunk ARRAY[0..511] OF BYTE False False
TxChunkLen UINT False False
iTx UINT False False
TxBuffer ARRAY[0..511] OF BYTE False False
tmpWeightData Idecon_WeightData False False
tmpStatPData Idecon_StatPData False False
rTrigRcv R_TRIG False False
bExecuteRcv BOOL False False
iRcvState INT False False
END VAR
VAR External
G_Idecon_Config False
G_System_Error False
G_ProgressiveID False
G_OPCUA_Idecon False
G_Idecon_StatP False
G_Idecon_LastWeight False
G_Idecon_LastSTATSV False
G_Idecon_WorkMode False
G_Idecon_BatchCode False
G_Idecon_ProductionOrder False
END VAR
// ---------- Program ProcessIdecon---------------//
IF NOT Enable THEN
Connected := FALSE;
RxChunkLen := 0;
RxChunkValid := FALSE;
TxConsumed := FALSE;
Command := '';
CommandTrigger := FALSE;
prevCommandTrigger := FALSE;
prevTxOutValid := FALSE;
pollResetSV := FALSE;
pollResetP := FALSE;
END_IF;
tmpIpAddress := Config.IpAdr;
tmpPort := Config.PortNo;
fbTcpConnect(
Execute := (Enable AND NOT Connected),
DstAdr := tmpIpAddress, // Passiamo la variabile semplice (FIX per "Member of structure")
DstTcpPort := tmpPort, // Passiamo la variabile semplice
SrcTcpPort := 0, // (Opzionale) 0 = Assegnazione automatica porta locale
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;
// ---------------------------------------------------------
// MACCHINA A STATI "INFINITY LOOP" (Per lettura continua)
// ---------------------------------------------------------
CASE iRcvState OF
0: // STATO IDLE: Pronti a ricevere?
// Se siamo connessi e il blocco non è occupato o in errore...
IF Connected AND NOT bRcvBusy AND NOT bRcvError THEN
bExecuteRcv := TRUE; // ALZA IL FRONTE DI SALITA
iRcvState := 10; // Passa in attesa
END_IF;
10: // STATO WAIT: Aspetta i dati
IF bRcvDone THEN
// Dati ricevuti! Andiamo a resettare
iRcvState := 20;
ELSIF bRcvError OR NOT Connected THEN
// Se cade la connessione o c'è errore socket
iRcvState := 99;
END_IF;
20: // STATO RESET (FONDAMENTALE PER LEGGERE IL SUCCESSIVO)
bExecuteRcv := FALSE; // Abbassa Execute
// Aspettiamo che il blocco confermi di essersi spento (Done deve tornare False)
// Senza questo check, il riarmo è troppo veloce e fallisce!
IF NOT bRcvDone AND NOT bRcvBusy THEN
iRcvState := 0; // Torna all'inizio per leggere il prossimo
END_IF;
99: // STATO ERRORE
bExecuteRcv := FALSE;
// Se l'errore sparisce o la connessione cade, resetta la macchina
IF NOT bRcvError THEN
iRcvState := 0;
END_IF;
END_CASE;
// --- Chiamata al Blocco (Fuori dal CASE) ---
fbSocketRcv(
Execute := bExecuteRcv,
Socket := SocketID_Int,
TimeOut := 1, //RcvTimeout, // Assicurati sia basso (es. 10 o 50 ms)
Size := UINT#512,
RcvDat := RxChunk[0],
Done => bRcvDone,
Busy => bRcvBusy,
Error => bRcvError,
ErrorID => wRcvErrorID,
RcvSize => RxChunkLen
);
rTrigRcv(CLK := bRcvDone);
RxChunkValid := rTrigRcv.Q AND (RxChunkLen > 0);
// Gestione disconnessione su errore grave
IF bRcvError THEN
Connected := FALSE;
iRcvState := 99;
END_IF;
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;
CommandTrigger := 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;
Idecon(
Enable := Enable,
MsgFilter := MsgFilter,
RxIn := RxChunk,
RxInLen := RxChunkLen,
RxInValid := RxChunkValid,
TxConsumed := TxConsumed,
Command := Command,
CommandTrigger := CommandTrigger
);
tmpWeightData := Idecon.Weight;
tmpStatPData := Idecon.StatP;
// Gestione Messaggi
newMsg := (Idecon.LastMessage <> prevLastMessage);
prevLastMessage := Idecon.LastMessage;
newWeightMsg := newMsg AND IDECON_StartsWith(Idecon.LastMessage, 'WEIGHT=');
IF newWeightMsg AND NOT prevWeightMsg THEN
G_ProgressiveID := G_ProgressiveID + 1;
END_IF;
prevWeightMsg := newWeightMsg;
G_Idecon_LastSTATSV := Idecon.LastSTATSV;
IF tmpWeightData.Valid THEN
G_Idecon_LastWeight.DateTimeText := tmpWeightData.DateTimeText;
G_Idecon_LastWeight.ProductionOrder := tmpWeightData.ProductionOrder;
G_Idecon_LastWeight.BatchCode := tmpWeightData.BatchCode;
G_Idecon_LastWeight.RecipeName := tmpWeightData.RecipeName;
G_Idecon_LastWeight.LineCode := tmpWeightData.LineCode;
G_Idecon_LastWeight.SerialNumber := tmpWeightData.SerialNumber;
G_Idecon_LastWeight.WeightMg := tmpWeightData.WeightMg;
G_Idecon_LastWeight.DeltaToNominalMg:= tmpWeightData.DeltaToNominalMg;
G_Idecon_LastWeight.Classification := tmpWeightData.Classification;
G_Idecon_LastWeight.Valid := TRUE;
END_IF;
// --- E anche qui usiamo tmpStatPData ---
IF tmpStatPData.Valid THEN
G_Idecon_StatP.TotalProducts := tmpStatPData.TotalProducts;
G_Idecon_StatP.TotalAccepted := tmpStatPData.TotalAccepted;
G_Idecon_StatP.RejectedMinus := tmpStatPData.RejectedMinus;
G_Idecon_StatP.RejectedMinusMinus := tmpStatPData.RejectedMinusMinus;
G_Idecon_StatP.RejectedPlus := tmpStatPData.RejectedPlus;
G_Idecon_StatP.RejectedPlusPlus := tmpStatPData.RejectedPlusPlus;
G_Idecon_StatP.CannotBeWeighed := tmpStatPData.CannotBeWeighed;
G_Idecon_StatP.MetalCategory := tmpStatPData.MetalCategory;
G_Idecon_StatP.Valid := TRUE;
END_IF;
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;
// OPC UA Mapping
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));
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;
G_OPCUA_Idecon.LastWeight.Valid := G_Idecon_LastWeight.Valid;
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;
// Invio Socket
sendTrigger := (Idecon.TxOutValid AND NOT prevTxOutValid);
prevTxOutValid := Idecon.TxOutValid;
IF sendTrigger THEN
// Copiamo i dati dall'output dell'FB al buffer LOCALE
// Questo evita l'errore "Member of function block instance variable"
TxBuffer := Idecon.TxOut;
END_IF;
fbSocketSend(
Execute := (Enable AND Connected AND sendTrigger),
Socket := SocketID_Int,
SendDat := TxBuffer[0], // --- CORREZIONE 2: Usa buffer locale ---
Size := Idecon.TxOutLen,
Done => bSendDone,
Busy => bSendBusy,
Error => bSendError,
ErrorID => wSendErrorID
);
TxConsumed := bSendDone;
IF bSendError THEN
Connected := FALSE;
END_IF;
fbClose(
Execute := (Enable AND NOT Connected),
Socket := SocketID_Int
);

Binary file not shown.

73
app.py
View File

@ -4,10 +4,14 @@ import re
from datetime import datetime from datetime import datetime
import shutil import shutil
import uuid import uuid
import ollama # Import spostato all'inizio per efficienza import ollama
from qdrant_client import QdrantClient from qdrant_client import QdrantClient
from qdrant_client.http.models import PointStruct from qdrant_client.http.models import PointStruct
# --- CONFIGURAZIONE HARD-CODED PER ROMPERE IL BLOCCO 127.0.0.1 ---
OLLAMA_URL = "http://192.168.1.243:11434"
# -----------------------------------------------------------------------------
# Define user roles mapping # Define user roles mapping
USER_ROLES = { USER_ROLES = {
'moglie@esempio.com': 'business', 'moglie@esempio.com': 'business',
@ -45,7 +49,7 @@ async def connect_to_qdrant():
try: try:
client.get_collection(collection_name) client.get_collection(collection_name)
except Exception as e: except Exception:
client.create_collection( client.create_collection(
collection_name=collection_name, collection_name=collection_name,
vectors_config={"size": 768, "distance": "Cosine"} vectors_config={"size": 768, "distance": "Cosine"}
@ -54,31 +58,35 @@ async def connect_to_qdrant():
return client return client
async def get_embeddings(text): async def get_embeddings(text):
# --- CORREZIONE CRITICA --- # --- FIX: Splitto Host e Port per evitare confusione ---
# Inizializza il client usando l'URL completo (come in message), NON 'host=...' client = ollama.Client(host=OLLAMA_URL) # Uso l'URL intero
# Questo evita l'errore "127.0.0.1:porta casuale"
ollama_api_base = os.getenv('OLLAMA_API_BASE', 'http://192.168.1.243:11434')
client = ollama.Client(ollama_api_base)
# Controllo lunghezza testo # Controllo lunghezza testo
if len(text) > 12000: if len(text) > 12000:
text = text[:12000] text = text[:12000]
try:
response = client.embed(model='nomic-embed-text', input=text) response = client.embed(model='nomic-embed-text', input=text)
# Gestione compatibilità risposta (embedding vs embeddings) # Gestione risposta
if 'embeddings' in response: if 'embeddings' in response:
return response['embeddings'][0] return response['embeddings'][0]
return response['embedding'] return response.get('embedding')
except Exception as e:
print(f"Errore Embedding: {e}")
return []
# Nuova funzione per CERCARE nei documenti (RAG)
async def search_qdrant(query_text, user_role): async def search_qdrant(query_text, user_role):
"""Cerca documenti pertinenti su Qdrant""" """Cerca documenti pertinenti su Qdrant"""
try: try:
qdrant_client = await connect_to_qdrant() qdrant_client = await connect_to_qdrant()
query_embedding = await get_embeddings(query_text) query_embedding = await get_embeddings(query_text)
# Cerca i 3 documenti più simili alla domanda # Se non trova embedding (errore connessione), non cercare
if not query_embedding:
return ""
# Cerca i 3 documenti più simili
search_result = qdrant_client.search( search_result = qdrant_client.search(
collection_name="documents", collection_name="documents",
query_vector=query_embedding, query_vector=query_embedding,
@ -86,33 +94,31 @@ async def search_qdrant(query_text, user_role):
) )
contexts = [] contexts = []
# FIX: controllo sicurezza per evitare 'list index out of range'
if search_result:
for hit in search_result: for hit in search_result:
try:
if 'payload' in hit and 'file_name' in hit['payload']: if 'payload' in hit and 'file_name' in hit['payload']:
contexts.append(f"Documento: {hit['payload']['file_name']}") contexts.append(f"Documento: {hit['payload']['file_name']}")
except Exception:
pass
return "\n".join(contexts) return "\n".join(contexts)
except Exception as e: except Exception as e:
print(f"Errore ricerca: {e}") print(f"Errore ricerca Qdrant: {e}")
return "" return ""
@cl.on_chat_start @cl.on_chat_start
async def chat_start(): async def chat_start():
# Set the user's email to a hardcoded value for testing purposes # Hardcode per test
user_email = "admin@esempio.com" user_email = "admin@esempio.com"
# Determine the user's role based on the email
user_role = USER_ROLES.get(user_email, 'guest') user_role = USER_ROLES.get(user_email, 'guest')
# Create workspace directory if it doesn't exist
create_workspace(user_role) create_workspace(user_role)
# Initialize history in the session
cl.user_session.set("history", []) cl.user_session.set("history", [])
# Set the user's role in the session
cl.user_session.set("role", user_role) cl.user_session.set("role", user_role)
# Send a welcome message based on the user's role
if user_role == 'admin': if user_role == 'admin':
await cl.Message(content="Welcome, Admin!").send() await cl.Message(content="Welcome, Admin!").send()
elif user_role == 'engineering': elif user_role == 'engineering':
@ -126,55 +132,50 @@ async def chat_start():
@cl.on_message @cl.on_message
async def message(message): async def message(message):
# Retrieve the user's role from the session
user_role = cl.user_session.get("role", 'guest') user_role = cl.user_session.get("role", 'guest')
if not user_role: if not user_role:
await cl.Message(content="User role not found").send() await cl.Message(content="User role not found").send()
return return
# Initialize the Ollama client
ollama_api_base = os.getenv('OLLAMA_API_BASE', 'http://192.168.1.243:11434')
try: try:
client = ollama.Client(ollama_api_base) # Client Ollama URL Hardcoded
client = ollama.Client(host=OLLAMA_URL)
# Retrieve the history from the session and limit it # History & Sliding Window
history = cl.user_session.get("history", []) history = cl.user_session.get("history", [])
history = limit_history(history) history = limit_history(history)
# --- RAG STEP: Cerca nei documenti prima di chattare --- # --- RAG STEP 1: Cerca nei documenti ---
context_text = await search_qdrant(message.content, user_role) context_text = await search_qdrant(message.content, user_role)
# Se trova documenti, inietta il contesto come "System Message" # Se trova contesto, iniettalo
if context_text: if context_text:
system_prompt = f"Contexto dai documenti:\n{context_text}\n\nRispondi usando questo contesto." system_prompt = f"Contexto dai documenti:\n{context_text}\n\nRispondi usando questo contesto."
history.insert(0, {"role": "system", "content": system_prompt}) history.insert(0, {"role": "system", "content": system_prompt})
# Append the new user message to the history
history.append({"role": "user", "content": message.content}) history.append({"role": "user", "content": message.content})
# Check for Uploads # Gestione Upload e Indexing
if message.elements: if message.elements:
uploaded_files = [] uploaded_files = []
for element in message.elements: for element in message.elements:
try: try:
# Save file to disk
dest_path = os.path.join(WORKSPACES_DIR, user_role, element.name) dest_path = os.path.join(WORKSPACES_DIR, user_role, element.name)
with open(element.path, 'rb') as src, open(dest_path, 'wb') as dst: with open(element.path, 'rb') as src, open(dest_path, 'wb') as dst:
shutil.copyfileobj(src, dst) shutil.copyfileobj(src, dst)
# Indexing on Qdrant if .txt
if element.name.endswith('.txt'): if element.name.endswith('.txt'):
with open(dest_path, 'r') as f: with open(dest_path, 'r') as f:
content = f.read() content = f.read()
# Indexing
embeddings = await get_embeddings(content) embeddings = await get_embeddings(content)
if embeddings:
qdrant_client = await connect_to_qdrant() qdrant_client = await connect_to_qdrant()
point_id = uuid.uuid4() point_id = uuid.uuid4()
point = PointStruct(id=point_id, vector=embeddings, payload={"file_name": element.name}) point = PointStruct(id=point_id, vector=embeddings, payload={"file_name": element.name})
qdrant_client.upsert(collection_name="documents", points=[point]) qdrant_client.upsert(collection_name="documents", points=[point])
await cl.Message(content=f"Documento '{element.name}' indicizzato.").send() await cl.Message(content=f"Documento '{element.name}' indicizzato.").send()
uploaded_files.append(element.name) uploaded_files.append(element.name)
@ -184,10 +185,10 @@ async def message(message):
if uploaded_files: if uploaded_files:
await cl.Message(content=f"Files saved: {', '.join(uploaded_files)}").send() await cl.Message(content=f"Files saved: {', '.join(uploaded_files)}").send()
# Call the model # Chat
response = client.chat(model='qwen2.5-coder:7b', messages=history) response = client.chat(model='qwen2.5-coder:7b', messages=history)
# Extract code blocks # Code Extracting
code_blocks = re.findall(r"```python(.*?)```", response['message']['content'], re.DOTALL) code_blocks = re.findall(r"```python(.*?)```", response['message']['content'], re.DOTALL)
elements = [] elements = []
@ -196,11 +197,9 @@ async def message(message):
file_path = save_code_to_file(code, user_role) file_path = save_code_to_file(code, user_role)
elements.append(cl.File(name=os.path.basename(file_path), path=file_path)) elements.append(cl.File(name=os.path.basename(file_path), path=file_path))
# Append AI response to history
history.append({"role": "assistant", "content": response['message']['content']}) history.append({"role": "assistant", "content": response['message']['content']})
cl.user_session.set("history", history) cl.user_session.set("history", history)
# Send final message
await cl.Message(content=response['message']['content'], elements=elements).send() await cl.Message(content=response['message']['content'], elements=elements).send()
except Exception as e: except Exception as e:

View File

@ -15,7 +15,7 @@ services:
- ai-station-net - ai-station-net
qdrant: qdrant:
image: qdrant/qdrant:v1.0.0 image: qdrant/qdrant:latest
ports: ports:
- "6333:6333" - "6333:6333"
volumes: volumes: