command: Fix except declaration for get_comedi_cmd_pointer
[pycomedi.git] / pycomedi / calibration.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 """Pythonic wrappers for converting between Comedilib and physical units
18
19 For one-off conversions, use the functions `comedi_to_physical` and
20 `comedi_from_physical`.  For repeated conversions, use an instance of
21 `CalibratedConverter`.
22 """
23
24 from libc cimport stdlib as _stdlib
25 from libc cimport string as _string
26 cimport numpy as _numpy
27 import numpy as _numpy
28
29 from pycomedi cimport _comedi_h
30 from pycomedi cimport _comedilib_h
31 from . import _error
32 from . import constant as _constant
33 from . import utility as _utility
34
35
36 cdef void _python_to_charp(
37     char **charp, object obj, object encoding) except *:
38     """Convert a Python string into a `char *`.
39
40     Cython automatically converts string or byte array to a `char *`
41     for use with external C libraries.  However, the resulting
42     pointers are only valid until the Python object is garbage
43     collected.  For the `Calibration` class, we need persistent
44     pointers that will be manually freed later.  This function creates
45     these manual copies.
46     """
47     cdef char *ret
48     cdef char *src
49     if charp[0] is not NULL:  # charp[0] is the same as *charp
50         _stdlib.free(charp[0])
51         charp[0] = NULL
52     if hasattr(obj, 'encode'):
53         obj = obj.encode(encoding, 'strict')
54     src = obj
55     ret = <char *> _stdlib.malloc(len(obj) + 1)
56     if ret is NULL:
57         raise MemoryError()
58     _string.strncpy(ret, src, len(obj) + 1)
59     charp[0] = ret
60
61 cdef void _setup_comedi_polynomial_t(
62     _comedilib_h.comedi_polynomial_t *p, coefficients, expansion_origin
63     ) except *:
64     """Setup the `comedi_polynomial_t` at `p`
65
66     * `coefficients` is an iterable containing polynomial coefficients
67     * `expansion_origin` is the center of the polynomial expansion
68     """
69     for i,x in enumerate(coefficients):
70         p.coefficients[i] = x
71     p.order = len(coefficients)-1
72     p.expansion_origin = expansion_origin
73
74 cdef object _convert(
75     _comedilib_h.comedi_polynomial_t *p, object data, object direction):
76     """Apply the polynomial conversion `p` to `data`.
77
78     `direction` should be a value from `constant.CONVERSION_DIRECTION`.
79     """
80     to_physical = (_constant.bitwise_value(direction)
81                    == _constant.CONVERSION_DIRECTION.to_physical.value)
82     if _numpy.isscalar(data):
83         if to_physical:
84             return _comedilib_h.comedi_to_physical(data, p)
85         else:
86             return _comedilib_h.comedi_from_physical(data, p)
87     if to_physical:
88         dtype = _numpy.double
89     else:
90         dtype = _utility.lsampl
91     array = _numpy.array(data, dtype=dtype)
92     for i,d in enumerate(data):
93         if to_physical:
94             array[i] = _comedilib_h.comedi_to_physical(d, p)
95         else:
96             array[i] = _comedilib_h.comedi_from_physical(d, p)
97     return array
98
99 cpdef comedi_to_physical(data, coefficients, expansion_origin):
100     """Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
101
102     * `data` is the value to be converted (scalar or array-like)
103     * `coefficients` and `expansion_origin` should be appropriate
104       for `_setup_comedi_polynomial_t`.  TODO: expose it's docstring?
105
106     The conversion algorithm is::
107
108         x = sum_i c_i * (d-d_o)^i
109
110     where `x` is the returned physical value, `d` is the supplied data,
111     `c_i` is the `i`\th coefficient, and `d_o` is the expansion origin.
112
113     >>> print comedi_to_physical.__doc__  # doctest: +ELLIPSIS
114     Convert Comedi bit values (`lsampl_t`) to physical units (`double`)
115     ...
116     >>> comedi_to_physical(1, [1, 2, 3], 2)
117     2.0
118     >>> comedi_to_physical([1, 2, 3], [1, 2, 3], 2)
119     array([ 2.,  1.,  6.])
120     """
121     cdef _comedilib_h.comedi_polynomial_t p
122     _setup_comedi_polynomial_t(&p, coefficients, expansion_origin)
123     return _convert(&p, data, _constant.CONVERSION_DIRECTION.to_physical)
124
125 cpdef comedi_from_physical(data, coefficients, expansion_origin):
126     """Convert physical units to Comedi bit values
127
128     Like `comedi_to_physical` but converts `double` -> `lsampl_t`.
129
130     >>> comedi_from_physical(1, [1,2,3], 2)
131     2L
132     >>> comedi_from_physical([1, 2, 3], [1, 2, 3], 2)
133     array([2, 1, 6], dtype=uint32)
134     """
135     cdef _comedilib_h.comedi_polynomial_t p
136     _setup_comedi_polynomial_t(&p, coefficients, expansion_origin)
137     return _convert(&p, data, _constant.CONVERSION_DIRECTION.from_physical)
138
139
140 cdef class CalibratedConverter (object):
141     """Apply a converion polynomial
142
143     Usually you would get the this converter from
144     `DataChannel.get_converter()` or similar. but for testing, we'll
145     just create one out of thin air.
146
147     >>> c = CalibratedConverter(
148     ...     to_physical_coefficients=[1, 2, 3],
149     ...     to_physical_expansion_origin=1)
150     >>> c  # doctest: +NORMALIZE_WHITESPACE
151     <CalibratedConverter
152      to_physical:{coefficients:[1.0, 2.0, 3.0] origin:1.0}
153      from_physical:{coefficients:[0.0] origin:0.0}>
154
155     >>> c.to_physical(1)
156     1.0
157     >>> c.to_physical([0, 1, 2])
158     array([ 2.,  1.,  6.])
159     >>> c.to_physical(_numpy.array([0, 1, 2, 3], dtype=_numpy.uint))
160     array([  2.,   1.,   6.,  17.])
161
162     >>> c.get_to_physical_expansion_origin()
163     1.0
164     >>> c.get_to_physical_coefficients()
165     array([ 1.,  2.,  3.])
166
167     For some soft-calibrated boards, there is no from_physical
168     conversion polynomial.
169
170     >>> c = CalibratedConverter(
171     ...     from_physical_error=Exception('no conversion polynomial'))
172     >>> c.from_physical(1.0)
173     Traceback (most recent call last):
174       ...
175     Exception: no conversion polynomial
176
177     However, even with the error, you can extract dummy coefficients.
178
179     >>> c.get_from_physical_expansion_origin()
180     0.0
181     >>> c.get_from_physical_coefficients()
182     array([ 0.])
183     """
184     def __cinit__(self):
185         self._from_physical_error = None
186
187     def __init__(self, to_physical_coefficients=None,
188                  to_physical_expansion_origin=0,
189                  from_physical_coefficients=None,
190                  from_physical_expansion_origin=0,
191                  from_physical_error=None):
192         if to_physical_coefficients:
193             _setup_comedi_polynomial_t(
194                 &self._to_physical, to_physical_coefficients,
195                  to_physical_expansion_origin)
196         if from_physical_coefficients:
197             _setup_comedi_polynomial_t(
198                 &self._from_physical, from_physical_coefficients,
199                  from_physical_expansion_origin)
200         self._from_physical_error = from_physical_error
201
202     cdef _str_poly(self, _comedilib_h.comedi_polynomial_t polynomial):
203         return '{coefficients:%s origin:%s}' % (
204             [float(polynomial.coefficients[i])
205              for i in range(polynomial.order+1)],
206             float(polynomial.expansion_origin))
207
208     def __str__(self):
209         return '<%s to_physical:%s from_physical:%s>' % (
210             self.__class__.__name__, self._str_poly(self._to_physical),
211             self._str_poly(self._from_physical))
212
213     def __repr__(self):
214         return self.__str__()
215
216     cpdef to_physical(self, data):
217         return _convert(&self._to_physical, data,
218                         _constant.CONVERSION_DIRECTION.to_physical)
219
220     cpdef from_physical(self, data):
221         if self._from_physical_error is not None:
222             raise self._from_physical_error
223         return _convert(&self._from_physical, data,
224                         _constant.CONVERSION_DIRECTION.from_physical)
225
226     cpdef get_to_physical_expansion_origin(self):
227         return self._to_physical.expansion_origin
228
229     cpdef get_to_physical_coefficients(self):
230         ret = _numpy.ndarray((self._to_physical.order+1,), _numpy.double)
231         for i in xrange(len(ret)):
232             ret[i] = self._to_physical.coefficients[i]
233         return ret
234
235     cpdef get_from_physical_expansion_origin(self):
236         return self._from_physical.expansion_origin
237
238     cpdef get_from_physical_coefficients(self):
239         ret = _numpy.ndarray((self._from_physical.order+1,), _numpy.double)
240         for i in xrange(len(ret)):
241             ret[i] = self._from_physical.coefficients[i]
242         return ret
243
244
245 cdef class Caldac (object):
246     """Class wrapping comedi_caldac_t
247
248     >>> from .device import Device
249     >>> from . import constant
250
251     >>> d = Device('/dev/comedi0')
252     >>> d.open()
253
254     >>> c = d.parse_calibration()
255     >>> s = c.settings[0]
256     >>> print(s)
257     <CalibrationSetting device:/dev/comedi0 subdevice:0>
258     >>> caldac = s.caldacs[0]
259     >>> print(caldac)
260     <Caldac subdevice:5 channel:4 value:255>
261
262     >>> d.close()
263
264     You can also allocate `Caldac` instances on your own.  The
265     allocated memory will be automatically freed when the instance is
266     garbage collected.
267
268     >>> caldac = Caldac()
269     >>> caldac.subdevice == None
270     True
271     >>> caldac.subdevice = 1
272     Traceback (most recent call last):
273       ...
274     AssertionError: load caldac first
275     >>> caldac.allocate()
276     >>> caldac.subdevice
277     0L
278     >>> caldac.subdevice = 1
279     """
280     def __cinit__(self):
281         self.caldac = NULL
282         self._local = False
283
284     def __dealloc__(self):
285         if self.caldac is not NULL and self._local:
286             _stdlib.free(self.caldac)
287             self.caldac = NULL
288             self._local = False
289
290     def __str__(self):
291         fields = ['{}:{}'.format(f, getattr(self, f))
292                   for f in ['subdevice', 'channel', 'value']]
293         return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields))
294
295     def __repr__(self):
296         return self.__str__()
297
298     def allocate(self):
299         assert not self._local, 'already allocated'
300         self.caldac = <_comedilib_h.comedi_caldac_t *> _stdlib.malloc(
301             sizeof(_comedilib_h.comedi_caldac_t *))
302         if self.caldac is NULL:
303             raise MemoryError()
304         self._local = True
305         self.subdevice = 0
306         self.channel = 0
307         self.value = 0
308
309     def _subdevice_get(self):
310         if self.caldac is not NULL:
311             return self.caldac.subdevice
312     def _subdevice_set(self, value):
313         assert self.caldac is not NULL, 'load caldac first'
314         self.caldac.subdevice = value
315     subdevice = property(fget=_subdevice_get, fset=_subdevice_set)
316
317     def _channel_get(self):
318         if self.caldac is not NULL:
319             return self.caldac.channel
320     def _channel_set(self, value):
321         assert self.caldac is not NULL, 'load caldac first'
322         self.caldac.channel = value
323     channel = property(fget=_channel_get, fset=_channel_set)
324
325     def _value_get(self):
326         if self.caldac is not NULL:
327             return self.caldac.value
328     def _value_set(self, value):
329         assert self.caldac is not NULL, 'load caldac first'
330         self.caldac.value = value
331     value = property(fget=_value_get, fset=_value_set)
332
333
334 cdef class CalibrationSetting (object):
335     """Class wrapping comedi_calibration_setting_t
336
337     >>> from .device import Device
338     >>> from . import constant
339
340     >>> d = Device('/dev/comedi0')
341     >>> d.open()
342
343     >>> c = d.parse_calibration()
344     >>> s = c.settings[0]
345     >>> print(s)
346     <CalibrationSetting device:/dev/comedi0 subdevice:0>
347     >>> print(s.subdevice)  # doctest: +ELLIPSIS
348     <pycomedi.subdevice.Subdevice object at 0x...>
349
350     >>> for s in c.settings:
351     ...     print('{} {}'.format(s.subdevice.index, s.subdevice.get_type()))
352     ...     print('  channels: {}'.format(s.channels))
353     ...     print('  ranges: {}'.format(s.ranges))
354     ...     print('  arefs: {}'.format(s.arefs))
355     ...     print('  caldacs:')
356     ...     for caldac in s.caldacs:
357     ...         print('    {}'.format(caldac))
358     ...     print('  soft_calibration:')
359     ...     sc = s.soft_calibration
360     ...     print('    to physical coefficients: {}'.format(
361     ...         sc.get_to_physical_coefficients()))
362     ...     print('    to physical origin: {}'.format(
363     ...         sc.get_to_physical_expansion_origin()))
364     ...     print('    from physical coefficients: {}'.format(
365     ...         sc.get_from_physical_coefficients()))
366     ...     print('    from physical origin: {}'.format(
367     ...         sc.get_from_physical_expansion_origin()))
368     ... # doctest: +REPORT_UDIFF
369     0 ai
370       channels: []
371       ranges: []
372       arefs: []
373       caldacs:
374         <Caldac subdevice:5 channel:4 value:255>
375         <Caldac subdevice:5 channel:2 value:255>
376         <Caldac subdevice:5 channel:3 value:255>
377         <Caldac subdevice:5 channel:0 value:255>
378         <Caldac subdevice:5 channel:5 value:255>
379         <Caldac subdevice:5 channel:1 value:1>
380       soft_calibration:
381         to physical coefficients: [ 0.]
382         to physical origin: 0.0
383         from physical coefficients: [ 0.]
384         from physical origin: 0.0
385     0 ai
386       channels: []
387       ranges: [ 8  9 10 11 12 13 14 15]
388       arefs: []
389       caldacs:
390         <Caldac subdevice:5 channel:6 value:255>
391         <Caldac subdevice:5 channel:7 value:0>
392       soft_calibration:
393         to physical coefficients: [ 0.]
394         to physical origin: 0.0
395         from physical coefficients: [ 0.]
396         from physical origin: 0.0
397     1 ao
398       channels: [0]
399       ranges: [0 2]
400       arefs: []
401       caldacs:
402         <Caldac subdevice:5 channel:16 value:255>
403         <Caldac subdevice:5 channel:19 value:0>
404         <Caldac subdevice:5 channel:17 value:0>
405         <Caldac subdevice:5 channel:18 value:0>
406       soft_calibration:
407         to physical coefficients: [ 0.]
408         to physical origin: 0.0
409         from physical coefficients: [ 0.]
410         from physical origin: 0.0
411     1 ao
412       channels: [0]
413       ranges: [1 3]
414       arefs: []
415       caldacs:
416         <Caldac subdevice:5 channel:16 value:239>
417         <Caldac subdevice:5 channel:19 value:0>
418         <Caldac subdevice:5 channel:17 value:0>
419         <Caldac subdevice:5 channel:18 value:0>
420       soft_calibration:
421         to physical coefficients: [ 0.]
422         to physical origin: 0.0
423         from physical coefficients: [ 0.]
424         from physical origin: 0.0
425     1 ao
426       channels: [1]
427       ranges: [0 2]
428       arefs: []
429       caldacs:
430         <Caldac subdevice:5 channel:20 value:255>
431         <Caldac subdevice:5 channel:23 value:0>
432         <Caldac subdevice:5 channel:21 value:0>
433         <Caldac subdevice:5 channel:22 value:0>
434       soft_calibration:
435         to physical coefficients: [ 0.]
436         to physical origin: 0.0
437         from physical coefficients: [ 0.]
438         from physical origin: 0.0
439     1 ao
440       channels: [1]
441       ranges: [1 3]
442       arefs: []
443       caldacs:
444         <Caldac subdevice:5 channel:20 value:249>
445         <Caldac subdevice:5 channel:23 value:0>
446         <Caldac subdevice:5 channel:21 value:0>
447         <Caldac subdevice:5 channel:22 value:0>
448       soft_calibration:
449         to physical coefficients: [ 0.]
450         to physical origin: 0.0
451         from physical coefficients: [ 0.]
452         from physical origin: 0.0
453
454     Test setting various attributes.
455
456     >>> s = c.settings[-1]
457     >>> s.channels = [0, 1, 2]
458     >>> s.channels
459     array([0, 1, 2])
460     >>> s.ranges = [0, 1]
461     >>> s.ranges
462     array([0, 1])
463     >>> s.arefs = [0]
464     >>> s.arefs
465     array([0])
466     >>> caldacs = []
467     >>> for i in range(3):
468     ...     caldac = Caldac()
469     ...     caldac.allocate()
470     ...     caldac.subdevice = i
471     ...     caldac.channel = 2*i
472     ...     caldac.value = 3*i
473     ...     caldacs.append(caldac)
474     >>> s.caldacs = caldacs
475
476     >>> d.close()
477     """
478     def __cinit__(self):
479         self.setting = NULL
480         self.subdevice = None
481
482     def __init__(self, subdevice):
483         super(CalibrationSetting, self).__init__()
484         self.subdevice = subdevice
485
486     def __str__(self):
487         fields = [
488             'device:{}'.format(self.subdevice.device.filename),
489             'subdevice:{}'.format(self.subdevice.index),
490             ]
491         return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields))
492
493     def _channels_get(self):
494         if self.setting is NULL:
495             return None
496         ret = _numpy.ndarray(shape=(self.setting.num_channels,), dtype=int)
497         # TODO: point into existing data array?
498         for i in range(self.setting.num_channels):
499             ret[i] = self.setting.channels[i]
500         return ret
501     def _channels_set(self, value):
502         assert self.setting is not NULL, 'load setting first'
503         if self.setting.channels is not NULL:
504             _stdlib.free(self.setting.channels)
505         length = len(value)
506         self.setting.channels = <unsigned int *> _stdlib.malloc(
507             length * sizeof(unsigned int))
508         if self.setting.channels is NULL:
509             self.setting.num_channels = 0
510             raise MemoryError()
511         self.setting.num_channels = length
512         for i,x in enumerate(value):
513             if i >= length:
514                 raise ValueError((i, length))
515             self.setting.channels[i] = x
516     channels = property(fget=_channels_get, fset=_channels_set)
517
518     def _ranges_get(self):
519         if self.setting is NULL:
520             return None
521         ret = _numpy.ndarray(shape=(self.setting.num_ranges,), dtype=int)
522         # TODO: point into existing data array?
523         for i in range(self.setting.num_ranges):
524             ret[i] = self.setting.ranges[i]
525         return ret
526     def _ranges_set(self, value):
527         assert self.setting is not NULL, 'load setting first'
528         if self.setting.ranges is not NULL:
529             _stdlib.free(self.setting.ranges)
530         length = len(value)
531         self.setting.ranges = <unsigned int *> _stdlib.malloc(
532             length * sizeof(unsigned int))
533         if self.setting.ranges is NULL:
534             self.setting.num_ranges = 0
535             raise MemoryError()
536         self.setting.num_ranges = length
537         for i,x in enumerate(value):
538             if i >= length:
539                 raise ValueError((i, length))
540             self.setting.ranges[i] = x
541     ranges = property(fget=_ranges_get, fset=_ranges_set)
542
543     def _arefs_get(self):
544         if self.setting is NULL:
545             return None
546         ret = _numpy.ndarray(shape=(self.setting.num_arefs,), dtype=int)
547         # TODO: point into existing data array?
548         for i in range(self.setting.num_arefs):
549             ret[i] = self.setting.arefs[i]
550         return ret
551     def _arefs_set(self, value):
552         assert self.setting is not NULL, 'load setting first'
553         length = len(value)
554         for i,x in enumerate(value):
555             if i >= _comedilib_h.CS_MAX_AREFS_LENGTH:
556                 raise ValueError((i, _comedilib_h.CS_MAX_AREFS_LENGTH))
557             self.setting.arefs[i] = x
558         for i in range(length, _comedilib_h.CS_MAX_AREFS_LENGTH):
559             self.setting.arefs[i] = 0
560         self.setting.num_arefs = length
561     arefs = property(fget=_arefs_get, fset=_arefs_set)
562
563     def _caldacs_get(self):
564         if self.setting is NULL:
565             return None
566         if not self.setting.num_caldacs:
567             return []
568         # TODO: point into existing data array?
569         ret = []
570         for i in range(self.setting.num_caldacs):
571             c = Caldac()
572             c.caldac = &self.setting.caldacs[i]
573             ret.append(c)
574         return ret
575     cdef _caldacs_set_single(self, index, Caldac caldac):
576         self.setting.caldacs[index] = caldac.caldac[0]
577     def _caldacs_set(self, value):
578         assert self.setting is not NULL, 'load setting first'
579         if self.setting.caldacs is not NULL:
580             _stdlib.free(self.setting.caldacs)
581         length = len(value)
582         self.setting.caldacs = <_comedilib_h.comedi_caldac_t *> _stdlib.malloc(
583             length * sizeof(_comedilib_h.comedi_caldac_t))
584         if self.setting.caldacs is NULL:
585             self.setting.num_caldacs = 0
586             raise MemoryError()
587         self.setting.num_caldacs = length
588         for i,x in enumerate(value):
589             if i >= length:
590                 raise ValueError((i, length))
591             self._caldacs_set_single(i, x)
592     caldacs = property(fget=_caldacs_get, fset=_caldacs_set)
593
594     def _soft_calibration_get(self):
595         cdef CalibratedConverter ret
596         if self.setting is NULL:
597             return None
598         ret = CalibratedConverter()
599         if self.setting.soft_calibration.to_phys is not NULL:
600             ret._to_physical = self.setting.soft_calibration.to_phys[0]
601         if self.setting.soft_calibration.from_phys is not NULL:
602             ret._from_physical = self.setting.soft_calibration.from_phys[0]
603         return ret
604     cpdef _soft_calibration_set(self, CalibratedConverter value):
605         assert self.setting is not NULL, 'load setting first'
606         if (self.setting.soft_calibration.to_phys is NULL and
607             (value._to_physical.expansion_origin or
608              value._to_physical.order >= 0)):
609             self.setting.soft_calibration.to_phys = (
610                 <_comedilib_h.comedi_polynomial_t *> _stdlib.malloc(
611                     sizeof(_comedilib_h.comedi_polynomial_t)))
612         self.setting.soft_calibration.to_phys[0] = value._to_physical
613         if (self.setting.soft_calibration.from_phys is NULL and
614             (value._from_physical.expansion_origin or
615              value._from_physical.order >= 0)):
616             self.setting.soft_calibration.from_phys = (
617                 <_comedilib_h.comedi_polynomial_t *> _stdlib.malloc(
618                     sizeof(_comedilib_h.comedi_polynomial_t)))
619         self.setting.soft_calibration.from_phys[0] = value._from_physical
620     soft_calibration = property(
621         fget=_soft_calibration_get, fset=_soft_calibration_set)
622
623
624 cdef class Calibration (object):
625     """A board calibration configuration.
626
627     Wraps comedi_calibration_t.
628
629     Warning: You probably want to use the `.from_file()` method or
630     `device.parse_calibration()` rather than initializing this
631     stucture by hand.
632
633     >>> from .device import Device
634     >>> from . import constant
635
636     >>> d = Device('/dev/comedi0')
637     >>> d.open()
638
639     >>> c = d.parse_calibration()
640
641     >>> print(c)
642     <Calibration device:/dev/comedi0>
643     >>> c.driver_name
644     'ni_pcimio'
645     >>> c.board_name
646     'pci-6052e'
647
648     >>> c.settings  # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
649     [<pycomedi.calibration.CalibrationSetting object at 0x...>,
650      ...
651      <pycomedi.calibration.CalibrationSetting object at 0x...>]
652     >>> print(c.settings[0])
653     <CalibrationSetting device:/dev/comedi0 subdevice:0>
654
655     >>> name = c.driver_name
656     >>> c.driver_name = "Override with your own value"
657     >>> c.driver_name = name
658
659     >>> d.close()
660     """
661     def __cinit__(self):
662         self.calibration = NULL
663         self.device = None
664
665     def __init__(self, device):
666         super(Calibration, self).__init__()
667         self.device = device
668
669     def __dealloc__(self):
670         if self.calibration is not NULL:
671             _comedilib_h.comedi_cleanup_calibration(self.calibration)
672             self.calibration = NULL
673
674     def __str__(self):
675         fields = ['device:{}'.format(self.device.filename)]
676         return '<{} {}>'.format(self.__class__.__name__, ' '.join(fields))
677
678     def __repr__(self):
679         return '<{} {}>'.format(self.__class__.__name__, id(self))
680
681     def _driver_name_get(self):
682         if self.calibration is NULL:
683             return None
684         return self.calibration.driver_name
685     def _driver_name_set(self, value):
686         assert self.calibration is not NULL, 'load calibration first'
687         _python_to_charp(&self.calibration.driver_name, value, 'ascii')
688     driver_name = property(fget=_driver_name_get, fset=_driver_name_set)
689
690     def _board_name_get(self):
691         if self.calibration is NULL:
692             return None
693         return self.calibration.board_name
694     def _board_name_set(self, value):
695         assert self.calibration is not NULL, 'load calibration first'
696         _python_to_charp(&self.calibration.board_name, value, 'ascii')
697     board_name = property(fget=_board_name_get, fset=_board_name_set)
698
699     def _settings_get(self):
700         if self.calibration is NULL:
701             return None
702         ret = []
703         for i in range(self.calibration.num_settings):
704             s = <CalibrationSetting> CalibrationSetting(
705                 subdevice=self.device.subdevice(
706                     index=self.calibration.settings[i].subdevice))
707             s.setting = &self.calibration.settings[i]
708             ret.append(s)
709         return ret
710     def _settings_set(self, value):
711         assert self.calibration is not NULL, 'load calibration first'
712         return None
713     settings = property(fget=_settings_get, fset=_settings_set)
714
715     cpdef from_file(self, path):
716         self.calibration = _comedilib_h.comedi_parse_calibration_file(path)
717         if self.calibration == NULL:
718             _error.raise_error(
719                 function_name='comedi_parse_calibration_file')
720
721
722 # TODO: see comedi_caldac_t and related at end of comedilib.h