Skip to content

driver/pyvisa: extend Test and Measurement equipment support#1835

Open
Sabolik wants to merge 13 commits intolabgrid-project:masterfrom
vzlu:vzlu/pyvisa
Open

driver/pyvisa: extend Test and Measurement equipment support#1835
Sabolik wants to merge 13 commits intolabgrid-project:masterfrom
vzlu:vzlu/pyvisa

Conversation

@Sabolik
Copy link
Copy Markdown

@Sabolik Sabolik commented Mar 4, 2026

Description

Main purpose:

  • Extend generic support for T&M instruments (SCPI based test & measurement equipment) using already existing pyvisa driver with appropriate backend (e.g. pyvisa-py)
  • Add pyvisa remote access by introducing an agent wrapper module.
  • Extend pyvisa driver with the option to select specific resource type (e.g. INSTR/SOCKET).
  • Add remote client support.
  • Introduce the tminstrument driver as high-level wrapper for low-level drivers, (pyvisa, usbtmc), with PowerProtocol support. Potentially replacing the standalone usbtmc driver.
  • Add backend support for various laboratory instruments, e.g. convenient command wrapper for SCPI commands.

The implementation was tested only by using it with devices on our lab environment, both locally and remotely for following interfaces:

  • TCPIP INSTR
  • TCPIP SOCKET
  • ASRL INSTR
  • USB INSTR

Checklist

  • Documentation for the feature
  • Tests for the feature
  • The arguments and description in doc/configuration.rst have been updated
  • Add a section on how to use the feature to doc/usage.rst
  • Add a section on how to use the feature to doc/development.rst
  • PR has been tested
  • Man pages have been regenerated

Copy link
Copy Markdown
Member

@Emantor Emantor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remote: client: add remote pyvisa access needs a commit message explaining why the change is done.

I wonder if the current backend() calling mechanic can't be simplified to be more explicit instead of inspecting the imported module backend and calling functions on it again. It looks like we are cramming a lot of calls through the backend() function, while we have the explicit module with function definitions for each instrument. Why not call the functions on the module directly?

def query(self, cmd):
return self.proxy.query(self.device_identifier, self.pyvisa_resource.backend, cmd)
def query(self, cmd, timeout=TIMEOUT_DEFAULT):
return self.proxy.query(self.device_identifier, self.pyvisa_resource.backend, cmd, timeout).rstrip()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rstrip() is added here, can you add a description to the commit why it was added?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The termination might differ among instruments. Another option might be to properly set the read_termination parameter of the resource, but I would rather keep rstrip for simplicity.

Comment thread labgrid/driver/pyvisadriver.py Outdated
from ..util.agentwrapper import AgentWrapper
from .common import Driver

TIMEOUT_DEFAULT = 2000 # Default timeout for visa operations in ms
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I prefer having the default in the keyword arguments i.e. def command(self, cmd, timeout=2000) and we also want a docstring which documents this to be in miliseconds.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Thanks

Args:
type (str): device resource type following the pyVISA resource syntax, e.g. ASRL, TCPIP...
url (str): device identifier on selected resource, e.g. <ip> for TCPIP resource
url (str, default=''): optional device identifier on selected resource, e.g. <ip> for TCPIP resource
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can the default be changed to an empty string here? Is this not mandatory?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is optional. Some interfaces are fully specified by the type parameter, e.g. serial devices like ASRL/dev/ttyACM0.

Comment thread labgrid/resource/pyvisa.py Outdated

type = attr.ib(validator=attr.validators.instance_of(str))
url = attr.ib(default="", validator=attr.validators.instance_of(str))
resource = attr.ib(default="INSTR", validator=attr.validators.instance_of(str))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a finite amount of resource type definitions we might want to extend the validator, but this can be added later.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be fixed based on the VISA documantation, but honestly I am not familiar with all of them, so I am not sure what are the consequences. But the most common are INSTR and SOCKET and RAW, might be good idea to create a validator for them.

@Sabolik Sabolik force-pushed the vzlu/pyvisa branch 3 times, most recently from 10cc6cf to fdda33e Compare March 5, 2026 14:46
@Sabolik
Copy link
Copy Markdown
Author

Sabolik commented Mar 5, 2026

@Emantor thanks a lot for review, all comments should be fixed now.

Regarding backend() implementation:

  • I think there is a direct call, once you call for it and proper module is imported based on identification (first call).
  • There is an option to list all backend methods available in case None is passed instead of backend command
  • I pushed a minor change fixing overwriting _self.backend module each time backend is called.

@threexc
Copy link
Copy Markdown
Contributor

threexc commented Mar 10, 2026

@Sabolik thanks for doing this work. Do you have some example device names you've tested this with? I have an SDG1032X waveform generator which supports VISA that I may try it out on.

@Sabolik
Copy link
Copy Markdown
Author

Sabolik commented Mar 10, 2026

@Sabolik thanks for doing this work. Do you have some example device names you've tested this with? I have an SDG1032X waveform generator which supports VISA that I may try it out on.

it should be pretty generic from the SCPI commanding point of view. There might be (optionally) backend implemented as SCPI convenient commands wrapper. Few equipment are already supported, listed here.

Let me give you few possible ways how to access the equipment (USB rigol DM3068 as example):

  • Directly (just for explanation purpose):

Usage example:

## bind target to driver directly in code

from labgrid.resource.pyvisa import PyVISADevice
from labgrid.driver.tminstrument import TMInstrument
from labgrid import Target

t = Target('example')
# equipment connected via USB
PyVISADevice(t, name=None, type='USB0', url='6833::3220::DM3O224500792::0', backend='@py')

# using low-level pvisa driver
from labgrid.driver.pyvisadriver import PyVISADriver
visa_driver = PyVISADriver(t, name="tm-inst-visa-rigol-dmm")
t.activate(visa_driver)
visa_driver.identify()
# equivalent to
visa_driver.query('*IDN?')

# or using of high level driver (TMInstrument) with VISA backend (Reomended way)
from labgrid.driver.tminstrument import TMInstrument
tm_inst_driver = TMInstrument(t, name="tm-inst-visa-rigol-dmm")
t.activate(tm_inst_driver)
tm_inst_driver.identify()
# equivalent to
tm_inst_driver.query('*IDN?')
  • Bind resource to drivers via environment configuration:

Config (config_visa.yaml):

targets:
  main:
    resources:
    - PyVISADevice:
        name: dmm-rigol
        type: "USB0"
        url: "6833::3220::DM3O224500792::0"
        backend: "@py"
    drivers:
    - PyVISADriver:
        name: driver-visa-rigol-dmm
        bindings: { pyvisa_resource : 'dmm-rigol'}
    - TMInstrument: 
          bindings: { inst : 'driver-visa-rigol-dmm' }
          name: 'tm-inst-visa-rigol-dmm'

Usage example:

## bind target to driver using config file

from labgrid import Environment
env = Environment('config_visa.yaml')
targets=env.get_target()

inst_visa_rigol_psu = targets.get_driver(TMInstrument, name="tm-inst-visa-rigol-dmm")
inst_visa_rigol_psu.identify()
# equivalent to
inst_visa_rigol_psu.query('*IDN?')

You may control the device also remotely using labgrid-client, if resources are properly exported, see example here

@Emantor
Copy link
Copy Markdown
Member

Emantor commented Mar 19, 2026

@threexc Did you find some time to test this?

@threexc
Copy link
Copy Markdown
Contributor

threexc commented Mar 19, 2026

@threexc Did you find some time to test this?

I've been looking at it. I was able to do a basic test on my side with my SDG1032X and confirm it can communicate:

(venv) tgamblin@outpost:~/workspace/git/labgrid-power-up$ labgrid-client -c workbench/wb-client.yaml inst identify --name 'tm-inst-sdg1032x'
Selected role main and place workbench from configuration file
/usr/lib/python3/dist-packages/pyvisa_py/protocols/usbtmc.py:116: UserWarning: Unexpected MsgID format. Consider updating the device's firmware. See https://github.com/pyvisa/pyvisa-py/issues/20Expected message id was 2, got 46.
  warnings.warn(
Siglent Technologies,SDG1032X,SDG1XDFQ6R5627,1.01.01.33R

This is with the following exporter config for the device:

  wb-generator-1:
    cls: 'PyVISADevice'
    type: 'USB0'
    url: '0xF4EC::0x1103::SDG1XDFQ6R5627::0'
    backend: '@py'

and a client config like:

      - PyVISADriver:
          name: 'sdg1032x-driver'
          bindings:
            pyvisa_resource: 'wb-generator-1'
      - TMInstrument:
          name: 'tm-inst-sdg1032x'
          bindings:
            inst: 'sdg1032x-driver'

Seems reasonable enough to me. My only major issue with this PR is that there are examples in the commits but not really any updates to the docs? It would be good to have decent examples of the exporter and client configs, along with example commands to run (the ones in some of the commit messages that I've been pointed at are probably enough).

@Sabolik, can you add another commit to reflect that?

Comment thread labgrid/driver/usbtmcdriver.py Outdated
assert isinstance(cmd, str)
cmd = b2s(cmd.encode("ASCII") + b"\n")
res = s2b(self.wrapper.usbtmc(self.index, cmd, read=True))
res = s2b(self.wrapper.usbtmc(self.dev_index, cmd, read=True))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is any change in this file really needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be needed. The reason is that the same attribute is used by power resource as power port identification. Power resource is directly supported by tminstrument driver while conflicting with low-level usbtmc driver (usbtmc device identification)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually not needed. There was conflict between power driver implementation using USBTMC resource used locally in past in our lab, but this is not part of this PR.

Comment thread labgrid/util/agents/visa_instrument.py Outdated
Comment thread labgrid/util/agents/visa_instrument.py Outdated
leinher and others added 10 commits March 30, 2026 14:11
Signed-off-by: Hermann Leinweber <hermann.leinweber@erweka.com>
The agent supports remote access to instruments connected locally, e.g.
via USB or a serial port.  The pyVISA library also supports USBTMC
devices; therefore, the dedicated USBTMC driver can be replaced by
pyVISA.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Introduce NetworkPyVISADevice for remote access to pyVISA resources.
Use an agent wrapper to support both local and remote resource access.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
It has been replaced by visa_instrument.py agent.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Allow users to configure the timeout for resource I/O operations while
keeping the original 2-second default.

Remove possible termination characters. This might differ among various instruments/manufacturers.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Supports VISA resource types, see
https://www.ni.com/docs/en-US/bundle/ni-visa/page/visa-resource-types.html?srsltid=AfmBOopVXVhQ0TRR_zFesL1Bj90aKB7_MFSa3nbKvgZ47RRAbv-WIlAE

Defaults to INSTR resource type. If a raw socket is used (e.g.,
TCPIP::IPaddress::Port::SOCKET), set the termination character. Always
clear the resource after it is opened.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Provides a common interface for SCPI-compatible instruments. The
instrument can be controlled using different low-level drivers, such as
PyVISA, USBTMC, or custom backends. An appropriate command backend can
be mapped within the driver for convenient instrument control.
PowerProtocol is supported.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Add a method to the tminstrument driver for querying iterables,
such as lists of readings.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Add tminstrument driver access using a remote labgrid client.
Following functions are available:
- command: send a command to the instrument
- query: send a command and read the response from the instrument
- query_iterable: send a command and read the response from the instrument as a list of floats

usage example:

$ labgrid-client -c config_remote.yaml inst identify --name "tm-inst-visa-rigol-psu_ch2"

with "tm-inst-visa-rigol-psu_ch2" driver configured in config_remote.yaml (expected ps-rigol resource exported):

targets:
  main:
    resources:
      RemotePlace:
        name: !template 'MyPlace'
    drivers:
    - PyVISADriver:
        name: driver-visa-rigol-psu_ch2
        bindings: { pyvisa_resource : 'ps-rigol'}
    - TMInstrument:
          bindings: { inst : 'driver-visa-rigol-psu_ch2' }
          name: 'tm-inst-visa-rigol-psu_ch2'

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
Add convenient functionality for:
- Rigol Technologies DP832, 3-channel laboratory power supply
- Siglent Technologies SDM3065X-SC laboratory DMM
- RND 320-KD3305P, 3-channel laboratory power supply
- ITECH Electronics IT-M3632, power supply and regenerative DC load

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
@Sabolik Sabolik force-pushed the vzlu/pyvisa branch 2 times, most recently from 1a4bb48 to 1d8be29 Compare March 30, 2026 12:38
Sabolik and others added 2 commits March 30, 2026 14:43
apply suggestion from @gastmaier:
- use find_spec from importlib to check for module availability.
- remove un-necessary try-catch block

Co-authored-by: Jorge Marques <2892061+gastmaier@users.noreply.github.com>
Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
- update pyvisadriver part
- add:
  - tminstrument driver
  - tminstrumentpower driver
  - networkpyvisa resource
  - extend pyvisa resource usage

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
It shows how to use more then pne instrument. Here as example two USBTMC instrumnets, multimeter / power supply.

Signed-off-by: Martin Sabol <martin.sabol@seznam.cz>
@Sabolik
Copy link
Copy Markdown
Author

Sabolik commented Apr 13, 2026

@threexc Did you find some time to test this?

I've been looking at it. I was able to do a basic test on my side with my SDG1032X and confirm it can communicate:

(venv) tgamblin@outpost:~/workspace/git/labgrid-power-up$ labgrid-client -c workbench/wb-client.yaml inst identify --name 'tm-inst-sdg1032x'
Selected role main and place workbench from configuration file
/usr/lib/python3/dist-packages/pyvisa_py/protocols/usbtmc.py:116: UserWarning: Unexpected MsgID format. Consider updating the device's firmware. See https://github.com/pyvisa/pyvisa-py/issues/20Expected message id was 2, got 46.
  warnings.warn(
Siglent Technologies,SDG1032X,SDG1XDFQ6R5627,1.01.01.33R

This is with the following exporter config for the device:

  wb-generator-1:
    cls: 'PyVISADevice'
    type: 'USB0'
    url: '0xF4EC::0x1103::SDG1XDFQ6R5627::0'
    backend: '@py'

and a client config like:

      - PyVISADriver:
          name: 'sdg1032x-driver'
          bindings:
            pyvisa_resource: 'wb-generator-1'
      - TMInstrument:
          name: 'tm-inst-sdg1032x'
          bindings:
            inst: 'sdg1032x-driver'

Seems reasonable enough to me. My only major issue with this PR is that there are examples in the commits but not really any updates to the docs? It would be good to have decent examples of the exporter and client configs, along with example commands to run (the ones in some of the commit messages that I've been pointed at are probably enough).

@Sabolik, can you add another commit to reflect that?

  • doc/configuration.rst: has been updated
  • examples/tminstrument: added example with two instruments, tested
  • labgrid-client usage example, I am unfortunatelly not sure where to put such example. There is generic labgrid-client CLI usage well docummented on Manual Pages.

@Sabolik Sabolik closed this Apr 13, 2026
@Sabolik Sabolik reopened this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants