UNPKG

node-red-contrib-xkeys_joystick

Version:

Xkeys joystick node for Node-RED using DCD Protocol

291 lines (267 loc) 9.84 kB
module.exports = function(RED) { const XKEYS_VENDOR_ID = "1523"; var mqtt = require('mqtt'); const connectUrl = 'mqtt://localhost'; const qos = 0; // Connection to local broker should be good var httpAdminDataProducts = {}; var httpAdminDataDevices = {}; function XkeysJoystick(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/#', function (err, granted) { 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', 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.server_id}`); if (message_obj.msg_type == "hello") { console.log(`Hello from DCDP server at ${message_obj.server_id} - 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 a dict of info objects httpAdminDataDevices = []; message_obj.devices.forEach( (device) => { if (device.vendorId == XKEYS_VENDOR_ID) { httpAdminDataDevices.push(device); } }); 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 = {}; // Restrict products to X-keys devices Object.keys(message_obj.data).forEach( (device) => { if (message_obj.data[device].vendorId == XKEYS_VENDOR_ID ) { httpAdminDataProducts[device] = message_obj.data[device]; } }); } else if (message_obj.msg_type == "device_layout_result") { //console.log(`device_layout_result: ${JSON.stringify(message_obj.svg_layout)}`); var layout = message_obj.svg_layout; const devicenam = Object.keys(layout)[0] /* Only proceed if result is for this device */ if ((devicenam.length == 0) || (devicenam != previous_devicenam) ) { return; } var devicedata = layout[devicenam]; if (Object.keys(devicedata).length == 0) { //console.log(`devicedata: None was returned`); /* Generate a buffer with data.length == 0 * Then client will respond by clearing existing layout */ layout[devicenam] = {"no_device":{"type":"Buffer","data":[]}}; devicedata = layout[devicenam]; } //console.log(`devicenam: ${devicenam}`); //console.log(`devicedata: ${JSON.stringify(devicedata)}`); const svg_filename_base = Object.keys(devicedata)[0]; const buf = Buffer.from(layout[devicenam][svg_filename_base]); //console.log(`buffer: ${buf}`); var d = { id:node.config.id }; d.data = buf.toString("base64"); try { RED.comms.publish("xkeys_joystick-image", d); } catch (err) { console.error(err); } } else if (message_obj.msg_type == "joystick_event") { // Check that PID & UID are what we're interested in var pid_list = this.config.pid_list || "[]"; var pids = JSON.parse(pid_list); if (pids.length == 0 || pids.includes(parseInt(message_obj.product_id))) { // Either no PID was configured i.e. any PID will do // or this event is for a PID that has been configured // Now filter Controller # if (message_obj.control_id == this.config.control_id || this.config.control_id == "") { // Either this event was for a configured control_id or no control_id was configured // Now filter Unit # if (message_obj.unit_id == this.config.unit_id || this.config.unit_id == "") { // Either event was for a configured unit_id or no unit_id was configured // Now filter Duplicate # if (message_obj.duplicate_id == this.config.duplicate_id || this.config.duplicate_id == "") { // Either event was for a configured duplicate_id or no duplicate_id was configured // Prepare output msg var msg = {}; msg["payload"] = { "device" : message_obj.device, "vendor_id" : message_obj.vendor_id, "product_id" : message_obj.product_id, "unit_id" : message_obj.unit_id, "duplicate_id" : message_obj.duplicate_id, "control_id" : message_obj.control_id, "x" : message_obj.x, "y" : message_obj.y, "z" : message_obj.z, "deltaZ" : message_obj.deltaZ }; node.send(msg); } } } } } else { // Logging here may be useful but is quietened for production //node.log('Received unhandled request: ' + message_obj.request); } } catch (e) { node.log('ERROR parsing message: ' + e); } }) // Input var previous_devicenam = "Any X-keys"; node.on('input', function(msg) { const mkeys = Object.keys(msg.payload); if (mkeys.includes("device") && (msg.payload.device == "Any X-keys")) { // Erase any existing layout (send empty image) var d = { id:node.id }; d.data = {"no_device":{"type":"Buffer","data":[]}}.toString("base64"); try { RED.comms.publish("xkeys_joystick-image", d); } catch (err) { console.error(err); } } else if (mkeys.includes("msg_type") && (msg.payload.msg_type == "layout_output")) { node.log(`Requesting: JSON.stringify({msg_type:"device_layout", device: msg.payload.device})`); client.publish('/dcdp/node', JSON.stringify({msg_type:"device_layout", device: msg.payload.device})); } previous_devicenam = msg.payload["device"]; }); 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]; const devs = Object.keys(httpAdminDataDevices); var device_matched = false; var regex_string = "" var regex; if (pids.length == 0) { // No product_ids provided => ANY product_id if (vendor_id) { regex_string = vendor_id + "-"; } else { regex_string = "\[0-9\]+-"; } 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; } // END function device_connected } // END function XkeysJoystick RED.nodes.registerType("xkeys_joystick", XkeysJoystick); RED.httpAdmin.get("/xkeys_joystick/products", function (req, res) { res.json(httpAdminDataProducts); }); RED.httpAdmin.get("/xkeys_joystick/devices", function (req, res) { res.json(httpAdminDataDevices); }); RED.httpAdmin.post("/xkeys_joystick_inject/:id", RED.auth.needsPermission("xkeys_joystick_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); } }); // END RED.httpAdmin.post() }