From 71d600b4ddf42bf1a4e308a10df0da73665d755d Mon Sep 17 00:00:00 2001 From: vps1_gitea_admin Date: Thu, 12 Mar 2026 15:20:34 +0000 Subject: [PATCH] old version --- diffuser.html | 89 +++++++ diffuser.js | 162 +++++++++++++ diffuser_class.js | 589 ++++++++++++++++++++++++++++++++++++++++++++++ graph.js | 152 ++++++++++++ package.json | 24 ++ 5 files changed, 1016 insertions(+) create mode 100644 diffuser.html create mode 100644 diffuser.js create mode 100644 diffuser_class.js create mode 100644 graph.js create mode 100644 package.json diff --git a/diffuser.html b/diffuser.html new file mode 100644 index 0000000..bfbb310 --- /dev/null +++ b/diffuser.html @@ -0,0 +1,89 @@ + + + + + + diff --git a/diffuser.js b/diffuser.js new file mode 100644 index 0000000..bd2f31a --- /dev/null +++ b/diffuser.js @@ -0,0 +1,162 @@ +module.exports = function (RED) { + function diffuser(config) { + //create node + RED.nodes.createNode(this, config); + + //call this => node so whenver you want to call a node function type node and the function behind it + var node = this; + + //fetch class + var Diffuser = require("./dependencies/diffuser_class"); + + //make new class on creation to work with. + var diffuser = new Diffuser(); + + //fetch name from node into the measurement code + diffuser.name = config.name; + diffuser.number = config.number; + diffuser.id = diffuser.name+diffuser.number; + diffuser.i_n_elements = config.i_elements; + diffuser.i_diff_density = Number(config.i_diff_density); + diffuser.i_m_water = config.i_m_water; + + // create internal vars + this.interval_id = null; + let internalTickRate = 1; + function update_node_state() { + //alarm has prio over warning so comes first in the if else statement + if (diffuser.alarm.state == true) { + //display status + node.status({ fill: "red", shape: "dot", text: diffuser.alarm.text[0] }); + } + else if(diffuser.warning.state == true ){ + //display status + node.status({ fill: "yellow", shape: "dot", text: diffuser.warning.text[0] }); + } + else if(diffuser.idle == true){ + node.status({ fill: "gray", shape: "dot", text: diffuser.o_kgo2_h + " Kg o2 / h"}); + } + else{ + node.status({fill: "green", shape: "dot", text: diffuser.o_kgo2_h + " Kg o2 / h"}); + } + } + + //update on creation + update_node_state(); + + function send_output(){ + + //define empty msgs + let msgs = []; + + let dynList = { + iPressure: diffuser.i_pressure, + iMWater: diffuser.i_m_water, + iFlow: diffuser.i_flow, + nFlow: diffuser.n_flow, + oOtr: diffuser.o_otr, + oPLoss: diffuser.o_p_total, + oKgo2H: diffuser.o_kgo2_h, + oFlowElement: diffuser.o_flow_element, + efficiency: diffuser.o_combined_eff, + //threshold: diffuser.threshold //EXPERIMENTAL! + } + + let specList = { + name: diffuser.name, + number: diffuser.number, + //supplier: diffuser.supplier, + //type: diffuser.type, + density: diffuser.i_diff_density, + nElements: diffuser.i_n_elements, + alfaF: diffuser.i_alfa_factor + } + + msgs[0] = {topic: "kgo2/h" , payload: {kgo2h: diffuser.o_kgo2_h , tot_p_loss : diffuser.o_p_total} ,id: diffuser.id }; + msgs[1] = {topic: "object", payload : diffuser}; + msgs[2] = { + topic:"outputdbase", + payload:[ + + { + measurement: diffuser.name+diffuser.number, + fields : dynList, + tags: { + group: "values", + }, + timestamp: new Date() + }, + { + measurement: diffuser.name+diffuser.number, + fields : specList, + tags: { + group: "specs", + }, + timestamp: new Date() + }, + ] + } // output to broker + + //send outputs + node.send(msgs); + } + + //never ending functions + function tick(){ + update_node_state(); + send_output(); + + } + + // register child on first output this timeout is needed because of node - red stuff + setTimeout( + () => { + + /*---execute code on first start----*/ + let msgs = []; + msgs[3] = { topic : "registerChild" , payload: diffuser }; + //send msg + this.send(msgs); + }, + 100 + ); + + //declare refresh interval internal node + setTimeout( + () => { + /*---execute code on first start----*/ + this.interval_id = setInterval(function(){ tick() },(internalTickRate * 1000)) + }, + 1000 + ); + + + //-------------------------------------------------------------------->>what to do on input + node.on("input", function (msg,send,done) { + + //change density of diffusers + if(msg.topic == "density"){ + diffuser.i_diff_density = Number(msg.payload); + } + + //change input flow + if(msg.topic == "air_flow"){ + diffuser.i_flow = Number(msg.payload); + } + + //change water height + if(msg.topic == "height_water"){ + diffuser.i_m_water = Number(msg.payload); + } + + done(); + + }); + + // tidy up any async code here - shutdown connections and so on. + node.on('close', function() { + clearTimeout(this.interval_id); + }); + } + RED.nodes.registerType("diffuser", diffuser); +}; \ No newline at end of file diff --git a/diffuser_class.js b/diffuser_class.js new file mode 100644 index 0000000..f240940 --- /dev/null +++ b/diffuser_class.js @@ -0,0 +1,589 @@ +/* +Copyright: +Year : (c) 2023 +Author : Rene De Ren +Contact details : zn375ix3@gmail.com +Location : The Netherlands + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The author shall be notified of any and all improvements or adaptations this software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +//Diffised Aeration Devices (example fine bubble aeration) + +class Diffuser{ + + /*------------------- Construct and set vars -------------------*/ + constructor(supplier,type) { + + this.init = false; + + /* --------------------load depenencies ------------- */ + this.Interpolation = require('../../predict/dependencies/interpolation') ; //load class + this.interpolation = new this.Interpolation; //general use of interpolation object + + this.Fysics = require('./../../convert/dependencies/fysics') ; //load class + this.fysics = new this.Fysics; //general use of fysics + + this.Graph = require('./graph') ; //load class + this.graph = new this.Graph; //general use of fysics + + //load default pressure curve depending on type + this.specs = this.load_specs(); + + //after loading specs load curve builder + this.Predict = require('../../predict/dependencies/predict_class') ; //load class + this.predict_otr = new this.Predict(); //load otr curve + this.predict_otr.i_curve = this.specs.otr_curve; + + this.predict_p = new this.Predict(); + this.predict_p.i_curve = this.specs.p_curve; // load pressure curve + this.predict_p.i_f = 0; // set f to dim 0 because there is no other dim for pressure + + this.predictO2saturation = new this.Predict(); + this.predictO2saturation.i_curve = this.fysics.o2Solubility; // load solubility curve for o2 in water + + + //load converter ./dependencies/index + this.convert = require('./../../convert/dependencies/index'); + + /* ------------ static vars ---------------*/ + this.name = ""; // user defined name + this.number = 0 ; // user defined number + this.id = ""; // unique id from node red? + this.idle = true; // is this idle (not outputting kg o2 / h) + this.desc = "diffuser"; // description of the current object + this.supplier = supplier; // supplier of diffuser + this.type = type; // type of diffuser + + /* ---------- Inputs -------------- */ + this.i_flow = 0 ; // input actual flow rate expressed in Nm3/h + this.i_diff_density = 5; // intput actual density of diffusers + this.i_pressure = 0 ; // this is pressure in header expressed in mbar to calculated air density. + this.i_local_atm_pressure = 1013,25 ; // local atm pressure in mbar + this.i_alfa_factor = 0.7 ; // stnd alfa factor + this.i_water_density = 997 ; // this.water_molar_mass * this.num_moles_water // water density in kg/m3; + this.i_m_water = 0; // input actual height in meter water ABOVE the diffuser + this.i_n_elements = 1; // input for amount of diffusers we need to divide the flow over to get the nm per element + + /*---------calculated parameters ----------*/ + this.n_flow = 0 ; // converted input to normalized conditions of the diffusers flow parameter (x) + this.n_kg = this.fysics.calc_air_dens(this.specs.units.Nm3.pressure,this.specs.units.Nm3.RH,this.specs.units.Nm3.temp); // calculated normalized kg for air density depending on specs kg /m3 + + /* ---------- Outputs -------------- */ + this.o_otr = 0; // predicted oxygen transfer rate + this.o_p_flow = 0; // predicted pressure loss over flow rate + this.o_p_water = 0; // predicted pressure loss over meter water above diffuser + this.o_p_total = 0; // predicted total pressure loss + this.o_kg = 0; // predicted kg of air + this.o_kg_h = 0; // predicted kg per hour + this.o_kgo2_h = 0; // predicted oxygen transfer per hour + this.o_kgo2_h_min = 0; // predicted min kgo2 / hour for zone controller + this.o_kgo2_h_max = 0; // predicted max kgo2 / hour for zone controller + this.o_kgo2 = 0 ; // current oxygen input + this.o_flow_element = 0 ; // flow per element + this.o_otr_max = 0; // store max otr for easy access + this.o_otr_min = 0; // store min otr for easy access + this.o_combined_eff = 0; // combined efficiency + this.o_histogram = 0; // keep track of histogram x % of time for OTR (efficiency) over a max historical value ? or a counter which gets higher over time without. + this.o_slope = 0; + + /*-----------alarms---------------*/ + //putting alarms in 1 array always gets all the alarms that are currently active or inactive + //an alarm is a trigger to stop any process feeding the diffusers + this.alarm = { + text:[], + state:false, + flow:{ + min:{state:false,hyst:10}, //when there isnt enough flow to ensure the correct distribution of air + max:{state:false,hyst:10},//when there is to much flow per diffuser and exceeds the suppliers limits + }, + pressure:{ + min:{state:false,hyst:10}, // see min flow + max:{state:false,hyst:10}, // see max flow + }, + }; + + /*-----------warnings---------------*/ + //putting alarms in 1 array always gets all the alarms that are currently active or inactive + //a warning is a trigger to alert users on eradic behavior or some other activity that might cause damage in the future so users can investigate what is going on + this.warning = { + text:[], // fill this with the warnings we want to display + state:false, + deviation:{ + pressure:{state:false,hyst:2},//when there is a deviation versus expected pressures of the baseline that exceeds a hyst of x % of deviation before becomming true + }, + flow:{ + min:{state:false,hyst:2}, //when there isnt enough flow to ensure the correct distribution of air + max:{state:false,hyst:2},//when there is to much flow per diffuser and exceeds the suppliers limits + }, + pressure:{ + min:{state:false,hyst:2}, // see min flow + max:{state:false,hyst:2}, // see max flow + }, + }; + + /*-------------error handling -----------*/ + this.error = { + text:[], + state:false, + } + + this.init = true; + } + + /*------------------- GETTER/SETTERS -------------------*/ + + set i_n_elements(x){ + + //check if this input is a number + if(Number.isNaN(x)){ + this.error.state = true; + this.error.text.push("number of elements not of type number"); + } + + // you cant have partial elements + x = Math.round(x); + + //check if init has allready been exectued + if ( x <= 0 ) { + this.error.state = true; + this.error.text.push("0 elements input"); + } + + //set densi3ty to value + this._i_n_elements = x; + + } + + get i_n_elements(){ + return this._i_n_elements; + } + + get i_diff_density(){ + return this._i_diff_density; + } + + set i_diff_density(value){ + + //set densi3ty to value + this._i_diff_density = value; + + //check if init has allready been exectued + if(this.init == true){ + //submit new value to prediction + this.predict_otr.i_f = value; + //refresh predictions + this.o_otr = this.predict_otr.o_y; + + } + + } + + set o_otr(value){ + + //set density to value + this._o_otr = Math.round( value * 100 ) / 100; + + //check if init has allready been exectued + if(this.init == true){ + + //current output in kg o2 / h + this.o_kgo2_h = this.convert ( this.o_otr * this.n_flow * this.i_m_water ).from('g').to('kg') ; + + /*make max and min calculations to use them in a kg load zone controller*/ + this.o_kgo2_h_min = this.convert ( this.o_otr_min * this.n_flow * this.i_m_water ).from('g').to('kg') ; + this.o_kgo2_h_max = this.convert ( this.o_otr_max * this.n_flow * this.i_m_water ).from('g').to('kg') ; + + //divide by 3600 to get the current ouput in kg + this.o_kgo2 = this.o_kgo2_h / 3600 ; + + } + + } + + get o_otr(){ + return this._o_otr; + } + + //set meter water column above the diffuser in meters + set i_m_water(value){ + + //set density to value + this._i_m_water = value; + + //convert height to pressure in mbar + this.o_p_water = this.fysics.heigth_to_pressure(this.i_water_density,value); + this.o_p_total = this.o_p_water + this.o_p_flow ; + + //recalc values + this.i_flow = this.i_flow; + } + + get i_m_water(){ + return this._i_m_water + } + + set o_kgo2_h(value){ + this._o_kgo2_h = Math.round( value * 100 ) / 100; + } + + get o_kgo2_h(){ + return this._o_kgo2_h; + } + + set o_p_total(value){ + this._o_p_total = Math.round( value * 100 ) / 100; + } + + get o_p_total(){ + return this._o_p_total; + } + + set o_p_flow(value){ + + //set density to value + this._o_p_flow = Math.round( value * 100 ) / 100; + + //recalc total + this.o_p_total = this.o_p_water + value ; + } + + get o_p_flow(){ + return this._o_p_flow; + } + + set i_flow(value){ + //set density to value + this._i_flow = value; + + if(this.init == true){ + //any flow smaller or equal to zero means diffusers are not being supplied with air and are not active. + if(value > 0){ + + // idle to off + this.idle = false; + + //calc otr and p + this.calc_otr_p(value); + + } + else{ + this.idle = true; + this.o_otr = 0; + this.o_p_flow = 0; + this.o_flow_element = 0; + this.o_p_total = 0; + } + } + } + + get i_flow(){ + return this._i_flow; + } + + + /*------------------------- functions ----------------------------*/ + //a diffuser has a combined efficiency + combine_eff(o_otr,o_otr_min,o_otr_max,o_p_flow,o_p_min,o_p_max){ + //highest otr is best efficiency possible + let eff1 = this.interpolation.interpolate_lin_single_point(o_otr,o_otr_min,o_otr_max,0,1); + //lowest pressure is best pressure possible + let eff2 = this.interpolation.interpolate_lin_single_point(o_p_flow,o_p_min,o_p_max,1,0); + + let result = eff1 * eff2 * 100 ; + + return result; + + } + + // do all actions in order to calculate the outputs for flow and pressure related stuf + calc_otr_p(flow){ + //convert to kg using pressure,rh,temperature we need to get actual data to do this else we assume that normalized equals site + //total input pressure equals atm pressure + measured pressure in system + let tot_i_pressure = this.convert(this.i_local_atm_pressure + this.i_pressure).from('mbar').to('bar'); //calculated output in kg air + this.o_kg = this.fysics.calc_air_dens(tot_i_pressure,0,20); + this.o_kg_h = this.o_kg * flow ; + + //convert this to the normal m3 of diffuser density data + this.n_flow = ( this.o_kg / this.n_kg ) * flow; + + //calculate how much flow per element + this.o_flow_element = Math.round( ( this.n_flow / this.i_n_elements ) * 100 ) / 100 ; + + // input x values in predictors (we could make functions and return outputs dunno whats better) + this.predict_otr.i_x = this.o_flow_element; + this.predict_p.i_x = this.o_flow_element; + + //store otr max and min + this.o_otr_min = this.predict_otr.c_fxy_y_min; + this.o_otr_max = this.predict_otr.c_fxy_y_max; + + //store min and max pressure + this.o_p_min = this.predict_p.c_fxy_y_min; + this.o_p_max = this.predict_p.c_fxy_y_max; + + // predict oxygen transfer rate + this.o_otr = this.predict_otr.o_y; + // predict pressure output of diffuser + this.o_p_flow = this.predict_p.o_y; + + //calculated combined efficiency in % where 100 % is ideal efficieny and 0 is worst + this.o_combined_eff = Math.round(this.combine_eff(this.o_otr,this.o_otr_min,this.o_otr_max,this.o_p_flow,this.o_p_min,this.o_p_max) * 100 ) /100; + + //slope otr curve + this.graph.calc(this.predict_otr.i_x,this.predict_otr.o_y); + this.o_slope = this.graph.slope; + + //go through functions + this.warning_check(); + this.alarm_check(); + + } + + // calculate average saturation over the water depth of the diffuser + calcAvgSolubility(temp){ + + //calculate average pressure in water column + let avgPMbar = ( this.o_p_water + this.i_local_atm_pressure ) / 2; + let avgPBar = this.convert(avgPMbar).from('mbar').to('bar'); + + // input x values in predictors (we could make functions and return outputs dunno whats better) + this.predictO2saturation.i_f = avgPBar; // set f to temperature + this.predictO2saturation.i_x = temp; // set temperature + + // predict oxygen transfer rate + return this.predictO2saturation.o_y; //return average saturation value + + + } + + warning_check(){ + + //warnings do not require to be resetted. we need to log them somewhere in the database so its easy to check if these values are being exceeded + + //empty tekst before filling again and always reset state + this.warning.text = []; + this.warning.state = false; + + /* ------------------ Flow warnings ------------------*/ + //define hyst for flow + let min_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.warning.flow.min.hyst / 100); + let max_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.warning.flow.max.hyst / 100); + + //min flow + if(this.o_flow_element < this.predict_p.c_fxy_x_min - min_flow_hyst ){ + this.warning.flow.min.state = true; + this.warning.state = true; + this.warning.text.push(" Warning : input flow " + this.o_flow_element + " is less then minimum allowed flow of " + ( this.predict_p.c_fxy_x_min - min_flow_hyst )); + } + else{ //auto reset + this.warning.flow.min.state = false; + } + + //max flow + if(this.o_flow_element > this.predict_p.c_fxy_x_max + max_flow_hyst ){ + this.warning.flow.max.state = true; + this.warning.state = true; + this.warning.text.push("Warning input flow " + this.o_flow_element + " is exceeding nominal value of " + ( this.predict_p.c_fxy_x_max + max_flow_hyst )); + } + else{ //auto reset + this.warning.flow.max.state = false; + } + } + + alarm_check(){ + + //warnings do not require to be resetted. we need to log them somewhere in the database so its easy to check if these values are being exceeded + + //empty tekst before filling again and reset general state + this.alarm.text = []; + this.alarm.state = false; + + /* ------------------ Flow warnings ------------------*/ + //define hyst for flow + let min_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.alarm.flow.min.hyst / 100); + let max_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.alarm.flow.max.hyst / 100); + + //min flow + if(this.o_flow_element < this.predict_p.c_fxy_x_min - min_flow_hyst ){ + this.alarm.flow.min.state = true; + this.alarm.state = true; + this.alarm.text.push("Alarm input flow " + this.o_flow_element + " is less then minimum allowed flow of " + ( this.predict_p.c_fxy_x_min - min_flow_hyst )); + } + else{ //auto reset + this.warning.flow.min.state = false; + } + + //max flow + if(this.o_flow_element > this.predict_p.c_fxy_x_max + max_flow_hyst ){ + this.alarm.flow.max.state = true; + this.alarm.state = true; + this.alarm.text.push("Alarm input flow " + this.o_flow_element + " is exceeding an absolute max value of " + ( this.predict_p.c_fxy_x_max + max_flow_hyst )); + } + else{ //auto reset + this.alarm.flow.max.state = false; + } + + + } + + //fetch curve and load it into object var + load_specs(){ + + //for now hardcoded will do as example from sulzer + let Sulzerspecs = { + supplier : "sulzer", + type : "pik300", + units:{ + Nm3: { "temp": 20, "pressure" : 1.01325 , "RH" : 0 }, + t_otr_curve : { f : "diffuser density in % ", x : "Nm3/h", y: { o2_weight : "g", flow : "Nm3/h" , height: "m" } } , // according to DIN + t_p_curve : { x : "flow", y : "mbar" } + }, + otr_curve: { + 5: // diffuser density expressed in % + { + x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // flow expressed in normal m3/h + y:[28, 27, 26, 25, 24.5, 24, 23.5, 23.2, 23, 22.9], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m + }, + 10: // diffuser density expressed in % + { + x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // flow expressed in normal m3/h + y:[31, 29.5, 28.5, 27, 26, 25.5, 25, 24.7, 24.2, 24], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m + } + }, + p_curve:{ //difuser pressure loss curve + 0: // if curve doesnt have more than 1 dimension just fill in zero here or whatever + { + x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // Flow expressed in nm3/h + y:[25,26,28,30,35,38,42,48,55,68] //pressure expressed in mbar + } + }, + solubility_curve:{ + 1: // abs bar + { + x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius + y:[14.6,12.8,11.3,10.1,9.1,8.3,7.6,7,6.5,6,5.6], // mg/l + }, + 2: // abs bar + { + x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius + y:[29.2,25.5,22.6,20.2,18.2,16.5,15.2,14,12.9,12,11.3], // mg/l + }, + 4: // abs bar + { + x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius + y:[58.4,51.1,45.1,40.3,36.4,33.1,30.3,27.9,25.9,24,22.7], // mg/l + }, + } + } + + //for now hardcoded will do as example from sulzer + let specs = { + supplier : "GVA", + type : "ELASTOX-R", + units:{ + Nm3: { "temp": 20, "pressure" : 1.01325 , "RH" : 0 }, + t_otr_curve : { f : "diffuser density in % ", x : "Nm3/h", y: { o2_weight : "g", flow : "Nm3/h" , height: "m" } } , // according to DIN + t_p_curve : { x : "flow", y : "mbar" } + }, + otr_curve: { + 2.4: // diffuser density expressed in % + { + x:[2,3,4,5,6,7,8,9,10], // flow expressed in normal m3/h + y:[26,25,24,23.5,23,22.75,22.5,22.25,22], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m + } + }, + p_curve:{ //difuser pressure loss curve + 0: // if curve doesnt have more than 1 dimension just fill in zero here or whatever + { + x:[2,3,4,5,6,7,8,9,10,11,12], // Flow expressed in nm3/h + y:[40,42.5,45,47.5,50,51.5,53,54.5,56,57.5,59] //pressure expressed in mbar + } + } + } + + return specs; + } + + + //testing converter function + converttest(){ + let test = this.convert(1).from('l').to('ml'); + return test ; + } + + +} // end of class + +/* +var diffuser = new Diffuser("sulzer","PIK300"); + +// define inputs for diffuser +diffuser.i_m_water = 5; // set water column above diffuser +diffuser.i_n_elements = 1; +diffuser.i_diff_density = 5; // set density in % +diffuser.i_flow = 4; // set flow rate in m3/h + +// outputs are +console.log("--------------Inputs--------------") +console.log("Diffuser density : "+ diffuser.i_diff_density + " %"); +console.log("Flow : " + diffuser.i_flow + " m3/h" ); +console.log("Flow rate per element : " + diffuser.o_flow_element + " m3/h"); +console.log("Number of elements : " + diffuser.i_n_elements ); +console.log("--------------Outputs--------------") +console.log("converted input flow to diffuser normalized flow : " + diffuser.n_flow + " m3/h "); +console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter"); +console.log("Pressure loss over diffusers : " + diffuser.o_p_flow + " mbar" ); +console.log("Pressure loss over water column:" + diffuser.o_p_water + " mbar" ); +console.log("Total pressure loss : " + diffuser.o_p_total + " mbar"); +console.log("predicted diffuser oxygen input for bio : " + diffuser.o_kgo2_h + " kg o2 / h"); +console.log("Predicted actual input o2 : " + diffuser.o_kgo2 + " kg o2 / s " ); +console.log("max otr " + diffuser.o_otr_max); +console.log("displaying warnings : " + JSON.stringify(diffuser.warning.text) ); + +//change flow +diffuser.i_diff_density = 5; // set density in % +//diffuser.i_flow = 4; + +console.log("--------------Inputs--------------") +console.log("Diffuser density : "+ diffuser.i_diff_density + " %"); +console.log("Flow : " + diffuser.i_flow + " m3/h" ); +console.log("--------------Outputs--------------") +console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter"); +console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" ); +console.log("slope: " + diffuser.o_slope ); + +diffuser.i_flow = 5; +console.log("--------------Inputs--------------") +console.log("Diffuser density : "+ diffuser.i_diff_density + " %"); +console.log("Flow : " + diffuser.i_flow + " m3/h" ); +console.log("--------------Outputs--------------") +console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter"); +console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" ); +console.log("slope: " + diffuser.o_slope ); + +diffuser.i_diff_density = 0; + +console.log("--------------Inputs--------------") +console.log("Diffuser density : "+ diffuser.i_diff_density + " %"); +console.log("Flow : " + diffuser.i_flow + " m3/h" ); +console.log("--------------Outputs--------------") +console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter"); +console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" ); +// +//*/ +module.exports = Diffuser; diff --git a/graph.js b/graph.js new file mode 100644 index 0000000..81864bd --- /dev/null +++ b/graph.js @@ -0,0 +1,152 @@ +/* +Copyright: +Year : (c) 2023 +Author/licensor : Rene De Ren +Contact details : zn375ix3@gmail.com +Location : The Netherlands +Licensee : Waterschap brabantse delta +Address: Bouvignelaan 5, 4836 AA Breda + +Permission is hereby granted, to the licensee , to use this software only for the purposes of testing it in its R&D lab. + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The author shall be notified of any and all improvements or adaptations this software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +*/ + +//A class to interact and manipulate machines with a parabolic curve +class Graph { + /*------------------- Construct and set vars -------------------*/ + constructor() { + + // current x and y + this.x = 0; + this.y = 0; + + //previous y and x + this.y_prev = 0; + this.x_prev = 0; + + //slope of current value + this.slope = 0; + + + } + + set slope(value){ + this._slope = Math.round( value * 100 ) / 100; + } + get slope(){ + return this._slope; + } + + calc(x,y){ + + //store old values + this.x_prev = this.x; + this.y_prev = this.y; + + //store new values + this.x = x; + this.y = y; + + //calc slope + this.slope = this.calc_slope(this.x,this.y,this.x_prev,this.y_prev); + + + } + + calc_slope(x,y,x_prev,y_prev){ + + let slope = 0; + let incline = false; + let decline = false; + + if( x_prev > x){ + decline = true; + incline = false; + } + else if( x_prev < x ){ + decline = false; + incline = true; + } + + //determine decline / incl + if( (!decline && !incline) ){ + slope = 0; + } + else if(decline){ + slope = -( (y - y_prev) / ( x - x_prev) ) ; + } + else if(incline){ + slope = ( (y - y_prev) / ( x - x_prev) ) ; + } + + return slope; + } + + // 1 a dimensional peak ( THIS WILL FIND ONLY 1 PEAK!! so assuming the array only has one !) + one_dim_peak_finder(array){ + + //define array length + let array_length = array.length; + //start position in array is middle + let recursive_pos = array_length/2; + //define end conditions + let max_iterations = 100; + let done = false; + let iteration = 0; + let peak_found = false; + + while(!done){ + + //find peak going left + if( array[recursive_pos-1] > array[recursive_pos] ){ + //calc new pos + recursive_pos = (recursive_pos-1) / 2; + } + //find peak going right + else if( array[recursive_pos+1] > array[recursive_pos] ){ + //calc new pos + recursive_pos = (recursive_pos+1) / 2; + } + //found peak! + else{ + done = true; + peak_found = true; + position_peak = recursive_pos; + } + + //end prematurely + if(max_iterations >= iteration){ + done = true; + peak_found = false; + position_peak = null; + } + iteration++; + } + //build response + let obj_peak = { found:peak_found , value_of_peak: array[position_peak] , position_peak:position_peak }; + + return obj_peak; + } + + +} // end of class + + +/* +var graph = new Graph(); + + +//*/ + +module.exports = Graph; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..b316fad --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "diffuser", + "version": "0.0.1", + "description": "Control module diffuser", + "main": "diffuser", + "scripts": { + "test": "diffuser" + }, + "repository": { + "type": "git", + "url": "diffuser" + }, + "keywords": [ + "diffuser" + ], + "author": "Rene De Ren", + "license": "MIT", + "node-red" : { + "nodes": { + "diffuser": "diffuser.js" + + } + } +} \ No newline at end of file