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`, …).
> 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.
| 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`, …) |
S88 colours: Control Module `#a9daee`, Equipment `#86bbdd`, Unit `#50a8d9`, Process Cell `#0c99d9`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
1. Click the `measurement 42` inject — sends `topic: 'measurement'` (legacy alias of `data.measurement`) with payload `42`.
2. Watch Port 0 in the debug pane: `mAbs` updates immediately. After a few injects `totalMinValue` / `totalMaxValue` start tracking the rolling min/max.
3. Toggle the simulator: send `topic: 'set.simulator'`. `tick()` (1000 ms) starts driving `inputValue` through `Simulator.step()`.
4. Trigger calibration: send `topic: 'cmd.calibrate'`. If the rolling window is stable (`stdDev <= config.calibration.stabilityThreshold`) the calibrator captures the current output as the new `config.scaling.offset`.
> **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 after `gifsicle -O3 --lossy=80`.
| `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. |
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](Reference-Architecture#lifecycle) for the full path.