> ## Documentation Index
> Fetch the complete documentation index at: https://nominal-wh-instro-512-featlib-shared-transport-ownership-so.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# VisaDriver

> VISA transport driver for building custom SCPI instrument drivers

# VisaDriver

`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](https://pyvisa.readthedocs.io/) 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)](https://www.ivifoundation.org/specifications/) 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.
</Note>

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

To author an instrument driver that composes `VisaDriver`, see the driver-development section of the instrument guide for that category: [power supplies](/instrumentation/psu#custom-driver-development), [electronic loads](/instrumentation/eload#custom-driver-development), [multimeters](/instrumentation/dmm#custom-driver-development), [oscilloscopes](/instrumentation/oscilloscope#custom-driver-development), or [DAQ](/instrumentation/daq#driver-development). This page is the transport reference those guides 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:

```python theme={null}
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`](#visaconfig) when you need to override the backend, terminators, timeouts, or serial settings.

## Key concepts

### Lifecycle

`VisaDriver` follows the [standard transport lifecycle](/instrumentation/transports/overview#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](/instrumentation/transports/overview#atomic-multi-step-sequences) and [shared ownership](/instrumentation/transports/overview#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`](#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=...))`.
</Tip>

### Timeouts

The `recv` timeout in [`TimeoutConfig`](#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`](#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:

```python theme={null}
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:

  ```python theme={null}
  import pyvisa
  rm = pyvisa.ResourceManager()
  print(rm.list_resources())
  ```
</Tip>

## 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](/instrumentation/transports/overview#shared-ownership).                                                                                         |
| `close(holder=None, on_last_release=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 runs `on_last_release`, then tears the connection down. See [Shared ownership](/instrumentation/transports/overview#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.
</Tip>

<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.
</Note>
