UNPKG

node-red-contrib-digital-factory

Version:

Node-Red nodes for Supply Chain Wizard's Digital Factory Platform

451 lines (392 loc) 20.1 kB
module.exports = function(RED) { // Main function called by Node-RED function scwDigitalFactoryNode(config) { RED.nodes.createNode(this,config); let node = this; var flowContext = this.context().flow; var localTwin = {}; // Retrieve the config node node.hub = RED.nodes.getNode(config.hub); node.portal = RED.nodes.getNode(config.portal); const statusEnum = { idle: { color: "gray", text: "Idle" }, disconnected: { color: "red", text: "Disconnected" }, connected: { color: "green", text: "Connected" }, sending: { color: "blue", text: "Sending message" }, sent: { color: "blue", text: "Sent message" }, received: { color: "yellow", text: "Received" }, error: { color: "red", text: "Error" }, twinUpdated: { color: "blue", text: "Twin Updated"}, uploadError: { color: "gray", text: "Upload Error" }, uploaded: { color: "blue", text: "File Uploaded" }, twinReceived : { color: "blue", text: "Desired Props Received" } }; var setStatus = function (status) { node.status({ fill: status.color, shape: "dot", text: status.text }); } //setStatus(statusEnum.connected); let Protocol = require('azure-iot-device-mqtt').Mqtt; let Client = require('azure-iot-device').Client; let Message = require('azure-iot-device').Message; if(!node.hub || !node.hub.deviceId || !node.hub.credentials.deviceKey) {return} let deviceId = "" if (node.hub.deviceId && node.hub.deviceIdType) { let msg = {} RED.util.evaluateNodeProperty(node.hub.deviceId,node.hub.deviceIdType,node,msg,(err,ivalue) => { if (err) { node.error(err) } else { deviceId = ivalue } }); } let deviceKey = "" if (node.hub.deviceKey && node.hub.deviceKeyType) { let msg = {} RED.util.evaluateNodeProperty(node.hub.deviceKey,node.hub.deviceKeyType,node,msg,(err,ivalue) => { if (err) { node.error(err) } else { deviceKey = ivalue } }); } if(!deviceId || !deviceKey) {return} let deviceConnectionString = "HostName=scw.azure-devices.net;DeviceId="+deviceId+";SharedAccessKey="+deviceKey let client = Client.fromConnectionString(deviceConnectionString, Protocol); function connectCallback (err) { if (err) { node.error('Could not connect: ' + err.message); client.close(); client = undefined; //client.open(connectCallback); return } setStatus(statusEnum.connected) node.log('Connected to IoT Hub'); // Create device Twin try {client.getTwin(function(err, twin) { if (err) { node.error('could not get twin'); } else { flowContext.set("twinProps", twin.properties) //flowContext.set("twin", twin) localTwin = twin.properties twin.on('properties.desired', function(delta) { setStatus(statusEnum.twinReceived); let msgToSend; msgToSend = {topic : "twinUpdate", payload: delta} node.send(msgToSend) Object.keys(delta).forEach(function(key) { if (delta[key] === null && localTwin.desired[key]) { // If our patch contains a null value, but we have a record of // this module, then this is a delete operation. delete localTwin[key]; } else if (delta[key]) { if (localTwin.desired[key]) { // Our patch contains a module, and we've seen this before. // Must be an update operation. // Store the complete object instead of just the delta localTwin.desired[key] = twin.properties.desired[key]; } else { // Our patch contains a module, but we've never seen this // before. Must be an add operation. // Store the complete object instead of just the delta //console.log(twin.properties.desired) localTwin.desired[key] = twin.properties.desired[key]; } } }); // flowContext.set("twinProps", localTwin) }); } }) } catch (error) {node.error(error)} try {client.onDeviceMethod('customMethod', onCustomMethod);} catch (error) {node.error(error)}; try {client.onDeviceMethod('systemMethod', onSystemMethod);} catch (error) {node.error(error)}; function onCustomMethod(request, response) { node.status({ fill: "blue", shape: "dot", text: "Received " + request.methodName }); flowContext.set(request.methodName + "_" + request.requestId, {request: request, response: response}) let msgToSend; msgToSend = {topic : request.methodName, messageId : request.requestId, payload: request.payload} if(request.payload != null && typeof request.payload !== "undefined") {msgToSend.action = request.payload.action} node.send(msgToSend, false) } function backup(response, filename, includeLogs) { try { let stream = require('stream') let archiver = require("archiver") let streamifier = require("streamifier") let deviceDate = new Date(); if (filename && filename != "") { filename = "backup_app-" + filename + ".zip" } else { filename = "backup_app-" + deviceId + "-" + deviceDate.getUTCFullYear() + "-" + (deviceDate.getUTCMonth() + 1) + "-" + deviceDate.getUTCDate() + "-" + deviceDate.getUTCHours() + "-" + deviceDate.getUTCMinutes() + "-" + deviceDate.getUTCSeconds() + ".zip" } class WritableBufferStream extends stream.Writable { constructor(options) { super(options); this._chunks = []; } _write (chunk, enc, callback) { this._chunks.push(chunk); return callback(null); } _destroy(err, callback) { this._chunks = null; return callback(null); } toBuffer() { return Buffer.concat(this._chunks); } } let writeStream = new WritableBufferStream(); // wait for the writing to finish writeStream.on('finish', () => { // console log pdf as bas64 string let myBuffer = writeStream.toBuffer() let myStream = streamifier.createReadStream(myBuffer); client.uploadToBlob(filename, myStream, myBuffer.byteLength, function (err) { if (err) {response.send(500,'Error uploading file: ' + err.toString()) } else { response.send(200,'File uploaded : ' + filename) } }); }); let archive = archiver('zip', { zlib: { level: 9 } // Sets the compression level. }); archive.pipe(writeStream); let workingDir = RED.settings.userDir; if (includeLogs) { archive.glob('!(node_modules|.npm)',{ cwd: workingDir, dot:true }); archive.glob('!(node_modules|.npm)/**',{ cwd: workingDir, dot:true }); } else { archive.glob('!(node_modules|.npm|logs)',{ cwd: workingDir, dot:true }); archive.glob('!(node_modules|.npm|logs)/**',{ cwd: workingDir, dot:true }); } archive.finalize(); } catch (error) { console.log(error) } } function onSystemMethod(request, response) { node.status({ fill: "blue", shape: "dot", text: "Received " + request.methodName }); if(request.payload === null || typeof request.payload == "undefined") {response.send(500, 'Payload Required'); return} let os = require("os") let isWin = (os.platform() === 'win32'); switch (request.payload.action) { case "screenshot": if (!isWin) {response.send(500, 'Only supported on Windows systems'); return;} let nircmd = require('nircmd'); let deviceDate = new Date(); let filename = request.payload.filename if (filename && filename != "") { filename = "screenshot-" + filename + ".jpg" } else { filename = "screenshot-" + deviceId + "-" + deviceDate.getUTCFullYear() + "-" + (deviceDate.getUTCMonth() + 1) + "-" + deviceDate.getUTCDate() + "-" + deviceDate.getUTCHours() + "-" + deviceDate.getUTCMinutes() + "-" + deviceDate.getUTCSeconds() + ".jpg" } nircmd('savescreenshot ' + filename).then(() => { let fs = require('fs'); fs.stat(filename, function (err, stats) { const rr = fs.createReadStream(filename); client.uploadToBlob(filename, rr, stats.size, function (err) { if (err) {response.send(500,'Error uploading file: ' + err.toString()); fs.unlink(filename, (err) => { if (err) { console.error(err) return } //file removed }) } else { response.send(200,'File uploaded : ' + filename) fs.unlink(filename, (err) => { if (err) { console.error(err) return } //file removed }) } }); }); }); break; case "restart_app": try { let { spawnSync } = require( 'child_process' ); let cmd = spawnSync( 'pm2', [ 'restart', 'node-red' ] ); response.send(200,JSON.stringify({stderr: cmd.stderr.toString(), stdout: cmd.stdout.toString()})); } catch (error) { console.log(error) } break; case "shutdown_app": try { let { spawnSync } = require( 'child_process' ); let cmd = spawnSync( 'pm2', [ 'stop', 'node-red' ] ); response.send(200,JSON.stringify({stderr: cmd.stderr.toString(), stdout: cmd.stdout.toString()})); } catch (error) { console.log(error) } break; case "npm": try { let { spawnSync } = require( 'child_process' ); let cmd = spawnSync( 'npm', request.payload.args ); response.send(200,JSON.stringify({stderr: cmd.stderr.toString(), stdout: cmd.stdout.toString()})); } catch (error) { console.log(error) } break; case "backup": backup(response, null, request.payload.includeLogs) break; case "ping": response.send(200, 'Pong'); break; default: response.send(500, 'Action not supported'); break; } } client.on('disconnect', function () { if (client) { try { node.warn('Disconnecting from Azure IoT Hub'); client.removeAllListeners(); client = null; setStatus(statusEnum.disconnected); } catch (error) { node.error("Disconnect : " + JSON.stringify(error)) } } }); client.on('error', function (err) { node.error("Client Error : " + JSON.stringify(err.message)); }); client.on('message', function (msg) { // We received a message // node.log('Message received from Azure IoT Hub\n Id: ' + msg.messageId + '\n Payload: ' + msg.data); let receivedMessage = {} try { receivedMessage.payload = JSON.parse(msg.data.toString()) } catch (error) { receivedMessage.payload = msg.data.toString() } receivedMessage.topic = "c2d" receivedMessage.messageId = "-" node.send(receivedMessage); client.complete(msg, function () {}); }); } client.open(connectCallback); node.on('input', function(msg, send, done) { if (!client) { try { node.log('Reconnecting to IoT Hub'); client = Client.fromConnectionString(deviceConnectionString, Protocol); client.open(connectCallback); } catch (error) { node.error("case 1 " + JSON.stringify(error)) done(error) } } switch (msg.topic) { case "twinUpdate": // create a patch to send to the hub let patch = msg.payload; // send the patch let localTwin = flowContext.get("twinProps") localTwin.reported.update(patch, function(err) { if (err) done(err); setStatus(statusEnum.twinUpdated) }); break; case "telemetry": let data = JSON.stringify(msg.payload); let message = new Message(data); client.sendEvent(message, function (err) { if (err) { done(err) } else { setStatus(statusEnum.sent) done();} }); break case "fileUpload": let streamifier = require('streamifier'); let path = require("path"); let filename = path.basename(msg.filename); let payload = msg.payload if (!filename) {done("filename must be provided")} if (!payload) {done("may.payload must be provided")} let myStream = streamifier.createReadStream(msg.payload); client.uploadToBlob(filename, myStream, msg.payload.byteLength, function (err) { if (err) { setStatus(statusEnum.uploadError); done('Error uploading file: ' + err.toString()); } else { setStatus(statusEnum.uploaded) done(); } }); break default: done("Unknown Topic Received"); break; } }); node.on('close', function(removed, done) { if (removed) { // This node has been deleted node.log('Disconnecting from IoT Hub'); client.removeAllListeners(); client.close( function() { } ); client = null; } else { //node restarted try { node.log('Disconnecting from IoT Hub'); client.removeAllListeners(); client.close( function() { } ); node.log('Reconnecting to IoT Hub'); client = Client.fromConnectionString(deviceConnectionString, Protocol); client.open(connectCallback); } catch (error) { node.log(error) } } if (done) {done();} }); } // Registration of the node into Node-RED RED.nodes.registerType("digitalfactory",scwDigitalFactoryNode, { defaults: { name: {value:""}, hub: {value:"", type:"DF Device Hub"}, portal: {value: "", type:"DF Portal"}, } } ); }