diff --git a/pumpingStation.html b/pumpingStation.html
index f5a74d2..97b42ae 100644
--- a/pumpingStation.html
+++ b/pumpingStation.html
@@ -17,16 +17,17 @@
color: "#0c99d9", // color for the node based on the S88 schema
defaults: {
- // Define specific properties
- scaling: { value: false },
- i_min: { value: 0, required: true },
- i_max: { value: 0, required: true },
- i_offset: { value: 0 },
- o_min: { value: 0, required: true },
- o_max: { value: 1, required: true },
- simulator: { value: false },
- smooth_method: { value: "" },
- count: { value: "10", required: true },
+ // Define station-specific properties
+ simulator: { value: false },
+ basinVolume: { value: 1 }, // m³, total empty basin
+ basinHeight: { value: 1 }, // m, floor to top
+ 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
+
+ // Advanced reference information
+ refHeight: { value: "NAP" }, // reference height
+ basinBottomRef: { value: 1 }, // absolute elevation of basin floor
//define asset properties
uuid: { value: "" },
@@ -71,89 +72,42 @@
// Wait for the menu data to be ready before initializing the editor
waitForMenuData();
- // THIS IS NODE SPECIFIC --------------- Initialize the dropdowns and other specific UI elements -------------- this should be derived from the config in the future (make config based menu)
- // Populate smoothing methods dropdown
- const smoothMethodSelect = document.getElementById('node-input-smooth_method');
- const options = window.EVOLV?.nodes?.pumpingStation?.config?.smoothing?.smoothMethod?.rules?.values || [];
+ // NODE SPECIFIC
+ document.getElementById("node-input-basinVolume");
+ document.getElementById("node-input-basinHeight");
+ document.getElementById("node-input-heightInlet");
+ document.getElementById("node-input-heightOutlet");
+ document.getElementById("node-input-heightOverflow");
+ document.getElementById("node-input-refHeight");
+ document.getElementById("node-input-basinBottomRef");
- // Clear existing options
- smoothMethodSelect.innerHTML = '';
-
- // Add empty option
- const emptyOption = document.createElement('option');
- emptyOption.value = '';
- emptyOption.textContent = 'Select method...';
- smoothMethodSelect.appendChild(emptyOption);
-
- // Add smoothing method options
- options.forEach(option => {
- const optionElement = document.createElement('option');
- optionElement.value = option.value;
- optionElement.textContent = option.value;
- optionElement.title = option.description; // Add tooltip with full description
- smoothMethodSelect.appendChild(optionElement);
- });
-
- // Set current value if it exists
- if (this.smooth_method) {
- smoothMethodSelect.value = this.smooth_method;
- }
-
- // --- Scale rows toggle ---
- const chk = document.getElementById('node-input-scaling');
- const rowMin = document.getElementById('row-input-i_min');
- const rowMax = document.getElementById('row-input-i_max');
-
- function toggleScalingRows() {
- const show = chk.checked;
- rowMin.style.display = show ? 'block' : 'none';
- rowMax.style.display = show ? 'block' : 'none';
+ const refHeightEl = document.getElementById("node-input-refHeight");
+ if (refHeightEl) {
+ refHeightEl.value = this.refHeight || "NAP";
}
- // wire and initialize
- chk.addEventListener('change', toggleScalingRows);
- toggleScalingRows();
//------------------- END OF CUSTOM config UI ELEMENTS ------------------- //
},
oneditsave: function () {
const node = this;
- // Validate asset properties using the asset menu
- if (window.EVOLV?.nodes?.pumpingStation?.assetMenu?.saveEditor) {
- success = window.EVOLV.nodes.pumpingStation.assetMenu.saveEditor(this);
- }
+ //window.EVOLV?.nodes?.pumpingStation?.assetMenu?.saveEditor?.(node);
+ window.EVOLV?.nodes?.pumpingStation?.loggerMenu?.saveEditor?.(node);
+ window.EVOLV?.nodes?.pumpingStation?.positionMenu?.saveEditor?.(node);
- // Validate logger properties using the logger menu
- if (window.EVOLV?.nodes?.pumpingStation?.loggerMenu?.saveEditor) {
- success = window.EVOLV.nodes.pumpingStation.loggerMenu.saveEditor(node);
- }
+ //node specific
+ node.refHeight = document.getElementById("node-input-refHeight").value || "NAP";
+ node.simulator = document.getElementById("node-input-simulator").checked;
- // save position field
- if (window.EVOLV?.nodes?.pumpingStation?.positionMenu?.saveEditor) {
- window.EVOLV.nodes.pumpingStation.positionMenu.saveEditor(this);
- }
-
- // Save basic properties
- ["smooth_method"].forEach(
- (field) => (node[field] = document.getElementById(`node-input-${field}`).value || "")
- );
-
- // Save numeric and boolean properties
- ["scaling", "simulator"].forEach(
- (field) => (node[field] = document.getElementById(`node-input-${field}`).checked)
- );
-
- ["i_min", "i_max", "i_offset", "o_min", "o_max", "count"].forEach(
- (field) => (node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0)
- );
-
- // Validation checks
- if (node.scaling && (isNaN(node.i_min) || isNaN(node.i_max))) {
- RED.notify("Scaling enabled, but input range is incomplete!", "error");
- }
+ ["basinVolume","basinHeight","heightInlet","heightOutlet","heightOverflow","basinBottomRef"]
+ .forEach(field => {
+ node[field] = parseFloat(document.getElementById(`node-input-${field}`).value) || 0;
+ });
+ node.refHeight = document.getElementById("node-input-refHeight").value || "";
},
+
});
@@ -161,72 +115,60 @@
diff --git a/src/specificClass.js b/src/specificClass.js
index c759f5a..52c02ea 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -19,19 +19,20 @@ class pumpingStation {
windowSize: this.config.smoothing.smoothWindow
});
- // pumpingStation-specific properties
- this.flowrate = null; // Function to calculate flow rate based on water level rise or fall
- this.timeBeforeOverflow = null; // Time before the basin overflows at current inflow rate
- this.timeBeforeEmpty = null; // Time before the basin empties at current outflow rate
- this.heightInlet = null; // Height of the inlet pipe from the bottom of the basin
- this.heightOutlet = null; // Height of the outlet pipe from the bottom of the basin
- this.heightOverflow = null; // Height of the overflow point from the bottom of the basin
- this.volume = null; // Total volume of water in the basin, calculated from water level and basin dimensions
- this.emptyVolume = null; // Volume in the basin when empty (at level of outlet pipe)
- this.fullVolume = null; // Volume in the basin when at level of overflow point
- this.crossSectionalArea = null; // Cross-sectional area of the basin, used to calculate volume from water level
+ // init basin object in pumping station
+ this.basin = {
+ volumeWater : null,// Total volume of water in the basin, calculated from water level and basin di
+ emptyVolume : null,// Volume in the basin when empty (at level of outlet pipe)
+ fullVolume : null,// Volume in the basin when at level of overflow point
+ crossSectionalArea: null,// Cross-sectional area of the basin, used to calculate volume from water level
+ };
- // Initialize basin-specific properties from config
+ // pumping station specifics
+ this.calculatedFlowrate = null,// Function to calculate flow rate based on water level rise or fall NO MEASUREMENT this is the predicted value which should match a flowrate if we have it and we have to check mass balance ? Look at the pumps connected to the group controller or directly to this node and check incoming vs outgoing?
+ this.timeBeforeOverflow = null,// Time before the basin overflows at current inflow rate at level of heightOutlet
+ this.timeBeforeEmpty = null,// Time before the basin empties at current outflow rate at level of heightInlet
+
+ // Initialize basin-specific properties and calculate used parameters
this.initBasinProperties();
}
@@ -96,6 +97,7 @@ class pumpingStation {
// context handler for pressure updates
updateMeasuredPressure(value, position, context = {}) {
+ // init temp
let kelvinTemp = null;
//pressure updates come from pressure boxes inside the basin they get converted to a level and stored as level measured at position inlet or outlet
@@ -117,20 +119,50 @@ class pumpingStation {
}
this.logger.debug(`Using temperature: ${kelvinTemp} K for calculations`);
const density = coolprop.PropsSI('D','T',kelvinTemp,'P',101325,'Water'); //density in kg/m3 at temp and surface pressure
- const g =
+ const g = 9.80665;
//calculate how muc flow went in or out based on pressure difference
this.logger.debug(`Using pressure: ${pressure} for calculations`);
}
initBasinProperties() {
- // Initialize basin-specific properties from config
- this.heightInlet = this.config.basin.heightInlet || 0; // Default to 0 if not specified
- this.heightOutlet = this.config.basin.heightOutlet || 0; // Default to 0 if not specified
- this.heightOverflow = this.config.basin.heightOverflow || 0; // Default to 0 if not specified
- this.crossSectionalArea = this.config.basin.crossSectionalArea || 1; // Default to 1 m² if not specified
+
+ // Load and calc basic params
+ 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;
+ //calculated params
+ const surfaceArea = volEmptyBasin / heightBasin;
+ const maxVol = heightBasin * surfaceArea; // if Basin where to ever fill up completely this is the water volume
+ const maxVolOverflow = heightOverflow * surfaceArea ; // Max water volume before you start loosing water to overflow
+ const minVol = heightInlet * surfaceArea;
+ const minVolOut = heightOutlet * surfaceArea ; // this will indicate if its an open end or a closed end.
+
+ this.basin.volEmptyBasin = volEmptyBasin ;
+ this.basin.heightBasin = heightBasin ;
+ this.basin.heightInlet = heightInlet ;
+ this.basin.heightOutlet = heightOutlet ;
+ this.basin.heightOverflow = heightOverflow ;
+ this.basin.surfaceArea = surfaceArea ;
+ this.basin.maxVol = maxVol ;
+ this.basin.maxVolOverflow = maxVolOverflow;
+ this.basin.minVol = minVol ;
+ this.basin.minVolOut = minVolOut ;
+
+ this.logger.debug(
+ `Basin initialized | area=${surfaceArea.toFixed(2)} m², max=${maxVol.toFixed(2)} m³, overflow=${maxVolOverflow.toFixed(2)} m³`
+ );
+
+
}
+_calcVolumeFromLevel(level) {
+ const surfaceArea = this.basin.surfaceArea;
+ return Math.max(level, 0) * surfaceArea;
+}
+
getOutput() {
return {