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
27 from calibration cimport CalibratedConverter as _CalibratedConverter
28 from calibration cimport Calibration as _Calibration
29 from range cimport Range as _Range
30 from subdevice_holder cimport SubdeviceHolder as _SubdeviceHolder
32 from pycomedi import LOG as _LOG
33 from chanspec import ChanSpec as _ChanSpec
34 from pycomedi import PyComediError as _PyComediError
36 import constant as _constant
39 cdef class Channel (object):
40 """Class bundling channel-related functions
42 >>> from .device import Device
43 >>> from . import constant
45 >>> d = Device('/dev/comedi0')
47 >>> s = d.get_read_subdevice()
48 >>> c = s.channel(index=0)
54 >>> c.get_range(index=0)
55 <Range unit:volt min:-10.0 max:10.0>
56 >>> c.find_range(constant.UNIT.volt, 0, 5)
57 <Range unit:volt min:0.0 max:5.0>
61 cdef public _SubdeviceHolder subdevice
68 def __init__(self, subdevice, index):
69 super(Channel, self).__init__()
70 self.subdevice = subdevice
73 cdef _comedilib_h.comedi_t * _device(self) except *:
74 return self.subdevice._device()
76 def get_maxdata(self):
77 ret = _comedilib_h.comedi_get_maxdata(
78 self._device(), self.subdevice.index, self.index)
80 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
83 def get_n_ranges(self):
84 ret = _comedilib_h.comedi_get_n_ranges(
85 self._device(), self.subdevice.index, self.index)
87 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
90 cdef _get_range(self, index):
91 cdef _comedilib_h.comedi_range *rng
93 # Memory pointed to by the return value is freed on Device.close().
94 rng = _comedilib_h.comedi_get_range(
95 self._device(), self.subdevice.index, self.index, index)
97 _error.raise_error(function_name='comedi_get_range')
98 ret = _Range(value=index)
99 ret.set_comedi_range(rng[0])
100 # rng[0] is a sneaky way to dereference rng, since Cython
101 # doesn't support *rng.
104 @cython.always_allow_keywords(True)
105 def get_range(self, index):
106 "`Range` instance for the `index`\ed range."
107 return self._get_range(index)
109 def _find_range(self, unit, min, max):
111 ret = _comedilib_h.comedi_find_range(
112 self._device(), self.subdevice.index, self.index,
113 _constant.bitwise_value(unit), min, max)
115 _error.raise_error(function_name='comedi_find_range', ret=ret)
118 def find_range(self, unit, min, max):
121 `unit` should be an item from `constants.UNIT`.
123 return self.get_range(self._find_range(unit, min, max))
125 def ranges(self, **kwargs):
126 "Iterate through all available ranges."
128 for i in range(self.get_n_ranges()):
129 #yield self.subdevice(i, **kwargs)
130 # Generators are not supported in Cython 0.14.1
131 ret.append(self.get_range(i, **kwargs))
135 cdef class DigitalChannel (Channel):
136 """Channel configured for reading or writing digital data.
138 >>> from .device import Device
139 >>> from . import constant
141 >>> d = Device('/dev/comedi0')
143 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
144 >>> c = s.channel(0, factory=DigitalChannel)
151 <Range unit:volt min:0.0 max:5.0>
153 >>> direction = c.dio_get_config()
154 >>> direction # doctest: +SKIP
157 >>> c.dio_config(_constant.IO_DIRECTION.input)
158 >>> data = c.dio_read()
162 >>> c.dio_config(_constant.IO_DIRECTION.output)
165 >>> c.dio_config(direction)
169 def dio_config(self, dir):
170 """Change input/output properties
172 `dir` should be an item from `constants.IO_DIRECTION`.
174 ret = _comedilib_h.comedi_dio_config(
175 self._device(), 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._device(), self.subdevice.index, self.index, &dir)
189 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
190 return _constant.IO_DIRECTION.index_by_value(dir)
194 cpdef unsigned int bit
195 ret = _comedilib_h.comedi_dio_read(
196 self._device(), self.subdevice.index, self.index, &bit)
198 _error.raise_error(function_name='comedi_dio_read', ret=ret)
201 def dio_write(self, bit):
203 ret = _comedilib_h.comedi_dio_write(
204 self._device(), self.subdevice.index, self.index, bit)
206 _error.raise_error(function_name='comedi_dio_write', ret=ret)
209 cdef class AnalogChannel (Channel):
210 """Channel configured for reading or writing analog data.
212 `range` should be a `Range` instance, `aref` should be an
213 `constants.AREF` instance. If not specified, defaults are chosen
214 based on the capabilities of the subdevice.
216 >>> from .device import Device
217 >>> from . import constant
219 >>> d = Device('/dev/comedi0')
221 >>> s = d.get_read_subdevice()
222 >>> c = s.channel(0, factory=AnalogChannel)
225 <Range unit:volt min:-10.0 max:10.0>
229 You can calibrate the channel with the default channel calibration.
231 >>> c.apply_calibration()
233 Or you can use a parsed (and possibly altered) calibration.
235 >>> calibration = d.parse_calibration()
236 >>> c.apply_calibration(calibration=calibration)
238 >>> data = c.data_read()
239 >>> data # doctest: +SKIP
241 >>> converter = c.get_converter()
242 >>> converter # doctest: +NORMALIZE_WHITESPACE
244 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
245 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
246 >>> physical_data = converter.to_physical(data)
247 >>> physical_data # doctest: +SKIP
248 -0.029755092698558021
249 >>> converter.from_physical(physical_data) == data
252 >>> data = c.data_read_n(5)
253 >>> data # doctest: +SKIP
254 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
256 >>> c.data_read_hint()
257 >>> c.data_read() # doctest: +SKIP
260 >>> data = c.data_read_delayed(nano_sec=1e3)
261 >>> data # doctest: +SKIP
264 >>> s = d.get_write_subdevice()
265 >>> c = s.channel(0, factory=AnalogChannel)
267 >>> converter = c.get_converter()
268 >>> converter # doctest: +NORMALIZE_WHITESPACE
270 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
271 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
273 >>> c.data_write(converter.from_physical(0))
277 Even after the device is closed, the range information is
281 <Range unit:volt min:-10.0 max:10.0>
283 cdef public _Range range
284 cdef public object aref
286 def __init__(self, range=None, aref=None, **kwargs):
287 super(AnalogChannel, self).__init__(**kwargs)
289 range = self.get_range(0)
290 elif isinstance(range, int):
291 range = self.get_range(range)
294 flags = self.subdevice.get_flags()
295 for ar in _constant.AREF:
296 if getattr(flags, ar.name):
299 raise _PyComediError(
300 '%s does not support any known analog reference type (%s)'
301 % (self.subdevice, flags))
308 cdef _comedi_h.lsampl_t data
309 ret = _comedilib_h.comedi_data_read(
310 self._device(), self.subdevice.index, self.index,
311 _constant.bitwise_value(self.range),
312 _constant.bitwise_value(self.aref),
315 _error.raise_error(function_name='comedi_data_read', ret=ret)
318 def data_read_n(self, n):
319 "Read `n` samples (timing between samples is undefined)."
320 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
321 ret = _comedilib_h.comedi_data_read_n(
322 self._device(), self.subdevice.index, self.index,
323 _constant.bitwise_value(self.range),
324 _constant.bitwise_value(self.aref),
325 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
327 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
330 def data_read_hint(self):
331 """Tell driver which channel/range/aref you will read next
333 Used to prepare an analog input for a subsequent call to
334 comedi_data_read. It is not necessary to use this function,
335 but it can be useful for eliminating inaccuaracies caused by
336 insufficient settling times when switching the channel or gain
337 on an analog input. This function sets an analog input to the
338 channel, range, and aref specified but does not perform an
339 actual analog to digital conversion.
341 Alternatively, one can simply use `.data_read_delayed()`,
342 which sets up the input, pauses to allow settling, then
343 performs a conversion.
345 ret = _comedilib_h.comedi_data_read_hint(
346 self._device(), self.subdevice.index, self.index,
347 _constant.bitwise_value(self.range),
348 _constant.bitwise_value(self.aref))
350 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
352 def data_read_delayed(self, nano_sec=0):
353 """Read single sample after delaying specified settling time.
355 Although the settling time is specified in integer
356 nanoseconds, the actual settling time will be rounded up to
357 the nearest microsecond.
359 cdef _comedi_h.lsampl_t data
360 ret = _comedilib_h.comedi_data_read_delayed(
361 self._device(), self.subdevice.index, self.index,
362 _constant.bitwise_value(self.range),
363 _constant.bitwise_value(self.aref),
364 &data, int(nano_sec))
366 _error.raise_error(function_name='comedi_data_read_delayed',
370 def data_write(self, data):
373 Returns 1 (the number of data samples written).
375 ret = _comedilib_h.comedi_data_write(
376 self._device(), self.subdevice.index, self.index,
377 _constant.bitwise_value(self.range),
378 _constant.bitwise_value(self.aref),
381 _error.raise_error(function_name='comedi_data_write', ret=ret)
384 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
387 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
388 self, direction, _Calibration calibration) except *:
389 """Get a calibration polynomial for a software-calibrated channel
391 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
393 cdef _comedilib_h.comedi_polynomial_t poly
394 rc = _comedilib_h.comedi_get_softcal_converter(
395 self.subdevice.index, self.index,
396 _constant.bitwise_value(self.range),
397 _constant.bitwise_value(direction),
398 calibration.calibration, &poly)
400 _error.raise_error(function_name='comedi_get_softcal_converter',
404 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
405 self, direction) except *:
406 """Get a calibration polynomial for a hardware-calibrated channel
408 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
410 cdef _comedilib_h.comedi_polynomial_t poly
411 rc = _comedilib_h.comedi_get_hardcal_converter(
412 self._device(), self.subdevice.index, self.index,
413 _constant.bitwise_value(self.range),
414 _constant.bitwise_value(direction), &poly)
416 _error.raise_error(function_name='comedi_get_hardcal_converter',
420 cdef _get_converter(self, _Calibration calibration):
421 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
422 cdef _CalibratedConverter ret
423 flags = self.subdevice.get_flags()
424 from_physical_error = None
425 if flags.soft_calibrated:
426 if calibration is None:
427 calibration = self.subdevice.device.parse_calibration()
428 to_physical = self.get_softcal_converter(
429 _constant.CONVERSION_DIRECTION.to_physical,
432 from_physical = self.get_softcal_converter(
433 _constant.CONVERSION_DIRECTION.from_physical,
435 except _PyComediError as e:
436 from_physical_error = e
438 to_physical = self.get_hardcal_converter(
439 _constant.CONVERSION_DIRECTION.to_physical)
440 from_physical = self.get_hardcal_converter(
441 _constant.CONVERSION_DIRECTION.from_physical)
442 ret = _CalibratedConverter(from_physical_error=from_physical_error)
443 ret._to_physical = to_physical
444 if from_physical_error is not None:
445 ret._from_physical = from_physical
448 def get_converter(self, calibration=None):
449 return self._get_converter(calibration)
451 cdef _apply_calibration(self, char *path):
453 p = self.subdevice.device.get_default_calibration_path()
454 # automatically get a char * refernce into the Python string p
456 ret = _comedilib_h.comedi_apply_calibration(
457 self._device(), self.subdevice.index, self.index,
458 _constant.bitwise_value(self.range),
459 _constant.bitwise_value(self.aref),
463 function_name='comedi_apply_calibration', ret=ret)
466 cdef _apply_parsed_calibration(self, _Calibration calibration):
467 ret = _comedilib_h.comedi_apply_parsed_calibration(
468 self._device(), self.subdevice.index, self.index,
469 _constant.bitwise_value(self.range),
470 _constant.bitwise_value(self.aref),
471 calibration.calibration)
474 function_name='comedi_apply_parsed_calibration', ret=ret)
477 def apply_calibration(self, _Calibration calibration=None, path=None):
478 """Apply a calibration to this channel configuration
480 `calibration` may None or a `Calibration` instance. If it is
481 a `Calibration` instance, that instance is used for the
482 calibration. Otherwise we look at `path`. If `path` is None,
483 we use the default device calibration, otherwise we try and
484 use the calibration file located at `path`.
486 if calibration is not None:
487 self._apply_parsed_calibration(calibration)
488 elif path is not None:
489 self._apply_calibration(path)
491 self._apply_calibration(NULL)