Files
valve/wiki/Home.md
znetsixe 8aa5b5e23e P9.3: wiki/Home.md following 14-section visual-first template + wiki:* scripts
Auto-generated topic-contract + data-model sections via shared wikiGen
script. Hand-written Mermaid diagrams for position-in-platform, code
map, child registration, lifecycle, configuration, state chart (where
applicable).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:17:38 +02:00

260 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# valve
> **Reflects code as of `e27135b` · regenerated `2026-05-11` via `npm run wiki:all`**
> If 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
```mermaid
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
```mermaid
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-run `npm run wiki:contract`.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Effect |
|---|---|---|---|
| `set.mode` | `setMode` | `string` | Replaces the named state value with the supplied payload. |
| `cmd.startup` | _(none)_ | `any` | Triggers an action / sequence — not idempotent. |
| `cmd.shutdown` | _(none)_ | `any` | Triggers an action / sequence — not idempotent. |
| `cmd.estop` | `emergencystop`, `emergencyStop` | `any` | Triggers an action / sequence — not idempotent. |
| `execSequence` | _(none)_ | `object` | _(see handler)_ |
| `set.position` | `execMovement` | `object` | Replaces the named state value with the supplied payload. |
| `data.flow` | `updateFlow` | `object` | Pushes a value into the node's measurement stream. |
| `query.curve` | `showcurve` | `any` | Read-only query; node replies on the same msg. |
| `child.register` | `registerChild` | `string` | Parent/child plumbing — registers or unregisters a child node. |
<!-- END AUTOGEN: topic-contract -->
## 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.
```mermaid
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 -->|"&lt;type&gt;.measured.&lt;position&gt;"| 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
```mermaid
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`.
<!-- BEGIN AUTOGEN: data-model -->
| 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` |
<!-- END AUTOGEN: data-model -->
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
```mermaid
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 | 01 | gas formula clamp |
## 10. State chart
```mermaid
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 |