#!/usr/bin/env python
from smbus import SMBus
import time
from struct import *
import array
import numpy as np
import math

# instead of having some varisable associated to the class that we should
# update continuously with the the chip, we only access to the variables
# to the chip via set_/get_ functions

class ADUCv2p1:
    # Initialize the connection to the board
    # addr: i2c addres, can be obtained by running "i2detect -y 1" from terminal
    # if with_about = True, will print information from the board
    def __init__(self,addr,with_about=False):
        self.bus_pi = SMBus(1)
        self._addr = addr;
        self.get_wf_len();
        if with_about==True:
            print( self.get_text())
            print( "----------------\n")

    # Read last valid waveform from the microcontroller, up to 256 points
    def read_data(self):
        dat = []
        if self._wf_len>=256:
            N=16
            self._wf_len = 256
        else:
            N = int(self._wf_len/16+1)
            ## add an error message in case wf_len/16 is not an integer?

        self.set_transf(1)# to remove    
        for i in range(0,N):
            block = self.bus_pi.read_i2c_block_data(self._addr,i,32)
            buff = pack("32B",*block)
            dat = np.append(dat,unpack("<16H",buff))
        self.set_transf(0) #to remove   
        
        dat=np.uint16(dat)
        return dat[0:self._wf_len]

    # 
    def get_text(self):
        dat = []        
        for i in range(0,16):
            block = self.bus_pi.read_i2c_block_data(self._addr,i+50,32)
            buff = pack("32B",*block)
            dat = np.append(dat,unpack("32c",buff))        
        return ''.join(dat.astype(str))#''.join(str((dat[0:3]).tostring()))

    # sets DACX where X is the channel ch to the voltage V (in the range 0 to 4095)
    # Make sure you are in a mode where the voltage is not being inmediately overwritten
    def set_Vout(self,ch,V):
        self.bus_pi.write_word_data(self._addr,100+ch,V)
        #execute the setting commande on the chip
        self.bus_pi.read_i2c_block_data(self._addr,105,1)
        return 

    def get_Vout(self,ch):
        #execute the reading commande on the chip
        self.bus_pi.read_i2c_block_data(self._addr,104,1)
        block = self.bus_pi.read_i2c_block_data(self._addr,100+ch,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the input offset (in the range 0 to 4095)
    def set_offset(self, V):
        set_Vout(0, V)

    def get_offset(self):
        return self.get_Vout(0)

    # sets the value of the coarse output voltage when the box is in learn mode
    def set_Vlearn(self,V):
        self.bus_pi.write_word_data(self._addr,110,V)
        return 

    def get_Vlearn(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,110,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # reads the height of the last measured signal
    def get_Vin(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,111,8)
        buff = pack("8B",*block)
        dat = unpack("<d",buff)   
        dat=np.float64(dat)
        return dat[0]

    # reads the height of the last measured ground
    def get_Vin_gnd(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,112,8)
        buff = pack("8B",*block)
        dat = unpack("<d",buff)   
        dat=np.float64(dat)
        return dat[0]

    # 
    def get_ID(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,120,1)
        self._addr = block[0]
        return block[0]>>1

    def set_ID(self,ID):
        self._addr = ID
        return self.bus_pi.write_byte_data(self._addr,120,ID<<1)

    # 
    def get_channel(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,121,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    def set_channel(self,channel):
        return self.bus_pi.write_word_data(self._addr,121,channel)

    # 
    def set_transf(self,status):
        return self.bus_pi.write_byte_data(self._addr,122,status)


    # Sets the remote_trigg flag. Set to 0 so that the microcontroller uses the trigger
    # input, set it to 1 so that it continuously triggers
    def set_remote_trigg(self,status):
        return self.bus_pi.write_byte_data(self._addr,115,status)

    def get_remote_trigg(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,115,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Sets the number of recorded points, up to a maximum of 256. Sampling frequency is approx. 1MHz.
    def set_wf_len(self,wf_len):
        self._wf_len = wf_len
        if wf_len>256:
            print( "sorry, it is not possible to have more than 256 point per waveform.\n")
            self._wf_len = 256
        return self.bus_pi.write_word_data(self._addr,123,self._wf_len)

    def get_wf_len(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,123,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        self._wf_len = dat[0]
        return dat[0]

    #
    def get_trigg_cnt(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,124,4)
        buff = pack("4B",*block)
        dat = unpack("<I",buff)   
        dat=np.uint32(dat)
        return dat[0]

    #
    def get_wf_cnt(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,113,8)
        buff = pack("8B",*block)
        dat = unpack("<Q",buff)   
        dat=np.uint64(dat)
        return int(dat[0])

    #
    def get_v_cnt(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,114,8)
        buff = pack("8B",*block)
        dat = unpack("<I",buff)   
        dat=np.uint32(dat)
        return dat[0]

    # 
    def set_test(self,tmp):
        self.bus_pi.write_word_data(self._addr,141,tmp)
        self.bus_pi.read_i2c_block_data(self._addr,105,1)
        return

    def get_test(self):
        self.bus_pi.read_i2c_block_data(self._addr,104,1)
        block = self.bus_pi.read_i2c_block_data(self._addr,141,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    #     
    def set_erase(self):
        #self.bus_pi.read_i2c_block_data(self._addr,106,1)
        self.bus_pi.write_byte_data(self._addr,106,0)
        time.sleep(0.04)

        return
    
    #
    def get_probe(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,142,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    #
    def set_probe(self,tmp):
        self.bus_pi.write_word_data(self._addr,142,tmp)
        return
        
    # Set the value we are aiming to achieve in the signal when in lock mode 
    def set_Vset(self,Vset):
        return self.bus_pi.write_word_data(self._addr,125,Vset)

    def get_Vset(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,125,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the number of waveforms to average before stabilizing
    def set_N(self,N):
        return self.bus_pi.write_word_data(self._addr,126,N)

    def get_N(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,126,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # 
    def get_FEE_addr(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,140,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    def set_FEE_addr(self,FEE_addr):
        return self.bus_pi.write_word_data(self._addr,140,FEE_addr)

    # Set the point at which we start measuring the signal
    def set_start(self,start):
        return self.bus_pi.write_word_data(self._addr,127,start)

    def get_start(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,127,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the point at which we stop measuring the signal
    def get_stop(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,128,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    def set_stop(self,stop):
        return self.bus_pi.write_word_data(self._addr,128,stop)

    # Set the point at which we start measuring the background
    def set_start_gnd(self,start_gnd):
        return self.bus_pi.write_word_data(self._addr,131,start_gnd)
    
    def get_start_gnd(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,131,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the point at which we stop measuring the background
    def set_stop_gnd(self,stop_gnd):
        return self.bus_pi.write_word_data(self._addr,132,stop_gnd)

    def get_stop_gnd(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,132,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set whether the microcontroller should automatically select the best offset
    # and gain when in learn mode. auto_set_pga=0 to turn off the functionality and
    # to do it manually using set_pga and set_Vout(0, x)
    def set_auto_set_pga(self,auto_set_pga):
        return self.bus_pi.write_word_data(self._addr,129,auto_set_pga)

    def get_auto_set_pga(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,129,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the ratio of sensitivities of the fine to coarse voltage outputs of the board
    # This is 20 as per circuit design, but can be tuned more precisely
    def set_coarse_fine_ratio(self,coarse_fine_ratio):
        return self.bus_pi.write_word_data(self._addr,130,coarse_fine_ratio)

    def get_coarse_fine_ratio(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,130,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set whether the microcontroller should subtract the background when measuring.
    # status=0 to disable this. status=1 to enable it.
    def set_enab_gnd(self,status):
        return self.bus_pi.write_byte_data(self._addr,133,status)

    def get_enab_gnd(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,133,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]
    
    # Set the pga_state, this is defined via state = g*16+8 where g is such that the gain G
    # in the input measurements is G=2^g and G is one of {1, 2, 4, 8, 16, 32, 64, 128}
    # i.e. 0<g<7
    def set_pga_state(self,state):
        self.bus_pi.write_byte_data(self._addr,136,state)
        #execute set on the chip
        self.bus_pi.read_i2c_block_data(self._addr,137,1)
        return

    def get_pga_state(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,136,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]
    
    def read_pga_state(self):
        #execute set on the chip
        self.bus_pi.read_i2c_block_data(self._addr,138,1)
        return 

    def set_input_gain(self, gain):
        state = int(math.log2(gain))*16 + 8
        self.set_pga_state(state)

    def get_input_gain(self):
        state = self.get_pga_state()
        return 2**(state-8)//16

    # Set the feedback gain
    def set_Gain(self,Gain):
        tmp = (Gain)
        buff = pack("<f",Gain)
        dat = unpack("4B",buff)
        return self.bus_pi.write_i2c_block_data(self._addr,134,[dat[0],dat[1],dat[2],dat[3]])

    def get_Gain(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,134,4)
        buff = pack("4B",*block)
        dat = unpack("<f",buff)   
        dat=np.float32(dat)
        return dat[0]

    # Set the maximum step step_max, allowed in one iteration of the loop
    def set_step_max(self,step_max):
        return self.bus_pi.write_word_data(self._addr,135,step_max)

    def get_step_max(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,135,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Read whether we are in learn (0) or lock mode (1)
    def get_mode(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,139,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]

    # Set the relative sensitivities of the coarse and fine output
    def set_coarse_fine_ratio(self,ratio):
        tmp = (Gain)
        buff = pack("<f",ratio)
        dat = unpack("4B",buff)
        return self.bus_pi.write_i2c_block_data(self._addr,140,[dat[0],dat[1],dat[2],dat[3]])

    def get_coarse_fine_ratio(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,140,4)
        buff = pack("4B",*block)
        dat = unpack("<f",buff)   
        dat=np.float32(dat)
        return dat[0]

    # Read whether we are out of lock
    def get_out_of_lock(self):
        block = self.bus_pi.read_i2c_block_data(self._addr,141,2)
        buff = pack("2B",*block)
        dat = unpack("<H",buff)   
        dat=np.uint16(dat)
        return dat[0]


    # Debugging
    def read_all_param(self):
        print( "start = %d" %self.get_start())
        print( "stop = %d" %self.get_stop())
        print( "Vset = %d" %self.get_Vset())
        print( "wf_len = %d" %self.get_wf_len())
        print ("step = %d" %self.get_step())
        print( "N = %d" %self.get_N())
        print( "Channel = %d" %self.get_channel())
        print( "ID = %d" %self.get_ID())
    

    '''def set_test_all_param(self):
        self.set_start(0)
        self.set_stop(10)
        self.set_Vset(20)
        self.set_wf_len(30)
        self.set_step(40)
        self.set_N(60)'''



