- Banner hash updated to8540328(was stale2a6a0bc). - Section 9: flag constraints.flowmeter not wired in buildDomainConfig (effectively always true at runtime); add nominalFlowMin / flowMax / maxRainRef rows that were missing from the config table. - Section 12: add two new debug recipes — measurement child ignored (assetType must be "flow", not "flow-electromagnetic") and flowmeter=false has no runtime effect. - Section 14 #3: expand flowmeter limitation to call out buildDomainConfig gap. - Section 14 #5: add S88 colour cleanup note (#4f8582 teal → #50a8d9 Unit). - AUTOGEN markers untouched; npm run wiki:all re-ran cleanly before commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
15 KiB
monster
Reflects code as of
8540328· 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 | Unit | Effect |
|---|---|---|---|---|
cmd.start |
i_start |
any |
— | Trigger / release the sampler start gate. |
set.schedule |
monsternametijden |
any |
— | Replace the sampling-times schedule. |
set.rain |
rain_data |
any |
— | Push current rain-event data into the sampler logic. |
data.flow |
input_q |
object |
— | Push the upstream flow measurement (payload: {value, unit}). |
set.mode |
setMode |
any |
— | Switch the monster between auto / manual modes. |
set.model-prediction |
model_prediction |
any |
— | Push the upstream rain-prediction snapshot used by the sampler. |
child.register |
registerChild |
string |
— | Register a child node (typically a measurement) with this monster. |
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 |
number | — | 0 |
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 |
number | — | 0 |
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 | ⚠️ In schema but NOT wired in buildDomainConfig — effectively always true at runtime. |
| Min sample interval (s) | constraints.minSampleIntervalSec |
60 |
≥ 0 | cooldown guard |
| Nominal flow min (m³/h) | constraints.nominalFlowMin |
0 |
≥ 0 | lower bound for rain-driven flow prediction band |
| Flow max (m³/h) | constraints.flowMax |
0 |
≥ 0 | upper bound for rain-driven flow prediction band |
| Max rain reference | constraints.maxRainRef |
10 |
> 0 | rain-index that maps to flowMax |
| 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 |
| Measurement child ignored at register | Child's config.asset.type is set to something other than "flow" (e.g. "flow-electromagnetic"). Monster only accepts asset.type = "flow" or unset. Set asset.type: "flow" on the measurement node. |
specificClass.js → _wireMeasurementChild |
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 |
flowmeter=false has no effect |
constraints.flowmeter is in the schema but not wired in buildDomainConfig. The sampling program always runs in proportional mode. |
src/nodeClass.js → buildDomainConfig |
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. constraints.flowmeter is also not forwarded in buildDomainConfig. |
sampling/samplingProgram.js, src/nodeClass.js |
| 4 | Sub-sample volume hard-coded at 50 mL (schema enforces min=max=50). |
generalFunctions/src/configs/monster.json |
| 5 | S88 colour cleanup pending: node editor colour is #4f8582 (teal) in monster.html. Correct Unit-level colour is #50a8d9. Section 2 diagram already uses the correct colour. The editor node tile must be updated separately. |
monster.html → color: field |