From 1643eda29a036492b2506c1ed1981e0c13fdda73 Mon Sep 17 00:00:00 2001 From: "W. Trevor King" Date: Tue, 19 Apr 2011 09:17:37 -0400 Subject: [PATCH] Mostly-complete Cython implementation. I need some work on Cython upstream to entirely remove the requirement on the SWIG-wrapped comedi module. --- .gitignore | 2 + README | 6 +- doc/software_timed_analog_IO.txt | 59 +- doc/software_timed_digital_IO.txt | 11 +- doc/synchronized_analog_IO.txt | 70 +- pycomedi/__init__.py | 2 + pycomedi/_comedi_h.pxd | 817 ++++++++++++++++++++++++ pycomedi/_comedilib_h.pxd | 200 ++++++ pycomedi/_error.pyx | 63 ++ pycomedi/calibration.pxd | 17 + pycomedi/calibration.pyx | 177 +++++ pycomedi/channel.pyx | 410 ++++++++++++ pycomedi/chanspec.pyx | 69 ++ pycomedi/classes.py | 775 ---------------------- pycomedi/command.pxd | 12 + pycomedi/command.pyx | 255 ++++++++ pycomedi/constant.pxd | 6 + pycomedi/{constants.py => constant.pyx} | 145 ++++- pycomedi/device.pxd | 15 + pycomedi/device.pyx | 270 ++++++++ pycomedi/instruction.pxd | 12 + pycomedi/instruction.pyx | 119 ++++ pycomedi/library.pyx | 41 ++ pycomedi/range.pxd | 12 + pycomedi/range.pyx | 71 ++ pycomedi/subdevice.pxd | 17 + pycomedi/subdevice.pyx | 411 ++++++++++++ pycomedi/utility.py | 217 ++++--- setup.py | 30 +- test.sh | 11 + 30 files changed, 3371 insertions(+), 951 deletions(-) create mode 100644 pycomedi/_comedi_h.pxd create mode 100644 pycomedi/_comedilib_h.pxd create mode 100644 pycomedi/_error.pyx create mode 100644 pycomedi/calibration.pxd create mode 100644 pycomedi/calibration.pyx create mode 100644 pycomedi/channel.pyx create mode 100644 pycomedi/chanspec.pyx delete mode 100644 pycomedi/classes.py create mode 100644 pycomedi/command.pxd create mode 100644 pycomedi/command.pyx create mode 100644 pycomedi/constant.pxd rename pycomedi/{constants.py => constant.pyx} (70%) create mode 100644 pycomedi/device.pxd create mode 100644 pycomedi/device.pyx create mode 100644 pycomedi/instruction.pxd create mode 100644 pycomedi/instruction.pyx create mode 100644 pycomedi/library.pyx create mode 100644 pycomedi/range.pxd create mode 100644 pycomedi/range.pyx create mode 100644 pycomedi/subdevice.pxd create mode 100644 pycomedi/subdevice.pyx create mode 100755 test.sh diff --git a/.gitignore b/.gitignore index 567360f..b188918 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ build dist dummy_py *.pyc +*.c +*.so pycomedi.egg-info pycomedi/*.pyc diff --git a/README b/README index c6eccf6..6edb8d5 100644 --- a/README +++ b/README @@ -46,8 +46,6 @@ Cython_ Comedilib interface cython dev-python/cython .. [#clb] In the `wtk overlay`_. -TODO: Migrate from SWIG bindings to direct calls on comedilib via Cython. - Installing by hand ------------------ @@ -76,9 +74,9 @@ Integration tests with:: $ nosetests --with-doctest --doctest-extension=txt doc -Internal unit tests with:: +Run both integration tests and internal unit tests with:: - $ nosetests --with-doctest pycomedi + $ ./test.sh Licence diff --git a/doc/software_timed_analog_IO.txt b/doc/software_timed_analog_IO.txt index 2b325c3..e9b0413 100644 --- a/doc/software_timed_analog_IO.txt +++ b/doc/software_timed_analog_IO.txt @@ -1,7 +1,8 @@ Import required modules. ->>> from pycomedi.classes import Device, DataChannel ->>> from pycomedi.constants import SUBDEVICE_TYPE, AREF, UNIT +>>> from pycomedi.device import Device +>>> from pycomedi.channel import AnalogChannel +>>> from pycomedi.constant import SUBDEVICE_TYPE, AREF, UNIT Open a device. @@ -15,7 +16,7 @@ select the subdevice directly by index). Generate a list of channels you wish to control. ->>> channels = [subdevice.channel(i, factory=DataChannel, aref=AREF.diff) +>>> channels = [subdevice.channel(i, factory=AnalogChannel, aref=AREF.diff) ... for i in (0, 1, 2, 3)] Configure the channels. @@ -27,9 +28,15 @@ Read/write sequentially. >>> value = [c.data_read_delayed(nano_sec=1e3) for c in channels] >>> value # doctest: +SKIP -[0, 9634, 0, 15083] +[65535L, 23424L, 0L, 0L] -TODO: convert to physical values. +Use a converter to convert these to physical values + +>>> converters = [c.get_converter() for c in channels] +>>> [c.to_physical(v) for c,v in zip(converters, value)] # doctest: +SKIP +[5.0, 1.787136644541085, 0.0, 0.0] +>>> [c.range.unit for c in channels] +[<_NamedInt volt>, <_NamedInt volt>, <_NamedInt volt>, <_NamedInt volt>] Close the device when you're done. @@ -45,10 +52,14 @@ voltage. >>> device.open() >>> ai_subdevice = device.find_subdevice_by_type(SUBDEVICE_TYPE.ai) >>> ao_subdevice = device.find_subdevice_by_type(SUBDEVICE_TYPE.ao) ->>> ai_channel = ai_subdevice.channel(0, factory=DataChannel, aref=AREF.diff) ->>> ao_channel = ao_subdevice.channel(0, factory=DataChannel, aref=AREF.diff) ->>> ai_channel.range = ai_channel.find_range(unit=UNIT.volt, min=0, max=5) ->>> ao_channel.range = ao_channel.find_range(unit=UNIT.volt, min=0, max=5) +>>> ai_channel = ai_subdevice.channel(0, factory=AnalogChannel, aref=AREF.diff) +>>> ao_channel = ao_subdevice.channel(0, factory=AnalogChannel, aref=AREF.diff) +>>> ai_channel.range = ai_channel.find_range(unit=UNIT.volt, min=0, max=10) +>>> ai_channel.range + +>>> ao_channel.range = ao_channel.find_range(unit=UNIT.volt, min=0, max=10) +>>> ao_channel.range + >>> ao_maxdata = ao_channel.get_maxdata() >>> ai_maxdata = ai_channel.get_maxdata() >>> ao_start = 0.3 * ao_maxdata @@ -57,18 +68,30 @@ voltage. >>> ao_data = linspace(ao_start, ao_stop, points) >>> ai_data = [] >>> for i in range(points): -... written = ao_channel.data_write(ao_data[i]) -... assert written == 1, written -... ai_data.append(ai_channel.data_read_delayed(nano_sec=1e3)) +... ao_channel.data_write(ao_data[i]) +... ai_data.append(ai_channel.data_read_delayed(nano_sec=10e3)) +>>> ao_converter = ao_channel.get_converter() +>>> ai_converter = ai_channel.get_converter() +>>> ao_data = ao_converter.to_physical(ao_data) +>>> ai_data = ao_converter.to_physical(ai_data) >>> ao_data ->>> ai_data +array([ 2.9999237 , 3.44441901, 3.88876173, 4.33325704, 4.77775235, + 5.22209506, 5.66659037, 6.11108568, 6.5554284 , 6.9999237 ]) +>>> ai_data # doctest: +SKIP +array([ 3.0156405 , 3.46089876, 3.90630961, 4.35156786, 4.79682612, + 5.24238956, 5.687953 , 6.13351644, 6.57862211, 7.02433814]) >>> scaled_ao_data = [d/float(ao_maxdata) for d in ao_data] >>> scaled_ai_data = [d/float(ai_maxdata) for d in ai_data] >>> gradient,intercept,r_value,p_value,std_err = linregress( ... scaled_ao_data, scaled_ai_data) ->>> gradient ->>> intercept ->>> r_value ->>> p_value ->>> std_err +>>> abs(gradient - 1.0) < 0.01 +True +>>> abs(intercept) < 0.001 +True +>>> r_value > 0.999 +True +>>> p_value < 1e-14 +True +>>> std_err < 1e-4 +True >>> device.close() diff --git a/doc/software_timed_digital_IO.txt b/doc/software_timed_digital_IO.txt index b764638..9533a9a 100644 --- a/doc/software_timed_digital_IO.txt +++ b/doc/software_timed_digital_IO.txt @@ -1,7 +1,8 @@ Import required modules. ->>> from pycomedi.classes import Device, DataChannel ->>> from pycomedi.constants import SUBDEVICE_TYPE, IO_DIRECTION +>>> from pycomedi.device import Device +>>> from pycomedi.channel import DigitalChannel +>>> from pycomedi.constant import SUBDEVICE_TYPE, IO_DIRECTION Open a device. @@ -15,7 +16,7 @@ select the subdevice directly by index). Generate a list of channels you wish to control. ->>> channels = [subdevice.channel(i, factory=DataChannel) +>>> channels = [subdevice.channel(i, factory=DigitalChannel) ... for i in (0, 1, 2, 3)] Configure the channels. @@ -27,13 +28,13 @@ Read/write either as a block... >>> value = subdevice.dio_bitfield(base_channel=0) >>> value # doctest: +SKIP -7 +255L ... or sequentially. >>> value = [c.dio_read() for c in channels] >>> value # doctest: +SKIP -[1, 1, 1, 0] +[1, 1, 1, 1] Close the device when you're done. diff --git a/doc/synchronized_analog_IO.txt b/doc/synchronized_analog_IO.txt index 823427b..1791cf9 100644 --- a/doc/synchronized_analog_IO.txt +++ b/doc/synchronized_analog_IO.txt @@ -1,11 +1,13 @@ Import required modules. ->>> from numpy import arange, linspace, zeros, sin, pi ->>> from pycomedi.classes import (Device, StreamingSubdevice, DataChannel, -... Chanlist) ->>> from pycomedi.constants import (AREF, CMDF, INSN, SUBDEVICE_TYPE, TRIG_SRC, +>>> from numpy import arange, linspace, zeros, sin, pi, float32 +>>> from pycomedi.device import Device +>>> from pycomedi.subdevice import StreamingSubdevice +>>> from pycomedi.channel import AnalogChannel +>>> from pycomedi.chanspec import ChanSpec +>>> from pycomedi.constant import (AREF, CMDF, INSN, SUBDEVICE_TYPE, TRIG_SRC, ... UNIT) ->>> from pycomedi.utility import inttrig_insn, subdevice_dtype, Reader, Writer +>>> from pycomedi.utility import inttrig_insn, Reader, Writer Open a device. @@ -22,12 +24,12 @@ select the subdevice directly by index). Generate a list of channels you wish to control. ->>> ai_channels = Chanlist( -... [ai_subdevice.channel(i, factory=DataChannel, aref=AREF.diff) -... for i in (0, 1, 2, 3)]) ->>> ao_channels = Chanlist( -... [ao_subdevice.channel(i, factory=DataChannel, aref=AREF.diff) -... for i in (0, 1)]) +>>> ai_channels = [ +... ai_subdevice.channel(i, factory=AnalogChannel, aref=AREF.diff) +... for i in (0, 1, 2, 3)] +>>> ao_channels = [ +... ao_subdevice.channel(i, factory=AnalogChannel, aref=AREF.diff) +... for i in (0, 1)] Configure the channels. @@ -36,8 +38,8 @@ Configure the channels. Use the subdevice flags to determine data types. ->>> ai_dtype = subdevice_dtype(ai_subdevice) ->>> ao_dtype = subdevice_dtype(ao_subdevice) +>>> ai_dtype = ai_subdevice.get_dtype() +>>> ao_dtype = ao_subdevice.get_dtype() Allocate buffers (with channel data interleaved). @@ -57,10 +59,10 @@ Get rough commands. >>> frequency = 1000.0 # Hz >>> scan_period_ns = int(1e9 / frequency) # nanosecond period ->>> ai_subdevice.get_cmd_generic_timed(len(ai_channels), scan_period_ns) ->>> ai_cmd = ai_subdevice.cmd ->>> ao_subdevice.get_cmd_generic_timed(len(ao_channels), scan_period_ns) ->>> ao_cmd = ao_subdevice.cmd +>>> ai_cmd = ai_subdevice.get_cmd_generic_timed( +... len(ai_channels), scan_period_ns) +>>> ao_cmd = ao_subdevice.get_cmd_generic_timed( +... len(ao_channels), scan_period_ns) Setup multi-scan run triggering. @@ -73,15 +75,6 @@ Setup multi-scan run triggering. >>> ao_cmd.stop_src = TRIG_SRC.count >>> ao_cmd.stop_arg = n_samps -Setup multi-channel scan triggering (handled by get_cmd_generic_timed?). - -ai_cmd.scan_begin_src = TRIG_SRC.timer -ai_cmd.scan_begin_arg -ai_cmd.convert_src -ai_cmd.convert_arg -ai_cmd.scan_end_src -ai_cmd.scan_end_arg - Add channel lists. >>> ai_cmd.chanlist = ai_channels @@ -128,12 +121,37 @@ Join the reader and writer threads when they finish. >>> reader.join() >>> ai_buffer # doctest: +SKIP +array([[32669, 27117, 24827, 23111], + [33711, 27680, 25273, 23453], +... + [31627, 24590, 22482, 22045], + [32668, 25381, 22937, 22189]], dtype=uint16) + array([[32342, 31572, 30745, 31926], [33376, 31797, 30904, 31761], ... [31308, 24246, 22215, 21824], [32343, 25044, 22659, 21959]], dtype=uint16) +Use a converter to convert these to physical values + +>>> converters = [c.get_converter() for c in ai_channels] +>>> ai_physical = zeros(ai_buffer.shape, dtype=float32) +>>> for i,c in enumerate(converters): +... ai_physical[:,i] = c.to_physical(ai_buffer[:,i]) +>>> ai_physical # doctest: +SKIP +array([[ -3.00602727e-02, -1.72442210e+00, -2.42328525e+00, + -2.94697499e+00], + [ 2.87937731e-01, -1.55260551e+00, -2.28717470e+00, + -2.84260321e+00], +... + [ -3.48058283e-01, -2.49561310e+00, -3.13893342e+00, + -3.27229714e+00], + [ -3.03654540e-02, -2.25421524e+00, -3.00007629e+00, + -3.22835135e+00]], dtype=float32) +>>> [c.range.unit for c in ai_channels] +[<_NamedInt volt>, <_NamedInt volt>, <_NamedInt volt>, <_NamedInt volt>] + Close the device when you're done. >>> device.close() diff --git a/pycomedi/__init__.py b/pycomedi/__init__.py index 9b746a5..d6eabe4 100644 --- a/pycomedi/__init__.py +++ b/pycomedi/__init__.py @@ -13,6 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +"A Pythonic wrapper around Comedilib" + import logging as _logging diff --git a/pycomedi/_comedi_h.pxd b/pycomedi/_comedi_h.pxd new file mode 100644 index 0000000..234d751 --- /dev/null +++ b/pycomedi/_comedi_h.pxd @@ -0,0 +1,817 @@ +# Copyright + +"Cython interface to comedi.h" + + +cdef extern from 'comedi.h': + # From http://docs.cython.org/src/userguide/external_C_code.html + # If the header file uses macros to define constants, translate + # them into a dummy enum declaration. + enum: COMEDI_MAJOR + #"comedi's major device number" + + enum: COMEDI_NDEVICES + #"""maximum number of minor devices. + # + #This can be increased, although kernel structures are currently + #statically allocated, thus you don't want this to be much more + #than you actually use. + #""" + + enum: COMEDI_NDEVCONFOPTS + #"number of config options in the config structure" + + # length of nth chunk of firmware data + enum: COMEDI_DEVCONF_AUX_DATA3_LENGTH + enum: COMEDI_DEVCONF_AUX_DATA2_LENGTH + enum: COMEDI_DEVCONF_AUX_DATA1_LENGTH + enum: COMEDI_DEVCONF_AUX_DATA0_LENGTH + enum: COMEDI_DEVCONF_AUX_DATA_HI + #"most significant 32 bits of pointer address (if needed)" + enum: COMEDI_DEVCONF_AUX_DATA_LO + #"least significant 32 bits of pointer address" + enum: COMEDI_DEVCONF_AUX_DATA_LENGTH + #"total data length" + + enum: COMEDI_NAMELEN + #"max length of device and driver names" + + ctypedef unsigned int lsampl_t + ctypedef unsigned short sampl_t + + # CR_PACK and CR_PACK_FLAGS replaced by .chanspec.Chanspec + + enum: CR_FLAGS_MASK + enum: CR_ALT_FILTER + enum: CR_DITHER + enum: CR_DEGLITCH + enum: CR_ALT_SOURCE + enum: CR_EDGE + enum: CR_INVERT + + enum: AREF_GROUND + #"analog ref = analog ground" + enum: AREF_COMMON + #"analog ref = analog common" + enum: AREF_DIFF + #"analog ref = differential" + enum: AREF_OTHER + #"analog ref = other (undefined)" + +# /* counters -- these are arbitrary values */ +# enum: GPCT_RESET 0x0001 +# enum: GPCT_SET_SOURCE 0x0002 +# enum: GPCT_SET_GATE 0x0004 +# enum: GPCT_SET_DIRECTION 0x0008 +# enum: GPCT_SET_OPERATION 0x0010 +# enum: GPCT_ARM 0x0020 +# enum: GPCT_DISARM 0x0040 +# enum: GPCT_GET_INT_CLK_FRQ 0x0080 +# +# enum: GPCT_INT_CLOCK 0x0001 +# enum: GPCT_EXT_PIN 0x0002 +# enum: GPCT_NO_GATE 0x0004 +# enum: GPCT_UP 0x0008 +# enum: GPCT_DOWN 0x0010 +# enum: GPCT_HWUD 0x0020 +# enum: GPCT_SIMPLE_EVENT 0x0040 +# enum: GPCT_SINGLE_PERIOD 0x0080 +# enum: GPCT_SINGLE_PW 0x0100 +# enum: GPCT_CONT_PULSE_OUT 0x0200 +# enum: GPCT_SINGLE_PULSE_OUT 0x0400 +# +# /* instructions */ +# +# enum: INSN_MASK_WRITE 0x8000000 +# enum: INSN_MASK_READ 0x4000000 +# enum: INSN_MASK_SPECIAL 0x2000000 +# +# enum: INSN_READ ( 0 | INSN_MASK_READ) +# enum: INSN_WRITE ( 1 | INSN_MASK_WRITE) +# enum: INSN_BITS ( 2 | INSN_MASK_READ|INSN_MASK_WRITE) +# enum: INSN_CONFIG ( 3 | INSN_MASK_READ|INSN_MASK_WRITE) +# enum: INSN_GTOD ( 4 | INSN_MASK_READ|INSN_MASK_SPECIAL) +# enum: INSN_WAIT ( 5 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) +# enum: INSN_INTTRIG ( 6 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) +# +# /* trigger flags */ +# /* These flags are used in comedi_trig structures */ +# +# enum: TRIG_BOGUS 0x0001 /* do the motions */ +# enum: TRIG_DITHER 0x0002 /* enable dithering */ +# enum: TRIG_DEGLITCH 0x0004 /* enable deglitching */ +# //enum: TRIG_RT 0x0008 /* perform op in real time */ +# enum: TRIG_CONFIG 0x0010 /* perform configuration, not triggering */ +# enum: TRIG_WAKE_EOS 0x0020 /* wake up on end-of-scan events */ +# //enum: TRIG_WRITE 0x0040 /* write to bidirectional devices */ +# +# /* command flags */ +# /* These flags are used in comedi_cmd structures */ +# +# enum: CMDF_PRIORITY 0x00000008 /* try to use a real-time interrupt while performing command */ +# +# enum: TRIG_RT CMDF_PRIORITY /* compatibility definition */ +# +# enum: CMDF_WRITE 0x00000040 +# enum: TRIG_WRITE CMDF_WRITE /* compatibility definition */ +# +# enum: CMDF_RAWDATA 0x00000080 +# +# enum: COMEDI_EV_START 0x00040000 +# enum: COMEDI_EV_SCAN_BEGIN 0x00080000 +# enum: COMEDI_EV_CONVERT 0x00100000 +# enum: COMEDI_EV_SCAN_END 0x00200000 +# enum: COMEDI_EV_STOP 0x00400000 +# +# enum: TRIG_ROUND_MASK 0x00030000 +# enum: TRIG_ROUND_NEAREST 0x00000000 +# enum: TRIG_ROUND_DOWN 0x00010000 +# enum: TRIG_ROUND_UP 0x00020000 +# enum: TRIG_ROUND_UP_NEXT 0x00030000 +# +# /* trigger sources */ +# +# enum: TRIG_ANY 0xffffffff +# enum: TRIG_INVALID 0x00000000 +# +# enum: TRIG_NONE 0x00000001 /* never trigger */ +# enum: TRIG_NOW 0x00000002 /* trigger now + N ns */ +# enum: TRIG_FOLLOW 0x00000004 /* trigger on next lower level trig */ +# enum: TRIG_TIME 0x00000008 /* trigger at time N ns */ +# enum: TRIG_TIMER 0x00000010 /* trigger at rate N ns */ +# enum: TRIG_COUNT 0x00000020 /* trigger when count reaches N */ +# enum: TRIG_EXT 0x00000040 /* trigger on external signal N */ +# enum: TRIG_INT 0x00000080 /* trigger on comedi-internal signal N */ +# enum: TRIG_OTHER 0x00000100 /* driver defined */ +# +# /* subdevice flags */ +# +# enum: SDF_BUSY 0x0001 /* device is busy */ +# enum: SDF_BUSY_OWNER 0x0002 /* device is busy with your job */ +# enum: SDF_LOCKED 0x0004 /* subdevice is locked */ +# enum: SDF_LOCK_OWNER 0x0008 /* you own lock */ +# enum: SDF_MAXDATA 0x0010 /* maxdata depends on channel */ +# enum: SDF_FLAGS 0x0020 /* flags depend on channel */ +# enum: SDF_RANGETYPE 0x0040 /* range type depends on channel */ +# enum: SDF_MODE0 0x0080 /* can do mode 0 */ +# enum: SDF_MODE1 0x0100 /* can do mode 1 */ +# enum: SDF_MODE2 0x0200 /* can do mode 2 */ +# enum: SDF_MODE3 0x0400 /* can do mode 3 */ +# enum: SDF_MODE4 0x0800 /* can do mode 4 */ +# enum: SDF_CMD 0x1000 /* can do commands (deprecated) */ +# enum: SDF_SOFT_CALIBRATED 0x2000 /* subdevice uses software calibration */ +# enum: SDF_CMD_WRITE 0x4000 /* can do output commands */ +# enum: SDF_CMD_READ 0x8000 /* can do input commands */ +# +# enum: SDF_READABLE 0x00010000 /* subdevice can be read (e.g. analog input) */ +# enum: SDF_WRITABLE 0x00020000 /* subdevice can be written (e.g. analog output) */ +# enum: SDF_WRITEABLE SDF_WRITABLE /* spelling error in API */ +# enum: SDF_INTERNAL 0x00040000 /* subdevice does not have externally visible lines */ +# enum: SDF_RT 0x00080000 /* DEPRECATED: subdevice is RT capable */ +# enum: SDF_GROUND 0x00100000 /* can do aref=ground */ +# enum: SDF_COMMON 0x00200000 /* can do aref=common */ +# enum: SDF_DIFF 0x00400000 /* can do aref=diff */ +# enum: SDF_OTHER 0x00800000 /* can do aref=other */ +# enum: SDF_DITHER 0x01000000 /* can do dithering */ +# enum: SDF_DEGLITCH 0x02000000 /* can do deglitching */ +# enum: SDF_MMAP 0x04000000 /* can do mmap() */ +# enum: SDF_RUNNING 0x08000000 /* subdevice is acquiring data */ +# enum: SDF_LSAMPL 0x10000000 /* subdevice uses 32-bit samples */ +# enum: SDF_PACKED 0x20000000 /* subdevice can do packed DIO */ +# /* re recyle these flags for PWM */ +# enum: SDF_PWM_COUNTER SDF_MODE0 /* PWM can automatically switch off */ +# enum: SDF_PWM_HBRIDGE SDF_MODE1 /* PWM is signed (H-bridge) */ +# +# +# +# /* subdevice types */ +# + enum comedi_subdevice_type: + COMEDI_SUBD_UNUSED, # unused by driver + COMEDI_SUBD_AI, # analog input + COMEDI_SUBD_AO, # analog output + COMEDI_SUBD_DI, # digital input + COMEDI_SUBD_DO, # digital output + COMEDI_SUBD_DIO, # digital input/output + COMEDI_SUBD_COUNTER, # counter + COMEDI_SUBD_TIMER, # timer + COMEDI_SUBD_MEMORY, # memory, EEPROM, DPRAM + COMEDI_SUBD_CALIB, # calibration DACs + COMEDI_SUBD_PROC, # processor, DSP + COMEDI_SUBD_SERIAL, # serial IO + COMEDI_SUBD_PWM # PWM + +# /* configuration instructions */ +# +# enum configuration_ids { +# INSN_CONFIG_DIO_INPUT = 0, +# INSN_CONFIG_DIO_OUTPUT = 1, +# INSN_CONFIG_DIO_OPENDRAIN = 2, +# INSN_CONFIG_ANALOG_TRIG = 16, +# // INSN_CONFIG_WAVEFORM = 17, +# // INSN_CONFIG_TRIG = 18, +# // INSN_CONFIG_COUNTER = 19, +# INSN_CONFIG_ALT_SOURCE = 20, +# INSN_CONFIG_DIGITAL_TRIG = 21, +# INSN_CONFIG_BLOCK_SIZE = 22, +# INSN_CONFIG_TIMER_1 = 23, +# INSN_CONFIG_FILTER = 24, +# INSN_CONFIG_CHANGE_NOTIFY = 25, +# +# /*ALPHA*/ INSN_CONFIG_SERIAL_CLOCK = 26, +# INSN_CONFIG_BIDIRECTIONAL_DATA = 27, +# INSN_CONFIG_DIO_QUERY = 28, +# INSN_CONFIG_PWM_OUTPUT = 29, +# INSN_CONFIG_GET_PWM_OUTPUT = 30, +# INSN_CONFIG_ARM = 31, +# INSN_CONFIG_DISARM = 32, +# INSN_CONFIG_GET_COUNTER_STATUS = 33, +# INSN_CONFIG_RESET = 34, +# INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR = 1001, // Use CTR as single pulsegenerator +# INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR = 1002, // Use CTR as pulsetraingenerator +# INSN_CONFIG_GPCT_QUADRATURE_ENCODER = 1003, // Use the counter as encoder +# INSN_CONFIG_SET_GATE_SRC = 2001, // Set gate source +# INSN_CONFIG_GET_GATE_SRC = 2002, // Get gate source +# INSN_CONFIG_SET_CLOCK_SRC = 2003, // Set master clock source +# INSN_CONFIG_GET_CLOCK_SRC = 2004, // Get master clock source +# INSN_CONFIG_SET_OTHER_SRC = 2005, // Set other source +# // INSN_CONFIG_GET_OTHER_SRC = 2006, // Get other source +# INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE = 2006, // Get size in bytes of subdevice's on-board fifos used during streaming input/output +# INSN_CONFIG_SET_COUNTER_MODE = 4097, +# INSN_CONFIG_8254_SET_MODE = INSN_CONFIG_SET_COUNTER_MODE, /* deprecated */ +# INSN_CONFIG_8254_READ_STATUS = 4098, +# INSN_CONFIG_SET_ROUTING = 4099, +# INSN_CONFIG_GET_ROUTING = 4109, +# /* PWM */ +# INSN_CONFIG_PWM_SET_PERIOD = 5000, /* sets frequency */ +# INSN_CONFIG_PWM_GET_PERIOD = 5001, /* gets frequency */ +# INSN_CONFIG_GET_PWM_STATUS = 5002, /* is it running? */ +# INSN_CONFIG_PWM_SET_H_BRIDGE = 5003, /* sets H bridge: duty cycle and sign bit for a relay at the same time*/ +# INSN_CONFIG_PWM_GET_H_BRIDGE = 5004 /* gets H bridge data: duty cycle and the sign bit */ +# }; +# +# enum comedi_io_direction { +# COMEDI_INPUT = 0, +# COMEDI_OUTPUT = 1, +# COMEDI_OPENDRAIN = 2 +# }; +# +# enum comedi_support_level +# { +# COMEDI_UNKNOWN_SUPPORT = 0, +# COMEDI_SUPPORTED, +# COMEDI_UNSUPPORTED +# }; +# +# /* ioctls */ +# +# enum: CIO 'd' +# enum: COMEDI_DEVCONFIG _IOW(CIO,0,comedi_devconfig) +# enum: COMEDI_DEVINFO _IOR(CIO,1,comedi_devinfo) +# enum: COMEDI_SUBDINFO _IOR(CIO,2,comedi_subdinfo) +# enum: COMEDI_CHANINFO _IOR(CIO,3,comedi_chaninfo) +# enum: COMEDI_TRIG _IOWR(CIO,4,comedi_trig) +# enum: COMEDI_LOCK _IO(CIO,5) +# enum: COMEDI_UNLOCK _IO(CIO,6) +# enum: COMEDI_CANCEL _IO(CIO,7) +# enum: COMEDI_RANGEINFO _IOR(CIO,8,comedi_rangeinfo) +# enum: COMEDI_CMD _IOR(CIO,9,comedi_cmd) +# enum: COMEDI_CMDTEST _IOR(CIO,10,comedi_cmd) +# enum: COMEDI_INSNLIST _IOR(CIO,11,comedi_insnlist) +# enum: COMEDI_INSN _IOR(CIO,12,comedi_insn) +# enum: COMEDI_BUFCONFIG _IOR(CIO,13,comedi_bufconfig) +# enum: COMEDI_BUFINFO _IOWR(CIO,14,comedi_bufinfo) +# enum: COMEDI_POLL _IO(CIO,15) +# + # structures + + ctypedef struct comedi_cmd: + unsigned int subdev + unsigned int flags + unsigned int start_src + unsigned int start_arg + unsigned int scan_begin_src + unsigned int scan_begin_arg + unsigned int convert_src + unsigned int convert_arg + unsigned int scan_end_src + unsigned int scan_end_arg + unsigned int stop_src + unsigned int stop_arg + unsigned int *chanlist # channel/range list + unsigned int chanlist_len + sampl_t *data # data list, size depends on subd flags + unsigned int data_len + + ctypedef struct comedi_insn: + unsigned int insn + unsigned int n + lsampl_t *data + unsigned int subdev + unsigned int chanspec + unsigned int unused[3] + + ctypedef struct comedi_insnlist: + unsigned int n_insns + comedi_insn *insns + + ctypedef struct comedi_chaninfo: + unsigned int subdev + lsampl_t *maxdata_list + unsigned int *flaglist + unsigned int *rangelist + unsigned int unused[4] + + ctypedef struct comedi_subdinfo: + unsigned int type + unsigned int n_chan + unsigned int subd_flags + unsigned int timer_type + unsigned int len_chanlist + lsampl_t maxdata + unsigned int flags # channel flags + unsigned int range_type # lookup in kernel + unsigned int settling_time_0 + unsigned insn_bits_support # see support_level enum for values + unsigned int unused[8] + + ctypedef struct comedi_devinfo: + unsigned int version_code + unsigned int n_subdevs + char driver_name[COMEDI_NAMELEN] + char board_name[COMEDI_NAMELEN] + int read_subdevice + int write_subdevice + int unused[30] + + ctypedef struct comedi_devconfig: + char board_name[COMEDI_NAMELEN] + int options[COMEDI_NDEVCONFOPTS] + + ctypedef struct comedi_rangeinfo: + unsigned int range_type + void *range_ptr + + ctypedef struct comedi_krange: + int min # fixed point, multiply by 1e-6 + int max # fixed point, multiply by 1e-6 + unsigned int flags + + ctypedef struct comedi_bufconfig: + unsigned int subdevice + unsigned int flags + unsigned int maximum_size + unsigned int size + unsigned int unused[4] + + ctypedef struct comedi_bufinfo: + unsigned int subdevice + unsigned int bytes_read + unsigned int buf_write_ptr + unsigned int buf_read_ptr + unsigned int buf_write_count + unsigned int buf_read_count + unsigned int bytes_written + unsigned int unused[4] + + struct comedi_trig: + unsigned int subdev # subdevice + unsigned int mode # mode + unsigned int flags + unsigned int n_chan # number of channels + unsigned int *chanlist # channel/range list + sampl_t *data # data list, size depends on subd flags + unsigned int n # number of scans + unsigned int trigsrc + unsigned int trigvar + unsigned int trigvar1 + unsigned int data_len + unsigned int unused[3] + +# /* range stuff */ +# +# enum: __RANGE(a,b) ((((a)&0xffff)<<16)|((b)&0xffff)) +# +# enum: RANGE_OFFSET(a) (((a)>>16)&0xffff) +# enum: RANGE_LENGTH(b) ((b)&0xffff) +# +# enum: RF_UNIT(flags) ((flags)&0xff) +# enum: RF_EXTERNAL (1<<8) +# +# enum: UNIT_volt 0 +# enum: UNIT_mA 1 +# enum: UNIT_none 2 +# +# enum: COMEDI_MIN_SPEED ((unsigned int)0xffffffff) +# +# /* callback stuff */ +# /* only relevant to kernel modules. */ +# +# enum: COMEDI_CB_EOS 1 /* end of scan */ +# enum: COMEDI_CB_EOA 2 /* end of acquisition */ +# enum: COMEDI_CB_BLOCK 4 /* new data has arrived: wakes up write()/read() */ +# enum: COMEDI_CB_EOBUF 8 /* DEPRECATED: end of buffer */ +# enum: COMEDI_CB_ERROR 16 /* card error during acquisition */ +# enum: COMEDI_CB_OVERFLOW 32 /* buffer overflow/underflow */ +# +# /**********************************************************/ +# /* everything after this line is ALPHA */ +# /**********************************************************/ +# +# /* +# 8254 specific configuration. +# +# It supports two config commands: +# +# 0 ID: INSN_CONFIG_SET_COUNTER_MODE +# 1 8254 Mode +# I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 +# OR'ed with: +# I8254_BCD, I8254_BINARY +# +# 0 ID: INSN_CONFIG_8254_READ_STATUS +# 1 <-- Status byte returned here. +# B7=Output +# B6=NULL Count +# B5-B0 Current mode. +# +# */ +# +# enum i8254_mode { +# I8254_MODE0 = (0 << 1), /* Interrupt on terminal count */ +# I8254_MODE1 = (1 << 1), /* Hardware retriggerable one-shot */ +# I8254_MODE2 = (2 << 1), /* Rate generator */ +# I8254_MODE3 = (3 << 1), /* Square wave mode */ +# I8254_MODE4 = (4 << 1), /* Software triggered strobe */ +# I8254_MODE5 = (5 << 1), /* Hardware triggered strobe (retriggerable) */ +# I8254_BCD = 1, /* use binary-coded decimal instead of binary (pretty useless) */ +# I8254_BINARY = 0 +# }; +# +# static inline unsigned NI_USUAL_PFI_SELECT(unsigned pfi_channel) { +# if (pfi_channel < 10) +# return 0x1 + pfi_channel; +# else +# return 0xb + pfi_channel; +# } static inline unsigned NI_USUAL_RTSI_SELECT(unsigned rtsi_channel) { +# if (rtsi_channel < 7) +# return 0xb + rtsi_channel; +# else +# return 0x1b; +# } +# /* mode bits for NI general-purpose counters, set with INSN_CONFIG_SET_COUNTER_MODE */ +# enum: NI_GPCT_COUNTING_MODE_SHIFT 16 +# enum: NI_GPCT_INDEX_PHASE_BITSHIFT 20 +# enum: NI_GPCT_COUNTING_DIRECTION_SHIFT 24 +# enum ni_gpct_mode_bits { +# NI_GPCT_GATE_ON_BOTH_EDGES_BIT = 0x4, +# NI_GPCT_EDGE_GATE_MODE_MASK = 0x18, +# NI_GPCT_EDGE_GATE_STARTS_STOPS_BITS = 0x0, +# NI_GPCT_EDGE_GATE_STOPS_STARTS_BITS = 0x8, +# NI_GPCT_EDGE_GATE_STARTS_BITS = 0x10, +# NI_GPCT_EDGE_GATE_NO_STARTS_NO_STOPS_BITS = 0x18, +# NI_GPCT_STOP_MODE_MASK = 0x60, +# NI_GPCT_STOP_ON_GATE_BITS = 0x00, +# NI_GPCT_STOP_ON_GATE_OR_TC_BITS = 0x20, +# NI_GPCT_STOP_ON_GATE_OR_SECOND_TC_BITS = 0x40, +# NI_GPCT_LOAD_B_SELECT_BIT = 0x80, +# NI_GPCT_OUTPUT_MODE_MASK = 0x300, +# NI_GPCT_OUTPUT_TC_PULSE_BITS = 0x100, +# NI_GPCT_OUTPUT_TC_TOGGLE_BITS = 0x200, +# NI_GPCT_OUTPUT_TC_OR_GATE_TOGGLE_BITS = 0x300, +# NI_GPCT_HARDWARE_DISARM_MASK = 0xc00, +# NI_GPCT_NO_HARDWARE_DISARM_BITS = 0x000, +# NI_GPCT_DISARM_AT_TC_BITS = 0x400, +# NI_GPCT_DISARM_AT_GATE_BITS = 0x800, +# NI_GPCT_DISARM_AT_TC_OR_GATE_BITS = 0xc00, +# NI_GPCT_LOADING_ON_TC_BIT = 0x1000, +# NI_GPCT_LOADING_ON_GATE_BIT = 0x4000, +# NI_GPCT_COUNTING_MODE_MASK = 0x7 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_NORMAL_BITS = +# 0x0 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_QUADRATURE_X1_BITS = +# 0x1 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_QUADRATURE_X2_BITS = +# 0x2 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_QUADRATURE_X4_BITS = +# 0x3 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_TWO_PULSE_BITS = +# 0x4 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_COUNTING_MODE_SYNC_SOURCE_BITS = +# 0x6 << NI_GPCT_COUNTING_MODE_SHIFT, +# NI_GPCT_INDEX_PHASE_MASK = 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, +# NI_GPCT_INDEX_PHASE_LOW_A_LOW_B_BITS = +# 0x0 << NI_GPCT_INDEX_PHASE_BITSHIFT, +# NI_GPCT_INDEX_PHASE_LOW_A_HIGH_B_BITS = +# 0x1 << NI_GPCT_INDEX_PHASE_BITSHIFT, +# NI_GPCT_INDEX_PHASE_HIGH_A_LOW_B_BITS = +# 0x2 << NI_GPCT_INDEX_PHASE_BITSHIFT, +# NI_GPCT_INDEX_PHASE_HIGH_A_HIGH_B_BITS = +# 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, +# NI_GPCT_INDEX_ENABLE_BIT = 0x400000, +# NI_GPCT_COUNTING_DIRECTION_MASK = +# 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, +# NI_GPCT_COUNTING_DIRECTION_DOWN_BITS = +# 0x00 << NI_GPCT_COUNTING_DIRECTION_SHIFT, +# NI_GPCT_COUNTING_DIRECTION_UP_BITS = +# 0x1 << NI_GPCT_COUNTING_DIRECTION_SHIFT, +# NI_GPCT_COUNTING_DIRECTION_HW_UP_DOWN_BITS = +# 0x2 << NI_GPCT_COUNTING_DIRECTION_SHIFT, +# NI_GPCT_COUNTING_DIRECTION_HW_GATE_BITS = +# 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, +# NI_GPCT_RELOAD_SOURCE_MASK = 0xc000000, +# NI_GPCT_RELOAD_SOURCE_FIXED_BITS = 0x0, +# NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS = 0x4000000, +# NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS = 0x8000000, +# NI_GPCT_OR_GATE_BIT = 0x10000000, +# NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000 +# }; +# +# /* Bits for setting a clock source with +# * INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters. */ +# enum ni_gpct_clock_source_bits { +# NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f, +# NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0, +# NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS = 0x1, +# NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS = 0x2, +# NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS = 0x3, +# NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS = 0x4, +# NI_GPCT_NEXT_TC_CLOCK_SRC_BITS = 0x5, +# NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS = 0x6, /* NI 660x-specific */ +# NI_GPCT_PXI10_CLOCK_SRC_BITS = 0x7, +# NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS = 0x8, +# NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS = 0x9, +# NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK = 0x30000000, +# NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS = 0x0, +# NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS = 0x10000000, /* divide source by 2 */ +# NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS = 0x20000000, /* divide source by 8 */ +# NI_GPCT_INVERT_CLOCK_SRC_BIT = 0x80000000 +# }; +# static inline unsigned NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(unsigned n) { /* NI 660x-specific */ +# return 0x10 + n; +# } +# static inline unsigned NI_GPCT_RTSI_CLOCK_SRC_BITS(unsigned n) { +# return 0x18 + n; +# } +# static inline unsigned NI_GPCT_PFI_CLOCK_SRC_BITS(unsigned n) { /* no pfi on NI 660x */ +# return 0x20 + n; +# } +# +# /* Possibilities for setting a gate source with +# INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters. +# May be bitwise-or'd with CR_EDGE or CR_INVERT. */ +# enum ni_gpct_gate_select { +# /* m-series gates */ +# NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0, +# NI_GPCT_AI_START2_GATE_SELECT = 0x12, +# NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT = 0x13, +# NI_GPCT_NEXT_OUT_GATE_SELECT = 0x14, +# NI_GPCT_AI_START1_GATE_SELECT = 0x1c, +# NI_GPCT_NEXT_SOURCE_GATE_SELECT = 0x1d, +# NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT = 0x1e, +# NI_GPCT_LOGIC_LOW_GATE_SELECT = 0x1f, +# /* more gates for 660x */ +# NI_GPCT_SOURCE_PIN_i_GATE_SELECT = 0x100, +# NI_GPCT_GATE_PIN_i_GATE_SELECT = 0x101, +# /* more gates for 660x "second gate" */ +# NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201, +# NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e, +# /* m-series "second gate" sources are unknown, +# we should add them here with an offset of 0x300 when known. */ +# NI_GPCT_DISABLED_GATE_SELECT = 0x8000, +# }; +# static inline unsigned NI_GPCT_GATE_PIN_GATE_SELECT(unsigned n) { +# return 0x102 + n; +# } +# static inline unsigned NI_GPCT_RTSI_GATE_SELECT(unsigned n) { +# return NI_USUAL_RTSI_SELECT(n); +# } +# static inline unsigned NI_GPCT_PFI_GATE_SELECT(unsigned n) { +# return NI_USUAL_PFI_SELECT(n); +# } +# static inline unsigned NI_GPCT_UP_DOWN_PIN_GATE_SELECT(unsigned n) { +# return 0x202 + n; +# } +# +# /* Possibilities for setting a source with +# INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters. */ +# enum ni_gpct_other_index { +# NI_GPCT_SOURCE_ENCODER_A, +# NI_GPCT_SOURCE_ENCODER_B, +# NI_GPCT_SOURCE_ENCODER_Z +# }; +# enum ni_gpct_other_select { +# /* m-series gates */ +# // Still unknown, probably only need NI_GPCT_PFI_OTHER_SELECT +# NI_GPCT_DISABLED_OTHER_SELECT = 0x8000, +# }; +# static inline unsigned NI_GPCT_PFI_OTHER_SELECT(unsigned n) { +# return NI_USUAL_PFI_SELECT(n); +# } +# +# /* start sources for ni general-purpose counters for use with +# INSN_CONFIG_ARM */ +# enum ni_gpct_arm_source { +# NI_GPCT_ARM_IMMEDIATE = 0x0, +# NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1, /* Start both the counter and the adjacent paired counter simultaneously */ +# /* NI doesn't document bits for selecting hardware arm triggers. If +# the NI_GPCT_ARM_UNKNOWN bit is set, we will pass the least significant +# bits (3 bits for 660x or 5 bits for m-series) through to the hardware. +# This will at least allow someone to figure out what the bits do later. */ +# NI_GPCT_ARM_UNKNOWN = 0x1000, +# }; +# +# /* digital filtering options for ni 660x for use with INSN_CONFIG_FILTER. */ +# enum ni_gpct_filter_select { +# NI_GPCT_FILTER_OFF = 0x0, +# NI_GPCT_FILTER_TIMEBASE_3_SYNC = 0x1, +# NI_GPCT_FILTER_100x_TIMEBASE_1 = 0x2, +# NI_GPCT_FILTER_20x_TIMEBASE_1 = 0x3, +# NI_GPCT_FILTER_10x_TIMEBASE_1 = 0x4, +# NI_GPCT_FILTER_2x_TIMEBASE_1 = 0x5, +# NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6 +# }; +# +# /* PFI digital filtering options for ni m-series for use with INSN_CONFIG_FILTER. */ +# enum ni_pfi_filter_select { +# NI_PFI_FILTER_OFF = 0x0, +# NI_PFI_FILTER_125ns = 0x1, +# NI_PFI_FILTER_6425ns = 0x2, +# NI_PFI_FILTER_2550us = 0x3 +# }; +# +# /* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */ +# enum ni_mio_clock_source { +# NI_MIO_INTERNAL_CLOCK = 0, +# NI_MIO_RTSI_CLOCK = 1, /* doesn't work for m-series, use NI_MIO_PLL_RTSI_CLOCK() */ +# /* the NI_MIO_PLL_* sources are m-series only */ +# NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2, +# NI_MIO_PLL_PXI10_CLOCK = 3, +# NI_MIO_PLL_RTSI0_CLOCK = 4 +# }; +# static inline unsigned NI_MIO_PLL_RTSI_CLOCK(unsigned rtsi_channel) { +# return NI_MIO_PLL_RTSI0_CLOCK + rtsi_channel; +# } +# +# /* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING. +# The numbers assigned are not arbitrary, they correspond to the bits required +# to program the board. */ +# enum ni_rtsi_routing { +# NI_RTSI_OUTPUT_ADR_START1 = 0, +# NI_RTSI_OUTPUT_ADR_START2 = 1, +# NI_RTSI_OUTPUT_SCLKG = 2, +# NI_RTSI_OUTPUT_DACUPDN = 3, +# NI_RTSI_OUTPUT_DA_START1 = 4, +# NI_RTSI_OUTPUT_G_SRC0 = 5, +# NI_RTSI_OUTPUT_G_GATE0 = 6, +# NI_RTSI_OUTPUT_RGOUT0 = 7, +# NI_RTSI_OUTPUT_RTSI_BRD_0 = 8, +# NI_RTSI_OUTPUT_RTSI_OSC = 12 /* pre-m-series always have RTSI clock on line 7 */ +# }; +# static inline unsigned NI_RTSI_OUTPUT_RTSI_BRD(unsigned n) { +# return NI_RTSI_OUTPUT_RTSI_BRD_0 + n; +# } +# +# /* Signals which can be routed to an NI PFI pin on an m-series board +# with INSN_CONFIG_SET_ROUTING. These numbers are also returned +# by INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though +# their routing cannot be changed. The numbers assigned are +# not arbitrary, they correspond to the bits required +# to program the board. */ +# enum ni_pfi_routing { +# NI_PFI_OUTPUT_PFI_DEFAULT = 0, +# NI_PFI_OUTPUT_AI_START1 = 1, +# NI_PFI_OUTPUT_AI_START2 = 2, +# NI_PFI_OUTPUT_AI_CONVERT = 3, +# NI_PFI_OUTPUT_G_SRC1 = 4, +# NI_PFI_OUTPUT_G_GATE1 = 5, +# NI_PFI_OUTPUT_AO_UPDATE_N = 6, +# NI_PFI_OUTPUT_AO_START1 = 7, +# NI_PFI_OUTPUT_AI_START_PULSE = 8, +# NI_PFI_OUTPUT_G_SRC0 = 9, +# NI_PFI_OUTPUT_G_GATE0 = 10, +# NI_PFI_OUTPUT_EXT_STROBE = 11, +# NI_PFI_OUTPUT_AI_EXT_MUX_CLK = 12, +# NI_PFI_OUTPUT_GOUT0 = 13, +# NI_PFI_OUTPUT_GOUT1 = 14, +# NI_PFI_OUTPUT_FREQ_OUT = 15, +# NI_PFI_OUTPUT_PFI_DO = 16, +# NI_PFI_OUTPUT_I_ATRIG = 17, +# NI_PFI_OUTPUT_RTSI0 = 18, +# NI_PFI_OUTPUT_PXI_STAR_TRIGGER_IN = 26, +# NI_PFI_OUTPUT_SCXI_TRIG1 = 27, +# NI_PFI_OUTPUT_DIO_CHANGE_DETECT_RTSI = 28, +# NI_PFI_OUTPUT_CDI_SAMPLE = 29, +# NI_PFI_OUTPUT_CDO_UPDATE = 30 +# }; +# static inline unsigned NI_PFI_OUTPUT_RTSI(unsigned rtsi_channel) { +# return NI_PFI_OUTPUT_RTSI0 + rtsi_channel; +# } +# +# /* Signals which can be routed to output on a NI PFI pin on a 660x board +# with INSN_CONFIG_SET_ROUTING. The numbers assigned are +# not arbitrary, they correspond to the bits required +# to program the board. Lines 0 to 7 can only be set to +# NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to +# NI_660X_PFI_OUTPUT_COUNTER. */ +# enum ni_660x_pfi_routing { +# NI_660X_PFI_OUTPUT_COUNTER = 1, // counter +# NI_660X_PFI_OUTPUT_DIO = 2, // static digital output +# }; +# +# /* NI External Trigger lines. These values are not arbitrary, but are related to +# the bits required to program the board (offset by 1 for historical reasons). */ +# static inline unsigned NI_EXT_PFI(unsigned pfi_channel) { +# return NI_USUAL_PFI_SELECT(pfi_channel) - 1; +# } +# static inline unsigned NI_EXT_RTSI(unsigned rtsi_channel) { +# return NI_USUAL_RTSI_SELECT(rtsi_channel) - 1; +# } +# +# /* status bits for INSN_CONFIG_GET_COUNTER_STATUS */ +# enum comedi_counter_status_flags { +# COMEDI_COUNTER_ARMED = 0x1, +# COMEDI_COUNTER_COUNTING = 0x2, +# COMEDI_COUNTER_TERMINAL_COUNT = 0x4, +# }; +# +# /* Clock sources for CDIO subdevice on NI m-series boards. +# Used as the scan_begin_arg for a comedi_command. These +# sources may also be bitwise-or'd with CR_INVERT to change polarity. */ +# enum ni_m_series_cdio_scan_begin_src { +# NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0, +# NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18, +# NI_CDIO_SCAN_BEGIN_SRC_AI_CONVERT = 19, +# NI_CDIO_SCAN_BEGIN_SRC_PXI_STAR_TRIGGER = 20, +# NI_CDIO_SCAN_BEGIN_SRC_G0_OUT = 28, +# NI_CDIO_SCAN_BEGIN_SRC_G1_OUT = 29, +# NI_CDIO_SCAN_BEGIN_SRC_ANALOG_TRIGGER = 30, +# NI_CDIO_SCAN_BEGIN_SRC_AO_UPDATE = 31, +# NI_CDIO_SCAN_BEGIN_SRC_FREQ_OUT = 32, +# NI_CDIO_SCAN_BEGIN_SRC_DIO_CHANGE_DETECT_IRQ = 33 +# }; +# static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) { +# return NI_USUAL_PFI_SELECT(pfi_channel); +# } +# static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_RTSI(unsigned +# rtsi_channel) { +# return NI_USUAL_RTSI_SELECT(rtsi_channel); +# } +# +# /* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command +# on NI boards. These scan begin sources can also be bitwise-or'd with +# CR_INVERT to change polarity. */ +# static inline unsigned NI_AO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) { +# return NI_USUAL_PFI_SELECT(pfi_channel); +# } +# static inline unsigned NI_AO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) { +# return NI_USUAL_RTSI_SELECT(rtsi_channel); +# } +# +# /* Bits for setting a clock source with +# * INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice. */ +# enum ni_freq_out_clock_source_bits { +# NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC, // 10 MHz +# NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC // 100 KHz +# }; +# +# /* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for +# * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ +# enum amplc_dio_clock_source { +# AMPLC_DIO_CLK_CLKN, /* per channel external clock +# input/output pin (pin is only an +# input when clock source set to this +# value, otherwise it is an output) */ +# AMPLC_DIO_CLK_10MHZ, /* 10 MHz internal clock */ +# AMPLC_DIO_CLK_1MHZ, /* 1 MHz internal clock */ +# AMPLC_DIO_CLK_100KHZ, /* 100 kHz internal clock */ +# AMPLC_DIO_CLK_10KHZ, /* 10 kHz internal clock */ +# AMPLC_DIO_CLK_1KHZ, /* 1 kHz internal clock */ +# AMPLC_DIO_CLK_OUTNM1, /* output of preceding counter channel +# (for channel 0, preceding counter +# channel is channel 2 on preceding +# counter subdevice, for first counter +# subdevice, preceding counter +# subdevice is the last counter +# subdevice) */ +# AMPLC_DIO_CLK_EXT /* per chip external input pin */ +# }; +# +# /* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for +# * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ +# enum amplc_dio_gate_source { +# AMPLC_DIO_GAT_VCC, /* internal high logic level */ +# AMPLC_DIO_GAT_GND, /* internal low logic level */ +# AMPLC_DIO_GAT_GATN, /* per channel external gate input */ +# AMPLC_DIO_GAT_NOUTNM2, /* negated output of counter channel +# minus 2 (for channels 0 or 1, +# channel minus 2 is channel 1 or 2 on +# the preceding counter subdevice, for +# the first counter subdevice the +# preceding counter subdevice is the +# last counter subdevice) */ +# AMPLC_DIO_GAT_RESERVED4, +# AMPLC_DIO_GAT_RESERVED5, +# AMPLC_DIO_GAT_RESERVED6, +# AMPLC_DIO_GAT_RESERVED7 +# }; diff --git a/pycomedi/_comedilib_h.pxd b/pycomedi/_comedilib_h.pxd new file mode 100644 index 0000000..962d66b --- /dev/null +++ b/pycomedi/_comedilib_h.pxd @@ -0,0 +1,200 @@ +# Copyright + +"Cython interface to comedilib.h" + +from _comedi_h cimport * + + +cdef extern from 'comedilib.h': + ctypedef struct comedi_t: + pass + + ctypedef struct comedi_range: + double min + double max + unsigned int unit + + comedi_t *comedi_open(char *fn) + int comedi_close(comedi_t *it) + + # logging + + int comedi_loglevel(int loglevel) + void comedi_perror(char *s) + char *comedi_strerror(int errnum) + int comedi_errno() + int comedi_fileno(comedi_t *it) + + # device queries + + int comedi_get_n_subdevices(comedi_t *it) + # COMEDI_VERSION_CODE handled by device.Device.get_version_code() + int comedi_get_version_code(comedi_t *it) + char *comedi_get_driver_name(comedi_t *it) + char *comedi_get_board_name(comedi_t *it) + int comedi_get_read_subdevice(comedi_t *dev) + int comedi_get_write_subdevice(comedi_t *dev) + + # subdevice queries + + int comedi_get_subdevice_type(comedi_t *it,unsigned int subdevice) + int comedi_find_subdevice_by_type(comedi_t *it,int type,unsigned int subd) + int comedi_get_subdevice_flags(comedi_t *it,unsigned int subdevice) + int comedi_get_n_channels(comedi_t *it,unsigned int subdevice) + int comedi_range_is_chan_specific(comedi_t *it,unsigned int subdevice) + int comedi_maxdata_is_chan_specific(comedi_t *it,unsigned int subdevice) + + # channel queries + + lsampl_t comedi_get_maxdata(comedi_t *it,unsigned int subdevice, + unsigned int chan) + int comedi_get_n_ranges(comedi_t *it,unsigned int subdevice, + unsigned int chan) + comedi_range * comedi_get_range(comedi_t *it,unsigned int subdevice, + unsigned int chan,unsigned int range) + int comedi_find_range(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int unit,double min,double max) + + # buffer queries + + int comedi_get_buffer_size(comedi_t *it,unsigned int subdevice) + int comedi_get_max_buffer_size(comedi_t *it,unsigned int subdevice) + int comedi_set_buffer_size(comedi_t *it,unsigned int subdevice, + unsigned int len) + + # low-level stuff + + int comedi_do_insnlist(comedi_t *it,comedi_insnlist *il) + int comedi_do_insn(comedi_t *it,comedi_insn *insn) + int comedi_lock(comedi_t *it,unsigned int subdevice) + int comedi_unlock(comedi_t *it,unsigned int subdevice) + + # synchronous stuff + + int comedi_data_read(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int range,unsigned int aref,lsampl_t *data) + int comedi_data_read_n(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int range,unsigned int aref,lsampl_t *data, unsigned int n) + int comedi_data_read_hint(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int range,unsigned int aref) + int comedi_data_read_delayed(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int range,unsigned int aref,lsampl_t *data, unsigned int nano_sec) + int comedi_data_write(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int range,unsigned int aref,lsampl_t data) + int comedi_dio_config(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int dir) + int comedi_dio_get_config(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int *dir) + int comedi_dio_read(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int *bit) + int comedi_dio_write(comedi_t *it,unsigned int subd,unsigned int chan, + unsigned int bit) + int comedi_dio_bitfield2(comedi_t *it,unsigned int subd, + unsigned int write_mask, unsigned int *bits, unsigned int base_channel) + + # streaming I/O (commands) + + int comedi_get_cmd_src_mask(comedi_t *dev,unsigned int subdevice, + comedi_cmd *cmd) + int comedi_get_cmd_generic_timed(comedi_t *dev,unsigned int subdevice, + comedi_cmd *cmd, unsigned chanlist_len, unsigned scan_period_ns) + int comedi_cancel(comedi_t *it,unsigned int subdevice) + int comedi_command(comedi_t *it,comedi_cmd *cmd) + int comedi_command_test(comedi_t *it,comedi_cmd *cmd) + int comedi_poll(comedi_t *dev,unsigned int subdevice) + + # buffer control + + int comedi_set_max_buffer_size(comedi_t *it, unsigned int subdev, + unsigned int max_size) + int comedi_get_buffer_contents(comedi_t *it, unsigned int subdev) + int comedi_mark_buffer_read(comedi_t *it, unsigned int subdev, + unsigned int bytes) + int comedi_mark_buffer_written(comedi_t *it, unsigned int subdev, + unsigned int bytes) + int comedi_get_buffer_offset(comedi_t *it, unsigned int subdev) + + # structs and functions used for parsing calibration files + + ctypedef struct comedi_caldac_t: + unsigned int subdevice + unsigned int channel + unsigned int value + + enum: COMEDI_MAX_NUM_POLYNOMIAL_COEFFICIENTS + + ctypedef struct comedi_polynomial_t: + double coefficients[COMEDI_MAX_NUM_POLYNOMIAL_COEFFICIENTS] + double expansion_origin + unsigned order + + ctypedef struct comedi_softcal_t: + comedi_polynomial_t *to_phys + comedi_polynomial_t *from_phys + + enum: CS_MAX_AREFS_LENGTH + + ctypedef struct comedi_calibration_setting_t: + unsigned int subdevice + unsigned int *channels + unsigned int num_channels + unsigned int *ranges + unsigned int num_ranges + unsigned int arefs[ CS_MAX_AREFS_LENGTH ] + unsigned int num_arefs + comedi_caldac_t *caldacs + unsigned int num_caldacs + comedi_softcal_t soft_calibration + + ctypedef struct comedi_calibration_t: + char *driver_name + char *board_name + comedi_calibration_setting_t *settings + unsigned int num_settings + + comedi_calibration_t* comedi_parse_calibration_file( + char *cal_file_path ) + int comedi_apply_parsed_calibration( comedi_t *dev, unsigned int subdev, + unsigned int channel, unsigned int range, unsigned int aref, + comedi_calibration_t *calibration ) + char* comedi_get_default_calibration_path( comedi_t *dev ) + void comedi_cleanup_calibration( comedi_calibration_t *calibration ) + int comedi_apply_calibration( comedi_t *dev, unsigned int subdev, + unsigned int channel, unsigned int range, unsigned int aref, + char *cal_file_path) + + # New stuff to provide conversion between integers and physical values that + # can support software calibrations. + enum comedi_conversion_direction: + COMEDI_TO_PHYSICAL, + COMEDI_FROM_PHYSICAL + int comedi_get_softcal_converter( + unsigned subdevice, unsigned channel, unsigned range, + comedi_conversion_direction direction, + comedi_calibration_t *calibration, + comedi_polynomial_t *polynomial) + int comedi_get_hardcal_converter( + comedi_t *dev, unsigned subdevice, unsigned channel, unsigned range, + comedi_conversion_direction direction, + comedi_polynomial_t *polynomial) + double comedi_to_physical(lsampl_t data, + comedi_polynomial_t *conversion_polynomial) + lsampl_t comedi_from_physical(double data, + comedi_polynomial_t *conversion_polynomial) + # + #int comedi_internal_trigger(comedi_t *dev, unsigned subd, unsigned trignum); + #/* INSN_CONFIG wrappers */ + #int comedi_arm(comedi_t *device, unsigned subdevice, unsigned source); + #int comedi_reset(comedi_t *device, unsigned subdevice); + #int comedi_get_clock_source(comedi_t *device, unsigned subdevice, unsigned channel, unsigned *clock, unsigned *period_ns); + #int comedi_get_gate_source(comedi_t *device, unsigned subdevice, unsigned channel, + # unsigned gate, unsigned *source); + #int comedi_get_routing(comedi_t *device, unsigned subdevice, unsigned channel, unsigned *routing); + #int comedi_set_counter_mode(comedi_t *device, unsigned subdevice, unsigned channel, unsigned mode_bits); + #int comedi_set_clock_source(comedi_t *device, unsigned subdevice, unsigned channel, unsigned clock, unsigned period_ns); + #int comedi_set_filter(comedi_t *device, unsigned subdevice, unsigned channel, unsigned filter); + #int comedi_set_gate_source(comedi_t *device, unsigned subdevice, unsigned channel, unsigned gate_index, unsigned gate_source); + #int comedi_set_other_source(comedi_t *device, unsigned subdevice, unsigned channel, + # unsigned other, unsigned source); + #int comedi_set_routing(comedi_t *device, unsigned subdevice, unsigned channel, unsigned routing); + #int comedi_get_hardware_buffer_size(comedi_t *device, unsigned subdevice, enum comedi_io_direction direction); diff --git a/pycomedi/_error.pyx b/pycomedi/_error.pyx new file mode 100644 index 0000000..003f0e1 --- /dev/null +++ b/pycomedi/_error.pyx @@ -0,0 +1,63 @@ +# Copyright + +"Useful error checking wrappers around Comedilib function calls" + +# No relative imports in Cython yet, see +# http://trac.cython.org/cython_trac/ticket/542 +from pycomedi import LOG as _LOG +from pycomedi import PyComediError as _PyComediError + +cimport _comedilib_h + + +def raise_error(function_name='', ret=None, error_msg=None): + """Report an error while executing a comedilib function + + >>> raise_error(function_name='myfn', ret=-1) + Traceback (most recent call last): + ... + PyComediError: myfn: Success (-1) + >>> raise_error(function_name='myfn', ret=-1, error_msg='some error') + Traceback (most recent call last): + ... + PyComediError: myfn (some error): Success (-1) + """ + errno = _comedilib_h.comedi_errno() + comedi_msg = _comedilib_h.comedi_strerror(errno) + #_comedilib_h.comedi_perror(function_name) + if error_msg: + msg = '%s (%s): %s (%s)' % (function_name, error_msg, comedi_msg, ret) + else: + msg = '%s: %s (%s)' % (function_name, comedi_msg, ret) + raise _PyComediError(msg) + + +def _comedi_getter(name, is_invalid): + # Hmm, cannot get function by name, or pass differing function pointers... + #def comedi_get(function_name, *args, **kwargs): + def comedi_get(function, *args, **kwargs): + if 'error_msg' in kwargs: + error_msg = kwargs.pop('error_msg') + else: + error_msg = 'error while running %s with %s and %s' % ( + function_name, args, kwargs) + #fn = getattr(_comedilib_h, function_name) + fn = function # workaround until I get getattr() working + function_name = function.__name__ + + _LOG.debug('calling %s with %s %s' % (function_name, args, kwargs)) + + ret = fn(*args, **kwargs) + _LOG.debug(' call to %s returned %s' % (function_name, ret)) + if is_invalid(ret): + raise_error( + error_msg=error_msg, function_name=function_name, ret=ret) + return ret + #comedi_get.__name__ = name + #comedi_get.__doc__ = ( + # "Execute Comedilib's `(*args, **kwargs)` safely.") + return comedi_get + +comedi_int = _comedi_getter('comedi_int', lambda ret: ret < 0) +comedi_ptr = _comedi_getter('comedi_ptr', lambda ret: ret == None) +comedi_tup = _comedi_getter('comedi_tup', lambda ret: ret[0] < 0) diff --git a/pycomedi/calibration.pxd b/pycomedi/calibration.pxd new file mode 100644 index 0000000..110f959 --- /dev/null +++ b/pycomedi/calibration.pxd @@ -0,0 +1,17 @@ +# Copyright + +"Expose `CalibratedConverter` internals at the C level for other Cython modules" + +cimport _comedilib_h + + +cdef class CalibratedConverter (object): + cdef _comedilib_h.comedi_polynomial_t _to_physical, _from_physical + + cdef _str_poly(self, _comedilib_h.comedi_polynomial_t polynomial) + cpdef to_physical(self, data) + cpdef from_physical(self, data) + cpdef get_to_physical_expansion_origin(self) + cpdef get_to_physical_coefficients(self) + cpdef get_from_physical_expansion_origin(self) + cpdef get_from_physical_coefficients(self) diff --git a/pycomedi/calibration.pyx b/pycomedi/calibration.pyx new file mode 100644 index 0000000..baa7a55 --- /dev/null +++ b/pycomedi/calibration.pyx @@ -0,0 +1,177 @@ +# Copyright + +"""Pythonic wrappers for converting between Comedilib and physical units + +For one-off conversions, use the functions `comedi_to_physical` and +`comedi_from_physical`. For repeated conversions, use an instance of +`CalibratedConverter`. +""" + +cimport numpy as _numpy +import numpy as _numpy + +cimport _comedi_h +cimport _comedilib_h +import constant as _constant + + +cdef void _setup_comedi_polynomial_t( + _comedilib_h.comedi_polynomial_t *p, coefficients, expansion_origin): + """Setup the `comedi_polynomial_t` at `p` + + * `coefficients` is an iterable containing polynomial coefficients + * `expansion_origin` is the center of the polynomial expansion + """ + for i,x in enumerate(coefficients): + p.coefficients[i] = x + p.order = len(coefficients)-1 + p.expansion_origin = expansion_origin + +cdef object _convert( + _comedilib_h.comedi_polynomial_t *p, object data, object direction): + """Apply the polynomial conversion `p` to `data`. + + `direction` should be a value from `constant.CONVERSION_DIRECTION`. + """ + to_physical = (_constant.bitwise_value(direction) + == _constant.CONVERSION_DIRECTION.to_physical.value) + if _numpy.isscalar(data): + if to_physical: + return _comedilib_h.comedi_to_physical(data, p) + else: + return _comedilib_h.comedi_from_physical(data, p) + if to_physical: + dtype = _numpy.double + else: + dtype = _numpy.uint + array = _numpy.array(data, dtype=dtype) + for i,d in enumerate(data): + if to_physical: + array[i] = _comedilib_h.comedi_to_physical(d, p) + else: + array[i] = _comedilib_h.comedi_from_physical(d, p) + return array + +cpdef comedi_to_physical(data, coefficients, expansion_origin): + """Convert Comedi bit values (`lsampl_t`) to physical units (`double`) + + * `data` is the value to be converted (scalar or array-like) + * `coefficients` and `expansion_origin` should be appropriate + for `_setup_comedi_polynomial_t`. TODO: expose it's docstring? + + The conversion algorithm is:: + + x = sum_i c_i * (d-d_o)^i + + where `x` is the returned physical value, `d` is the supplied data, + `c_i` is the `i`\th coefficient, and `d_o` is the expansion origin. + + >>> print comedi_to_physical.__doc__ # doctest: +ELLIPSIS + Convert Comedi bit values (`lsampl_t`) to physical units (`double`) + ... + >>> comedi_to_physical(1, [1, 2, 3], 2) + 2.0 + >>> comedi_to_physical([1, 2, 3], [1, 2, 3], 2) + array([ 2., 1., 6.]) + """ + cdef _comedilib_h.comedi_polynomial_t p + _setup_comedi_polynomial_t(&p, coefficients, expansion_origin) + return _convert(&p, data, _constant.CONVERSION_DIRECTION.to_physical) + +cpdef comedi_from_physical(data, coefficients, expansion_origin): + """Convert physical units to Comedi bit values + + Like `comedi_to_physical` but converts `double` -> `lsampl_t`. + + >>> comedi_from_physical(1, [1,2,3], 2) + 2L + >>> comedi_from_physical([1, 2, 3], [1, 2, 3], 2) + array([2, 1, 6], dtype=uint32) + """ + cdef _comedilib_h.comedi_polynomial_t p + _setup_comedi_polynomial_t(&p, coefficients, expansion_origin) + return _convert(&p, data, _constant.CONVERSION_DIRECTION.from_physical) + + +cdef class CalibratedConverter (object): + """Apply a converion polynomial + + Usually you would get the this converter from + `DataChannel.get_converter()` or similar. but for testing, we'll + just create one out of thin air. + + >>> c = CalibratedConverter( + ... to_physical_coefficients=[1, 2, 3], + ... to_physical_expansion_origin=1) + >>> c # doctest: +NORMALIZE_WHITESPACE + + + >>> c.to_physical(1) + 1.0 + >>> c.to_physical([0, 1, 2]) + array([ 2., 1., 6.]) + >>> c.to_physical(_numpy.array([0, 1, 2, 3], dtype=_numpy.uint)) + array([ 2., 1., 6., 17.]) + + >>> c.get_to_physical_expansion_origin() + 1.0 + >>> c.get_to_physical_coefficients() + array([ 1., 2., 3.]) + """ + def __init__(self, to_physical_coefficients=None, + to_physical_expansion_origin=0, + from_physical_coefficients=None, + from_physical_expansion_origin=0): + if to_physical_coefficients: + _setup_comedi_polynomial_t( + &self._to_physical, to_physical_coefficients, + to_physical_expansion_origin) + if from_physical_coefficients: + _setup_comedi_polynomial_t( + &self._from_physical, from_physical_coefficients, + from_physical_expansion_origin) + + cdef _str_poly(self, _comedilib_h.comedi_polynomial_t polynomial): + return '{coefficients:%s origin:%s}' % ( + [float(polynomial.coefficients[i]) + for i in range(polynomial.order+1)], + float(polynomial.expansion_origin)) + + def __str__(self): + return '<%s to_physical:%s from_physical:%s>' % ( + self.__class__.__name__, self._str_poly(self._to_physical), + self._str_poly(self._from_physical)) + + def __repr__(self): + return self.__str__() + + cpdef to_physical(self, data): + return _convert(&self._to_physical, data, + _constant.CONVERSION_DIRECTION.to_physical) + + cpdef from_physical(self, data): + return _convert(&self._from_physical, data, + _constant.CONVERSION_DIRECTION.from_physical) + + cpdef get_to_physical_expansion_origin(self): + return self._to_physical.expansion_origin + + cpdef get_to_physical_coefficients(self): + ret = _numpy.ndarray((self._to_physical.order+1,), _numpy.double) + for i in xrange(len(ret)): + ret[i] = self._to_physical.coefficients[i] + return ret + + cpdef get_from_physical_expansion_origin(self): + return self._from_physical.expansion_origin + + cpdef get_from_physical_coefficients(self): + ret = _numpy.ndarray((self._from_physical.order+1,), _numpy.double) + for i in xrange(len(ret)): + ret[i] = self._from_physical.coefficients[i] + return ret + + +# TODO: see comedi_caldac_t and related at end of comedilib.h diff --git a/pycomedi/channel.pyx b/pycomedi/channel.pyx new file mode 100644 index 0000000..c30de49 --- /dev/null +++ b/pycomedi/channel.pyx @@ -0,0 +1,410 @@ +# Copyright + +"Wrap channel-wide Comedi functions in `Channel` and related classes" + +cimport numpy as _numpy +import numpy as _numpy + +cimport _comedi_h +cimport _comedilib_h +from calibration cimport CalibratedConverter as _CalibratedConverter +from range cimport Range as _Range +from subdevice cimport Subdevice as _Subdevice + +from pycomedi import LOG as _LOG +from chanspec import ChanSpec as _ChanSpec +from pycomedi import PyComediError as _PyComediError +import _error +import constant as _constant + + +cdef class Channel (object): + """Class bundling channel-related functions + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + >>> s = d.get_read_subdevice() + >>> c = s.channel(0) + + >>> c.get_maxdata() + 65535L + >>> c.get_n_ranges() + 16 + >>> c.get_range(0) + + >>> c.find_range(constant.UNIT.volt, 0, 5) + + + >>> d.close() + """ + cdef public _Subdevice subdevice + cdef public int index + + def __cinit__(self): + self.index = -1 + + def __init__(self, subdevice, index): + super(Channel, self).__init__() + self.subdevice = subdevice + self.index = index + + def get_maxdata(self): + ret = _comedilib_h.comedi_get_maxdata( + self.subdevice.device.device, + self.subdevice.index, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_maxdata', ret=ret) + return ret + + def get_n_ranges(self): + ret = _comedilib_h.comedi_get_n_ranges( + self.subdevice.device.device, + self.subdevice.index, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_n_ranges', ret=ret) + return ret + + cdef _get_range(self, index): + cdef _comedilib_h.comedi_range *rng + cdef _Range ret + # Memory pointed to by the return value is freed on Device.close(). + rng = _comedilib_h.comedi_get_range( + self.subdevice.device.device, + self.subdevice.index, self.index, index) + if rng is NULL: + _error.raise_error(function_name='comedi_get_range') + ret = _Range(value=index) + ret.set_comedi_range(rng[0]) + # rng[0] is a sneaky way to dereference rng, since Cython + # doesn't support *rng. + return ret + + def get_range(self, index): + return self._get_range(index) + + def _find_range(self, unit, min, max): + "Search for range" + ret = _comedilib_h.comedi_find_range( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(unit), min, max) + if ret < 0: + _error.raise_error(function_name='comedi_find_range', ret=ret) + return ret + + def find_range(self, unit, min, max): + """Search for range + + `unit` should be an item from `constants.UNIT`. + """ + return self.get_range(self._find_range(unit, min, max)) + + +cdef class DigitalChannel (Channel): + """Channel configured for reading or writing digital data. + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio) + >>> c = s.channel(0, factory=DigitalChannel) + + >>> c.get_maxdata() + 1L + >>> c.get_n_ranges() + 1 + >>> c.get_range(0) + + + >>> direction = c.dio_get_config() + >>> direction # doctest: +SKIP + <_NamedInt input> + + >>> c.dio_config(_constant.IO_DIRECTION.input) + >>> data = c.dio_read() + >>> data + 1 + + >>> c.dio_config(_constant.IO_DIRECTION.output) + >>> c.dio_write(1) + + >>> c.dio_config(direction) + + >>> d.close() + """ + def dio_config(self, dir): + """Change input/output properties + + `dir` should be an item from `constants.IO_DIRECTION`. + """ + ret = _comedilib_h.comedi_dio_config( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(dir)) + if ret < 0: + _error.raise_error(function_name='comedi_dio_config', ret=ret) + + def dio_get_config(self): + """Query input/output properties + + Return an item from `constant.IO_DIRECTION`. + """ + cpdef unsigned int dir + ret = _comedilib_h.comedi_dio_get_config( + self.subdevice.device.device, + self.subdevice.index, self.index, &dir) + if ret < 0: + _error.raise_error(function_name='comedi_dio_get_config', ret=ret) + return _constant.IO_DIRECTION.index_by_value(dir) + + def dio_read(self): + "Read a single bit" + cpdef unsigned int bit + ret = _comedilib_h.comedi_dio_read( + self.subdevice.device.device, + self.subdevice.index, self.index, &bit) + if ret < 0: + _error.raise_error(function_name='comedi_dio_read', ret=ret) + return int(bit) + + def dio_write(self, bit): + "Write a single bit" + ret = _comedilib_h.comedi_dio_write( + self.subdevice.device.device, + self.subdevice.index, self.index, bit) + if ret < 0: + _error.raise_error(function_name='comedi_dio_write', ret=ret) + + +cdef class AnalogChannel (Channel): + """Channel configured for reading or writing analog data. + + `range` should be a `Range` instance, `aref` should be an + `constants.AREF` instance. If not specified, defaults are chosen + based on the capabilities of the subdevice. + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + >>> s = d.get_read_subdevice() + >>> c = s.channel(0, factory=AnalogChannel) + + >>> c.range + + >>> c.aref + <_NamedInt ground> + + >>> data = c.data_read() + >>> data # doctest: +SKIP + 32670L + >>> converter = c.get_converter() + >>> converter # doctest: +NORMALIZE_WHITESPACE + + >>> physical_data = converter.to_physical(data) + >>> physical_data # doctest: +SKIP + -0.029755092698558021 + >>> converter.from_physical(physical_data) == data + True + + >>> data = c.data_read_n(5) + >>> data # doctest: +SKIP + array([32674, 32673, 32674, 32672, 32675], dtype=uint32) + + >>> c.data_read_hint() + >>> c.data_read() # doctest: +SKIP + 32672L + + >>> data = c.data_read_delayed(nano_sec=1e3) + >>> data # doctest: +SKIP + 32672L + + >>> s = d.get_write_subdevice() + >>> c = s.channel(0, factory=AnalogChannel) + + >>> converter = c.get_converter() + >>> converter # doctest: +NORMALIZE_WHITESPACE + + + >>> c.data_write(converter.from_physical(0)) + + >>> d.close() + + Even after the device is closed, the range information is + retained. + + >>> c.range + + """ + cdef public _Range range + cdef public object aref + + def __init__(self, range=None, aref=None, **kwargs): + super(AnalogChannel, self).__init__(**kwargs) + if range == None: + range = self.get_range(0) + self.range = range + if aref == None: + flags = self.subdevice.get_flags() + for ar in _constant.AREF: + if getattr(flags, ar.name): + aref = ar + break + raise _PyComediError( + '%s does not support any known analog reference type (%s)' + % (self.subdevice, flags)) + self.aref = aref + + # syncronous stuff + + def data_read(self): + "Read one sample" + cdef _comedi_h.lsampl_t data + ret = _comedilib_h.comedi_data_read( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(self.aref), + &data) + if ret < 0: + _error.raise_error(function_name='comedi_data_read', ret=ret) + return data + + def data_read_n(self, n): + "Read `n` samples (timing between samples is undefined)." + data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32) + ret = _comedilib_h.comedi_data_read_n( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(self.aref), + <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n) + if ret < 0: + _error.raise_error(function_name='comedi_data_read_n', ret=ret) + return data + + def data_read_hint(self): + """Tell driver which channel/range/aref you will read next + + Used to prepare an analog input for a subsequent call to + comedi_data_read. It is not necessary to use this function, + but it can be useful for eliminating inaccuaracies caused by + insufficient settling times when switching the channel or gain + on an analog input. This function sets an analog input to the + channel, range, and aref specified but does not perform an + actual analog to digital conversion. + + Alternatively, one can simply use `.data_read_delayed()`, + which sets up the input, pauses to allow settling, then + performs a conversion. + """ + ret = _comedilib_h.comedi_data_read_hint( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(self.aref)) + if ret < 0: + _error.raise_error(function_name='comedi_data_read_hint', ret=ret) + + def data_read_delayed(self, nano_sec=0): + """Read single sample after delaying specified settling time. + + Although the settling time is specified in integer + nanoseconds, the actual settling time will be rounded up to + the nearest microsecond. + """ + cdef _comedi_h.lsampl_t data + ret = _comedilib_h.comedi_data_read_delayed( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(self.aref), + &data, int(nano_sec)) + if ret < 0: + _error.raise_error(function_name='comedi_data_read_delayed', + ret=ret) + return data + + def data_write(self, data): + """Write one sample + + Returns 1 (the number of data samples written). + """ + ret = _comedilib_h.comedi_data_write( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(self.aref), + int(data)) + if ret != 1: + _error.raise_error(function_name='comedi_data_write', ret=ret) + + def chanspec(self): + return _ChanSpec(chan=self.index, range=self.range, aref=self.aref) + + + cdef _comedilib_h.comedi_polynomial_t get_softcal_converter( + self, direction, calibration): + """ + + `direction` should be a value from `constant.CONVERSION_DIRECTION`. + """ + cdef _comedilib_h.comedi_polynomial_t poly + #rc = _comedilib_h.comedi_get_softcal_converter( + # self.subdevice.device.device, + # self.subdevice.index, self.index, + # _constant.bitwise_value(self.range), + # _constant.bitwise_value(direction), + # calibration, &poly) + #if rc < 0: + # _error.raise_error(function_name='comedi_get_softcal_converter', + # ret=rc) + return poly + + cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter( + self, direction): + """ + + `direction` should be a value from `constant.CONVERSION_DIRECTION`. + """ + cdef _comedilib_h.comedi_polynomial_t poly + rc = _comedilib_h.comedi_get_hardcal_converter( + self.subdevice.device.device, + self.subdevice.index, self.index, + _constant.bitwise_value(self.range), + _constant.bitwise_value(direction), &poly) + if rc < 0: + _error.raise_error(function_name='comedi_get_hardcal_converter', + ret=rc) + return poly + + cdef _get_converter(self, calibration): + cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical + cdef _CalibratedConverter ret + flags = self.subdevice.get_flags() + if flags.soft_calibrated: + #if calibration is None: + # calibration = self.subdevice.device.parse_calibration() + raise NotImplementedError() + else: + to_physical = self.get_hardcal_converter( + _constant.CONVERSION_DIRECTION.to_physical) + from_physical = self.get_hardcal_converter( + _constant.CONVERSION_DIRECTION.from_physical) + ret = _CalibratedConverter() + ret._to_physical = to_physical + ret._from_physical = from_physical + return ret + + def get_converter(self, calibration=None): + return self._get_converter(calibration) diff --git a/pycomedi/chanspec.pyx b/pycomedi/chanspec.pyx new file mode 100644 index 0000000..6c8d001 --- /dev/null +++ b/pycomedi/chanspec.pyx @@ -0,0 +1,69 @@ +# Copyright + +"Replace Comedi's CR_PACK and related macros with a Python class" + +cimport _comedi_h +import constant as _constant + + +class ChanSpec (_constant.BitwiseOperator): + """Channel specification bitfield + + >>> from .constant import AREF, CR + >>> c = ChanSpec(chan=1, range=3, aref=AREF.diff, + ... flags=CR.edge|CR.invert) + >>> print c + + >>> c.chan + 1L + >>> c.chan = 2 + >>> c.chan + 2L + """ + _fields = ['chan', 'range', 'aref', 'flags'] + _all = 0xffffffffL + + def __init__(self, chan=0, range=0, aref=0, flags=0): + self.value = 0L + self.chan = chan + self.range = range + self.aref = aref + self.flags = flags + + def __str__(self): + # TODO: consolidate to a utility class or function + fields = ['%s:%s' % (f, getattr(self, f)) for f in self._fields] + return '<%s %s>' % (self.__class__.__name__, ' '.join(fields)) + + def __repr__(self): + return self.__str__() + + def _chan_get(self): + return self.value & 0xff + def _chan_set(self, value): + self.value &= self._all - 0xff + self.value |= _constant.bitwise_value(value) & 0xff + chan = property(fget=_chan_get, fset=_chan_set) + + def _range_get(self): + return (self.value >> 16) & 0xff + def _range_set(self, value): + self.value &= self._all - (0xff << 16) + self.value |= (_constant.bitwise_value(value) & 0xff) << 16 + range = property(fget=_range_get, fset=_range_set) + + def _aref_get(self): + v = (self.value >> 24) & 0x03 + return _constant.AREF.index_by_value(v) + def _aref_set(self, value): + self.value &= self._all - (0x03 << 24) + self.value |= (_constant.bitwise_value(value) & 0x03) << 24 + aref = property(fget=_aref_get, fset=_aref_set) + + def _flags_get(self): + v = self.value & _constant.CR._all.value + return _constant.FlagValue(_constant.CR, v) + def _flags_set(self, value): + self.value &= self._all - _constant.CR._all.value + self.value |= _constant.bitwise_value(value) & _constant.CR._all.value + flags = property(fget=_flags_get, fset=_flags_set) diff --git a/pycomedi/classes.py b/pycomedi/classes.py deleted file mode 100644 index 7a5c14d..0000000 --- a/pycomedi/classes.py +++ /dev/null @@ -1,775 +0,0 @@ -# Copyright (C) 2010-2011 W. Trevor King -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"Object oriented wrappers around the comedi module." - -import os as _os - -import comedi as _comedi -import numpy as _numpy - -from . import LOG as _LOG -from . import PyComediError as _PyComediError -from . import constants as _constants - - -def _comedi_arg(arg): - "Replace arguments with their comedilib value." - if isinstance(arg, _constants._BitwiseOperator): - return arg.value - elif isinstance(arg, Command): - _LOG.debug(str(arg)) - return arg.cmd - elif isinstance(arg, Chanlist): - return arg.chanlist() - return arg - -def _comedi_getter(name, is_invalid): - def comedi_get(function_name, *args, **kwargs): - if 'error_msg' in kwargs: - error_msg = kwargs.pop('error_msg') - else: - error_msg = 'error while running %s with %s and %s' % ( - function_name, args, kwargs) - fn = getattr(_comedi, function_name) - - _LOG.debug('calling %s with %s %s' % (function_name, args, kwargs)) - - args = list(args) - for i,arg in enumerate(args): - args[i] = _comedi_arg(arg) - for key,value in kwargs.iteritems(): - kwargs[key] = _comedi_arg(value) - - ret = fn(*args, **kwargs) - _LOG.debug(' call to %s returned %s' % (function_name, ret)) - if is_invalid(ret): - errno = _comedi.comedi_errno() - comedi_msg = _comedi.comedi_strerror(errno) - _comedi.comedi_perror(function_name) - raise _PyComediError('%s: %s (%s)' % (error_msg, comedi_msg, ret)) - return ret - comedi_get.__name__ = name - comedi_get.__doc__ = ( - 'Execute `comedi.(*args, **kwargs)` safely.') - return comedi_get - -_comedi_int = _comedi_getter('comedi_int', lambda ret: ret < 0) -_comedi_ptr = _comedi_getter('comedi_ptr', lambda ret: ret == None) -_comedi_tup = _comedi_getter('comedi_tup', lambda ret: ret[0] < 0) - - -def _cache(method): - def wrapper(self, *args, **kwargs): - key = (method.__name__, args, str(kwargs)) - if key not in self._cache: - self._cache[key] = method(self, *args, **kwargs) - return self._cache[key] - wrapper.__name__ = method.__name__ - wrapper.__doc__ = method.__doc__ - return wrapper - - -class CacheObject (object): - """An object that caches return values for decoreated merthods. - - >>> class A (CacheObject): - ... @_cache - ... def double(self, x): - ... print 'calculating 2*%d' % x - ... return x*2 - >>> a = A() - >>> a.double(2) - calculating 2*2 - 4 - >>> a.double(2) - 4 - >>> a.double(3) - calculating 2*3 - 6 - >>> a.double(3) - 6 - >>> print sorted(a._cache.keys()) - [('double', (2,), '{}'), ('double', (3,), '{}')] - """ - def __init__(self): - self.clear_cache() - - def clear_cache(self): - self._cache = {} - - -class Channel (CacheObject): - def __init__(self, subdevice, index): - super(Channel, self).__init__() - self._subdevice = subdevice - self._index = index - - @_cache - def get_maxdata(self): - return _comedi_int( - 'comedi_get_maxdata', self._subdevice._device._device, - self._subdevice._index, self._index) - - @_cache - def get_n_ranges(self): - return _comedi_int( - 'comedi_get_n_ranges', self._subdevice._device._device, - self._subdevice._index, self._index) - - def get_range(self, index): - r = _comedi_ptr( - 'comedi_get_range', self._subdevice._device._device, - self._subdevice._index, self._index, index) - return Range(index=index, range=r) - - @_cache - def _find_range(self, unit, min, max): - "Search for range" - return _comedi_int( - 'comedi_find_range', self._subdevice._device._device, - self._subdevice._index, self._index, unit.value, min, max) - - def find_range(self, unit, min, max): - """Search for range - - `unit` should be an item from `constants.UNIT`. - """ - return self.get_range(self._find_range(unit, min, max)) - - -class Subdevice (CacheObject): - def __init__(self, device, index): - super(Subdevice, self).__init__() - self._device = device - self._index = index - - @_cache - def get_type(self): - "Type of subdevice (from `SUBDEVICE_TYPE`)" - _type = _comedi_int( - 'comedi_get_subdevice_type', self._device._device, self._index) - return _constants.SUBDEVICE_TYPE.index_by_value(_type) - - @_cache - def _get_flags(self): - "Subdevice flags" - return _comedi_int( - 'comedi_get_subdevice_flags', self._device._device, self._index) - - def get_flags(self): - "Subdevice flags (an `SDF` `FlagValue`)" - return _constants.FlagValue( - _constants.SDF, self._get_flags()) - - @_cache - def n_channels(self): - "Number of subdevice channels" - return _comedi_int( - 'comedi_get_n_channels', self._device._device, self._index) - - @_cache - def range_is_chan_specific(self): - return _comedi_int( - 'comedi_range_is_chan_specific', self._device._device, self._index) - - @_cache - def maxdata_is_chan_specific(self): - return _comedi_int( - 'comedi_maxdata_is_chan_specific', - self._device._device, self._index) - - def lock(self): - "Reserve the subdevice" - _comedi_int('comedi_lock', self._device._device, self._index) - - def unlock(self): - "Release the subdevice" - _comedi_int('comedi_unlock', self._device._device, self._index) - - def dio_bitfield(self, bits=0, write_mask=0, base_channel=0): - """Read/write multiple digital channels. - - `bits` and `write_mask` are bit fields with the least - significant bit representing channel `base_channel`. - - Returns a bit field containing the read value of all input - channels and the last written value of all output channels. - """ - rc,bits = _comedi_tup( - 'comedi_dio_bitfield2', self._device._device, - self._index, write_mask, bits, base_channel) - return bits - - # extensions to make a more idomatic Python interface - - def insn(self): - insn = self._device.insn() - insn.subdev = self._index - return insn - - def channel(self, index, factory=Channel, **kwargs): - "`Channel` instance for the `index`\ed channel." - return factory(subdevice=self, index=index, **kwargs) - - -class Device (CacheObject): - "Class bundling device-related functions." - def __init__(self, filename): - super(Device, self).__init__() - self.filename = filename - self._device = None - self.file = None - - def open(self): - "Open device" - self._device = _comedi_ptr('comedi_open', self.filename) - self.file = _os.fdopen(self.fileno(), 'r+') - self.clear_cache() - - def close(self): - "Close device" - self.file.flush() - self.file.close() - _comedi_int('comedi_close', self._device) - self._device = None - self.file = None - self.clear_cache() - - @_cache - def fileno(self): - "File descriptor for this device" - return _comedi_int('comedi_fileno', self._device) - - @_cache - def get_n_subdevices(self): - "Number of subdevices" - self._cache - return _comedi_int('comedi_get_n_subdevices', self._device) - - @_cache - def get_version_code(self): - """Comedi version code. - - This is a kernel-module level property, but a valid device is - necessary to communicate with the kernel module. - - Returns a tuple of version numbers, e.g. `(0, 7, 61)`. - """ - version = _comedi_int('comedi_get_version_code', self._device) - ret = [] - for i in range(3): - ret.insert(0, version & (2**8-1)) - version >>= 2**8 # shift over 8 bits - return tuple(ret) - - @_cache - def get_driver_name(self): - "Comedi driver name" - return _comedi_ptr('get_driver_name', self._device) - - @_cache - def get_board_name(self): - "Comedi board name" - return _comedi_ptr('get_board_name', self._device) - - @_cache - def _get_read_subdevice(self): - "Find streaming input subdevice index" - return _comedi_int('comedi_get_read_subdevice', self._device) - - def get_read_subdevice(self, **kwargs): - "Find streaming input subdevice" - return self.subdevice(self._get_read_subdevice(), **kwargs) - - @_cache - def _get_write_subdevice(self): - "Find streaming output subdevice index" - return _comedi_int('comedi_get_write_subdevice', self._device) - - def _get_write_subdevice(self, **kwargs): - "Find streaming output subdevice" - return self.subdevice(self._get_write_subdevice(), **kwargs) - - @_cache - def _find_subdevice_by_type(self, subdevice_type): - "Search for a subdevice index for type `subdevice_type`)." - return _comedi_int( - 'comedi_find_subdevice_by_type', - self._device, subdevice_type.value, 0) # 0 is starting subdevice - - def find_subdevice_by_type(self, subdevice_type, **kwargs): - """Search for a subdevice by type `subdevice_type`)." - - `subdevice_type` should be an item from `constants.SUBDEVICE_TYPE`. - """ - return self.subdevice( - self._find_subdevice_by_type(subdevice_type), **kwargs) - - def do_insnlist(self, insnlist): - """Perform multiple instructions - - Returns the number of successfully completed instructions. - """ - return _comedi_int('comedi_do_insn', self._device, insn) - - def do_insn(self, insn): - """Preform a single instruction. - - Returns an instruction-specific integer. - """ - return _comedi_int('comedi_do_insn', self._device, insn) - - def get_default_calibration_path(self): - "The default calibration path for this device" - assert self._device != None, ( - 'Must call get_default_calibration_path on an open device.') - return _comedi_ptr('comedi_get_default_calibration_path', self._device) - - # extensions to make a more idomatic Python interface - - def insn(self): - return _comedi.comedi_insn_struct() - - def subdevices(self, **kwargs): - "Iterate through all available subdevices." - for i in range(self.n_subdevices): - yield self.subdevice(i, **kwargs) - - def subdevice(self, index, factory=Subdevice, **kwargs): - return factory(device=self, index=index, **kwargs) - - -class Range (object): - def __init__(self, index, range): - self.index = index - self.range = range - - def __getattr__(self, name): - return getattr(self.range, name) - - -class Command (object): - """Wrap `comedi.comedi_cmd` with a nicer interface. - - Examples - -------- - - >>> from .utility import set_cmd_chanlist, set_cmd_data - >>> CMDF = _constants.CMDF - >>> TRIG_SRC = _constants.TRIG_SRC - >>> c = Command() - >>> c.subdev = 1 - >>> c.flags = CMDF.priority | CMDF.write - >>> c.start_src = TRIG_SRC.int | TRIG_SRC.now - >>> c.scan_begin_src = TRIG_SRC.timer - >>> c.scan_begin_arg = 10 - >>> c.scan_convert_src = TRIG_SRC.now - >>> c.scan_end_src = TRIG_SRC.count - >>> c.scan_end_arg = 4 - >>> c.stop_src = TRIG_SRC.none - >>> set_cmd_chanlist(c, []) - >>> set_cmd_data(c, [1,2,3]) - >>> print c # doctest: +ELLIPSIS, +REPORT_UDIFF - Comedi command: - subdev : 1 - flags : priority|write - start_src : now|int - start_arg : 0 - scan_begin_src : timer - scan_begin_arg : 10 - convert_src : - - convert_arg : 0 - scan_end_src : count - scan_end_arg : 4 - stop_src : none - stop_arg : 0 - chanlist : - chanlist_len : 0 - data : - data_len : 3 - """ - _str_fields = [ - 'subdev', 'flags', 'start_src', 'start_arg', 'scan_begin_src', - 'scan_begin_arg', 'convert_src', 'convert_arg', 'scan_end_src', - 'scan_end_arg', 'stop_src', 'stop_arg', 'chanlist', 'chanlist_len', - 'data', 'data_len'] - - def __init__(self): - self.cmd = _comedi.comedi_cmd_struct() - - def _get_flag_field(self, name, flag): - f = _constants.FlagValue(flag, getattr(self.cmd, name)) - return f - - def get_flags(self): - return self._get_flag_field('flags', _constants.CMDF) - - def get_trigger_field(self, name): - return self._get_flag_field(name, _constants.TRIG_SRC) - - def __str__(self): - values = {} - for f in self._str_fields: - if f.endswith('_src'): - values[f] = str(self.get_trigger_field(f)) - elif f == 'flags': - values[f] = str(self.get_flags()) - else: - values[f] = getattr(self, f) - max_len = max([len(f) for f in self._str_fields]) - lines = ['%*s : %s' % (max_len, f, values[f]) - for f in self._str_fields] - return 'Comedi command:\n %s' % '\n '.join(lines) - - def __setattr__(self, name, value): - if name == 'cmd': - return super(Command, self).__setattr__(name, value) - return setattr(self.cmd, name, _comedi_arg(value)) - - def __getattr__(self, name): - return getattr(self.cmd, name) # TODO: lookup _NamedInt? - - -class DataChannel (Channel): - """Channel configured for reading data. - - `range` should be a `Range` instance, `aref` should be an - `constants.AREF` instance, - """ - def __init__(self, range=None, aref=None, **kwargs): - super(DataChannel, self).__init__(**kwargs) - self.range = range - self.aref = aref - - # syncronous stuff - - def data_read(self): - "Read one sample" - read,data = _comedi_tup( - 'comedi_data_read', self._subdevice._device._device, - self._subdevice._index, self._index, self.range.index, self.aref) - return data - - def data_read_n(self, n): - "Read `n` samples (timing between samples is undefined)." - read,data = _comedi_tup( - 'comedi_data_read', self._subdevice._device._device, - self._subdevice._index, self._index, self.range.index, self.aref, - n) - return data - - def data_read_hint(self): - """Tell driver which channel/range/aref you will read next - - Used to prepare an analog input for a subsequent call to - comedi_data_read. It is not necessary to use this function, - but it can be useful for eliminating inaccuaracies caused by - insufficient settling times when switching the channel or gain - on an analog input. This function sets an analog input to the - channel, range, and aref specified but does not perform an - actual analog to digital conversion. - - Alternatively, one can simply use `.data_read_delayed()`, which - sets up the input, pauses to allow settling, then performs a - conversion. - """ - _comedi_int( - 'comedi_data_read_hint', self._subdevice._device._device, - self._subdevice._index, self._index, self.range.index, - self.aref.value) - - def data_read_delayed(self, nano_sec=0): - """Read single sample after delaying specified settling time. - - Although the settling time is specified in integer - nanoseconds, the actual settling time will be rounded up to - the nearest microsecond. - """ - read,data = _comedi_tup( - 'comedi_data_read_delayed', self._subdevice._device._device, - self._subdevice._index, self._index, self.range.index, - self.aref.value, int(nano_sec)) - return data - - def data_write(self, data): - "Write one sample" - written = _comedi_int( - 'comedi_data_write', self._subdevice._device._device, - self._subdevice._index, self._index, self.range.index, self.aref, - int(data)) - return written - - def dio_config(self, dir): - """Change input/output properties - - `dir` should be an item from `constants.IO_DIRECTION`. - """ - _comedi_int( - 'comedi_dio_config', self._subdevice._device._device, - self._subdevice._index, self._index, dir) - - def _dio_get_config(self): - "Query input/output properties" - return _comedi_int( - 'comedi_dio_get_config', self._subdevice._device._device, - self._subdevice._index, self._index) - - def dio_get_config(self): - """Query input/output properties - - Return an item from `constants.IO_DIRECTION`. - """ - return _constants.IO_DIRECTION.index_by_value(self._dio_get_config()) - - def dio_read(self): - "Read a single bit" - rc,bit = _comedi_tup( - 'comedi_dio_read', self._subdevice._device._device, - self._subdevice._index, self._index) - return bit - - def dio_write(self, bit): - "Write a single bit" - return _comedi_int( - 'comedi_dio_write', self._subdevice._device._device, - self._subdevice._index, self._index, bit) - - def cr_pack(self): - return _comedi.cr_pack(self._index, self.range.index, self.aref.value) - - -class SlowlyVaryingChannel (Channel): - "Slowly varying channel" - def __init__(self, **kwargs): - super(SlowlyVaryingChannel, self).__init__(**kwargs) - self._sv = _comedi.comedi_sv_t() - self.init() - - def init(self): - "Initialise `._sv`" - _comedi_int( - 'comedi_sv_init', self._sv, self._subdevice._device._device, - self._subdevice._index, self._index) - - def update(self): - "Update internal `._sv` parameters." - _comedi_int('comedi_sv_update', self._sv) - - def measure(self): - """Measure a slowy varying signal. - - Returns `(num_samples, physical_value)`. - """ - return _comedi_tup( - 'comedi_sv_measure', self._sv) - - -class StreamingSubdevice (Subdevice): - "Streaming I/O channel" - def __init__(self, **kwargs): - super(StreamingSubdevice, self).__init__(**kwargs) - self.cmd = Command() - - def get_cmd_src_mask(self): - """Detect streaming input/output capabilities - - The command capabilities of the subdevice indicated by the - parameters device and subdevice are probed, and the results - placed in the command structure *command. The trigger source - elements of the command structure are set to be the bitwise-or - of the subdevice's supported trigger sources. Other elements - in the structure are undefined. - """ - rc = _comedi_int( - 'comedi_get_cmd_src_mask', self._device._device, self._index, - self.cmd) - - def get_cmd_generic_timed(self, chanlist_len, scan_period_ns=0): - """Detect streaming input/output capabilities - - The command capabilities of the subdevice indicated by the - parameters device and subdevice are probed, and the results - placed in the command structure pointed to by the parameter - command. The command structure *command is modified to be a - valid command that can be used as a parameter to - comedi_command (after the command has additionally been - assigned a valid chanlist array). The command measures scans - consisting of chanlist_len channels at a scan rate that - corresponds to a period of scan_period_ns nanoseconds. The - rate is adjusted to a rate that the device can handle. - """ - rc = _comedi_int( - 'comedi_get_cmd_generic_timed', self._device._device, self._index, - self.cmd, chanlist_len, scan_period_ns) - - def cancel(self): - "Stop streaming input/output in progress." - _comedi_int('comedi_cancel', self._device._device, self._index) - - def command(self): - "Start streaming input/output" - _comedi_int('comedi_command', self._device._device, self.cmd) - - _command_test_errors = [ - None, # valid - 'unsupported *_src trigger', # unsupported trigger bits zeroed - 'unsupported *_src combo, or multiple triggers', - '*_arg out of range', # offending members adjusted to valid values - '*_arg required adjustment', # e.g. trigger timing period rounded - 'invalid chanlist', # e.g. some boards require same range across chans - ] - - def command_test(self): - "Test streaming input/output configuration" - rc = _comedi.comedi_command_test( - self._device._device, _comedi_arg(self.cmd)) - return self._command_test_errors[rc] - - def poll(self): - """Force updating of streaming buffer - - If supported by the driver, all available samples are copied - to the streaming buffer. These samples may be pending in DMA - buffers or device FIFOs. If successful, the number of - additional bytes available is returned. - """ - return _comedi_int('comedi_poll', self._device._device, self._index) - - def get_buffer_size(self): - "Streaming buffer size of subdevice" - return _comedi_int( - 'comedi_get_buffer_size', self._device._device, self._index) - - def set_buffer_size(self, size): - """Change the size of the streaming buffer - - Returns the new buffer size in bytes. - - The buffer size will be set to size bytes, rounded up to a - multiple of the virtual memory page size. The virtual memory - page size can be determined using `sysconf(_SC_PAGE_SIZE)`. - - This function does not require special privileges. However, it - is limited to a (adjustable) maximum buffer size, which can be - changed by a priveliged user calling - `.comedi_set_max_buffer_size`, or running the program - `comedi_config`. - """ - return _comedi_int( - 'comedi_set_buffer_size', - self._device._device, self._index, size) - - def get_max_buffer_size(self): - "Maximum streaming buffer size of subdevice" - return _comedi_int( - 'comedi_get_max_buffer_size', self._device._device, self._index) - - def set_max_buffer_size(self, max_size): - """Set the maximum streaming buffer size of subdevice - - Returns the old (max?) buffer size on success. - """ - return _comedi_int( - 'comedi_set_max_buffer_size', self._device._device, self._index, - max_size) - - def get_buffer_contents(self): - "Number of bytes available on an in-progress command" - return _comedi_int( - 'comedi_get_buffer_contents', self._device._device, self._index) - - def mark_buffer_read(self, num_bytes): - """Next `num_bytes` bytes in the buffer are no longer needed - - Returns the number of bytes successfully marked as read. - - This method should only be used if you are using a `mmap()` to - read data from Comedi's buffer (as opposed to calling `read()` - on the device file), since Comedi will automatically keep - track of how many bytes have been transferred via `read()` - calls. - """ - return _comedi_int( - 'comedi_mark_buffer_read', self._device._device, self._index, - num_bytes) - - def mark_buffer_written(self, num_bytes): - """Next `num_bytes` bytes in the buffer are no longer needed - - Returns the number of bytes successfully marked as written. - - This method should only be used if you are using a `mmap()` to - read data from Comedi's buffer (as opposed to calling - `write()` on the device file), since Comedi will automatically - keep track of how many bytes have been transferred via - `write()` calls. - """ - return _comedi_int( - 'comedi_mark_buffer_written', self._device._device, self._index, - num_bytes) - - def get_buffer_offset(self): - """Offset in bytes of the read(/write?) pointer in the streaming buffer - - This offset is only useful for memory mapped buffers. - """ - return _comedi_int( - 'comedi_get_buffer_offset', self._device._device, self._index) - - -class Chanlist (list): - def chanlist(self): - ret = _comedi.chanlist(len(self)) - for i,channel in enumerate(self): - ret[i] = channel.cr_pack() - return ret - - -class CalibratedConverter (object): - """Apply a converion polynomial - - Usually you would get the conversion polynomial from - `Channel.get_hardcal_converter()` or similar. bit for testing, - we'll just create one out of thin air. - - TODO: we'll need to use Cython, because the current SWIG bindings - don't provide a way to create or edit `double *` arrays. - - >>> p = _comedi.comedi_polynomial_t() - >>> p.order = 2 - >>> p.coefficients[0] = 1 # this fails. Silly SWIG. - >>> p.coefficients[1] = 2 - >>> p.coefficients[2] = 3 - >>> dir(p.coefficients) - >>> p.coefficients = _numpy.array([1, 2, 3, 4], dtype=_numpy.double) - >>> p.expansion_origin = -1; - - >>> c = CalibratedConverter(polynomial=p) - >>> c(-1) - >>> c(_numpy.array([-1, 0, 0.5, 2], dtype=_numpy.double)) - """ - def __init__(self, polynomial): - self.polynomial = polynomial - - def __call__(self, data): - # Iterating through the coefficients fails. Silly SWIG. - coefficients = list(reversed(self.polynomial.coefficients))[0:p.order] - print coefficients - print self.polynomial.expansion_origin - return _numpy.polyval( - coefficients, data-self.polynomial.expansion_origin) - -# see comedi_caldac_t and related at end of comedilib.h diff --git a/pycomedi/command.pxd b/pycomedi/command.pxd new file mode 100644 index 0000000..ff60f7a --- /dev/null +++ b/pycomedi/command.pxd @@ -0,0 +1,12 @@ +# Copyright + +"Expose `Command` internals at the C level for other Cython modules" + +cimport _comedi_h + + +cdef class Command (object): + cdef _comedi_h.comedi_cmd _cmd + cdef public list _fields + + cdef _comedi_h.comedi_cmd *get_comedi_cmd_pointer(self) diff --git a/pycomedi/command.pyx b/pycomedi/command.pyx new file mode 100644 index 0000000..beab7a9 --- /dev/null +++ b/pycomedi/command.pyx @@ -0,0 +1,255 @@ +# Copyright + +"Wrap Comedi's `comedi_cmd` struct in the `Command` class" + +cimport libc.stdlib as _stdlib +import numpy as _numpy + +cimport _comedi_h +cimport _comedilib_h +from pycomedi import PyComediError as _PyComediError +from chanspec import ChanSpec as _ChanSpec +import constant as _constant + + +cdef class Command (object): + """A Comedi command + + >>> from .constant import AREF, CMDF, TRIG_SRC + >>> from .channel import AnalogChannel + >>> from .chanspec import ChanSpec + >>> from .device import Device + + >>> c = Command() + >>> print str(c) + subdev: 0 + flags: - + start_src: - + start_arg: 0 + scan_begin_src: - + scan_begin_arg: 0 + convert_src: - + convert_arg: 0 + scan_end_src: - + scan_end_arg: 0 + stop_src: - + stop_arg: 0 + chanlist: [] + data: [] + + `data` takes any iterable that supports `length()` and returns NumPy arrays. + + >>> c.data = [1, 2, 3] + >>> type(c.data) + + + `subdev` is currently just an integer (not a `Subdevice` instance). + + >>> c.subdev = 3 + >>> c.subdev + 3L + >>> type(c.subdev) + + + `flags` and trigger sources return `FlagValue` instances. + + >>> c.flags # doctest: +ELLIPSIS + + >>> c.flags = CMDF.priority | CMDF.write + + >>> c.start_src # doctest: +ELLIPSIS + + >>> c.start_src = TRIG_SRC.int | TRIG_SRC.now + >>> c.scan_begin_src = TRIG_SRC.timer + >>> c.scan_begin_arg = 10 + >>> c.convert_src = TRIG_SRC.now + >>> c.scan_end_src = TRIG_SRC.count + >>> c.scan_end_arg = 4 + >>> c.stop_src = TRIG_SRC.none + + Because `ChanSpec` instances store their value internally (not + using the value stored in the `Command` instance), direct + operations on them have no effect on the intruction. + + >>> chanlist = [ + ... ChanSpec(chan=0, aref=AREF.diff), + ... ChanSpec(chan=1, aref=AREF.diff), + ... ] + >>> c.chanlist = chanlist + >>> c.chanlist[0] + + >>> c.chanlist[0].aref = AREF.ground + >>> c.chanlist[0] + + + To have an effect, you need to explicity set the `chanlist` attribute: + + >>> chanlist = c.chanlist + >>> chanlist[0].aref = AREF.ground + >>> c.chanlist = chanlist + >>> c.chanlist[0] + + + You can also set chanspec items with `AnalogChannel` instances (or + any object that has a `chanspec` method). + + >>> d = Device('/dev/comedi0') + >>> d.open() + >>> subdevice = d.get_read_subdevice() + >>> c.chanlist = [subdevice.channel(1, factory=AnalogChannel, + ... aref=AREF.diff)] + >>> d.close() + + >>> print str(c) + subdev: 3 + flags: priority|write + start_src: now|int + start_arg: 0 + scan_begin_src: timer + scan_begin_arg: 10 + convert_src: now + convert_arg: 0 + scan_end_src: count + scan_end_arg: 4 + stop_src: none + stop_arg: 0 + chanlist: [] + data: [1 2 3] + """ + def __cinit__(self): + self._cmd.chanlist = NULL + self._cmd.data = NULL + self._fields = [ + 'subdev', 'flags', 'start_src', 'start_arg', 'scan_begin_src', + 'scan_begin_arg', 'convert_src', 'convert_arg', 'scan_end_src', + 'scan_end_arg', 'stop_src', 'stop_arg', 'chanlist', 'data'] + + def __dealloc__(self): + if self._cmd.chanlist is not NULL: + _stdlib.free(self._cmd.chanlist) + if self._cmd.data is not NULL: + _stdlib.free(self._cmd.data) + + cdef _comedi_h.comedi_cmd *get_comedi_cmd_pointer(self): + return &self._cmd + + def __str__(self): + max_field_length = max([len(f) for f in self._fields]) + lines = [] + for f in self._fields: + lines.append('%*s: %s' % (max_field_length, f, getattr(self, f))) + return '\n'.join(lines) + + def _subdev_get(self): + return self._cmd.subdev + def _subdev_set(self, value): + self._cmd.subdev = _constant.bitwise_value(value) + subdev = property(fget=_subdev_get, fset=_subdev_set) + + def _flags_get(self): + return _constant.FlagValue(_constant.CMDF, self._cmd.flags) + def _flags_set(self, value): + self._cmd.flags = _constant.bitwise_value(value) + flags = property(fget=_flags_get, fset=_flags_set) + + def _start_src_get(self): + return _constant.FlagValue(_constant.TRIG_SRC, self._cmd.start_src) + def _start_src_set(self, value): + self._cmd.start_src = _constant.bitwise_value(value) + start_src = property(fget=_start_src_get, fset=_start_src_set) + + def _start_arg_get(self): + return self._cmd.start_arg + def _start_arg_set(self, value): + self._cmd.start_arg = value + start_arg = property(fget=_start_arg_get, fset=_start_arg_set) + + def _scan_begin_src_get(self): + return _constant.FlagValue(_constant.TRIG_SRC, self._cmd.scan_begin_src) + def _scan_begin_src_set(self, value): + self._cmd.scan_begin_src = _constant.bitwise_value(value) + scan_begin_src = property(fget=_scan_begin_src_get, fset=_scan_begin_src_set) + + def _scan_begin_arg_get(self): + return self._cmd.scan_begin_arg + def _scan_begin_arg_set(self, value): + self._cmd.scan_begin_arg = value + scan_begin_arg = property(fget=_scan_begin_arg_get, fset=_scan_begin_arg_set) + + def _convert_src_get(self): + return _constant.FlagValue(_constant.TRIG_SRC, self._cmd.convert_src) + def _convert_src_set(self, value): + self._cmd.convert_src = _constant.bitwise_value(value) + convert_src = property(fget=_convert_src_get, fset=_convert_src_set) + + def _convert_arg_get(self): + return self._cmd.convert_arg + def _convert_arg_set(self, value): + self._cmd.convert_arg = value + convert_arg = property(fget=_convert_arg_get, fset=_convert_arg_set) + + + def _scan_end_src_get(self): + return _constant.FlagValue(_constant.TRIG_SRC, self._cmd.scan_end_src) + def _scan_end_src_set(self, value): + self._cmd.scan_end_src = _constant.bitwise_value(value) + scan_end_src = property(fget=_scan_end_src_get, fset=_scan_end_src_set) + + def _scan_end_arg_get(self): + return self._cmd.scan_end_arg + def _scan_end_arg_set(self, value): + self._cmd.scan_end_arg = value + scan_end_arg = property(fget=_scan_end_arg_get, fset=_scan_end_arg_set) + + def _stop_src_get(self): + return _constant.FlagValue(_constant.TRIG_SRC, self._cmd.stop_src) + def _stop_src_set(self, value): + self._cmd.stop_src = _constant.bitwise_value(value) + stop_src = property(fget=_stop_src_get, fset=_stop_src_set) + + def _stop_arg_get(self): + return self._cmd.stop_arg + def _stop_arg_set(self, value): + self._cmd.stop_arg = value + stop_arg = property(fget=_stop_arg_get, fset=_stop_arg_set) + + def _chanlist_get(self): + ret = list() + for i in range(self._cmd.chanlist_len): + c = _ChanSpec() + c.value = self._cmd.chanlist[i] + ret.append(c) + return ret + def _chanlist_set(self, value): + if self._cmd.chanlist is not NULL: + _stdlib.free(self._cmd.chanlist) + self._cmd.chanlist_len = len(value) + self._cmd.chanlist = _stdlib.malloc( + self._cmd.chanlist_len*sizeof(unsigned int)) + if self._cmd.chanlist is NULL: + self._cmd.chanlist_len = 0 + raise _PyComediError('out of memory?') + for i,x in enumerate(value): + if hasattr(x, 'chanspec'): + x = x.chanspec() + self._cmd.chanlist[i] = _constant.bitwise_value(x) + chanlist = property(fget=_chanlist_get, fset=_chanlist_set) + + def _data_get(self): + data = _numpy.ndarray(shape=(self._cmd.data_len,), dtype=_numpy.uint16) + # TODO: point into existing data array? + for i in range(self._cmd.data_len): + data[i] = self._cmd.data[i] + return data + def _data_set(self, value): + if self._cmd.data is not NULL: + _stdlib.free(self._cmd.data) + self._cmd.data_len = len(value) + self._cmd.data = <_comedi_h.sampl_t *>_stdlib.malloc( + self._cmd.data_len*sizeof(_comedi_h.sampl_t)) + if self._cmd.data is NULL: + self._cmd.data_len = 0 + raise _PyComediError('out of memory?') + for i,x in enumerate(value): + self._cmd.data[i] = x + data = property(fget=_data_get, fset=_data_set) diff --git a/pycomedi/constant.pxd b/pycomedi/constant.pxd new file mode 100644 index 0000000..3fa56f5 --- /dev/null +++ b/pycomedi/constant.pxd @@ -0,0 +1,6 @@ +# Copyright + +"Expose `BitwiseOperator` internals at the C level for other Cython modules" + +cdef class BitwiseOperator (object): + cdef public object value diff --git a/pycomedi/constants.py b/pycomedi/constant.pyx similarity index 70% rename from pycomedi/constants.py rename to pycomedi/constant.pyx index 550cf39..14b051f 100644 --- a/pycomedi/constants.py +++ b/pycomedi/constant.pyx @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Enums and flags are bundled into class instances for easier browsing. +"""Enums and flags are bundled into class instances for easier browsing >>> SUBDEVICE_TYPE # doctest: +NORMALIZE_WHITESPACE [<_NamedInt unused>, <_NamedInt ai>, <_NamedInt ao>, <_NamedInt di>, @@ -60,27 +60,74 @@ You can treat named integers as Python integers with bitwise operations, >>> a = TRIG_SRC.now | TRIG_SRC.follow | TRIG_SRC.time | 64 >>> a -<_BitwiseOperator 78> + >>> a.value 78 >>> TRIG_SRC.none & TRIG_SRC.now -<_BitwiseOperator 0> + -But because of the way Python operator overloading works, plain -integers must go at the end of bitwise chains. +Because of the way Python operator overloading works [#ops]_, you can +also start a bitwise chain with an integer. >>> 64 | TRIG_SRC.now -Traceback (most recent call last): - ... -TypeError: unsupported operand type(s) for |: 'int' and '_NamedInt' + + +.. [#ops] See `emulating numeric types`_ and `NotImplementedError` in + `the standard type hierarchy`_. + +.. _emulating numeric types: + http://docs.python.org/reference/datamodel.html#emulating-numeric-types +.. _the standard type hierarchy`_ + http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy """ from math import log as _log +import sys as _sys +import numpy as _numpy import comedi as _comedi +from pycomedi import LOG as _LOG + + +def bitwise_value(object): + """Convenience function for flexible value specification + + This funciton makes it easy for functions and methods to accept + either integers or `BitwiseOperator` instances as integer + parameters. + """ + if isinstance(object, BitwiseOperator): + return object.value + return object + + +def _pso(s, o): + """External definition of staticmethod until Cython supports the + usual syntax. + + http://docs.cython.org/src/userguide/limitations.html#behaviour-of-class-scopes + """ + if isinstance(s, BitwiseOperator): + s = s.value + if isinstance(o, BitwiseOperator): + o = o.value + return (long(s), long(o)) + + +cdef class BitwiseOperator (object): + """General class for bitwise operations. + + This class allows chaining bitwise operations between + `_NamedInt`\s and similar objects. + + Because flag values can be large, we cast all values to longs + before performing any bitwise operations. This avoids issues like + + >>> int.__or__(1073741824, 2147483648L) + NotImplemented + """ -class _BitwiseOperator (object): def __init__(self, value): self.value = value @@ -90,20 +137,20 @@ class _BitwiseOperator (object): def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.value) + _prepare_self_other = staticmethod(_pso) + def __and__(self, other): - "Bitwise and acts on `_BitwiseOperator.value`." - if isinstance(other, _BitwiseOperator): - other = other.value - return _BitwiseOperator(int.__and__(self.value, other)) + "Bitwise and acts on `BitwiseOperator.value`." + s,o = BitwiseOperator._prepare_self_other(self, other) + return BitwiseOperator(int(long.__and__(s, o))) def __or__(self, other): - "Bitwise or acts on `_BitwiseOperator.value`." - if isinstance(other, _BitwiseOperator): - other = other.value - return _BitwiseOperator(int.__or__(self.value, other)) + "Bitwise or acts on `BitwiseOperator.value`." + s,o = BitwiseOperator._prepare_self_other(self, other) + return BitwiseOperator(int(long.__or__(s, o))) -class _NamedInt (_BitwiseOperator): +class _NamedInt (BitwiseOperator): "A flag or enum item." def __init__(self, name, value, doc=None): super(_NamedInt, self).__init__(value) @@ -119,7 +166,7 @@ class _NamedInt (_BitwiseOperator): class _Enum (list): "An enumerated list" - def __init__(self, name, prefix, blacklist=None, whitelist=None, + def __init__(self, name, prefix='', blacklist=None, whitelist=None, translation=None): super(_Enum, self).__init__() self.name = name @@ -150,15 +197,23 @@ class _Enum (list): def _add_item(self, attr, item_name): item_value = getattr(_comedi, attr) + if item_value < 0: + _LOG.debug('big value for {0:s}: {1:d} ({1:b}) converted to {2:d} ({2:b})'.format( + attr, item_value, (1<<32) + item_value)) + item_value = (1<<32) + item_value # flags are unsigned 32 bit integers, but SWIG signs them item = _NamedInt(item_name, item_value, doc=attr) self.append(item) - self._name_keys[item_name] = item - if item_value in self._value_keys and item_name: + + def append(self, item): + super(_Enum, self).append(item) + self._name_keys[item.name] = item + if item.value in self._value_keys and item.name: raise ValueError('value collision in %s: %s = %s = %#x' - % (self.name, item_name, - self._value_keys[item_value], item_value)) - self._value_keys[item_value] = item - setattr(self, item_name, item) + % (self.name, item.name, + self._value_keys[item.value], item.value)) + self._value_keys[item.value] = item + setattr(self, item.name, item) + def index_by_name(self, name): return self._name_keys[name] @@ -176,7 +231,7 @@ class _Flag (_Enum): for flag in self: if flag.value == 0: self._empty = flag - elif flag.value < 0 or _log(flag.value, 2) % 1 != 0: + elif flag.value < 0 or abs(_log(flag.value, 2)) % 1 > 1e-13: # deal with rounding errors if self._all: raise ValueError( 'mutliple multi-bit flags in %s: %s = %#x and %s = %#x' @@ -240,6 +295,9 @@ class FlagValue (object): # blacklist deprecated values (and those belonging to other _Enums or _Flags) +CR = _Flag('ChanSpec flags', 'CR_', blacklist=['dither', 'deglitch']) +CR.alt_filter.doc += ' (can also mean "dither" or "deglitch")' + AREF = _Enum('analog_reference', 'AREF_') AREF.diff.doc += ' (differential)' AREF.other.doc += ' (other / undefined)' @@ -297,7 +355,7 @@ SDF.busy_owner.doc += ' (device is busy with your job)' SDF.locked.doc += ' (subdevice is locked)' SDF.lock_owner.doc += ' (you own lock)' SDF.maxdata.doc += ' (maxdata depends on channel)' -SDF.flags.doc += ' (flags depend on channel)' +SDF.flags.doc += ' (flags depend on channel (BROKEN))' SDF.rangetype.doc += ' (range type depends on channel)' SDF.soft_calibrated.doc += ' (subdevice uses software calibration)' SDF.cmd_write.doc += ' (can do output commands)' @@ -336,9 +394,30 @@ SUPPORT_LEVEL = _Enum('support_level', 'COMEDI_', whitelist=[ 'unknown_support', 'supported', 'unsupported']) UNIT = _Enum('unit', 'UNIT_', translation={'mA':'mA'}) - -CB = _Enum('callback_flags', 'COMEDI_CB_', blacklist=['block', 'eobuf']) -CB.eos.doc += ' (end of scan)' -CB.eoa.doc += ' (end of acquisition)' -CB.error.doc += ' (card error during acquisition)' -CB.overflow.doc += ' (buffer overflow/underflow)' +# The mA translation avoids lowercasing to 'ma'. + +CALLBACK = _Enum('callback_flags', 'COMEDI_CB_', blacklist=['block', 'eobuf']) +CALLBACK.eos.doc += ' (end of scan)' +CALLBACK.eoa.doc += ' (end of acquisition)' +CALLBACK.error.doc += ' (card error during acquisition)' +CALLBACK.overflow.doc += ' (buffer overflow/underflow)' + +CONVERSION_DIRECTION = _Enum('conversion_direction', 'COMEDI_', whitelist=[ + 'to_physical', 'from_physical']) + +# The following constants aren't declared in comedi.h or comedilib.h, +# but they should be. + +LOGLEVEL = _Enum('log level', '', whitelist=['']) +LOGLEVEL.append(_NamedInt('silent', 0, doc='Comedilib prints nothing.')) +LOGLEVEL.append(_NamedInt('bug', 1, doc=( + 'Comedilib prints error messages when there is a self-consistency ' + 'error (i.e., an internal bug (default).'))) +LOGLEVEL.append(_NamedInt('invalid', 2, doc=( + 'Comedilib prints an error message when an invalid parameter is ' + 'passed.'))) +LOGLEVEL.append(_NamedInt('error', 3, doc=( + 'Comedilib prints an error message whenever an error is generated ' + 'in the Comedilib library or in the C library, when called by ' + 'Comedilib.'))) +LOGLEVEL.append(_NamedInt('debug', 4, doc='Comedilib prints a lot of junk.')) diff --git a/pycomedi/device.pxd b/pycomedi/device.pxd new file mode 100644 index 0000000..9597fae --- /dev/null +++ b/pycomedi/device.pxd @@ -0,0 +1,15 @@ +# Copyright + +"Expose `Device` internals at the C level for other Cython modules" + +cimport _comedilib_h +from instruction cimport Insn as _Insn + + +cdef class Device (object): + cdef _comedilib_h.comedi_t * device + cdef public object file + cdef public object filename + + cpdef do_insnlist(self, insnlist) + cpdef do_insn(self, _Insn insn) diff --git a/pycomedi/device.pyx b/pycomedi/device.pyx new file mode 100644 index 0000000..bcb8672 --- /dev/null +++ b/pycomedi/device.pyx @@ -0,0 +1,270 @@ +# Copyright + +"Wrap device-wide Comedi functions in a `Device` class" + +import os as _os +cimport libc.stdlib as _stdlib + +from pycomedi import LOG as _LOG +from pycomedi import PyComediError as _PyComediError +cimport _comedi_h +cimport _comedilib_h +import _error +from instruction cimport Insn as _Insn +from instruction import Insn as _Insn +from subdevice import Subdevice as _Subdevice + + +cdef class Device (object): + """A Comedi device + + >>> from . import constant + + >>> d = Device('/dev/comediX') + >>> d.filename + '/dev/comediX' + + > d.open() # TODO: re-enable when there is a way to clear comedi_errno + Traceback (most recent call last): + ... + PyComediError: comedi_open (/dev/comediX): No such file or directory (None) + >>> d.filename = '/dev/comedi0' + >>> d.open() + >>> d.fileno() + 3 + >>> d.get_n_subdevices() + 14 + >>> d.get_version_code() + (0, 0, 76) + >>> d.get_driver_name() + 'ni_pcimio' + >>> s = d.get_read_subdevice() + >>> s.index + 0 + >>> s = d.get_write_subdevice() + >>> s.index + 1 + >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.calib) + >>> s.index + 5 + >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.pwm) + Traceback (most recent call last): + ... + PyComediError: comedi_find_subdevice_by_type: Success (-1) + + As a test instruction, we'll get the time of day, which fills in + the data field with `[seconds, microseconds]`. + + >>> insn = d.insn() + >>> insn.insn = constant.INSN.gtod + >>> insn.data = [0, 0] # space where the time value will be stored + >>> print str(insn) + insn: gtod + data: [0 0] + subdev: 0 + chanspec: + >>> d.do_insn(insn) + 2 + >>> print insn.data # doctest: +SKIP + [1297377578 105790] + >>> insn.data = [0, 0] + >>> d.do_insnlist([insn]) + 1 + >>> print insn.data # doctest: +SKIP + [1297377578 110559] + + >>> d.get_default_calibration_path() + '/var/lib/comedi/calibrations/ni_pcimio_pci-6052e_comedi0' + + >>> list(d.subdevices()) # doctest: +ELLIPSIS + [,...] + + >>> d.close() + """ + def __cinit__(self): + self.device = NULL + self.file = None + self.filename = None + + def __init__(self, filename): + super(Device, self).__init__() + self.filename = filename + + def open(self): + "Open device" + self.device = _comedilib_h.comedi_open(self.filename) + if self.device == NULL: + _error.raise_error(function_name='comedi_open', + error_msg=self.filename) + self.file = _os.fdopen(self.fileno(), 'r+') + + def close(self): + "Close device" + self.file.flush() + self.file.close() + ret = _comedilib_h.comedi_close(self.device) + if ret < 0: + _error.raise_error(function_name='comedi_close', ret=ret) + self.device = NULL + self.file = None + + def fileno(self): + "File descriptor for this device" + ret = _comedilib_h.comedi_fileno(self.device) + if ret < 0: + _error.raise_error(function_name='comedi_fileno', ret=ret) + return ret + + def get_n_subdevices(self): + "Number of subdevices" + ret = _comedilib_h.comedi_get_n_subdevices(self.device) + if ret < 0: + _error.raise_error(function_name='comedi_get_n_subdevices', + ret=ret) + return ret + + def get_version_code(self): + """Comedi version code. + + This is a kernel-module level property, but a valid device is + necessary to communicate with the kernel module. + + Returns a tuple of version numbers, e.g. `(0, 7, 61)`. + """ + version = _comedilib_h.comedi_get_version_code(self.device) + if version < 0: + _error.raise_error(function_name='comedi_get_version_code', + ret=version) + ret = [] + for i in range(3): + ret.insert(0, version & (2**8-1)) + version >>= 2**8 # shift over 8 bits + return tuple(ret) + + def get_driver_name(self): + "Comedi driver name" + ret = _comedilib_h.comedi_get_driver_name(self.device) + if ret == NULL: + _error.raise_error(function_name='comedi_get_driver_name', + ret=ret) + return ret + + def get_board_name(self): + "Comedi board name" + ret = _comedilib_h.comedi_get_board_name(self.device) + if ret == NULL: + _error.raise_error(function_name='comedi_get_driver_name', + ret=ret) + return ret + + def _get_read_subdevice(self): + "Find streaming input subdevice index" + ret = _comedilib_h.comedi_get_read_subdevice(self.device) + if ret < 0: + _error.raise_error(function_name='comedi_get_read_subdevice', + ret=ret) + return ret + + def get_read_subdevice(self, **kwargs): + "Find streaming input subdevice" + return self.subdevice(self._get_read_subdevice(), **kwargs) + + def _get_write_subdevice(self): + "Find streaming output subdevice index" + ret = _comedilib_h.comedi_get_write_subdevice(self.device) + if ret < 0: + _error.raise_error(function_name='comedi_get_write_subdevice', + ret=ret) + return ret + + def get_write_subdevice(self, **kwargs): + "Find streaming output subdevice" + return self.subdevice(self._get_write_subdevice(), **kwargs) + + def _find_subdevice_by_type(self, subdevice_type): + "Search for a subdevice index for type `subdevice_type`)." + ret = _comedilib_h.comedi_find_subdevice_by_type( + self.device, subdevice_type.value, 0) # 0 is starting subdevice + if ret < 0: + _error.raise_error(function_name='comedi_find_subdevice_by_type', + ret=ret) + return ret + + def find_subdevice_by_type(self, subdevice_type, **kwargs): + """Search for a subdevice by type `subdevice_type`)." + + `subdevice_type` should be an item from `constant.SUBDEVICE_TYPE`. + """ + return self.subdevice( + self._find_subdevice_by_type(subdevice_type), **kwargs) + + cpdef do_insnlist(self, insnlist): + """Perform multiple instructions + + Returns the number of successfully completed instructions. + """ + cdef _comedi_h.comedi_insnlist il + cdef _Insn i + il.n_insns = len(insnlist) + if il.n_insns == 0: + return + il.insns = <_comedi_h.comedi_insn *>_stdlib.malloc( + il.n_insns*sizeof(_comedi_h.comedi_insn)) + if il.insns is NULL: + raise _PyComediError('out of memory?') + try: + for j,insn in enumerate(insnlist): + i = insn + # By copying the pointer to data, changes to this + # copied instruction will also affect the original + # instruction's data. + il.insns[j] = i.get_comedi_insn() + ret = _comedilib_h.comedi_do_insnlist(self.device, &il) + finally: + _stdlib.free(il.insns) + if ret < 0: + _error.raise_error(function_name='comedi_do_insnlist', ret=ret) + return ret + + cpdef do_insn(self, _Insn insn): + """Preform a single instruction. + + Returns an instruction-specific integer. + """ + cdef _comedi_h.comedi_insn i + # By copying the pointer to data, changes to this + # copied instruction will also affect the original + # instruction's data. + i = insn.get_comedi_insn() + ret = _comedilib_h.comedi_do_insn( + self.device, &i) + if ret < 0: + _error.raise_error(function_name='comedi_do_insn', ret=ret) + return ret + + def get_default_calibration_path(self): + "The default calibration path for this device" + assert self.device != NULL, ( + 'must call get_default_calibration_path on an open device.') + ret = _comedilib_h.comedi_get_default_calibration_path(self.device) + if ret == NULL: + _error.raise_error( + function_name='comedi_get_default_calibration_path') + return ret + + # extensions to make a more idomatic Python interface + + def insn(self): + return _Insn() + + def subdevices(self, **kwargs): + "Iterate through all available subdevices." + ret = [] + for i in range(self.get_n_subdevices()): + #yield self.subdevice(i, **kwargs) + # Generators are not supported in Cython 0.14.1 + ret.append(self.subdevice(i, **kwargs)) + return ret + + def subdevice(self, index, factory=_Subdevice, **kwargs): + return factory(device=self, index=index, **kwargs) diff --git a/pycomedi/instruction.pxd b/pycomedi/instruction.pxd new file mode 100644 index 0000000..85153e7 --- /dev/null +++ b/pycomedi/instruction.pxd @@ -0,0 +1,12 @@ +# Copyright + +"Expose `Insn` internals at the C level for other Cython modules" + +cimport _comedi_h + + +cdef class Insn (object): + cdef _comedi_h.comedi_insn _insn + cdef public list _fields + + cdef _comedi_h.comedi_insn get_comedi_insn(self) diff --git a/pycomedi/instruction.pyx b/pycomedi/instruction.pyx new file mode 100644 index 0000000..30ee7c4 --- /dev/null +++ b/pycomedi/instruction.pyx @@ -0,0 +1,119 @@ +# Copyright + +"Wrap Comedi's `comedi_insn` struct in the `Insn` class" + +cimport libc.stdlib as _stdlib +import numpy as _numpy + +cimport _comedi_h +cimport _comedilib_h +from pycomedi import PyComediError as _PyComediError +from chanspec import ChanSpec as _ChanSpec +import constant as _constant + + +cdef class Insn (object): + """A Comedi instruction + + >>> from .constant import INSN, AREF + >>> i = Insn() + >>> print str(i) + insn: read + data: [] + subdev: 0 + chanspec: + >>> i.insn = INSN.write + + `data` takes any iterable that supports `length()` and returns NumPy arrays. + + >>> i.data = [1, 2, 3] + >>> type(i.data) + + + `subdev` is currently just an integer (not a `Subdevice` instance). + + >>> i.subdev = 3 + >>> i.subdev + 3 + >>> type(i.subdev) + + + Because `ChanSpec` instances store their value internally (not + using the value stored in the `Insn` instance), direct operations + on them have no effect on the intruction. + + >>> i.chanspec.aref = AREF.diff + >>> i.chanspec + + + To have an effect, you need to explicity set the `chanspec` attribute: + + >>> c = i.chanspec + >>> c.aref = AREF.diff + >>> i.chanspec = c + >>> i.chanspec + + + >>> print str(i) + insn: write + data: [1 2 3] + subdev: 3 + chanspec: + """ + def __cinit__(self): + self._insn.insn = _constant.INSN.read.value + self._insn.data = NULL + self._fields = ['insn', 'data', 'subdev', 'chanspec'] + + def __dealloc__(self): + if self._insn.data is not NULL: + _stdlib.free(self._insn.data) + + cdef _comedi_h.comedi_insn get_comedi_insn(self): + return self._insn + + def __str__(self): + max_field_length = max([len(f) for f in self._fields]) + lines = [] + for f in self._fields: + lines.append('%*s: %s' % (max_field_length, f, getattr(self, f))) + return '\n'.join(lines) + + def _insn_get(self): + return _constant.INSN.index_by_value(self._insn.insn) + def _insn_set(self, value): + self._insn.insn = _constant.bitwise_value(value) + insn = property(fget=_insn_get, fset=_insn_set) + + def _data_get(self): + data = _numpy.ndarray(shape=(self._insn.n,), dtype=_numpy.uint) + # TODO: point into existing data array? + for i in range(self._insn.n): + data[i] = self._insn.data[i] + return data + def _data_set(self, value): + if self._insn.data is not NULL: + _stdlib.free(self._insn.data) + self._insn.n = len(value) + self._insn.data = <_comedi_h.lsampl_t *>_stdlib.malloc( + self._insn.n*sizeof(_comedi_h.lsampl_t)) + if self._insn.data is NULL: + self._insn.n = 0 + raise _PyComediError('out of memory?') + for i,x in enumerate(value): + self._insn.data[i] = x + data = property(fget=_data_get, fset=_data_set) + + def _subdev_get(self): + return int(self._insn.subdev) + def _subdev_set(self, value): + self._insn.subdev = value + subdev = property(fget=_subdev_get, fset=_subdev_set) + + def _chanspec_get(self): + c = _ChanSpec() + c.value = self._insn.chanspec + return c + def _chanspec_set(self, value): + self._insn.chanspec = _constant.bitwise_value(value) + chanspec = property(fget=_chanspec_get, fset=_chanspec_set) diff --git a/pycomedi/library.pyx b/pycomedi/library.pyx new file mode 100644 index 0000000..9c1037d --- /dev/null +++ b/pycomedi/library.pyx @@ -0,0 +1,41 @@ +# Copyright + +"Wrap library-wide Comedi functions in a `Device` class" + +import os as _os + +cimport _comedilib_h +import constant as _constant + + +def set_loglevel(level): + """Control the verbosity of Comedilib debugging and error messages + + This function affects the output of debugging and error messages + from Comedilib. By increasing the loglevel, additional debugging + information will be printed. Error and debugging messages are + printed to the stream stderr. + + The default loglevel can be set by using the environment variable + COMEDI_LOGLEVEL. The default loglevel is 1. + + In order to conserve resources, some debugging information is + disabled by default when Comedilib is compiled. + + See `constants.LOGLEVEL` for a list of possible levels and their + meanings. + + Return value + ============ + + This function returns the previous loglevel. + + >>> from constant import LOGLEVEL + >>> level = set_loglevel(LOGLEVEL.error) + >>> level + <_NamedInt bug> + >>> set_loglevel(level) + <_NamedInt error> + """ + ret = _comedilib_h.comedi_loglevel(_constant.bitwise_value(level)) + return _constant.LOGLEVEL.index_by_value(ret) diff --git a/pycomedi/range.pxd b/pycomedi/range.pxd new file mode 100644 index 0000000..8fc048a --- /dev/null +++ b/pycomedi/range.pxd @@ -0,0 +1,12 @@ +# Copyright + +"Expose `Range` internals at the C level for other Cython modules" + +cimport _comedilib_h +from constant cimport BitwiseOperator as _BitwiseOperator + + +cdef class Range (_BitwiseOperator): + cdef _comedilib_h.comedi_range range + + cdef set_comedi_range(self, _comedilib_h.comedi_range range) diff --git a/pycomedi/range.pyx b/pycomedi/range.pyx new file mode 100644 index 0000000..d8f8137 --- /dev/null +++ b/pycomedi/range.pyx @@ -0,0 +1,71 @@ +# Copyright + +"Wrap `comedi_range` in a Python class" + +from constant cimport BitwiseOperator as _BitwiseOperator +import constant as _constant + + +cdef class Range (_BitwiseOperator): + """Stucture displaying a possible channel range + + Warning: You probably want to use `channel.Channel.get_range()` or + `channel.Channel.find_range()` rather than initializing this + stucture by hand. If you do initialize it by hand (or set any + values by hand), remember that it may no longer correspond to your + devices built-in range with that index. + + For consistency with other integer wrappers, the range index is + stored in the `.value` attribute. + + >>> from constant import UNIT + >>> r = Range(1) + >>> r + + >>> r.value + 1 + >>> r.unit = UNIT.mA + >>> r.min = -2.71828 + >>> r.max = 3.14159 + >>> r + + >>> r.unit + <_NamedInt mA> + """ + def __cinit__(self): + self.value = -1 + + def __init__(self, value): + self.range.unit = 0 + self.range.min = 0 + self.range.max = 0 + self.value = value + + cdef set_comedi_range(self, _comedilib_h.comedi_range range): + self.range = range + + def __str__(self): + fields = ['%s:%s' % (f, getattr(self, f)) + for f in ['unit', 'min', 'max']] + return '<%s %s>' % (self.__class__.__name__, ' '.join(fields)) + + def __repr__(self): + return self.__str__() + + def _unit_get(self): + return _constant.UNIT.index_by_value(self.range.unit) + def _unit_set(self, value): + self.range.unit = _constant.bitwise_value(value) + unit = property(fget=_unit_get, fset=_unit_set) + + def _min_get(self): + return self.range.min + def _min_set(self, value): + self.range.min = value + min = property(fget=_min_get, fset=_min_set) + + def _max_get(self): + return self.range.max + def _max_set(self, value): + self.range.max = value + max = property(fget=_max_get, fset=_max_set) diff --git a/pycomedi/subdevice.pxd b/pycomedi/subdevice.pxd new file mode 100644 index 0000000..c237af5 --- /dev/null +++ b/pycomedi/subdevice.pxd @@ -0,0 +1,17 @@ +# Copyright + +"Expose `Subdevice` internals at the C level for other Cython modules" + +from device cimport Device as _Device +from command cimport Command as _Command + + +cdef class Subdevice (object): + cdef public _Device device + cdef public int index + + cpdef dio_bitfield(self, unsigned int bits=*, write_mask=*, base_channel=*) + +cdef class StreamingSubdevice (Subdevice): + cdef public _Command cmd + cdef public list _command_test_errors diff --git a/pycomedi/subdevice.pyx b/pycomedi/subdevice.pyx new file mode 100644 index 0000000..e8ca4e5 --- /dev/null +++ b/pycomedi/subdevice.pyx @@ -0,0 +1,411 @@ +# Copyright + +"Wrap subdevice-wide Comedi functions in `Subdevice` and related classes" + +cimport _comedi_h +cimport _comedilib_h +cimport device as _device +cimport command as _command +from pycomedi import LOG as _LOG +import _error +from channel import Channel as _Channel +import chanspec as _chanspec +import constant as _constant +import command as _command +from utility import _subdevice_dtype, _subdevice_typecode + + +cdef class Subdevice (object): + """Class bundling subdevice-related functions + + >>> from .device import Device + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + + >>> s = d.get_read_subdevice() + >>> s.get_type() + <_NamedInt ai> + >>> f = s.get_flags() + >>> f # doctest: +ELLIPSIS + + >>> print str(f) + cmd_read|readable|ground|common|diff|other|dither + >>> s.get_n_channels() + 16 + >>> s.range_is_chan_specific() + False + >>> s.maxdata_is_chan_specific() + False + >>> s.lock() + >>> s.unlock() + + >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio) + >>> s.dio_bitfield() + 255L + + >>> s.get_dtype() + + >>> s.get_typecode() + 'H' + + >>> d.close() + """ + def __cinit__(self): + self.index = -1 + + def __init__(self, device, index): + super(Subdevice, self).__init__() + self.device = device + self.index = index + + def get_type(self): + "Type of subdevice (from `SUBDEVICE_TYPE`)" + ret = _comedilib_h.comedi_get_subdevice_type( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_subdevice_type', + ret=ret) + return _constant.SUBDEVICE_TYPE.index_by_value(ret) + + def _get_flags(self): + "Subdevice flags" + ret = _comedilib_h.comedi_get_subdevice_flags( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_subdevice_flags', + ret=ret) + return ret + + def get_flags(self): + "Subdevice flags (an `SDF` `FlagValue`)" + return _constant.FlagValue( + _constant.SDF, self._get_flags()) + + def get_n_channels(self): + "Number of subdevice channels" + ret = _comedilib_h.comedi_get_n_channels( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_n_channels', + ret=ret) + return ret + + def range_is_chan_specific(self): + ret = _comedilib_h.comedi_range_is_chan_specific( + self.device.device, self.index) + if ret < 0: + _error.raise_error( + function_name='comedi_range_is_chan_specific', ret=ret) + return ret == 1 + + def maxdata_is_chan_specific(self): + ret = _comedilib_h.comedi_maxdata_is_chan_specific( + self.device.device, self.index) + if ret < 0: + _error.raise_error( + function_name='comedi_maxdata_is_chan_specific', ret=ret) + return ret == 1 + + def lock(self): + "Reserve the subdevice" + ret = _comedilib_h.comedi_lock(self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_lock', ret=ret) + + def unlock(self): + "Release the subdevice" + ret = _comedilib_h.comedi_unlock(self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_unlock', ret=ret) + + cpdef dio_bitfield(self, unsigned int bits=0, write_mask=0, base_channel=0): + """Read/write multiple digital channels. + + `bits` and `write_mask` are bit fields with the least + significant bit representing channel `base_channel`. + + Returns a bit field containing the read value of all input + channels and the last written value of all output channels. + """ + ret = _comedilib_h.comedi_dio_bitfield2( + self.device.device, self.index, write_mask, &bits, base_channel) + if ret < 0: + _error.raise_error(function_name='comedi_dio_bitfield2', ret=ret) + return bits + + # extensions to make a more idomatic Python interface + + def insn(self): + insn = self.device.insn() + insn.subdev = self.index + return insn + + def channel(self, index, factory=_Channel, **kwargs): + "`Channel` instance for the `index`\ed channel." + return factory(subdevice=self, index=index, **kwargs) + + def get_dtype(self): + "Return the appropriate `numpy.dtype` based on subdevice flags" + return _subdevice_dtype(self) + + def get_typecode(self): + "Return the appropriate `array` type based on subdevice flags" + return _subdevice_typecode(self) + + +cdef class StreamingSubdevice (Subdevice): + """Streaming I/O subdevice + + >>> from .device import Device + >>> from .chanspec import ChanSpec + >>> from . import constant + + >>> d = Device('/dev/comedi0') + >>> d.open() + + >>> s = d.get_read_subdevice(factory=StreamingSubdevice) + + >>> cmd = s.get_cmd_src_mask() + >>> print str(cmd) + subdev: 0 + flags: - + start_src: now|ext|int + start_arg: 0 + scan_begin_src: timer|ext + scan_begin_arg: 0 + convert_src: timer|ext + convert_arg: 0 + scan_end_src: count + scan_end_arg: 0 + stop_src: none|count + stop_arg: 0 + chanlist: [] + data: [] + + >>> chanlist_len = 3 + >>> cmd = s.get_cmd_generic_timed(chanlist_len=chanlist_len, + ... scan_period_ns=1e3) + >>> print str(cmd) # doctest: +NORMALIZE_WHITESPACE + subdev: 0 + flags: - + start_src: now + start_arg: 0 + scan_begin_src: timer + scan_begin_arg: 9000 + convert_src: timer + convert_arg: 3000 + scan_end_src: count + scan_end_arg: 3 + stop_src: count + stop_arg: 2 + chanlist: [, + , + ] + data: [] + + >>> cmd.chanlist = [ChanSpec(chan=i, range=0) for i in range(chanlist_len)] + >>> s.cmd = cmd + >>> s.command_test() + >>> s.command() + >>> s.cancel() + + + >>> d.close() + """ + def __cinit__(self): + self.cmd = _command.Command() + self._command_test_errors = [ + None, # valid + 'unsupported *_src trigger', # unsupported trigger bits zeroed + 'unsupported *_src combo, or multiple triggers', + '*_arg out of range', # offending members adjusted to valid values + '*_arg required adjustment', # e.g. trigger timing period rounded + 'invalid chanlist', + # e.g. some boards require same range across channels + ] + + def get_cmd_src_mask(self): + """Detect streaming input/output capabilities + + The command capabilities of the subdevice indicated by the + parameters device and subdevice are probed, and the results + placed in the command structure *command. The trigger source + elements of the command structure are set to be the bitwise-or + of the subdevice's supported trigger sources. Other elements + in the structure are undefined. + """ + cdef _command.Command cmd + cmd = _command.Command() + ret = _comedilib_h.comedi_get_cmd_src_mask( + self.device.device, self.index, cmd.get_comedi_cmd_pointer()) + if ret < 0: + _error.raise_error(function_name='comedi_get_cmd_src_mask', ret=ret) + return cmd + + def get_cmd_generic_timed(self, chanlist_len, scan_period_ns=0): + """Detect streaming input/output capabilities + + The command capabilities of the subdevice indicated by the + parameters device and subdevice are probed, and the results + placed in the command structure pointed to by the parameter + command. The command structure *command is modified to be a + valid command that can be used as a parameter to + comedi_command (after the command has additionally been + assigned a valid chanlist array). The command measures scans + consisting of chanlist_len channels at a scan rate that + corresponds to a period of scan_period_ns nanoseconds. The + rate is adjusted to a rate that the device can handle. + + Note that the `ChanSpec` instances in `cmd.chanlist` are not + initialized to reasonable values. + """ + cdef _command.Command cmd + cmd = _command.Command() + ret = _comedilib_h.comedi_get_cmd_generic_timed( + self.device.device, self.index, cmd.get_comedi_cmd_pointer(), + chanlist_len, int(scan_period_ns)) + cmd.chanlist = [0 for i in range(chanlist_len)] + if ret < 0: + _error.raise_error(function_name='comedi_get_cmd_generic_timed', + ret=ret) + return cmd + + def cancel(self): + "Stop streaming input/output in progress." + ret = _comedilib_h.comedi_cancel(self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_cancel', ret=ret) + + def command(self): + "Start streaming input/output" + ret = _comedilib_h.comedi_command( + self.device.device, self.cmd.get_comedi_cmd_pointer()) + if ret < 0: + _error.raise_error(function_name='comedi_command', ret=ret) + + def command_test(self): + "Test streaming input/output configuration" + ret = _comedilib_h.comedi_command_test( + self.device.device, self.cmd.get_comedi_cmd_pointer()) + return self._command_test_errors[ret] + + def poll(self): + """Force updating of streaming buffer + + If supported by the driver, all available samples are copied + to the streaming buffer. These samples may be pending in DMA + buffers or device FIFOs. If successful, the number of + additional bytes available is returned. + """ + ret = _comedilib_h.comedi_poll(self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_poll', ret=ret) + return ret + + def get_buffer_size(self): + "Streaming buffer size of subdevice" + ret = _comedilib_h.comedi_get_buffer_size( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_buffer_size', ret=ret) + return ret + + def set_buffer_size(self, size): + """Change the size of the streaming buffer + + Returns the new buffer size in bytes. + + The buffer size will be set to size bytes, rounded up to a + multiple of the virtual memory page size. The virtual memory + page size can be determined using `sysconf(_SC_PAGE_SIZE)`. + + This function does not require special privileges. However, it + is limited to a (adjustable) maximum buffer size, which can be + changed by a priveliged user calling + `.comedi_set_max_buffer_size`, or running the program + `comedi_config`. + """ + ret = _comedilib_h.comedi_set_buffer_size( + self.device.device, self.index, int(size)) + if ret < 0: + _error.raise_error(function_name='comedi_set_buffer_size', ret=ret) + return ret + + def get_max_buffer_size(self): + "Maximum streaming buffer size of subdevice" + ret = _comedilib_h.comedi_get_max_buffer_size( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_max_buffer_size', + ret=ret) + return ret + + def set_max_buffer_size(self, max_size): + """Set the maximum streaming buffer size of subdevice + + Returns the old (max?) buffer size on success. + """ + ret = _comedilib_h.comedi_set_max_buffer_size( + self.device.device, self.index, int(max_size)) + if ret < 0: + _error.raise_error(function_name='comedi_set_max_buffer_size', + ret=ret) + return ret + + def get_buffer_contents(self): + "Number of bytes available on an in-progress command" + ret = _comedilib_h.comedi_get_buffer_contents( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_buffer_contents', + ret=ret) + return ret + + def mark_buffer_read(self, num_bytes): + """Next `num_bytes` bytes in the buffer are no longer needed + + Returns the number of bytes successfully marked as read. + + This method should only be used if you are using a `mmap()` to + read data from Comedi's buffer (as opposed to calling `read()` + on the device file), since Comedi will automatically keep + track of how many bytes have been transferred via `read()` + calls. + """ + ret = _comedilib_h.comedi_mark_buffer_read( + self.device.device, self.index, num_bytes) + if ret < 0: + _error.raise_error(function_name='comedi_mark_buffer_read', + ret=ret) + return ret + + def mark_buffer_written(self, num_bytes): + """Next `num_bytes` bytes in the buffer are no longer needed + + Returns the number of bytes successfully marked as written. + + This method should only be used if you are using a `mmap()` to + read data from Comedi's buffer (as opposed to calling + `write()` on the device file), since Comedi will automatically + keep track of how many bytes have been transferred via + `write()` calls. + """ + ret = _comedilib_h.comedi_mark_buffer_written( + self.device.device, self.index, num_bytes) + if ret < 0: + _error.raise_error(function_name='comedi_mark_buffer_written', + ret=ret) + return ret + + def get_buffer_offset(self): + """Offset in bytes of the read(/write?) pointer in the streaming buffer + + This offset is only useful for memory mapped buffers. + """ + ret = _comedilib_h.comedi_get_buffer_offset( + self.device.device, self.index) + if ret < 0: + _error.raise_error(function_name='comedi_get_buffer_offset', ret=ret) + return ret diff --git a/pycomedi/utility.py b/pycomedi/utility.py index 41174e2..08a8333 100644 --- a/pycomedi/utility.py +++ b/pycomedi/utility.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"Useful utility functions and classes." +"Useful utility functions and classes" import array as _array import mmap as _mmap @@ -21,12 +21,10 @@ import os as _os import threading as _threading import time as _time -import comedi as _comedi import numpy as _numpy from . import LOG as _LOG -from . import classes as _classes -from . import constants as _constants +from . import constant as _constant # types from comedi.h @@ -35,55 +33,18 @@ lsampl = _numpy.uint32 sampl_typecode = 'H' lsampl_typecode = 'L' -def subdevice_dtype(subdevice): +def _subdevice_dtype(subdevice): "Return the appropriate `numpy.dtype` based on subdevice flags" if subdevice.get_flags().lsampl: return lsampl return sampl -def subdevice_typecode(subdevice): +def _subdevice_typecode(subdevice): "Return the appropriate `array` type based on subdevice flags" if subdevice.get_flags().lsampl: return lsampl_typecode return sampl_typecode -def sampl_array(list): - "Convert a Python list/tuple into a `sampl` array" - ret = _comedi.sampl_array(len(list)) - for i,x in enumerate(list): - ret[i] = x - return ret - #return _array.array(sampl_typecode, list) - -def lsampl_array(list): - "Convert a Python list/tuple into an `lsampl` array" - ret = _comedi.lsampl_array(len(list)) - for i,x in enumerate(list): - ret[i] = x - return ret - #return _array.array(lsampl_typecode, list) - -def set_insn_chanspec(insn, channels): - "Configure `insn.chanspec` from the list `channels`" - chanlist = _classes.Chanlist(channels) - insn.chanspec = chanlist.chanlist() - -def set_insn_data(insn, data): - "Configure `insn.data` and `insn.n` from the iterable `data`" - insn.n = len(data) - insn.data = lsampl_array(data) - -def set_cmd_chanlist(cmd, channels): - "Configure `cmd.chanlist` and `cmd.chanlist_len` from the list `channels`" - chanlist = _classes.Chanlist(channels) - cmd.chanlist = chanlist.chanlist() - cmd.chanlist_len = len(chanlist) - -def set_cmd_data(cmd, data): - "Configure `cmd.data` and `cmd.data_len` from the iterable `data`" - cmd.data = sampl_array(data) - cmd.data_len = len(data) - def inttrig_insn(subdevice): """Setup an internal trigger for a given `subdevice` @@ -107,8 +68,8 @@ def inttrig_insn(subdevice): .. _section 4.4: http://www.comedi.org/doc/x621.html """ insn = subdevice.insn() - insn.insn = _constants.INSN.inttrig.value - set_insn_data(insn, [0]) + insn.insn = _constant.INSN.inttrig.value + insn.data = [0] return insn @@ -122,11 +83,16 @@ class _ReadWriteThread (_threading.Thread): def __init__(self, subdevice, buffer, name=None): if name == None: name = '<%s subdevice %d>' % ( - self.__class__.__name__, subdevice._index) + self.__class__.__name__, subdevice.index) self.subdevice = subdevice self.buffer = buffer + self._setup_buffer() super(_ReadWriteThread, self).__init__(name=name) + def _setup_buffer(self): + "Currently just a hook for an MMapWriter hack." + pass + def _file(self): """File for reading/writing data to `.subdevice` @@ -134,7 +100,7 @@ class _ReadWriteThread (_threading.Thread): it when you are finished. The file will eventually be closed when the backing `Device` instance is closed. """ - return self.subdevice._device.file + return self.subdevice.device.file class Reader (_ReadWriteThread): @@ -213,6 +179,12 @@ class Reader (_ReadWriteThread): shape=self.buffer.shape, dtype=self.buffer.dtype, buffer=buf) self.buffer[:] = a + #_LOG.critical('ai running? %s' % self.subdevice.get_flags().running) + #while self.subdevice.get_flags().running: + ##_LOG.critical('ai running? %s' % self.subdevice.get_flags().running) + # _time.sleep(0) + #_LOG.critical('ai running? %s' % self.subdevice.get_flags().running) + #_time.sleep(1) class Writer (_ReadWriteThread): @@ -237,9 +209,15 @@ class Writer (_ReadWriteThread): Run the test writer. - >>> r = TestWriter(subdevice=None, buffer=buf, name='Writer-doctest') - >>> r.start() - >>> r.join() + >>> preload = 3 + >>> w = TestWriter(subdevice=None, buffer=buf, name='Writer-doctest', + ... preload=preload) + >>> a = _array.array('H') + >>> a.fromfile(open(t, 'rb'), preload) + >>> a + array('H', [0, 10, 1]) + >>> w.start() + >>> w.join() >>> a = _array.array('H') >>> a.fromfile(open(t, 'rb'), buf.size) >>> a @@ -251,9 +229,14 @@ class Writer (_ReadWriteThread): >>> f.seek(0) >>> buf = _array.array('H', [2*x for x in buf.flat]) - >>> r = TestWriter(subdevice=None, buffer=buf, name='Writer-doctest') - >>> r.start() - >>> r.join() + >>> w = TestWriter(subdevice=None, buffer=buf, name='Writer-doctest', + ... preload=preload) + >>> a = _array.array('H') + >>> a.fromfile(open(t, 'rb'), preload) + >>> a + array('H', [0, 20, 2]) + >>> w.start() + >>> w.join() >>> a = _array.array('H') >>> a.fromfile(open(t, 'rb'), len(buf)) >>> a @@ -264,49 +247,79 @@ class Writer (_ReadWriteThread): >>> f.close() # no need for `close(fd)` >>> remove(t) """ - def run(self): + def __init__(self, *args, **kwargs): + preload = kwargs.pop('preload', 0) + super(Writer, self).__init__(*args, **kwargs) + if not _builtin_array(self.buffer): # numpy.ndarray + self.buffer = self.buffer.flat + preload_buffer = self.buffer[:preload] + self._preload_setup = {'remaining_buffer': self.buffer[preload:]} + f = self._file() + preload_buffer.tofile(f) + f.flush() + + def run(self): + remaining_buffer = self._preload_setup['remaining_buffer'] + del(self._preload_setup) + f = self._file() - self.buffer.tofile(f) + remaining_buffer.tofile(f) f.flush() + #_LOG.critical('ao running? %s' % self.subdevice.get_flags().running) + #while self.subdevice.get_flags().running: + ##_LOG.critical('ao running? %s' % self.subdevice.get_flags().running) + #_time.sleep(0) + #_LOG.critical('ao running? %s' % self.subdevice.get_flags().running) + #_time.sleep(1) class _MMapReadWriteThread (_ReadWriteThread): "`mmap()`-based reader/wrtier" def __init__(self, *args, **kwargs): + preload = kwargs.pop('preload', 0) + access = kwargs.pop('access') super(_MMapReadWriteThread, self).__init__(*args, **kwargs) - self._prot = None - - def _sleep_time(self, mmap_size): - "Expected seconds needed to write a tenth of the mmap buffer" - return 0 - def run(self): # all sizes measured in bytes builtin_array = _builtin_array(self.buffer) - mmap_size = self._mmap_size() - sleep_time = self._sleep_time(mmap_size) - mmap = _mmap.mmap( - self._fileno(), mmap_size, self._prot, _mmap.MAP_SHARED) - mmap_offset = 0 + mmap_size = int(self._mmap_size()) + mmap = _mmap.mmap(self._fileno(), mmap_size, access=access) buffer_offset = 0 - if builtin_array: - remaining = len(self.buffer) - else: # numpy.ndarray - remaining = self.buffer.size - remaining *= self.buffer.itemsize - action = self._initial_action( - mmap, buffer_offset, remaining, - mmap_size, action_bytes=mmap_size) + remaining = self._buffer_bytes(builtin_array) + action,mmap_offset = self._initial_action( + mmap, buffer_offset, remaining, mmap_size, action_bytes=mmap_size, + builtin_array=builtin_array) buffer_offset += action remaining -= action + self._preload_setup = { + 'builtin_array': builtin_array, + 'mmap_size': mmap_size, + 'mmap': mmap, + 'mmap_offset': mmap_offset, + 'buffer_offset': buffer_offset, + 'remaining': remaining, + } + def _sleep_time(self, mmap_size): + "Expected seconds needed to write a tenth of the mmap buffer" + return 0 + + def run(self): + builtin_array = self._preload_setup['builtin_array'] + mmap_size = self._preload_setup['mmap_size'] + mmap = self._preload_setup['mmap'] + mmap_offset = self._preload_setup['mmap_offset'] + buffer_offset = self._preload_setup['buffer_offset'] + remaining = self._preload_setup['remaining'] + del(self._preload_setup) + + sleep_time = self._sleep_time(mmap_size) while remaining > 0: action_bytes = self._action_bytes() if action_bytes > 0: action,mmap_offset = self._act( - mmap, mmap_offset, buffer_offset, remaining, - mmap_size, action_bytes=action_bytes, - builtin_array=builtin_array) + mmap, mmap_offset, buffer_offset, remaining, mmap_size, + action_bytes=action_bytes, builtin_array=builtin_array) buffer_offset += action remaining -= action else: @@ -321,7 +334,7 @@ class _MMapReadWriteThread (_ReadWriteThread): wrap = True else: wrap = False - action_size = min(action_bytes, remaining) + action_size = min(action_bytes, remaining, mmap_size-mmap_offset) self._mmap_action(mmap, buffer_offset, action_size, builtin_array) mmap.flush() # (offset, size), necessary? calls msync? self._mark_action(action_size) @@ -343,9 +356,15 @@ class _MMapReadWriteThread (_ReadWriteThread): # hooks for subclasses + def _buffer_bytes(self, builtin_array): + if builtin_array: + return len(self.buffer)*self.buffer.itemsize + else: # numpy.ndtype + return self.buffer.size*self.buffer.itemsize + def _initial_action(self, mmap, buffer_offset, remaining, mmap_size, - action_bytes=None): - return 0 + action_bytes, builtin_array): + return (0, 0) def _mmap_action(self, mmap, offset, size): raise NotImplementedError() @@ -374,13 +393,14 @@ class MMapReader (_MMapReadWriteThread): for _from,_to in [ # convert class and function names ('`read()`', '`mmap()`'), - ('Writer', 'MMapWriter'), + ('Reader', 'MMapReader'), ('def _file', _mmap_docstring_overrides)]: __doc__ = __doc__.replace(_from, _to) def __init__(self, *args, **kwargs): + assert 'access' not in kwargs + kwargs['access'] = _mmap.ACCESS_READ super(MMapReader, self).__init__(*args, **kwargs) - self._prot = _mmap.PROT_READ def _mmap_action(self, mmap, offset, size, builtin_array): offset /= self.buffer.itemsize @@ -403,17 +423,44 @@ class MMapWriter (_MMapReadWriteThread): __doc__ = Writer.__doc__ for _from,_to in [ ('`write()`', '`mmap()`'), - ('Reader', 'MMapReader'), - ('def _file', _mmap_docstring_overrides)]: + ('Writer', 'MMapWriter'), + ('def _file', _mmap_docstring_overrides), + ("f = _os.fdopen(fd, 'r+')", + "f = _os.fdopen(fd, 'r+'); f.write(6*'\\x00'); f.flush(); f.seek(0)"), + ("a.fromfile(open(t, 'rb'), buf.size)", + "a.fromfile(open(t, 'rb'), w._mmap_size()/a.itemsize)"), + ("a.fromfile(open(t, 'rb'), len(buf))", + "a.fromfile(open(t, 'rb'), w._mmap_size()/a.itemsize)"), + ("array('H', [0, 10, 1, 11, 2, 12])", "array('H', [11, 2, 12])"), + ("array('H', [0, 20, 2, 22, 4, 24])", "array('H', [22, 4, 24])")]: + __doc__ = __doc__.replace(_from, _to) def __init__(self, *args, **kwargs): + assert 'access' not in kwargs + kwargs['access'] = _mmap.ACCESS_WRITE super(MMapWriter, self).__init__(*args, **kwargs) - self._prot = _mmap.PROT_WRITE + + def _setup_buffer(self): self.buffer = buffer(self.buffer) + def _buffer_bytes(self, builtin_array): + return len(self.buffer) # because of buffer() in _setup_buffer + + def _initial_action(self, mmap, buffer_offset, remaining, mmap_size, + action_bytes, builtin_array): + action_size = min(action_bytes, remaining, mmap_size) + self._mmap_action(mmap, buffer_offset, action_size, builtin_array) + if action_size == mmap_size: + mmap.seek(0) + mmap_offset = 0 + else: + mmap_offset = action_size + return (action_size, mmap_offset) + def _mmap_action(self, mmap, offset, size, builtin_array): mmap.write(self.buffer[offset:offset+size]) + mmap.flush() def _mark_action(self, size): self.subdevice.mark_buffer_written(size) diff --git a/setup.py b/setup.py index 2f0394c..2f35c8e 100644 --- a/setup.py +++ b/setup.py @@ -3,11 +3,17 @@ "An object-oriented interface for the Comedi drivers." from distutils.core import setup +from distutils.extension import Extension +import os import os.path +from Cython.Distutils import build_ext +import numpy + from pycomedi import __version__ +package_name = 'pycomedi' classifiers = """\ Development Status :: 2 - Pre-Alpha Intended Audience :: Developers @@ -22,17 +28,31 @@ Topic :: Software Development :: Libraries :: Python Modules _this_dir = os.path.dirname(__file__) -setup(name='pycomedi', +ext_modules = [] +for filename in sorted(os.listdir(package_name)): + basename,extension = os.path.splitext(filename) + if extension == '.pyx': + ext_modules.append( + Extension( + '%s.%s' % (package_name, basename), + [os.path.join(package_name, filename)], + libraries=['comedi'], + include_dirs=[numpy.get_include()], + )) + +setup(name=package_name, version=__version__, maintainer='W. Trevor King', maintainer_email='wking@drexel.edu', - url='http://www.physics.drexel.edu/~wking/unfolding-disasters/posts/pycomedi/', - download_url='http://www.physics.drexel.edu/~wking/code/python/pycomedi-%s.tar.gz' % __version__, + url='http://www.physics.drexel.edu/~wking/unfolding-disasters/posts/%s/' % package_name, + download_url='http://www.physics.drexel.edu/~wking/code/python/%s-%s.tar.gz' % (package_name, __version__), license='GNU General Public License (GPL)', platforms=['all'], description=__doc__, long_description=open(os.path.join(_this_dir, 'README'), 'r').read(), classifiers=filter(None, classifiers.split('\n')), - packages=['pycomedi'], - provides=['pycomedi'], + packages=[package_name], + provides=[package_name], + cmdclass = {'build_ext': build_ext}, + ext_modules = ext_modules, ) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..8d7fcd1 --- /dev/null +++ b/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash -e +# -e to abort script at first error + +python setup.py build_ext --inplace +nosetests --with-doctest pycomedi +ls pycomedi | grep '.pyx$'| while read file; do + mod="${file/.pyx/}" + echo "$mod" + python -c "import doctest, sys; import pycomedi.$mod as m; r = doctest.testmod(m); print r; sys.exit(r.failed)" +done +nosetests --with-doctest --doctest-extension=.txt doc -- 2.26.2