Clone
3
Home
vps1_gitea_admin edited this page 2026-05-11 18:30:16 +00:00
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 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

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

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.

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.

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.

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

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

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 01 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