#!   /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

# Add common modules and libraries location to path
sys.path.append('../../../')
sys.path.append('../../../gnurabbit/python/')
sys.path.append('../../../common/')

# Import common modules
from ptsexcept import *
import rr

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


"""
test08: Test analogue front-end switches

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


NB_CHANNELS = 4

PRE_TRIG_SAMPLES = 1000
POST_TRIG_SAMPLES = 100000
NB_SHOTS = 1

ACQ_LENGTH = 50000 # in samples
ACQ_TIMEOUT = 10

AWG_SET_SLEEP = 0.3
SSR_SET_SLEEP = 0.005
BOX_SET_SLEEP = 0.01

ADC_MID_VALUE = 0
ADC_MID_TOL = 500

SW1_TOL = 30000
SW2_TOL = 5000
SW3_TOL = 5000
SW4_TOL = 10000
SW5_TOL = 100
SW6_TOL = 20000
SW7_TOL = 20000


def hex2signed(value):
    if(value & 0x8000):
        return -((~value & 0xFFFF) + 1)
    else:
        return value

def acq_config(fmc):
    print "Initialise FMC board\n"
    # 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)

def get_channels_mean(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" iinterrupt
    carrier.set_irq_en_mask(0x1)
    # Read ACQ_LENGTH samples after the trigger for all channels
    channels_data = carrier.get_data((trig_pos<<3), ACQ_LENGTH*8)
    # Disable "DMA done" iinterrupt
    carrier.set_irq_en_mask(0x0)
    channels_data = [hex2signed(item) for item in channels_data]
    # calculate mean value for each channel
    channels_mean = []
    for channel in range(1,NB_CHANNELS+1):
        channels_mean.append(mean(channels_data[channel-1::4]))
    return channels_mean

def set_awg_offset(gen, sine, offset):
    sine.dc = offset
    gen.play(sine)
    time.sleep(AWG_SET_SLEEP)

def print_current_adc_value(fmc, channel, file):
    adc_value = fmc.get_current_adc_value(channel)
    print "CH%d ADC value:0x%.4X  %d" % (channel, adc_value, adc_value)
    file.write('CH%d: 0x%.4X %d\n' % (channel, adc_value, adc_value))

# Routine to test SSR (Solid State Relay)
# Basic operation: Set AWG DC offset and SSRs -> read ADC value, change 1 SSR -> read ADC -> check ADC values difference
# Options: retry -> run the same test several times
#          threshold -> minimum number of try to pass the test
def sw_test(box, gen, sine, carrier, fmc, sw, ssr_1, ssr_2, diff_tol):
    print "\nTesting switch %d\n-------------------------" % sw

    error = 0
    for i in range(1,NB_CHANNELS+1):
        pass_nb = 0
        box.select_output_ch(i) # connect AWG to current channel
        time.sleep(BOX_SET_SLEEP)
        fmc.set_ssr(i,ssr_1)
        #time.sleep(SSR_SET_SLEEP)
        adc_value_before = get_channels_mean(fmc, carrier)[i-1]
        fmc.set_ssr(i,ssr_2)
        #time.sleep(SSR_SET_SLEEP)
        adc_value = get_channels_mean(fmc, carrier)[i-1]
        diff = adc_value_before-adc_value
        print "CH%d ssr=0x%.2X: %6d  ssr=0x%.2X: %6d  diff:%6d ,min_diff:%6d" % (i, ssr_1, adc_value_before, ssr_2, adc_value, abs(diff), diff_tol)
        if(diff_tol > abs(diff)):
            print "#### SW%d of channel %d is malfunctioning" % (sw, i)
            #raise PtsError('SW%d of channel %d is malfunctioning' % (sw, i))
            error += 1
        fmc.set_ssr(i,0x0)
        #time.sleep(SSR_SET_SLEEP)
    return error

def adc_mid_test(carrier, fmc, tol):
    print "\nTesting ADC middle scale, all switches opened.\n-------------------------"

    error = 0
    for i in range(1,NB_CHANNELS+1):
        ssr_1 = 0x0
        fmc.set_ssr(i,ssr_1)
        #time.sleep(SSR_SET_SLEEP)
        diff = get_channels_mean(fmc, carrier)[i-1]
        print "CH%d ssr=0x%.2X, value:%6d, expected value: %d, diff:%6d, tolerence:%d" % (i, ssr_1, diff, ADC_MID_VALUE, diff, tol)
        if(abs(diff) > tol):
            print "#### One of channel %d switches is malfunctioning" % i
            #raise PtsError('One of channel %d switches is malfunctioning' % i)
            error += 1
    return error


def main (default_directory='.'):

    # Constants declaration
    TEST_NB = 8
    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 firmware 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])

        # Set sine params -> ~ DC level
        sine.frequency = 0.000001
        sine.amplitude = 0.001
        sine.dc = 1

        # Set AWG and turn it ON
        gen.connect()
        gen.output = True

        # Configure acquisition
        acq_config(fmc)

        # Reset offset DACs to mid-scale (no offset)
        fmc.dc_offset_reset()

        # Uncomment the following block to check calibration box output connections
        """
        print "Ckecking calibration box output connections"
        set_awg_offset(gen, sine, 2)
        for i in range(1,NB_CHANNELS+1):
            print "Channel %d"%i
            fmc.set_ssr(i,0x1)
            time.sleep(SSR_SET_SLEEP)
            box.select_output_ch(i) # connect AWG to current channel
            time.sleep(BOX_SET_SLEEP)
            print get_channels_mean(fmc, carrier)
            fmc.set_ssr(i,0x0)
            time.sleep(SSR_SET_SLEEP)
        """

        # Test switches
        error = 0

        error += adc_mid_test(carrier, fmc, ADC_MID_TOL)

        # Set AWG offset
        awg_offset = 0.25
        set_awg_offset(gen, sine, awg_offset)
        print "\n=> AWG offset: %1.3fV" % awg_offset

        error += sw_test(box, gen, sine, carrier, fmc, 1, 0x00, 0x01, SW1_TOL)
        error += sw_test(box, gen, sine, carrier, fmc, 4, 0x01, 0x09, SW4_TOL)
        error += sw_test(box, gen, sine, carrier, fmc, 5, 0x41, 0x51, SW5_TOL)
        error += sw_test(box, gen, sine, carrier, fmc, 6, 0x00, 0x60, SW6_TOL)
        error += sw_test(box, gen, sine, carrier, fmc, 7, 0x01, 0x41, SW7_TOL)

        # Set AWG offset
        awg_offset = 0.01
        set_awg_offset(gen, sine, awg_offset)
        print "\n=> AWG offset: %1.3fV" % awg_offset

        error += sw_test(box, gen, sine, carrier, fmc, 2, 0x20, 0x22, SW2_TOL)
        error += sw_test(box, gen, sine, carrier, fmc, 3, 0x22, 0x26, SW3_TOL)

        # Make sure all switches are OFF
        for i in range(1,NB_CHANNELS+1):
            fmc.set_ssr(i,0x00)

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

        if(error == 0):
            print "\nAll switches are working fine!"

    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 occured during switches test
    if(error != 0):
        raise PtsError('An error occured during switches test, check log for details.')


    # Following commented code scan all SSR configurations to find good ones for testing switches
    """
    file = open("adc_analogue_test.txt", 'w')

    offset = 1
    set_awg_offset(gen, sine, offset)
    print('\nAWG offset: %f') % offset
    file.write('\n\nAWG offset: %f' % offset)

    for ssr in range(0x80):
        print('\nSSR: %.2X') % ssr
        file.write('\nSSR: %.2X\n' % ssr)
        for i in range(1,NB_CHANNELS+1):
            fmc.set_ssr(i,ssr)
            time.sleep(0.05)
            print_current_adc_value(fmc, i, file)
            fmc.set_ssr(i,0x0)
            time.sleep(0.05)


    offset = 0.5
    set_awg_offset(gen, sine, offset)
    print('\nAWG offset: %f') % offset
    file.write('\n\nAWG offset: %f' % offset)

    for ssr in range(0x80):
        print('\nSSR: %.2X') % ssr
        file.write('\nSSR: %.2X\n' % ssr)
        for i in range(1,NB_CHANNELS+1):
            fmc.set_ssr(i,ssr)
            time.sleep(0.05)
            print_current_adc_value(fmc, i, file)
            fmc.set_ssr(i,0x0)
            time.sleep(0.05)


    offset = 0.01
    set_awg_offset(gen, sine, offset)
    print('\nAWG offset: %f') % offset
    file.write('\n\nAWG offset: %f' % offset)

    for ssr in range(0x80):
        print('\nSSR: %.2X') % ssr
        file.write('\nSSR: %.2X\n' % ssr)
        for i in range(1,NB_CHANNELS+1):
            fmc.set_ssr(i,ssr)
            time.sleep(0.05)
            print_current_adc_value(fmc, i, file)
            fmc.set_ssr(i,0x0)
            time.sleep(0.05)


    offset = 0.25
    set_awg_offset(gen, sine, offset)
    print('\nAWG offset: %f') % offset
    file.write('\n\nAWG offset: %f' % offset)

    for ssr in range(0x80):
        print('\nSSR: %.2X') % ssr
        file.write('\nSSR: %.2X\n' % ssr)
        for i in range(1,NB_CHANNELS+1):
            fmc.set_ssr(i,ssr)
            time.sleep(0.05)
            print_current_adc_value(fmc, i, file)
            fmc.set_ssr(i,0x0)
            time.sleep(0.05)


    gen.output = False
    gen.close()
    sys.exit()
    """

    # Following commented code is for testing the tests
    """
    sw_test(box, gen, sine, 0.25, carrier, fmc, 1, 0x00, 0x00, SW1_TOL)
    sw_test(box, gen, sine, 0.25, carrier, fmc, 4, 0x01, 0x01, SW4_TOL)
    sw_test(box, gen, sine, 0.25, carrier, fmc, 5, 0x41, 0x41, SW5_TOL)
    sw_test(box, gen, sine, 0.25, carrier, fmc, 6, 0x00, 0x00, SW6_TOL)
    sw_test(box, gen, sine, 0.25, carrier, fmc, 7, 0x01, 0x01, SW7_TOL)
    sw_test(box, gen, sine, 0.01, carrier, fmc, 2, 0x20, 0x20, SW2_TOL)
    sw_test(box, gen, sine, 0.01, carrier, fmc, 3, 0x22, 0x22, SW3_TOL)
    """


if __name__ == '__main__' :
    main()
