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