Files
settler/wiki/Home.md
znetsixe 98052a16e7 docs(wiki): update Home.md to match WIKI_TEMPLATE §14-section standard
- Bump banner hash to 94b6616 (current HEAD)
- Section 9: add processOutputFormat + dbaseOutputFormat + enableLog fields
  that exist in settler.html but were absent from the config table
- Section 10: replace "Skipped" with precise stateless rationale
- Section 14: add item 5 — editor colour #e4a363 vs S88 #50a8d9 discrepancy,
  referencing node-red-flow-layout.md §16 for cleanup tracking
- Re-ran npm run wiki:all; AUTOGEN markers intact

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

12 KiB
Raw Blame History

settler

Reflects code as of 94b6616 · 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

settler is an S88 Unit that models a secondary clarifier. It takes the upstream reactor's effluent stream, performs a 13-species TSS mass balance, and splits it into three Fluent envelopes: clarified effluent, surplus sludge, and return sludge. A downstream return pump (rotatingMachine child) draws the return-sludge flow.

2. Position in the platform

flowchart LR
    upstream[reactor<br/>upstream<br/>Unit]:::unit
    settler[settler<br/>Unit]:::unit
    downstream[reactor<br/>downstream<br/>Unit]:::unit
    return[rotatingMachine<br/>return pump<br/>Equipment]:::equip
    tss[measurement<br/>type=quantity (tss)<br/>position=atequipment]:::ctrl

    upstream -.stateChange.-> settler
    settler -->|Fluent inlet=0,1,2| downstream
    return -->|child.register downstream| settler
    settler -.F_sr.-> return
    tss -->|quantity (tss).measured.atequipment| settler
    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
TSS mass-balance split (3 streams) Effluent / surplus / return derived from F_in * Cs[12] / C_TS.
Particulate zeroing in effluent Species 712 set to 0 in effluent when F_s > 0.
Particulate concentration in sludge Species 712 scaled by F_in / F_s in surplus + return.
Return-pump flow draw F_sr = min(pump flow, F_s). Surplus = F_s F_sr.
F_s clamp to F_in Prevents negative effluent when X_TS_in > C_TS.
Manual influent override data.influent lets ops supply { F, C } directly.
Multiple reactor upstreams Only one upstreamReactor slot; last registration wins.
Stateful FSM Stateless transform — recomputes on every push.

4. Code map

flowchart TB
    subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
        nc["buildDomainConfig()<br/>static DomainClass = Settler<br/>static commands"]
    end
    subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
        sc["Settler.configure()<br/>ChildRouter rules<br/>getEffluent — TSS split<br/>_connectReactor (manual listener)"]
    end
    subgraph commands["src/commands/"]
        cmds["index.js + handlers.js<br/>data.influent + aliases"]
    end
    nc --> sc
    nc --> cmds
Module Owns Read first if you're changing…
specificClass.js All domain logic: getEffluent split, reactor + machine + measurement wiring, getOutput, getStatusBadge. Mass-balance math, child wiring, telemetry shape.
commands/ Single command (data.influent) + aliases + payload validation. Manual-influent topic, new aliases.

Settler is small enough (~140 LOC) that no concern-split was needed (per P6.6).

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.influent influent, setInfluent any Push the influent stream (payload: {F: flow m3/h, C: [concentrations mg/L]}).
child.register registerChild string Register a child node (typically a measurement) with this settler.

6. Child registration

flowchart LR
    subgraph kids["accepted children (softwareType)"]
        m["measurement"]:::ctrl
        r["reactor<br/>upstream"]:::unit
        mach["machine<br/>downstream"]:::equip
    end
    m -->|"&lt;type&gt;.measured.&lt;position&gt;"| h_m[_connectMeasurement]
    r -.stateChange.-> h_r[_connectReactor<br/>manual listener]
    mach -->|registered| h_mach[_connectMachine<br/>sets returnPump]
    h_r --> pull[upstreamReactor.getEffluent]
    pull --> emit[notifyOutputChanged]
    classDef ctrl fill:#a9daee,color:#000
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
softwareType filter wired to side-effect
measurement any _connectMeasurement Re-emits on settler's measurements; quantity (tss) updates C_TS.
reactor positionVsParent=upstream (warns otherwise) _connectReactor Stores as upstreamReactor; subscribes to its own emitter (NOT measurements.emitter) for 'stateChange'.
machine positionVsParent=downstream _connectMachine Stores as returnPump; sets machine.upstreamSource = settler.

6.1 Reactor ↔ settler wiring (the load-bearing bit)

The reactor pushes its stateChange event on reactor.emitter, not reactor.measurements.emitter. The standard router.onMeasurement path can't subscribe — so settler attaches the listener manually inside _connectReactor. On each fire, settler pulls the upstream effluent via reactor.getEffluent and copies it into this.F_in + this.Cs_in.

reactor.getEffluent historically returned either an array (3-stream) or a single envelope — the 2026-03-02 _connectReactor fix preserves both shapes:

const raw = this.upstreamReactor.getEffluent;
const effluent = Array.isArray(raw) ? raw[0] : raw;
this.F_in = effluent.payload.F;
this.Cs_in = effluent.payload.C;
this.notifyOutputChanged();

If you change the reactor's effluent shape, this is the line to update.

7. Lifecycle — what one stateChange does

sequenceDiagram
    participant reactor as upstream reactor
    participant settler as settler
    participant pump as return pump child
    participant downstream as downstream consumer
    participant out as Port-0 output

    reactor->>settler: emitter.emit('stateChange')
    settler->>reactor: pull getEffluent
    reactor-->>settler: { F, C[13] }
    settler->>settler: F_in = F, Cs_in = C
    settler->>pump: read measurements.flow.measured.atequipment
    pump-->>settler: returnFlow
    settler->>settler: getEffluent — split into 3 inlets
    settler->>out: [Fluent inlet=0, Fluent inlet=1, Fluent inlet=2]
    out->>downstream: 3 msgs on Port 0

The split runs lazily inside getEffluent: each call recomputes from current F_in, Cs_in, C_TS, and the pump's reported flow.measured.atequipment.

8. Data model — getOutput()

Port 0 carries the 3-envelope Fluent stream directly; Port 1 (this snapshot) is the scalar dashboard view.

Key Type Unit Sample
C_TS number 2500
F_eff number 0
F_in number 0
F_return number 0
F_surplus number 0

Concrete sample (typical operating point):

{
  "F_in": 1000,
  "C_TS": 2500,
  "F_eff": 850.0,
  "F_surplus": 50.0,
  "F_return": 100.0
}

F_eff + F_surplus + F_return = F_in always holds (modulo float). Particulates concentrate by F_in / F_s in the surplus + return streams.

9. Configuration — editor form ↔ config keys

flowchart TB
    subgraph editor["Node-RED editor form"]
        f1[Name]
        f2[Process Output Format]
        f3[Database Output Format]
        f4[Logging level]
        f5[Position vs parent]
    end
    subgraph config["Domain config / nodeClass"]
        c1[general.name]
        c2[processOutputFormat → nodeClass]
        c3[dbaseOutputFormat → nodeClass]
        c4[general.logging.logLevel]
        c5[functionality.positionVsParent]
    end
    f1 --> c1
    f2 --> c2
    f3 --> c3
    f4 --> c4
    f5 --> c5
Form field Config key Default Range Where used
Name general.name Settler string display + Port-1 topic
Process Output Format processOutputFormat (nodeClass) process process / json / csv Port-0 serialisation
Database Output Format dbaseOutputFormat (nodeClass) influxdb influxdb / json / csv Port-1 serialisation
Logging level general.logging.logLevel info debug / info / warn / error logger threshold
Position vs parent functionality.positionVsParent downstream upstream / atEquipment / downstream parent-side routing
Software type functionality.softwareType settler string parent-side router filter
ID general.id null nullable string child registration key

Settler has no operational process config of its own — all behaviour is driven by runtime state (F_in, Cs_in, C_TS). Tune behaviour by feeding it different reactor effluents or C_TS measurements.

10. State chart

Not applicable — settler is stateless. There is no FSM. Every trigger (stateChange from the reactor, data.influent, or a quantity (tss) update) causes a fresh recompute of the 3 Fluent streams from the current runtime state and the split immediately re-emits.

11. Examples

Tier File What it shows Status
Basic examples/basic.flow.json Inject data.influent, watch 3-stream split in repo
Integration examples/integration.flow.json reactor (upstream) + settler + return pump in repo
Edge examples/edge.flow.json F_s clamp + zero-influent fallback in repo

One screenshot per tier where helpful. PNG ≤ 200 KB under wiki/_partial-screenshots/settler/.

12. Debug recipes

Symptom First thing to check Where to look
F_eff negative or NaN C_TS zero or Cs_in[12] huge. F_s clamp should prevent — confirm clamp present. specificClass.js → getEffluent
Settler never updates after reactor changes Reactor child not on 'upstream' position, or listener attached to wrong emitter. _connectReactor — listens on reactor.emitter, NOT measurements.emitter.
Return-sludge flow = 0 returnPump.measurements.type('flow').variant('measured').position('atEquipment') empty. Wire a flow measurement on the pump. _connectMachine, pump measurement chain.
3 Fluent envelopes not arriving downstream payload.inlet selector on the downstream reactor mismatches (0=eff, 1=surplus, 2=return). downstream reactor's data.fluent handler.
quantity (tss) updates don't change C_TS Measurement child's asset.type not quantity (tss) exactly. _updateMeasurement switch.

13. When you would NOT use this node

  • Use settler for secondary clarification downstream of a biological reactor. For primary sedimentation (raw sewage), the species-7-12 zeroing is wrong — model that as a separate process.
  • Don't use settler as a generic mass-balance node — the 13-species ASM3 vector is hard-coded.
  • Skip settler when the downstream reactor doesn't need a 3-stream split (e.g. single-tank SBR). A direct reactor → reactor wire is lighter.

14. Known limitations / current issues

# Issue Tracked in
1 Only one upstreamReactor slot — multi-reactor settlers not supported (last registration wins). _connectReactor
2 TSS mass balance uses index 12 (X_TS) hard-coded — coupled tightly to ASM3 species ordering. getEffluent, _updateMeasurement
3 Settler depends on mathjs (~14 MB install) but only uses it transitively via reactor; no direct mathjs call in settler code. package.json
4 No flow-balance check at runtime — if particulate concentration drives F_s above F_in, the clamp masks an upstream bug rather than warning. getEffluent
5 Editor colour is #e4a363 (orange) in settler.html but S88 Unit level requires #50a8d9 (blue). Diagrams in this wiki use the correct #50a8d9. Colour cleanup tracked in .claude/rules/node-red-flow-layout.md §16. settler.html