From eabaa1b0bfc775afc3d446d725e8ea69afc8e318 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Tue, 14 Oct 2025 16:32:44 +0200 Subject: [PATCH] writing core class --- pumpingStation.html | 188 +++++++++++++++---------------------------- src/specificClass.js | 68 +++++++++++----- 2 files changed, 115 insertions(+), 141 deletions(-) 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 {