1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
3 # Éric Piel <piel@delmic.com>
5 # This file is part of pycomedi.
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
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.
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/>.
19 "Wrap channel-wide Comedi functions in `Channel` and related classes"
22 cimport numpy as _numpy
23 import numpy as _numpy
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
31 from . import LOG as _LOG
32 from . import PyComediError as _PyComediError
34 from . import chanspec as _chanspec
35 from . import constant as _constant
38 cdef class Channel (object):
39 """Class bundling channel-related functions
41 >>> from .device import Device
42 >>> from . import constant
44 >>> d = Device('/dev/comedi0')
46 >>> s = d.get_read_subdevice()
47 >>> c = s.channel(index=0)
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>
60 cdef public _subdevice_holder.SubdeviceHolder subdevice
67 def __init__(self, subdevice, index):
68 super(Channel, self).__init__()
69 self.subdevice = subdevice
72 cdef _comedilib_h.comedi_t * _device(self) except *:
73 return self.subdevice._device()
75 def get_maxdata(self):
76 ret = _comedilib_h.comedi_get_maxdata(
77 self._device(), self.subdevice.index, self.index)
79 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
82 def get_n_ranges(self):
83 ret = _comedilib_h.comedi_get_n_ranges(
84 self._device(), self.subdevice.index, self.index)
86 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
89 cdef _get_range(self, index):
90 cdef _comedilib_h.comedi_range *rng
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)
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.
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)
108 def _find_range(self, unit, min, max):
110 ret = _comedilib_h.comedi_find_range(
111 self._device(), self.subdevice.index, self.index,
112 _constant.bitwise_value(unit), min, max)
114 _error.raise_error(function_name='comedi_find_range', ret=ret)
117 def find_range(self, unit, min, max):
120 `unit` should be an item from `constants.UNIT`.
122 return self.get_range(self._find_range(unit, min, max))
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)
130 cdef class DigitalChannel (Channel):
131 """Channel configured for reading or writing digital data.
133 >>> from .device import Device
134 >>> from . import constant
136 >>> d = Device('/dev/comedi0')
138 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
139 >>> c = s.channel(0, factory=DigitalChannel)
146 <Range unit:volt min:0.0 max:5.0>
148 >>> direction = c.dio_get_config()
149 >>> direction # doctest: +SKIP
152 >>> c.dio_config(_constant.IO_DIRECTION.input)
153 >>> data = c.dio_read()
157 >>> c.dio_config(_constant.IO_DIRECTION.output)
160 >>> c.dio_config(direction)
164 def dio_config(self, dir):
165 """Change input/output properties
167 `dir` should be an item from `constants.IO_DIRECTION`.
169 ret = _comedilib_h.comedi_dio_config(
170 self._device(), self.subdevice.index, self.index,
171 _constant.bitwise_value(dir))
173 _error.raise_error(function_name='comedi_dio_config', ret=ret)
175 def dio_get_config(self):
176 """Query input/output properties
178 Return an item from `constant.IO_DIRECTION`.
180 cpdef unsigned int dir
181 ret = _comedilib_h.comedi_dio_get_config(
182 self._device(), self.subdevice.index, self.index, &dir)
184 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
185 return _constant.IO_DIRECTION.index_by_value(dir)
189 cpdef unsigned int bit
190 ret = _comedilib_h.comedi_dio_read(
191 self._device(), self.subdevice.index, self.index, &bit)
193 _error.raise_error(function_name='comedi_dio_read', ret=ret)
196 def dio_write(self, bit):
198 ret = _comedilib_h.comedi_dio_write(
199 self._device(), self.subdevice.index, self.index, bit)
201 _error.raise_error(function_name='comedi_dio_write', ret=ret)
204 cdef class AnalogChannel (Channel):
205 """Channel configured for reading or writing analog data.
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.
211 >>> from .device import Device
212 >>> from . import constant
214 >>> d = Device('/dev/comedi0')
216 >>> s = d.get_read_subdevice()
217 >>> c = s.channel(0, factory=AnalogChannel)
220 <Range unit:volt min:-10.0 max:10.0>
224 You can calibrate the channel with the default channel calibration.
226 >>> c.apply_calibration()
228 Or you can use a parsed (and possibly altered) calibration.
230 >>> calibration = d.parse_calibration()
231 >>> c.apply_calibration(calibration=calibration)
233 >>> data = c.data_read()
234 >>> data # doctest: +SKIP
236 >>> converter = c.get_converter()
237 >>> converter # doctest: +NORMALIZE_WHITESPACE
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
247 >>> data = c.data_read_n(5)
248 >>> data # doctest: +SKIP
249 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
251 >>> c.data_read_hint()
252 >>> c.data_read() # doctest: +SKIP
255 >>> data = c.data_read_delayed(nano_sec=1e3)
256 >>> data # doctest: +SKIP
259 >>> s = d.get_write_subdevice()
260 >>> c = s.channel(0, factory=AnalogChannel)
262 >>> converter = c.get_converter()
263 >>> converter # doctest: +NORMALIZE_WHITESPACE
265 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
266 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
268 >>> c.data_write(converter.from_physical(0))
272 Even after the device is closed, the range information is
276 <Range unit:volt min:-10.0 max:10.0>
278 cdef public _range.Range range
279 cdef public object aref
281 def __init__(self, range=None, aref=None, **kwargs):
282 super(AnalogChannel, self).__init__(**kwargs)
284 range = self.get_range(0)
285 elif isinstance(range, int):
286 range = self.get_range(range)
289 flags = self.subdevice.get_flags()
290 for ar in _constant.AREF:
291 if getattr(flags, ar.name):
294 raise _PyComediError(
295 '%s does not support any known analog reference type (%s)'
296 % (self.subdevice, flags))
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),
310 _error.raise_error(function_name='comedi_data_read', ret=ret)
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)
322 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
325 def data_read_hint(self):
326 """Tell driver which channel/range/aref you will read next
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.
336 Alternatively, one can simply use `.data_read_delayed()`,
337 which sets up the input, pauses to allow settling, then
338 performs a conversion.
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))
345 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
347 def data_read_delayed(self, nano_sec=0):
348 """Read single sample after delaying specified settling time.
350 Although the settling time is specified in integer
351 nanoseconds, the actual settling time will be rounded up to
352 the nearest microsecond.
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))
361 _error.raise_error(function_name='comedi_data_read_delayed',
365 def data_write(self, data):
368 Returns 1 (the number of data samples written).
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),
376 _error.raise_error(function_name='comedi_data_write', ret=ret)
379 return _chanspec.ChanSpec(
380 chan=self.index, range=self.range, aref=self.aref)
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
387 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
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)
396 _error.raise_error(function_name='comedi_get_softcal_converter',
400 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
401 self, direction) except *:
402 """Get a calibration polynomial for a hardware-calibrated channel
404 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
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)
412 _error.raise_error(function_name='comedi_get_hardcal_converter',
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,
428 from_physical = self.get_softcal_converter(
429 _constant.CONVERSION_DIRECTION.from_physical,
431 except _PyComediError as e:
432 from_physical_error = e
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
445 def get_converter(self, calibration=None):
446 return self._get_converter(calibration)
448 cdef _apply_calibration(self, char *path):
450 p = self.subdevice.device.get_default_calibration_path()
451 # automatically get a char * refernce into the Python string 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),
460 function_name='comedi_apply_calibration', ret=ret)
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)
471 function_name='comedi_apply_parsed_calibration', ret=ret)
474 def apply_calibration(self, _calibration.Calibration calibration=None,
476 """Apply a calibration to this channel configuration
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`.
484 if calibration is not None:
485 self._apply_parsed_calibration(calibration)
486 elif path is not None:
487 self._apply_calibration(path)
489 self._apply_calibration(NULL)