upgrades
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* Encapsulates all node logic in a reusable class. In future updates we can split this into multiple generic classes and use the config to specifiy which ones to use.
|
||||
* This allows us to keep the Node-RED node clean and focused on wiring up the UI and event handlers.
|
||||
*/
|
||||
const { outputUtils, configManager } = require('generalFunctions');
|
||||
const { outputUtils, configManager, convert } = require('generalFunctions');
|
||||
const Specific = require("./specificClass");
|
||||
|
||||
class nodeClass {
|
||||
@@ -69,6 +69,10 @@ class nodeClass {
|
||||
samplingtime: Number(uiConfig.samplingtime),
|
||||
minVolume: Number(uiConfig.minvolume),
|
||||
maxWeight: Number(uiConfig.maxweight),
|
||||
nominalFlowMin: Number(uiConfig.nominalFlowMin),
|
||||
flowMax: Number(uiConfig.flowMax),
|
||||
maxRainRef: Number(uiConfig.maxRainRef),
|
||||
minSampleIntervalSec: Number(uiConfig.minSampleIntervalSec),
|
||||
},
|
||||
functionality: {
|
||||
positionVsParent: uiConfig.positionVsParent || 'atEquipment',
|
||||
@@ -110,20 +114,33 @@ try{
|
||||
const bucketVol = m.bucketVol;
|
||||
const maxVolume = m.maxVolume;
|
||||
const state = m.running;
|
||||
const mode = "AI" ; //m.mode;
|
||||
const mode = "AI"; //m.mode;
|
||||
const flowMin = m.nominalFlowMin;
|
||||
const flowMax = m.flowMax;
|
||||
|
||||
let status;
|
||||
|
||||
switch (state) {
|
||||
case false:
|
||||
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
|
||||
break;
|
||||
case true:
|
||||
status = { fill: "green", shape: "dot", text: `${mode}: ON => ${bucketVol} | ${maxVolume}` };
|
||||
break;
|
||||
if (m.invalidFlowBounds) {
|
||||
return {
|
||||
fill: "red",
|
||||
shape: "ring",
|
||||
text: `Config error: nominalFlowMin (${flowMin}) >= flowMax (${flowMax})`
|
||||
};
|
||||
}
|
||||
|
||||
return status;
|
||||
if (state) {
|
||||
const levelText = `${bucketVol}/${maxVolume} L`;
|
||||
const cooldownMs = typeof m.getSampleCooldownMs === 'function'
|
||||
? m.getSampleCooldownMs()
|
||||
: 0;
|
||||
|
||||
if (cooldownMs > 0) {
|
||||
const cooldownSec = Math.ceil(cooldownMs / 1000);
|
||||
return { fill: "yellow", shape: "ring", text: `SAMPLING (${cooldownSec}s) ${levelText}` };
|
||||
}
|
||||
|
||||
return { fill: "green", shape: "dot", text: `${mode}: RUNNING ${levelText}` };
|
||||
}
|
||||
|
||||
return { fill: "grey", shape: "ring", text: `${mode}: IDLE` };
|
||||
} catch (error) {
|
||||
this.node.error("Error in updateNodeStatus: " + error);
|
||||
return { fill: "red", shape: "ring", text: "Status Error" };
|
||||
@@ -182,6 +199,28 @@ try{
|
||||
const m = this.source;
|
||||
try {
|
||||
switch(msg.topic) {
|
||||
case 'input_q': {
|
||||
const value = Number(msg.payload?.value);
|
||||
const unit = msg.payload?.unit;
|
||||
if (!Number.isFinite(value) || !unit) {
|
||||
this.node.warn('input_q payload must include numeric value and unit.');
|
||||
break;
|
||||
}
|
||||
let converted = value;
|
||||
try {
|
||||
converted = convert(value).from(unit).to('m3/h');
|
||||
} catch (error) {
|
||||
this.node.warn(`input_q unit conversion failed: ${error.message}`);
|
||||
break;
|
||||
}
|
||||
m.handleInput('input_q', { value: converted, unit: 'm3/h' });
|
||||
break;
|
||||
}
|
||||
case 'i_start':
|
||||
case 'monsternametijden':
|
||||
case 'rain_data':
|
||||
m.handleInput(msg.topic, msg.payload);
|
||||
break;
|
||||
case 'registerChild': {
|
||||
const childId = msg.payload;
|
||||
const childObj = this.RED.nodes.getNode(childId);
|
||||
|
||||
@@ -20,9 +20,19 @@ class Monster{
|
||||
// -------------------------------------- fetch dependencies --------------------------
|
||||
//this.math = require('mathjs');
|
||||
|
||||
//place holders for output data
|
||||
this.output = {} ; // object to place all relevant outputs in and preform event change check on
|
||||
//measurements
|
||||
this.measurements = new MeasurementContainer({
|
||||
autoConvert: true,
|
||||
windowSize: 50,
|
||||
defaultUnits: {
|
||||
flow: 'm3/h',
|
||||
volume: 'm3'
|
||||
}
|
||||
}, this.logger);
|
||||
|
||||
//child registration
|
||||
this.child = {} ; // register childs
|
||||
this.childRegistrationUtils = new childRegistrationUtils(this);
|
||||
|
||||
//Specific object info
|
||||
this.aquonSampleName = "112100" ; // aquon sample name to start automatic sampling on the basis of the document
|
||||
@@ -32,19 +42,26 @@ class Monster{
|
||||
this.sumRain = 0 ; // total sum of rain over time window + n hours and - n hours
|
||||
this.avgRain = 0 ; // total divided by number of locations to get average over total time
|
||||
this.daysPerYear = 0 ; // how many days remaining for this year
|
||||
this.lastRainUpdate = 0 ; // timestamp of last rain data update
|
||||
this.rainMaxRef = 10 ; // mm reference for scaling linear prediction
|
||||
this.rainStaleMs = 2 * 60 * 60 * 1000; // 2 hours
|
||||
|
||||
// outputs
|
||||
this.pulse = false; // output pulse to sampling machine
|
||||
this.bucketVol = 0; // how full is the sample?
|
||||
this.sumPuls = 0; // number of pulses so far
|
||||
this.predFlow = 0; // predicted flow over sampling time in hours, expressed in m3
|
||||
this.bucketWeight = 0; // actual weight of bucket
|
||||
this.bucketWeight = 0; // actual weight of bucket
|
||||
|
||||
//inputs
|
||||
this.q = 0; // influent flow in m3/h
|
||||
this.q = 0; // influent flow in m3/h (effective)
|
||||
this.manualFlow = null; // manual flow override value in m3/h
|
||||
this.i_start = false // when true, the program gets kicked off calculating what it needs to take samples
|
||||
this.sampling_time = config.constraints.samplingtime; // time expressed in hours over which the sampling will run (currently 24)
|
||||
this.emptyWeightBucket = config.asset.emptyWeightBucket; // empty weight of the bucket
|
||||
this.nominalFlowMin = config.constraints.nominalFlowMin; // nominal dry-day flow in m3/h
|
||||
this.flowMax = config.constraints.flowMax; // max inflow in m3/h
|
||||
this.minSampleIntervalSec = config.constraints.minSampleIntervalSec || 60; // min seconds between samples
|
||||
|
||||
// internal vars
|
||||
this.temp_pulse = 0; // each interval pulses send out 1 and then reset
|
||||
@@ -63,6 +80,10 @@ class Monster{
|
||||
this.m3PerTick = 0; // actual measured flow in m3 per second
|
||||
this.m3Total = 0; // total measured flow over sampling time in m3
|
||||
this.running = false; // define if sampling is running or not
|
||||
this.invalidFlowBounds = false; // whether nominalFlowMin/flowMax are invalid
|
||||
this.lastSampleTime = 0; // last sample (pulse) timestamp
|
||||
this.lastSampleWarnTime = 0; // last warning timestamp for cooldown
|
||||
this.missedSamples = 0; // count blocked samples due to cooldown
|
||||
|
||||
this.qLineRaw = {}; // see example
|
||||
this.minSeen = {}; // keeps track of minimum ever seen so far in a time period for each hour (over totals not every value)
|
||||
@@ -80,7 +101,7 @@ class Monster{
|
||||
|
||||
//old prediction factor
|
||||
this.predFactor = 0.7; // define factor as multiplier for prediction
|
||||
|
||||
|
||||
//track program start and stop
|
||||
this.start_time = Date.now(); // default start time
|
||||
this.stop_time = Date.now(); // default stop time
|
||||
@@ -89,6 +110,9 @@ class Monster{
|
||||
this.timeLeft = 0; // time in seconds
|
||||
this.currHour = new Date().getHours(); // on init define in which hour we are 0 - 23
|
||||
|
||||
if (Number.isFinite(config?.constraints?.maxRainRef)) {
|
||||
this.rainMaxRef = config.constraints.maxRainRef;
|
||||
}
|
||||
|
||||
this.init = true; // end of constructor
|
||||
|
||||
@@ -98,129 +122,247 @@ class Monster{
|
||||
|
||||
}
|
||||
|
||||
_syncOutput() {
|
||||
this.output = this.output || {};
|
||||
this.output.pulse = this.pulse;
|
||||
this.output.running = this.running;
|
||||
this.output.bucketVol = this.bucketVol;
|
||||
this.output.bucketWeight = this.bucketWeight;
|
||||
this.output.sumPuls = this.sumPuls;
|
||||
this.output.predFlow = this.predFlow;
|
||||
this.output.predM3PerSec = this.predM3PerSec;
|
||||
this.output.timePassed = this.timePassed;
|
||||
this.output.timeLeft = this.timeLeft;
|
||||
this.output.m3Total = this.m3Total;
|
||||
this.output.q = this.q;
|
||||
this.output.maxVolume = this.maxVolume;
|
||||
this.output.minVolume = this.minVolume;
|
||||
this.output.nextDate = this.nextDate;
|
||||
this.output.daysPerYear = this.daysPerYear;
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
this._syncOutput();
|
||||
return this.output;
|
||||
}
|
||||
|
||||
/*------------------- GETTER/SETTERS Dynamics -------------------*/
|
||||
set monsternametijden(value){
|
||||
|
||||
if(this.init){
|
||||
if(Object.keys(value).length > 0){
|
||||
|
||||
//check if push is in valid format and not null
|
||||
if(
|
||||
typeof value[0].SAMPLE_NAME !== 'undefined'
|
||||
&&
|
||||
typeof value[0].DESCRIPTION !== 'undefined'
|
||||
&&
|
||||
typeof value[0].SAMPLED_DATE !== 'undefined'
|
||||
&&
|
||||
typeof value[0].START_DATE !== 'undefined'
|
||||
&&
|
||||
typeof value[0].END_DATE !== 'undefined'
|
||||
){
|
||||
|
||||
//each time this changes we load the next date applicable for this function
|
||||
this._monsternametijden = value;
|
||||
|
||||
//fetch dates
|
||||
this.regNextDate(value);
|
||||
|
||||
}
|
||||
else{
|
||||
// Monsternametijden object Wrong format contact AQUON
|
||||
}
|
||||
}
|
||||
else{
|
||||
// Monsternametijden object Wrong format contact AQUON
|
||||
}
|
||||
/*------------------- INPUT HANDLING -------------------*/
|
||||
handleInput(topic, payload) {
|
||||
switch (topic) {
|
||||
case 'i_start':
|
||||
this.i_start = Boolean(payload);
|
||||
break;
|
||||
case 'monsternametijden':
|
||||
this.updateMonsternametijden(payload);
|
||||
break;
|
||||
case 'rain_data':
|
||||
this.updateRainData(payload);
|
||||
break;
|
||||
case 'input_q':
|
||||
this.updateManualFlow(payload);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
get monsternametijden(){
|
||||
return this._monsternametijden;
|
||||
updateMonsternametijden(value) {
|
||||
if (!this.init || !value || Object.keys(value).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof value[0]?.SAMPLE_NAME !== 'undefined' &&
|
||||
typeof value[0]?.DESCRIPTION !== 'undefined' &&
|
||||
typeof value[0]?.SAMPLED_DATE !== 'undefined' &&
|
||||
typeof value[0]?.START_DATE !== 'undefined' &&
|
||||
typeof value[0]?.END_DATE !== 'undefined'
|
||||
) {
|
||||
this.monsternametijden = value;
|
||||
this.regNextDate(value);
|
||||
}
|
||||
}
|
||||
|
||||
set rain_data(value){
|
||||
|
||||
//retrieve precipitation expected during the coming day and precipitation of yesterday
|
||||
this._rain_data = value;
|
||||
updateRainData(value) {
|
||||
this.rain_data = value;
|
||||
this.lastRainUpdate = Date.now();
|
||||
|
||||
//only update after init and is not running.
|
||||
if(this.init && !this.running){
|
||||
if (this.init && !this.running) {
|
||||
this.updatePredRain(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
get rain_data(){
|
||||
return this._rain_data;
|
||||
}
|
||||
|
||||
set bucketVol(val){
|
||||
|
||||
//Put val in local var
|
||||
this._bucketVol = val;
|
||||
|
||||
//Place into output object
|
||||
this.output.bucketVol = val;
|
||||
|
||||
// update bucket weight
|
||||
updateBucketVol(val) {
|
||||
this.bucketVol = val;
|
||||
this.bucketWeight = val + this.emptyWeightBucket;
|
||||
}
|
||||
|
||||
get bucketVol(){
|
||||
return this._bucketVol;
|
||||
getSampleCooldownMs() {
|
||||
if (!this.lastSampleTime) {
|
||||
return 0;
|
||||
}
|
||||
const remaining = (this.minSampleIntervalSec * 1000) - (Date.now() - this.lastSampleTime);
|
||||
return Math.max(0, remaining);
|
||||
}
|
||||
|
||||
set minVolume(val){
|
||||
|
||||
//Protect against 0
|
||||
val == 0 ? val = 1 : val = val;
|
||||
|
||||
this._minVolume = val;
|
||||
|
||||
//Place into output object
|
||||
this.output.minVolume = val;
|
||||
validateFlowBounds() {
|
||||
const min = Number(this.nominalFlowMin);
|
||||
const max = Number(this.flowMax);
|
||||
const valid = Number.isFinite(min) && Number.isFinite(max) && min >= 0 && max > 0 && min < max;
|
||||
this.invalidFlowBounds = !valid;
|
||||
if (!valid) {
|
||||
this.logger.warn(`Invalid flow bounds. nominalFlowMin=${this.nominalFlowMin}, flowMax=${this.flowMax}`);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
get minVolume(){
|
||||
return this._minVolume;
|
||||
getRainIndex() {
|
||||
if (!this.lastRainUpdate) {
|
||||
return 0;
|
||||
}
|
||||
if (Date.now() - this.lastRainUpdate > this.rainStaleMs) {
|
||||
return 0;
|
||||
}
|
||||
return Number.isFinite(this.avgRain) ? this.avgRain : 0;
|
||||
}
|
||||
|
||||
set q(val){
|
||||
|
||||
//Put val in local var
|
||||
this._q = val;
|
||||
|
||||
//Place into output object
|
||||
this.output.q = val;
|
||||
|
||||
getPredictedFlowRate() {
|
||||
const min = Number(this.nominalFlowMin);
|
||||
const max = Number(this.flowMax);
|
||||
if (!Number.isFinite(min) || !Number.isFinite(max) || min < 0 || max <= 0 || min >= max) {
|
||||
return 0;
|
||||
}
|
||||
const rainIndex = this.getRainIndex();
|
||||
const scale = Math.max(0, Math.min(1, this.rainMaxRef > 0 ? rainIndex / this.rainMaxRef : 0));
|
||||
return min + (max - min) * scale;
|
||||
}
|
||||
|
||||
get q(){
|
||||
return this._q;
|
||||
|
||||
updateManualFlow(payload = {}) {
|
||||
const value = Number(payload.value);
|
||||
if (!Number.isFinite(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = payload.unit || 'm3/h';
|
||||
this.manualFlow = value;
|
||||
this.measurements
|
||||
.type('flow')
|
||||
.variant('manual')
|
||||
.position('atequipment')
|
||||
.value(value, Date.now(), unit);
|
||||
}
|
||||
|
||||
handleMeasuredFlow(eventData) {
|
||||
const value = Number(eventData?.value);
|
||||
if (!Number.isFinite(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = String(eventData.position || 'atequipment').toLowerCase();
|
||||
const unit = eventData.unit || 'm3/h';
|
||||
this.measurements
|
||||
.type('flow')
|
||||
.variant('measured')
|
||||
.position(position)
|
||||
.value(value, eventData.timestamp || Date.now(), unit);
|
||||
}
|
||||
|
||||
getMeasuredFlow() {
|
||||
const positions = ['upstream', 'downstream', 'atequipment'];
|
||||
const values = [];
|
||||
|
||||
positions.forEach((position) => {
|
||||
const measured = this.measurements
|
||||
.type('flow')
|
||||
.variant('measured')
|
||||
.position(position)
|
||||
.getCurrentValue();
|
||||
|
||||
if (Number.isFinite(measured)) {
|
||||
values.push(measured);
|
||||
}
|
||||
});
|
||||
|
||||
if (!values.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sum = values.reduce((total, curr) => total + curr, 0);
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
getManualFlow() {
|
||||
const manual = this.measurements
|
||||
.type('flow')
|
||||
.variant('manual')
|
||||
.position('atequipment')
|
||||
.getCurrentValue();
|
||||
|
||||
return Number.isFinite(manual) ? manual : null;
|
||||
}
|
||||
|
||||
getEffectiveFlow() {
|
||||
const measured = this.getMeasuredFlow();
|
||||
const manual = this.getManualFlow();
|
||||
|
||||
if (measured != null && manual != null) {
|
||||
return (measured + manual) / 2;
|
||||
}
|
||||
|
||||
if (measured != null) {
|
||||
return measured;
|
||||
}
|
||||
|
||||
if (manual != null) {
|
||||
return manual;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
registerChild(child, softwareType) {
|
||||
if (softwareType !== 'measurement' || !child?.measurements?.emitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childType = child?.config?.asset?.type;
|
||||
if (childType && childType !== 'flow') {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = (eventData) => this.handleMeasuredFlow(eventData);
|
||||
child.measurements.emitter.on('flow.measured.upstream', handler);
|
||||
child.measurements.emitter.on('flow.measured.downstream', handler);
|
||||
child.measurements.emitter.on('flow.measured.atequipment', handler);
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
const output = this.measurements.getFlattenedOutput();
|
||||
const flowRate = Number(this.q) || 0;
|
||||
const m3PerPulse = Number(this.m3PerPuls) || 0;
|
||||
const pulseFraction = Number(this.temp_pulse) || 0;
|
||||
const targetVolumeM3 = Number(this.targetVolume) > 0 ? this.targetVolume / 1000 : 0;
|
||||
const flowToNextPulseM3 = m3PerPulse > 0 ? Math.max(0, (1 - pulseFraction) * m3PerPulse) : 0;
|
||||
const timeToNextPulseSec = flowRate > 0 && flowToNextPulseM3 > 0
|
||||
? Math.round((flowToNextPulseM3 / (flowRate / 3600)) * 100) / 100
|
||||
: 0;
|
||||
const targetProgressPct = targetVolumeM3 > 0
|
||||
? Math.round((this.m3Total / targetVolumeM3) * 10000) / 100
|
||||
: 0;
|
||||
const targetDeltaM3 = targetVolumeM3 > 0
|
||||
? Math.round((this.m3Total - targetVolumeM3) * 10000) / 10000
|
||||
: 0;
|
||||
|
||||
output.pulse = this.pulse;
|
||||
output.running = this.running;
|
||||
output.bucketVol = this.bucketVol;
|
||||
output.bucketWeight = this.bucketWeight;
|
||||
output.sumPuls = this.sumPuls;
|
||||
output.predFlow = this.predFlow;
|
||||
output.predM3PerSec = this.predM3PerSec;
|
||||
output.timePassed = this.timePassed;
|
||||
output.timeLeft = this.timeLeft;
|
||||
output.m3Total = this.m3Total;
|
||||
output.q = this.q;
|
||||
output.nominalFlowMin = this.nominalFlowMin;
|
||||
output.flowMax = this.flowMax;
|
||||
output.invalidFlowBounds = this.invalidFlowBounds;
|
||||
output.minSampleIntervalSec = this.minSampleIntervalSec;
|
||||
output.missedSamples = this.missedSamples;
|
||||
output.sampleCooldownMs = this.getSampleCooldownMs();
|
||||
output.maxVolume = this.maxVolume;
|
||||
output.minVolume = this.minVolume;
|
||||
output.nextDate = this.nextDate;
|
||||
output.daysPerYear = this.daysPerYear;
|
||||
output.m3PerPuls = this.m3PerPuls;
|
||||
output.m3PerPulse = this.m3PerPuls;
|
||||
output.pulsesRemaining = Math.max(0, (this.targetPuls || 0) - (this.sumPuls || 0));
|
||||
output.pulseFraction = pulseFraction;
|
||||
output.flowToNextPulseM3 = flowToNextPulseM3;
|
||||
output.timeToNextPulseSec = timeToNextPulseSec;
|
||||
output.targetVolumeM3 = targetVolumeM3;
|
||||
output.targetProgressPct = targetProgressPct;
|
||||
output.targetDeltaM3 = targetDeltaM3;
|
||||
output.predictedRateM3h = this.getPredictedFlowRate();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*------------------- FUNCTIONS -------------------*/
|
||||
@@ -357,15 +499,15 @@ class Monster{
|
||||
}
|
||||
|
||||
|
||||
get_model_prediction(){
|
||||
// Offline-safe fallback: assume constant inflow `q` during `sampling_time`.
|
||||
// `q` is in m3/h; `sampling_time` is in hours; result is total predicted volume in m3.
|
||||
get_model_prediction(){
|
||||
// Linear predictor based on rain index with flow bounds.
|
||||
const samplingHours = Number(this.sampling_time) || 0;
|
||||
const flowM3PerHour = Number(this.q) || 0;
|
||||
const predictedRate = this.getPredictedFlowRate();
|
||||
const fallbackRate = this.getEffectiveFlow();
|
||||
const flowM3PerHour = predictedRate > 0 ? predictedRate : fallbackRate;
|
||||
const fallback = Math.max(0, flowM3PerHour * samplingHours);
|
||||
|
||||
this.predFlow = fallback;
|
||||
this._syncOutput();
|
||||
return this.predFlow;
|
||||
}
|
||||
|
||||
@@ -487,13 +629,19 @@ async model_loader(inputs){
|
||||
|
||||
// ------------------ Run once on conditions and start sampling
|
||||
if( ( (this.i_start ) || ( Date.now() >= this.nextDate ) ) && !this.running ){
|
||||
|
||||
if (!this.validateFlowBounds()) {
|
||||
this.running = false;
|
||||
this.i_start = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.running = true;
|
||||
|
||||
// reset persistent vars
|
||||
this.temp_pulse = 0;
|
||||
this.pulse = false;
|
||||
this.bucketVol = 0;
|
||||
this.updateBucketVol(0);
|
||||
this.sumPuls = 0;
|
||||
this.m3Total = 0;
|
||||
this.timePassed = 0; // time in seconds
|
||||
@@ -534,14 +682,30 @@ async model_loader(inputs){
|
||||
|
||||
// check if we need to send out a pulse (stop sending pulses if capacity is reached)
|
||||
if(this.temp_pulse >= 1 && this.sumPuls < this.absMaxPuls){
|
||||
// reset
|
||||
this.temp_pulse += -1;
|
||||
// send out a pulse and add to count
|
||||
this.pulse = true;
|
||||
// count pulses
|
||||
this.sumPuls++;
|
||||
// update bucket volume each puls
|
||||
this.bucketVol = Math.round(this.sumPuls * this.volume_pulse * 100) / 100;
|
||||
const now = Date.now();
|
||||
const cooldownMs = this.minSampleIntervalSec * 1000;
|
||||
const blocked = this.lastSampleTime && (now - this.lastSampleTime) < cooldownMs;
|
||||
|
||||
if (blocked) {
|
||||
this.missedSamples++;
|
||||
this.pulse = false;
|
||||
this.temp_pulse = Math.min(this.temp_pulse, 1);
|
||||
|
||||
if (!this.lastSampleWarnTime || (now - this.lastSampleWarnTime) > cooldownMs) {
|
||||
this.lastSampleWarnTime = now;
|
||||
this.logger.warn(`Sampling too fast. Cooldown active for ${Math.ceil((cooldownMs - (now - this.lastSampleTime)) / 1000)}s.`);
|
||||
}
|
||||
} else {
|
||||
// reset
|
||||
this.temp_pulse += -1;
|
||||
// send out a pulse and add to count
|
||||
this.pulse = true;
|
||||
this.lastSampleTime = now;
|
||||
// count pulses
|
||||
this.sumPuls++;
|
||||
// update bucket volume each pulse
|
||||
this.updateBucketVol(Math.round(this.sumPuls * this.volume_pulse * 100) / 100);
|
||||
}
|
||||
|
||||
}
|
||||
else{
|
||||
@@ -566,7 +730,7 @@ async model_loader(inputs){
|
||||
this.m3PerPuls = 0;
|
||||
this.temp_pulse = 0;
|
||||
this.pulse = false;
|
||||
this.bucketVol = 0;
|
||||
this.updateBucketVol(0);
|
||||
this.sumPuls = 0;
|
||||
this.timePassed = 0; // time in seconds
|
||||
this.timeLeft = 0; // time in seconds
|
||||
@@ -599,7 +763,10 @@ async model_loader(inputs){
|
||||
|
||||
// ------------------ 1.0 Main program loop ------------------
|
||||
this.logger.debug('Monster tick running');
|
||||
|
||||
|
||||
//resolve effective flow in m3/h
|
||||
this.q = this.getEffectiveFlow();
|
||||
|
||||
//calculate flow based on input
|
||||
this.flowCalc();
|
||||
|
||||
@@ -608,8 +775,6 @@ async model_loader(inputs){
|
||||
|
||||
//logQ for predictions / forecasts
|
||||
this.logQoverTime();
|
||||
|
||||
this._syncOutput();
|
||||
}
|
||||
|
||||
regNextDate(monsternametijden){
|
||||
@@ -707,11 +872,11 @@ const mConfig={
|
||||
},
|
||||
}
|
||||
|
||||
const monster = new Monster(mConfig);
|
||||
(async () => {
|
||||
const intervalId = setInterval(() => {
|
||||
monster.tick()
|
||||
;},1000)
|
||||
|
||||
})();
|
||||
|
||||
if (require.main === module) {
|
||||
const monster = new Monster(mConfig);
|
||||
(async () => {
|
||||
const intervalId = setInterval(() => {
|
||||
monster.tick();
|
||||
}, 1000);
|
||||
})();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user