CODESYS - the IEC 61131-3 automation software

Welcome to the official CODESYS Forum by 3S-Smart Software Solutions GmbH | A member of the CODESYS Group
Deutsche Version English version russian version 
It is currently Mon Feb 24, 2020 8:50 pm

All times are UTC+01:00




Post new topic  Reply to topic  [ 4 posts ] 
Author Message
PostPosted: Wed Dec 11, 2019 3:54 pm 
Offline

Joined: Fri Oct 18, 2019 6:11 pm
Posts: 3
I'm using the Net Base Services function blocks to communicate with LXI devices over TCP. I've successfully communicated with a Keithely DAQ6510 with no issues, but I'm having issues with a Rigol DS1054Z scope. The difference between them, as far as I can tell, is the Rigol sends a TCP Keep-Alive packet when a command will take longer to execute and the Keithley does not.

I discovered this by using a simple python script to run commands in the same way as the PLC would and used wireshark to check the TCP packets for what was going on. I can't do the same for the PLC to Rigol connection because I can't do a packet capture on those interfaces.

Is this a limitation of the Net Base Services library? Is there another way to handle the TCP Keep-Alive? I know the PLC can handle the TCP Keep-Alive because the MSSQL library I'm using is able to handle Keep-Alive packets.

I get a TCP_RECEIVE_ERROR (6012) when I try to perform a TCP_Read after the device sends the Keep-Alive.

This same function block is used both on the Keithley and Rigol devices.

Code:
FUNCTION_BLOCK LXI_Client
VAR_INPUT
   // Configuration variables
   // IP Address
   sConfigIPAddress      : STRING(20) := '192.168.10.2' ;
   // Default Port 5025
   uiConfigPort         : UINT := 5025;
   // Name to appear in logs   
   sName             : STRING(20);
   // Rising Edge Triggered Reset
   xReset : BOOL;
   // Rising Edge Triggered Connect
   xConnect : BOOL;
   
END_VAR
VAR_OUTPUT
   // ID Name of Device
   sIDN : STRING(255);
   // Diagnostic Info
   sDiagnose          : STRING(80);
   // Device Busy
   xBusy : BOOL;
   // Device Connected
   xConnected : BOOL;
   // Error Flag
   xError : BOOL;
   // Error String
   sError : STRING(80);
   // High when values from query are ready in arValues
   xValuesReady : BOOL;
   // Query Values
   arValues : ARRAY[0..MAXVAL] OF REAL;
END_VAR
VAR
   // TCP Connection
   tcp_client             : NBS.TCP_Client := (udiTimeOut:= 5000000); // timeout in micro-seconds
   tcp_read             : NBS.TCP_Read;
   tcp_write             : NBS.TCP_Write;
   uiPort                : UINT;
   IPAddr                : NBS.IP_ADDR;   
   eError                : NBS.ERROR;
   eLxiError : ENO.errno;
   bufSend : ARRAY[0..MAXBUF] OF BYTE;
   bufRecv : ARRAY[0..MAXBUF] OF BYTE;
   
   // Edge Trigs
   etConnect : R_TRIG;
   etReset : R_TRIG;
   
   // Diagnostic Information
   sOldIDN : STRING(255);

   // Helpers   
   mxLxi0                : LXI_STATE;
   iPosN : INT;
   xWaitOnDevice : BOOL;
   tmrReadTimeout : TON := (PT:=T#5S);
   tmrKeepAlive : TON := (PT:=T#30S);
   
   // AppendConfig Vars
   posSend : INT;
   
   // ProcessRecv Vars
   smProcessRecv : INT;
   iVal : INT;
   sVal : STRING(20);
   iStart : INT;
   iEnd : INT;
   iRecvLen : INT;
   sRecv : STRING(255);
   
   // Fixed Messages
   _sIDN : STRING(6) := '*IDN?$N';
END_VAR

VAR CONSTANT
   MAXBUF : UINT := 1023;
   MAXVAL : UINT := 24;
END_VAR

etConnect(CLK:=xConnect);
etReset(CLK:=xReset);

CASE mxLxi0 OF

LXI_STATE.STOPPED:
   IPAddr.sAddr := sConfigIPAddress;
   uiPort := uiConfigPort;
   tcp_client(xEnable := FALSE);
   tcp_read(xEnable := FALSE);
   tcp_write(xExecute := FALSE);
   xBusy := FALSE;
   xConnected := FALSE;
   xError := FALSE;
   eError := tcp_client.eError;
   eLxiError := ENO.ESUCCESS;
   IF etConnect.Q THEN
      mxLxi0 := LXI_STATE.INIT;
   END_IF
   sDiagnose := 'not connected';
   
LXI_STATE.INIT:   // init client
   xBusy := TRUE;
   tcp_client(xEnable := TRUE, ipAddr := IPAddr, uiPort := uiPort);
   IF tcp_client.xBusy AND tcp_client.hConnection <> CAA.gc_hINVALID THEN
      mxLxi0 := LXI_STATE.IDN_SEND;
      
   ELSIF tcp_client.xError THEN
      eError := tcp_client.eError;
      mxLxi0 := LXI_STATE.ERROR;
   END_IF
   sDiagnose := 'connecting';
   
LXI_STATE.IDN_SEND: // ID The Device
   tcp_write(xExecute := tcp_client.xActive, hConnection := tcp_client.hConnection, szSize := sizeof(_sIDN)-1, pData := ADR(_sIDN));
   IF tcp_write.xDone THEN      // message sent, go back to idle mode
      tcp_write(xExecute := FALSE);
      sOldIDN := sIDN;
      mxLxi0 := LXI_STATE.IDN_RECV;
   ELSIF tcp_write.xError THEN
      tcp_client(xEnable := TRUE);
      IF NOT tcp_client.xActive AND NOT tcp_client.xError THEN   // reinit
         tcp_write(xExecute := FALSE);
         tcp_client(xEnable := FALSE);
         mxLxi0 := LXI_STATE.INIT;
      ELSIF tcp_read.xError THEN   // error
         eError := tcp_write.eError;
         mxLxi0 := LXI_STATE.ERROR;
      ELSE
         ;         
      END_IF
   END_IF
   sDiagnose := 'connected - sending *IDN?';
   
LXI_STATE.IDN_RECV:   // wait for incoming IDN
   sDiagnose := 'receiving *IDN?';
   tmrReadTimeout(IN:=TRUE);
   tcp_read(xEnable := tcp_client.xActive, hConnection := tcp_client.hConnection, szSize := SIZEOF(bufRecv), pData := ADR(bufRecv));
   IF tcp_read.xError THEN
      tcp_client(xEnable := TRUE);
      IF NOT tcp_client.xActive AND NOT tcp_client.xError THEN   // reinit
         tcp_read(xEnable := FALSE);
         tcp_client(xEnable := FALSE);
         mxLxi0 := LXI_STATE.INIT;
      ELSIF tcp_read.xError THEN   // error
         eError := tcp_read.eError;
         mxLxi0 := LXI_STATE.ERROR;
      ELSE
         ;
      END_IF
   END_IF

   IF tcp_read.xReady THEN
      mxLxi0 := LXI_STATE.WAIT_FOR_MESSAGE;
      tmrReadTimeout(IN:=FALSE);
      iPosN := OB.BUFFER_SEARCH(ADR(bufRecv),UINT_TO_INT(SIZEOF(bufRecv)), '$N', 0, FALSE);
      sIDN := OB.BUFFER_TO_STRING(ADR(bufRecv),SIZEOF(bufRecv), 0, INT_TO_UINT(iPosN - 1));
   END_IF
   IF tmrReadTimeout.Q THEN
      eLxiError := ENO.ETIMEDOUT;
      mxLxi0 := LXI_STATE.ERROR;
   END_IF
LXI_STATE.WAIT_FOR_MESSAGE: // idle mode - wait for message in queue to send
   xBusy := FALSE;
   xConnected := TRUE;
   sDiagnose := 'waiting for messages';
   
   // Use IDN query as keep alive - connection checking
   tmrKeepAlive(IN:=TRUE);
   IF tmrKeepAlive.Q THEN
      tmrKeepAlive(IN:=FALSE);
      mxLxi0 := LXI_STATE.IDN_SEND;
      xBusy := TRUE;
   END_IF

LXI_STATE.SEND_MESSAGE:
   xBusy := TRUE;
   tmrKeepAlive(IN:=FALSE);
   tcp_write(xExecute := tcp_client.xActive, hConnection := tcp_client.hConnection, szSize := INT_TO_UDINT(SIZEOF(BYTE) * MAX(1, posSend)), pData := ADR(bufSend));
   IF tcp_write.xDone THEN      // message sent, go back to idle mode
      tcp_write(xExecute := FALSE);
      IF IsQuery(ADR(bufSend), SIZEOF(bufSend)) THEN
         mxLxi0 := LXI_STATE.WAIT_FOR_RESPONSE;
      ELSE
         mxLxi0 := LXI_STATE.WAIT_FOR_MESSAGE;
      END_IF
      OB._BUFFER_CLEAR(ADR(bufSend), SIZEOF(bufSend));
      posSend := 0;
      
   ELSIF tcp_write.xError THEN
      tcp_client(xEnable := TRUE);
      IF NOT tcp_client.xActive AND NOT tcp_client.xError THEN   // reinit
         tcp_write(xExecute := FALSE);
         tcp_client(xEnable := FALSE);
         mxLxi0 := LXI_STATE.INIT;
      ELSIF tcp_read.xError THEN   // error
         eError := tcp_write.eError;
         mxLxi0 := LXI_STATE.ERROR;
      ELSE
         ;         
      END_IF
   END_IF
   sDiagnose := 'sending message';
   
LXI_STATE.WAIT_FOR_RESPONSE:
   sDiagnose := 'receiving';
   IF xWaitOnDevice THEN
      tmrReadTimeout(PT:=T#60S);
   ELSE
      tmrReadTimeout(PT:=T#5S);
   END_IF
   tmrReadTimeout(IN:=TRUE);
   OB._BUFFER_CLEAR(ADR(bufRecv), SIZEOF(bufRecv));
   tcp_read(xEnable := tcp_client.xActive, hConnection := tcp_client.hConnection, szSize := SIZEOF(bufRecv), pData := ADR(bufRecv));
   IF tcp_read.xError THEN
      tcp_client(xEnable := TRUE);
      IF NOT tcp_client.xActive AND NOT tcp_client.xError THEN   // reinit
         tcp_read(xEnable := FALSE);
         tcp_client(xEnable := FALSE);
         xConnected := FALSE;
         mxLxi0 := LXI_STATE.INIT;
      ELSIF tcp_read.xError THEN   // error
         eError := tcp_read.eError;
         mxLxi0 := LXI_STATE.ERROR;
      ELSE
         ;
      END_IF
   END_IF

   IF tcp_read.xReady THEN
      tmrReadTimeout(IN:=FALSE);
      xWaitOnDevice := FALSE;
      // writeRead will set state to wait for message
      mxLxi0 := LXI_STATE.PROCESS_RESPONSE;
   END_IF
   IF tmrReadTimeout.Q THEN
      mxLxi0 := LXI_STATE.ERROR;
      eLxiError := ENO.ETIMEDOUT;
      tmrReadTimeout(IN:=FALSE);
      xWaitOnDevice := FALSE;
   END_IF
   
LXI_STATE.PROCESS_RESPONSE:
   ProcessRecv();
   mxLxi0 := LXI_STATE.ACK_RESPONSE;
   
LXI_STATE.ACK_RESPONSE:
   IF NOT xValuesReady THEN
      mxLxi0 := LXI_STATE.WAIT_FOR_MESSAGE;
   END_IF

LXI_STATE.ERROR:
   xError := TRUE;
   xConnected := TRUE;
   sDiagnose := 'Error ';
   sDiagnose := CONCAT(sDiagnose, INT_TO_STRING(eError));
   sDiagnose := CONCAT(sDiagnose, ' LxiError:');
   sDiagnose := CONCAT(sDiagnose, INT_TO_STRING(eLxiError));
   
END_CASE

OB._BUFFER_INSERT(CONCAT(sName, ': '), 0, ADR(sDiagnose), SIZEOF(sDiagnose));

IF xError THEN
   sError := sDiagnose;
ELSE
   sError := '';
END_IF

IF etReset.Q THEN
   posSend := 0;
   OB._BUFFER_CLEAR(ADR(bufSend), SIZEOF(bufSend));
   OB._BUFFER_CLEAR(ADR(bufRecv), SIZEOF(bufRecv));
   tmrReadTimeout(IN:=FALSE);
   mxLxi0 := LXI_STATE.STOPPED;
END_IF



Top
   
PostPosted: Thu Dec 12, 2019 4:34 pm 
Offline
Frequent User
Frequent User

Joined: Fri Feb 23, 2018 3:41 pm
Posts: 180
Could it be due too "sizeof(_sIDN)-1" ?

I would expect the sizeof result to be 6, so you would not pass $n at the end of the string to the write block as you should.

EDIT : telling this because if the write is busy, then read will go on error.

Also when you come from WAIT_FOR_RESPONSE, I don't see call for tcp_read with execute false.


Top
   
PostPosted: Fri Dec 13, 2019 5:43 pm 
Offline

Joined: Fri Oct 18, 2019 6:11 pm
Posts: 3
sizefof(_sIDN) returns the length of the string + the null terminating byte. Sending the null byte causes errors on the scope and prevents any sort of query response. That's why the -1.

Good call on the missing tcp_read(xEnable := FALSE); Maybe that is blocking the tcp stack from handling the keep-alive. Not an issue on the other device which doesn't send the keep alive, but could be the issue here.

I'll test that out.


Top
   
PostPosted: Fri Dec 13, 2019 9:51 pm 
Offline

Joined: Fri Oct 18, 2019 6:11 pm
Posts: 3
dFx: That was the issue. Added the tcp_read(xEnable := FALSE); after completing the read and the keep-alive works.


Top
   
Display posts from previous:  Sort by  
Post new topic  Reply to topic  [ 4 posts ] 

All times are UTC+01:00


Who is online

Users browsing this forum: No registered users and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Limited