Program Fact 5. To Run a Test Program

The following Python script runs an existing Test Program. The script shows that the :JOBS SCPI subsystem is used to return and parse the measurement results. Notice that the visa address selects hislip1 for Station 1.

This Test Program runs without modification regardless of the measurements performed, settings selected , impairments used, or if user instruments measurements are included.

Copy
#******************************************************************************
#    MIT License
#    Copyright(c) 2023 Keysight Technologies
#    Permission is hereby granted, free of charge, to any person obtaining a copy
#    of this software and associated documentation files (the "Software"), to deal
#    in the Software without restriction, including without limitation the rights
#    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#    copies of the Software, and to permit persons to whom the Software is
#    furnished to do so, subject to the following conditions:
#    The above copyright notice and this permission notice shall be included in all
#    copies or substantial portions of the Software.
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#    SOFTWARE.
#******************************************************************************

import pyvisa as visa  # import VISA library
visa_address = 'TCPIP0::localhost::hislip1,4880::INSTR'  # Address of Station 1
STATIONNUM = '1'  # Station number to test


class Station(object):
    """ Represents a Station child process. """
    def __init__(self, visa_manager, number):
        self.app = visa_manager
        self.number = str(number)  # Station number shown on FlexOTO GUI Tab
        self.job_ids = list()  # Job IDs returned by ":TPRogram:RUN?" command


def open_station_connection(address, station_number):
    """ Returns a FlexOTO station visa object. """
    rm = visa.ResourceManager()
    connection = rm.open_resource(address)
    connection.timeout = 20000  # Set connection timeout to 20s
    connection.read_termination = '\n'
    connection.write_termination = '\n'
    station = Station(connection, station_number)
    print('VISA connection to Session ' + station_number + ' established.', flush=True)
    return station


def run_station(station):
    """ Runs Session and saves a list of Job IDs in station object. Job IDs are used later to identify
    measurement results. """
    job_ids = station.app.query(':TPRogram:RUN?').split(',')
    print('Session ' + station.number + ' Test Program started. Acquiring and analyzing data. Please wait.', flush=True)
    return job_ids


def monitor_station(station):
    """ Returns after all measurement acquisitions and analysis is complete. """
    print('Waiting for Session ' + station.number + ' standard measurements to complete...')
    station.app.query(':JOBS:ACQuire:COMPlete?')
    station.app.query('*OPC?')  # Wait for all jobs to be done with analysis.
    return


def get_jobid_measurement_units(station, job):
    """ """
    tokens = {'WATT': 'W', 'DBM': 'dBm', 'DEC': 'dB', 'PERC': '%', 'RAT': ': 1'}
    punits = tokens[station.app.query(':JOBS:CONFig:PUNits? ' + job)]
    erunits = tokens[station.app.query(':JOBS:CONFig:ERUNits? ' + job)]
    return punits, erunits


def add_measurement_units_to_result(meas, punits, erunits):
    """ Measurement units are returned for all related measurement in the same Job ID. If you want different units
     in two related commends (for example, average and peak-peak powers), put the measurement on different
     Test Program lines which results in different Job IDs. """
    measurement = meas['Value']
    if 'TDECQ' in meas['Name']:
        measurement += ' dB'
    elif 'Ceq' in meas['Name']:
        measurement += ' dB'
    elif 'Outer OMA' in meas['Name']:
        measurement += ' ' + punits
    elif 'Outer ER' in meas['Name']:
        measurement += ' ' + erunits
    elif 'RLM (802.3 A_120D)' in meas['Name']:  # Linearity
        measurement += ''
    elif 'RLM (802.3 CL_94)' in meas['Name']:  # Linearity
        measurement += ''
    elif 'RLM (CEI)' in meas['Name']:  # Linearity
        measurement += ''
    elif 'Level 0' in meas['Name']:
        measurement += ' W'
    elif 'Level 1' in meas['Name']:
        measurement += ' W'
    elif 'Level 2' in meas['Name']:
        measurement += ' W'
    elif 'Level 3' in meas['Name']:
        measurement += ' W'
    elif 'Trans. Time' in meas['Name']:
        measurement += ' ps'
    elif 'Overshoot' in meas['Name']:
        measurement += ' %'
    elif 'Undershoot' in meas['Name']:
        measurement += ' %'
    elif 'Power Excursion' in meas['Name']:
        measurement += ' ' + punits
    elif 'Average Power' in meas['Name']:
        measurement += ' ' + punits
    elif 'Pk-Pk Power' in meas['Name']:
        measurement += ' ' + punits
    return measurement


def get_results_for_Job_ID(station, job):
    """ For Job ID, returns a measurement results string and converts the string to a list of
    measurement results dictionaries. Fixture/lane dict followed by one dict for each measurement.
    Convert:
        'Fixture=DUT Fixture 1,Lane=Lane 4;Name=TDECQ,Value=1E-2,Status=Correct;'
    To:
        [{'Fixture': 'DUT Fixture 1','Lane': 'Lane 4'},
        {'Name': 'TDECQ','Value': '1E-2, 'Status':, 'Correct'},
        { ...}]
    """
    meas_list = station.app.query(':JOBS:RESults? ' + job).split(';')
    for i in range(0, len(meas_list)):
        measurement_fields = meas_list[i].split(',')  # Results list of one measurement
        for n in range(0, len(measurement_fields)):
            measurement_fields[n] = measurement_fields[n].split('=')  # eg, ['Name','TDECQ']
        meas_list[i] = dict(measurement_fields)  # create dictionary with Name, Value, and Status keys
    measurements = list()
    for meas in meas_list:  # Remove non-measurements from results
        values = meas.values()
        if ('Waveform' in values) or ('Eye Diagram' in values):
            continue
        measurements.append(meas)
    return measurements


def print_results_for_Job_ID(station, job, measurements):
    """ Prints results for Job ID. """
    user_meas_names = {'User Meas1', 'User Meas2'}
    punits, erunits = get_jobid_measurement_units(station, job)
    fixture_lane = measurements.pop(0)
    fixture = fixture_lane['Fixture']
    lane = fixture_lane['Lane']
    print('\n\tJob: ' + job + ',\tFixture: "' + fixture + '"\t"' + lane + '"')
    print()
    for meas in measurements:
        meas_with_units = add_measurement_units_to_result(meas, punits, erunits)
        if 'Correct' in meas['Status']:
            print('\t\t' + fixture + ', ' + lane + ': ' + meas['Name'] + ': ' + meas_with_units)
        else:
            print('\t\t' + fixture + ', ' + lane + ': "' + meas['Status'] + '"')
            if meas['Name'] not in user_meas_names:
                aengine = station.app.query(':JOBS:RESults:INFO:AENGine? ' + job)
                amodule = station.app.query(':JOBS:RESults:INFO:AMODule? ' + job)
                print('\t\t\tAcquisition Engine: ' + aengine + ' with ' + 'Acquisition Module: ' + amodule)


def save_all_results_to_file(station):
    """ Saves all results in zip files. """
    station.app.write(':DISK:RESults:SAVE:SELection ALL')
    filename = 'Results_Session' + station.number
    station.app.write(':DISK:RESults:FNAMe "' + filename + '"')
    station.app.write(':DISK:RESults:SAVE')
    print('\n\tSession ' + station.number + ' zip results file: ' + filename + '.zip')


def rerun_test_plan():
    """ Prompt user for another Test Plan run. """
    answer = input('\nRun the Test Plane again (y/n)?: ').lower()
    if 'y' in answer:
        return True
    else:
        return False


def remove_test_results(station):
    """ Removes test results. """
    station.app.write(':JOBS:RESults:REMove:ALL')


def close_station(station):
    """ Places Session1 and Primary in local mode. """
    station.app.write(':SYSTem:GTLocal')
    station.app.close()


# main loop
tst_station = open_station_connection(visa_address, STATIONNUM)
while True:
    tst_station.job_ids = run_station(tst_station)  # returns new Job IDs
    monitor_station(tst_station)
    print('\nStation ' + tst_station.number + ' measurements:')
    for job in tst_station.job_ids:
        meas_list = get_results_for_Job_ID(tst_station, job)
        print_results_for_Job_ID(tst_station, job, meas_list)
    save_all_results_to_file(tst_station)
    if not rerun_test_plan():
        break
    remove_test_results(tst_station)
close_station(tst_station)