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-pypyvisa - 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 powerSignal 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 outputOscilloscope
# 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 voltageNetwork 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 dataBest 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 timeoutInstrument Identification
# Query instrument identification
idn = instrument.query('*IDN?')
print(f"Instrument: {idn}")
# Log instrument info
with optixlog.run("test", config={"instrument": idn}) as client:
passBatch 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 100Real-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
- SDK Examples - More logging examples
- SDK API Reference - Complete API documentation
- Advanced Usage - Advanced patterns