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_BLOCKLXI_ClientVAR_INPUT
  //Configurationvariables
  //IPAddress
  sConfigIPAddress    : STRING(20) :='192.168.10.2';
  //DefaultPort5025
  uiConfigPort      : UINT :=5025;
  //Nametoappearinlogs Â
  sName         : STRING(20);
  //RisingEdgeTriggeredReset
  xReset : BOOL;
  //RisingEdgeTriggeredConnect
  xConnect : BOOL;
 Â
END_VARVAR_OUTPUT
  //IDNameofDevice
  sIDN : STRING(255);
  //DiagnosticInfo
  sDiagnose       : STRING(80);
  //DeviceBusy
  xBusy : BOOL;
  //DeviceConnected
  xConnected : BOOL;
  //ErrorFlag
  xError : BOOL;
  //ErrorString
  sError : STRING(80);
  //HighwhenvaluesfromqueryarereadyinarValues
  xValuesReady : BOOL;
  //QueryValues
  arValues : ARRAY[0..MAXVAL] OFREAL;END_VARVAR
  //TCPConnection
  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] OFBYTE;
  bufRecv : ARRAY[0..MAXBUF] OFBYTE;
 Â
  //EdgeTrigs
  etConnect : R_TRIG;
  etReset : R_TRIG;
 Â
  //DiagnosticInformation
  sOldIDN : STRING(255);
  //Helpers Â
  mxLxi0           : LXI_STATE;
  iPosN : INT;
  xWaitOnDevice : BOOL;
  tmrReadTimeout : TON :=(PT:=T#5S);
  tmrKeepAlive : TON :=(PT:=T#30S);
 Â
  //AppendConfigVars
  posSend : INT;
 Â
  //ProcessRecvVars
  smProcessRecv : INT;
  iVal : INT;
  sVal : STRING(20);
  iStart : INT;
  iEnd : INT;
  iRecvLen : INT;
  sRecv : STRING(255);
 Â
  //FixedMessages
  _sIDN : STRING(6) :='*IDN?$N';END_VARVARCONSTANT
  MAXBUF : UINT :=1023;
  MAXVAL : UINT :=24;END_VARetConnect(CLK:=xConnect);etReset(CLK:=xReset);CASEmxLxi0OFLXI_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;
  IFetConnect.QTHEN
    mxLxi0 :=LXI_STATE.INIT;
  END_IF
  sDiagnose :='not connected';
 Â
LXI_STATE.INIT:Â Â //initclient
  xBusy :=TRUE;
  tcp_client(xEnable :=TRUE, ipAddr :=IPAddr, uiPort :=uiPort);
  IFtcp_client.xBusyANDtcp_client.hConnection<>CAA.gc_hINVALIDTHEN
    mxLxi0 :=LXI_STATE.IDN_SEND;
   Â
  ELSIFtcp_client.xErrorTHEN
    eError :=tcp_client.eError;
    mxLxi0 :=LXI_STATE.ERROR;
  END_IF
  sDiagnose :='connecting';
 Â
LXI_STATE.IDN_SEND: //IDTheDevice
  tcp_write(xExecute :=tcp_client.xActive, hConnection :=tcp_client.hConnection, szSize :=sizeof(_sIDN)-1, pData :=ADR(_sIDN));
  IFtcp_write.xDoneTHEN    //messagesent, gobacktoidlemode
    tcp_write(xExecute :=FALSE);
    sOldIDN :=sIDN;
    mxLxi0 :=LXI_STATE.IDN_RECV;
  ELSIFtcp_write.xErrorTHEN
    tcp_client(xEnable :=TRUE);
    IFNOTtcp_client.xActiveANDNOTtcp_client.xErrorTHEN  //reinit
      tcp_write(xExecute :=FALSE);
      tcp_client(xEnable :=FALSE);
      mxLxi0 :=LXI_STATE.INIT;
    ELSIFtcp_read.xErrorTHEN  //error
      eError :=tcp_write.eError;
      mxLxi0 :=LXI_STATE.ERROR;
    ELSE
      ;     Â
    END_IF
  END_IF
  sDiagnose :='connected - sending *IDN?';
 Â
LXI_STATE.IDN_RECV:Â Â //waitforincomingIDN
  sDiagnose :='receiving *IDN?';
  tmrReadTimeout(IN:=TRUE);
  tcp_read(xEnable :=tcp_client.xActive, hConnection :=tcp_client.hConnection, szSize :=SIZEOF(bufRecv), pData :=ADR(bufRecv));
  IFtcp_read.xErrorTHEN
    tcp_client(xEnable :=TRUE);
    IFNOTtcp_client.xActiveANDNOTtcp_client.xErrorTHEN  //reinit
      tcp_read(xEnable :=FALSE);
      tcp_client(xEnable :=FALSE);
      mxLxi0 :=LXI_STATE.INIT;
    ELSIFtcp_read.xErrorTHEN  //error
      eError :=tcp_read.eError;
      mxLxi0 :=LXI_STATE.ERROR;
    ELSE
      ;
    END_IF
  END_IF
  IFtcp_read.xReadyTHEN
    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
  IFtmrReadTimeout.QTHEN
    eLxiError :=ENO.ETIMEDOUT;
    mxLxi0 :=LXI_STATE.ERROR;
  END_IFLXI_STATE.WAIT_FOR_MESSAGE: //idlemode-waitformessageinqueuetosend
  xBusy :=FALSE;
  xConnected :=TRUE;
  sDiagnose :='waiting for messages';
 Â
  //UseIDNqueryaskeepalive-connectionchecking
  tmrKeepAlive(IN:=TRUE);
  IFtmrKeepAlive.QTHEN
    tmrKeepAlive(IN:=FALSE);
    mxLxi0 :=LXI_STATE.IDN_SEND;
    xBusy :=TRUE;
  END_IFLXI_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));
  IFtcp_write.xDoneTHEN    //messagesent, gobacktoidlemode
    tcp_write(xExecute :=FALSE);
    IFIsQuery(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;
   Â
  ELSIFtcp_write.xErrorTHEN
    tcp_client(xEnable :=TRUE);
    IFNOTtcp_client.xActiveANDNOTtcp_client.xErrorTHEN  //reinit
      tcp_write(xExecute :=FALSE);
      tcp_client(xEnable :=FALSE);
      mxLxi0 :=LXI_STATE.INIT;
    ELSIFtcp_read.xErrorTHEN  //error
      eError :=tcp_write.eError;
      mxLxi0 :=LXI_STATE.ERROR;
    ELSE
      ;     Â
    END_IF
  END_IF
  sDiagnose :='sending message';
 Â
LXI_STATE.WAIT_FOR_RESPONSE:
  sDiagnose :='receiving';
  IFxWaitOnDeviceTHEN
    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));
  IFtcp_read.xErrorTHEN
    tcp_client(xEnable :=TRUE);
    IFNOTtcp_client.xActiveANDNOTtcp_client.xErrorTHEN  //reinit
      tcp_read(xEnable :=FALSE);
      tcp_client(xEnable :=FALSE);
      xConnected :=FALSE;
      mxLxi0 :=LXI_STATE.INIT;
    ELSIFtcp_read.xErrorTHEN  //error
      eError :=tcp_read.eError;
      mxLxi0 :=LXI_STATE.ERROR;
    ELSE
      ;
    END_IF
  END_IF
  IFtcp_read.xReadyTHEN
    tmrReadTimeout(IN:=FALSE);
    xWaitOnDevice :=FALSE;
    //writeReadwillsetstatetowaitformessage
    mxLxi0 :=LXI_STATE.PROCESS_RESPONSE;
  END_IF
  IFtmrReadTimeout.QTHEN
    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:
  IFNOTxValuesReadyTHEN
    mxLxi0 :=LXI_STATE.WAIT_FOR_MESSAGE;
  END_IFLXI_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_CASEOB._BUFFER_INSERT(CONCAT(sName, ': '), 0, ADR(sDiagnose), SIZEOF(sDiagnose));IFxErrorTHEN
  sError :=sDiagnose;ELSE
  sError :='';END_IFIFetReset.QTHEN
  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
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
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.
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.
dFx: That was the issue. Added the tcp_read(xEnable := FALSE); after completing the read and the keep-alive works.