Build

When the Instrument Has No Dedicated Software

By makemind · Mar 12, 2026

On the bench there is a power supply, a multimeter and a temperature logger. Three vendors.

Getting all three onto one screen never quite works. Each vendor gives you a GUI for their instrument, and that GUI is for one box of theirs. Putting another company's multimeter on that screen is not that software's job. So in the end you write it yourself — install drivers, write comms code, build a screen to plot the values, and when the experiment changes, edit all of it.

But instruments already know how to talk. There is nothing to invent, only something to wrap.

Three vendors answer three ways

Throw *IDN? and all three reply.

IDN VENDOR-A,PSU-3010,SN421337,1.4
IDN VENDOR-B,DMM-71,SN090210,2.0
IDN VENDOR-C,TL-4,SN005150,0.9

So far, the same. Ask for a value and they diverge.

psu1:MEAS:VOLT?  ->  +3.29505E+00
dmm1:MEAS:CURR?  ->  0.4119\r
temp1:MEAS:TEMP? ->  26.9 C

Three lines all saying one number, in three ways.

  • The supply answers in exponent notation
  • The multimeter is plain decimal but arrives with a carriage return
  • The logger sends the unit glued to the value

All three are SCPI. And still they differ like this. This is the real reason instrumentation code gets messy — not that the protocol is hard, but that three vendors use the same protocol slightly differently.

One place to absorb it

Each instrument gets one function that reads a number. That is the only place that knows that vendor's habit.

/// `+3.30000E+00` — exponent notation. Dart parses it directly.
static double exponent(String raw) => double.parse(raw.trim());

/// `0.4125\r\n` — plain, but with a carriage return that will silently
/// break a naive parse if it is not trimmed.
static double plain(String raw) => double.parse(raw.trim());

/// `24.8 C` — the unit is glued to the value. Split it off, and keep the
/// unit out of the number rather than out of the record.
static double withUnit(String raw) => double.parse(raw.trim().split(' ').first);

The instrument declarations each hold one of those.

static const _psu = _Instrument('psu1', 'VENDOR-A', _Instrument.exponent);
static const _dmm = _Instrument('dmm1', 'VENDOR-B', _Instrument.plain);
static const _tmp = _Instrument('temp1', 'VENDOR-C', _Instrument.withUnit);

A fourth vendor's box adds one line. The branching doesn't spread across the code.

The dialect nearly vanished before reaching the code

This came out of building it. At first lines were split with Dart's default LineSplitter, and that ate the carriage return first. The multimeter's \r never reached plain().

Behaviourally there is no problem. But the place that is supposed to know that vendor's habit ends up doing nothing. When the next instrument sends \r\r\n it blows up there, and nobody knows where to look.

So only the newline is cut.

/// Lines are split on the newline only, deliberately. Dart's LineSplitter
/// would swallow a vendor's carriage return before this code ever sees it,
/// and then the one place that is supposed to know about that vendor's habit
/// would never be exercised. Terminators are a property of the instrument, so
/// they arrive intact and get dealt with once.

The check confirms the three dialects actually arrived. If the instruments turn uniform, this sample demonstrates nothing.

grep -q 'psu1:MEAS:VOLT? -> +[0-9]\.[0-9]*E+0' captures/run.log || exit 1
grep -q 'dmm1:MEAS:CURR? -> [0-9]*\.[0-9]*\\r' captures/run.log || exit 1
grep -q 'temp1:MEAS:TEMP? -> [0-9]*\.[0-9]* C'  captures/run.log || exit 1

One screen

Turn the output on and read all three.

BENCH — 3.295 volts VENDOR-A · 0.4119 amps VENDOR-B · 26.9 celsius VENDOR-C · 1.36 W
BENCH — 3.295 volts VENDOR-A · 0.4119 amps VENDOR-B · 26.9 celsius VENDOR-C · 1.36 W
after output on: 3.295 V · 0.4119 A · 26.9 C · 1.36 W
verbatim: psu1:MEAS:VOLT? -> +3.29505E+00 | dmm1:MEAS:CURR? -> 0.4119\r | temp1:MEAS:TEMP? -> 26.9 C

The screen puts each vendor's name under its number. Which value came from which box stays on the screen — once three values take one shape the provenance is easy to erase, and that is the first thing to ask when a number looks wrong mid-experiment.

Raise it to 9 volts.

8.986 volts · 1.1233 amps · 40.5 celsius · 10.09 W
8.986 volts · 1.1233 amps · 40.5 celsius · 10.09 W
asked for 9 V -> meter reads 8.986 V, 40.5 C, 10.09 W

Temperature went from 26.9 to 40.5. Because the logger answered that, not because the screen computed it from power.

This content requires Developer or above

Sign in and upgrade your plan to continue reading.

View Plans
Twitter