# 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 `` 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: }` 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: }` 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: , positionVsParent, distance }` to its upstream parent (typically a `valveGroupControl`). `positionVsParent` defaults to `'atEquipment'`. `getOutput()` keys per tick include: `__` 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`). ## Events emitted by `source.measurements.emitter` The `MeasurementContainer` fires `..` 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.`, `pressure.predicted.` — when upstream pressure data arrives via `data.flow`-driven recompute or direct measurement pushes. - `flow.measured.`, `flow.predicted.` — 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.