PyVISA
PyVISADocs

Instrument Control Patterns

PyVISA patterns for resource management, error handling, and multi-instrument coordination in Python test scripts.

PyVISA connects Python to any VISA-compatible instrument over USB, Ethernet, GPIB, or RS-232. This page covers the patterns that keep your scripts reliable in production: resource cleanup, error recovery, and coordinating multiple instruments.

Resource Management

Leaked VISA sessions cause "resource busy" errors and eventually lock out the instrument. Always close what you open.

Context Managers

The cleanest approach. Resources close automatically, even if an exception fires.

import pyvisa

with pyvisa.ResourceManager() as rm:
    with rm.open_resource("USB0::0x1234::0x5678::SN::INSTR") as inst:
        inst.timeout = 5000
        print(inst.query("*IDN?"))
# Both inst and rm are closed here

try/finally

Use this when you need the instrument handle across multiple functions or when context managers don't fit.

import pyvisa

rm = pyvisa.ResourceManager()
inst = rm.open_resource("USB0::0x1234::0x5678::SN::INSTR")
try:
    inst.timeout = 5000
    inst.write_termination = '\n'
    inst.read_termination = '\n'
    print(inst.query("*IDN?"))
finally:
    inst.close()
    rm.close()

Log measurements automatically

TofuPilot records test results from your PyVISA scripts, tracks pass/fail rates, and generates compliance reports. Free to start.

Error Handling

VISA Errors

PyVISA raises VisaIOError with a numeric error code. The two you'll hit most often are timeout (-1073807339) and invalid resource (-1073807343).

import pyvisa
from pyvisa import VisaIOError

rm = pyvisa.ResourceManager()
try:
    inst = rm.open_resource("USB0::0x1234::0x5678::SN::INSTR")
    inst.timeout = 5000
    response = inst.query("*IDN?")
    print(response.strip())
except VisaIOError as e:
    if e.error_code == -1073807339:
        print("Timeout: instrument not responding. Check cable and power.")
    elif e.error_code == -1073807343:
        print("Invalid resource string. Run rm.list_resources() to see what's available.")
    else:
        print(f"VISA error: {e}")
finally:
    rm.close()

SCPI Error Queue

Instruments queue SCPI errors internally. Drain the queue after a command sequence to catch problems early.

def drain_error_queue(inst):
    """Read all errors from the instrument's SCPI error queue."""
    errors = []
    while True:
        err = inst.query("SYST:ERR?")
        if err.startswith("+0") or err.startswith("0"):
            break
        errors.append(err.strip())
    return errors

# After a sequence of writes
inst.write("CONF:VOLT:DC")
inst.write("VOLT:DC:NPLC 10")
errors = drain_error_queue(inst)
if errors:
    raise RuntimeError(f"SCPI errors: {errors}")

Multi-Instrument Coordination

Production test stations typically involve a power supply, a signal source, and one or more measurement instruments. The pattern below keeps setup, test, and teardown organized.

import pyvisa
import time

def run_dut_characterization():
    rm = pyvisa.ResourceManager()

    psu = rm.open_resource("ASRL/dev/ttyUSB0::INSTR")
    psu.baud_rate = 115200
    psu.timeout = 5000

    siggen = rm.open_resource("USB0::0x2A8D::0x0001::MY52345678::INSTR")
    siggen.timeout = 5000

    scope = rm.open_resource("USB0::0x0699::0x0363::C065089::INSTR")
    scope.timeout = 10000

    try:
        # Power up
        psu.write("VOLT 5.0")
        psu.write("CURR 1.0")
        psu.write("OUTP ON")
        time.sleep(1)  # settling

        # Inject test signal
        siggen.write("SOUR:FUNC SIN")
        siggen.write("SOUR:FREQ 1000")
        siggen.write("SOUR:VOLT 1.0")
        siggen.write("OUTP ON")
        time.sleep(0.5)

        # Measure response
        scope.write("SING")
        scope.query("*OPC?")
        frequency = float(scope.query("MEASU:FREQ?"))
        amplitude = float(scope.query("MEASU:PK2PK?"))

        # Measure power draw
        supply_v = float(psu.query("MEAS:VOLT?"))
        supply_i = float(psu.query("MEAS:CURR?"))

        results = {
            "output_frequency_hz": frequency,
            "output_amplitude_vpp": amplitude,
            "power_consumption_w": supply_v * supply_i,
            "passed": abs(frequency - 1000) < 10,
        }
        return results

    finally:
        siggen.write("OUTP OFF")
        psu.write("OUTP OFF")
        for r in [scope, siggen, psu]:
            r.close()
        rm.close()

results = run_dut_characterization()
for k, v in results.items():
    print(f"  {k}: {v}")

Debugging

Enable PyVISA's built-in logging to see every VISA call on the wire.

import pyvisa
import logging

# Log to console
pyvisa.log_to_screen(logging.DEBUG)

# Or log to file
logging.basicConfig(
    filename='pyvisa_debug.log',
    level=logging.DEBUG,
    format='%(asctime)s %(name)s %(levelname)s %(message)s'
)

rm = pyvisa.ResourceManager()
inst = rm.open_resource("USB0::0x1234::0x5678::SN::INSTR")
inst.query("*IDN?")  # This call and its response will be logged

Common Pitfalls

MistakeConsequenceFix
No inst.close() in error paths"Resource busy" on next runUse context managers or try/finally
Default 2 s timeout for slow commandsVisaIOError on calibration, self-testSet inst.timeout per operation
Opening/closing in a loopSlow, fragileOpen once, reuse the handle
Ignoring SYST:ERR?Silent wrong measurementsDrain the error queue after setup
Hardcoded resource stringsBreaks on different PCsUse rm.list_resources() or config files