3 "Wrap channel-wide Comedi functions in `Channel` and related classes"
5 cimport numpy as _numpy
10 from calibration cimport CalibratedConverter as _CalibratedConverter
11 from range cimport Range as _Range
12 from subdevice cimport Subdevice as _Subdevice
14 from pycomedi import LOG as _LOG
15 from chanspec import ChanSpec as _ChanSpec
16 from pycomedi import PyComediError as _PyComediError
18 import constant as _constant
21 cdef class Channel (object):
22 """Class bundling channel-related functions
24 >>> from .device import Device
25 >>> from . import constant
27 >>> d = Device('/dev/comedi0')
29 >>> s = d.get_read_subdevice()
37 <Range unit:volt min:-10.0 max:10.0>
38 >>> c.find_range(constant.UNIT.volt, 0, 5)
39 <Range unit:volt min:0.0 max:5.0>
43 cdef public _Subdevice subdevice
49 def __init__(self, subdevice, index):
50 super(Channel, self).__init__()
51 self.subdevice = subdevice
54 def get_maxdata(self):
55 ret = _comedilib_h.comedi_get_maxdata(
56 self.subdevice.device.device,
57 self.subdevice.index, self.index)
59 _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
62 def get_n_ranges(self):
63 ret = _comedilib_h.comedi_get_n_ranges(
64 self.subdevice.device.device,
65 self.subdevice.index, self.index)
67 _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
70 cdef _get_range(self, index):
71 cdef _comedilib_h.comedi_range *rng
73 # Memory pointed to by the return value is freed on Device.close().
74 rng = _comedilib_h.comedi_get_range(
75 self.subdevice.device.device,
76 self.subdevice.index, self.index, index)
78 _error.raise_error(function_name='comedi_get_range')
79 ret = _Range(value=index)
80 ret.set_comedi_range(rng[0])
81 # rng[0] is a sneaky way to dereference rng, since Cython
82 # doesn't support *rng.
85 def get_range(self, index):
86 return self._get_range(index)
88 def _find_range(self, unit, min, max):
90 ret = _comedilib_h.comedi_find_range(
91 self.subdevice.device.device,
92 self.subdevice.index, self.index,
93 _constant.bitwise_value(unit), min, max)
95 _error.raise_error(function_name='comedi_find_range', ret=ret)
98 def find_range(self, unit, min, max):
101 `unit` should be an item from `constants.UNIT`.
103 return self.get_range(self._find_range(unit, min, max))
106 cdef class DigitalChannel (Channel):
107 """Channel configured for reading or writing digital data.
109 >>> from .device import Device
110 >>> from . import constant
112 >>> d = Device('/dev/comedi0')
114 >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
115 >>> c = s.channel(0, factory=DigitalChannel)
122 <Range unit:volt min:0.0 max:5.0>
124 >>> direction = c.dio_get_config()
125 >>> direction # doctest: +SKIP
128 >>> c.dio_config(_constant.IO_DIRECTION.input)
129 >>> data = c.dio_read()
133 >>> c.dio_config(_constant.IO_DIRECTION.output)
136 >>> c.dio_config(direction)
140 def dio_config(self, dir):
141 """Change input/output properties
143 `dir` should be an item from `constants.IO_DIRECTION`.
145 ret = _comedilib_h.comedi_dio_config(
146 self.subdevice.device.device,
147 self.subdevice.index, self.index,
148 _constant.bitwise_value(dir))
150 _error.raise_error(function_name='comedi_dio_config', ret=ret)
152 def dio_get_config(self):
153 """Query input/output properties
155 Return an item from `constant.IO_DIRECTION`.
157 cpdef unsigned int dir
158 ret = _comedilib_h.comedi_dio_get_config(
159 self.subdevice.device.device,
160 self.subdevice.index, self.index, &dir)
162 _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
163 return _constant.IO_DIRECTION.index_by_value(dir)
167 cpdef unsigned int bit
168 ret = _comedilib_h.comedi_dio_read(
169 self.subdevice.device.device,
170 self.subdevice.index, self.index, &bit)
172 _error.raise_error(function_name='comedi_dio_read', ret=ret)
175 def dio_write(self, bit):
177 ret = _comedilib_h.comedi_dio_write(
178 self.subdevice.device.device,
179 self.subdevice.index, self.index, bit)
181 _error.raise_error(function_name='comedi_dio_write', ret=ret)
184 cdef class AnalogChannel (Channel):
185 """Channel configured for reading or writing analog data.
187 `range` should be a `Range` instance, `aref` should be an
188 `constants.AREF` instance. If not specified, defaults are chosen
189 based on the capabilities of the subdevice.
191 >>> from .device import Device
192 >>> from . import constant
194 >>> d = Device('/dev/comedi0')
196 >>> s = d.get_read_subdevice()
197 >>> c = s.channel(0, factory=AnalogChannel)
200 <Range unit:volt min:-10.0 max:10.0>
204 >>> data = c.data_read()
205 >>> data # doctest: +SKIP
207 >>> converter = c.get_converter()
208 >>> converter # doctest: +NORMALIZE_WHITESPACE
210 to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
211 from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
212 >>> physical_data = converter.to_physical(data)
213 >>> physical_data # doctest: +SKIP
214 -0.029755092698558021
215 >>> converter.from_physical(physical_data) == data
218 >>> data = c.data_read_n(5)
219 >>> data # doctest: +SKIP
220 array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
222 >>> c.data_read_hint()
223 >>> c.data_read() # doctest: +SKIP
226 >>> data = c.data_read_delayed(nano_sec=1e3)
227 >>> data # doctest: +SKIP
230 >>> s = d.get_write_subdevice()
231 >>> c = s.channel(0, factory=AnalogChannel)
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}>
239 >>> c.data_write(converter.from_physical(0))
243 Even after the device is closed, the range information is
247 <Range unit:volt min:-10.0 max:10.0>
249 cdef public _Range range
250 cdef public object aref
252 def __init__(self, range=None, aref=None, **kwargs):
253 super(AnalogChannel, self).__init__(**kwargs)
255 range = self.get_range(0)
258 flags = self.subdevice.get_flags()
259 for ar in _constant.AREF:
260 if getattr(flags, ar.name):
263 raise _PyComediError(
264 '%s does not support any known analog reference type (%s)'
265 % (self.subdevice, flags))
272 cdef _comedi_h.lsampl_t data
273 ret = _comedilib_h.comedi_data_read(
274 self.subdevice.device.device,
275 self.subdevice.index, self.index,
276 _constant.bitwise_value(self.range),
277 _constant.bitwise_value(self.aref),
280 _error.raise_error(function_name='comedi_data_read', ret=ret)
283 def data_read_n(self, n):
284 "Read `n` samples (timing between samples is undefined)."
285 data = _numpy.ndarray(shape=(n,), dtype=_numpy.uint32)
286 ret = _comedilib_h.comedi_data_read_n(
287 self.subdevice.device.device,
288 self.subdevice.index, self.index,
289 _constant.bitwise_value(self.range),
290 _constant.bitwise_value(self.aref),
291 <_comedilib_h.lsampl_t *>_numpy.PyArray_DATA(data), n)
293 _error.raise_error(function_name='comedi_data_read_n', ret=ret)
296 def data_read_hint(self):
297 """Tell driver which channel/range/aref you will read next
299 Used to prepare an analog input for a subsequent call to
300 comedi_data_read. It is not necessary to use this function,
301 but it can be useful for eliminating inaccuaracies caused by
302 insufficient settling times when switching the channel or gain
303 on an analog input. This function sets an analog input to the
304 channel, range, and aref specified but does not perform an
305 actual analog to digital conversion.
307 Alternatively, one can simply use `.data_read_delayed()`,
308 which sets up the input, pauses to allow settling, then
309 performs a conversion.
311 ret = _comedilib_h.comedi_data_read_hint(
312 self.subdevice.device.device,
313 self.subdevice.index, self.index,
314 _constant.bitwise_value(self.range),
315 _constant.bitwise_value(self.aref))
317 _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
319 def data_read_delayed(self, nano_sec=0):
320 """Read single sample after delaying specified settling time.
322 Although the settling time is specified in integer
323 nanoseconds, the actual settling time will be rounded up to
324 the nearest microsecond.
326 cdef _comedi_h.lsampl_t data
327 ret = _comedilib_h.comedi_data_read_delayed(
328 self.subdevice.device.device,
329 self.subdevice.index, self.index,
330 _constant.bitwise_value(self.range),
331 _constant.bitwise_value(self.aref),
332 &data, int(nano_sec))
334 _error.raise_error(function_name='comedi_data_read_delayed',
338 def data_write(self, data):
341 Returns 1 (the number of data samples written).
343 ret = _comedilib_h.comedi_data_write(
344 self.subdevice.device.device,
345 self.subdevice.index, self.index,
346 _constant.bitwise_value(self.range),
347 _constant.bitwise_value(self.aref),
350 _error.raise_error(function_name='comedi_data_write', ret=ret)
353 return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
356 cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
357 self, direction, calibration):
360 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
362 cdef _comedilib_h.comedi_polynomial_t poly
363 #rc = _comedilib_h.comedi_get_softcal_converter(
364 # self.subdevice.device.device,
365 # self.subdevice.index, self.index,
366 # _constant.bitwise_value(self.range),
367 # _constant.bitwise_value(direction),
368 # calibration, &poly)
370 # _error.raise_error(function_name='comedi_get_softcal_converter',
374 cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
378 `direction` should be a value from `constant.CONVERSION_DIRECTION`.
380 cdef _comedilib_h.comedi_polynomial_t poly
381 rc = _comedilib_h.comedi_get_hardcal_converter(
382 self.subdevice.device.device,
383 self.subdevice.index, self.index,
384 _constant.bitwise_value(self.range),
385 _constant.bitwise_value(direction), &poly)
387 _error.raise_error(function_name='comedi_get_hardcal_converter',
391 cdef _get_converter(self, calibration):
392 cdef _comedilib_h.comedi_polynomial_t to_physical, from_physical
393 cdef _CalibratedConverter ret
394 flags = self.subdevice.get_flags()
395 if flags.soft_calibrated:
396 #if calibration is None:
397 # calibration = self.subdevice.device.parse_calibration()
398 raise NotImplementedError()
400 to_physical = self.get_hardcal_converter(
401 _constant.CONVERSION_DIRECTION.to_physical)
402 from_physical = self.get_hardcal_converter(
403 _constant.CONVERSION_DIRECTION.from_physical)
404 ret = _CalibratedConverter()
405 ret._to_physical = to_physical
406 ret._from_physical = from_physical
409 def get_converter(self, calibration=None):
410 return self._get_converter(calibration)