Files
diffuser/wiki/Reference-Contracts.md

179 lines
10 KiB
Markdown
Raw Normal View History

# Reference — Contracts
![code-ref](https://img.shields.io/badge/code--ref-4973a8b-blue)
> [!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](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.
<!-- 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 -->
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** &mdash; the diffuser has no operational modes (auto / virtualControl / fysicalControl). Every input topic is accepted from every source.
---
## Data model &mdash; `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
<!-- BEGIN AUTOGEN: data-model — populate via wiki-gen tool (TODO) -->
| 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 | % (0&ndash;100) | `_combineEff` | Combined OTR / ΔP score: high OTR + low ΔP &rarr; 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 | &mdash; | `this.i_flow ≤ 0` | Derived predicate, not an FSM state. |
| `warning` | array of strings | &mdash; | `this.warning.text` | Flow-per-element band excursions at &pm; 2 % hysteresis. |
| `alarm` | array of strings | &mdash; | `this.alarm.text` | Flow-per-element band excursions at &pm; 10 % hysteresis. |
<!-- END AUTOGEN: data-model -->
### 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 &mdash; tracked in [Limitations](Reference-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 &mdash; 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 &ge; 1 | Sequential zone number; used in the node label. |
| Element count | `diffuser.elements` | `1` | int &ge; 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 10&ndash;25 | Curve-family key. Multi-coverage curves are interpolated; single-coverage curves are clamped. Replaces the legacy "elements per m²" semantics &mdash; an earlier refactor mislabelled this column. |
| Water height | `diffuser.waterHeight` | `0` | m &ge; 0 | Static head + kg O₂/h factor. |
| Alpha factor | `diffuser.alfaFactor` | `0.7` | typically 0&ndash;1 | Oxygen-transfer correction. |
| Header pressure | `diffuser.headerPressure` | `0` | mbar &ge; 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³ &ge; 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 &mdash; 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 &harr; 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](Reference-Limitations)).
---
## Child registration
The diffuser is a **leaf node** &mdash; it accepts no children. Itself, it registers with the upstream parent (typically a `reactor`) at startup via the Port-2 handshake.
```mermaid
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 | &mdash; | none accepted |
---
## Events emitted
| Emitter | Event | When |
|:---|:---|:---|
| `source.emitter` | `'output-changed'` | At the end of every `_recalculate()` &mdash; 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. |
---
## Related pages
| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, OTR / ΔP pipeline, output ports |
| [Reference &mdash; Examples](Reference-Examples) | Shipped flows + debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | Known issues and open questions |
| [EVOLV &mdash; Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
| [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |