Files
valveGroupControl/wiki/Reference-Contracts.md
znetsixe 9552e4fba9 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:12 +02:00

13 KiB

Reference — Contracts

code-ref

Note

Pending full node review (2026-05). Content reflects CONTRACT.md and current source only.

Full topic contract, configuration schema, and child-registration filters for valveGroupControl. Source of truth: src/commands/index.js, src/specificClass.js configure(), and the schema at generalFunctions/src/configs/valveGroupControl.json.

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) Switch operational mode via source.setMode(payload). Each mode has its own allow-list of sources (mode.allowedSources).
set.position setpoint any No-op pending Phase 7. Reserved for future per-valve positional override; the handler is debug-logged only.
child.register registerChild string (child node id) Resolve via RED.nodes.getNode; if the child exposes .source, register through childRegistrationUtils.registerChild(child.source, msg.positionVsParent).
cmd.execSequence execSequence { source, action, parameter } Forward to source.handleInput(source, action, parameter). The action typically names a sequence; the parameter typically names the state list.
data.totalFlow totalFlowChange number, { value, position?, variant?, unit? }, or { source, action, ... } volumeFlowRate (default m3/h) Update total measured/predicted flow at the configured position; drives calcValveFlows to re-distribute. If payload.source is present, route via handleInput(src, action, payload); otherwise treat as parent/totalFlowChange.
cmd.emergencyStop emergencyStop, emergencystop optional { source } Run the emergencystop sequence via handleInput(src, 'emergencystop'). Default source is parent.
set.reconcileInterval setReconcileInterval number — seconds (> 0) seconds Re-tune the periodic flow-reconciliation interval (setReconcileIntervalSeconds). Min clamp 100 ms. Non-finite or ≤ 0 logs a warn and is dropped.

Mode / source allow-lists

A topic that survives the registry still passes through flowControllerhandleInput, which enforces:

if (!host.isValidSourceForMode(source, host.currentMode)) {
  this.logger.warn(`Source '${source}' is not valid for mode '${this.currentMode}'.`);
  return { status: false, feedback: ... };
}

Defaults from the schema:

Mode allowedActions allowedSources
auto statusCheck, execSequence, emergencyStop, valvePositionChange, totalFlowChange, valveDeltaPchange parent, GUI, fysical
virtualControl statusCheck, execSequence, emergencyStop, valvePositionChange, totalFlowChange, valveDeltaPchange GUI, fysical
fysicalControl statusCheck, emergencyStop fysical
maintenance statusCheck (schema does NOT define allowedSources.maintenance; isValidSourceForMode returns false for every source — effectively monitoring-only)

Warning

Source contradiction: CONTRACT.md describes set.mode as switching between "auto / manual control modes", but the schema defines four modes (auto / virtualControl / fysicalControl / maintenance) and specificClass.setMode validates against the schema's enum. The wider four-mode set is the implementation. TODO: tighten the prose in CONTRACT.md to enumerate the schema modes.

Warning

Source contradiction: the schema declares an mode.allowedActions table, but the running implementation only consults isValidSourceForModeisValidActionForMode is not implemented on VGC. Action allow-lists are effectively dead config. TODO: either implement the action check (mirroring rotatingMachine's pattern) or remove allowedActions from the schema.


Data model — getOutput() shape

Composed each tick by src/io/output.getOutput(). Delta-compressed: consumers see only keys whose getCurrentValue() is non-null.

Scalar keys

Key Type Source Notes
mode string vgc.currentMode auto / virtualControl / fysicalControl / maintenance.
maxDeltaP number vgc.maxDeltaP Cached max delta-P over registered valves (in output pressure unit, default mbar). Same data is also surfaced via the measurement-derived key deltaMax_predicted_pressure.

Measurement-derived keys

For every (type, variant, position) in MeasurementContainer with a finite value, the flattened output emits:

<position>_<variant>_<type>
Example key Unit Source Notes
atEquipment_measured_flow m³/h upstream source flow.measured.* events; data.totalFlow with variant=measured Total measured flow at the group inlet.
atEquipment_predicted_flow m³/h written by distributeFlow as sum(accepted) Sum of per-valve accepted flows after Kv-share + residual.
deltaMax_predicted_pressure mbar written by calcMaxDeltaP Max pressure.predicted.delta across registered valves.

Delta compression: only changed fields are sent per tick. Consumers must cache and merge. See outputUtils.formatMsg.

Status badge

io/output.getStatusBadge:

<mode> | flow=<int> <flowUnit> | <N> valve(s) connected | (or 'No valves')
State Fill
getAvailableValves().length > 0 green dot
getAvailableValves().length === 0 red dot

flow is the rounded flow.measured.atEquipment, or flow.predicted.atEquipment if no measured value is available.


Configuration schema — editor form to config keys

Source of truth: generalFunctions/src/configs/valveGroupControl.json plus nodeClass.buildDomainConfig (which returns {} — no domain overrides).

General (config.general)

Form field Config key Default Notes
Name general.name "ValveGroupControl" Node label, status badge prefix (via topic).
(auto-assigned) general.id null Node-RED node id.
Default unit general.unit "unitless" (schema) / m3/h (configure() overrides via unitPolicy.output('flow')) Re-derived 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 "" (per CONTRACT.md) Used in the Port-2 register payload sent to the upstream parent. (Not in the JSON schema; supplied at runtime from the editor.)
(hidden) functionality.softwareType "valvegroupcontrol" Constant.
(hidden) functionality.role "ValveGroupController" Constant.

Asset (config.asset)

VGC's asset block is informational — there is no curve to load, no model registry, no allowed-unit validation.

Form field Config key Default Notes
Asset UUID asset.uuid null Globally-unique identifier.
Geolocation asset.geoLocation {x:0, y:0, z:0}
Supplier asset.supplier "Unknown" Informational.
Type asset.type "valve" Classification only.
Sub-type asset.subType "Unknown"
Model asset.model "Unknown" Informational; no registry lookup.
Accuracy asset.accuracy null

Mode (config.mode)

Form field Config key Default Range Notes
Mode mode.current auto auto / virtualControl / fysicalControl / maintenance The active operational mode.
(defaults) mode.allowedActions.<mode> see Mode allow-lists enforced by flowController (NOT implemented — see warning above)
(defaults) mode.allowedSources.<mode> see Mode allow-lists enforced by isValidSourceForMode

Sequences (config.sequences)

Per-sequence state-transition lists. Defaults:

Sequence States
startup [starting, warmingup, operational]
shutdown [stopping, coolingdown, idle]
emergencystop [emergencystop, off]
boot [idle, starting, warmingup, operational]

executeSequence(name) iterates the list and awaits state.transitionToState(stateName) per step. The default state object is created at boot with currentState = 'operational' so executeSequence works without a pre-warmup phase. (See Architecture — What VGC does NOT have.)

Calculation mode (config.calculationMode)

Value Description
low Calculations run at fixed intervals (time-based).
medium (default) Calculations run when new setpoints arrive or measured changes occur (event-driven).
high Calculations run on all event-driven info, including every movement.

Warning

calculationMode is in the schema but is not currently consulted by specificClass or nodeClass. The tick interval is fixed at tickInterval = 1000 ms and only retunable through set.reconcileInterval. TODO: wire calculationMode through or remove it.

Flow reconciliation (runtime only)

flowReconciliation lives on the domain (not in the schema):

Field Default Notes
maxPasses 2 Max iterations of the Kv-share residual loop.
residualTolerance 0.001 Stops loop when `

These are read by solveFlowDistribution each call; not currently exposed via a topic or editor field.

Unit policy

Source: src/specificClass.js.

Quantity Canonical (internal) Output (rendered) Required-unit
Flow m3/s m3/h
Pressure Pa mbar

requireUnitForTypes: ['pressure', 'flow'] — MeasurementContainer rejects writes that omit unit for these types.


Child registration

Source: src/specificClass.js _registerValve / _registerSource and src/sources/fluidContract.js.

Software type Filter Wired to Side-effect
valve child exposes updateFlow, state.getCurrentState, measurements (_isValveLike) Stored in vgc.valves[id]; events bound. Subscribes to state.emitter.positionChange (→ calcValveFlows) and emitter.deltaPChange (→ calcMaxDeltaP). Triggers an initial calcValveFlows + calcMaxDeltaP + refreshFluidContract.
machine (incl. canonicalised rotatingmachine) router callback registerSource (sources/fluidContract) Subscribes to 6 flow event names on child.measurements.emitter; subscribes to child.emitter.fluidContractChange.
machinegroup (incl. canonicalised machinegroupcontrol) router callback registerSource Same as machine.
pumpingstation router callback registerSource Same as machine.
valvegroupcontrol router callback registerSource Cascaded VGC; accepted by router. Not exercised in production — see Limitations.

Position labels accepted from children are upstream, downstream, atEquipment (and case variants — normalised internally).

Source flow events

bindSource attaches a listener for every event name in SOURCE_FLOW_EVENTS:

flow.predicted.downstream
flow.predicted.atEquipment
flow.predicted.atequipment
flow.measured.downstream
flow.measured.atEquipment
flow.measured.atequipment

The handler reads eventData.value (number) and eventData.unit and writes vgc.updateFlow(variant, value, 'atEquipment', unit). variant is derived from the event-name middle segment (measured vs predicted).

Fluid contract reconciliation

See Architecture — Source aggregation for the full reconciliation logic. The aggregated fluidContract is exposed via vgc.getFluidContract():

{
  "status": "resolved" | "conflict" | "inferred" | "unknown",
  "serviceType": "liquid" | "gas" | null,
  "upstreamServiceTypes": ["liquid"],
  "sourceCount": 2,
  "message": "Upstream fluid resolved as liquid.",
  "source": "valvegroupcontrol"
}

Changes are broadcast via source.emitter.emit('fluidContractChange', ...).


Page Why
Home Intuitive overview
Reference — Architecture Code map, flow-distribution loop, source aggregation
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