update sjoerd #1
701
dependencies/monster/SpeficicClass.js
vendored
Normal file
701
dependencies/monster/SpeficicClass.js
vendored
Normal file
File diff suppressed because one or more lines are too long
81
dependencies/monster/monster_class.js
vendored
81
dependencies/monster/monster_class.js
vendored
@@ -1,81 +0,0 @@
|
|||||||
|
|
||||||
// code is kopie van rotating machine specific class
|
|
||||||
|
|
||||||
//load local dependencies
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');
|
|
||||||
const { name } = require('../../generalFunctions/src/convert/lodash/lodash._shimkeys');
|
|
||||||
|
|
||||||
class Monster {
|
|
||||||
|
|
||||||
/*------------------- Construct and set vars -------------------*/
|
|
||||||
constructor(machineConfig = {}, stateConfig = {}, errorMetricsConfig = {}) {
|
|
||||||
|
|
||||||
//basic setup
|
|
||||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
|
||||||
|
|
||||||
this.logger = new logger(machineConfig.general.logging.enabled,machineConfig.general.logging.logLevel, machineConfig.general.name);
|
|
||||||
this.configManager = new configManager();
|
|
||||||
this.defaultConfig = this.configManager.getConfig('monster'); // Load default config for monster ( use software type name ? )
|
|
||||||
this.configUtils = new configUtils(this.defaultConfig);
|
|
||||||
|
|
||||||
// Load a specific curve
|
|
||||||
this.model = machineConfig.asset.model; // Get the model from the machineConfig
|
|
||||||
this.curve = this.model ? loadCurve(this.model) : null;
|
|
||||||
|
|
||||||
//Init config and check if it is valid
|
|
||||||
this.config = this.configUtils.initConfig(machineConfig);
|
|
||||||
|
|
||||||
//add unique name for this node.
|
|
||||||
this.config = this.configUtils.updateConfig(this.config, {general:{name: this.config.functionality?.softwareType + "_" + machineConfig.general.id}}); // add unique name if not present
|
|
||||||
|
|
||||||
if (!this.model || !this.curve) {
|
|
||||||
this.logger.error(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`);
|
|
||||||
// Set prediction objects to null to prevent method calls
|
|
||||||
this.predictFlow = null;
|
|
||||||
this.predictPower = null;
|
|
||||||
this.predictCtrl = null;
|
|
||||||
this.hasCurve = false;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
this.hasCurve = true;
|
|
||||||
this.config = this.configUtils.updateConfig(this.config, {
|
|
||||||
asset: { ...this.config.asset, machineCurve: this.curve }
|
|
||||||
});
|
|
||||||
machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
|
||||||
this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq }); // load nq (x : ctrl , y : flow relationship)
|
|
||||||
this.predictPower = new predict({ curve: this.config.asset.machineCurve.np }); // load np (x : ctrl , y : power relationship)
|
|
||||||
this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = new state(stateConfig, this.logger); // Init State manager and pass logger
|
|
||||||
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
|
||||||
|
|
||||||
// Initialize measurements
|
|
||||||
this.measurements = new MeasurementContainer();
|
|
||||||
this.interpolation = new interpolation();
|
|
||||||
|
|
||||||
this.flowDrift = null;
|
|
||||||
|
|
||||||
this.currentMode = this.config.mode.current;
|
|
||||||
this.currentEfficiencyCurve = {};
|
|
||||||
this.cog = 0;
|
|
||||||
this.NCog = 0;
|
|
||||||
this.cogIndex = 0;
|
|
||||||
this.minEfficiency = 0;
|
|
||||||
this.absDistFromPeak = 0;
|
|
||||||
this.relDistFromPeak = 0;
|
|
||||||
|
|
||||||
// When position state changes, update position
|
|
||||||
this.state.emitter.on("positionChange", (data) => {
|
|
||||||
this.logger.debug(`Position change detected: ${data}`);
|
|
||||||
this.updatePosition();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.child = {}; // object to hold child information so we know on what to subscribe
|
|
||||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Monster;
|
|
||||||
182
monster oud.js
Normal file
182
monster oud.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
module.exports = function (RED) {
|
||||||
|
function monster(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;
|
||||||
|
|
||||||
|
try{
|
||||||
|
|
||||||
|
|
||||||
|
// fetch monster object from monster.js
|
||||||
|
const Monster = require("./dependencies/monster/monster_class");
|
||||||
|
const OutputUtils = require("../generalFunctions/helper/outputUtils");
|
||||||
|
|
||||||
|
const mConfig={
|
||||||
|
general: {
|
||||||
|
name: config.name,
|
||||||
|
id: node.id,
|
||||||
|
unit: config.unit,
|
||||||
|
logging:{
|
||||||
|
logLevel: config.logLevel,
|
||||||
|
enabled: config.enableLog,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
supplier: config.supplier,
|
||||||
|
subType: config.subType,
|
||||||
|
model: config.model,
|
||||||
|
emptyWeightBucket: config.emptyWeightBucket,
|
||||||
|
},
|
||||||
|
constraints: {
|
||||||
|
minVolume: config.minVolume,
|
||||||
|
maxWeight: config.maxWeight,
|
||||||
|
samplingtime: config.samplingtime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// make new monster on creation to work with.
|
||||||
|
const m = new Monster(mConfig);
|
||||||
|
|
||||||
|
// put m on node memory as source
|
||||||
|
node.source = m;
|
||||||
|
|
||||||
|
//load output utils
|
||||||
|
const output = new OutputUtils();
|
||||||
|
|
||||||
|
//internal vars
|
||||||
|
this.interval_id = null;
|
||||||
|
|
||||||
|
//updating node state
|
||||||
|
function updateNodeStatus() {
|
||||||
|
try{
|
||||||
|
|
||||||
|
const bucketVol = m.bucketVol;
|
||||||
|
const maxVolume = m.maxVolume;
|
||||||
|
const state = m.running;
|
||||||
|
const mode = "AI" ; //m.mode;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
node.error("Error in updateNodeStatus: " + error);
|
||||||
|
return { fill: "red", shape: "ring", text: "Status Error" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tick(){
|
||||||
|
try{
|
||||||
|
// load status node
|
||||||
|
const status = updateNodeStatus();
|
||||||
|
// kick time based function in node
|
||||||
|
m.tick();
|
||||||
|
//show node status
|
||||||
|
node.status(status);
|
||||||
|
} catch (error) {
|
||||||
|
node.error("Error in tick function: " + error);
|
||||||
|
node.status({ fill: "red", shape: "ring", text: "Tick Error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register child on first output this timeout is needed because of node - red stuff
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
|
||||||
|
/*---execute code on first start----*/
|
||||||
|
let msgs = [];
|
||||||
|
|
||||||
|
msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upstream" };
|
||||||
|
msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downstream" };
|
||||||
|
|
||||||
|
//send msg
|
||||||
|
this.send(msgs);
|
||||||
|
},
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
|
//declare refresh interval internal node
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
/*---execute code on first start----*/
|
||||||
|
this.interval_id = setInterval(function(){ tick() },1000)
|
||||||
|
},
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
node.on('input', function (msg,send,done) {
|
||||||
|
try{
|
||||||
|
switch(msg.topic) {
|
||||||
|
case 'registerChild':
|
||||||
|
const childId = msg.payload;
|
||||||
|
const childObj = RED.nodes.getNode(childId);
|
||||||
|
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
||||||
|
break;
|
||||||
|
case 'setMode':
|
||||||
|
m.setMode(msg.payload);
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
m.i_start = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
node.error("Error in input function: " + error);
|
||||||
|
node.status({ fill: "red", shape: "ring", text: "Input Error" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.topic == "i_flow"){
|
||||||
|
monster.q = parseFloat(msg.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.topic == "i_start"){
|
||||||
|
monster.i_start = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.topic == "model_prediction"){
|
||||||
|
let var1 = msg.payload.dagvoorheen;
|
||||||
|
let var2 = msg.payload.dagnadien;
|
||||||
|
monster.get_model_prediction(var1, var2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.topic == "aquon_monsternametijden"){
|
||||||
|
monster.monsternametijden = msg.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(msg.topic == "rain_data"){
|
||||||
|
monster.rain_data = msg.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
//register child classes
|
||||||
|
if(msg.topic == "registerChild"){
|
||||||
|
let child = msg.payload;
|
||||||
|
monster.registerChild(child);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
// tidy up any async code here - shutdown connections and so on.
|
||||||
|
node.on('close', function() {
|
||||||
|
clearTimeout(this.interval_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
node.error("Error in monster function: " + error);
|
||||||
|
node.status({ fill: "red", shape: "ring", text: "Monster Error" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType("monster", monster);
|
||||||
|
|
||||||
|
};
|
||||||
201
monster.js
201
monster.js
@@ -1,182 +1,35 @@
|
|||||||
module.exports = function (RED) {
|
const nameOfNode = 'monster';
|
||||||
function monster(config) {
|
const nodeClass = require('./src/nodeClass.js');
|
||||||
|
const { MenuManager, configManager } = require('generalFunctions');
|
||||||
|
|
||||||
// create node
|
module.exports = function(RED) {
|
||||||
|
// 1) Register the node type and delegate to your class
|
||||||
|
RED.nodes.registerType(nameOfNode, function(config) {
|
||||||
RED.nodes.createNode(this, config);
|
RED.nodes.createNode(this, config);
|
||||||
|
this.nodeClass = new nodeClass(config, RED, this, nameOfNode);
|
||||||
|
});
|
||||||
|
|
||||||
// call this => node so whenver you want to call a node function type node and the function behind it
|
// 2) Setup the dynamic menu & config endpoints
|
||||||
var node = this;
|
const menuMgr = new MenuManager();
|
||||||
|
const cfgMgr = new configManager();
|
||||||
|
|
||||||
try{
|
// Serve /monster/menu.js
|
||||||
|
RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => {
|
||||||
|
try {
|
||||||
// fetch monster object from monster.js
|
const script = menuMgr.createEndpoint(nameOfNode, ['logger','position']);
|
||||||
const Monster = require("./dependencies/monster/monster_class");
|
res.type('application/javascript').send(script);
|
||||||
const OutputUtils = require("../generalFunctions/helper/outputUtils");
|
} catch (err) {
|
||||||
|
res.status(500).send(`// Error generating menu: ${err.message}`);
|
||||||
const mConfig={
|
|
||||||
general: {
|
|
||||||
name: config.name,
|
|
||||||
id: node.id,
|
|
||||||
unit: config.unit,
|
|
||||||
logging:{
|
|
||||||
logLevel: config.logLevel,
|
|
||||||
enabled: config.enableLog,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
asset: {
|
|
||||||
supplier: config.supplier,
|
|
||||||
subType: config.subType,
|
|
||||||
model: config.model,
|
|
||||||
emptyWeightBucket: config.emptyWeightBucket,
|
|
||||||
},
|
|
||||||
constraints: {
|
|
||||||
minVolume: config.minVolume,
|
|
||||||
maxWeight: config.maxWeight,
|
|
||||||
samplingtime: config.samplingtime,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// make new monster on creation to work with.
|
// Serve /monster/configData.js
|
||||||
const m = new Monster(mConfig);
|
RED.httpAdmin.get(`/${nameOfNode}/configData.js`, (req, res) => {
|
||||||
|
try {
|
||||||
// put m on node memory as source
|
const script = cfgMgr.createEndpoint(nameOfNode);
|
||||||
node.source = m;
|
res.type('application/javascript').send(script);
|
||||||
|
} catch (err) {
|
||||||
//load output utils
|
res.status(500).send(`// Error generating configData: ${err.message}`);
|
||||||
const output = new OutputUtils();
|
|
||||||
|
|
||||||
//internal vars
|
|
||||||
this.interval_id = null;
|
|
||||||
|
|
||||||
//updating node state
|
|
||||||
function updateNodeStatus() {
|
|
||||||
try{
|
|
||||||
|
|
||||||
const bucketVol = m.bucketVol;
|
|
||||||
const maxVolume = m.maxVolume;
|
|
||||||
const state = m.running;
|
|
||||||
const mode = "AI" ; //m.mode;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
} catch (error) {
|
|
||||||
node.error("Error in updateNodeStatus: " + error);
|
|
||||||
return { fill: "red", shape: "ring", text: "Status Error" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
function tick(){
|
|
||||||
try{
|
|
||||||
// load status node
|
|
||||||
const status = updateNodeStatus();
|
|
||||||
// kick time based function in node
|
|
||||||
m.tick();
|
|
||||||
//show node status
|
|
||||||
node.status(status);
|
|
||||||
} catch (error) {
|
|
||||||
node.error("Error in tick function: " + error);
|
|
||||||
node.status({ fill: "red", shape: "ring", text: "Tick Error" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register child on first output this timeout is needed because of node - red stuff
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
|
|
||||||
/*---execute code on first start----*/
|
|
||||||
let msgs = [];
|
|
||||||
|
|
||||||
msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upstream" };
|
|
||||||
msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downstream" };
|
|
||||||
|
|
||||||
//send msg
|
|
||||||
this.send(msgs);
|
|
||||||
},
|
|
||||||
100
|
|
||||||
);
|
|
||||||
|
|
||||||
//declare refresh interval internal node
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
/*---execute code on first start----*/
|
|
||||||
this.interval_id = setInterval(function(){ tick() },1000)
|
|
||||||
},
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
|
|
||||||
node.on('input', function (msg,send,done) {
|
|
||||||
try{
|
|
||||||
switch(msg.topic) {
|
|
||||||
case 'registerChild':
|
|
||||||
const childId = msg.payload;
|
|
||||||
const childObj = RED.nodes.getNode(childId);
|
|
||||||
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
|
||||||
break;
|
|
||||||
case 'setMode':
|
|
||||||
m.setMode(msg.payload);
|
|
||||||
break;
|
|
||||||
case 'start':
|
|
||||||
m.i_start = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
node.error("Error in input function: " + error);
|
|
||||||
node.status({ fill: "red", shape: "ring", text: "Input Error" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.topic == "i_flow"){
|
|
||||||
monster.q = parseFloat(msg.payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.topic == "i_start"){
|
|
||||||
monster.i_start = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.topic == "model_prediction"){
|
|
||||||
let var1 = msg.payload.dagvoorheen;
|
|
||||||
let var2 = msg.payload.dagnadien;
|
|
||||||
monster.get_model_prediction(var1, var2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.topic == "aquon_monsternametijden"){
|
|
||||||
monster.monsternametijden = msg.payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(msg.topic == "rain_data"){
|
|
||||||
monster.rain_data = msg.payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
//register child classes
|
|
||||||
if(msg.topic == "registerChild"){
|
|
||||||
let child = msg.payload;
|
|
||||||
monster.registerChild(child);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
// tidy up any async code here - shutdown connections and so on.
|
|
||||||
node.on('close', function() {
|
|
||||||
clearTimeout(this.interval_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
node.error("Error in monster function: " + error);
|
|
||||||
node.status({ fill: "red", shape: "ring", text: "Monster Error" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("monster", monster);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
220
src/nodeClass.js
Normal file
220
src/nodeClass.js
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* node class.js
|
||||||
|
*
|
||||||
|
* 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 Specific = require("./specificClass");
|
||||||
|
|
||||||
|
class nodeClass {
|
||||||
|
/**
|
||||||
|
* Create a Node.
|
||||||
|
* @param {object} uiConfig - Node-RED node configuration.
|
||||||
|
* @param {object} RED - Node-RED runtime API.
|
||||||
|
*/
|
||||||
|
constructor(uiConfig, RED, nodeInstance, nameOfNode) {
|
||||||
|
|
||||||
|
// Preserve RED reference for HTTP endpoints if needed
|
||||||
|
this.node = nodeInstance; // This is the Node-RED node instance, we can use this to send messages and update status
|
||||||
|
this.RED = RED; // This is the Node-RED runtime API, we can use this to create endpoints if needed
|
||||||
|
this.name = nameOfNode; // This is the name of the node, it should match the file name and the node type in Node-RED
|
||||||
|
this.source = null; // Will hold the specific class instance
|
||||||
|
this.config = null; // Will hold the merged configuration
|
||||||
|
|
||||||
|
// Load default & UI config
|
||||||
|
this._loadConfig(uiConfig,this.node);
|
||||||
|
|
||||||
|
// Instantiate core class
|
||||||
|
this._setupSpecificClass(uiConfig);
|
||||||
|
|
||||||
|
// Wire up event and lifecycle handlers
|
||||||
|
this._bindEvents();
|
||||||
|
this._registerChild();
|
||||||
|
this._startTickLoop();
|
||||||
|
this._attachInputHandler();
|
||||||
|
this._attachCloseHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and merge default config with user-defined settings.
|
||||||
|
* @param {object} uiConfig - Raw config from Node-RED UI.
|
||||||
|
*/
|
||||||
|
_loadConfig(uiConfig,node) {
|
||||||
|
|
||||||
|
// Merge UI config over defaults
|
||||||
|
this.config = {
|
||||||
|
general: {
|
||||||
|
id: node.id, // node.id is for the child registration process
|
||||||
|
unit: uiConfig.unit, // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards)
|
||||||
|
logging: {
|
||||||
|
enabled: uiConfig.enableLog,
|
||||||
|
logLevel: uiConfig.logLevel
|
||||||
|
}
|
||||||
|
},
|
||||||
|
asset: {
|
||||||
|
uuid: uiConfig.assetUuid, //need to add this later to the asset model
|
||||||
|
tagCode: uiConfig.assetTagCode, //need to add this later to the asset model
|
||||||
|
supplier: uiConfig.supplier,
|
||||||
|
category: uiConfig.category, //add later to define as the software type
|
||||||
|
type: uiConfig.assetType,
|
||||||
|
model: uiConfig.model,
|
||||||
|
unit: uiConfig.unit
|
||||||
|
},
|
||||||
|
functionality: {
|
||||||
|
positionVsParent: uiConfig.positionVsParent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility for formatting outputs
|
||||||
|
this._output = new outputUtils();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate the core Measurement logic and store as source.
|
||||||
|
*/
|
||||||
|
_setupSpecificClass(uiConfig) {
|
||||||
|
const monsterConfig = this.config;
|
||||||
|
|
||||||
|
this.source = new Specific(monsterConfig);
|
||||||
|
|
||||||
|
//store in node
|
||||||
|
this.node.source = this.source; // Store the source in the node instance for easy access
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind events to Node-RED status updates. Using internal emitter. --> REMOVE LATER WE NEED ONLY COMPLETE CHILDS AND THEN CHECK FOR UPDATES
|
||||||
|
*/
|
||||||
|
_bindEvents() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateNodeStatus() {
|
||||||
|
const m = this.source;
|
||||||
|
try{
|
||||||
|
const bucketVol = m.bucketVol;
|
||||||
|
const maxVolume = m.maxVolume;
|
||||||
|
const state = m.running;
|
||||||
|
const mode = "AI" ; //m.mode;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
} catch (error) {
|
||||||
|
node.error("Error in updateNodeStatus: " + error);
|
||||||
|
return { fill: "red", shape: "ring", text: "Status Error" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Register this node as a child upstream and downstream.
|
||||||
|
* Delayed to avoid Node-RED startup race conditions.
|
||||||
|
*/
|
||||||
|
_registerChild() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.node.send([
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{ topic: 'registerChild', payload: this.config.general.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
|
||||||
|
]);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the periodic tick loop.
|
||||||
|
*/
|
||||||
|
_startTickLoop() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this._tickInterval = setInterval(() => this._tick(), 1000);
|
||||||
|
|
||||||
|
// Update node status on nodered screen every second ( this is not the best way to do this, but it works for now)
|
||||||
|
this._statusInterval = setInterval(() => {
|
||||||
|
const status = this._updateNodeStatus();
|
||||||
|
this.node.status(status);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a single tick: update measurement, format and send outputs.
|
||||||
|
*/
|
||||||
|
_tick() {
|
||||||
|
this.source.tick();
|
||||||
|
|
||||||
|
const raw = this.source.getOutput();
|
||||||
|
const processMsg = this._output.formatMsg(raw, this.config, 'process');
|
||||||
|
const influxMsg = this._output.formatMsg(raw, this.config, 'influxdb');
|
||||||
|
|
||||||
|
// Send only updated outputs on ports 0 & 1
|
||||||
|
this.node.send([processMsg, influxMsg]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach the node's input handler, routing control messages to the class.
|
||||||
|
*/
|
||||||
|
_attachInputHandler() {
|
||||||
|
this.node.on('input', (msg, send, done) => {
|
||||||
|
/* Update to complete event based node by putting the tick function after an input event */
|
||||||
|
const m = this.source;
|
||||||
|
switch(msg.topic) {
|
||||||
|
case 'registerChild':
|
||||||
|
// Register this node as a child of the parent node
|
||||||
|
const childId = msg.payload;
|
||||||
|
const childObj = this.RED.nodes.getNode(childId);
|
||||||
|
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
|
||||||
|
break;
|
||||||
|
case 'setMode':
|
||||||
|
m.setMode(msg.payload);
|
||||||
|
break;
|
||||||
|
case 'execSequence':
|
||||||
|
const { source, action, parameter } = msg.payload;
|
||||||
|
m.handleInput(source, action, parameter);
|
||||||
|
break;
|
||||||
|
case 'execMovement':
|
||||||
|
const { source: mvSource, action: mvAction, setpoint } = msg.payload;
|
||||||
|
m.handleInput(mvSource, mvAction, Number(setpoint));
|
||||||
|
break;
|
||||||
|
case 'flowMovement':
|
||||||
|
const { source: fmSource, action: fmAction, setpoint: fmSetpoint } = msg.payload;
|
||||||
|
m.handleInput(fmSource, fmAction, Number(fmSetpoint));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'emergencystop':
|
||||||
|
const { source: esSource, action: esAction } = msg.payload;
|
||||||
|
m.handleInput(esSource, esAction);
|
||||||
|
break;
|
||||||
|
case 'showWorkingCurves':
|
||||||
|
m.showWorkingCurves();
|
||||||
|
send({ topic : "Showing curve" , payload: m.showWorkingCurves() });
|
||||||
|
break;
|
||||||
|
case 'CoG':
|
||||||
|
m.showCoG();
|
||||||
|
send({ topic : "Showing CoG" , payload: m.showCoG() });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up timers and intervals when Node-RED stops the node.
|
||||||
|
*/
|
||||||
|
_attachCloseHandler() {
|
||||||
|
this.node.on('close', (done) => {
|
||||||
|
clearInterval(this._tickInterval);
|
||||||
|
clearInterval(this._statusInterval);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nodeClass;
|
||||||
Reference in New Issue
Block a user