构建

即使仪器没有专用软件

作者: makemind · 2026年3月12日

实验台上摆着电源、万用表和温度记录仪。三家的产品。

把这三台凑到同一块屏上,这活儿总是接不上。每家都给你一套自家仪器的 GUI,而那是给 他们自己那一台用的。把别家的万用表也放进那块屏,不是那套软件的事。所以最后还是自己写——装驱动、写通信代码、再做一块画值的屏,实验一变,这一整套全得改。

可是 仪器早就会说话。 要发明的东西没有,要包的东西才有。

三家用三种方式回答

丢一个 *IDN?,三台都答。

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

到这里都一样。一问值就分开了。

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

三行说的都是一个数字,却是三种写法。

  • 电源用 指数写法回答
  • 万用表是普通十进制,但带着一个 回车符过来
  • 温度记录仪把 单位粘在值上

三台都是 SCPI。仍然差成这样。而这正是仪器代码变脏的真正原因——不是协议难,而是 三家把同一个协议用得各有各的习惯。

把吸收的地方收成一处

每台设备配 一个读数字的函数。那是唯一知道那家习惯的地方。

/// `+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);

设备声明各自咬着其中一个。

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);

来了第四家的设备,就多一行。 分支不会散到代码各处。

方言差点在到达代码之前就消失了

这是做的时候碰上的。一开始用 Dart 默认的 LineSplitter 切行,结果它先把回车符吃掉了。 万用表的 \r 根本没走到 plain()

行为上没问题。但是 那个本该知道这家习惯的地方,实际上什么也没做。 等下一台设备送来 \r\r\n,就在那里炸,而没人知道该看哪儿。

所以只切换行符再往下传。

/// 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.

校验会看这三种方言是不是真的到了。设备要是变得整齐划一,这个样例就什么也证明不了。

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

一块屏

打开输出,读三台。

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

屏上每个数字下面都标着厂商名。哪个值来自哪个箱子,这件事留在屏上——三个值一旦变成一个形状,出处就很容易被抹掉,而实验中数字不对时,第一个该问的正是这个。

升到 9 伏。

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

温度从 26.9 升到 40.5。是温度记录仪这么答的,不是屏根据功率算出来的。

此内容需要开发者或更高等级

登录并升级您的方案即可继续阅读。

查看方案
Twitter