FlexEye testing: returning stdout from subprocess

This Python example demonstates using FlexEye independent acquisition. The example works with FlexDCA offline and installs simulated modules. No hardware is required. For this example, two programs are used. One program is used to launch another program as a child process. The child process performs a measurement on FlexEye sessions. To work, both programs must be located in the same folder. From the sidebar shown in this topic, you must save these two files:

  • FlexEye_stdout.py (parent process), or
  • FlexEye_stdout_child.py (child process)

The child process, subprocess_stdout.py, is run on six simulated FlexEye channels using Python's standard subprocess library.

The child process acquires 64 waveforms and performs both eye width and eye height measurements on the eye. This example shows you how to perform these tasks:

  • The child process returns the eye width and eye height measurements using stdout, which may be of value to you in some instances.
  • The parent process uses the communicate() method to pause its execution and read stdout from each child process. This allows the parent process to calculate the total time required to complete all child processes, which you can compare to the time required by one measurement which takes about 9 seconds.

Parent Script (FlexEye_stdout.py)

When all child processes complete, the view_each_session() function automatically selects each FlexEye Station tab for a few seconds. This allows you to view each Station's waveform and Results table.

Copy

FlexEye_stdout.py

# -*- coding: utf-8 -*-
""" Launches subprocess: FlexEye_stdout_child.py
Demonstrates FlexEye independent eye acquisition using FlexDCA
with a simulated module. Each channel is run as an independent sampling
oscilloscope. This program:
- Demonstrates FlexEye Parallel Eye Analysis.
- Runs FlexEye_stdout_child.py script on each Session.
- Child processes returns stdout to notify parent that child has
  finished and return measurement results.
- FlexDCA is used offline on a PC.
- Simulated modules are automatically installed in slots 5 and 6.
- The "sessions" dictionary, that is near the top of the script,
defines the FlexEye sessions to be created. Dictionary keys represent
FlexEye session numbers and the dictionary values represents
valid channels to measure for the session. The assignments show
the variety of channel combinations allowed for sessions.
- Each session will run as independent oscilloscope.
- Each session is automatically displayed. """

import subprocess
import sys
import time
import pyvisa as visa  # import VISA library

ADDRESS = 'TCPIP0::localhost::hislip0,4880::INSTR'
IOTIMEOUT = 60000  # 60 seconds
SLOT5 = '5'
SLOT6 = '6'
# FlexEye channel assignments for sessions
sessions = {1: '5A', 2: '5B', 3: '5C', 4: '5D', 5: '6A', 6: '6B',
            7: '', 8: '', 9: '', 10: '', 11: '', 12: '', 13: '', 14: '', 15: ''}


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 initialize_FlexDCA(flexdca):
    """ Place FlexDCA in a know starting state.
    Checks to see if FlexEye is already running. If so exit FlexEye. Perform
    a default setup.
    """
    flexeye_state = flexdca.query(':FEYE:STATe?')
    if 'RUN' in flexeye_state:
        flexdca.query(':FEYE:STATe PAUSe;*OPC?')
        flexdca.query(':FEYE:STATe STOP;*OPC?')
    elif 'PAUSe' in flexeye_state:
        flexdca.query(':FEYE:STATe STOP;*OPC?')
    flexdca.write('*CLS')
    flexdca.query(':SYSTem:DEFault;*OPC?')
    flexdca.query(':SYSTem:MODE EYE;*OPC?')


def install_simulated_module(flexdca, slot):
    """ Installs a simulated module in the first available slot.
    Second argument selects type of module based on arguments to
    :EMODules:SLOT:SELection command. If argument is missing, a
    quad-electrical module is installed. Returns a slot number (string). '0'
    if no available slots.
    """
    flexdca.write(':EMODules:SLOT'+slot+':SELection QEM')
    for c in ['A', 'B', 'C', 'D']:
        channel = slot + c
        flexdca.write(':SOURce'+channel+':FORMat NRZ')
        flexdca.write(':SOURce'+channel+':DRATe 9.95328e9')
        flexdca.write(':SOURce'+channel+':WTYPe DATA')
        flexdca.write(':SOURce'+channel+':PLENgth 127')
        # 90 mV amplitude
        flexdca.write(':SOURce'+channel+':AMPLitude 90E-3')
        # Add 3 uV random noise
        flexdca.write(':SOURce'+channel+':NOISe:RN 3.0E-6')
        # Add 4 ps random jitter
        flexdca.write(':SOURce'+channel+':JITTer:RJ 4.0E-12')
        print('Simulated module installed in slot ' + slot, flush=True)
        return slot


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 configure_FlexEye(flexdca, sessions):
    """ Configures each FlexEye session to the state defined in the
    sessions dictionary. Then, turns FlexEye on. This populates FlexDCA's
    FlexEye Streaming Setup dialog.
    """
    print('Starting FlexEye. Please wait...', flush=True)
    for session in sessions:  # iterate through 15 session keys
        # create iterable list of channels from comma separated string
        channels = sessions[session].split(',')
        if channels[0]:  # if channels list has channels
            for ch in channels:  # run through channel tupple
                if ch[0] in 'D':  # diff channel, eg, D1A
                    s = ch[1:3]   # eg, 1A
                    flexdca.query(':DIFF'+s+':DMODe ON;*OPC?')
                    flexdca.write(':SOURce'+s+':DIFFerential ON')
                    flexdca.write(':DIFF'+s+':DISPlay ON')
                    flexdca.write(':FEYE:CHANnel' + s + ':SID SESSion' +
                                  str(session))
                    flexdca.write(':FEYE:DIFF' + s + ':ENABled ON')
                else:  # configure channel, eg, 1A
                    flexdca.write(':CHANnel' + ch + ':DISPlay ON')
                    flexdca.write(':FEYE:CHANnel' + ch + ':SID SESSion' +
                                  str(session))
                    flexdca.write(':FEYE:CHANnel' + ch + ':ENABled ON')
    flexdca.query(':FEYE:STATe RUN;*OPC?')
    print('Sessions are configured.', flush=True)
    sys.stdout.flush()  # Flush print message in stdout


def run_child_processes(primary_address, sessions):
    """ Starts the FlexEye child process for each session. Creates
    the session VISA address based on the address of the primary
    process. Passes the address and list of session channels as
    arguments to the child process.
    """
    print('Starting child processes.\n')
    child_processes = []  # list of child processes
    command = ['python', 'FlexEye_stdout_child.py', '', '']
    for session in sessions:  # iterate through 15 session keys
        channel = sessions[session]  # comma separated string of channels
        if channel:  # if valid session with channels
            command[2] = primary_address.replace('hislip0',
                                                 'hislip' + str(session))
            command[3] = channel
            p = subprocess.Popen(command,
                                 stdout=subprocess.PIPE,
                                 universal_newlines=True)
            child_processes.append(p)
    return child_processes


def show_results_of_processes(child_processes):
    """ Wait for each child process to end. Reads the stdout message string
    from each child process and prints measurement results.
    The parent process waits for all results.
    """
    for processes in child_processes:
        out = processes.communicate()[0]
        s1, s2, s3 = out.split(',')
        print('Channel ' + s1 + ' measurements:')
        print('   Eye Width: ' + s2)
        print('   Eye Height: ' + s3, flush=True)
        sys.stdout.flush()  # Flush print message in stdout


def report_run_time(start_time):
    """ Calculates total time for all child processes to run.
    """
    process_time = int(time.time() - start_time)
    m, s = divmod(process_time, 60)
    print('Time required for all child processess to complete was:\n' +
          '    ' + str(m) + ' minutes, ' + str(s) + ' seconds.', flush=True)


def view_each_session(flexdca):
    """ Automatically briefly displays each session.
    """
    print('\nAutomatically displaying each FlexEye tab.', flush=True)
    flexdca.write(':FEYE:VIEW PRIMary')
    time.sleep(2)
    for session in range(1, 7):
        flexdca.write(':FEYE:VIEW SESSion' + str(session))
        time.sleep(3)


FlexDCA = open_flexdca_connection(ADDRESS)
FlexDCA.timeout = IOTIMEOUT
initialize_FlexDCA(FlexDCA)
install_simulated_module(FlexDCA, SLOT5)
install_simulated_module(FlexDCA, SLOT6)
all_channels_off(FlexDCA)
configure_FlexEye(FlexDCA, sessions)
start_time = time.time()
processes = run_child_processes(ADDRESS, sessions)
show_results_of_processes(processes)
report_run_time(start_time)
view_each_session(FlexDCA)
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()
print('Program finished.')

Child Script (FlexEye_stdout_child.py)

A print() statement is used to send the eye width and eye height measurements back to the parent process.

Copy

FlexEye_stdout_chld.py

# -*- coding: utf-8 -*-
""" Subprocess: FlexEye_stdout_child.py launched by FlexEye_stdout.py
This is an example of FlexEye independent acquisitions.
The eye width and eye height are returned as a string to the calling program
via stdout: "width,height"
If more than one channel is passed as an argument to this child program,
only the first channel will be used. All others are ignored.
"""

import sys
import pyvisa as visa

address = sys.argv[1]  # VISA address command-line argument
s = sys.argv[2]  # comma separated string of channels
channels = s.split(',')  # a list of channels

rm = visa.ResourceManager(r'C:\WINDOWS\system32\visa64.dll')
FlexDCA = rm.open_resource(address)
FlexDCA.timeout = 20000  # Set connection timeout to 20s.
FlexDCA.read_termination = '\n'
FlexDCA.write_termination = '\n'
FlexDCA.query(':SYSTem:DEFault;*OPC?')
FlexDCA.write(':CHANnel' + channels[0] + ':DISPlay ON')
FlexDCA.query(':SYSTem:AUToscale;*OPC?')
FlexDCA.write(':ACQuire:STOP')
FlexDCA.write(':ACQuire:CDISplay')
FlexDCA.write(':MEASure:EYE:LIST:CLEar')
# A limit acquisition test ensures that enough waveform data is available
# to start measuring the eye.
FlexDCA.write(':LTESt:ACQuire:CTYPe:WAVeforms 64')
FlexDCA.write(':LTESt:ACQuire:STATe ON')
FlexDCA.query(':ACQuire:RUN;*OPC?')  # wait for limit test to complete
FlexDCA.write(':LTESt:ACQuire:STATe OFF')
FlexDCA.write(':MEASure:EYE:EWIDth:SOURce1 CHAN' + channels[0])
FlexDCA.write(':MEASure:EYE:EWIDth')
FlexDCA.write(':MEASure:EYE:EHEight:SOURce1 CHAN' + channels[0])
FlexDCA.write(':MEASure:EYE:EHEight')
width = FlexDCA.query(':MEASure:EYE:EWIDth?')
height = FlexDCA.query(':MEASure:EYE:EHEight?')
# stdout returned to parent process
print(channels[0] + ',' + width + ',' + height, flush=True)
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()