Files
valveGroupControl/wiki/Reference-Contracts.md
znetsixe d81aedc9bc docs(wiki): regenerate topic-contract AUTOGEN block via wiki-gen
Replaces the agent-written placeholder inside Reference-Contracts.md with
the authoritative table generated from src/commands/index.js. Both the
BEGIN and END markers are normalized to the canonical form used by
`@evolv/wiki-gen`.

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

12 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 Switch the valve group between auto / manual control modes.
set.position setpoint any Set the group-level valve position (currently a no-op pending Phase 7).
child.register registerChild string Register a child valve with this group.
cmd.execSequence execSequence object Run a group-wide sequence (startup / shutdown / emergencystop).
data.totalFlow totalFlowChange any Notify the group that the total flow setpoint has changed.
cmd.emergencyStop emergencyStop, emergencystop any Trigger an emergency stop across all valves in the group.
set.reconcileInterval setReconcileInterval any Update the reconciliation interval (seconds).

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