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"
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)
284 flags = self.subdevice.get_flags()
285 for ar in _constant.AREF:
286 if getattr(flags, ar.name):
289 raise _PyComediError(
290 '%s does not support any known analog reference type (%s)'
291 % (self.subdevice, flags))
298 cdef _comedi_h.lsampl_t data
299 ret = _comedilib_h.comedi_data_read(
300 self.subdevice.device.device,
301 self.subdevice.index, self.index,
302 _constant.bitwise_value(self.range),
303 _constant.bitwise_value(self.aref),
306 _error.raise_error(function_name='comedi_data_read', ret=ret)
309 def data_read_n(self, n):
310 "Read `n` samples (timing between samples is undefined)."
311 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
312 ret = _comedilib_h.comedi_data_read_n(
313 self.subdevice.device.device,
314 self.subdevice.index, self.index,
315 _constant.bitwise_value(self.range),
316 _constant.bitwise_value(self.aref),
317 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
319 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
322 def data_read_hint(self):
323 """Tell driver which channel/range/aref you will read next
325 Used to prepare an analog input for a subsequent call to
326 comedi_data_read. It is not necessary to use this function,
327 but it can be useful for eliminating inaccuaracies caused by
328 insufficient settling times when switching the channel or gain
329 on an analog input. This function sets an analog input to the
330 channel, range, and aref specified but does not perform an
331 actual analog to digital conversion.
333 Alternatively, one can simply use `.data_read_delayed()`,
334 which sets up the input, pauses to allow settling, then
335 performs a conversion.
337 ret = _comedilib_h.comedi_data_read_hint(
338 self.subdevice.device.device,
339 self.subdevice.index, self.index,
340 _constant.bitwise_value(self.range),
341 _constant.bitwise_value(self.aref))
343 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
345 def data_read_delayed(self, nano_sec=0):
346 """Read single sample after delaying specified settling time.
348 Although the settling time is specified in integer
349 nanoseconds, the actual settling time will be rounded up to
350 the nearest microsecond.
352 cdef _comedi_h.lsampl_t data
353 ret = _comedilib_h.comedi_data_read_delayed(
354 self.subdevice.device.device,
355 self.subdevice.index, self.index,
356 _constant.bitwise_value(self.range),
357 _constant.bitwise_value(self.aref),
358 &data, int(nano_sec))
360 _error.raise_error(function_name='comedi_data_read_delayed',
364 def data_write(self, data):
367 Returns 1 (the number of data samples written).
369 ret = _comedilib_h.comedi_data_write(
370 self.subdevice.device.device,
371 self.subdevice.index, self.index,
372 _constant.bitwise_value(self.range),
373 _constant.bitwise_value(self.aref),
376 _error.raise_error(function_name='comedi_data_write', ret=ret)
379 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
382 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
383 self, direction, calibration):
386 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
388 cdef _comedilib_h.comedi_polynomial_t poly
389 #rc = _comedilib_h.comedi_get_softcal_converter(
390 # self.subdevice.device.device,
391 # self.subdevice.index, self.index,
392 # _constant.bitwise_value(self.range),
393 # _constant.bitwise_value(direction),
394 # calibration, &poly)
396 # _error.raise_error(function_name='comedi_get_softcal_converter',
400 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
404 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
406 cdef _comedilib_h.comedi_polynomial_t poly
407 rc = _comedilib_h.comedi_get_hardcal_converter(
408 self.subdevice.device.device,
409 self.subdevice.index, self.index,
410 _constant.bitwise_value(self.range),
411 _constant.bitwise_value(direction), &poly)
413 _error.raise_error(function_name='comedi_get_hardcal_converter',
417 cdef _get_converter(self, calibration):
418 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
419 cdef _CalibratedConverter ret
420 flags = self.subdevice.get_flags()
421 if flags.soft_calibrated:
422 #if calibration is None:
423 # calibration = self.subdevice.device.parse_calibration()
424 raise NotImplementedError()
426 to_physical = self.get_hardcal_converter(
427 _constant.CONVERSION_DIRECTION.to_physical)
428 from_physical = self.get_hardcal_converter(
429 _constant.CONVERSION_DIRECTION.from_physical)
430 ret = _CalibratedConverter()
431 ret._to_physical = to_physical
432 ret._from_physical = from_physical
435 def get_converter(self, calibration=None):
436 return self._get_converter(calibration)