Synchronizing workflow monster
This commit is contained in:
@@ -1,2 +1,717 @@
|
||||
module.exports = require("../dependencies/monster/SpeficicClass");
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const {logger,configUtils,configManager, MeasurementContainer, predict, interpolation, childRegistrationUtils} = require('generalFunctions');
|
||||
|
||||
class Monster{
|
||||
/*------------------- Construct and set vars -------------------*/
|
||||
constructor(config={}) {
|
||||
|
||||
//init
|
||||
this.init = false; // keep track of init
|
||||
|
||||
//basic setup
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
|
||||
this.logger = new logger(config.general.logging.enabled,config.general.logging.logLevel, config.general.name);
|
||||
this.configManager = new configManager();
|
||||
this.defaultConfig = this.configManager.getConfig('monster'); // Load default config for rotating machine ( use software type name ? )
|
||||
this.configUtils = new configUtils(this.defaultConfig);
|
||||
|
||||
// -------------------------------------- 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
|
||||
this.child = {} ; // register childs
|
||||
|
||||
//Specific object info
|
||||
this.aquonSampleName = "112100" ; // aquon sample name to start automatic sampling on the basis of the document
|
||||
this.monsternametijden = {} ; // json monsternametijden file?
|
||||
this.rain_data = {} ; // precipitation data
|
||||
this.aggregatedOutput = {} ; // object that does not contain momentary values but a combination of all kinds of data over a fixed period of time
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
//inputs
|
||||
this.q = 0; // influent flow 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
|
||||
|
||||
// internal vars
|
||||
this.temp_pulse = 0; // each interval pulses send out 1 and then reset
|
||||
this.volume_pulse = 0.05; // define volume pulse expressed in L
|
||||
this.minVolume = config.constraints.minVolume;// define min volume in a sampling cabinet before a sample is declared valid expressed in L
|
||||
this.maxVolume = 0; // calculated maxvolume depending on own weight
|
||||
this.maxWeight = config.constraints.maxWeight;// define max volume in a sampling cabinet before a sample is declared invalid expressed in L
|
||||
this.cap_volume = 55; // abs max capacity of bucket (volume) in liters
|
||||
this.targetVolume = 0; // volume of sampling cabinet that model aims for
|
||||
this.minPuls = 0; // calculates the min pulses depending on min_vol and max_vol
|
||||
this.maxPuls = 0; // calculates the max pulses depending on min_vol and max_vol
|
||||
this.absMaxPuls = 0; // capacity of sampling cabinet (number of pulses)
|
||||
this.targetPuls = 0; // keeps track of the desired amount of pulses (+- 50% tolerance), based on aimed volume
|
||||
this.m3PerPuls = 0; // each pulse is equal to a number of m3
|
||||
this.predM3PerSec = 0; // predicted flow in m3 per second
|
||||
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.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)
|
||||
this.maxSeen = {}; // keeps track of maximum ever seen so far in a time period for each hour (over totals not every value)
|
||||
this.qLineRefined = {}; // this should be the ( quantiles? ) classified in the datasets
|
||||
this.calcTimeShiftDry = 0; // What is the delay after a dry period of minimum n hours
|
||||
this.calcTimeShiftWet = 0;
|
||||
this.calcCapacitySewer = 0;
|
||||
// how much rain goes to the sewage ? -> calculate surface area of hardend / sewage.
|
||||
|
||||
this.minDryHours = 0; // what is the minimum of dry hours before we can calculate timeshift? spot this with moving average?
|
||||
this.minWetHours = 0; // how long does it take to remove all the rain?
|
||||
this.resolution = 0; // Number of chunks in qLineRaw / define how big the window is to sum all values ( for now we need to take 1 hour or bigger resolutions but in the future smaller is better to see more accurate correlations)
|
||||
this.tmpTotQ = 0; // keep track of sum of q within resolution window
|
||||
|
||||
//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
|
||||
this.flowTime = 0; //keep track in detail how much time between 2 ticks for more accurate flow measurement
|
||||
this.timePassed = 0; // time in seconds
|
||||
this.timeLeft = 0; // time in seconds
|
||||
this.currHour = new Date().getHours(); // on init define in which hour we are 0 - 23
|
||||
|
||||
|
||||
this.init = true; // end of constructor
|
||||
|
||||
//set boundries and targets after init based on above settings
|
||||
this.set_boundries_and_targets();
|
||||
|
||||
|
||||
}
|
||||
|
||||
_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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get monsternametijden(){
|
||||
return this._monsternametijden;
|
||||
}
|
||||
|
||||
set rain_data(value){
|
||||
|
||||
//retrieve precipitation expected during the coming day and precipitation of yesterday
|
||||
this._rain_data = value;
|
||||
|
||||
//only update after init and is not 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
|
||||
this.bucketWeight = val + this.emptyWeightBucket;
|
||||
}
|
||||
|
||||
get bucketVol(){
|
||||
return this._bucketVol;
|
||||
}
|
||||
|
||||
set minVolume(val){
|
||||
|
||||
//Protect against 0
|
||||
val == 0 ? val = 1 : val = val;
|
||||
|
||||
this._minVolume = val;
|
||||
|
||||
//Place into output object
|
||||
this.output.minVolume = val;
|
||||
}
|
||||
|
||||
get minVolume(){
|
||||
return this._minVolume;
|
||||
}
|
||||
|
||||
set q(val){
|
||||
|
||||
//Put val in local var
|
||||
this._q = val;
|
||||
|
||||
//Place into output object
|
||||
this.output.q = val;
|
||||
|
||||
}
|
||||
|
||||
get q(){
|
||||
return this._q;
|
||||
}
|
||||
|
||||
/*------------------- FUNCTIONS -------------------*/
|
||||
|
||||
set_boundries_and_targets(){
|
||||
|
||||
// define boundries for algorithm
|
||||
this.maxVolume = this.maxWeight - this.emptyWeightBucket ; // substract bucket weight of max volume assuming they are both on a 1 to 1 ratio
|
||||
this.minPuls = Math.round(this.minVolume / this.volume_pulse); // minimum pulses we want before we have a valid sample
|
||||
this.maxPuls = Math.round(this.maxVolume / this.volume_pulse); // maximum pulses we can handle (otherwise sample is too heavy)
|
||||
this.absMaxPuls = Math.round(this.cap_volume / this.volume_pulse); // number of pulses a sample can contain before overflowing
|
||||
// define target values
|
||||
this.targetVolume = this.minVolume * Math.sqrt(this.maxVolume / this.minVolume);
|
||||
//old way
|
||||
//this.targetVolume = Math.round( ( ( (this.maxVolume - this.minVolume) / 2 ) + this.minVolume ) * 100) / 100; // calculate middle between min and max
|
||||
// correct target values
|
||||
this.targetPuls = Math.round(this.targetVolume / this.volume_pulse) ; // define desired amount of pulses (in this case our prediction can deviate 50% up and 50% down without a problem)
|
||||
}
|
||||
|
||||
|
||||
updatePredRain(value){
|
||||
//make date objects to define relative time window
|
||||
let now = new Date(Date.now());
|
||||
let past = new Date(Date.now());
|
||||
let future = new Date(Date.now());
|
||||
let totalRaw = {};
|
||||
let totalProb = {};
|
||||
let totalAvg = {};
|
||||
|
||||
//refine object with different values
|
||||
let rain = {};
|
||||
rain.hourly = {}; // an object with timestamps and aggreated over all locations summed precipation in mm
|
||||
rain.hourly.time = [];
|
||||
rain.hourly.precipationRaw = [];
|
||||
rain.hourly.precipationProb = [];
|
||||
|
||||
let numberOfLocations = 0;
|
||||
|
||||
//Make timestamp + 24 hours
|
||||
future.setHours(now.getHours() + 24);
|
||||
|
||||
//Make timestamp - 24hours
|
||||
past.setHours(now.getHours() - 24);
|
||||
|
||||
//go through all locations and sum up the average precipation of each location so we have summed precipation over every hour
|
||||
Object.entries(value).forEach(([locationKey, location],locationindex) => {
|
||||
|
||||
//number of locations
|
||||
numberOfLocations++;
|
||||
|
||||
// make an object to keep track of the dataset we load
|
||||
this.aggregatedOutput[locationKey] = {};
|
||||
this.aggregatedOutput[locationKey].tag = {};
|
||||
this.aggregatedOutput[locationKey].tag.latitude = location.latitude;
|
||||
this.aggregatedOutput[locationKey].tag.longitude = location.longitude;
|
||||
this.aggregatedOutput[locationKey].precipationRaw = {};
|
||||
this.aggregatedOutput[locationKey].precipationProb = {};
|
||||
|
||||
|
||||
//loop through object for each location over all hourlys
|
||||
Object.entries(location.hourly.time).forEach(([key, time], index) => {
|
||||
|
||||
this.aggregatedOutput[locationKey].precipationRaw[key] = {};
|
||||
this.aggregatedOutput[locationKey].precipationProb[key] = {};
|
||||
|
||||
//convert string output to a date object
|
||||
let checkdate = new Date(time);
|
||||
|
||||
//convert date to milliseconds timestamps
|
||||
let currTimestamp = checkdate.getTime();
|
||||
let probability = 100; //default probility unless otherwise defined
|
||||
|
||||
if(typeof location.hourly.precipitation_probability !== 'undefined'){
|
||||
probability = location.hourly.precipitation_probability[key];
|
||||
|
||||
}
|
||||
|
||||
if(probability > 0){
|
||||
probability /= 100;
|
||||
}
|
||||
|
||||
// only interested in dates before timeframe and after to make use of
|
||||
// ( currTimestamp >= now && currTimestamp < future) || ( currTimestamp < now && currTimestamp > past )
|
||||
if( true ){
|
||||
|
||||
typeof totalRaw[currTimestamp] === 'undefined' ? totalRaw[currTimestamp] = 0 : null;
|
||||
typeof totalProb[currTimestamp] === 'undefined' ? totalProb[currTimestamp] = 0 : null;
|
||||
|
||||
//placed probability into the equation
|
||||
totalRaw[currTimestamp] += location.hourly.precipitation[key] ;
|
||||
totalProb[currTimestamp] += ( location.hourly.precipitation[key] * probability ) ;
|
||||
|
||||
//keep track of all requested data
|
||||
this.aggregatedOutput[locationKey].precipationRaw[key]["val"] = location.hourly.precipitation[key]; // raw data from open weather data
|
||||
this.aggregatedOutput[locationKey].precipationRaw[key]["time"] = currTimestamp;
|
||||
|
||||
this.aggregatedOutput[locationKey].precipationProb[key]["val"] = probability; // probability of open weather
|
||||
this.aggregatedOutput[locationKey].precipationProb[key]["time"] = currTimestamp;
|
||||
|
||||
}
|
||||
|
||||
//remove dead info
|
||||
if(Object.keys(this.aggregatedOutput[locationKey].precipationRaw[key]).length == 0 ){
|
||||
delete this.aggregatedOutput[locationKey].precipationRaw[key];
|
||||
};
|
||||
|
||||
if(Object.keys(this.aggregatedOutput[locationKey].precipationProb[key]).length == 0 ){
|
||||
delete this.aggregatedOutput[locationKey].precipationProb[key];
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
//total sum expected over time window (just for ref now not so important anymore)
|
||||
this.sumRain = Object.values(totalProb).reduce((sum, value) => sum + value, 0);
|
||||
this.avgRain = this.sumRain / numberOfLocations;
|
||||
|
||||
//make average over prob
|
||||
Object.entries(totalProb).forEach(([key, sum],index) => {
|
||||
typeof totalAvg[key] === 'undefined' ? totalAvg[key] = 0 : null;
|
||||
totalAvg[key] = sum / numberOfLocations;
|
||||
});
|
||||
|
||||
//make new prediction
|
||||
return this.aggregatedOutput;
|
||||
}
|
||||
|
||||
// for getting the day of the year (0-365)
|
||||
getDayOfYear(ts){
|
||||
const start = new Date(ts.getFullYear(), 0, 1);
|
||||
const diff = ts - start;
|
||||
const oneDay = 1000 * 60 * 60 * 24;
|
||||
return Math.floor(diff / oneDay);
|
||||
}
|
||||
|
||||
|
||||
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.
|
||||
const samplingHours = Number(this.sampling_time) || 0;
|
||||
const flowM3PerHour = Number(this.q) || 0;
|
||||
const fallback = Math.max(0, flowM3PerHour * samplingHours);
|
||||
|
||||
this.predFlow = fallback;
|
||||
this._syncOutput();
|
||||
return this.predFlow;
|
||||
}
|
||||
|
||||
// Legacy/experimental model-based prediction (kept for reference; not used by default).
|
||||
get_model_prediction_from_rain(){
|
||||
|
||||
// combine 24 hourly predictions to make one daily prediction (for the next 24 hours including the current hour)
|
||||
let inputs = [];
|
||||
for (let predHour = 0; predHour <= 23; predHour++) {
|
||||
|
||||
// select 48 timestamps based on hour te be predicted
|
||||
let now = new Date();
|
||||
const lastHour = new Date(now.setHours(now.getHours() + predHour));
|
||||
let timestamps = this.rain_data[0].hourly.time.map(ts => new Date(ts));
|
||||
let timestamps_48 = timestamps.filter(ts => ts <= lastHour).slice(-48)
|
||||
|
||||
// for each relevant hour calculate the mean precipitation across all areas
|
||||
let precipitation = [];
|
||||
for (let i = 0; i < timestamps.length; i++) {
|
||||
|
||||
if(timestamps_48.includes(timestamps[i])) {
|
||||
|
||||
let values = [];
|
||||
for (let j = 0; j < this.rain_data.length; j++) {
|
||||
|
||||
values.push(this.rain_data[j].hourly.precipitation[i]);
|
||||
}
|
||||
let mean = values.reduce((sum, value) => sum + value, 0) / this.rain_data.length;
|
||||
precipitation.push(mean);
|
||||
}
|
||||
}
|
||||
|
||||
// generate seasonal variables for model: hour of day, day of week, day of year (last 2 with sin cos transformation)
|
||||
let hour = timestamps_48.map(ts => ts.getHours());
|
||||
let weekdayJS = timestamps_48.map(ts => ts.getDay()); // Javascript weekday
|
||||
let weekdayPY = weekdayJS.map(weekdayJS => (weekdayJS + 6) % 7); // Python weekday
|
||||
let weekdaySin = weekdayPY.map(weekdayPY => Math.sin(2 * Math.PI * weekdayPY / 7));
|
||||
let weekdayCos = weekdayPY.map(weekdayPY => Math.cos(2 * Math.PI * weekdayPY / 7));
|
||||
let dayOfYear = timestamps_48.map(ts => this.getDayOfYear(ts));
|
||||
let dayOfYearSin = dayOfYear.map(day => Math.sin(2 * Math.PI * day / 365));
|
||||
let dayOfYearCos = dayOfYear.map(day => Math.cos(2 * Math.PI * day / 365));
|
||||
|
||||
// standardize variables for prediction and 'zip' them
|
||||
const scaling = [
|
||||
{
|
||||
"hour mean": 11.504046716524947,
|
||||
"weekdaySin mean": -0.00023422353487966347,
|
||||
"weekdayCos mean": 0.0033714029956787715,
|
||||
"dayOfYearSin mean": 0.06748893577363864,
|
||||
"dayOfYearCos mean": -0.02137433139416939,
|
||||
"precipitation mean": 0.0887225073082283
|
||||
},
|
||||
{
|
||||
"hour scale": 6.92182769305216,
|
||||
"weekdaySin scale": 0.7073194528907719,
|
||||
"weekdayCos scale": 0.7068859670013796,
|
||||
"dayOfYearSin scale": 0.701099604274817,
|
||||
"dayOfYearCos scale": 0.7095405037003095,
|
||||
"precipitation scale": 0.4505403578968155
|
||||
},
|
||||
{
|
||||
"Flow (m3/h) mean": 1178.7800890533754
|
||||
},
|
||||
{
|
||||
"Flow (m3/h) scale": 1025.3973622173557
|
||||
}
|
||||
]
|
||||
const means = scaling[0];
|
||||
const scales = scaling[1];
|
||||
|
||||
let features = [hour, weekdaySin, weekdayCos, dayOfYearSin, dayOfYearCos, precipitation];
|
||||
const names = ["hour", "weekdaySin", "weekdayCos", "dayOfYearSin", "dayOfYearCos", "precipitation"]
|
||||
|
||||
features = features.map((arr, i) =>
|
||||
arr.map(value => (value - means[`${names[i]} mean`]) / scales[`${names[i]} scale`]));
|
||||
[hour, weekdaySin, weekdayCos, dayOfYearSin, dayOfYearCos, precipitation] = features;
|
||||
|
||||
const zipped = this.zip(hour, weekdaySin, weekdayCos, dayOfYearSin, dayOfYearCos, precipitation);
|
||||
|
||||
// collect inputdata for model
|
||||
inputs.push(zipped);
|
||||
|
||||
}
|
||||
const output = this.model_loader(inputs);
|
||||
console.log('Final output: ' + output);
|
||||
}
|
||||
|
||||
async model_loader(inputs){
|
||||
|
||||
let dailyPred = 0;
|
||||
|
||||
try {
|
||||
|
||||
|
||||
// Try loading with default input shape*/
|
||||
const path = 'nodes/generalFunctions/datasets/lstmData/tfjs_model/model.json';
|
||||
const model = await this.modelLoader.loadModelPath(path);
|
||||
console.log('Model loaded successfully!');
|
||||
|
||||
// make predictions
|
||||
for (const input of inputs) {
|
||||
|
||||
const inputTensor = tf.tensor3d([input]);
|
||||
const predict = model.predict(inputTensor);
|
||||
let predictValue = await predict.data();
|
||||
|
||||
// back-transformation because of standardization of the response variable
|
||||
predictValue = predictValue[0] * 1024.1940942 + 1188.0105115;
|
||||
dailyPred += predictValue;
|
||||
}
|
||||
console.log('Daily prediction: ' + dailyPred);
|
||||
} catch (error) {
|
||||
console.error('Failed to load model:', error);
|
||||
}
|
||||
return dailyPred;
|
||||
}
|
||||
|
||||
sampling_program(){
|
||||
|
||||
// ------------------ Run once on conditions and start sampling
|
||||
if( ( (this.i_start ) || ( Date.now() >= this.nextDate ) ) && !this.running ){
|
||||
|
||||
this.running = true;
|
||||
|
||||
// reset persistent vars
|
||||
this.temp_pulse = 0;
|
||||
this.pulse = false;
|
||||
this.bucketVol = 0;
|
||||
this.sumPuls = 0;
|
||||
this.m3Total = 0;
|
||||
this.timePassed = 0; // time in seconds
|
||||
this.timeLeft = 0; // time in seconds
|
||||
this.predM3PerSec = 0;
|
||||
|
||||
//run prediction to ensure its value is filled
|
||||
this.get_model_prediction();
|
||||
|
||||
// define m3 per pulse for this run and round to int !
|
||||
this.m3PerPuls = Math.round(this.predFlow / this.targetPuls);
|
||||
this.predM3PerSec = this.predFlow / this.sampling_time / 60 / 60; // predicted m3 per time
|
||||
|
||||
// define start and stop time based on calender data
|
||||
this.start_time = Date.now();
|
||||
this.stop_time = Date.now() + (this.sampling_time * 60 * 60 * 1000); // convert to milliseconds
|
||||
|
||||
//reset parameters and look for next date
|
||||
this.regNextDate(this.monsternametijden);
|
||||
|
||||
// reset start
|
||||
this.i_start = false;
|
||||
}
|
||||
|
||||
// ------------------ Run for as long as sampling time is not greater than stop time
|
||||
if(this.stop_time > Date.now()){
|
||||
|
||||
// define time vars
|
||||
this.timePassed = Math.round( ( Date.now() - this.start_time ) / 1000);
|
||||
this.timeLeft = Math.round( ( this.stop_time - Date.now() ) / 1000);
|
||||
|
||||
// calc temp pulse rate
|
||||
let update = this.m3PerTick / this.m3PerPuls;
|
||||
|
||||
// update values
|
||||
this.temp_pulse += update;
|
||||
this.m3Total += this.m3PerTick;
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
if( this.sumPuls > this.absMaxPuls){
|
||||
|
||||
// find out how to reschedule sample automatically?
|
||||
}
|
||||
|
||||
//update pulse when its true
|
||||
if(this.pulse){
|
||||
this.pulse = false; // continue but don't send out a pulse
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//after setting once dont do it again
|
||||
if(this.running){
|
||||
// Vars can only be 0 if this is not running
|
||||
this.m3PerPuls = 0;
|
||||
this.temp_pulse = 0;
|
||||
this.pulse = false;
|
||||
this.bucketVol = 0;
|
||||
this.sumPuls = 0;
|
||||
this.timePassed = 0; // time in seconds
|
||||
this.timeLeft = 0; // time in seconds
|
||||
this.predFlow = 0;
|
||||
this.predM3PerSec = 0;
|
||||
this.m3Total = 0;
|
||||
this.running = false; // end of sampling program (stop_time reached)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flowCalc(){
|
||||
//reset timePassed
|
||||
let timePassed = 0;
|
||||
|
||||
// each tick calc flowtimepassed
|
||||
this.flowTime > 0 ? timePassed = ( Date.now() - this.flowTime) / 1000 : timePassed = 0 ;
|
||||
|
||||
//conver to m3 per tick
|
||||
this.m3PerTick = this.q / 60 / 60 * timePassed ;
|
||||
|
||||
// put new timestamp
|
||||
this.flowTime = Date.now();
|
||||
|
||||
}
|
||||
|
||||
//goes through time related functions
|
||||
tick(){
|
||||
|
||||
// ------------------ 1.0 Main program loop ------------------
|
||||
this.logger.debug('Monster tick running');
|
||||
|
||||
//calculate flow based on input
|
||||
this.flowCalc();
|
||||
|
||||
//run sampling program
|
||||
this.sampling_program();
|
||||
|
||||
//logQ for predictions / forecasts
|
||||
this.logQoverTime();
|
||||
|
||||
this._syncOutput();
|
||||
}
|
||||
|
||||
regNextDate(monsternametijden){
|
||||
|
||||
let next_date = new Date(new Date().setFullYear(new Date().getFullYear() + 1));
|
||||
let n_days_remaining = 0;
|
||||
|
||||
if(typeof monsternametijden !== 'undefined'){
|
||||
// loop through lines
|
||||
Object.entries(monsternametijden).forEach(([key, line],index) => {
|
||||
|
||||
//console.log(line.START_DATE);
|
||||
//check if date is not null
|
||||
if(line.START_DATE != "NULL"){
|
||||
let curr_date_conv = new Date(line.START_DATE);
|
||||
let curr_date = curr_date_conv.getTime();
|
||||
|
||||
//check if sample name is this sample and if date is bigger than now.
|
||||
if(line.SAMPLE_NAME == this.aquonSampleName && curr_date > Date.now() ){
|
||||
|
||||
//only keep date that is bigger than current but smaller than the ones that follow after it.
|
||||
if(curr_date < next_date){ next_date = curr_date; }
|
||||
|
||||
// check if its within this year only show those days as days remaining
|
||||
if( new Date().getFullYear() == curr_date_conv.getFullYear() ){ n_days_remaining++; }
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
else{
|
||||
//this.warning.push(3);
|
||||
}
|
||||
|
||||
//store vars remaining
|
||||
this.daysPerYear = n_days_remaining;
|
||||
this.nextDate = next_date;
|
||||
}
|
||||
|
||||
logQoverTime(){
|
||||
|
||||
//store currHour in temp obj for easy ref
|
||||
let h = this.currHour;
|
||||
|
||||
// define rain hour of which the correlation is the biggest this doesnt belong in this section do this afterwards
|
||||
// let rainH = h - this.calcTimeShift ;
|
||||
|
||||
// how much rain fell on rainH (define category)
|
||||
|
||||
// fetch current hour from actual time
|
||||
const currentHour = new Date().getHours();
|
||||
|
||||
//on hour change begin log
|
||||
if(h !== currentHour ){
|
||||
|
||||
//write current total to object
|
||||
this.qLineRaw.h = this.tmpTotQ
|
||||
|
||||
//reset tmpTotQ
|
||||
|
||||
//set this.currHour to currentHour
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//create objects where to push arrays in to keep track of data
|
||||
createMinMaxSeen(){
|
||||
//check which hour it is , then make sum , after sum is complete check which hour it is
|
||||
//loop over sampling time expressed in hours
|
||||
for(let h = 1; h < this.sampling_time ; h++){
|
||||
this.minSeen = {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // end of class
|
||||
|
||||
module.exports = Monster;
|
||||
|
||||
|
||||
const mConfig={
|
||||
general: {
|
||||
name: "Monster",
|
||||
logging:{
|
||||
logLevel: "debug",
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
asset: {
|
||||
emptyWeightBucket: 3,
|
||||
},
|
||||
constraints: {
|
||||
minVolume: 4,
|
||||
maxWeight: 23,
|
||||
},
|
||||
}
|
||||
|
||||
const monster = new Monster(mConfig);
|
||||
(async () => {
|
||||
const intervalId = setInterval(() => {
|
||||
monster.tick()
|
||||
;},1000)
|
||||
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user