Files
valve/CONTRACT.md
znetsixe 167b1026f1 docs(CONTRACT): document valve's lack of a maintenance state machine
valve's schema mode enum includes `maintenance` (which gates sources)
but the FSM has no `entermaintenance` / `exitmaintenance` states and
the schema's `sequences` block has only startup / shutdown /
emergencystop / boot. Maintenance mode therefore disables external
sources but doesn't run a maintenance sequence — different shape
from rotatingMachine. Added a Limitations section to the CONTRACT so
this is explicit rather than surfacing as a wiki TODO.

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

6.6 KiB

valve — Contract

Generated from src/commands/index.js (canonical topic + alias list) plus the hand-written events section. Keep ≤ 100 lines.

Inputs (msg.topic on Port 0)

Canonical Aliases (deprecated) Payload Effect
set.mode setMode string — one of the allowed mode names Calls source.setMode(payload). Invalid mode logs warn and is dropped.
cmd.startup { source?: string } Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'startup').
cmd.shutdown { source?: string } Calls source.handleInput(payload.source ?? 'parent', 'execSequence', 'shutdown'). Pre-shutdown the valve ramps to position 0 if currently operational.
cmd.estop emergencystop, emergencyStop { source?: string, action?: string } Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'emergencystop').
execSequence — (legacy umbrella) { 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.
set.position execMovement { source, action, setpoint } — setpoint coerced to Number; valve position percent in [0, 100] Calls source.handleInput(payload.source ?? 'parent', payload.action ?? 'execMovement', Number(payload.setpoint)).
data.flow updateFlow { variant, value, position, unit? }variant ∈ {'measured','predicted'} Pushes a flow value into the measurement container at <position> and triggers a deltaP recompute through the hydraulic model.
query.curve showcurve none Calls source.showCurve() and replies on Port 0 with { topic: 'Showing curve', payload: <result> } via ctx.send.
child.register registerChild string — child Node-RED id; msg.positionVsParent carries position Resolves 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.

Aliases log a one-time deprecation warning the first time they fire.

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. The deprecation warning fires once. Future-Phase-7 removal of execSequence is a behavioural change — callers must migrate to the canonical topics.

Outputs (msg.topic on Port 0/1/2)

  • Port 0 (process): msg.topic = config.general.name. Payload built by outputUtils.formatMsg(..., 'process') from getOutput() — delta-compressed (only changed fields are emitted). On query.curve the node additionally emits { topic: 'Showing curve', payload: <result> } as a synchronous reply on Port 0.
  • Port 1 (InfluxDB telemetry): same shape as Port 0, formatted with the 'influxdb' formatter.
  • Port 2 (registration): at startup the node sends one { topic: 'child.register', payload: <node.id>, positionVsParent, distance } to its upstream parent (typically a valveGroupControl). positionVsParent defaults to 'atEquipment'.

getOutput() keys per tick include: <position>_<variant>_<type> slots from the measurement container (e.g. delta_predicted_pressure, downstream_measured_flow), plus state, percentageOpen, moveTimeleft, mode.

Events emitted by source.emitter

  • deltaPChange — fires whenever the hydraulic model recomputes a finite deltaP. Data: the deltaP value in unitPolicy.output.pressure (default mbar). Consumed by valveGroupControl to update group totals.
  • fluidCompatibilityChange — fires when the upstream fluid-contract status changes (status / expected / received / sourceCount / message). Data: FluidCompatibility.getCompatibility().
  • fluidContractChange — fires whenever the fluid contract that this valve advertises downstream changes. Data: FluidCompatibility.getContract().

Events emitted by source.state.emitter

  • positionChange — fires when the position percentage changes (per movement tick). Data: { position, state, mode, timestamp }. The valve itself listens and triggers a Kv lookup + deltaP recompute.
  • stateChange — fires on transitions of the operating state machine (idle → starting → warmingup → operational → accelerating → decelerating → stopping → coolingdown → idle, plus off).

Limitations vs rotatingMachine

  • No maintenance state machine. The schema's mode.current enum accepts maintenance (gates sources via isValidSourceForMode), but the FSM has no entermaintenance / exitmaintenance states and the sequences schema declares only startup, shutdown, emergencystop, and boot. Configuring maintenance mode therefore disables external command sources but does not put the valve through a maintenance sequence. Aligns with valve's role as a passive flow-controlled actuator; lift to RM-style FSM if/when site maintenance procedures require explicit state transitions.

Events emitted by source.measurements.emitter

The MeasurementContainer fires <type>.<variant>.<position> whenever a series receives a new value. Parents subscribe via the generic child.measurements.emitter.on(eventName, ...) handshake. valve publishes:

  • pressure.predicted.delta — predicted pressure drop across the valve.
  • pressure.measured.<position>, pressure.predicted.<position> — when upstream pressure data arrives via data.flow-driven recompute or direct measurement pushes.
  • flow.measured.<position>, flow.predicted.<position> — mirrored from upstream sources via data.flow.

Position labels are normalised to lowercase in the event name.

Children registered by this node

valve accepts upstream sources (machine, rotatingmachine, machinegroup, machinegroupcontrol, pumpingstation, valvegroupcontrol, …) via child.register. The handler records each child for fluid-contract tracking: the valve reads either the child's getFluidContract() result, its asset.serviceType field, or a default per software type (liquid for the rotating-equipment family). It then subscribes to the child's fluidContractChange so re-keyed contracts propagate.