1 # Some Comedi operations common to analog and digital IO
2 # Copyright (C) 2007,2008 W. Trevor King
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.
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.
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"""
23 class pycomediError (Exception) :
24 "Error in pycomedi.common"
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)))
36 class SubdeviceFlags (object) :
38 Descriptions from http://www.comedi.org/doc/r5153.html
39 (the comedi_get_subdevice_flags documentation)
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.
53 The subdevice is "Busy", and the command it is running was started
54 by the current process.
57 The subdevice has been locked by comedi_lock().
60 The subdevice is locked, and was locked by the current process.
63 The maximum data value for the subdevice depends on the channel.
66 The subdevice flags depend on the channel (unfinished/broken
70 The range type depends on the channel.
73 The subdevice supports asynchronous commands.
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.
82 The subdevice can be read (e.g. analog input).
85 The subdevice can be written to (e.g. analog output).
88 The subdevice does not have externally visible lines.
91 The subdevice supports AREF_GROUND.
94 The subdevice supports AREF_COMMON.
97 The subdevice supports AREF_DIFF.
100 The subdevice supports AREF_OTHER
103 The subdevice supports dithering (via the CR_ALT_FILTER chanspec
107 The subdevice supports deglitching (via the CR_ALT_FILTER chanspec
111 An asynchronous command is running. You can use this flag to poll
112 for the completion of an output command.
115 The subdevice uses the 32 bit lsampl_t type instead of the 16 bit
116 sampl_t for asynchronous command data.
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.
123 def __init__(self, comedi) :
124 self.lastUpdate = 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 :
137 return(self._check(self._comedi.SDF_BUSY))
138 def busyOwner(self) :
139 return(self._check(self._comedi.SDF_BUSY_OWNER))
141 return(self._check(self._comedi.SDF_LOCKED))
142 def lockOwner(self) :
143 return(self._check(self._comedi.SDF_LOCK_OWNER))
145 return(self._check(self._comedi.SDF_MAXDATA))
147 return(self._check(self._comedi.SDF_FLAGS))
148 def rangetype(self) :
149 return(self._check(self._comedi.SDF_RANGETYPE))
151 return(self._check(self._comedi.SDF_CMD))
152 def softCalibrated(self) :
153 return(self._check(self._comedi.SDF_SOFT_CALIBRATED))
155 return(self._check(self._comedi.SDF_READABLE))
157 return(self._check(self._comedi.SDF_WRITABLE))
159 return(self._check(self._comedi.SDF_INTERNAL))
161 return(self._check(self._comedi.SDF_GROUND))
163 return(self._check(self._comedi.SDF_COMMON))
165 return(self._check(self._comedi.SDF_DIFF))
167 return(self._check(self._comedi.SDF_OTHER))
169 return(self._check(self._comedi.SDF_DITHER))
171 return(self._check(self._comedi.SDF_DEGLITCH))
173 return(self._check(self._comedi.SDF_RUNNING))
175 return(self._check(self._comedi.SDF_LSAMPL))
177 return(self._check(self._comedi.SDF_PACKED))
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()
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) :
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]
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,)]
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).
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"
244 self._setup_device_type(subdevice, devtype)
245 self._setup_channels(chan, aref, range)
250 if self.state == "Closed" :
251 dev = self._comedi.comedi_open(self.filename)
253 self._comedi.comedi_perror("comedi_open")
254 raise pycomediError, "Cannot open %s" % self.filename
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
262 raise pycomediError, "Invalid file descriptor %d" % dev
266 if self.state != "Closed" :
267 rc = self._comedi.comedi_close(self.dev)
269 self._comedi.comedi_perror("comedi_close")
270 raise pycomediError, "Cannot close %s" % self.filename
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".
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
282 subdevice: the analog output subdevice (-1 for autodetect)
283 devtype: the devoce type
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
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
295 self._comedi.comedi_perror("comedi_find_subdevice_by_type")
296 raise pycomediError, "Could not find a %d device" % (self._devtype)
298 self.subdev = subdevice
299 type = self._comedi.comedi_get_subdevice_type(self.dev, self.subdev)
300 if type != self._devtype :
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)
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.
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,)]
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).
328 self.nchan = len(self.chan)
329 self._aref = _expand_tuple(aref, self.nchan)
330 self._range = _expand_tuple(rng, self.nchan)
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)
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) :
355 oor = self._comedi.COMEDI_OOR_NAN
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])
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])
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])
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])
369 class PyComediSingleIO (PyComediIO) :
370 "Software timed single-point input/ouput"
371 def __init__(self, **kwargs) :
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]
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,)]
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).
398 PyComediIO.__init__(self, **kwargs)
399 def write_chan_index(self, chan_index, data) :
401 chan_index: the channel you wish to write to
402 data: the value you wish to write to that channel
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) :
414 chan_index: the channel you wish to read from
416 data: the value read from that channel
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