Files
valve/wiki/Reference-Contracts.md
znetsixe 7acd6c2ce0 fix(commands): point set.mode description at the schema enum
Old description said "auto / manual" but the schema declares four modes
(auto, virtualControl, fysicalControl, maintenance). New description
enumerates the allowed values and refers readers to the schema as the
source of truth. wiki-gen regenerated Reference-Contracts.md to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:05:37 +02:00

255 lines
13 KiB
Markdown

# Reference &mdash; Contracts
![code-ref](https://img.shields.io/badge/code--ref-8c2b2c0-blue)
> [!NOTE]
> Full topic contract, configuration schema, and child-registration filters for `valve`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/valve.json`.
>
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only. For an intuitive overview, return to the [Home](Home).
---
## Topic contract
The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
| `set.mode` | `setMode` | `string` | — | Switch the operating mode. Allowed: `auto`, `virtualControl`, `fysicalControl`, `maintenance` (schema-validated in `valve.json``mode.current`). |
| `cmd.startup` | — | any | — | Initiate the valve startup sequence. |
| `cmd.shutdown` | — | any | — | Initiate the valve shutdown sequence. |
| `cmd.estop` | `emergencystop`, `emergencyStop` | any | — | Trigger an emergency stop on the valve. |
| `execSequence` | — | `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. |
<!-- END AUTOGEN: topic-contract -->
### `execSequence` demux
The pre-refactor topic `execSequence` carried `{source, action, parameter}` where `action` selected the verb. The command registry does not natively dispatch by payload content, so `execSequence` keeps its own descriptor whose handler forwards directly to the canonical `cmd.startup` / `cmd.shutdown` / `cmd.estop` handler based on `payload.action`. A deprecation warning fires once. Future-Phase-7 removal of `execSequence` is a behavioural change &mdash; callers must migrate to the canonical topics.
### Mode / source allow-lists
A topic that survives the registry still passes through `flowController.handleInput`:
```js
if (!this.isValidSourceForMode(source, this.host.currentMode)) {
this.logger.warn(`Source '${source}' is not valid for mode '${currentMode}'.`);
return { status: false, feedback: msg };
}
```
Defaults from `valve.json`:
| Mode | `allowedSources` | `allowedActions` (schema) |
|:---|:---|:---|
| `auto` | `parent, GUI, fysical` | `statusCheck, execMovement, execSequence, emergencyStop` |
| `virtualControl` | `GUI, fysical` | `statusCheck, execMovement, execSequence, emergencyStop` |
| `fysicalControl` | `fysical` | `statusCheck, emergencyStop` |
| `maintenance` | _(none)_ | `statusCheck` |
> [!NOTE]
> `flowController.handleInput` currently enforces only the source side. The schema's `allowedActions` is defined but not gated in code &mdash; flagged as a TODO in [Architecture](Reference-Architecture#mode--source-allow-lists). Source: `nodes/valve/src/flow/flowController.js` line 13.
A rejected request logs at warn and short-circuits.
---
## Data model &mdash; `getOutput()` shape
Composed each tick by `src/io/output.js` `buildOutput()`. Delta-compressed: consumers see only the keys that changed.
### Per-measurement keys
For every `(type, variant, position)` stored in MeasurementContainer with a finite value, the flattened output emits:
```
<position>_<variant>_<type>
```
This is the **legacy three-segment** key shape (no trailing `<childId>`). Position labels are normalised to lowercase. valve does **not** use the four-segment `<type>.<variant>.<position>.<childId>` shape that `rotatingMachine` emits.
<!-- BEGIN AUTOGEN: data-model — populate via wiki-gen tool (TODO) -->
| Key | Type | Unit | Sample / notes |
|:---|:---|:---|:---|
| `state` | string | &mdash; | `"operational"` &mdash; one of the FSM states. |
| `percentageOpen` | number | % | `60` &mdash; current position `0..100`. |
| `moveTimeleft` | number | s | `0` &mdash; seconds remaining on the current move. |
| `mode` | string | &mdash; | `"auto"` / `"virtualControl"` / `"fysicalControl"` / `"maintenance"`. |
| `delta_predicted_pressure` | number | mbar | Predicted deltaP across the valve. Emitted on the next position tick once kv > 0 and a finite flow is known. |
| `downstream_predicted_flow` | number | m3/h | Last flow pushed via `data.flow` with `variant=predicted`. |
| `downstream_measured_flow` | number | m3/h | Last flow pushed via `data.flow` with `variant=measured`, or written by a registered flow measurement child. |
| `downstream_predicted_pressure` | number | mbar | Last predicted pressure written upstream. |
| `downstream_measured_pressure` | number | mbar | Last measured pressure from a registered pressure measurement child. |
<!-- END AUTOGEN -->
### Status badge
`buildStatusBadge` in `io/output.js`:
```
<mode>: <state-symbol> <position%>% 💨<flow><unit> ΔP<deltaP> <unit>
```
(The `position` / `flow` / `deltaP` line only appears in `operational` / `warmingup` / `accelerating` / `decelerating`; other states show just `<mode>: <symbol>`.)
State symbols (per `STATE_SYMBOLS` map in `io/output.js`):
| State | Symbol | Fill |
|:---|:---:|:---|
| `off` | ⬛ | red |
| `idle` | ⏸️ | blue |
| `operational` | ⏵️ | green |
| `starting` | ⏯️ | yellow |
| `warmingup` | 🔄 | green |
| `accelerating` | ⏩ | yellow |
| `decelerating` | ⏪ | yellow |
| `stopping` | ⏹️ | yellow |
| `coolingdown` | ❄️ | yellow |
When `getFluidCompatibility().status` is `mismatch` or `conflict`, the badge is overridden to a yellow ring with `⚠ <message>` appended.
---
## Configuration schema &mdash; editor form to config keys
Source of truth: `generalFunctions/src/configs/valve.json` plus `nodeClass.buildDomainConfig`.
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `valve` | Re-derived in `configure()`. |
| (auto-assigned) | `general.id` | `null` | Node-RED node id. |
| Default unit | `general.unit` | `m3/h` (schema) | Re-resolved to the unitPolicy `output.flow` in `configure()`. |
| Enable logging | `general.logging.enabled` | `true` | Master switch. |
| Log level | `general.logging.logLevel` | `info` | `debug` / `info` / `warn` / `error`. |
### Functionality (`config.functionality`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Position vs parent | `functionality.positionVsParent` | `atEquipment` | One of `atEquipment` / `upstream` / `downstream`. Used in the child-register payload that goes UP to VGC. |
| (hidden) | `functionality.softwareType` | `valve` | Constant. |
| (hidden) | `functionality.role` | `controller` | Constant. |
### Asset (`config.asset`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Asset UUID | `asset.uuid` | `null` | Globally-unique identifier. |
| Tag code | `asset.tagCode` | `null` | |
| Geolocation | `asset.geoLocation` | `{x:0, y:0, z:0}` | |
| Model | `asset.model` | `null` | Optional. If set, resolves curve + supplier / type / allowed units via `assetResolver.resolveAssetMetadata('valve', model)`. If null, the predictor uses the inline `valveCurve`. |
| Deployment unit | `asset.unit` | `null` | Must appear in the registry's allowed list for the model when set. |
| Accuracy | `asset.accuracy` | `null` | Optional. |
| Valve curve | `asset.valveCurve` | `{ '1.204': { '1': { x: [0..100 by 10], y: [0,18,50,95,150,216,337,564,882,1398,1870] } } }` | Nested map: outer key = density (kg per nm³), middle = diameter (mm), leaf = `{x: position%, y: Kv (m³/h)}`. |
Runtime options that bypass `config` and reach `configure()` via `Valve._pendingExtras.runtimeOptions`:
| UI field | runtimeOptions key | Default | Effect |
|:---|:---|:---|:---|
| Service type | `serviceType` | derived (`gas` if not `liquid`) | Picks the hydraulic formula in `ValveHydraulicModel`. |
| Fluid density | `fluidDensity` | model default (997 / 1.204) | Sets `host.rho`. |
| Fluid temperature K | `fluidTemperatureK` | 293.15 | Sets `host.T`. |
| Gas choke ratio limit | `gasChokedRatioLimit` | 0.7 | Cap for the gas hydraulic formula. |
> [!WARNING]
> **Legacy asset fields rejected.** `supplier`, `category`, and `assetType` are no longer node config &mdash; the registry derives them from the model. Flows saved before the AssetResolver refactor will throw a startup error from `_rejectLegacyAssetFields` with a clear migration message. Re-open the node, re-select the model from the asset menu, and save.
### State times (`stateConfig.time`)
Set on the state machine via `nodeClass.buildDomainConfig` from editor fields:
| Form field | Config key | Notes |
|:---|:---|:---|
| Startup Time | `time.starting` | Time spent in `starting` before transitioning to `warmingup`. |
| Warmup Time | `time.warmingup` | Time in `warmingup` &mdash; **non-interruptible** safety. |
| Shutdown Time | `time.stopping` | Time in `stopping`. |
| Cooldown Time | `time.coolingdown` | Time in `coolingdown` &mdash; **non-interruptible** safety. |
> [!NOTE]
> TODO: confirm canonical defaults. `valve.json` does not declare them inline; they come from `generalFunctions/src/configs/state.json` or the parent state-machine schema. Source: `nodeClass.buildDomainConfig` lines 19&ndash;26.
### Movement (`stateConfig.movement`)
| Form field | Config key | Notes |
|:---|:---|:---|
| Reaction Speed | `movement.speed` | Position ramp rate (%/s). E.g. `1` means setpoint 60 from 0 takes ~60 s. |
### Sequences (`config.sequences`)
State-transition lists per sequence name. Defaults:
| Sequence | States |
|:---|:---|
| `startup` | `[starting, warmingup, operational]` |
| `shutdown` | `[stopping, coolingdown, idle]` |
| `emergencystop` | `[emergencystop, off]` |
| `boot` | `[idle, starting, warmingup, operational]` |
Note: unlike `rotatingMachine`, `valve.json` does not ship `entermaintenance` / `exitmaintenance` sequences. TODO: confirm whether maintenance transitions are intentionally manual.
### Mode (`config.mode`)
| Form field | Config key | Default | Range |
|:---|:---|:---|:---|
| Mode | `mode.current` | `auto` | `auto` / `virtualControl` / `fysicalControl` / `maintenance` |
| (defaults) | `mode.allowedActions.<mode>` | see [Topic contract](#mode--source-allow-lists) | schema only &mdash; not currently gated in code |
| (defaults) | `mode.allowedSources.<mode>` | see above | enforced by `flowController.isValidSourceForMode` |
### Unit policy
Source: `src/specificClass.js` lines 20&ndash;24.
| Quantity | Canonical (internal) | Output (rendered) | Required-unit |
|:---|:---|:---|:---:|
| Pressure | `Pa` | `mbar` | ✓ |
| Flow | `m3/s` | `m3/h` | ✓ |
| Temperature | `K` | `C` | ✓ |
`requireUnitForTypes` means MeasurementContainer rejects writes that omit `unit` for these types. The hydraulic model itself reads back via fixed `FORMULA_UNITS = {pressure: 'mbar', flow: 'm3/h', temperature: 'K'}`.
### Calculation mode (`config.calculationMode`)
`low` / `medium` (default) / `high` &mdash; declared in the schema. TODO: confirm whether the dispatch path consults `calculationMode` for trigger frequency. Source: `valve.json` lines 346&ndash;366.
---
## Child registration
Source: `src/specificClass.js` lines 100&ndash;101 and `src/fluid/fluidCompatibility.js`.
valve **overrides** `BaseDomain.registerChild` so registrations fall into `FluidCompatibility.registerChild` rather than the generic ChildRouter. Upstream sources feed the fluid-contract aggregator; measurement children attach via the standard measurement handshake and land in `MeasurementRouter`.
| Software type | Side-effect | Subscribed events |
|:---|:---|:---|
| `machine` / `rotatingmachine` | Stored as upstream source; reads `getFluidContract()` or `asset.serviceType`, defaulting to `liquid` for the rotating-equipment family. Recomputes aggregate service type. | `fluidContractChange` |
| `machinegroup` / `machinegroupcontrol` | Same; recomputes aggregate service type. | `fluidContractChange` |
| `pumpingstation` | Same. | `fluidContractChange` |
| `valvegroupcontrol` | Same. | `fluidContractChange` |
| `measurement` (asset.type=`pressure`, position=`*`) | Routed through `MeasurementRouter.updatePressure(variant, value, position, unit)`; triggers a deltaP recompute. | `<type>.measured.<position>` |
| `measurement` (asset.type=`flow`, position=`*`) | Routed through `MeasurementRouter.updateFlow(variant, value, position, unit)`; triggers a deltaP recompute. | `<type>.measured.<position>` |
The valve's `_updateMeasurement` path (via `updateMeasurement(variant, subType, value, position, unit)`) currently handles `pressure` and `flow`. `power` is recognised but ignored.
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
| [Reference &mdash; Examples](Reference-Examples) | Shipped flows + debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [EVOLV &mdash; Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
| [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |