1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
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"
20 cimport numpy as _numpy
21 import numpy as _numpy
25 from calibration cimport CalibratedConverter as _CalibratedConverter
26 from range cimport Range as _Range
27 from subdevice cimport Subdevice as _Subdevice
29 from pycomedi import LOG as _LOG
30 from chanspec import ChanSpec as _ChanSpec
31 from pycomedi import PyComediError as _PyComediError
33 import constant as _constant
36 cdef class Channel (object):
37 """Class bundling channel-related functions
39 >>> from .device import Device
40 >>> from . import constant
42 >>> d = Device('/dev/comedi0')
44 >>> s = d.get_read_subdevice()
45 >>> c = s.channel(index=0)
51 >>> c.get_range(index=0)
52 <Range unit:volt min:-10.0 max:10.0>
53 >>> c.find_range(constant.UNIT.volt, 0, 5)
54 <Range unit:volt min:0.0 max:5.0>
58 cdef public _Subdevice subdevice
64 def __init__(self, subdevice, index):
65 super(Channel, self).__init__()
66 self.subdevice = subdevice
69 def get_maxdata(self):
70 ret = _comedilib_h.comedi_get_maxdata(
71 self.subdevice.device.device,
72 self.subdevice.index, self.index)
74 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
77 def get_n_ranges(self):
78 ret = _comedilib_h.comedi_get_n_ranges(
79 self.subdevice.device.device,
80 self.subdevice.index, self.index)
82 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
85 cdef _get_range(self, index):
86 cdef _comedilib_h.comedi_range *rng
88 # Memory pointed to by the return value is freed on Device.close().
89 rng = _comedilib_h.comedi_get_range(
90 self.subdevice.device.device,
91 self.subdevice.index, self.index, index)
93 _error.raise_error(function_name='comedi_get_range')
94 ret = _Range(value=index)
95 ret.set_comedi_range(rng[0])
96 # rng[0] is a sneaky way to dereference rng, since Cython
97 # doesn't support *rng.
100 @cython.always_allow_keywords(True)
101 def get_range(self, index):
102 "`Range` instance for the `index`\ed range."
103 return self._get_range(index)
105 def _find_range(self, unit, min, max):
107 ret = _comedilib_h.comedi_find_range(
108 self.subdevice.device.device,
109 self.subdevice.index, self.index,
110 _constant.bitwise_value(unit), min, max)
112 _error.raise_error(function_name='comedi_find_range', ret=ret)
115 def find_range(self, unit, min, max):
118 `unit` should be an item from `constants.UNIT`.
120 return self.get_range(self._find_range(unit, min, max))
122 def ranges(self, **kwargs):
123 "Iterate through all available ranges."
125 for i in range(self.get_n_ranges()):
126 #yield self.subdevice(i, **kwargs)
127 # Generators are not supported in Cython 0.14.1
128 ret.append(self.get_range(i, **kwargs))
132 cdef class DigitalChannel (Channel):
133 """Channel configured for reading or writing digital data.
135 >>> from .device import Device
136 >>> from . import constant
138 >>> d = Device('/dev/comedi0')
140 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
141 >>> c = s.channel(0, factory=DigitalChannel)
148 <Range unit:volt min:0.0 max:5.0>
150 >>> direction = c.dio_get_config()
151 >>> direction # doctest: +SKIP
154 >>> c.dio_config(_constant.IO_DIRECTION.input)
155 >>> data = c.dio_read()
159 >>> c.dio_config(_constant.IO_DIRECTION.output)
162 >>> c.dio_config(direction)
166 def dio_config(self, dir):
167 """Change input/output properties
169 `dir` should be an item from `constants.IO_DIRECTION`.
171 ret = _comedilib_h.comedi_dio_config(
172 self.subdevice.device.device,
173 self.subdevice.index, self.index,
174 _constant.bitwise_value(dir))
176 _error.raise_error(function_name='comedi_dio_config', ret=ret)
178 def dio_get_config(self):
179 """Query input/output properties
181 Return an item from `constant.IO_DIRECTION`.
183 cpdef unsigned int dir
184 ret = _comedilib_h.comedi_dio_get_config(
185 self.subdevice.device.device,
186 self.subdevice.index, self.index, &dir)
188 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
189 return _constant.IO_DIRECTION.index_by_value(dir)
193 cpdef unsigned int bit
194 ret = _comedilib_h.comedi_dio_read(
195 self.subdevice.device.device,
196 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.subdevice.device.device,
205 self.subdevice.index, self.index, bit)
207 _error.raise_error(function_name='comedi_dio_write', ret=ret)
210 cdef class AnalogChannel (Channel):
211 """Channel configured for reading or writing analog data.
213 `range` should be a `Range` instance, `aref` should be an
214 `constants.AREF` instance. If not specified, defaults are chosen
215 based on the capabilities of the subdevice.
217 >>> from .device import Device
218 >>> from . import constant
220 >>> d = Device('/dev/comedi0')
222 >>> s = d.get_read_subdevice()
223 >>> c = s.channel(0, factory=AnalogChannel)
226 <Range unit:volt min:-10.0 max:10.0>
230 >>> data = c.data_read()
231 >>> data # doctest: +SKIP
233 >>> converter = c.get_converter()
234 >>> converter # doctest: +NORMALIZE_WHITESPACE
236 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
237 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
238 >>> physical_data = converter.to_physical(data)
239 >>> physical_data # doctest: +SKIP
240 -0.029755092698558021
241 >>> converter.from_physical(physical_data) == data
244 >>> data = c.data_read_n(5)
245 >>> data # doctest: +SKIP
246 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
248 >>> c.data_read_hint()
249 >>> c.data_read() # doctest: +SKIP
252 >>> data = c.data_read_delayed(nano_sec=1e3)
253 >>> data # doctest: +SKIP
256 >>> s = d.get_write_subdevice()
257 >>> c = s.channel(0, factory=AnalogChannel)
259 >>> converter = c.get_converter()
260 >>> converter # doctest: +NORMALIZE_WHITESPACE
262 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
263 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
265 >>> c.data_write(converter.from_physical(0))
269 Even after the device is closed, the range information is
273 <Range unit:volt min:-10.0 max:10.0>
275 cdef public _Range range
276 cdef public object aref
278 def __init__(self, range=None, aref=None, **kwargs):
279 super(AnalogChannel, self).__init__(**kwargs)
281 range = self.get_range(0)
282 elif isinstance(range, int):
283 range = self.get_range(range)
286 flags = self.subdevice.get_flags()
287 for ar in _constant.AREF:
288 if getattr(flags, ar.name):
291 raise _PyComediError(
292 '%s does not support any known analog reference type (%s)'
293 % (self.subdevice, flags))
300 cdef _comedi_h.lsampl_t data
301 ret = _comedilib_h.comedi_data_read(
302 self.subdevice.device.device,
303 self.subdevice.index, self.index,
304 _constant.bitwise_value(self.range),
305 _constant.bitwise_value(self.aref),
308 _error.raise_error(function_name='comedi_data_read', ret=ret)
311 def data_read_n(self, n):
312 "Read `n` samples (timing between samples is undefined)."
313 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
314 ret = _comedilib_h.comedi_data_read_n(
315 self.subdevice.device.device,
316 self.subdevice.index, self.index,
317 _constant.bitwise_value(self.range),
318 _constant.bitwise_value(self.aref),
319 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
321 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
324 def data_read_hint(self):
325 """Tell driver which channel/range/aref you will read next
327 Used to prepare an analog input for a subsequent call to
328 comedi_data_read. It is not necessary to use this function,
329 but it can be useful for eliminating inaccuaracies caused by
330 insufficient settling times when switching the channel or gain
331 on an analog input. This function sets an analog input to the
332 channel, range, and aref specified but does not perform an
333 actual analog to digital conversion.
335 Alternatively, one can simply use `.data_read_delayed()`,
336 which sets up the input, pauses to allow settling, then
337 performs a conversion.
339 ret = _comedilib_h.comedi_data_read_hint(
340 self.subdevice.device.device,
341 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.subdevice.device.device,
357 self.subdevice.index, self.index,
358 _constant.bitwise_value(self.range),
359 _constant.bitwise_value(self.aref),
360 &data, int(nano_sec))
362 _error.raise_error(function_name='comedi_data_read_delayed',
366 def data_write(self, data):
369 Returns 1 (the number of data samples written).
371 ret = _comedilib_h.comedi_data_write(
372 self.subdevice.device.device,
373 self.subdevice.index, self.index,
374 _constant.bitwise_value(self.range),
375 _constant.bitwise_value(self.aref),
378 _error.raise_error(function_name='comedi_data_write', ret=ret)
381 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
384 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
385 self, direction, calibration):
388 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
390 cdef _comedilib_h.comedi_polynomial_t poly
391 #rc = _comedilib_h.comedi_get_softcal_converter(
392 # self.subdevice.device.device,
393 # self.subdevice.index, self.index,
394 # _constant.bitwise_value(self.range),
395 # _constant.bitwise_value(direction),
396 # calibration, &poly)
398 # _error.raise_error(function_name='comedi_get_softcal_converter',
402 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
406 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
408 cdef _comedilib_h.comedi_polynomial_t poly
409 rc = _comedilib_h.comedi_get_hardcal_converter(
410 self.subdevice.device.device,
411 self.subdevice.index, self.index,
412 _constant.bitwise_value(self.range),
413 _constant.bitwise_value(direction), &poly)
415 _error.raise_error(function_name='comedi_get_hardcal_converter',
419 cdef _get_converter(self, calibration):
420 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
421 cdef _CalibratedConverter ret
422 flags = self.subdevice.get_flags()
423 if flags.soft_calibrated:
424 #if calibration is None:
425 # calibration = self.subdevice.device.parse_calibration()
426 raise NotImplementedError()
428 to_physical = self.get_hardcal_converter(
429 _constant.CONVERSION_DIRECTION.to_physical)
430 from_physical = self.get_hardcal_converter(
431 _constant.CONVERSION_DIRECTION.from_physical)
432 ret = _CalibratedConverter()
433 ret._to_physical = to_physical
434 ret._from_physical = from_physical
437 def get_converter(self, calibration=None):
438 return self._get_converter(calibration)