#!   /usr/bin/env   python
#    coding: utf8

# Copyright CERN, 2011
# Author: Matthieu Cattin <matthieu.cattin@cern.ch>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 30/5/2012

# Import system modules
import sys
import time
import os

# Import specific modules
from fmc_adc_spec import *
from fmc_adc import *
from numpy import *
from pylab import *
from calibr_box import *
import find_usb_tty
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *

# Import common modules
from ptsexcept import *
import rr


"""
test09: Test analogue front-end frequency response,
        with limits check

Note: Requires test00.py to run first to load the gateware!
"""

NB_CHANNELS = 4

AWG_SET_SLEEP = 0.3
SSR_SET_SLEEP = 0.005
BOX_SET_SLEEP = 0.1

ACQ_TIMEOUT = 10

PRE_TRIG_SAMPLES = 10
POST_TRIG_SAMPLES = 10000
NB_SHOTS = 1

ACQ_LENGTH = 10000 # in samples

# col 0: freq
# col 1: expected attenuation (in dB)
# col 2: tolerance on attenuation (in dB)
points = [[10E3 ,   0  ,  1],
          [100E3,   0  ,  1],
          [1E6  ,   0  ,  1],
          #[5E6  ,   0  ,  1],
          [10E6 ,   0  ,  1],
          #[12E6 ,   0  ,  1],
          #[14E6 ,   0  ,  1],
          #[16E6 ,   0  ,  2],
          [18E6 ,   0  ,  2],
          #[20E6 ,   0  ,  2],
          [22E6 ,   0  ,  2],
          [24E6 ,  -0.217,  2],
          [26E6 ,  -0.434,  2],
          [28E6 ,  -0.869,  2],
          [30E6 ,  -1.52,  2],
          #[32E6 ,  -1.95,  2],
          #[34E6 ,  -2.82,  2],
          #[36E6 ,  -3.69,  2],
          #[38E6 , -4.56,  2],
          [40E6 , -5.86,  2],
          #[42E6 , -6.51,  2],
          #[44E6 , -7.6,  2],
          #[46E6 , -8.68,  2],
          #[48E6 , -9.77,  2],
          #[50E6 , -13.46,  3],
          [60E6 , -16.5,  3],
          [80E6 , -28.66,  3]]


def open_all_channels(fmc):
    for i in range(1,NB_CHANNELS+1):
        fmc.set_input_range(i, 'OPEN')
        time.sleep(SSR_SET_SLEEP)

def fmc_adc_init(spec, fmc):
    print "Initialise FMC board."
    # Reset offset DACs
    fmc.dc_offset_reset()
    # Make sure all switches are OFF
    open_all_channels(fmc)
    # Set trigger
    fmc.set_soft_trig()
    # Set acquisition
    fmc.set_pre_trig_samples(PRE_TRIG_SAMPLES)
    fmc.set_post_trig_samples(POST_TRIG_SAMPLES)
    fmc.set_shots(NB_SHOTS)
    # Print configuration
    #fmc.print_adc_core_config()

def set_awg_freq(gen, sine, freq):
    sine.frequency = freq
    gen.play(sine)
    #print('Sine frequency:%3.3fMHz')%(sine.frequency/1E6)
    time.sleep(AWG_SET_SLEEP)

# Converts two's complement hex to signed
def hex2signed(value):
    if(value & 0x8000):
        return -((~value & 0xFFFF) + 1)
    else:
        return value

# Converts digital value to volts
def digital2volt(value, full_scale, nb_bit):
    return float(value) * float(full_scale)/2**nb_bit

def acq_channels(fmc, carrier):
    # Make sure no acquisition is running
    fmc.stop_acq()
    # Start acquisition
    fmc.start_acq()
    # Trigger
    fmc.sw_trig()
    # Wait end of acquisition
    timeout = 0
    time.sleep(0.001)
    while('IDLE' != fmc.get_acq_fsm_state()):
        time.sleep(.01)
        timeout += 1
        if(ACQ_TIMEOUT < timeout):
            print "Acquisition timeout. Missing trigger?."
            print "Acq FSm state: %s"%fmc.get_acq_fsm_state()
            return 1
    # Retrieve data trough DMA
    trig_pos = fmc.get_trig_pos()
    # Enable "DMA done" interrupt
    carrier.enable_dma_done_irq()
    # Read ACQ_LENGTH samples after the trigger for all channels
    channels_data = carrier.get_data(trig_pos, ACQ_LENGTH*8)
    # Disable "DMA done" interrupt
    carrier.disable_dma_done_irq()
    channels_data = [hex2signed(item) for item in channels_data]
    channels_data = [digital2volt(item,1.0,16) for item in channels_data]
    return channels_data

def show_result_graph(points, ch_att):
    pt = array(points)
    freq = pt[:,0]
    a_min = pt[:,1] - pt[:,2]
    a_max = pt[:,1] + pt[:,2]
    cutoff = [-3] * len(points)
    semilogx(freq, ch_att[0], 'b', label='Channel 1')
    semilogx(freq, ch_att[1], 'g', label='Channel 2')
    semilogx(freq, ch_att[2], 'm', label='Channel 3')
    semilogx(freq, ch_att[3], 'c', label='Channel 4')
    semilogx(freq, a_min, 'r:', label='Lower limit')
    semilogx(freq, a_max, 'r:', label='Upper limit')
    semilogx(freq, cutoff, 'k', label='-3dB')
    grid(color='k', linestyle='--', linewidth=1)
    legend(loc='upper left')
    show()
    return 0


def main (default_directory='.'):

    # Constants declaration
    TEST_NB = 9
    EXPECTED_BITSTREAM_TYPE = 0x1

    # Calibration box vendor and product IDs
    BOX_USB_VENDOR_ID = 0x10c4 # Cygnal Integrated Products, Inc.
    BOX_USB_PRODUCT_ID = 0xea60 # CP210x Composite Device
    # Agilent AWG serial access vendor and product IDs
    AWG_USB_VENDOR_ID = 0x0403 # Future Technology Devices International, Ltd
    AWG_USB_PRODUCT_ID = 0x6001 # FT232 USB-Serial (UART) IC
    AWG_BAUD = 57600


    start_test_time = time.time()
    print "================================================================================"
    print "Test%02d start\n" % TEST_NB

    # SPEC object declaration
    print "Loading hardware access library and opening device.\n"
    spec = rr.Gennum()

    # Carrier object declaration (SPEC board specific part)
    # Used to check that the gateware is loaded.
    try:
        carrier = CFmcAdc100mSpec(spec, EXPECTED_BITSTREAM_TYPE)
    except FmcAdc100mSpecOperationError as e:
        raise PtsCritical("Carrier init failed, test stopped: %s" % e)

    # Mezzanine object declaration (FmcAdc100m14b4cha board specific part)
    try:
        fmc = CFmcAdc100m(spec)
    except FmcAdc100mOperationError as e:
        raise PtsCritical("Mezzanine init failed, test stopped: %s" % e)


    try:
        # Others objects declaration
        usb_tty = find_usb_tty.CttyUSB()
        awg_tty = usb_tty.find_usb_tty(AWG_USB_VENDOR_ID, AWG_USB_PRODUCT_ID)
        box_tty = usb_tty.find_usb_tty(BOX_USB_VENDOR_ID, BOX_USB_PRODUCT_ID)
        gen = Agilent33250A(device=awg_tty[0], bauds=AWG_BAUD)
        sine = SineWaveform()
        box = CCalibr_box(box_tty[0])

        # Initialise fmc adc
        fmc_adc_init(spec, fmc)

        # Set sine params
        sine.frequency = 1E6
        sine.amplitude = 0.8
        sine.dc = 0
        # print('Sine frequency:%3.3fMHz amplitude:%2.3fVp offset:%2.3fV')%(sine.frequency/1E6, sine.amplitude, sine.dc)

        # Set AWG
        gen.connect()
        gen.play(sine)
        gen.load50 = True
        gen.output = True

        # Test frequency response of all channels
        print "\nFrequency response measurement."
        ch_ampl = [[],[],[],[]]
        for i in range(1,NB_CHANNELS+1):
            fmc.set_input_range(i, '1V')
            fmc.set_input_term(i, 'ON')
            time.sleep(SSR_SET_SLEEP)
            box.select_output_ch(i) # connect AWG to current channel
            time.sleep(BOX_SET_SLEEP)
            for j in range(len(points)):
                set_awg_freq(gen, sine, points[j][0])
                channel_data = []
                channels_data = acq_channels(fmc, carrier)
                channel_data = channels_data[i-1::4]
                ampl = max(channel_data)-min(channel_data)
                print "CH%d frequency:%8.0f Hz amplitude:%f V"%(i, points[j][0], ampl)
                ch_ampl[i-1].append(ampl)
            fmc.set_input_range(i, 'OPEN')
            fmc.set_input_term(i, 'OFF')

        # Convert volts to dB
        ch_att = [[],[],[],[]]
        for ch in range(NB_CHANNELS):
            vref = ch_ampl[ch][0]
            print "Channel %d vref=%f"%(ch, vref)
            ch_att[ch] = [(20*log10(item/vref)) for item in ch_ampl[ch]]

        # Check that frequency response is in the limits
        print "\nFrequency response check."
        error = 0
        for i in range(len(points)):
            for ch in range(NB_CHANNELS):
                if((ch_att[ch][i] < points[i][1]-points[i][2]) | (ch_att[ch][i] > points[i][1]+points[i][2])):
                    print "Channel %d frequency response is out of range at freq:%2.3fMHz"%(ch+1, points[i][0]/1E6)
                    print "Attenuation:%3.3fdB, expected:%2.1f +/-%2.1fdB"%(ch_att[ch][i], points[i][1], points[i][2])
                    error += 1
                    # raise PtsError('Channel %d frequency response is out of range at freq:%2.3fMHz'%(i, points[j][0]/1E6))
        if error == 0:
            print "OK!"

        # print freqency response to log
        print('\nChannels frequency response in dB')
        print('Frequency, Expected value, tolerance, CH1 attenuation, CH2 attenuation, CH3 attenuation, CH4 attenuation, Mean attenuation')
        for i in range(len(points)):
            ch_att_mean = (ch_att[0][i]+ch_att[1][i]+ch_att[2][i]+ch_att[3][i])/4
            print "%8.0f, %3d, %2d, % 3.3f, % 3.3f, % 3.3f, % 3.3f, %f"%(points[i][0], 
                                                                         points[i][1], 
                                                                         points[i][2], 
                                                                         ch_att[0][i], 
                                                                         ch_att[1][i], 
                                                                         ch_att[2][i], 
                                                                         ch_att[3][i], 
                                                                         ch_att_mean)

        # print frequency response to a separate file
        file_name = "test09_freq_resp.txt"
        f = open(file_name, 'w')
        for i in range(len(points)):
            f.write('%8.0f, %2.1f, %2.1f, % 3.3f, % 3.3f, % 3.3f, % 3.3f\n'%(points[i][0], 
                                                                             points[i][1], 
                                                                             points[i][2], 
                                                                             ch_att[0][i], 
                                                                             ch_att[1][i], 
                                                                             ch_att[2][i], 
                                                                             ch_att[3][i]))
        f.close()

        # Plot results -> use test10.py to plot the results!
        # show_result_graph(points, ch_att)

        # Make sure all switches are OFF
        open_all_channels(fmc)

        # Switch AWG OFF
        gen.output = False
        gen.close()

    except(FmcAdc100mSpecOperationError, FmcAdc100mOperationError, CalibrBoxOperationError) as e:
        raise PtsError("Test failed: %s" % e)


    print ""
    print "==> End of test%02d" % TEST_NB
    print "================================================================================"
    end_test_time = time.time()
    print "Test%02d elapsed time: %.2f seconds\n" % (TEST_NB, end_test_time-start_test_time)

    # Check if an error occurred during frequency response test
    if(error != 0):
        raise PtsError('An error occurred during frequency response test, check log for details.')


if __name__ == '__main__' :
    main()
