SPS-Teilnehmer A und B
Die benötigte Funktionalität wurde in dem Funktionsbaustein FB_PeerToPeer gekapselt. Jeder der Kommunikationspartner benutzt eine Instanz des FB_PeerToPeer-Funktionsbausteins. Durch eine steigende Flanke am bEnable-Eingang wird der Baustein aktiviert. Dabei wird ein neuer UDP-Socket geöffnet und der Datenaustausch gestartet. Die Socket-Adresse wird durch die Variablen sLocalHost und nLocalPort festgelegt. Eine fallende Flanke stoppt den Datenaustausch und schließt den Socket. Die zu sendenden Daten werden per Referenz (VAR_IN_OUT) über die Variable sendFifo an den Baustein übergeben. Die empfangenen Daten werden in die Variable receiveFifo abgelegt.
Name | Default-Wert | Beschreibung |
---|---|---|
g_sTcIpConnSvrAddr | '' | Die Netzwerkadresse des TwinCAT TCP/IP Connection Servers. Default: Leerstring (der Server befindet sich auf dem lokalen PC) |
bLogDebugMessages | TRUE | Aktiviert/deaktiviert das Schreiben von Nachrichten ins Logbuch des Betriebssystems |
PLCPRJ_ERROR_SENDFIFO_OVERFLOW | 16#8103 | Fehlercode Beispielprojekt: Der Sende-Fifo ist voll. |
PLCPRJ_ERROR_RECFIFO_OVERFLOW | 16#8104 | Fehlercode Beispielprojekt: Der Empfangs-Fifo ist voll. |
FUNCTION_BLOCK FB_PeerToPeer

Schnittstelle
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
Realisierung
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
Programm MAIN
Nach einem Programm-Download oder SPS-Reset müssen die vorher geöffneten Sockets geschlossen werden. Dies geschieht beim SPS-Start durch den einmaligen Aufruf einer Instanz des FB_SocketCloseAll-Funktionsbausteins. Bei einer steigender Flanke an einer der Variablen: bSendOnceToItself oder bSendOnceToRemote wird ein neuer Fifo-Eintrag generiert und in den Sende-Fifo abgelegt. Empfangene Nachrichten werden aus dem Empfangs-Fifo entnommen und in einer Messagebox angezeigt.
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