Skip to content
BenchMesh

Connectors · pip install benchmesh

Python package

The benchmesh package puts your bench in Python: read and set instruments by name, follow live readings, load recordings into pandas, run flows - in scripts, Jupyter notebooks and pytest. No dependencies, Python 3.9 or newer.

Install

Terminal
pip install benchmesh              # the client, no dependencies
pip install "benchmesh[pandas]"    # plus pandas for DataFrames

The BenchMesh app (or benchmesh-server) must be running: the package talks to its API at http://127.0.0.1:57666 on the same computer. Nothing needs to be switched on for that.

Quick start

Python
import benchmesh

bench = benchmesh.connect()            # $BENCHMESH_URL or http://127.0.0.1:57666

for inst in bench.instruments():
    print(inst.id, inst.classes, inst.model, "online" if inst.online else "offline")

psu = bench["tenma-72-2540"]           # the id from the Instruments drawer
psu.set("voltage", 5.0)                # checked by the server against 0-60 V
psu.set("output", "ON")                # switches the output on at once
print(psu.read("VOUT"))                # latest reading: 4.94 (float, volts)
print(psu.status())                    # {"VOUT": 4.94, "IOUT": 0.012, "OUTPUT": "ON", ...}
print(psu.query("voltage"))            # asks the instrument now
psu.set("output", "OFF")

Every instrument of a class works the same way; names, limits, ranges and modes come from the instrument’s definition, exactly as in the app. An instrument with several classes takes cls=, a multi-channel one ch=:

Python
spm = bench["owon-spm3103"]             # a supply with a built-in meter
spm.set("voltage", 3.3, cls="PSU")
spm.read("MEAS", cls="DMM")
scope.set("scale", 0.5, ch=2)          # channels 1-9, default 1

Values

  • Measured values become float in base units (V, A, W, Ω). A meter that reports something that is not a number (such as OL) gives that text back.
  • Modes and switches are strings ("ON", "VOLT"), flags bool, missing values None, binary results bytes.
  • raw=True on query, read and status returns the server’s JSON unchanged.
  • Setter values are sent as given and checked by the server against the definition; an invalid one raises BadRequest before anything reaches the instrument.

Read a meter, adjust a load

The classic bench loop: hold 5 V on a source by raising or lowering an electronic load’s current while a separate meter watches the voltage. bench.watch() reads the latest values BenchMesh already polls, so the loop adds no traffic to the instruments.

hold_voltage.py
import benchmesh

bench = benchmesh.connect()
load = bench["owon-oel1515"]
current = 0.0
load.set("mode", "CURR")
load.set("curr", current)
load.set("input", "ON")                # the load starts drawing current
try:
    for t, values in bench.watch({"v": ("owon-xdm1241", "DMM", "MEAS")}, interval=0.5, duration=60):
        v = values["v"]
        if v is None:
            continue                   # meter offline
        current = min(current + 0.01, 1.0) if v > 5.0 else max(current - 0.01, 0.0)
        load.set("curr", round(current, 3))
finally:
    load.set("input", "OFF")

For loops that must keep running when your script does not - unattended, with a guaranteed cleanup - write a flow instead; the app runs it with the same safety rules as everything else.

Recordings in pandas

The app records on whole seconds on one shared timeline; your script only steps the voltage. The recording appears in the app’s Recording drawer as well:

sweep_to_pandas.py
import time
import benchmesh

bench = benchmesh.connect()
psu = bench["tenma-72-2540"]
psu.set("voltage", 1.0)
psu.set("output", "ON")
try:
    with bench.record("sweep", {"vout": (psu.id, "PSU", "VOUT"),
                                "meter": ("owon-xdm1241", "DMM", "MEAS")}) as rec:
        for v in (1.0, 2.0, 3.0, 4.0, 5.0):
            psu.set("voltage", v)
            time.sleep(2)
finally:
    psu.set("output", "OFF")

df = rec.to_pandas()                   # index: time (UTC), one column per label
df.plot()

Older recordings, for example in a Jupyter notebook (pip install "benchmesh[pandas]"):

Jupyter
rec = bench.recordings()[0]             # newest first
df = rec.to_pandas()
df.resample("10s").mean().plot(subplots=True)

rec.data() returns plain lists {label: (times_s, values)} without pandas, and rec.export_csv() the CSV the app exports.

Automation flows

Python
for f in bench.flows():
    print(f.id, f.name)

status = bench.run_flow("hold-5v", params={"target": 4.8}, arm=True,
                        on_event=lambda e: print(e["msg"]))
print(status.state, status.vars)       # "finished" / "failed" / "aborted"

A flow that can switch outputs on needs arm=True, otherwise the server refuses it with Refused. run_flow waits until the run ends; a timeout or Ctrl-C aborts it, and its cleanup still runs.

From another computer

On the bench computer switch on remote access (Instruments → Remote access: a password, then the switch) and note the address and the certificate fingerprint. Then, on any computer of your local network:

Python
bench = benchmesh.connect(
    "https://192.168.1.20:57667",
    password="the password",
    fingerprint="AB:CD:...:EF",        # as shown in the app; case and colons do not matter
)

The certificate is the app’s own, so the package pins it: right after the TLS handshake it compares the certificate’s SHA-256 fingerprint with the one you give, and only then sends the password. BENCHMESH_URL, BENCHMESH_PASSWORD and BENCHMESH_FINGERPRINT can replace the arguments. Signed in with the view-only password, reads, watch and recordings work and every change raises AuthenticationError (403).

Reference

CallGives
benchmesh.connect(url=None, *, password=None, fingerprint=None, timeout=30.0)A Bench; checks that the server answers.
bench.instruments(cls=None) / bench.instrument(id) / bench[id]Instruments, optionally of one class.
bench.watch(signals, interval=0.5, duration=None)Iterator of (time_s, {label: value}), only when a reading changed.
bench.record(name, signals, interval_s=1)A recording on the app’s timeline; as a context manager it stops on exit.
bench.recordings() / bench.recording(id)Recordings, newest first.
bench.flows() / bench.run_flow(flow, params=None, *, arm=False, wait=True, timeout=None)Saved flows; run one and wait for the result.
bench.run_status() / bench.abort_run() / bench.continue_run()The current run.
inst.set(op, value, *, cls=None, ch=1)Run a setter.
inst.query(op, *, cls=None, ch=1)Run a query on the instrument now.
inst.read(field, ...) / inst.status(...)The latest polled reading(s) - nothing sent to the instrument.
inst.operations(cls=None)Every query and setter with type, limits, unit, allowed values and whether it energises.
rec.data() / rec.to_pandas() / rec.export_csv()Samples as lists, a DataFrame or the app’s CSV.

A signal is (device_id, class, field) or (device_id, class, field, channel). Prefer existing VISA code? Every instrument also answers SCPI on a virtual SCPI port.

Errors

ExceptionWhen
ServerNotRunningNothing answers at the address - start the BenchMesh app.
BadRequestA value outside the setter’s limits or not allowed (400 / 422).
NotFoundUnknown instrument, class, operation or field.
RefusedThe instrument definition refused it (a mode it needs, parked, a flow that energises without arm).
InstrumentUnavailableThe instrument is offline or timed out.
AuthenticationErrorWrong password or no session (remote access).
FingerprintMismatchThe certificate is not the one you pinned.

All are BenchMeshError with .status and .detail, the server’s message.