'''
Created on Jun 7, 2012

@author: manuel
'''
import threading
import comedi as c
import os
import struct
import time
import numpy as np

DEVICE = '/dev/comedi0'
SUBDEVICE = 0
NBCHANS = 2
CHANS = [0, 1]
RANGES = [0, 0]
REFS = [c.AREF_GROUND, c.AREF_GROUND]
SAMPLE_FREQ = 1000
BUFFER_SIZE = 1000

DEBUG = True

def deinterleaveData(inData, nChan):
    '''
    takes a linear array with data points interleaved [a1,b1,c1,a2,b2,c2,....aN-1,bN-1,cN-1,aN,bN]
    and return a (nChan,N) numpy.array with the data deinterleaved
    [[a1,a2,a3,...aN-1],
     [b1,b2,b3,...bN-1],
     [c1,c2,c3,...cN-1]]
    
    if the length of the input array was not a multiple of nChan, the remaining points are returned in remainData.
    Otherwise, remainData is an empty array
    '''
    n = len(inData)
    nToKeep = n-(n % nChan)
    outData = np.array(inData[:nToKeep],dtype=int).reshape((-1,nChan)).transpose()
    remainData = inData[nToKeep:]
    return outData,remainData

def toPhysicalUnits(inData, dev, subdev, chans, inRanges):
    '''
    converts the int values obtained from comedi to a physical value
    arguments:
    inData: a (nChan,N) numpy array
    dev: the comedi device used to obtain the data
    subdev: the comedi subdevice used to obtain the data
    chans: a (nChan,1) array containing the channels from which the data was obtained
    inRanges: a (nChan,1) array containing the id of the chanRange used for each channel. the actual chanRange will be obtained
              from comedi using comedi_get_range()
    '''
    nChan,_ = inData.shape
    maxDatas = []
    physicalRanges = []
    for chan,chanRange in zip(chans,inRanges):
        maxDatas.append(c.comedi_get_maxdata(dev,subdev,chan))
        physicalRanges.append(c.comedi_get_range(dev,subdev,chan,chanRange))
    out = np.array(inData,dtype=float)
    for i in range(nChan):
        out[i] = out[i]*((float(physicalRanges[i].max)-float(physicalRanges[i].min))/float(maxDatas[i])) + float(physicalRanges[i].min)
    return out
    
    

class main(object):
    def __init__(self):
        #configure the comedi device
        self.dev = c.comedi_open(str(DEVICE))
        self.subdev = SUBDEVICE
        if not self.dev: raise Exception("Error opening Comedi device: %s" % c.comedi_perror(DEVICE))
        #create channel list
        nbChans = NBCHANS
        myChanList = c.chanlist(nbChans)
        for i in range(nbChans):
            myChanList[i] = c.cr_pack(CHANS[i], RANGES[i], REFS[i])
        #create a command structure
        cmd = c.comedi_cmd_struct()
        ret = c.comedi_get_cmd_generic_timed(self.dev, self.subdev, cmd, nbChans, int(1e9 / SAMPLE_FREQ))
        if ret: raise Exception("Error comedi_get_cmd_generic failed")
        cmd.chanlist = myChanList # adjust for our particular context
        cmd.chanlist_len = nbChans
        cmd.scan_end_arg = nbChans
        cmd.stop_src = c.TRIG_NONE
        
        #test our comedi command a few times.
        ret = 0
        for i in range(2):
            ret = c.comedi_command_test(self.dev, cmd)
            if ret < 0: raise Exception("comedi_command_test failed: %s" % (c.comedi_perror('comedi_command_test')))

        #Start the command
        ret = c.comedi_command(self.dev, cmd)
        if ret <> 0: raise Exception("comedi_command failed... %s" % (c.comedi_perror('comedi_command')))
        
        #start reader thread
        if DEBUG: print "creating reader thread"
        self.helper = readerThread(self.dev, self.subdev, CHANS, RANGES)        
        self.helper.start()
        try:
            while True: 
                time.sleep(1)
                if DEBUG: print "sleeping..."
                if not self.helper.reading:
                    print "helper thread stopped reading, stopping the execution"
                    self.stop()
        except KeyboardInterrupt:
            self.stop()
        
    def stop(self):
        #self.helper.reading = False
        self.helper.join()
        if DEBUG: print "stopping background thread"
        self.helper = None
        c.comedi_cancel(self.dev,self.subdev)

    def __del__(self):
        self.stop()

class readerThread(threading.Thread):
    def __init__(self, dev, subdev, chans, ranges):
        if DEBUG: print "in readerThread __init__"
        threading.Thread.__init__(self)
        
        self.dev = dev
        self.subdev = subdev
        self.chans = chans
        self.ranges = ranges
        
        #get a file-descriptor for reading
        self.fd = c.comedi_fileno(self.dev)
        if self.fd <= 0: raise Exception("Error obtaining Comedi device file descriptor: %s" % (c.comedi_perror('comedi_fileno')))
        self.nbChans = len(chans)
        self.reading = True
        
    def run(self):
        if DEBUG: print "Reader thread running..."
        while (self.reading):
            data=None
            n=0
            try:
                if DEBUG: print "reading..."
                data = os.read(self.fd,BUFFER_SIZE)
                n = len(data)/2 # 2 bytes per 'H'                
                if DEBUG: print "read %d data points"%n
                if DEBUG: print "unpacking..."
                data = struct.unpack('%dH'%n,data)
                if DEBUG: print "de-interleaving..."
                out,data = deinterleaveData(data,self.nbChans)
                if DEBUG: print "returned a (%d,%d) array and kept %d for next round"%(out.shape[0],out.shape[1],len(data))
                if DEBUG: print "transforming into physical units"
                out = toPhysicalUnits(out,self.dev,self.subdev,self.chans,self.ranges)
                if DEBUG: print out
            except IOError:
                raise Exception("Fatal Error: %s"%(c.comedi_perror('read')))
            except KeyboardInterrupt:
                self.reading=False

if __name__ == '__main__':
    main()
