Back to blog
Ali KamalyAli Kamaly
August 14, 2026
12 min read
Instrument Automation

Python Instrument Control Libraries Compared 2026 (PyVISA, QCoDeS)

Five Python libraries for driving lab instruments, the same measurement written in each, and a clear answer on which to pick depending on whether you want a transport layer or a framework.

Python Instrument Control Libraries Compared 2026 (PyVISA, QCoDeS)

PyVISA is the transport layer, and the other libraries are frameworks built on top of it. That distinction settles most of the confusion in this comparison: you are not choosing between five equivalent options, you are choosing how much structure you want above the same underlying plumbing.

This post writes the same measurement in each library, compares them on driver coverage and workflow fit, and gives a clear recommendation by situation.

The layer diagram

your test code
      |
PyMeasure / QCoDeS / InstrumentKit / tm_devices     <- optional framework layer
      |
    PyVISA                                          <- transport
      |
NI-VISA / Keysight IO Libraries / pyvisa-py         <- backend
      |
USB / LAN / GPIB / serial

Everything except tm_devices in some configurations goes through PyVISA. So the question is not "which library talks to instruments" but "how much of the structure above the wire do I want provided".

The same measurement, five ways

The task: configure a DMM for DC volts and take a reading.

PyVISA

import pyvisa

rm = pyvisa.ResourceManager()
dmm = rm.open_resource("USB0::0x2A8D::0x1301::MY57200001::INSTR")
dmm.write("CONF:VOLT:DC 10,0.0001")
voltage = float(dmm.query("READ?"))

You write the SCPI. Total control, zero help.

PyMeasure

from pymeasure.instruments.keysight import Keysight34465A

dmm = Keysight34465A("USB0::0x2A8D::0x1301::MY57200001::INSTR")
dmm.mode = "DCV"
voltage = dmm.voltage

Typed properties. The SCPI is inside the instrument class. Autocomplete works, and an invalid mode raises rather than silently queueing an instrument error.

QCoDeS

from qcodes.instrument_drivers.Keysight import Keysight34465A

dmm = Keysight34465A("dmm", "USB0::0x2A8D::0x1301::MY57200001::INSTR")
voltage = dmm.volt()

Parameters are callables, and everything measured is automatically recorded into QCoDeS's dataset and database.

InstrumentKit

import instruments as ik

dmm = ik.generic_scpi.SCPIMultimeter.open_visa("USB0::0x2A8D::...::INSTR")
dmm.mode = dmm.Mode.voltage_dc
voltage = dmm.measure()

Units-aware through pint, so voltage carries volts as a unit rather than being a bare float.

tm_devices (Tektronix)

from tm_devices import DeviceManager

with DeviceManager() as dm:
    dmm = dm.add_dmm("192.168.1.60")
    voltage = dmm.commands.measure.voltage.dc.query()

Typed access to the full command tree, Tektronix hardware only.

Comparison

PyVISAPyMeasureQCoDeSInstrumentKittm_devices
LayerTransportFrameworkFrameworkFrameworkVendor framework
LicenceMITMITMITAGPLApache
Instrument driversNone, you write SCPILarge collectionLarge collectionModerateTektronix only
Typed propertiesNoYesYesYesYes
Units handlingNoPartialPartialYes, via pintNo
Experiment runnerNoYesYesNoNo
Live plottingNoYesVia plottrNoNo
Data storageNoCSVSQLite databaseNoNo
Learning curveLowModerateSteepLowLow
Best forAnythingLab experimentsResearch sweepsSmall scriptsTektronix benches

Which to choose

Start with PyVISA if

  • You are learning, or writing your first instrument script
  • Your instrument has no driver in any framework, which is common for older or niche hardware
  • You want minimum dependencies
  • You are building your own abstraction and do not want someone else's opinions

This covers most industrial validation work. The PyVISA tutorial is the starting point, and wrapping each instrument in a small class of your own gives you most of what a framework provides, tailored to what you actually do.

Choose PyMeasure if

  • Your work is a sweep with live plotting, which is its core use case
  • Your instruments are in its driver collection
  • You want experiment management without QCoDeS's conceptual weight

PyMeasure hits a good balance. The Procedure and Worker model handles long-running experiments with graceful abort properly, which is genuinely hard to do by hand.

Choose QCoDeS if

  • You are in a research lab, particularly quantum or condensed matter, where it is the convention
  • You need multi-dimensional parameter sweeps with automatic dataset capture
  • You want measurements stored in a queryable database by default

QCoDeS is excellent at what it targets and heavy for a pass and fail production test. Its concepts, station, parameter, measurement context, take real time to learn.

Choose InstrumentKit if

  • You want unit safety, which prevents an entire class of scaling bugs
  • Your scripts are small and you want minimal ceremony

Note the AGPL licence. For internal tooling this is normally fine, but check it against your organisation's policy before it reaches a product.

Choose tm_devices if

  • Your bench is Tektronix

It is Tektronix's own supported package and strictly better than their older OpenChoice utilities for automation. Use it for Tektronix hardware and PyVISA for everything else on the same bench.

The driver coverage trap

The most common reason teams pick a framework is "it has a driver for my instrument". Check this properly before committing.

  1. 1

    Find the driver in the repository, not in the marketing. Search the actual source tree for your model.

  2. 2

    Read it. Coverage is uneven. A driver may implement voltage measurement and not the trigger configuration you need.

  3. 3

    Check when it was last touched. Community drivers for older instruments can go years without maintenance.

  4. 4

    Test it against your actual unit. Firmware revisions change behaviour, and drivers are usually written against one revision.

A partially implemented driver is worse than no driver, because you inherit its structure and then fight it. If the driver covers less than what you need, it is often faster to write your own class over PyVISA.

A pattern that ages well

Whichever you choose, put your own interface in front of it. Test code should express the test, not the library.

from typing import Protocol

class Supply(Protocol):
    def set_voltage(self, volts: float) -> None: ...
    def output(self, on: bool) -> None: ...
    def measure_current(self) -> float: ...

Implement that over PyVISA today. If you later move to PyMeasure, or swap the physical instrument for a different model, only the implementation changes. Every test keeps working.

This is fifteen minutes of work that repeatedly saves days, and it is the single most useful habit in Python instrument code.

Handling instruments no library supports

Every bench has at least one instrument with no driver anywhere: an old source, a niche load, something from a vendor with three products. This is not a problem, and it is worth showing how small the work is.

import pyvisa

class GenericLoad:
    """Minimal driver for an electronic load. Eight methods covers a bench."""

    def __init__(self, resource: str):
        self.inst = pyvisa.ResourceManager().open_resource(resource)
        self.inst.timeout = 10000
        self.inst.write("*RST"); self.inst.write("*CLS")

    def constant_current(self, amps: float) -> None:
        self.inst.write("FUNC CURR")
        self.inst.write(f"CURR {amps}")

    def input(self, on: bool) -> None:
        self.inst.write(f"INP {'ON' if on else 'OFF'}")

    def measure_voltage(self) -> float:
        return float(self.inst.query("MEAS:VOLT?"))

    def measure_current(self) -> float:
        return float(self.inst.query("MEAS:CURR?"))

    def check_errors(self) -> None:
        response = self.inst.query("SYST:ERR?").strip()
        if not response.startswith(("0,", "+0,")):
            raise RuntimeError(response)

    def close(self) -> None:
        self.input(False)
        self.inst.close()

That is a complete, usable driver in forty lines, written from the instrument's programming guide in perhaps an hour. It exposes exactly what your tests need and nothing else, which makes it easier to read than a community driver covering the full command set.

The lesson is that driver coverage should not drive the library choice as heavily as people let it. A missing driver is an hour, not a blocker.

Testing instrument code without instruments

The strongest argument for the Python route, and the one that never appears in feature comparisons: you can test the test.

class FakeSupply:
    def __init__(self): self.voltage = 0.0; self.on = False
    def set_voltage(self, v): self.voltage = v
    def output(self, on): self.on = on
    def measure_current(self): return 0.1 if self.on else 0.0

def test_sequence_disables_output_on_failure():
    psu = FakeSupply()
    with pytest.raises(AssertionError):
        run_regulation_test(psu, dmm=FakeDmm(bad=True))
    assert psu.on is False, "output left enabled after a failed test"

That test runs in CI, on a laptop, with no hardware, and it catches the single most expensive class of bug in lab automation: a failure path that leaves a device powered.

No graphical environment offers a practical equivalent, and it is available in every library on this page because they are all just Python underneath.

Pinning and packaging, the part that decides whether this lasts

Library choice matters less than dependency discipline. A bench that works today and breaks after an unrelated install is the most common failure of a Python instrument stack, and it has nothing to do with which library you picked.

# requirements.txt, pinned
pyvisa==1.14.1
pyvisa-py==0.7.2          # pure-Python backend, no vendor VISA needed
numpy==1.26.4
pymeasure==0.14.0

Three rules:

  1. 1

    Pin exact versions and commit the file. Not >=. A bench is not a library.

  2. 2

    Use a virtual environment per bench. Two stations with different instruments should not share a Python installation.

  3. 3

    Record the VISA backend explicitly. pyvisa.ResourceManager() picks a backend by search order, which is fine until a machine has two installed. Name it: ResourceManager("@py") for pyvisa-py, or the path to the vendor library.

pyvisa-py is worth knowing about specifically. It is a pure-Python VISA implementation that speaks TCPIP and USB without NI-VISA or Keysight IO Libraries installed at all, which removes the largest install-time dependency from the stack. It does not cover GPIB as completely, so a GPIB bench still wants a vendor backend. See GPIB vs USB vs LAN for which transports you actually need.

What a thin wrapper of your own looks like

The recommendation below is "pyVISA plus your own wrapper", which deserves a concrete shape rather than being left as advice.

import pyvisa

class Instrument:
    """Minimal base: open, query with error checking, close."""

    def __init__(self, resource: str, timeout_ms: int = 10000, backend: str = ""):
        self._rm = pyvisa.ResourceManager(backend)
        self.io = self._rm.open_resource(resource)
        self.io.timeout = timeout_ms
        self.idn = self.io.query("*IDN?").strip()

    def cmd(self, command: str):
        self.io.write(command)
        self._raise_on_error(command)

    def ask(self, command: str) -> str:
        value = self.io.query(command).strip()
        self._raise_on_error(command)
        return value

    def _raise_on_error(self, context: str):
        code, _, message = self.io.query("SYST:ERR?").strip().partition(",")
        if code.strip() not in ("0", "+0"):
            raise RuntimeError(f"{self.idn}: {context} -> {code}{message}")

    def close(self):
        self.io.close(); self._rm.close()

    def __enter__(self): return self
    def __exit__(self, *exc): self.close()

About forty lines, and it gives you the three things every abstraction library is really selling: automatic error checking after every command, a context manager so sessions close on exception, and one place to change the timeout policy. Subclass it per instrument class and the model-specific SCPI stays contained.

The reason to write this rather than import it is that you now own the error-handling policy, which is the part that differs most between benches and the part a general-purpose library has to guess at.

Choosing quickly

SituationLibrary
Any SCPI instrument, full control, no magicpyVISA directly
No vendor VISA installable on the machinepyVISA with the @py backend
Tektronix bench, want typed model supporttm_devices
Research bench, many instrument types, want abstractionsPyMeasure
NI DAQ hardware channelsnidaqmx
One instrument, vendor ships a packageThe vendor package, then wrap it

The honest default is pyVISA directly with a thin wrapper of your own. Abstraction libraries save time when they cover your exact models and cost time when they do not, and the coverage question is model-specific rather than vendor-specific.

Common mistakes

  • Choosing a library before checking it supports your exact models. Coverage is per model, not per vendor. Check first, it takes ten minutes.
  • Not pinning versions. See above. This is the most common way a working bench stops working.
  • Letting the backend be chosen implicitly. Name it, especially on machines with more than one VISA installed.
  • Wrapping every instrument in a class before you have two of them. Abstraction earns its keep at the second instrument, not the first.
  • Using an abstraction library for the one instrument it does not cover. You end up with two idioms in one codebase. Drop to raw SCPI for that instrument and keep the interface consistent.
  • Ignoring `SYST:ERR?`. No library saves you from this. See Keysight Command Expert alternatives.
  • Sharing one Python environment across benches. Different instruments, different pins, different problems.

Where TestFlow fits

These libraries solve transport and, in some cases, structure. None of them tell you which commands a specific instrument needs for a specific measurement.

  1. 1

    Connect your instruments. Pick the manufacturer and model, paste the VISA address (USB, LAN, GPIB, or serial), and the agent knows what is on your bench. No bench yet? Use a placeholder address, build the full automation, and swap in the real address when you are in the lab.

  2. 2

    Tell the agent what to test, in plain English. For example, "run a VI sweep from 1 to 10 V in 1 V steps at 0.5 A load current," or "suggest the tests for a power-management device."

  3. 3

    The agent builds the complete workflow in seconds. Instrument-aware automation appears on the canvas, with the generated scripts visible in a code panel you can inspect and edit.

  4. 4

    Run it in your lab. Click Run and the status panel streams results step by step, with measured values inline (VOUT = 3.301 V, asserted 3.2 to 3.4 V, PASS). One click exports a structured PDF report, or the raw results as CSV.

The TestFlow builder: a plain-English request on the left, the generated instrument workflow in the centre, and the live run with its streaming SCPI execution log on the right.
The TestFlow agent turning a plain-English request into a runnable workflow, then running it on the bench. Click to enlarge.
  • Vendor-neutral by design. One workflow drives Keysight, Tektronix, Rohde & Schwarz, NI, Rigol, Keithley, Anritsu, and more over standard VISA and SCPI.
  • Browser-based and shareable. Workflows live in your workspace, so a sequence built in one lab runs the same way in another.
  • Free version to start. Sign in at app.testflowinc.com and build your first workflow today; plans and quotes are on the pricing page.
Instrument vendors TestFlow drives over VISA and SCPI: Keysight, Tektronix, Rohde & Schwarz, NI, Keithley, Agilent, Anritsu, Siglent, Chroma, Fluke, Yokogawa, Kikusui, TDK-Lambda, ESPEC, Watlow, Pickering, Copper Mountain, inTEST, Thermonics, and Microchip
Works with the instruments already on your bench. Full list on the supported instruments page.

The step-by-step walkthrough, VISA address formats, and Test Planner prompts are all in the TestFlow product guide.

Frequently asked questions

What is the best Python library for instrument control?

PyVISA for general use, because it is the transport layer everything else builds on and works with any SCPI instrument. PyMeasure if you want experiment structure and plotting included. QCoDeS if you are in a research lab with complex parameter sweeps.

What is the difference between PyVISA and PyMeasure?

PyVISA is a transport layer that sends and receives strings. PyMeasure builds on it and adds instrument classes with typed properties, an experiment runner, live plotting, and data management. PyMeasure is a framework, PyVISA is plumbing.

Is QCoDeS suitable for production test?

It is designed for research, particularly quantum and condensed matter labs, with strong support for complex parameter sweeps and a built-in database. It works elsewhere but its conventions assume a research workflow rather than a pass and fail production one.

Do I need PyVISA if I use PyMeasure?

PyMeasure uses PyVISA underneath as its default adapter, so it is installed either way. You do not interact with it directly unless you need an instrument PyMeasure does not have a class for.

Which library has the most instrument drivers?

PyMeasure and QCoDeS both ship substantial driver collections. Coverage is uneven across vendors, so check for your specific models before choosing on this basis. Any missing instrument is straightforward to add over PyVISA.

Can I mix these libraries in one script?

Yes, since they all sit on PyVISA. A common pattern is using PyMeasure classes for instruments it supports and raw PyVISA for anything it does not.

Ready to automate your lab?

Connect your instruments, describe a test in plain English, and TestFlow builds and runs it in minutes.

Tags

python instrument controlpyvisa vs pymeasureqcodesinstrumentkitpython lab automation librarytm_devices
Share this article:
Ali Kamaly

Article by

Ali Kamaly

Ali Kamaly is the Co-Founder and CEO of TestFlow, an AI-native platform for electronics test automation. He writes about test automation, lab validation, and the infrastructure behind modern hardware engineering.

Put it on your bench this week

A new way for testing, from specs to automated sequences, capture clean data, and accelerate your validation cycle.