stlehmann / pyads

Python wrapper for TwinCAT ADS
MIT License
264 stars 96 forks source link

Beckhoff Linux Distro: Connection closed by remote #433

Open DanielJovanovski opened 1 day ago

DanielJovanovski commented 1 day ago

Hi,

I am running the Linux distribution provided by Beckhoff, which includes the TwinCAT runtime and the ADS router. I am trying to use the PyADS library to communicate with either the local runtime or another TwinCAT runtime on a Windows system.

In both cases, there is already an ADS route present. However, I am struggling to get PyADS to work as expected. After reviewing the PyADS documentation and troubleshooting, I suspect the issue might be related to how routes are handled on Linux, given that the Beckhoff distribution already has an integrated ADS router.

The PyADS documentation states: _For Linux systems the route is created automatically on the client-side. For the target-side, you can use add_route_to_plc()._

Since a route is already present, could this be the reason for my issues?

image

This is my Python code

import pyads
import time

TARGET_NETID = "127.0.0.1.1.1"  

print(f"Attempting to connect to PLC at AMS Net ID: {TARGET_NETID}, Port: {pyads.PORT_TC3PLC1}")

try:
    with pyads.Connection(TARGET_NETID, pyads.PORT_TC3PLC1) as plc:
        print("Connection established successfully.")

        print("Checking PLC status...")
        state, device_state = plc.read_state()
        print(f"PLC State: {state}, Device State: {device_state}")

        if state != pyads.ADSSTATE_RUN:
            print("PLC is not in RUN state. Exiting...")
            exit(1)
        else:
            print("PLC is in RUN state. Proceeding to read variables...")

        while True:
            try:
                # Read the value of MAIN.bOutput_1
                print("Reading value of 'MAIN.bOutput_1'...")
                variable = plc.read_by_name('MAIN.bOutput_1', pyads.PLCTYPE_BOOL)
                print(f"Current value of 'MAIN.bOutput_1': {variable}")

                # Wait for 1 second before next read
                time.sleep(1)

            except pyads.ADSError as e:
                print(f"Error reading 'MAIN.bOutput_1': {e}")
                break

except pyads.ADSError as e:
    print(f"Failed to connect or communicate with PLC: {e}")
except KeyboardInterrupt:
    print("Script interrupted by user.")
finally:
    print("Exiting script.")

If I remove the route from the Linux machine, and only have it on the Windows machine, I instead get the following error. (10.200.238.2.1.1 is the AMS Net id to my Windows machine). image

But from my development computer, we can clearly see that the target port exists (same code) image

chrisbeardy commented 1 day ago

Hi, the documentation you are referring too assumes that you are using a linux client to access a Windows Beckhoff PLC and that the Linux client has no ADS router (e.g. not the Beckhoff distribution). The Beckhoff Linux system is very new and pyads has never been tested with it (so this is an interesting test), it also means that the documentation predates the Beckhoff Linux distribution. I would have liked to think if you are running the pyads on the same system as the linux PLC then it would just work with localhost and you wouldn't need to create any routes.

I'm slighty confused by your 3 screenshots: is the first screenshot you running pyads on the Beckhoff Linux PC? Is the second screenshot you running pyads on a Windows machine with PLC running on Linux? and the third screenshot you running both pyads and the PLC on a windows machine?

I wouldn’t be surpirsed if there is some issue with pyads connecting correctly to a Beckhoff Linux PLC as it stands and the code would probably be trying to access its own ADS router type functionalty which may cause issues. It may be an easy fix though...

DanielJovanovski commented 1 day ago

Thanks for a quick reply - and sorry for the confusion. Let me clarify.

I would have liked to think if you are running the pyads on the same system as the linux PLC then it would just work with localhost and you wouldn't need to create any routes. For local communication, there is a built-in loopback route, which I was referring to. I did not need to create a route when running locally, only externally

Screenshot 1: PyADS on Beckhoff Linux distro <-> local TwinCAT runtime. No route created by me, but a built-in loopback should be present as I stated earlier. Since it actually connects in this case, the loopback should be there. The question is why it gets "closed by remote".

Screenshot 2: (Two tests, same result therefore only one screenshot) Test 1: PyADS on Beckhoff Linux distro <-> external W10 TwinCAT runtime. Route created on W10 via TwinCAT Route GUI but only on the W10 machine (Since the documentation stated that PyADS creates the route). Using the following setting. I cleared the StaticRoutes.XML and restarted before doing this test. image

Test 2: PyADS on Beckhoff Linux distro <-> external W10 TwinCAT runtime. Route created on W10 via TwinCAT Route GUI with a two-way route (both on "target" and "remote" as Beckhoff calls it). image

Screenshot 3: PyADS on my W11 laptop in PyCharm <-> same external W10 TwinCAT runtime as in screenshot 2. Route created normally as we usually do with the route GUI. (The screenshot from "Test 2" above). This was just to verify that the code was actually working on Windows.

Screenshot 4: image I made a new test now (essentially screenshot 2, Test 1) but adding the ip adress in the pyads.connection method as well. _pyads.Connection(TARGET_NETID, pyads.PORTTC3PLC1,"10.200.233.39") as plc:

It could clearly connect this time (so route is created by PyADS), but connection closed by remote is still thrown.