Ran update-copyright.py.
[pycomedi.git] / pycomedi / channel.pyx
1 # Copyright (C) 2011-2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of pycomedi.
4 #
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
8 # version.
9 #
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.
13 #
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/>.
16
17 "Wrap channel-wide Comedi functions in `Channel` and related classes"
18
19 cimport cython
20 cimport numpy as _numpy
21 import numpy as _numpy
22
23 cimport _comedi_h
24 cimport _comedilib_h
25 from calibration cimport CalibratedConverter as _CalibratedConverter
26 from range cimport Range as _Range
27 from subdevice cimport Subdevice as _Subdevice
28
29 from pycomedi import LOG as _LOG
30 from chanspec import ChanSpec as _ChanSpec
31 from pycomedi import PyComediError as _PyComediError
32 import _error
33 import constant as _constant
34
35
36 cdef class Channel (object):
37     """Class bundling channel-related functions
38
39     >>> from .device import Device
40     >>> from . import constant
41
42     >>> d = Device('/dev/comedi0')
43     >>> d.open()
44     >>> s = d.get_read_subdevice()
45     >>> c = s.channel(index=0)
46
47     >>> c.get_maxdata()
48     65535L
49     >>> c.get_n_ranges()
50     16
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>
55
56     >>> d.close()
57     """
58     cdef public _Subdevice subdevice
59     cdef public int index
60
61     def __cinit__(self):
62         self.index = -1
63
64     def __init__(self, subdevice, index):
65         super(Channel, self).__init__()
66         self.subdevice = subdevice
67         self.index = index
68
69     def get_maxdata(self):
70         ret = _comedilib_h.comedi_get_maxdata(
71             self.subdevice.device.device,
72             self.subdevice.index, self.index)
73         if ret < 0:
74             _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
75         return ret
76
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)
81         if ret < 0:
82             _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
83         return ret
84
85     cdef _get_range(self, index):
86         cdef _comedilib_h.comedi_range *rng
87         cdef _Range ret
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)
92         if rng is NULL:
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.
98         return ret
99
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)
104
105     def _find_range(self, unit, min, max):
106         "Search for range"
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)
111         if ret < 0:
112             _error.raise_error(function_name='comedi_find_range', ret=ret)
113         return ret
114
115     def find_range(self, unit, min, max):
116         """Search for range
117
118         `unit` should be an item from `constants.UNIT`.
119         """
120         return self.get_range(self._find_range(unit, min, max))
121
122     def ranges(self, **kwargs):
123         "Iterate through all available ranges."
124         ret = []
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))
129         return ret
130
131
132 cdef class DigitalChannel (Channel):
133     """Channel configured for reading or writing digital data.
134
135     >>> from .device import Device
136     >>> from . import constant
137
138     >>> d = Device('/dev/comedi0')
139     >>> d.open()
140     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
141     >>> c = s.channel(0, factory=DigitalChannel)
142
143     >>> c.get_maxdata()
144     1L
145     >>> c.get_n_ranges()
146     1
147     >>> c.get_range(0)
148     <Range unit:volt min:0.0 max:5.0>
149
150     >>> direction = c.dio_get_config()
151     >>> direction  # doctest: +SKIP
152     <_NamedInt input>
153
154     >>> c.dio_config(_constant.IO_DIRECTION.input)
155     >>> data = c.dio_read()
156     >>> data
157     1
158
159     >>> c.dio_config(_constant.IO_DIRECTION.output)
160     >>> c.dio_write(1)
161
162     >>> c.dio_config(direction)
163
164     >>> d.close()
165     """
166     def dio_config(self, dir):
167         """Change input/output properties
168
169         `dir` should be an item from `constants.IO_DIRECTION`.
170         """
171         ret = _comedilib_h.comedi_dio_config(
172             self.subdevice.device.device,
173             self.subdevice.index, self.index,
174             _constant.bitwise_value(dir))
175         if ret < 0:
176             _error.raise_error(function_name='comedi_dio_config', ret=ret)
177
178     def dio_get_config(self):
179         """Query input/output properties
180
181         Return an item from `constant.IO_DIRECTION`.
182         """
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)
187         if ret < 0:
188             _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
189         return _constant.IO_DIRECTION.index_by_value(dir)
190
191     def dio_read(self):
192         "Read a single bit"
193         cpdef unsigned int bit
194         ret = _comedilib_h.comedi_dio_read(
195             self.subdevice.device.device,
196             self.subdevice.index, self.index, &bit)
197         if ret < 0:
198             _error.raise_error(function_name='comedi_dio_read', ret=ret)
199         return int(bit)
200
201     def dio_write(self, bit):
202         "Write a single bit"
203         ret = _comedilib_h.comedi_dio_write(
204             self.subdevice.device.device,
205             self.subdevice.index, self.index, bit)
206         if ret < 0:
207             _error.raise_error(function_name='comedi_dio_write', ret=ret)
208
209
210 cdef class AnalogChannel (Channel):
211     """Channel configured for reading or writing analog data.
212
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.
216
217     >>> from .device import Device
218     >>> from . import constant
219
220     >>> d = Device('/dev/comedi0')
221     >>> d.open()
222     >>> s = d.get_read_subdevice()
223     >>> c = s.channel(0, factory=AnalogChannel)
224
225     >>> c.range
226     <Range unit:volt min:-10.0 max:10.0>
227     >>> c.aref
228     <_NamedInt ground>
229
230     >>> data = c.data_read()
231     >>> data  # doctest: +SKIP
232     32670L
233     >>> converter = c.get_converter()
234     >>> converter  # doctest: +NORMALIZE_WHITESPACE
235     <CalibratedConverter
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
242     True
243
244     >>> data = c.data_read_n(5)
245     >>> data  # doctest: +SKIP
246     array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
247
248     >>> c.data_read_hint()
249     >>> c.data_read()  # doctest: +SKIP
250     32672L
251
252     >>> data = c.data_read_delayed(nano_sec=1e3)
253     >>> data  # doctest: +SKIP
254     32672L
255
256     >>> s = d.get_write_subdevice()
257     >>> c = s.channel(0, factory=AnalogChannel)
258
259     >>> converter = c.get_converter()
260     >>> converter  # doctest: +NORMALIZE_WHITESPACE
261     <CalibratedConverter
262      to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
263      from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
264
265     >>> c.data_write(converter.from_physical(0))
266
267     >>> d.close()
268
269     Even after the device is closed, the range information is
270     retained.
271
272     >>> c.range
273     <Range unit:volt min:-10.0 max:10.0>
274     """
275     cdef public _Range range
276     cdef public object aref
277
278     def __init__(self, range=None, aref=None, **kwargs):
279         super(AnalogChannel, self).__init__(**kwargs)
280         if range == None:
281             range = self.get_range(0)
282         elif isinstance(range, int):
283             range = self.get_range(range)
284         self.range = range
285         if aref == None:
286             flags = self.subdevice.get_flags()
287             for ar in _constant.AREF:
288                 if getattr(flags, ar.name):
289                     aref = ar
290                     break
291                 raise _PyComediError(
292                     '%s does not support any known analog reference type (%s)'
293                     % (self.subdevice, flags))
294         self.aref = aref
295
296     # syncronous stuff
297
298     def data_read(self):
299         "Read one sample"
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),
306             &data)
307         if ret < 0:
308             _error.raise_error(function_name='comedi_data_read', ret=ret)
309         return data
310
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)
320         if ret < 0:
321             _error.raise_error(function_name='comedi_data_read_n', ret=ret)
322         return data
323
324     def data_read_hint(self):
325         """Tell driver which channel/range/aref you will read next
326
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.
334
335         Alternatively, one can simply use `.data_read_delayed()`,
336         which sets up the input, pauses to allow settling, then
337         performs a conversion.
338         """
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))
344         if ret < 0:
345             _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
346
347     def data_read_delayed(self, nano_sec=0):
348         """Read single sample after delaying specified settling time.
349
350         Although the settling time is specified in integer
351         nanoseconds, the actual settling time will be rounded up to
352         the nearest microsecond.
353         """
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))
361         if ret < 0:
362             _error.raise_error(function_name='comedi_data_read_delayed',
363                                ret=ret)
364         return data
365
366     def data_write(self, data):
367         """Write one sample
368
369         Returns 1 (the number of data samples written).
370         """
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),
376             int(data))
377         if ret != 1:
378             _error.raise_error(function_name='comedi_data_write', ret=ret)
379
380     def chanspec(self):
381         return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
382
383
384     cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
385         self, direction, calibration):
386         """
387
388         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
389         """
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)
397         #if rc < 0:
398         #    _error.raise_error(function_name='comedi_get_softcal_converter',
399         #                       ret=rc)
400         return poly
401
402     cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
403         self, direction):
404         """
405
406         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
407         """
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)
414         if rc < 0:
415             _error.raise_error(function_name='comedi_get_hardcal_converter',
416                                ret=rc)
417         return poly
418
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()
427         else:
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
435         return ret
436
437     def get_converter(self, calibration=None):
438         return self._get_converter(calibration)