diff --git a/.gitignore b/.gitignore index 2295d6c..ce0256c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.a *.pyc lasp_rtaudio.cxx +lasp_uldaq.cxx dist CMakeFiles CMakeCache.txt diff --git a/lasp/device/CMakeLists.txt b/lasp/device/CMakeLists.txt index 17d6bd6..25920ce 100644 --- a/lasp/device/CMakeLists.txt +++ b/lasp/device/CMakeLists.txt @@ -1,11 +1,14 @@ include_directories(/usr/include/rtaudio) set_source_files_properties(lasp_rtaudio.pyx PROPERTIES CYTHON_IS_CXX TRUE) +set_source_files_properties(lasp_uldaq.pyx PROPERTIES CYTHON_IS_CXX TRUE) set_source_files_properties(lasp_rtaudio.cxx PROPERTIES COMPILE_FLAGS "${CMAKE_CXX_FLAGS} ${CYTHON_EXTRA_CXX_FLAGS}") cython_add_module(lasp_rtaudio lasp_rtaudio.pyx) +cython_add_module(lasp_uldaq lasp_uldaq.pyx) target_link_libraries(lasp_rtaudio pthread rtaudio) +target_link_libraries(lasp_uldaq uldaq) if(win32) target_link_libraries(lasp_rtaudio python37) endif(win32) diff --git a/lasp/device/__init__.py b/lasp/device/__init__.py index 9fa6d59..545f53f 100644 --- a/lasp/device/__init__.py +++ b/lasp/device/__init__.py @@ -1,3 +1,6 @@ #!/usr/bin/python3 -from .lasp_rtaudio import * from .lasp_daqconfig import * +from .lasp_avtype import * +from .lasp_rtaudio import * +from .lasp_uldaq import * + diff --git a/lasp/device/lasp_avtype.py b/lasp/device/lasp_avtype.py new file mode 100644 index 0000000..32d75d1 --- /dev/null +++ b/lasp/device/lasp_avtype.py @@ -0,0 +1,9 @@ + +__all__ = ['AvType'] + +class AvType: + """Specificying the type of data, for adding and removing callbacks from + the stream.""" + audio_input = 1 + audio_output = 2 + video = 4 diff --git a/lasp/device/lasp_common_decls.pxd b/lasp/device/lasp_common_decls.pxd new file mode 100644 index 0000000..a086ad4 --- /dev/null +++ b/lasp/device/lasp_common_decls.pxd @@ -0,0 +1,54 @@ +import sys +include "config.pxi" +cimport cython +from .lasp_daqconfig import DeviceInfo +from .lasp_avtype import AvType +from libcpp.string cimport string +from libcpp.vector cimport vector +from libc.stdlib cimport malloc, free +from libc.stdio cimport printf, fprintf, stderr +from libc.string cimport memcpy, memset +from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF + +__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string', + 'get_sampwidth_from_format_string'] + + +cdef extern from "lasp_cppthread.h" nogil: + cdef cppclass CPPThread[T,F]: + CPPThread(F threadfunction, T data) + void join() + + void CPPsleep(unsigned int ms) + +cdef extern from "lasp_cppqueue.h" nogil: + cdef cppclass SafeQueue[T]: + SafeQueue() + void enqueue(T t) + T dequeue() + size_t size() const + bool empty() const + + +cdef extern from "atomic" namespace "std" nogil: + cdef cppclass atomic[T]: + T load() + void store(T) + +cdef extern from "lasp_pyarray.h": + PyObject* data_to_ndarray(void* data, + int n_rows,int n_cols, + int typenum, + bool transfer_ownership, + bool F_contiguous) + + +cdef inline void copyChannel(void* to, void* from_, + unsigned bytesperchan, + unsigned toindex, + unsigned fromindex) nogil: + memcpy( &(( to)[bytesperchan*toindex]), + &(( from_)[bytesperchan*fromindex]), + bytesperchan) + + diff --git a/lasp/device/lasp_daqconfig.py b/lasp/device/lasp_daqconfig.py index 4a4c825..fbad2b0 100644 --- a/lasp/device/lasp_daqconfig.py +++ b/lasp/device/lasp_daqconfig.py @@ -14,11 +14,27 @@ from ..lasp_common import lasp_shelve, Qty, SIQtys from typing import List +class InputMode: + differential = 'Differential' + single_ended = 'Single-ended' + pseudo_differential = 'Pseudo-differential' + undefined = 'Undefined' + +class CouplingMode: + ac = 'AC' + dc = 'DC' + undefined = 'Undefined' + +class Range: + oneV = '+/- 1 V' + tenV = '+/- 10 V' + undefined = 'Undefined' + @dataclass class DeviceInfo: api: int index: int - probed: bool + probed: bool # name: str outputchannels: int inputchannels: int @@ -26,7 +42,7 @@ class DeviceInfo: samplerates: list sampleformats: list prefsamplerate: int - + hasInputIEPE: bool = False @dataclass_json @@ -36,6 +52,9 @@ class DAQChannel: channel_name: str sensitivity: float qty: Qty + range_: str = 'Undefined' + IEPE_enabled: bool = False + @dataclass_json diff --git a/lasp/device/lasp_rtaudio.pyx b/lasp/device/lasp_rtaudio.pyx index 2342b74..2c3e7fb 100644 --- a/lasp/device/lasp_rtaudio.pyx +++ b/lasp/device/lasp_rtaudio.pyx @@ -1,23 +1,8 @@ -import sys -include "config.pxi" -cimport cython -from .lasp_daqconfig import DeviceInfo -from libcpp.string cimport string -from libcpp.vector cimport vector -from libc.stdlib cimport malloc, free -from libc.stdio cimport printf, fprintf, stderr -from libc.string cimport memcpy, memset -from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF +include "lasp_common_decls.pxd" -__all__ = ['AvType', 'RtAudio', 'get_numpy_dtype_from_format_string', +__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string', 'get_sampwidth_from_format_string'] -class AvType: - """Specificying the type of data, for adding and removing callbacks from - the stream.""" - audio_input = 1 - audio_output = 2 - video = 4 cdef extern from "RtAudio.h" nogil: ctypedef unsigned long RtAudioStreamStatus @@ -141,33 +126,6 @@ cdef extern from "RtAudio.h" nogil: unsigned int getStreamSampleRate() void showWarnings(bool value) -cdef extern from "lasp_cppthread.h" nogil: - cdef cppclass CPPThread[T,F]: - CPPThread(F threadfunction, T data) - void join() - - void CPPsleep(unsigned int ms) - -cdef extern from "lasp_cppqueue.h" nogil: - cdef cppclass SafeQueue[T]: - SafeQueue() - void enqueue(T t) - T dequeue() - size_t size() const - bool empty() const - - -cdef extern from "atomic" namespace "std" nogil: - cdef cppclass atomic[T]: - T load() - void store(T) - -cdef extern from "lasp_pyarray.h": - PyObject* data_to_ndarray(void* data, - int n_rows,int n_cols, - int typenum, - bool transfer_ownership, - bool F_contiguous) _formats_strkey = { '8-bit integers': (RTAUDIO_SINT8, 1, np.int8), @@ -192,7 +150,6 @@ def get_sampwidth_from_format_string(format_string): return _formats_strkey[format_string][-2] - # It took me quite a long time to fully understand Cython's idiosyncrasies # concerning C(++) callbacks, the GIL and passing Python objects as pointers # into C(++) functions. But finally, here it is! @@ -205,13 +162,6 @@ def get_sampwidth_from_format_string(format_string): # """ # memcpy(buf, arr.data, arr.size*arr.itemsize) -cdef void copyChannel(void* to, void* from_, - unsigned bytesperchan, - unsigned toindex, - unsigned fromindex) nogil: - memcpy( &(( to)[bytesperchan*toindex]), - &(( from_)[bytesperchan*fromindex]), - bytesperchan) ctypedef struct _Stream: @@ -522,9 +472,6 @@ cdef class RtAudio: } return apidict - cpdef unsigned int getDeviceCount(self): - return self._rtaudio.getDeviceCount() - cpdef unsigned int getDefaultOutputDevice(self): return self._rtaudio.getDefaultOutputDevice() diff --git a/lasp/device/lasp_uldaq.pxd b/lasp/device/lasp_uldaq.pxd new file mode 100644 index 0000000..a109da1 --- /dev/null +++ b/lasp/device/lasp_uldaq.pxd @@ -0,0 +1,387 @@ +include "lasp_common_decls.pxd" + +cdef extern from "uldaq.h" nogil: + + ctypedef enum DaqDeviceInterface: + USB_IFC + BLUETOOTH_IFC + ETHERNET_IFC + ANY_IFC + + ctypedef struct DaqDeviceDescriptor: + char productName[64] + unsigned int productId + DaqDeviceInterface devInterface + char devString[64] + char uniqueId[64] + char reserved[512] + + ctypedef long long DaqDeviceHandle + + ctypedef struct TransferStatus: + unsigned long long currentScanCount + unsigned long long currentTotalCount + long long currentIndex + char reserved[64] + + ctypedef enum UlError: + ERR_NO_ERROR + ERR_UNHANDLED_EXCEPTION + ERR_BAD_DEV_HANDLE + ERR_BAD_DEV_TYPE + ERR_USB_DEV_NO_PERMISSION + ERR_USB_INTERFACE_CLAIMED + ERR_DEV_NOT_FOUND + ERR_DEV_NOT_CONNECTED + ERR_DEAD_DEV + ERR_BAD_BUFFER_SIZE + ERR_BAD_BUFFER + ERR_BAD_MEM_TYPE + ERR_BAD_MEM_REGION + ERR_BAD_RANGE + ERR_BAD_AI_CHAN + ERR_BAD_INPUT_MODE + ERR_ALREADY_ACTIVE + ERR_BAD_TRIG_TYPE + ERR_OVERRUN + ERR_UNDERRUN + ERR_TIMEDOUT + ERR_BAD_OPTION + ERR_BAD_RATE + ERR_BAD_BURSTIO_COUNT + ERR_CONFIG_NOT_SUPPORTED + ERR_BAD_CONFIG_VAL + ERR_BAD_AI_CHAN_TYPE + ERR_ADC_OVERRUN + ERR_BAD_TC_TYPE + ERR_BAD_UNIT + ERR_BAD_QUEUE_SIZE + ERR_BAD_CONFIG_ITEM + ERR_BAD_INFO_ITEM + ERR_BAD_FLAG + ERR_BAD_SAMPLE_COUNT + ERR_INTERNAL + ERR_BAD_COUPLING_MODE + ERR_BAD_SENSOR_SENSITIVITY + ERR_BAD_IEPE_MODE + ERR_BAD_AI_CHAN_QUEUE + ERR_BAD_AI_GAIN_QUEUE + ERR_BAD_AI_MODE_QUEUE + ERR_FPGA_FILE_NOT_FOUND + ERR_UNABLE_TO_READ_FPGA_FILE + ERR_NO_FPGA + ERR_BAD_ARG + ERR_MIN_SLOPE_VAL_REACHED + ERR_MAX_SLOPE_VAL_REACHED + ERR_MIN_OFFSET_VAL_REACHED + ERR_MAX_OFFSET_VAL_REACHED + ERR_BAD_PORT_TYPE + ERR_WRONG_DIG_CONFIG + ERR_BAD_BIT_NUM + ERR_BAD_PORT_VAL + ERR_BAD_RETRIG_COUNT + ERR_BAD_AO_CHAN + ERR_BAD_DA_VAL + ERR_BAD_TMR + ERR_BAD_FREQUENCY + ERR_BAD_DUTY_CYCLE + ERR_BAD_INITIAL_DELAY + ERR_BAD_CTR + ERR_BAD_CTR_VAL + ERR_BAD_DAQI_CHAN_TYPE + ERR_BAD_NUM_CHANS + ERR_BAD_CTR_REG + ERR_BAD_CTR_MEASURE_TYPE + ERR_BAD_CTR_MEASURE_MODE + ERR_BAD_DEBOUNCE_TIME + ERR_BAD_DEBOUNCE_MODE + ERR_BAD_EDGE_DETECTION + ERR_BAD_TICK_SIZE + ERR_BAD_DAQO_CHAN_TYPE + ERR_NO_CONNECTION_ESTABLISHED + ERR_BAD_EVENT_TYPE + ERR_EVENT_ALREADY_ENABLED + ERR_BAD_EVENT_PARAMETER + ERR_BAD_CALLBACK_FUCNTION + ERR_BAD_MEM_ADDRESS + ERR_MEM_ACCESS_DENIED + ERR_DEV_UNAVAILABLE + ERR_BAD_RETRIG_TRIG_TYPE + ERR_BAD_DEV_VER + ERR_BAD_DIG_OPERATION + ERR_BAD_PORT_INDEX + ERR_OPEN_CONNECTION + ERR_DEV_NOT_READY + ERR_PACER_OVERRUN + ERR_BAD_TRIG_CHANNEL + ERR_BAD_TRIG_LEVEL + ERR_BAD_CHAN_ORDER + ERR_TEMP_OUT_OF_RANGE + ERR_TRIG_THRESHOLD_OUT_OF_RANGE + ERR_INCOMPATIBLE_FIRMWARE + ERR_BAD_NET_IFC + ERR_BAD_NET_HOST + ERR_BAD_NET_PORT + ERR_NET_IFC_UNAVAILABLE + ERR_NET_CONNECTION_FAILED + ERR_BAD_CONNECTION_CODE + ERR_CONNECTION_CODE_IGNORED + ERR_NET_DEV_IN_USE + ERR_BAD_NET_FRAME + ERR_NET_TIMEOUT + ERR_DATA_SOCKET_CONNECTION_FAILED + ERR_PORT_USED_FOR_ALARM + ERR_BIT_USED_FOR_ALARM + ERR_CMR_EXCEEDED + ERR_NET_BUFFER_OVERRUN + ERR_BAD_NET_BUFFER + + ctypedef enum AiInputMode: + AI_DIFFERENTIAL = 1, + AI_SINGLE_ENDED = 2, + AI_PSEUDO_DIFFERENTIAL = 3 + + ctypedef enum Range: + BIP10VOLTS + BIP5VOLTS + BIP4VOLTS + BIP2PT5VOLTS + BIP2VOLTS + BIP1PT25VOLTS + BIP1VOLTS + BIPPT625VOLTS + BIPPT5VOLTS + BIPPT25VOLTS + BIPPT125VOLTS + BIPPT2VOLTS + BIPPT1VOLTS + BIPPT078VOLTS + BIPPT05VOLTS + BIPPT01VOLTS + BIPPT005VOLTS + BIP3VOLTS + BIPPT312VOLTS + BIPPT156VOLTS + UNI15VOLTS + UNI20VOLTS + UNI10VOLTS + UNI5VOLTS + UNI4VOLTS + UNI2PT5VOLTS + UNI2VOLTS + UNI1PT25VOLTS + UNI1VOLTS + UNIPT625VOLTS + UNIPT5VOLTS + UNIPT25VOLTS + UNIPT125VOLTS + UNIPT2VOLTS + UNIPT1VOLTS + UNIPT078VOLTS + UNIPT05VOLTS + UNIPT01VOLTS + UNIPT005VOLTS + MA0TO20 + + ctypedef enum AdcTimingMode: + ADC_TM_AUTO + ADC_TM_HIGH_RES + ADC_TM_HIGH_SPEED + + ctypedef enum IepeMode: + IEPE_ENABLED + IEPE_DISABLED + + ctypedef enum CouplingMode: + CM_DC + CM_AC + + ctypedef enum TriggerType: + TRIG_NONE + TRIG_POS_EDGE + TRIG_NEG_EDGE + TRIG_HIGH + TRIG_LOW + GATE_HIGH + GATE_LOW + TRIG_RISING + TRIG_FALLING + TRIG_ABOVE + TRIG_BELOW + GATE_ABOVE + GATE_BELOW + GATE_IN_WINDOW + GATE_OUT_WINDOW + TRIG_PATTERN_EQ + TRIG_PATTERN_NE + TRIG_PATTERN_ABOVE + TRIG_PATTERN_BELOW + + ctypedef enum ScanStatus: + SS_IDLE + SS_RUNNING + + ctypedef enum ScanOption: + SO_DEFAULTIO + SO_SINGLEIO + SO_BLOCKIO + SO_BURSTIO + SO_CONTINUOUS + SO_EXTCLOCK + SO_EXTTRIGGER + SO_RETRIGGER + SO_BURSTMODE + SO_PACEROUT + SO_EXTTIMEBASE + SO_TIMEBASEOUT + + ctypedef enum DaqInScanFlag: + DAQINSCAN_FF_DEFAULT + DAQINSCAN_FF_NOSCALEDATA + DAQINSCAN_FF_NOCALIBRATEDATA + DAQINSCAN_FF_NOCLEAR + + ctypedef enum DaqOutScanFlag: + DAQOUTSCAN_FF_DEFAULT + DAQOUTSCAN_FF_NOSCALEDATA + DAQOUTSCAN_FF_NOCALIBRATEDATA + + ctypedef enum DaqInChanType: + DAQI_ANALOG_DIFF + DAQI_ANALOG_SE + DAQI_DIGITAL + DAQI_CTR16 + DAQI_CTR32 + DAQI_CTR48 + DAQI_DAC + + ctypedef struct DaqInChanDescriptor: + int channel + DaqInChanType type + Range range + + ctypedef enum DaqOutChanType: + DAQO_ANALOG + DAQO_DIGITAL + + ctypedef struct DaqOutChanDescriptor: + int channel + DaqOutChanType type + Range range + + ctypedef enum DaqEventType: + DE_NONE + DE_ON_DATA_AVAILABLE + DE_ON_INPUT_SCAN_ERROR + DE_ON_END_OF_INPUT_SCAN + DE_ON_OUTPUT_SCAN_ERROR + DE_ON_END_OF_OUTPUT_SCAN + + ctypedef enum WaitType: + WAIT_UNTIL_DONE + + ctypedef enum DevInfoItem: + DEV_INFO_HAS_AI_DEV + DEV_INFO_HAS_AO_DEV + DEV_INFO_HAS_DIO_DEV + DEV_INFO_HAS_CTR_DEV + DEV_INFO_HAS_TMR_DEV + DEV_INFO_HAS_DAQI_DEV + DEV_INFO_HAS_DAQO_DEV + DEV_INFO_DAQ_EVENT_TYPES + DEV_INFO_MEM_REGIONS + + ctypedef enum AiInfoItem: + AI_INFO_RESOLUTION + AI_INFO_NUM_CHANS + AI_INFO_NUM_CHANS_BY_MODE + AI_INFO_NUM_CHANS_BY_TYPE + AI_INFO_CHAN_TYPES + AI_INFO_SCAN_OPTIONS + AI_INFO_HAS_PACER + AI_INFO_NUM_DIFF_RANGES + AI_INFO_NUM_SE_RANGES + AI_INFO_DIFF_RANGE + AI_INFO_SE_RANGE + AI_INFO_TRIG_TYPES + AI_INFO_MAX_QUEUE_LENGTH_BY_MODE + AI_INFO_QUEUE_TYPES + AI_INFO_QUEUE_LIMITS + AI_INFO_FIFO_SIZE + AI_INFO_IEPE_SUPPORTED + + ctypedef enum AiConfigItem: + AI_CFG_CHAN_TYPE + AI_CFG_CHAN_TC_TYPE + AI_CFG_SCAN_CHAN_TEMP_UNIT + AI_CFG_SCAN_TEMP_UNIT + AI_CFG_ADC_TIMING_MODE + AI_CFG_AUTO_ZERO_MODE + AI_CFG_CAL_DATE + AI_CFG_CHAN_IEPE_MODE + AI_CFG_CHAN_COUPLING_MODE + AI_CFG_CHAN_SENSOR_CONNECTION_TYPE + AI_CFG_CHAN_OTD_MODE + AI_CFG_OTD_MODE + AI_CFG_CAL_TABLE_TYPE + AI_CFG_REJECT_FREQ_TYPE + AI_CFG_EXP_CAL_DATE + + ctypedef enum AoConfigItem: + AO_CFG_SYNC_MODE + AO_CFG_CHAN_SENSE_MODE + + ctypedef enum AiConfigItemDbl: + AI_CFG_CHAN_SLOPE + AI_CFG_CHAN_OFFSET + AI_CFG_CHAN_SENSOR_SENSITIVITY + AI_CFG_CHAN_DATA_RATE + + ctypedef enum AInScanFlag: + AINSCAN_FF_DEFAULT + AINSCAN_FF_NOSCALEDATA + AINSCAN_FF_NOCALIBRATEDATA + + UlError ulGetDaqDeviceInventory(DaqDeviceInterface interfaceTypes, DaqDeviceDescriptor daqDevDescriptors[], unsigned int* numDescriptors ) + + DaqDeviceHandle ulCreateDaqDevice(DaqDeviceDescriptor daqDevDescriptor) + UlError ulConnectDaqDevice(DaqDeviceHandle daqDeviceHandle); + + UlError ulDisconnectDaqDevice(DaqDeviceHandle daqDeviceHandle); + UlError ulReleaseDaqDevice(DaqDeviceHandle daqDeviceHandle); + + UlError ulAISetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, unsigned int index, long long configValue) + UlError ulAISetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl configItem, unsigned int index, double configValue) + UlError ulAIGetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, unsigned int index, long long* configValue); + + UlError ulDaqInScan(DaqDeviceHandle daqDeviceHandle, DaqInChanDescriptor chanDescriptors[], int numChans, int samplesPerChan, double* rate, ScanOption options, DaqInScanFlag flags, double data[]); + UlError ulDaqInScanStatus(DaqDeviceHandle daqDeviceHandle, ScanStatus* status, TransferStatus* xferStatus); + UlError ulDaqInScanStop(DaqDeviceHandle daqDeviceHandle); + UlError ulDaqInScanWait(DaqDeviceHandle daqDeviceHandle, WaitType waitType, long long waitParam, double timeout); + + UlError ulDaqOutScan(DaqDeviceHandle daqDeviceHandle, DaqOutChanDescriptor chanDescriptors[], int numChans, int samplesPerChan, double* rate, ScanOption options, DaqOutScanFlag flags, double data[]); + UlError ulDaqOutScanStatus(DaqDeviceHandle daqDeviceHandle, ScanStatus* status, TransferStatus* xferStatus); + UlError ulDaqOutScanStop(DaqDeviceHandle daqDeviceHandle); + UlError ulDaqOutSetTrigger(DaqDeviceHandle daqDeviceHandle, TriggerType type, DaqInChanDescriptor trigChanDescriptor, double level, double variance, unsigned int retriggerSampleCount); + + + # UlError ulAIGetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl configItem, unsigned int index, double* configValue); + # UlError ulAIGetConfigStr(DaqDeviceHandle daqDeviceHandle, AiConfigItemStr configItem, unsigned int index, char* configStr, unsigned int* maxConfigLen); +# ctypedef enum AiIn +# { +# /** Returns the minimum scan rate in samples per second to the \p infoValue argument. Index is ignored. */ +# AI_INFO_MIN_SCAN_RATE = 1000, + +# /** Returns the maximum scan rate in samples per second to the \p infoValue argument. Index is ignored. */ +# AI_INFO_MAX_SCAN_RATE = 1001, + +# /** Returns the maximum throughput in samples per second to the \p infoValue argument. Index is ignored. */ +# AI_INFO_MAX_THROUGHPUT = 1002, + +# /** Returns the maximum scan rate in samples per second when using the ::SO_BURSTIO ScanOption to the \p infoValue argument. Index is ignored. */ +# AI_INFO_MAX_BURST_RATE = 1003, + +# /** Returns the maximum throughput in samples per second when using the ::SO_BURSTIO ScanOption to the \p infoValue argument. Index is ignored. */ +# AI_INFO_MAX_BURST_THROUGHPUT = 1004 +# }AiInfoItemDbl + diff --git a/lasp/device/lasp_uldaq.pyx b/lasp/device/lasp_uldaq.pyx new file mode 100644 index 0000000..04d6f47 --- /dev/null +++ b/lasp/device/lasp_uldaq.pyx @@ -0,0 +1,624 @@ +from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF +from .lasp_daqconfig import (DeviceInfo, InputMode, Range as pyRange, + DAQChannel) +from .lasp_avtype import AvType + +__all__ = ['UlDT9837A', 'UlDaq'] + +DEF MAX_DEF_COUNT = 100 + +cdef struct DaqThreadData: + + unsigned int samplesPerBlock + double samplerate + + DaqDeviceHandle handle + + SafeQueue[void*] *inputQueue + SafeQueue[void*] *outputQueue + + DaqInChanDescriptor* inChanDescriptors + unsigned ninputChanDescriptors + + DaqOutChanDescriptor* outChanDescriptors + unsigned noutputChanDescriptors + + atomic[bool] stopThread + CPPThread[void*, void (*)(void*)] *thread + +cdef void eventCallbackFunction(DaqDeviceHandle handle, DaqEventType eventType, + unsigned long long eventData, void* userData): + + pass + +cdef void ulThreadFunction(void* threaddata_void) nogil: + """ + Stream thread function + """ + cdef: + DaqThreadData* td = threaddata_void + TransferStatus instatus + TransferStatus outstatus + UlError err + ScanOption scanoptions + DaqInScanFlag inscanflags + DaqOutScanFlag outscanflags + # AScanFlag inscanflags + double* outbuffer, inbuffer + + # inbuffer = NULL + # outbuffer = NULL + + inscanflags = DAQINSCAN_FF_NOSCALEDATA + outscanflags = DAQOUTSCAN_FF_NOSCALEDATA + + scanoptions = SO_CONTINUOUS + + if td.noutputChanDescriptors > 0: + + # Enable input and outputs + err = ulDaqOutScan(td.handle, + td.outChanDescriptors, + td.noutputChanDescriptors, + td.samplesPerBlock, + &td.samplerate, + scanoptions, + outscanflags, + outbuffer) + if err: + fprintf(stderr, 'Error starting data output\n') + return + + + + if td.ninputChanDescriptors > 0: + # Enable input and outputs + # err = ulDaqOutScan(td.handle, + # td.outChanDescriptors, + # td.noutputChanDescriptors, + # td.samplesPerBlock, + # &td.samplerate, + # scanoptions, + # outscanflags, + # outbuffer) + # if err: + # fprintf(stderr, 'Error starting data output\n') + # return + pass + + +cdef class UlDT9837A: + cdef: + DaqDeviceHandle handle + bint handle_connected + bint stream_running + unsigned int ninputchannels + unsigned int noutputchannels + object input_range + object enabled_inputs + bint output_enabled + bint monitor_gen + DaqThreadData *threaddata + + def __cinit__(self, unsigned int deviceno): + self.handle = 0 + self.handle_connected = False + self.ninputchannels = 4 + self.noutputchannels = 1 + self.input_range = 4*[False] + self.enabled_inputs = 4*[False] + self.output_enabled = False + self.threaddata = NULL + self.monitor_gen = False + + cdef: + DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT] + DaqDeviceDescriptor descriptor + DaqDeviceInterface interfaceType = ANY_IFC + + UlError err + + unsigned int numdevs = MAX_DEF_COUNT + + err = ulGetDaqDeviceInventory(interfaceType, + devdescriptors, + &numdevs) + if(err != ERR_NO_ERROR): + raise RuntimeError(f'Device inventarization failed: {err}') + + if deviceno >= numdevs: + raise ValueError(f'Device number {deviceno} too high {err}. This could happen when the device is currently not connected') + + descriptor = devdescriptors[deviceno] + # get a handle to the DAQ device associated with the first descriptor + self.handle = ulCreateDaqDevice(descriptor); + + if self.handle == 0: + raise RuntimeError ("Unable to create a handle to the specified DAQ device. Is the device currently in use?"); + + err = ulConnectDaqDevice(self.handle) + if err != ERR_NO_ERROR: + raise RuntimeError(f'Unable to connect to device: {err}') + + + cdef void startScan(self, + unsigned int samplesPerBlock, + SafeQueue[void*] *inputQueue, + SafeQueue[void*] *outputQueue): + + cdef: + int i, j + # Sanity checks + if inputQueue and (not any(self.enabled_inputs) and (not self.monitor_gen)): + raise ValueError('Input queue given, but no input channels enabled and monitor output disabled') + if outputQueue and not self.output_enabled: + raise ValueError('Output queue given, but output channel is not enabled') + # End sanity checks + + cdef: + DaqThreadData *threaddata + threaddata = malloc(sizeof(threaddata)) + if not threaddata: + raise MemoryError() + + threaddata.samplesPerBlock = samplesPerBlock + threaddata.handle = self.handle + threaddata.inputQueue = inputQueue + threaddata.outputQueue = outputQueue + threaddata.inChanDescriptors = NULL + threaddata.outChanDescriptors = NULL + threaddata.ninputChanDescriptors = 0 + threaddata.noutputChanDescriptors = 0 + + neninputchannels = sum([1 if self.enabled_inputs == True else 0]) + + threaddata.inputQueue = inputQueue + threaddata.outputQueue = inputQueue + threaddata.thread = NULL + + j = 0 + if self.monitor_gen: + neninputchannels +=1 + j+=1 + + threaddata.inChanDescriptors = malloc(neninputchannels*sizeof(DaqInChanDescriptor)) + if not threaddata.inChanDescriptors: + self.cleanupThreadData(threaddata) + raise MemoryError() + + if self.monitor_gen: + threaddata.inChanDescriptors[0].chantype = DAQI_DAC + threaddata.inChanDescriptors[0].channel = 7 + threaddata.inChanDescriptors[0].range = BIP10VOLTS + + for i in range(neninputchannels): + if self.enabled_inputs[i]: + threaddata.inChanDescriptors[j].chantype = DAQI_ANALOG_DIFF + threaddata.inChanDescriptors[j].channel = i + threaddata.inChanDescriptors[j].range = BIP10VOLTS if self.input_range[i] else BIP1VOLTS + j+=1 + + threaddata.ninputChanDescriptors = neninputchannels + + if self.output_enabled: + threaddata.outChanDescriptors = malloc(sizeof(DaqInChanDescriptor)) + if not threaddata.outChanDescriptors: + self.cleanupThreadData(threaddata) + raise MemoryError() + self.threaddata.outChanDescriptors[0].channel = 0 + self.threaddata.outChanDescriptors[0].type = DAQO_ANALOG + self.threaddata.outChanDescriptors[0].range = BIP10VOLTS + + self.threaddata.thread = new CPPThread[void*, void (*)(void*)]( + ulThreadFunction, + self.threaddata) + + + def setInputChannelConfig(self, unsigned chnum, channelconfig: DAQChannel): + if self.threaddata: + raise RuntimeError('Cannot change settings while sampling') + cdef: + int i + UlError err + IepeMode iepe + CouplingMode cm + + if chnum >= self.ninputchannels: + raise RuntimeError('Invalid input channel number') + + self.input_range[chnum] = True if channelconfig.range_ == pyRange.tenV else False + self.enabled_input[chnum] = channelconfig.channel_enabled + + iepe = IEPE_ENABLED if channelconfig.IEPE_enabled else IEPE_DISABLED + cm = CM_AC if channelconfig.IEPE_enabled else CM_DC + + err = ulAISetConfig(self.handle, AI_CFG_CHAN_IEPE_MODE, chnum, + iepe) + if err != ERR_NO_ERROR: + raise RuntimeError('Fatal: could not set IEPE mode') + + err = ulAISetConfig(self.handle, AI_CFG_CHAN_COUPLING_MODE, chnum, cm); + if err != ERR_NO_ERROR: + raise RuntimeError('Fatal: could not set coupling mode') + + err = ulAISetConfigDbl(self.handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, + chnum, channelconfig.sensitivity) + if err != ERR_NO_ERROR: + raise RuntimeError('Fatal: could not set sensitivity') + + def setOutputChannelConfig(self, unsigned chnum, channelconfig: + DAQChannel, bint monitor_gen): + if self.threaddata: + raise RuntimeError('Cannot change settings while sampling') + if chnum >= self.noutputchannels: + raise RuntimeError('Invalid output channel number') + + if monitor_gen and not channelconfig.channel_enabled: + raise RuntimeError('Output channel should be enabled to enable channel monitoring') + self.output_enabled = channelconfig.channel_enabled + self.monitor_gen = monitor_gen + + def __dealloc__(self): + + if self.handle_connected: + ulDisconnectDaqDevice(self.handle) + ulReleaseDaqDevice(self.handle) + + cdef cleanupThreadData(self, DaqThreadData* td): + if td is NULL: + return + if td.thread: + td.stopThread.store(True) + td.thread.join() + del td.thread + td.thread = NULL + + if td.inChanDescriptors: + free(td.inChanDescriptors) + td.inChanDescriptors = NULL + + if td.outChanDescriptors: + free(td.outChanDescriptors) + td.outChanDescriptors = NULL + free(td) + + + +cdef class UlDaq: + cdef: + DaqDeviceHandle handle + + def __cinit__(self): + + """ + Acquires a daq handle, and opens the device + + """ + self.handle = 0 + + # # Open the device to probe the number of input and output ch. + # handle = ulCreateDaqDevice(descriptor) + # if not handle: + # raise RuntimeError('Unable to create device handle on device') + + cpdef int getDeviceCount(self): + cdef: + DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT] + DaqDeviceInterface interfaceType = ANY_IFC + UlError err + + unsigned int numdevs = MAX_DEF_COUNT + + err = ulGetDaqDeviceInventory(interfaceType, + devdescriptors, + &numdevs) + if(err != ERR_NO_ERROR): + raise RuntimeError(f'Device inventarization failed: {err}') + + devices = [] + return numdevs + + def getDeviceInfo(self, unsigned int deviceno): + """ + + """ + cdef: + DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT] + DaqDeviceDescriptor descriptor + DaqDeviceInterface interfaceType = ANY_IFC + DaqDeviceHandle handle + + UlError err + + unsigned int numdevs = MAX_DEF_COUNT + + err = ulGetDaqDeviceInventory(interfaceType, + devdescriptors, + &numdevs) + if(err != ERR_NO_ERROR): + raise RuntimeError(f'Device inventarization failed: {err}') + + if deviceno >= numdevs: + raise ValueError(f'Device number {deviceno} too high {err}') + + descriptor = devdescriptors[deviceno] + + + if descriptor.productName == b'DT9837A': + # Create proper interface name + if descriptor.devInterface == DaqDeviceInterface.USB_IFC: + name = 'USB - ' + elif descriptor.devInterface == DaqDeviceInterface.BLUETOOTH_IFC: + name = 'Bluetooth - ' + elif descriptor.devInterface == DaqDeviceInterface.ETHERNET_IFC: + name = 'Ethernet - ' + + name += descriptor.productName.decode('utf-8') + ', id ' + \ + descriptor.uniqueId.decode('utf-8') + + return DeviceInfo( + api = -1, + index = deviceno, + probed = True, + name = name, + outputchannels = 1, + inputchannels = 4, + duplexchannels = 0, + samplerates = [100, 500, 1000, 2000, 4000, 8000, 16000, 20000, 32000, 48000, 50000] , + sampleformats = ['64-bit floats'], + prefsamplerate = 48000, + hasInputIEPE = True) + + else: + raise RuntimeError(f"No config found for device \"{descriptor.productName.decode('utf-8')}\".") + + # @cython.nonecheck(True) + # def openStream(self, + # avstream + # ): + # """ + # Opens a stream with specified parameters + + # Args: + # avstream: AvStream instance + + # Returns: None + # """ + + # if self._stream is not NULL: + # raise RuntimeError('Stream is already opened.') + + # daqconfig = avstream.daqconfig + # avtype = avstream.avtype + # device = avstream.device + + # cdef: + # bint duplex_mode = daqconfig.duplex_mode + # bint monitorOutput = daqconfig.monitor_gen + # size_t sw # Sample width in bytes + # unsigned int nFramesPerBlock = unsigned int(daqconfig.nFramesPerBlock) + # int firstinputchannel, firstoutputchannel + # int lastinputchannel, lastoutputchannel + # unsigned int ninputchannels_forwarded=0 + # unsigned int ninputchannels_uldaq=0 + # unsigned int noutputchannels_uldaq=0 + # unsigned int noutputchannels_forwarded=0 + # unsigned int samplerate + # int i + # bint input_stream=False, output_stream=False + # bool* inputChannelsEnabled + # bool* outputChannelsEnabled + + # if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512: + # raise ValueError('Invalid number of nFramesPerBlock') + + # if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10: + # raise ValueError('Invalid number of outputDelayBlocks') + + # try: + + # # Determine sample rate and sample format, determine whether we are an + # # input or an output stream, or both + # if avtype == AvType.audio_input or duplex_mode: + # # Here, we override the sample format in case of duplex mode. + # sampleformat = daqconfig.en_input_sample_format + # samplerate = int(daqconfig.en_input_rate) + # input_stream = True + # if duplex_mode: + # output_stream = True + # else: + # sampleformat = daqconfig.en_output_sample_format + # samplerate = int(daqconfig.en_output_rate) + # output_stream = True + + # sw = 64 + + # # All set, allocate the stream! + # self._stream = <_Stream*> malloc(sizeof(_Stream)) + # if self._stream == NULL: + # raise MemoryError('Could not allocate stream: memory error.') + + # self._stream.pyCallback = avstream._audioCallback + # # Increase reference count to the callback + # Py_INCREF( avstream._audioCallback) + + # self._stream.sw = sw + # self._stream.stopThread.store(False) + # self._stream.inputQueue = NULL + # self._stream.outputQueue = NULL + # self._stream.outputDelayQueue = NULL + + # self._stream.thread = NULL + + # self._stream.outputDelayBlocks = outputDelayBlocks + # self._stream.ninputchannels_forwarded = 0 + # self._stream.noutputchannels_forwarded = 0 + # self._stream.inputChannelsEnabled = NULL + # self._stream.outputChannelsEnabled = NULL + + # # Create channel maps for input channels, set input stream + # # parameters + # if input_stream: + # firstinputchannel = daqconfig.firstEnabledInputChannelNumber() + # lastinputchannel = daqconfig.lastEnabledInputChannelNumber() + # ninputchannels_uldaq = lastinputchannel-firstinputchannel+1 + + # if lastinputchannel < 0 or ninputchannels_uldaq < 1: + # raise ValueError('Not enough input channels selected') + # input_ch = daqconfig.input_channel_configs + + # inputChannelsEnabled = malloc(sizeof(bool)*ninputchannels_uldaq) + # self._stream.inputChannelsEnabled = inputChannelsEnabled + + # for i in range(firstinputchannel, lastinputchannel+1): + # ch_en = input_ch[i].channel_enabled + # if ch_en: + # ninputchannels_forwarded += 1 + # inputChannelsEnabled[i] = ch_en + + + # self._stream.inputQueue = new SafeQueue[void*]() + # self._stream.ninputchannels_forwarded = ninputchannels_forwarded + + + # # Create channel maps for output channels + # if output_stream: + # firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber() + # lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber() + # noutputchannels_uldaq = lastoutputchannel-firstoutputchannel+1 + + # if lastoutputchannel < 0 or noutputchannels_uldaq < 1: + # raise ValueError('Not enough output channels selected') + # output_ch = daqconfig.output_channel_configs + + # outputChannelsEnabled = malloc(sizeof(bool)*noutputchannels_uldaq) + # self._stream.outputChannelsEnabled = outputChannelsEnabled + # for i in range(firstoutputchannel, lastoutputchannel+1): + # ch_en = output_ch[i].channel_enabled + # if ch_en: + # noutputchannels_forwarded += 1 + # outputChannelsEnabled[i] = ch_en + + # rtOutputParams_ptr = &self._stream.outputParams + # rtOutputParams_ptr.deviceId = device.index + # rtOutputParams_ptr.nChannels = noutputchannels_uldaq + # rtOutputParams_ptr.firstChannel = firstoutputchannel + + # self._stream.outputQueue = new SafeQueue[void*]() + # self._stream.noutputchannels_forwarded = noutputchannels_forwarded + + # if monitorOutput and duplex_mode: + # self._stream.ninputchannels_forwarded += noutputchannels_forwarded + + # # self._uldaq.openStream(rtOutputParams_ptr, + # # rtInputParams_ptr, + # # _formats_strkey[sampleformat][0], + # # samplerate, + # # &nFramesPerBlock, + # # audioCallback, + # # self._stream, + # # &streamoptions, # Stream options + # # errorCallback # Error callback + # # ) + + # # self._stream.nBytesPerChan = nFramesPerBlock*sw + # # self._stream.nFramesPerBlock = nFramesPerBlock + + # except Exception as e: + # print('Exception occured in stream opening: ', e) + # self.cleanupStream(self._stream) + # self._stream = NULL + # raise e + + # with nogil: + # self._stream.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, + # self._stream) + # # Allow it to start + # CPPsleep(500) + # pass + + # return nFramesPerBlock + + # cdef cleanupStream(self, _Stream* stream): + # # printf('Entrance function cleanupStream...\n') + # cdef: + # void* ptr + # if stream == NULL: + # return + + # with nogil: + # if stream.thread: + # stream.stopThread.store(True) + # if stream.inputQueue: + # # If waiting in the input queue, hereby we let it run. + # stream.inputQueue.enqueue(NULL) + # # printf('Joining thread...\n') + # # HERE WE SHOULD RELEASE THE GIL, as exiting the thread function + # # will require the GIL, which is locked by this thread! + # stream.thread.join() + # # printf('Thread joined!\n') + # del stream.thread + # stream.thread = NULL + + # if stream.inputChannelsEnabled: + # free(stream.inputChannelsEnabled) + # if stream.outputChannelsEnabled: + # free(stream.outputChannelsEnabled) + + # if stream.outputQueue: + # while not stream.outputQueue.empty(): + # free(stream.outputQueue.dequeue()) + # del stream.outputQueue + # if stream.inputQueue: + # while not stream.inputQueue.empty(): + # free(stream.inputQueue.dequeue()) + # del stream.inputQueue + # if stream.outputDelayQueue: + # while not stream.outputDelayQueue.empty(): + # free(stream.outputDelayQueue.dequeue()) + # del stream.outputDelayQueue + # fprintf(stderr, "End cleanup stream queues...\n") + + # if stream.pyCallback: + # Py_DECREF( stream.pyCallback) + # stream.pyCallback = NULL + # # fprintf(stderr, "End cleanup callback...\n") + # free(stream) + + # def startStream(self): + # self._uldaq.startStream() + + # def stopStream(self): + # if self._stream is NULL: + # raise RuntimeError('Stream is not opened') + # try: + # self._uldaq.stopStream() + # except: + # pass + + # def closeStream(self): + # # print('closeStream') + # if self._stream is NULL: + # raise RuntimeError('Stream is not opened') + # # Closing stream + # self._uldaq.closeStream() + # self.cleanupStream(self._stream) + # self._stream = NULL + + # def abortStream(self): + # if self._stream is NULL: + # raise RuntimeError('Stream is not opened') + # self._uldaq.abortStream() + + # def isStreamOpen(self): + # return self._uldaq.isStreamOpen() + + # def isStreamRunning(self): + # return self._uldaq.isStreamRunning() + + # def getStreamTime(self): + # return self._uldaq.getStreamTime() + + # def setStreamTime(self, double time): + # return self._uldaq.setStreamTime(time) +