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