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