# Reference — 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. | 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. | ### `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: ``` __ ``` This is the **legacy three-segment** key shape (no trailing ``). Position labels are normalised to lowercase. valve does **not** use the four-segment `...` shape that `rotatingMachine` emits. | 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. | ### Status badge `buildStatusBadge` in `io/output.js`: ``` : % 💨 ΔP ``` (The `position` / `flow` / `deltaP` line only appears in `operational` / `warmingup` / `accelerating` / `decelerating`; other states show just `: `.) 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 `⚠ ` 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.` | see [Topic contract](#mode--source-allow-lists) | schema only — not currently gated in code | | (defaults) | `mode.allowedSources.` | 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. | `.measured.` | | `measurement` (asset.type=`flow`, position=`*`) | Routed through `MeasurementRouter.updateFlow(variant, value, position, unit)`; triggers a deltaP recompute. | `.measured.` | 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 |