247 lines
8.3 KiB
JavaScript
247 lines
8.3 KiB
JavaScript
module.exports = function (RED) {
|
|
function rotatingMachine(config) {
|
|
RED.nodes.createNode(this, config);
|
|
var node = this;
|
|
|
|
try {
|
|
// Load Machine class and curve data
|
|
const Machine = require("./dependencies/machine/machine");
|
|
const OutputUtils = require("../generalFunctions/helper/outputUtils");
|
|
|
|
const machineConfig = {
|
|
general: {
|
|
name: config.name || "Default Machine",
|
|
id: node.id,
|
|
logging: {
|
|
enabled: config.eneableLog,
|
|
logLevel: config.logLevel
|
|
}
|
|
},
|
|
asset: {
|
|
supplier: config.supplier || "Unknown",
|
|
type: config.machineType || "generic",
|
|
subType: config.subType || "generic",
|
|
model: config.model || "generic",
|
|
machineCurve: config.machineCurve
|
|
}
|
|
};
|
|
|
|
const stateConfig = {
|
|
general: {
|
|
logging: {
|
|
enabled: config.eneableLog,
|
|
logLevel: config.logLevel
|
|
}
|
|
},
|
|
movement: {
|
|
speed: Number(config.speed)
|
|
},
|
|
time: {
|
|
starting: Number(config.startup),
|
|
warmingup: Number(config.warmup),
|
|
stopping: Number(config.shutdown),
|
|
coolingdown: Number(config.cooldown)
|
|
}
|
|
};
|
|
|
|
// Create machine instance
|
|
const m = new Machine(machineConfig, stateConfig);
|
|
|
|
// put m on node memory as source
|
|
node.source = m;
|
|
|
|
//load output utils
|
|
const output = new OutputUtils();
|
|
|
|
function updateNodeStatus() {
|
|
try {
|
|
const mode = m.currentMode;
|
|
const state = m.state.getCurrentState();
|
|
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue());
|
|
const power = Math.round(m.measurements.type("power").variant("predicted").position('upstream').getCurrentValue());
|
|
let symbolState;
|
|
switch(state){
|
|
case "off":
|
|
symbolState = "⬛";
|
|
break;
|
|
case "idle":
|
|
symbolState = "⏸️";
|
|
break;
|
|
case "operational":
|
|
symbolState = "⏵️";
|
|
break;
|
|
case "starting":
|
|
symbolState = "⏯️";
|
|
break;
|
|
case "warmingup":
|
|
symbolState = "🔄";
|
|
break;
|
|
case "accelerating":
|
|
symbolState = "⏩";
|
|
break;
|
|
case "stopping":
|
|
symbolState = "⏹️";
|
|
break;
|
|
case "coolingdown":
|
|
symbolState = "❄️";
|
|
break;
|
|
case "decelerating":
|
|
symbolState = "⏪";
|
|
break;
|
|
}
|
|
const position = m.state.getCurrentPosition();
|
|
const roundedPosition = Math.round(position * 100) / 100;
|
|
|
|
let status;
|
|
switch (state) {
|
|
case "off":
|
|
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
|
|
break;
|
|
case "idle":
|
|
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
|
|
break;
|
|
case "operational":
|
|
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
|
|
break;
|
|
case "starting":
|
|
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
|
break;
|
|
case "warmingup":
|
|
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
|
|
break;
|
|
case "accelerating":
|
|
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}%| 💨${flow}m³/h | ⚡${power}kW` };
|
|
break;
|
|
case "stopping":
|
|
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
|
break;
|
|
case "coolingdown":
|
|
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
|
|
break;
|
|
case "decelerating":
|
|
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
|
|
break;
|
|
default:
|
|
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
|
|
}
|
|
return status;
|
|
} catch (error) {
|
|
node.error("Error in updateNodeStatus: " + error.message);
|
|
return { fill: "red", shape: "ring", text: "Status Error" };
|
|
}
|
|
}
|
|
|
|
function tick() {
|
|
try {
|
|
const status = updateNodeStatus();
|
|
node.status(status);
|
|
|
|
//get output
|
|
const classOutput = m.getOutput();
|
|
const dbOutput = output.formatMsg(classOutput, m.config, "influxdb");
|
|
const pOutput = output.formatMsg(classOutput, m.config, "process");
|
|
|
|
//console.log(pOutput);
|
|
|
|
//only send output on values that changed
|
|
let msgs = [];
|
|
msgs[0] = pOutput;
|
|
msgs[1] = dbOutput;
|
|
|
|
node.send(msgs);
|
|
|
|
} 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 {
|
|
|
|
/* Update to complete event based node by putting the tick function after an input event */
|
|
|
|
|
|
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 '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 'showCompleteCurve':
|
|
m.showCompleteCurve();
|
|
send({ topic : "Showing curve" , payload: m.showCompleteCurve() });
|
|
break;
|
|
case 'CoG':
|
|
m.showCoG();
|
|
send({ topic : "Showing CoG" , payload: m.showCoG() });
|
|
break;
|
|
}
|
|
|
|
if (done) done();
|
|
} catch (error) {
|
|
node.error("Error processing input: " + error.message);
|
|
if (done) done(error);
|
|
}
|
|
});
|
|
|
|
node.on('close', function(done) {
|
|
if (node.interval_id) clearTimeout(node.interval_id);
|
|
if (node.tick_interval) clearInterval(node.tick_interval);
|
|
if (done) done();
|
|
});
|
|
|
|
} catch (error) {
|
|
node.error("Fatal error in node initialization: " + error.stack);
|
|
node.status({fill: "red", shape: "ring", text: "Fatal Error"});
|
|
}
|
|
}
|
|
|
|
RED.nodes.registerType("rotatingMachine", rotatingMachine);
|
|
}; |