# valve ![code-ref](https://img.shields.io/badge/code--ref-8c2b2c0-blue) ![s88](https://img.shields.io/badge/S88-Equipment_Module-86bbdd) ![status](https://img.shields.io/badge/status-pending--review-orange) A `valve` models a single actuated throttling valve. It loads a supplier Kv-vs-position characteristic curve, drives a position FSM (`accelerating` / `decelerating` through `operational`), and recomputes pressure drop from flow + Kv via a hydraulic model that picks a liquid or gas formula by `serviceType`. Used standalone, or as a child of `valveGroupControl`, downstream of a `rotatingMachine` / `machineGroupControl` / `pumpingStation`. > [!NOTE] > Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only. --- ## At a glance | Thing | Value | |:---|:---| | What it represents | One actuated throttling valve — supplier Kv curve, position FSM, deltaP estimate | | S88 level | Equipment Module | | Use it when | You need a position-controlled valve whose deltaP depends on flow, Kv(position), and service-type (gas / liquid) | | Don't use it for | Fixed-restriction orifices, non-return / check valves, or curveless throttling devices (no fallback model) | | Children it accepts | Upstream sources (`rotatingmachine`, `machinegroup` / `machinegroupcontrol`, `pumpingstation`, `valvegroupcontrol`) for fluid-contract tracking; `measurement` for pressure / flow | | Parents it talks to | `valveGroupControl` (typical) or any node that issues `set.position` / `cmd.startup` / `cmd.shutdown` | --- ## How it fits ```mermaid flowchart LR parent[valveGroupControl]:::unit -->|set.position
cmd.startup / shutdown| v[valve
Equipment]:::equip src["rotatingMachine /
MGC / pumpingStation"]:::unit -->|child.register
(fluid contract)| v m_p[measurement
pressure]:::ctrl -.measured.-> v m_f[measurement
flow]:::ctrl -.measured.-> v v -->|child.register| parent v -.->|evt.deltaPChange
evt.fluidCompatibilityChange
evt.fluidContractChange| parent classDef unit fill:#50a8d9,color:#000 classDef equip fill:#86bbdd,color:#000 classDef ctrl fill:#a9daee,color:#000 ``` S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. --- ## Try it — 3-minute demo Import the basic example flow, deploy, and drive a single valve through a position move. ```bash curl -X POST -H 'Content-Type: application/json' \ --data @nodes/valve/examples/basic.flow.json \ http://localhost:1880/flow ``` > [!NOTE] > The shipped `examples/{basic,integration,edge}.flow.json` files are minimal stubs (one inject → valve → debug). A tiered `01 - Basic Manual Control.json` / `02 - Integration with Valve Group.json` / `03 - Dashboard Visualization.json` set, matching the `rotatingMachine` template, is on the backlog. Until then, drive the node directly with injects. What to send after deploy (the topics map one-to-one to entries in [Reference — Contracts](Reference-Contracts#topic-contract)): 1. `set.mode = virtualControl` — lets the GUI source drive the valve (parent path is for grouped use). 2. `cmd.startup` — FSM runs `idle → starting → warmingup → operational`. 3. `set.position = {setpoint: 60}` (position %) — valve ramps from 0 to 60; state goes `operational → accelerating → operational`. Each position tick fires a Kv lookup + deltaP recompute. 4. `data.flow = {variant: 'measured', value: 25, position: 'downstream', unit: 'm3/h'}` — push flow so the hydraulic model has something to chew on. `delta_predicted_pressure` updates and `evt.deltaPChange` fires upward. 5. `cmd.shutdown` — if currently `operational`, the controller first ramps to position 0, then transitions `stopping → coolingdown → idle`. > [!IMPORTANT] > **GIF needed.** Demo recording of steps 1–5 with the live status badge. Save as `wiki/_partial-gifs/valve/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`. --- ## The seven things you'll send | Topic | Aliases | Payload | What it does | |:---|:---|:---|:---| | `set.mode` | `setMode` | `"auto"` \| `"virtualControl"` \| `"fysicalControl"` \| `"maintenance"` | Switch operational mode. Source allow-list per mode (defaults from `valve.json`). | | `cmd.startup` | — | `{ source?: string }` | Run the configured `startup` sequence (default `[starting, warmingup, operational]`). | | `cmd.shutdown` | — | `{ source?: string }` | Run `shutdown`. If currently `operational`, first ramps the valve to position 0, then transitions `stopping → coolingdown → idle`. | | `cmd.estop` | `emergencystop`, `emergencyStop` | `{ source?: string, action?: string }` | Trigger an emergency stop — runs the `emergencystop` sequence (default `[emergencystop, off]`). | | `set.position` | `execMovement` | `{ source?: string, action?: string, setpoint: number }` | Move the valve to a position (control-%, `0..100`). Setpoint is coerced to `Number`. | | `data.flow` | `updateFlow` | `{ variant, value, position, unit? }` — `variant ∈ {'measured','predicted'}` | Push a flow measurement; triggers a Kv lookup + deltaP recompute via the hydraulic model. | | `query.curve` | `showcurve` | any | Reply on Port 0 with `{ topic: 'Showing curve', payload: }`. | Plus the registration topic emitted upward at startup and accepted from real `measurement` children: | Topic | Aliases | Payload | |:---|:---|:---| | `child.register` | `registerChild` | child Node-RED id (string); `msg.positionVsParent` carries the position label | The legacy umbrella `execSequence` (`{action: 'startup' \| 'shutdown' \| 'emergencystop'}`) is still accepted — it forwards to the canonical `cmd.*` handler and logs a one-time deprecation warning. Scheduled for removal in Phase 7. --- ## What you'll see come out Sample Port 0 message (delta-compressed, while operational at ~60 % open with a 25 m³/h flow): ```json { "topic": "valve#valve_a", "payload": { "state": "operational", "percentageOpen": 60, "moveTimeleft": 0, "mode": "auto", "downstream_measured_flow": 25, "downstream_predicted_flow": 0, "delta_predicted_pressure": 84 } } ``` Key shape: **`__`** — the legacy three-segment shape. Position labels are lowercase (`downstream`, `delta`, `upstream`). `valve` does **not** use the four-segment `...` shape that `rotatingMachine` emits. | Field | Meaning | |:---|:---| | `state` | Current FSM state. See [Architecture — FSM](Reference-Architecture#fsm). | | `percentageOpen` | Current position (`0..100`). 0 = closed, 100 = fully open. | | `moveTimeleft` | Seconds remaining on the current position move (0 when stationary). | | `mode` | One of `auto` / `virtualControl` / `fysicalControl` / `maintenance`. | | `delta_predicted_pressure` | Predicted deltaP across the valve (output unit `mbar`). | | `downstream_predicted_flow` / `_measured_flow` | Last flow pushed via `data.flow` (output unit `m3/h`). | | `downstream_measured_pressure` / `_predicted_pressure` | Pressure measurements pushed via the `MeasurementRouter`. | --- ## Need more? | Page | What you'll find | |:---|:---| | [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters | | [Reference — Architecture](Reference-Architecture) | Code map, FSM, hydraulic model, lifecycle | | [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes | | [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions | [EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)