온실 하나에 센서가 여러 개 붙는다. 온도, 습도, CO2. 여기에 환기창과 밸브 같은 움직이는 것들이 더 붙는다.
이게 처음 설치할 때는 문제가 아니다. 세 개면 세 개를 코드에 적으면 된다. 문제는 3년 뒤에 온다. 온도계가 고장 나서 다른 제품으로 바꾼다. 그 제품은 화씨로 잰다. CO2 센서를 하나 더 단다. 밸브를 추가한다.
그때마다 제어 코드를 고쳐야 한다면, 그 온실은 설치한 사람 없이는 못 고치는 물건이 된다.
노드가 자기를 말한다
이 샘플에서 각 노드는 여섯 칸으로 자기를 선언한다.
printf("%s{\"id\":\"%s\",\"role\":\"%s\",\"kind\":\"%s\","
"\"model\":\"%s\",\"unit\":\"%s\",\"value\":%.1f}",
i ? "," : "", n->id, n->role, n->kind, n->model,
n->unit, n->value);
id · role · kind · model · unit · value.
unit 이 칸으로 있는 게 핵심이다. 화씨로 재는 온도계는 화씨라고 말한다. 남 좋으라고 미리 섭씨로 바꿔 주지 않는다. 하드웨어는 자기가 아는 것만 정직하게 말하고, 맞추는 일은 위에서 한다.
맞추는 자리는 한 곳이다
/// 규칙이 쓰는 단위로 옮긴 값.
///
/// 이 샘플에서 단위를 아는 곳은 여기 하나뿐이고, 모델명이 아니라 노드가
/// 선언한 unit 을 보고 판단한다. 아무도 들어 본 적 없는 센서라도 자기 단위를
/// 말하므로 여기로 정확히 들어온다.
double get canonicalValue {
switch (unit) {
case 'F':
return (value - 32) * 5 / 9;
default:
return value;
}
}
모델명으로 분기했다면 이 자리가 곧 목록이 된다. 새 제품을 살 때마다 줄이 늘고, 목록에 없는 제품이 들어오면 조용히 틀린 값을 쓴다.
unit 을 보면 처음 보는 제품도 알아서 맞는다.
규칙은 부품 번호를 모른다
재배 규칙 하나가 일곱 줄이다.
Rule(
name: 'vent above 26C',
whenKind: 'temperature',
above: 26.0,
thenKind: 'vent',
setTo: 80.0,
elseSetTo: 0.0,
),
어느 센서인지 안 적혀 있다. 어느 릴레이인지도 안 적혀 있다. 읽는 값의 종류와 움직일 것의 종류만 있다.
TH-100 이든 FX-200 이든 kind 가 temperature 면 이 규칙이 본다.
갈아 끼워 봤다
설치 A — 섭씨 온도계, 노드 셋.
A: discovered 3 nodes — t1:temperature:TH-100(C), h1:humidity:HM-20(pct), v1:vent:VT-9(pct)
A: vent above 26C: 26.1 -> v1=80.0


이제 온도계를 화씨 모델로 바꾸고 CO2 센서와 밸브를 증설한다.
=== pass B: probe swapped for a Fahrenheit model, two nodes added ===
server code: unchanged rules: unchanged rebuild: none
B: discovered 5 nodes — t1:temperature:FX-200(F), h1:humidity:HM-20(pct),
c1:co2:CO-5(ppm), v1:vent:VT-9(pct), w1:valve:WV-3(pct)
B: vent above 26C: 26.1 -> v1=80.0 · water below 60% humidity: 62.8 -> w1=0.0


서버 코드 안 바뀜. 규칙 안 바뀜. 재빌드 없음.
그리고 vent above 26C 가 두 설치에서 똑같이 26.1에서 발동했다. 화씨 센서가 보낸 값이 한 곳에서 섭씨로 옮겨졌기 때문이다. 그 한 곳이 없었으면 화씨 79도가 26도 임계값을 그냥 넘겨서 환기창이 계속 열려 있었을 것이다.