Quick Start (C++ / TCP Client)

Dieses Quick Start zeigt die Implementierung eines TCP Clients als TwinCAT 3 C++ Projekt.

Das Engineering System muss dabei die Voraussetzungen für TwinCAT 3 C++ erfüllen.

Das Beispiel ist auch als Download Sample 01 verfügbar.

TwinCAT C++ Projekt anlegen

In diesem Arbeitsschritt wird ein neues TwinCAT 3 C++ Projekt angelegt.

1. Legen Sie ein neues TwinCAT Projekt an
Quick Start (C++ / TCP Client) 1:
Quick Start (C++ / TCP Client) 2:
2. Fügen Sie ein TwinCAT C++ Projekt hinzu
Quick Start (C++ / TCP Client) 3:
3. Wählen Sie ein Driver Projekt aus
Quick Start (C++ / TCP Client) 4:
4. Verwenden Sie als Grundlage für den TCP Client den Wizard für eine Modul-Klasse mit „Cyclic IO“.
Quick Start (C++ / TCP Client) 5:
Als Ergebnis liegt ein fertiges TwinCAT C++ Projekt vor.

Quick Start (C++ / TCP Client) 6:

TMC Editor zum Anlegen von Interfaces, Pointern und Parametern

Nach dem Anlegen des Projektes wird in diesem Arbeitsschritt die Implementierung des C++ TCP Clients vorgenommen.

1. Das durch den Wizard erstellte Modul muss das Interface „ITcIoTcpProtocolRecv“ implementieren. Öffnen Sie den TMC Editor, indem Sie auf die TMC Datei des Projektes doppelklicken. Fügen Sie das Interface dem Modul unter „Implemented Interfaces“ hinzu.
Quick Start (C++ / TCP Client) 7:Unter „Implemented Interfaces“ öffnen Sie eine Auswahl der bereitstehenden Interfaces durch einen Klick auf den „+“-Button. Wählen Sie hier „ITcIoTcpProtocolRecv“ aus.
2. Zusätzlich benötigen Sie einen Interface Pointer „ITcIOTcpProtocol“.
Quick Start (C++ / TCP Client) 8:
3. Durch Anlegen eines Parameters werden die zu kontaktierende Server IP Adresse und der Port konfigurierbar.
Quick Start (C++ / TCP Client) 9:
Quick Start (C++ / TCP Client) 10:
4. Benutzen Sie nun den TMC Code Generator, um den Code des C++ Moduls vorzubereiten.
Quick Start (C++ / TCP Client) 11:
Starten Sie den TMC Code Generator indem Sie auf dem C++ Projekt im Kontexmenü (Rechts-Klick) den entsprechenden Menüpunkt auswählen.
Alle Schritte im TMC Editor sind nun abgeschlossen.

TCP Client implementieren

1. Legen Sie in der Modul Header Datei (hier: Modul1.h) zwei Member-Variablen an.
ULONG     m_SockId;
BOOL m_bSendRequest;  //set by debugger for sending a http command
ULONG m_connections;  //count number of connection attempts
HRESULT m_hrSend;     //Last hr of SendData
2. Diese werden in dem Constructor (Module1.cpp) initialisiert.
CModule1::CModule1()
    : m_Trace(m_TraceLevelMax, m_spSrv)
    , m_TraceLevelMax(tlAlways)
    , m_hrSend(0)
{
    m_SockId = 0; //added
    m_bSendRequest = true; //added
    m_connections = 0; //added
}
3. Der Interface Pointer m_spTcpProt wird nun in der Transition SO (also in Methode SetObjStateSO) initialisiert.
HRESULT CTcpClient::SetObjStateSO()
{
    m_Trace.Log(tlVerbose, FENTERA);
    RESULT hr = S_OK;
    if (SUCCEEDED(hr) && m_spTcpProt.HasOID())    //added
    {                                             //added
        hr = m_spSrv->TcQuerySmartObjectInterface(m_spTcpProt); //added
    }                                             //added
    hr = FAILED(hr) ? hr : AddModuleToCaller();
4. In der Transition OS (also Methode SetObjStateOS) wird eine evtl. vorhandene Verbindung abgebaut und der Socket freigegeben.
///////////////////////////////////////////////////////////////////////////////
// State transition from OP to SAFEOP
HRESULT CTcpClient::SetObjStateOS()
{
    //start added code
    m_Trace.Log(tlVerbose, FENTERA);
    HRESULT hr = S_OK;

    if ( m_SockId != 0 )
    {
        if (m_spTcpProt->IsConnected(m_SockId) == S_OK)
        {
            m_spTcpProt->Close(m_SockId);
            m_spTcpProt->CheckReceived();
        }
     m_spTcpProt->FreeSocket(m_SockId);
     m_SockId = 0;
    }

    RemoveModuleFromCaller();

    m_Trace.Log(tlVerbose, FLEAVEA "hr=0x%08x", hr);
    return hr;
    //end added code
}
5. In der „CycleUpdate“ Methode, die zyklisch aufgerufen wird, wird der eigentliche Ablauf implementiert. Hier wird eine TCP Verbindung zu einem Server aufgebaut (Adresse wird in Parametern „m_TcpServerIpAddress“ und „m_TcpServerPort“ bereitgestellt). Das Handle zur Verbindung wird in der Member-Variable „m_SockId“ abgelegt. Die Verbindung wird genutzt, um einen einfachen http-GET-Request abzusetzen.

HRESULT CTcpClient::CycleUpdate(ITcTask* ipTask, ITcUnknown* ipCaller, ULONG_PTR context)
{
    HRESULT hr = S_OK;
    //start added code
    if ( m_SockId == 0 )
    {
        if (SUCCEEDED_DBG(hr = m_spTcpProt->AllocSocket(THIS_CAST(ITcIoTcpProtocolRecv), m_SockId)))
        {
            if (FAILED(hr = m_spTcpProt->Connect(m_SockId, ((PULONG)&m_TcpServerIpAddress)[0], m_TcpServerPort)))
            {
                m_spTcpProt->FreeSocket(m_SockId);
                m_SockId = 0;
            }
else {
                m_connections++; //count number of connections
            }
        }
    }
    else
    {
    if ( m_bSendRequest && m_spTcpProt->IsConnected(m_SockId) == S_OK )
    {
        PCHAR pRequest = "GET / HTTP/1.1\r\nHOST: beckhoff.com\r\n\r\n ";
        ULONG nSendData = 0;
        m_hrSend = m_spTcpProt->SendData(m_SockId, strlen(pRequest), pRequest, nSendData);
        m_bSendRequest = false;
    }
    }

    m_spTcpProt->CheckReceived();

    //end added code
    return hr;
}
6. Das Modul implementiert das Interface „ITcIoTcpProtocolRecv“, wodurch der TMC Code Generator eine Methode „ReceiveEvent“ angelegt hat. Diese wird aufgerufen, wenn ein Event empfangen wurde und muss somit mit den unterschiedlichen Event-Typen umgehen können.
HRESULT CTcpClient::ReceiveEvent(ULONG socketId, TCPIP_EVENT tcpEvent)
{
//start added code
m_Trace.Log(tlInfo, FLEAVEA "Receive TCP Event: SocketId: %d Event: %d \n", socketId, tcpEvent);

    switch (tcpEvent)
    {
    case TCPIP_EVENT_ERROR:
    case TCPIP_EVENT_RESET:
    case TCPIP_EVENT_TIMEOUT:
    m_Trace.Log(tlInfo, FLEAVEA "Connection to remote server failed!\n");
           m_SockId = 0;
        break;
    case TCPIP_EVENT_CONN_CLOSED:
        m_Trace.Log(tlInfo, FLEAVEA "Close connection: SocketId: %d \n", socketId);
          m_SockId = 0;
        break;
    case TCPIP_EVENT_CONN_INCOMING:
    case TCPIP_EVENT_KEEP_ALIVE:
    case TCPIP_EVENT_CONN_IDLE:
    case TCPIP_EVENT_DATA_SENT:
    case TCPIP_EVENT_DATA_RECEIVED:
        break;
    default:
        break;
    }
    return S_OK;
    //end added code
}
7. Äquivalent zu der „ReceiveEvent“ Methode wurde eine „ReceiveData“ Methode ebenfalls aus dem Interface „ITcIoTcpProtocolRecv“ angelegt. Diese ist für das Empfangen der Daten zuständig und wird wie folgt implementiert:
HRESULT CTcpClient::ReceiveData(ULONG socketId, ULONG nData, PVOID pData)
{
//start added code
    HRESULT hr = S_OK;
    PCHAR pResponse = new CHAR[100];
    memset(pResponse, 0, 100);
    memcpy(pResponse, pData, min(100, nData));
    m_Trace.Log(tlInfo, FLEAVEA "Receive answer w/ length %d : first 100 chars:'%s'", nData, pResponse);
    return hr;
//end added code
}
8. Das Modul ist nun fertig und kann kompiliert werden. (Rechts-Klick auf das Projekt “Build”).
9. Eine Instanz des Moduls wird angelegt:
Dafür Rechts-Klick auf das C++ Projekt
Quick Start (C++ / TCP Client) 12:
und Auswahl des Moduls
Quick Start (C++ / TCP Client) 13:
Die Instanz wird mit einem Task verbunden, sodass die „CycleUpdate“ Methode aufgerufen wird. Quick Start (C++ / TCP Client) 14:

Vorbereitung Netzwerkkarte

Stellen Sie für das TCP/UDP RT Modul sicher, dass der RT-Ethernet-Adapter in der TwinCAT Solution auf die richtige Netzwerkkarte (mit TwinCAT Treiber) verbunden ist.

Quick Start (C++ / TCP Client) 15:

Nur Lokale Konfiguration

Die Installation des Treibers auf kompatiblen Netzwerkkarten über den Button „Compatible Devices“ erfolgt immer lokal. Auf einer Steuerung mit TwinCAT XAR kann das mitinstallierte Programm TcRteInstall.exe (normalerweise unter C:\TwinCAT\3.1\System) genutzt werden.

Quick Start (C++ / TCP Client) 16:

“TCP/UDP RT” Modul Konfiguration

Hinweis

Variablennamen

Hier werden Variablennamen in Bezug auf TCP verwendet. Diese sind entsprechend zu ersetzen.

1. Legen Sie das „TCP/UDP RT“ Modul unterhalb des RT-Ethernet-Adapters an, indem Sie „Add Object(s)…“ im Context-Menü anwählen.
Quick Start (C++ / TCP Client) 17:
2. Dann wählen Sie das „TCP/UDP RT“ Modul aus:
Quick Start (C++ / TCP Client) 18:
Das TCP/UDP RT Objekt wird unterhalb des Adapters angelegt.
Quick Start (C++ / TCP Client) 19:
3. Parametrieren Sie die zuvor angelegte Instanz des Moduls (hier: Modul1) unter „Interface Pointer“ „TcpProt“ mit der OID des angelegten „TCP/UDP RT“ Objekts: Quick Start (C++ / TCP Client) 20:
4. Bei PLC Projekten ist diese Konfiguration ebenso an der Instanz vorzunehmen, hier jedoch unter dem Reiter „Symbol Initialization“:
Quick Start (C++ / TCP Client) 21:
Damit ist die Konfiguration abgeschlossen
Quick Start (C++ / TCP Client) 22:

Verbindungsabbruch durch Betriebssystem bei Promiscuous Mode

Wenn an dem RT-Ethernet Adapter im Tab „Adapter“ der Promiscuous Mode eingeschaltet ist, werden eintreffende TCP Verbindungsaufbauten durch das Betriebssystem abgebrochen, da dieses einen im TCP/UDP RT Objekt geöffneten Port nicht kennt.

Handhabung

1. Das Beispiel ist betriebsbereit, nachdem Sie an der Modulinstanz sowohl die TcpServerIpAddress als auch den TcpServerPort konfiguriert haben:
Quick Start (C++ / TCP Client) 23:
Hinweis Mögliche Fehlerquelle: Hier im Beispiel wird ein Test-Webserver 62.159.14.51 abgefragt. Dafür ist im Sourcecode oben ein entsprechender HTTP Befehl hinterlegt. IP-Adresse, Port und dieser HTTP Befehl müssen ggf. angepasst werden.
2. Nach dem Aktivieren der Konfiguration können Sie im Output sowohl Log Meldungen (vgl. Source Code) als auch die ersten 100 Bytes der Antwort des Servers sehen:
Quick Start (C++ / TCP Client) 24:
3. Für die Ausgabe dieser Meldungen kann der „Tracelevel“ (auf Info) konfiguriert werden:
Quick Start (C++ / TCP Client) 25:

Der Ablauf erfolgt einmalig beim Start des Programms.

Wenn „m_bSendRequest“ auf TRUE gesetzt wird (z.B. durch das TwinCAT Live Watch), wird ein neuer Request gesendet. Die Rückgabe der SendData Methode wird in hrSend abgelegt – für das Beispiel kann sie per Debugger beobachtet werden.