# Carlos Medrano ctmedra@unizar.es
# GPL v3
# https://www.gnu.org/licenses/gpl-3.0.html
# It works with linux

import threading
import time
import tkinter as tk

directions = ['I', 'O']
directionsInt = {'I':0, 'O':1}

FIO7 = 7
FIO6 = 6
FIO5 = 5
FIO4 = 4
FIO3 = 3
FIO2 = 2
FIO1 = 1
FIO0 = 0
FIOList = [FIO0, FIO1, FIO2, FIO3, FIO4, FIO5, FIO6, FIO7]

#CARD = Card()
LOCK = threading.Lock()
DESTROY = False

def BitDirWrite(fioNum, direction):
    cmdList = ['BitDirWrite', fioNum, direction]
    #pin.setDirection(direction)
    return cmdList
##
def BitDirRead(fioNum):
    cmdList = ['BitDirRead', fioNum]
    #print(cmdList)
    return cmdList
##
def DAC0_16(val):
    if(type(val) != int or val<0 or val > (2**16)-1):
        print('El valor para DAC debe ser entero entre 0 y 65535')
    cmdList = ['DAC0_16', val]
    return cmdList
##
def DAC1_16(val):
    if(type(val) != int or val<0 or val > (2**16)-1):
        print('El valor para DAC debe ser entero entre 0 y 65535')
    cmdList = ['DAC1_16', val]
    return cmdList        


class Pin:
    def __init__(self, direction, ad = 'D', nbits=1, vmin=0.0, vmax=1.0):
        self.direction = direction
        self.ad = ad
        self.nbits = nbits
        self.vmin = vmin
        self.vmax = vmax
        self.value = 1.0
    def setDirection(self, direction):
        self.direction = direction
    def getDirection(self):
        return self.direction
    def setAD(self, ad):
        self.ad = ad
    def getAD(self):
        return self.ad
    def setNbits(self,nbits):
        self.nbits = nbits
    def getNbits(self):
        return self.nbits
    def setV(self,vmin, vmax):
        self.vmin = float(vmin)
        self.vmax = float(vmax)
    def getV(self):
        return (self.vmin,self.vmax)
    def getValue(self):
        return self.value
    def setValue(self, value):
        """
        if(type(value)!=int or value<0):
            raise ValueError
        if(value > 2**(self.nbits-1)):
            raise ValueError
        """
        self.value = value
        return

class Card:
    def __init__(self):
        self.FIO7 = Pin('I')
        self.FIO6 = Pin('I')
        self.FIO5 = Pin('I')
        self.FIO4 = Pin('I')
        self.FIO3 = Pin('I', 'A', 12, 0, 2.4)
        self.FIO2 = Pin('I', 'A', 12, 0, 2.4)
        self.FIO1 = Pin('I', 'A', 12, 0, 2.4)
        self.FIO0 = Pin('I', 'A', 12, 0, 2.4)
        self.DAC0 = Pin('O', 'A', 10, 0.0, 5.0)
        self.DAC1 = Pin('O', 'A', 10, 0.0, 5.0)
##
    def getPin(self, fioNum):
        if(type(fioNum)!=int):
            print('El valor del pin debe ser entero')
            raise ValueError
        if(fioNum <FIO0 or fioNum> FIO7):
            print('El valor del pin debe estar comprendido entre FI0 y FI7')
            raise ValueError
        if(fioNum == 7): return self.FIO7
        if(fioNum == 6): return self.FIO6
        if(fioNum == 5): return self.FIO5
        if(fioNum == 4): return self.FIO4
        if(fioNum == 3): return self.FIO3
        if(fioNum == 2): return self.FIO2
        if(fioNum == 1): return self.FIO1
        if(fioNum == 0): return self.FIO0
##
class App():
    def __init__(self, card):
        self.root = tk.Tk()
        self.card = card
        row = 0
        self.clk = tk.Label(text="")
        self.clk.grid(row = row, column = 1)
        row = row+1
        self.fio7do = tk.Label(text="0")
        self.fio7do.grid(row=row, column=0)
        self.fio7diVal = tk.IntVar()
        self.fio7di = tk.Checkbutton(text='FIO7', variable = self.fio7diVal)
        self.fio7di.grid(row = row, column = 1)
        self.fio7ai = tk.Scale(from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio7ai.grid(row=row, column = 2)
        ##
        row = row+1
        self.fio6do = tk.Label(text="0")
        self.fio6do.grid(row=row, column=0)
        self.fio6diVal = tk.IntVar()
        self.fio6di = tk.Checkbutton(text='FIO6', variable = self.fio6diVal)
        self.fio6di.grid(row = row, column = 1)
        self.fio6ai = tk.Scale(from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio6ai.grid(row=row, column = 2)
        ##
        row = row+1
        self.fio5do = tk.Label(text="0")
        self.fio5do.grid(row=row, column=0)
        self.fio5diVal = tk.IntVar()
        self.fio5di = tk.Checkbutton(text='FIO5', variable = self.fio5diVal)
        self.fio5di.grid(row = row, column = 1)
        self.fio5ai = tk.Scale(from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio5ai.grid(row=row, column = 2)
        ##
        row = row+1
        self.fio4do = tk.Label(text="0")
        self.fio4do.grid(row=row, column=0)
        self.fio4diVal = tk.IntVar()
        self.fio4di = tk.Checkbutton(text='FIO4', variable = self.fio4diVal)
        self.fio4di.grid(row = row, column = 1)
        self.fio4ai = tk.Scale(from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio4ai.grid(row=row, column = 2)
        ## 4 bajos analogicos
        row = row +1
        self.fio3ai =  tk.Scale(label = 'FIO3', from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio3ai.grid(row=row, column = 0)
        row = row +1
        self.fio2ai =  tk.Scale(label = 'FIO2', from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio2ai.grid(row=row, column = 0)
        row = row +1
        self.fio1ai =  tk.Scale(label = 'FIO1', from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio1ai.grid(row=row, column = 0)
        row = row +1
        self.fio0ai =  tk.Scale(label = 'FIO0', from_ = -10.0, to = 10.0, resolution = 0.01, orient = tk.HORIZONTAL)
        self.fio0ai.grid(row=row, column = 0)
        # Ahora las salida analogicas
        row = row -1
        self.dac1 =  tk.Scale(label = 'DAC1', from_ = 0.0, to = 5.0, resolution = 0.001, orient = tk.HORIZONTAL, state = tk.DISABLED)
        self.dac1.grid(row=row, column = 2)
        row = row + 1
        self.dac0 =  tk.Scale(label = 'DAC0', from_ = 0.0, to = 5.0, resolution = 0.001, orient = tk.HORIZONTAL, state = tk.DISABLED)
        self.dac0.grid(row=row, column = 2)
        self.update_clock()
        self.root.mainloop()
    ##
    def updatePin(self, pin, fiodo, fiodi, fioai):
        if(pin.getAD() == 'A'):
            #print('A')
            fiodo.config(text="", state = tk.DISABLED)
            fiodi.config(state = tk.DISABLED)
            fioai.config(state = tk.NORMAL)
        else:
            fioai.config(state = tk.DISABLED)
            if(pin.getDirection()=='I'):
                fiodo.config(text="", state = tk.DISABLED)
                fiodi.config(state = tk.NORMAL)
            else:
                fiodo.config(text=str(int(pin.getValue())), state = tk.NORMAL)
                fiodi.config(state = tk.DISABLED)
            #print('D')        
    ##
    def update_clock(self):
        try:
            # Actualizamos la interfaz
            LOCK.acquire()
            now = time.strftime("%H:%M:%S")
            self.clk.configure(text=now)
            obj = self.card
            pin = obj.getPin(FIO7)
            self.updatePin(pin, self.fio7do, self.fio7di, self.fio7ai)
            pin = obj.getPin(FIO6)
            self.updatePin(pin, self.fio6do, self.fio6di, self.fio6ai)
            pin = obj.getPin(FIO5)
            self.updatePin(pin, self.fio5do, self.fio5di, self.fio5ai)
            pin = obj.getPin(FIO4)
            self.updatePin(pin, self.fio4do, self.fio4di, self.fio4ai)
            ##
            # Leemos los FIO y actualizamos el objeto
            for fioNum in FIOList:
                pin = obj.getPin(fioNum)
                if(fioNum in [FIO4, FIO5, FIO6, FIO7]):
                    if(pin.getDirection() == 'I' and pin.getAD() == 'D'):
                        if(fioNum==FIO7):
                            #print(self.fio7diVal.get())
                            pin.setValue(self.fio7diVal.get())
                            #print obj.FIO7.getValue()
                        elif(fioNum == FIO6):
                            pin.setValue(self.fio6diVal.get())
                        elif(fioNum == FIO5):
                            pin.setValue(self.fio5diVal.get())
                        elif(fioNum == FIO4):
                            pin.setValue(self.fio4diVal.get())
                        else:
                            pass
                    if(pin.getDirection() == 'I' and pin.getAD() == 'A'):
                        if(fioNum==FIO7):
                            #print(self.fio7ai.get())
                            pin.setValue(self.fio7ai.get())
                        elif(fioNum == FIO6):
                            pin.setValue(self.fio6ai.get())
                        elif(fioNum == FIO5):
                            pin.setValue(self.fio5ai.get())
                        elif(fioNum == FIO4):
                            pin.setValue(self.fio4ai.get())
                        else:
                            pass
                else:
                    if(fioNum == FIO3):
                        pin.setValue(self.fio3ai.get())
                    elif(fioNum == FIO2):
                        pin.setValue(self.fio2ai.get())
                    elif(fioNum == FIO1):
                        pin.setValue(self.fio1ai.get())
                    elif(fioNum == FIO0):
                        pin.setValue(self.fio0ai.get())
            pin = obj.DAC1
            val = pin.getValue() # Solo 10 bits ya en la tarjeta
            val = 5.0*val/(2**pin.getNbits()-1.0)
            self.dac1.config(state = tk.NORMAL)
            self.dac1.set(val)
            self.dac1.config(state = tk.DISABLED)
            pin = obj.DAC0
            val = pin.getValue() # Solo 10 bits ya en la tarjeta
            val = 5.0*val/(2**pin.getNbits()-1.0)
            self.dac0.config(state = tk.NORMAL)
            self.dac0.set(val)
            self.dac0.config(state = tk.DISABLED)
            global DESTROY
            if(not DESTROY):
                self.root.after(1000, self.update_clock)
            else:
                DESTROY = False
                self.root.destroy()
        finally:
            LOCK.release()
##
def startUI(card):
    app = App(card)
    print('adios')
    pass
##
class AlreadyOpenDevice(Exception):
    pass
##
class U3:
    count = 0
    # Use Locks in this class but not in Card class
    def __init__(self):
        if(U3.count!=0):
            print('Ya estaba abierta. Cierra la tarjeta antes')
            raise AlreadyOpenDevice
        self.daq = Card()
        self.ui_thread = threading.Thread(target = startUI, args = (self.daq,))
        self.ui_thread.start()
        while(not self.ui_thread.is_alive()):
            pass
        U3.count += 1
    ##
    def close(self):
        global DESTROY
        DESTROY = True
        self.ui_thread.join()
        U3.count -= 1
    ##
    def configIO(self, FIOAnalog=None):
        if(FIOAnalog is None):
            # no hace falta usar el lock porque desde la UI nunca podremos
            # cambiar la configuracion
            ret = 0x0F
            if(self.daq.FIO7.getAD()=='A'): ret = ret | 0x80
            if(self.daq.FIO6.getAD()=='A'): ret = ret | 0x40
            if(self.daq.FIO5.getAD()=='A'): ret = ret | 0x20
            if(self.daq.FIO4.getAD()=='A'): ret = ret | 0x10
            return hex(ret)
        if(type(FIOAnalog)!=int or FIOAnalog<0 or FIOAnalog > 0xFF):
            print('FIOANalog no es entero o no tiene el valor adecuado')
            raise ValueError
        vals = [0x80, 0x40, 0x20, 0x10]
        pins = [self.daq.FIO7, self.daq.FIO6, self.daq.FIO5, self.daq.FIO4]
        try:
            # El lock es necesario porque la UI usa Direction por ejemplo
            # o el tipo de pin
            LOCK.acquire()
            for hval, pin in zip(vals, pins):
                if(hval & FIOAnalog >0):
                    pin.setDirection('I')
                    pin.setAD('A')
                    pin.setNbits(12)
                    pin.setV(0.0, 2.4)
                    pin.setValue(0.0)
                else:
                    # As digital
                    pin.setAD('D')
                    pin.setNbits(1)
                    pin.setV(0.0, 1.0)
                    pin.setValue(1.0)
        finally:
            LOCK.release()
        return 'FIOAnalog : ' + hex(FIOAnalog)
    ##
    def getFeedback(self, cmdList):
        cmd = cmdList[0]
        if(cmd == 'BitDirRead'):
            p = cmdList[1]
            #print(p)
            pin = self.daq.getPin(p)
            if(pin.getAD()!='D'):
                print('La operacion  BitDirRead debe ser sobre un pin digital')
                raise ValueError
            return directionsInt[pin.getDirection()]
        if(cmd == 'BitDirWrite'):
            p = cmdList[1]
            direction = cmdList[2]
            pin = self.daq.getPin(p)
            if(pin.getAD()!='D'):
                print('La operacion  BitDirWrite debe ser sobre un pin digital')
                raise ValueError
            if(direction not in [0,1]):
                print('La direccion del pin debe ser 0 o 1')
                raise ValueError
            try:
                # Lock necesario porque UI usa Direction
                LOCK.acquire()
                pin.setDirection(directions[direction])
            finally:
                LOCK.release()
        if(cmd == 'DAC0_16' or cmd == 'DAC1_16'):
            if(cmd == 'DAC0_16'):
                pin = self.daq.DAC0
            else:
                pin = self.daq.DAC1
            val = 0x3FF & (cmdList[1] >> 6) # Solo 10 bits
            try:
                LOCK.acquire()
                pin.setValue(val)
            finally:
                LOCK.release()
    ##
    def setFIOState(self, fioNum, state = 1):
        if(state not in [0,1]):
            print('El valor del estado debe ser 0 o 1')
            raise ValueError
        if(fioNum not in FIOList):
            print('El numero de pin no es correcto')
            raise ValueError
        if(fioNum not in [FIO4,FIO5,FIO6,FIO7]):
            print('La operacion setFIOState no se puede realizar en pines analogicos')
            raise ValueError
        try:
            LOCK.acquire()
            pin = self.daq.getPin(fioNum)
            pin.setDirection('O')
            pin.setAD('D')
            pin.setV(0.0, 1.0)
            pin.setNbits(1)
            pin.setValue(state)
        finally:
            LOCK.release()
    ##
    def getFIOState(self, fioNum):
        try:
            LOCK.acquire()
            pin = self.daq.getPin(fioNum)
            # Que pasa si es analogico ??
            if(pin.getAD()!='D'):
                print('La operacion getFIOState solo se realiza en pines digitales')
                raise ValueError
            val = pin.getValue()
        finally:
            LOCK.release()
        return int(val)
    ##
    def getAIN(self, posChannel, negChannel = 31):
        # OJO mejorar con la resolucion
        if(posChannel not in FIOList):
            print('posChannel tiene que ser un FIO valido')
            raise ValueError
        if(negChannel not in [31, 32, FIO4, FIO5, FIO6, FIO7]):
            print('negChannel tiene que ser 31, 32 o un FIO4 a FIO7')
            raise ValueError
        if(negChannel == 31):
            if(posChannel in [FIO4, FIO5, FIO6, FIO7]):
                try:
                    LOCK.acquire()
                    pin = self.daq.getPin(posChannel)
                    if(pin.getAD() != 'A'):
                        print('El pin tiene que esta configurado como analogico')
                        raise ValueError
                    val = pin.getValue()
                    val = min(val, 2.4)
                    val = max(val, 0.0)
                    pin.setValue(val)
                finally:
                    LOCK.release()
                return val
            elif(posChannel in [FIO0, FIO1, FIO2, FIO3]):
                try:
                    LOCK.acquire()
                    pin = self.daq.getPin(posChannel)
                    val = pin.getValue()
                    val = min(val, 10.3)
                    val = max(val, -10.3)
                    pin.setValue(val)
                finally:
                    LOCK.release()
                return val
        elif(negChannel == 32):
            if(posChannel in [FIO4, FIO5, FIO6, FIO7]):
                try:
                    LOCK.acquire()
                    pin = self.daq.getPin(posChannel)
                    if(pin.getAD() != 'A'):
                        print('El pin tiene que esta configurado como analogico')
                        raise ValueError
                    val = pin.getValue()
                    val = min(val, 3.6)
                    val = max(val, 0.0)
                    pin.setValue(val)
                finally:
                    LOCK.release()
                return val
            elif(posChannel in [FIO0, FIO1, FIO2, FIO3]):
                try:
                    LOCK.acquire()
                    pin = self.daq.getPin(posChannel)  
                    val = pin.getValue()
                    val = min(val, 20.0)
                    val = max(val, -10.0)
                    pin.setValue(val)
                finally:
                    LOCK.release()
                return val
            else:
                pass
        else:
            # negChannel es FIO4-7
            if(posChannel not in [FIO4, FIO5, FIO6, FIO7]):
                print('En la medida diferencial posChannel debe ser FIO4 a FIO7')
                raise ValueError
            try:
                LOCK.acquire()
                pin1 = self.daq.getPin(posChannel)
                pin2 = self.daq.getPin(negChannel)
                if(pin2.getAD() !='A' or pin1.getAD() !='A'):
                    print('Los pines tienen que estar configurados como analogicos')
                    raise ValueError
                val1 = pin1.getValue()
                val2 = pin2.getValue()
                val = val1-val2
                val = min(val, 2.44)
                val = max(val, -2.44)
                pin1.setValue(val1)
                pin2.setValue(val2)
            finally:
                LOCK.release()
            return val
