CAA Net Base Services - TCP Keep-Alive

jtebokkel
2019-12-11
2019-12-13
  • jtebokkel

    jtebokkel - 2019-12-11

    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.

    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
    
     
  • dFx

    dFx - 2019-12-12

    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.

     
  • jtebokkel

    jtebokkel - 2019-12-13

    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.

     
  • jtebokkel

    jtebokkel - 2019-12-13

    dFx: That was the issue. Added the tcp_read(xEnable := FALSE); after completing the read and the keep-alive works.

     

Log in to post a comment.