mathiask88 / node-snap7

node.js wrapper for snap7
MIT License
163 stars 59 forks source link

S7 Client returns invalid Connected status #51

Closed leovujanic closed 4 years ago

leovujanic commented 5 years ago

Hi,

I'm running the latest version of node-snap7 and having trouble with S7Client.Connected() method which returns true in a case when the client was connected to PLC and network broke in the meanwhile (eg. turn off WIFI).

mathiask88 commented 5 years ago

Hi, sockets are not able to notice if the connection is interrupted until they send a message and this message times out. So if you interrupt your network the connection status will be true until the client sends a new message and receives a timeout.

leovujanic commented 5 years ago

Thank you @mathiask88 for the response. Not sure if I'm doing something wrong but with the code bellow, Connected() always returns true. I'm using TS and non-blocking calls in my app, this is just a snippet to check behavior. After the first read, before the timeout fires, I disable WIFI. Second readDb call fails but Connected still returns true.

const snap7 = require('node-snap7');

const S7Client = new snap7.S7Client();

function readDb(readCnt) {
    const res = S7Client.DBRead(2, 10, 2);
    if (res === false) {
        console.error(`Unable to read from DB [${readCnt}]`);
        return;
    }
    console.log(`Result[${readCnt}]: ${res.readUInt16BE(0)}`);
}

S7Client.ConnectTo('192.168.1.40', 0, 1, function (err) {
    if (err) {
        console.error(' >> Connection failed. Code #' + err + ' - ' + S7Client.ErrorText(err));
        return false;
    }

    console.log(`Connected[1]: ${S7Client.Connected()}`);
    readDb(1);
    console.log(`Connected[2]: ${S7Client.Connected()}`);

    setTimeout(function () {
        readDb(2);
        console.log(`Connected[3]: ${S7Client.Connected()}`);
    }, 10000);
});

process.on('SIGINT', function () {
    S7Client.Disconnect();
});

and the output is:

Connected[1]: true
Result[1]: 23
Connected[2]: true
Unable to read from DB [2]
Connected[3]: true

If I manually call Disconnect() then Connected() returns false as expected, or in the case when the connection to PLC was never established.

mathiask88 commented 5 years ago

Sorry, I've currently no test setup ready. I'll search for my S7-1500 in the cellar and test it next week :)

leovujanic commented 5 years ago

I think the same behavior is reported in python wrapper for snap7 - https://github.com/gijzelaerr/python-snap7/issues/111 . Their conclusion is that this is a bug in the snap7 lib. I'll check that issue is reported to snap7 lib directly. Maybe this helps you.

mathiask88 commented 5 years ago

Can you write out the LastError() here console.error(`Unable to read from DB [${readCnt}]`); ? Connection property is only set to false if socket errors with WSAECONNRESET

leovujanic commented 5 years ago

Error code is 655470 and message is: ISO: An error occurred during recv TCP : Connection timed out.

mathiask88 commented 5 years ago

Now we can discuss if a WSAETIMEDOUT really is a disconnect 😃 But what you can do is to check for the timeout error code and disconnect from client side.

leovujanic commented 5 years ago

Yes, that's exactly what I have done, it was a minor design change. :)

I can make another test day after tomorrow and check if the client is able to read after timeout error occurs and the connection is established again. If it fails to read, from my point of view, WSAETIMEDOUT should be considered as "disconnected". What do you think? Does that make sense to you?

mathiask88 commented 5 years ago

@leovujanic What was the result, can this be closed?

leovujanic commented 5 years ago

Hi @mathiask88 , sorry for not replying, I was tight with time and didn't manage to test it in an isolated environment. I will do it through the weekend and reply.

leovujanic commented 5 years ago

Hi @mathiask88 , I apologize for the late reply. I've made a few tests and the conclusion is that the client can recover from Connection timed out error. But interesting thing to note is that after n time out errors (or after some time - not sure), a new error is received ISO : An error occurred during send TCP : Other Socket error (32), number 589856. After that error client is unable to auto-reconnect and Connected() method always returns true. My conclusion is that Connected() will return false if the initial connection to plc wasn't established or Disconnect() is called.

This is the code:

const snap7 = require('node-snap7');

const S7Client = new snap7.S7Client();
let interval = null;

function readDb(readCnt) {
    const res = S7Client.DBRead(2, 250.0, 2);
    if (res === false) {
        console.error(`Unable to read from DB [${readCnt}] ${S7Client.ErrorText(S7Client.LastError())} | ${S7Client.LastError()}`);
        return;
    }
    console.log(`Result[${readCnt}]: ${res.readUInt16BE(0)}`);
}

S7Client.ConnectTo('192.168.1.40', 0, 1, function(err) {
    if (err) {
        console.error(' >> Connection failed. Code #' + err + ' - ' + S7Client.ErrorText(err));
        console.log(`Connected[2]: ${S7Client.Connected()}`);
        return false;
    }

    readDb(1);

    console.log(`Connected[1]: ${S7Client.Connected()}`);

    setTimeout(function() {
        // take time to disconnect
        let cnt = 2;
        interval = setInterval(() => {
            readDb(cnt);
            console.log(`Connected[${cnt}]: ${S7Client.Connected()}`);
            cnt++;
        }, 1000);

    }, 6000);
});

process.on('SIGINT', function() {
    console.log("Caught interrupt signal");
    clearInterval(interval);
    S7Client.Disconnect();
});

and output example (connect -> timeout -> reconnect -> disconnect):

Result[1]: 0
Connected[1]: true
Unable to read from DB [2]  ISO : An error occurred during recv TCP : Connection timed out
Connected[2]: true
Unable to read from DB [3]  ISO : An error occurred during recv TCP : Connection timed out
Connected[3]: true
Unable to read from DB [4]  ISO : An error occurred during recv TCP : Connection timed out
Connected[4]: true
...
Result[9]: 0
Connected[9]: true
Result[10]: 0
Connected[10]: true
Unable to read from DB [11]  ISO : An error occurred during recv TCP : Connection timed out
Connected[11]: true
...
Unable to read from DB [22]  ISO : An error occurred during recv TCP : Connection timed out
Connected[22]: true
Unable to read from DB [23]  ISO : An error occurred during recv TCP : Connection timed out
Connected[23]: true
Unable to read from DB [24]  ISO : An error occurred during send TCP : Other Socket error (32)
Connected[24]: true
Unable to read from DB [25]  ISO : An error occurred during send TCP : Other Socket error (32)
Connected[25]: true
Unable to read from DB [26]  ISO : An error occurred during send TCP : Other Socket error (32)
...
unocelli commented 4 years ago

Hi, I'm using the latest version of node-snap7 in FUXA project and I'm looking for a valid solution to detecting the disconnection of the PLC since the Connected() method in that case don't work?

leovujanic commented 4 years ago

Hi, @unocelli !

I have implemented an error handler that filters socket related errors and in case of socket error, triggers reconnect mechanism. In reconnecting attempt I'm checking if Connected() returns true and calling Disconnect() followed by Connect(). At the time of writing, I didn't find a more elegant way to do it. I hope this helps you.

leovujanic commented 4 years ago

I am closing this because it's more related to snap7 lib then this one.

@mathiask88 thank you for your support!

Apollon77 commented 4 years ago

@leovujanic is you error handler the code above or how does it look like? Could you share it?