実装

計測器に専用ソフトが無くても

著者: 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 で行を切っていたが、それがキャリッジリターンを先に食べてしまった。 マルチメータの \rplain() に届きさえしなかった。

動作には問題がない。ところが その会社の癖を知る場所が、実際には何もしていない状態になる。 次の機器が \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