@metz-connect/node-red-ewio2
Version:
Provides nodes to access EWIO2 interfaces like IOs and metering - from Node-RED
282 lines (264 loc) • 12.6 kB
JavaScript
/**
* @file Configuration node to handle EWIO2 settings and connection to EWIO2.
* @author Metz Connect
*/
/**
* ewio2 config node namespace.
* @namespace Ewio2ConfigNode
*/
const dgram = require("dgram");
const os = require("os");
const ewioConnFile = require("./backend/ewio2Connection");
const {log, pubSub} = require("./backend/general");
const MESSAGE = Buffer.from("EWIO2?");
const MESSAGE2 = Buffer.from("EWIO2!");
const PORT = 1030;
const BROADCAST_ADDR = "255.255.255.255";
module.exports = function(RED) {
/**
* Stores settings from EWIO2 configuration menu.
* Closes websocket connection to EWIO2, if EWIO2 configuration node is disabled or deleted.
* @memberof Ewio2ConfigNode
* @param {Object} config - EWIO2 config settings from EWIO2 configuratoin menu (frontend).
*/
function EwioConfigNode(config) {
RED.nodes.createNode(this,config);
this.name = config.name;
this.host = config.host;
this.hostTemp = config.hostTemp;
this.encryption = config.encryption;
this.tlsConfig = config.tlsConfig;
let node = this;
node.on("close", function(removed, done) {
const MD5 = require("crypto-js/md5");
// node deleted or deactivated --> close and delete websocket
if (removed) {
log(node, 'EWIO2 config node disabled / deleted -> close websocket connection AND delete ewio2Connections entry: ' + node.id);
const userData = {configNodeId: node.id, host: node.host, user: node.credentials.username, pw: MD5(node.credentials.password), enc: node.encryption};
ewioConnFile.closeDeleteWebsocket( function () {
log(node, 'callback closeDeleteWebsocket fct DONE');
done();
}, userData, RED);
}
// node restarted
else {
done();
}
});
}
RED.nodes.registerType("EWIO2",EwioConfigNode, {
credentials: {
username: { type: "text" },
password: { type: "password" }
}
});
/**
* Send static files to frontend.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.get("/get/ewio2/scripts/*", function (req, res) {
var options = {
root: __dirname + "/frontend/",
dotfiles: "deny"
};
res.sendFile(req.params[0], options);
});
/**
* Receives discovery http post from frontend and starts discovery function.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/get/ewio2/discovery", RED.auth.needsPermission("ewio2.write"), function (req, res) {
discoverEwios(req.body);
res.sendStatus(200);
});
/**
* Sends the two discovery messages to broadcast address of all available network interfaces.
* Handles received discovery answers, wait 3s before the results are sorted. As last step the results are send to frontend.
* @memberof Ewio2ConfigNode
* @param {Object} idStrings - info strings (in selected browser language) of the current EWIO2, e.g. "noInfo" and "thisDevice".
*/
async function discoverEwios(idStrings) {
const discovery = require("./backend/discovery");
const netAddr = discovery.getNetworkInterfaceAddresses(os.networkInterfaces());
let ewioData = [];
let sockets = [];
// start broadcast for the address of current network interface
netAddr.forEach(function(localAddress) {
let socket = dgram.createSocket("udp4");
sockets.push(socket);
socket.bind(0, localAddress, function () {
socket.setBroadcast(true);
socket.send(MESSAGE, 0, MESSAGE.length, PORT, BROADCAST_ADDR);
socket.send(MESSAGE2, 0, MESSAGE2.length, PORT, BROADCAST_ADDR);
});
socket.on("message", function (message, remote) {
ewioData = discovery.handleReceivedMessage(message, remote, ewioData);
});
});
await discovery.waitForTimeout();
sockets.forEach(function(sock) {
sock.close();
});
ewioData = discovery.sortResults(ewioData);
// send array with found EWIO2 as response
RED.comms.publish("publish/ewio2/discovered", JSON.stringify( { data: ewioData, infoStrings: idStrings } ));
}
/**
* Checks if model string file is available in file system. This is needed to decide if Node-RED is running on EWIO2 or on PC.
* Closes websocket connection to EWIO2, if EWIO2 configuration node is disabled or deleted.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/get/ewio2/model", RED.auth.needsPermission("ewio2.write"), function (req, res) {
const fs = require('fs');
var ewioModel;
try {
// Node-RED running on EWIO2 (/var/etc/info/ewio2_model.info available)
ewioModel = fs.readFileSync("/var/etc/info/ewio2_model.info", {encoding: 'utf-8'});
}
catch (err) {
// Node-RED running on Win or Linux PC (/var/etc/info/ewio2_model.info not available)
ewioModel = "";
}
res.send(ewioModel);
});
/**
* Receives get settings http get request from frontend and returns settings.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.get("/get/ewio2/settings", RED.auth.needsPermission("ewio2.write"), function (req, res) {
const {getLogEnable} = require("./backend/general");
const logSettings = {enable: getLogEnable()};
res.send(logSettings);
});
/**
* Receives settings http post request from frontend and stores settings.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/put/ewio2/settings", RED.auth.needsPermission("ewio2.write"), function (req, res) {
const {setLogfileSettings} = require("./backend/general");
// convert string to bool
setLogfileSettings((req.body.enable == "true"));
res.sendStatus(200);
});
/**
* Backend functionality to get config data from EWIO2 and send them to frontend, to show in configuration menu.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/get/ewio2/configdata/:iotype/:prevIoPort/:nodeId/:counterId", RED.auth.needsPermission("ewio2.write"), async function (req, res) {
const node = RED.nodes.getNode(req.params.nodeId);
log(node, "get config data backend reached");
let userData;
// decide between frontend and backend user data, or a combination
if (RED.nodes.getNode(req.body.data.configNodeId)) {
userData = ewioConnFile.updateUserData(RED.nodes.getNode(req.body.data.configNodeId), req.body.data, RED, req.body.data.configNodeId);
}
// use frontend user data (e.g. create new node with new EWIO2 configuration node (where no credentials,... are available))
else if (req.body.data.user && req.body.data.pw) {
userData = ewioConnFile.updateUserData(undefined, req.body.data, RED, req.body.data.configNodeId);
}
// deactivated EWIO2 configuration node
else {
pubSub.publish("show-status", {"color": "red", "shape": "ring", "message": "status.configNodeMissing", "configNodeId": req.body.data.configNodeId});
RED.comms.publish("publish/ewio2/error/" + req.body.data.configNodeId, {code: ("status.configNodeMissing")});
res.sendStatus(200);
}
log(node, 'userData: ' + JSON.stringify(userData, null, 4));
if (userData) {
if (typeof userData === "number") {
log(node, 'sendStatus: ' + userData);
res.sendStatus(userData);
}
else {
try {
await ewioConnFile.getConfigData(userData, req.params.iotype, req.params.prevIoPort, req.params.counterId, req.params.nodeId, RED, req.body.tlsConfig);
res.sendStatus(200);
}
catch (error) {
log(node, 'get config backend error: ' + error)
res.sendStatus(404)
}
}
}
});
/**
* Backend functionality to close websocket connection and delete entry from ewio2Connections map. This is done when a EWIO2 configuration is saved or deleted.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/put/ewio2/closeDeleteWebsocket", RED.auth.needsPermission("ewio2.write"), function (req, res) {
const node = RED.nodes.getNode(req.body.configNodeId);
log(node, 'closeDeleteWebsocket backend reached');
let userData;
// decide between frontend and backend user data, or a combination
if (RED.nodes.getNode(req.body.configNodeId)) {
userData = ewioConnFile.updateUserData(RED.nodes.getNode(req.body.configNodeId), req.body, RED, req.body.configNodeId);
}
// use frontend user data (e.g. create new node with new EWIO2 configuration node (where no credentials,... are available))
else if (req.body.user && req.body.pw) {
userData = ewioConnFile.updateUserData(undefined, req.body, RED, req.body.configNodeId);
}
// deployed node and NOT deployed EWIO2 config node: create EWIO2 config node with bad credentials -> save -> edit EWIO2 config node -> delete --> show status for deployed node
else {
pubSub.publish("show-status", {"color": "red", "shape": "ring", "message": "status.configNodeMissing", "configNodeId": req.body.configNodeId});
RED.comms.publish("publish/ewio2/error/" + req.body.configNodeId, {code: ("status.configNodeMissing")});
res.sendStatus(200);
}
log(node, 'userData: ' + JSON.stringify(userData, null, 4))
if (userData) {
if (typeof userData === "number") {
log(node, 'sendStatus: ' + userData)
res.sendStatus(userData);
}
else {
try {
log(node, 'close delete websocket');
ewioConnFile.closeDeleteWebsocket(undefined, userData, RED);
res.sendStatus(200);
}
catch (error) {
log(node, 'close delete websocket backend error: ' + error);
res.sendStatus(404)
}
}
}
});
/**
* Receives analog input configuration http post request from frontend and set configuration at EWIO2.
* @memberof Ewio2ConfigNode
*/
RED.httpAdmin.post("/put/ewio2/aiConfig/:configValue/:ioPort/:nodeId", RED.auth.needsPermission("ewio2.write"), async function (req, res) {
const node = RED.nodes.getNode(req.params.nodeId);
log(node, 'put ai config backend reached');
let userData;
// decide between frontend and backend user data, or a combination
if (RED.nodes.getNode(req.body.data.configNodeId)) {
userData = ewioConnFile.updateUserData(RED.nodes.getNode(req.body.data.configNodeId), req.body.data, RED, req.body.data.configNodeId);
}
// use frontend user data (e.g. create new node with new EWIO2 configuration node (where no credentials,... are available))
else if (req.body.data.user && req.body.data.pw) {
userData = ewioConnFile.updateUserData(undefined, req.body.data, RED, req.body.data.configNodeId);
}
// deactivated EWIO2 configuration node
else {
pubSub.publish("show-status", {"color": "red", "shape": "ring", "message": "status.configNodeMissing", "configNodeId": req.body.data.configNodeId});
RED.comms.publish("publish/ewio2/error/" + req.body.data.configNodeId, {code: ("status.configNodeMissing")});
res.sendStatus(200);
}
log(node, 'userData: ' + JSON.stringify(userData, null, 4));
if (userData) {
if (typeof userData === "number") {
log(node, 'sendStatus: ' + userData);
res.sendStatus(userData);
}
else {
try {
await ewioConnFile.setValue(userData, req.params.configValue, "ai", req.params.ioPort, req.params.nodeId, RED, "setConfig", req.body.tlsConfig);
res.sendStatus(200);
}
catch (error) {
log(node, 'put ai config backend error: ' + error);
res.sendStatus(404)
}
}
}
});
}