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);//timeoutinmicro-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.