Files
measurement/wiki/Home.md
znetsixe 1a16f9c4f1 docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:42:10 +02:00

7.4 KiB
Raw Blame History

measurement

code-ref s88 status

A measurement turns a raw sensor signal into a validated, scaled, smoothed reading and re-emits it for any upstream parent. Two modes: analog (one channel built from the flat config — classic 420 mA / PLC style) and digital (one Channel per config.channels[] entry — MQTT / IoT JSON style). It is a leaf in the S88 hierarchy — no children of its own — and registers itself as a child of any parent that accepts measurements (rotatingMachine, machineGroupControl, pumpingStation, reactor, monster, …).

Note

Pending full node review (2026-05). Content reflects CONTRACT.md, src/commands/index.js, and current source only. Some sections are best-effort placeholders pending the next pass.


At a glance

Thing Value
What it represents One sensor signal — pressure / flow / power / temperature / level / …
S88 level Control Module
Use it when You need to scale, offset, smooth, outlier-filter, or simulate a sensor reading before handing it to an equipment / unit / process-cell node
Don't use it for Sensor fusion, threshold-trip alarms, or as a control output — this node is read-only signal conditioning
Children it accepts None — leaf node
Parents it talks to Any node that subscribes to <type>.measured.<position> events (rotatingMachine, MGC, pumpingStation, reactor, monster, …)

How it fits

flowchart LR
    raw[Raw sensor / MQTT / inject<br/>analog scalar or digital object]
    m[measurement<br/>Control Module]:::ctrl
    p1[rotatingMachine<br/>Equipment]:::equip
    p2[machineGroupControl<br/>Unit]:::unit
    p3[pumpingStation<br/>Process Cell]:::pc

    raw -->|data.measurement| m
    m -->|child.register<br/>(Port 2 at startup)| p1
    m -->|child.register| p2
    m -->|child.register| p3
    m -.->|"&lt;type&gt;.measured.&lt;position&gt;"| p1
    m -.->|"&lt;type&gt;.measured.&lt;position&gt;"| p2
    m -.->|"&lt;type&gt;.measured.&lt;position&gt;"| p3
    classDef pc fill:#0c99d9,color:#fff
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef ctrl fill:#a9daee,color:#000

S88 colours: Control Module #a9daee, Equipment #86bbdd, Unit #50a8d9, Process Cell #0c99d9. Source of truth: .claude/rules/node-red-flow-layout.md.


Try it — 1-minute demo

Import the basic example flow, deploy, and drive a single sensor through scaling + smoothing.

curl -X POST -H 'Content-Type: application/json' \
  --data @nodes/measurement/examples/basic.flow.json \
  http://localhost:1880/flows

What to do after deploy:

  1. Click the measurement 42 inject — sends topic: 'measurement' (legacy alias of data.measurement) with payload 42.
  2. Watch Port 0 in the debug pane: mAbs updates immediately. After a few injects totalMinValue / totalMaxValue start tracking the rolling min/max.
  3. Toggle the simulator: send topic: 'set.simulator'. tick() (1000 ms) starts driving inputValue through Simulator.step().
  4. Trigger calibration: send topic: 'cmd.calibrate'. If the rolling window is stable (stdDev <= config.calibration.stabilityThreshold) the calibrator captures the current output as the new config.scaling.offset.

Important

GIF needed. Demo recording of steps 14 with the live status badge. Save as wiki/_partial-gifs/measurement/01-basic-demo.gif, target ≤ 1 MB after gifsicle -O3 --lossy=80.


The four things you'll send

Topic Aliases Payload What it does
data.measurement measurement analog: number (or numeric string); digital: {<channelKey>: number, &hellip;} Push a raw reading into the pipeline. Wrong shape for the configured mode logs a hint suggesting the other mode.
set.simulator simulator (ignored) Toggle the built-in Simulator random-walk on / off. Mutates config.simulation.enabled.
set.outlier-detection outlierDetection (ignored) Toggle outlier detection on the analog pipeline. Mutates config.outlierDetection.enabled.
cmd.calibrate calibrate (ignored) Run a one-shot calibration. Captures the current output as config.scaling.offset; aborts with a warn if the buffer is not stable.

Aliases log a one-time deprecation warning the first time they fire.


What you'll see come out

Sample Port 0 message (analog mode, after a few injects):

{
  "topic": "measurement#sensor_a",
  "payload": {
    "mAbs": 0.42,
    "mPercent": 42,
    "totalMinValue": 0.12,
    "totalMaxValue": 0.78,
    "totalMinSmooth": 0.20,
    "totalMaxSmooth": 0.65
  }
}

Sample Port 0 message (digital mode):

{
  "topic": "measurement#multi",
  "payload": {
    "channels": {
      "level-a": { "mAbs": 1.84, "mPercent": 73.6, "totalMinValue": 0.1, "totalMaxValue": 2.4 },
      "temp-a":  { "mAbs": 18.2, "mPercent": 36.4, "totalMinValue": 14.0, "totalMaxValue": 22.1 }
    }
  }
}
Field Meaning
mAbs Latest output value in scaling-units (after offset + scaling + smoothing).
mPercent Same value mapped to interpolation.percentMin..percentMax (default 0..100).
totalMinValue / totalMaxValue Rolling min/max of raw (pre-scaling) values. 0 until first sample.
totalMinSmooth / totalMaxSmooth Rolling min/max of the smoothed output.

Additionally the source.measurements.emitter fires <type>.measured.<position> on every accepted update — parents subscribe to that event through the child.measurements.emitter handshake established at register time. See Architecture — Lifecycle for the full path.


How the pipeline behaves

flowchart LR
    in[input value] --> out{outlierDetection.enabled?}
    out -- yes --> oc[_isOutlier]
    oc -- outlier --> drop[drop + warn]
    oc -- ok --> off[apply scaling.offset]
    out -- no --> off
    off --> mm[update raw totalMin/Max]
    mm --> sc{scaling.enabled?}
    sc -- yes --> lin[linear map<br/>input range → abs range]
    sc -- no --> sm[pass-through]
    lin --> sm
    sm --> sw[push to storedValues<br/>length capped by smoothWindow]
    sw --> sf[smoothMethod:<br/>mean / median / kalman / &hellip;]
    sf --> sm2[update smooth totalMin/Max]
    sm2 --> wo[round + write outputAbs<br/>+ emit measurement event]

The same pipeline runs per Channel instance — once in analog mode, N times in digital mode.


Need more?

Page What you'll find
Reference — Contracts Full topic contract, config schema, child-registration handshake
Reference — Architecture Code map, lifecycle, analog vs digital branching, per-Channel pipeline
Reference — Examples Shipped example flows + debug recipes
Reference — Limitations When not to use, known limitations, open questions

EVOLV master wiki · Topology Patterns · Topic Conventions