birkenfeld / ads-rs

Rust crate to access PLCs via the Beckhoff ADS protocol
https://crates.io/crates/ads
Apache License 2.0
40 stars 8 forks source link

Cannot connect to localhost #4

Closed Mebus closed 2 years ago

Mebus commented 2 years ago

Hi,

I am trying to connect to the local plc like this:

use ads::{Handle};

fn main() -> ads::Result<()> {

    println!("ADS port: {}", ads::PORT);

    // Open a connection to an ADS device identified by hostname/IP and port.
    // For TwinCAT devices, a route must be set to allow the client to connect.
    // The source AMS address is automatically generated from the local IP,
    // but can be explicitly specified as the third argument.
    let client = ads::Client::new(("localhost", ads::PORT),  ads::Timeouts::new(std::time::Duration::from_secs(1)), None)?;

    // Specify the target ADS device to talk to, by NetID and AMS port.
    // Port 851 usually refers to the first PLC instance.
    let device = client.device(ads::AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851));

    // Ensure that the PLC instance is running.
    assert!(device.get_state()?.0 == ads::AdsState::Run);

    // Request a handle to a named symbol in the PLC instance.
    let handle = Handle::new(device, "MY_SYMBOL")?;
    // Read data in form of an u32 from the handle.
    let mut data = [0; 4];
    handle.read(&mut data)?;
    println!("MY_SYMBOL value is {}", u32::from_le_bytes(data));

    // Connection will be closed when the client is dropped.
    Ok(())
}

But I get this:

ADS port: 48898
Error: Io("connecting TCP socket with timeout", Error { kind: TimedOut, message: "connection timed out" })

The Python ADS client connects without any problems on this machine. What can I do to fix this?

Mebus

birkenfeld commented 2 years ago

Hmm strange. Can you check if a) localhost properly resolves (or connect to "127.0.0.1" instead) and b) the ADS server listens on that interface (and not just an external one)?

Since it's on localhost, I assume it's not a Beckhoff PLC where you need to have an ADS route in place?

Mebus commented 2 years ago

This:

telnet localhost 48898

connects to something. It's a Beckhoff IPC. What kind of route do I need to add?

Mebus

birkenfeld commented 2 years ago

I mean a normal ADS route on the PLC side.

Still would be interesting to know what happens if you use "127.0.0.1" as the address instead of "localhost".

Mebus commented 2 years ago

When I use "127.0.0.1" I get:

ADS port: 48898
Verify ADS state:
Error: Ads("read state", "Port disabled - system service not started", 18)

But the PLC is running.

(Added som println!s to know where it fails.)

Mebus

Mebus commented 2 years ago

Have you tried rust-rs directly on a Beckhoff PLC connecting via "127.0.0.1"?

Mebus

birkenfeld commented 2 years ago

Ok, this is already progress. I remembered that there are some issues with resolving "localhost" on Windows...

Now you are connected and routing is fine (otherwise you wouldn't get any response).

The question is now which services are running, and which you are connecting to. Are you still using AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851) ? You might have to replace that with the actual NetId of the IPC.

birkenfeld commented 2 years ago

And I have to admit I never ran ads-rs directly on an IPC connecting to 127.0.0.1

Mebus commented 2 years ago

Even if I use the local AMS NetID that is not 127.0.0.1.1.1, I get the same error.

Can you probably try this on your machine, to see if the problem also exists there?

Mebus

Mebus commented 2 years ago

Are you interested in further debugging this problem?

Mebus

birkenfeld commented 2 years ago

Yes, sorry, I just had a few days off.

So to summarize, you always get Error: Ads("read state", "Port disabled - system service not started", 18) when connecting to port 851?

Can you try to connect to port 10000 and see if that one responds ok?

Mebus commented 2 years ago

So to summarize, you always get Error: Ads("read state", "Port disabled - system service not started", 18) when connecting to port 851?

Yes.

And Port 10000 is not open on 127.0.0.1.

Mebus

Mebus commented 2 years ago

I found a similar problem in a NodeJS implementation here:

https://github.com/roccomuso/node-ads/issues/8

I also changed the local AMSNetID when connecting, but this did not help either. Do you have an idea?

Mebus

birkenfeld commented 2 years ago

And Port 10000 is not open on 127.0.0.1.

Just to be sure, I mean the ADS port, not the TCP port. The ADS port is always 48898.

What kind of IPC is that, and which version of TwinCat is it running?

Can you show me the exact code you used successfully with the Python client?

I found a similar problem in a NodeJS implementation here: I also changed the local AMSNetID when connecting, but this did not help either. Do you have an idea?

I don't think this is a problem, since the connection itself works (you're getting a reply from TwinCat, even though it's an error).

birkenfeld commented 2 years ago

BTW, you can also use the adstool example binary to do some quick checks. Can you try:

cargo run --example adstool 127.0.0.1 info
cargo run --example adstool 127.0.0.1 state
cargo run --example adstool 127.0.0.1 target-desc
cargo run --example adstool 127.0.0.1/:800 state
cargo run --example adstool 127.0.0.1/:801 state
cargo run --example adstool 127.0.0.1/:851 state
Mebus commented 2 years ago

BTW, you can also use the adstool example binary to do some quick checks. Can you try:

cargo run --example adstool 127.0.0.1 info
cargo run --example adstool 127.0.0.1 state
cargo run --example adstool 127.0.0.1 target-desc
cargo run --example adstool 127.0.0.1/:800 state
cargo run --example adstool 127.0.0.1/:801 state
cargo run --example adstool 127.0.0.1/:851 state
PS C:\Users\Mebus\Desktop\adstest\ads-rs> cargo run --example adstool 127.0.0.1  info
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target\debug\examples\adstool.exe 127.0.0.1 info`
NetID: 10.176.222.52.1.1
Hostname: MebusDevPC
TwinCAT version: 3.1.4024
OS version: Windows NT 10.0.19042
Fingerprint: bf6d2a95e626580f4151209f318b95ccaa1c464950a38e242db5857e2e2b0fdb
PS C:\Users\Mebus\Desktop\adstest\ads-rs> cargo run --example adstool 127.0.0.1 state
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target\debug\examples\adstool.exe 127.0.0.1 state`
Error: get device info: Port disabled - system service not started (0x12)
error: process didn't exit successfully: `target\debug\examples\adstool.exe 127.0.0.1 state` (exit code: 1)
PS C:\Users\Mebus\Desktop\adstest\ads-rs> cargo run --example adstool 127.0.0.1/:851 state
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target\debug\examples\adstool.exe 127.0.0.1/:851 state`
Error: get device info: Port disabled - system service not started (0x12)
error: process didn't exit successfully: `target\debug\examples\adstool.exe 127.0.0.1/:851 state` (exit code: 1)
PS C:\Users\Mebus\Desktop\adstest\ads-rs>

Mebus

birkenfeld commented 2 years ago

Is the TwinCat in config mode or in run mode?

Mebus commented 2 years ago

Can you show me the exact code you used successfully with the Python client?

Not the exact code, but the important parts of it:

import pyads

ads_route = "127.0.0.1.1.1"
ads_port = 851
plc = pyads.Connection(ads_route, ads_port)
plc.open()
reactor_vessel_pressure = plc.read_by_name(
            "GVL_Reactor.fVesselPressure", pyads.PLCTYPE_REAL
        )
print(reactor_vessel_pressure)
plc.close()

(This works on the machine.)

Mebus

Mebus commented 2 years ago

Is the TwinCat in config mode or in run mode?

The gear tray icon is green.

Mebus

birkenfeld commented 2 years ago

Ok, let's assume for a moment that the get_state method just doesn't work. Can you try this:

let client = ads::Client::new(("127.0.0.1", ads::PORT), ads::Timeouts::none(), None)?;
let device = client.device(ads::AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851));
let handle = ads::Handle::new(device, "GVL_Reactor.fVesselPressure")?;
let mut data = [0; 4];
handle.read(&mut data)?;
println!("value is {}", f32::from_le_bytes(data));
Mebus commented 2 years ago

Ok, let's assume for a moment that the get_state method just doesn't work. Can you try this:

let client = ads::Client::new(("127.0.0.1", ads::PORT), ads::Timeouts::none(), None)?;
let device = client.device(ads::AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851));
let handle = ads::Handle::new(device, "GVL_Reactor.fVesselPressure")?;
let mut data = [0; 4];
handle.read(&mut data)?;
println!("value is {}", f32::from_le_bytes(data));

I modified it to this:

    let client = ads::Client::new(("127.0.0.1", ads::PORT), ads::Timeouts::none(), None).unwrap();
    let device = client.device(ads::AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851));
    let handle = ads::Handle::new(device, "GVL_Reactor.fVesselPressure").unwrap();
    let mut data = [0; 4];
    handle.read(&mut data).unwrap();
    println!("value is {}", f32::from_le_bytes(data));

And I got this:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Ads("write and read data", "Port disabled - system service not started", 18)', src\main.rs:40:74
stack backtrace:
   0:     0x7ff67fde4e5f - <unknown>
   1:     0x7ff67fdf859a - <unknown>
   2:     0x7ff67fde1469 - <unknown>
   3:     0x7ff67fde6fbb - <unknown>
   4:     0x7ff67fde6bae - <unknown>
   5:     0x7ff67fde75b1 - <unknown>
   6:     0x7ff67fde746d - <unknown>
   7:     0x7ff67fde5767 - <unknown>
   8:     0x7ff67fde7149 - <unknown>
   9:     0x7ff67fdff835 - <unknown>
  10:     0x7ff67fdff943 - <unknown>
  11:     0x7ff67fd81845 - <unknown>
  12:     0x7ff67fd83126 - <unknown>
  13:     0x7ff67fd82f6c - <unknown>
  14:     0x7ff67fd8df9b - <unknown>
  15:     0x7ff67fd8134b - <unknown>
  16:     0x7ff67fd811a1 - <unknown>
  17:     0x7ff67fddd83e - <unknown>
  18:     0x7ff67fd8116f - <unknown>
  19:     0x7ff67fd832a6 - <unknown>
  20:     0x7ff67fdfdeb8 - <unknown>
  21:     0x7ffa42547034 - BaseThreadInitThunk
  22:     0x7ffa42b82651 - RtlUserThreadStart

Might that be because of my modification?

Mebus

birkenfeld commented 2 years ago

Ok, same thing here. At this point I don't really know how to diagnose further. I saw that pyads seems to use the TwinCAT ADS router on Windows, so there might be some difference in behavior there. I can try to reproduce this but need to set up a test system.

birkenfeld commented 2 years ago

Might that be because of my modification?

No, the TwinCat error is the same, you're just getting a panic due to the unwrap() which is expected.

Mebus commented 2 years ago

Ok, it would be great if you could resolve this :-)

Mebus

birkenfeld commented 2 years ago

I reproduced your problem on a Windows VM. When checking with Wireshark how pyads is communicating with the PLC, I didn't see any TCP traffic, so it must be a completely different internal way of talking to TwinCAT. This is happening in the ADS DLL, so no good way to find out the details.

The way that I found works is to use the external (non-127.0.0.1) IP of the machine, and the correct NetID. In that case, however, you also have to add an ADS route pointing back to yourself, otherwise you'll see "Error receiving reply (route set?): unexpected end of file".

Mebus commented 2 years ago

The source code of the Beckhoff DLL should be available here:

https://github.com/Beckhoff/ADS

Can you look into it to see how it does it?

Also I think it's not so easy to dump traffic of the Windows loopback interface.

Mebus

birkenfeld commented 2 years ago

The source code of the Beckhoff DLL should be available here:

That's only an implementation for non-native systems, from the Readme: "This library is intended to provide easy use as ADS client applications running on non-windows systems". The source of the DLL shipped with TwinCAT is not available, AFAIK.

Also I think it's not so easy to dump traffic of the Windows loopback interface.

Well, at least I could see the traffic I generated using the Rust version on loopback without problems.

Mebus commented 2 years ago

Can you inspect

https://www.npmjs.com/package/node-ads

?

Mebus

birkenfeld commented 2 years ago

Does that one work for you?

birkenfeld commented 2 years ago

OK, success! Looking at the other node package (ads-client) brought the enlightenment.

Please try out master. The connect method now takes a Source as the third argument, set it to Source::Request. Then you should be good to go (at least it worked for me with the latest TwinCAT 3.1.4024.29).

Mebus commented 2 years ago

Hi birkenfled!

Great. Now it looks better 😃

PS C:\Users\Mebus\Desktop\adstest\ads-rs> cargo run --example adstool 127.0.0.1 state
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target\debug\examples\adstool.exe 127.0.0.1 state`
Device: TwinCAT System 3.1.4024
Current state: Run
PS C:\Users\Mebus\Desktop\adstest\ads-rs>

Unfortunately I cannot read the reactor vessel pressure yet:

    let dest_address = "127.0.0.1";

    let source = if matches!(dest_address, "127.0.0.1" | "localhost") {
        ads::Source::Request
    } else {
        ads::Source::Auto
    };

    let client = ads::Client::new(("127.0.0.1", ads::PORT), ads::Timeouts::none(), source).unwrap();
    let device = client.device(ads::AmsAddr::new([127, 0, 0, 1, 1, 1].into(), 851));
    let handle = ads::Handle::new(device, "GVL_Reactor.fVesselPressure").unwrap();
    let mut data = [0; 4];
    handle.read(&mut data).unwrap();
    println!("The VesselPressure is: {} kPa", f32::from_le_bytes(data));

This gives:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Reply("write and read data", "unexpected source address", 0)', src\main.rs:48:74
stack backtrace:
   0:     0x7ff790bb614f - <unknown>
   1:     0x7ff790bc988a - <unknown>
   2:     0x7ff790bb26f9 - <unknown>
   ...

Have you implemented the new source address thing everywhere, where it is neccessary?

My dependencies look like this at the moment:

[dependencies]
#ads = "0.3.1"
ads = {git = "https://github.com/birkenfeld/ads-rs"}

That should work to make it use the current master branch, right?

Mebus

birkenfeld commented 2 years ago

Thanks for testing. This is because of the target NetID 127.0.0.1.1.1 - I didn't test with that. Now this NetID is mappend to the local NetID we get from the router.

Please retry (you need to call cargo update to get the newest master revision).

birkenfeld commented 2 years ago

I've now also added a nicer API to read values. You can replace this:

    let mut data = [0; 4];
    handle.read(&mut data).unwrap();
    println!("The VesselPressure is: {} kPa", f32::from_le_bytes(data));

by

    println!("The VesselPressure is: {} kPa", handle.read_value::<f32>().unwrap());
Mebus commented 2 years ago

Hi,

thanks! I did some more testing. That looks better now. But can you please increase the version number of your package so that I can use it in a normal way in the Cargo.toml file?

Mebus

birkenfeld commented 2 years ago

Yes, I was just waiting for your confirmation. Will release 0.4 now.