Files
valve/wiki/Reference-Contracts.md
znetsixe 87214788d2 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>
2026-05-19 09:42:11 +02:00

15 KiB
Raw Blame History

Reference — Contracts

code-ref

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.


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 (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.

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:

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. 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.

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:

<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 warmingupnon-interruptible safety.
Shutdown Time time.stopping Time in stopping.
Cooldown Time time.coolingdown Time in coolingdownnon-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 1926.

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 schema only — not currently gated in code
(defaults) mode.allowedSources.<mode> see above enforced by flowController.isValidSourceForMode

Unit policy

Source: src/specificClass.js lines 2024.

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 346366.


Child registration

Source: src/specificClass.js lines 100101 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.


Page Why
Home Intuitive overview
Reference — Architecture Code map, FSM, hydraulic-model pipeline
Reference — Examples Shipped flows + debug recipes
Reference — Limitations Known issues and open questions
EVOLV — Topic Conventions Platform-wide topic rules
EVOLV — Telemetry Port 0 / 1 / 2 InfluxDB layout