Files
measurement/CONTRACT.md
znetsixe 5d79314229 feat(units) + style: command unit-handling, frost dbase option, palette #D4A02E
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>
2026-05-21 15:06:37 +02:00

4.0 KiB

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 scalarmsg.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 objectmsg.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.