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

import sys
import rr
import time
import os
from numpy import *
from pylab import *

from ptsexcept import *

import fmc_adc
import spec_fmc_adc
import calibr_box
import find_usb_tty
import cp210x_eeprom
from PAGE.Agilent33250A import *
from PAGE.SineWaveform import *


"""
test15: Test trigger configurations

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

# 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

NB_CHANNELS = 4

AWG_SET_SLEEP = 0.3
SSR_SET_SLEEP = 0.05
BOX_SET_SLEEP = 0.01
DAC_SET_SLEEP = 0.01

ACQ_TIMEOUT = 10

PRE_TRIG_SAMPLES = 100
POST_TRIG_SAMPLES = 100000
NB_SHOTS = 1

ACQ_LENGTH = 50000 # in samples

ADC_NBITS = 16 # ADC chip is 14 bits, but shifted to 16 bits in the firmware
ADC_FS = 10
DAC_NBITS = 16
DAC_FS = 10 # DAC full scale range is 10V


def load_firmware(default_directory):
    print "Load firmware to FPGA"
    path_fpga_loader = '../../../gnurabbit/user/fpga_loader';
    path_firmware = '../firmwares/spec_fmcadc100m14b4cha.bin';
    firmware_loader = os.path.join(default_directory, path_fpga_loader)
    bitstream = os.path.join(default_directory, path_firmware)
    print firmware_loader + ' ' + bitstream
    os.system( firmware_loader + ' ' + bitstream )
    time.sleep(2);

def disconnect_channels(fmc):
    for i in range(1,NB_CHANNELS+1):
        fmc.set_ssr(i, 0x00)
        time.sleep(SSR_SET_SLEEP)

def fmc_adc_init(spec, fmc):
    print "\nInitialise FMC board"
    # Reset offset DACs
    fmc.dc_offset_reset()
    # Make sure all switches are OFF
    disconnect_channels(fmc)
    # Set acquisition
    fmc.set_pre_trig_samples(PRE_TRIG_SAMPLES)
    fmc.set_post_trig_samples(POST_TRIG_SAMPLES)
    fmc.set_shots(NB_SHOTS)
    #fmc.print_adc_core_config()

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

# Converts signed to two's complement hex
def signed2hex(value):
    if value < 0:
        return (((abs(value) ^ 0xffff) + 1) & 0xffff)
    else:
        return value

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

# Converts volts to digital value with half full range offset
def volt2digital(value, full_scale, nb_bit):
    digital = (value + full_scale/2) * 2**nb_bit/full_scale
    if(digital > 2**nb_bit - 1):
        digital = 2**nb_bit - 1
    if(digital < 0):
        digital = 0
    #print('volt2digital: %2.9f > %2.9f')%(value,digital)
    return int(digital)

# Converts volts to digital value
def volt2digital_without_offset(value, full_scale, nb_bit):
    if(value > (2**nb_bit)/2 - 1):
        value = (2**nb_bit)/2 - 1
    if(value < -((2**nb_bit)/2)):
        value = -((2**nb_bit)/2)
    digital = (value) * 2**nb_bit/full_scale
    #print('volt2digital: %2.9f > %2.9f')%(value,digital)
    return int(digital)

def acq_start(fmc):
    # Make sure no acquisition is running
    fmc.stop_acq()
    # Start acquisition
    fmc.start_acq()

def acq_get_data(spec_fmc, fmc, adc_fs, adc_nbits=16):
    # Wait end of acquisition
    timeout = 0
    print "Acq FSM state: %s"%fmc.get_acq_fsm_state()
    time.sleep(0.1)
    print "Acq FSM state: %s"%fmc.get_acq_fsm_state()
    while('IDLE' != fmc.get_acq_fsm_state()):
        time.sleep(0.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
    spec_fmc.set_irq_en_mask(0x1)
    # Read ACQ_LENGTH samples after the trigger for all channels
    channels_data = spec_fmc.get_data((trig_pos<<3), ACQ_LENGTH*8)
    # Disable "DMA done" iinterrupt
    spec_fmc.set_irq_en_mask(0x0)
    channels_data = [hex2signed(item) for item in channels_data]
    channels_data = [digital2volt(item,adc_fs,adc_nbits) for item in channels_data]
    return channels_data

def plot_ch(data, ylimit, trig_level=0):
    sample = arange(len(data)/4)
    clf()
    trig = [trig_level] * len(sample)
    plot(sample, trig, 'r', label='Trigger level')
    plot(sample, data[0::4], 'b', label='Channel 1')
    #plot(sample, data[1::4], 'g', label='Channel 2')
    #plot(sample, data[2::4], 'c', label='Channel 3')
    #plot(sample, data[3::4], 'm', label='Channel 4')
    ylim(-ylimit-(ylimit/10.0), ylimit+(ylimit/10.0))
    xlim(0, 200)
    grid(which='both')
    legend()
    draw()
    show()
    return 0


def main (default_directory='.'):

    # Load firmware
    load_firmware = raw_input('Do you want to load the firmware? [y,n]')
    if(load_firmware == 'y'):
        # Load firmware to FPGA
        path_fpga_loader = '../../../gnurabbit/user/fpga_loader';
        if('y' == raw_input('Test firmware? [y,n]')):
            path_firmware = '../firmwares/spec_fmcadc100m14b4cha_test.bin';
        else:
            path_firmware = '../firmwares/spec_fmcadc100m14b4cha.bin';
        firmware_loader = os.path.join(default_directory, path_fpga_loader)
        bitstream = os.path.join(default_directory, path_firmware)
        print firmware_loader + ' ' + bitstream
        os.system( firmware_loader + ' ' + bitstream )
        time.sleep(2);

    # Objects declaration
    spec = rr.Gennum() # bind to the SPEC board
    spec_fmc = spec_fmc_adc.CSpecFmcAdc100Ms(spec)
    fmc = fmc_adc.CFmcAdc100Ms(spec)
    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)
    #print "AWG:%s"%awg_tty[0]
    #print "BOX:%s"%box_tty[0]
    gen = Agilent33250A(device=awg_tty[0], bauds=AWG_BAUD)
    sine = SineWaveform()
    box = calibr_box.CCalibr_box(box_tty[0])
    box_eeprom = cp210x_eeprom.CCP210x_Eeprom("%X"%BOX_USB_VENDOR_ID, "%X"%BOX_USB_PRODUCT_ID)

    # Initialise fmc adc
    fmc_adc_init(spec, fmc)

    # Set sine params
    sine.frequency = 1E6
    sine.amplitude = 8
    sine.dc = 0
    print "\nAWG sine frequency:%3.3fMHz amplitude:%2.3fVp offset:%2.3fV"%(sine.frequency/1E6, sine.amplitude, sine.dc)

    # Connect to AWG
    gen.connect()
    gen.play(sine)
    gen.output = True
    gen.sync = True
    time.sleep(AWG_SET_SLEEP)

    # Set UTC
    current_time = time.time()
    utc_seconds = int(current_time)
    fmc.set_utc_second_cnt(utc_seconds)
    print "\nUTC core seconds counter: %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: %d"%fmc.get_utc_coarse_cnt()

    # Connects channel 1 to AWG
    ch = 1
    fmc.set_input_range(ch, '10V')
    fmc.set_input_term(ch, 'ON')
    time.sleep(SSR_SET_SLEEP)
    box.select_output_ch(ch)
    time.sleep(BOX_SET_SLEEP)

    """
    #======================================================================
    # Software trigger
    #======================================================================
    print "\n----------------------------------------\nTest software trigger\n"
    hw_sel   = 0 # hardware trigger selection (0=internal, 1=external)
    hw_pol   = 0 # hardware trigger polarity (0=positive edge/slope, 1=negative edge/slope)
    hw_en    = 0 # hardware trigger enable (bool)
    sw_en    = 1 # software trigger enable (bool)
    int_sel  = 0 # hardware internal trigger channel selection [0..3]
    int_thres= 0 # hardware internal trigger threshold (16-bit signed)
    delay    = 0 # trigger delay in sampling clock ticks
    fmc.set_trig_config(hw_sel, hw_pol, hw_en, sw_en, int_sel, int_thres, delay)
    print "Start acquisition"
    acq_start(fmc)
    print "Wait until acquisition FSM is in WAIT_TRIG"
    while('WAIT_TRIG' != fmc.get_acq_fsm_state()):
        pass
    print "Issue a software trigger"
    fmc.sw_trig()
    print "Get data"
    ch_data = acq_get_data(spec_fmc, fmc, ADC_FS)

    # Plot the acquisition
    #plot_ch(ch_data, (ADC_FS/2))

    #======================================================================
    # Hardware external trigger, positive edge
    #======================================================================
    print "\n----------------------------------------\nTest software trigger\n"
    hw_sel   = 1 # hardware trigger selection (0=internal, 1=external)
    hw_pol   = 0 # hardware trigger polarity (0=positive edge/slope, 1=negative edge/slope)
    hw_en    = 1 # hardware trigger enable (bool)
    sw_en    = 0 # software trigger enable (bool)
    int_sel  = 0 # hardware internal trigger channel selection [0..3]
    int_thres= 0 # hardware internal trigger threshold (16-bit signed)
    delay    = 0 # trigger delay in sampling clock ticks
    fmc.set_trig_config(hw_sel, hw_pol, hw_en, sw_en, int_sel, int_thres, delay)
    print "Start acquisition"
    acq_start(fmc)
    print "Wait for external hardware trigger"
    print "Get data"
    ch_data = acq_get_data(spec_fmc, fmc, ADC_FS)

    # Plot the acquisition
    #plot_ch(ch_data, (ADC_FS/2))

    #======================================================================
    # Hardware external trigger, negative edge
    #======================================================================
    print "\n----------------------------------------\nTest software trigger\n"
    hw_sel   = 1 # hardware trigger selection (0=internal, 1=external)
    hw_pol   = 1 # hardware trigger polarity (0=positive edge/slope, 1=negative edge/slope)
    hw_en    = 1 # hardware trigger enable (bool)
    sw_en    = 0 # software trigger enable (bool)
    int_sel  = 0 # hardware internal trigger channel selection [0..3]
    int_thres= 0 # hardware internal trigger threshold (16-bit signed)
    delay    = 0 # trigger delay in sampling clock ticks
    fmc.set_trig_config(hw_sel, hw_pol, hw_en, sw_en, int_sel, int_thres, delay)
    print "Start acquisition"
    acq_start(fmc)
    print "Wait for external hardware trigger"
    print "Get data"
    ch_data = acq_get_data(spec_fmc, fmc, ADC_FS)

    # Plot the acquisition
    #plot_ch(ch_data, (ADC_FS/2))
    """
    #======================================================================
    # Hardware internal trigger, positive slope
    #======================================================================
    print "\n----------------------------------------\nTest software trigger\n"
    hw_sel   = 0 # hardware trigger selection (0=internal, 1=external)
    hw_pol   = 0 # hardware trigger polarity (0=positive edge/slope, 1=negative edge/slope)
    hw_en    = 1 # hardware trigger enable (bool)
    sw_en    = 0 # software trigger enable (bool)
    int_sel  = ch-1 # hardware internal trigger channel selection [0..3]
    trig_level = 0 # hardware internal trigger threshold (16-bit signed)
    delay    = 0 # trigger delay in sampling clock ticks
    int_thres = volt2digital_without_offset(trig_level, ADC_FS, ADC_NBITS)
    int_thres = signed2hex(int_thres)
    print "int_thres=0x%.4X"%int_thres
    fmc.set_trig_config(hw_sel, hw_pol, hw_en, sw_en, int_sel, int_thres, delay)
    print "Start acquisition"
    acq_start(fmc)
    print "Wait for external hardware trigger"
    print "Get data"
    ch_data = acq_get_data(spec_fmc, fmc, ADC_FS)

    # Plot the acquisition
    plot_ch(ch_data, (ADC_FS/2), trig_level)


    # Print time-tags
    print "\nUTC time-tags:"
    trig_tag = fmc.get_utc_trig_tag()
    start_tag = fmc.get_utc_start_tag()
    stop_tag = fmc.get_utc_stop_tag()
    end_tag = fmc.get_utc_end_tag()
    print "Trigger time-tag   : %10.10f [s]"%(trig_tag[2]+(trig_tag[3]*8E-9))
    print "Acq start time-tag : %10.10f [s]"%(start_tag[2]+(start_tag[3]*8E-9))
    print "Acq stop time-tag  : %10.10f [s]"%(stop_tag[2]+(stop_tag[3]*8E-9))
    print "Acq end time-tag   : %10.10f [s]"%(end_tag[2]+(end_tag[3]*8E-9))

    """
    # print aqcuisition to file
    file_name = raw_input('Enter a file name (default=log_test15.txt):')
    if file_name == "":
        file_name = "log_test15.txt"
    file = open(file_name, 'w')
    file.write("CH1 value, CH2 value, CH3 value, CH4 value\n")
    for i in range(0,len(channels_data),4):
        file.write("%d, %d, %d, %d\n"%(channels_data[i], channels_data[i+1], channels_data[i+2], channels_data[i+3]))
    """

    # Make sure all switches are OFF
    disconnect_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.')


if __name__ == '__main__' :
    main()
