Beckhoff / ADS

Beckhoff protocol to communicate with TwinCAT devices.
MIT License
491 stars 193 forks source link

AmsRouter does not permit renewing a broken TCP connection #197

Open FloCD opened 1 year ago

FloCD commented 1 year ago

Hello,

i am currently testing the ADS library, especially reconnection behaviour and behaviour with more than one connection.

What works so far (in synchronous fashion):

Therefore I have (based on the example) written a small program which synchronously connects two connections to the same target. One Connection only connects initially, the second one reads values from the SPS and also tries to reconnect (when the SPS is going into config mode):

#include "AdsLib.h"
#include "AdsVariable.h"

#include <array>
#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
#include <ctime>
#include <signal.h>

static bool readByNameExample(std::unique_ptr<AdsDevice>& route, const std::string& loggingPrefix)
{
    try {
        AdsVariable<uint8_t> readVar {*route, "MAIN.testVar"};
        std::cout << loggingPrefix << ": " << __FUNCTION__ << "():\n";
        std::cout << loggingPrefix << ": ADS read " << std::hex << (uint32_t)readVar << '\n';
    } catch (const AdsException& ex) {
        std::cout << loggingPrefix << ": Error: " << ex.errorCode << "\n";
        std::cout << loggingPrefix << ": AdsException message: " << ex.what() << "\n";

        return false;
    }
    return true;
}

static const AmsNetId remoteNetId_1 {<NETID1>};
static const char remoteIpV4_1[] = "<IPADDRESS1>";
//static const AmsNetId remoteNetId_2 {<NETID2};
//static const char remoteIpV4_2[] = "<IPADDRESS2>";

static void Connect(std::unique_ptr<AdsDevice>& route, const std::string& remoteIpV4, const AmsNetId& remoteNetId, const std::string& loggingPrefix)
{
    while(true){
        std::cout << loggingPrefix << ": Connecting ..." << "\n";
        bool connected{true};
        try {
            route = std::make_unique<AdsDevice>(remoteIpV4, remoteNetId, 899);
        } catch (const AdsException& ex) {
            std::cout << loggingPrefix << ": Error: " << ex.errorCode << "\n";
            std::cout << loggingPrefix << ": AdsException message: " << ex.what() << "\n";
            connected = false;
        } catch (const std::runtime_error& ex) {
                std::cout << loggingPrefix << ": " << ex.what() << '\n';
                connected = false;
        }

        if(connected){
            break;
        }
    }
    std::cout << loggingPrefix << ": Connected!" << "\n";
}

static void Disconnect(std::unique_ptr<AdsDevice>& route, const std::string& loggingPrefix)
{
    route.reset();
    std::cout <<  loggingPrefix << ": disconnected!" << '\n';
}

enum class CONN_STATE
{
    DISCONNECTED,
    CONNECTED,
    READING,
    RECONNECT_EXPECTED
};

static CONN_STATE stateRoute1{CONN_STATE::DISCONNECTED};
static CONN_STATE stateRoute2{CONN_STATE::DISCONNECTED};

static void ConnectionStateMachine(CONN_STATE& connectionState, 
                                   std::unique_ptr<AdsDevice>& route, 
                                   const std::string& remoteIpV4, 
                                   const AmsNetId& remoteNetId,
                                   const std::string& loggingPrefix)
{
    switch(connectionState)
    {
        case CONN_STATE::DISCONNECTED:
            Connect(route, remoteIpV4, remoteNetId, loggingPrefix);
            connectionState = CONN_STATE::CONNECTED;
            break;
        case CONN_STATE::CONNECTED:
            connectionState = CONN_STATE::READING;
            break;
        case CONN_STATE::READING:
            if(!readByNameExample(route, loggingPrefix))
            {
                connectionState = CONN_STATE::RECONNECT_EXPECTED;
                break;
            }
            std::this_thread::sleep_for(std::chrono::seconds(1));
            break;
        case CONN_STATE::RECONNECT_EXPECTED:
            Disconnect(route, loggingPrefix);
            std::this_thread::sleep_for(std::chrono::seconds(10));
            connectionState = CONN_STATE::DISCONNECTED;
            break;
        default:
            break;
    }
} 

static void runExample()
{
    signal(SIGPIPE, SIG_IGN);

    // uncomment and adjust if automatic AmsNetId deduction is not working as expected
    bhf::ads::SetLocalAddress({<LOCAL_NETID>});

    std::unique_ptr<AdsDevice> route1;
    std::unique_ptr<AdsDevice> route2;

    ConnectionStateMachine(stateRoute1, route1, remoteIpV4_1, remoteNetId_1, "connection 1");
    while(true)
    {
        ConnectionStateMachine(stateRoute2, route2, remoteIpV4_1, remoteNetId_1, "connection 2");
    }
}

int main()
{
    runExample();
    std::cout << "Hit ENTER to continue\n";
    //std::cin.ignore();
}

In the output the following happens:

connection 1: Connecting ...
connection 1: Connected!
connection 2: Connecting ...
connection 2: Connected!
connection 2: readByNameExample():
connection 2: ADS read 41
connection 2: readByNameExample():
connection 2: ADS read a5
connection 2: readByNameExample():
connection 2: ADS read a
connection 2: readByNameExample():
connection 2: ADS read 6e
connection 2: readByNameExample():
connection 2: ADS read d3
connection 2: readByNameExample():
connection 2: ADS read 38
connection 2: readByNameExample():
connection 2: ADS read 9d
connection 2: readByNameExample():
connection 2: ADS read 1
connection 2: readByNameExample():
connection 2: ADS read 66
connection 2: readByNameExample():
connection 2: ADS read ca
connection 2: readByNameExample():
connection 2: ADS read 2f
connection 2: readByNameExample():
connection 2: ADS read 93
2023-04-04T13:33:53+0200 Info: connection closed by remote
connection 2: Error: 745
connection 2: AdsException message: Ads operation failed with error code 1861.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
connection 2: Error: 745
connection 2: AdsException message: Ads operation failed with error code 1861.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:34:21+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:34:31+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:34:41+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:34:51+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:35:01+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:35:11+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:35:21+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!
connection 2: Connecting ...
connection 2: Connected!
2023-04-04T13:35:31+0200 Error: write frame failed with error: Broken pipe
connection 2: Error: ffffffff
connection 2: AdsException message: Ads operation failed with error code 4294967295.
connection 2: disconnected!

My guess is that the AmsRouter still holds the connection opened by one of the connections and therefore does not permit a clean disconnect (see function AmsRouter::DeleteIfLastConnection() ).

If there is the need for more information I am happy to help!

All the best

Florian

pbruenn commented 1 year ago

2023-04-04T13:33:53+0200 Info: connection closed by remote means the remote target has closed the TCP connection. Now, all your AdsDevice instances (routes) are invalid. You would have to delete all of them and add them back, to force AmsRouter to reestablish the TCP connection. The AmsRouter class is no real ADS router like you find in TwinCAT. It is more an AMS -> TCP translator. Without any connection handling.

Your problem is similar to https://github.com/Beckhoff/ADS/issues/46 https://github.com/Beckhoff/ADS/issues/190

FloCD commented 1 year ago

Yes I also think that this would be a possible way to resolve that issue. Are there any plans to enhance the library regarding this scenario? PS: I doubt that issue #190 is similar to mine, because in my scenario I connect to a different target, not localhost.