From 9122b14368573770538809a01657d23a3abb310a Mon Sep 17 00:00:00 2001 From: znetsixe Date: Mon, 11 May 2026 15:17:41 +0200 Subject: [PATCH] P9.3: wiki/Home.md following 14-section visual-first template + wiki:* scripts 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) --- package.json | 5 +- wiki/Home.md | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 wiki/Home.md diff --git a/package.json b/package.json index 49cd7b2..71a524c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,10 @@ "description": "Control module diffuser", "main": "diffuser", "scripts": { - "test": "node --test test/" + "test": "node --test test/", + "wiki:contract": "node ../generalFunctions/scripts/wikiGen.js contract ./src/commands/index.js --write ./wiki/Home.md", + "wiki:datamodel": "node ../generalFunctions/scripts/wikiGen.js datamodel ./src/specificClass.js --write ./wiki/Home.md", + "wiki:all": "npm run wiki:contract && npm run wiki:datamodel" }, "repository": { "type": "git", diff --git a/wiki/Home.md b/wiki/Home.md new file mode 100644 index 0000000..ed22efb --- /dev/null +++ b/wiki/Home.md @@ -0,0 +1,223 @@ +# diffuser + +> **Reflects code as of `15cfb22` · 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 + +**diffuser** models an aeration-diffuser zone. Given header pressure, water-column height, alpha factor, element count and airflow, it interpolates a supplier OTR curve, normalises airflow to Nm³/h, and emits oxygen-transfer rate plus a reactor-zone OTR for the downstream parent. Used as a leaf Equipment Module under a `reactor`. + +## 2. Position in the platform + +```mermaid +flowchart LR + src[blower / MGC / dashboard]:::unit -->|data.flow| diff[diffuser
Equipment]:::equip + setters[dashboard setters]:::ctrl -->|set.density / set.water-height /
set.elements / set.alfa-factor /
set.header-pressure| diff + diff -->|child.register| reactor[reactor
Unit]:::unit + classDef unit fill:#50a8d9,color:#000 + classDef equip fill:#86bbdd,color:#000 + classDef ctrl fill:#a9daee,color:#000 +``` + +S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`. + +## 3. Capability matrix + +| Capability | Status | Notes | +|---|---|---| +| Supplier OTR curve interpolation | ✅ | Density-keyed; falls back to single key when only one available. | +| Air-density correction (header + atm) | ✅ | `_calcAirDensityMbar` per ideal-gas mix. | +| Per-element flow tracking | ✅ | `o_flowElement = nFlow / nElements`. | +| Static head loss from water column | ✅ | `_heightToPressureMbar`. | +| Warning / alarm bands on flow-per-element | ✅ | Hysteresis 2 % (warn) / 10 % (alarm). | +| Reactor-zone OTR for parent | ✅ | `oZoneOtr` derived from `diffuser.zoneVolume`. | +| Idle handling at flow ≤ 0 | ✅ | Resets all derived outputs to zero. | +| Typed `MeasurementContainer` emission | ❌ | All output flows via `getOutput()`; no typed series yet. | + +## 4. Code map + +```mermaid +flowchart TB + subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"] + nc["buildDomainConfig()
static DomainClass, commands"] + end + subgraph domain["specificClass.js — orchestrator (BaseDomain)"] + sc["Diffuser.configure()
loads supplier specs
setters → _recalculate()"] + end + subgraph concerns["src/ concern modules"] + cmds["commands/
topic registry + handlers"] + end + nc --> sc + nc --> cmds +``` + +| Module | Owns | Read first if you're changing… | +|---|---|---| +| `commands/` | Input-topic registry + per-topic handlers | Topic naming, payload validation. | +| `specificClass.js` (single file) | Setters, OTR/ΔP curve interpolation, alarms, output composition | Anything domain-side. | + +The diffuser was a small node so the P6.4 refactor did not split it into per-concern directories. Future work may extract `curves/` and `alarms/` if the file grows past ~250 lines. + +## 5. Topic contract + +> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`. + + + +| Canonical topic | Aliases | Payload | Effect | +|---|---|---|---| +| `data.flow` | `air_flow` | `number` | Pushes a value into the node's measurement stream. | +| `set.density` | `density` | `number` | Replaces the named state value with the supplied payload. | +| `set.water-height` | `height_water` | `number` | Replaces the named state value with the supplied payload. | +| `set.header-pressure` | `header_pressure` | `number` | Replaces the named state value with the supplied payload. | +| `set.elements` | `elements` | `number` | Replaces the named state value with the supplied payload. | +| `set.alfa-factor` | `alfaFactor` | `number` | Replaces the named state value with the supplied payload. | + + + +## 6. Child registration + +diffuser is a leaf node — it accepts no children. It registers itself with its upstream parent (typically a reactor) at startup via the standard Port-2 handshake. + +```mermaid +flowchart LR + diff[diffuser]:::equip -->|child.register payload=node.id
positionVsParent=atEquipment| reactor[reactor / parent]:::unit + classDef equip fill:#86bbdd,color:#000 + classDef unit fill:#50a8d9,color:#000 +``` + +| Direction | Counterparty | Side-effect | +|---|---|---| +| outbound at startup | upstream reactor | sends `child.register` on Port 2 with `positionVsParent` default `atEquipment` | +| inbound | — | none accepted | + +## 7. Lifecycle — what one event does + +```mermaid +sequenceDiagram + participant src as upstream source + participant diff as diffuser + participant curve as supplier specs + participant out as Port-0 + + src->>diff: data.flow (Nm³/h) + diff->>diff: setFlow → _recalculate + alt flow ≤ 0 + diff->>diff: idle = true, reset derived outputs + else flow > 0 + diff->>diff: air-density correction (atm + header) + diff->>curve: interpolate OTR by density + flow/element + diff->>curve: interpolate ΔP curve by flow/element + diff->>diff: kg O₂/h, combined efficiency, slope + diff->>diff: _checkLimits (warn / alarm bands) + end + diff->>diff: notifyOutputChanged() + diff->>out: msg{topic, payload (delta-compressed)} +``` + +## 8. Data model — `getOutput()` + +What lands on Port 0. Composed in `Diffuser.getOutput()`, then delta-compressed by `outputUtils.formatMsg`. + + + +| Key | Type | Unit | Sample | +|---|---|---|---| +| `alarm` | array | — | `[…]` | +| `efficiency` | number | — | `0` | +| `iFlow` | number | — | `0` | +| `iMWater` | number | — | `0` | +| `iPressure` | number | — | `0` | +| `idle` | boolean | — | `true` | +| `nFlow` | number | — | `0` | +| `oFlowElement` | number | — | `0` | +| `oKgo2H` | number | — | `0` | +| `oOtr` | number | — | `0` | +| `oPLoss` | number | — | `0` | +| `oZoneOtr` | number | — | `0` | +| `slope` | number | — | `0` | +| `warning` | array | — | `[…]` | + + + +`oZoneOtr` is `kg O₂ / m³ / day`; it is `0` when `diffuser.zoneVolume` is unset. + +## 9. Configuration — editor form ↔ config keys + +```mermaid +flowchart TB + subgraph editor["Node-RED editor form"] + f1[Element count] + f2[Diffuser density] + f3[Water height] + f4[Header pressure] + f5[Alpha factor] + f6[Zone volume] + end + subgraph config["Domain config slice"] + c1[diffuser.elements] + c2[diffuser.density] + c3[diffuser.waterHeight] + c4[diffuser.headerPressure] + c5[diffuser.alfaFactor] + c6[diffuser.zoneVolume] + end + f1 --> c1 + f2 --> c2 + f3 --> c3 + f4 --> c4 + f5 --> c5 + f6 --> c6 +``` + +| Form field | Config key | Default | Range | Where used | +|---|---|---|---|---| +| Element count | `diffuser.elements` | `1` | int ≥ 1 | per-element flow | +| Diffuser density | `diffuser.density` | `2.4` | > 0 | OTR curve key | +| Water height | `diffuser.waterHeight` | `0` | ≥ 0 (m) | static head + kg O₂/h | +| Header pressure | `diffuser.headerPressure` | `0` | ≥ 0 (mbar) | air density correction | +| Alpha factor | `diffuser.alfaFactor` | `0.7` | 0–1 | oxygen-transfer correction | +| Local atm. pressure | `diffuser.localAtmPressure` | `1013.25` | > 0 (mbar) | density baseline | +| Water density | `diffuser.waterDensity` | `997` | > 0 (kg/m³) | static head | +| Zone volume | `diffuser.zoneVolume` | `0` | ≥ 0 (m³) | `oZoneOtr` | + +## 10. State chart + +Skipped — diffuser is stateless. Every input setter recomputes the full output snapshot; there are no transitions to track. The `idle` flag is a derived predicate (`i_flow ≤ 0`), not a state. + +## 11. Examples + +| Tier | File | What it shows | Mandatory? | +|---|---|---|---| +| Basic | `examples/01-Basic.flow.json` | Inject `data.flow` + dashboard, no parent | ✅ | +| Integration | `examples/02-Integration.flow.json` | diffuser registered under a reactor zone | ✅ | +| Dashboard | `examples/03-Dashboard.flow.json` | Live FlowFuse charts (OTR, kg O₂/h, efficiency) | ⭕ | + +Screenshots under `wiki/_partial-screenshots/diffuser/` when produced. Docker compose snippet under `examples/README.md`. + +## 12. Debug recipes + +| Symptom | First thing to check | Where to look | +|---|---|---| +| `oOtr` stuck at zero | `i_flow` is zero or negative → `idle = true`. | `_recalculate` early-return | +| Warning / alarm always firing | Flow-per-element outside curve `minX` / `maxX` ± hysteresis. | `_checkLimits` | +| `oZoneOtr` is zero despite valid OTR | `diffuser.zoneVolume` is unset or non-positive. | `getReactorOtr` | +| `nFlow` differs from `iFlow` at non-zero flow | Air-density correction — header pressure or atm differ from reference. | `_calcAirDensityMbar` | +| `efficiency` flat at 0 | OTR or ΔP curve span is zero in the operating band. | `_combineEff` | + +> 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 diffuser for a **fine-bubble aeration zone** with a supplier OTR curve. For coarse-bubble or jet aeration, model OTR externally. +- Don't use diffuser when the upstream blower already publishes oxygen-transfer telemetry — diffuser duplicates the calculation. +- Skip diffuser if you only need flow-per-element warning bands without OTR — a `measurement` node with bands is lighter. + +## 14. Known limitations / current issues + +| # | Issue | Tracked in | +|---|---|---| +| 1 | **Port-count change (Phase 6):** pre-refactor the diffuser exposed 4 outputs (process, dbase, reactor control with `topic: 'OTR'`, parent registration). The reactor-control message merged into Port 0 as `oZoneOtr`; consumers reading the dedicated control port must migrate to `payload.oZoneOtr`. No alias is provided — the shape differs (single value vs full process payload). | CONTRACT.md `## Port-count change` | +| 2 | Supplier specs are hard-coded inside `_loadSpecs()` (GVA / ELASTOX-R). A configurable supplier registry is pending. | `specificClass.js _loadSpecs` | +| 3 | No typed `MeasurementContainer` emission — `oOtr` / `oZoneOtr` cannot be subscribed via the generic `ChildRouter` handshake. Parents must read Port 0 messages. | CONTRACT.md `## Events emitted` | +| 4 | Warning / alarm thresholds are fixed (2 % / 10 % hysteresis); not yet config-driven. | `configure()` literals |