stlehmann / pyads

Python wrapper for TwinCAT ADS
MIT License
254 stars 94 forks source link

Problem to communicate with PLC after setting up connection #228

Closed mtroback closed 2 years ago

mtroback commented 3 years ago

Hi, This is probably not the best way to ask questions, maybe I should turn to stackoverflow instead, but I figure this is the best shot at getting good answers.

I've successfully set up pyads on my windows computer and I am able to use the pyads.read_by_name function without problems. It took me a while to figure out stuff, but it's working.

Now I have moved to my Raspberry Pi trying to communicate with the same PLC. This time it is trickier in the sense that I have to setup my own route. I have tried to follow the example in the Quickstart and I can successfully set up the route, but just for the sake of understanding from my point of view I have a quick question: The Client addresses are supposed to be the computer on which I am running python? So I choose the CLIENT_NETID by using the computer IP and add .1.1? And the CLIENT_IP is the computers IP? The the TARGET_* is all the information of the PLC?

Then I don't understand why you use a NetId pointing at the computer localhost IP when you set up a connection. This was unsuccessfull for me, but it was successfull using a NetId of the target IP instead (tracebacks omitted for the sake of clarity, 192.168.3.1 is the raspberry pi, 192.168.3.9 is the PLC):

In [3]: pyads.add_route_to_plc('192.168.3.1.1.1', '192.168.3.1', '192.168.3.9', 'Administrator', '1', route_name='route')
Out[3]: True

In [4]: plc = pyads.Connection('127.0.0.1.1.1', 851)

In [5]: plc.open()
2021-03-10T14:35:01+0100 Error: Connect TCP socket failed with: 111
------
ADSError: ADSError: target port not found   ADS Server not started (6).

In [6]: plc = pyads.Connection('192.168.3.9.1.1', 851)

In [7]: plc.open()
2021-03-10T14:35:20+0100 Info: Connected to 192.168.3.9

In [8]: plc.read_device_info()
---------------------------------------------------------------------------
ADSError                                  Traceback (most recent call last)

ADSError: ADSError: target machine not found    Missing ADS routes (7).

In [9]: plc.read_by_name('MAIN.IN_1')
---------------------------------------------------------------------------
ADSError                                  Traceback (most recent call last)

ADSError: ADSError: target machine not found    Missing ADS routes (7).

So it looks like it is connected, but the route is missing? Can you spot what I'm doing wrong here?

I'm writing from my windows computer which has TwinCAT running and route set up in the background from the windows computer to the PLC I assume, could this be a problem? I am not connected from the windows computer.

I understand that you are working on this on the side, and if you don't have time for this type of questions I'll try to ask Beckhoff support and see how they can help me. Thanks for a great project!

mtroback commented 3 years ago

I actually managed to get the connection working today, wow..

This is what I did, and maybe this should be updated in the quick start?


In [1]: import pyads

In [2]: pyads.add_route_to_plc('192.168.3.1.1.1', '192.168.3.1', '192.168.3.9', 'Administrator', '1', route_name='route')
Out[2]: True

In [3]: plc = pyads.Connection?
Init signature: pyads.Connection(ams_net_id: str = None, ams_net_port: int = None, ip_address: str = None) -> None
Docstring:
Class for managing the connection to an ADS device.

:ivar str ams_net_id: AMS net id of the remote device
:ivar int ams_net_port: port of the remote device
:ivar str ip_address: the ip address of the device

:note: If no IP address is given the ip address is automatically set
    to first 4 parts of the Ams net id.
File:           /usr/local/lib/python3.7/dist-packages/pyads/ads.py
Type:           type

In [5]: plc = pyads.Connection(pyads.ads.adsGetNetIdForPLC('192.168.3.9'), 851, '192.168.3.9')

In [6]: plc.__dict__
Out[6]:
{'_port': None,
 '_adr': <AmsAddress 5.83.131.126.1.1:851>,
 'ip_address': '192.168.3.9',
 'ams_net_id': '5.83.131.126.1.1',
 'ams_net_port': 851,
 '_open': False,
 '_notifications': {},
 '_symbol_info_cache': {}}

In [7]: plc.open()
2021-03-12T15:36:34+0100 Info: Connected to 192.168.3.9

In [8]: plc.read_by_name('MAIN.IN_1')
Out[8]: 1
chrisbeardy commented 3 years ago

Yes I guess it can be confusing that the docs use 127.0.0.1.1.1 as the example and net Id as this is not linked to localhost in any way.

In the situation when you are trying to connect from the raspberry pi. The Pi is the local/client and the PLC is the remote/target. So when adding the route you are adding the pi to the route table of the remote PLC so have to specify the pis AMS net ID, which in the case of Linux you have to make up, normally by adding .1.1 to the IP address. This normally only has to be done once.

After establishing the route when using the Connection class, you are specifying the remote PLC to connect to. Just specifying the Net id and the port is usually enough here without having to specify the up address. Unfortunately the Beckhoff PLC does not always follow the convention of using the ip address plus .1.1 as the and net id for the plc. As you can see from using the get info. The I'd starts with 5.83.

So using plc = pyads.Connection("5.83.131.126.1.1", 851) should be enough.

We could clear this up on the docs

mtroback commented 3 years ago

Thank you very much for the explanation, I've gone through the documentation at Beckhoff and it is still a bit unclear which is client/server/remote/target so thank you very much for straightening that out for me.

And I've seen somewhere in the docs, although I can't find it now, that the AMS Net id is based on the MAC address of the PLC, so in my case the 4 last bytes of the MAC is 05:53:83:7f which translates to decimal 5.83.131.127. I'm sure they have a good reason to use the MAC instead of IP (or maybe it is an backup in case of missing DHCP server on the network).

A final question: The add_route_to_plc will set up a (ams?) route from my Raspberry Pi (the client) to the PLC? And this is where I declare the Ams Net Id I want the client to have? And once this route has been setup you will be able to communicate with the PLC, for example you seem to be able to get the Ams Net Id once you are connected to the IP. Does this mean it is theoretically possible to use Connect without supplying Ams Net Id if there is only one route set up?

Once again, thanks for the explanation and the wonderful port of the C libraries. I will try to use a Beckhoff PLC CX8190 as a competent I/O extension for the Raspberry Pi, controlled from Python.

I will try to update the docs to cover this conversation, hopefully it will help other newbies.

mtroback commented 3 years ago

Alright, I've been studying the docs a bit better now, especially the routing.rst document.

In the routing page is stated that you don't need add_route_to_plc, you can simply use the Connection directly. But later is says that ADS requires a route both from client to target and target to client, which I feel is a contradiction to the previous statement that you don't need to add_route_to_plc. And also, if you only use the Connect you don't supply username and password, how does this work?

And I think the wording/nomenclature differs a bit between the same items, could you please help me straighten out this? If I understand it correctly the PC executing the python code is the client, thus we have a client ip, client Ams Net Id. And the code is working with a target (normally a PLC, but could be another PC) and that gives us a target ip and a target Ams Net Id. However in the routing.rst there are variables named remote_ip, remote_ads and sender_ams.

If you or someone else have the time to help me understand I will do my best to update the docs to have them more aligned, using the same ams net id , ip addresses and variable names everywhere.

chrisbeardy commented 3 years ago

I can help you on this. This weekend I shall try to outline it then if you create a fork and new branch for doc changes, (if it is just doc changes do not worry about running black or tox.) I can then help out and when ready you can create a PR which @stlehmann can review and merge.

stlehmann commented 3 years ago

In the routing page is stated that you don't need add_route_to_plc, you can simply use the Connection directly

Could you please give me a hint where exactly this is this written in the docs? Must be a leftover or an older documentation?

In the Quickstart you will find this passage:

You need to create routes on your client pc and on your target plc via TwinCAT before any communication can take place. If you are on Windows you can use the TwinCAT router. For Linux systems the route is created automatically on the client-side. For the target-side you can use add_route_to_plc().

And there is also an example given how to add a route to the target. Routing is always a killer-issue which is why I reworked the documentation a bit. But it seems it's still a bit fuzzy. The thing is that routing in ADS needs to be done in both ways. We have a client (PC) and a target (PLC) which both have an AMS NetID to identify them in ADS communication. In order to communicate ADS needs to know the IP address behind a NetID. This means you will need to add two routes:

If you are on Windows you can add a route PC->PLC via TwinCAT. TwinCAT will also add the route PLC->PC automatically for you if you do this.

On Linux there is no TwinCAT so both routes will need to be added manually. For convenience the route PC->PLC is added if you create the Connection object. The route PLC->PC would need to be added manually inside the PLC by TwinCAT. For more convenience there is the function add_route_to_plc which does this for you and was actually reverse-engineered from TwinCAT communication.

mtroback commented 3 years ago

Wow, I must say that I really appreciate all the work all of you have put into this project, without it I wouldn't even consider using a Beckhoff PLC in my application.

Could you please give me a hint where exactly this is this written in the docs? Must be a leftover or an older documentation?

It turns out that if I read all of the text, you do need to create a route from the target to the client as well, but I only read up to the example code: To create a new route on Linux you can simply use the Connection class. It connects to the target and creates a route to it on your client.

And I think the documentation is really good, it is simply difficult for me as a beginner whom has never worked with PLC before.

But now I clearly understand that the add_route_to_plc doesn't add a route from the client to the PLC/target, it adds the "reverse" route which is needed from the PLC/target to the client (so that the target can communicate with the client I assume). Nice work of reverse-engineering there! And I guess this is why the username/password is needed, in order to log in to the PLC/target and make changes.

Thank you so much for all your help and explanations.

chrisbeardy commented 3 years ago

@mtroback if you still want to clean up the docs to make them more beginner friendly, do you need any more info?

mtroback commented 3 years ago

@chrisbeardy I think I have it all figured out now, thanks! It will probably take a week or two, but I'll start working on it :)

mtroback commented 3 years ago

@chrisbeardy @stlehmann It took some time before I got back to this, but I've tried to simplify the documentation. I guess the proper way is to create a pull request now, but I feel a bit insecure and would like a first comment before that, in case you strongly disagree with what I've done (but maybe the pull request is a better place to discuss this? Never done one before..)

Also, I removed the part in routing.doc where there was something about open_port and close_port and set_local_address since I didn't understand why it was there..

https://github.com/stlehmann/pyads/compare/master...mtroback:master

dslemusp commented 3 years ago

Thank you all for the library and also for the efforts on improving the documentation. I have found this thread specially useful. @mtroback I have checked some of the modifications on the documentation and have made some small comments (I think the changes haven't been pushed yet, as I don't see it in the official documentation). Hope this helps.

Thank you again for the effort

stlehmann commented 2 years ago

@dslemusp thanks for your effort. Please create a new PR that contains your proposed changes. As this issue is a bit older please rebase to master firstw.

mtroback commented 2 years ago

I'm back at working with the PLC so I will rebase and get things in a shape where I can create a PR. Some impressive progress in the project during the summer, great work you've done!

mtroback commented 2 years ago

@dslemusp Comments sounds good, but I'm still a newbie at github, where can I find you comments?

stlehmann commented 2 years ago

I'll close this issue as it is handled in #281.