1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
2 # Éric Piel <piel@delmic.com>
4 # This file is part of pycomedi.
6 # pycomedi is free software: you can redistribute it and/or modify it under the
7 # terms of the GNU General Public License as published by the Free Software
8 # Foundation, either version 2 of the License, or (at your option) any later
11 # pycomedi is distributed in the hope that it will be useful, but WITHOUT ANY
12 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along with
16 # pycomedi. If not, see <http://www.gnu.org/licenses/>.
18 "Wrap channel-wide Comedi functions in `Channel` and related classes"
21 cimport numpy as _numpy
22 import numpy as _numpy
26 from calibration cimport CalibratedConverter as _CalibratedConverter
27 from calibration cimport Calibration as _Calibration
28 from range cimport Range as _Range
29 from subdevice cimport Subdevice as _Subdevice
31 from pycomedi import LOG as _LOG
32 from chanspec import ChanSpec as _ChanSpec
33 from pycomedi import PyComediError as _PyComediError
35 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 subdevice
66 def __init__(self, subdevice, index):
67 super(Channel, self).__init__()
68 self.subdevice = subdevice
71 def get_maxdata(self):
72 ret = _comedilib_h.comedi_get_maxdata(
73 self.subdevice.device.device,
74 self.subdevice.index, self.index)
76 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
79 def get_n_ranges(self):
80 ret = _comedilib_h.comedi_get_n_ranges(
81 self.subdevice.device.device,
82 self.subdevice.index, self.index)
84 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
87 cdef _get_range(self, index):
88 cdef _comedilib_h.comedi_range *rng
90 # Memory pointed to by the return value is freed on Device.close().
91 rng = _comedilib_h.comedi_get_range(
92 self.subdevice.device.device,
93 self.subdevice.index, self.index, index)
95 _error.raise_error(function_name='comedi_get_range')
96 ret = _Range(value=index)
97 ret.set_comedi_range(rng[0])
98 # rng[0] is a sneaky way to dereference rng, since Cython
99 # doesn't support *rng.
102 @cython.always_allow_keywords(True)
103 def get_range(self, index):
104 "`Range` instance for the `index`\ed range."
105 return self._get_range(index)
107 def _find_range(self, unit, min, max):
109 ret = _comedilib_h.comedi_find_range(
110 self.subdevice.device.device,
111 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."
127 for i in range(self.get_n_ranges()):
128 #yield self.subdevice(i, **kwargs)
129 # Generators are not supported in Cython 0.14.1
130 ret.append(self.get_range(i, **kwargs))
134 cdef class DigitalChannel (Channel):
135 """Channel configured for reading or writing digital data.
137 >>> from .device import Device
138 >>> from . import constant
140 >>> d = Device('/dev/comedi0')
142 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
143 >>> c = s.channel(0, factory=DigitalChannel)
150 <Range unit:volt min:0.0 max:5.0>
152 >>> direction = c.dio_get_config()
153 >>> direction # doctest: +SKIP
156 >>> c.dio_config(_constant.IO_DIRECTION.input)
157 >>> data = c.dio_read()
161 >>> c.dio_config(_constant.IO_DIRECTION.output)
164 >>> c.dio_config(direction)
168 def dio_config(self, dir):
169 """Change input/output properties
171 `dir` should be an item from `constants.IO_DIRECTION`.
173 ret = _comedilib_h.comedi_dio_config(
174 self.subdevice.device.device,
175 self.subdevice.index, self.index,
176 _constant.bitwise_value(dir))
178 _error.raise_error(function_name='comedi_dio_config', ret=ret)
180 def dio_get_config(self):
181 """Query input/output properties
183 Return an item from `constant.IO_DIRECTION`.
185 cpdef unsigned int dir
186 ret = _comedilib_h.comedi_dio_get_config(
187 self.subdevice.device.device,
188 self.subdevice.index, self.index, &dir)
190 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
191 return _constant.IO_DIRECTION.index_by_value(dir)
195 cpdef unsigned int bit
196 ret = _comedilib_h.comedi_dio_read(
197 self.subdevice.device.device,
198 self.subdevice.index, self.index, &bit)
200 _error.raise_error(function_name='comedi_dio_read', ret=ret)
203 def dio_write(self, bit):
205 ret = _comedilib_h.comedi_dio_write(
206 self.subdevice.device.device,
207 self.subdevice.index, self.index, bit)
209 _error.raise_error(function_name='comedi_dio_write', ret=ret)
212 cdef class AnalogChannel (Channel):
213 """Channel configured for reading or writing analog data.
215 `range` should be a `Range` instance, `aref` should be an
216 `constants.AREF` instance. If not specified, defaults are chosen
217 based on the capabilities of the subdevice.
219 >>> from .device import Device
220 >>> from . import constant
222 >>> d = Device('/dev/comedi0')
224 >>> s = d.get_read_subdevice()
225 >>> c = s.channel(0, factory=AnalogChannel)
228 <Range unit:volt min:-10.0 max:10.0>
232 You can calibrate the channel with the default channel calibration.
234 >>> c.apply_calibration()
236 Or you can use a parsed (and possibly altered) calibration.
238 >>> calibration = d.parse_calibration()
239 >>> c.apply_calibration(calibration=calibration)
241 >>> data = c.data_read()
242 >>> data # doctest: +SKIP
244 >>> converter = c.get_converter()
245 >>> converter # doctest: +NORMALIZE_WHITESPACE
247 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
248 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
249 >>> physical_data = converter.to_physical(data)
250 >>> physical_data # doctest: +SKIP
251 -0.029755092698558021
252 >>> converter.from_physical(physical_data) == data
255 >>> data = c.data_read_n(5)
256 >>> data # doctest: +SKIP
257 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
259 >>> c.data_read_hint()
260 >>> c.data_read() # doctest: +SKIP
263 >>> data = c.data_read_delayed(nano_sec=1e3)
264 >>> data # doctest: +SKIP
267 >>> s = d.get_write_subdevice()
268 >>> c = s.channel(0, factory=AnalogChannel)
270 >>> converter = c.get_converter()
271 >>> converter # doctest: +NORMALIZE_WHITESPACE
273 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
274 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
276 >>> c.data_write(converter.from_physical(0))
280 Even after the device is closed, the range information is
284 <Range unit:volt min:-10.0 max:10.0>
286 cdef public _Range range
287 cdef public object aref
289 def __init__(self, range=None, aref=None, **kwargs):
290 super(AnalogChannel, self).__init__(**kwargs)
292 range = self.get_range(0)
293 elif isinstance(range, int):
294 range = self.get_range(range)
297 flags = self.subdevice.get_flags()
298 for ar in _constant.AREF:
299 if getattr(flags, ar.name):
302 raise _PyComediError(
303 '%s does not support any known analog reference type (%s)'
304 % (self.subdevice, flags))
311 cdef _comedi_h.lsampl_t data
312 ret = _comedilib_h.comedi_data_read(
313 self.subdevice.device.device,
314 self.subdevice.index, self.index,
315 _constant.bitwise_value(self.range),
316 _constant.bitwise_value(self.aref),
319 _error.raise_error(function_name='comedi_data_read', ret=ret)
322 def data_read_n(self, n):
323 "Read `n` samples (timing between samples is undefined)."
324 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
325 ret = _comedilib_h.comedi_data_read_n(
326 self.subdevice.device.device,
327 self.subdevice.index, self.index,
328 _constant.bitwise_value(self.range),
329 _constant.bitwise_value(self.aref),
330 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
332 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
335 def data_read_hint(self):
336 """Tell driver which channel/range/aref you will read next
338 Used to prepare an analog input for a subsequent call to
339 comedi_data_read. It is not necessary to use this function,
340 but it can be useful for eliminating inaccuaracies caused by
341 insufficient settling times when switching the channel or gain
342 on an analog input. This function sets an analog input to the
343 channel, range, and aref specified but does not perform an
344 actual analog to digital conversion.
346 Alternatively, one can simply use `.data_read_delayed()`,
347 which sets up the input, pauses to allow settling, then
348 performs a conversion.
350 ret = _comedilib_h.comedi_data_read_hint(
351 self.subdevice.device.device,
352 self.subdevice.index, self.index,
353 _constant.bitwise_value(self.range),
354 _constant.bitwise_value(self.aref))
356 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
358 def data_read_delayed(self, nano_sec=0):
359 """Read single sample after delaying specified settling time.
361 Although the settling time is specified in integer
362 nanoseconds, the actual settling time will be rounded up to
363 the nearest microsecond.
365 cdef _comedi_h.lsampl_t data
366 ret = _comedilib_h.comedi_data_read_delayed(
367 self.subdevice.device.device,
368 self.subdevice.index, self.index,
369 _constant.bitwise_value(self.range),
370 _constant.bitwise_value(self.aref),
371 &data, int(nano_sec))
373 _error.raise_error(function_name='comedi_data_read_delayed',
377 def data_write(self, data):
380 Returns 1 (the number of data samples written).
382 ret = _comedilib_h.comedi_data_write(
383 self.subdevice.device.device,
384 self.subdevice.index, self.index,
385 _constant.bitwise_value(self.range),
386 _constant.bitwise_value(self.aref),
389 _error.raise_error(function_name='comedi_data_write', ret=ret)
392 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
395 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
396 self, direction, calibration):
399 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
401 cdef _comedilib_h.comedi_polynomial_t poly
402 rc = _comedilib_h.comedi_get_softcal_converter(
403 self.subdevice.index, self.index,
404 _constant.bitwise_value(self.range),
405 _constant.bitwise_value(direction),
406 <_comedilib_h.comedi_calibration_t*> calibration, &poly)
408 _error.raise_error(function_name='comedi_get_softcal_converter',
412 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
416 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
418 cdef _comedilib_h.comedi_polynomial_t poly
419 rc = _comedilib_h.comedi_get_hardcal_converter(
420 self.subdevice.device.device,
421 self.subdevice.index, self.index,
422 _constant.bitwise_value(self.range),
423 _constant.bitwise_value(direction), &poly)
425 _error.raise_error(function_name='comedi_get_hardcal_converter',
429 cdef _get_converter(self, calibration):
430 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
431 cdef _CalibratedConverter ret
432 flags = self.subdevice.get_flags()
433 if flags.soft_calibrated:
434 if calibration is None:
435 calibration = self.subdevice.device.parse_calibration()
436 to_physical = self.get_softcal_converter(
437 _constant.CONVERSION_DIRECTION.to_physical,
439 from_physical = self.get_softcal_converter(
440 _constant.CONVERSION_DIRECTION.from_physical,
443 to_physical = self.get_hardcal_converter(
444 _constant.CONVERSION_DIRECTION.to_physical)
445 from_physical = self.get_hardcal_converter(
446 _constant.CONVERSION_DIRECTION.from_physical)
447 ret = _CalibratedConverter()
448 ret._to_physical = to_physical
449 ret._from_physical = from_physical
452 def get_converter(self, calibration=None):
453 return self._get_converter(calibration)
455 cdef _apply_calibration(self, char *path):
457 p = self.subdevice.device.get_default_calibration_path()
458 # automatically get a char * refernce into the Python string p
460 ret = _comedilib_h.comedi_apply_calibration(
461 self.subdevice.device.device,
462 self.subdevice.index, self.index,
463 _constant.bitwise_value(self.range),
464 _constant.bitwise_value(self.aref),
468 function_name='comedi_apply_calibration', ret=ret)
471 cdef _apply_parsed_calibration(self, _Calibration calibration):
472 ret = _comedilib_h.comedi_apply_parsed_calibration(
473 self.subdevice.device.device,
474 self.subdevice.index, self.index,
475 _constant.bitwise_value(self.range),
476 _constant.bitwise_value(self.aref),
477 calibration.calibration)
480 function_name='comedi_apply_parsed_calibration', ret=ret)
483 def apply_calibration(self, calibration=None, path=None):
484 """Apply a calibration to this channel configuration
486 `calibration` may None or a `Calibration` instance. If it is
487 a `Calibration` instance, that instance is used for the
488 calibration. Otherwise we look at `path`. If `path` is None,
489 we use the default device calibration, otherwise we try and
490 use the calibration file located at `path`.
492 if calibration is not None:
493 self._apply_parsed_calibration(calibration)
494 elif path is not None:
495 self._apply_calibration(path)
497 self._apply_calibration(NULL)