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