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>
This commit is contained in:
380
wiki/Home.md
380
wiki/Home.md
@@ -1,13 +1,28 @@
|
||||
# monster
|
||||
|
||||
> **Reflects code as of `8540328` · regenerated `2026-05-11` via `npm run wiki:all`**
|
||||
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
|
||||
  
|
||||
|
||||
## 1. What this node is
|
||||
A `monster` models the physical "monsternamekast" (composite-sampling cabinet) on a wastewater treatment plant. It runs an AQUON-scheduled, flow-proportional sampling program: aggregates measured + manual flow, blends in a rain-scaled prediction, integrates volume, and emits a `pulse` event whenever the integrated volume crosses `m³ per pulse`. Used downstream of `reactor` / `settler` for compliance + process diagnostics — but note that the node only **integrates flow** in the current implementation; multi-constituent reporting (NH4, NO3, COD, TSS, …) is a downstream consumer concern, not produced by this node.
|
||||
|
||||
**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.
|
||||
> [!NOTE]
|
||||
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
|
||||
|
||||
## 2. Position in the platform
|
||||
---
|
||||
|
||||
## At a glance
|
||||
|
||||
| Thing | Value |
|
||||
|:---|:---|
|
||||
| What it represents | One composite-sampling cabinet running AQUON-scheduled, flow-proportional sampling |
|
||||
| S88 level | Unit |
|
||||
| Use it when | You need scheduled composite samples with flow-proportional pulses for a benchtop analyser / lab pickup |
|
||||
| Don't use it for | Generic flow totalising (use `measurement`), ad-hoc grab samples (fire `cmd.start` once instead), or analyser results — monster emits pulses, not analyte values |
|
||||
| Children it accepts | `measurement` with `asset.type='flow'` (or unset) |
|
||||
| Parents it talks to | Any node that issues `cmd.start` / `set.schedule` / `set.rain` / `data.flow` (typically a Process Cell coordinator) |
|
||||
|
||||
---
|
||||
|
||||
## How it fits
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -15,304 +30,143 @@ flowchart LR
|
||||
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
|
||||
flow_dn[measurement<br/>type=flow<br/>position=downstream]:::ctrl
|
||||
op[(operator / AQUON)]
|
||||
weather[(Open-Meteo)]
|
||||
|
||||
flow_up -->|flow.measured.upstream| monster
|
||||
flow_at -->|flow.measured.atequipment| monster
|
||||
flow_dn -->|flow.measured.downstream| monster
|
||||
op -->|set.schedule / data.flow / cmd.start| monster
|
||||
weather -->|set.rain| monster
|
||||
monster -->|child.register| parent
|
||||
monster -.evt.output.-> parent
|
||||
monster -.evt output (pulse / bucketVol).-> 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`.
|
||||
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. The editor node tile in `monster.html` is currently `#4f8582` (teal) and pending cleanup — Section 16 of the layout rules tracks it.
|
||||
|
||||
## 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. |
|
||||
## Try it — 3-minute demo
|
||||
|
||||
## 4. Code map
|
||||
Import the basic example flow, deploy, and drive the sampler through a single run.
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```bash
|
||||
curl -X POST -H 'Content-Type: application/json' \
|
||||
--data @nodes/monster/examples/basic.flow.json \
|
||||
http://localhost:1880/flow
|
||||
```
|
||||
|
||||
| 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. |
|
||||
What to click after deploy (the inject buttons map to the topics in [Reference — Contracts](Reference-Contracts#topic-contract)):
|
||||
|
||||
## 5. Topic contract
|
||||
1. `set.rain` — push an Open-Meteo precipitation snapshot so `sumRain` / `avgRain` populate (otherwise the rain-scaled prediction stays at `nominalFlowMin`).
|
||||
2. `set.schedule` — push an AQUON row array so `nextDate` arms.
|
||||
3. `data.flow = {value: 240, unit: 'm3/h'}` — supply a manual flow value (or wire a `measurement` child for the measured path).
|
||||
4. `cmd.start = true` — releases the start gate. On the next 1000 ms tick the sampling program checks `validateFlowBounds`; if `nominalFlowMin < flowMax`, `_beginRun` fires, `running` flips to `true`, and the bucket integrator starts.
|
||||
5. Watch Port 0: `pulse` blips to `true` for one tick whenever `temp_pulse` crosses 1; `bucketVol` rises in 50 mL steps (the hard-coded `subSampleVolume`); `sumPuls` increments.
|
||||
6. After `constraints.samplingtime` hours `_endRun` fires and `running` flips to `false`.
|
||||
|
||||
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
|
||||
> [!IMPORTANT]
|
||||
> **GIF needed.** Demo recording of steps 1–6 with the live status panel. Save as `wiki/_partial-gifs/monster/basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
|
||||
|
||||
<!-- BEGIN AUTOGEN: topic-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. |
|
||||
## The seven things you'll send
|
||||
|
||||
<!-- END AUTOGEN: topic-contract -->
|
||||
| Topic | Aliases | Payload | What it does |
|
||||
|:---|:---|:---|:---|
|
||||
| `cmd.start` | `i_start` | truthy / falsy | Sets `source.i_start`. On the next tick a sampling run begins if `validateFlowBounds` passes. |
|
||||
| `set.schedule` | `monsternametijden` | array of AQUON rows (`SAMPLE_NAME`, `DESCRIPTION`, `SAMPLED_DATE`, `START_DATE`, `END_DATE`) | Stores the schedule and recomputes `nextDate` + `daysPerYear` for the configured `aquonSampleName`. |
|
||||
| `set.rain` | `rain_data` | per-location rain forecast (Open-Meteo shape) | Aggregates hourly precipitation into `sumRain` / `avgRain`; feeds the rain-scaled flow prediction. Ignored while `running=true`. |
|
||||
| `data.flow` | `input_q` | `{ value: number, unit: string }` | Converts to m³/h and pushes into `flow.manual.atequipment`. Blends with measured-child flow in `getEffectiveFlow()`. |
|
||||
| `set.mode` | `setMode` | string | Reserved — handler delegates to `source.setMode()` which is currently undefined. No-op today. |
|
||||
| `set.model-prediction` | `model_prediction` | numeric | Reserved — handler delegates to `source.setModelPrediction()` which is currently undefined. No-op today. |
|
||||
| `child.register` | `registerChild` | string (child node id) | Register a `measurement` child. Port 2 wiring does this automatically. |
|
||||
|
||||
## 6. Child registration
|
||||
There is no `query.*` topic on monster (unlike `rotatingMachine`'s `query.curves` / `query.cog`). All state is on Port 0.
|
||||
|
||||
Mirrors the `ChildRouter` declaration in `specificClass.js → configure()`. Monster only accepts `measurement` children whose `asset.type` is `flow` (or unset).
|
||||
---
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
## What you'll see come out
|
||||
|
||||
| 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
|
||||
|
||||
```mermaid
|
||||
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.
|
||||
|
||||
<!-- BEGIN AUTOGEN: data-model -->
|
||||
|
||||
| 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` |
|
||||
|
||||
<!-- END AUTOGEN: data-model -->
|
||||
|
||||
**Concrete sample** (mid-run snapshot):
|
||||
Sample Port 0 message (mid-run snapshot, delta-compressed):
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
"topic": "monster#cabinet_1",
|
||||
"payload": {
|
||||
"running": true,
|
||||
"pulse": false,
|
||||
"bucketVol": 1.25,
|
||||
"bucketWeight": 4.25,
|
||||
"sumPuls": 25,
|
||||
"m3PerPuls": 4,
|
||||
"m3Total": 100.0,
|
||||
"q": 215.4,
|
||||
"predFlow": 240.0,
|
||||
"predM3PerSec": 0.067,
|
||||
"timeLeft": 12340,
|
||||
"timePassed": 600,
|
||||
"targetVolumeM3": 0.005,
|
||||
"targetProgressPct": 41.6,
|
||||
"targetDeltaL": -2.93,
|
||||
"predictedRateM3h": 240.0,
|
||||
"sumRain": 3.2,
|
||||
"avgRain": 0.13,
|
||||
"nextDate": 1746940800000,
|
||||
"daysPerYear": 12,
|
||||
"missedSamples": 0,
|
||||
"sampleCooldownMs": 0,
|
||||
"invalidFlowBounds": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Concrete samples must come from a known-good test run. Regenerate when concern modules change shape.
|
||||
| Field | Meaning |
|
||||
|:---|:---|
|
||||
| `running` | Run/idle flag — monster has no formal FSM, just this boolean and the timer fields below. |
|
||||
| `pulse` | `true` for the tick on which a pulse is emitted; falls back to `false` on the next tick. |
|
||||
| `bucketVol` / `bucketWeight` | Accumulated composite volume (L) and total bucket mass (kg = vol + `emptyWeightBucket`). |
|
||||
| `sumPuls` / `pulsesRemaining` | Pulses fired this run vs target ceiling. |
|
||||
| `m3PerPuls` | Volume per pulse, set at `_beginRun` from `predFlow / targetPuls`. |
|
||||
| `m3Total` | Total integrated flow this run (m³). |
|
||||
| `q` | Effective flow (m³/h) — `flowTracker.getEffectiveFlow()` blends measured + manual. |
|
||||
| `predFlow` / `predM3PerSec` | Predicted total volume over the run window (m³) and its average rate. |
|
||||
| `timeLeft` / `timePassed` | Run-window seconds remaining / elapsed. |
|
||||
| `targetVolumeM3` / `targetProgressPct` / `targetDeltaL` | Target composite volume (m³), % of target accumulated, signed L delta against target. |
|
||||
| `predictedRateM3h` | Rain-scaled flow prediction (m³/h) — between `nominalFlowMin` and `flowMax`. |
|
||||
| `sumRain` / `avgRain` | Probability-weighted hourly precipitation, summed / averaged across locations. |
|
||||
| `nextDate` / `daysPerYear` | Next scheduled run epoch ms; count of remaining runs this calendar year. |
|
||||
| `missedSamples` / `sampleCooldownMs` | Cooldown-blocked pulse count; ms remaining on the active cooldown. |
|
||||
| `invalidFlowBounds` | True when `nominalFlowMin >= flowMax` — gates `_beginRun`. |
|
||||
|
||||
## 9. Configuration — editor form ↔ config keys
|
||||
Full Port-0 key list: see [Reference — Contracts — Data model](Reference-Contracts#data-model--getoutput-shape).
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
Key shape is **flat scalars on the snapshot** plus a `MeasurementContainer.getFlattenedOutput()` flush of `flow.<variant>.<position>` entries. monster does **not** use the four-segment `<type>.<variant>.<position>.<childId>` shape that `rotatingMachine` emits — no `childId` is appended because monster fans incoming children into the same three positions and the latest value wins.
|
||||
|
||||
| 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
|
||||
## Status badge
|
||||
|
||||
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.
|
||||
| State | Badge | Fill |
|
||||
|:---|:---|:---|
|
||||
| `invalidFlowBounds=true` | `Config error: nominalFlowMin (…) >= flowMax (…)` | red |
|
||||
| `running=true` + cooldown active | `SAMPLING (Ns) · <bucketVol>/<maxVolume> L` | yellow ring |
|
||||
| `running=true` + normal | `AI: RUNNING · <bucketVol>/<maxVolume> L` | green dot |
|
||||
| idle | `AI: IDLE` | grey ring |
|
||||
|
||||
## 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 |
|
||||
## Need more?
|
||||
|
||||
One screenshot per tier where helpful. PNG ≤ 200 KB under `wiki/_partial-screenshots/monster/`.
|
||||
| Page | What you'll find |
|
||||
|:---|:---|
|
||||
| [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters |
|
||||
| [Reference — Architecture](Reference-Architecture) | Code map, sampling-program loop, prediction + cooldown pipeline |
|
||||
| [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes |
|
||||
| [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions |
|
||||
|
||||
## 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.start` once and tear it down — monster is overkill for ad-hoc work.
|
||||
- Don't use monster as a generic flow totaliser. Use a `measurement` child 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 |
|
||||
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)
|
||||
|
||||
Reference in New Issue
Block a user