Level-armed shift, derived dryRunLevel, side-panel editor + manual q_out
Runtime (specificClass.js): - Replace direction-based hysteresis with level-armed _shiftArmed state. Arms when level rises past shiftLevel; disarms when level drops below startLevel. While armed, ramp foot moves to startLevel and ramp top to shiftLevel — both ends shift left, then saturate at 100 % up to maxLevel. - _scaleLevelToFlowPercent now takes (rampStartLevel, rampTopLevel) so the saturation point follows the shift state. - New setManualOutflow mirroring setManualInflow. Adapter (nodeClass.js): - Pipe enableShiftedRamp / shiftLevel through to control.levelbased. - New q_out topic handler. Editor (pumpingStation.html + new src/editor/ modules): - Split monolithic <script> into modules: index.js (helpers), basin-diagram.js, mode-preview.js, hover-couple.js, oneditprepare.js, oneditsave.js — served via /pumpingStation/editor/:file. - Mode preview redrawn per the SVG diagrams: OFF tier below 0 %, 0 % flat from start→inlet, ramp inlet→max, optional shifted-down curve start→shift with 100 % saturation past shift. - Mode preview gains zone bands (dryRun / safetyLow / safe / safetyHigh / overflow), level markers (dryRun derived, start, inlet, max, shift, overflow), validation ribbon that blocks save on bad ordering. - Auto-default shiftLevel to 0.9 × maxLevel on enable so the marker is always visible. - All level inputs moved to a side panel left of each diagram, color- coded to match line strokes; hover-couple highlights the paired SVG line on input focus / mouseover. - Removed UI for non-static parameters: minHeightBasedOn, pipelineLength, maxDischargeHead, staticHead, defaultFluid, maxInflowRate, temperatureReferenceDegC, timeleftToFullOrEmptyThresholdSeconds, inletPipeDiameter, outletPipeDiameter, minLevel (now derived = dryRunLevel). - foreignObject inputs in basin SVG removed (single source of truth in side panel). Dashboard example (examples/basic-dashboard.flow.json): - Add manual Q_OUT slider + q_out builder mirroring the existing q_in trio so the basin can be exercised end-to-end without a connected rotating-machine downstream. Tests (test/basic/specificClass.test.js): - Replace direction-shift test with two new cases covering shift-disabled hold-zone behaviour and shift-armed/disarmed transitions through shiftLevel and startLevel boundaries. 53/53 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,14 @@ class PumpingStation {
|
||||
// levelbased mode. Exposed in getOutput() for dashboards.
|
||||
this.percControl = 0;
|
||||
|
||||
// --- Level-armed hysteresis state ---
|
||||
// _shiftArmed flips true when level rises past shiftLevel (with
|
||||
// enableShiftedRamp). While armed, the demand ramp's lower foot
|
||||
// is startLevel instead of inflowLevel — so on the way down the
|
||||
// pumps stay aggressive until level falls below startLevel, at
|
||||
// which point the arm clears.
|
||||
this._shiftArmed = false;
|
||||
|
||||
// --- Flow dead-band ---
|
||||
// flowThreshold (m3/s) prevents control actions on noise.
|
||||
// Default 1e-4 m3/s ≈ 0.36 m3/h — below this, net flow is
|
||||
@@ -271,6 +279,11 @@ class PumpingStation {
|
||||
this.measurements.type('flow').variant('predicted').position('in').child('manual-qin').value(num, timestamp, unit);
|
||||
}
|
||||
|
||||
setManualOutflow(value, timestamp = Date.now(), unit) {
|
||||
const num = Number(value);
|
||||
this.measurements.type('flow').variant('predicted').position('out').child('manual-qout').value(num, timestamp, unit);
|
||||
}
|
||||
|
||||
/* --------------------------- Tick / Control --------------------------- */
|
||||
|
||||
tick() {
|
||||
@@ -314,7 +327,7 @@ class PumpingStation {
|
||||
_controlLogic(direction) {
|
||||
switch (this.mode) {
|
||||
case 'levelbased':
|
||||
this._controlLevelBased();
|
||||
this._controlLevelBased(direction);
|
||||
break;
|
||||
case 'flowbased':
|
||||
this._controlFlowBased?.();
|
||||
@@ -326,7 +339,7 @@ class PumpingStation {
|
||||
}
|
||||
}
|
||||
|
||||
async _controlLevelBased() {
|
||||
async _controlLevelBased(_direction) {
|
||||
const { startLevel, minLevel } = this.config.control.levelbased;
|
||||
const levelUnit = this.measurements.getUnit('level');
|
||||
|
||||
@@ -336,33 +349,45 @@ class PumpingStation {
|
||||
return;
|
||||
}
|
||||
|
||||
// Level-based pump control via MGC — three zones:
|
||||
// Level-based pump control via MGC (see wiki/modes/levelbased.md).
|
||||
//
|
||||
// level < minLevel → STOP (unconditional MGC shutdown)
|
||||
// minLevel ≤ level < startLevel → DEAD ZONE (hysteresis; keep last cmd)
|
||||
// level ≥ startLevel → RUN (linear [startLevel..maxLevel] → [0..100 %])
|
||||
// See wiki/modes/levelbased.md for the full transfer-function diagram.
|
||||
// level < startLevel → 0 % (pumps held off)
|
||||
// level in [startLevel..rampStart] → 0 % (HOLD zone)
|
||||
// level in [rampStart..maxLevel] → 0..100 % (linear or log curve)
|
||||
// level > maxLevel → ≥100 % (MGC clamps internally)
|
||||
//
|
||||
// With enableShiftedRamp:
|
||||
// rampStart = inflowLevel by default
|
||||
// when level rises past shiftLevel → arm → rampStart = startLevel
|
||||
// when level drops below startLevel → disarm → rampStart = inflowLevel
|
||||
// Without enableShiftedRamp: rampStart = inflowLevel always.
|
||||
|
||||
// STOP — below minLevel, always shut down regardless of direction.
|
||||
if (level < minLevel) {
|
||||
this.percControl = 0;
|
||||
Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
|
||||
return;
|
||||
}
|
||||
|
||||
// DEAD ZONE — between minLevel and startLevel, do nothing.
|
||||
// Pumps that are running keep their last command; pumps that
|
||||
// are off stay off. This prevents rapid on/off cycling.
|
||||
if (level < startLevel) {
|
||||
this._updateShiftArmed(level);
|
||||
const rampStartLevel = this._levelBasedRampStart();
|
||||
const rampTopLevel = this._levelBasedRampTop();
|
||||
|
||||
// HOLD/MINIMUM DEMAND — below the active ramp start, command 0 %
|
||||
// without latching dry-run. Dry-run remains the safety layer's job.
|
||||
if (level < rampStartLevel) {
|
||||
this.percControl = 0;
|
||||
await this._applyMachineGroupLevelControl(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// RUN — above startLevel, compute demand and forward to MGC.
|
||||
// _scaleLevelToFlowPercent maps [startLevel..maxLevel] → [0..100].
|
||||
// Above maxLevel the MGC clamps internally.
|
||||
const rawPercControl = this._scaleLevelToFlowPercent(level);
|
||||
// RUN — above rampStartLevel, compute demand and forward to MGC.
|
||||
// _scaleLevelToFlowPercent maps [rampStartLevel..rampTopLevel] → [0..100].
|
||||
// Above rampTopLevel demand saturates at 100 %.
|
||||
const rawPercControl = this._scaleLevelToFlowPercent(level, rampStartLevel, rampTopLevel);
|
||||
const percControl = Math.max(0, rawPercControl);
|
||||
this.percControl = percControl;
|
||||
this.logger.debug(`Level-based control: level=${level} percControl=${percControl}`);
|
||||
this.logger.debug(`Level-based control: level=${level} armed=${this._shiftArmed} foot=${rampStartLevel} top=${rampTopLevel} percControl=${percControl}`);
|
||||
|
||||
await this._applyMachineGroupLevelControl(percControl);
|
||||
}
|
||||
@@ -503,10 +528,60 @@ class PumpingStation {
|
||||
return null;
|
||||
}
|
||||
|
||||
_scaleLevelToFlowPercent(level) {
|
||||
const { startLevel, maxLevel } = this.config.control.levelbased;
|
||||
this.logger.debug(`Scaling startLevel=${startLevel} maxLevel=${maxLevel}`);
|
||||
return this.interpolate.interpolate_lin_single_point(level, startLevel, maxLevel, 0, 100);
|
||||
_levelBasedRampStart() {
|
||||
const { startLevel, enableShiftedRamp } = this.config.control.levelbased;
|
||||
const inflowLevel = this.basin?.inflowLevel;
|
||||
if (enableShiftedRamp && this._shiftArmed) return startLevel;
|
||||
if (Number.isFinite(inflowLevel)) return inflowLevel;
|
||||
return startLevel;
|
||||
}
|
||||
|
||||
_levelBasedRampTop() {
|
||||
// Returns the upper level at which demand saturates at 100 %.
|
||||
// While the shift is armed, top moves left from maxLevel to shiftLevel
|
||||
// so output reaches 100 % earlier and stays at 100 % until level
|
||||
// falls back through shiftLevel on the way down.
|
||||
const { maxLevel, enableShiftedRamp, shiftLevel } = this.config.control.levelbased;
|
||||
if (enableShiftedRamp && this._shiftArmed
|
||||
&& Number.isFinite(shiftLevel) && shiftLevel > 0
|
||||
&& shiftLevel <= maxLevel) {
|
||||
return shiftLevel;
|
||||
}
|
||||
return maxLevel;
|
||||
}
|
||||
|
||||
_updateShiftArmed(level) {
|
||||
const { enableShiftedRamp, shiftLevel, startLevel } = this.config.control.levelbased;
|
||||
if (!enableShiftedRamp) {
|
||||
this._shiftArmed = false;
|
||||
return;
|
||||
}
|
||||
const trigger = Number.isFinite(shiftLevel) && shiftLevel > 0 ? shiftLevel : null;
|
||||
if (!this._shiftArmed && trigger != null && level >= trigger) {
|
||||
this._shiftArmed = true;
|
||||
this.logger.debug(`Shift armed at level=${level} (shiftLevel=${trigger})`);
|
||||
} else if (this._shiftArmed && Number.isFinite(startLevel) && level < startLevel) {
|
||||
this._shiftArmed = false;
|
||||
this.logger.debug(`Shift disarmed at level=${level} (startLevel=${startLevel})`);
|
||||
}
|
||||
}
|
||||
|
||||
_scaleLevelToFlowPercent(level, rampStartLevel, rampTopLevel) {
|
||||
const { maxLevel, curveType = 'linear', logCurveFactor = 9 } = this.config.control.levelbased;
|
||||
const start = Number.isFinite(rampStartLevel) ? rampStartLevel : this.config.control.levelbased.startLevel;
|
||||
const top = Number.isFinite(rampTopLevel) ? rampTopLevel : maxLevel;
|
||||
if (!Number.isFinite(level) || !Number.isFinite(start) || !Number.isFinite(top)) return 0;
|
||||
if (top <= start) return level >= top ? 100 : 0;
|
||||
|
||||
const x = Math.max(0, Math.min(1, (level - start) / (top - start)));
|
||||
if (curveType === 'log') {
|
||||
const factor = Number.isFinite(Number(logCurveFactor)) && Number(logCurveFactor) > 0
|
||||
? Number(logCurveFactor)
|
||||
: 9;
|
||||
return 100 * (Math.log1p(factor * x) / Math.log1p(factor));
|
||||
}
|
||||
|
||||
return x * 100;
|
||||
}
|
||||
|
||||
_levelRate(variant) {
|
||||
@@ -634,7 +709,7 @@ class PumpingStation {
|
||||
* Only a manual override or emergency can restart them.
|
||||
* safetyControllerActive = true → blocks _controlLogic.
|
||||
*
|
||||
* 2. ABOVE overflow level (overfill): pumps CANNOT stop.
|
||||
* 2. ABOVE high-volume safety level: pumps CANNOT stop.
|
||||
* Shuts down UPSTREAM equipment only (stop more water coming in).
|
||||
* Does NOT shut down downstream pumps or machine groups — they
|
||||
* must keep draining. Does NOT set safetyControllerActive — the
|
||||
@@ -642,7 +717,7 @@ class PumpingStation {
|
||||
* dictated by the current level (which will be >100% near overflow,
|
||||
* meaning all pumps at maximum via the normal demand curve).
|
||||
* Only a manual override or emergency stop can shut pumps during
|
||||
* an overfill event.
|
||||
* a high-volume or overflowing event.
|
||||
*/
|
||||
_safetyController(remainingTime, direction) {
|
||||
this.safetyControllerActive = false;
|
||||
@@ -660,21 +735,34 @@ class PumpingStation {
|
||||
enableDryRunProtection,
|
||||
dryRunThresholdPercent,
|
||||
enableOverfillProtection,
|
||||
overfillThresholdPercent,
|
||||
enableHighVolumeSafety,
|
||||
timeleftToFullOrEmptyThresholdSeconds
|
||||
} = this.config.safety || {};
|
||||
|
||||
const dryRunEnabled = Boolean(enableDryRunProtection);
|
||||
const overfillEnabled = Boolean(enableOverfillProtection);
|
||||
const highVolumeSafetyEnabled = Boolean(enableHighVolumeSafety ?? enableOverfillProtection);
|
||||
const timeProtectionEnabled = timeleftToFullOrEmptyThresholdSeconds > 0;
|
||||
const triggerHighVol = this.basin.maxVolAtOverflow * ((Number(overfillThresholdPercent) || 0) / 100);
|
||||
const triggerLowVol = this.basin.minVol * (1 + ((Number(dryRunThresholdPercent) || 0) / 100));
|
||||
const safety = this._computeSafetyPoints();
|
||||
const triggerHighVol = safety.highVolumeSafetyVol;
|
||||
const triggerLowVol = safety.dryRunSafetyVol;
|
||||
const currentLevel = this._pickVariant('level', this.levelVariants, 'atequipment', 'm');
|
||||
|
||||
this.safetyState = {
|
||||
dryRunActive: false,
|
||||
highVolumeActive: false,
|
||||
isOverflowing: Number.isFinite(currentLevel) && currentLevel >= this.basin.overflowLevel,
|
||||
dryRunLevel: safety.dryRunLevel,
|
||||
highVolumeSafetyLevel: safety.highVolumeSafetyLevel,
|
||||
dryRunSafetyVol: safety.dryRunSafetyVol,
|
||||
highVolumeSafetyVol: safety.highVolumeSafetyVol
|
||||
};
|
||||
|
||||
// Rule 1: DRY-RUN — below minLevel, pumps cannot run.
|
||||
if (direction === 'draining') {
|
||||
const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
|
||||
const dryRunTriggered = dryRunEnabled && vol < triggerLowVol;
|
||||
if (timeTriggered || dryRunTriggered) {
|
||||
this.safetyState.dryRunActive = true;
|
||||
// Shut down all downstream equipment — pumps must stop.
|
||||
Object.values(this.machines).forEach((machine) => {
|
||||
const pos = machine?.config?.functionality?.positionVsParent;
|
||||
@@ -700,8 +788,9 @@ class PumpingStation {
|
||||
// running to maintain pump demand.
|
||||
if (direction === 'filling') {
|
||||
const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
|
||||
const overfillTriggered = overfillEnabled && vol > triggerHighVol;
|
||||
if (timeTriggered || overfillTriggered) {
|
||||
const highVolumeTriggered = highVolumeSafetyEnabled && vol > triggerHighVol;
|
||||
if (timeTriggered || highVolumeTriggered) {
|
||||
this.safetyState.highVolumeActive = true;
|
||||
// Shut down UPSTREAM only — stop more water coming in.
|
||||
Object.values(this.machines).forEach((machine) => {
|
||||
const pos = machine?.config?.functionality?.positionVsParent;
|
||||
@@ -713,7 +802,7 @@ class PumpingStation {
|
||||
// NOTE: machine groups (downstream pumps) are NOT shut down.
|
||||
// They must keep draining to prevent overflow from worsening.
|
||||
this.logger.warn(
|
||||
`Overfill safety: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down upstream equipment only — pumps keep running`
|
||||
`High-volume safety: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down upstream equipment only — pumps keep running`
|
||||
);
|
||||
// NOTE: safetyControllerActive is NOT set — level control
|
||||
// keeps commanding pumps at maximum demand.
|
||||
@@ -721,6 +810,25 @@ class PumpingStation {
|
||||
}
|
||||
}
|
||||
|
||||
_computeSafetyPoints() {
|
||||
const safety = this.config.safety || {};
|
||||
const dryRunPct = Number(safety.dryRunThresholdPercent) || 0;
|
||||
const highPct = Number(
|
||||
safety.highVolumeSafetyThresholdPercent ?? safety.overfillThresholdPercent ?? 98
|
||||
) || 0;
|
||||
const dryRunSafetyVol = this.basin.minVol * (1 + (dryRunPct / 100));
|
||||
const dryRunLevel = this._calcLevelFromVolume(dryRunSafetyVol);
|
||||
const highVolumeSafetyVol = this.basin.maxVolAtOverflow * (highPct / 100);
|
||||
const highVolumeSafetyLevel = this._calcLevelFromVolume(highVolumeSafetyVol);
|
||||
|
||||
return {
|
||||
dryRunSafetyVol,
|
||||
dryRunLevel,
|
||||
highVolumeSafetyVol,
|
||||
highVolumeSafetyLevel
|
||||
};
|
||||
}
|
||||
|
||||
/* --------------------------- Basin --------------------------- */
|
||||
|
||||
/**
|
||||
@@ -740,9 +848,11 @@ class PumpingStation {
|
||||
const minHeightBasedOn = this.config.hydraulics.minHeightBasedOn;
|
||||
const volEmptyBasin = this.config.basin.volume; // m3 — total basin capacity
|
||||
const heightBasin = this.config.basin.height; // m — floor to rim
|
||||
const inflowLevel = this.config.basin.inflowLevel; // m — sewer feed pipe centre
|
||||
const outflowLevel = this.config.basin.outflowLevel; // m — pump suction pipe centre
|
||||
const inflowLevel = this.config.basin.inflowLevel; // m — inlet pipe bottom/invert
|
||||
const outflowLevel = this.config.basin.outflowLevel; // m — outlet/pump suction pipe top
|
||||
const overflowLevel = this.config.basin.overflowLevel; // m — overflow weir crest
|
||||
const inletPipeDiameter = this.config.basin.inletPipeDiameter;
|
||||
const outletPipeDiameter = this.config.basin.outletPipeDiameter;
|
||||
|
||||
// Constant cross-section assumption: volume = level × area
|
||||
const surfaceArea = volEmptyBasin / heightBasin;
|
||||
@@ -762,6 +872,8 @@ class PumpingStation {
|
||||
inflowLevel,
|
||||
outflowLevel,
|
||||
overflowLevel,
|
||||
inletPipeDiameter,
|
||||
outletPipeDiameter,
|
||||
surfaceArea,
|
||||
maxVol,
|
||||
maxVolAtOverflow,
|
||||
@@ -786,26 +898,21 @@ class PumpingStation {
|
||||
*
|
||||
* Strict invariants (bottom → top):
|
||||
* 0 < outflowLevel < inflowLevel < overflowLevel ≤ basinHeight
|
||||
* dryRunTriggerLevel ≤ minLevel ≤ startLevel < maxLevel ≤ overflowLevel
|
||||
* dryRunLevel ≤ minLevel ≤ startLevel ≤ inflowLevel < maxLevel ≤ highVolumeSafetyLevel < overflowLevel
|
||||
*
|
||||
* dryRunTriggerLevel and the overfill trigger are DERIVED — computed
|
||||
* dryRunLevel and highVolumeSafetyLevel are DERIVED — computed
|
||||
* from minVol × (1 + dryRunThresholdPercent/100) and overflowLevel ×
|
||||
* overfillThresholdPercent/100 in the safety layer. Validating those
|
||||
* highVolumeSafetyThresholdPercent/100 in the safety layer. Validating those
|
||||
* catches config that would let minLevel sit below where safety has
|
||||
* already force-stopped the pumps (no-op control band).
|
||||
*/
|
||||
_validateThresholdOrdering() {
|
||||
const basin = this.basin;
|
||||
const lvl = this.config.control?.levelbased || {};
|
||||
const safety = this.config.safety || {};
|
||||
|
||||
// Derived safety trigger levels (level-space equivalents of what
|
||||
// _safetyController does in volume-space).
|
||||
const dryRunPct = Number(safety.dryRunThresholdPercent) || 0;
|
||||
const overfillPct = Number(safety.overfillThresholdPercent) || 100;
|
||||
const refLowLevel = basin.minHeightBasedOn === 'inlet' ? basin.inflowLevel : basin.outflowLevel;
|
||||
const dryRunLevel = refLowLevel * (1 + dryRunPct / 100);
|
||||
const overfillLevel = basin.overflowLevel * (overfillPct / 100);
|
||||
const safetyPoints = this._computeSafetyPoints();
|
||||
const dryRunLevel = safetyPoints.dryRunLevel;
|
||||
const highVolumeSafetyLevel = safetyPoints.highVolumeSafetyLevel;
|
||||
|
||||
const checks = [
|
||||
['outflowLevel', basin.outflowLevel, '<', 'inflowLevel', basin.inflowLevel],
|
||||
@@ -813,8 +920,10 @@ class PumpingStation {
|
||||
['overflowLevel', basin.overflowLevel, '<=', 'basinHeight', basin.heightBasin],
|
||||
['dryRunLevel', dryRunLevel, '<=', 'minLevel', lvl.minLevel],
|
||||
['minLevel', lvl.minLevel, '<=', 'startLevel', lvl.startLevel],
|
||||
['startLevel', lvl.startLevel, '<', 'maxLevel', lvl.maxLevel],
|
||||
['maxLevel', lvl.maxLevel, '<=', 'overfillLevel', overfillLevel],
|
||||
['startLevel', lvl.startLevel, '<=', 'inflowLevel', basin.inflowLevel],
|
||||
['inflowLevel', basin.inflowLevel, '<', 'maxLevel', lvl.maxLevel],
|
||||
['maxLevel', lvl.maxLevel, '<=', 'highVolumeSafetyLevel', highVolumeSafetyLevel],
|
||||
['highVolumeSafetyLevel', highVolumeSafetyLevel, '<', 'overflowLevel', basin.overflowLevel],
|
||||
];
|
||||
|
||||
const issues = [];
|
||||
@@ -844,21 +953,38 @@ class PumpingStation {
|
||||
|
||||
getOutput() {
|
||||
const output = this.measurements.getFlattenedOutput();
|
||||
const safety = this._computeSafetyPoints();
|
||||
output.direction = this.state.direction;
|
||||
output.flowSource = this.state.flowSource;
|
||||
output.timeleft = this.state.seconds;
|
||||
output.volEmptyBasin = this.basin.volEmptyBasin;
|
||||
output.inflowLevel = this.basin.inflowLevel;
|
||||
output.outflowLevel = this.basin.outflowLevel;
|
||||
output.overflowLevel = this.basin.overflowLevel;
|
||||
output.inletPipeDiameter = this.basin.inletPipeDiameter;
|
||||
output.outletPipeDiameter = this.basin.outletPipeDiameter;
|
||||
output.maxVol = this.basin.maxVol;
|
||||
output.minVol = this.basin.minVol;
|
||||
output.maxVolAtOverflow = this.basin.maxVolAtOverflow;
|
||||
output.minVolAtOutflow = this.basin.minVolAtOutflow;
|
||||
output.minVolAtInflow = this.basin.minVolAtInflow;
|
||||
output.minHeightBasedOn = this.basin.minHeightBasedOn;
|
||||
output.dryRunLevel = safety.dryRunLevel;
|
||||
output.dryRunSafetyVol = safety.dryRunSafetyVol;
|
||||
output.highVolumeSafetyLevel = safety.highVolumeSafetyLevel;
|
||||
output.highVolumeSafetyVol = safety.highVolumeSafetyVol;
|
||||
output.isOverflowing = Boolean(this.safetyState?.isOverflowing);
|
||||
output.safetyState = this._deriveSafetyState();
|
||||
output.percControl = this.percControl;
|
||||
return output;
|
||||
}
|
||||
|
||||
_deriveSafetyState() {
|
||||
if (this.safetyState?.isOverflowing) return 'overflowing';
|
||||
if (this.safetyState?.highVolumeActive) return 'highVolume';
|
||||
if (this.safetyState?.dryRunActive) return 'dryRun';
|
||||
return 'normal';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PumpingStation;
|
||||
@@ -887,15 +1013,19 @@ if (require.main === module) {
|
||||
height: 10,
|
||||
inflowLevel: 3,
|
||||
outflowLevel: 0.2,
|
||||
overflowLevel: 3.2
|
||||
overflowLevel: 3.2,
|
||||
inletPipeDiameter: 0.4,
|
||||
outletPipeDiameter: 0.3
|
||||
},
|
||||
hydraulics: {
|
||||
refHeight: 'NAP',
|
||||
basinBottomRef: 0
|
||||
basinBottomRef: 0,
|
||||
minHeightBasedOn: 'outlet'
|
||||
},
|
||||
safety: {
|
||||
enableDryRunProtection:false,
|
||||
enableOverfillProtection:false
|
||||
enableHighVolumeSafety:false,
|
||||
highVolumeSafetyThresholdPercent: 98
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1036,4 +1166,4 @@ if (require.main === module) {
|
||||
console.error('Demo failed:', err);
|
||||
});
|
||||
}
|
||||
//*/
|
||||
//*/
|
||||
|
||||
Reference in New Issue
Block a user