UNPKG

node-red-contrib-xkeys_joystick

Version:

Xkeys joystick node for Node-RED using DCD Protocol

301 lines (266 loc) 11.8 kB
<script type="text/javascript"> function doInject(node, customMsg) { console.log(`doInject: ${node.id}`); //console.log(`doInject: ${JSON.stringify(customMsg)}`); $.ajax({ url: "xkeys_joystick_inject/" + node.id, type: "POST", data: JSON.stringify(customMsg), contentType: "application/json; charset=utf-8" }); } // END doInject() RED.nodes.registerType('xkeys_joystick',{ category: 'Xkeys', color: '#5dc8f4', defaults: { newname: {value:"X-keys Joystick"}, name: {value:""}, device: {value:""}, productid: {value:"", validate:function(v){ return v==""?true:(999 < parseInt(v) && parseInt(v) < 10000); } }, pid_list: {value:"[]"}, unit_id: {value:"", validate:function(v){ return v==""?true:(-1 < parseInt(v) && parseInt(v) < 256); } }, duplicate_id: {value:"", validate:function(v){ return v==""?true:(-1 < parseInt(v) && parseInt(v) < 256); } }, control_id: {value:"", validate:function(v){ return v==""?true:(-1 < parseInt(v)); } }, layout_output: {value: false}, layout_width: {value: 240}, }, inputs:0, outputs:1, icon: "X-keysXicon.png", label: function() { return this.name||this.newname||"X-keys Joystick"; }, labelStyle: function () { return this.newname?"node_label_italic":""; }, paletteLabel: "xk joystick", oneditprepare: function() { var device_options = []; device_options.push({ value: "Any X-keys", label: "Press Deploy button to see device list"}); $.getJSON('xkeys_joystick/products', function(product_data) { var devs = Object.keys(product_data); if (devs.length !== 0) { for (var d in devs) { if (devs.hasOwnProperty(d)) { if (product_data[devs[d]].hasOwnProperty("hasJoystick")) { //console.log(`${product_data[devs[d]].name} hasJoystick`); device_options.push({ label: product_data[devs[d]].name, value: devs[d]}); } } } } if (device_options.length > 1) { // Replace suggestion to deploy flow device_options[0] = { value: "Any X-keys", label: "Any X-keys"}; } $("#node-input-device").typedInput({ types: [{ value: "device", options: device_options }] }); if ($("#node-input-device").val() == "Any X-keys") { $("#node-input-productid").attr("placeholder", "Any"); } else { $("#node-input-productid").attr("placeholder", "Auto"); } $("#node-input-productid").on('change', function(ev, evtype, value) { // If productid is deleted (becomes Auto), // reset pid_list to whatever device is currently selected if ($("#node-input-productid").val().length == 0) { //var current_device = $("#node-input-device").val(); set_pid_list($("#node-input-device").val()); } }); $("#node-input-device").on('change', function(ev, evtype, value) { set_pid_list(value); }); /* * Setup pid_list for a given device */ function set_pid_list (devicenam) { if (devs.includes(devicenam)) { // Collect the possible PIDs var hidDevs = []; product_data[devicenam].hidDevices.forEach(function(product_id) { hidDevs.push(product_id[0]); }) // Save the pid_list for later matching with attached devices $("#node-input-pid_list").val(JSON.stringify(hidDevs)); $("#node-input-productid").attr("placeholder", "Auto"); $("#node-input-productid").val(""); } else if (devicenam == "Any X-keys") { $("#node-input-pid_list").val(JSON.stringify([])); $("#node-input-productid").attr("placeholder", "Any"); $("#node-input-productid").val(""); } else { console.log("Unknown device selected"); } }; // End of set_pid_list(devicenam) }); // END $.getJSON }, // END oneditprepare oneditsave: function () { let node = this; // Construct label based on configured options node.newname = $("#node-input-device").typedInput("value"); var duplicate_id = $("#node-input-duplicate_id").val(); var unit_id = $("#node-input-unit_id").val(); var control_id = $("#node-input-control_id").val(); if (unit_id) { node.newname += " (" + unit_id + ")" + " Joystick"; } else { node.newname += " Joystick"; } if (control_id) { node.newname += " " + control_id; } // Handle case when productid was specified var productid = $("#node-input-productid").val(); if (productid) { var pid_list = []; pid_list.push(parseInt(productid)); $("#node-input-pid_list").val(JSON.stringify(pid_list)); } var layout_width = $("#node-input-layout_width").val(); if (!layout_width) { layout_width = $("#node-input-layout_width").attr("placeholder"); $("#node-input-layout_width").val(layout_width); } node.layout_width = parseInt(layout_width); /* Inject a layout display message */ remove(node.id); // Clear any existing layout if ($("#node-input-layout_output").prop("checked")) { doInject(node, {"payload": {"msg_type": "layout_output", "device": $("#node-input-device").typedInput("value")}}); } } // END oneditsave() }); // END RED.nodes.registerType() var remove = function(nodeid) { const id = nodeid; const $img = document.getElementById("image-output-img-" + id); $img && $img.remove(); } var render = function(id, data, node, layout_width) { let $img = document.getElementById("image-output-img-" + id); if (!$img) { const $container = document.getElementById(id); if (!$container) { return } const img = document.createElementNS("http://www.w3.org/2000/svg", 'image'); img.setAttribute('id', "image-output-img-" + id); img.setAttribute('x', '0'); img.setAttribute('y', '46'); img.setAttribute('width', layout_width || DEFAULT_IMAGE_WIDTH); img.addEventListener("click", function(){remove(id)}, {once:true}); $container.insertBefore(img, $container.lastChild.nextSibling); $img = img; } if (data.startsWith("http")) { $img.setAttribute('href', data); } else { $img.setAttribute('href', "data:image/svg+xml;base64," + data); } } RED.comms.subscribe('xkeys_joystick-image', function (event, data) { var layout_width = $("#node-input-layout_width").val(); if ((data.hasOwnProperty("data")) && (data.data.length > 0)) { var svg_data = data.data; render(data.id, data.data, RED.nodes.node(data.id), layout_width); } else { remove(data.id); } }); // END RED.comms.subscribe() </script> <script type="text/html" data-template-name="xkeys_joystick"> <div class="form-row"> <label for="node-input-device"><i class="fa fa-keyboard-o"></i> Device</label> <input type="text" id="node-input-device" placeholder="Device"> </div> <div class="form-row"> <label for="node-input-productid"><i class="fa fa-info"></i> Product ID</label> <input type="text" id="node-input-productid" placeholder="Any"> </div> <div class="form-row"> <label for="node-input-unit_id"><i class="fa fa-info-circle"></i> Unit ID</label> <input type="text" id="node-input-unit_id" placeholder="Any"> </div> <div class="form-row"> <label for="node-input-duplicate_id"><i class="fa fa-list-ol"></i> Duplicate ID</label> <input type="text" id="node-input-duplicate_id" placeholder="Any"> </div> <div class="form-row"> <div class="form-row"> <label for="node-input-control_id"><i class="fa fa-square-o"></i> Controller #</label> <input type="text" id="node-input-control_id" placeholder="All"> </div> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Label</label> <input type="text" id="node-input-name" placeholder="Auto"> </div> <div class="form-row"> <label for="node-input-layout_output" style="width:380px;"><i class="fa fa-sqare-o"> </i> Enable Layout Output</label> <input type="checkbox" id="node-input-layout_output" style="width:64px;" checked="false"> </div> <div class="form-row"> <label for="node-input-layout_width"><i class="fa fa-arrows-h"></i> Layout width</label> <input type="text" id="node-input-layout_width" placeholder=240> </div> <div class="form-row"> <input type="hidden" id="node-input-pid_list"> </div> </script> <!-- HELP --> <script type="text/html" data-help-name="xkeys_joystick"> <p>X-keys input node, sends changes from the joystick control. Generates a <code>msg.payload</code> with X,Y,Z and delta Z info when the handle is moved. This node is only for the X,Y,Z data from the joystick.</p> <h3>Properties</h3> <p>By default this node will send analog events for any joystick on any X-keys connected by the USB. By setting these properties the node will only send events matching the desired criteria.</p> <li><b>Device:</b> Select from the list the name of specific source X-keys </li> <li><b>Product ID:</b> A specific PID can be set, usually the device name is sufficient and this can be left to the default.</li> <li><b>Unit ID:</b> A specific Unit can be selected, this is useful if there are 2 indentical X-keys. Each one can have a different UID if set using the xk Set Unit ID node. </li> <li><b>Controller #:</b> An Index if there are more than one joystick on one X-keys. Currently all units only have one. Leave this value at the default.</li> <h3>Outputs</h3> <p><code>msg.payload</code> An object that contains the compete information from the input event</p> <li><code>msg.payload.device</code> is an abbreviated name of the source device e.g. XK12JOYSTICK which represents the XK-12 Joystick device </li> <li><code>msg.payload.pid</code> is the Product ID of the source device e.g. 1065 for the XK-12 Joystick </li> <li><code>msg.payload.uid</code> is the Unit ID of the source device, typically 0 from the factory but assignable 0-255 using the xk Set Unit ID node </li> <li><code>msg.payload.index</code> is the Controller # which caused the event, default is 0 for devices with only 1 joystick. </li> <li><code>msg.payload.x</code> is the X motion, 0 to 127 from center to full right, 0 to -127 from center to full left. </li> <li><code>msg.payload.y</code> is the Y motion, 0 to 127 from center to full up, 0 to -127 from center to full down. </li> <li><code>msg.payload.z</code> is the Z Position, twist of stick, absolute 0 to 255, rolls over. </li> <li><code>msg.payload.delta_z</code> is the integer value of the change of the Z position since last read. Positive =CW, Negtive =CCW </li> <h3>Details</h3> <p>This node is used to get analog changes events from a Joystick on a connected X-keys. The xk button node would be used to get the button events. </p> <p>The X and Y axis are typical for a joystick. The Z (rotation of stick) is an absolute position; it is static and does not return to zero by a spring as a typical joystick. The delta_z payload compares the current value to the last value and accounts for the zero crossing of the position value. </p> <p>Several filters can be set in the node’s properties to make the node respond to specific X-keys and/or specific actions. Alternatively, the payload may be passed on to a switch node to branch the flow for each type of action or device. </p> <p>This node requires the X-keys Server to be installed. X-keys server requires an MQTT broker, which also must be installed.</p> <h3>References</h3> <ul> <li><a href="https://www.xkeys.com">For a full description of all X-keys products</a></li> <li><a href="https://gitlab.com/chris.willing/xkeys-server">X-keys server installation</a></li> </ul> </script>