dev-Rene #1

Merged
renederen merged 21 commits from dev-Rene into main 2025-12-08 10:00:07 +00:00
3 changed files with 47 additions and 63 deletions
Showing only changes of commit 5a575a29fe - Show all commits

View File

@@ -24,6 +24,7 @@
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
minHeightBasedOn: { value: "outlet" }, // basis for minimum height check: inlet or outlet
// Advanced reference information
refHeight: { value: "NAP" }, // reference height
@@ -86,6 +87,10 @@
refHeightEl.value = this.refHeight || "NAP";
}
const minHeightBasedOnEl = document.getElementById("node-input-minHeightBasedOn");
if (minHeightBasedOnEl) {
minHeightBasedOnEl.value = this.minHeightBasedOn;
}
//------------------- END OF CUSTOM config UI ELEMENTS ------------------- //
},
@@ -98,6 +103,7 @@
//node specific
node.refHeight = document.getElementById("node-input-refHeight").value || "NAP";
node.minHeightBasedOn = document.getElementById("node-input-minHeightBasedOn").value || "outlet";
node.simulator = document.getElementById("node-input-simulator").checked;
["basinVolume","basinHeight","heightInlet","heightOutlet","heightOverflow","basinBottomRef"]
@@ -151,6 +157,13 @@
<hr>
<!-- Reference data -->
<div class="form-row">
<label for="node-input-minHeightBasedOn"><i class="fa fa-arrows-v"></i> Minimum Height Based On</label>
<select id="node-input-minHeightBasedOn" style="width:60%;">
<option value="inlet">Inlet Elevation</option>
<option value="outlet">Outlet Elevation</option>
</select>
</div>
<div class="form-row">
<label for="node-input-refHeight"><i class="fa fa-map-marker"></i> Reference height</label>
<select id="node-input-refHeight" style="width:60%;">

View File

@@ -63,6 +63,7 @@ class nodeClass {
},
hydraulics:{
refHeight: uiConfig.refHeight,
minHeightBasedOn: uiConfig.minHeightBasedOn,
basinBottomRef: uiConfig.basinBottomRef,
}
};
@@ -191,13 +192,9 @@ class nodeClass {
this.node.on('input', (msg, send, done) => {
switch (msg.topic) {
//example
/*case 'simulator':
this.source.toggleSimulation();
case 'changemode':
this.source.changeMode(msg.payload);
break;
default:
this.source.handleInput(msg);
break;
*/
case 'registerChild':
// Register this node as a child of the parent node
const childId = msg.payload;

View File

@@ -106,7 +106,7 @@ class PumpingStation {
//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.minVolOut * ( this.config.safety.dryRunThresholdPercent/100 );
const triggerLowVol = this.basin.minVol * ( this.config.safety.dryRunThresholdPercent/100 );
// trigger conditions for draining
if(direction == "draining"){
@@ -165,10 +165,10 @@ class PumpingStation {
}
changeMode(newMode){
if ( this.config.control.allowedModes.has(newMode) ){
const currentMode = this.mode;
this.logger.info(`Control mode changing from ${currentMode} to ${newMode}`);
this.mode = newMode;
this.logger.info(`Control mode changed from ${currentMode} to ${newMode}`);
}
else{
this.logger.warn(`Attempted to change to unsupported control mode: ${newMode}`);
@@ -176,48 +176,6 @@ class PumpingStation {
}
/* old levelcontrol
async _controlLevelBased(snapshot, remainingTime) {
// current volume as a percentage of usable capacity
const vol = this._resolveVolume(snapshot);
if (vol == null) {
this.logger.warn('No valid volume found for level-based control');
return;
}
const { thresholds, timeThresholdSeconds } = this.config.control.levelbased;
const percentFull = (vol / this.basin.maxVolOverflow) * 100;
// pick thresholds that are now crossed but were not crossed before
const newlyCrossed = thresholds.filter(t => percentFull >= t && !this._levelState.crossed.has(t));
this.logger.debug(`Level-based control: vol=${vol.toFixed(2)} m³ (${percentFull.toFixed(1)}%), newly crossed thresholds: [${newlyCrossed.join(', ')}]`);
if (!newlyCrossed.length) return;
const now = Date.now();
if (!this._levelState.dwellUntil) {
this._levelState.dwellUntil = now + timeThresholdSeconds * 1000;
this.logger.debug(`Level-based control: waiting ${timeThresholdSeconds}s before acting`);
return;
}
this.logger.debug(`Level-based control: dwelling for another ${Math.round((this._levelState.dwellUntil - now) / 1000)} seconds`);
if (now < this._levelState.dwellUntil) return; // still waiting
this._levelState.dwellUntil = null; // dwell satisfied, let pumps start
for (const threshold of newlyCrossed) {
const nextMachine = this._nextIdleMachine();
if (!nextMachine) break;
this._levelState.crossed.add(threshold);
this.logger.info(
`level-based control: threshold ${threshold}% reached, starting "${nextMachine.config.general.name}" (vol=${vol.toFixed(2)} m³)`
);
await nextMachine.handleInput('parent', 'execSequence', 'startup');
}
}
*/
async _controlLevelBased(snapshot, remainingTime) {
@@ -337,12 +295,19 @@ class PumpingStation {
case "flowbased":
this._controlFlowBased();
break;
case "manual":
this._manualControl();
break;
default:
this.logger.warn(`Unsupported control mode: ${mode}`);
}
}
_manualControl() {
// Nothing to do - manual mode
}
//calibrate the predicted volume to a known value
calibratePredictedVolume(calibratedVol, timestamp = Date.now()){
@@ -835,17 +800,22 @@ class PumpingStation {
initBasinProperties() {
const volEmptyBasin = this.config.basin.volume;
const heightBasin = this.config.basin.height;
const heightInlet = this.config.basin.heightInlet;
const heightOutlet = this.config.basin.heightOutlet;
const heightOverflow = this.config.basin.heightOverflow;
//is min height based on inlet or outlet elevation?
const minHeightBasedOn = this.config.hydraulics.minHeightBasedOn;
const surfaceArea = volEmptyBasin / heightBasin;
const maxVol = heightBasin * surfaceArea;
const maxVolOverflow = heightOverflow * surfaceArea;
const minVol = heightOutlet * surfaceArea;
const minVolOut = heightInlet * surfaceArea;
const volEmptyBasin = this.config.basin.volume; //volume when basin is empty
const heightBasin = this.config.basin.height; //total height of basin
const heightInlet = this.config.basin.heightInlet; //height at which inlet is located
const heightOutlet = this.config.basin.heightOutlet; //height at which outlet is located
const heightOverflow = this.config.basin.heightOverflow; //height at which overflow occurs
const surfaceArea = volEmptyBasin / heightBasin; //assume uniform cross section for now
const maxVol = heightBasin * surfaceArea; //maximum volume when basin is full
const maxVolOverflow = heightOverflow * surfaceArea; //maximum volume before overflow occurs
const minVolOut = heightOutlet * surfaceArea; //minimum volume to have outlet just above basin bottom
const minVolIn = heightInlet * surfaceArea; //minimum volume to have inlet just above waterline
const minVol = (minHeightBasedOn === "inlet") ? minVolIn : minVolOut;
this.logger.debug(`Basin min volume based on ${minHeightBasedOn} : ${minVol.toFixed(2)} m3`);
this.basin = {
volEmptyBasin,
@@ -856,11 +826,13 @@ class PumpingStation {
surfaceArea,
maxVol,
maxVolOverflow,
minVolIn,
minVolOut,
minVol,
minVolOut
minHeightBasedOn
};
this.measurements.type('volume').variant('predicted').position('atEquipment').value(heightOutlet).unit('m3');
this.measurements.type('volume').variant('predicted').position('atEquipment').value(minVol).unit('m3');
this.logger.debug(
`Basin initialized | area=${surfaceArea.toFixed(2)} m2, max=${maxVol.toFixed(2)} m3, overflow=${maxVolOverflow.toFixed(2)} m3`
@@ -899,6 +871,8 @@ class PumpingStation {
output.minVol = this.basin.minVol;
output.maxVolOverflow = this.basin.maxVolOverflow;
output.minVolOut = this.basin.minVolOut;
output.minVolIn = this.basin.minVolIn;
output.minHeightBasedOn = this.basin.minHeightBasedOn;
return output;
}
}