Auto-generated topic-contract + data-model sections via shared wikiGen script. Hand-written Mermaid diagrams for position-in-platform, code map, child registration, lifecycle, configuration, state chart (where applicable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
monster
Reflects code as of
2a6a0bc· regenerated2026-05-11vianpm run wiki:allIf this banner is stale, the page may be out of date. Treat as informative, not authoritative.
1. What this node is
monster is an S88 Unit that runs an AQUON-scheduled flow-proportional sampling program. It aggregates measured + manual flow, blends a rain-scaled prediction, and emits a pulse whenever the integrated volume crosses m³ per pulse. Drives the physical "monsternamekast" (composite sampling cabinet) on a wastewater treatment plant.
2. Position in the platform
flowchart LR
parent[plant parent<br/>Process Cell]:::pc
monster[monster<br/>Unit]:::unit
flow_up[measurement<br/>type=flow<br/>position=upstream]:::ctrl
flow_at[measurement<br/>type=flow<br/>position=atequipment]:::ctrl
op[(operator / AQUON)]
weather[(Open-Meteo)]
flow_up -->|flow.measured.upstream| monster
flow_at -->|flow.measured.atequipment| monster
op -->|set.schedule / data.flow / cmd.start| monster
weather -->|set.rain| monster
monster -->|child.register| parent
monster -.evt.output.-> parent
classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000
S88 colours: Process Cell #0c99d9, Unit #50a8d9, Control Module #a9daee. Source of truth: .claude/rules/node-red-flow-layout.md.
3. Capability matrix
| Capability | Status | Notes |
|---|---|---|
| Flow-proportional sampling pulse | ✅ | Triggered when integrated temp_pulse ≥ 1. |
| Time-bounded sampling run | ✅ | Run length governed by constraints.samplingtime (hours). |
| AQUON schedule auto-start | ✅ | set.schedule parses AQUON rows; nextDate arms the next run. |
| Rain-scaled flow prediction | ✅ | set.rain aggregates Open-Meteo hourly precipitation into sumRain / avgRain. |
| Measured + manual flow blend | ✅ | flowTracker.getEffectiveFlow() picks measured if recent, else manual. |
| Minimum sample-interval cooldown | ✅ | Skips pulses inside minSampleIntervalSec window. |
| Bucket / weight tracking | ✅ | bucketVol, bucketWeight track the composite container. |
| Sub-sample volume override | ❌ | subSampleVolume fixed at 50 mL per schema. |
| Stateful FSM | ❌ | Monster is run/idle only — no formal state machine. |
4. Code map
flowchart TB
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
nc["buildDomainConfig()<br/>static DomainClass = Monster<br/>static commands"]
end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Monster.configure()<br/>wires concerns, ChildRouter rules<br/>tick() → flowCalc → samplingProgram"]
end
subgraph concerns["src/ concern modules"]
parameters["parameters/<br/>bounds + targets + rain index"]
flow["flow/<br/>FlowTracker (measured + manual)"]
rain["rain/<br/>RainAggregator (sum + avg)"]
schedule["schedule/<br/>AQUON next-date parser"]
sampling["sampling/<br/>samplingProgram + flowCalc"]
io["io/<br/>output + statusBadge"]
commands["commands/<br/>topic registry + handlers"]
end
nc --> sc
sc --> parameters
sc --> flow
sc --> rain
sc --> schedule
sc --> sampling
sc --> io
nc --> commands
| Module | Owns | Read first if you're changing… |
|---|---|---|
parameters/ |
Bounds (minPuls, absMaxPuls, targetPuls), rain-index lookup, predicted-flow rate. |
Sampling bounds, rain scaling math. |
flow/ |
FlowTracker — measured-child latch + manual flow + effective-flow pick. |
Flow source priority, dead-band logic. |
rain/ |
RainAggregator — fold hourly Open-Meteo rows into sumRain / avgRain. |
Rain inputs, forecast horizon. |
schedule/ |
AQUON schedule parsing, nextDate + daysPerYear derivation. |
Schedule arming, sample-name filtering. |
sampling/ |
_beginRun / _endRun / _maybeEmitPulse — pulse integrator + cooldown guard. |
Pulse timing, cooldown behaviour. |
io/ |
buildOutput, buildStatusBadge. |
Port-0 payload shape, status badge text. |
commands/ |
Topic registry + payload validation + unit conversion. | New input topics, alias deprecation. |
5. Topic contract
Auto-generated from
src/commands/index.js. Do NOT hand-edit between the markers. Re-runnpm run wiki:contract.
| Canonical topic | Aliases | Payload | Effect |
|---|---|---|---|
cmd.start |
i_start |
any |
Triggers an action / sequence — not idempotent. |
set.schedule |
monsternametijden |
any |
Replaces the named state value with the supplied payload. |
set.rain |
rain_data |
any |
Replaces the named state value with the supplied payload. |
data.flow |
input_q |
object |
Pushes a value into the node's measurement stream. |
set.mode |
setMode |
any |
Replaces the named state value with the supplied payload. |
set.model-prediction |
model_prediction |
any |
Replaces the named state value with the supplied payload. |
6. Child registration
Mirrors the ChildRouter declaration in specificClass.js → configure(). Monster only accepts measurement children whose asset.type is flow (or unset).
flowchart LR
subgraph kids["accepted children (softwareType)"]
m_up["measurement<br/>asset.type=flow<br/>position=upstream"]:::ctrl
m_at["measurement<br/>asset.type=flow<br/>position=atequipment"]:::ctrl
m_dn["measurement<br/>asset.type=flow<br/>position=downstream"]:::ctrl
end
m_up -->|flow.measured.upstream| ft[FlowTracker.handleMeasuredFlow]
m_at -->|flow.measured.atequipment| ft
m_dn -->|flow.measured.downstream| ft
ft --> eff[getEffectiveFlow]
classDef ctrl fill:#a9daee,color:#000
| softwareType | filter | wired to | side-effect |
|---|---|---|---|
measurement |
asset.type=flow (or missing) |
flowTracker.handleMeasuredFlow |
Latches latest measured flow for getEffectiveFlow. |
measurement |
asset.type anything else |
ignored | Logged once at register. |
7. Lifecycle — the sampling-program loop
sequenceDiagram
participant child as measurement child
participant ops as operator / AQUON
participant monster as monster
participant ft as flowTracker
participant sp as samplingProgram
participant out as Port-0 output
child->>monster: flow.measured.<position>
ops->>monster: set.schedule / cmd.start / data.flow
Note over monster: every 1000 ms tick
monster->>ft: getEffectiveFlow()
monster->>monster: flowCalc → m3PerTick
monster->>sp: samplingProgram
alt cmd.start OR Date.now() ≥ nextDate
sp->>sp: validateFlowBounds → _beginRun
end
alt running AND stop_time > now
sp->>sp: integrate temp_pulse += m3PerTick / m3PerPuls
sp->>sp: _maybeEmitPulse (cooldown-guarded)
else stop_time elapsed
sp->>sp: _endRun
end
monster->>out: msg{topic, payload (delta-compressed)}
One pulse per integrated m³ per pulse. The cooldown guard suppresses pulses inside minSampleIntervalSec and increments missedSamples.
8. Data model — getOutput()
What lands on Port 0. Built in buildOutput() from m.measurements.getFlattenedOutput() plus the sampling-run snapshot.
| Key | Type | Unit | Sample |
|---|---|---|---|
avgRain |
number | — | 0 |
bucketVol |
number | — | 0 |
bucketWeight |
number | — | 0 |
daysPerYear |
number | — | 0 |
flowMax |
undefined | — | null |
flowToNextPulseM3 |
number | — | 0 |
invalidFlowBounds |
boolean | — | false |
m3PerPuls |
number | — | 0 |
m3PerPulse |
number | — | 0 |
m3Total |
number | — | 0 |
maxVolume |
number | — | 20 |
minSampleIntervalSec |
number | — | 60 |
minVolume |
number | — | 5 |
missedSamples |
number | — | 0 |
nextDate |
undefined | — | null |
nominalFlowMin |
undefined | — | null |
predFlow |
number | — | 0 |
predM3PerSec |
number | — | 0 |
predictedRateM3h |
number | — | 0 |
pulse |
boolean | — | false |
pulseFraction |
number | — | 0 |
pulsesRemaining |
number | — | 200 |
q |
number | — | 0 |
running |
boolean | — | false |
sampleCooldownMs |
number | — | 0 |
sumPuls |
number | — | 0 |
sumRain |
number | — | 0 |
targetDeltaL |
number | — | -10 |
targetDeltaM3 |
number | — | -0.01 |
targetProgressPct |
number | — | 0 |
targetVolumeM3 |
number | — | 0.01 |
timeLeft |
number | — | 0 |
timePassed |
number | — | 0 |
timeToNextPulseSec |
number | — | 0 |
Concrete sample (mid-run snapshot):
{
"pulse": false,
"running": true,
"bucketVol": 1.25,
"sumPuls": 25,
"predFlow": 240.0,
"m3PerPuls": 4,
"q": 215.4,
"timeLeft": 12340,
"targetVolumeM3": 0.005,
"targetProgressPct": 41.6,
"sumRain": 3.2,
"avgRain": 0.13,
"nextDate": 1746940800000
}
Concrete samples must come from a known-good test run. Regenerate when concern modules change shape.
9. Configuration — editor form ↔ config keys
flowchart TB
subgraph editor["Node-RED editor form"]
f1[Sampling time hr]
f2[Sampling period hr]
f3[Min volume L]
f4[Max weight kg]
f5[Empty bucket weight kg]
f6[Flowmeter on/off]
f7[Min sample interval s]
end
subgraph config["Domain config slice"]
c1[constraints.samplingtime]
c2[constraints.samplingperiod]
c3[constraints.minVolume]
c4[constraints.maxWeight]
c5[asset.emptyWeightBucket]
c6[constraints.flowmeter]
c7[constraints.minSampleIntervalSec]
end
f1 --> c1
f2 --> c2
f3 --> c3
f4 --> c4
f5 --> c5
f6 --> c6
f7 --> c7
| Form field | Config key | Default | Range | Where used |
|---|---|---|---|---|
| Sampling time (hr) | constraints.samplingtime |
0 |
≥ 0 | run length, predFlow |
| Sampling period (hr) | constraints.samplingperiod |
24 |
≥ 1 | schedule arming |
| Min volume (L) | constraints.minVolume |
5 |
≥ 5 | bounds + invalid-flow guard |
| Max weight (kg) | constraints.maxWeight |
23 |
≤ 23 | bucket overload |
| Empty bucket weight (kg) | asset.emptyWeightBucket |
3 |
≥ 0 | bucketWeight |
| Sub-sample volume (mL) | constraints.subSampleVolume |
50 |
fixed | per-pulse volume |
| Flowmeter present | constraints.flowmeter |
true |
bool | proportional vs time mode |
| Min sample interval (s) | constraints.minSampleIntervalSec |
60 |
≥ 0 | cooldown guard |
| Intake speed (m/s) | constraints.intakeSpeed |
0.3 |
≥ 0 | informational |
| Intake diameter (mm) | constraints.intakeDiameter |
12 |
≥ 0 | informational |
10. State chart
Skipped — monster has no formal state machine. The running boolean toggles when _beginRun / _endRun fire. See section 7 for the sampling-program sequence, which is the closest analogue.
11. Examples
| Tier | File | What it shows | Status |
|---|---|---|---|
| Basic | examples/basic.flow.json |
Inject + manual flow + dashboard, no parent | ✅ in repo |
| Integration | examples/integration.flow.json |
monster + measurement child + AQUON schedule | ✅ in repo |
| Dashboard | examples/monster-dashboard.flow.json |
Live FlowFuse charts (pulse, bucket, predFlow) | ✅ in repo |
| Edge | examples/edge.flow.json |
Cooldown guard + invalid-flow bounds | ✅ in repo |
| API | examples/monster-api-dashboard.flow.json |
dashboardAPI consumer wired in | ✅ in repo |
One screenshot per tier where helpful. PNG ≤ 200 KB under wiki/_partial-screenshots/monster/.
12. Debug recipes
| Symptom | First thing to check | Where to look |
|---|---|---|
pulse never fires |
Is running true? Check validateFlowBounds log — invalid bounds short-circuits the run. |
parameters/parameters.js |
| Pulses arrive too fast / skipped | Cooldown guard active. Inspect missedSamples + minSampleIntervalSec. |
sampling/samplingProgram.js → _maybeEmitPulse |
q always zero |
Measured-flow child not registered, manual flow not pushed. Watch Port 2 + data.flow history. |
flow/flowTracker.js |
nextDate not arming |
set.schedule payload didn't include matching aquonSampleName row. |
schedule/schedule.js → regNextDate |
sumRain zero with rain input |
rainAggregator.update ran while running=true (skipped). |
specificClass.js → updateRainData |
Bucket overfilled before stop_time |
m3PerPuls rounded down; check predFlow vs effective q. |
sampling/samplingProgram.js → _beginRun |
Never ship
enableLog: 'debug'in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
13. When you would NOT use this node
- Use monster for AQUON-scheduled composite sampling on a wastewater plant. For a single grab sample on demand, fire
cmd.startonce and tear it down — monster is overkill for ad-hoc work. - Don't use monster as a generic flow totaliser. Use a
measurementchild with the right type/variant for raw flow integration. - Skip monster if you don't need pulse-proportional dosing — the time-mode (
flowmeter=false) is currently informational only.
14. Known limitations / current issues
| # | Issue | Tracked in |
|---|---|---|
| 1 | Edge test sampling-guards.edge.test.js cooldown-guard case is a pre-existing failure — the cooldown skip increments missedSamples but the assertion expects a different timing. |
test/edge/sampling-guards.edge.test.js |
| 2 | set.mode and set.model-prediction are reserved — handlers delegate to optional methods that don't exist yet. |
commands/handlers.js |
| 3 | Time-only mode (flowmeter=false) is not exercised — the sampling program assumes a flow source. |
sampling/samplingProgram.js |
| 4 | Sub-sample volume hard-coded at 50 mL (schema enforces min=max=50). |
generalFunctions/src/configs/monster.json |