node-red-contrib-xkeys_joystick
Version:
Xkeys joystick node for Node-RED using DCD Protocol
301 lines (266 loc) • 11.8 kB
HTML
<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>