UNPKG

node-red-contrib-xkeys_writedata

Version:

Xkeys Write Data node for Node-RED with Dynamic Control Data Protocol (DCDP)

307 lines (283 loc) 9.56 kB
module.exports = function(RED) { const XKEYS_VENDOR_ID = "1523"; var mqtt = require('mqtt'); const connectUrl = 'mqtt://localhost'; const qos = 0; var httpAdminDataProducts = {}; var httpAdminDataDevices = []; function XkeysWriteData(config) { RED.nodes.createNode(this, config); var node = this; node.config = config; //node.log(`node.config = ${JSON.stringify(node.config)}`); //node.log(`myId = ${node.config.id}`); var client = mqtt.connect(connectUrl); client.on('reconnect', (error) => { node.log('reconnecting:', error) }) client.on('error', (error) => { node.log('Connection failed:', error) }) client.on('connect', () => { node.log('connected') client.subscribe({'/dcdp/server/#':{qos:qos}}, function (err, granted) { if (!err) { node.log("Subscribed OK, granted: " + JSON.stringify(granted)); client.publish('/dcdp/node', JSON.stringify({msg_type:"all_product_data"})); client.publish('/dcdp/node/xkeys_writedata', JSON.stringify({msg_type:"list_attached"})); } else { node.log('Subscription failed: ' + err) } }) }) client.on('close', () => { node.log("connection closed"); }) client.on('message', (topic, message) => { //node.log(`received topic:${topic}, msg: ${message}`); var message_obj = ""; try { message_obj = JSON.parse(message); //node.log("SID = " + message_obj.sid); if (message_obj.msg_type == "hello") { console.log(`Hello from DCDP server at ${message_obj.sid} - must have just (re)started `); // In case dcdp-server restarted with updated devices/product list client.publish('/dcdp/node', JSON.stringify({msg_type:"all_product_data"})); client.publish('/dcdp/node', JSON.stringify({msg_type:"list_attached"})); } else if (message_obj.msg_type == "list_attached_result") { // data should be an array of infos httpAdminDataDevices = []; message_obj.devices.forEach( (device) => { // Limit to xkeys devices if (device.vendorId == XKEYS_VENDOR_ID) { httpAdminDataDevices.push(device); } }); // Based on attached deviceList, are we connected to something? var pid_list = this.config.pid_list || "[]"; if (device_connected(this.config.vendor_id || XKEYS_VENDOR_ID, JSON.parse(pid_list), this.config.unit_id, this.config.duplicate_id)) { node.status( {fill:"green",shape:"dot",text:"connected"} ); } else { node.status( {fill:"red",shape:"ring",text:"disconnected"} ); } } else if (message_obj.msg_type == "all_product_data_result") { // data should be a dict of product objects httpAdminDataProducts = {}; Object.keys(message_obj.data).forEach( (device) => { // Restrict to X-keys products if (message_obj.data[device].vendorId == XKEYS_VENDOR_ID ) { httpAdminDataProducts[device] = message_obj.data[device]; } }); } else { // Logging here may be useful but is quietened for production //node.log(`Received unhandled request: ${message_obj.msg_type}`); } } catch (e) { node.log('ERROR parsing message: ' + e); } }) /* * Input messages * We expect a msg.payloads of * */ node.on('input', function(msg) { //console.log(`INPUT: ${JSON.stringify(msg)}`); console.log(`msg.payload: ${JSON.stringify(msg.payload)}`); /* * For this node we require a pid_list, unit_id & duplicate_id all to be configured in the node itself. * * A commandcode can originate from the node itself (internal), * or from another prior node in the flow (external). * An internal command code takes precedence over an external one, * so only use the external one if no internal commandcode exists. */ var commandcode = []; var configured_commandcode = []; var message_commandcode = []; if (!node.config.commandcode ) { node.config.commandcode = "[]"; } try { configured_commandcode = JSON.parse(node.config.commandcode); } catch (err) { node.log("Error parsing configured commandcode: " + err); return; } if (configured_commandcode.length > 0) { commandcode = configured_commandcode; } else { try { message_commandcode = JSON.parse(msg.payload.commandcode); commandcode = message_commandcode; } catch (err) { node.log("Error parsing commandcode: " + err); return; } } /* Don't bother sending an empty commandcode */ if (commandcode.length == 0) { return; } /* If pid_list has multiple members, find the (first) one that matches an attached device. */ var configured_pid_list = []; var target_pid; try { configured_pid_list = JSON.parse(node.config.pid_list); configured_pid_list.every( (pid) => { if (httpAdminDataDevices.find( (device) => device.productId == pid)) { target_pid = pid.toString(); return false; } return true; }); } catch (err) { console.log(`Caught exception parsing node.config.pid_list. ${err}`); } var quad_list = []; var quad_list_regex_string = XKEYS_VENDOR_ID; quad_list_regex_string += "-" + target_pid; if (node.config.unit_id) { quad_list_regex_string += "-" + node.config.unit_id; } else { quad_list_regex_string += "-\[0-9\]+"; } if (node.config.duplicate_id) { quad_list_regex_string += "-" + node.config.duplicate_id; } else { quad_list_regex_string += "-\[0-9\]+"; } //console.log(`quad_list_regex_string = ${quad_list_regex_string}`); var regex = new RegExp(quad_list_regex_string); httpAdminDataDevices.forEach( (dev) => { //console.log(`dev: ${JSON.stringify(dev.device_quad)}`); if (regex.test(dev.device_quad)) { quad_list.push(dev.device_quad); } }); //console.log(`quad_list = ${JSON.stringify(quad_list)}`); if (quad_list.length == 0 ) { node.log(`No devices matched: discontinuing`); return; } /* Prepare output msg but only bother if we have a device with matching device_quad. */ httpAdminDataDevices.every( (device) => { if (quad_list.includes(device.device_quad)) { const command_message = { msg_type : "command", command_type : "write_data", vendor_id : XKEYS_VENDOR_ID, product_id : device.device_quad.split('-')[1], unit_id : device.device_quad.split('-')[2], duplicate_id : device.device_quad.split('-')[3], byte_array : commandcode } console.log(`command_message = ${JSON.stringify(command_message)}`); client.publish('/dcdp/node', JSON.stringify(command_message)); // Don't break the every loop on first match - find all matches. //return false; } return true; }); }) this.on('close', function(done) { client.end(); done(); }) // Does any attached device match specified pids, unit_id & dup_id ? // pids: array of possible PIDs for a device (empty => ANY) // unit_id: unitId of a device // dup_id: duplicate_id of a device function device_connected(...deviceArgs) { const vendor_id = deviceArgs[0]; const pids = deviceArgs[1]; const unit_id = deviceArgs[2]; const dup_id = deviceArgs[3]; var device_matched = false; var regex_string = "" var regex; if (pids.length == 0) { if (vendor_id) { regex_string = vendor_id + "-"; } else { regex_string = "\[0-9\]+-"; } // No product_ids provided => ANY product_id regex_string = regex_string + "\[0-9\]+-"; if (unit_id) { regex_string = regex_string + unit_id + "-"; } else { regex_string = regex_string + "\[0-9\]+-"; } if (dup_id) { regex_string = regex_string + dup_id; } else { regex_string = regex_string + "\[0-9\]+"; } regex = new RegExp(regex_string); httpAdminDataDevices.forEach( (item) => { if (regex.test(item.device_quad)) { device_matched = true; } }) } else { // An array of endpoints provided pids.forEach(function (item) { if (vendor_id) { regex_string = vendor_id + "-"; } else { regex_string = "\[0-9\]+-" } regex_string = regex_string + item + "-"; if (unit_id) { regex_string = regex_string + unit_id + "-"; } else { regex_string = regex_string + "\[0-9\]+-"; } if (dup_id) { regex_string = regex_string + dup_id; } else { regex_string = regex_string + "\[0-9\]+"; } regex = new RegExp(regex_string); httpAdminDataDevices.forEach( (item) => { if (regex.test(item.device_quad)) { device_matched = true; } }) }) } return device_matched; } // function device_connected } // function XkeysWriteData RED.nodes.registerType("xkeys_writedata", XkeysWriteData); RED.httpAdmin.get("/xkeys_writedata/products", function (req, res) { res.json(httpAdminDataProducts); }); RED.httpAdmin.get("/xkeys_writedata/devices", function (req, res) { res.json(httpAdminDataDevices); }); RED.httpAdmin.post("/xkeys_writedata_inject/:id", RED.auth.needsPermission("xkeys_writedata_inject.write"), function(req,res) { //console.log("posting something: " + JSON.stringify(req.body)); var node = RED.nodes.getNode(req.params.id); if (node != null) { try { if (req.body) { node.receive(req.body); } else { node.receive(); } res.sendStatus(200); } catch (err) { res.sendStatus(500); node.error(RED._("inject.failed",{error:err.toString()})); } } else { console.log("bad post"); res.sendStatus(404); } }); }