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

PLC devices A and B 1:

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 a raising 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