Quick Start (C++ / TCP Client)

This Quick Start shows the implementation of a TCP client as a TwinCAT 3 C++ project.

The engineering system must meet the requirements for TwinCAT 3 C++.

The example is also available for download under Sample 01.

Creating a TwinCAT C++ project

In this step, a new TwinCAT 3 C++ project is created.

1. Create a new TwinCAT project
Quick Start (C++ / TCP Client) 1:
Quick Start (C++ / TCP Client) 2:
2. Add a TwinCAT C++ project
Quick Start (C++ / TCP Client) 3:
3. Select a Driver project
Quick Start (C++ / TCP Client) 4:
4. Use the wizard for a module class with "Cyclic IO" as the basis for the TCP client.
Quick Start (C++ / TCP Client) 5:
The result is a complete TwinCAT C++ project.

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

TMC editor for creating interfaces, pointers and parameters

After creating the project, the next step involves implementation of the C++ TCP client.

1. The module created by the wizard must implement the interface "ITcIoTcpProtocolRecv". Open the TMC editor by double-clicking on the TMC file for the project. Add the interface to the module under "Implemented Interfaces".
Quick Start (C++ / TCP Client) 7:Under "Implemented Interfaces" open a selection of the available interfaces by clicking on the "+" button. Select "ITcIoTcpProtocolRecv".
2. In addition, an "ITcIOTcpProtocol" interface pointer is required.
Quick Start (C++ / TCP Client) 8:
3. By creating a parameter the server IP address to be contacted and the port become configurable.
Quick Start (C++ / TCP Client) 9:
Quick Start (C++ / TCP Client) 10:
4. Now use the TMC code generator to prepare the code of the C++ module.
Quick Start (C++ / TCP Client) 11:
Start the TMC code generator by selecting the appropriate menu item in the context menu (right-click) of the C++ project.
All steps in the TMC editor are now completed.

Implement TCP client

1. Create two member variables in the module header file (here: Modul1.h).
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. These are initialized in the Constructor (Module1.cpp).
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. The interface pointer m_spTcpProt is now initialized in the Transition SO (i.e. in method SetObjStateSO).
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 the Transition OS (i.e. method SetObjStateOS) a connection that may exist is closed, and the socket is released.
///////////////////////////////////////////////////////////////////////////////
// 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. The actual process is implemented in the "CycleUpdate" method, which is called cyclically. Establishes a TCP connection to a server (address is provided in parameters "m_TcpServerIpAddress" and "m_TcpServerPort"). The connection handle is stored in the member variable "m_SockId". The connection is used to issue a simple http GET request.

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. The module implements the interface "ITcIoTcpProtocolRecv", as a result of which the TMC code generator created a "ReceiveEvent" method. This is called when an event is received and must therefore be able to deal with a wide range of event types.
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. Analogous to the "ReceiveEvent" method, a "ReceiveData" method was created from the "ITcIoTcpProtocolRecv" interface. It is responsible for receiving the data and is implemented as follows:
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. The module is now ready and can be compiled. (Right-click on "Build" project).
9. An instance of the module is created:
Right-click on the C++ project
Quick Start (C++ / TCP Client) 12:
and select the module
Quick Start (C++ / TCP Client) 13:
The instance is associated with a task, so that the "CycleUpdate" method is called. Quick Start (C++ / TCP Client) 14:

Preparing the network card

For the TCP/UDP RT module, make sure that the RT Ethernet adapter in the TwinCAT solution is connected with the correct network card (with TwinCAT driver).

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

Local configuration only

Installation of the driver on compatible network cards via the button "Compatible Devices" always takes place locally. On a controller with TwinCAT XAR, the program TcRteInstall.exe can be used. It is included in the installation (usually under C:TwinCAT\3.1\System).

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

“TCP/UDP RT” module configuration

Notice Variable names relating to TCP are used here. They have to be substituted accordingly.

1. Create the “TCP/UDP RT” module under the RT Ethernet adapter by selecting “Add Object(s)…” in the context menu.
Quick Start (C++ / TCP Client) 17:
2. Then select the “TCP/UDP RT” module:
Quick Start (C++ / TCP Client) 18:
The TCP/UDP RT object is created under the adapter.
Quick Start (C++ / TCP Client) 19:
3. Parameterize the previously created instance of the module (here: Module1) under “Interface Pointer” “TcpProt” with the OID of the created “TCP/UDP RT” object: Quick Start (C++ / TCP Client) 20:
4. For PLC projects this configuration is also done at the instance, under the tab “Symbol Initialization”:
Quick Start (C++ / TCP Client) 21:
The configuration is thus completed
Quick Start (C++ / TCP Client) 22:

Disconnection by the operating system in Promiscuous mode

If Promiscuous mode is active at the RT Ethernet adapter in the “Adapter” tab, any TCP connection attempts are blocked by the operating system, since it does not recognize a port opened in the TCP/UDP RT object.

Handling

1. The sample is ready to use once you have configured both the TcpServerIpAddress and the TcpServerPort at the module instance:
Quick Start (C++ / TCP Client) 23:
Notice Possible source of error: A test web server 62.159.14.51 is queried in the sample. A corresponding HTTP command is stored in the source code. IP address, port, and this HTTP command may have to be adjusted.
2. After activating the configuration you can see log messages (see source code) and the first 100 bytes of the response from the server in the output:
Quick Start (C++ / TCP Client) 24:
3. To output these messages the “Tracelevel” can be configured (via Info):
Quick Start (C++ / TCP Client) 25:

The procedure is carried out once when the program starts.

A new request is sent if “m_bSendRequest” is set to TRUE (e.g. through TwinCAT Live Watch). The return of the SendData method is stored in hrSend. For the sample it can be monitored via the debugger.