Add docstring to Channel.get_range.
[pycomedi.git] / pycomedi / channel.pyx
1 # Copyright (C) 2011-2012 W. Trevor King <wking@drexel.edu>
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 numpy as _numpy
20 import numpy as _numpy
21
22 cimport _comedi_h
23 cimport _comedilib_h
24 from calibration cimport CalibratedConverter as _CalibratedConverter
25 from range cimport Range as _Range
26 from subdevice cimport Subdevice as _Subdevice
27
28 from pycomedi import LOG as _LOG
29 from chanspec import ChanSpec as _ChanSpec
30 from pycomedi import PyComediError as _PyComediError
31 import _error
32 import constant as _constant
33
34
35 cdef class Channel (object):
36     """Class bundling channel-related functions
37
38     >>> from .device import Device
39     >>> from . import constant
40
41     >>> d = Device('/dev/comedi0')
42     >>> d.open()
43     >>> s = d.get_read_subdevice()
44     >>> c = s.channel(0)
45
46     >>> c.get_maxdata()
47     65535L
48     >>> c.get_n_ranges()
49     16
50     >>> c.get_range(0)
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>
54
55     >>> d.close()
56     """
57     cdef public _Subdevice subdevice
58     cdef public int index
59
60     def __cinit__(self):
61         self.index = -1
62
63     def __init__(self, subdevice, index):
64         super(Channel, self).__init__()
65         self.subdevice = subdevice
66         self.index = index
67
68     def get_maxdata(self):
69         ret = _comedilib_h.comedi_get_maxdata(
70             self.subdevice.device.device,
71             self.subdevice.index, self.index)
72         if ret < 0:
73             _error.raise_error(function_name='comedi_get_maxdata', ret=ret)
74         return ret
75
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)
80         if ret < 0:
81             _error.raise_error(function_name='comedi_get_n_ranges', ret=ret)
82         return ret
83
84     cdef _get_range(self, index):
85         cdef _comedilib_h.comedi_range *rng
86         cdef _Range ret
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)
91         if rng is NULL:
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.
97         return ret
98
99     def get_range(self, index):
100         "`Range` instance for the `index`\ed range."
101         return self._get_range(index)
102
103     def _find_range(self, unit, min, max):
104         "Search for range"
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)
109         if ret < 0:
110             _error.raise_error(function_name='comedi_find_range', ret=ret)
111         return ret
112
113     def find_range(self, unit, min, max):
114         """Search for range
115
116         `unit` should be an item from `constants.UNIT`.
117         """
118         return self.get_range(self._find_range(unit, min, max))
119
120
121 cdef class DigitalChannel (Channel):
122     """Channel configured for reading or writing digital data.
123
124     >>> from .device import Device
125     >>> from . import constant
126
127     >>> d = Device('/dev/comedi0')
128     >>> d.open()
129     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
130     >>> c = s.channel(0, factory=DigitalChannel)
131
132     >>> c.get_maxdata()
133     1L
134     >>> c.get_n_ranges()
135     1
136     >>> c.get_range(0)
137     <Range unit:volt min:0.0 max:5.0>
138
139     >>> direction = c.dio_get_config()
140     >>> direction  # doctest: +SKIP
141     <_NamedInt input>
142
143     >>> c.dio_config(_constant.IO_DIRECTION.input)
144     >>> data = c.dio_read()
145     >>> data
146     1
147
148     >>> c.dio_config(_constant.IO_DIRECTION.output)
149     >>> c.dio_write(1)
150
151     >>> c.dio_config(direction)
152
153     >>> d.close()
154     """
155     def dio_config(self, dir):
156         """Change input/output properties
157
158         `dir` should be an item from `constants.IO_DIRECTION`.
159         """
160         ret = _comedilib_h.comedi_dio_config(
161             self.subdevice.device.device,
162             self.subdevice.index, self.index,
163             _constant.bitwise_value(dir))
164         if ret < 0:
165             _error.raise_error(function_name='comedi_dio_config', ret=ret)
166
167     def dio_get_config(self):
168         """Query input/output properties
169
170         Return an item from `constant.IO_DIRECTION`.
171         """
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)
176         if ret < 0:
177             _error.raise_error(function_name='comedi_dio_get_config', ret=ret)
178         return _constant.IO_DIRECTION.index_by_value(dir)
179
180     def dio_read(self):
181         "Read a single bit"
182         cpdef unsigned int bit
183         ret = _comedilib_h.comedi_dio_read(
184             self.subdevice.device.device,
185             self.subdevice.index, self.index, &bit)
186         if ret < 0:
187             _error.raise_error(function_name='comedi_dio_read', ret=ret)
188         return int(bit)
189
190     def dio_write(self, bit):
191         "Write a single bit"
192         ret = _comedilib_h.comedi_dio_write(
193             self.subdevice.device.device,
194             self.subdevice.index, self.index, bit)
195         if ret < 0:
196             _error.raise_error(function_name='comedi_dio_write', ret=ret)
197
198
199 cdef class AnalogChannel (Channel):
200     """Channel configured for reading or writing analog data.
201
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.
205
206     >>> from .device import Device
207     >>> from . import constant
208
209     >>> d = Device('/dev/comedi0')
210     >>> d.open()
211     >>> s = d.get_read_subdevice()
212     >>> c = s.channel(0, factory=AnalogChannel)
213
214     >>> c.range
215     <Range unit:volt min:-10.0 max:10.0>
216     >>> c.aref
217     <_NamedInt ground>
218
219     >>> data = c.data_read()
220     >>> data  # doctest: +SKIP
221     32670L
222     >>> converter = c.get_converter()
223     >>> converter  # doctest: +NORMALIZE_WHITESPACE
224     <CalibratedConverter
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
231     True
232
233     >>> data = c.data_read_n(5)
234     >>> data  # doctest: +SKIP
235     array([32674, 32673, 32674, 32672, 32675], dtype=uint32)
236
237     >>> c.data_read_hint()
238     >>> c.data_read()  # doctest: +SKIP
239     32672L
240
241     >>> data = c.data_read_delayed(nano_sec=1e3)
242     >>> data  # doctest: +SKIP
243     32672L
244
245     >>> s = d.get_write_subdevice()
246     >>> c = s.channel(0, factory=AnalogChannel)
247
248     >>> converter = c.get_converter()
249     >>> converter  # doctest: +NORMALIZE_WHITESPACE
250     <CalibratedConverter
251      to_physical:{coefficients:[-10.0, 0.00030518043793392844] origin:0.0}
252      from_physical:{coefficients:[0.0, 3276.75] origin:-10.0}>
253
254     >>> c.data_write(converter.from_physical(0))
255
256     >>> d.close()
257
258     Even after the device is closed, the range information is
259     retained.
260
261     >>> c.range
262     <Range unit:volt min:-10.0 max:10.0>
263     """
264     cdef public _Range range
265     cdef public object aref
266
267     def __init__(self, range=None, aref=None, **kwargs):
268         super(AnalogChannel, self).__init__(**kwargs)
269         if range == None:
270             range = self.get_range(0)
271         self.range = range
272         if aref == None:
273             flags = self.subdevice.get_flags()
274             for ar in _constant.AREF:
275                 if getattr(flags, ar.name):
276                     aref = ar
277                     break
278                 raise _PyComediError(
279                     '%s does not support any known analog reference type (%s)'
280                     % (self.subdevice, flags))
281         self.aref = aref
282
283     # syncronous stuff
284
285     def data_read(self):
286         "Read one sample"
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),
293             &data)
294         if ret < 0:
295             _error.raise_error(function_name='comedi_data_read', ret=ret)
296         return data
297
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)
307         if ret < 0:
308             _error.raise_error(function_name='comedi_data_read_n', ret=ret)
309         return data
310
311     def data_read_hint(self):
312         """Tell driver which channel/range/aref you will read next
313
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.
321
322         Alternatively, one can simply use `.data_read_delayed()`,
323         which sets up the input, pauses to allow settling, then
324         performs a conversion.
325         """
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))
331         if ret < 0:
332             _error.raise_error(function_name='comedi_data_read_hint', ret=ret)
333
334     def data_read_delayed(self, nano_sec=0):
335         """Read single sample after delaying specified settling time.
336
337         Although the settling time is specified in integer
338         nanoseconds, the actual settling time will be rounded up to
339         the nearest microsecond.
340         """
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))
348         if ret < 0:
349             _error.raise_error(function_name='comedi_data_read_delayed',
350                                ret=ret)
351         return data
352
353     def data_write(self, data):
354         """Write one sample
355
356         Returns 1 (the number of data samples written).
357         """
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),
363             int(data))
364         if ret != 1:
365             _error.raise_error(function_name='comedi_data_write', ret=ret)
366
367     def chanspec(self):
368         return _ChanSpec(chan=self.index, range=self.range, aref=self.aref)
369
370
371     cdef _comedilib_h.comedi_polynomial_t get_softcal_converter(
372         self, direction, calibration):
373         """
374
375         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
376         """
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)
384         #if rc < 0:
385         #    _error.raise_error(function_name='comedi_get_softcal_converter',
386         #                       ret=rc)
387         return poly
388
389     cdef _comedilib_h.comedi_polynomial_t get_hardcal_converter(
390         self, direction):
391         """
392
393         `direction` should be a value from `constant.CONVERSION_DIRECTION`.
394         """
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)
401         if rc < 0:
402             _error.raise_error(function_name='comedi_get_hardcal_converter',
403                                ret=rc)
404         return poly
405
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()
414         else:
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
422         return ret
423
424     def get_converter(self, calibration=None):
425         return self._get_converter(calibration)