Bumps: - nodes/generalFunctions 75d16c6 -> e50be2e (permissive unit check + measurement schema additions) - nodes/measurement f7c3dc2 -> 495b4cf (digital mode + dispatcher fix + 59 new tests + rewritten README + UI) Wiki: - wiki/manuals/nodes/measurement.md — new user manual covering analog and digital modes, topic reference, smoothing/outlier methods, unit policy, and the pre-fix dispatcher bug advisory. - wiki/sessions/2026-04-13-measurement-digital-mode.md — session note with findings, fix scope, test additions, and dual-mode E2E results. - wiki/index.md — links both pages and adds the missing 2026-04-13 rotatingMachine session entry that was omitted from the earlier commit. Status: measurement is now trial-ready in both analog and digital modes. 71/71 unit tests green (was 12), dual-mode E2E on live Dockerized Node-RED verifies analog regression and a three-channel MQTT-style payload (temperature/humidity/pressure) dispatching independently with per-channel smoothing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8.8 KiB
title, node, updated, status
| title | node | updated | status |
|---|---|---|---|
| measurement — User Manual | measurement | 2026-04-13 | trial-ready |
measurement — User Manual
The measurement node is the sensor-side of every EVOLV flow. It takes raw signal data, applies offset / scaling / smoothing / outlier rejection, and publishes a conditioned value into the shared MeasurementContainer. A parent equipment node (rotatingMachine, pumpingStation, reactor, ...) subscribes automatically via the child-registration handshake on port 2.
At a glance
| Item | Value |
|---|---|
| Node category | EVOLV |
| Inputs | 1 (message-driven) |
| Outputs | 3 — process / dbase / parent |
| Tick period | 1 s |
| Input modes | analog (default) — one scalar per msg. digital — object payload with many keys. |
| Smoothing methods | 12 (none, mean, min, max, sd, lowPass, highPass, weightedMovingAverage, bandPass, median, kalman, savitzkyGolay) |
| Outlier methods | 3 (zScore, iqr, modifiedZScore) |
Choosing a mode
Analog — one scalar per message (PLC / 4-20 mA)
The classic pattern — what the node did before v1.1. msg.payload is a single number. The node runs one offset → scaling → smoothing → outlier pipeline and emits exactly one MeasurementContainer slot keyed by the asset's type + position.
{ "topic": "measurement", "payload": 12.34 }
Use when one Node-RED measurement node represents one physical sensor.
Digital — object payload, many channels (MQTT / IoT / JSON)
Use when one Node-RED measurement node represents one physical device that publishes multiple readings. Common shapes:
{ "topic": "measurement",
"payload": { "temperature": 22.5, "humidity": 45, "pressure": 1013 } }
{ "topic": "measurement",
"payload": { "co2": 618, "voc": 122, "pm25": 8 } }
Each top-level key maps to a channel with its own type, position, unit, and pipeline parameters. Unknown keys are ignored (logged at debug).
Configuration
Common (both modes)
- Asset (menu): supplier, category, asset type (
assetType), model, unit. - Logger (menu): log level + enable flag.
- Position (menu):
upstream/atEquipment/downstream, optional distance offset.
Analog fields
| Field | Meaning |
|---|---|
| Scaling | enables linear interpolation from source range to process range |
| Source Min / Max | raw input bounds (e.g. 4 / 20 for mA) |
| Input Offset | additive bias applied before scaling |
| Process Min / Max | mapped output bounds (e.g. 0 / 3000 for mbar) |
| Simulator | internal random-walk source for testing |
| Smoothing | method (dropdown) |
| Window | smoothing window size |
Digital fields
- Input Mode: set to
digitalin the dropdown. - Channels (JSON): array of channel definitions.
Each channel:
{
"key": "temperature",
"type": "temperature",
"position": "atEquipment",
"unit": "C",
"scaling": { "enabled": false, "inputMin": 0, "inputMax": 1, "absMin": -50, "absMax": 150, "offset": 0 },
"smoothing": { "smoothWindow": 5, "smoothMethod": "mean" },
"outlierDetection": { "enabled": true, "method": "zScore", "threshold": 3 }
}
scaling / smoothing / outlierDetection are optional — missing sections inherit the top-level analog-mode fields. key is the JSON field name inside msg.payload; type is the MeasurementContainer axis — any string works, not just the physical-unit-backed defaults.
Input topics
| Topic | Payload | Effect |
|---|---|---|
measurement |
number (analog) / object (digital) | drives the pipeline |
simulator |
— | toggle the internal random-walk simulator |
outlierDetection |
— | toggle outlier rejection |
calibrate |
— | set the offset so the current output matches Source Min (scaling on) or Process Min (scaling off). Requires a stable window — aborts if the signal is fluctuating. |
Output ports
Port 0 — process
Delta-compressed payload.
Analog shape:
{ "mAbs": 4.2, "mPercent": 42, "totalMinValue": 0, "totalMaxValue": 100,
"totalMinSmooth": 0, "totalMaxSmooth": 4.2 }
Digital shape:
{ "channels": {
"temperature": { "key": "temperature", "type": "temperature", "position": "atEquipment",
"unit": "C", "mAbs": 24, "mPercent": 37,
"totalMinValue": 22.5, "totalMaxValue": 25.5,
"totalMinSmooth": 22.5, "totalMaxSmooth": 24 },
"humidity": { ... },
"pressure": { ... }
} }
Port 1 — dbase
InfluxDB line-protocol telemetry. Tags = asset metadata; fields = measurements. See InfluxDB Schema Design.
Port 2 — parent
{ topic: "registerChild", payload: <nodeId>, positionVsParent, distance } — emitted once ~200 ms after deploy so the parent equipment node registers this sensor.
Pipeline per value
- Outlier check (if enabled) — rejects via zScore / IQR / modifiedZScore. Rejected values never advance, don't update min/max, don't emit.
- Offset —
value + scaling.offset. - Scaling (if enabled) — linear interpolation from
[inputMin, inputMax]to[absMin, absMax]with boundary clamping. - Smoothing — current value pushed into the rolling window; the configured method produces the smoothed output.
- Min/Max tracking — both raw (pre-smoothing) and smoothed min/max tracked for display.
- Constrain — smoothed value clamped to
[absMin, absMax]. - Emit —
MeasurementContainer.type(...).variant('measured').position(...).distance(...).value(out, ts, unit)triggers the event<type>.measured.<position>(lowercase) that the parent equipment subscribes to.
In digital mode, each channel runs this pipeline independently.
Smoothing methods — quick reference
| Method | Use case |
|---|---|
none |
pass raw value through — useful for testing |
mean |
simple arithmetic average over window |
min / max |
worst-case / peak reporting |
sd |
outputs standard deviation (noise indicator) |
median |
outlier-resistant central tendency |
weightedMovingAverage |
later samples weighted higher |
lowPass |
EMA-style attenuation of high-frequency noise |
highPass |
emphasises rapid changes (step detection) |
bandPass |
lowPass + highPass - raw — band-of-interest filtering |
kalman |
recursive noise filter, converges to steady value |
savitzkyGolay |
polynomial smoothing over 5-point window |
Outlier methods — quick reference
| Method | Best when |
|---|---|
zScore |
signal is approximately normal; threshold = # of SDs |
iqr |
signal is non-normal; robust to skewed distributions |
modifiedZScore |
small samples; uses median / MAD instead of mean / SD |
Historical bug fixed 2026-04-13: The dispatcher compared against camelCase keys (
lowPass,zScore, ...) but the validator lowercases enum values. Result: 4 smoothing methods and 2 outlier methods were silently no-ops when chosen from the editor — they fell through to the "unknown" branch and emitted the raw last value. Review any flow deployed before 2026-04-13 that relied on these methods.
Unit policy
Unknown measurement types (anything not in the container's built-in measureMap: pressure, flow, power, temperature, volume, length, mass, energy) are accepted without unit compatibility checks. This lets digital channels use humidity (%), co2 (ppm), arbitrary IoT units. Known types still validate strictly.
Example flow (digital)
[
{ "id": "dig", "type": "measurement",
"mode": "digital",
"channels": "[{\"key\":\"temperature\",\"type\":\"temperature\",\"position\":\"atEquipment\",\"unit\":\"C\",\"scaling\":{\"enabled\":false,\"absMin\":-50,\"absMax\":150},\"smoothing\":{\"smoothWindow\":5,\"smoothMethod\":\"mean\"}},{\"key\":\"humidity\",\"type\":\"humidity\",\"position\":\"atEquipment\",\"unit\":\"%\",\"scaling\":{\"enabled\":false,\"absMin\":0,\"absMax\":100},\"smoothing\":{\"smoothWindow\":5,\"smoothMethod\":\"mean\"}}]",
...
}
]
Testing
cd nodes/measurement
npm test
71 tests — coverage includes every smoothing method, every outlier strategy, scaling, interpolation, constrain, calibration, stability, simulation, per-channel pipelines, digital-mode dispatch, malformed-channel handling, event emits.
End-to-end benchmark scripts live in the superproject at /tmp/m_e2e_baseline.py (analog) and /tmp/m_digital_e2e.py (digital). Run against a Dockerized Node-RED stack (docker compose up -d nodered).
Production status
Trial-ready as of 2026-04-13 after the session that fixed the silent dispatcher bug and added digital mode. See session 2026-04-13 and the memory file node_measurement.md.