diff --git a/pumpingStation.html b/pumpingStation.html index 6103316..749ad8c 100644 --- a/pumpingStation.html +++ b/pumpingStation.html @@ -24,6 +24,11 @@ heightInlet: { value: 0.8 }, // m, centre of inlet pipe above floor heightOutlet: { value: 0.2 }, // m, centre of outlet pipe above floor heightOverflow: { value: 0.9 }, // m, overflow elevation + timeleftToFullOrEmptyThresholdSeconds:{value:0}, // time threshold to safeguard starting or stopping pumps in seconds + enableDryRunProtection: { value: true }, + enableOverfillProtection: { value: true }, + dryRunThresholdPercent: { value: 2 }, + overfillThresholdPercent: { value: 98 }, minHeightBasedOn: { value: "outlet" }, // basis for minimum height check: inlet or outlet // Advanced reference information @@ -92,6 +97,38 @@ minHeightBasedOnEl.value = this.minHeightBasedOn; } + const dryRunToggle = document.getElementById("node-input-enableDryRunProtection"); + const dryRunPercent = document.getElementById("node-input-dryRunThresholdPercent"); + const overfillToggle = document.getElementById("node-input-enableOverfillProtection"); + const overfillPercent = document.getElementById("node-input-overfillThresholdPercent"); + + const toggleInput = (toggleEl, inputEl) => { + if (!toggleEl || !inputEl) { return; } + inputEl.disabled = !toggleEl.checked; + inputEl.parentElement.classList.toggle('disabled', inputEl.disabled); + }; + + if (dryRunToggle && dryRunPercent) { + dryRunToggle.checked = !!this.enableDryRunProtection; + dryRunPercent.value = Number.isFinite(this.dryRunThresholdPercent) ? this.dryRunThresholdPercent : 2; + dryRunToggle.addEventListener('change', () => toggleInput(dryRunToggle, dryRunPercent)); + toggleInput(dryRunToggle, dryRunPercent); + } + + if (overfillToggle && overfillPercent) { + overfillToggle.checked = !!this.enableOverfillProtection; + overfillPercent.value = Number.isFinite(this.overfillThresholdPercent) ? this.overfillThresholdPercent : 98; + overfillToggle.addEventListener('change', () => toggleInput(overfillToggle, overfillPercent)); + toggleInput(overfillToggle, overfillPercent); + } + + const timeLeftInput = document.getElementById("node-input-timeleftToFullOrEmptyThresholdSeconds"); + if (timeLeftInput) { + timeLeftInput.value = Number.isFinite(this.timeleftToFullOrEmptyThresholdSeconds) + ? this.timeleftToFullOrEmptyThresholdSeconds + : 0; + } + //------------------- END OF CUSTOM config UI ELEMENTS ------------------- // }, oneditsave: function () { @@ -106,12 +143,14 @@ node.minHeightBasedOn = document.getElementById("node-input-minHeightBasedOn").value || "outlet"; node.simulator = document.getElementById("node-input-simulator").checked; - ["basinVolume","basinHeight","heightInlet","heightOutlet","heightOverflow","basinBottomRef"] + ["basinVolume","basinHeight","heightInlet","heightOutlet","heightOverflow","basinBottomRef","timeleftToFullOrEmptyThresholdSeconds","dryRunThresholdPercent","overfillThresholdPercent"] .forEach(field => { node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0; }); node.refHeight = document.getElementById("node-input-refHeight").value || ""; + node.enableDryRunProtection = document.getElementById("node-input-enableDryRunProtection").checked; + node.enableOverfillProtection = document.getElementById("node-input-enableOverfillProtection").checked; }, }); @@ -176,6 +215,38 @@ +
+ + +
+ + +
+ +
+ + + Prevent pumps from running on low volume +
+
+ + +
+ +
+ + + Stop filling when approaching overflow +
+
+ + +
+
diff --git a/src/nodeClass.js b/src/nodeClass.js index f6b6a0f..29dd618 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -65,6 +65,13 @@ class nodeClass { refHeight: uiConfig.refHeight, minHeightBasedOn: uiConfig.minHeightBasedOn, basinBottomRef: uiConfig.basinBottomRef, + }, + safety:{ + enableDryRunProtection: uiConfig.enableDryRunProtection, + dryRunThresholdPercent: uiConfig.dryRunThresholdPercent, + enableOverfillProtection: uiConfig.enableOverfillProtection, + overfillThresholdPercent: uiConfig.overfillThresholdPercent, + timeleftToFullOrEmptyThresholdSeconds: uiConfig.timeleftToFullOrEmptyThresholdSeconds } }; diff --git a/src/specificClass.js b/src/specificClass.js index 508c8d2..0ed7e98 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -103,10 +103,19 @@ class PumpingStation { return; } - //get threshholds from config - const timeThreshhold = this.config.safety.timeleftToFullOrEmptyThresholdSeconds; //seconds - const triggerHighVol = this.basin.maxVolOverflow * ( this.config.safety.overfillThresholdPercent/100 ); - const triggerLowVol = this.basin.minVol * ( this.config.safety.dryRunThresholdPercent/100 ); + const { + enableDryRunProtection, + dryRunThresholdPercent, + enableOverfillProtection, + overfillThresholdPercent, + timeleftToFullOrEmptyThresholdSeconds + } = this.config.safety || {}; + + const dryRunEnabled = Boolean(enableDryRunProtection); + const overfillEnabled = Boolean(enableOverfillProtection); + const timeProtectionEnabled = timeleftToFullOrEmptyThresholdSeconds > 0; + const triggerHighVol = this.basin.maxVolOverflow * ((Number(overfillThresholdPercent) || 0) / 100); + const triggerLowVol = this.basin.minVol * (1 + ((Number(dryRunThresholdPercent) || 0) / 100)); // trigger conditions for draining if(direction == "draining"){ @@ -116,7 +125,10 @@ class PumpingStation { `direction=${String(direction)}; triggerLowVol=${Number.isFinite(triggerLowVol) ? triggerLowVol.toFixed(2) + ' m3' : 'N/A'}` ); - if( (remainingTime < timeThreshhold && remainingTime !== null) || vol < triggerLowVol || vol == null){ + const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds; + const dryRunTriggered = dryRunEnabled && vol < triggerLowVol; + + if (timeTriggered || dryRunTriggered) { //shut down all downstream or atequipment machines,pumping stations and machine groups Object.entries(this.machines).forEach(([machineId, machine]) => { const position = machine?.config?.functionality?.positionVsParent; @@ -142,7 +154,11 @@ class PumpingStation { `remainingTime=${Number.isFinite(remainingTime) ? remainingTime.toFixed(1) + ' s' : 'N/A'}; ` + `direction=${String(direction)}; triggerHighVol=${Number.isFinite(triggerHighVol) ? triggerHighVol.toFixed(2) + ' m3' : 'N/A'}` ); - if( (remainingTime < timeThreshhold && remainingTime !== null) || vol > triggerHighVol || vol == null){ + + const timeTriggered =timeProtectionEnabled &&remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds; + const overfillTriggered = overfillEnabled && vol > triggerHighVol; + + if (timeTriggered || overfillTriggered) { //shut down all upstream machines,pumping stations and machine groups Object.entries(this.machines).forEach(([machineId, machine]) => { const position = machine?.config?.functionality?.positionVsParent; @@ -476,6 +492,7 @@ class PumpingStation { _onLevelMeasurement(position, value, context = {}) { + this.measurements.type('level').variant('measured').position(position).value(value).unit(context.unit); const levelSeries = this.measurements.type('level').variant('measured').position(position); const levelMeters = levelSeries.getCurrentValue('m'); @@ -1014,7 +1031,7 @@ if (require.main === module) { const inflowSensor = new Measurement(createFlowMeasurementConfig('InfluentFlow', 'in')); const outflowSensor = new Measurement(createFlowMeasurementConfig('PumpDischargeFlow', 'out')); - //station.childRegistrationUtils.registerChild(levelSensor, levelSensor.config.functionality.softwareType); + station.childRegistrationUtils.registerChild(levelSensor, levelSensor.config.functionality.softwareType); //station.childRegistrationUtils.registerChild(inflowSensor, inflowSensor.config.functionality.softwareType); //station.childRegistrationUtils.registerChild(outflowSensor, outflowSensor.config.functionality.softwareType); @@ -1023,7 +1040,7 @@ if (require.main === module) { // Seed initial measurements - //seedSample(levelSensor, 'level', 1.8, 'm'); + seedSample(levelSensor, 'level', 1.8, 'm'); //seedSample(inflowSensor, 'flow', 0.35, 'm3/s'); //seedSample(outflowSensor, 'flow', 0.20, 'm3/s');