stlehmann / pyads

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

Read_by_name with array elements leads to ADSError on TwinCAT2 #188

Closed stlehmann closed 2 years ago

stlehmann commented 3 years ago

This issue was reported by mail to me. It seems that it is possible to address array elements with read_by_name directly. I honestly didn' t know this works. Also I don't know if it makes sense because it increases the traffic if you read all members of an array separately instead of reading the whole array at once.

import pyads

sArrPos_Time = []
sArrPos_Text = []

minRange = 0
maxRange = 43

# connect to plc and open connection
plc = pyads.Connection('5.59.38.162.1.1', pyads.PORT_SPS1)
plc.open()

# read int value by name
for i in range(minRange,maxRange):
    var1 = '.stLogErrorArray[%s].tErrorTime' % (i)
    var2 = '.stLogErrorArray[%s].sErrorString' % (i)
    sArrPos_Time.append(plc.read_by_name(var1, pyads.PLCTYPE_DT))
    sArrPos_Text.append(plc.read_by_name(var2, pyads.PLCTYPE_STRING))

# close connection
plc.close()

This will lead to the following error:

Traceback (most recent call last):

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 197, in _run_module_as_main

    return _run_code(code, main_globals, None,

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in _run_code

    exec(code, run_globals)

  File "c:\Users\MFI\.vscode\extensions\ms-python.python-2020.12.424452561\pythonFiles\lib\python\debugpy\__main__.py", line 45, in <module>

    cli.main()

  File "c:\Users\MFI\.vscode\extensions\ms-python.python-2020.12.424452561\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 444, in main

    run()

  File "c:\Users\MFI\.vscode\extensions\ms-python.python-2020.12.424452561\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py", line 285, in run_file

    runpy.run_path(target_as_str, run_name=compat.force_str("__main__"))

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 268, in run_path

   return _run_module_code(code, init_globals, run_name,

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 97, in _run_module_code

    _run_code(code, mod_globals, init_globals,

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\runpy.py", line 87, in _run_code

    exec(code, run_globals)

  File "c:\Daten\VSCodeWorkspace\Scripts\ADSExample.py", line 22, in <module>

    sArrPos_Text.append(plc.read_by_name(var2, pyads.PLCTYPE_STRING))

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\site-packages\pyads\ads.py", line 825, in read_by_name

    return adsSyncReadByNameEx(

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\site-packages\pyads\pyads_ex.py", line 1033, in adsSyncReadByNameEx

    value = adsSyncReadReqEx2(

  File "c:\Users\MFI\AppData\Local\Programs\Python\Python39\lib\site-packages\pyads\pyads_ex.py", line 783, in adsSyncReadReqEx2

    raise ADSError(error_code)

pyads.pyads_ex.ADSError: ADSError: invalid parameter value(s) (1798).

I couldn't verfify this issue myself because I currently got other issues with TwinCAT but will try to do so soon. Any thoughts?

chrisbeardy commented 3 years ago

This is actually in the docs: image

I've used this to read single plc variables from an array before.

I just tried again now by looping through an array and pyads seems to have no trouble reading this. TwinCAT 4024.10 python 3.6. image image

I've just run though a range of scenarios to try and replicate the error.

I don't think its a case of trying to access an item that is not in the array (e.g. wrong range in example) as if you do that you get the following error: image

Or the wrong symbol/variable name as then you get: image

Or the wrong pyads plc datatype: image

I noticed the user is using python 3.9, I'll have to download that to give it a go, but this seems to be an ADS error. But I can't replicate it and not sure what "invalid parameter value" means.

stlehmann commented 3 years ago

@chrisbeardy Thanks for trying to replicate this error. I just finally got my TwinCAT up and running on Windows 10 and gave it a try with an array of integers and an array of structs. Both worked well enough.

chrisbeardy commented 3 years ago

I also noticed, looking at the variable names in the example ".stLogErrorArray". This must be a TwinCat 2 System. As TwinCat 2 Global vars can be address with just the . at the fron whereas TC3 needs the full name. Maybe this is a TC2 issue with arrays.

stlehmann commented 3 years ago

Thanks @chrisbeardy. I encouraged the original questioneer to join this Github discussion. Also I will give him a short note that it might be related to TwinCAT2.

mfischlin commented 3 years ago

Hi

I am the questioneer and i can give you some more details. It's correct that i use TwinCat2 with a CX5010-0111 which is running with a Windows Embedded CE 6 and TwinCAT-2-PLC-Runtime. My notebook is running with Windows 10.

Every array position includes a struct with a timestamp (type DT) and an error (type string). The array is 201 elements long (0..200) and is from type VAR_GLOBAL RETAIN PERSISTENT. I made several tests. With array dimensions < 43 elements (0..41), everything is working correct and no error occure. But if the array is >= 43 elements (0..42 and higher) the exception occured. The exception occured everytime 17 elements before the end of the array and it occured only at the string-elements. I can read the DT-elements up to element 200 without an exception.

As a test i tried to read a region of the array. If i read the elements 180..185 of an array[0..200] the error occure at element 183 as well.

I asked the Beckhoff Support Switzerland if something is wrong with my code or if they have an idea, what the problem could be. They could not found a problem in the PLC-code and told me it must be a problem with pyads.

I made a small test project. You can find it in the zip-file below. (python-script and TwinCat-project) ADS_Test.zip

I hope I could give some information to narrow down the error.

chrisbeardy commented 3 years ago

Hi, @mfischlin, thanks for posting.

I have not managed to try your exact code as I do not have TwinCat2, however I tried to replicate with TC3 as best as possible did not manage to get any errors.

I know this does not solve the problem, but is it possible to circumvent the issue by reading the whole structure array and then iterating over the it in python to append what you want.

e.g.

structure_def = (
    ("tErrortime", pyads.PLCTYPE_DT, 1),
    ("sErrorString", pyads.PLCTYPE_STRING, 1),
)
error_dict = plc.read_structure_by_name(".stLogErrorArray", structure_def, array_size=201)

for i in range(minRange,maxRange):
    error_time = error_dict....
    error_string = error_dict...

    sArrPos_Time.append(error_time)
    sArrPos_Text.append(error_string)

The format you would get back is an OrderedDict of OrderedDicts. This can esaaily be converted to normal dictionaries by using the json module error_dict = json.loads(json.dumps(error_dict ))

Depending on the PLC code requirments, sometimes I actually invert it so I have a structure of arrays, then it comes back a bit nicer into python, as you have a dictionary of arrays or errors so can iterate over them more easily.

Differecnces are in this example I have from somewhere else:

import pyads
import json

single_struct_def = (
    ("timestamp", pyads.PLCTYPE_STRING, 1),
    ("force", pyads.PLCTYPE_REAL, 1),
    ("pos", pyads.PLCTYPE_REAL, 1),
    ("someInt", pyads.PLCTYPE_INT, 1),
    ("aBool", pyads.PLCTYPE_BOOL, 1),
)
size_of_single = pyads.size_of_structure(single_struct_def)

array_struct_def = (
    ("timestamp", pyads.PLCTYPE_STRING, 11),
    ("force", pyads.PLCTYPE_REAL, 11),
    ("pos", pyads.PLCTYPE_REAL, 11),
    ("someInt", pyads.PLCTYPE_INT, 11),
    ("aBool", pyads.PLCTYPE_BOOL, 11),
)
size_of_array = pyads.size_of_structure(array_struct_def)

def main():
    plc = pyads.Connection("169.254.198.195.1.1", 851)
    plc.open()
    array_of_struct = plc.read_structure_by_name("DATALOG.arrayOfStruct", single_struct_def, array_size=11,
                                                 structure_size=size_of_single * 11)
    struct_of_array = plc.read_structure_by_name("DATALOG.structOfArray", array_struct_def,
                                                 structure_size=size_of_array)
    plc.close()

    print(array_of_struct)
    print(struct_of_array)

    array_of_struct = json.loads(json.dumps(array_of_struct))  # using json because of nested ordered dicts
    print(array_of_struct)
    struct_of_array = dict(struct_of_array)
    print(struct_of_array)

if __name__ == '__main__':
    main()
mfischlin commented 3 years ago

Thank you for your help @chrisbeardy. I will read the structure array like you describe.

chrisbeardy commented 3 years ago

Thank you for your help @chrisbeardy. I will read the structure array like you describe.

That's ok 😁 I hope it works out ok for you.