#!   /usr/bin/env   python

# Copyright Creotech SA, 2016
# Author: Marek Guminski <marek.guminski@creotech.pl>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org


import spec
import fmcmasterfip
import utilities as util
import time
import decode_masterfip as decmf
import ptsexcept
import numpy

import string

PLOT=True



def get_message_edges( data, samplesperbit, threshold ):
    
        edges=[]
        idle = True
        cnt = 0
        for i in xrange(len(data)):
            if data[i] > threshold :
                cnt = 2*samplesperbit
                if idle:
                    idle = False
                    edges.append(i)
            elif cnt > 0:
                cnt -= 1
            
            elif not idle:
                idle = True
                edges.append(i)
                
        return edges 
            

def adc_spi_communication( dut ):
    # software reset should be given first
    # its self cleaning, so there is no point in reading this register back
    dut.write_adc_reg_spi(0,0x80)
    
    time.sleep(0.1)
    
    # register 0x4 is used to set test pattern 
    # so anything may be written and read without make any harm
    
    # read default register content 
    reg_default = dut.read_adc_reg_spi(0x4) & 0xff
    
    # write some data 
    dut.write_adc_reg_spi(0x4,0xaa)
    reg_aa = dut.read_adc_reg_spi(0x4) & 0xff
    
    # set other bits
    dut.write_adc_reg_spi(0x4,0x55)
    reg_55 = dut.read_adc_reg_spi(0x4) & 0xff
    
    return [reg_default, reg_aa, reg_55 ]

def test_adc_spi( dut ):
    # disable ADC power and try to talk to it
    # should fail
    dut.adc_power(0)
    [off_reg_default, off_reg_aa, off_reg_55 ] = adc_spi_communication(dut)
    
    # power up device
    dut.adc_power(1)
    # wait a little
    time.sleep(0.5)
    
    
    # power up device
    dut.adc_power(1)
    # wait a little
    time.sleep(0.5)
    
    # repeat SPI test
    # this time it should succeed
    [on_reg_default, on_reg_aa, on_reg_55 ] = adc_spi_communication(dut)
    
    
    # process test results
    result ={} 
    
    # default values are not specified
    # so I'm just saving them in log
    util.info_msg("Value read from ADC register 0x4 during power down: %x" % off_reg_default )
    util.info_msg("Value read from ADC register 0x4 during after power up: %x" % on_reg_default )
    
    # log other read values
    util.info_err_msg("Value read from ADC register 0x4 during power down, after writing 0xaa: %x" % off_reg_aa, off_reg_aa == 0xaa  )
    util.info_err_msg("Value read from ADC register 0x4 during power down, after writing 0x55: %x" % off_reg_55 , off_reg_55 == 0x55 )
    
    util.info_err_msg("Value read from ADC register 0x4 during power up, after writing 0xaa: %x" % on_reg_aa, on_reg_aa != 0xaa )
    util.info_err_msg("Value read from ADC register 0x4 during power up, after writing 0x55: %x" % on_reg_55, on_reg_55 != 0x55 )
    
        
    # verify test results
    result['Writing value 0xAA to disabled ADC over SPI']        = 1 if off_reg_aa       != 0xaa else 0;
    result['Writing value 0x55 to disabled ADC over SPI']        = 1 if off_reg_55       != 0x55 else 0;
    result['Writing value 0xAA to enabled ADC over SPI']         = 1 if on_reg_aa        == 0xaa else 0;
    result['Writing value 0x55 to enabled ADC over SPI']         = 1 if on_reg_55        == 0x55 else 0;
    
    
    return result

def verify_rx_status( stat ):
    
    # assume test failure
    test_result = {
        'Number of bytes'   : 0,
        'CRC'               : 0,
        'Frame size'        : 0,
        'Frame format'      : 0,
        'Preamble'          : 0 
    }
    
    if util.bitvector(stat, 8, 16) != 10:
        util.info_msg("Wrong number of bytes received")
    else:
        test_result['Number of bytes'] = 1
        
    if ( util.bitvector(stat, 3, 5) == 0x1 ) and ( util.bitvector(stat, 2, 2) == 1 ):
        util.info_msg("CRC error")
    else :
        test_result['CRC'] = 1
    
    if ( util.bitvector(stat, 3, 5) == 0x2 ) and ( util.bitvector(stat, 2, 2) == 1 ):
        util.info_msg("Unexpected frame size")
    else :
        test_result['Frame size'] = 1
    
    if util.bitvector(stat, 1, 1) == 0 :
        util.info_msg("Not complete frame received")
    else :
        test_result['Frame format'] = 1
    
    if util.bitvector(stat, 0, 0) == 0 :
        util.info_msg("Preamble not detected")
    else :
        test_result['Preamble'] = 1
    
    
    
    return test_result
    
    
    
def  verify_presence( carrier ):
    util.info_msg("Bus transmission: checking presence")
    
    # Send ID_DAT for presence variable (0x14)
    # nanoFIPdiag address (common to all such modules in CERN): 7F
    carrier.send_id_dat(0x7F14)
        
    # wait for response
    time.sleep(1)
    
    # Read RP_DAT reply from nanoFIPdiag
    [stat, ctrl, data ] = carrier.read_rp_dat()
    
    
    test_result = verify_rx_status( stat )
    
    util.info_msg("Control field in bus response: 0x%x" % ctrl)
    
    # replay should have ctrl value equal to 2
    if ctrl == 2:
        test_result['Data control field'] = 1
    else :
        test_result['Data control field'] = 0
    # presence variable data is always the same
    if util.bitvector(data, 0, 8*6) ==  0x00f00003800550 :
        util.info_msg ("Presence variable OK!")
        test_result['Data field'] = 1
    
    elif (data == 0x0):
        util.info_msg("No WorldFIP traffic")
        test_result['Data field'] = 0
        
    else:
        util.info_msg("Data errors")
        test_result['Data field'] = 0
    
    util.info_msg ("")
    
    return test_result


def wait_acquisition( proc, shouldsucced=True, maxdel = 20 ):
    
    retval = True
    
    for i in xrange(maxdel):
        # wait a while
        time.sleep(0.01)
        
        # check if process terminated
        if proc.poll() != None :
            util.info_err_msg("Acquisition finished after %d ms" % ((i+1)*10), not shouldsucced )
            break
        
        # if acquisition didn't start 
        # after maxdel tries something is wrong
        # kill process and report error
        if i == maxdel-1:
            util.info_err_msg("Acquisition did NOT finish during %d ms" % ( maxdel *10 ), shouldsucced )
            util.info_msg("Killing the process")
            retval = False
            proc.kill()
            break
    
    # communicate with terminated process
    # in order to catch it's output
    [serr, sout] = proc.communicate()
    serr = string.replace(serr, "\n", "\n\t")
    sout = string.replace(sout, "\n", "\n\t")
    # report output
    util.info_msg("Acquisition process returned:")
    util.info_msg("\t%s" % serr)
    util.info_msg("\t%s" % sout)
    
    return retval

def init(topdir = util.TOPDIRNAME, firmpath = util.FIRMWARE_PATH):
    abspath = util.find_prj_topdir(topdir);
    carrier = spec.spec( abspath );
    
    # FPGA bitstream and FMC presence are verified during initialization
    dut     = fmcmasterfip.fmcmasterfip(carrier, abspath, firmpath)
    
    return [carrier, dut, abspath ]

def test_transmission( dut, transmission, should_succed = True, primary = True ):
     
    result={}
    samplesperbit = 20
    customconf = dut.customize_adc_settings(9+13, samplesperbit)
    
    # set up ADC acquisition
    proc = dut.setup_acquisition( customconf );
    
    # send presence variable request
    # echo box should replay
    if transmission:
        result = util.merge_dictionaries( result, verify_presence( dut ) )
    
    # wait for acquisition
    # it acquisition doesn't start in some time
    # waiting will be terminated
    wait_acquisition( proc, should_succed )
        
    # read file with ADC samples
    # make lists of positive, negative and differential transmission samples
    [diff, pos, neg ] = dut.read_adc_samples( )
    
    
    
    if isinstance(diff, int):
        result['Captured ADC transmission' ] = 0
        result['Number of frames captured by ADC' ] = 0
        result['Request message captured by ADC' ] = 0
        result['Response message captured by ADC' ] = 0
    else:    
        result[ 'Captured ADC transmission' ] = 1

###############################################
        util.section_msg("Decoding acquired data")
        util.info_msg("___________Please node that if an error occur in this section raw data should be examined.")
        util.info_msg("___________Error might be caused by data processing errors!")
        
        # make sure that both channels exceed threshold       
        result['Captured data exceed threshold on positive channel'] = 1 if max(pos) > customconf.trigger_threshold else 0
        result['Captured data exceed threshold on negative channel'] = 1 if max(neg) > customconf.trigger_threshold else 0

        # would be good to analyze request and response separately
        # in order do do that i'm looking for samples exceeding trigger threshold
        edges = get_message_edges(pos, samplesperbit, customconf.trigger_threshold)
        
        # algorithm might not be very robust 
        if len(edges) != 4:
            util.err_msg("Could not distinct start and of messages based on samples.")
        else:
            # if it succeeded i should get 4 samples marking start and end of transmission
            # pairs of these samples create message boundaries
            txpos = pos[edges[0]:edges[1]]
            rxpos = pos[edges[2]:edges[3]]
            
            txneg = neg[edges[0]:edges[1]]
            rxneg = neg[edges[2]:edges[3]]
            
            # request frame acquired on primary transformer side has some positive offset
            # detecting this offset should enable to determine whether primary or secondary side was sampled
            # since offset is not well defined it is important to make sure that mean values will be logged 
            util.info_msg("\nMean values of acquired frames:")
            
            util.info_msg("\tRequest frame - Positive Channel: %d" % numpy.mean(txpos) )
            util.info_msg("\tRequest frame - Positive Channel: %d" % numpy.mean(txneg) )
            util.info_msg("\tResponse frame - Positive Channel: %d" % numpy.mean(rxpos))
            util.info_msg("\tResponse frame - Positive Channel: %d" % numpy.mean(rxneg))
            
            util.info_msg("")
            
            # checking if mean value of a message exceed threshold
            # it should be true only for request message sampled on primary side
            # all other messages should have mean value close to 0
            TX_MEAN_THD= 5e3
            if primary:
                result['ADC relays identification based on request message'] = 1 if ( numpy.mean(txpos) > TX_MEAN_THD ) and ( numpy.mean(txneg) > TX_MEAN_THD ) else 0
            else:
                result['ADC relays identification based on request message'] = 1 if ( numpy.mean(txpos) < TX_MEAN_THD ) and ( numpy.mean(txneg) < TX_MEAN_THD ) else 0
            
            result['ADC relays identification based on response message'] = 1 if ( numpy.mean(rxpos) < TX_MEAN_THD ) and ( numpy.mean(rxneg) < TX_MEAN_THD ) else 0
        try:
            decoded = decmf.decode(diff, 5e3, samplesperbit )
        
#################################
            util.section_msg("Verifying decoded data content")
        
            # verify number of captured frames 
            if len(decoded) == 2:
                result['Number of frames captured by ADC' ] = 1
                util.info_msg("Captured two frames with ADC")
            else:
                util.err_msg("Data captured by ADC doesn't contain two frames")
                result['Number of frames captured by ADC' ] = 0
                
            if decoded[0][0:3] == [ 0x3, 0x14, 0x7f]:
                result['Request message captured by ADC' ] = 1
                util.info_msg("Found request message")
            else:
                tmp = ""
                for b in decoded[0][0:3]:
                    tmp += " %x" % b
    
                util.err_msg("Unsuspected request frame found: %s" % tmp)
                result['Request message captured by ADC' ] = 0
            
            if decoded[1][0:8] == [ 0x02, 0x50, 0x05, 0x80, 0x03, 0x00, 0xF0, 0x00 ]:
                result['Response message captured by ADC' ] = 1
                util.info_msg("Found response message")
            else:
                tmp = ""
                for b in decoded[1][0:8]:
                    tmp += " %x" % b
                util.err_msg("Unsuspected response frame found: %s"% tmp)
                result['Response message captured by ADC' ] = 0
            
            util.info_msg("Decoding messages captured by ADC finished successful")
            result['Decoding of messages captured by ADC' ] = 1
        except:
            util.err_msg("Failed to decode wordfip message")
            result['Decoding of messages captured by ADC' ] = 0
            result['Request message captured by ADC' ] = 0
            result['Number of frames captured by ADC' ] = 0
            result['Response message captured by ADC' ] = 0
            
    
    if (result[ 'Captured ADC transmission' ] + result['Number of frames captured by ADC' ] + result['Request message captured by ADC' ] + result['Response message captured by ADC' ]) == 4:
        if should_succed:
            util.info_msg("Test succeeded")
        else:
            util.info_msg("Test failed")
    else:
        if not should_succed:
            util.info_msg("Test succeeded")
        else:
            util.info_msg("Test failed")
    
    return result
    

def main (card=None, default_directory='.',suite=None, serial=""):
    
    testname= "Test03: Power switching, ADC, Relays"
    util.header_msg( testname, [ "ADC powering", 
                                "ADC communication", 
                                "ADC data acquisition", 
                                "ADC relays switching",
                                "ADC front end"] )
    
    
    box = util.calibration_box_init()
    
    util.section_msg("Hardware initialization")
    [carrier, dut, abspath ] =  init();
    
    
#     starttime=datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    prefix="mfdata_"
    starttime=""
    outputpath = util.test03_outputpath
    # make sure that bus communication is possible
    util.section_msg("Bus communication test")
    result = verify_presence( dut );

#####################################################
    # should be called before powering ADC on
    util.section_msg("ADC serial interface test")
    result = util.merge_dictionaries(result, test_adc_spi(dut) )

#####################################################
    
    dut.adc_init();
    
    time.sleep(1)

#####################################################
    # close primary side relay
    
    dut.close_relay(1);
    time.sleep(1)
#####################################################
    
    
    util.section_msg("Sampling WorldFIP bus during normal traffic. Sampling transformer pins connected to LOCAL bus driver.")
    result = util.merge_dictionaries_prefix(result, test_transmission( dut, True, True, True ), "", "Primary transformer side. " )
    dut.save_transmission(outputpath+prefix+starttime+"_primary")

    util.section_msg("Sampling WorldFIP bus WITHOUT traffic. Sampling transformer pins connected to LOCAL bus driver.")
    tmp = test_transmission( dut, False, False, True );
    result["Captured ADC transmission on primary transformer side without without generating any traffic"] = 1 if tmp[ 'Captured ADC transmission'] == 0 else 0
    dut.save_transmission(outputpath+prefix+starttime+"_primary_notraffic")
  
  
  
#####################################################
    # close primary side relay
    dut.close_relay(2);
    time.sleep(1)
    #####################################################
    
    
    util.section_msg("Sampling WorldFIP bus during normal traffic. Sampling transformer pins connected to EXTERNAL bus.")
    result = util.merge_dictionaries_prefix(result, test_transmission( dut, True, True, False ), "", "Secondary transformer side. " )
    dut.save_transmission(outputpath+prefix+starttime+"_secondary")
    
    util.section_msg("Sampling WorldFIP bus WITHOUT traffic. Sampling transformer pins connected to EXTERNAL bus driver.")
    tmp = test_transmission( dut, False, False, False );
    result["Captured ADC transmission on secondary transformer side without generating any traffic"] = 1 if tmp['Captured ADC transmission'] == 0 else 0
    dut.save_transmission(outputpath+prefix+starttime+"_secondary_notraffic")
  
   
#####################################################
    # disconnect both relays
    dut.close_relay(0);
    time.sleep(1)
#####################################################

    util.section_msg("Sampling WorldFIP bus during normal traffic. ADC is disconnected from transformer!")
    tmp = test_transmission( dut, True, False, False );
    result["Captured ADC transmission when ADC should be disconnected"] = 1 if tmp[ 'Captured ADC transmission'] == 0 else 0
    dut.save_transmission(outputpath+prefix+starttime+"_disconnected")
    
    
    util.section_msg("Sampling WorldFIP bus without traffic. ADC is disconnected from transformer!")
    tmp = test_transmission( dut, False, False, False );
    result["Captured ADC transmission when ADC should be disconnected and no traffic is generated"] = 1 if tmp[ 'Captured ADC transmission'] == 0 else 0
    dut.save_transmission(outputpath+prefix+starttime+"_disconnected_notraffic")

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

    errors = util.summarise_test_results( testname, result)
    
    util.info_msg("Please note that data acquired by ADC was backed up in log/adcdata directory")
    return 0
        
if __name__ == '__main__' :
    main()
