Instrumental icon indicating copy to clipboard operation
Instrumental copied to clipboard

Support for NIDAQBase?

Open jondoesntgit opened this issue 5 years ago • 25 comments

Hey Nate,

So I just got GPIB and NI-DAQmx-Base working on a CentOS 7 install in the Digonnet lab. I'm super excited to be able to run my experiment on a linux machine, and exercise all of the power of the Linux.

The problem is that NI doesn't play nice with driver support for Linux, and so they only support our NI 9215 USB BNC DAQ via NI-DAQmx-Base, not NI-DAQmx. This means that I can't control the device via the nidaqmx Python package, but I can control it using the PyDAQmx package.

If I wanted to create some sort of support for this instance in instrumental, should I make another class, perhaps NIDAQbase that wraps around NIDAQmx-Base-supported devices.

jondoesntgit avatar Mar 28 '19 03:03 jondoesntgit

I believe I know someone who was using daqs.ni with NI-DAQmx Base on a Mac a few years ago, so that suggests we may be able to get away without creating a separate class. What are the main issues you're seeing when using Base?

natezb avatar Mar 28 '19 03:03 natezb

>>> from instrumental.drivers.daq.ni import DAQ
Traceback (most recent call last):
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/__init__.py", line 67, in load_lib
    lib_module = import_module(lib_name, pkg)
  File "/home/daq/anaconda3/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 965, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'instrumental.drivers.daq._nilib'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 104, in <module>
    class NiceNI(NiceLib):
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 105, in NiceNI
    _info_ = load_lib('ni', __package__)
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/__init__.py", line 73, in load_lib
    build_module.build(**kwargs)
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/_build_ni.py", line 27, in build
    build_lib(header_info, lib_names, '_nilib', __file__)
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/build.py", line 113, in build_lib
    lib_path = handle_lib_name(lib_name, filedir)
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/util.py", line 156, in handle_lib_name
    raise ValueError("Cannot find library '{}'".format(lib_names))
ValueError: Cannot find library '('nidaqmx',)'

jondoesntgit avatar Mar 28 '19 04:03 jondoesntgit

Try changing the lib name for linux* in _build_ni.py to the name of the Base lib on your machine, perhaps nidaqmxbase.

Is NI-DAQmx available on Linux at all, or just Base? If it's only base, then _build_ni.py should probably be fixed for everyone.

natezb avatar Mar 28 '19 04:03 natezb

You may also have to update header_info if that's pointing to the wrong thing.

natezb avatar Mar 28 '19 04:03 natezb

;tldr

NI-DAQmx is available on Linux, but it only supports a small subset of NI DAQs. (see http://www.ni.com/product-documentation/6913/en/, http://www.ni.com/download/ni-linux-device-drivers-2018/7664/en/).

The problem is that a lot of the kernel specs for linux are GPL, and NI doesn't develop drivers for these GPL kernel specs, leaving the Linux community relying on older, more limited versions of NI drivers software. The kernel specs weren't always GPL, but as GPL was added more more things, NI supported fewer of them.

jondoesntgit avatar Mar 28 '19 04:03 jondoesntgit

So this is what I've got:

$ ls /usr/local/natinst/nidaqmxbase/include/
NIDAQmxBase.h

And I changed the _build_ni.py to

$ cat _build_ni.py
# -*- coding: utf-8 -*-
# Copyright 2016 Nate Bogdanowicz
from nicelib import build_lib

header_info = {
    'win*': {
        'path': (
            r"{PROGRAMFILES(X86)}\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include",
            r"{PROGRAMFILES}\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include",
        ),
        'header': 'NIDAQmx.h'
    },
    'linux*': {
        'path': '/usr/local/natinst/nidaqmxbase/include',
        'header': 'NIDAQmxBase.h'
    },
    'darwin*': {
        'path': '/Applications/National Instruments/NI-DAQmx Base/includes',
        'header': 'NIDAQmxBase.h'
    },
}

lib_names = {'win*': 'nicaiu', 'linux*': 'nidaqmxbase', 'darwin*': 'nidaqmxbase'}


def build():
    build_lib(header_info, lib_names, '_nilib', __file__)


if __name__ == '__main__':
    build()

After your changes:

>>> from instrumental.drivers.daq.ni import DAQ
/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/process.py:1451: UserWarning: Unsupported c_ast type <class 'pycparser.c_ast.EmptyStatement'>
  warnings.warn(str(e))
/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/process.py:1454: UserWarning: Un-pythonable macro NICatHelper
  warnings.warn("Un-pythonable macro {}".format(macro.name))
/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/process.py:1455: UserWarning: <generator>:2:34: before: #
  warnings.warn(str(e))
/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/process.py:1451: UserWarning: Unsupported constant type 'double'
  warnings.warn(str(e))
Traceback (most recent call last):
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1076, in __getattr__
    return getattr(cls._ffilib, name)
AttributeError: cffi library 'libnidaqmxbase.so.15' has no function, constant or global variable named 'CreateTask'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 104, in <module>
    class NiceNI(NiceLib):
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 942, in __init__
    cls._create_niceobject_classes()
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1043, in _create_niceobject_classes
    niceclass._patch(cls)
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 596, in _patch
    init = getattr(parent_lib, init)
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1078, in __getattr__
    raise AttributeError("{} has no attribute named '{}'".format(cls.__name__, name))
AttributeError: NiceNI has no attribute named 'CreateTask'

jondoesntgit avatar Mar 28 '19 04:03 jondoesntgit

You might enable logging with log_to_screen() to get more detailed info about what's going on. My first guess is that you might have to add a prefix to the list, though DAQmxBase_ is already included.

This is a pretty tricky situation. The simplest thing might be to switch wholesale to Base for linux. A more involved solution might include creating separate low-level libs for DAQmx and DAQmx Base, and switching which one gets used. There's still the problem that the "ideal" behavior would use the DAQmx library if supported, and fall back to Base. This is an issue because the NiceNI class can only point to one library. So, there may need to be separate NiceNI classes if the support needs to be this good. That may also require some changes to nicelib, to add better support for inheriting from a common base, if we're to avoid duplicating all of the Sig definitions.

natezb avatar Mar 28 '19 04:03 natezb

I set up log_to_screen

>>> import logging
>>> from instrumental.log import log_to_screen
>>> log_to_screen(level=logging.DEBUG)
<RootLogger root (DEBUG)>

And here's the log dump (because it's long, I put it in a gist)

https://gist.github.com/jondoesntgit/8a9cb4584893471b96123cf19ce39395

jondoesntgit avatar Mar 28 '19 13:03 jondoesntgit

Here is a working script that dumps an array 1000 samples onto the screen from the 9215 USB (BNC):

#!/usr/bin/env python

from PyDAQmx import *
import numpy
from ctypes import byref

# Declaration of variable passed by reference
taskHandle = TaskHandle()
read = int32()
data = numpy.zeros((1000,), dtype=numpy.float64)

try:
    # DAQmx Configure Code
    DAQmxCreateTask("",byref(taskHandle))
    DAQmxCreateAIVoltageChan(taskHandle,b"Dev1/ai0","",DAQmx_Val_Cfg_Default,-10.0,10.0,DAQmx_Val_Volts,None)
    DAQmxCfgSampClkTiming(taskHandle,"",10000.0,DAQmx_Val_Rising,DAQmx_Val_FiniteSamps,1000)

    # DAQmx Start Code
    DAQmxStartTask(taskHandle)

    # DAQmx Read Code
    DAQmxReadAnalogF64(taskHandle,1000,10.0,DAQmx_Val_GroupByChannel,data,1000, read,None)

    print("Acquired %d points"%read.value)
except DAQError as err:
    print("DAQmx Error: %s"%err)
finally:
    if taskHandle:
        # DAQmx Stop Code
        DAQmxStopTask(taskHandle)
        DAQmxClearTask(taskHandle)

print(data)

If it's helpful, I can give you SSH access to the computer in the lab, and we can debug together.

jondoesntgit avatar Mar 28 '19 13:03 jondoesntgit

Here is where NIDAQmxBase.h provides CreateTask:

https://gist.github.com/jondoesntgit/154d6ffbc71f0987f31b06ca3aad8005#file-nidaqmxbase-h-L1177-L1189

jondoesntgit avatar Mar 28 '19 14:03 jondoesntgit

I made some progress. I added this to instrumental/drivers/daq/ni.py:

$ git diff

diff --git a/instrumental/drivers/daq/ni.py b/instrumental/drivers/daq/ni.py
index a708ebb..66ab1e4 100644
--- a/instrumental/drivers/daq/ni.py
+++ b/instrumental/drivers/daq/ni.py
@@ -103,7 +103,7 @@ def ret_errcheck(code):

 class NiceNI(NiceLib):
     _info_ = load_lib('ni', __package__)
-    _prefix_ = ('DAQmxBase_', 'DAQmx_', 'DAQmx')
+    _prefix_ = ('DAQmxBase_', 'DAQmxBase', 'DAQmx_', 'DAQmx')
     _buflen_ = 1024
     _use_numpy_ = True
     _ret_ = ret_errcheck

Now, I'm getting this error

>>> from instrumental.drivers.daq.ni import DAQ
/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py:1059: UserWarning: Conflicting name assert_line___LINE__, ignoring
  warnings.warn("Conflicting name {}, ignoring".format(shortname))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 372, in <module>
    ProductCategory.MSeriesDAQ:
  File "/home/daq/anaconda3/lib/python3.7/enum.py", line 348, in __getattr__
    raise AttributeError(name) from None
AttributeError: MSeriesDAQ

jondoesntgit avatar Mar 28 '19 14:03 jondoesntgit

The problem is that this block always goes to the except statement:

https://github.com/mabuchilab/Instrumental/blob/82a23b841f15491d3e9b3746d4baeb176b78d40a/instrumental/drivers/daq/ni.py#L289-L293

Which ends up removing all of the devices from ProductCategory

I found that replacing the try block with pass suppressed the error, but I'm not sure if that is what's causing my new problems:

$ lsdaq
--------------------------------
Detecting National Instruments DAQ Devices
Found the following DAQ Devices:
NI USB-9215A with BNC:"Dev1"    (USB0::0x3923::0x71DB::00E52E4C::RAW)
--------------------------------
>>> from instrumental import list_instruments
>>> list_instruments()
[]
>>> from instrumental.drivers.daq.ni import NIDAQ
>>> NIDAQ(name='Dev1')
Traceback (most recent call last):
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1076, in __getattr__
    return getattr(cls._ffilib, name)
AttributeError: cffi library 'libnidaqmxbase.so.15' has no function, constant or global variable named 'GetSysDevNames'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 524, in __new__
    return instrument(inst, **kwds)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1275, in instrument
    inst = find_nonvisa_instrument(params)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1166, in find_nonvisa_instrument
    full_params = find_full_params(normalized_params, driver_module)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1245, in find_full_params
    for inst_params in driver_module.list_instruments():
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 71, in list_instruments
    dev_names = split_list(NiceNI.GetSysDevNames())
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1078, in __getattr__
    raise AttributeError("{} has no attribute named '{}'".format(cls.__name__, name))
AttributeError: NiceNI has no attribute named 'GetSysDevNames'
>>> NIDAQ(model='USB-9215A')
Traceback (most recent call last):
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1076, in __getattr__
    return getattr(cls._ffilib, name)
AttributeError: cffi library 'libnidaqmxbase.so.15' has no function, constant or global variable named 'GetSysDevNames'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 524, in __new__
    return instrument(inst, **kwds)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1275, in instrument
    inst = find_nonvisa_instrument(params)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1166, in find_nonvisa_instrument
    full_params = find_full_params(normalized_params, driver_module)
  File "/home/daq/src/Instrumental/instrumental/drivers/__init__.py", line 1245, in find_full_params
    for inst_params in driver_module.list_instruments():
  File "/home/daq/src/Instrumental/instrumental/drivers/daq/ni.py", line 71, in list_instruments
    dev_names = split_list(NiceNI.GetSysDevNames())
  File "/home/daq/anaconda3/lib/python3.7/site-packages/nicelib/nicelib.py", line 1078, in __getattr__
    raise AttributeError("{} has no attribute named '{}'".format(cls.__name__, name))
AttributeError: NiceNI has no attribute named 'GetSysDevNames'
>>>

jondoesntgit avatar Mar 28 '19 14:03 jondoesntgit

Yeah, it appears that nidaqmxbase.h does not have the ProductCategory constants. We can patch over that by using strings instead of enums as the key to the _internal_channels dict, so that's not a serious issue.

It would be helpful to see an up-to-date log output that shows this most recent error.

natezb avatar Mar 28 '19 17:03 natezb

Besides what I already posted, what would be the most helpful?

jondoesntgit avatar Mar 28 '19 18:03 jondoesntgit

I can get past the current error caused by GetSysDevNames not being defined by hardcoding dev_names = ['Dev1'], and then commenting out mode=NiceNI.Device.GetDevProductType(dev_name)... but then there are a lot of library calls that go on when the instrument is initialized, and it doesn't seem the the Device object supports any of them. I could probably hard-code a bunch of patches in to make it work, but I think for now, my time will be best spent by using PyDAQmx.

jondoesntgit avatar Mar 28 '19 18:03 jondoesntgit

Nate, it appears that NI doesn't fully support all of NIDAQBase on Linux machines. This renders nidaq almost useless on the machine that I'm using.

I've been using the workaround of a custom script that utilizes PyDAQmx in the mean time. Is there any value to writing an alternate class that duck-types the same as the DAQ classes that you've written in this repo? The class would be loaded as a fallback if nidaq causes errors.

jondoesntgit avatar Mar 10 '20 18:03 jondoesntgit

If that Linux subset is quite restricted, I do think it could be valuable to make a Linux variant of the class as you suggest.

However, anything that's implementable with PyDAQmx should be implementable with NiceLib, since they're just wrapping the same lib (either NI-DAQmx or NI-DAQmx Base). Given that, I don't see the value in mixing the two approaches. Is there something in particular you can get working with PyDAQmx that you can't with NiceLib? Or is it just that Instrumental's NiceNI class as-implemented has too many unsupported functions in it?

natezb avatar Mar 14 '20 21:03 natezb

In order to answer that question, I'd have to understand the NiceNI class a little better. I know that I ran into a lot of difficulties trying to run the Instrumental implementation out of the box on my linux distro. I may try a ground-up approach where I try to get some low-level functionality working, and build up from there.

jondoesntgit avatar Mar 19 '20 22:03 jondoesntgit

I had a chance to spend a few hours wading through NiceNI, nicelib, and comparing NIDAQmxBase to NIDAQmx. Here's what I found:

NIDAQmxBase supports a very limited subset of NIDAQmx. Specifically, none of the NiceNI Device subclass attributes are supported (starting with GetDevIsSimulated and onwards). A smattering of other attributes need to be removed as well (see the commented-out lines)

class NiceNI(NiceLib):
    _info_ = load_lib('ni', __package__)
    _prefix_ = ('DAQmxBase_', 'DAQmxBase', 'DAQmx_', 'DAQmx')
    _buflen_ = 1024
    _use_numpy_ = True
    _ret_ = ret_errcheck

    # GetErrorString = Sig('in', 'buf', 'len')
    # GetSysDevNames = Sig('buf', 'len')
    GetExtendedErrorInfo = Sig('buf', 'len=2048')
    CreateTask = Sig('in', 'out')

    class Task(NiceObject):
        """A Nice-wrapped NI Task"""
        _init_ = 'CreateTask'

        StartTask = Sig('in')
        StopTask = Sig('in')
        ClearTask = Sig('in')

        # WaitUntilTaskDone = Sig('in', 'in')
        IsTaskDone = Sig('in', 'out')
        # TaskControl = Sig('in', 'in')
        CreateAIVoltageChan = Sig('in', 'in', 'in', 'in', 'in', 'in', 'in', 'in')
        CreateAOVoltageChan = Sig('in', 'in', 'in', 'in', 'in', 'in', 'in')
        CreateDIChan = Sig('in', 'in', 'in', 'in')
        CreateDOChan = Sig('in', 'in', 'in', 'in')
        ReadAnalogF64 = Sig('in', 'in', 'in', 'in', 'arr', 'len=in', 'out', 'ignore')
        # ReadAnalogScalarF64 = Sig('in', 'in', 'out', 'ignore')
        ReadDigitalScalarU32 = Sig('in', 'in', 'out', 'ignore')
        ReadDigitalU32 = Sig('in', 'in', 'in', 'in', 'arr', 'len=in', 'out', 'ignore')
        # ReadDigitalLines = Sig('in', 'in', 'in', 'in', 'arr', 'len=in', 'out', 'out', 'ignore')
        WriteAnalogF64 = Sig('in', 'in', 'in', 'in', 'in', 'in', 'out', 'ignore')
        # WriteAnalogScalarF64 = Sig('in', 'in', 'in', 'in', 'ignore')
        WriteDigitalU32 = Sig('in', 'in', 'in', 'in', 'in', 'in', 'out', 'ignore')
        WriteDigitalScalarU32 = Sig('in', 'in', 'in', 'in', 'ignore')
        CfgSampClkTiming = Sig('in', 'in', 'in', 'in', 'in', 'in')
        CfgImplicitTiming = Sig('in', 'in', 'in')
        # CfgOutputBuffer = Sig('in', 'in')
        CfgAnlgEdgeStartTrig = Sig('in', 'in', 'in', 'in')
        CfgDigEdgeStartTrig = Sig('in', 'in', 'in')
        CfgDigEdgeRefTrig = Sig('in', 'in', 'in', 'in')
        # GetAOUseOnlyOnBrdMem = Sig('in', 'in', 'out')
        # SetAOUseOnlyOnBrdMem = Sig('in', 'in', 'in')
        # GetBufInputOnbrdBufSize = Sig('in', 'out')

        _sigs_ = sig_pattern((
            ('Get{}', Sig('in', 'out')),
            ('Set{}', Sig('in', 'in')),
        ),(
            # 'SampTimingType',
            # 'SampQuantSampMode',
            # 'ReadOffset',
            # 'ReadRelativeTo',
            # 'ReadOverWrite',
            # 'SampQuantSampPerChan',
            # 'BufInputBufSize',
            # 'BufOutputBufSize',
            # 'BufOutputOnbrdBufSize',
        ))

None of these commented-out commands are accessible using PyDAQmx. The program will run if they're not commented out, but will generate many warnings in the logger.

I can now import DAQ from ni, but without any surviving class attributes for a Device, Device merely becomes a relatively trivial class that only holds onto the device name. NIDAQmxBase also does not have the ability to list attached devices. On the other hand, I have access to a binary executable at /etc/natinst/nidaqmxbase/bin/daqmxbase_listdevices that outputs

NI USB-9215A with BNC:"Dev1"    (USB0::0x3923::0x71DB::00E52E4C::RAW)

I may be able to use this in place of the list-devices command.

One thing that concerns me is that I get a segmentation fault whenever I close the program. As I said earlier, I can now import DAQ from ni, but even if I don't do anything further, I get a segmentation fault upon merely from importing DAQ. Do you know of anything in nicelib that would cause this behavior? I don't get segmentation faults when I load PyDAQmx.

jondoesntgit avatar Apr 02 '20 06:04 jondoesntgit

Regarding the Seg Fault, the issue seems to be with the cffi package. Whenever the cffi package tries to open libnidaqmxbase.so.15, when the program eventually finishes, there's a Segmentation Fault. In PyDAQmx, they load libnidaqmxbase.so.15 with ctypes, not with cffi, which does not produce an error.

I've documented the behavior in the cffi bug tracker: https://foss.heptapod.net/pypy/cffi/issues/448

jondoesntgit avatar Apr 07 '20 22:04 jondoesntgit

Interesting re the segfault, thanks for investigating. This isn't something I recall coming across before.

natezb avatar Apr 08 '20 00:04 natezb

NIDAQmxBase doesn't support ReadAnalogScalarF64 (which reads exactly one value from a channel). I've tried to duck-type it by setting up a task to read two values very quickly using ReadAnalogF64 (which reads multiple values, provided that the clock is set up). I've been trying to mirror the configuration and read steps that work when I use PyDAQmx, but no matter what happens, it says that I read zero samples and returns an array of zeros.

There don't seem to be any functions in the NIDAQmxBase instruction set that tell me that I'm getting any heartbeat from the device (unless you're actually getting full data from it). The only one that gets somewhat close is IsTaskClose.

I'm wondering if whatever is causing the segfault is messing up ReadAnalogF64.

jondoesntgit avatar Apr 08 '20 16:04 jondoesntgit

In my case, IsTaskDone() always returns 0. Here is a function that I'm using to try to duck_type the Minitask's read_AI_scalar function...

         n_samples = 20
         buf_size = n_samples
         fsamp = '1000.0 Hz'
         timeout_s = 2
         self.config_timing(fsamp, n_samples)
         print('DEBUG: IS TASK DONE:', self._mx_task.IsTaskDone())
         print('DEBUG:Started task')
         self.start()
         print('DEBUG: IS TASK DONE:', self._mx_task.IsTaskDone())
         data, n_samples_read = self._mx_task.ReadAnalogF64(n_samples, timeout_s, Val.GroupByChannel, b     uf_size)
         print(data, n_samples_read)
         print('DEBUG: IS TASK DONE:', self._mx_task.IsTaskDone())
         self.stop()
         print('DEBUG: IS TASK DONE:', self._mx_task.IsTaskDone())
        value = np.mean(data)

I always read 0 samples, get an array of zeros, and all of the IsTaskDone() queries return zero.

I wonder if there is some problem opening up the library.

jondoesntgit avatar Apr 08 '20 16:04 jondoesntgit

I tried different versions of cffi, but that didn't fix the problem. I installed yum install libffi-devel but still got segfaults.

I can confirm that cffi works on my system for a simple program (using this tutorial)

//cmult.c
float cmult(int int_param, float float_param) {
    float return_value = int_param * float_param;
    return return_value;
}
//cmult.h
float cmult(int int_param, float float_param);
# task.py
import cffi
import pathlib

print('building CFFI module')
ffi = cffi.FFI()

this_dir = pathlib.Path().absolute()
h_file_name = this_dir / 'cmult.h'
with open(h_file_name) as h_file:
    ffi.cdef(h_file.read())
ffi.set_source('cffi_example',
     '#include "cmult.h"',
    libraries=['cmult'],library_dirs=[this_dir.as_posix()],
     extra_link_args=['-Wl,-rpath,.'],
    )
ffi.compile()
# cffi_test.py

import cffi_example
if __name__ == '__main__':
    x, y = 6, 2.3
    answer = cffi_example.lib.cmult(x, y)
    print('%.1f' % answer)
$ gcc -shared -o libcmult.so -fPIC cmult.c
$ python task.py
$ python cffi_test.py
13.8

jondoesntgit avatar Apr 08 '20 21:04 jondoesntgit

Another quirk to deal with:

In AnalogReadF64, NIDAQmxBase always errors out if the timeout is 0 or -1. If the number of requested samples is set to -1 (or any other negative number), it always clears the device buffer, and writes two samples to the array by reference, and says that two samples were read.

I'm doing some benchmarking, and a Python call on my lab computer indicates that about 96% of the time, AnalogReadF64 executes in 5 ms or less when enough samples are in the buffer. The execution time seems to be independent of the number of samples read (whether its 1 sample, 10 samples, or 100 samples).

Execution time (ms) % timeout
0 100.00
1 37.96
2 21.20
3 7.56
4 1.69
5 0.06
6 0.02
7 0.02
8 0.01
9 0.01

My idea for a workaround is to have the minitask to guess how many samples are available, and try to read it in chunks, with extremely short timeouts for smaller chunks.

For example, if the minitask thinks that there are 200 samples available,

  1. Read 180 samples with a 100 ms timeout
  2. Read 18 samples with 10 ms timeouts until the DAQ returns a timeout error
  3. Read 2 samples with a 10 ms timeout until DAQ returns a timeout error
  4. Read 1 sample with 1 ms timeout until DAQ returns a timeout error

This todo-list would change based on the sample rate, the timeout that the user passes to the algorithm, and the number of expected data points to read.

That's my best idea to replace the behavior of trying to duck-type the functions to read all available samples in the buffer.

jondoesntgit avatar Apr 17 '20 20:04 jondoesntgit