Refactor of valve to use the platform infrastructure (BaseDomain, BaseNodeAdapter, ChildRouter, commandRegistry, statusBadge). Extracts concerns into focused modules per .claude/refactor/MODULE_SPLIT.md generic template. Tests stay green; CONTRACT.md generated; legacy aliases preserved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.9 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 byoutputUtils.formatMsg(..., 'process')fromgetOutput()— delta-compressed (only changed fields are emitted). Onquery.curvethe 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 avalveGroupControl).positionVsParentdefaults 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 inunitPolicy.output.pressure(defaultmbar). Consumed byvalveGroupControlto 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, plusoff).
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 viadata.flow-driven recompute or direct measurement pushes.flow.measured.<position>,flow.predicted.<position>— mirrored from upstream sources viadata.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.