docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
254
wiki/Reference-Contracts.md
Normal file
254
wiki/Reference-Contracts.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Reference — Contracts
|
||||
|
||||

|
||||
|
||||
> [!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 — populate via wiki-gen tool (TODO) -->
|
||||
|
||||
| Canonical topic | Aliases | Payload | Unit | Effect |
|
||||
|:---|:---|:---|:---|:---|
|
||||
| `set.mode` | `setMode` | `string` (`auto` / `virtualControl` / `fysicalControl` / `maintenance`) | — | Calls `source.setMode(payload)`. Invalid mode logs `warn` and is dropped. |
|
||||
| `cmd.startup` | — | `{ source?: string }` | — | `source.handleInput(payload.source ?? 'parent', 'execSequence', 'startup')` — runs the configured `startup` sequence (default `[starting, warmingup, operational]`). |
|
||||
| `cmd.shutdown` | — | `{ source?: string }` | — | `source.handleInput(..., 'execSequence', 'shutdown')`. Pre-shutdown the valve ramps to position 0 if currently `operational`. |
|
||||
| `cmd.estop` | `emergencystop`, `emergencyStop` | `{ source?: string, action?: string }` | — | `source.handleInput(payload.source ?? 'parent', payload.action ?? 'emergencystop')` — runs the `emergencystop` sequence (default `[emergencystop, off]`). |
|
||||
| `set.position` | `execMovement` | `{ source?: string, action?: string, setpoint: number }` | control % (no `units`; no `percent` measure in convert) | `source.handleInput(..., 'execMovement', Number(payload.setpoint))` — moves the valve to a position via `state.moveTo`. |
|
||||
| `data.flow` | `updateFlow` | `{ variant, value, position, unit? }` — `variant ∈ {'measured','predicted'}` | `volumeFlowRate` (default `m3/h`) | `source.updateFlow(...)` — pushes a flow value into MeasurementContainer at `<position>` and triggers a deltaP recompute. |
|
||||
| `query.curve` | `showcurve` | any | — | `source.showCurve()` — replies on **Port 0** with `{ topic: 'Showing curve', payload: <result> }` via `ctx.send`. |
|
||||
| `child.register` | `registerChild` | `string` (child node id); `msg.positionVsParent` carries the position label | — | Resolves the child via `RED.nodes.getNode(payload)` and registers it through `childRegistrationUtils.registerChild(child.source, msg.positionVsParent)`. The valve's `registerChild` records the child for fluid-contract tracking. |
|
||||
| `execSequence` | — (legacy umbrella, `_legacy: true`) | `{ source, action, parameter }` with `action ∈ {'startup','shutdown','emergencyStop','emergencystop'}` | — | Content-based router: forwards to canonical `cmd.startup` / `cmd.shutdown` / `cmd.estop` based on `payload.action`. Unknown action logs `warn`. Prefer the canonical `cmd.*` topics. |
|
||||
|
||||
<!-- END AUTOGEN -->
|
||||
|
||||
### `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 — 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 — 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 — `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 | — | `"operational"` — one of the FSM states. |
|
||||
| `percentageOpen` | number | % | `60` — current position `0..100`. |
|
||||
| `moveTimeleft` | number | s | `0` — seconds remaining on the current move. |
|
||||
| `mode` | string | — | `"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 — 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 — 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` — **non-interruptible** safety. |
|
||||
| Shutdown Time | `time.stopping` | Time in `stopping`. |
|
||||
| Cooldown Time | `time.coolingdown` | Time in `coolingdown` — **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–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 — not currently gated in code |
|
||||
| (defaults) | `mode.allowedSources.<mode>` | see above | enforced by `flowController.isValidSourceForMode` |
|
||||
|
||||
### Unit policy
|
||||
|
||||
Source: `src/specificClass.js` lines 20–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` — declared in the schema. TODO: confirm whether the dispatch path consults `calculationMode` for trigger frequency. Source: `valve.json` lines 346–366.
|
||||
|
||||
---
|
||||
|
||||
## Child registration
|
||||
|
||||
Source: `src/specificClass.js` lines 100–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 — Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
|
||||
| [Reference — Examples](Reference-Examples) | Shipped flows + debug recipes |
|
||||
| [Reference — Limitations](Reference-Limitations) | Known issues and open questions |
|
||||
| [EVOLV — Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
|
||||
| [EVOLV — Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |
|
||||
Reference in New Issue
Block a user