Files
valve/wiki/Reference-Examples.md
znetsixe 87214788d2 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:11 +02:00

144 lines
8.9 KiB
Markdown

# Reference &mdash; Examples
![code-ref](https://img.shields.io/badge/code--ref-8c2b2c0-blue)
> [!NOTE]
> Every example flow shipped under `nodes/valve/examples/`, plus how to load them, what they show, and the debug recipes that go with them. Live source: `nodes/valve/examples/`.
>
> Pending full node review (2026-05). The shipped flows are currently minimal stubs; tiered demos (matching the `rotatingMachine` template) are on the backlog.
---
## Shipped examples
| File | Tier | Dependencies | What it shows |
|:---|:---:|:---|:---|
| `basic.flow.json` | 1 (stub) | EVOLV only | Minimal: one `inject` &rarr; one `valve` &rarr; one `debug`. Sanity check that the node loads. |
| `integration.flow.json` | 2 (stub) | EVOLV only | Same shape as basic; placeholder for VGC + measurement integration. |
| `edge.flow.json` | 3 (stub) | EVOLV only | Placeholder for edge cases (gas-choke, e-stop, invalid setpoints). |
> [!IMPORTANT]
> **Tiered example flows TODO.** Replace the three stubs with `01 - Basic Manual Control.json` / `02 - Integration with Valve Group.json` / `03 - Dashboard Visualization.json` following the `rotatingMachine` template. Track in `.agents/improvements/IMPROVEMENTS_BACKLOG.md` and validate live against Docker-stack Node-RED. Screenshots / GIFs land under `wiki/_partial-screenshots/valve/` and `wiki/_partial-gifs/valve/`.
---
## 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/valve/examples/basic.flow.json" \
http://localhost:1880/flow
```
Use `POST /flow` (single tab, full replace) or `POST /flows` (full deploy) depending on whether other tabs are already loaded.
---
## Driving the basic flow manually
The shipped `basic.flow.json` has a single `inject` wired to the valve. To exercise the FSM + hydraulic model, send the following sequence by hand (e.g. via additional inject nodes you wire in, or the Admin API):
1. `set.mode` &mdash; payload `"virtualControl"` &mdash; lets the GUI source drive the valve.
2. `cmd.startup` &mdash; payload `{}`. FSM walks `idle &rarr; starting &rarr; warmingup &rarr; operational`. Watch `state` on Port 0.
3. `set.position` &mdash; payload `{"setpoint": 60}`. FSM goes `operational &rarr; accelerating &rarr; operational`; `percentageOpen` ramps 0 &rarr; 60 at `movement.speed` %/s.
4. `data.flow` &mdash; payload `{"variant": "measured", "value": 25, "position": "downstream", "unit": "m3/h"}`. Flow lands in MeasurementContainer; `MeasurementRouter.updateFlow` recomputes deltaP. `delta_predicted_pressure` appears on Port 0; `evt.deltaPChange` fires upward.
5. `data.flow` &mdash; payload `{"variant": "measured", "value": 0, "position": "downstream", "unit": "mbar"}` to push downstream pressure as well (needed for the gas-flow path).
6. `cmd.shutdown` &mdash; payload `{}`. Because the valve is `operational`, the controller first ramps `percentageOpen` to 0, then `state` transitions `stopping &rarr; coolingdown &rarr; idle`.
> [!IMPORTANT]
> **GIF needed.** Demo recording of steps 1&ndash;6 + status badge progression. Save as `wiki/_partial-gifs/valve/01-basic-demo.gif`, target &le; 1&nbsp;MB after `gifsicle -O3 --lossy=80`.
### Try the position-residue handler
After the valve reaches `operational` at 60 %:
1. Send `set.position = {setpoint: 20}`. State goes `operational &rarr; decelerating &rarr; ...`.
2. While `decelerating`, send `set.position = {setpoint: 80}`.
3. `state.moveTo` recognises the residue state, transitions back to `operational` synchronously, then ramps up to 80. No setpoint is lost.
This is the same residue mechanism `rotatingMachine` uses for fast retargets.
### Try the e-stop sequence
From `operational`, send `cmd.estop`. The valve runs the `emergencystop` sequence (`[emergencystop, off]`). Allowed transitions out of `emergencystop` are `idle` / `off` / `maintenance`. To restart, drop to `idle` first (`cmd.shutdown` from `off` may not work depending on the state graph &mdash; TODO: confirm).
---
## Integration with `valveGroupControl`
> [!IMPORTANT]
> **TODO: Tier-2 example.** A proper integration flow with `valveGroupControl` + 2&times;valve children + an upstream `rotatingMachine` / `pumpingStation` for fluid-contract tracking is on the backlog. Screenshot under `wiki/_partial-screenshots/valve/02-integration.png`.
When built, the integration flow will demonstrate:
- Auto-registration via Port 2 at deploy &mdash; each valve's `child.register` reaches the VGC; no manual wiring needed.
- Upstream-source registration &mdash; a `rotatingMachine` registered as a child of the valve feeds `getFluidContract()` into `FluidCompatibility`. Status flips from `pending` / `compatible` / `mismatch` based on `serviceType` agreement.
- `evt.deltaPChange` propagation from each valve to the VGC for group-level deltaP aggregation.
---
## Dashboard visualization
> [!IMPORTANT]
> **TODO: Tier-3 example.** A FlowFuse Dashboard 2.0 page (`@flowfuse/node-red-dashboard`) with control buttons (mode, startup, shutdown, e-stop, position slider), live status (state badge, position %, deltaP, flow), and trend charts (deltaP, position) is on the backlog. Save as `03 - Dashboard Visualization.json`.
---
## 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).
---
## Debug recipes
| Symptom | First thing to check | Where to look |
|:---|:---|:---|
| Editor throws `legacy asset field(s) [supplier, ...]` on deploy | Flow predates the AssetResolver refactor. Re-open the node, pick the model from the asset menu, save. The registry derives supplier / category / type. | `src/nodeClass.js` `_rejectLegacyAssetFields`. |
| Status badge shows `⚠ <message>` (yellow ring) | `getFluidCompatibility().status` is `mismatch` or `conflict`. An upstream source advertised a service type that doesn't match this valve's expected type. | `src/fluid/fluidCompatibility.js`, `getFluidCompatibility()`. |
| `delta_predicted_pressure` stuck at `0` or missing | `kv` is 0 (valve closed), the FSM isn't in `operational` / `accelerating` / `decelerating`, or no flow has landed. For gas flow, also needs a finite `downstream_measured_pressure`. | `state.getCurrentState()`, `percentageOpen`, `MeasurementRouter.updateDeltaP`. |
| `set.position` has no effect | Source not in `mode.allowedSources[currentMode]`. Watch for `Source '...' is not valid for mode '...'` in the warn log. | `src/flow/flowController.js` `isValidSourceForMode`. |
| `data.flow` payloads aren't reflected on Port 0 | Payload shape: `{variant: 'measured'\|'predicted', value: <number>, position: <string>, unit?: 'm3/h'}`. Missing `variant` warns `Unrecognized variant '...' for flow update`. Missing `value` warns `Received null or undefined value for flow update`. | `src/measurement/measurementRouter.js` `updateFlow`. |
| Gas-flow deltaP saturates at a ceiling | The choked-flow cap fired (`isChoked: true` in `hydraulicDiagnostics`). Increase `gasChokedRatioLimit` or revise downstream pressure. | `src/hydraulicModel.js` `_calculateGasDeltaP`. |
| `query.curve` returns empty `valveCurve` | `asset.model` not found by `assetResolver`; the predictor falls back to inline `asset.valveCurve` &mdash; check that exists. | `src/curve/supplierCurve.js` `SupplierCurvePredictor.snapshot()`. |
| FSM stuck in `accelerating` / `decelerating` | A move was aborted with `returnToOperationalOnAbort = false`. Send a new `set.position` &mdash; the residue handler in `state.moveTo` transitions back to `operational` first. | `generalFunctions/src/state/state.js` `moveTo` residue branch. |
| Per-valve Port 0 key names differ from what your dashboard expects | valve uses `<position>_<variant>_<type>` (e.g. `delta_predicted_pressure`, `downstream_measured_flow`). `rotatingMachine` uses `<type>.<variant>.<position>.<childId>`. Don't mix them. | `src/io/output.js` `buildOutput`. |
> Never ship `enableLog: 'debug'` in a demo &mdash; fills the container log within seconds and obscures real errors.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Contracts](Reference-Contracts) | Topic + config + child filters |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [valveGroupControl &mdash; Examples](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Reference-Examples) | Group-control demo flows |
| [EVOLV &mdash; Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) | Where valve fits in a larger plant |