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 cimport Subdevice as _Subdevice
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 _Subdevice subdevice
67 def __init__(self, subdevice, index):
68 super(Channel, self).__init__()
69 self.subdevice = subdevice
72 def get_maxdata(self):
73 ret = _comedilib_h.comedi_get_maxdata(
74 self.subdevice.device.device,
75 self.subdevice.index, self.index)
77 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
80 def get_n_ranges(self):
81 ret = _comedilib_h.comedi_get_n_ranges(
82 self.subdevice.device.device,
83 self.subdevice.index, self.index)
85 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
88 cdef _get_range(self, index):
89 cdef _comedilib_h.comedi_range *rng
91 # Memory pointed to by the return value is freed on Device.close().
92 rng = _comedilib_h.comedi_get_range(
93 self.subdevice.device.device,
94 self.subdevice.index, self.index, index)
96 _error.raise_error(function_name='comedi_get_range')
97 ret = _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.subdevice.device.device,
112 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.subdevice.device.device,
176 self.subdevice.index, self.index,
177 _constant.bitwise_value(dir))
179 _error.raise_error(function_name='comedi_dio_config', ret=ret)
181 def dio_get_config(self):
182 """Query input/output properties
184 Return an item from `constant.IO_DIRECTION`.
186 cpdef unsigned int dir
187 ret = _comedilib_h.comedi_dio_get_config(
188 self.subdevice.device.device,
189 self.subdevice.index, self.index, &dir)
191 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
192 return _constant.IO_DIRECTION.index_by_value(dir)
196 cpdef unsigned int bit
197 ret = _comedilib_h.comedi_dio_read(
198 self.subdevice.device.device,
199 self.subdevice.index, self.index, &bit)
201 _error.raise_error(function_name='comedi_dio_read', ret=ret)
204 def dio_write(self, bit):
206 ret = _comedilib_h.comedi_dio_write(
207 self.subdevice.device.device,
208 self.subdevice.index, self.index, bit)
210 _error.raise_error(function_name='comedi_dio_write', ret=ret)
213 cdef class AnalogChannel (Channel):
214 """Channel configured for reading or writing analog data.
216 `range` should be a `Range` instance, `aref` should be an
217 `constants.AREF` instance. If not specified, defaults are chosen
218 based on the capabilities of the subdevice.
220 >>> from .device import Device
221 >>> from . import constant
223 >>> d = Device('/dev/comedi0')
225 >>> s = d.get_read_subdevice()
226 >>> c = s.channel(0, factory=AnalogChannel)
229 <Range unit:volt min:-10.0 max:10.0>
233 You can calibrate the channel with the default channel calibration.
235 >>> c.apply_calibration()
237 Or you can use a parsed (and possibly altered) calibration.
239 >>> calibration = d.parse_calibration()
240 >>> c.apply_calibration(calibration=calibration)
242 >>> data = c.data_read()
243 >>> data # doctest: +SKIP
245 >>> converter = c.get_converter()
246 >>> converter # doctest: +NORMALIZE_WHITESPACE
248 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
249 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
250 >>> physical_data = converter.to_physical(data)
251 >>> physical_data # doctest: +SKIP
252 -0.029755092698558021
253 >>> converter.from_physical(physical_data) == data
256 >>> data = c.data_read_n(5)
257 >>> data # doctest: +SKIP
258 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
260 >>> c.data_read_hint()
261 >>> c.data_read() # doctest: +SKIP
264 >>> data = c.data_read_delayed(nano_sec=1e3)
265 >>> data # doctest: +SKIP
268 >>> s = d.get_write_subdevice()
269 >>> c = s.channel(0, factory=AnalogChannel)
271 >>> converter = c.get_converter()
272 >>> converter # doctest: +NORMALIZE_WHITESPACE
274 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
275 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
277 >>> c.data_write(converter.from_physical(0))
281 Even after the device is closed, the range information is
285 <Range unit:volt min:-10.0 max:10.0>
287 cdef public _Range range
288 cdef public object aref
290 def __init__(self, range=None, aref=None, **kwargs):
291 super(AnalogChannel, self).__init__(**kwargs)
293 range = self.get_range(0)
294 elif isinstance(range, int):
295 range = self.get_range(range)
298 flags = self.subdevice.get_flags()
299 for ar in _constant.AREF:
300 if getattr(flags, ar.name):
303 raise _PyComediError(
304 '%s does not support any known analog reference type (%s)'
305 % (self.subdevice, flags))
312 cdef _comedi_h.lsampl_t data
313 ret = _comedilib_h.comedi_data_read(
314 self.subdevice.device.device,
315 self.subdevice.index, self.index,
316 _constant.bitwise_value(self.range),
317 _constant.bitwise_value(self.aref),
320 _error.raise_error(function_name='comedi_data_read', ret=ret)
323 def data_read_n(self, n):
324 "Read `n` samples (timing between samples is undefined)."
325 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
326 ret = _comedilib_h.comedi_data_read_n(
327 self.subdevice.device.device,
328 self.subdevice.index, self.index,
329 _constant.bitwise_value(self.range),
330 _constant.bitwise_value(self.aref),
331 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
333 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
336 def data_read_hint(self):
337 """Tell driver which channel/range/aref you will read next
339 Used to prepare an analog input for a subsequent call to
340 comedi_data_read. It is not necessary to use this function,
341 but it can be useful for eliminating inaccuaracies caused by
342 insufficient settling times when switching the channel or gain
343 on an analog input. This function sets an analog input to the
344 channel, range, and aref specified but does not perform an
345 actual analog to digital conversion.
347 Alternatively, one can simply use `.data_read_delayed()`,
348 which sets up the input, pauses to allow settling, then
349 performs a conversion.
351 ret = _comedilib_h.comedi_data_read_hint(
352 self.subdevice.device.device,
353 self.subdevice.index, self.index,
354 _constant.bitwise_value(self.range),
355 _constant.bitwise_value(self.aref))
357 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
359 def data_read_delayed(self, nano_sec=0):
360 """Read single sample after delaying specified settling time.
362 Although the settling time is specified in integer
363 nanoseconds, the actual settling time will be rounded up to
364 the nearest microsecond.
366 cdef _comedi_h.lsampl_t data
367 ret = _comedilib_h.comedi_data_read_delayed(
368 self.subdevice.device.device,
369 self.subdevice.index, self.index,
370 _constant.bitwise_value(self.range),
371 _constant.bitwise_value(self.aref),
372 &data, int(nano_sec))
374 _error.raise_error(function_name='comedi_data_read_delayed',
378 def data_write(self, data):
381 Returns 1 (the number of data samples written).
383 ret = _comedilib_h.comedi_data_write(
384 self.subdevice.device.device,
385 self.subdevice.index, self.index,
386 _constant.bitwise_value(self.range),
387 _constant.bitwise_value(self.aref),
390 _error.raise_error(function_name='comedi_data_write', ret=ret)
393 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
396 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
397 self, direction, calibration):
400 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
402 cdef _comedilib_h.comedi_polynomial_t poly
403 rc = _comedilib_h.comedi_get_softcal_converter(
404 self.subdevice.index, self.index,
405 _constant.bitwise_value(self.range),
406 _constant.bitwise_value(direction),
407 <_comedilib_h.comedi_calibration_t*> calibration, &poly)
409 _error.raise_error(function_name='comedi_get_softcal_converter',
413 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
417 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
419 cdef _comedilib_h.comedi_polynomial_t poly
420 rc = _comedilib_h.comedi_get_hardcal_converter(
421 self.subdevice.device.device,
422 self.subdevice.index, self.index,
423 _constant.bitwise_value(self.range),
424 _constant.bitwise_value(direction), &poly)
426 _error.raise_error(function_name='comedi_get_hardcal_converter',
430 cdef _get_converter(self, calibration):
431 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
432 cdef _CalibratedConverter ret
433 flags = self.subdevice.get_flags()
434 if flags.soft_calibrated:
435 if calibration is None:
436 calibration = self.subdevice.device.parse_calibration()
437 to_physical = self.get_softcal_converter(
438 _constant.CONVERSION_DIRECTION.to_physical,
440 from_physical = self.get_softcal_converter(
441 _constant.CONVERSION_DIRECTION.from_physical,
444 to_physical = self.get_hardcal_converter(
445 _constant.CONVERSION_DIRECTION.to_physical)
446 from_physical = self.get_hardcal_converter(
447 _constant.CONVERSION_DIRECTION.from_physical)
448 ret = _CalibratedConverter()
449 ret._to_physical = to_physical
450 ret._from_physical = from_physical
453 def get_converter(self, calibration=None):
454 return self._get_converter(calibration)
456 cdef _apply_calibration(self, char *path):
458 p = self.subdevice.device.get_default_calibration_path()
459 # automatically get a char * refernce into the Python string p
461 ret = _comedilib_h.comedi_apply_calibration(
462 self.subdevice.device.device,
463 self.subdevice.index, self.index,
464 _constant.bitwise_value(self.range),
465 _constant.bitwise_value(self.aref),
469 function_name='comedi_apply_calibration', ret=ret)
472 cdef _apply_parsed_calibration(self, _Calibration calibration):
473 ret = _comedilib_h.comedi_apply_parsed_calibration(
474 self.subdevice.device.device,
475 self.subdevice.index, self.index,
476 _constant.bitwise_value(self.range),
477 _constant.bitwise_value(self.aref),
478 calibration.calibration)
481 function_name='comedi_apply_parsed_calibration', ret=ret)
484 def apply_calibration(self, calibration=None, path=None):
485 """Apply a calibration to this channel configuration
487 `calibration` may None or a `Calibration` instance. If it is
488 a `Calibration` instance, that instance is used for the
489 calibration. Otherwise we look at `path`. If `path` is None,
490 we use the default device calibration, otherwise we try and
491 use the calibration file located at `path`.
493 if calibration is not None:
494 self._apply_parsed_calibration(calibration)
495 elif path is not None:
496 self._apply_calibration(path)
498 self._apply_calibration(NULL)