Python Automation of Agilent Instruments

Note: This app note only applies to automation using a CPython installation on a Windows machine and assumes some prior knowledge and installation of Python.

Contents


General SCPI Instrumentation Control

Requirements:

  1. Installation of a VISA library such as [Agilent's IO Libraries](http://www.agilent.com/find/iolibs)
  2. Installation of the [PyVISA](https://github.com/hgrecco/pyvisa) Python library

Example of use with an Agilent [90000 X Series](www.agilent.com/find/90000x-scope) Oscilloscope:

In [1]:
from visa import instrument # Import the instrument function from the visa module
scope = instrument("TCPIP0::130.30.240.155::inst0::INSTR") # Connect to the scope using the VISA address (see below)
scope.ask("*IDN?") # Query the IDN properties of the scope
Out[1]:
'Agilent Technologies,MSOX93204A,MY53240105,04.60.0016'

The instrument's visa address can be found in the IO Libraries Connection Expert:

Now let's do something more interesting...

In [12]:
import numpy as np
import matplotlib.pyplot as plt
# Allow plots to appear inline with the IPython notebook
%pylab inline
scope.write(":TIM:SCALE 600e-12")
scope.write(":DIG") # Capture a single waveform
wfm_ascii = scope.ask(":WAV:DATA?") # Get the waveform data
wfm_ascii = wfm_ascii[:-1] # Remove a trailing ,
wfm = [float(s) for s in wfm_ascii.split(',')] # Convert the ascii list of strings to a list of floats
sa_rate = float(scope.ask(":ACQ:SRAT:ANAL?")) # Get the scope's sample rate
mem_depth = float(scope.ask(":ACQ:POIN?")) # Get the current memory depth
t = np.linspace(-mem_depth/sa_rate,mem_depth/sa_rate, len(wfm)) # Calculate the sample times of the waveform
plt.plot(t,wfm) # Plot the waveform vs sample times
Populating the interactive namespace from numpy and matplotlib
Out[12]:
[<matplotlib.lines.Line2D at 0x7f1f690>]

How about a screenshot from the instrument to confirm the waveform data we just pulled?

In [13]:
scope.write("disp:data? png")
data = scope.read_raw()    # Read back the raw binary data as a string
hash_idx = data.find('#')    # Find the start of the image data
len_datalen = int(data[hash_idx+1])    # The 1st character after '#' indicates the length of the length field
data_start_idx = hash_idx+len_datalen+2    # The binary image data starts here
datalen = int(data[hash_idx+2:hash_idx+len_datalen+2])    # The length of the binary data
img_data = data[data_start_idx:data_start_idx+datalen]    # Extract just the image binary data

from IPython.display import Image # Ipython has some nice tools to display image data.
embed = Image(img_data)
embed
Out[13]:

A more complete example of handling binary values from an instrument coming soon... To save the screenshot to a local file:

In [15]:
target = open('screenshot.png','wb')
target.write(img_data)
target.close()


Automation of Infiniium Compliance Applications

You can find more information about our scope apps at: www.agilent.com/find/scope-apps

Requirements:

  1. Installation of a [Python for .NET](http://pythonnet.sourceforge.net) Python module
    * Unzip & put the files **clr.pyd** & **Python.Runtime.dll** into the \DLLs directory of your Python installation (ex: C:\Python27\DLLS\)*

  2. Installation of the [Infiniium Remote Programming Toolkit](http://www.agilent.com/find/rpi)
    * Once installed go to the Tools directory & copy the file **Agilent.Infiniium.AppFW.Remote.dll** into the same Python install \DLLs directory*

  3. The Remote Programming Interface for the Infiniium Compliance Applications is built on a .NET API and requires a **LAN connection** to the scope.

Example:

In [2]:
# First set up the connection to the RPI DLL via the Python for .NET module
import clr # Import the Common Runtime Library Python module
clr.AddReference("Agilent.Infiniium.AppFW.Remote") # Create a reference from CLR to the Compliance App DLL
from Agilent.Infiniium.AppFW.Remote import * # Import the entire compliance app namespace from the Compliance App DLL
scopeIpAddress = "130.30.240.155"

Now we're ready to start...

In [3]:
scope.write(":SYSTEM:LAUNCH 'N5393C PCIExpress Test App'") # Launch the compliance app using the pyvisa instrument connection
remoteObj = RemoteAteUtilities.GetRemoteAte(scopeIpAddress) # Connect to the compliance app
remoteApp = IRemoteAte(remoteObj)

If we want to take a look at the test ID's & test names currently available we can do that using the TestInfo type:

In [14]:
remoteApp.SetConfig("TestPoint_Transmitter", "1.0")
testsinfos = remoteApp.GetCurrentOptions("TestsInfo")
for test in testsinfos:
    print test.ID, test.Name
2101 Tx, Unit Interval (PCIE 2.0, 2.5 GT/s)
2110 Tx, Template Tests (PCIE 2.0, 2.5 GT/s)
2120 Tx, Median to Max Jitter (PCIE 2.0, 2.5 GT/s)
2130 Tx, Eye-Width (PCIE 2.0, 2.5 GT/s)
2140 Tx, Peak Differential Output Voltage (Transition)(PCIE 2.0, 2.5 GT/s)
21400 Tx, Peak Differential Output Voltage (Non Transition)(PCIE 2.0, 2.5 GT/s)
2151 Tx, Rise/Fall Time (PCIE 2.0, 2.5 GT/s)
2160 Tx, Deemphasized Voltage Ratio (PCIE 2.0, 2.5 GT/s)
2170 Tx, RMS AC Peak Common Mode Output Voltage (PCIE 2.0, 2.5 GT/s)
2181 Tx, Avg DC Common Mode Voltage (PCIE 2.0, 2.5 GT/s)
2182 Tx, DC Common Mode Output Voltage Variation (PCIE 2.0, 2.5 GT/s)
2185 Tx, DC Common Mode Line Delta (PCIE 2.0, 2.5 GT/s)

Now let's select some measurements & run some tests:

In [5]:
remoteApp.SuppressMessages = True # Suppress the GUI prompts
remoteApp.NewProject(True) # Create a new project
# Set various Configuration Parameters
remoteApp.SetConfig("DevicePCIErev", "PCIE 2.0")
remoteApp.SetConfig("TestPoint_AddInCard", "1.0")
remoteApp.SetConfig("DataRateOpt", "2.5 GT/s")
remoteApp.SetConfig("EnableSignalCheck", "0.0")
remoteApp.SelectedTests = [2301, 2310, 2320, 2330, 2340, 2350] # Select the tests to run
remoteApp.Run() # Run the selected tests
# Set up the project save options & save it to disk
saveOptions = SaveProjectOptions()
saveOptions.BaseDirectory = "c:\\temp"
saveOptions.Name = "Demo"
saveOptions.OverwriteExisting = True
projectFullPath = remoteApp.SaveProjectCustom(saveOptions)
# Set up the project results options and then get the results
resultOptions = ResultOptions()
resultOptions.TestIds = [2301]
resultOptions.IncludeCsvData = True
customResults = remoteApp.GetResultsCustom(resultOptions)
results = customResults.CsvResults
#remoteApp.Exit(True,True) # Exit the application
In [6]:
# An example of how to manipulate and display results using the Pandas library
import pandas as pd
from StringIO import StringIO
df_data = pd.read_csv(StringIO(results), sep=',', header=0, quotechar = '"')
pd.set_option('display.max_colwidth', 22)
df_data
Out[6]:
Test ID Test Name Measured Item Trial 1 Value
0 2301 Add-in Card Tx, Un... Data Lane Lane0
1 2301 Add-in Card Tx, Un... Note: Non-SSC Limits Use...
2 2301 Add-in Card Tx, Un... Actual Value 401.7540
3 2301 Add-in Card Tx, Un... #3500 UI Blocks Me... 992144
4 2301 Add-in Card Tx, Un... Margin -680.8
5 2301 Add-in Card Tx, Un... Min UI 401.7430
6 2301 Add-in Card Tx, Un... Max UI 401.7540
7 2301 Add-in Card Tx, Un... Mean UI 401.7480
8 2301 Add-in Card Tx, Un... Worst Case Data Rate 2489085360.693
9 2301 Add-in Card Tx, Un... Mean Data Rate 2489122534.524
10 2301 Add-in Card Tx, Un... Connection Type Chan 1,3 - Direct ...

Similarly for Compliance Applications on FlexDCA:

In [ ]:
from visa import *
dca = instrument("TCPIP0::130.30.240.150::inst0::INSTR")
dca.ask("*idn?")
In [ ]:
dca.write(":SYST:LAUN 'N1012A OIF CEI 3_0'") # Launch the compliance app


Automation of N5990A Automated Test Applications for Receiver Test

More information about N5990A solutions

Requirements:

  1. Installation of a [Python for .NET](http://pythonnet.sourceforge.net) Python module/
    * Unzip & put the files **clr.pyd** & **Python.Runtime.dll** into the \DLLs directory of your Python installation (ex: C:\Python27\DLLS\)*

  2. The Valiframe dll’s that needed are typically installed in the following directory: C:\Program Files (x86)\BitifEye\ValiFrame\PCI-Express3 You can copy over the ones you need to a directory that Python looks in but the easiest way to reference them from your Python script is to add this directory to the sys.path variable which tells the Python interpreter where to look for modules and dll’s.

Example:

In [3]:
import sys 
sys.path.append('C:\\Program Files (x86)\\BitifEye\\ValiFrame\\PCI-Express3') # Add the location of the Valiframe dll's to the system path
import clr # Import the Common Runtime Library Python module
clr.AddReference("ValiFrameRemote") # Create a reference from CLR to the Valiframe App DLL
from BitifEye.ValiFrame.ValiFrameRemote import * # Import the entire valiframe app namespace from the DLL
my_vf_pcie = ValiFrameRemote() # Creates an instance of the ValiFrameRemote class
my_vf_pcie.InitApplication("PciExpress3") # Initialize the application
my_vf_pcie.LoadProject("my_pcie3_proj.vfp")
#my_vf_pcie.ConfigureApplication() # This method creates a GUI prompt

There are a couple of interesting things to be aware of when using .NET dll's in Python via the pythonnet module.

  1. A .NET method may require the user to pass the variable name it wishes to modify as an argument. Python does not support "pass by reference" but the variable name must exist in python before it can be handed to the .NET method. The .NET method will simply point to the new instance.
    The example below shows how to handle this.
  2. .NET attributes can often return .NET collections such as Arrays. The object returned by python.net is indexed and iterable in Python but it is not a native Python collection object like a list.
    The example below shows a simple way to convert Arrays of Strings and Int32s to lists of strings & ints.
In [4]:
procedureIDs = procedureNames = [] # The variable names must be assigned to something but are overwritten in the next call
_, procedureIDs, procedureNames = my_vf_pcie.GetProcedures(procedureIDs, procedureNames)
procedureIDs = [int(id) for id in procedureIDs] # Convert to a python list of integers
procedureNames = [str(name) for name in procedureNames] # Convert to a python list of strings
procs = zip(procedureIDs, procedureNames) # Zip the id's & names together. A dictionary may also be a useful way to store these.
for proc_id,proc_name in procs:
    print proc_id, proc_name
300101 Random Jitter Calibration
300103 De-Emphasis Calibration
300104 Eye Height Calibration
301101 Random Jitter Calibration
301103 De-Emphasis Calibration
301104 Eye Height Calibration
302120 Equalization Preset Calibration
302121 Equalization Custom Preset Calibration
302122 Random Jitter Calibration
302123 Sinusoidal Jitter Calibration
302125 DM Sinusoidal Interference Calibration
302126 Eye Height and Width Calibration
302127 Compliance Eye Calibration
310101 Compliance Test
311101 Compliance Test
312120 Preset Compliance Test
312121 Compliance Test
320101 Unit Interval 
320102 Template Tests 
320103 Median to Max Jitter 
320104 Eye-Width 
320105 Peak Differential Output Voltage (Transition)
320106 Peak Differential Output Voltage (NonTransition)
321140 Unit Interval 
321141 Template Tests -3.5dB 
321142 Peak Differential Output Voltage -3.5dB 
321143 Eye-Width -3.5dB with crosstalk 
321144 RMS Random Jitter -3.5dB with crosstalk 
321145 Maximum Deterministic Jitter -3.5dB with crosstalk 
321146 Total Jitter at BER-12 -3.5dB with crosstalk 
321147 Eye-Width -3.5dB without crosstalk 
321148 RMS Random Jitter -3.5dB without crosstalk 
321149 Maximum Deterministic Jitter -3.5dB without crosstalk 
321150 Total Jitter at BER-12 -3.5dB without crosstalk 
322160 Unit Interval 
322161 Template Tests 
322162 Eye-Width 
322163 Peak Differential Output Voltage 

Valiframe often uses dialog windows to give the user information or prompt the user to make a change to the setup. There are different ways to handle this in an automation environment. Below is a basic way to suppress all dialog windows but there is a more complete example of how to handle messaging and events in Valiframe

In [5]:
my_vf_pcie.DialogPopUp += DialogShowEventHandler(VFDialogInformation()) # Register the Dialog event handler (suppresses all Dialogs)
In [6]:
eyewidth_result = str()
my_vf_pcie.RunProcedure(320104, eyewidth_result)
Out[6]:
(True,
 u'<?xml version="1.0" encoding="utf-16"?>\r\n<TestResults>\r\n  <Summary>\r\n    <ProcedureName>Eye-Width </ProcedureName>\r\n    <ProcedureID>320104</ProcedureID>\r\n    <Result>Incomplete</Result>\r\n    <DateTime>11/13/2013 12:32:21 PM</DateTime>\r\n  </Summary>\r\n</TestResults>')

Copyright © 2011 Agilent Technologies Inc. All rights reserved.

You have a royalty-free right to use, modify, reproduce and distribute this content (and/or any modified version) in any way you find useful, provided that you agree that Agilent has no warranty, obligations or liability.