measurement.html:
• sidebar swatch → #D4A02E (amber, sensor family) — EVOLV palette redesign
2026-05-21 (see superproject .claude/rules/node-red-flow-layout.md §10.0).
• Add "frost" option to dbaseOutputFormat dropdown (CoreSync FROST handoff).
src/commands/handlers.js + test/basic/commands-units.basic.test.js:
• Unit handling for data.measurement command. Analog + digital modes both
accept scalar / object / per-channel-map payloads; supplied units are
converted into the channel's configured (dropdown) unit.
CONTRACT.md: document the unit semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
4.0 KiB
Markdown
79 lines
4.0 KiB
Markdown
# measurement — Contract
|
|
|
|
Hand-maintained for Phase 3; the `## Inputs` table is generated from
|
|
`src/commands/index.js` (see Phase 9 generator). Keep ≤ 80 lines.
|
|
|
|
## Inputs (msg.topic on Port 0)
|
|
|
|
| Canonical | Aliases (deprecated) | Payload | Effect |
|
|
|---|---|---|---|
|
|
| `set.simulator` | `simulator` | none (payload ignored) | Toggles `source.toggleSimulation()` — flips `config.simulation.enabled`. |
|
|
| `set.outlier-detection` | `outlierDetection` | none (payload ignored) | Toggles `source.toggleOutlierDetection()` — flips `config.outlierDetection.enabled`. |
|
|
| `cmd.calibrate` | `calibrate` | none | Calls `source.calibrate()` — captures the current input as the zero/reference offset. |
|
|
| `data.measurement` | `measurement` | mode-dependent — see **Payload shape** below | Pushes a sensor reading into the pipeline. Analog → `source.inputValue`; digital → `source.handleDigitalPayload(<flat map>)`. Wrong shape for the configured mode logs a helpful warning suggesting the other mode. |
|
|
|
|
Aliases log a one-time deprecation warning the first time they fire.
|
|
|
|
### `data.measurement` payload shape
|
|
|
|
Both modes accept the same three forms, mirroring pumpingStation's
|
|
`set.inflow` contract:
|
|
|
|
- **Bare scalar** — `msg.payload = 12.5` (number or numeric string). The unit
|
|
falls back to `msg.unit`, and finally to the channel's configured unit
|
|
(the dropdown selection in the node editor).
|
|
- **Rich object** — `msg.payload = { value, unit?, timestamp? }`. Used per-
|
|
call to declare the unit of a single sample.
|
|
- **Digital map** (digital mode only) — `msg.payload = { <channelKey>: <bare scalar | rich object>, … }`. Each entry follows the rules above independently, so different channels in one message may carry different units.
|
|
|
|
When a supplied unit differs from the channel's configured unit, the value
|
|
is converted into the channel unit via `generalFunctions.convert` before it
|
|
enters the outlier / scaling / smoothing pipeline. If the supplied unit is
|
|
unknown or belongs to a different measure (e.g. `kg` on a `pressure`
|
|
channel), the handler logs a warning and uses the raw value treated as the
|
|
channel unit — the sample is not silently dropped.
|
|
|
|
## Outputs (msg.topic on Port 0/1/2)
|
|
|
|
- **Port 0 (process):** `msg.topic = config.general.name`. Payload built by
|
|
`outputUtils.formatMsg(..., 'process')` from `getOutput()` (analog) or
|
|
`getDigitalOutput()` (digital). Delta-compressed — only changed fields are
|
|
emitted.
|
|
- **Port 1 (InfluxDB telemetry):** same shape as Port 0, formatted with the
|
|
`'influxdb'` formatter.
|
|
- **Port 2 (registration):** at startup the node sends one
|
|
`{ topic: 'registerChild', payload: <node.id>, positionVsParent, distance }`
|
|
to its parent.
|
|
|
|
## Events emitted by `source.measurements.emitter`
|
|
|
|
The `MeasurementContainer` fires `<type>.measured.<position>` whenever a
|
|
matching series receives a new value. The type / position labels are set
|
|
from `config.asset.type` and `config.functionality.positionVsParent`
|
|
(analog), or per-channel from `config.channels[*]` (digital). Examples:
|
|
|
|
- `pressure.measured.upstream`
|
|
- `flow.measured.atequipment`
|
|
- `level.measured.downstream`
|
|
- `temperature.measured.atequipment`
|
|
|
|
Position labels are always lowercase in the event name. Parents subscribe
|
|
through the generic `child.measurements.emitter.on(eventName, ...)` handshake
|
|
established by `childRegistrationUtils`.
|
|
|
|
In digital mode one input message can fan out into several events — one
|
|
per channel that accepted a value on that tick.
|
|
|
|
The legacy internal `source.emitter` also fires `'mAbs'` with the current
|
|
scaled absolute value (analog mode only). This is deprecated in favour of
|
|
`measurements.emitter` and kept only for the editor status badge during the
|
|
refactor window.
|
|
|
|
## Children registered by this node
|
|
|
|
None — `measurement` is a leaf in the S88 hierarchy (Control Module). It
|
|
registers itself as a child of an upstream parent (rotatingMachine,
|
|
pumpingStation, reactor, monster, …) but does not accept its own children.
|
|
Registration goes via Port 2 at startup and is keyed off
|
|
`positionVsParent` / `distance` in the node's UI config.
|