Saves Results table to an HTML file

This Python example, results_table_to_html.py, returns the values shown on FlexDCA's Results table (Jitter and Amplitude tables in Jitter Mode) and creates an HTML topic that contains the results. You can open this file in your web browser or import the HTML file into Microsoft Excel. The file is saved in the script's folder. The script uses the :MEASure:RESults? query to return the values. The script's Measurement() class encapsulates the entry fields for each measurement in FlexDCA's results table. The following table is an example of the output HTML file which was created from a waveform from an N1090A DCA-M:

Algorithm

  1. Open a FlexDCA session via LAN.
  2. Identifies FlexDCA and the FlexDCA's mode. Returns the data from the Results or Jitter and Amplitude tables. The returned tale data is a list.
  3. Replaces abbreviations in the data and marks any "9.91E+37" results as invalid.
  4. Creates a Measurement object for each measurement with properties for each item in the results table.
  5. Converts each number string for each table entry from scientific notation to engeneering notation and adds the measurement units.
  6. Creates an HTML topic with CSS styles.
  7. Creates the headings for an HTML table.
  8. Creates the data rows in an HTML table.
  9. Writes the HTML string to an HTML file nameed for the instrument mode.
  10. Close the FlexDCA session.

Example Script

Copy

MEAS-RESults-to-html.py

# -*- coding: utf-8 -*-
""" Controls FlexDCA or DCA-X from PC). This script opens a connection to
FlexDCA and installs a simulated module.
With a waveform and measurements viewed on FlexDCA's screen, this script:
    1. Uses :MEAS:RES? to return all measurements.
    2. Creates an HTML topic that contains the results.
    3. Places html topic in the script's folder.
    4. You can open and view the HTML topic in a web browser or import the
        topic into Microsoft Excel.
"""
import pyvisa as visa  # import VISA library

ADDRESS = 'TCPIP0::localhost::hislip0,4880::INSTR'
#  ADDRESS = 'TCPIP0::K-86100D-00003::hislip0,4880::INSTR'  # DCA-X
CHANNEL = '5A'
CHANNEL2 = '5B'
FILENAME = 'Eye Mode Measurements.html'


class Measurement(object):
    """ Class that represents the returned results for one measurement.
    The HTML table headings are also included for easy reference when
    modifying the script for different results.
    """
    def __init__(self):
        """ """
        self.name = ''  # name of measurement
        self.source = ''
        self.current = ''
        self.min = ''
        self.max = ''
        self.count = ''
        self.unit_of_meas = ''


def open_flexdca_connection(address):
    """ Opens visa connection to FlexFlexDCA. """
    print('Connecting to Flexdca ...')
    try:
        rm = visa.ResourceManager()
        connection = rm.open_resource(address)
        connection.timeout = 20000  # Set connection timeout to 20s
        connection.read_termination = '\n'
        connection.write_termination = '\n'
        inst_id = connection.query('*IDN?')
        print('\nFlexDCA connection established to:\n' + inst_id, flush=True)
    except (visa.VisaIOError, visa.InvalidSession):
        print('\nVISA ERROR: Cannot open instrument address.\n', flush=True)
        return None
    except Exception as other:
        print('\nVISA ERROR: Cannot connect to instrument:', other, flush=True)
        print('\n')
        return None
    return connection


def all_channels_off(flexdca):
    """ Turns all available channels off. """
    for slot in '12345678':
        for letter in 'ABCD':
            channel = slot + letter
            flexdca.write(':CHANnel' + channel + ':DISPlay OFF')


def install_simulated_module(flexdca, channel, model, signal='NRZ'):
    """ Simplified installation of a simulated FlexDCA module. """
    slot = channel[0]
    flexdca.write(':EMODules:SLOT' + slot + ':SELection ' + model)
    if signal in 'NRZ':
        flexdca.write(':SOURce' + channel + ':FORMat NRZ')
    else:
        flexdca.write(':SOURce' + channel + ':FORMat PAM4')
    flexdca.write(':SOURce' + channel + ':DRATe 9.95328E+9')
    flexdca.write(':SOURce' + channel + ':WTYPe DATA')
    flexdca.write(':SOURce' + channel + ':PLENgth 127')
    flexdca.write(':SOURce' + channel + ':AMPLitude 90E-3')
    flexdca.write(':SOURce' + channel + ':NOISe:RN 3.0E-6')
    flexdca.write(':SOURce' + channel + ':JITTer:RJ 4.0E-12')
    flexdca.write(':CHANnel' + channel + ':DISPlay ON')


def configure_FlexDCA(flexdca, channel, channel2):
    """ Installs a simulated module and prepares FlexDCA for
    measurements.
    """
    flexdca.query(':SYSTem:DEFault;*OPC?')
    all_channels_off(flexdca)
    install_simulated_module(flexdca, channel, 'DEM')
    flexdca.write(':SOUR' + channel2 + ':FILTer:CUTOFF 2.5E+10')
    flexdca.write(':CHAN' + channel + ':DISPlay ON')
    flexdca.write(':CHAN' + channel2 + ':DISPlay ON')
    flexdca.write(':ACQuire:RUN')
    flexdca.write(':SYSTem:MODE EYE')
    flexdca.query(':SYSTem:AUToscale;*OPC?')
    flexdca.query(':TRIGger:PLOCk ON;*OPC?')  # pattern lock on


def waveform_acquisition(flexdca):
    """ Perform a pattern acquisition limit test to capture
    waveform data.
    """
    flexdca.query(':SYSTEM:AUToscale;*OPC?')
    flexdca.write(':ACQuire:STOP')  # single acquisition mode
    flexdca.write(':ACQuire:CDISplay')  # Clear display
    flexdca.write(':LTESt:ACQuire:CTYPe:PATTerns 200')
    flexdca.write(':LTESt:ACQuire:STATe ON')
    flexdca.query(':ACQuire:RUN;*OPC?')
    flexdca.write(':LTESt:ACQuire:STATe OFF')


def select_eye_measurements(flexdca, channel, channel2):
    """ Populate FlexDCA's Results table with Eye measurements. """
    flexdca.write(':MEASure:EYE:LIST:CLEar')
    flexdca.write(':MEASure:EYE:RISetime')
    flexdca.write(':MEASure:EYE:FALLtime')
    flexdca.write(':MEASure:EYE:OLEVel')
    flexdca.write(':MEASure:EYE:ZLEVel')
    flexdca.write(':MEASure:EYE:EWIDth:FORMat TIME')
    flexdca.write(':MEASure:EYE:EWIDth')
    flexdca.write(':MEASure:EYE:EHEight')
    flexdca.write(':MEASure:EYE:AMPLitude')
    flexdca.write(':MEASure:EYE:DELTatime:SOURce1 CHAN' + channel)
    flexdca.write(':MEASure:EYE:DELTatime:SOURce2 CHAN' + channel2)
    flexdca.write(':MEASure:EYE:DELTatime')
    flexdca.write(':MEASure:EYE:BITRate')
    flexdca.write(':DISPlay:TSMode FSIZe')


def get_measurement_results(flexdca):
    """ Returns all scalar measurements that are listed in FlexDCA's
    results panel.
    """
    return flexdca.query(':MEASure:RESults?')


def replace_abbreviations(data):
    """ Returned results can contain abbreviations that will be replaced by
    more descriptive text. δ is HTML entity for Greek letter.
    """
    corrections = {'9.91E+37': '<em>NaN (Invalid)</em>',
                   'NLIM': '<em>Not Limited</em>',
                   'delta - delta': '&delta;&ndash;&delta;',
                   'DELTA': '&Delta;'}
    for key, val in corrections.items():
        data = data.replace(key, val)
    return data


def create_measurement_objects(data):
    """ Creates a list of measurement objects. Each object encapsulates
    the properties of each measurement. The input data string looks
    like:
    'Name=Rise Time,Source=5A,Current=2.496E-1,...Name=Fall Time...'
    """
    # split raw data to measurement results list
    list_all_measurements = data.replace(',Name=', '\rName=').split('\r')
    # instantiate measurement objects
    measurements = []
    for meas in list_all_measurements:
        meas_object = Measurement()
        list_single_meas_data = meas.split(',')  # eg. ['Name=Rise Time',...]
        for item in list_single_meas_data:  # eg. current value
            param, value = item.split('=')
            if 'Name' in param:
                meas_object.name = value
            elif 'Source' in param:
                meas_object.source = value
            elif 'Current' in param:
                meas_object.current = value
            elif 'Min' in param:
                meas_object.min = value
            elif 'Max' in param:
                meas_object.max = value
            elif 'Count' in param:
                meas_object.count = value
        measurements.append(meas_object)
    return measurements


def find_unit_of_measure(flexdca, measurements):
    """ For each measurement object, initialize the unit_of_meas
    variable.
    """
    units = {'Pulse': 's', 'Time': 's', 'Jitter': 's', 'TJ': 's',
             'DJ': 's', 'RJ': 's', 'J1': 's', 'J2': 's', 'J3': 's',
             'J4': 's', 'J5': 's', 'J6': 's', 'J7': 's', 'J8': 's',
             'J9': 's', 'Skew': 's', 'Period': 's', 'T[': 's',
             'Ampl[': 's', 'Level': 'V', 'Height': 'V', 'Maximum': 'V',
             'Minimum': 'V', 'Eye Ampl': 'V', 'Ampl at Lower': 'V',
             'Ampl at Middle': 'V', 'Ampl at Upper': 'V',
             'Average Power': 'V', 'TI': 'V', 'DI': 'V', 'RN': 'V',
             'Peak': 'V', 'RMS': 'V', 'Avg': 'V', 'Power': 'W',
             'OMA': 'W', 'Top': 'W', 'Base': 'W', 'Ratio': '', 'SNR': '',
             'Linearity': '', '%': '%', 'Duty Cycle': '%',
             'Overshoot': '%', 'Preshoot': '%', 'Bit Rate': 'b/s',
             'Hits': 'hits', 'Ext. Ratio': 'dB', 'TDEC': 'dB'}
    for meas in measurements:
        for key in units.keys():
            if key in meas.name:
                meas.unit_of_meas = units[key]
                break
        if meas.unit_of_meas == 'V':
            if meas.source[0] in '12345678':  # not a differential channel
                yunits = ':CHANnel' + meas.source[:2] + ':YUNits?'
                if flexdca.query(yunits) == 'WATT':
                    meas.unit_of_meas = 'W'  # for an optical input channel
            if meas.source[0] in 'T':  # trace not channel
                if 'Percent' in meas.source:
                    meas.unit_of_meas = '%'  # for an optical input channel
                elif 'Ohms' in meas.source:
                    meas.unit_of_meas = '&Omega;'
    return measurements


def convert_meas_values(measurements):
    """ For each measurement object, initialize the unit_of_meas
    variable.
    """
    DELTAU = '\u0394'  # Δ
    for meas in measurements:
        if 'Bit Rate' in meas.name:
            resolution = '1.000'
        else:
            resolution = '1.00'
        if '&Delta; Time' in meas.name:
            name = DELTAU + ' Time'
        else:
            name = meas.name
        print('{} on {}: {}'.format(name, meas.source, meas.current, 'E'))
        meas.current = convert_number(meas.current, resolution)
        meas.current += meas.unit_of_meas
        meas.min = convert_number(meas.min, resolution)
        meas.min += meas.unit_of_meas
        meas.max = convert_number(meas.max, resolution)
        meas.max += meas.unit_of_meas
    return measurements


def eng_notation(numstring, resolution):
    """  Converts a string in scientific notation to engineering notation.
     Unit multiplier character is appended to in final return string.
    numstring
        Number string for conversion. For example, '12.3e-4'.
    resolution
        An real that indicates number of decimal places in
        the result. For example, '0.01' would give '1.23 m'
    Returns str representing number with multiplier character.
    """
    import decimal as dc
    MU = '\u03BC'  # μ
    if numstring in '9.91E+37':
        return ''
    dc.getcontext().prec = 10  # prevent limiting of decimal places
    multiplier = {12: 'T', 9: 'G', 6: 'M', 3: 'k', 0: '', -3: 'm',
                  -6: MU, -9: 'n', -12: 'p', -15: 'f'}
    numberReal = dc.Decimal(numstring)
    # absolute value is not between 0 and 1 (fraction)
    if abs(numberReal) >= 1:
        exponentMult = 0
        while int(numberReal) != 0:
            numberReal = numberReal / 1000
            exponentMult += 1
        numberReal *= 1000
        exponentMult -= 1
    elif (abs(numberReal) > 0) and (abs(numberReal) < 1):  # fraction
        exponentMult = 0
        while int(numberReal) == 0:
            numberReal = numberReal * 1000
            exponentMult += 1
        exponentMult *= -1
    elif numberReal == 0:  # number must be zero
        exponentMult = 0
    exponentMult *= 3
    if exponentMult == -15:
        n = numberReal.quantize(dc.Decimal('1'))
    else:
        n = numberReal.quantize(dc.Decimal(str(resolution)))
    return str(n) + ' ' + multiplier[exponentMult]


def convert_number(number, resolution):
    try:
        float(number)  # test if float
        return eng_notation(number, resolution)
    except ValueError:
        return number


def create_html_topic(fout):
    """ Creates head of HTML topic and opening tags for the measurement
    table.
    """
    html_intro = '<!DOCTYPE html>\n\
<html lang="en">\n\
<head>\n\
<meta charset="utf-8">\n\
<style type="text/css">\n\
\tbody, table {font: 12px Arial;}\n\
\tcaption {font: 14px Arial;font-weight: bold;}\n\
\tth, td {padding: 5px 10px 5px 10px;}\n\
\ttable, th, td {border: 1px solid #000;}\n\
\tth {border-bottom: 3px solid #000;}\n\
\ttr.striped {background-color: #e9f2d5;}\n\
\tem {color: red;}\n\
</style>\n\
<title>Eye Mode Measurements</title>\n\
</head>\n\n\
<body>\n\
<table>\n\
<caption>Results of Eye Mode Measurements</caption>\n'
    fout.write(html_intro)


def create_html_table_heading(fout, measurements):
    """ Creates the HTML table's heading row.
    """
    titles = ['Measurement', 'Source', 'Current', 'Minimum',
              'Maximum', 'Count']
    html_table_head = '<tr>'
    for item in titles:
        html_table_head += '<th>' + item + '</th>'
    html_table_head += '</tr>\n'
    fout.write(html_table_head)


def create_html_table_entries(fout, measurements):
    """ Creates HTML table rows with data. The background of every other row
    is colored for easier viewing. This is accomplished using a CSS class.
    """
    html_table = ''
    row = 0  # row number used to color alternate rows
    for meas in measurements:
        if row % 2:
            html_table += '<tr>'
        else:
            html_table += '<tr class="striped">'  # CSS to color table row
        html_table += '<td>' + meas.name + '</td>'
        html_table += '<td>' + meas.source + '</td>'
        html_table += '<td>' + meas.current + '</td>'
        html_table += '<td>' + meas.min + '</td>'
        html_table += '<td>' + meas.max + '</td>'
        html_table += '<td>' + meas.count + '</td>'
        html_table += '</tr>\n'
        row += 1
    html_table += '</table>\n\n</body>\n</html>'
    fout.write(html_table)


FlexDCA = open_flexdca_connection(ADDRESS)
configure_FlexDCA(FlexDCA, CHANNEL, CHANNEL2)
waveform_acquisition(FlexDCA)
select_eye_measurements(FlexDCA, CHANNEL, CHANNEL2)
data = get_measurement_results(FlexDCA)
data = replace_abbreviations(data)
measurements = create_measurement_objects(data)
measurements = find_unit_of_measure(FlexDCA, measurements)
measurements = convert_meas_values(measurements)
fout = open(FILENAME, 'wt')
create_html_topic(fout)
create_html_table_heading(fout, measurements)
create_html_table_entries(fout, measurements)
FlexDCA.write(':SYSTem:GTLocal')
fout.close()
print('\n\nFile created in the script\'s folder:')
print('    "' + FILENAME + '"\n')
print('Open the file in web browser. Or,\n import into Microsoft Excel.')