Install
pip install benchmesh # the client, no dependencies
pip install "benchmesh[pandas]" # plus pandas for DataFramesThe 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
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=:
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 1Values
- Measured values become
floatin base units (V, A, W, Ω). A meter that reports something that is not a number (such asOL) gives that text back. - Modes and switches are strings (
"ON","VOLT"), flagsbool, missing valuesNone, binary resultsbytes. raw=Trueonquery,readandstatusreturns the server’s JSON unchanged.- Setter values are sent as given and checked by the server against the definition; an invalid one raises
BadRequestbefore 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.
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:
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]"):
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
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:
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
| Call | Gives |
|---|---|
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
| Exception | When |
|---|---|
ServerNotRunning | Nothing answers at the address - start the BenchMesh app. |
BadRequest | A value outside the setter’s limits or not allowed (400 / 422). |
NotFound | Unknown instrument, class, operation or field. |
Refused | The instrument definition refused it (a mode it needs, parked, a flow that energises without arm). |
InstrumentUnavailable | The instrument is offline or timed out. |
AuthenticationError | Wrong password or no session (remote access). |
FingerprintMismatch | The certificate is not the one you pinned. |
All are BenchMeshError with .status and .detail, the server’s message.