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


"""
test37: Test trigger timetags (single and multi shot modes)

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


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 = 100
BYTES_PER_SAMPLE = 2

TRIG_TIMETAG_BYTES = 16


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 dc offset DACs\n  open all channels\n  config trigger: software"
    # 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)

# 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 make_acq(fmc, pause, shots=1):
    # Make sure no acquisition is running
    fmc.stop_acq()
    time.sleep(pause)
    # Start acquisition
    fmc.start_acq()
    # Trigger
    for i in range(shots):
        time.sleep(pause)
        fmc.sw_trig()
        #print("Trigger %d"%i)
    # 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 fmc.get_trig_pos()

def get_acq_data(carrier, start_addr, data_length):
    # Retrieve data trough DMA
    carrier.enable_dma_done_irq()
    channels_data = carrier.get_data(start_addr, data_length)
    carrier.disable_dma_done_irq()
    return channels_data


def plot_channels(ch_data, adc_fs):
    ylimit = adc_fs/2
    sample = arange(len(ch_data)/4)
    # Convert raw data to volts
    ch_data = [hex2signed(item) for item in ch_data]
    ch_data = [digital2volt(item,adc_fs,16) for item in ch_data]
    # Plot
    plot(sample, ch_data[0::4], 'b', label='Channel 1')
    plot(sample, ch_data[1::4], 'g', label='Channel 2')
    plot(sample, ch_data[2::4], 'm', label='Channel 3')
    plot(sample, ch_data[3::4], 'c', label='Channel 4')
    ylim(-ylimit-(ylimit/10.0), ylimit+(ylimit/10.0))
    grid(color='k', linestyle='--', linewidth=1)
    legend(loc='upper left')
    show()
    return 0


def main (default_directory='.'):

    # Constants declaration
    TEST_NB = 37
    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

    error = 0


    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
    spec.load_firmware(FMC_ADC_BITSTREAM)
    time.sleep(2)

    # 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])

        # Initialise fmc adc
        fmc_adc_init(spec, fmc)

        # Use test data instead of data from ADC
        # fmc.test_data_en()

        # Use data pattern instead of ADC data
        # fmc.testpat_en(0x2000)

        # Set UTC
        current_time = time.time()
        utc_seconds = int(current_time)
        fmc.set_utc_second_cnt(utc_seconds)
        utc_coarse = int((current_time - utc_seconds)/8E-9)
        fmc.set_utc_coarse_cnt(utc_coarse)
        print("\nUTC core initialisation:\n  seconds counter: 0x%08X\n  coarse counter: 0x%08X" %(fmc.get_utc_second_cnt(), fmc.get_utc_coarse_cnt()))

        # Acquisition parameters
        ACQ_PAUSE = 1 # pause between acq. stop and start, start and trigger
        IN_RANGE  = '100mV'
        IN_TERM   = 'ON'
        ADC_FS = {'10V':10.0, '1V':1.0, '100mV':0.1}

        print("\nAnalog inputs config:\n  termination: %s\n  range: %s"%(IN_TERM, IN_RANGE))

        for ch in range(NB_CHANNELS):
            # Configure analogue input
            fmc.set_input_range(ch+1, IN_RANGE)
            fmc.set_input_term(ch+1, IN_TERM)
            time.sleep(SSR_SET_SLEEP)

        # Set sine params
        sine.frequency = 1E6
        sine.amplitude = 0.8 * ADC_FS[IN_RANGE]
        sine.dc = 0
        print "\nSine generator:\n  frequency: %3.3fMHz\n  amplitude: %2.3fVp\n  offset: %2.3fV" % (sine.frequency/1E6, sine.amplitude, sine.dc)

        # Set AWG
        gen.connect()
        gen.play(sine)
        gen.output = True
        time.sleep(AWG_SET_SLEEP)

        # connect AWG to channel 1
        box.select_output_ch(1)
        time.sleep(BOX_SET_SLEEP)


        ######################################################################################
        # Single shot
        print("\n\n--------------------------------------------------")
        print("Single shot")
        NB_SHOTS = 1
        fmc.set_shots(NB_SHOTS)
        print("\nAcquisition config:\n  shots: %d\n  pre-trigger: %d\n  post-trigger: %d"%(NB_SHOTS, PRE_TRIG_SAMPLES, POST_TRIG_SAMPLES))

        # Perform acquisition and retrieve data from DDR
        trig_pos = make_acq(fmc, ACQ_PAUSE, NB_SHOTS)
        data_length = ((PRE_TRIG_SAMPLES + 1 + POST_TRIG_SAMPLES) * NB_CHANNELS * BYTES_PER_SAMPLE) + TRIG_TIMETAG_BYTES
        start_addr = (trig_pos-(PRE_TRIG_SAMPLES*8))
        acq_data = get_acq_data(carrier, start_addr, data_length)

        # Extract trigger timetag from data
        #  timetag = 4x 32-bit words
        #  acq_data is 16-bit wide -> last 8 cells corresponds to the timetag
        data_trig_tag = []
        tmp_data = []
        for i in range(8):
            tmp_data.append(acq_data.pop(-1))
        for i in range(7,0,-2):
            data_trig_tag.append(((tmp_data[i-1] << 16) + tmp_data[i]))

        print("\nNumber of acquired samples: %d"%(len(acq_data)/4))

        # Get time-tags from registers
        trig_tag = fmc.get_utc_trig_tag()

        # Print timetags
        print("")
        print('Trigger time-tag (from reg)    meta: 0x%08X seconds: 0x%08X coarse: 0x%08X fine: 0x%08X'%(trig_tag[0], trig_tag[1], trig_tag[2], trig_tag[3]))
        print('Trigger time-tag (from data)   meta: 0x%08X seconds: 0x%08X coarse: 0x%08X fine: 0x%08X'%(data_trig_tag[0], data_trig_tag[1], data_trig_tag[2], data_trig_tag[3]))

        # Compare trigger timetag from data and from register
        if trig_tag != data_trig_tag:
            error += 1
            print("\n  ERROR ==> Trigger timetag from data and from register don't match!     ###")
        else:
            print("\n  OK ==> Trigger timetag from data and from register match!")

        # Plot all channels
        #plot_channels(acq_dataq, ADC_FS[IN_RANGE])


        ######################################################################################
        # Multi-shot
        print("\n\n--------------------------------------------------")
        print("Multi-shot")
        NB_SHOTS = 10
        fmc.set_shots(NB_SHOTS)
        print("\nAcquisition config:\n  shots: %d\n  pre-trigger: %d\n  post-trigger: %d"%(NB_SHOTS, PRE_TRIG_SAMPLES, POST_TRIG_SAMPLES))

        # Perform acquisition and retrieve data from DDR
        make_acq(fmc, ACQ_PAUSE, NB_SHOTS)
        shot_length = ((PRE_TRIG_SAMPLES + 1 + POST_TRIG_SAMPLES)*NB_CHANNELS*BYTES_PER_SAMPLE) + TRIG_TIMETAG_BYTES
        data_length = shot_length * NB_SHOTS
        start_addr = 0
        print("\nMake acquisistion:\n  shot length: %d\n  data length: %d"%(shot_length, data_length))
        acq_data = get_acq_data(carrier, start_addr, data_length)

        # split data in shots
        shot_data = []
        shot_length = shot_length/2 # acq_data is a 16-bit word array
        for shot in range(NB_SHOTS):
            shot_start = (shot*shot_length)
            shot_end = ((shot+1)*shot_length)
            shot_data.append(acq_data[shot_start:shot_end])
            print("  shot nb: %2d  shot start: %5d  shot end: %5d -> length: %d"%(shot, shot_start, shot_end-1, len(shot_data[-1])))

        # Extract trigger timetag from data
        #  timetag = 4x 32-bit words
        #  acq_data is 16-bit wide -> last 8 cells corresponds to the timetag
        print("\nExtract trigger timetags:")
        shot_trig_tag = []
        for shot in range(NB_SHOTS):
            data_trig_tag = []
            tmp_data = []
            for i in range(8):
                tmp_data.append(shot_data[shot].pop(-1))
            for i in range(7,0,-2):
                data_trig_tag.append(((tmp_data[i-1] << 16) + tmp_data[i]))
            shot_trig_tag.append(data_trig_tag)
            print("  nb shot %d: nb samples: %d"%(shot,len(shot_data[shot])/4))

        # Get time-tags from registers
        trig_tag = fmc.get_utc_trig_tag()

        # Print all trigger timetags from data
        print("")
        for shot in range(NB_SHOTS):
            print('Trigger time-tag (from data)   meta: 0x%08X seconds: 0x%08X coarse: 0x%08X fine: 0x%08X'%
                  (shot_trig_tag[shot][0], shot_trig_tag[shot][1], shot_trig_tag[shot][2], shot_trig_tag[shot][3]))
        print('Trigger time-tag (from reg)    meta: 0x%08X seconds: 0x%08X coarse: 0x%08X fine: 0x%08X'%
              (trig_tag[0], trig_tag[1], trig_tag[2], trig_tag[3]))

        # Compare trigger timetag from data (last shot) and from register
        if trig_tag != shot_trig_tag[-1]:
            error += 1
            print("\n  ERROR ==> Trigger timetag from data and from register don't match!     ###")
        else:
            print("\n  OK ==> Trigger timetag from data and from register match!")


        ######################################################################################

        # Make sure all switches are OFF
        open_all_channels(fmc)

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

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