--- myst: html_meta: description: "Publish pymeasure instrument commands and readings through any instro publisher without rewriting drivers" --- # Pymeasure Instruments {.lead} Publish the commands you send to a [pymeasure](https://pymeasure.readthedocs.io) 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()`](#publish-from-existing-code) adds publishing to pymeasure code you already have. - [`PymeasureInstrument`](#write-new-instro-code) wraps an instrument in new instro code, with tags and background polling. Setting a property publishes a `Command` on `..cmd`, and reading one publishes a `Measurement` on `.`. That includes access inside driver methods such as `apply_voltage()`. Channels take the property's name unless you [set your own](#channel-names). The [example notebook](/examples/notebooks/pymeasure_publishing.md) runs all of this against simulated instruments. ## Install ```bash uv add "instro[pymeasure]" ``` ## Publish from existing code `attach()` adds publishing to an instrument you already use, with any list of instro [publishers](publishers.md). In a pymeasure `Procedure`, attach in `startup()` and close the recorder in `shutdown()`. ```python 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="")]) 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`](/library/concepts.md), so it also has tags, the background daemon and `get_channel()`. ```python from pymeasure.instruments.keysight import KeysightE36312A from instro.pymeasure import PymeasureInstrument with PymeasureInstrument( name="psu", instrument=KeysightE36312A("USB0::0x2A8D::0x1102::MY12345678::INSTR"), 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=`: ```bash instro pymeasure map keysight.KeysightE36312A -o psu_map.json ``` ```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})"} } } ``` ```python 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 ` to the command, or call `ChannelMap.generate()` with the connected instrument. The notebook below builds a map in Python. See [CLI](/cli.md#instro-pymeasure) 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](/ref/library/pymeasure.md) for every option.