Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.4 KiB
measurement
A measurement turns a raw sensor signal into a validated, scaled, smoothed reading and re-emits it for any upstream parent. Two modes: analog (one channel built from the flat config — classic 4–20 mA / PLC style) and digital (one Channel per config.channels[] entry — MQTT / IoT JSON style). It is a leaf in the S88 hierarchy — no children of its own — and registers itself as a child of any parent that accepts measurements (rotatingMachine, machineGroupControl, pumpingStation, reactor, monster, …).
Note
Pending full node review (2026-05). Content reflects
CONTRACT.md,src/commands/index.js, and current source only. Some sections are best-effort placeholders pending the next pass.
At a glance
| Thing | Value |
|---|---|
| What it represents | One sensor signal — pressure / flow / power / temperature / level / … |
| S88 level | Control Module |
| Use it when | You need to scale, offset, smooth, outlier-filter, or simulate a sensor reading before handing it to an equipment / unit / process-cell node |
| Don't use it for | Sensor fusion, threshold-trip alarms, or as a control output — this node is read-only signal conditioning |
| Children it accepts | None — leaf node |
| Parents it talks to | Any node that subscribes to <type>.measured.<position> events (rotatingMachine, MGC, pumpingStation, reactor, monster, …) |
How it fits
flowchart LR
raw[Raw sensor / MQTT / inject<br/>analog scalar or digital object]
m[measurement<br/>Control Module]:::ctrl
p1[rotatingMachine<br/>Equipment]:::equip
p2[machineGroupControl<br/>Unit]:::unit
p3[pumpingStation<br/>Process Cell]:::pc
raw -->|data.measurement| m
m -->|child.register<br/>(Port 2 at startup)| p1
m -->|child.register| p2
m -->|child.register| p3
m -.->|"<type>.measured.<position>"| p1
m -.->|"<type>.measured.<position>"| p2
m -.->|"<type>.measured.<position>"| p3
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
S88 colours: Control Module #a9daee, Equipment #86bbdd, Unit #50a8d9, Process Cell #0c99d9. Source of truth: .claude/rules/node-red-flow-layout.md.
Try it — 1-minute demo
Import the basic example flow, deploy, and drive a single sensor through scaling + smoothing.
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/measurement/examples/basic.flow.json \
http://localhost:1880/flows
What to do after deploy:
- Click the
measurement 42inject — sendstopic: 'measurement'(legacy alias ofdata.measurement) with payload42. - Watch Port 0 in the debug pane:
mAbsupdates immediately. After a few injectstotalMinValue/totalMaxValuestart tracking the rolling min/max. - Toggle the simulator: send
topic: 'set.simulator'.tick()(1000 ms) starts drivinginputValuethroughSimulator.step(). - Trigger calibration: send
topic: 'cmd.calibrate'. If the rolling window is stable (stdDev <= config.calibration.stabilityThreshold) the calibrator captures the current output as the newconfig.scaling.offset.
Important
GIF needed. Demo recording of steps 1–4 with the live status badge. Save as
wiki/_partial-gifs/measurement/01-basic-demo.gif, target ≤ 1 MB aftergifsicle -O3 --lossy=80.
The four things you'll send
| Topic | Aliases | Payload | What it does |
|---|---|---|---|
data.measurement |
measurement |
analog: number (or numeric string); digital: {<channelKey>: number, …} |
Push a raw reading into the pipeline. Wrong shape for the configured mode logs a hint suggesting the other mode. |
set.simulator |
simulator |
(ignored) | Toggle the built-in Simulator random-walk on / off. Mutates config.simulation.enabled. |
set.outlier-detection |
outlierDetection |
(ignored) | Toggle outlier detection on the analog pipeline. Mutates config.outlierDetection.enabled. |
cmd.calibrate |
calibrate |
(ignored) | Run a one-shot calibration. Captures the current output as config.scaling.offset; aborts with a warn if the buffer is not stable. |
Aliases log a one-time deprecation warning the first time they fire.
What you'll see come out
Sample Port 0 message (analog mode, after a few injects):
{
"topic": "measurement#sensor_a",
"payload": {
"mAbs": 0.42,
"mPercent": 42,
"totalMinValue": 0.12,
"totalMaxValue": 0.78,
"totalMinSmooth": 0.20,
"totalMaxSmooth": 0.65
}
}
Sample Port 0 message (digital mode):
{
"topic": "measurement#multi",
"payload": {
"channels": {
"level-a": { "mAbs": 1.84, "mPercent": 73.6, "totalMinValue": 0.1, "totalMaxValue": 2.4 },
"temp-a": { "mAbs": 18.2, "mPercent": 36.4, "totalMinValue": 14.0, "totalMaxValue": 22.1 }
}
}
}
| Field | Meaning |
|---|---|
mAbs |
Latest output value in scaling-units (after offset + scaling + smoothing). |
mPercent |
Same value mapped to interpolation.percentMin..percentMax (default 0..100). |
totalMinValue / totalMaxValue |
Rolling min/max of raw (pre-scaling) values. 0 until first sample. |
totalMinSmooth / totalMaxSmooth |
Rolling min/max of the smoothed output. |
Additionally the source.measurements.emitter fires <type>.measured.<position> on every accepted update — parents subscribe to that event through the child.measurements.emitter handshake established at register time. See Architecture — Lifecycle for the full path.
How the pipeline behaves
flowchart LR
in[input value] --> out{outlierDetection.enabled?}
out -- yes --> oc[_isOutlier]
oc -- outlier --> drop[drop + warn]
oc -- ok --> off[apply scaling.offset]
out -- no --> off
off --> mm[update raw totalMin/Max]
mm --> sc{scaling.enabled?}
sc -- yes --> lin[linear map<br/>input range → abs range]
sc -- no --> sm[pass-through]
lin --> sm
sm --> sw[push to storedValues<br/>length capped by smoothWindow]
sw --> sf[smoothMethod:<br/>mean / median / kalman / …]
sf --> sm2[update smooth totalMin/Max]
sm2 --> wo[round + write outputAbs<br/>+ emit measurement event]
The same pipeline runs per Channel instance — once in analog mode, N times in digital mode.
Need more?
| Page | What you'll find |
|---|---|
| Reference — Contracts | Full topic contract, config schema, child-registration handshake |
| Reference — Architecture | Code map, lifecycle, analog vs digital branching, per-Channel pipeline |
| Reference — Examples | Shipped example flows + debug recipes |
| Reference — Limitations | When not to use, known limitations, open questions |