Some checks failed
CI / lint-and-test (push) Has been cancelled
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>
204 lines
8.8 KiB
Markdown
204 lines
8.8 KiB
Markdown
---
|
|
title: measurement — User Manual
|
|
node: measurement
|
|
updated: 2026-04-13
|
|
status: 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.
|
|
|
|
```json
|
|
{ "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:
|
|
|
|
```json
|
|
{ "topic": "measurement",
|
|
"payload": { "temperature": 22.5, "humidity": 45, "pressure": 1013 } }
|
|
```
|
|
|
|
```json
|
|
{ "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 `digital` in the dropdown.
|
|
- **Channels (JSON)**: array of channel definitions.
|
|
|
|
Each channel:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{ "mAbs": 4.2, "mPercent": 42, "totalMinValue": 0, "totalMaxValue": 100,
|
|
"totalMinSmooth": 0, "totalMaxSmooth": 4.2 }
|
|
```
|
|
|
|
**Digital** shape:
|
|
|
|
```json
|
|
{ "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](../../concepts/influxdb-schema-design.md).
|
|
|
|
### 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
|
|
|
|
1. **Outlier check** (if enabled) — rejects via zScore / IQR / modifiedZScore. Rejected values never advance, don't update min/max, don't emit.
|
|
2. **Offset** — `value + scaling.offset`.
|
|
3. **Scaling** (if enabled) — linear interpolation from `[inputMin, inputMax]` to `[absMin, absMax]` with boundary clamping.
|
|
4. **Smoothing** — current value pushed into the rolling window; the configured method produces the smoothed output.
|
|
5. **Min/Max tracking** — both raw (pre-smoothing) and smoothed min/max tracked for display.
|
|
6. **Constrain** — smoothed value clamped to `[absMin, absMax]`.
|
|
7. **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)
|
|
|
|
```json
|
|
[
|
|
{ "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
|
|
|
|
```bash
|
|
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](../../sessions/2026-04-13-measurement-digital-mode.md) and the memory file `node_measurement.md`.
|