PLC devices A and B
The required functionality was encapsulated in the function block FB_PeerToPeer. Each of the communication partners uses an instance of the FB_PeerToPeer function block. The block is activated through a rising edge at the bEnable input. A new UDP socket is opened, and data exchange commences. The socket address is specified via the variables sLocalHost and nLocalPort. A falling edge stops the data exchange and closes the socket. The data to be sent are transferred to the block through a reference (VAR_IN_OUT) via the variable sendFifo. The data received are stored in the variable receiveFifo.
Name | Default value | Description |
---|---|---|
g_sTcIpConnSvrAddr | '' | Network address of the TwinCAT TCP/IP Connection Server. Default: Empty string (the server is located on the local PC); |
bLogDebugMessages | TRUE | Activates/deactivates writing of messages into the log book of the operating system; |
PLCPRJ_ERROR_SENDFIFO_OVERFLOW | 16#8103 | Sample project error code: The send Fifo is full. |
PLCPRJ_ERROR_RECFIFO_OVERFLOW | 16#8104 | Sample project error code: The receive Fifo is full. |
FUNCTION_BLOCK FB_PeerToPeer

Interface
VAR_IN_OUT
sendFifo : FB_Fifo;
receiveFifo : FB_Fifo;
END_VAR
VAR_INPUT
sLocalHost : STRING(15);
nLocalPort : UDINT;
bEnable : BOOL;
END_VAR
VAR_OUTPUT
bCreated : BOOL;
bBusy : BOOL;
bError : BOOL;
nErrId : UDINT;
END_VAR
VAR
fbCreate : FB_SocketUdpCreate;
fbClose : FB_SocketClose;
fbReceiveFrom : FB_SocketUdpReceiveFrom;
fbSendTo : FB_SocketUdpSendTo;
hSocket : T_HSOCKET;
eStep : E_ClientServerSteps;
sendTo : ST_FifoEntry;
receivedFrom : ST_FifoEntry;
END_VAR
Implementation
CASE eStep OF
UDP_STATE_IDLE:
IF bEnable XOR bCreated THEN
bBusy := TRUE;
bError := FALSE;
nErrid := 0;
IF bEnable THEN
eStep := UDP_STATE_CREATE_START;
ELSE
eStep := UDP_STATE_CLOSE_START;
END_IF
ELSIF bCreated THEN
sendFifo.RemoveHead( old => sendTo );
IF sendFifo.bOk THEN
eStep := UDP_STATE_SEND_START;
ELSE (* empty *)
eStep := UDP_STATE_RECEIVE_START;
END_IF
ELSE
bBusy := FALSE;
END_IF
UDP_STATE_CREATE_START:
fbCreate( bExecute := FALSE );
fbCreate( sSrvNetId:= g_sTcIpConnSvrAddr,
sLocalHost:= sLocalHost,
nLocalPort:= nLocalPort,
bExecute:= TRUE );
eStep := UDP_STATE_CREATE_WAIT;
UDP_STATE_CREATE_WAIT:
fbCreate( bExecute := FALSE );
IF NOT fbCreate.bBusy THEN
IF NOT fbCreate.bError THEN
bCreated := TRUE;
hSocket := fbCreate.hSocket;
eStep := UDP_STATE_IDLE;
LogMessage( 'Socket opened (UDP)!', hSocket );
ELSE
LogError( 'FB_SocketUdpCreate', fbCreate.nErrId );
nErrId := fbCreate.nErrId;
eStep := UDP_STATE_ERROR;
END_IF
END_IF
UDP_STATE_SEND_START:
fbSendTo( bExecute := FALSE );
fbSendTo( sSrvNetId:=g_sTcIpConnSvrAddr,
sRemoteHost := sendTo.sRemoteHost,
nRemotePort := sendTo.nRemotePort,
hSocket:= hSocket,
pSrc:= ADR( sendTo.msg ),
cbLen:= LEN( sendTo.msg ) + 1, (* include the end delimiter *)
bExecute:= TRUE );
eStep := UDP_STATE_SEND_WAIT;
UDP_STATE_SEND_WAIT:
fbSendTo( bExecute := FALSE );
IF NOT fbSendTo.bBusy THEN
IF NOT fbSendTo.bError THEN
eStep := UDP_STATE_RECEIVE_START;
ELSE
LogError( 'FB_SocketSendTo (UDP)', fbSendTo.nErrId );
nErrId := fbSendTo.nErrId;
eStep := UDP_STATE_ERROR;
END_IF
END_IF
UDP_STATE_RECEIVE_START:
MEMSET( ADR( receivedFrom ), 0, SIZEOF( receivedFrom ) );
fbReceiveFrom( bExecute := FALSE );
fbReceiveFrom( sSrvNetId:=g_sTcIpConnSvrAddr,
hSocket:= hSocket,
pDest:= ADR( receivedFrom.msg ),
cbLen:= SIZEOF( receivedFrom.msg ) - 1, (*without string delimiter *)
bExecute:= TRUE );
eStep := UDP_STATE_RECEIVE_WAIT;
UDP_STATE_RECEIVE_WAIT:
fbReceiveFrom( bExecute := FALSE );
IF NOT fbReceiveFrom.bBusy THEN
IF NOT fbReceiveFrom.bError THEN
IF fbReceiveFrom.nRecBytes > 0 THEN
receivedFrom.nRemotePort := fbReceiveFrom.nRemotePort;
receivedFrom.sRemoteHost := fbReceiveFrom.sRemoteHost;
receiveFifo.AddTail( new := receivedFrom );
IF NOT receiveFifo.bOk THEN(* Check for fifo overflow *)
LogError( 'Receive fifo overflow!', PLCPRJ_ERROR_RECFIFO_OVERFLOW );
END_IF
END_IF
eStep := UDP_STATE_IDLE;
ELSIF fbReceiveFrom.nErrId = 16#80072746 THEN
LogError( 'The connection is reset by remote side.', fbReceiveFrom.nErrId );
eStep := UDP_STATE_IDLE;
ELSE
LogError( 'FB_SocketUdpReceiveFrom (UDP client/server)', fbReceiveFrom.nErrId );
nErrId := fbReceiveFrom.nErrId;
eStep := UDP_STATE_ERROR;
END_IF
END_IF
UDP_STATE_CLOSE_START:
fbClose( bExecute := FALSE );
fbClose( sSrvNetId:= g_sTcIpConnSvrAddr,
hSocket:= hSocket,
bExecute:= TRUE );
eStep := UDP_STATE_CLOSE_WAIT;
UDP_STATE_CLOSE_WAIT:
fbClose( bExecute := FALSE );
IF NOT fbClose.bBusy THEN
LogMessage( 'Socket closed (UDP)!', hSocket );
bCreated := FALSE;
MEMSET( ADR(hSocket), 0, SIZEOF(hSocket));
IF fbClose.bError THEN
LogError( 'FB_SocketClose (UDP)', fbClose.nErrId );
nErrId := fbClose.nErrId;
eStep := UDP_STATE_ERROR;
ELSE
bBusy := FALSE;
bError := FALSE;
nErrId := 0;
eStep := UDP_STATE_IDLE;
END_IF
END_IF
UDP_STATE_ERROR: (* Error step *)
bError := TRUE;
IF bCreated THEN
eStep := UDP_STATE_CLOSE_START;
ELSE
bBusy := FALSE;
eStep := UDP_STATE_IDLE;
END_IF
END_CASE
MAIN program
Previously opened sockets must be closed after a program download or a PLC reset. During PLC start-up, this is done by calling an instance of the FB_SocketCloseAll function block. If one of the variables bSendOnceToItself or bSendOnceToRemote has an rising edge, a new Fifo entry is generated and stored in the send Fifo. Received messages are removed from the receive Fifo and displayed in a message box.
PROGRAM MAIN
VAR CONSTANT
LOCAL_HOST_IP : STRING(15) := '';
LOCAL_HOST_PORT : UDINT := 1001;
REMOTE_HOST_IP : STRING(15) := '172.16.2.209';
REMOTE_HOST_PORT : UDINT := 1001;
END_VAR
VAR
fbSocketCloseAll : FB_SocketCloseAll;
bCloseAll : BOOL := TRUE;
fbPeerToPeer : FB_PeerToPeer;
sendFifo : FB_Fifo;
receiveFifo : FB_Fifo;
sendToEntry : ST_FifoEntry;
entryReceivedFrom : ST_FifoEntry;
tmp : STRING;
bSendOnceToItself : BOOL;
bSendOnceToRemote : BOOL;
END_VAR
IF bCloseAll THEN (*On PLC reset or program download close all old connections *)
bCloseAll := FALSE;
fbSocketCloseAll( sSrvNetId:= g_sTcIpConnSvrAddr, bExecute:= TRUE, tTimeout:= T#10s );
ELSE
fbSocketCloseAll( bExecute:= FALSE );
END_IF
IF NOT fbSocketCloseAll.bBusy AND NOT fbSocketCloseAll.bError THEN
IF bSendOnceToRemote THEN
bSendOnceToRemote := FALSE; (* clear flag *)
sendToEntry.nRemotePort := REMOTE_HOST_PORT; (* remote host port number*)
sendToEntry.sRemoteHost := REMOTE_HOST_IP; (* remote host IP address *)
sendToEntry.msg := 'Hello remote host!'; (* message text*);
sendFifo.AddTail( new := sendToEntry ); (* add new entry to the send queue*)
IF NOT sendFifo.bOk THEN (* check for fifo overflow*)
LogError( 'Send fifo overflow!', PLCPRJ_ERROR_SENDFIFO_OVERFLOW );
END_IF
END_IF
IF bSendOnceToItself THEN
bSendOnceToItself := FALSE; (* clear flag *)
sendToEntry.nRemotePort := LOCAL_HOST_PORT; (* nRemotePort == nLocalPort => send it to itself *)
sendToEntry.sRemoteHost := LOCAL_HOST_IP; (* sRemoteHost == sLocalHost =>send it to itself *)
sendToEntry.msg := 'Hello itself!'; (* message text*);
sendFifo.AddTail( new := sendToEntry ); (* add new entry to the send queue*)
IF NOT sendFifo.bOk THEN (* check for fifo overflow*)
LogError( 'Send fifo overflow!', PLCPRJ_ERROR_SENDFIFO_OVERFLOW );
END_IF
END_IF
(* send and receive messages *)
fbPeerToPeer( sendFifo := sendFifo, receiveFifo := receiveFifo, sLocalHost := LOCAL_HOST_IP, nLocalPort := LOCAL_HOST_PORT, bEnable := TRUE );
(* remove all received messages from receive queue *)
REPEAT
receiveFifo.RemoveHead( old => entryReceivedFrom );
IF receiveFifo.bOk THEN
tmp := CONCAT( 'RECEIVED from: ', entryReceivedFrom.sRemoteHost );
tmp := CONCAT( tmp, ', Port: ' );
tmp := CONCAT( tmp, UDINT_TO_STRING( entryReceivedFrom.nRemotePort ) );
tmp := CONCAT( tmp, ', msg: %s' );
ADSLOGSTR( ADSLOG_MSGTYPE_HINT OR ADSLOG_MSGTYPE_MSGBOX, tmp, entryReceivedFrom.msg );
END_IF
UNTIL NOT receiveFifo.bOk
END_REPEAT
END_IF