Skip to Content
Hardware Testing with SCPI

Hardware Testing with SCPI

Automate hardware tests using SCPI (Standard Commands for Programmable Instruments) and log results with OptixLog.

Overview

SCPI (Standard Commands for Programmable Instruments) is a standard protocol for controlling test equipment like:

  • Oscilloscopes (Keysight, Tektronix, Rigol)
  • Signal Generators (Agilent, Rohde & Schwarz)
  • Power Meters (Keysight, Anritsu)
  • Network Analyzers (Keysight, Anritsu)
  • Optical Test Equipment (EXFO, VIAVI)

OptixLog SDK can log all your hardware test results, making it easy to track measurements, compare test runs, and maintain test history.

Setup

Install Required Libraries

pip install http://optixlog.com/optixlog-0.0.4-py3-none-any.whl pyvisa pyvisa-py

pyvisa - Python interface for VISA (Virtual Instrument Software Architecture) pyvisa-py - Pure Python backend for pyvisa (no drivers needed)

Connect to Instruments

import pyvisa import optixlog # Initialize VISA resource manager rm = pyvisa.ResourceManager() # List available instruments resources = rm.list_resources() print(f"Available instruments: {resources}") # Connect to instrument (example: USB, GPIB, or TCP/IP) # USB: 'USB0::0x1234::0x5678::INSTR' # GPIB: 'GPIB0::1::INSTR' # TCP/IP: 'TCPIP0::192.168.1.100::inst0::INSTR' instrument = rm.open_resource('USB0::0x1234::0x5678::INSTR')

Basic SCPI Communication

Sending Commands

# Write command (no response expected) instrument.write('*RST') # Reset instrument instrument.write(':FREQ 1.55E9') # Set frequency to 1.55 GHz # Query command (response expected) frequency = instrument.query(':FREQ?') # Query frequency print(f"Current frequency: {frequency} Hz")

Reading Measurements

# Single measurement power = float(instrument.query(':MEAS:POW?')) print(f"Power: {power} dBm") # Multiple measurements instrument.write(':INIT:CONT ON') # Continuous measurement for i in range(10): power = float(instrument.query(':MEAS:POW?')) print(f"Measurement {i}: {power} dBm")

Logging with OptixLog

Basic Power Measurement

import pyvisa import optixlog import time rm = pyvisa.ResourceManager() instrument = rm.open_resource('USB0::0x1234::0x5678::INSTR') with optixlog.run( "power_measurement", config={ "instrument": "Keysight Power Meter", "wavelength": 1550, "unit": "dBm" } ) as client: # Initialize instrument instrument.write('*RST') instrument.write(':WAV 1550') # Set wavelength to 1550 nm # Take measurements measurements = [] for step in range(100): power = float(instrument.query(':MEAS:POW?')) measurements.append(power) # Log to OptixLog client.log(step=step, power=power) time.sleep(0.1) # Log measurement statistics import numpy as np client.log(step=100, mean_power=np.mean(measurements), std_power=np.std(measurements), min_power=np.min(measurements), max_power=np.max(measurements)) # Log plot client.log_plot("power_vs_time", range(len(measurements)), measurements, title="Power vs Time", xlabel="Time (samples)", ylabel="Power (dBm)") instrument.close()

Wavelength Sweep

import pyvisa import optixlog import numpy as np rm = pyvisa.ResourceManager() power_meter = rm.open_resource('USB0::0x1234::0x5678::INSTR') tunable_laser = rm.open_resource('USB0::0xABCD::0xEF01::INSTR') wavelengths = np.linspace(1530, 1570, 100) # 100 points from 1530-1570 nm with optixlog.run( "wavelength_sweep", config={ "wavelength_range": [1530, 1570], "points": 100, "instrument": "Tunable Laser + Power Meter" } ) as client: transmission = [] for i, wavelength in enumerate(wavelengths): # Set laser wavelength tunable_laser.write(f':WAV {wavelength}') time.sleep(0.1) # Wait for laser to stabilize # Measure power power = float(power_meter.query(':MEAS:POW?')) transmission.append(power) # Log each measurement client.log(step=i, wavelength=wavelength, power=power, transmission=power) # Log spectrum plot client.log_plot("transmission_spectrum", wavelengths, transmission, title="Transmission Spectrum", xlabel="Wavelength (nm)", ylabel="Power (dBm)") # Log statistics client.log(step=len(wavelengths), peak_wavelength=wavelengths[np.argmax(transmission)], peak_power=np.max(transmission), bandwidth_3db=calculate_3db_bandwidth(wavelengths, transmission)) power_meter.close() tunable_laser.close()

Multi-Parameter Sweep

import pyvisa import optixlog import numpy as np rm = pyvisa.ResourceManager() signal_gen = rm.open_resource('GPIB0::1::INSTR') oscilloscope = rm.open_resource('GPIB0::2::INSTR') frequencies = np.linspace(1e9, 10e9, 50) # 1-10 GHz amplitudes = [-10, -5, 0, 5, 10] # dBm with optixlog.run( "multi_parameter_sweep", config={ "frequencies": list(frequencies), "amplitudes": amplitudes, "instruments": ["Signal Generator", "Oscilloscope"] } ) as client: results = [] for freq_idx, frequency in enumerate(frequencies): for amp_idx, amplitude in enumerate(amplitudes): # Configure signal generator signal_gen.write(f':FREQ {frequency}') signal_gen.write(f':POW {amplitude}') signal_gen.write(':OUTP ON') time.sleep(0.1) # Measure on oscilloscope voltage = float(oscilloscope.query(':MEAS:VOLT:MAX?')) phase = float(oscilloscope.query(':MEAS:PHAS?')) # Log measurement step = freq_idx * len(amplitudes) + amp_idx client.log(step=step, frequency=frequency, amplitude=amplitude, voltage=voltage, phase=phase) results.append({ 'frequency': frequency, 'amplitude': amplitude, 'voltage': voltage, 'phase': phase }) # Log 2D heatmap voltage_matrix = np.array([r['voltage'] for r in results]).reshape(len(frequencies), len(amplitudes)) client.log_array_as_image("voltage_heatmap", voltage_matrix, cmap='hot', title="Voltage vs Frequency and Amplitude", colorbar=True) signal_gen.close() oscilloscope.close()

Common SCPI Commands

Power Meter

# Keysight/Agilent Power Meter instrument.write('*RST') # Reset instrument.write(':WAV 1550') # Set wavelength (nm) instrument.write(':UNIT DBM') # Set unit to dBm power = float(instrument.query(':MEAS:POW?')) # Measure power

Signal Generator

# Keysight Signal Generator instrument.write('*RST') instrument.write(':FREQ 1.55E9') # Set frequency (Hz) instrument.write(':POW -10') # Set power (dBm) instrument.write(':OUTP ON') # Enable output instrument.write(':OUTP OFF') # Disable output

Oscilloscope

# Tektronix Oscilloscope instrument.write('*RST') instrument.write(':ACQ:TYPE AVER') # Set acquisition type instrument.write(':ACQ:AVER 100') # Set averaging voltage = float(instrument.query(':MEAS:VOLT:MAX?')) # Measure max voltage

Network Analyzer

# Keysight Network Analyzer instrument.write('*RST') instrument.write(':SENS:FREQ:STAR 1E9') # Start frequency instrument.write(':SENS:FREQ:STOP 10E9') # Stop frequency instrument.write(':SENS:SWE:POIN 201') # Number of points instrument.write(':INIT') # Start sweep instrument.query('*OPC?') # Wait for completion s21 = instrument.query(':CALC:DATA:SNP?') # Get S21 data

Best Practices

Error Handling

import pyvisa import optixlog rm = pyvisa.ResourceManager() try: instrument = rm.open_resource('USB0::0x1234::0x5678::INSTR') with optixlog.run("hardware_test") as client: # Test code pass except pyvisa.errors.VisaIOError as e: print(f"Instrument error: {e}") client.log(step=0, error=str(e)) except Exception as e: print(f"Unexpected error: {e}") finally: if 'instrument' in locals(): instrument.close()

Timeout Configuration

instrument = rm.open_resource('USB0::0x1234::0x5678::INSTR') instrument.timeout = 10000 # 10 seconds timeout

Instrument Identification

# Query instrument identification idn = instrument.query('*IDN?') print(f"Instrument: {idn}") # Log instrument info with optixlog.run("test", config={"instrument": idn}) as client: pass

Batch Measurements

with optixlog.run("batch_measurements") as client: measurements = [] for step in range(1000): power = float(instrument.query(':MEAS:POW?')) measurements.append({"step": step, "power": power}) # Batch log for efficiency client.log_batch(measurements[:100]) # Log in batches of 100

Real-World Example: Optical Component Testing

import pyvisa import optixlog import numpy as np import time rm = pyvisa.ResourceManager() laser = rm.open_resource('USB0::0x1234::0x5678::INSTR') power_meter = rm.open_resource('USB0::0xABCD::0xEF01::INSTR') # Test configuration wavelengths = np.linspace(1520, 1580, 200) input_power = -5 # dBm with optixlog.run( "optical_component_test", config={ "component": "Waveguide Coupler", "wavelength_range": [1520, 1580], "input_power": input_power, "test_date": "2025-11-24" } ) as client: # Initialize instruments laser.write('*RST') power_meter.write('*RST') power_meter.write(':WAV 1550') transmission = [] insertion_loss = [] for i, wavelength in enumerate(wavelengths): # Set laser wavelength and power laser.write(f':WAV {wavelength}') laser.write(f':POW {input_power}') laser.write(':OUTP ON') time.sleep(0.2) # Stabilization time # Measure output power output_power = float(power_meter.query(':MEAS:POW?')) transmission.append(output_power) insertion_loss.append(input_power - output_power) # Log measurement client.log(step=i, wavelength=wavelength, input_power=input_power, output_power=output_power, insertion_loss=input_power - output_power) # Log spectrum client.log_plot("transmission_spectrum", wavelengths, transmission, title="Transmission Spectrum", xlabel="Wavelength (nm)", ylabel="Power (dBm)") client.log_plot("insertion_loss", wavelengths, insertion_loss, title="Insertion Loss", xlabel="Wavelength (nm)", ylabel="Loss (dB)") # Log statistics client.log(step=len(wavelengths), min_loss=np.min(insertion_loss), max_loss=np.max(insertion_loss), avg_loss=np.mean(insertion_loss), bandwidth_3db=calculate_bandwidth(wavelengths, insertion_loss)) laser.close() power_meter.close()

Integration with Test Automation

PyTest Integration

import pytest import pyvisa import optixlog @pytest.fixture def instrument(): rm = pyvisa.ResourceManager() inst = rm.open_resource('USB0::0x1234::0x5678::INSTR') yield inst inst.close() def test_power_measurement(instrument): with optixlog.run("pytest_power_test") as client: instrument.write('*RST') power = float(instrument.query(':MEAS:POW?')) # Log result client.log(step=0, power=power) # Assert assert -10 <= power <= 10, f"Power out of range: {power} dBm"

Next Steps