Coder Social home page Coder Social logo

pyads's Introduction

pyads - Python package

PyPI version Anaconda-Server Badge Anaconda-Server Badge

CI Coverage Status Documentation Status Downloads Downloads

This is a python wrapper for TwinCATs ADS library. It provides python functions for communicating with TwinCAT devices. pyads uses the C API provided by TcAdsDll.dll on Windows adslib.so on Linux. The documentation for the ADS API is available on infosys.beckhoff.com.

Documentation: http://pyads.readthedocs.io/en/latest/index.html

Issues: In order to assist with issue management, please keep the issue tracker reserved for bugs and feature requests. For any questions, particularly around usage, route creation and ads error messages when reading or writing variables, please use Stack Overflow tagging the question with twincat-ads and state you are using the pyads library.

Installation

From PyPi:

pip install pyads

From conda-forge:

conda install pyads

From source:

git clone https://github.com/MrLeeh/pyads.git --recursive
cd pyads
python setup.py install

Features

  • connect to a remote TwinCAT device like a plc or a PC with TwinCAT
  • create routes on Linux devices and on remote plcs
  • supports TwinCAT 2 and TwinCAT 3
  • read and write values by name or address
  • read and write DUTs (structures) from the plc
  • notification callbacks

Basic usage

import pyads

# connect to plc and open connection
plc = pyads.Connection('127.0.0.1.1.1', pyads.PORT_TC3PLC1)
plc.open()

# read int value by name
i = plc.read_by_name("GVL.int_val")

# write int value by name
plc.write_by_name("GVL.int_val", i)

# close connection
plc.close()

Contributing guidelines

Contributions are very much welcome. pyads is under development. However it is a side-project so please have some patience when creating issues or PRs. Please also follow the Contributing Guidelines.

pyads's People

Contributors

1130164523 avatar asolchen avatar bdcoyle avatar bejer avatar bpartzsch avatar carstenschroeder avatar chrisbeardy avatar chrisjbremner avatar corylutton avatar cowo78 avatar dabrowne avatar dependabot[bot] avatar dgl-cw avatar johannesloibl avatar klauer avatar kryskool avatar lukabelingar avatar mdauphin avatar milanplatisa avatar na-svendlich avatar pjaneck avatar pylipp avatar robertoroos avatar sawey avatar scop avatar simonschmeisser avatar stlehmann avatar windrad6 avatar wulmer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyads's Issues

Couldn't communicate.

Hello, I'm trying to communicate CX2020 with PC.

But I couldn't understand how to find the proper setting for getting global variables.

Network setting is follwings.

 Name | AmsNetId | IP | Port
Desktop | 192.168.0.15.1.1 | 169.254.170.72
EAP3 | 5.53.192.206.4.1 | 169.254.193.18 | 27908
EAP2 | 5.53.192.206.3.1 | 169.254.37.249 | 27907
EatherCat | 5.53.192.206.2.1 |  27905
Target NetId | 5.53.192.206.1.1 |  
Local NetId | 192.168.0.15.1.1

Pyads code is just simple.
plc = pyads.Connection(5.53.192.206.3.1,27907,169.254.37.249)
plc.open()
ADSError:timeout elapsed (1861)

I tried combination of AmsNetId and port like 48898,851.

How should I config network setting?

Ads Error 10049 with Python2

>>> import pyads
>>> pyads.open_port()
33237
>>> pyads.get_local_address()
<AmsAddress 10.100.3.81.1.1:33237>

>>> adr = pyads.AmsAddr("5.36.84.188.1.1",851)

>>> adr
<AmsAddress 5.36.84.188.1.1:851>

>>> pyads.read_by_name(adr, 'DigitalOutputs.UMB_CONNECTED', pyads.PLCTYPE_BOOL)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\site-packages\pyads\ads.py", line 149, in read_by_name
    return adsSyncReadByName(adr, data_name, plc_datatype)
  File "C:\Python27\lib\site-packages\pyads\pyads.py", line 329, in adsSyncReadB
yName
    dataName, PLCTYPE_STRING
  File "C:\Python27\lib\site-packages\pyads\pyads.py", line 258, in adsSyncReadW
riteReq
    raise ADSError(err_code)
  File "C:\Python27\lib\site-packages\pyads\pyads.py", line 27, in __init__
    self.msg = "{} ({})".format(ERROR_CODES[self.err_code], self.err_code)
KeyError: 10049

The Error seems to be a Winsock Error
http://infosys.beckhoff.com/english.php?content=../content/1033/tcpipserver/html/tcplclibtcpip_e_winsockerror.htm&id=

WSAEADDRNOTAVAIL := 10049 ,(* The requested address is not valid in its context. *)

The error also occurs when using pyads.read_device_info or read_state. Only occurs for versions higher 1.3.0.

Reading array data

Hello, I'm new to using TwinCAT and your library.
On my TC2 device I have an ARRAY [1..160] OF channel, each channel stores variables "Status" (USINT) and "Data" (UINT). My task is to read array data to keep track of changes.
I am currently reading values of the array in a loop, iterating by offset. On each iteration I read status and data from one channel. This is definitely not a good approach as it takes too long to gather all data in the array.
I have seen examples of reading arrays like PLCTYPE_ARR_type, but I don't know what to do if I have an array of channels.

Python crashes after receiving a second notification

After moving my code from Windows to Linux I get crashes after the second notification. Both running pyads 2.2.5

This is an example of code that works on Windows but crashes on Linux:

import pyads
from time import sleep
import os

if not os.name == 'nt': # On Linux
    pyads.open_port()
    adr = pyads.AmsAddr(AMS, pyads.PORT_SPS2)
    pyads.add_route(adr, IP)
    sleep(8)
    print(pyads.get_local_address())

plc = pyads.Connection(AMS, pyads.PORT_SPS2, IP)
plc.open()

@plc.notification(pyads.PLCTYPE_REAL)
def callback(handle, name, timestamp, value):
    print(
        '{0}: received new notitifiction for variable "{1}", value: {2}'
        .format(name, timestamp, value)
    )

handles = plc.add_device_notification('REALVALUE', pyads.NotificationAttrib(4), callback)
sleep(10)
plc.del_device_notification(handles[0], handles[1])

The output on Linux is:
REALVALUE: received new notitifiction for variable "2017-10-10 11:22:06.225441", value: 1368.362548828125
REALVALUE: received new notitifiction for variable "2017-10-10 11:22:06.230441", value: 1367.6650390625
python3: AdsLib/RingBuffer.h:71: void RingBuffer::Read(size_t): Assertion `n <= BytesAvailable()' failed.
Aborted (core dumped)

"struct.error: unpack requires a buffer of 1 bytes" when trying to add notification

I have a Twincat 2 project, with UDT's as global variabels.

I was hoping to be able to get notifications when boolean-variables changes, by doing the following:

plc.connection.add_device_notification('.dStatus.bHardwareOk', pyads.NotificationAttrib(4), cb)

My callback function looks like this:

@plc.connection.notification(pyads.PLCTYPE_BOOL)
    def callback(handle, name, timestamp, value):
        print('{}: recieved notification for {} :: {}'.format(timestamp, name, value))

I get the following error:

Traceback (most recent call last):
File "_ctypes/callbacks.c", line 232, in 'calling callback function'
File "C:\Users\Esben L\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyads\pyads_ex.py", line 678, in wrapper
return callback(notification, data_name)
File "C:\Users\Esben L\AppData\Local\Programs\Python\Python37-32\lib\site-packages\pyads\ads.py", line 737, in func_wrapper
bytearray(data)[:data_size]
struct.error: unpack requires a buffer of 1 bytes

When I add a variable directly, like "MAIN.testbool", it works as expected. Reading the README, I see that the callback notification is not supported for structs.

Is there any way to get around this? Almost all of my variables are places in structs, for.... well for structures sake ;)

Any help and tips would be appreciated! And thank you for the effort put into this project!

pip install fails

Hi,

my install using pip fails with the following message. I am using Anaconda 5.1.0 and python 3.6.


(CONDA_ENV) D:\git\folder>pip install pyads
Collecting pyads
  Using cached https://files.pythonhosted.org/packages/34/8b/a9be7f22408f24e49c8ac38fde82c0b9b084e8fd9ec59fe9141e242f1fe9/pyads-2.2.8.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "C:\Users\user\AppData\Local\Temp\pip-build-qf3ps617\pyads\setup.py", line 133, in <module>
        with open('README.md', 'r') as f:
    FileNotFoundError: [Errno 2] No such file or directory: 'README.md'

Extract notification timestamp and variable name in callback decorator

To relieve the user from the need of using ctypes data structure operations for evaluating the value of a changed variable a notification decorator has been introduced.

This decorator takes a datatype as an attribute to specify the datatype of the return value. This decorator should be enhanced so that it returns the name of the variable that was changed as well as the timestamp of the change as a datetime object.

Linux: pyads uses wrong IP adress

Hello!

First I would like to say thank you for providing pyads as open source! It fits great for my home automation project at home.

I found the following issue:
If the AMS addr. is not derived from the IP address of the remote maschine pyads seems to use a wrong IP address to connect .

I use the newest version of pyads from git from today.

Example:
Remote maschine:
AMS addr.: 5.2.102.179.1.1
IP addr: 192.168.6.21

The following calls lead to an error:

import pyads
pyads.open_port()
30000
adr = pyads.AmsAddr('5.2.102.179.1.1', pyads.PORT_SPS1)
pyads.add_route(adr, '192.168.6.21')
2017-07-27T09:04:36+0000 Info: Connecting to 192.168.6.21 << correct

pyads.get_local_address()
<AmsAddress 192.168.6.27.1.1:30000>

plc = pyads.Connection('5.2.102.179.1.1', pyads.PORT_SPS1)
plc.open()
2017-07-27T09:04:53+0000 Info: Connecting to 5.2.102.179 << false
2017-07-27T09:07:04+0000 Error: Connect TCP socket failed with: 110
2017-07-27T09:07:04+0000 Info: connection closed by remote
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.7/dist-packages/pyads/ads.py", line 309, in open
adsAddRoute(self._adr.netIdStruct(), self.ip_address)
File "/usr/local/lib/python2.7/dist-packages/pyads/pyads_ex.py", line 75, in wrapper
return fn(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/pyads/pyads_ex.py", line 99, in adsAddRoute
raise ADSError(error_code)
pyads.pyads.ADSError: ADSError: Internal error (1)

pyads.structs Structure definition inaccuracies

  1. Some pyads structure data types are not platform/architecture-independent. Specifically, on Darwin x86_64:
ctypes.sizeof(ctypes.c_ulong) == 8

whereas it's expected to be equivalent to ctypes.c_uint32 (4 bytes). This is used in multiple pyads.structs structures.

  1. SAdsVersion version and revision should be unsigned (Reference: https://infosys.beckhoff.com/english.php?content=../content/1033/tcadsdll2/html/tcadsdll_strucadsversion.htm&id=3841831357073376293)

  2. SAdsSymbolEntry is missing the buffer which contains the symbol name, type name, and comment. (Using this working C++ code as a reference: https://bitbucket.org/europeanspallationsource/m-epics-twincat-ads/src/24f81b23c52355ea0088bebd509680470e5f24b5/adsApp/src/adsAsynPortDriverUtils.h?at=master#lines-110)

How to get notification to work on Linux?

Hi

I'm running the test as described here to handling notification callbacks on my Debian 9 Linux. I run the testserver on separate screen that showed that the notifications was added, but I the callback function has never been called.
I checked a lot, but I couldn't find the mistake.

In first ipython screen

import pyads
from pyads.testserver import *
server = AdsTestServer(handler=AdvancedHandler())
server.start()

In second ipython screen

import pyads
plc = pyads.Connection('127.0.0.1.1.1', 48898)
plc.open()
@plc.notification(pyads.PLCTYPE_INT)
def callback(handle, name, timestamp, value):
    print(
        '{1}: received new notitifiction for variable "{0}", value: {2}'
        .format(name, timestamp, value)
    )
handles = plc.add_device_notification('GVL.intvar',
                                          pyads.NotificationAttrib(2), callback)
# Write to the variable to trigger a notification
plc.write_by_name('GVL.intvar', 123, pyads.PLCTYPE_INT)

In first ipython terminal I have:

INFO:Command received: READWRITE (index group=61443, index offset=0, read length=4, write length=11, write data=GVL.intvar)
INFO:Command received: ADD_DEVICE_NOTIFICATION
INFO:Command received: READWRITE (index group=61443, index offset=0, read length=4, write length=11, write data=GVL.intvar)
INFO:Command received: WRITE (index group=61445, index offset=0, data length=2, value={
INFO:Command received: WRITE (index group=61446, index offset=0, data length=4, value=

But in second I cannot see the callback runs.
Thanks

Linux AMS server not working. Connection via Windows is working

The other day I was trying to connect to a Beckhoff CX5120 PLC running windows and TC3 runtime. We managed to connect via a windows machine and read back some PLC variables. Our master machine is a Linux PC so windows is no option in this case.

On Linux ( Arch ) however, the pyads 2.2 package does not work, it seems the AMS server is not working properly. Netstat tells me a tcp connection is opened but when I try to make a ADS connection it fails with a timeout.
Is there a good way to debug this thing? What kind of information can I collect in order to get to a working sollution?

Cannot communicate with TwinCAT 3 PLC (CX9020) on Ubuntu 16.04

Hi,

First of all, thank you so much for sharing this library. It's an amazing effort and it has made my life so much easier... at least on Windows! 😉

I have a Beckhoff CX9020 running with TwinCAT v3.1 that runs a small program, which controls a servomotor. I have made it so that when I set MAIN.bStart to True, my servomotor rotates 360 degrees and then stops. Using pyads on Windows 10 I was able to successfully set my variable using the write_by_name method in Python. I followed your guide and created a route in the TwinCAT Router UI, which then allowed me to communicate with the PLC in Python. This worked like a charm!

However, when trying the same thing in Ubuntu 16.04.3 LTS (with Python 3.5.2), I am having some issues. It seems as if the connection opens and then immediately closes when I try to connect to the PLC. I really don't understand why, as I believe I have correctly followed the steps provided in the guide and in the examples provided in some other related issues. See here:

sebastian@SB-Ubuntu:~$ ipython3
Python 3.5.2 (default, Sep 14 2017, 22:51:06) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import pyads

In [2]: plc = pyads.Connection('5.38.157.217.1.1', 851, '192.168.1.84')

In [3]: plc.open()
2017-10-23T14:30:18+0200 Info: Connected to 192.168.1.84

2017-10-23T14:30:18+0200 Info: connection closed by remote
In [4]: plc.write_by_name('MAIN.bStart', True, pyads.PLCTYPE_BOOL)
---------------------------------------------------------------------------
ADSError                                  Traceback (most recent call last)
<ipython-input-4-783030c2433f> in <module>()
----> 1 plc.write_by_name('MAIN.bStart', True, pyads.PLCTYPE_BOOL)

/usr/local/lib/python3.5/dist-packages/pyads/ads.py in write_by_name(self, data_name, value, plc_datatype)
    531         if linux:
    532             return adsSyncWriteByNameEx(self._port, self._adr, data_name,
--> 533                                         value, plc_datatype)
    534         else:
    535             return adsSyncWriteByName(self._adr, data_name, value,

/usr/local/lib/python3.5/dist-packages/pyads/pyads_ex.py in adsSyncWriteByNameEx(port, address, data_name, value, data_type)
    495     handle = adsSyncReadWriteReqEx2(
    496         port, address, ADSIGRP_SYM_HNDBYNAME, 0x0,
--> 497         PLCTYPE_UDINT, data_name, PLCTYPE_STRING
    498     )
    499 

/usr/local/lib/python3.5/dist-packages/pyads/pyads_ex.py in adsSyncReadWriteReqEx2(port, address, index_group, index_offset, read_data_type, value, write_data_type)
    365 
    366     if err_code:
--> 367         raise ADSError(err_code)
    368 
    369     # If we're reading a value of predetermined size (anything but a string),

ADSError: ADSError: timeout elapsed (1861)

I have also tried running plc.open_port() before attempting to connect to the PLC. In addition, I have also tried creating the Connection object without the target IP parameter. I am getting the same error every time.

The AMS Net ID of my PLC is 5.38.157.217.1.1 and, on Windows, I can connect to it on port 851. By running arp-scan I can see that my PLC has gotten the IP 192.168.1.84 by router's DHCP server.

sebastian@SB-Ubuntu:~$ sudo arp-scan --interface=wlp5s0 --localnet
[sudo] password for sebastian: 
Interface: wlp5s0, datalink type: EN10MB (Ethernet)
Starting arp-scan 1.8.1 with 256 hosts (http://www.nta-monitor.com/tools/arp-scan/)
192.168.1.1	88:a6:c6:e2:42:93	(Unknown)
192.168.1.8	58:00:e3:d6:e1:05	(Unknown)
192.168.1.55	9c:eb:e8:43:af:24	BizLink (Kunshan) Co.,Ltd
192.168.1.84	00:01:05:26:9d:d9	Beckhoff Automation GmbH
192.168.1.90	9c:eb:e8:43:af:24	BizLink (Kunshan) Co.,Ltd
192.168.1.99	34:97:f6:d3:8c:30	(Unknown)
192.168.1.78	9c:b6:d0:df:d6:71	(Unknown)

7 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.8.1: 256 hosts scanned in 1.321 seconds (193.79 hosts/sec). 7 responded

The IP of my Ubuntu laptop is 192.168.1.82. Here are some more details about my own network interface:

wlp5s0    Link encap:Ethernet  HWaddr 58:00:e3:4f:4a:e7  
          inet addr:192.168.1.82  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::63a5:9c67:34b4:3da3/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:12856 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5534 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:13604275 (13.6 MB)  TX bytes:808785 (808.7 KB)

According to some other GitHub issues, it should not be necessary to manually create the route before trying to connect to the PLC. I tried doing so anyway but I still get the message that it connects and then immediately disconnects. Anyway, it shouldn't be necessary to create the route, as I already created it from Windows. Or is there something I am not understanding correctly? I have dual-boot running on my laptop so when I am in Ubuntu my laptop has the same IP as when I am in Windows.

I really hope you can help me resolve this issue, as I would love to use pyads on Linux. Please let me know if you need any additional information.

Thank you very much!

In Linux when Connection Lost how to fix it amsroute

Hello, I am writing python3.5 with using library on raspberrypi.This Service read tags from plc periodicly.
But When connection lost between raspberry pi and beckhoff plc, again not fixed connection I am solving this issue using deleterouting addrouting.Is there any Correct solution ?

Main.py :

from Clients.Client import BeckhoffClient
from time import sleep

plc = BeckhoffClient('5.22.252.149.1.1', 801, '192.168.1.24')
while True:
    try:
        if not plc.is_connected():
            success = plc.connect_plc()
            print('Connection Successfully')
    except Exception as ex:
        print(ex)
    sleep(10)

Client.py :

import pyads


class BeckhoffClient:
    def __init__(self, amsNetId, port, targetIp):
        self.AmsNetID = amsNetId
        self.Port = port
        self.TargetIp = targetIp
        self.Connection = pyads.Connection(self.AmsNetID, self.Port, self.TargetIp)

    def connect_plc(self):
        try:
            adr = pyads.AmsAddr(self.AmsNetID, pyads.PORT_SPS1)
            pyads.delete_route(adr)
            pyads.add_route(adr, self.TargetIp)
            self.Connection.open()
            if self.Connection.is_open:
                self.Connection.read_device_info()

                return True
            else:
                return False
        except Exception as ex:
            self.Connection.close()
            raise ex

    def is_connected(self):
        if self.Connection.is_open:
            try:
                self.Connection.read_device_info()
                return True
            except Exception as ex:
                return False
        else:
            return False


'pip install pyads' fails on setup.py line 133 trying to read README.md

Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Windows\system32>pip install pyads
Collecting pyads
Using cached https://files.pythonhosted.org/packages/19/b3/7d7f221f22b764422da
ec04868500f341a00518bd67a3ce5a3f036ccb956/pyads-2.2.11.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "", line 1, in
File "C:\Users\user\AppData\Local\Temp\pip-install-el3gpde0\pyads\setup
.py", line 133, in
long_description = read('README.md')
File "C:\Users\user\AppData\Local\Temp\pip-install-el3gpde0\pyads\setup
.py", line 20, in read
encoding=kwargs.get("encoding", "utf8")
FileNotFoundError: [Errno 2] No such file or directory: 'C:\Users\user
\AppData\Local\Temp\pip-install-el3gpde0\pyads\README.md'

----------------------------------------

Command "python setup.py egg_info" failed with error code 1 in C:\Users\user
AppData\Local\Temp\pip-install-el3gpde0\pyads\

adsSyncWriteByName and STRING

Hi,
maybe I'm doing something wrong, but I can't read and write STRING variables from TwinCAT (2.11) using the adsSyncWriteByName methode. When reading it returns just the first character and when writing it throws: "ADSError: parameter size not correct (1797)".

Thanks for any help,
Peter

PS: I looked up the implementation of PLCTYPE_STRING and found that it is just a c_char. So the solution is to multiply the type by the length of the string... Too simple...
Thanks for the nice wrapper!

KeyError -1

Dear,

First of all thanks for this library, it has been very useful.

In a (fast) cyclic program I occasionally get KeyError -1 exceptions when reading or writing data.

I get these messages when running the application in a console when the exceptions occur.
2017-09-27T21:44:38+0200 Warning: Port: 30001 already in use as 0

This is the only reference I found on the web describing the same error:
Beckhoff/ADS#20

Any idea how this can be solved?

Thanks in advance,

Alexander

Install through pip fails with ELF CLASS error

Hi,

This is just a minor bug. If i install your library through pip on a raspberry pi, the installation fails with an ELF CLASS error. (x86 vs x64) This happened since the adslib.so has been included as a shared library. I found a workaround by overwriting the adslib.so of an older version (2.1.0).

Regards

Target port not found ADS Server not started (6)

Hi All,
I am trying to control some variables on the PLC through pyads. Unfortunately I keep on receiving always the same error message. 'target port not found ADS Server not started (6)'. Any ideas on how to get around this issue? Thanks a lot in advance.

`import pyads

plc = pyads.Connection('10.2.9.142.1.1', 851)
plc.open()
plc.read_device_info()`

Add device notification methods

Hey,
I don't know if you're already working on this but it would be nice to have the methods "AdsSyncAddDeviceNotificationReq" & "AdsSyncDelDeviceNotificationReq" to use callbacks on variable changes.
I will try to implement them myself.
Regards,
Peter

Add proper support for multiple ams ports

Hey,

Right now all PLC connections run over the same ams port which is opend in the open_port method in ads.py. This is not desirerable for multi-connection-applications such as multi threading. It would be better to have one port for each new PLC connection.

Regards,
Peter

Set own AmsNetID

Hello,

I am trying to use pyads from within a docker container - this works fine, but since the AmsNetID is depentent on the IP Address, and the "internal" IP Address of a container is not necesseraly fixed, the AmsNetID changes a lot, resulting in routing problems on the remote device. Is there a way to set the AmsNetID indepently of the IP Address?

Best regards

Method existance check in "adsSyncReadWriteReq"

When i was trying to rewrite the "Reading the PLC variable declaration of an individual variable" example from "https://infosys.beckhoff.com/content/1033/tc3_adssamples_c/html/tcadsdll_api_cpp_sample14.htm?id=17573" in Python i got the following error.

C:\test>python pyads_dump_single.py
Traceback (most recent call last):
File "pyads_dump_single.py", line 26, in
dataSIBN = adsSyncReadWriteReq(adr, ADSIGRP_SYM_INFOBYNAMEEX, 0, bufferType,
varName, PLCTYPE_STRING)
File "C:\Python27\lib\site-packages\pyads\pyads.py", line 260, in adsSyncReadW
riteReq
return readData.value
AttributeError: 'c_ubyte_Array_65535' object has no attribute 'value'

So i decided to add method existance check to the adsSyncReadWriteReq function on my PC, just like it is done in the "adsSyncReadReq"

# ORIGINAL
#return readData.value
# ORIGINAL

# TEST
if hasattr(readData, 'value'):
    return readData.value
else:
    if type(plcReadDataType).__name__ == 'PyCArrayType':
        dout = [i for i in readData]
        return dout
    else:
        # if we return structures, they may not have a value attribute
        return readData
# TEST

and after that it works fine. May be it should be done in the "pyads" library somehow.

Linux: KeyError: 2127924964 on plc.close()

On linux the following error occures:

import pyads
pyads.open_port()
30000
plc = pyads.Connection('5.2.102.179.1.1', pyads.PORT_SPS1, '192.168.6.21')
plc.open()
2017-07-27T10:31:42+0000 Info: Connecting to 192.168.6.21
plc.read_by_name('.gWSM_Scene_ID_Buero', pyads.PLCTYPE_BYTE)
2
plc.close()
2017-07-27T10:32:02+0000 Info: connection closed by remote
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.7/dist-packages/pyads/ads.py", line 322, in close
adsDelRoute(self._adr.netIdStruct())
File "/usr/local/lib/python2.7/dist-packages/pyads/pyads_ex.py", line 75, in wrapper
return fn(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/pyads/pyads_ex.py", line 118, in adsDelRoute
raise ADSError(error_code)
File "/usr/local/lib/python2.7/dist-packages/pyads/pyads.py", line 34, in init
self.msg = "{} ({})".format(ERROR_CODES[self.err_code], self.err_code)
KeyError: 2127924964

I use the newest version of pyads from git from today on raspbian.

Convenience function for reading and writing string values

def adsSyncReadReqString(adr, indexGroup, indexOffset, bufferSize=1024,encoding='utf-8',errors='strict'):
    """
    :summary: Read a string synchronous from an ADS-device
    :param pyads.structs.AmsAddr adr: local or remote AmsAddr
    :param int indexGroup: PLC storage area, according to the INDEXGROUP
        constants
    :param int indexOffset: PLC storage address
    :param int bufferSize: number of bytes in the transfer buffer
    :param str encoding: see bytes.decode
    :param str errors: see bytes.decode
    :rtype: str
    :return: value: **value**

    """

    adsSyncReadReqFct = pyads._adsDLL.AdsSyncReadReq

    pAmsAddr = pointer(adr.amsAddrStruct())
    nIndexGroup = c_ulong(indexGroup)
    nIndexOffset = c_ulong(indexOffset)

    data = bytes(bufferSize)
    nLength = c_ulong(len(data))
    errCode = adsSyncReadReqFct(
        pAmsAddr, nIndexGroup, nIndexOffset, nLength, data)

    return data.rstrip(b'\0').decode(encoding=encoding,errors=errors)

<AmsAddress 0.0.0.0.1.1:30000>

Hello, when i try to use pyads.get_local_address(), it always returns <AmsAddress 0.0.0.0.1.1:30000>, my issue is on the 0.0.0.0 instead of my ip, how can i get around this?
I don't want to have it as 0.0.0.0 because i need to add the ip to the PLC.

Thank you.

Missed field in the "SAdsSymbolEntry" sructure

When i was trying to read the variables information from the runtime like in the "Read PLC variable declaration" example from "https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adssamples_c/html/tcadsdll_api_cpp_sample10.htm&id=17583" in Python i have found that the "dataType" field was missed the "SAdsSymbolEntry" sructure. The next important thing is that ctypes strucures without "pack = 1" field can have wrong size in bytes and field values in such structure may be wrong after the data were copied in that structure.
Here is the test script http://pastebin.com/qGMnkK2F
And its output on my PC http://pastebin.com/Q1hNFZQj

Return raw array data for further processing with numpy

I'm transferring several float arrays with millions of samples each second by using read_by_name with PLCTYPE_ARR_REAL(1000000).
After profiling i noticed the function is spending most of its time in converting the ctypes array to a Python list:
if type(data_type).__name__ == "PyCArrayType": return [i for i in data]
Adding a "raw" argument, that returns the ctypes array directly, and reading it into a numpy array via np.frombuffer afterwards/manually reduces the time spent in this function by ~80% and increases the overall throughput a lot.

Connection.open can't work on windows machines

On windows machines "adsAddRoute" during plc.open() will called, so you get an RuntimeError:

image

Currently it's possible to get it work with the following workaround:

image

Please add a platform check in your open method like this:

image

PLC Connection Linux

I'm trying to connect to a Beckhoff PLC using this library on Linux and I cannot get a connection to hold. The code:

with pyads.Connection('5.13.82.20.1.1', pyads.PORT_SPS1, '192.168.200.211') as plc:
	return plc.read_by_name('.AUTO_WITH_PARTS', pyads.PLCTYPE_BOOL)

On Windows this runs flawlessly, but on Ubuntu it displays:

2019-07-11T10:01:01-0400 Info: Connected to 192.168.200.211
2019-07-11T10:01:06-0400 Info: connection closed by remote

When I don't specify the IP address it sits there for a few minutes and returns:
2019-07-11T10:06:51-0400 Error: Connect TCP socket failed with: 110

Do I still need to add my Ubuntu PC's AMS address to the PLC manually as discussed here?

Output from setup.py test

----------- coverage: platform linux, python 3.6.9-final-0 -----------
Name                  Stmts   Miss  Cover
-----------------------------------------
pyads/__init__.py        10      0   100%
pyads/ads.py            178     15    92%
pyads/constants.py      123      4    97%
pyads/errorcodes.py       1      0   100%
pyads/filetimes.py       21      0   100%
pyads/pyads_ex.py       234     38    84%
pyads/structs.py         93     15    84%
pyads/utils.py            7      0   100%
-----------------------------------------
TOTAL                   667     72    89%

ADSError: ROUTERERR_PORTALREADYINUSE (1286)

Hi,

Thanks for your work on this. Do you have an insight on to why I'd be getting error 1286? My code is as follows:

pyads.open_port()
remote_ip = '192.168.1.38'
adr = pyads.AmsAddr('5.59.211.2.1.1',851)
pyads.add_route(adr,remote_ip)
plc = pyads.Connection('5.59.211.2.1.1',851)
plc.open()

In my case, 192.168.1.38 is the remote machine, with NetID 5.59.211.2.1.1.

The 'add_route' method returns "connected to 192.168.1.38", but then plc.open() gives the port in use error.

I noticed the route is not in the routing table of the remote PLC... does it work on remote machines instead of 127.0.0.1.1.1? I don't see a method for adding the password, so I'm not sure how it would work. To work around that, like I've done with node-ads in the past, I created a static local route to my machine from the PLC side, but I'm not able to connect, given the error above.

I'm on ubuntu with a win7 TC3 PLC.

Thanks!

Reading Boolean inside an FB which is instantiated in an Array

Hi,

First, thanks for this python library. It makes it very easy to access PLC variables.
I have one problem accessing a Boolean inside an FB which is instantiated in an Array.
I have the following scenario:

  • I have an FB calls IoOutput with a output_ AT%Q* : BOOL instantiated inside
  • This IoOutput FB is instantiated as an Array of three outputs (output_[0], output_[1], output_[2])
  • And this Array is instantiated in another FB and so on to finally hosted in the MAIN Prg

Now i want to access the Boolean inside the IoOutput FB but that does not work
I tried the following call: redLight = pyads.read_by_name(adr, 'MAIN.ampel1_.output_.output_[0].output_', pyads.PLCTYPE_BOOL)

Did you try such an example => Accessing Variables in FBs which are instantiated as Array?

Hope you understand my problem, maybe with the pic attached...

Thanks,

Matthias
output
symbolnotfound

Edit1: I know that the error (ADSError: Symbol not found) comes up with this call, but i can normally access another variable which is not instantiated inside an array of FB
Edit2: My first try was without the trailing slash, my second try was with trailing slash which you can see inside the command window

pip install pyads leaves "src" folder in virtenv base folder

The remaining "src" folder with the adslib sources is not needed anymore but adds tons of git changes in a dev. environment (for e.g. HomeAssistant).

image

Could the src folder be deleted after install?

Wouldn't it be better to change the name of the folder to e.g. ADSLibSrc or place it elsewhere during install, in order to prevent overlapping names?

array datatypes for function read_write

Hi,
I am currently trying to use the function read_write on a Connection instance. However I figured out that for the write parameter (plc_write_datatype) only none-array types are working. It would be very nice to be able to use array types as well.
I assume implementing this is quiet trivial as it is already working fine for the write function.
Just copying the folllowing code from pyads_ex.py / adsSyncWriteReqEx

    else:
        if type(plc_data_type).__name__ == "PyCArrayType":
            data = plc_data_type(*value)
        else:
            data = plc_data_type(value)

.. to adsSyncReadWriteReqEx2. But changing plc_data_type to write_data_type.

PS: Thanks for sharing your great work on this Python package.

Different SAdsNotificationHeader for Windows and Linux ADS version

The arrangement of the fields in the struct SAdsNotificationHeader seems to differ between Windows ADS and Linux ADS.

When using the adslib.so the struct is arranged this way:

class SAdsNotificationHeader(Structure):
    _pack_ = 1
    _fields_ = [("hNotification", c_uint32),
                ("nTimeStamp", c_uint64),
                ("cbSampleSize", c_uint32),
                ("data", POINTER(c_ubyte))]

On Windows however the parsing would show unfitting values for both nTimeStamp and hNotification. In fact both fields are swapped in the Windows version:

class SAdsNotificationHeader(Structure):
    _pack_ = 1
    _fields_ = [("nTimeStamp", c_uint64),
                ("hNotification", c_uint32)
                ("cbSampleSize", c_uint32),
                ("data", POINTER(c_ubyte))]

This issue has been already addressed on #33. @dabrowne Maybe it would be best to just swap the two fields in the adslib? What do you thing?

Saving device notification callback handle makes wrong assumptions

Hey,

when using device notifications, you save the handles to the callback functions here:

https://github.com/stlehmann/pyads/blob/af76fd5/pyads/pyads_ex.py#L692

This is correct in the sense that it is required to avoid garbage collection of the handles, leading to crashes. What is not correct is that they are indexed in the handle array only by the notification handle. When using notifications on multiple AMS netids or ports, TcAdsDll may give back the same notification handle twice. The handles seem to be scoped by netid/port (which makes sense since they are determined by the ADS server you query). If this happens, the prior callback gets overwritten and Python crashes (this took a long time to find...). It is quite likely (read: almost guaranteed) to happen when you have newly started an XAR and register multiple notifications on multiple tasks/ports.

There is an easy solution to this, which is saving the callback indexed by netid, port, and notification handle. And additionally checking the callback array so that it does not get overwritten by some accident.

But I think that the current way of handling notification handles is a bit weird anyway. As "user handle", you get back the handle of the variable resolved by ADSIGRP_SYM_HNDBYNAME, which you can't really use anyway. In my opinion, the library should take care of remembering that handle to free it later when deleting the notification, and the user does not even need to see it. So a different solution could be to save a structure per netid/port/handle tuple that contains both the variable handle (to free it later) and a reference to the callback function (so it does not get garbage-collected). That would modify the interface of add_device_notification however.

Please tell me what you think so I can implement the solution that is more likely to be accepted as PR.

Read Frame Error due to NetBIOS

When I make a connection to a specific PLC (don't know the model offhand) it queries my computer's NetBIOS name directly. Ubuntu denies this request, and then the PLC loops through its saved hostnames broadcasting and waiting for an answer which my PC eventually responds to. Trouble is, even when I set the timeout to 30 seconds it still "times out".

2019-07-15T08:58:51-0400 Info: Connected to 192.168.200.210
2019-07-15T08:59:02-0400 Error: read frame failed with error: 104
2019-07-15T08:59:02-0400 Info: connection closed by remote

That read error happens when the PLC sends a TCP message to my computer acknowledging its existence and pyads (or the underlying ADS library) closes the connection.
image

Is there a way to make Ubuntu respond to these direct NetBIOS requests? If not, then is there a way to prevent the connection from closing when there is a read frame error?

Notification limited to 8 byte?

I am trying to add a notification for a STRUCT that contain 571 bool

When the notification is received I can only access 8 bytes while the datasize is 598 (should be 571?):

In [5]: contents.cbSampleSize
Out[5]: 598
In [6]: len(bytearray(contents.data))
Out[6]: 8

From wireshark I can see the following:
image

This is on Windows...

Add context manager support

Hey Stefan,
to make the library even more pythonic it would be nice to handle the connection with a context manager. I already wrote a class and will open a pull request as soon as you accept the previous one.
Regards,
Peter

Callback Function not working for Windows platform

When trying to add a device notification on a Windows computer, I am getting:

Traceback (most recent call last):
  File "ADSReporting.py", line 61, in <module>
    plc.add_device_notification('MAIN.i', pyads.NotificationAttrib(2), callback)

  File "C:\Users\ajacques\AppData\Local\Continuum\anaconda3\lib\site-packages\py
ads\ads.py", line 623, in add_device_notification
    user_handle)
  File "C:\Users\ajacques\AppData\Local\Continuum\anaconda3\lib\site-packages\py
ads\pyads_ex.py", line 585, in adsSyncAddDeviceNotificationReqEx
    raise TypeError("Callback function type can't be None")
TypeError: Callback function type can't be None

This error is coming from pyads_ex.py:

if LNOTEFUNC is None:
        raise TypeError("Callback function type can't be None")

But it appears that LNOTEFUNC is only populated if the platform is Linux:

LNOTEFUNC = None

# load dynamic ADS library
if platform_is_windows():
    _adsDLL = ctypes.windll.TcAdsDll  # type: Union[ctypes.CDLL, ctypes.WinDLL]

elif platform_is_linux:
    # try to load local adslib.so in favor to global one
    local_adslib = os.path.join(os.path.dirname(__file__), 'adslib.so')
    if os.path.isfile(local_adslib):
        adslib = local_adslib
    else:
        adslib = 'adslib.so'

    _adsDLL = ctypes.CDLL(adslib)

    LNOTEFUNC = ctypes.CFUNCTYPE(None, ctypes.POINTER(SAmsAddr),
                                 ctypes.POINTER(SAdsNotificationHeader),
                                 ctypes.c_ulong)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.