Don't use: from pycomedi cimport module as _module
[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         ret = []
154         for i in range(self.get_n_channels()):
155             #yield self.channel(i, **kwargs)
156             # Generators are not supported in Cython 0.14.1
157             ret.append(self.channel(i, **kwargs))
158         return ret
159
160     def channel(self, index, factory=_channel.Channel, **kwargs):
161         "`Channel` instance for the `index`\ed channel."
162         return factory(subdevice=self, index=index, **kwargs)
163
164     def get_dtype(self):
165         "Return the appropriate `numpy.dtype` based on subdevice flags"
166         return _subdevice_dtype(self)
167
168     def get_typecode(self):
169         "Return the appropriate `array` type based on subdevice flags"
170         return _subdevice_typecode(self)
171
172
173 cdef class StreamingSubdevice (Subdevice):
174     """Streaming I/O subdevice
175
176     >>> from .device import Device
177     >>> from .chanspec import ChanSpec
178     >>> from . import constant
179
180     >>> d = Device('/dev/comedi0')
181     >>> d.open()
182
183     >>> s = d.get_read_subdevice(factory=StreamingSubdevice)
184
185     >>> cmd = s.get_cmd_src_mask()
186     >>> print str(cmd)
187             subdev: 0
188              flags: -
189          start_src: now|ext|int
190          start_arg: 0
191     scan_begin_src: timer|ext
192     scan_begin_arg: 0
193        convert_src: timer|ext
194        convert_arg: 0
195       scan_end_src: count
196       scan_end_arg: 0
197           stop_src: none|count
198           stop_arg: 0
199           chanlist: []
200               data: []
201
202     >>> chanlist_len = 3
203     >>> cmd = s.get_cmd_generic_timed(chanlist_len=chanlist_len,
204     ...     scan_period_ns=1e3)
205     >>> print str(cmd)  # doctest: +NORMALIZE_WHITESPACE
206             subdev: 0
207              flags: -
208          start_src: now
209          start_arg: 0
210     scan_begin_src: timer
211     scan_begin_arg: 9000
212        convert_src: timer
213        convert_arg: 3000
214       scan_end_src: count
215       scan_end_arg: 3
216           stop_src: count
217           stop_arg: 2
218           chanlist: [<ChanSpec chan:0 range:0 aref:ground flags:->,
219                      <ChanSpec chan:0 range:0 aref:ground flags:->,
220                      <ChanSpec chan:0 range:0 aref:ground flags:->]
221               data: []
222
223     >>> cmd.chanlist = [ChanSpec(chan=i, range=0) for i in range(chanlist_len)]
224     >>> s.cmd = cmd
225     >>> s.command_test()
226     >>> s.command()
227     >>> s.cancel()
228
229
230     >>> d.close()
231     """
232     def __cinit__(self):
233         self.cmd = _command.Command()
234         self._command_test_errors = [
235             None,  # valid
236             'unsupported *_src trigger',  # unsupported trigger bits zeroed
237             'unsupported *_src combo, or multiple triggers',
238             '*_arg out of range',  # offending members adjusted to valid values
239             '*_arg required adjustment',  # e.g. trigger timing period rounded
240             'invalid chanlist',
241             # e.g. some boards require same range across channels
242             ]
243
244     def get_cmd_src_mask(self):
245         """Detect streaming input/output capabilities
246
247         The command capabilities of the subdevice indicated by the
248         parameters device and subdevice are probed, and the results
249         placed in the command structure *command.  The trigger source
250         elements of the command structure are set to be the bitwise-or
251         of the subdevice's supported trigger sources.  Other elements
252         in the structure are undefined.
253         """
254         cdef _command.Command cmd
255         cmd = _command.Command()
256         ret = _comedilib_h.comedi_get_cmd_src_mask(
257             self._device(), self.index, cmd.get_comedi_cmd_pointer())
258         if ret < 0:
259             _error.raise_error(function_name='comedi_get_cmd_src_mask', ret=ret)
260         return cmd
261
262     def get_cmd_generic_timed(self, chanlist_len, scan_period_ns=0):
263         """Detect streaming input/output capabilities
264
265         The command capabilities of the subdevice indicated by the
266         parameters device and subdevice are probed, and the results
267         placed in the command structure pointed to by the parameter
268         command.  The command structure *command is modified to be a
269         valid command that can be used as a parameter to
270         comedi_command (after the command has additionally been
271         assigned a valid chanlist array).  The command measures scans
272         consisting of chanlist_len channels at a scan rate that
273         corresponds to a period of scan_period_ns nanoseconds.  The
274         rate is adjusted to a rate that the device can handle.
275
276         Note that the `ChanSpec` instances in `cmd.chanlist` are not
277         initialized to reasonable values.
278         """
279         cdef _command.Command cmd
280         cmd = _command.Command()
281         ret = _comedilib_h.comedi_get_cmd_generic_timed(
282             self._device(), self.index, cmd.get_comedi_cmd_pointer(),
283             chanlist_len, int(scan_period_ns))
284         cmd.chanlist = [0 for i in range(chanlist_len)]
285         if ret < 0:
286             _error.raise_error(function_name='comedi_get_cmd_generic_timed',
287                                ret=ret)
288         return cmd
289
290     def cancel(self):
291         "Stop streaming input/output in progress."
292         ret = _comedilib_h.comedi_cancel(self._device(), self.index)
293         if ret < 0:
294             _error.raise_error(function_name='comedi_cancel', ret=ret)
295
296     def command(self):
297         "Start streaming input/output"
298         ret = _comedilib_h.comedi_command(
299             self._device(), self.cmd.get_comedi_cmd_pointer())
300         if ret < 0:
301             _error.raise_error(function_name='comedi_command', ret=ret)
302
303     def command_test(self):
304         "Test streaming input/output configuration"
305         ret = _comedilib_h.comedi_command_test(
306             self._device(), self.cmd.get_comedi_cmd_pointer())
307         return self._command_test_errors[ret]
308
309     def poll(self):
310         """Force updating of streaming buffer
311
312         If supported by the driver, all available samples are copied
313         to the streaming buffer. These samples may be pending in DMA
314         buffers or device FIFOs. If successful, the number of
315         additional bytes available is returned.
316         """
317         ret = _comedilib_h.comedi_poll(self._device(), self.index)
318         if ret < 0:
319             _error.raise_error(function_name='comedi_poll', ret=ret)
320         return ret
321
322     def get_buffer_size(self):
323         "Streaming buffer size of subdevice"
324         ret = _comedilib_h.comedi_get_buffer_size(
325             self._device(), self.index)
326         if ret < 0:
327             _error.raise_error(function_name='comedi_get_buffer_size', ret=ret)
328         return ret
329
330     def set_buffer_size(self, size):
331         """Change the size of the streaming buffer
332
333         Returns the new buffer size in bytes.
334
335         The buffer size will be set to size bytes, rounded up to a
336         multiple of the virtual memory page size. The virtual memory
337         page size can be determined using `sysconf(_SC_PAGE_SIZE)`.
338
339         This function does not require special privileges. However, it
340         is limited to a (adjustable) maximum buffer size, which can be
341         changed by a priveliged user calling
342         `.comedi_set_max_buffer_size`, or running the program
343         `comedi_config`.
344         """
345         ret = _comedilib_h.comedi_set_buffer_size(
346             self._device(), self.index, int(size))
347         if ret < 0:
348             _error.raise_error(function_name='comedi_set_buffer_size', ret=ret)
349         return ret
350
351     def get_max_buffer_size(self):
352         "Maximum streaming buffer size of subdevice"
353         ret = _comedilib_h.comedi_get_max_buffer_size(
354             self._device(), self.index)
355         if ret < 0:
356             _error.raise_error(function_name='comedi_get_max_buffer_size',
357                                ret=ret)
358         return ret
359
360     def set_max_buffer_size(self, max_size):
361         """Set the maximum streaming buffer size of subdevice
362
363         Returns the old (max?) buffer size on success.
364         """
365         ret = _comedilib_h.comedi_set_max_buffer_size(
366             self._device(), self.index, int(max_size))
367         if ret < 0:
368             _error.raise_error(function_name='comedi_set_max_buffer_size',
369                                ret=ret)
370         return ret
371
372     def get_buffer_contents(self):
373         "Number of bytes available on an in-progress command"
374         ret = _comedilib_h.comedi_get_buffer_contents(
375             self._device(), self.index)
376         if ret < 0:
377             _error.raise_error(function_name='comedi_get_buffer_contents',
378                                ret=ret)
379         return ret
380
381     def mark_buffer_read(self, num_bytes):
382         """Next `num_bytes` bytes in the buffer are no longer needed
383
384         Returns the number of bytes successfully marked as read.
385
386         This method should only be used if you are using a `mmap()` to
387         read data from Comedi's buffer (as opposed to calling `read()`
388         on the device file), since Comedi will automatically keep
389         track of how many bytes have been transferred via `read()`
390         calls.
391         """
392         ret = _comedilib_h.comedi_mark_buffer_read(
393             self._device(), self.index, num_bytes)
394         if ret < 0:
395             _error.raise_error(function_name='comedi_mark_buffer_read',
396                                ret=ret)
397         return ret
398
399     def mark_buffer_written(self, num_bytes):
400         """Next `num_bytes` bytes in the buffer are no longer needed
401
402         Returns the number of bytes successfully marked as written.
403
404         This method should only be used if you are using a `mmap()` to
405         read data from Comedi's buffer (as opposed to calling
406         `write()` on the device file), since Comedi will automatically
407         keep track of how many bytes have been transferred via
408         `write()` calls.
409         """
410         ret = _comedilib_h.comedi_mark_buffer_written(
411             self._device(), self.index, num_bytes)
412         if ret < 0:
413             _error.raise_error(function_name='comedi_mark_buffer_written',
414                                ret=ret)
415         return ret
416
417     def get_buffer_offset(self):
418         """Offset in bytes of the read(/write?) pointer in the streaming buffer
419
420         This offset is only useful for memory mapped buffers.
421         """
422         ret = _comedilib_h.comedi_get_buffer_offset(
423             self._device(), self.index)
424         if ret < 0:
425             _error.raise_error(function_name='comedi_get_buffer_offset', ret=ret)
426         return ret