Pymeasure Instruments#

Publish the commands you send to a pymeasure instrument, and the values you read from it, through any instro publisher. Your drivers don’t change.

You can use it in two ways:

  • attach() adds publishing to pymeasure code you already have.

  • PymeasureInstrument wraps an instrument in new instro code, with tags and background polling.

Setting a property publishes a Command on <name>.<channel>.cmd, and reading one publishes a Measurement on <name>.<channel>. That includes access inside driver methods such as apply_voltage(). Channels take the property’s name unless you set your own. The example notebook runs all of this against simulated instruments.

Install#

uv add "instro[pymeasure]"

Publish from existing code#

attach() adds publishing to an instrument you already use, with any list of instro publishers. In a pymeasure Procedure, attach in startup() and close the recorder in shutdown().

from pymeasure.instruments.keithley import Keithley2400

from instro.lib import NominalCorePublisher
from instro.pymeasure import attach

keithley = Keithley2400("GPIB::24")
recorder = attach(keithley, "smu", [NominalCorePublisher(dataset_rid="<dataset rid>")])

keithley.apply_voltage()       # existing code, unchanged
keithley.source_voltage = 1.0  # publishes smu.source_voltage.cmd
print(keithley.current)        # publishes smu.current

recorder.close()  # flushes and closes the publishers; the instrument stays connected

Write new instro code#

PymeasureInstrument is an instro Instrument, so it also has tags, the background daemon and get_channel().

from pymeasure.instruments.keysight import KeysightE36312A

from instro.pymeasure import PymeasureInstrument

with PymeasureInstrument(
    name="psu",
    instrument=KeysightE36312A("USB0::0x2A8D::0x1102::MY12345678::INSTR"),
    dataset_rid="<dataset rid>",
    poll=["ch_1.voltage", "ch_1.current"],  # read on every background_interval
) as psu:
    psu.device.ch_1.voltage_setpoint = 5  # .device is the pymeasure instrument
    psu.start()
    print(psu.get_channel("ch1.current", wait_for_new_samples=True).latest)

close() closes the instrument’s connection. It doesn’t call the driver’s shutdown(), which on many drivers turns outputs off, so call psu.device.shutdown() first if you want that. Pass close_device=False to keep the connection open.

Channel names#

Every property the driver defines is published under its attribute path.

pymeasure

Published as

supply.ch_1.voltage_setpoint = 5

psu.ch1.voltage_setpoint.cmd = 5.0

supply.ch_1.current

psu.ch1.current

supply.ch_1.output_enabled (bool)

psu.ch1.output_enabled = 1.0 or 0.0

a string or mapped value

a string channel

a list of up to 16 values ("1,2,3")

….0, ….1, ….2

instro skips properties inherited from pymeasure’s base classes (id, status, next_error) and arrays longer than 16 values, such as traces. It logs a warning the first time it skips an array.

To choose the names, generate a channel map, edit it, and pass it as channel_map=:

instro pymeasure map keysight.KeysightE36312A -o psu_map.json
{
  "format": "instro-pymeasure-channel-map/1",
  "include_unmapped": false,
  "channels": {
    "ch_1.voltage_setpoint": {"channel": "out1.v_set", "kind": "control", "get": "VOLT? (@{ch})", "set": "VOLT %g, (@{ch})"},
    "ch_2.voltage_setpoint": {"channel": null, "kind": "control", "get": "VOLT? (@{ch})", "set": "VOLT %g, (@{ch})"}
  }
}
attach(supply, "psu", publishers, channel_map="psu_map.json")

Change an entry’s channel to rename it, or set it to null to stop publishing it. Properties missing from the map aren’t published, unless you set "include_unmapped": true. instro rejects a map when you attach if it names a property the instrument doesn’t have, gives two properties one name, or uses a name ending in .cmd.

Some drivers add channels in __init__ instead of with ChannelCreator. For those, add --resource <VISA resource> to the command, or call ChannelMap.generate() with the connected instrument. The notebook below builds a map in Python. See CLI for the commands.

Failures and cleanup#

  • A set or read that raises publishes nothing.

  • A publisher that raises is logged. The set or read still completes, because the instrument has already acted.

  • close() or detach() stops background polling and restores the original classes.

See the API reference for every option.