A `diffuser` models a single aeration-diffuser zone — the gas-side dynamics of a fine-bubble grid sitting under a reactor's water column. Given a header pressure, water height, alpha factor, element count and incoming air flow, it normalises the air to Nm³/h, interpolates a supplier OTR curve (oxygen transfer rate vs specific flux) plus a ΔP curve, and emits oxygen-transfer power (kg O₂/h), efficiency, per-element flow, and a reactor-zone OTR. Used as a leaf Equipment Module under a `reactor` (or any aeration train).
> Pending full node review (2026-05). Content reflects `CONTRACT.md`, `src/commands/index.js`, `src/specificClass.js` and `generalFunctions/src/configs/diffuser.json` only. Test scaffolding under `test/basic/`, `test/integration/`, `test/edge/` is still placeholder-level — a domain-test pass remains TODO.
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. Per the layout rule, diffuser lives on lane **L3** of the Process Plant tab, wrapped in a `#86bbdd` Node-RED group.
> Example flows currently shipped: `examples/basic.flow.json`, `examples/integration.flow.json`, `examples/edge.flow.json`. The `examples/README.md` is a one-line placeholder ("Placeholder structure"); a proper per-tier README following the rotatingMachine template is TODO.
What to send after deploy (each inject maps one-to-one to a topic in [Reference — Contracts](Reference-Contracts#topic-contract)):
1.`set.water-height = 5` — sets a 5 m column above the diffuser. Static head pressure is recomputed; `oPLoss` updates.
2.`set.elements = 100` — declares 100 elements in this zone. Per-element flow + specific flux now scale by this denominator.
3.`set.density = 15` — bottom-coverage percentage; selects which curve family is interpolated.
5.`set.alfa-factor = 0.7` — the α correction used in the kg O₂/h calculation.
6.`data.flow = 200` — push 200 Nm³/h into the model. Watch Port 0: `oOtr`, `oKgo2H`, `oFluxPerM2`, `efficiency`, `slope` all populate. Setting `data.flow = 0` flips `idle` back to true and resets the derived outputs.
> [!IMPORTANT]
> **GIF needed.** Demo recording of steps 1–6 with the live status badge. Save as `wiki/_partial-gifs/diffuser/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
---
## The six things you'll send
| Topic | Aliases | Payload | What it does |
|:---|:---|:---|:---|
| `data.flow` | `air_flow` | `number` — Nm³/h | Pushes the measured air flow into the model. Clamped to ≥ 0. Triggers a full recompute. |
| `set.density` | `density` | `number` — bottom-coverage % | Selects the curve family used for OTR interpolation. Curves multi-keyed by coverage get linearly interpolated; single-key curves are clamped. |
| `set.water-height` | `height_water` | `number` — m | Sets the water column above the diffuser. Recomputes static head + total ΔP. Clamped to ≥ 0. |
| `set.elements` | `elements` | `number` — integer > 0 | Active element count. Drives per-element flow + total membrane area used for specific flux. |
| `set.alfa-factor` | `alfaFactor` | `number` (typically 0–1) | Alpha correction used in the kg O₂/h calculation. |
Aliases log a one-time deprecation warning the first time they fire. There are no query topics today (the entire state is on Port 0).
---
## What you'll see come out
Sample Port 0 message (delta-compressed, while operational with flow = 200 Nm³/h, 100 elements, water height 5 m):
> The numbers above are illustrative — the exact values depend on the loaded supplier curve (`gva-elastox-r` by default). Run the basic example flow to see real values for your asset.
Where rotatingMachine emits per-measurement keys (`<type>.<variant>.<position>.<childId>`), diffuser uses a flat camelCase schema (`oOtr`, `oKgo2H`, `oFlowElement`, …). The diffuser does **not** currently publish typed `MeasurementContainer` series — parents must consume Port 0 directly. Promoting `oOtr` / `oZoneOtr` to typed measurement series is tracked in [Reference — Limitations](Reference-Limitations#known-limitations).