# valveGroupControl
  
A `valveGroupControl` (VGC) coordinates a group of `valve` children that share a common manifold — selector-valve banks, dosing-valve trains, mixing manifolds. It accepts a group-level total flow target, splits the flow proportional to each valve's Kv rating, runs a residual-reconciliation pass against what every valve actually accepted, and aggregates max delta-P across the group. It also reconciles upstream-source fluid-contract advertisements (`liquid` / `gas`) into one group-level service-type view that downstream consumers can read.
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
---
## At a glance
| Thing | Value |
|:---|:---|
| What it represents | A parallel valve manifold — 2 + valves sharing a header, distributing one total flow target |
| S88 level | Unit |
| Use it when | You have 2 + parallel valves that should share an upstream flow target proportional to their Kv |
| Don't use it for | A single valve (wire `valve` directly), series valves (the Kv-share solver assumes parallel branches), or a manifold whose upstream already publishes per-branch setpoints |
| Children it accepts | `valve` (group members) + upstream sources (`machine` / `rotatingmachine` / `machinegroup` / `machinegroupcontrol` / `pumpingstation` / `valvegroupcontrol`) |
| Parents it talks to | Any upstream node — typically `pumpingStation`, `reactor`, or another VGC; VGC registers via Port 2 |
---
## How it fits
```mermaid
flowchart LR
src[machine / MGC /
pumpingStation
upstream source]:::unit -.flow.predicted.*.-> vgc[valveGroupControl
Unit]:::unit
vgc -->|updateFlow predicted
downstream| v1[valve A]:::equip
vgc -->|updateFlow predicted
downstream| v2[valve B]:::equip
v1 -->|positionChange
deltaPChange| vgc
v2 -->|positionChange
deltaPChange| vgc
vgc -->|child.register| parent[upstream parent]:::pc
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,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 2-valve group with an injected total-flow setpoint.
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/valveGroupControl/examples/basic.flow.json \
http://localhost:1880/flow
```
What to click after deploy (the inject buttons map to canonical topics in [Reference — Contracts](Reference-Contracts#topic-contract)):
1. `child.register` for each `valve` — or rely on Port-2 wiring to auto-register.
2. `set.mode = auto` — lets the parent source drive the group.
3. `data.totalFlow = 80` (with `unit: 'm3/h'`) — VGC splits 80 m³/h across the available valves by Kv share; runs up to `maxPasses: 2` residual passes; writes back `atEquipment_predicted_flow` = sum of accepted per-valve flows.
4. `cmd.execSequence` with `{action: "startup"}` — runs the group-wide startup sequence through `executeSequence`, transitioning the group state machine through each step.
5. `cmd.emergencyStop` — runs the `emergencystop` sequence on all valves (`[emergencystop, off]`).
6. `set.reconcileInterval = 2` — re-tunes the periodic tick to 2 s (`reconcileIntervalChange` event triggers the adapter to restart its tick loop; minimum 100 ms).
> [!IMPORTANT]
> **GIF needed.** Demo recording of steps 1–6 with the live status panel. Save as `wiki/_partial-gifs/valveGroupControl/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. Each mode has its own allow-list of sources (`mode.allowedSources`). |
| `set.position` | `setpoint` | any | **No-op pending Phase 7.** Reserved for future per-valve positional override. Debug-logged only. |
| `child.register` | `registerChild` | `string` (child node id) | Manually register a child via `RED.nodes.getNode`; Port 2 wiring does this automatically in most flows. |
| `cmd.execSequence` | `execSequence` | `{ source, action, parameter }` | Forward to `source.handleInput(source, action, parameter)` — runs a group-wide sequence (`startup` / `shutdown` / `emergencystop` / `boot`). |
| `data.totalFlow` | `totalFlowChange` | number, `{ value, position?, variant?, unit? }`, or `{ source, action, ... }` | Update the total measured/predicted flow at the configured position; triggers `calcValveFlows` to re-distribute across valves. |
| `cmd.emergencyStop` | `emergencyStop`, `emergencystop` | optional `{ source }` | Run the `emergencystop` sequence on all valves. |
| `set.reconcileInterval` | `setReconcileInterval` | number — seconds (> 0) | Re-tune the periodic flow-reconciliation interval. Min clamp 100 ms. |
Aliases log a one-time deprecation warning the first time they fire.
---
## What you'll see come out
Sample Port 0 message (delta-compressed, after a `data.totalFlow = 80` split across two valves):
```json
{
"topic": "valveGroupControl#VGC1",
"payload": {
"mode": "auto",
"maxDeltaP": 1450,
"atEquipment_measured_flow": 80,
"atEquipment_predicted_flow": 80,
"deltaMax_predicted_pressure": 1450
}
}
```
Key shape: **`__`** — same as MGC's key shape (inverse of `rotatingMachine`'s per-measurement form). The output reflects the group aggregate, not per-valve snapshots; per-valve detail comes off each valve's own Port 0.
| Field | Meaning |
|:---|:---|
| `mode` | Current operational mode (`auto` / `virtualControl` / `fysicalControl` / `maintenance`). |
| `maxDeltaP` | Max delta-P across registered valves — refreshed whenever a child emits `deltaPChange`. Also surfaced as `deltaMax_predicted_pressure` via the measurement container. |
| `atEquipment_measured_flow` | Total measured flow at the group inlet (from an upstream source's `flow.measured.*` event). |
| `atEquipment_predicted_flow` | Sum of per-valve accepted flows after the Kv-share + residual pass. |
| `deltaMax_predicted_pressure` | Max delta-P across the group, written via the measurement container at position `delta` / variant `predicted` / type `pressure`. |
---
## Flow-distribution loop — what one event does
When a `data.totalFlow` arrives (or an upstream source publishes `flow.predicted.*` / `flow.measured.*`), VGC re-distributes by Kv share:
```mermaid
flowchart LR
src[data.totalFlow /
upstream source event] --> upd[updateFlow
predicted/measured atEquipment]
upd --> avail[getAvailableValves
state ≠ off/maintenance, kv > 0]
avail --> solve[solveFlowDistribution
share by Kv / totalKv]
solve --> push[valve.updateFlow predicted
downstream]
push --> readback[read accepted from
flow.predicted.downstream]
readback --> residual[residual = target − sum(accepted)]
residual -->|residual > tol & passes < max| solve
residual --> writeback[write flow.predicted.atEquipment
= sum(accepted)]
writeback --> dp[calcMaxDeltaP]
dp --> emit[notifyOutputChanged]
```
Reconciliation defaults (`flowReconciliation`):
| Field | Default | Notes |
|:---|:---:|:---|
| `maxPasses` | `2` | Max iterations of the residual-correction loop. |
| `residualTolerance` | `0.001` | Stops the loop when `|residual| <` tolerance (canonical units — m³/s for flow). |
A valve is **available** if: `state.getCurrentState() !== 'off'` and `!== 'maintenance'`, `currentMode !== 'maintenance'`, and `kv > 0`. Unavailable valves are skipped and receive `updateFlow('predicted', 0, 'downstream')`.
VGC has **no FSM of its own** — state semantics belong to the child valves. `specificClass` instantiates a state object internally and stamps it `operational` at boot for sequence dispatch; the group's only coordination loop is the Kv-share solver above.
---
## Need more?
| Page | What you'll find |
|:---|:---|
| [Reference — Contracts](Reference-Contracts) | Topic registry, config schema, child-registration filters |
| [Reference — Architecture](Reference-Architecture) | Code map, flow-distribution loop, source aggregation, output ports |
| [Reference — Examples](Reference-Examples) | Shipped flows, debug recipes |
| [Reference — Limitations](Reference-Limitations) | When not to use, known issues, 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)