1 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
3 # This file is part of pycomedi.
5 # pycomedi is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) any later
10 # pycomedi is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # pycomedi. If not, see <http://www.gnu.org/licenses/>.
17 "Wrap channel-wide Comedi functions in `Channel` and related classes"
19 cimport numpy as _numpy
20 import numpy as _numpy
24 from calibration cimport CalibratedConverter as _CalibratedConverter
25 from range cimport Range as _Range
26 from subdevice cimport Subdevice as _Subdevice
28 from pycomedi import LOG as _LOG
29 from chanspec import ChanSpec as _ChanSpec
30 from pycomedi import PyComediError as _PyComediError
32 import constant as _constant
35 cdef class Channel (object):
36 """Class bundling channel-related functions
38 >>> from .device import Device
39 >>> from . import constant
41 >>> d = Device('/dev/comedi0')
43 >>> s = d.get_read_subdevice()
51 <Range unit:volt min:-10.0 max:10.0>
52 >>> c.find_range(constant.UNIT.volt, 0, 5)
53 <Range unit:volt min:0.0 max:5.0>
57 cdef public _Subdevice subdevice
63 def __init__(self, subdevice, index):
64 super(Channel, self).__init__()
65 self.subdevice = subdevice
68 def get_maxdata(self):
69 ret = _comedilib_h.comedi_get_maxdata(
70 self.subdevice.device.device,
71 self.subdevice.index, self.index)
73 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
76 def get_n_ranges(self):
77 ret = _comedilib_h.comedi_get_n_ranges(
78 self.subdevice.device.device,
79 self.subdevice.index, self.index)
81 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
84 cdef _get_range(self, index):
85 cdef _comedilib_h.comedi_range *rng
87 # Memory pointed to by the return value is freed on Device.close().
88 rng = _comedilib_h.comedi_get_range(
89 self.subdevice.device.device,
90 self.subdevice.index, self.index, index)
92 _error.raise_error(function_name='comedi_get_range')
93 ret = _Range(value=index)
94 ret.set_comedi_range(rng[0])
95 # rng[0] is a sneaky way to dereference rng, since Cython
96 # doesn't support *rng.
99 def get_range(self, index):
100 "`Range` instance for the `index`\ed range."
101 return self._get_range(index)
103 def _find_range(self, unit, min, max):
105 ret = _comedilib_h.comedi_find_range(
106 self.subdevice.device.device,
107 self.subdevice.index, self.index,
108 _constant.bitwise_value(unit), min, max)
110 _error.raise_error(function_name='comedi_find_range', ret=ret)
113 def find_range(self, unit, min, max):
116 `unit` should be an item from `constants.UNIT`.
118 return self.get_range(self._find_range(unit, min, max))
121 cdef class DigitalChannel (Channel):
122 """Channel configured for reading or writing digital data.
124 >>> from .device import Device
125 >>> from . import constant
127 >>> d = Device('/dev/comedi0')
129 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
130 >>> c = s.channel(0, factory=DigitalChannel)
137 <Range unit:volt min:0.0 max:5.0>
139 >>> direction = c.dio_get_config()
140 >>> direction # doctest: +SKIP
143 >>> c.dio_config(_constant.IO_DIRECTION.input)
144 >>> data = c.dio_read()
148 >>> c.dio_config(_constant.IO_DIRECTION.output)
151 >>> c.dio_config(direction)
155 def dio_config(self, dir):
156 """Change input/output properties
158 `dir` should be an item from `constants.IO_DIRECTION`.
160 ret = _comedilib_h.comedi_dio_config(
161 self.subdevice.device.device,
162 self.subdevice.index, self.index,
163 _constant.bitwise_value(dir))
165 _error.raise_error(function_name='comedi_dio_config', ret=ret)
167 def dio_get_config(self):
168 """Query input/output properties
170 Return an item from `constant.IO_DIRECTION`.
172 cpdef unsigned int dir
173 ret = _comedilib_h.comedi_dio_get_config(
174 self.subdevice.device.device,
175 self.subdevice.index, self.index, &dir)
177 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
178 return _constant.IO_DIRECTION.index_by_value(dir)
182 cpdef unsigned int bit
183 ret = _comedilib_h.comedi_dio_read(
184 self.subdevice.device.device,
185 self.subdevice.index, self.index, &bit)
187 _error.raise_error(function_name='comedi_dio_read', ret=ret)
190 def dio_write(self, bit):
192 ret = _comedilib_h.comedi_dio_write(
193 self.subdevice.device.device,
194 self.subdevice.index, self.index, bit)
196 _error.raise_error(function_name='comedi_dio_write', ret=ret)
199 cdef class AnalogChannel (Channel):
200 """Channel configured for reading or writing analog data.
202 `range` should be a `Range` instance, `aref` should be an
203 `constants.AREF` instance. If not specified, defaults are chosen
204 based on the capabilities of the subdevice.
206 >>> from .device import Device
207 >>> from . import constant
209 >>> d = Device('/dev/comedi0')
211 >>> s = d.get_read_subdevice()
212 >>> c = s.channel(0, factory=AnalogChannel)
215 <Range unit:volt min:-10.0 max:10.0>
219 >>> data = c.data_read()
220 >>> data # doctest: +SKIP
222 >>> converter = c.get_converter()
223 >>> converter # doctest: +NORMALIZE_WHITESPACE
225 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
226 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
227 >>> physical_data = converter.to_physical(data)
228 >>> physical_data # doctest: +SKIP
229 -0.029755092698558021
230 >>> converter.from_physical(physical_data) == data
233 >>> data = c.data_read_n(5)
234 >>> data # doctest: +SKIP
235 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
237 >>> c.data_read_hint()
238 >>> c.data_read() # doctest: +SKIP
241 >>> data = c.data_read_delayed(nano_sec=1e3)
242 >>> data # doctest: +SKIP
245 >>> s = d.get_write_subdevice()
246 >>> c = s.channel(0, factory=AnalogChannel)
248 >>> converter = c.get_converter()
249 >>> converter # doctest: +NORMALIZE_WHITESPACE
251 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
252 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
254 >>> c.data_write(converter.from_physical(0))
258 Even after the device is closed, the range information is
262 <Range unit:volt min:-10.0 max:10.0>
264 cdef public _Range range
265 cdef public object aref
267 def __init__(self, range=None, aref=None, **kwargs):
268 super(AnalogChannel, self).__init__(**kwargs)
270 range = self.get_range(0)
273 flags = self.subdevice.get_flags()
274 for ar in _constant.AREF:
275 if getattr(flags, ar.name):
278 raise _PyComediError(
279 '%s does not support any known analog reference type (%s)'
280 % (self.subdevice, flags))
287 cdef _comedi_h.lsampl_t data
288 ret = _comedilib_h.comedi_data_read(
289 self.subdevice.device.device,
290 self.subdevice.index, self.index,
291 _constant.bitwise_value(self.range),
292 _constant.bitwise_value(self.aref),
295 _error.raise_error(function_name='comedi_data_read', ret=ret)
298 def data_read_n(self, n):
299 "Read `n` samples (timing between samples is undefined)."
300 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
301 ret = _comedilib_h.comedi_data_read_n(
302 self.subdevice.device.device,
303 self.subdevice.index, self.index,
304 _constant.bitwise_value(self.range),
305 _constant.bitwise_value(self.aref),
306 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
308 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
311 def data_read_hint(self):
312 """Tell driver which channel/range/aref you will read next
314 Used to prepare an analog input for a subsequent call to
315 comedi_data_read. It is not necessary to use this function,
316 but it can be useful for eliminating inaccuaracies caused by
317 insufficient settling times when switching the channel or gain
318 on an analog input. This function sets an analog input to the
319 channel, range, and aref specified but does not perform an
320 actual analog to digital conversion.
322 Alternatively, one can simply use `.data_read_delayed()`,
323 which sets up the input, pauses to allow settling, then
324 performs a conversion.
326 ret = _comedilib_h.comedi_data_read_hint(
327 self.subdevice.device.device,
328 self.subdevice.index, self.index,
329 _constant.bitwise_value(self.range),
330 _constant.bitwise_value(self.aref))
332 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
334 def data_read_delayed(self, nano_sec=0):
335 """Read single sample after delaying specified settling time.
337 Although the settling time is specified in integer
338 nanoseconds, the actual settling time will be rounded up to
339 the nearest microsecond.
341 cdef _comedi_h.lsampl_t data
342 ret = _comedilib_h.comedi_data_read_delayed(
343 self.subdevice.device.device,
344 self.subdevice.index, self.index,
345 _constant.bitwise_value(self.range),
346 _constant.bitwise_value(self.aref),
347 &data, int(nano_sec))
349 _error.raise_error(function_name='comedi_data_read_delayed',
353 def data_write(self, data):
356 Returns 1 (the number of data samples written).
358 ret = _comedilib_h.comedi_data_write(
359 self.subdevice.device.device,
360 self.subdevice.index, self.index,
361 _constant.bitwise_value(self.range),
362 _constant.bitwise_value(self.aref),
365 _error.raise_error(function_name='comedi_data_write', ret=ret)
368 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
371 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
372 self, direction, calibration):
375 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
377 cdef _comedilib_h.comedi_polynomial_t poly
378 #rc = _comedilib_h.comedi_get_softcal_converter(
379 # self.subdevice.device.device,
380 # self.subdevice.index, self.index,
381 # _constant.bitwise_value(self.range),
382 # _constant.bitwise_value(direction),
383 # calibration, &poly)
385 # _error.raise_error(function_name='comedi_get_softcal_converter',
389 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
393 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
395 cdef _comedilib_h.comedi_polynomial_t poly
396 rc = _comedilib_h.comedi_get_hardcal_converter(
397 self.subdevice.device.device,
398 self.subdevice.index, self.index,
399 _constant.bitwise_value(self.range),
400 _constant.bitwise_value(direction), &poly)
402 _error.raise_error(function_name='comedi_get_hardcal_converter',
406 cdef _get_converter(self, calibration):
407 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
408 cdef _CalibratedConverter ret
409 flags = self.subdevice.get_flags()
410 if flags.soft_calibrated:
411 #if calibration is None:
412 # calibration = self.subdevice.device.parse_calibration()
413 raise NotImplementedError()
415 to_physical = self.get_hardcal_converter(
416 _constant.CONVERSION_DIRECTION.to_physical)
417 from_physical = self.get_hardcal_converter(
418 _constant.CONVERSION_DIRECTION.from_physical)
419 ret = _CalibratedConverter()
420 ret._to_physical = to_physical
421 ret._from_physical = from_physical
424 def get_converter(self, calibration=None):
425 return self._get_converter(calibration)