roccomuso / node-ads

NodeJS Twincat ADS protocol implementation
58 stars 21 forks source link

How to access local ads server? #8

Closed acidos closed 6 years ago

acidos commented 6 years ago

I have successfully accessed remote ads target. But if I want to access target on the same machine(node app and target are same PC), then I get timeout.

var options = {
    host: "10.10.2.100",
    amsNetIdTarget: "10.10.2.100.1.1",
    amsNetIdSource: "10.10.2.100.1.1",
    amsPortTarget: 801
}

var myHandle = {
    symname: '.NoBeam',
    bytelength: ads.BOOL,  
    propname: 'value'      
}
var client = ads.connect(options, function() {
    this.read(myHandle, function(err, handle) {
        if (err) console.log(err)
        else console.log(handle.value)
        this.end()
    })
})

yields to timeout message printed only. 10.10.2.100 (and I also tried 127.0.0.1) added into static routing table.

Using TC2.

roccomuso commented 6 years ago

Did you put the access rule on the plc configuration? Have you been able to do it with another client?

acidos commented 6 years ago

C# client connecting to the same PLC from the same host just fine. Actually for local connection it's not necessary to put any access rule. But I put rule anyway, because without rule, your client is generating ECONNRESET exception. Once the rule is there, then timeout.

roccomuso commented 6 years ago

Ok, Can you try enabling debug? DEBUG=node-ads node myclient.js

acidos commented 6 years ago

on Windows I did: set DEBUG=node-ads then I run as usual node index.js. No change in output, perhaps it doesn't reach your code with debug.

c:\pkg>node index.js
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at _errnoException (util.js:992:11)
    at TCP.onread (net.js:618:25)

My best guess would be usage of amsNetIdSource. I never seen such parameter in any client (C++, C#).

acidos commented 6 years ago

I captured ADS traffic for your client and working client. Here is what I found.

1. (sender to target)request to create handle for symbol   
2. (target to sender)returned handle
3. (sender to target)ads read request with given handle
4. (target to sender)data provided

In your client I see the following sequence:

1. (sender to target)request to create handle for symbol   
2. (target to sender)returned handle
3. (sender to target)ADS Read/Write Response with error code target not found!?!?
end of messaging

It could be that the capturing program missed something, but I doubt. If you are using AmsAdsViewer, I can provide with good/bad capture files.

acidos commented 6 years ago

Routing in case of local connection is another mystery. Like I said, other clients doesn't need any routing in order to connect to PLC locally. However, your client requires explicit adding 127.0.0.1 into static routing table.

roccomuso commented 6 years ago

I'm including @ovi1337 so maybe he could help debug since I don't have a TC2 right now.

acidos commented 6 years ago

I have some feeling for the routing issue, that using C#, C++ client, which are using COM/DCOM communication probably directly, so they don't need routing. This theory is supporting also the fact, that I used rawcap utility to capture local traffic (127.0.0.1 and assigned IP), but couldn't see any packets while using C# client. However, I was able to see AMS packets from your client.

ovi1337 commented 6 years ago

@acidos the amsNetIdTarget and amsNetIdSource can't be the same, because this is a TCP/IP socket connection and you need a clear target and a destination. You can add a new route with another TargetId in the router configuration of your PLC like 10.10.2.100.1.1 and 10.10.2.100.1.2. Your node-ads project is basically an own simulated PLC.

acidos commented 6 years ago

@ovi1337 it was not straight forward to add 2 routing entries with the same 127.0.0.1. With few attempts I was able to add and read was successfull.

ovi1337 commented 6 years ago

And now it's working for you?

acidos commented 6 years ago

Yes, although still not clear why C#/C+6 client producing AMS traffic with same sender/target ams net ID.

image

sanderd17 commented 6 years ago

@acidos, could you share your config?

I'm also trying to add two entries, but can't seem to figure that one out.

acidos commented 6 years ago

@sanderd17

image

Note the second entry comment, it says Sub route. The comment is not added by me.

PLCHome commented 6 years ago

If I understood correctly, it is on the router on the PLC. I found this:

  1. TwinCAT AMS Router doesn't allow multiple TCP connections from the same host. So when you use two AdsLib instances on the same host to connect to the same TwinCAT router, you will see that TwinCAT will close the first TCP connection and only respond to the newest.
  2. As ADS is transmitted over a TCP connection, there is no real time guarantee.

You can set up a second IPv4 on the PC and assign to this a ADS NET ID under Twincat. But you have to disable DHCP and enter all IP addresses by hand.

If you want to run Twincat System Manager and Node-ADS on the same PC, that does not work either. The LIB from Beckhoff has the same problems https://github.com/Beckhoff/ADS/issues/30. (Thanx @scelle)

acidos commented 6 years ago

Not sure if it's related to localhost restrictions, but even though I use .end in order to clean up connection, in event viewer I always see

TCP/IP Connection: Socket to Peer Name: 127.0.0.1, Port: 17657 is connected, current socket will be closed!

after each connect->write->end cycle. Port number is different of course each time. Could it be that connection is not teardown properly?

PLCHome commented 6 years ago

The .end works asynchronously. I had a similar problem with node red implementation. You have to wait for the callback from .end before you start a new connection. In the Note Red implementation, I added a sleep of 1s. But I'm still surprised that it works locally at all, but I assume that the system manager is not running.

    node.on('close', function (done) {
      node.adsNotificationNodes = []
      node.systemNodes = []
      internalRestart(done)
    })

    function internalRestart(done) {
      internalSetConnectState(adsHelpers.connectState.DISCONNECTING)
      clearTimeout(conncetTimer)
      if (node.adsClient) {
        node.adsClient.end(function (){
          internalSetConnectState(adsHelpers.connectState.DISCONNECTED)
          delete (node.adsClient)
          var sleep = setInterval(function () {
            clearTimeout(sleep)
            done()
          },1000)
        })
      } else {
        internalSetConnectState(adsHelpers.connectState.DISCONNECTED)
        done()
      }
    }

This is an example from the implementation, done is called when the connection is down.

acidos commented 6 years ago

System manager is up and running in Run mode. Between calls there is delay of 5 minutes.

var options = {
    host: "127.0.0.1",
    amsNetIdTarget: "10.10.2.100.1.1",
    amsNetIdSource: "10.10.2.100.1.15",
    amsPortTarget: 801
}

function intervalFunc() {
    PKG('http://xxx', 'yyy', 'zzz', 'TL_X', sensors, function (error, values){
          if(error == null)
          {
                var client = ads.connect(options, function() {
                    WriteAds(client, values[0]).
                    then(() => WriteAds(client, values[1])).
                    then(() => WriteAds(client, values[2])).
                    then(() => WriteAds(client, values[3])).
                    then(() => WriteAds(client, values[4])).
                    then(() => WriteAds(client, values[5])).
                    then(() => WriteAds(client, values[6])).
                    then(() => WriteAds(client, validityHandle)).
                    catch((err) => { 
                        log.error("Error writing to ADS: ", err);
                    }).
                    finally(() => ReleaseAndDisconnect(client))
                })
          }
          else
          {
            log.error('Error retrieving data from web: ', error);
            process.exit(1);
          }
      });
}

intervalFunc();
setInterval(intervalFunc, 300000);

function ReleaseAndDisconnect(client)
{
    return new Promise(function(resolve, reject){
        client.end()
        resolve()
    })
}

function WriteAds(client, h)
{
    return new Promise(function(resolve, reject){
        console.log('Writing: ' + h.symname)
        client.write(h, function(err) {
            if (err) 
            {
                reject(err)
            }
            else
            {
                resolve()
            }
        })
    })
}
PLCHome commented 6 years ago

Hmm: client.end(function (){ resolve() })

Please try the code without running System Manager on the system. Or run by another computer, just for testing. Beckhoff does not write for nothing in his ADS-Dll documentation that the router does not support two ADS connections with the same IP. The system manager and the ADS client do not work for me on the same client. I had to give the client 2 IP addresses.

The hint of Beckhoff on the local system to use the TcAdsDll unfortunately does not work as we use ads.

acidos commented 6 years ago

I run my node client from different host, where there is no TC, with adding rule into static route, but the message is still there

TCP/IP Connection: Socket to Peer Name: <IP of remote client PC, where there is no TC>, Port: 41695 is connected, current socket will be closed!

So in the ADS capture I see ADSIGRP_SYM_RELEASEHND messages are there, but the info message clearly tells that TCP connection is not tear down properly. Basically it's not related to local/remote host use cases.

PLCHome commented 6 years ago

So in the ADS capture I see ADSIGRP_SYM_RELEASEHND messages are there, but the info message clearly tells that TCP connection is not tear down properly. Basically it's not related to local/remote host use cases.

I discovered the following in end (). Maybe that's the problem:

var end = function (cb) {
  var ads = this
  ads.tcpClient.removeListener('data', ads.dataCallback)
  releaseSymHandles.call(ads, function () {
    releaseNotificationHandles.call(ads, function () {
      if (ads.tcpClient) {
        // ads.tcpClient.end(); // reijo removed
        ads.tcpClient.destroy() // reijo added
      }
      if (cb !== undefined) cb.call(ads)
    })
  })
}

may be end for tear down?

        ads.tcpClient.end(); // reijo removed
        // ads.tcpClient.destroy() // reijo added
acidos commented 6 years ago

may be end for tear down?

.end won't teardown connection, but will tidy up the stream only. Anyway, I went to node_modules/node-ads/lib/ads.js and opened .end. Also tried only with .end. Still get the current socket will be closed notification in Event Viewer.

PLCHome commented 6 years ago

I find this hint in the Beckhoff documentation:

How to implement an ADS client

  1. Close ADS channel Always close ADS channel PortClose() – be aware to release handles before !

Command Overview

PortClose () is a function of the C ++ implementation. But I can not find a command for the ADS. I'm still not sure if the problem is in the socket or in the communication. The handles are released, I currently use to test an old CE PLC, I unfortunately have no monitor for this.

Node.js v10.10.0 Documentation Net Socket

I only see the function end() and destroy().

acidos commented 6 years ago

I unfortunately have no monitor for this

I don't use monitor for that, only Event Viewer

image

My setInterval is 5 min, so the events.

PLCHome commented 6 years ago

Which node version do you use? node --version Which TwinCat version do you use?

Closing the connection is part of the socket. TCPConnectionTermination

Therefore, there is no special code. Tonight I'll see if I can copy this behavior. Im using NODE v6.14.4 on RPI under Docker and TC 2.11.2605 on PLC

acidos commented 6 years ago
c:\pkg>node --version
v8.11.3

c:\pkg>npm --version
6.4.1

TC 2.11 (build 2249)

everything on single Beckhoff industrial PC

PLCHome commented 6 years ago

When you start your app for the first time after a reboot and you are done for the first cycle, you will see that the system is giving exactly the same message.

TCP/IP Verbindung: Socket to Peer Name: 192.168.2.60, Port: 18180 is connected, current socket will be closed!

The same message is issued by a Beckhoff ADS client with open Systemmanager for the first and each new connection (Enter the route again in the System Manager).

TCP/IP Verbindung: Socket to Peer Name: 192.168.2.44, Port: 28383 is connected, current socket will be closed!

That the port is counted up is apparently a problem of Beckhoff. Why this is so ... ask the Beckhoff hotline. If you want to have less events just do not disconnect. Check by a cyclic query on the symtable. If the symtbale number changes, the connection must be rebuilt. Or if the query does not work.

If you set up the route, you can specify a timeout. After this time, the connection disappears. This can be seen with netstat -an. The port is counted up as well. And the port in the system log, is not the TCP Port.

I have just tested with a CX5020 with Microsoft Windows XP Embedded. There is no system manager running on the PLC. I had to install node 5.20, because from node 6 xp is no longer supported. I had no problem with node-ads accessing the PLC via the IP with a 2nd adsid. 127.0.0.1 did not work.

acidos commented 6 years ago

So with TCP/IP Verbindung it's clear now. 127.0.0.1 should have worked, with just special connection names. ads-routing

PLCHome commented 6 years ago

@roccomuso please close