Sending write command without variable value (length 0)
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
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.
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. 🚀
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,
)
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.
Let us know how you get on and if you can make pyads work, feel free to make a PR