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