빌드

계측기에 전용 소프트웨어가 없어도

작성: 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