#!   /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 pylab import *
from calibr_box import *
import find_usb_tty
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *


"""
test43: Tests saturation (-> no wraparound)
"""


NB_CHANNELS = 4

AWG_SET_SLEEP = 0.3
SSR_SET_SLEEP = 0.05
BOX_SET_SLEEP = 0.01

ACQ_TIMEOUT = 10

PRE_TRIG_SAMPLES = 10
POST_TRIG_SAMPLES = 200
NB_SHOTS = 1
BYTES_PER_SAMPLE = 2

TRIG_TIMETAG_BYTES = 16

RANGES = ['10V', '1V', '100mV']


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.\n"
    # Reset offset DACs
    fmc.dc_offset_reset()
    # Make sure all switches are OFF
    open_all_channels(fmc)
    # Set software 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)

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

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

# Converts hex gain value to float
def gain2float(value):
    dec = (value & 0x8000) >> 15
    frac = value & 0x7FFF
    return (float) (dec + (frac * 1.0/2**15))

def get_corr_values(fmc):
    off_corr = fmc.get_adc_offset_corr(1)
    print("Offset corr:0x%04X (%d)"%(off_corr, hex2signed(off_corr)))
    gain_corr = fmc.get_adc_gain_corr(1)
    print("Gain corr  :0x%04X (%1.6f)"%(gain_corr, gain2float(gain_corr)))

def acq_channels(fmc, carrier, pause):
    # Make sure no acquisition is running
    fmc.stop_acq()
    time.sleep(pause)
    # Start acquisition
    fmc.start_acq()
    time.sleep(pause)
    # Trigger
    fmc.sw_trig()
    # Wait end of acquisition
    timeout = 0
    while('IDLE' != fmc.get_acq_fsm_state()):
        time.sleep(.1)
        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 samples for all channels + trigger timetag
    data_length = ((PRE_TRIG_SAMPLES + 1 + POST_TRIG_SAMPLES) * NB_CHANNELS * BYTES_PER_SAMPLE) + TRIG_TIMETAG_BYTES
    channels_data = carrier.get_data((trig_pos-(PRE_TRIG_SAMPLES*8)), data_length)
    trig_timetag = []
    data = []
    for i in range(8):
        data.append(channels_data.pop(-1))
    for i in range(0,8,2):
        trig_timetag.append(((data[i] << 16) + data[i+1]))
    # Disable "DMA done" interrupt
    carrier.disable_dma_done_irq()
    #print("raw hex data: 0x%08X"%(channels_data[0]))
    channels_data = [hex2signed(item) for item in channels_data]
    #print("signed data : 0x%08X (%d)"%(channels_data[0], channels_data[0]))
    return channels_data, trig_timetag

def plot_channel(ch_data_1, ch_data_2, ch_data_3, ch_data_4, sat, ylimit):
    sample = arange(len(ch_data_1))
    plot(sample, ch_data_1, 'go-', label='reset')
    plot(sample, ch_data_2, 'bo-', label='dc offset to min')
    plot(sample, ch_data_3, 'mo-', label='neg adc offset corr')
    plot(sample, ch_data_4, 'co-', label='with saturation')
    plot(sample, [sat]*len(sample), 'm')
    plot(sample, [-sat]*len(sample), 'm')
    plot(sample, [32764]*len(sample), 'g')
    plot(sample, [-32765]*len(sample), 'g')
    ylim(-ylimit-(ylimit/10.0), ylimit+(ylimit/10.0))
    grid(color='k', linestyle=':', linewidth=1)
    legend(loc='upper left')
    xlabel('Samples')
    ylabel('Raw ADC values (two\'s complement)')
    title('Variable saturation, limit: %d'%sat)
    #draw()
    show()
    return 0


def main (default_directory='.'):

    # Constants declaration
    TEST_NB = 43
    FMC_ADC_BITSTREAM = '../firmwares/spec_fmcadc100m14b4cha.bin'
    FMC_ADC_BITSTREAM = os.path.join(default_directory, FMC_ADC_BITSTREAM)
    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
    EEPROM_BIN_FILENAME = "eeprom_content.out"
    EEPROM_BIN_FILENAME = os.path.join(default_directory, EEPROM_BIN_FILENAME)
    EEPROM_SIZE = 8192 # in Bytes
    CALIBR_BIN_FILENAME = "calibration_data.bin"
    CALIBR_BIN_FILENAME = os.path.join(default_directory, CALIBR_BIN_FILENAME)


    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()

    # Load FMC ADC firmware
    print "Loading FMC ADC firmware: %s\n" % FMC_ADC_BITSTREAM
    if(os.path.isfile(FMC_ADC_BITSTREAM)):
        spec.load_firmware(FMC_ADC_BITSTREAM)
        time.sleep(2)
    else:
        raise PtsCritical("Firmware file \"%s\" is missing, test stopped." % FMC_ADC_BITSTREAM)

    # 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:
        # Initialise fmc adc
        fmc_adc_init(spec, fmc)

        # Use data pattern instead of ADC data
        #fmc.testpat_en(0x1FFF) # max
        #fmc.testpat_en(0x0) # mid
        #fmc.testpat_en(0x2000) # min

        # Set UTC
        current_time = time.time()
        utc_seconds = int(current_time)
        fmc.set_utc_second_cnt(utc_seconds)
        #print "UTC core seconds counter initialised to : %d" % fmc.get_utc_second_cnt()

        utc_coarse = int((current_time - utc_seconds)/8E-9)
        fmc.set_utc_coarse_cnt(utc_coarse)
        #print "UTC core coarse  counter initialised to : %d" % fmc.get_utc_coarse_cnt()

        # Print configuration
        #fmc.print_adc_core_config()

        # Print ADC config
        #fmc.print_adc_config()

        # Acquisition parameters
        ACQ_PAUSE = 1 # pause between acq. stop and start, start and trigger
        IN_RANGE  = 'CAL'
        IN_TERM   = 'OFF'

        ##################################################
        # Reset DC offset
        ##################################################
        fmc.dc_offset_reset()
        print("\nReset DC offset.")

        ##################################################
        # Acquire channel 1
        ##################################################
        print "Acquiring channel 1"
        # Configure analogue input
        fmc.set_input_range(1, IN_RANGE)
        fmc.set_input_term(1, IN_TERM)
        time.sleep(SSR_SET_SLEEP)
        # Perform an acquisition
        acq_data, trig_timetag = acq_channels(fmc, carrier, ACQ_PAUSE)
        channel_data = acq_data[0::4]
        #print("Number of samples: %d"%(len(channel_data)))

        # Calculate middle value
        ch_max = max(channel_data)
        ch_min = min(channel_data)
        ch_mid = (abs(ch_max) - abs(ch_min))
        ch_mean = mean(channel_data)
        print("Channel max=%d, min=%d, mid=%d, mean=%d"%(ch_max, ch_min, ch_mid, ch_mean))

        ch_data_1 = channel_data

        # Plot channel
        #plot_channel(channel_data, ch_mean, 32768)


        ##################################################
        # Apply a DC offset
        ##################################################
        dc_offset = 0xFFFF
        print("\nSet dc offset to: 0x%08X"%(dc_offset))
        fmc.set_dc_offset(1, dc_offset)

        ##################################################
        # Acquire channel 1
        ##################################################
        print "Acquiring channel 1"
        # Configure analogue input
        fmc.set_input_range(1, IN_RANGE)
        fmc.set_input_term(1, IN_TERM)
        time.sleep(SSR_SET_SLEEP)
        # Perform an acquisition
        acq_data, trig_timetag = acq_channels(fmc, carrier, ACQ_PAUSE)
        channel_data = acq_data[0::4]
        #print("Number of samples: %d"%(len(channel_data)))

        # Calculate middle value
        ch_max = max(channel_data)
        ch_min = min(channel_data)
        ch_mid = (abs(ch_max) - abs(ch_min))
        ch_mean = mean(channel_data)
        print("Channel max=%d, min=%d, mid=%d, mean=%d"%(ch_max, ch_min, ch_mid, ch_mean))

        ch_data_2 = channel_data

        # Check for error
        if ch_mid != 0:
            raise PtsError("Unexpected middle value %d (must be 0)" % ch_mid)
        if ch_mean != -32767:
            raise PtsError("Unexpected mean value %d (must be -32767)" % ch_mean)

        # Plot channel
        #plot_channel(channel_data, ch_mean, 32768)


        ##################################################
        # Apply ADC offset correction
        ##################################################
        offset_corr = 0xFF8F
        print("\nSet ADC offset correction to: 0x%08X"%(offset_corr))
        fmc.set_adc_gain_offset_corr(1, 0x8000, offset_corr)

        ##################################################
        # Acquire channel 1
        ##################################################
        print "Acquiring channel 1"
        # Configure analogue input
        fmc.set_input_range(1, IN_RANGE)
        fmc.set_input_term(1, IN_TERM)
        time.sleep(SSR_SET_SLEEP)
        # Perform an acquisition
        acq_data, trig_timetag = acq_channels(fmc, carrier, ACQ_PAUSE)
        channel_data = acq_data[0::4]
        #print("Number of samples: %d"%(len(channel_data)))

        # Calculate middle value
        ch_max = max(channel_data)
        ch_min = min(channel_data)
        ch_mid = (abs(ch_max) - abs(ch_min))
        ch_mean = mean(channel_data)
        print("Channel max=%d, min=%d, mid=%d, mean=%d"%(ch_max, ch_min, ch_mid, ch_mean))

        ch_data_3 = channel_data

        # Check for error
        if ch_mid != 0:
            raise PtsError("Unexpected middle value %d (must be 0)" % ch_mid)
        if ch_mean != -32767:
            raise PtsError("Unexpected mean value %d (must be -32767)" % ch_mean)

        # Plot channel
        #plot_channel(channel_data, ch_mean, 32768)

        ##################################################
        # Set saturation
        ##################################################
        sat_thres = 10000
        print("\nSet saturation to: 0x%08X"%(sat_thres))
        fmc.fmc_adc_csr.set_field('CH1_SAT', 'VAL', sat_thres)
        fmc.fmc_adc_csr.set_field('CH2_SAT', 'VAL', sat_thres)
        fmc.fmc_adc_csr.set_field('CH3_SAT', 'VAL', sat_thres)
        fmc.fmc_adc_csr.set_field('CH4_SAT', 'VAL', sat_thres)

        ##################################################
        # Acquire channel 1
        ##################################################
        print "Acquiring channel 1"
        # Perform an acquisition
        acq_data, trig_timetag = acq_channels(fmc, carrier, ACQ_PAUSE)
        channel_data = acq_data[0::4]
        #print("Number of samples: %d"%(len(channel_data)))

        # Calculate middle value
        ch_max = max(channel_data)
        ch_min = min(channel_data)
        ch_mid = (abs(ch_max) - abs(ch_min))
        ch_mean = mean(channel_data)
        print("Channel max=%d, min=%d, mid=%d, mean=%d"%(ch_max, ch_min, ch_mid, ch_mean))

        ch_data_4 = channel_data

        # Check for error
        if ch_mid != 0:
            raise PtsError("Unexpected middle value %d (must be 0)" % ch_mid)
        if ch_mean != -sat_thres:
            raise PtsError("Unexpected mean value %d (must be %d)" % (ch_mean, -sat_thres))


        ##################################################
        # Plot channel
        ##################################################
        plot_channel(ch_data_1, ch_data_2, ch_data_3, ch_data_4, sat_thres, 32768)

        # Make sure all switches are OFF
        open_all_channels(fmc)


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

    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)


if __name__ == '__main__' :
    main()
