command: Fix except declaration for get_comedi_cmd_pointer
[pycomedi.git] / pycomedi / channel.pyx
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
3 #                         Éric Piel <piel@delmic.com>
4 #
5 # This file is part of pycomedi.
6 #
7 # pycomedi is free software: you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation, either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # pycomedi is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # pycomedi.  If not, see <http://www.gnu.org/licenses/>.
18
19 "Wrap channel-wide Comedi functions in `Channel` and related classes"
20
21 cimport cython
22 cimport numpy as _numpy
23 import numpy as _numpy
24
25 from pycomedi cimport _comedi_h
26 from pycomedi cimport _comedilib_h
27 from pycomedi cimport calibration as _calibration
28 from pycomedi cimport range as _range
29 from pycomedi cimport subdevice_holder as _subdevice_holder
30
31 from . import LOG as _LOG
32 from . import PyComediError as _PyComediError
33 from . import _error
34 from . import chanspec as _chanspec
35 from . import constant as _constant
36
37
38 cdef class Channel (object):
39     """Class bundling channel-related functions
40
41     >>> from .device import Device
42     >>> from . import constant
43
44     >>> d = Device('/dev/comedi0')
45     >>> d.open()
46     >>> s = d.get_read_subdevice()
47     >>> c = s.channel(index=0)
48
49     >>> c.get_maxdata()
50     65535L
51     >>> c.get_n_ranges()
52     16
53     >>> c.get_range(index=0)
54     <Range unit:volt min:-10.0 max:10.0>
55     >>> c.find_range(constant.UNIT.volt, 0, 5)
56     <Range unit:volt min:0.0 max:5.0>
57
58     >>> d.close()
59     """
60     cdef public _subdevice_holder.SubdeviceHolder subdevice
61     cdef public int index
62
63     def __cinit__(self):
64         self.subdevice = None
65         self.index = -1
66
67     def __init__(self, subdevice, index):
68         super(Channel, self).__init__()
69         self.subdevice = subdevice
70         self.index = index
71
72     cdef _comedilib_h.comedi_t * _device(self) except *:
73         return self.subdevice._device()
74
75     def get_maxdata(self):
76         ret = _comedilib_h.comedi_get_maxdata(
77             self._device(), self.subdevice.index, self.index)
78         if ret < 0:
79             _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
80         return ret
81
82     def get_n_ranges(self):
83         ret = _comedilib_h.comedi_get_n_ranges(
84             self._device(), self.subdevice.index, self.index)
85         if ret < 0:
86             _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
87         return ret
88
89     cdef _get_range(self, index):
90         cdef _comedilib_h.comedi_range *rng
91         cdef _range.Range ret
92         # Memory pointed to by the return value is freed on Device.close().
93         rng = _comedilib_h.comedi_get_range(
94             self._device(), self.subdevice.index, self.index, index)
95         if rng is NULL:
96             _error.raise_error(function_name='comedi_get_range')
97         ret = _range.Range(value=index)
98         ret.set_comedi_range(rng[0])
99         # rng[0] is a sneaky way to dereference rng, since Cython
100         # doesn't support *rng.
101         return ret
102
103     @cython.always_allow_keywords(True)
104     def get_range(self, index):
105         "`Range` instance for the `index`\ed range."
106         return self._get_range(index)
107
108     def _find_range(self, unit, min, max):
109         "Search for range"
110         ret = _comedilib_h.comedi_find_range(
111             self._device(), self.subdevice.index, self.index,
112             _constant.bitwise_value(unit), min, max)
113         if ret < 0:
114             _error.raise_error(function_name='comedi_find_range', ret=ret)
115         return ret
116
117     def find_range(self, unit, min, max):
118         """Search for range
119
120         `unit` should be an item from `constants.UNIT`.
121         """
122         return self.get_range(self._find_range(unit, min, max))
123
124     def ranges(self, **kwargs):
125         "Iterate through all available ranges."
126         for i in range(self.get_n_ranges()):
127             yield self.get_range(i, **kwargs)
128
129
130 cdef class DigitalChannel (Channel):
131     """Channel configured for reading or writing digital data.
132
133     >>> from .device import Device
134     >>> from . import constant
135
136     >>> d = Device('/dev/comedi0')
137     >>> d.open()
138     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
139     >>> c = s.channel(0, factory=DigitalChannel)
140
141     >>> c.get_maxdata()
142     1L
143     >>> c.get_n_ranges()
144     1
145     >>> c.get_range(0)
146     <Range unit:volt min:0.0 max:5.0>
147
148     >>> direction = c.dio_get_config()
149     >>> direction  # doctest: +SKIP
150     <_NamedInt input>
151
152     >>> c.dio_config(_constant.IO_DIRECTION.input)
153     >>> data = c.dio_read()
154     >>> data
155     1
156
157     >>> c.dio_config(_constant.IO_DIRECTION.output)
158     >>> c.dio_write(1)
159
160     >>> c.dio_config(direction)
161
162     >>> d.close()
163     """
164     def dio_config(self, dir):
165         """Change input/output properties
166
167         `dir` should be an item from `constants.IO_DIRECTION`.
168         """
169         ret = _comedilib_h.comedi_dio_config(
170             self._device(), self.subdevice.index, self.index,
171             _constant.bitwise_value(dir))
172         if ret < 0:
173             _error.raise_error(function_name='comedi_dio_config', ret=ret)
174
175     def dio_get_config(self):
176         """Query input/output properties
177
178         Return an item from `constant.IO_DIRECTION`.
179         """
180         cpdef unsigned int dir
181         ret = _comedilib_h.comedi_dio_get_config(
182             self._device(), self.subdevice.index, self.index, &dir)
183         if ret < 0:
184             _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
185         return _constant.IO_DIRECTION.index_by_value(dir)
186
187     def dio_read(self):
188         "Read a single bit"
189         cpdef unsigned int bit
190         ret = _comedilib_h.comedi_dio_read(
191             self._device(), self.subdevice.index, self.index, &bit)
192         if ret < 0:
193             _error.raise_error(function_name='comedi_dio_read', ret=ret)
194         return int(bit)
195
196     def dio_write(self, bit):
197         "Write a single bit"
198         ret = _comedilib_h.comedi_dio_write(
199             self._device(), self.subdevice.index, self.index, bit)
200         if ret < 0:
201             _error.raise_error(function_name='comedi_dio_write', ret=ret)
202
203
204 cdef class AnalogChannel (Channel):
205     """Channel configured for reading or writing analog data.
206
207     `range` should be a `Range` instance, `aref` should be an
208     `constants.AREF` instance.  If not specified, defaults are chosen
209     based on the capabilities of the subdevice.
210
211     >>> from .device import Device
212     >>> from . import constant
213
214     >>> d = Device('/dev/comedi0')
215     >>> d.open()
216     >>> s = d.get_read_subdevice()
217     >>> c = s.channel(0, factory=AnalogChannel)
218
219     >>> c.range
220     <Range unit:volt min:-10.0 max:10.0>
221     >>> c.aref
222     <_NamedInt ground>
223
224     You can calibrate the channel with the default channel calibration.
225
226     >>> c.apply_calibration()
227
228     Or you can use a parsed (and possibly altered) calibration.
229
230     >>> calibration = d.parse_calibration()
231     >>> c.apply_calibration(calibration=calibration)
232
233     >>> data = c.data_read()
234     >>> data  # doctest: +SKIP
235     32670L
236     >>> converter = c.get_converter()
237     >>> converter  # doctest: +NORMALIZE_WHITESPACE
238     <CalibratedConverter
239      to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
240      from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
241     >>> physical_data = converter.to_physical(data)
242     >>> physical_data  # doctest: +SKIP
243     -0.029755092698558021
244     >>> converter.from_physical(physical_data) == data
245     True
246
247     >>> data = c.data_read_n(5)
248     >>> data  # doctest: +SKIP
249     array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
250
251     >>> c.data_read_hint()
252     >>> c.data_read()  # doctest: +SKIP
253     32672L
254
255     >>> data = c.data_read_delayed(nano_sec=1e3)
256     >>> data  # doctest: +SKIP
257     32672L
258
259     >>> s = d.get_write_subdevice()
260     >>> c = s.channel(0, factory=AnalogChannel)
261
262     >>> converter = c.get_converter()
263     >>> converter  # doctest: +NORMALIZE_WHITESPACE
264     <CalibratedConverter
265      to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
266      from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
267
268     >>> c.data_write(converter.from_physical(0))
269
270     >>> d.close()
271
272     Even after the device is closed, the range information is
273     retained.
274
275     >>> c.range
276     <Range unit:volt min:-10.0 max:10.0>
277     """
278     cdef public _range.Range range
279     cdef public object aref
280
281     def __init__(self, range=None, aref=None, **kwargs):
282         super(AnalogChannel, self).__init__(**kwargs)
283         if range == None:
284             range = self.get_range(0)
285         elif isinstance(range, int):
286             range = self.get_range(range)
287         self.range = range
288         if aref == None:
289             flags = self.subdevice.get_flags()
290             for ar in _constant.AREF:
291                 if getattr(flags, ar.name):
292                     aref = ar
293                     break
294                 raise _PyComediError(
295                     '%s does not support any known analog reference type (%s)'
296                     % (self.subdevice, flags))
297         self.aref = aref
298
299     # syncronous stuff
300
301     def data_read(self):
302         "Read one sample"
303         cdef _comedi_h.lsampl_t data
304         ret = _comedilib_h.comedi_data_read(
305             self._device(), self.subdevice.index, self.index,
306             _constant.bitwise_value(self.range),
307             _constant.bitwise_value(self.aref),
308             &data)
309         if ret < 0:
310             _error.raise_error(function_name='comedi_data_read', ret=ret)
311         return data
312
313     def data_read_n(self, n):
314         "Read `n` samples (timing between samples is undefined)."
315         data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
316         ret = _comedilib_h.comedi_data_read_n(
317             self._device(), self.subdevice.index, self.index,
318             _constant.bitwise_value(self.range),
319             _constant.bitwise_value(self.aref),
320             <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
321         if ret < 0:
322             _error.raise_error(function_name='comedi_data_read_n', ret=ret)
323         return data
324
325     def data_read_hint(self):
326         """Tell driver which channel/range/aref you will read next
327
328         Used to prepare an analog input for a subsequent call to
329         comedi_data_read.  It is not necessary to use this function,
330         but it can be useful for eliminating inaccuaracies caused by
331         insufficient settling times when switching the channel or gain
332         on an analog input.  This function sets an analog input to the
333         channel, range, and aref specified but does not perform an
334         actual analog to digital conversion.
335
336         Alternatively, one can simply use `.data_read_delayed()`,
337         which sets up the input, pauses to allow settling, then
338         performs a conversion.
339         """
340         ret = _comedilib_h.comedi_data_read_hint(
341             self._device(), self.subdevice.index, self.index,
342             _constant.bitwise_value(self.range),
343             _constant.bitwise_value(self.aref))
344         if ret < 0:
345             _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
346
347     def data_read_delayed(self, nano_sec=0):
348         """Read single sample after delaying specified settling time.
349
350         Although the settling time is specified in integer
351         nanoseconds, the actual settling time will be rounded up to
352         the nearest microsecond.
353         """
354         cdef _comedi_h.lsampl_t data
355         ret = _comedilib_h.comedi_data_read_delayed(
356             self._device(), self.subdevice.index, self.index,
357             _constant.bitwise_value(self.range),
358             _constant.bitwise_value(self.aref),
359             &data, int(nano_sec))
360         if ret < 0:
361             _error.raise_error(function_name='comedi_data_read_delayed',
362                                ret=ret)
363         return data
364
365     def data_write(self, data):
366         """Write one sample
367
368         Returns 1 (the number of data samples written).
369         """
370         ret = _comedilib_h.comedi_data_write(
371             self._device(), self.subdevice.index, self.index,
372             _constant.bitwise_value(self.range),
373             _constant.bitwise_value(self.aref),
374             int(data))
375         if ret != 1:
376             _error.raise_error(function_name='comedi_data_write', ret=ret)
377
378     def chanspec(self):
379         return _chanspec.ChanSpec(
380             chan=self.index, range=self.range, aref=self.aref)
381
382
383     cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
384         self, direction, _calibration.Calibration calibration) except *:
385         """Get a calibration polynomial for a software-calibrated channel
386
387         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
388         """
389         cdef _comedilib_h.comedi_polynomial_t poly
390         rc = _comedilib_h.comedi_get_softcal_converter(
391             self.subdevice.index, self.index,
392             _constant.bitwise_value(self.range),
393             _constant.bitwise_value(direction),
394             calibration.calibration, &poly)
395         if rc < 0:
396             _error.raise_error(function_name='comedi_get_softcal_converter',
397                                ret=rc)
398         return poly
399
400     cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
401         self, direction) except *:
402         """Get a calibration polynomial for a hardware-calibrated channel
403
404         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
405         """
406         cdef _comedilib_h.comedi_polynomial_t poly
407         rc = _comedilib_h.comedi_get_hardcal_converter(
408             self._device(), self.subdevice.index, self.index,
409             _constant.bitwise_value(self.range),
410             _constant.bitwise_value(direction), &poly)
411         if rc < 0:
412             _error.raise_error(function_name='comedi_get_hardcal_converter',
413                                ret=rc)
414         return poly
415
416     cdef _get_converter(self, _calibration.Calibration calibration):
417         cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
418         cdef _calibration.CalibratedConverter ret
419         flags = self.subdevice.get_flags()
420         from_physical_error = None
421         if flags.soft_calibrated:
422             if calibration is None:
423                 calibration = self.subdevice.device.parse_calibration()
424             to_physical = self.get_softcal_converter(
425                 _constant.CONVERSION_DIRECTION.to_physical,
426                 calibration)
427             try:
428                 from_physical = self.get_softcal_converter(
429                     _constant.CONVERSION_DIRECTION.from_physical,
430                     calibration)
431             except _PyComediError as e:
432                 from_physical_error = e
433         else:
434             to_physical = self.get_hardcal_converter(
435                 _constant.CONVERSION_DIRECTION.to_physical)
436             from_physical = self.get_hardcal_converter(
437                 _constant.CONVERSION_DIRECTION.from_physical)
438         ret = _calibration.CalibratedConverter(
439             from_physical_error=from_physical_error)
440         ret._to_physical = to_physical
441         if from_physical_error is None:
442             ret._from_physical = from_physical
443         return ret
444
445     def get_converter(self, calibration=None):
446         return self._get_converter(calibration)
447
448     cdef _apply_calibration(self, char *path):
449         if path is NULL:
450             p = self.subdevice.device.get_default_calibration_path()
451             # automatically get a char * refernce into the Python string p
452             path = p
453         ret = _comedilib_h.comedi_apply_calibration(
454             self._device(), self.subdevice.index, self.index,
455             _constant.bitwise_value(self.range),
456             _constant.bitwise_value(self.aref),
457             path)
458         if ret < 0:
459             _error.raise_error(
460                 function_name='comedi_apply_calibration', ret=ret)
461         return ret
462
463     cdef _apply_parsed_calibration(self, _calibration.Calibration calibration):
464         ret = _comedilib_h.comedi_apply_parsed_calibration(
465             self._device(), self.subdevice.index, self.index,
466             _constant.bitwise_value(self.range),
467             _constant.bitwise_value(self.aref),
468             calibration.calibration)
469         if ret < 0:
470             _error.raise_error(
471                 function_name='comedi_apply_parsed_calibration', ret=ret)
472         return ret
473
474     def apply_calibration(self, _calibration.Calibration calibration=None,
475                           path=None):
476         """Apply a calibration to this channel configuration
477
478         `calibration` may None or a `Calibration` instance.  If it is
479         a `Calibration` instance, that instance is used for the
480         calibration.  Otherwise we look at `path`.  If `path` is None,
481         we use the default device calibration, otherwise we try and
482         use the calibration file located at `path`.
483         """
484         if calibration is not None:
485             self._apply_parsed_calibration(calibration)
486         elif path is not None:
487             self._apply_calibration(path)
488         else:
489             self._apply_calibration(NULL)