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