Compare commits
1 Commits
530f84ae5b
...
b825ac1d6d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b825ac1d6d |
186
wiki/Home.md
186
wiki/Home.md
@@ -1,45 +1,50 @@
|
|||||||
# pumpingStation
|
# pumpingStation
|
||||||
|
|
||||||
> **Reflects code as of `d2384b1` · regenerated `<YYYY-MM-DD>` via `npm run wiki:all`**
|
> **Reflects code as of `530f84a` · 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.
|
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
|
||||||
|
|
||||||
## 1. What this node is
|
## 1. What this node is
|
||||||
|
|
||||||
**pumpingStation** is an S88 Process Cell that owns a wet-well basin and orchestrates the pumps that drain it. It tracks measured + predicted volume, evaluates safety interlocks (dry-run, overfill), and dispatches a control strategy that hands a demand setpoint to one or more downstream machine groups or individual pumps.
|
**pumpingStation** is an S88 Process Cell that owns a wet-well basin and orchestrates the pumps that drain it. It tracks measured and predicted volume, evaluates safety interlocks (dry-run, overfill), and dispatches a control strategy that hands a demand setpoint to one or more downstream machine groups or individual pumps. Stateful (control mode) and tick-driven (1 s integrator). See [`wiki/functional-description.md`](functional-description) for the full behaviour spec.
|
||||||
|
|
||||||
## 2. Position in the platform
|
## 2. Position in the platform
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
ps[pumpingStation<br/>Process Cell]:::pc
|
|
||||||
meas_lvl[measurement<br/>type=level<br/>position=atequipment]:::ctrl
|
meas_lvl[measurement<br/>type=level<br/>position=atequipment]:::ctrl
|
||||||
meas_in[measurement<br/>type=flow<br/>position=upstream]:::ctrl
|
meas_in[measurement<br/>type=flow<br/>position=upstream]:::ctrl
|
||||||
|
ps[pumpingStation<br/>Process Cell]:::pc
|
||||||
mgc[machineGroupControl<br/>Unit]:::unit
|
mgc[machineGroupControl<br/>Unit]:::unit
|
||||||
|
pump[rotatingMachine<br/>Equipment]:::equip
|
||||||
|
|
||||||
meas_lvl -.data.-> ps
|
meas_lvl -->|level.measured.atequipment| ps
|
||||||
meas_in -.data.-> ps
|
meas_in -->|flow.measured.upstream| ps
|
||||||
ps -->|set.demand| mgc
|
pump -->|child.register| mgc
|
||||||
mgc -.evt.flow-predicted.-> ps
|
|
||||||
mgc -->|child.register| ps
|
mgc -->|child.register| ps
|
||||||
|
mgc -->|flow.predicted.downstream| ps
|
||||||
|
ps -->|set.demand| mgc
|
||||||
classDef pc fill:#0c99d9,color:#fff
|
classDef pc fill:#0c99d9,color:#fff
|
||||||
classDef unit fill:#50a8d9,color:#000
|
classDef unit fill:#50a8d9,color:#000
|
||||||
|
classDef equip fill:#86bbdd,color:#000
|
||||||
classDef ctrl fill:#a9daee,color:#000
|
classDef ctrl fill:#a9daee,color:#000
|
||||||
```
|
```
|
||||||
|
|
||||||
S88 colours: Process Cell `#0c99d9`, Unit `#50a8d9`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
|
S88 colours: Process Cell `#0c99d9`, Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md §10.1`.
|
||||||
|
|
||||||
## 3. Capability matrix
|
## 3. Capability matrix
|
||||||
|
|
||||||
| Capability | Status | Notes |
|
| Capability | Status | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Predicts basin volume from net flow | ✅ | Integrator seeded from `basin.minVol`; recomputes level. |
|
| Predicts basin volume from net flow | ✅ | Integrator seeded from `basin.minVol`; recomputes level each tick. |
|
||||||
| Accepts measured level / volume / pressure | ✅ | Routed via `measurementRouter` on child registration. |
|
| Accepts measured level / volume / pressure / flow | ✅ | Routed via `measurementRouter` on child registration. |
|
||||||
| Level-based control strategy | ✅ | Linear or log ramp between `minLevel` and `maxLevel`. |
|
| Level-based control strategy | ✅ | Linear or log ramp between `startLevel` and `maxLevel`. |
|
||||||
| Flow-based control strategy | ✅ | PID against `flowSetpoint`. |
|
| Flow-based control strategy | ✅ | PID against `flowSetpoint`. |
|
||||||
| Manual demand passthrough | ✅ | `set.demand` only honoured in `manual` mode. |
|
| Manual demand passthrough | ✅ | `set.demand` only honoured in `manual` mode. |
|
||||||
| Dry-run safety interlock | ✅ | Stops downstream pumps when volume < `minVol` while draining. |
|
| Dry-run safety interlock | ✅ | Shuts downstream pumps when volume < `minVol` while draining. Blocks control. |
|
||||||
| Overfill safety interlock | ✅ | Stops upstream equipment when volume crosses overfill threshold. |
|
| Overfill safety interlock | ✅ | Shuts upstream equipment when volume > threshold while filling. Control keeps running. |
|
||||||
| Cascaded children (sub-stations) | ⚠️ | Accepted via `pumpingstation` softwareType but not exercised in production. |
|
| No-data panic | ✅ | Shuts ALL machines and blocks control when no volume reading is available. |
|
||||||
|
| Cascaded sub-stations | ⚠️ | Accepted via `pumpingstation` softwareType but not exercised in production. |
|
||||||
|
| pressureBased / powerBased / hybrid modes | ❌ | Enumerated in schema but not dispatched — only `levelbased`, `flowbased`, `manual`. |
|
||||||
|
|
||||||
## 4. Code map
|
## 4. Code map
|
||||||
|
|
||||||
@@ -49,14 +54,14 @@ flowchart TB
|
|||||||
nc["buildDomainConfig()<br/>static DomainClass, commands<br/>static tickInterval = 1000 ms"]
|
nc["buildDomainConfig()<br/>static DomainClass, commands<br/>static tickInterval = 1000 ms"]
|
||||||
end
|
end
|
||||||
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
|
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
|
||||||
sc["PumpingStation.configure()<br/>declares ChildRouter rules<br/>tick() → safety → control"]
|
sc["PumpingStation.configure()<br/>declares ChildRouter rules<br/>tick() → flowAggregator → safety → control"]
|
||||||
end
|
end
|
||||||
subgraph concerns["src/ concern modules"]
|
subgraph concerns["src/ concern modules"]
|
||||||
basin["basin/<br/>BasinGeometry + thresholdValidator"]
|
basin["basin/<br/>BasinGeometry · thresholdValidator"]
|
||||||
measurement["measurement/<br/>flowAggregator + router + calibration"]
|
measurement["measurement/<br/>flowAggregator · measurementRouter · calibration"]
|
||||||
control["control/<br/>levelbased / flowbased / manual"]
|
control["control/<br/>levelBased · flowBased · manual · dispatch"]
|
||||||
safety["safety/<br/>SafetyController"]
|
safety["safety/<br/>SafetyController"]
|
||||||
commands["commands/<br/>topic registry + handlers"]
|
commands["commands/<br/>topic registry · handlers"]
|
||||||
end
|
end
|
||||||
nc --> sc
|
nc --> sc
|
||||||
sc --> basin
|
sc --> basin
|
||||||
@@ -70,7 +75,7 @@ flowchart TB
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `basin/` | Geometry, volume↔level conversion, threshold ordering | Capacity, level↔volume math, fill %. |
|
| `basin/` | Geometry, volume↔level conversion, threshold ordering | Capacity, level↔volume math, fill %. |
|
||||||
| `measurement/` | Net-flow aggregation, predicted-volume integrator, calibration | Predicted volume / time-to-full. |
|
| `measurement/` | Net-flow aggregation, predicted-volume integrator, calibration | Predicted volume / time-to-full. |
|
||||||
| `control/` | Control strategy dispatch (`levelbased`, `flowbased`, `manual`) | Demand calculation, mode behaviour. |
|
| `control/` | Strategy dispatch (`levelbased`, `flowbased`, `manual`) | Demand calculation, mode behaviour. |
|
||||||
| `safety/` | Dry-run + overfill rules, pump-shutdown side-effects | Safety envelope, alarm reactions. |
|
| `safety/` | Dry-run + overfill rules, pump-shutdown side-effects | Safety envelope, alarm reactions. |
|
||||||
| `commands/` | Input-topic registry and handlers | New input topics, payload validation. |
|
| `commands/` | Input-topic registry and handlers | New input topics, payload validation. |
|
||||||
|
|
||||||
@@ -101,14 +106,14 @@ flowchart LR
|
|||||||
subgraph kids["accepted children (softwareType)"]
|
subgraph kids["accepted children (softwareType)"]
|
||||||
m["measurement"]:::ctrl
|
m["measurement"]:::ctrl
|
||||||
mach["machine<br/>(rotatingMachine)"]:::equip
|
mach["machine<br/>(rotatingMachine)"]:::equip
|
||||||
mgc["machinegroup"]:::unit
|
mgc["machinegroup<br/>(machineGroupControl)"]:::unit
|
||||||
sub["pumpingstation<br/>(sub-station)"]:::pc
|
sub["pumpingstation<br/>(sub-station)"]:::pc
|
||||||
end
|
end
|
||||||
m -->|"<type>.measured.<position>"| route1[_subscribeMeasurement<br/>routes to measurementRouter]
|
m -->|"<type>.measured.<position>"| route1[_subscribeMeasurement<br/>→ measurementRouter]
|
||||||
mach -->|flow.predicted.<in or out>| route2[_subscribePredictedFlow<br/>+ flowAggregator]
|
mach -->|flow.predicted.out| route2[_subscribePredictedFlow<br/>+ flowAggregator]
|
||||||
mgc -->|flow.predicted.<in or out>| route2
|
mgc -->|flow.predicted.out| route2
|
||||||
sub -->|flow.predicted.<in or out>| route2
|
sub -->|flow.predicted.out| route2
|
||||||
route1 --> tick[tick]
|
route1 --> tick[tick / integrator]
|
||||||
route2 --> tick
|
route2 --> tick
|
||||||
classDef ctrl fill:#a9daee,color:#000
|
classDef ctrl fill:#a9daee,color:#000
|
||||||
classDef equip fill:#86bbdd,color:#000
|
classDef equip fill:#86bbdd,color:#000
|
||||||
@@ -118,10 +123,10 @@ flowchart LR
|
|||||||
|
|
||||||
| softwareType | onRegister side-effect | Subscribed events |
|
| softwareType | onRegister side-effect | Subscribed events |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `measurement` | `_subscribeMeasurement(child)` — registers in MeasurementContainer. | `<type>.measured.<position>` for any type (pressure, level, flow, …). |
|
| `measurement` | `_subscribeMeasurement(child)` — writes to MeasurementContainer by type + position. | `<type>.measured.<position>` for any type (level, flow, pressure, …). |
|
||||||
| `machine` | Stored in `this.machines[id]`. **Skipped when a machineGroup parent is present** to avoid double-counting. | `flow.predicted.<in|out>` per the child's `positionVsParent`. |
|
| `machine` | Added to `this.machines`. **Skipped when a `machinegroup` is present** — avoids double-counting predicted flow. | `flow.predicted.<in\|out>` per `positionVsParent`. |
|
||||||
| `machinegroup` | Stored in `this.machineGroups[id]`. | `flow.predicted.<in|out>`. |
|
| `machinegroup` | Added to `this.machineGroups`. | `flow.predicted.<in\|out>`. |
|
||||||
| `pumpingstation` | Stored in `this.stations[id]`. | `flow.predicted.<in|out>`. |
|
| `pumpingstation` | Added to `this.stations`. | `flow.predicted.<in\|out>`. |
|
||||||
|
|
||||||
## 7. Lifecycle — what one tick does
|
## 7. Lifecycle — what one tick does
|
||||||
|
|
||||||
@@ -134,14 +139,22 @@ sequenceDiagram
|
|||||||
participant ctl as control strategy
|
participant ctl as control strategy
|
||||||
participant out as Port-0 output
|
participant out as Port-0 output
|
||||||
|
|
||||||
child->>ps: data event (measured.level / flow.predicted.out)
|
child->>ps: data event (level.measured.atequipment / flow.predicted.out)
|
||||||
ps->>ps: ChildRouter dispatches to handler
|
ps->>ps: ChildRouter dispatches to _subscribeMeasurement / _subscribePredictedFlow
|
||||||
Note over ps: every 1000 ms (static tickInterval)
|
Note over ps: every 1000 ms (static tickInterval = 1000)
|
||||||
ps->>fa: tick() — net flow, ETA, predicted volume
|
ps->>fa: tick() — net flow · ETA · predicted volume integrator
|
||||||
ps->>sf: evaluate({direction, secondsRemaining})
|
ps->>sf: evaluate({direction, secondsRemaining})
|
||||||
alt safety blocked
|
alt no-volume-data panic
|
||||||
sf-->>ps: blocked=true, reason
|
sf-->>ps: blocked=true, reason='no-volume-data'
|
||||||
Note over ctl: skipped this tick
|
sf-->>ps: ALL machines shut down
|
||||||
|
else dry-run (vol < minVol AND draining)
|
||||||
|
sf-->>ps: blocked=true, reason='dry-run'
|
||||||
|
sf-->>ps: downstream machines + machineGroups shut down
|
||||||
|
else overfill (vol > threshold AND filling)
|
||||||
|
sf-->>ps: blocked=false, reason='overfill'
|
||||||
|
sf-->>ps: upstream machines + child stations shut down
|
||||||
|
ps->>ctl: dispatch(mode, ctx, controlState)
|
||||||
|
ctl-->>ps: percControl updated — pumps keep draining
|
||||||
else safety clear
|
else safety clear
|
||||||
ps->>ctl: dispatch(mode, ctx, controlState)
|
ps->>ctl: dispatch(mode, ctx, controlState)
|
||||||
ctl-->>ps: percControl updated
|
ctl-->>ps: percControl updated
|
||||||
@@ -150,6 +163,8 @@ sequenceDiagram
|
|||||||
ps->>out: msg{topic, payload (delta-compressed)}
|
ps->>out: msg{topic, payload (delta-compressed)}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For control-strategy details see [`wiki/modes/`](modes/README).
|
||||||
|
|
||||||
## 8. Data model — `getOutput()`
|
## 8. Data model — `getOutput()`
|
||||||
|
|
||||||
What lands on Port 0. Built in `getOutput()`, then delta-compressed by `outputUtils.formatMsg`.
|
What lands on Port 0. Built in `getOutput()`, then delta-compressed by `outputUtils.formatMsg`.
|
||||||
@@ -197,15 +212,15 @@ flowchart TB
|
|||||||
f1[Basin: volume / height]
|
f1[Basin: volume / height]
|
||||||
f2[Levels: inflow / outflow / overflow]
|
f2[Levels: inflow / outflow / overflow]
|
||||||
f3[Control mode]
|
f3[Control mode]
|
||||||
f4[Level setpoints: min / start / max]
|
f4[Level-based setpoints: startLevel / stopLevel / minLevel / maxLevel]
|
||||||
f5[Safety: dry-run % / overfill %]
|
f5[Safety: dry-run % / high-volume %]
|
||||||
end
|
end
|
||||||
subgraph config["Domain config slice"]
|
subgraph config["Domain config slice"]
|
||||||
c1[basin.volume<br/>basin.height]
|
c1[basin.volume<br/>basin.height]
|
||||||
c2[basin.inflowLevel<br/>basin.outflowLevel<br/>basin.overflowLevel]
|
c2[basin.inflowLevel<br/>basin.outflowLevel<br/>basin.overflowLevel]
|
||||||
c3[control.mode]
|
c3[control.mode]
|
||||||
c4[control.levelbased.minLevel<br/>control.levelbased.startLevel<br/>control.levelbased.maxLevel]
|
c4[control.levelbased.startLevel<br/>control.levelbased.stopLevel<br/>control.levelbased.minLevel<br/>control.levelbased.maxLevel]
|
||||||
c5[safety.dryRunThresholdPercent<br/>safety.overfillThresholdPercent]
|
c5[safety.dryRunThresholdPercent<br/>safety.highVolumeSafetyThresholdPercent]
|
||||||
end
|
end
|
||||||
f1 --> c1
|
f1 --> c1
|
||||||
f2 --> c2
|
f2 --> c2
|
||||||
@@ -218,78 +233,101 @@ flowchart TB
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `basinVolume` | `basin.volume` | `1` | > 0 (m³) | `BasinGeometry` |
|
| `basinVolume` | `basin.volume` | `1` | > 0 (m³) | `BasinGeometry` |
|
||||||
| `basinHeight` | `basin.height` | `1` | > 0 (m) | `BasinGeometry` |
|
| `basinHeight` | `basin.height` | `1` | > 0 (m) | `BasinGeometry` |
|
||||||
| `inflowLevel` | `basin.inflowLevel` | `2` | ≥ 0 (m) | threshold validator, control |
|
| `inflowLevel` | `basin.inflowLevel` | `0.8` | ≥ 0 (m) | threshold validator, control ramp foot |
|
||||||
| `outflowLevel` | `basin.outflowLevel` | `0.2` | ≥ 0 (m) | dead-volume floor |
|
| `outflowLevel` | `basin.outflowLevel` | `0.2` | ≥ 0 (m) | dead-volume floor |
|
||||||
| `overflowLevel` | `basin.overflowLevel` | `2.5` | > 0 (m) | overfill safety |
|
| `overflowLevel` | `basin.overflowLevel` | `0.9` | > 0 (m) | overfill safety ceiling |
|
||||||
| `controlMode` | `control.mode` | `levelbased` | enum | `control/dispatch` |
|
| `controlMode` | `control.mode` | `levelbased` | enum | `control/dispatch` |
|
||||||
| `minLevel` | `control.levelbased.minLevel` | `1` | ≥ 0 (m) | `levelBased.run` |
|
| `levelCurveType` | `control.levelbased.curveType` | `linear` | `linear` \| `log` | `levelBased.run` |
|
||||||
| `startLevel` | `control.levelbased.startLevel` | `1` | ≥ minLevel | ramp foot |
|
| `logCurveFactor` | `control.levelbased.logCurveFactor` | `9` | > 0 | log-curve steepness |
|
||||||
| `maxLevel` | `control.levelbased.maxLevel` | `4` | ≤ overflowLevel | ramp top |
|
| `enableShiftedRamp` | `control.levelbased.enableShiftedRamp` | `false` | bool | hysteresis ramp |
|
||||||
| `enableDryRunProtection` | `safety.enableDryRunProtection` | `true` | bool | `SafetyController` |
|
| `startLevel` | `control.levelbased.startLevel` | `null` | ≥ 0 (m) | ramp zero-point |
|
||||||
| `dryRunThresholdPercent` | `safety.dryRunThresholdPercent` | `2` | 0–100 % | dry-run trip |
|
| `stopLevel` | `control.levelbased.stopLevel` | `null` | ≥ 0 (m) | Schmitt-trigger off threshold |
|
||||||
| `enableOverfillProtection` | `safety.enableOverfillProtection` | `true` | bool | overfill safety |
|
| `minLevel` | `control.levelbased.minLevel` | `null` | ≥ 0 (m) | `levelBased.run` |
|
||||||
| `overfillThresholdPercent` | `safety.overfillThresholdPercent` | `98` | 0–100 % | overfill trip |
|
| `maxLevel` | `control.levelbased.maxLevel` | `null` | ≤ overflowLevel (m) | ramp 100 % point |
|
||||||
|
| `flowSetpoint` | `control.flowbased.setpoint` | `null` | ≥ 0 (m³/h) | flow-PID target |
|
||||||
|
| `enableDryRunProtection` | `safety.enableDryRunProtection` | `true` | bool | `SafetyController._dryRunRule` |
|
||||||
|
| `dryRunThresholdPercent` | `safety.dryRunThresholdPercent` | `2` | 0–100 % | dry-run trip volume |
|
||||||
|
| `enableHighVolumeSafety` | `safety.enableHighVolumeSafety` | `true` | bool | `SafetyController._overfillRule` |
|
||||||
|
| `highVolumeSafetyThresholdPercent` | `safety.highVolumeSafetyThresholdPercent` | `98` | 0–100 % | overfill trip volume |
|
||||||
|
| `timeleftToFullOrEmptyThresholdSeconds` | `safety.timeleftToFullOrEmptyThresholdSeconds` | `0` | ≥ 0 (s) | ETA-based pre-trip guard |
|
||||||
|
|
||||||
|
> `enableOverfillProtection` and `overfillThresholdPercent` are **deprecated aliases** still accepted by `SafetyController` for back-compat. Use `enableHighVolumeSafety` and `highVolumeSafetyThresholdPercent` in new flows. See `OPEN_QUESTIONS.md` (B1.2 resolved).
|
||||||
|
|
||||||
## 10. State chart
|
## 10. State chart
|
||||||
|
|
||||||
Two orthogonal state vectors: **control mode** (operator-driven) and **safety state** (data-driven). The diagram shows them together — most transitions are independent.
|
pumpingStation has two orthogonal state vectors: **control mode** (operator-driven, persistent) and **safety state** (data-driven, evaluated every tick). The e-stop path is the no-volume-data panic that shuts all machines independently.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
state ControlMode {
|
state ControlMode {
|
||||||
[*] --> none
|
[*] --> levelbased
|
||||||
none --> levelbased: set.mode
|
|
||||||
levelbased --> flowbased : set.mode
|
levelbased --> flowbased : set.mode
|
||||||
flowbased --> manual : set.mode
|
flowbased --> manual : set.mode
|
||||||
manual --> levelbased : set.mode
|
manual --> levelbased : set.mode
|
||||||
|
manual --> none : set.mode
|
||||||
levelbased --> none : set.mode
|
levelbased --> none : set.mode
|
||||||
|
none --> levelbased : set.mode
|
||||||
}
|
}
|
||||||
|
|
||||||
state SafetyState {
|
state SafetyState {
|
||||||
[*] --> nominal
|
[*] --> nominal
|
||||||
nominal --> dryRun : vol < minVol AND draining
|
nominal --> dryRun : vol < minVol AND draining
|
||||||
nominal --> overfill: vol > overfillThreshold AND filling
|
nominal --> overfill : vol > highVolThreshold AND filling
|
||||||
|
nominal --> panic : no volume reading
|
||||||
dryRun --> nominal : vol ≥ minVol
|
dryRun --> nominal : vol ≥ minVol
|
||||||
overfill --> nominal: vol ≤ overfillThreshold
|
overfill --> nominal : vol ≤ highVolThreshold
|
||||||
|
panic --> nominal : volume reading restored
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
While the safety state is `dryRun`, control dispatch is **skipped** entirely. While `overfill`, control still runs (pumps must keep draining) but upstream equipment is shut down.
|
| Safety state | `blocked` | Control dispatch | Side-effects |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `nominal` | false | runs normally | — |
|
||||||
|
| `dryRun` | **true** | **skipped** | downstream machines + machineGroups shut down |
|
||||||
|
| `overfill` | false | runs (pumps must drain) | upstream machines + child stations shut down |
|
||||||
|
| `panic` | **true** | **skipped** | **ALL** machines shut down |
|
||||||
|
|
||||||
|
`dryRun` is triggered when `direction='draining'` AND vol < `minVol × (1 + dryRunThresholdPercent/100)`.
|
||||||
|
`overfill` is triggered when `direction='filling'` AND vol > `maxVolAtOverflow × (highVolumeSafetyThresholdPercent/100)`.
|
||||||
|
|
||||||
## 11. Examples
|
## 11. Examples
|
||||||
|
|
||||||
Example flows live under `examples/` in the repo. The structured tier-1/2/3 flows for this node are still in progress; until they land, the standalone simulator demo is the only runnable artefact.
|
All three tiers are written and runnable. Import any file via the Node-RED editor or the Admin API.
|
||||||
|
|
||||||
| Tier | File | What it shows | Status |
|
| Tier | File | What it shows | Status |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Basic | `examples/01-Basic.flow.json` | Inject + dashboard, single basin, no parent | ⏳ TBD |
|
| Basic | `examples/01-Basic.json` | Single pumpingStation driven by inject nodes — no parent, no dashboard. Try `set.inflow`, `set.mode`, `cmd.calibrate.volume`. | ✅ |
|
||||||
| Integration | `examples/02-Integration.flow.json` | pumpingStation + MGC + 2 pumps + measurement children | ⏳ TBD |
|
| Integration | `examples/02-Integration.json` | pumpingStation + `machineGroupControl` + 2 `rotatingMachine` pumps + level `measurement`. Demonstrates Phase-2 parent/child handshake and `levelbased` control driving real pumps. | ✅ |
|
||||||
| Dashboard | `examples/03-Dashboard.flow.json` | Live FlowFuse charts (level, net flow, ETA) | ⏳ TBD |
|
| Dashboard | `examples/03-Dashboard.json` | Tier 2 plumbing + FlowFuse Dashboard 2.0 page — 3 charts (flow / level / volume %), mode dropdown, demand slider. | ✅ |
|
||||||
| Headless | `examples/standalone-demo.js` | Node.js-only simulator, no Node-RED | ✅ in repo |
|
| Headless | `examples/standalone-demo.js` | Node.js-only simulator, no Node-RED required. | ✅ |
|
||||||
|
|
||||||
|
See `examples/README.md` for layout conventions (link channels, lane positions, group boxes).
|
||||||
|
|
||||||
## 12. Debug recipes
|
## 12. Debug recipes
|
||||||
|
|
||||||
| Symptom | First thing to check | Where to look |
|
| Symptom | First thing to check | Where to look |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Status badge stuck on `❔ 0.0%` | Did any volume / level measurement register? Watch Port 2 + first-child event. | Editor debug tap on Port 2 + `_subscribeMeasurement` log line. |
|
| Status badge stuck on `❔ 0.0%` | No volume/level measurement registered yet. Watch Port 2. | Editor debug tap on Port 2 + `_subscribeMeasurement` log line. |
|
||||||
| `direction` always `steady` | Net flow inside `general.flowThreshold` dead-band (default 0.0001 m³/s). | `flowAggregator.deriveDirection`. |
|
| `direction` always `steady` | Net flow inside `general.flowThreshold` dead-band (default 0.0001 m³/s ≈ 0.36 m³/h). | `flowAggregator.deriveDirection`. |
|
||||||
| `set.demand` ignored | Mode isn't `manual`. Check `set.mode` history. | `handlers.setDemand` debug log. |
|
| `set.demand` ignored | Mode isn't `manual`. Confirm with `set.mode=manual` first. | `handlers.setDemand` debug log. |
|
||||||
| Predicted volume drifts off measured | Calibration needed — fire `cmd.calibrate.volume` with a known reading. | `measurement/calibration.js`. |
|
| Predicted volume drifts off measured | Integrator needs a calibration anchor. Fire `cmd.calibrate.volume` with a known basin volume. | `measurement/calibration.js`. |
|
||||||
| Pumps don't stop on dry-run | `safety.enableDryRunProtection` must be `true` AND the orchestrator must see `direction='draining'`. | `SafetyController.evaluate`. |
|
| Pumps don't stop on dry-run | `safety.enableDryRunProtection` must be `true` AND `direction` must be `'draining'`. | `SafetyController._dryRunRule`. |
|
||||||
| Threshold-ordering warnings on startup | `validateThresholdOrdering` printed `inflowLevel < overflowLevel` style violations. | `basin/thresholdValidator.js`. |
|
| Threshold-ordering warnings on startup | `validateThresholdOrdering` detected violations (e.g. `inflowLevel > overflowLevel`). | `basin/thresholdValidator.js`. |
|
||||||
|
| All machines shut down immediately | No volume reading reached the node — panic path in SafetyController. Check child registration sequence. | `SafetyController.evaluate` line 59. |
|
||||||
|
|
||||||
> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
|
> 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
|
## 13. When you would NOT use this node
|
||||||
|
|
||||||
- Use pumpingStation for a **wet-well basin** that needs orchestrated drainage. For a single pump with no basin model, use `rotatingMachine` directly.
|
- Use `rotatingMachine` directly for a single pump with no basin model. pumpingStation adds overhead that pays off only when you need predicted volume, time-to-full, or multi-pump orchestration.
|
||||||
- Don't use pumpingStation to schedule a fixed pump rota — its modes are reactive (level / flow / manual). Use an external scheduler if you need a calendar-driven schedule.
|
- Don't use pumpingStation to schedule a fixed pump rota. Its control modes are reactive (level / flow / manual demand), not calendar-driven. Use an external scheduler and wire it in via `set.demand`.
|
||||||
- Skip pumpingStation if you don't need predicted volume / time-to-full. A bare `machineGroupControl` is lighter when the upstream basin is modelled elsewhere.
|
- Skip pumpingStation if you only need flow or pressure measurements with no wet-well state. A bare `machineGroupControl` is lighter when the basin is modelled elsewhere or not at all.
|
||||||
|
|
||||||
## 14. Known limitations / current issues
|
## 14. Known limitations / current issues
|
||||||
|
|
||||||
| # | Issue | Tracked in |
|
| # | Issue | Tracked in |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 1 | Cascaded `pumpingstation` children accepted but not exercised in production — semantics of nested stations are not test-covered. | TBD |
|
| 1 | Cascaded `pumpingstation` children accepted but semantics of nested stations are not test-covered in production scenarios. | TBD — exercise in Docker E2E before promoting. |
|
||||||
| 2 | `pressureBased`, `percentageBased`, `powerBased`, and `hybrid` are in the config enum but not implemented as control strategies. | `control/index.js` — only `levelbased` / `flowbased` / `manual` dispatched. |
|
| 2 | `pressureBased`, `percentageBased`, `powerBased`, and `hybrid` are listed in the config enum but not dispatched — only `levelbased`, `flowbased`, `manual` are implemented. | `control/index.js` |
|
||||||
| 3 | Predicted-volume integrator can drift over long horizons without a measured-level calibration source. | `cmd.calibrate.volume` is operator-triggered, not automatic. |
|
| 3 | Predicted-volume integrator drifts over long horizons without a measured-level calibration source. `cmd.calibrate.volume` is operator-triggered, not automatic. | Operator procedure; auto-calibration from level sensor is future work. |
|
||||||
| 4 | Tier 1/2/3 example flows not yet written — current `examples/` only contains the standalone simulator. | P2.14 (Docker E2E) + P9 wiki cleanup. |
|
| 4 | `enableOverfillProtection` / `overfillThresholdPercent` deprecated aliases still accepted by `SafetyController` (back-compat). Remove after one release cycle. | B1.2 resolved in `OPEN_QUESTIONS.md`. |
|
||||||
|
|||||||
Reference in New Issue
Block a user