Mostly-complete Cython implementation.
authorW. Trevor King <wking@drexel.edu>
Tue, 19 Apr 2011 13:17:37 +0000 (09:17 -0400)
committerW. Trevor King <wking@drexel.edu>
Tue, 19 Apr 2011 13:17:46 +0000 (09:17 -0400)
I need some work on Cython upstream to entirely remove the requirement
on the SWIG-wrapped comedi module.

30 files changed:
.gitignore
README
doc/software_timed_analog_IO.txt
doc/software_timed_digital_IO.txt
doc/synchronized_analog_IO.txt
pycomedi/__init__.py
pycomedi/_comedi_h.pxd [new file with mode: 0644]
pycomedi/_comedilib_h.pxd [new file with mode: 0644]
pycomedi/_error.pyx [new file with mode: 0644]
pycomedi/calibration.pxd [new file with mode: 0644]
pycomedi/calibration.pyx [new file with mode: 0644]
pycomedi/channel.pyx [new file with mode: 0644]
pycomedi/chanspec.pyx [new file with mode: 0644]
pycomedi/classes.py [deleted file]
pycomedi/command.pxd [new file with mode: 0644]
pycomedi/command.pyx [new file with mode: 0644]
pycomedi/constant.pxd [new file with mode: 0644]
pycomedi/constant.pyx [moved from pycomedi/constants.py with 70% similarity]
pycomedi/device.pxd [new file with mode: 0644]
pycomedi/device.pyx [new file with mode: 0644]
pycomedi/instruction.pxd [new file with mode: 0644]
pycomedi/instruction.pyx [new file with mode: 0644]
pycomedi/library.pyx [new file with mode: 0644]
pycomedi/range.pxd [new file with mode: 0644]
pycomedi/range.pyx [new file with mode: 0644]
pycomedi/subdevice.pxd [new file with mode: 0644]
pycomedi/subdevice.pyx [new file with mode: 0644]
pycomedi/utility.py
setup.py
test.sh [new file with mode: 0755]

index 567360f95bff84deb545243d94d368beb14f58e6..b188918a07a7e959959e2115c86842d47c5fe481 100644 (file)
@@ -2,5 +2,7 @@ build
 dist
 dummy_py
 *.pyc
+*.c
+*.so
 pycomedi.egg-info
 pycomedi/*.pyc
diff --git a/README b/README
index c6eccf6503ecd6558bfd98e467a63c31dc497ed2..6edb8d5d54fdfd57cd2891a2dc42ffa2c549d369 100644 (file)
--- 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
index 2b325c32ae741e8d3648654a28117900a943ee99..e9b04130563c51701ef38c44fa3be3db5977d663 100644 (file)
@@ -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
+<Range unit:volt min:0.0 max:10.0>
+>>> ao_channel.range = ao_channel.find_range(unit=UNIT.volt, min=0, max=10)
+>>> ao_channel.range
+<Range unit:volt min:0.0 max:10.0>
 >>> 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()
index b76463822ff8ff2ada6fe953dd23d2375dad615a..9533a9ac72d544241b092a248ce22db465f226d2 100644 (file)
@@ -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.
 
index 823427b066dccb8c8bd7eed0c39801ec27550867..1791cf9a0e559081d9b7d4c3c239b46dfb105248 100644 (file)
@@ -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()
index 9b746a5bb9a6dbd0d9bea6245e4ff3f5767bf821..d6eabe464a1c464edb43d814fc1ed7cfdcb89eeb 100644 (file)
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+"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 (file)
index 0000000..234d751
--- /dev/null
@@ -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 (file)
index 0000000..962d66b
--- /dev/null
@@ -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 (file)
index 0000000..003f0e1
--- /dev/null
@@ -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 `<function_name>(*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 (file)
index 0000000..110f959
--- /dev/null
@@ -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 (file)
index 0000000..baa7a55
--- /dev/null
@@ -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
+    <CalibratedConverter
+     to_physical:{coefficients:[1.0, 2.0, 3.0] origin:1.0}
+     from_physical:{coefficients:[0.0] origin:0.0}>
+
+    >>> 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 (file)
index 0000000..c30de49
--- /dev/null
@@ -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)
+    <Range unit:volt min:-10.0 max:10.0>
+    >>> c.find_range(constant.UNIT.volt, 0, 5)
+    <Range unit:volt min:0.0 max:5.0>
+
+    >>> 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)
+    <Range unit:volt min:0.0 max:5.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
+    <Range unit:volt min:-10.0 max:10.0>
+    >>> c.aref
+    <_NamedInt ground>
+
+    >>> data = c.data_read()
+    >>> data  # doctest: +SKIP
+    32670L
+    >>> converter = c.get_converter()
+    >>> converter  # doctest: +NORMALIZE_WHITESPACE
+    <CalibratedConverter
+     to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
+     from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
+    >>> 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
+    <CalibratedConverter
+     to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
+     from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
+
+    >>> c.data_write(converter.from_physical(0))
+
+    >>> d.close()
+
+    Even after the device is closed, the range information is
+    retained.
+
+    >>> c.range
+    <Range unit:volt min:-10.0 max:10.0>
+    """
+    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 (file)
index 0000000..6c8d001
--- /dev/null
@@ -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
+    <ChanSpec chan:1 range:3 aref:diff flags:edge|invert>
+    >>> 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 (file)
index 7a5c14d..0000000
+++ /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 <http://www.gnu.org/licenses/>.
-
-"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.<function_name>(*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 : <comedi.lsampl_array... at 0x...>
-        chanlist_len : 0
-                data : <comedi.sampl_array... at 0x...>
-            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 (file)
index 0000000..ff60f7a
--- /dev/null
@@ -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 (file)
index 0000000..beab7a9
--- /dev/null
@@ -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)
+    <type 'numpy.ndarray'>
+
+    `subdev` is currently just an integer (not a `Subdevice` instance).
+
+    >>> c.subdev = 3
+    >>> c.subdev
+    3L
+    >>> type(c.subdev)
+    <type 'long'>
+
+    `flags` and trigger sources return `FlagValue` instances.
+
+    >>> c.flags  # doctest: +ELLIPSIS
+    <pycomedi.constant.FlagValue object at 0x...>
+    >>> c.flags = CMDF.priority | CMDF.write
+
+    >>> c.start_src  # doctest: +ELLIPSIS
+    <pycomedi.constant.FlagValue object at 0x...>
+    >>> 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]
+    <ChanSpec chan:0 range:0 aref:diff flags:->
+    >>> c.chanlist[0].aref = AREF.ground
+    >>> c.chanlist[0]
+    <ChanSpec chan:0 range:0 aref:diff flags:->
+
+    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]
+    <ChanSpec chan:0 range:0 aref:ground flags:->
+
+    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: [<ChanSpec chan:1 range:0 aref:diff flags:->]
+              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 = <unsigned int *>_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 (file)
index 0000000..3fa56f5
--- /dev/null
@@ -0,0 +1,6 @@
+# Copyright
+
+"Expose `BitwiseOperator` internals at the C level for other Cython modules"
+
+cdef class BitwiseOperator (object):
+    cdef public object value
similarity index 70%
rename from pycomedi/constants.py
rename to pycomedi/constant.pyx
index 550cf39e9dbe1829a803ccaead27c7ea17819870..14b051f5ad03dc890a3fd572472879f40160bf96 100644 (file)
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-"""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>
+<BitwiseOperator 78>
 >>> a.value
 78
 >>> TRIG_SRC.none & TRIG_SRC.now
-<_BitwiseOperator 0>
+<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'
+<BitwiseOperator 66>
+
+.. [#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 (file)
index 0000000..9597fae
--- /dev/null
@@ -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 (file)
index 0000000..bcb8672
--- /dev/null
@@ -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: <ChanSpec chan:0 range:0 aref:ground flags:->
+    >>> 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
+    [<pycomedi.subdevice.Subdevice object at 0x...>,...]
+
+    >>> 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 (file)
index 0000000..85153e7
--- /dev/null
@@ -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 (file)
index 0000000..30ee7c4
--- /dev/null
@@ -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: <ChanSpec chan:0 range:0 aref:ground flags:->
+    >>> i.insn = INSN.write
+
+    `data` takes any iterable that supports `length()` and returns NumPy arrays.
+
+    >>> i.data = [1, 2, 3]
+    >>> type(i.data)
+    <type 'numpy.ndarray'>
+
+    `subdev` is currently just an integer (not a `Subdevice` instance).
+
+    >>> i.subdev = 3
+    >>> i.subdev
+    3
+    >>> type(i.subdev)
+    <type 'int'>
+
+    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
+    <ChanSpec chan:0 range:0 aref:ground flags:->
+
+    To have an effect, you need to explicity set the `chanspec` attribute:
+
+    >>> c = i.chanspec
+    >>> c.aref = AREF.diff
+    >>> i.chanspec = c
+    >>> i.chanspec
+    <ChanSpec chan:0 range:0 aref:diff flags:->
+
+    >>> print str(i)
+        insn: write
+        data: [1 2 3]
+      subdev: 3
+    chanspec: <ChanSpec chan:0 range:0 aref:diff flags:->
+    """
+    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 (file)
index 0000000..9c1037d
--- /dev/null
@@ -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 (file)
index 0000000..8fc048a
--- /dev/null
@@ -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 (file)
index 0000000..d8f8137
--- /dev/null
@@ -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
+    <Range unit:volt min:0.0 max:0.0>
+    >>> r.value
+    1
+    >>> r.unit = UNIT.mA
+    >>> r.min = -2.71828
+    >>> r.max = 3.14159
+    >>> r
+    <Range unit:mA min:-2.71828 max:3.14159>
+    >>> 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 (file)
index 0000000..c237af5
--- /dev/null
@@ -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 (file)
index 0000000..e8ca4e5
--- /dev/null
@@ -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
+    <pycomedi.constant.FlagValue object at 0x...>
+    >>> 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()
+    <type 'numpy.uint16'>
+    >>> 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: [<ChanSpec chan:0 range:0 aref:ground flags:->,
+                     <ChanSpec chan:0 range:0 aref:ground flags:->,
+                     <ChanSpec chan:0 range:0 aref:ground flags:->]
+              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
index 41174e28992ab84f8139a51181aa8116273628be..08a83334a98160d90bf5d72a0dbd91c942a9e2f7 100644 (file)
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-"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)
index 2f0394ccd7b8930de508f0593054420e0f7aacd4..2f35c8e81d1df519e822bb3734a9190df16ed769 100644 (file)
--- 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 (executable)
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