# Reference &mdash; Architecture

![code-ref](https://img.shields.io/badge/code--ref-b825ac1-blue)

> [!NOTE]
> Code structure for `pumpingStation`: the three-tier sandwich, the `src/` layout, the FSM, the lifecycle, and the output-port pipeline. Everything here is reproducible from `src/`. For an intuitive overview, return to [Home](Home).

---

## Three-tier code layout

```
nodes/pumpingStation/
|
+-- pumpingStation.js              entry: RED.nodes.registerType('pumpingstation', NodeClass)
|
+-- src/
|     nodeClass.js                 extends BaseNodeAdapter (Node-RED bridge)
|     specificClass.js             extends BaseDomain (orchestration only)
|     |
|     +-- commands/
|     |     index.js               topic descriptors
|     |     handlers.js            pure handler functions
|     |
|     +-- basin/
|     |     BasinGeometry.js       basin shape, level <-> volume conversion
|     |     thresholdValidator.js  derives + validates safety / control thresholds
|     |
|     +-- measurement/
|     |     flowAggregator.js      net-flow + predicted-volume integrator
|     |     measurementRouter.js   routes measurement-child events
|     |     calibration.js         calibrate-to-known-level / volume helpers
|     |
|     +-- control/
|     |     index.js               mode dispatcher (levelbased, manual, ...)
|     |
|     +-- safety/
|           safetyController.js    dry-run + high-volume + panic guards
```

### Tier responsibilities

| Tier | File | What it owns | Touches `RED.*` |
|:---|:---|:---|:---:|
| entry | `pumpingStation.js` | Type registration | Yes |
| nodeClass | `src/nodeClass.js` | Input routing, tick loop, output ports, status badge | Yes |
| specificClass | `src/specificClass.js` | Wire concern modules in `configure()`; run them in `tick()`; nothing more | No |

The specificClass is stitching, not implementation. All real work lives in `basin/`, `measurement/`, `control/`, `safety/`.

---

## State chart &mdash; safety controller

The pumpingStation does not have a per-mode FSM (control modes are stateless transfer functions). The state machine that matters is the **safety controller**, which can block or pass control commands.

```mermaid
stateDiagram-v2
    [*] --> running
    running --> blocked_dryrun: level < dryRunLevel
    running --> blocked_highvolume: level >= highVolumeSafetyLevel
    running --> blocked_panic: no-data panic timer expires
    blocked_dryrun --> running: level recovers above hysteresis
    blocked_highvolume --> running: level falls below hysteresis
    blocked_panic --> running: data resumes
```

Each `blocked_*` state sets `safety.blocked = true` on Port 0 and prevents the control layer from emitting a non-zero demand. The hysteresis is mode-independent and lives in `src/safety/safetyController.js`.

### Safety-rules asymmetry

The `dryRunLevel` and `highVolumeSafetyLevel` rules differ in **which children they stop**:

![Dry-run vs high-volume safety asymmetry](diagrams/safety-rules.drawio.svg)

| Rule | What stops | Why |
|:---|:---|:---|
| Dry run | All children (pumps off) | Pumps cavitate without water; protect the equipment |
| High volume | Only outflow-side pumps | Spill is the lesser evil; some pumps may still serve safety functions |

---

## Lifecycle &mdash; one tick

```mermaid
sequenceDiagram
    autonumber
    participant tick as 1s tick
    participant sc as specificClass.tick()
    participant fa as flowAggregator
    participant safe as safetyController
    participant ctrl as control[mode]
    participant out as Port 0 / 1

    tick->>sc: tick()
    sc->>fa: update predicted volume
    fa->>fa: pick best net-flow source (measured / aggregated)
    sc->>safe: evaluate
    alt safety blocked
        safe-->>sc: { blocked: true }
        Note over sc: skip control layer
    else safe to run
        sc->>ctrl: strategies[mode].run(context)
        ctrl-->>sc: demand 0..100
    end
    sc->>out: getOutput() &mdash; emit Port 0 + Port 1 deltas
```

Each tick is 1 Hz. The output pipeline (Port 0 + Port 1) is driven by `outputUtils.formatMsg` &mdash; only changed fields are sent.

---

## Output ports

| Port | Carries | Sample shape |
|:---|:---|:---|
| 0 (process) | Delta-compressed state snapshot consumed by downstream Node-RED logic | `{topic, payload: {level, volume, demand, direction, safety, etaSeconds}}` |
| 1 (telemetry) | InfluxDB line-protocol string with the same fields as Port 0 | `pumpingStation,id=PS1 level=1.62,volume=32.4 ...` |
| 2 (register / control) | `child.register` upward at init; internal control plumbing later | `{topic: 'child.register', payload: {ref, softwareType, config}}` |

See [EVOLV &mdash; Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) for the full InfluxDB layout.

---

## Tick timing and event sources

| Source | Where it fires | What it triggers |
|:---|:---|:---|
| `setInterval(1000)` | `BaseNodeAdapter` lifecycle | `specificClass.tick()` &mdash; the per-second integrator update |
| `measurement` emitter event | Child node's `emitter.emit(<type>.measured.<position>, ...)` | `measurementRouter` updates the basin balance |
| Inbound `msg.topic` | Node-RED input wire | `commandRegistry` dispatch to a handler |
| `child.register` from another node | Port 2 of a child | `_subscribeMeasurement` or `_subscribePredictedFlow` |

---

## Where to start reading

| If you're changing... | Read first |
|:---|:---|
| Basin geometry, level/volume conversion | `src/basin/BasinGeometry.js`, `src/basin/thresholdValidator.js` |
| Net-flow selection, predicted-volume integration | `src/measurement/flowAggregator.js` |
| Calibration commands | `src/measurement/calibration.js` |
| Control modes (level-based, manual, future modes) | `src/control/index.js` |
| Safety blocks | `src/safety/safetyController.js` |
| Topic dispatch | `src/commands/index.js` + `src/commands/handlers.js` |
| Adapter, ticking, output ports | `src/nodeClass.js` (and `BaseNodeAdapter` in `generalFunctions`) |

---

## Related pages

| Page | Why |
|:---|:---|
| [Home](Home) | Intuitive overview |
| [Reference &mdash; Contracts](Reference-Contracts) | Topic + config + child filters |
| [Reference &mdash; Examples](Reference-Examples) | Shipped example flows |
| [Reference &mdash; Limitations](Reference-Limitations) | Known limitations and open questions |
| [EVOLV &mdash; Architecture](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Architecture) | Platform-wide three-tier pattern |
