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>
108 lines
6.6 KiB
Markdown
108 lines
6.6 KiB
Markdown
# 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.
|