Table of Contents
- valve
- 1. What this node is
- 2. Position in the platform
- 3. Capability matrix
- 4. Code map
- 5. Topic contract
- 6. Child registration
- 7. Lifecycle — what one event does
- 8. Data model — getOutput()
- 9. Configuration — editor form ↔ config keys
- 10. State chart
- 11. Examples
- 12. Debug recipes
- 13. When you would NOT use this node
- 14. Known limitations / current issues
valve
Reflects code as of
e27135b· regenerated2026-05-11vianpm run wiki:allIf this banner is stale, the page may be out of date. Treat as informative, not authoritative.
1. What this node is
valve models a single throttling valve. It loads a supplier characteristic curve (Kv-vs-position), drives an FSM-style move sequence for opening/closing, and recomputes pressure drop across the valve from current flow + Kv. Used standalone or as a child of valveGroupControl when grouped.
2. Position in the platform
flowchart LR
vgc[valveGroupControl<br/>Unit]:::unit -->|set.position| this[valve<br/>Equipment]:::equip
src[machine / MGC / PS<br/>upstream source]:::unit -->|child.register| this
meas[measurement<br/>type=pressure / flow]:::ctrl -.data.-> this
this -->|child.register| vgc
this -->|evt.deltaPChange| vgc
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
S88 colours: Unit #50a8d9, Equipment #86bbdd, Control Module #a9daee. Source of truth: .claude/rules/node-red-flow-layout.md.
3. Capability matrix
| Capability | Status | Notes |
|---|---|---|
| Predicts deltaP from flow + Kv | ✅ | Hydraulic model picks liquid vs gas formula per serviceType. |
| Loads supplier curve by model name | ✅ | asset.model resolved through loadModel; inline curve override supported. |
| Position move FSM | ✅ | opening / closing states with interruptible setpoints. |
| Startup / shutdown sequences | ✅ | Pre-shutdown ramps to position 0 when operational. |
| Emergency-stop sequence | ✅ | Aliased cmd.estop → state-machine emergencystop. |
| Fluid-contract aggregation | ✅ | Tracks upstream service type via registered sources. |
| Gas-choke detection | ⚠️ | Capped at gasChokedRatioLimit; surfaced in hydraulicDiagnostics. |
| Multi-parent registration | ⚠️ | Allowed but not exercised in production tests. |
4. Code map
flowchart TB
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
nc["buildDomainConfig()<br/>static DomainClass, commands"]
end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Valve.configure()<br/>wires concern modules<br/>installs FluidCompatibility registerChild"]
end
subgraph concerns["src/ concern modules"]
state["state/<br/>stateBindings → positionChange"]
fluid["fluid/<br/>FluidCompatibility"]
curve["curve/<br/>SupplierCurvePredictor"]
meas["measurement/<br/>MeasurementRouter + FORMULA_UNITS"]
flow["flow/<br/>FlowController (setpoint, sequences)"]
io["io/<br/>buildOutput + buildStatusBadge"]
hyd["hydraulicModel.js<br/>ValveHydraulicModel"]
end
nc --> sc
sc --> state
sc --> fluid
sc --> curve
sc --> meas
sc --> flow
sc --> io
sc --> hyd
| Module | Owns | Read first if you're changing… |
|---|---|---|
state/ |
Bindings from state-machine positionChange → updatePosition() |
Move-finished triggers. |
fluid/ |
Service-type compatibility, contract aggregation | Gas-vs-liquid mismatch warnings. |
curve/ |
Supplier Kv curve load + interpolation | Curve fitting, model selection. |
measurement/ |
Pressure/flow routing + deltaP recompute | What triggers a recalc. |
flow/ |
Sequence + setpoint execution | Startup / shutdown / move semantics. |
io/ |
Port-0 output shape + status badge | What lands on the wire each tick. |
5. Topic contract
Auto-generated from
src/commands/index.js. Do NOT hand-edit between the markers. Re-runnpm run wiki:contract.
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
set.mode |
setMode |
string |
— | Switch the valve between auto / manual control modes. |
cmd.startup |
(none) | any |
— | Initiate the valve startup sequence. |
cmd.shutdown |
(none) | any |
— | Initiate the valve shutdown sequence. |
cmd.estop |
emergencystop, emergencyStop |
any |
— | Trigger an emergency stop on the valve. |
execSequence |
(none) | object |
— | Legacy umbrella that demuxes payload.action to startup / shutdown / estop. |
set.position |
execMovement |
object |
— | Move the valve to a control-% position via execMovement. |
data.flow |
updateFlow |
object |
— | Push a measured flow into the valve (variant + position + unit). |
query.curve |
showcurve |
any |
— | Return the valve characteristic curve on the reply port. |
child.register |
registerChild |
string |
— | Register a child measurement with this valve. |
6. Child registration
valve overrides BaseDomain's default registerChild with FluidCompatibility.registerChild so upstream-source contracts feed the fluid aggregator. Measurement children attach through the generic measurement handshake.
flowchart LR
subgraph kids["accepted children (softwareType)"]
src["machine / rotatingmachine /<br/>machinegroup / pumpingstation /<br/>valvegroupcontrol"]:::unit
m["measurement"]:::ctrl
end
src -->|getFluidContract| fluid[FluidCompatibility<br/>aggregates serviceType]
m -->|"<type>.measured.<position>"| router[MeasurementRouter<br/>updatePressure / updateFlow]
router --> deltaP[updateDeltaP<br/>writes pressure.predicted.delta]
fluid --> evt1[evt.fluidCompatibilityChange]
deltaP --> evt2[evt.deltaPChange]
classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000
| softwareType | onRegister side-effect | Subscribed events |
|---|---|---|
machine / rotatingmachine |
Stored as upstream source; reads getFluidContract() or default liquid. |
fluidContractChange. |
machinegroup / machinegroupcontrol |
Same; recomputes aggregate service type. | fluidContractChange. |
pumpingstation |
Same. | fluidContractChange. |
valvegroupcontrol |
Same. | fluidContractChange. |
measurement |
Routed via measurement handshake; values land in MeasurementContainer. | <type>.measured.<position>. |
7. Lifecycle — what one event does
sequenceDiagram
participant parent as valveGroupControl
participant valve as valve
participant state as state FSM
participant hyd as hydraulicModel
participant out as Port-0
parent->>valve: set.position { setpoint: 60 }
valve->>state: moveTo(60)
state-->>valve: positionChange ticks
valve->>valve: predictKv(position)
valve->>hyd: calculateDeltaPMbar(q, kv, downP, rho, T)
hyd-->>valve: { deltaPMbar, details }
valve->>valve: write pressure.predicted.delta
valve->>parent: emitter.emit('deltaPChange', deltaP)
valve->>out: msg{topic, payload (delta-compressed)}
8. Data model — getOutput()
What lands on Port 0. Composed in io/output.buildOutput, then delta-compressed by outputUtils.formatMsg.
| Key | Type | Unit | Sample |
|---|---|---|---|
state |
string | — | "operational" |
percentageOpen |
number | % | 0 |
moveTimeleft |
number | s | 0 |
mode |
string | — | "auto" |
downstream_predicted_flow |
number | m3/h | 0 |
downstream_measured_flow |
number | m3/h | (emitted when measurement child present) |
downstream_predicted_pressure |
number | mbar | (emitted when upstream pressure present) |
downstream_measured_pressure |
number | mbar | (emitted when measurement child present) |
delta_predicted_pressure |
number | mbar | 0 |
Measurement-derived keys follow the legacy <position>_<variant>_<type> shape (e.g. downstream_predicted_flow, delta_predicted_pressure) and are emitted only when the container holds a finite value.
9. Configuration — editor form ↔ config keys
flowchart TB
subgraph editor["Node-RED editor form"]
f1[Mode]
f2[Asset model]
f3[Service type]
f4[Diameter]
f5[Fluid density / temperature]
f6[Inline valveCurve override]
end
subgraph config["Domain config slice"]
c1[mode.current]
c2[asset.model]
c3[asset.serviceType]
c4[asset.valveDiameter]
c5[asset.fluidDensity / fluidTemperatureK]
c6[asset.valveCurve]
end
f1 --> c1
f2 --> c2
f3 --> c3
f4 --> c4
f5 --> c5
f6 --> c6
| Form field | Config key | Default | Range | Where used |
|---|---|---|---|---|
| Mode | mode.current |
per schema | enum | setMode, flowController |
| Asset model | asset.model |
null |
string | SupplierCurvePredictor |
| Service type | asset.serviceType |
per asset | gas / liquid |
ValveHydraulicModel |
| Diameter | asset.valveDiameter |
per asset | > 0 (m) | curve key selection |
| Fluid density | asset.fluidDensity |
model default | > 0 (kg/m³) | hydraulic formula |
| Fluid temperature | asset.fluidTemperatureK |
model default | > 0 (K) | hydraulic formula |
| Choked-flow cap | asset.gasChokedRatioLimit |
per asset | 0–1 | gas formula clamp |
10. State chart
stateDiagram-v2
[*] --> off
off --> idle: cmd.startup
idle --> opening: set.position > 0
opening --> operational: position reached
operational --> opening: set.position changed
operational --> closing: set.position < current
closing --> closed: position == 0
closed --> opening: set.position > 0
operational --> stopping: cmd.shutdown (ramps to 0)
stopping --> idle: cooldown elapsed
operational --> emergencystop: cmd.estop
emergencystop --> off: cmd.reset
The opening / closing states cover the move-in-progress window; positionChange ticks fire until the setpoint is reached, then the FSM lands on operational. Pre-shutdown ramp to 0 is enforced by FlowController.executeSequence('shutdown').
11. Examples
| Tier | File | What it shows | Mandatory? |
|---|---|---|---|
| Basic | examples/01-Basic.flow.json |
Inject set.position + dashboard, no parent |
✅ |
| Integration | examples/02-Integration.flow.json |
valve + VGC + upstream source | ✅ |
| Dashboard | examples/03-Dashboard.flow.json |
Live FlowFuse charts (position, ΔP, flow) | ⭕ |
Screenshots under wiki/_partial-screenshots/valve/ when produced. Docker compose snippet under examples/README.md.
12. Debug recipes
| Symptom | First thing to check | Where to look |
|---|---|---|
Status badge shows ⚠ no input |
Did any pressure / flow measurement register? Watch Port 2. | Editor debug tap on Port 2 |
delta_predicted_pressure stuck at zero |
Is kv > 0? FSM may be in off / closed. |
state.getCurrentState() |
| Gas mismatch warning on status badge | fluidCompatibility.status is mismatch / conflict. |
getFluidCompatibility() |
query.curve returns empty curve |
Asset model not found by loadModel; fallback to config.asset.valveCurve. |
SupplierCurvePredictor.snapshot() |
| deltaP non-finite | Downstream gauge pressure absolute term ≤ 0, or choked ratio reached. | hydraulicDiagnostics |
Never ship
enableLog: 'debug'in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
13. When you would NOT use this node
- Use valve for a throttling element with a known Kv curve. For a fixed-restriction orifice with no actuator, model the deltaP externally.
- Don't use valve to model a non-return / check valve — no position control or curve fitting is exposed.
- Skip valve when an upstream source provides flow directly and no pressure-drop estimate is needed; just wire the source straight to the parent.
14. Known limitations / current issues
| # | Issue | Tracked in |
|---|---|---|
| 1 | Gas-choke detection is a hard cap, not a smooth transition — chart traces show a step at the choked-ratio limit. | hydraulicModel.js |
| 2 | Multi-parent registration is allowed but not exercised in production tests. | CONTRACT.md ## Children registered by this node |
| 3 | set.position move sequences are interruptible but tests cover happy-path only. |
P10 test-suite refactor |