Replaces the agent-written placeholder inside Reference-Contracts.md with the authoritative table generated from src/commands/index.js. Both the BEGIN and END markers are normalized to the canonical form used by `@evolv/wiki-gen`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
Reference — Contracts
Note
Full topic contract, configuration schema, and child-registration handshake for
measurement. Source of truth:src/commands/index.js,src/commands/handlers.js,src/specificClass.jsconfigure(), and the schema atgeneralFunctions/src/configs/measurement.json.Pending full node review (2026-05). Hand-written best-effort placeholder where indicated. For an intuitive overview, return to Home.
Topic contract
The registry lives in src/commands/index.js. Each descriptor maps a canonical msg.topic to a handler; aliases emit a one-time deprecation warning the first time they fire.
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
set.simulator |
simulator |
any | — | Toggle the built-in simulator on / off. |
set.outlier-detection |
outlierDetection |
any | — | Toggle / configure outlier detection on the measurement pipeline. |
cmd.calibrate |
calibrate |
any | — | Trigger a one-shot calibration of the measurement. |
data.measurement |
measurement |
any | — | Push a raw measurement (analog: number; digital: per-channel object). |
Payload-shape rules
| Mode | Accepted | Rejected (logs warn) |
|---|---|---|
analog |
number; numeric string (trimmed, non-empty, parses with Number) |
object payload (hint: "Switch Input Mode to 'digital' …"); non-numeric string |
digital |
object { key1: number, key2: number, … } — keys must match config.channels[*].key |
number (hint: "Switch Input Mode to 'analog' …"); array; any non-object |
Unknown channel keys in a digital payload are collected and reported at debug level via digital payload contained unmapped keys: <list>.
Source / mode allow-lists
Note
TODO:
measurementdoes not appear to implement aflowController-style action/source allow-list (consultsrc/specificClass.js); it relies on the topic registry's typeof checks. If a future hardening pass adds mode-source gating, fold the table in here.
Data model — getOutput() shape
Source: src/specificClass.js getOutput() / getDigitalOutput() and src/channel.js getOutput(). Delta-compressed by outputUtils.formatMsg: consumers see only the keys that changed.
Analog mode (Measurement.getOutput())
| Key | Type | Unit | Notes |
|---|---|---|---|
mAbs |
number | scaling units (asset.unit / general.unit) |
Latest output value after offset + scaling + smoothing. Rounded to 2 dp. |
mPercent |
number | % | Output mapped to interpolation.percentMin..percentMax. Rounded to 2 dp. |
totalMinValue |
number | scaling units | Rolling minimum of the post-offset, pre-smoothing values. Reported as 0 until the first sample. |
totalMaxValue |
number | scaling units | Rolling maximum of the same. Reported as 0 until the first sample. |
totalMinSmooth |
number | scaling units | Rolling minimum of the smoothed output. Starts at 0. |
totalMaxSmooth |
number | scaling units | Rolling maximum of the smoothed output. Starts at 0. |
Digital mode (Measurement.getDigitalOutput())
{
"channels": {
"<channel.key>": {
"key": "<channel.key>",
"type": "<channel.type>",
"position": "<channel.position>",
"unit": "<channel.unit>",
"mAbs": <number>,
"mPercent": <number>,
"totalMinValue": <number>,
"totalMaxValue": <number>,
"totalMinSmooth": <number>,
"totalMaxSmooth": <number>
}
// ... one entry per channel that has produced output
}
}
Status badge
Measurement.getStatusBadge():
| Mode | Badge text | Fill / shape |
|---|---|---|
analog |
<mAbs> <unit> (e.g. 0.42 bar) |
green / dot |
digital |
digital · <N> channel(s) |
blue / ring |
The legacy source.emitter fires 'mAbs' (analog only) and is kept for the editor status badge during the refactor window — see Limitations.
Events emitted on source.measurements.emitter
The shared MeasurementContainer fires <type>.measured.<position> whenever a Channel's rounded output changes. The type / position come from:
- analog:
config.asset.typeandconfig.functionality.positionVsParent. - digital: per-channel
config.channels[i].typeandconfig.channels[i].position(falls back to the node-levelpositionVsParentwhen missing).
Position labels are normalised to lowercase in the event name (upstream, downstream, atequipment). Examples:
pressure.measured.upstreamflow.measured.atequipmentlevel.measured.downstreamtemperature.measured.atequipment
Parents subscribe through the generic child.measurements.emitter.on(eventName, …) handshake established by childRegistrationUtils (in generalFunctions).
In digital mode one input message can fan out into several events — one per channel that accepted a value on that tick.
Configuration schema — editor form to config keys
Source of truth: generalFunctions/src/configs/measurement.json plus nodeClass.buildDomainConfig. Defaults below come from the schema.
General (config.general)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Name | general.name |
Sensor |
Human-readable label. |
| (auto-assigned) | general.id |
null |
Node-RED node id. |
| Default unit | general.unit |
unitless |
Falls back to the asset unit. |
| Enable logging | general.logging.enabled |
true |
Master switch. |
| Log level | general.logging.logLevel |
info |
debug / info / warn / error. |
Functionality (config.functionality)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Software type | functionality.softwareType |
measurement |
Constant. |
| Role | functionality.role |
Sensor |
Constant. |
| Position vs parent | functionality.positionVsParent |
atEquipment |
One of atEquipment / upstream / downstream. Used in the child.register payload and as the suffix of the measurement event name. |
| Distance offset | functionality.distance |
null |
Optional spatial offset; sent with child.register. |
Asset (config.asset)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Asset UUID | asset.uuid |
null |
Globally-unique identifier. |
| Tag code / number | asset.tagCode / asset.tagNumber |
null |
Asset-registry identifiers. |
| Geolocation | asset.geoLocation |
{x:0, y:0, z:0} |
|
| Supplier | asset.supplier |
Unknown |
Free text. |
| Category | asset.category |
sensor |
sensor / measurement. |
| Asset type | asset.type |
pressure |
Required. Matches the type axis on MeasurementContainer and the parent's filter (e.g. flow, power, temperature). |
| Model | asset.model |
Unknown |
Free text. |
| Asset unit | asset.unit |
unitless |
Output unit label for the measurement event payload. |
| Accuracy | asset.accuracy |
null |
Optional sensor accuracy. |
| Repeatability | asset.repeatability |
null |
Optional repeatability metric. |
Important
asset.typemust match the exact string the parent listens for. The parent's filter is typically the bare type (flow,pressure,power, …) — a measurement configured asflow-electromagneticwill not register with aflow-only filter on its parent (see Limitations).
Scaling (config.scaling)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Scaling enabled | scaling.enabled |
false |
When false, the input is passed through with only the offset applied. |
| Input min/max | scaling.inputMin / scaling.inputMax |
0 / 1 |
Source range; clamps the input before mapping. |
| Output min/max | scaling.absMin / scaling.absMax |
50 / 100 |
Target range. |
| Offset | scaling.offset |
0 |
Added before scaling; mutated by cmd.calibrate. |
Smoothing (config.smoothing)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Window size | smoothing.smoothWindow |
10 |
>= 1. Rolling buffer length. |
| Method | smoothing.smoothMethod |
mean |
One of none / mean / min / max / sd / median / weightedMovingAverage / lowPass / highPass / bandPass / kalman / savitzkyGolay. |
Outlier detection (config.outlierDetection)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Enabled | outlierDetection.enabled |
false |
Toggle with set.outlier-detection. |
| Method | outlierDetection.method |
zScore |
One of zScore / iqr / modifiedZScore. |
| Threshold | outlierDetection.threshold |
3 |
Method-specific (e.g. z > 3, mz > 3.5). |
Simulation (config.simulation)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Enabled | simulation.enabled |
false |
When true, tick() (1000 ms) drives inputValue via Simulator.step(). |
| Safe calibration time | simulation.safeCalibrationTime |
100 |
ms before calibration is finalised in sim mode. |
Interpolation (config.interpolation)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Percent min | interpolation.percentMin |
0 |
Lower bound of the mPercent output. |
| Percent max | interpolation.percentMax |
100 |
Upper bound. |
Calibration (config.calibration)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Stability threshold | calibration.stabilityThreshold |
0.01 |
Absolute stdDev ceiling (in scaling-units) below which the buffer is considered stable. Fits the default [50,100] range; tighten / relax for your sensor. |
Mode (config.mode)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Input mode | mode.current |
analog |
analog (one channel, scalar payload) or digital (N channels, object payload). |
Channels (config.channels[] — digital only)
In digital mode, each entry in config.channels defines its own pipeline:
| Field | Required | Falls back to |
|---|---|---|
key |
yes | — (skipped if missing) |
type |
yes | — (skipped if missing) |
position |
no | config.functionality.positionVsParent → atEquipment |
unit |
no | config.asset.unit → unitless |
distance |
no | config.functionality.distance → null |
scaling |
no | {enabled:false, inputMin:0, inputMax:1, absMin:0, absMax:1, offset:0} |
smoothing |
no | config.smoothing |
outlierDetection |
no | config.outlierDetection |
interpolation |
no | config.interpolation |
Invalid entries (missing key or type) are logged and skipped. An empty config.channels[] in digital mode logs digital mode enabled but config.channels is empty; no channels will be emitted.
Asset registration (config.assetRegistration)
Used by the /measurement/asset-reg admin endpoint to register / sync the asset with the upstream asset registry. Not part of the runtime data path.
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Profile / location / process ids | assetRegistration.{profileId, locationId, processId} |
1 |
Free integer ids in the asset registry. |
| Status | assetRegistration.status |
actief |
Lifecycle status. |
| Child assets | assetRegistration.childAssets |
[] |
List of child asset ids. |
Output (config.output)
| Form field | Config key | Default | Notes |
|---|---|---|---|
| Process output | output.process |
process |
process / json / csv. Port-0 formatter. |
| Database output | output.dbase |
influxdb |
influxdb / json / csv. Port-1 formatter. |
Unit policy
Note
TODO:
measurementdoes not currently declare aunitPolicyblock on itsBaseDomainconfiguration (unlikerotatingMachine). The per-channelunitis carried verbatim into theMeasurementContainerwrite at_writeOutput. If a future hardening pass adds a unit-policy enforcement, add the canonical / output / required-unit table here. SeeCONTRACT.mdfor the current invariants.
Child registration
Source: src/specificClass.js configure (registers itself via the BaseDomain plumbing) and the standard childRegistrationUtils handshake in generalFunctions.
measurement does not accept children. It only registers itself as a child on its upstream parent.
| Layer | Direction | Topic / event | Payload |
|---|---|---|---|
| Startup (Port 2) | child → parent | registerChild |
{topic: 'registerChild', payload: <node.id>, positionVsParent, distance} |
| Runtime | child → parent | <asset.type>.measured.<positionVsParent> on child.measurements.emitter |
{value, timestamp, unit, distance?} (per MeasurementContainer.value()) |
| What | softwareType payload | Side-effect on parent |
|---|---|---|
| Registration | measurement |
Parent attaches a listener for <asset.type>.measured.<positionVsParent> on the child's measurements.emitter. |
| Subsequent updates | event on child.measurements.emitter |
Parent mirrors the value into its own MeasurementContainer slot. |
Position labels are normalised to lowercase in the event name (upstream, downstream, atequipment); the positionVsParent field in the register payload is sent as configured (preserves case).
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Architecture | Code map + per-Channel pipeline + lifecycle |
| Reference — Examples | Shipped flows + debug recipes |
| Reference — Limitations | Known issues and open questions |
| EVOLV — Topic Conventions | Platform-wide topic rules |
| EVOLV — Telemetry | Port 0 / 1 / 2 InfluxDB layout |