Files
diffuser/wiki/Home.md
znetsixe 6372bdc926 P11.6 diffuser wiki: banner hash + section 9 form completeness + section 14 category/colour notes
- Bump banner hash from 15cfb228cc02ee (latest HEAD).
- Section 9: add missing `number` form field; mark localAtmPressure /
  waterDensity as hidden defaults with no form row; update mermaid diagram.
- Section 14: add issue #5 (category 'wbd typical' vs 'EVOLV') and issue
  #6 (S88 colour already set — §16 note now stale).
- npm run wiki:all run prior to edit; AUTOGEN markers untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:04:57 +02:00

232 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# diffuser
> **Reflects code as of `8cc02ee` · 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<br/>Equipment]:::equip
setters[dashboard setters]:::ctrl -->|set.density / set.water-height /<br/>set.elements / set.alfa-factor /<br/>set.header-pressure| diff
diff -->|child.register| reactor[reactor<br/>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()<br/>static DomainClass, commands"]
end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Diffuser.configure()<br/>loads supplier specs<br/>setters → _recalculate()"]
end
subgraph concerns["src/ concern modules"]
cmds["commands/<br/>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`.
<!-- BEGIN AUTOGEN: topic-contract -->
| Canonical topic | Aliases | Payload | Unit | Effect |
|---|---|---|---|---|
| `data.flow` | `air_flow` | `number` | `volumeFlowRate` (default `m3/h`) | Push the measured air flow into the diffuser model. |
| `set.density` | `density` | `number` | — | Update the air density used in OTR / SOTR calculations. |
| `set.water-height` | `height_water` | `number` | — | Update the water column height above the diffusers (m). |
| `set.header-pressure` | `header_pressure` | `number` | — | Update the header (supply) pressure feeding the diffusers (mbar). |
| `set.elements` | `elements` | `number` | — | Update the count of active diffuser elements. |
| `set.alfa-factor` | `alfaFactor` | `number` | — | Update the alfa factor used in oxygen-transfer correction. |
<!-- END AUTOGEN: topic-contract -->
## 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<br/>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`.
<!-- BEGIN AUTOGEN: data-model -->
| 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 | — | `[…]` |
<!-- END AUTOGEN: data-model -->
`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"]
f0[Zone number]
f1[Element count]
f2[Diffuser density]
f3[Water height]
f4[Header pressure]
f5[Alpha factor]
f6[Zone volume]
end
subgraph config["Domain config slice"]
c0[diffuser.number]
c1[diffuser.elements]
c2[diffuser.density]
c3[diffuser.waterHeight]
c4[diffuser.headerPressure]
c5[diffuser.alfaFactor]
c6[diffuser.zoneVolume]
c7["diffuser.localAtmPressure (hidden)"]
c8["diffuser.waterDensity (hidden)"]
end
f0 --> c0
f1 --> c1
f2 --> c2
f3 --> c3
f4 --> c4
f5 --> c5
f6 --> c6
```
| Form field | Config key | Default | Range | Where used |
|---|---|---|---|---|
| Zone number | `diffuser.number` | `1` | int ≥ 1 | node label (`name_N`) |
| 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` | 01 | oxygen-transfer correction |
| _(hidden default)_ | `diffuser.localAtmPressure` | `1013.25` | > 0 (mbar) | density baseline |
| _(hidden default)_ | `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 |
| 5 | **Node category mismatch:** `diffuser.html` registers under `category: 'wbd typical'` instead of `'EVOLV'`. All other platform nodes target `'EVOLV'` for consistent palette grouping in the editor. | `diffuser.html` line 3 |
| 6 | **S88 colour — resolved:** `diffuser.html` already declares `color: '#86bbdd'` (Equipment Module). The WIKI_TEMPLATE §16 note listing diffuser as having "no colour set" is stale and can be removed in the next template refresh. | `diffuser.html` line 4 |