Files
diffuser/wiki/Reference-Contracts.md
znetsixe 8c03fe774c 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>
2026-05-19 09:42:13 +02:00

11 KiB
Raw Blame History

Reference — Contracts

code-ref

Note

Pending full node review (2026-05). Topic contract, output shape and configuration schema for diffuser. Sources of truth: src/commands/index.js, src/specificClass.js getOutput(), and generalFunctions/src/configs/diffuser.json.

For an intuitive overview, return to the Home.


Topic contract

The registry lives in src/commands/index.js. Each descriptor maps a canonical msg.topic to its handler in src/commands/handlers.js; aliases emit a one-time deprecation warning the first time they fire.

Canonical topic Aliases Payload Unit Effect
data.flow air_flow number volumeFlowRate (default m3/h) Push the measured air flow into the diffuser model. Clamps to ≥ 0 and recomputes OTR.
set.density density number bottom-coverage % Update the diffuser bottom-coverage % used as the OTR curve family key.
set.water-height height_water number m Update the water column height above the diffusers. Clamps to ≥ 0 and recomputes static head + total pressure.
set.header-pressure header_pressure number mbar (gauge) Update the header (supply) pressure above atmospheric. Feeds air-density correction.
set.elements elements number integer > 0 Update the count of active diffuser elements. Drives per-element flow and total membrane area.
set.alfa-factor alfaFactor number dimensionless (typically 01) Update the alpha correction used in the kg O₂/h calculation.

There are no query topics today (no query.curves / query.cog analogue). The full state is on Port 0 every time a setter fires.

There are no mode / source allow-lists — the diffuser has no operational modes (auto / virtualControl / fysicalControl). Every input topic is accepted from every source.


Data model — getOutput() shape

Composed in Diffuser.getOutput() then delta-compressed by outputUtils.formatMsg('process'). Consumers see only the keys that changed since the last emit.

Scalar keys

Key Type Unit Source Notes
iPressure number mbar (gauge) this.i_pressure Echo of last set.header-pressure.
iMWater number m this.i_m_water Echo of last set.water-height.
iFlow number Nm³/h (config-defaulted) this.i_flow Echo of last data.flow.
nFlow number Nm³/h derived Normalised air flow at standard conditions (T=20 °C, p=1.01325 bar, RH=0). Rounded 2 dp.
oOtr number g O₂ / Nm³ curve interpolation Oxygen transfer rate at current density + flux. Rounded 2 dp.
oPLoss number mbar o_p_water + o_p_flow Total head loss: static head from water column + diffuser ΔP. Rounded 2 dp.
oKgo2H number kg O₂ / h derived Mass-rate of oxygen transfer. Uses α-factor and water height.
oFlowElement number Nm³/h per element nFlow / elements Per-element air flow. Rounded 2 dp.
oFluxPerM2 number Nm³ / (h · m² membrane) nFlow / totalMembraneArea Canonical curve x-axis. Rounded 2 dp.
efficiency number % (0100) _combineEff Combined OTR / ΔP score: high OTR + low ΔP → high score. Rounded 2 dp.
slope number g O₂/Nm³ per Nm³/(h·m²) curve segment Local OTR-vs-flux slope at the operating point. Rounded 3 dp.
oZoneOtr number kg O₂ / m³ / day getReactorOtr(zoneVolume) Reactor-zone OTR. Zero when diffuser.zoneVolume is unset or non-positive.
idle boolean this.i_flow ≤ 0 Derived predicate, not an FSM state.
warning array of strings this.warning.text Flow-per-element band excursions at ± 2 % hysteresis.
alarm array of strings this.alarm.text Flow-per-element band excursions at ± 10 % hysteresis.

Per-measurement keys

Note

The diffuser does not emit typed MeasurementContainer keys. There is no <type>.<variant>.<position>.<childId> shape on this node. Parents that want OTR / ΔP via the standard ChildRouter handshake have to wait for the future phase that promotes oOtr / oZoneOtr to typed series — tracked in Limitations.

Status badge

getStatusBadge() in specificClass.js:

Condition Symbol Fill Text
alarm.state (error compose) red first entry of alarm.text
warning.state yellow first entry of warning.text
idle (no alarm/warn) (idle compose) grey <oKgo2H> kg o2 / h
active (no alarm/warn) 🟢 green (default) <oKgo2H> kg o2 / h

Configuration schema — editor form to config keys

Source of truth: generalFunctions/src/configs/diffuser.json + src/nodeClass.js buildDomainConfig.

General (config.general)

Form field Config key Default Notes
Name general.name "Diffuser" Human-readable label.
(auto-assigned) general.id null Node-RED node id.
Default unit general.unit "Nm3/h" Default airflow unit.
Enable logging general.logging.enabled true Master switch.
Log level general.logging.logLevel info debug / info / warn / error.

Asset (config.asset)

Form field Config key Default Notes
Asset model asset.model "gva-elastox-r" Curve registry id. Resolved via loadCurve(model). Falls back to the default on miss.
Asset tag number asset.assetTagNumber "" External asset registry tag (Bedrijfsmiddelenregister).

Functionality (config.functionality)

Form field Config key Default Notes
Software type functionality.softwareType "diffuser" Constant. Used in the parent-register handshake.
Role functionality.role "Aeration diffuser" Free-text role label.
Position vs parent functionality.positionVsParent "atEquipment" One of upstream / atEquipment / downstream. Carried on the child.register Port-2 message to the reactor.

Diffuser (config.diffuser)

Form field Config key Default Range Notes
Zone number diffuser.number 1 int ≥ 1 Sequential zone number; used in the node label.
Element count diffuser.elements 1 int ≥ 1 Number of active diffuser elements.
Membrane area / element diffuser.membraneAreaPerElement null m² > 0 Overrides curve _meta.membraneArea_m2_per_element. Final fallback is 0.18 m² (Jäger TD-65 / GVA).
Diffuser density (bottom coverage) diffuser.density 15 % > 0, typical 1025 Curve-family key. Multi-coverage curves are interpolated; single-coverage curves are clamped. Replaces the legacy "elements per m²" semantics — an earlier refactor mislabelled this column.
Water height diffuser.waterHeight 0 m ≥ 0 Static head + kg O₂/h factor.
Alpha factor diffuser.alfaFactor 0.7 typically 01 Oxygen-transfer correction.
Header pressure diffuser.headerPressure 0 mbar ≥ 0 (gauge) Above atmospheric. Feeds air-density correction.
Local atmospheric pressure diffuser.localAtmPressure 1013.25 mbar > 0 Density baseline (hidden by default).
Water density diffuser.waterDensity 997 kg/m³ > 0 Static head calculation (hidden by default).
Zone volume diffuser.zoneVolume 0 m³ ≥ 0 Aeration zone volume. When > 0, populates oZoneOtr (kg O₂ / m³ / day).

Unit policy

The diffuser uses a non-canonical, supplier-curve-friendly unit policy — airflow lives in Nm³/h (not m³/s) on the wire, and pressure stays in mbar (not Pa) at every boundary. Internal arithmetic converts mbar ↔ Pa where needed (_heightToPressureMbar, _calcAirDensityMbar).

Quantity Boundary unit Internal Notes
Air flow Nm3/h Nm3/h Normalised internally; curves are in this unit.
Header pressure mbar (gauge) Pa (intermediate) Converted in _calcAirDensityMbar.
Atmospheric pressure mbar Pa (intermediate) Same.
Water height m m Converted to mbar head via _heightToPressureMbar.
Membrane area m² / element m² / element Curve metadata or config override.
Temperature hardcoded 20 °C K (internal in _calcAirDensityMbar) No temperature input topic today.

This deliberately diverges from rotatingMachine's canonical-Pa/m³·s⁻¹/W/K policy because the supplier curves and operator-facing dashboards are all in Nm³/h + mbar. The cost is reduced reuse with MeasurementContainer (see Limitations).


Child registration

The diffuser is a leaf node — it accepts no children. Itself, it registers with the upstream parent (typically a reactor) at startup via the Port-2 handshake.

flowchart LR
    diff[diffuser]:::equip -->|child.register<br/>payload = node.id<br/>positionVsParent = atEquipment<br/>distance| 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 (Port 2) sends child.register with positionVsParent default atEquipment and the configured distance
inbound none accepted

Events emitted

Emitter Event When
source.emitter 'output-changed' At the end of every _recalculate() — i.e. on every input setter. BaseNodeAdapter listens and pushes delta-compressed Port 0 + Port 1 messages.
source.measurements.emitter (none) The diffuser does not currently publish typed MeasurementContainer series. Future phase.

Page Why
Home Intuitive overview
Reference — Architecture Code map, OTR / ΔP pipeline, output ports
Reference — Examples Shipped flows + debug recipes
Reference — Limitations Known issues and open questions
EVOLV — Topic Conventions Platform-wide topic rules
EVOLV — Telemetry Port 0 / 1 / 2 InfluxDB layout