Files
measurement/wiki/Reference-Examples.md
znetsixe 1a16f9c4f1 docs(wiki): full 5-page wiki matching the rotatingMachine reference format
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>
2026-05-19 09:42:10 +02:00

149 lines
8.9 KiB
Markdown

# Reference &mdash; Examples
![code-ref](https://img.shields.io/badge/code--ref-b884c0f-blue)
> [!NOTE]
> Every example flow shipped under `nodes/measurement/examples/`, plus how to load them, what they show, and the debug recipes that go with them. Live source: `nodes/measurement/examples/`.
>
> Pending full node review (2026-05). Tier-1/2/3 visual-first example flows are still TODO (tracked in the superproject `MEMORY.md` "TODO: Example Flows"). The current shipped flows pre-date the refactor; treat them as smoke tests, not as production templates.
---
## Shipped examples
| File | Tier | Dependencies | What it shows | Status |
|:---|:---:|:---|:---|:---|
| `basic.flow.json` | 1 | EVOLV only | Single measurement node driven by inject buttons &mdash; analog scalar input, scaling enabled, three debug taps on Port 0/1/2. | Legacy pre-refactor shape, still imports. |
| `integration.flow.json` | 2 | EVOLV only | Parent-child wiring &mdash; measurement registers as a child of another node and emits its `<type>.measured.<position>` events. | Legacy pre-refactor shape. |
| `edge.flow.json` | 3 | EVOLV only | Invalid / edge payload driving for robustness checks (non-numeric strings, object in analog mode, &hellip;). | Legacy pre-refactor shape. |
The three legacy files predate the AssetResolver refactor and the analog-vs-digital mode flag. They still deploy (the editor will accept the older shape and `nodeClass.buildDomainConfig` reshapes whatever it finds), but the recommended Tier-1/2/3 visual-first replacements are still to be written.
> [!IMPORTANT]
> **TODO &mdash; Tier-1/2/3 visual-first flows.** Replace the three legacy files with:
> - `01 - Basic Analog.json` &mdash; one measurement, inject + scaling + smoothing + outlier-detection toggle + simulator.
> - `02 - Integration with rotatingMachine.json` &mdash; measurement registered as a pressure sensor on a `rotatingMachine`, Port 2 auto-register on deploy, parent's prediction updates as the measurement value moves.
> - `03 - Digital Multi-Channel.json` &mdash; one measurement in `digital` mode with 2&ndash;3 channels (e.g. `level-a`, `temp-a`, `flow-a`) fed by a single object-payload inject.
---
## Loading a flow
### Via the editor
1. Open the Node-RED editor at `http://localhost:1880`.
2. Menu &rarr; Import &rarr; drag the JSON file.
3. Click Deploy.
### Via the Admin API
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/measurement/examples/basic.flow.json \
http://localhost:1880/flows
```
---
## Example &mdash; `basic.flow.json`
Single-measurement flow with the minimum kit to exercise scaling.
### Nodes on the tab
| Type | Purpose |
|:---|:---|
| `inject` | One-shot `topic: 'measurement', payload: 42` (legacy alias of `data.measurement`) |
| `measurement` | The unit under test &mdash; analog mode, scaling enabled (0..100 &rarr; 0..10), `mean` smoothing, window 5 |
| `debug` &times; 3 | Port 0 (process), Port 1 (InfluxDB), Port 2 (registration) |
### What to do after deploy
1. Click the inject. Port 0 fires with `mAbs ≈ 4.2` (42 scaled into 0..10), `mPercent ≈ 42`.
2. Send another value via the same inject (edit the inject payload to `60`). `totalMinValue` / `totalMaxValue` start tracking, `mAbs` jumps to ~6.0.
3. Send `topic: 'set.simulator'` (use a second inject). `tick()` starts driving `inputValue` through `Simulator.step()` every 1000 ms; Port 0 updates appear automatically.
4. Send `topic: 'cmd.calibrate'`. If `stdDev <= 0.01` (the default `stabilityThreshold`), `config.scaling.offset` jumps to `inputMin - currentOutput`; if not, a warn appears in the log.
5. Send `topic: 'set.outlier-detection'`, then inject a wildly out-of-band value (e.g. `9999`). With outlier detection on the value is dropped with `Outlier detected. Ignoring value=9999`.
> [!IMPORTANT]
> **Screenshot needed.** Editor capture of `basic.flow.json` plus the Port 0 debug output. Save as `wiki/_partial-screenshots/measurement/basic-flow.png`. Replace this callout with the image link.
---
## Example &mdash; `integration.flow.json`
Demonstrates the parent-child handshake: the measurement node's Port 2 auto-fires `child.register` to its parent on deploy, and the parent then receives the `<type>.measured.<position>` event whenever a new reading lands.
> [!IMPORTANT]
> **Screenshot needed.** Editor capture of `integration.flow.json` showing the wiring. Save as `wiki/_partial-screenshots/measurement/integration-flow.png`.
> [!NOTE]
> TODO: confirm the integration flow targets a real EVOLV parent (e.g. `rotatingMachine`) versus a mock function node; if it's a mock, the Tier-2 replacement should use a real parent.
---
## Example &mdash; `edge.flow.json`
Drives the node with malformed inputs to verify the warn paths land cleanly:
- Non-numeric string in analog mode &rarr; `Invalid numeric measurement payload: <value>`.
- Object payload in analog mode &rarr; `analog mode received an object payload (keys: &hellip;). Switch Input Mode to 'digital' &hellip;`.
- Numeric scalar in digital mode &rarr; `digital mode received a number (&hellip;); expected an object &hellip;`.
- Outlier toggle on/off mid-stream &rarr; verifies `analogChannel.outlierDetection.enabled` mirrors `config.outlierDetection.enabled`.
> [!IMPORTANT]
> **Screenshot needed.** Editor capture of `edge.flow.json` plus the log lines each inject triggers. Save as `wiki/_partial-screenshots/measurement/edge-flow.png`.
---
## Debug recipes
| Symptom | First thing to check | Where to look |
|:---|:---|:---|
| Parent never receives `<type>.measured.<position>` | `asset.type` must match the parent's filter exactly (e.g. `flow` &mdash; not `flow-electromagnetic`). Position labels lowercase in the event name. | `config.asset.type` + parent's `childRegistrationUtils` filter. |
| Outliers seem to pass through | `outlierDetection.enabled` may be off (default `false`). Toggle with `set.outlier-detection`. With `<2` samples in the buffer, `_isOutlier` returns `false` regardless. | `Channel._isOutlier`. |
| `cmd.calibrate` does nothing | Calibrator requires `stdDev <= calibration.stabilityThreshold` over `storedValues`. If `storedValues.length < 2`, `isStable()` returns `false` (legacy shape). | `src/calibration/calibrator.js` `isStable`, `calibrate`. |
| Digital payload silently dropped | Unknown channel keys are reported only at `debug` log level (`digital payload contained unmapped keys`). Numeric values that fail `Number.isFinite` warn at `warn`. | `Measurement.handleDigitalPayload`. |
| Simulator still running after toggle off | `tick()` reads `config.simulation.enabled` each tick. Confirm the toggle actually mutated the config (the `set.simulator` handler is idempotent &mdash; it just flips). | `Measurement.tick`, `toggleSimulation`. |
| Port 0 emits nothing after `data.measurement` | Analog: `_writeOutput` only emits when `rounded !== outputAbs`. A repeated identical value is silent by design. | `Channel._writeOutput`. |
| `mPercent` is stuck at `0` or unbounded | `processRange <= 0` (i.e. `absMax <= absMin`); percent falls back to `totalMinValue / totalMaxValue` which start at `0` / `0`. Configure `absMin < absMax`. | `Channel._computePercent`. |
| Scaling output looks clamped | `_applyScaling` clamps the input to `[inputMin, inputMax]` before mapping. Wide-band sensors need `inputMin / inputMax` set to the full physical range. | `Channel._applyScaling`. |
| `mAbs` jumps after `cmd.calibrate` | Expected. Calibration sets `config.scaling.offset = baseline - currentOutputAbs`, which makes the next reading land on the baseline (`inputMin` when scaling enabled, `absMin` otherwise). | `Calibrator.calibrate`. |
| Legacy `setpoint` / `simulator` topics work without warning | First fire emits a one-time deprecation warning via `BaseNodeAdapter`'s alias handling. Subsequent fires are silent &mdash; the topic still works. | `commands/index.js` `aliases`. |
> Never ship `enableLog: 'debug'` in a demo &mdash; fills the container log within seconds and obscures real errors.
---
## Docker compose snippet
To bring up Node-RED + InfluxDB with EVOLV nodes pre-loaded:
```yaml
# docker-compose.yml (extract)
services:
nodered:
build: ./docker/nodered
ports: ['1880:1880']
volumes:
- ./docker/nodered/data:/data/evolv
influxdb:
image: influxdb:2.7
ports: ['8086:8086']
```
Full file: [EVOLV/docker-compose.yml](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/docker-compose.yml).
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Contracts](Reference-Contracts) | Topic + config + child registration |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map + per-`Channel` pipeline + lifecycle |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [rotatingMachine &mdash; Examples](https://gitea.wbd-rd.nl/RnD/rotatingMachine/wiki/Reference-Examples) | Most common consumer of measurement |
| [EVOLV &mdash; Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) | Where measurement fits in a larger plant |