Bumped version to 0.2
[pycomedi.git] / pycomedi / common.py
1 # Some Comedi operations common to analog and digital IO
2 # Copyright (C) 2007,2008  W. Trevor King
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 """Some Comedi operations common to analog and digital IO"""
17
18 import comedi as c
19 import time
20
21 VERSION = "0.2"
22
23 class pycomediError (Exception) :
24     "Error in pycomedi.common"
25     pass
26
27 def _expand_tuple(tup, length) : 
28     "Expand an iterable TUP to a tuple of LENGTH by repeating the last element"
29     if len(tup) > length :
30         raise simAioError, "Tuple too long."
31     elif len(tup) < length :
32         temp_tup = tup + tuple((tup[-1],)*(length-len(tup)))
33         tup = temp_tup
34     return tup
35
36 class SubdeviceFlags (object) :
37     """
38     Descriptions from http://www.comedi.org/doc/r5153.html
39     (the comedi_get_subdevice_flags documentation)
40     
41     SDF_BUSY:
42     The subdevice is busy performing an asynchronous command. A
43     subdevice being "busy" is slightly different from the "running"
44     state flagged by SDF_RUNNING. A "running" subdevice is always
45     "busy", but a "busy" subdevice is not necessarily "running". For
46     example, suppose an analog input command has been completed by the
47     hardware, but there are still samples in Comedi's buffer waiting
48     to be read out. In this case, the subdevice is not "running", but
49     is still "busy" until all the samples are read out or
50     comedi_cancel() is called.
51     
52     SDF_BUSY_OWNER:
53     The subdevice is "Busy", and the command it is running was started
54     by the current process.
55     
56     SDF_LOCKED:
57     The subdevice has been locked by comedi_lock().
58     
59     SDF_LOCK_OWNER:
60     The subdevice is locked, and was locked by the current process.
61     
62     SDF_MAXDATA:
63     The maximum data value for the subdevice depends on the channel.
64     
65     SDF_FLAGS:
66     The subdevice flags depend on the channel (unfinished/broken
67     support in library).
68     
69     SDF_RANGETYPE:
70     The range type depends on the channel.
71     
72     SDF_CMD:
73     The subdevice supports asynchronous commands.
74     
75     SDF_SOFT_CALIBRATED:
76     The subdevice relies on the host to do calibration in software.
77     Software calibration coefficients are determined by the
78     comedi_soft_calibrate utility. See the description of the
79     comedi_get_softcal_converter() function for more information.
80     
81     SDF_READABLE:
82     The subdevice can be read (e.g. analog input).
83     
84     SDF_WRITABLE:
85     The subdevice can be written to (e.g. analog output).
86     
87     SDF_INTERNAL:
88     The subdevice does not have externally visible lines.
89     
90     SDF_GROUND:
91     The subdevice supports AREF_GROUND.
92     
93     SDF_COMMON:
94     The subdevice supports AREF_COMMON.
95     
96     SDF_DIFF:
97     The subdevice supports AREF_DIFF.
98     
99     SDF_OTHER:
100     The subdevice supports AREF_OTHER
101     
102     SDF_DITHER:
103     The subdevice supports dithering (via the CR_ALT_FILTER chanspec
104     flag).
105
106     SDF_DEGLITCH:
107     The subdevice supports deglitching (via the CR_ALT_FILTER chanspec
108     flag).
109     
110     SDF_RUNNING:
111     An asynchronous command is running. You can use this flag to poll
112     for the completion of an output command.
113     
114     SDF_LSAMPL:
115     The subdevice uses the 32 bit lsampl_t type instead of the 16 bit
116     sampl_t for asynchronous command data.
117     
118     SDF_PACKED:
119     The subdevice uses bitfield samples for asynchronous command data,
120     one bit per channel (otherwise it uses one sampl_t or lsampl_t per
121     channel).  Commonly used for digital subdevices.
122     """
123     def __init__(self, comedi) :
124         self.lastUpdate = None
125         self._flags = None
126         self._comedi = comedi
127     def update(self, dev, subdev) :
128         "Must be called on an open device"
129         self._flags = self._comedi.comedi_get_subdevice_flags(dev, subdev)
130         self.lastupdate = time.time()
131     def _check(self, sdf_flag) :
132         assert self._flags != None, "Cannot return status of uninitialized flag"
133         if self._flags & sdf_flag :
134             return True
135         return False        
136     def busy(self) :
137         return(self._check(self._comedi.SDF_BUSY))
138     def busyOwner(self) :
139         return(self._check(self._comedi.SDF_BUSY_OWNER))
140     def locked(self) :
141         return(self._check(self._comedi.SDF_LOCKED))
142     def lockOwner(self) :
143         return(self._check(self._comedi.SDF_LOCK_OWNER))
144     def maxdata(self) :
145         return(self._check(self._comedi.SDF_MAXDATA))
146     def flags(self) :
147         return(self._check(self._comedi.SDF_FLAGS))
148     def rangetype(self) :
149         return(self._check(self._comedi.SDF_RANGETYPE))
150     def cmd(self) :
151         return(self._check(self._comedi.SDF_CMD))
152     def softCalibrated(self) :
153         return(self._check(self._comedi.SDF_SOFT_CALIBRATED))
154     def readable(self) :
155         return(self._check(self._comedi.SDF_READABLE))
156     def writable(self) :
157         return(self._check(self._comedi.SDF_WRITABLE))
158     def internal(self) :
159         return(self._check(self._comedi.SDF_INTERNAL))
160     def ground(self) :
161         return(self._check(self._comedi.SDF_GROUND))
162     def common(self) :
163         return(self._check(self._comedi.SDF_COMMON))
164     def diff(self) :
165         return(self._check(self._comedi.SDF_DIFF))
166     def other(self) :
167         return(self._check(self._comedi.SDF_OTHER))
168     def dither(self) :
169         return(self._check(self._comedi.SDF_DITHER))
170     def deglitch(self) :
171         return(self._check(self._comedi.SDF_DEGLITCH))
172     def running(self) :
173         return(self._check(self._comedi.SDF_RUNNING))
174     def lsampl(self) :
175         return(self._check(self._comedi.SDF_LSAMPL))
176     def packed(self) :
177         return(self._check(self._comedi.SDF_PACKED))
178     def __str__(self) :
179         s = ""
180         s += "busy           : %s\n" % self.busy()
181         s += "busyOwner      : %s\n" % self.busyOwner()
182         s += "locked         : %s\n" % self.locked()
183         s += "lockedOwner    : %s\n" % self.lockOwner()
184         s += "maxdata        : %s\n" % self.maxdata()
185         s += "flags          : %s\n" % self.flags()
186         s += "rangetype      : %s\n" % self.rangetype()
187         s += "cmd            : %s\n" % self.cmd()
188         s += "softCalibrated : %s\n" % self.softCalibrated()
189         s += "readable       : %s\n" % self.readable()
190         s += "writable       : %s\n" % self.writable()
191         s += "internal       : %s\n" % self.internal()
192         s += "ground         : %s\n" % self.ground()
193         s += "common         : %s\n" % self.common()
194         s += "diff           : %s\n" % self.diff()
195         s += "other          : %s\n" % self.other()
196         s += "dither         : %s\n" % self.dither()
197         s += "deglitch       : %s\n" % self.deglitch()
198         s += "running        : %s\n" % self.running()
199         s += "lsampl         : %s\n" % self.lsampl()
200         s += "packed         : %s" % self.packed()
201         return s
202
203 class PyComediIO (object) :
204     "Base class for Comedi IO operations"
205     def __init__(self, filename="/dev/comedi0", subdevice=-1, devtype=c.COMEDI_SUBD_AI, chan=(0,1,2,3), aref=(c.AREF_GROUND,), range=(0,), output=False, dev=None) :
206         """inputs:
207           filename:  comedi device file for your device ["/dev/comedi0"].
208           subdevice: the IO subdevice [-1 for autodetect]
209           devtype: the device type [c.COMEDI_SUBD_AI]
210             values include
211               comedi.COMEDI_SUBD_DI
212               comedi.COMEDI_SUBD_DO
213               comedi.COMEDI_SUBD_DIO
214               comedi.COMEDI_SUBD_AI
215               comedi.COMEDI_SUBD_AO
216           chan: an iterable of the channels you wish to control [(0,1,2,3)]
217           aref: the analog references for these channels [(comedi.AREF_GROUND,)]
218             values include
219               comedi.AREF_GROUND
220               comedi.AREF_COMMON
221               comedi.AREF_DIFF
222               comedi.AREF_OTHER
223           range: the ranges for these channels [(0,)]
224           output: whether to use the lines as output (vs input) (False)
225           dev: if you've already opened the device file, pass in the
226                open device (None for internal open)
227         Note that neither aref nor range need to be the same length as
228         the channel tuple.  If they are shorter, their last entry will
229         be repeated as needed.  If they are longer, pycomediError will
230         be raised (was: their extra entries will not be used).
231         """
232         self.verbose = False
233         self._comedi = c # keep a local copy around
234         # sometimes I got errors on exitting python, which looked like
235         # the imported comedi package was unset before my IO had a
236         # chance to call comedi_close().  We avoid that by keeping a
237         # local reference here.
238         self.filename = filename
239         self.state = "Closed"
240         if dev == None :
241             self.open()
242         else :
243             self.fakeOpen(dev)
244         self._setup_device_type(subdevice, devtype)
245         self._setup_channels(chan, aref, range)
246         self.output = output
247     def __del__(self) :
248         self.close()
249     def open(self) :
250         if self.state == "Closed" :
251             dev = self._comedi.comedi_open(self.filename)
252             if dev < 0 :
253                 self._comedi.comedi_perror("comedi_open")
254                 raise pycomediError, "Cannot open %s" % self.filename
255             self.fakeOpen(dev)
256     def fakeOpen(self, dev):
257         """fake open: if you open the comedi device file yourself, use this
258         method to pass in the opened file descriptor and declare the
259         port "Open".
260         """
261         if dev < 0 :
262             raise pycomediError, "Invalid file descriptor %d" % dev
263         self.dev = dev
264         self.state = "Open"
265     def close(self) :
266         if self.state != "Closed" :
267             rc = self._comedi.comedi_close(self.dev)
268             if rc < 0 :
269                 self._comedi.comedi_perror("comedi_close")
270                 raise pycomediError, "Cannot close %s" % self.filename
271             self.fakeClose()
272     def fakeClose(self):
273         """fake close: if you want to close the comedi device file yourself,
274         use this method to let the port know it has been "Closed".
275         """
276         self.dev = None
277         self.state = "Closed"
278     def _setup_device_type(self, subdevice, devtype) :
279         """check that the specified subdevice exists,
280         searching for an appropriate subdevice if subdevice == -1
281         inputs:
282           subdevice: the analog output subdevice (-1 for autodetect)
283           devtype: the devoce type
284             values include
285               comedi.COMEDI_SUBD_DI
286               comedi.COMEDI_SUBD_DO
287               comedi.COMEDI_SUBD_DIO
288               comedi.COMEDI_SUBD_AI
289               comedi.COMEDI_SUBD_AO
290         """
291         self._devtype = devtype
292         if (subdevice < 0) : # autodetect an output device
293             self.subdev = self._comedi.comedi_find_subdevice_by_type(self.dev, self._devtype, 0) # 0 is starting subdevice
294             if self.subdev < 0 :
295                 self._comedi.comedi_perror("comedi_find_subdevice_by_type")
296                 raise pycomediError, "Could not find a %d device" % (self._devtype)
297         else :
298             self.subdev = subdevice
299             type = self._comedi.comedi_get_subdevice_type(self.dev, self.subdev)
300             if type != self._devtype :
301                 if type < 0 :
302                     self._comedi.comedi_perror("comedi_get_subdevice_type")
303                 raise pycomediError, "Comedi subdevice %d has wrong type %d" % (self.subdev, type)
304         self.flags = SubdeviceFlags(self._comedi)
305         self.updateFlags()
306     def updateFlags(self) :
307         self.flags.update(self.dev, self.subdev)
308     def _setup_channels(self, chan, aref, rng) :
309         """check that the specified channels exists, and that the arefs and
310         ranges are legal for those channels.  Also allocate a range
311         item for each channel, to allow converting between physical
312         units and comedi units even when the device is not open.
313         inputs:
314           chan: an iterable of the channels you wish to control [(0,1,2,3)]
315           aref: the analog references for these channels [(comedi.AREF_GROUND,)]
316             values include
317               comedi.AREF_GROUND
318               comedi.AREF_COMMON
319               comedi.AREF_DIFF
320               comedi.AREF_OTHER
321           rng: the ranges for these channels [(0,)]
322         Note that neither aref nor rng need to be the same length as
323         the channel tuple.  If they are shorter, their last entry will
324         be repeated as needed.  If they are longer, pycomediError will
325         be raised (was: their extra entries will not be used).
326         """
327         self.chan = chan
328         self.nchan = len(self.chan)
329         self._aref = _expand_tuple(aref, self.nchan)
330         self._range = _expand_tuple(rng, self.nchan)
331         self.maxdata = []
332         self._comedi_range = []
333         subdev_n_chan = self._comedi.comedi_get_n_channels(self.dev, self.subdev)
334         self.cr_chan = self._comedi.chanlist(self.nchan)
335         for i,chan,aref,rng in zip(range(self.nchan), self.chan, self._aref, self._range) :
336             if int(chan) != chan :
337                 raise pycomediError, "Channels must be integers, not %s" % str(chan)
338             if chan >= subdev_n_chan :
339                 raise pycomediError, "Channel %d > subdevice %d's largest chan %d" % (chan, self.subdev, subdev_n_chan-1)
340             n_range = self._comedi.comedi_get_n_ranges(self.dev, self.subdev, chan)
341             if rng > n_range :
342                 raise pycomediError, "Range %d > subdevice %d, chan %d's largest range %d" % (rng, subdev, chan, n_range-1)
343             maxdata = self._comedi.comedi_get_maxdata(self.dev, self.subdev, chan)
344             self.maxdata.append(maxdata)
345             comrange = self._comedi.comedi_get_range(self.dev, self.subdev, chan, rng)
346             # comrange becomes invalid if device is closed, so make a copy...
347             comrange_copy = self._comedi.comedi_range()
348             comrange_copy.min = comrange.min
349             comrange_copy.max = comrange.max
350             comrange_copy.unit = comrange.unit
351             self._comedi_range.append(comrange_copy)
352             self.cr_chan[i] = self._comedi.cr_pack(chan, rng, aref)
353     def comedi_to_phys(self, chan_index, comedi, useNAN=True) :
354         if useNAN == True :
355             oor = self._comedi.COMEDI_OOR_NAN
356         else :
357             oor = self._comedi.COMEDI_OOR_NUMBER
358         self._comedi.comedi_set_global_oor_behavior(oor)
359         phys = self._comedi.comedi_to_phys(int(comedi), self._comedi_range[chan_index], self.maxdata[chan_index])
360         if self.verbose :
361             print "comedi %d = %g Volts on subdev %d, chan %d, range [%g, %g], max %d" % (comedi, phys, self.subdev, self.chan[chan_index], self._comedi_range[chan_index].max, self._comedi_range[chan_index].min, self.maxdata[chan_index])
362         return phys
363     def phys_to_comedi(self, chan_index, phys) :
364         comedi = self._comedi.comedi_from_phys(phys, self._comedi_range[chan_index], self.maxdata[chan_index])
365         if self.verbose : 
366             print "%g Volts = comedi %d on subdev %d, chan %d, range [%g, %g], max %d" % (phys, comedi, self.subdev, self.chan[chan_index], self._comedi_range[chan_index].max, self._comedi_range[chan_index].min, self.maxdata[chan_index])
367         return comedi
368
369 class PyComediSingleIO (PyComediIO) :
370     "Software timed single-point input/ouput"
371     def __init__(self, **kwargs) :
372         """inputs:
373           filename:  comedi device file for your device ["/dev/comedi0"].
374           subdevice: the analog output subdevice [-1 for autodetect]
375           devtype: the device type [c.COMEDI_SUBD_AI]
376             values include
377               comedi.COMEDI_SUBD_DI
378               comedi.COMEDI_SUBD_DO
379               comedi.COMEDI_SUBD_DIO
380               comedi.COMEDI_SUBD_AI
381               comedi.COMEDI_SUBD_AO
382           chan: an iterable of the channels you wish to control [(0,1,2,3)]
383           aref: the analog references for these channels [(comedi.AREF_GROUND,)]
384             values include
385               comedi.AREF_GROUND
386               comedi.AREF_COMMON
387               comedi.AREF_DIFF
388               comedi.AREF_OTHER
389           range: the ranges for these channels [(0,)]
390           output: whether to use the lines as output (vs input) (False)
391           dev: if you've already opened the device file, pass in the
392                open device (None for internal open)
393         Note that neither aref nor range need to be the same length as
394         the channel tuple.  If they are shorter, their last entry will
395         be repeated as needed.  If they are longer, pycomediError will
396         be raised (was: their extra entries will not be used).
397         """
398         PyComediIO.__init__(self, **kwargs)
399     def write_chan_index(self, chan_index, data) :
400         """inputs:
401           chan_index: the channel you wish to write to
402           data: the value you wish to write to that channel
403         """
404         if self.output != True :
405             raise pycomediError, "Must be an output to write"
406         rc = c.comedi_data_write(self.dev, self.subdev, self.chan[chan_index],
407                                  self._range[chan_index],
408                                  self._aref[chan_index], int(data));
409         if rc != 1 : # the number of samples written
410             self._comedi.comedi_perror("comedi_data_write")
411             raise pycomediError, "comedi_data_write returned %d" % rc
412     def read_chan_index(self, chan_index) :
413         """inputs:
414           chan_index: the channel you wish to read from
415         outputs:
416           data: the value read from that channel
417         """
418         if self.output != False :
419             raise pycomediError, "Must be an input to read"
420         rc, data = c.comedi_data_read(self.dev, self.subdev, self.chan[chan_index], self._range[chan_index], self._aref[chan_index]);
421         if rc != 1 : # the number of samples read
422             self._comedi.comedi_perror("comedi_data_read")
423             raise pycomediError, "comedi_data_read returned %d" % rc
424         return data
425