command: Fix except declaration for get_comedi_cmd_pointer
[pycomedi.git] / pycomedi / subdevice.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 subdevice-wide Comedi functions in `Subdevice` and related classes"
18
19 from pycomedi cimport _comedi_h
20 from pycomedi cimport _comedilib_h
21 from pycomedi cimport command as _command
22 from . import LOG as _LOG
23 from . import _error as _error
24 from . import channel as _channel
25 from . import constant as _constant
26 from . import command as _command
27 from pycomedi.subdevice_holder cimport SubdeviceHolder as _SubdeviceHolder
28 from .subdevice_holder import SubdeviceHolder as _SubdeviceHolder
29 from .utility import _subdevice_dtype, _subdevice_typecode
30
31
32 cdef class Subdevice (_SubdeviceHolder):
33     """Class bundling subdevice-related functions
34
35     >>> from .device import Device
36     >>> from . import constant
37
38     >>> d = Device('/dev/comedi0')
39     >>> d.open()
40
41     >>> s = d.get_read_subdevice()
42     >>> s.get_type()
43     <_NamedInt ai>
44     >>> f = s.get_flags()
45     >>> f  # doctest: +ELLIPSIS
46     <pycomedi.constant.FlagValue object at 0x...>
47     >>> print str(f)
48     cmd_read|readable|ground|common|diff|other|dither
49     >>> s.get_n_channels()
50     16
51     >>> s.range_is_chan_specific()
52     False
53     >>> s.maxdata_is_chan_specific()
54     False
55     >>> s.lock()
56     >>> s.unlock()
57
58     >>> s = d.find_subdevice_by_type(constant.SUBDEVICE_TYPE.dio)
59     >>> s.dio_bitfield()
60     255L
61
62     >>> s.get_dtype()
63     <type 'numpy.uint16'>
64     >>> s.get_typecode()
65     'H'
66
67     >>> d.close()
68     """
69     def get_type(self):
70         "Type of subdevice (from `SUBDEVICE_TYPE`)"
71         ret = _comedilib_h.comedi_get_subdevice_type(
72             self._device(), self.index)
73         if ret < 0:
74             _error.raise_error(function_name='comedi_get_subdevice_type',
75                                ret=ret)
76         return _constant.SUBDEVICE_TYPE.index_by_value(ret)
77
78     def _get_flags(self):
79         "Subdevice flags"
80         ret = _comedilib_h.comedi_get_subdevice_flags(
81             self._device(), self.index)
82         if ret < 0:
83             _error.raise_error(function_name='comedi_get_subdevice_flags',
84                                ret=ret)
85         return ret
86
87     def get_flags(self):
88         "Subdevice flags (an `SDF` `FlagValue`)"
89         return _constant.FlagValue(
90             _constant.SDF, self._get_flags())
91
92     def get_n_channels(self):
93         "Number of subdevice channels"
94         ret = _comedilib_h.comedi_get_n_channels(
95             self._device(), self.index)
96         if ret < 0:
97             _error.raise_error(function_name='comedi_get_n_channels',
98                                ret=ret)
99         return ret
100
101     def range_is_chan_specific(self):
102         ret = _comedilib_h.comedi_range_is_chan_specific(
103             self._device(), self.index)
104         if ret < 0:
105             _error.raise_error(
106                 function_name='comedi_range_is_chan_specific', ret=ret)
107         return ret == 1
108
109     def maxdata_is_chan_specific(self):
110         ret = _comedilib_h.comedi_maxdata_is_chan_specific(
111             self._device(), self.index)
112         if ret < 0:
113             _error.raise_error(
114                 function_name='comedi_maxdata_is_chan_specific', ret=ret)
115         return ret == 1
116
117     def lock(self):
118         "Reserve the subdevice"
119         ret = _comedilib_h.comedi_lock(self._device(), self.index)
120         if ret < 0:
121             _error.raise_error(function_name='comedi_lock', ret=ret)
122
123     def unlock(self):
124         "Release the subdevice"
125         ret = _comedilib_h.comedi_unlock(self._device(), self.index)
126         if ret < 0:
127             _error.raise_error(function_name='comedi_unlock', ret=ret)
128
129     cpdef dio_bitfield(self, unsigned int bits=0, write_mask=0, base_channel=0):
130         """Read/write multiple digital channels.
131
132         `bits` and `write_mask` are bit fields with the least
133         significant bit representing channel `base_channel`.
134
135         Returns a bit field containing the read value of all input
136         channels and the last written value of all output channels.
137         """
138         ret = _comedilib_h.comedi_dio_bitfield2(
139             self._device(), self.index, write_mask, &bits, base_channel)
140         if ret < 0:
141             _error.raise_error(function_name='comedi_dio_bitfield2', ret=ret)
142         return bits
143
144     # extensions to make a more idomatic Python interface
145
146     def insn(self):
147         insn = self.device.insn()
148         insn.subdev = self.index
149         return insn
150
151     def channels(self, **kwargs):
152         "Iterate through all available channels."
153         for i in range(self.get_n_channels()):
154             yield self.channel(i, **kwargs)
155
156     def channel(self, index, factory=_channel.Channel, **kwargs):
157         "`Channel` instance for the `index`\ed channel."
158         return factory(subdevice=self, index=index, **kwargs)
159
160     def get_dtype(self):
161         "Return the appropriate `numpy.dtype` based on subdevice flags"
162         return _subdevice_dtype(self)
163
164     def get_typecode(self):
165         "Return the appropriate `array` type based on subdevice flags"
166         return _subdevice_typecode(self)
167
168
169 cdef class StreamingSubdevice (Subdevice):
170     """Streaming I/O subdevice
171
172     >>> from .device import Device
173     >>> from .chanspec import ChanSpec
174     >>> from . import constant
175
176     >>> d = Device('/dev/comedi0')
177     >>> d.open()
178
179     >>> s = d.get_read_subdevice(factory=StreamingSubdevice)
180
181     >>> cmd = s.get_cmd_src_mask()
182     >>> print str(cmd)
183             subdev: 0
184              flags: -
185          start_src: now|ext|int
186          start_arg: 0
187     scan_begin_src: timer|ext
188     scan_begin_arg: 0
189        convert_src: timer|ext
190        convert_arg: 0
191       scan_end_src: count
192       scan_end_arg: 0
193           stop_src: none|count
194           stop_arg: 0
195           chanlist: []
196               data: []
197
198     >>> chanlist_len = 3
199     >>> cmd = s.get_cmd_generic_timed(chanlist_len=chanlist_len,
200     ...     scan_period_ns=1e3)
201     >>> print str(cmd)  # doctest: +NORMALIZE_WHITESPACE
202             subdev: 0
203              flags: -
204          start_src: now
205          start_arg: 0
206     scan_begin_src: timer
207     scan_begin_arg: 9000
208        convert_src: timer
209        convert_arg: 3000
210       scan_end_src: count
211       scan_end_arg: 3
212           stop_src: count
213           stop_arg: 2
214           chanlist: [<ChanSpec chan:0 range:0 aref:ground flags:->,
215                      <ChanSpec chan:0 range:0 aref:ground flags:->,
216                      <ChanSpec chan:0 range:0 aref:ground flags:->]
217               data: []
218
219     >>> cmd.chanlist = [ChanSpec(chan=i, range=0) for i in range(chanlist_len)]
220     >>> s.cmd = cmd
221     >>> s.command_test()
222     >>> s.command()
223     >>> s.cancel()
224
225
226     >>> d.close()
227     """
228     def __cinit__(self):
229         self.cmd = _command.Command()
230         self._command_test_errors = [
231             None,  # valid
232             'unsupported *_src trigger',  # unsupported trigger bits zeroed
233             'unsupported *_src combo, or multiple triggers',
234             '*_arg out of range',  # offending members adjusted to valid values
235             '*_arg required adjustment',  # e.g. trigger timing period rounded
236             'invalid chanlist',
237             # e.g. some boards require same range across channels
238             ]
239
240     def get_cmd_src_mask(self):
241         """Detect streaming input/output capabilities
242
243         The command capabilities of the subdevice indicated by the
244         parameters device and subdevice are probed, and the results
245         placed in the command structure *command.  The trigger source
246         elements of the command structure are set to be the bitwise-or
247         of the subdevice's supported trigger sources.  Other elements
248         in the structure are undefined.
249         """
250         cdef _command.Command cmd
251         cmd = _command.Command()
252         ret = _comedilib_h.comedi_get_cmd_src_mask(
253             self._device(), self.index, cmd.get_comedi_cmd_pointer())
254         if ret < 0:
255             _error.raise_error(function_name='comedi_get_cmd_src_mask', ret=ret)
256         return cmd
257
258     def get_cmd_generic_timed(self, chanlist_len, scan_period_ns=0):
259         """Detect streaming input/output capabilities
260
261         The command capabilities of the subdevice indicated by the
262         parameters device and subdevice are probed, and the results
263         placed in the command structure pointed to by the parameter
264         command.  The command structure *command is modified to be a
265         valid command that can be used as a parameter to
266         comedi_command (after the command has additionally been
267         assigned a valid chanlist array).  The command measures scans
268         consisting of chanlist_len channels at a scan rate that
269         corresponds to a period of scan_period_ns nanoseconds.  The
270         rate is adjusted to a rate that the device can handle.
271
272         Note that the `ChanSpec` instances in `cmd.chanlist` are not
273         initialized to reasonable values.
274         """
275         cdef _command.Command cmd
276         cmd = _command.Command()
277         ret = _comedilib_h.comedi_get_cmd_generic_timed(
278             self._device(), self.index, cmd.get_comedi_cmd_pointer(),
279             chanlist_len, int(scan_period_ns))
280         cmd.chanlist = [0 for i in range(chanlist_len)]
281         if ret < 0:
282             _error.raise_error(function_name='comedi_get_cmd_generic_timed',
283                                ret=ret)
284         return cmd
285
286     def cancel(self):
287         "Stop streaming input/output in progress."
288         ret = _comedilib_h.comedi_cancel(self._device(), self.index)
289         if ret < 0:
290             _error.raise_error(function_name='comedi_cancel', ret=ret)
291
292     def command(self):
293         "Start streaming input/output"
294         ret = _comedilib_h.comedi_command(
295             self._device(), self.cmd.get_comedi_cmd_pointer())
296         if ret < 0:
297             _error.raise_error(function_name='comedi_command', ret=ret)
298
299     def command_test(self):
300         "Test streaming input/output configuration"
301         ret = _comedilib_h.comedi_command_test(
302             self._device(), self.cmd.get_comedi_cmd_pointer())
303         return self._command_test_errors[ret]
304
305     def poll(self):
306         """Force updating of streaming buffer
307
308         If supported by the driver, all available samples are copied
309         to the streaming buffer. These samples may be pending in DMA
310         buffers or device FIFOs. If successful, the number of
311         additional bytes available is returned.
312         """
313         ret = _comedilib_h.comedi_poll(self._device(), self.index)
314         if ret < 0:
315             _error.raise_error(function_name='comedi_poll', ret=ret)
316         return ret
317
318     def get_buffer_size(self):
319         "Streaming buffer size of subdevice"
320         ret = _comedilib_h.comedi_get_buffer_size(
321             self._device(), self.index)
322         if ret < 0:
323             _error.raise_error(function_name='comedi_get_buffer_size', ret=ret)
324         return ret
325
326     def set_buffer_size(self, size):
327         """Change the size of the streaming buffer
328
329         Returns the new buffer size in bytes.
330
331         The buffer size will be set to size bytes, rounded up to a
332         multiple of the virtual memory page size. The virtual memory
333         page size can be determined using `sysconf(_SC_PAGE_SIZE)`.
334
335         This function does not require special privileges. However, it
336         is limited to a (adjustable) maximum buffer size, which can be
337         changed by a priveliged user calling
338         `.comedi_set_max_buffer_size`, or running the program
339         `comedi_config`.
340         """
341         ret = _comedilib_h.comedi_set_buffer_size(
342             self._device(), self.index, int(size))
343         if ret < 0:
344             _error.raise_error(function_name='comedi_set_buffer_size', ret=ret)
345         return ret
346
347     def get_max_buffer_size(self):
348         "Maximum streaming buffer size of subdevice"
349         ret = _comedilib_h.comedi_get_max_buffer_size(
350             self._device(), self.index)
351         if ret < 0:
352             _error.raise_error(function_name='comedi_get_max_buffer_size',
353                                ret=ret)
354         return ret
355
356     def set_max_buffer_size(self, max_size):
357         """Set the maximum streaming buffer size of subdevice
358
359         Returns the old (max?) buffer size on success.
360         """
361         ret = _comedilib_h.comedi_set_max_buffer_size(
362             self._device(), self.index, int(max_size))
363         if ret < 0:
364             _error.raise_error(function_name='comedi_set_max_buffer_size',
365                                ret=ret)
366         return ret
367
368     def get_buffer_contents(self):
369         "Number of bytes available on an in-progress command"
370         ret = _comedilib_h.comedi_get_buffer_contents(
371             self._device(), self.index)
372         if ret < 0:
373             _error.raise_error(function_name='comedi_get_buffer_contents',
374                                ret=ret)
375         return ret
376
377     def mark_buffer_read(self, num_bytes):
378         """Next `num_bytes` bytes in the buffer are no longer needed
379
380         Returns the number of bytes successfully marked as read.
381
382         This method should only be used if you are using a `mmap()` to
383         read data from Comedi's buffer (as opposed to calling `read()`
384         on the device file), since Comedi will automatically keep
385         track of how many bytes have been transferred via `read()`
386         calls.
387         """
388         ret = _comedilib_h.comedi_mark_buffer_read(
389             self._device(), self.index, num_bytes)
390         if ret < 0:
391             _error.raise_error(function_name='comedi_mark_buffer_read',
392                                ret=ret)
393         return ret
394
395     def mark_buffer_written(self, num_bytes):
396         """Next `num_bytes` bytes in the buffer are no longer needed
397
398         Returns the number of bytes successfully marked as written.
399
400         This method should only be used if you are using a `mmap()` to
401         read data from Comedi's buffer (as opposed to calling
402         `write()` on the device file), since Comedi will automatically
403         keep track of how many bytes have been transferred via
404         `write()` calls.
405         """
406         ret = _comedilib_h.comedi_mark_buffer_written(
407             self._device(), self.index, num_bytes)
408         if ret < 0:
409             _error.raise_error(function_name='comedi_mark_buffer_written',
410                                ret=ret)
411         return ret
412
413     def get_buffer_offset(self):
414         """Offset in bytes of the read(/write?) pointer in the streaming buffer
415
416         This offset is only useful for memory mapped buffers.
417         """
418         ret = _comedilib_h.comedi_get_buffer_offset(
419             self._device(), self.index)
420         if ret < 0:
421             _error.raise_error(function_name='comedi_get_buffer_offset', ret=ret)
422         return ret