VisaDriver#

VISA transport driver for building custom SCPI instrument drivers

VisaDriver is the VISA transport that Nominal’s built-in SCPI instrument drivers sit on top of. It is available as a public part of the library so customers can build their own drivers for VISA-attached instruments without having to wrap pyvisa themselves.

It is intentionally narrow: it opens, closes, and locks a VISA resource and exposes text and raw byte I/O. The caller chooses the command strings.

Note

VISA (Virtual Instrument Software Architecture) is the IVI Foundation standard for talking to instruments over GPIB, USB-TMC, TCP/IP (SOCKET, VXI-11, HiSLIP), and RS-232/RS-485. VisaDriver uses pyvisa under the hood.

VisaDriver addresses GPIB, USB-TMC, TCPIP (SOCKET, VXI-11, HiSLIP), and ASRL (serial) resources. For a comparison against the other transports, see Transports.

To author an instrument driver that composes VisaDriver, see the driver-development section for that category on Custom Instruments: power supplies, electronic loads, multimeters, waveform generators, or DAQ. This page is the transport reference those sections link back to.

Quickstart#

The most common use of VisaDriver is as a transport inside an instrument driver. Here it is on its own, talking to a VISA instrument directly:

from instro.lib.transports import VisaDriver

visa = VisaDriver("USB0::0x2A8D::0x0101::MY12345::INSTR")
visa.open()
try:
    identity = visa.query("*IDN?")
    print(identity)
finally:
    visa.close()

A VisaDriver is configured with either a plain VISA resource string (defaults applied) or a full VisaConfig when you need to override the backend, terminators, timeouts, or serial settings.

Key concepts#

Lifecycle#

VisaDriver follows the standard transport lifecycle: construct, open(), I/O, close(), with both open() and close() idempotent. Two behaviors are specific to VISA:

  • open() applies the resource configuration. It opens the pyvisa ResourceManager and the resource, then applies terminators, timeouts, and (for ASRL resources) serial settings.

  • The ResourceManager is shared process-wide. pyvisa caches one per backend across every driver in the process, so close() closes this driver’s resource and leaves the manager open for other drivers. pyvisa closes it through its own atexit handler.

Locking and shared ownership work the same for VisaDriver as for any transport.

Terminators#

VISA instruments are line-terminated. VisaDriver applies a configurable read terminator (stripped from incoming text) and write terminator (appended to outgoing text) when the resource is opened.

The defaults are read="\n" and write="\r\n", which works for most SCPI instruments. Override them through TerminatorConfig when an instrument’s programming manual specifies otherwise, as with USB-TMC devices that use "\n" for both directions, or older instruments that expect bare "\r".

Tip

If your first query to a new instrument hangs until the timeout fires, the most likely cause is a terminator mismatch. Check the device’s programming manual for the expected read/write terminators and pass them through VisaConfig(terminator=TerminatorConfig(read=..., write=...)).

Timeouts#

The recv timeout in TimeoutConfig is specified in seconds and is forwarded to pyvisa as the session timeout (converted to milliseconds internally). It controls how long a read or query waits for the instrument to respond before raising. The default is 15 seconds.

The connect and send fields are accepted by VisaConfig for forward compatibility but are not yet wired into per-operation overrides. Leave them at the defaults unless you have a reason to set them.

Serial settings#

When the VISA resource is an ASRL (RS-232 / RS-485) interface, VisaDriver applies SerialConfig (baud rate, data bits, stop bits, parity, and flow control) on open(). For any other interface type (USB-TMC, GPIB, TCPIP, …) the serial config is silently ignored, so you can leave it at the defaults.

Text vs. raw I/O#

VisaDriver exposes both a text path and a raw byte path:

Path

Methods

Behavior

Use for

Text

write, read, query

pyvisa applies write/read terminators and decodes responses as strings

SCPI commands and queries

Raw bytes

write_raw, read_raw, query_raw

Bytes pass through unchanged on write; reads return bytes and are not terminator-stripped

Binary payloads, like waveform downloads from oscilloscopes, image transfers, and vendor-specific binary blobs

query_raw is a convenience: it writes a text command (so the write terminator is still applied) and then reads the response as raw bytes. This matches the common SCPI pattern of asking for binary data with a text command like :WAV:DATA?.

Backends#

VisaConfig.visa_backend selects which pyvisa backend handles the resource. Most callers should leave it unset. When unset (None), instro uses the system IVI VISA implementation ("@ivi", e.g. NI-VISA or Keysight IO Libraries) and automatically falls back to the pure-Python "@py" backend when no IVI implementation is installed. Setting visa_backend to any explicit value (such as "@ivi", "@py", or "@sim") uses that backend as-is, with no fallback.

Raw TCP sockets and Nagle’s algorithm#

For raw TCPIP...::SOCKET resources, VisaDriver disables Nagle’s algorithm (TCP_NODELAY) on open(). IVI backends already do this by default, but the pure-Python "@py" backend does not, so on "@py" it would otherwise leave Nagle enabled. With Nagle on, several small back-to-back SCPI writes can be coalesced into one TCP segment, and some instruments’ lightweight LAN firmware resets the connection when that happens. Disabling it makes the "@py" SOCKET path behave like IVI. Set VisaConfig(tcp_nodelay=False) to opt out; it has no effect on non-socket transports.

Configuration#

For terminators, timeouts, or serial settings, pass a VisaConfig instead of a plain resource string:

from instro.lib.transports import (
    ControlFlow,
    Parity,
    SerialConfig,
    StopBits,
    TerminatorConfig,
    TimeoutConfig,
    VisaConfig,
    VisaDriver,
)

config = VisaConfig(
    visa_resource="ASRL/dev/ttyUSB0::INSTR",
    serial_config=SerialConfig(
        baud_rate=19200,
        data_bits=8,
        stop_bits=StopBits.ONE,
        parity=Parity.NONE,
        flow_control=ControlFlow.NONE,
    ),
    terminator=TerminatorConfig(read="\r", write="\r"),
    timeout=TimeoutConfig(recv=30),
)

visa = VisaDriver(config)

VisaConfig#

Top-level connection parameters.

Field

Required

Default

Description

visa_resource

Yes

VISA resource string, e.g. TCPIP0::host::5025::SOCKET or USB0::0x2A8D::0x0101::MY12345::INSTR

visa_backend

No

None

pyvisa backend specifier; unset uses "@ivi" and falls back to "@py" when no IVI implementation is installed. An explicit value is used as-is, with no fallback

serial_config

No

SerialConfig()

Serial settings, applied only when the resource is an ASRL interface

terminator

No

TerminatorConfig()

Read and write terminators

timeout

No

TimeoutConfig()

Operation timeouts

tcp_nodelay

No

True

Disable Nagle’s algorithm on raw TCP SOCKET connections. No effect on non-socket transports

TerminatorConfig#

Field

Required

Default

Description

read

No

"\n"

Terminator stripped from incoming text reads

write

No

"\r\n"

Terminator appended to outgoing text writes

TimeoutConfig#

Operation timeouts in seconds.

Field

Required

Default

Description

recv

No

15

Applied as the pyvisa session timeout. Controls how long read/query wait for a response

connect

No

30

Reserved for future per-operation overrides, currently not applied

send

No

15

Reserved for future per-operation overrides, currently not applied

SerialConfig#

Serial-line settings, applied when the VISA resource is an ASRL (RS-232/RS-485) interface. Ignored for all other interface types.

Field

Required

Default

Description

baud_rate

No

9600

Serial baud rate

data_bits

No

8

Data bits (5 to 8)

stop_bits

No

StopBits.ONE

StopBits.ONE, StopBits.ONE_POINT_FIVE, or StopBits.TWO

parity

No

Parity.NONE

Parity.NONE, Parity.ODD, Parity.EVEN, Parity.MARK, or Parity.SPACE

flow_control

No

ControlFlow.NONE

ControlFlow.NONE, ControlFlow.XON_XOFF, ControlFlow.RTS_CTS, or ControlFlow.DTR_DSR

VISA resource strings#

VisaDriver does not invent its own addressing scheme. The visa_resource string is passed straight through to pyvisa’s ResourceManager.open_resource(). Some commonly used forms:

Interface

Example resource string

USB-TMC

USB0::0x2A8D::0x0101::MY12345::INSTR

GPIB

GPIB0::5::INSTR

TCP/IP (VXI-11)

TCPIP0::192.168.1.50::inst0::INSTR

TCP/IP (HiSLIP)

TCPIP0::192.168.1.50::hislip0::INSTR

TCP/IP (raw SOCKET)

TCPIP0::192.168.1.50::5025::SOCKET

Serial (Windows)

ASRL3::INSTR (COM3)

Serial (Linux/macOS)

ASRL/dev/ttyUSB0::INSTR

Tip

To discover what’s attached, you can use pyvisa’s resource manager directly:

import pyvisa
rm = pyvisa.ResourceManager()
print(rm.list_resources())

Method reference#

Method

Purpose

VisaDriver(visa_resource)

Construct a driver from a VISA resource string or a VisaConfig. No I/O yet.

is_open

Property: True if the underlying VISA resource is open.

open(holder=None)

Open the resource manager and resource; apply terminator/timeout/serial config. Idempotent. Pass a holder to register shared ownership: returns True if holder is the first owner. See Shared ownership.

close(holder=None)

Close the resource and resource manager. Idempotent. Bare close() declines and logs a warning if another driver still holds the connection. With a holder, removes that owner; the close that empties the owner list tears the connection down. Raises UnknownHolderError if an unrecognized holder closes while others still own the connection. See Shared ownership.

write(command)

Write a text command; the configured write terminator is appended.

read()

Read a single response string up to (and stripping) the read terminator.

query(command)

Write a command and read the response as text.

write_raw(data)

Write raw bytes exactly as provided.

read_raw()

Read raw bytes from the instrument; no terminator stripping.

query_raw(command)

Write a text command, then read the response as raw bytes.

lock()

Return the reentrant resource lock for use as a context manager when multiple operations must execute atomically.

Error handling#

Error

Cause

RuntimeError: VisaDriver is not open. Call open() first.

A write/read/query/*_raw call was made before open(), or after close().

pyvisa.errors.VisaIOError

The underlying VISA call failed: timeout, resource not found, bus error, instrument disconnected, etc. The error_code attribute identifies the cause (e.g. VI_ERROR_TMO for timeout).

pyvisa.errors.LibraryError

The selected VISA backend could not be loaded. Because instro installs the default backend, this most often means an explicitly selected alternate backend is unavailable. Use the default backend or install the required vendor runtime.

Opening a GPIB...::INSTR resource fails

The native GPIB driver is missing. instro ships the gpib-ctypes binding, but GPIB hardware also needs NI-488.2 (NI interfaces) or linux-gpib (Linux). Install the driver your interface requires.

Tip

VisaDriver deliberately does not retry, reconnect, or wrap pyvisa errors. Higher-level recovery (retry policies, reconnection on transient failures, escalation to operators) belongs in the instrument driver or application code on top.

Note

SYST:ERR? is a SCPI convention, not a VisaDriver feature. VisaDriver does not poll the instrument’s error queue for you. Whether and how to do so is a per-instrument decision. Most SCPI instruments implement SYST:ERR? and respond with 0,"No error" when nothing is wrong, but some use SYSTEM:ERROR? (TDK Lambda) or a different status mechanism entirely. Consult the programming manual.