Obsidean_VM/04-SIDEL/09 - SAE452 - Diet as Regul.../FB2120 - MasseliTCP/FB2120 - MasseliTCP Read - ...

1088 lines
53 KiB
Markdown

***
Ho creato l'FB2120 per leggere direttamente i valori inviati dai masselli UR62 o UR29 utilizzando il protocollo ADAM tramite un gateway 485 a Ethernet. Ho scelto TCP sul gateway trasparente 485-Ethernet per migliorare la resilienza dei dati. Il protocollo TCP gestisce automaticamente le ritrasmissioni in caso di perdita di un frame, mantenendo l'ordine dei dati. Questa gestione è ideale per la velocità richiesta, poiché Ethernet opera a velocità molto superiori e permette di evitare la perdita di trame in caso di collisioni.
Il sistema di riconnessione gestisce diversi timeout per ristabilire la connessione in varie situazioni. Il valore di TimeBetweenFramesMs è stato creato per misurare la velocità con cui i Masselli inviano i dati. Altri contatori monitorano diversi tipi di errori per valutare l'affidabilità del sistema.
Per potere testare il sistema ho creato un simulatore che riesce a creare via 485 attraverso il gateway le stesse protocollo ADAM e ho testato sul ADAM che ci ha prestato Masselli e funziona correttamente. Una volta collaudata la simulazione dei dati digitali ho creato un server TCP che permette simulare il funzionamento e dal PLC prendere i valori como se ci fosse il Masseli colegato. Con questo setup ho potuto simulare diversi problemi che possono accadere nella comunicazione tra il PLC e il Gateway. Quando arrivi il misuratore dorerebbe essere funzionante.
Ho utilizzato un Gateway Waveshare WIFI 485/Ethernet ([Waveshare Industrial Grade Serial Server RS232/485 to WiFi And Ethernet, Modbus Gateway, MQTT Gateway, Metal Case, Wail-Mount and Rail-Mount Support : Amazon.it: Informatica](https://www.amazon.it/dp/B0CFXX5SKS?ref=ppx_yo2ov_dt_b_fed_asin_title) ), ma in realtà si può usare qualsiasi gateway 485 a Ethernet che diventa un server TCP trasparente. Sul lato PLC, è sufficiente un'istanza di FB/DB per misuratore, utilizzando solo le FB Siemens TCON e TRCV, già impiegate per la FB1. Non è stato necessario utilizzare SFC extra o modificare la configurazione hardware, poiché TCON gestisce la comunicazione senza bisogno di creare il link con NetPro. Per semplicità, ho inserito l'IP del server direttamente nel codice per rendere la chiamata alla FB più pulita.
La FB fornisce stati per gestire problemi di comunicazione e calcola il Brix e la corrente ricevuta digitalmente. Utilizzando questo sistema, si evita la doppia conversione DAC-ADC, riducendo la perdita di risoluzione; questo include la perdita del 25% di risoluzione per il discorso che il ADAM lavora a 0..20mA e solo se utilizzano 4..20mA. La FB legge i valori digitali direttamente dall'uscita del Masselli, senza perdite, e può essere usata in parallelo al ADAM come opzione. Il flusso via 485 può essere letto sia dal ADAM che dal Gateway contemporaneamente, sfruttando la comunicazione unidirezionale.
Video da i test: [Microsoft OneDrive](https://1drv.ms/v/c/129c0e590bbc3a9e/EedcDsWnAwFKkzfMdsEloHoBJoapLe0CjJq9dpG5ch6qzw?e=UpMASB)
Link dal software S7 utlizato per Test: [Microsoft OneDrive](https://1drv.ms/u/c/129c0e590bbc3a9e/ETpiLUQCwNRLn4rQFx8ygkcBDFsQ6HRxXy7o0FGd15FBcA?e=5fld6z)
***
#### La chiamata della FB sarebbe:
![[Pasted image 20250530095053.png]]
![[Pasted image 20250530095111.png]]
# Documentazione del Blocco Funzione: MaselliTCP
---
**Versione:** 1.8 **Descrizione:** Questa FB è progettata per leggere dati da un sensore Maselli tramite una connessione TCP/IP in modo continuo e ottimizzato. Il sensore invia frame di dati con un formato fisso di 12 byte: `#XX12.345CKCR` via RS485, questi dati vengono catturati con un gateway Waveshare che funziona da server TCP trasparente. L'FB gestisce la connessione, la ricezione ottimizzata tramite **burst reading**, il parsing dei dati con validazione ID e checksum, e il calcolo del valore Brix.
## Principali Miglioramenti v1.8 verso v1.3
- **Burst Reading**: Sistema di lettura ottimizzato che riduce il carico di ciclo PLC alternando fasi di lettura intensiva e pause
- **Controllo ID Dispositivo**: Validazione opzionale dell'ID del dispositivo Adam (posizioni 1-2 del frame)
- **Validazione Checksum**: Controllo checksum configurabile per garantire l'integrità dei dati
- **Buffer Ottimizzato**: Buffer di ricezione ridotto da 128 a 23 byte per maggiore efficienza
- **Organizzazione Dati Strutturata**: Uso di strutture per configurazione, statistiche, timing e diagnostica
- **Gestione Errori Avanzata**: Sistema di codici errore con priorità (0-12, maggiore = più critico)
- **Tabelle FIFO**: Storia opzionale degli ultimi 10 valori per debug (newest=[0], oldest=[9])
## Funzionamento di Base
L'FB opera attraverso una macchina a stati principale e una macchina a stati di lettura ottimizzata:
### Stati Principali (iState):
1. **IDLE (0):** Attesa abilitazione con ritardo anti-riconnessione rapida
2. **CONNECT (1):** Preparazione richiesta connessione TCP
3. **WAIT CONNECT (2):** Tentativo di connessione con timeout configurabile
4. **READING (3):** Lettura continua con sistema burst reading ottimizzato
5. **DISCONNECT (4):** Chiusura controllata della connessione
### Stati di Lettura Ottimizzata (iReadingState):
- **ACTIVE_READING (0):** Lettura intensiva durante finestra di ricezione dati
- **COMPLETED (1):** Frame valido processato, calcolo timing per frame successivo
- **PAUSED (2):** Pausa calcolata tra frame per ridurre carico ciclo PLC
Il sistema **burst reading** ottimizza i tempi di ciclo alternando:
- Fasi di lettura intensiva quando sono attesi i dati
- Pause calcolate per ridurre il carico computazionale
- Fase di inizializzazione per sincronizzazione con il sensore
## Parametri di Ingresso (VAR_INPUT)
|Nome|Tipo|Valore Predefinito|Descrizione|
|---|---|---|---|
|`xEnable`|`BOOL`||Abilita (`TRUE`) o disabilita (`FALSE`) la comunicazione con il sensore|
|`aRemoteIP`|`ARRAY[1..4] OF BYTE`|`[10,1,33,100]`|Indirizzo IP del server Gateway Maselli|
|`iRemotePort`|`INT`|`8899`|Porta TCP del server Gateway Maselli|
|`rBrixMax`|`REAL`|`80.0`|Valore Brix massimo che corrisponde a 20mA del segnale|
|`wConnectionID`|`WORD`|`W#16#10`|ID univoco per la connessione TCP (configurato nell'hardware del PLC)|
|`xEnableChecksum`|`BOOL`|`TRUE`|**NUOVO**: Abilita validazione checksum dei frame ricevuti|
|`rFramesPerSecond`|`REAL`|`3.0`|**NUOVO**: Frame attesi per secondo (per calcolo timing ottimizzato)|
|`iConnTimeoutSec`|`INT`|`10`|**NUOVO**: Timeout connessione in secondi|
|`iDataTimeoutSec`|`INT`|`30`|**NUOVO**: Timeout ricezione dati in secondi|
|`iRetryDelaySec`|`INT`|`2`|**NUOVO**: Ritardo tra tentativi di riconnessione|
|`xEnableFifoDebug`|`BOOL`|`FALSE`|**NUOVO**: Abilita tabelle FIFO per debug (ultimi 10 valori)|
|`xDisableBurstRead`|`BOOL`|`FALSE`|**NUOVO**: Disabilita burst reading (TRCV sempre attivo)|
|`iDeviceID`|`INT`|`0`|**NUOVO**: ID dispositivo da validare (0=disabilitato, 1-99=abilitato)|
## Parametri di Uscita (VAR_OUTPUT)
|Nome|Tipo|Descrizione|
|---|---|---|
|`xConnected`|`BOOL`|Indica lo stato della connessione TCP|
|`xDataValid`|`BOOL`|Impulso quando un nuovo set di dati validi è stato ricevuto e parsato|
|`xError`|`BOOL`|`TRUE` se si è verificato un errore|
|`wErrorCode`|`WORD`|Codice di errore legacy (per compatibilità)|
|`iErrorCode`|`INT`|**NUOVO**: Codice errore strutturato (0=OK, maggiore=più critico)|
|`rCurrentMA`|`REAL`|Valore di corrente in milliampere letto dal sensore|
|`rBrixValue`|`REAL`|Valore Brix calcolato|
|`xInitPhaseFinish`|`BOOL`|**NUOVO**: Flag completamento fase inizializzazione|
|`diTimeBetweenFramesMs`|`DINT`|Tempo trascorso tra gli ultimi due frame validi|
|`diReadingState`|`INT`|**NUOVO**: Stato corrente del sistema di lettura (0=Attivo, 1=Completato, 2=Pausa)|
## Strutture Dati Organizzate
### Configurazione (stConfig)
Parametri di configurazione consolidati per migliore organizzazione dei dati del DB.
### Statistiche (stStats)
- `diFrameCount`: Frame totali ricevuti
- `diErrorFrameCount`: Frame con errori di formato
- `diSuccessfulReconCount`: Riconnessioni riuscite
- `diChecksumErrorCount`: **NUOVO**: Errori checksum
- `diLostFramesCount`: **NUOVO**: Frame persi stimati
- `iConsecutiveBadFrames`: **NUOVO**: Frame consecutivi errati
### Timing (stTiming)
- Calcolo automatico intervalli frame basato su `rFramesPerSecond`
- Ottimizzazione dinamica del timing di lettura
- Rilevamento jitter e variazioni timing
### Diagnostica (stDiag)
Stati e informazioni di debug consolidate per facilitare la manutenzione.
## Codici Errore Strutturati (iErrorCode)
**Priorità crescente (0 = nessun errore, 12 = più critico):**
|Codice|Descrizione|
|---|---|
|0|Nessun errore|
|1|Errore checksum|
|2|Formato frame non valido|
|3|Caratteri non numerici|
|4|Dati fuori range (4-20mA)|
|5|Frame errati consecutivi multipli|
|6|Frame persi rilevati|
|7|Buffer ricezione TRCV troppo piccolo|
|8|Errore comunicazione TRCV|
|9|Timeout ricezione dati|
|10|Timeout connessione|
|11|Connessione TCP fallita|
|12|Connessione TCP terminata|
## Tabelle FIFO (Debug Opzionale)
Quando `xEnableFifoDebug = TRUE`:
- `arBrixHistory[0..9]`: Ultimi 10 valori Brix ([0]=più recente, [9]=più vecchio)
- `auiTimeHistory[0..9]`: Ultimi 10 timestamp ([0]=più recente, [9]=più vecchio)
## Note Aggiuntive
---
- **Buffer Ottimizzato**: Buffer di ricezione ridotto da 128 a 23 byte per maggiore efficienza memoria
- **Burst Reading**: Riduce significativamente il carico di ciclo PLC alternando lettura intensiva e pause calcolate
- **Validazione ID**: Controllo opzionale dell'ID dispositivo Adam nelle posizioni 1-2 del frame
- **Checksum**: Validazione hex checksum configurabile per garantire integrità dati
- **Timing Adattivo**: Calcolo automatico degli intervalli di lettura basato sulla frequenza frame configurata
- **Rilevamento Frame Persi**: Stima automatica dei frame persi basata su analisi timing
- **Fase Inizializzazione**: Gestione ottimizzata della sincronizzazione iniziale con il sensore
- **Reset Contatori**: I contatori si resettano automaticamente per prevenire overflow
### Source:
***
```pascal
//******************************************************************************
// Function Block: FB_MaselliTCP
// Description: Enhanced Maselli sensor TCP reader with optimized buffer and FIFO data
// Frame format: #XX12.345CKCR (12 fixed bytes)
//
// | Field | Start (1 char) | Adam address (2 chars) | Brix or Temperature value in mA (XX.YYY: 6 chars) | Checksum (2 chars) | Carriage return (1 char) |
// | :-----: | :------------: | :--------------------: | :-----------------------------------------------: | :----------------: | :----------------------: |
// | Example | # | 01 | 02.145 | CkCk | Cr |
//
// Version: 1.8 - Enhanced with structured data and optimized buffer management
// Platform: Siemens S7-300
//
// IMPROVEMENTS v1.8:
// - Structured data organization for better DB readability
// - Optimized buffer size (23 bytes instead of 128)
// - Enhanced reading state machine with initialization phase
// - Ordered FIFO tables for last 10 readings (newest at [0], oldest at [9])
// - Optional FIFO debug mode (disabled by default)
// - Improved synchronization logic with xInitPhaseFinish flag
// - Limited diAccumCycleMs accumulation only during iState = 3
//
// READING STATES:
// 0 = ACTIVE_READING - Receiving data burst, read intensively
// 1 = COMPLETED - Finished reading a valid frame, return to paused
// 2 = PAUSED - Not reading, waiting for next frame window
//
// FIFO TABLES (when xEnableFifoDebug = TRUE):
// arBrixHistory[0] = newest Brix value, arBrixHistory[9] = oldest
// auiTimeHistory[0] = newest timestamp, auiTimeHistory[9] = oldest
//
// ERROR CODES (iErrorCode) - Higher numbers = Higher priority:
// 0 = No error, 1 = Checksum error, 2 = Invalid frame format
// 3 = Invalid digit characters, 4 = Data out of range (4-20mA)
// 5 = Multiple consecutive bad frames, 6 = Lost frames detected
// 7 = TRCV receive buffer too small, 8 = TRCV communication error
// 9 = Data reception timeout, 10 = Connection timeout
// 11 = TCP connection failed, 12 = TCP connection terminated
//******************************************************************************
FUNCTION_BLOCK MaselliTCP
TITLE = 'Maselli TCP Reader - Enhanced Buffer Management'
VERSION : '1.8'
VAR_INPUT
xEnable : BOOL; // Enable communication
aRemoteIP : ARRAY[1..4] OF BYTE := [10,1,33,100]; // Remote server IP
iRemotePort : INT := 8899; // Server port
rBrixMax : REAL := 80.0; // Maximum Brix value at 20mA
wConnectionID : WORD := W#16#10; // TCP Connection ID
xEnableChecksum : BOOL := TRUE; // Enable checksum validation
rFramesPerSecond : REAL := 3.0; // Expected frames per second
iConnTimeoutSec : INT := 10; // Connection timeout in seconds
iDataTimeoutSec : INT := 30; // Data reception timeout in seconds
iRetryDelaySec : INT := 2; // Delay between reconnection attempts
xEnableFifoDebug : BOOL := FALSE; // Enable FIFO tables update (debug only)
xDisableBurstRead : BOOL := FALSE; // Disable burst reading (TRCV always active in reading state if TRUE)
iDeviceID : INT := 0; // Device ID to validate (0 = disabled, 1-99 = enabled). Checks aFrame[1] and aFrame[2].
END_VAR
VAR_OUTPUT
xConnected : BOOL; // Connection status
xDataValid : BOOL; // Pulse when valid data is available
xError : BOOL; // Active error
wErrorCode : WORD; // Legacy error code (for compatibility)
iErrorCode : INT; // Structured error code (0=OK, higher=more critical)
rCurrentMA : REAL; // Current mA value
rBrixValue : REAL; // Current Brix value
xInitPhaseFinish : BOOL; // Initialization phase completed flag
// Current frame timing
diTimeBetweenFramesMs : DINT; // Time elapsed between last two valid frames
diReadingState : INT; // Current reading state for diagnostics
END_VAR
// ============================================================================
// CONTROL VARIABLES
// ============================================================================
VAR // State Machine Control
iState : INT := 0; // Main state: 0=Idle, 1=Connect, 2=WaitConnect, 3=Reading, 4=Disconnect
iReadingState : INT := 0; // Reading state: 0=Active, 1=Completed, 2=Paused
iCurrentErrorCode : INT := 0; // Current error being evaluated
diAccumCycleMs : DINT := 0; // Accumulated cycle time (only in iState=3)
iConsecutiveGoodFrames : INT := 0; // Counter for consecutive structurally good frames
xFirstScan : BOOL := TRUE;
diInitPhaseTimerMs : DINT := 0; // Timer for initialization phase duration
END_VAR
VAR // Timing Control
tCurrentTime : TIME; // Current system time
tLastTime : TIME; // Previous time
diElapsedMs : DINT; // Elapsed time in ms
xTrcvTimeToCall : BOOL := TRUE; // TRCV call control
END_VAR
VAR // FIFO Tables - Last 10 readings (ordered: [0]=newest, [9]=oldest)
arBrixHistory : ARRAY[0..9] OF REAL; // Last 10 Brix values (newest first)
auiTimeHistory : ARRAY[0..9] OF INT; // Last 10 timestamps in ms (newest first)
END_VAR
// ============================================================================
// STRUCTURED DATA ORGANIZATION
// ============================================================================
VAR // Configuration Structure
stConfig : STRUCT
rFramesPerSecond : REAL := 3.0; // Expected frames per second
iConnTimeoutSec : INT := 10; // Connection timeout
iDataTimeoutSec : INT := 30; // Data timeout
iRetryDelaySec : INT := 2; // Retry delay
rBrixMax : REAL := 80.0; // Maximum Brix at 20mA
xEnableChecksum : BOOL := TRUE; // Checksum validation
iDeviceID : INT := 0; // Device ID to validate
xDisableBurstRead : BOOL; // Disable burst reading
END_STRUCT;
END_VAR
VAR // Statistics Structure
stStats : STRUCT
diFrameCount : DINT; // Total received frames
diErrorFrameCount : DINT; // Frames with format errors
diSuccessfulReconCount : DINT; // Successful reconnections
diChecksumErrorCount : DINT; // Checksum errors
diLostFramesCount : DINT; // Estimated lost frames
iConsecutiveBadFrames : INT; // Consecutive bad frames counter
END_STRUCT;
END_VAR
VAR // Timing Structure
stTiming : STRUCT
diFrameIntervalMs : DINT := 333; // Auto-calculated frame interval
iPreReadOffsetMs : INT := 50; // Pre-read offset
iMaxActiveReadingMs : INT := 200; // Max active reading time
diEstFrameMs : DINT := 1000; // Estimated frame interval
diNextFrameMs : DINT := 0; // Next expected frame time
diReadingStartMs : DINT := 0; // Reading start time
diMinFrameIntervalMs : DINT := DINT#999999; // Minimum observed interval
diMaxFrameIntervalMs : DINT := 0; // Maximum observed interval
diFrameJitterMs : DINT := 0; // Frame jitter
END_STRUCT;
END_VAR
VAR // Diagnostics Structure
stDiag : STRUCT
diState : INT; // Current state for debug
diTconStatus : WORD; // TCON status
diTrcvStatus : WORD; // TRCV status
diNextExpectedMs : DINT; // Next expected frame time
diCalcFrameIntervalMs : DINT; // Calculated frame interval
iCalcPreReadOffsetMs : INT; // Calculated pre-read offset
iCalcMaxActiveMs : INT; // Calculated max active time
rAvgCurrentMA : REAL; // Moving average of current
END_STRUCT;
END_VAR
// ============================================================================
// CONNECTION PARAMETERS
// ============================================================================
VAR // TCP Connection Parameters
stConnParams : STRUCT
block_length : WORD := W#16#40;
id : WORD;
connection_type : BYTE := B#16#11; // TCP
active_est : BOOL := TRUE; // Active client
local_device_id : BYTE := B#16#2; // Integrated PN
local_tsap_id_len : BYTE := B#16#0;
rem_subnet_id_len : BYTE := B#16#0;
rem_staddr_len : BYTE := B#16#4;
rem_tsap_id_len : BYTE := B#16#2;
next_staddr_len : BYTE := B#16#0;
local_tsap_id : ARRAY[1..16] OF BYTE;
rem_subnet_id : ARRAY[1..6] OF BYTE;
rem_staddr : ARRAY[1..6] OF BYTE;
rem_tsap_id : ARRAY[1..16] OF BYTE;
next_staddr : ARRAY[1..6] OF BYTE;
spare : WORD := W#16#0;
END_STRUCT;
END_VAR
// ============================================================================
// FUNCTION BLOCK INSTANCES AND INTERNAL DATA (Bottom of DB)
// ============================================================================
VAR // Frame Processing
i : INT;
j : INT;
iStartPos : INT;
aFrame : ARRAY[0..11] OF BYTE; // Current frame
iChecksumSum : INT;
bCalculatedChecksum : BYTE;
bReceivedChecksum : BYTE;
END_VAR
VAR // Connection Control
xTdisconReq : BOOL;
xTconReq : BOOL;
xTconReqOld : BOOL;
xTconPulse : BOOL;
iCyclesToWait : INT;
END_VAR
VAR // Timer Instances
tonConnTimeout : TON; // Connection timeout
tonRetryDelay : TON; // Retry delay
tonNoDataTimeout: TON; // No data timeout
tonDisconTimeout: TON; // Disconnect timeout
END_VAR
VAR // Communication Block Instances
TCON_DB : TCON; // TCP connection
TRCV_DB : TRCV; // TCP receive
TDISCON_DB : TDISCON; // TCP disconnect
END_VAR
VAR // Optimized Reception Buffer (23 bytes instead of 128)
aRxBuffer : ARRAY[0..22] OF BYTE; // Optimized buffer size
END_VAR
VAR // Buffer Management Structure
stBuffer : STRUCT
aCircBuffer : ARRAY[0..255] OF BYTE; // Circular buffer
iWritePtr : INT := 0; // Write pointer
iReadPtr : INT := 0; // Read pointer
iDataInBuffer : INT := 0; // Data in buffer
iRxLength : INT; // Last received length
END_STRUCT;
END_VAR
VAR_TEMP
tempByte : BYTE;
tempInt : INT;
tempIntValue : INT;
tempWord : WORD;
tempReal : REAL;
tempTime : TIME;
iAvailable : INT;
iBytesToRead : INT;
tempActualIntervalMs : DINT; // Stores actual interval for current frame processing cycle
tempReceivedID_Char1 : BYTE;
tempDiffDint : DINT; // Temporary DINT for offset adjustment calculation
tempReceivedID_Char2 : BYTE;
tempReceivedID_Val : INT;
END_VAR
BEGIN
// --- Read system time ---
tCurrentTime := TIME_TCK();
// --- Initialization on first scan ---
IF xFirstScan THEN
xFirstScan := FALSE;
tLastTime := tCurrentTime;
// Initialize configuration from inputs
stConfig.rFramesPerSecond := rFramesPerSecond;
stConfig.iConnTimeoutSec := iConnTimeoutSec;
stConfig.iDataTimeoutSec := iDataTimeoutSec;
stConfig.iRetryDelaySec := iRetryDelaySec;
stConfig.rBrixMax := rBrixMax;
stConfig.xEnableChecksum := xEnableChecksum;
stConfig.iDeviceID := iDeviceID;
stConfig.xDisableBurstRead := xDisableBurstRead;
// Clear connection parameters arrays
FOR i := 1 TO 16 DO
stConnParams.local_tsap_id[i] := B#16#00;
stConnParams.rem_tsap_id[i] := B#16#00;
END_FOR;
FOR i := 1 TO 6 DO
stConnParams.rem_subnet_id[i] := B#16#00;
stConnParams.rem_staddr[i] := B#16#00;
stConnParams.next_staddr[i] := B#16#00;
END_FOR;
// Configure connection parameters
stConnParams.id := wConnectionID;
stConnParams.rem_staddr[1] := aRemoteIP[1];
stConnParams.rem_staddr[2] := aRemoteIP[2];
stConnParams.rem_staddr[3] := aRemoteIP[3];
stConnParams.rem_staddr[4] := aRemoteIP[4];
// Remote port configuration
tempWord := INT_TO_WORD(iRemotePort);
stConnParams.rem_tsap_id[1] := WORD_TO_BYTE(SHR(IN:= tempWord, N:= 8));
stConnParams.rem_tsap_id[2] := WORD_TO_BYTE(tempWord AND W#16#FF);
// Reset all control variables
xTconReq := FALSE;
xTconReqOld := FALSE;
iCyclesToWait := 0;
stBuffer.iWritePtr := 0;
stBuffer.iReadPtr := 0;
stBuffer.iDataInBuffer := 0;
// Reset statistics
stStats.diFrameCount := 0;
stStats.diErrorFrameCount := 0;
stStats.diSuccessfulReconCount := 0;
stStats.diChecksumErrorCount := 0;
stStats.diLostFramesCount := 0;
stStats.iConsecutiveBadFrames := 0;
// Initialize timing parameters
IF stConfig.rFramesPerSecond > 0.1 THEN
stTiming.diFrameIntervalMs := REAL_TO_DINT(1000.0 / stConfig.rFramesPerSecond);
stTiming.iPreReadOffsetMs := DINT_TO_INT(stTiming.diFrameIntervalMs / 6);
stTiming.iMaxActiveReadingMs := DINT_TO_INT(stTiming.diFrameIntervalMs / 3);
IF stTiming.iPreReadOffsetMs < 20 THEN stTiming.iPreReadOffsetMs := 20; END_IF;
IF stTiming.iMaxActiveReadingMs < 50 THEN stTiming.iMaxActiveReadingMs := 50; END_IF;
ELSE
stTiming.diFrameIntervalMs := 1000;
stTiming.iPreReadOffsetMs := 50;
stTiming.iMaxActiveReadingMs := 200;
END_IF;
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
stTiming.diNextFrameMs := 0;
stTiming.diReadingStartMs := 0;
// Initialize FIFO tables (ordered arrays)
FOR i := 0 TO 9 DO
arBrixHistory[i] := 0.0;
auiTimeHistory[i] := 0;
END_FOR;
// Initialize other variables
diTimeBetweenFramesMs := 0;
diAccumCycleMs := 0;
iReadingState := 0; // Start in ACTIVE_READING
xTrcvTimeToCall := TRUE;
iCurrentErrorCode := 0;
iConsecutiveGoodFrames := 0;
diInitPhaseTimerMs := 0;
xInitPhaseFinish := FALSE;
END_IF;
// Calculate elapsed time since last call
IF tCurrentTime >= tLastTime THEN
diElapsedMs := TIME_TO_DINT(tCurrentTime - tLastTime);
if diElapsedMs > 1000 THEN
diElapsedMs := 1000; // Cap at 1 second
END_IF;
ELSE
diElapsedMs := 1; // Handle wrap-around
END_IF;
tLastTime := tCurrentTime;
// --- Diagnostic outputs ---
stDiag.diState := iState;
stDiag.diTconStatus := TCON_DB.STATUS;
stDiag.diTrcvStatus := TRCV_DB.STATUS;
diReadingState := iReadingState;
stDiag.diNextExpectedMs := stTiming.diNextFrameMs;
stDiag.diCalcFrameIntervalMs := stTiming.diFrameIntervalMs;
stDiag.iCalcPreReadOffsetMs := stTiming.iPreReadOffsetMs;
stDiag.iCalcMaxActiveMs := stTiming.iMaxActiveReadingMs;
// --- Error code evaluation (higher number = higher priority) ---
iCurrentErrorCode := 0;
// Priority 1: Checksum errors
IF stStats.diChecksumErrorCount > 0 AND diAccumCycleMs < 5000 THEN
iCurrentErrorCode := 1;
END_IF;
// Priority 2: Frame format errors
IF stStats.diErrorFrameCount > 0 AND diAccumCycleMs < 5000 THEN
iCurrentErrorCode := 2;
END_IF;
// Priority 4: Data out of range
IF rCurrentMA > 0.0 AND (rCurrentMA < 4.0 OR rCurrentMA > 20.0) AND
diAccumCycleMs < 2000 THEN
iCurrentErrorCode := 4;
END_IF;
// Priority 5: Multiple consecutive bad frames
IF stStats.iConsecutiveBadFrames >= 5 THEN
iCurrentErrorCode := 5;
END_IF;
// Priority 6: Lost frames
IF stStats.diLostFramesCount > 0 AND stTiming.diEstFrameMs > 0 AND
diAccumCycleMs > (stTiming.diEstFrameMs * 2) THEN
iCurrentErrorCode := 6;
END_IF;
// Priority 7-8: TRCV errors
IF TRCV_DB.ERROR THEN
IF TRCV_DB.STATUS = W#16#8088 THEN
iCurrentErrorCode := 7;
ELSE
iCurrentErrorCode := 8;
END_IF;
END_IF;
// Priority 9: Data timeout
IF tonNoDataTimeout.Q THEN
iCurrentErrorCode := 9;
END_IF;
// Priority 10: Connection timeout
IF tonConnTimeout.Q THEN
iCurrentErrorCode := 10;
END_IF;
// Priority 11: Connection failed
IF TCON_DB.ERROR THEN
iCurrentErrorCode := 11;
END_IF;
// Priority 12: Connection terminated
IF (TRCV_DB.STATUS = W#16#80A3) OR (TRCV_DB.STATUS = W#16#80C4) THEN
iCurrentErrorCode := 12;
END_IF;
// Set final error outputs
iErrorCode := iCurrentErrorCode;
xError := (iCurrentErrorCode > 0);
// --- Reset pulse outputs ---
xDataValid := FALSE;
// --- Pulse control for TCON ---
xTconPulse := xTconReq AND NOT xTconReqOld;
xTconReqOld := xTconReq;
// Enhanced Reading State Machine with Initialization Phase
IF iState <> 3 THEN // Only run reading states when in READING main state
iReadingState := 0; // Reset reading state
xInitPhaseFinish := FALSE; // Reset initialization phase finish
diAccumCycleMs := 0; // Reset cycle accumulator
diInitPhaseTimerMs := 0; // Reset init phase timer
iConsecutiveGoodFrames := 0; // Reset counter when not in active reading cycle
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
ELSE // iState = 3: READING
IF stStats.iConsecutiveBadFrames <= 5 THEN
diAccumCycleMs := diAccumCycleMs + diElapsedMs;
END_IF;
IF NOT xInitPhaseFinish THEN
diInitPhaseTimerMs := diInitPhaseTimerMs + diElapsedMs;
END_IF;
CASE iReadingState OF
0: // ACTIVE_READING - Receiving data burst, read intensively
xTrcvTimeToCall := TRUE; // Read every cycle
// During initialization phase, skip PAUSED state
IF xInitPhaseFinish THEN
// Normal operation - can transition to PAUSED
IF (stTiming.diNextFrameMs - diAccumCycleMs) > INT_TO_DINT(stTiming.iMaxActiveReadingMs) THEN
iReadingState := 2; // Go to PAUSED
xTrcvTimeToCall := FALSE;
END_IF;
END_IF;
1: // COMPLETED - Finished reading a valid frame
xTrcvTimeToCall := FALSE;
// Capture the actual time elapsed for this frame cycle before resetting diAccumCycleMs
tempActualIntervalMs := diAccumCycleMs;
// --- Time Between Frames Calculation ---
// Only update diTimeBetweenFramesMs if the current and previous frames were good.
IF iConsecutiveGoodFrames >= 2 THEN
diTimeBetweenFramesMs := tempActualIntervalMs;
END_IF;
// Always reset diAccumCycleMs for the next interval measurement.
diAccumCycleMs := 0;
// Update estimated frame interval (stTiming.diEstFrameMs)
IF NOT xInitPhaseFinish THEN
// During initialization, if we have a fresh reliable interval, use it directly.
IF iConsecutiveGoodFrames >= 2 AND diTimeBetweenFramesMs > 0 THEN
stTiming.diEstFrameMs := diTimeBetweenFramesMs;
END_IF;
// ELSE stTiming.diEstFrameMs remains its initial value (from config)
ELSE
// After initialization, smooth the estimate if diTimeBetweenFramesMs is valid.
IF diTimeBetweenFramesMs > 0 THEN
stTiming.diEstFrameMs := (stTiming.diEstFrameMs * 3 + diTimeBetweenFramesMs) / 4; // Smoothing
ELSE
// If diTimeBetweenFramesMs is not positive (e.g. never set, or problem), revert to default.
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
END_IF;
END_IF;
// Set next expected frame time
stTiming.diNextFrameMs := stTiming.diEstFrameMs;
// --- Dynamically adjust iPreReadOffsetMs (User Optimization) ---
// Adjust only after initialization and with stable timing data.
// Limits for iPreReadOffsetMs: 5ms to 50ms.
IF xInitPhaseFinish AND (iConsecutiveGoodFrames >= 2) THEN
// tempDiffDint = Estimated Interval for Next Frame - Actual Interval of Last Frame
tempDiffDint := stTiming.diNextFrameMs - diTimeBetweenFramesMs;
IF tempDiffDint < INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
IF stTiming.iPreReadOffsetMs > 5 THEN
stTiming.iPreReadOffsetMs := stTiming.iPreReadOffsetMs - 1;
END_IF;
ELSIF tempDiffDint > INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
IF stTiming.iPreReadOffsetMs < 50 THEN
stTiming.iPreReadOffsetMs := stTiming.iPreReadOffsetMs + 1;
END_IF;
END_IF;
END_IF;
// Detect lost frames based on the *actual* interval for the just-completed frame cycle
// compared to the current estimate of frame interval.
IF stTiming.diEstFrameMs > 0 AND tempActualIntervalMs > (stTiming.diEstFrameMs * 3 / 2) THEN
tempInt := DINT_TO_INT(tempActualIntervalMs / stTiming.diEstFrameMs) - 1;
IF tempInt > 0 THEN
stStats.diLostFramesCount := stStats.diLostFramesCount + INT_TO_DINT(tempInt);
END_IF;
END_IF;
iReadingState := 2;
2: // PAUSED - Not reading, waiting for next frame window
// Ensure iConsecutiveGoodFrames is reset if we are paused for a long time or before starting a new read attempt.
// This is mainly handled by the iState <> 3 check, but can be reinforced if needed.
IF NOT xInitPhaseFinish OR stConfig.xDisableBurstRead THEN
iReadingState := 0;
ELSE // Only used when xInitPhaseFinish = TRUE
IF (stTiming.diNextFrameMs - diAccumCycleMs) <= INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
iReadingState := 0; // Go to ACTIVE_READING
stTiming.diReadingStartMs := diAccumCycleMs;
ELSE
xTrcvTimeToCall := FALSE;
END_IF;
END_IF;
END_CASE;
END_IF;
// --- Main State Machine ---
CASE iState OF
0: // IDLE
xConnected := FALSE;
wErrorCode := W#16#0000;
// Retry delay
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iRetryDelaySec) * 1000);
tonRetryDelay(IN := TRUE, PT := tempTime);
IF xEnable AND tonRetryDelay.Q THEN
tonRetryDelay(IN := FALSE);
// Intentar desconectar primero por seguridad
xTdisconReq := TRUE;
TDISCON_DB(REQ := xTdisconReq, ID := wConnectionID);
// Resetear variables de control
xTconReq := FALSE;
xTconReqOld := FALSE;
iCyclesToWait := 0;
stBuffer.iWritePtr := 0;
stBuffer.iReadPtr := 0;
stBuffer.iDataInBuffer := 0;
iState := 1;
ELSIF NOT xEnable THEN
tonRetryDelay(IN := FALSE);
xTdisconReq := FALSE;
TDISCON_DB(REQ := FALSE, ID := wConnectionID);
END_IF;
1: // CONNECT - Prepare connection
iCyclesToWait := iCyclesToWait + 1;
IF iCyclesToWait >= 2 THEN
iCyclesToWait := 0;
xTconReq := TRUE;
xTconReqOld := FALSE; // ← AGREGAR ESTA LÍNEA
iState := 2;
END_IF;
2: // WAIT CONNECT - Wait for connection
TCON_DB(
REQ := xTconReq, // xTconPulse,
ID := wConnectionID,
CONNECT := stConnParams
);
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iConnTimeoutSec) * 1000);
tonConnTimeout(IN := TRUE, PT := tempTime);
tonNoDataTimeout(IN := FALSE);
IF TCON_DB.DONE THEN
xTconReq := FALSE;
xConnected := TRUE;
tonConnTimeout(IN := FALSE);
stStats.diSuccessfulReconCount := stStats.diSuccessfulReconCount + 1;
// Start initialization phase
diAccumCycleMs := 0; // Reset cycle accumulator when entering reading state
iReadingState := 0; // Start in ACTIVE_READING
diInitPhaseTimerMs := 0; // Reset init phase timer
stTiming.diReadingStartMs := 0;
iState := 3; // Go to continuous reading
ELSIF TCON_DB.ERROR THEN
IF TCON_DB.STATUS = W#16#80A3 THEN
// Conexión ya existe - ir a disconnect primero
xTconReq := FALSE;
xTconReqOld := FALSE;
iState := 4;
ELSE
xTconReq := FALSE;
xTconReqOld := FALSE;
wErrorCode := TCON_DB.STATUS;
tonConnTimeout(IN := FALSE);
iState := 4;
END_IF;
ELSIF tonConnTimeout.Q THEN
xTconReq := FALSE;
xTconReqOld := FALSE;
wErrorCode := W#16#8001;
tonConnTimeout(IN := FALSE);
iState := 4;
END_IF;
IF NOT xEnable THEN
xTconReq := FALSE;
tonConnTimeout(IN := FALSE);
iState := 4;
END_IF;
3: // READING - Enhanced reading with initialization phase
// Call TRCV based on reading state
IF xTrcvTimeToCall THEN
TRCV_DB(
EN_R := TRUE,
ID := wConnectionID,
LEN := 0, // Ad-hoc mode
DATA := aRxBuffer // Optimized 23-byte buffer
);
ELSE
TRCV_DB(
EN_R := FALSE,
ID := wConnectionID,
LEN := 0,
DATA := aRxBuffer
);
END_IF;
// Process TRCV result
IF TRCV_DB.NDR THEN
stBuffer.iRxLength := TRCV_DB.RCVD_LEN;
tonNoDataTimeout(IN := FALSE);
// Copy data to circular buffer
FOR i := 0 TO (stBuffer.iRxLength - 1) DO
stBuffer.aCircBuffer[stBuffer.iWritePtr] := aRxBuffer[i];
stBuffer.iWritePtr := (stBuffer.iWritePtr + 1) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer + 1;
IF stBuffer.iDataInBuffer > 256 THEN
stBuffer.iDataInBuffer := 256;
stBuffer.iReadPtr := (stBuffer.iReadPtr + 1) MOD 256;
END_IF;
END_FOR;
// --- Process frames in circular buffer ---
WHILE stBuffer.iDataInBuffer >= 12 DO
// Find first '#'
iStartPos := -1;
FOR j := 0 TO (stBuffer.iDataInBuffer - 1) DO
tempInt := (stBuffer.iReadPtr + j) MOD 256;
IF stBuffer.aCircBuffer[tempInt] = B#16#23 THEN
iStartPos := j;
EXIT;
END_IF;
END_FOR;
IF iStartPos >= 0 THEN
// Discard bytes before '#'
IF iStartPos > 0 THEN
stBuffer.iReadPtr := (stBuffer.iReadPtr + iStartPos) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - iStartPos;
END_IF;
IF stBuffer.iDataInBuffer >= 12 THEN
// Check format: '.' at position 5
IF stBuffer.aCircBuffer[(stBuffer.iReadPtr + 5) MOD 256] = B#16#2E THEN
// Copy frame
FOR i := 0 TO 11 DO
aFrame[i] := stBuffer.aCircBuffer[(stBuffer.iReadPtr + i) MOD 256];
END_FOR;
// --- Validate Device ID (if enabled) ---
// Adam address is in aFrame[1] and aFrame[2]
IF stConfig.iDeviceID > 0 AND stConfig.iDeviceID <= 99 THEN
tempReceivedID_Char1 := aFrame[1];
tempReceivedID_Char2 := aFrame[2];
// Check if ID characters are digits
IF (BYTE_TO_INT(tempReceivedID_Char1) >= 16#30 AND BYTE_TO_INT(tempReceivedID_Char1) <= 16#39) AND
(BYTE_TO_INT(tempReceivedID_Char2) >= 16#30 AND BYTE_TO_INT(tempReceivedID_Char2) <= 16#39) THEN
tempReceivedID_Val := (BYTE_TO_INT(tempReceivedID_Char1) - 16#30) * 10 +
(BYTE_TO_INT(tempReceivedID_Char2) - 16#30);
IF tempReceivedID_Val <> stConfig.iDeviceID THEN
// ID Mismatch
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
iConsecutiveGoodFrames := 0;
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
CONTINUE;
END_IF;
ELSE
// Invalid characters for ID in frame
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
iConsecutiveGoodFrames := 0;
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
CONTINUE;
END_IF;
END_IF;
// Validate digit characters
IF (BYTE_TO_INT(aFrame[3]) >= 16#30 AND BYTE_TO_INT(aFrame[3]) <= 16#39) AND
(BYTE_TO_INT(aFrame[4]) >= 16#30 AND BYTE_TO_INT(aFrame[4]) <= 16#39) AND
(BYTE_TO_INT(aFrame[6]) >= 16#30 AND BYTE_TO_INT(aFrame[6]) <= 16#39) AND
(BYTE_TO_INT(aFrame[7]) >= 16#30 AND BYTE_TO_INT(aFrame[7]) <= 16#39) AND
(BYTE_TO_INT(aFrame[8]) >= 16#30 AND BYTE_TO_INT(aFrame[8]) <= 16#39) THEN
// Checksum validation
IF stConfig.xEnableChecksum THEN
iChecksumSum := 0;
FOR i := 0 TO 8 DO
iChecksumSum := iChecksumSum + BYTE_TO_INT(aFrame[i]);
END_FOR;
bCalculatedChecksum := INT_TO_BYTE(iChecksumSum MOD 256);
// Extract received checksum
tempInt := BYTE_TO_INT(aFrame[9]);
IF tempInt >= 16#30 AND tempInt <= 16#39 THEN
tempInt := tempInt - 16#30;
ELSIF tempInt >= 16#41 AND tempInt <= 16#46 THEN
tempInt := tempInt - 16#37;
ELSIF tempInt >= 16#61 AND tempInt <= 16#66 THEN
tempInt := tempInt - 16#57;
ELSE
tempInt := 0;
END_IF;
bReceivedChecksum := INT_TO_BYTE(tempInt * 16);
tempInt := BYTE_TO_INT(aFrame[10]);
IF tempInt >= 16#30 AND tempInt <= 16#39 THEN
tempInt := tempInt - 16#30;
ELSIF tempInt >= 16#41 AND tempInt <= 16#46 THEN
tempInt := tempInt - 16#37;
ELSIF tempInt >= 16#61 AND tempInt <= 16#66 THEN
tempInt := tempInt - 16#57;
ELSE
tempInt := 0;
END_IF;
bReceivedChecksum := bReceivedChecksum OR INT_TO_BYTE(tempInt);
IF bCalculatedChecksum <> bReceivedChecksum THEN
stStats.diChecksumErrorCount := stStats.diChecksumErrorCount + 1;
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
CONTINUE;
END_IF;
END_IF;
// Parse valid frame
// At this point, frame is structurally good (format, digits, checksum OK or disabled)
iConsecutiveGoodFrames := iConsecutiveGoodFrames + 1;
IF iConsecutiveGoodFrames > 2 THEN // Cap at 2 for "last two frames" logic
iConsecutiveGoodFrames := 2;
END_IF;
stStats.iConsecutiveBadFrames := 0; // Reset consecutive bad *structural* frames counter
tempIntValue := 0;
tempInt := BYTE_TO_INT(aFrame[3]);
tempIntValue := (tempInt - 16#30) * 10;
tempInt := BYTE_TO_INT(aFrame[4]);
tempIntValue := tempIntValue + (tempInt - 16#30);
rCurrentMA := INT_TO_REAL(tempIntValue);
tempInt := BYTE_TO_INT(aFrame[6]);
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 10.0;
tempInt := BYTE_TO_INT(aFrame[7]);
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 100.0;
tempInt := BYTE_TO_INT(aFrame[8]);
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 1000.0;
// Calculate Brix
IF (rCurrentMA >= 4.0) AND (rCurrentMA <= 20.0) THEN
rBrixValue := (rCurrentMA - 4.0) * stConfig.rBrixMax / 16.0;
// *** UPDATE FIFO TABLES (if enabled) ***
IF xEnableFifoDebug THEN
// Shift all elements up (newest at [0], oldest discarded at [9])
FOR tempInt := 9 TO 1 BY -1 DO
arBrixHistory[tempInt] := arBrixHistory[tempInt - 1];
auiTimeHistory[tempInt] := auiTimeHistory[tempInt - 1];
END_FOR;
// Insert new values at [0]
arBrixHistory[0] := rBrixValue;
auiTimeHistory[0] := DINT_TO_INT(diAccumCycleMs MOD 32767); // Convert to UINT
END_IF;
// Set data valid pulse
xDataValid := TRUE;
// Check if initialization phase should finish
IF NOT xInitPhaseFinish AND diInitPhaseTimerMs > (stTiming.diFrameIntervalMs * 2) THEN
IF stBuffer.iRxLength = 12 THEN
xInitPhaseFinish := TRUE; // Synchronized and clean buffer
END_IF;
END_IF;
// Update moving average
IF stDiag.rAvgCurrentMA = 0.0 THEN
stDiag.rAvgCurrentMA := rCurrentMA;
ELSE
stDiag.rAvgCurrentMA := stDiag.rAvgCurrentMA * 0.9 + rCurrentMA * 0.1;
END_IF;
// stStats.iConsecutiveBadFrames already reset as frame is structurally good
END_IF;
// Frame processing complete - transition to COMPLETED
iReadingState := 1;
// Increment frame counter
stStats.diFrameCount := stStats.diFrameCount + 1;
IF stStats.diFrameCount >= DINT#2000000000 THEN
stStats.diFrameCount := 0;
END_IF;
ELSE // Invalid digits
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
END_IF;
ELSE // Invalid format (no '.' at position 5)
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
END_IF;
// Discard processed frame
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
ELSE // Not enough bytes for full frame
EXIT;
END_IF;
ELSE // No '#' found
stBuffer.iReadPtr := (stBuffer.iReadPtr + stBuffer.iDataInBuffer) MOD 256;
stBuffer.iDataInBuffer := 0;
EXIT;
END_IF;
END_WHILE;
ELSIF TRCV_DB.ERROR THEN
IF (TRCV_DB.STATUS = W#16#80A3) OR (TRCV_DB.STATUS = W#16#80C4) THEN
xConnected := FALSE;
TCON_DB(REQ := FALSE, ID := wConnectionID, CONNECT := stConnParams);
TRCV_DB(EN_R := FALSE, ID := wConnectionID, LEN := 0, DATA := aRxBuffer);
IF xTdisconReq THEN
xTdisconReq := FALSE;
TDISCON_DB(REQ := FALSE, ID := wConnectionID);
END_IF;
tonNoDataTimeout(IN := FALSE);
iState := 0;
ELSIF TRCV_DB.STATUS = W#16#8088 THEN
tonNoDataTimeout(IN := FALSE);
ELSE
wErrorCode := TRCV_DB.STATUS;
xConnected := FALSE;
iState := 4;
END_IF;
END_IF;
// Data timeout check - limited by diAccumCycleMs max = iDataTimeoutSec
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iDataTimeoutSec) * 1000);
tonNoDataTimeout(IN := TRUE, PT := tempTime);
IF tonNoDataTimeout.Q THEN
wErrorCode := W#16#8002;
iState := 4;
END_IF;
IF NOT xEnable THEN
iState := 4;
END_IF;
4: // DISCONNECT
xTdisconReq := TRUE;
TDISCON_DB(
REQ := xTdisconReq,
ID := wConnectionID
);
tonDisconTimeout(IN := xTdisconReq, PT := T#2S);
IF TDISCON_DB.DONE OR TDISCON_DB.ERROR OR tonDisconTimeout.Q THEN
xTdisconReq := FALSE;
TDISCON_DB(REQ := xTdisconReq, ID := wConnectionID);
xConnected := FALSE;
tonDisconTimeout(IN := FALSE);
TCON_DB(REQ := FALSE, ID := wConnectionID, CONNECT := stConnParams);
TRCV_DB(EN_R := FALSE, ID := wConnectionID, LEN := 0, DATA := aRxBuffer);
iState := 0;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
```