Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sending write command without variable value (length 0) #335

Open
CagtayFabry opened this issue Nov 17, 2022 · 5 comments
Open

Sending write command without variable value (length 0) #335

CagtayFabry opened this issue Nov 17, 2022 · 5 comments

Comments

@CagtayFabry
Copy link

I am trying to send an AdsSyncWriteReqEx command without any data using pyads

The equivalent C API command I am trying to send is using a nullptr as data (with length 0)

AdsSyncWriteReqEx(nPort, &mAddr, 0xF088, 0x00000000 | (3 & 0x000000FF), 0, nullptr);

and is taken from the TwinCAT 3 | Corrected timestamps - ADS consumer example.

I initially tried plc.write() which didn't work and also tried using the ADS bindings in pyads more directly after opening the ADS connection with plc.open()

from pyads.pyads_ex import adsSyncWriteReqEx

adsSyncWriteReqEx(plc._port, plc._adr, 0xF088, 0x00000000 | (3 & 0x000000FF), None, None)

but I run into the following error which I think indicates that None is not a valid replacement for nullptr.

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [5], line 2
      1 from pyads.pyads_ex import adsSyncWriteReqEx
----> 2 adsSyncWriteReqEx(plc._port, plc._adr, 0xF088, 0x00000000 | (3 & 0x000000FF), None, None)

File pyads/pyads_ex.py:495, in adsSyncWriteReqEx(port, address, index_group, index_offset, value, plc_data_type)
    493     data = value
    494 else:
--> 495     data = plc_data_type(value)
    497 data_pointer = ctypes.pointer(data)
    498 data_length = ctypes.sizeof(data)

TypeError: 'NoneType' object is not callable

Is there any way I could send this command using pyads? How to work with nullptr in python?
I am thankful for any suggestions

@chrisbeardy
Copy link
Collaborator

I am sorry that noone replied to you on this. I think this is because noone knows the solution. Sorry! This is a pretty advanced case though and not something I have come across people wanting to do. As this is more of a question that a bug, could you please kindly close this issue to help us manage the issue tracker.

@CagtayFabry
Copy link
Author

Thanks for looking into it @chrisbeardy

I agree that this is more of a feature request than a bug report (and a pretty advanced one probably)
If you don't mind I would like to keep this open, maybe someone scrolls past here at some point who knows how to implement this.

As for the feature itself, I think it would be a great addition if pyads would be able to handle receive the corrected timestamp information. 🚀

@chrisbeardy
Copy link
Collaborator

Looking into this in more detail, it looks like None should be used as a null pointer in python when using ctypes.

https://docs.python.org/3/library/ctypes.html#calling-functions

The issue here i think is the pyads function adsSyncWriteReqEx does not account for this being passed in and is throwing the error before it even calls the C DLL.

It actually calls the C DLL here

    error_code = sync_write_request(
        port,
        ams_address_pointer,
        index_group_c,
        index_offset_c,
        data_length,
        data_pointer,
    )

You could experiment with editing the pyads code to account for the None type being passed in.

e.g.

        elif type(value) is plc_data_type:
            data = value
        elif plc_data_type is None:
            data = None
        else:
            data = plc_data_type(value)

This may not work due to the lines:

        data_pointer = ctypes.pointer(data)
        data_length = ctypes.sizeof(data)

You could also try calling the C DLL manually yourself by accessing the "private" variable in the library, or just adding it yourself by cribbing the following:

if platform_is_windows():  # pragma: no cover, skip Windows test
    dlldir_handle = None
    if sys.version_info >= (3, 8) and "TWINCAT3DIR" in os.environ:
        # Starting with version 3.8, CPython does not consider the PATH environment
        # variable any more when resolving DLL paths. The following works with the default
        # installation of the Beckhoff TwinCAT ADS DLL.
        dll_path = os.environ["TWINCAT3DIR"] + "\\..\\AdsApi\\TcAdsDll"
        if platform.architecture()[0] == "64bit":
            dll_path += "\\x64"
        dlldir_handle = os.add_dll_directory(dll_path)
    try:
        _adsDLL = ctypes.WinDLL("TcAdsDll.dll")  # type: ignore
    finally:
        if dlldir_handle:
            # Do not clobber the load path for other modules
            dlldir_handle.close()
    NOTEFUNC = ctypes.WINFUNCTYPE(  # type: ignore
        ctypes.c_void_p,
        ctypes.POINTER(SAmsAddr),
        ctypes.POINTER(SAdsNotificationHeader),
        ctypes.c_ulong,
    )
    sync_write_request = pyads._adsDLL.AdsSyncWriteReqEx  # something like that, can't remember exact path, you'll be able to fudge the path

    ams_address_pointer = ctypes.pointer(address.amsAddrStruct())
    index_group_c = ctypes.c_ulong(index_group)
    index_offset_c = ctypes.c_ulong(index_offset)

     error_code = sync_write_request(
         plc._port,
        ctypes.pointer(plc._adr.amsAddrStruct())
        ctypes.c_ulong(0xF088),
        ctypes.c_ulong(0x00000000 | (3 & 0x000000FF)),
        None,
        None,
)

@CagtayFabry
Copy link
Author

This is some great insight, thank you! I will try to get it to work.

I agree, pyads has all the tools in place to make this work, might just need a few tweaks.

@chrisbeardy
Copy link
Collaborator

Let us know how you get on and if you can make pyads work, feel free to make a PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants