Merge calibaration branch into master.
[pycomedi.git] / pycomedi / device.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 device-wide Comedi functions in a `Device` class"
18
19 import os as _os
20 cimport libc.stdlib as _stdlib
21
22 from pycomedi import LOG as _LOG
23 from pycomedi import PyComediError as _PyComediError
24 cimport _comedi_h
25 cimport _comedilib_h
26 import _error
27 from calibration import Calibration as _Calibration
28 from instruction cimport Insn as _Insn
29 from instruction import Insn as _Insn
30 from subdevice import Subdevice as _Subdevice
31
32
33 cdef class Device (object):
34     """A Comedi device
35
36     >>> from . import constant
37
38     >>> d = Device('/dev/comediX')
39     >>> d.filename
40     '/dev/comediX'
41
42     > d.open()  # TODO: re-enable when there is a way to clear comedi_errno
43     Traceback (most recent call last):
44       ...
45     PyComediError: comedi_open (/dev/comediX): No such file or directory (None)
46     >>> d.filename = '/dev/comedi0'
47     >>> d.open()
48     >>> d.fileno()
49     3
50     >>> d.get_n_subdevices()
51     14
52     >>> d.get_version()
53     (0, 7, 76)
54     >>> d.get_driver_name()
55     'ni_pcimio'
56     >>> s = d.get_read_subdevice()
57     >>> s.index
58     0
59     >>> s = d.get_write_subdevice()
60     >>> s.index
61     1
62     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.calib)
63     >>> s.index
64     5
65     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.pwm)
66     Traceback (most recent call last):
67       ...
68     PyComediError: comedi_find_subdevice_by_type: Success (-1)
69
70     As a test instruction, we'll get the time of day, which fills in
71     the data field with `[seconds, microseconds]`.
72
73     >>> insn = d.insn()
74     >>> insn.insn = constant.INSN.gtod
75     >>> insn.data = [0, 0]  # space where the time value will be stored
76     >>> print str(insn)
77         insn: gtod
78         data: [0 0]
79       subdev: 0
80     chanspec: <ChanSpec chan:0 range:0 aref:ground flags:->
81     >>> d.do_insn(insn)
82     2
83     >>> print insn.data  # doctest: +SKIP
84     [1297377578     105790]
85     >>> insn.data = [0, 0]
86     >>> d.do_insnlist([insn])
87     1
88     >>> print insn.data  # doctest: +SKIP
89     [1297377578     110559]
90
91     >>> d.get_default_calibration_path()
92     '/var/lib/comedi/calibrations/ni_pcimio_pci-6052e_comedi0'
93
94     >>> list(d.subdevices())  # doctest: +ELLIPSIS
95     [<pycomedi.subdevice.Subdevice object at 0x...>,...]
96
97     >>> d.close()
98     """
99     def __cinit__(self):
100         self.device = NULL
101         self.file = None
102         self.filename = None
103
104     def __init__(self, filename):
105         super(Device, self).__init__()
106         self.filename = filename
107
108     def open(self):
109         "Open device"
110         self.device = _comedilib_h.comedi_open(self.filename)
111         if self.device == NULL:
112             _error.raise_error(function_name='comedi_open',
113                                error_msg=self.filename)
114         self.file = _os.fdopen(self.fileno(), 'r+')
115
116     def close(self):
117         "Close device"
118         self.file.flush()
119         self.file.close()
120         ret = _comedilib_h.comedi_close(self.device)
121         if ret < 0:
122             _error.raise_error(function_name='comedi_close', ret=ret)
123         self.device = NULL
124         self.file = None
125
126     def fileno(self):
127         "File descriptor for this device"
128         ret = _comedilib_h.comedi_fileno(self.device)
129         if ret < 0:
130             _error.raise_error(function_name='comedi_fileno', ret=ret)
131         return ret
132
133     def get_n_subdevices(self):
134         "Number of subdevices"
135         ret = _comedilib_h.comedi_get_n_subdevices(self.device)
136         if ret < 0:
137             _error.raise_error(function_name='comedi_get_n_subdevices',
138                                 ret=ret)
139         return ret
140
141     def get_version_code(self):
142         """Comedi version code as a single integer.
143
144         This is a kernel-module level property, but a valid device is
145         necessary to communicate with the kernel module.
146         """
147         version = _comedilib_h.comedi_get_version_code(self.device)
148         if version < 0:
149             _error.raise_error(function_name='comedi_get_version_code',
150                                 ret=version)
151         return version
152
153     def get_version(self):
154         """Comedi version as a tuple of version numbers.
155
156         Returns the result of `.get_version_code()`, but rephrased as
157         a tuple of version numbers, e.g. `(0, 7, 61)`.
158         """
159         version = self.get_version_code()
160         ret = []
161         for i in range(3):
162             ret.insert(0, version & 0xff)  # grab lowest 8 bits
163             version >>= 8  # shift over 8 bits
164         return tuple(ret)
165
166     def get_driver_name(self):
167         "Comedi driver name"
168         ret = _comedilib_h.comedi_get_driver_name(self.device)
169         if ret == NULL:
170             _error.raise_error(function_name='comedi_get_driver_name',
171                                 ret=ret)
172         return ret
173
174     def get_board_name(self):
175         "Comedi board name"
176         ret = _comedilib_h.comedi_get_board_name(self.device)
177         if ret == NULL:
178             _error.raise_error(function_name='comedi_get_driver_name',
179                                 ret=ret)
180         return ret
181
182     def _get_read_subdevice(self):
183         "Find streaming input subdevice index"
184         ret = _comedilib_h.comedi_get_read_subdevice(self.device)
185         if ret < 0:
186             _error.raise_error(function_name='comedi_get_read_subdevice',
187                                 ret=ret)
188         return ret
189
190     def get_read_subdevice(self, **kwargs):
191         "Find streaming input subdevice"
192         return self.subdevice(self._get_read_subdevice(), **kwargs)
193
194     def _get_write_subdevice(self):
195         "Find streaming output subdevice index"
196         ret = _comedilib_h.comedi_get_write_subdevice(self.device)
197         if ret < 0:
198             _error.raise_error(function_name='comedi_get_write_subdevice',
199                                 ret=ret)
200         return ret
201
202     def get_write_subdevice(self, **kwargs):
203         "Find streaming output subdevice"
204         return self.subdevice(self._get_write_subdevice(), **kwargs)
205
206     def _find_subdevice_by_type(self, subdevice_type):
207         "Search for a subdevice index for type `subdevice_type`)."
208         ret = _comedilib_h.comedi_find_subdevice_by_type(
209             self.device, subdevice_type.value, 0)  # 0 is starting subdevice
210         if ret < 0:
211             _error.raise_error(function_name='comedi_find_subdevice_by_type',
212                                 ret=ret)
213         return ret
214
215     def find_subdevice_by_type(self, subdevice_type, **kwargs):
216         """Search for a subdevice by type `subdevice_type`)."
217
218         `subdevice_type` should be an item from `constant.SUBDEVICE_TYPE`.
219         """
220         return self.subdevice(
221             self._find_subdevice_by_type(subdevice_type), **kwargs)
222
223     cpdef do_insnlist(self, insnlist):
224         """Perform multiple instructions
225
226         Returns the number of successfully completed instructions.
227         """
228         cdef _comedi_h.comedi_insnlist il
229         cdef _Insn i
230         il.n_insns = len(insnlist)
231         if il.n_insns == 0:
232             return
233         il.insns = <_comedi_h.comedi_insn *>_stdlib.malloc(
234             il.n_insns*sizeof(_comedi_h.comedi_insn))
235         if il.insns is NULL:
236             raise _PyComediError('out of memory?')
237         try:
238             for j,insn in enumerate(insnlist):
239                 i = insn
240                 # By copying the pointer to data, changes to this
241                 # copied instruction will also affect the original
242                 # instruction's data.
243                 il.insns[j] = i.get_comedi_insn()
244             ret = _comedilib_h.comedi_do_insnlist(self.device, &il)
245         finally:
246             _stdlib.free(il.insns)
247         if ret < len(insnlist):
248             _error.raise_error(function_name='comedi_do_insnlist', ret=ret)
249         return ret
250
251     cpdef do_insn(self, _Insn insn):
252         """Preform a single instruction.
253
254         Returns an instruction-specific integer.
255         """
256         cdef _comedi_h.comedi_insn i
257         # By copying the pointer to data, changes to this
258         # copied instruction will also affect the original
259         # instruction's data.
260         i = insn.get_comedi_insn()
261         ret = _comedilib_h.comedi_do_insn(
262             self.device, &i)
263         if ret < 0:
264             _error.raise_error(function_name='comedi_do_insn', ret=ret)
265         return ret
266
267     def get_default_calibration_path(self):
268         "The default calibration path for this device"
269         assert self.device != NULL, (
270             'must call get_default_calibration_path on an open device.')
271         ret = _comedilib_h.comedi_get_default_calibration_path(self.device)
272         if ret == NULL:
273             _error.raise_error(
274                 function_name='comedi_get_default_calibration_path')
275         return ret
276
277     def parse_calibration(self, path=None):
278         """The soft calibration from a file for this device.
279
280         If path is None, the default calibration file is used.
281         """
282         if path is None:
283             path = self.get_default_calibration_path()
284         c = _Calibration(device=self)
285         c.from_file(path)
286         return c
287
288     # extensions to make a more idomatic Python interface
289
290     def insn(self):
291         return _Insn()
292
293     def subdevices(self, **kwargs):
294         "Iterate through all available subdevices."
295         ret = []
296         for i in range(self.get_n_subdevices()):
297             #yield self.subdevice(i, **kwargs)
298             # Generators are not supported in Cython 0.14.1
299             ret.append(self.subdevice(i, **kwargs))
300         return ret
301
302     def subdevice(self, index, factory=_Subdevice, **kwargs):
303         return factory(device=self, index=index, **kwargs)