UNPKG

node-red-contrib-buffer-parser

Version:

Node-red nodes to convert values to and from buffer/array. Supports Big/Little Endian, BCD, byte swapping and much more

901 lines (839 loc) 58.4 kB
<!-- MIT License Copyright (c) 2020 Steve-Mcl Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. --> <script type="text/javascript"> (function () { // console.log("Initialising buffer-parser"); //uncomment me to help locate SRC in devtools! var compatibleDataTypes = { 'int': 'int8', 'uint': 'uint8', 'int16': 'int16be', 'uint16': 'uint16be', 'int32': 'int32be', 'uint32': 'uint32be', 'bigint64': 'bigint64be', 'biguint64': 'biguint64be', 'float': 'floatbe', 'double': 'doublebe', '16bit': '16bitbe', 'boolean': 'bool' } function describeItem (item, index) { if(!item) return ""; var name = item.name; var typeInfo = item.type; if(item.scale) { var operator = ""; try { var scale = item.scale.trim(); if(scale == "!" || scale == "!!") { operator = scale; } else { var matches = scale.matchAll( /\s*?([\/\-\+\*<>^!%=]*?)\s*?(\w+)/g); for (const match of matches) { operator = match["1"].trim(); break; } } } catch (e) { } switch (operator) { case "!": case "!!": case ">": case "<": case "==": case "!=": typeInfo = "boolean"; } } return name + " {" + typeInfo + "}"; } function describeConfig(node) { var uiSpec = node.specificationType == "ui"; if(!uiSpec) return "<dynamic>"; if(node.resultType == "buffer") return "Buffer"; var itemCount = (node && node.items) ? node.items.length : 0; if(!itemCount) return "No items specified!"; var t = ""; if(node.multipleResult) { switch (node.resultType) { case "value": t = itemCount + (itemCount == 1 ? " value" : " values") break; case "object": t = itemCount + (itemCount == 1 ? " object" : " objects") break; } if(node.setTopic) t += " (name as topic)"; else t += " (original msg topic)"; } else { switch (node.resultType) { case "value": t = "Array containing " + itemCount + (itemCount == 1 ? " value" : " values"); break; case "array": t = "Array containing " + itemCount + (itemCount == 1 ? " object" : " objects"); break; case "keyvalue": t = "Object containing " + itemCount + (itemCount == 1 ? " key/value property" : " key/value properties"); break; case "keyobject": t = "Object containing " + itemCount + (itemCount == 1 ? " key/object property" : " key/object properties"); break; } } return t; } function coerceDataType(t) { return compatibleDataTypes[t] || t; } function setupTypedInput(varName, allowableTypes, defType, defVal, isOptional, width) { var node = this; console.log("setupTypedInput", varName, allowableTypes, defType, defVal, isOptional, width); //TODO: move to common library function isObject(val) { if (val === null) { return false; } return (typeof val === 'object'); }; function isNumeric(n) { return n !== '' && !isNaN(parseFloat(n)) && isFinite(n); }; function isTypeInArray(arr, findType) { try { var typeToFind = findType; if (isObject(findType)) typeToFind = findType.value; for (var index = 0; index < arr.length; index++) { var element = arr[index]; var thisType = isObject(element) ? element.value : element; if (thisType === typeToFind) return true; } } catch (error) { } return false; } var varSel = "#node-input-" + varName; var typeSel = varSel + "Type"; var currentValue = node[varName]; var currentType = node[varName + "Type"]; var defShouldHaveNoValue = false; //if optional but 'none' is not yet an option - add it as first option in allowableTypes if (isOptional === true) { if (!isTypeInArray(allowableTypes, "none")) { allowableTypes.unshift("none"); } } //if defType is not in the list of parameter type, change defType to 1st item if (!isTypeInArray(allowableTypes, defType)) { var _firstType = allowableTypes[0]; if (isObject(_firstType)) { _firstType = _firstType.value; defShouldHaveNoValue = _firstType.hasValue == false; } defType = _firstType; } //if currently set type is not in the list of parameter types, set currentType to defType if (!isTypeInArray(allowableTypes, currentType)) { currentType = defType; } //catch all if (!currentType) { currentType = defType || ""; } var $ti = $(varSel); var alreadyBuilt = $ti.data("built") == true; if (alreadyBuilt) { //already built? just update types :) $ti.typedInput("types", allowableTypes); } else { var opts = { default: defType, typeField: $(typeSel), types: allowableTypes }; if (isOptional) { opts.default = defType || "none"; opts.validate = function (v) { //todo - handle this when spec changes return !!v; }; } $ti.typedInput(opts); $ti.data("built", true); } if (currentValue && currentType == "bool") { currentValue = (currentValue === true || currentValue === "true") ? "true" : "false"; } $ti.typedInput("type", currentType); $ti.typedInput("value", currentValue); if (width) $ti.typedInput("width", width); return $ti; } function getEditItem(index) { try { var items = $("#node-input-items-container").editableList('items'); var r = parseEditItem(items[index]); return parseEditItem(items[index]); } catch (error) { } return null; } function parseEditItem($row) { try { var rule = $($row); var r = {}; r.type = rule.find(".node-input-item-property-type").val() || "int16be"; r.name = rule.find(".node-input-item-property-name").val() || "item" + (i + 1); r.offset = parseInt(rule.find(".node-input-item-property-offset").val() || 0); r.length = parseInt(rule.find(".node-input-item-property-length").val() || 1); r.offsetbit = parseInt(rule.find(".node-input-item-property-offsetbit").val() || 0); r.scale = rule.find(".node-input-item-property-scale").val() || 0; r.mask = rule.find(".node-input-item-property-mask").val() || ''; return r; } catch (error) { } return null; } RED.nodes.registerType('buffer-parser', { category: 'parser', color: '#0090d4', defaults: { name: { value: "" }, data: { value: "payload", validate: RED.validators.typedInput("dataType") }, dataType: { value: "msg" }, specification: { value: "" }, specificationType: { value: "ui" }, items: { value: [{ name: "item1", type: "int16be", offset: 0, length: 1, mask: "" }], required: true }, swap1: { value: "" }, swap2: { value: "" }, swap3: { value: "" }, swap1Type: { value: "swap" }, swap2Type: { value: "swap" }, swap3Type: { value: "swap" }, msgProperty: { value: "payload" }, msgPropertyType: { value: "str" }, resultType: { value: "keyvalue" }, resultTypeType: { value: "return" }, multipleResult: { value: false }, fanOutMultipleResult: { value: false }, setTopic: { value: true }, outputs: {value:1} }, inputs: 1, outputs: 1, icon: "font-awesome/fa-expand", outputLabels: function(index) { var node = this; var item = node.items && node.items[index]; if (item && node.multipleResult && node.fanOutMultipleResult) { return describeItem(item, index); } else { return describeConfig(node); } }, label: function () { return this.name || "buffer parser"; }, oneditprepare: function () { //console.log("buffer-parser->oneditprepare()") var node = this; var sti = setupTypedInput.bind(this); var specOpt = { value: "ui", label: "UI Specification", hasValue: false } var swapOpt = { value: "swap", label: "Swap", title: "Swap", showLabel: true, // icon:"fa fa-exchange", options: [ { label: "none", value: '', title: '' }, { label: "16", value: 'swap16', title: 'Interprets data as an array of 16-bit integers and swaps the byte order in-place' }, { label: "32", value: 'swap32', title: 'Interprets data as an array of 32-bit integers and swaps the byte order in-place' }, { label: "64", value: 'swap64', title: 'Interprets data as an array of 64-bit integers and swaps the byte order in-place' }, ], default: "none" } var returnOpt = { value: "output", label: "output", title: "output", icon: "fa fa-sign-out", options: [ { label: "key/value", value: 'keyvalue', title: 'Send an object with results in key/value pairs in the msg property specified by "Output property". Use a fat arrow `=>` in the name to create object.properties e.g. `motor1=>power` will end up in `msg.payload.motor1.power`.' }, { label: "key/object", value: 'object', title: 'Send an object with results in key/object pairs (with the value and extended properties in the object) in the msg property specified by "Output property". Use a fat arrow `=>` in the name to create object.properties e.g. `motor1=>power` will end up in `msg.payload.motor1.power`.' }, { label: "values only (array)", value: 'value', title: 'Send a value (or array of values) in the msg property specified by "Output property"' }, { label: "array (of objects)", value: 'array', title: 'Send an array of objects in the msg property specified by "Output property"' }, { label: "buffer", value: 'buffer', title: 'Send a buffer in the msg property specified by "Output property"' }, ], default: "value" } var returnOpt2 = { value: "output", label: "output", title: "output", icon: "fa fa-sign-out", options: [ { label: "value only", value: 'value', title: 'Send a value in the msg property specified by "Output property"' }, { label: "object", value: 'object', title: 'Send an object in the msg property specified by "Output property"' }, ] } sti('data', ['msg', 'json', 'bin'], 'msg');//data var specificationField = sti('specification', [specOpt, 'msg', 'flow', 'global'], 'ui');//specification var sw1 = sti('swap1', [swapOpt, 'json', 'msg', 'flow', 'global', 'env'], 'swap');//specification var sw2 = sti('swap2', [swapOpt], 'swap');//specification var sw3 = sti('swap3', [swapOpt], 'swap');//specification // var resultTypeField = sti('resultType', [returnOpt, 'msg', 'flow', 'global', 'env'], 'output');//specification console.log("Setting up node-input-resultType", [returnOpt]); var resultTypeField = $("#node-input-resultType").typedInput({ default: "output", type: "output", types: [returnOpt], // typeField: "#node-input-resultTypeType" }); //var msgPropertyField = sti('msgProperty', ['str', 'msg', 'flow', 'global', 'env'], 'str');//specification var msgPropertyField = $("#node-input-msgProperty").typedInput({ types: [{ label: "msg.", value: "str" }] }); var multipleResultField = $("#node-input-multipleResult"); var fanOutMultipleResultField = $("#node-input-fanOutMultipleResult"); var setTopicField = $("#node-input-setTopic"); specificationField.on("change", function () { var v = $(this).val(); var t = specificationField.typedInput("type"); if (t == "ui") { $(".ui-row").show(); if (!v) specificationField.typedInput("value", "spec") console.log("calling RED.tray.resize();") RED.tray.resize(); } else { $(".ui-row").hide(); console.log("calling RED.tray.resize();") RED.tray.resize(); } }); resultTypeField.on("change", function () { var v = resultTypeField.typedInput("value"); var t = specificationField.typedInput("type"); if (t == "ui") { if (v != "buffer") { //console.log("showing items", v, t) $("#ui-row5").show(); } else { //console.log("hiding items", v, t) $("#ui-row5").hide(); } } else { //console.log("hiding items", v, t) $("#ui-row5").hide(); } }); multipleResultField.on("change", function () { var v = this.checked; if (v) { console.log("Setting up node-input-result", [returnOpt2]); resultTypeField.typedInput("types", [returnOpt2]); if($("#node-input-fanOutMultipleResult").prop("checked")) { var l = node.items.length || 1; // console.log("setting outputs to " + l) $("#node-input-outputs").val(l); node.outputs = l; } } else { console.log("Setting up node-input-resultType", [returnOpt]); resultTypeField.typedInput("types", [returnOpt]); // console.log("setting outputs to " + 1) $("#node-input-outputs").val(1); node.outputs = 1; } $("#node-input-setTopic").prop("disabled", !v); $("#node-input-fanOutMultipleResult").prop("disabled", !v); }); sw1.on("change", function () { var v = $(this).val(); var t = $(this).typedInput("type"); if (t != "swap" || !v || v == "none") { console.log('sw1.on("change" --> setting width 70%') sw1.typedInput("width", "calc(100% - 160px)"); sw2.typedInput('hide'); } else { console.log('sw1.on("change" --> setting width 23%') sw1.typedInput("width", "calc(34% - 160px)"); sw2.typedInput('show'); } sw2.change(); }); sw2.on("change", function () { var v1 = sw1.val(); var v2 = sw2.val(); var t = sw1.typedInput("type"); if (t != "swap" || !v1 || !v2 || v1 == "none" || v2 == "none") { sw3.typedInput('hide'); } else { sw3.typedInput('show'); } }); //in place upgrade from JSON specification to new UI if (node.specification && node.specificationType == "json") { try { var importSpec = JSON.parse(node.specification); if (importSpec.options.byteSwap === true) { sw1.typedInput("value", "swap16"); } else if (importSpec.options.byteSwap === false) { sw1.typedInput("value", ""); } else if (Array.isArray(importSpec.options.byteSwap)) { if (importSpec.options.byteSwap.length >= 1) { sw1.typedInput("value", importSpec.options.byteSwap[0]); if (importSpec.options.byteSwap.length >= 2) { sw2.typedInput("value", importSpec.options.byteSwap[1]); if (importSpec.options.byteSwap.length >= 3) { sw3.typedInput("value", importSpec.options.byteSwap[2]); } } } } resultTypeField.typedInput("value", importSpec.options.resultType || "keyvalue"); msgPropertyField.typedInput("value", importSpec.options.msgProperty || "payload"); var mr = importSpec.options.singleResult === false; var st = importSpec.options.setTopic === false ? false : true; multipleResultField.prop("checked", mr); fanOutMultipleResultField.prop("checked", false);//not possible setTopicField.prop("checked", st); specificationField.typedInput("type", "ui"); if (importSpec.items && Array.isArray(importSpec.items)) { node.items = importSpec.items; } node.specification = null; node.specificationType = null; } catch (error) { console.warn("import and upgrade of old fixed specification into new UI failed :(", error) } } $('#node-input-items-container').css('min-height', '150px').css('min-width', '420px').editableList({ addItem: function (container, i, opt) { var rule = opt; if (!rule.hasOwnProperty('type')) { //its a newly added item! rule = { type: "int16be", name: "item" + (i + 1), offset: 0, length: 1, offsetbit: 0, scale: 1, mask: '' };//default var prev = i > 0 ? getEditItem(i - 1) : null; if (prev && prev.type != null) { var byteOffsetMultiplier = 0; var bitOffset = 0; rule.type = prev.type; if (rule.type.indexOf("8") >= 0) byteOffsetMultiplier = 1; if (rule.type == "buffer") byteOffsetMultiplier = 1; if (rule.type == "byte") byteOffsetMultiplier = 1; if (rule.type == "string") byteOffsetMultiplier = 1; if (rule.type == "char") byteOffsetMultiplier = 1; if (rule.type == "ascii") byteOffsetMultiplier = 1; if (rule.type == "hex") byteOffsetMultiplier = 1; if (rule.type == "binary") byteOffsetMultiplier = 1; if (rule.type == "ucs2") byteOffsetMultiplier = 1; if (rule.type == "latin1") byteOffsetMultiplier = 1; if (rule.type.indexOf("16") >= 0) byteOffsetMultiplier = 2; if (rule.type.indexOf("bcd") >= 0) byteOffsetMultiplier = 2; if (rule.type.indexOf("32") >= 0) byteOffsetMultiplier = 4; if (rule.type.indexOf("double") >= 0) byteOffsetMultiplier = 4; if (rule.type.indexOf("float") >= 0) byteOffsetMultiplier = 4; if (rule.type.indexOf("64") >= 0) byteOffsetMultiplier = 8; if (rule.type == "bool") bitOffset = prev.length; if(prev.length > 0) { var byteOffset = (prev.length * byteOffsetMultiplier); if (prev.offset != null) rule.offset = prev.offset + byteOffset; if (prev.offsetbit != null) rule.offsetbit = prev.offsetbit + bitOffset; } else { if (prev.offset != null) rule.offset = prev.offset; if (prev.offsetbit != null) rule.offsetbit = prev.offsetbit; } if (prev.scale != null) rule.scale = prev.scale; if (prev.length != null) rule.length = prev.length; if (prev.mask != null) rule.mask = prev.mask; } } container.css({ overflow: 'hidden', whiteSpace: 'nowrap' }); let fragment = document.createDocumentFragment(); var row1 = $('<div/>', { style: "display:flex;", class: "buffer-parser-row" }).appendTo(fragment); var _types = { value: "type", label: "Type", showLabel: true, options: [ { value: "byte", label: "byte" }, { value: "int8", label: "int8" }, { value: "uint8", label: "uint8" }, { value: "int16le", label: "int16 (le)" }, { value: "int16be", label: "int16 (be)" }, { value: "uint16le", label: "uint16 (le)" }, { value: "uint16be", label: "uint16 (be)" }, { value: "int32le", label: "int32 (le)" }, { value: "int32be", label: "int32 (be)" }, { value: "uint32le", label: "uint32 (le)" }, { value: "uint32be", label: "uint32 (be)" }, { value: "bigint64le", label: "bigint64 (le)" }, { value: "bigint64be", label: "bigint64 (be)" }, { value: "biguint64le", label: "biguint64 (le)" }, { value: "biguint64be", label: "biguint64 (be)" }, { value: "floatle", label: "float (le)" }, { value: "floatbe", label: "float (be)" }, { value: "doublele", label: "double (le)" }, { value: "doublebe", label: "double (be)" }, { value: "8bit", label: "8bit" }, { value: "16bitle", label: "16bit (le)" }, { value: "16bitbe", label: "16bit (be)" }, { value: "bool", label: "bool" }, { value: "bcdle", label: "bcd (le)" }, { value: "bcdbe", label: "bcd (be)" }, { value: "string", label: "string" }, { value: "hex", label: "hex" }, { value: "ascii", label: "ascii" }, { value: "utf8", label: "utf8" }, { value: "utf16le", label: "utf16 (le)" }, { value: "ucs2", label: "ucs2" }, { value: "latin1", label: "latin1" }, { value: "binary", label: "binary" }, { value: "buffer", label: "buffer" }, ] } let row1_1 = $('<div/>', { style: "display:flex;", class: "buffer-parser-row-item" }).appendTo(row1); let typeField = $('<input/>', { class: "node-input-item-property-type", type: "text", width: "165px" }) .appendTo(row1_1) .typedInput({ types: [_types] }) let row1_2 = $('<div/>', { style: "display:flex;flex-grow:1;", class: "buffer-parser-row-item" }).appendTo(row1); let nameField = $('<input/>', { class: "node-input-item-property-name", type: "text", width: "125px" }) .appendTo(row1_2) .typedInput({ types: [{ label: "Name", value: "str" }] }) let row1_3 = $('<div/>', { style: "display:flex;flex-grow:0.15;", class: "buffer-parser-row-item" }).appendTo(row1); let lengthField = $('<input/>', { class: "node-input-item-property-length", type: "number", min: "0", width: "105px" }) .appendTo(row1_3) .typedInput({ types: [{ label: "Length", value: "num" }] }) let row1_4 = $('<div/>', { style: "display:flex;flex-grow:0.15;", class: "buffer-parser-row-item" }).appendTo(row1); let offsetField = $('<input/>', { class: "node-input-item-property-offset", type: "number", min: "0", width: "100px" }) .appendTo(row1_4) .typedInput({ types: [{ label: "Offset", value: "num" }] }) let row1_5 = $('<div/>', { style: "display:flex;flex-grow:0.15;", class: "buffer-parser-row-item" }).appendTo(row1); let maskField = $('<input/>', { class: "node-input-item-property-mask", min: "0", width: "150px" }) .appendTo(row1_5) .typedInput({ types: [ { value: "", label: "No mask", hasValue: false }, { label: "Mask", value: "str" } ], default: "" }) let row1_6 = $('<div/>', { style: "display:flex;flex-grow:0.15;", class: "buffer-parser-row-item" }).appendTo(row1); let offsetbitField = $('<input/>', { class: "node-input-item-property-offsetbit", type: "number", min: "0", width: "105px" }) .appendTo(row1_6) .typedInput({ types: [{ label: "bit offset", value: "num" }] }) let row1_7 = $('<div/>', { style: "display:flex;flex-grow:0.15;", class: "buffer-parser-row-item" }).appendTo(row1); let scaleField = $('<input/>', { class: "node-input-item-property-scale", width: "105px" }) .appendTo(row1_7) .typedInput({ types: [{ label: "scale", value: "str", icon: "fa fa-calculator" }] }) typeField.on("change", function () { var type = $(this).val(); var showbitOffset = ["bool", "boolean"].indexOf(type) > -1 var showScale = [ "int8", "uint8", "int16", "int16be", "int16le","uint16", "uint16be", "uint16le", "int32", "int32be", "int32le","uint32", "uint32be", "uint32le", "floatbe", "floatle", "doublebe", "doublele" ].indexOf(type) > -1 if (showbitOffset) { offsetbitField.parent().show(); offsetbitField.typedInput('show'); } else { offsetbitField.typedInput('hide'); offsetbitField.parent().hide(); } if (showScale) { scaleField.parent().show(); scaleField.typedInput('show'); } else { scaleField.typedInput('hide'); scaleField.parent().hide(); } if (type == "buffer") { maskField.parent().hide(); maskField.typedInput('hide'); } else { maskField.typedInput('show'); maskField.parent().show(); } }); rule.type = coerceDataType(rule.type); typeField.typedInput('type', "type"); typeField.typedInput('value', rule.type || "int16be"); nameField.typedInput('type', "str"); nameField.typedInput('value', rule.name || ("item" + (i + 1))); offsetField.typedInput('type', "num"); offsetField.typedInput('value', rule.offset || 0); lengthField.typedInput('type', "num"); lengthField.typedInput('value', rule.length || 1); maskField.typedInput('type', rule.mask ? 'str' : ''); maskField.typedInput('value', rule.mask); offsetbitField.typedInput('type', "num"); offsetbitField.typedInput('value', rule.offsetbit || 0); scaleField.typedInput('type', "num"); scaleField.typedInput('value', rule.scale || 1); typeField.change(); container[0].appendChild(fragment); }, removable: true, sortable: true }); for (var i = 0; i < this.items.length; i++) { var item = this.items[i]; $("#node-input-items-container").editableList('addItem', item); } multipleResultField.change();//cause setTopic to be enabled/disabled appropriately resultTypeField.change();//cause the items list to show/hide depending on selection $("#node-input-swap1").change(); //cause swap selectors to update console.log("calling RED.tray.resize();") RED.tray.resize(); }, oneditsave: function () { var items = $("#node-input-items-container").editableList('items'); var node = this; node.items = []; items.each(function (i) { var rule = $(this); var r = parseEditItem(rule); node.items.push(r); }); node.resultType = $("#node-input-resultType").typedInput("type"); var multipleResult = $("#node-input-multipleResult").prop("checked"); var fanOutMultipleResult = $("#node-input-fanOutMultipleResult").prop("checked"); if(multipleResult && fanOutMultipleResult) { var l = items.length || 1; // console.log("setting outputs to " + l) $("#node-input-outputs").val(l); node.outputs = l; } else { // console.log("setting outputs to " + 1) $("#node-input-outputs").val(1); node.outputs = 1; } }, oneditresize: function (size) { var rows = $("#dialog-form>div:not(.node-input-items-container-row)"); var height = size.height; for (var i = 0; i < rows.length; i++) { height -= $(rows[i]).outerHeight(true); } var editorRow = $("#dialog-form>div.node-input-items-container-row"); height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom"))); height += 16; $("#node-input-items-container").editableList('height', height); } }); })() </script> <script type="text/html" data-template-name="buffer-parser"> <div class="form-row buffer-parser-form-row"> <label for="node-input-name"><i class="icon-tag"></i> Name</label> <input type="text" id="node-input-name" style="width:calc(100% - 160px)" placeholder="Name"> </div> <div class="form-row buffer-parser-form-row"> <label for="node-input-data"><i class="fa fa-ellipsis-h"></i><span data-i18n="buffer-parser.label.data"> Property</span></label> <input type="hidden" id="node-input-dataType"> <input style="width:calc(100% - 160px)" type="text" id="node-input-data" placeholder=""> </div> <div class="form-row buffer-parser-form-row"> <label for="node-input-specification"><i class="fa fa-code"></i><span data-i18n="buffer-parser.label.specification"> Specification</span></label> <input type="hidden" id="node-input-specificationType"> <input style="width:calc(100% - 160px)" type="text" id="node-input-specification" placeholder=""> </div> <div class="form-row buffer-parser-form-row ui-row" id="ui-row1"> <label for="node-input-swap1"><i class="fa fa-exchange"></i><span data-i18n="buffer-parser.label.swap"> Byte swap</span></label> <span> <input type="hidden" id="node-input-swap1Type"> <input style="width:calc(100% - 160px)" type="text" id="node-input-swap1" placeholder=""> <input type="hidden" id="node-input-swap2Type"> <input style="width:calc(33% - 160px)" type="text" id="node-input-swap2" placeholder=""> <input type="hidden" id="node-input-swap3Type"> <input style="width:calc(33% - 160px)" type="text" id="node-input-swap3" placeholder=""> </span> </div> <div class="form-row buffer-parser-form-row ui-row" id="ui-row2"> <label for="node-input-msgProperty"><i class="fa fa-sign-out"></i><span data-i18n="buffer-parser.label.msgProperty"> Output property</span></label> <input type="hidden" id="node-input-msgPropertyType"> <input style="width:calc(100% - 160px)" type="text" id="node-input-msgProperty" placeholder="payload"> </div> <div class="form-row buffer-parser-form-row ui-row" id="ui-row3"> <label><i class="fa fa-sign-out"></i> <span data-i18n="buffer-parser.label.returnOptions"> Output options</span></label> <span> <label for="node-input-multipleResult" style="width:23%"> <input type="checkbox" id="node-input-multipleResult" style="display:inline-block; width:22px; vertical-align:baseline;" title="Send a separate message for each item" autocomplete="off"><span data-i18n="buffer-parser.label.multipleResult">multiple results</span> </label> <label for="node-input-setTopic" style="width:23%"> <input type="checkbox" id="node-input-setTopic" style="display:inline-block; width:22px; vertical-align:baseline;" title="Use the items name as the topic for the item, otherwise the incoming msg.topic will be used" autocomplete="off"><span data-i18n="buffer-parser.label.setTopic">set topic</span> </label> <label for="node-input-fanOutMultipleResult" style="width:23%"> <input type="checkbox" id="node-input-fanOutMultipleResult" style="display:inline-block; width:22px; vertical-align:baseline;" title="Send each item out of a separate node output" autocomplete="off"><span data-i18n="buffer-parser.label.fanOut">fan out</span> </label> <input type="hidden" id="node-input-outputs"/> </span> </div> <div class="form-row buffer-parser-form-row ui-row" id="ui-row4"> <label for="node-input-resultType"><i class="fa fa-sign-out"></i><span data-i18n="buffer-parser.label.resultType"> Output</span></label> <input type="hidden" id="node-input-resultTypeType"> <input style="width:calc(100% - 160px)" type="text" id="node-input-resultType" placeholder="value"> </div> <div class="form-row buffer-parser-form-row ui-row node-input-items-container-row" id="ui-row5"> <ol id="node-input-items-container"></ol> </div> </script> <style> ol#node-input-items-container .red-ui-typedInput-container { flex: 1; } .buffer-parser-form-row>label { width: 120px !important; } .buffer-parser-row-item { margin: 2px 4px 2px 2px; } .buffer-parser-row { flex-wrap: wrap; } span.buffer-parser-prop-name { padding: 0px 3px 2px 3px; margin: 1px; color: #AD1625; white-space: nowrap; background-color: #f7f7f9; border: 1px solid #e1e1e8; border-radius: 2px; font-family: monospace; } span.buffer-parser-prop-type { color: #666; font-style: italic; font-size: smaller; padding-left: 5px; } span.buffer-parser-prop-desc { /* color: #333; */ padding-left: 5px; } .buffer-parser-node-help div.buffer-parser-node-help-indent { margin: 0px 0px 0px 6px; } li.buffer-parser-help-text { list-style-type: none; /* Remove bullets */ margin-left: -18px; } li.buffer-parser-help-text > code { display: inline-block; width: 24px; } li.buffer-parser-help-text > span { color: #333; font-size: smaller; } li.buffer-parser-help-text > span > code { font-size: small; } </style> <script type="text/html" data-help-name="buffer-parser"> <p>A node that converts an array of integer (Int16) or a buffer in to an object of values based on the specification provided</p> <h3>Foreword</h3> <dl class="message-properties"> A number of examples have been included to help you do some common tasks. To use the examples, press the hamburger menu <a id="red-ui-header-button-sidemenu" class="button" href="#"><i class="fa fa-bars"></i></a> select <b>import</b> then <b>examples</b> </dl> <h3>Output</h3> <dl class="message-properties"> <dt>payload <span class="property-type">object | []</span></dt> <dd>the results.</dd> <h4>NOTES:<br> Payload can be set to an alternative <code>msg</code> property specified by "Output property"<br> Additional useful properties are available in the <code>msg</code> object. Use a debug node (set to show complete output) to inspect them </h4> </dl> <h3>UI specification</h3> <h4><b>Property...</b></h4> <div class="buffer-parser-node-help-indent"> The data to be processed in accordance with the specification. NOTE: The data must be a buffer or an array of 16 bit integers <div> <h4><b>Specification...</b></h4> <div class="buffer-parser-node-help-indent"> If Specification is set to "UI" then enter the specification in the fields provided below, otherwise, the specification must be an object provided in the format described below in <a href="#buffer-parser-help-dyn-spec">Dynamic specification</a> <div> <h4><b>Byte swap...</b></h4> <div class="buffer-parser-node-help-indent"> Swap permits 16 bit, 32 bit or 64 bit swap options. Swap is applied to the whole data before extracting the items specified. If the 1st swap option is set to msg, flow, global, it must be an array containing any number of "swap16" "swap32" "swap64" strings. If the 1st swap option is set to env, then the environment variable must be set to a comma separated list of swaps e.g. swap32,swap16 <div> <h4><b>Output property...</b></h4> <div class="buffer-parser-node-help-indent"> This allows you to return the result in a property other than payload. For example, you can have results returned to <code>msg.output</code> or <code>msg.result.data</code>. <div> <h4><b>Output options...</b></h4> <div class="buffer-parser-node-help-indent"> <ul> <li><span class="buffer-parser-prop-name">multiple results</span> <span class="buffer-parser-prop-desc"> When false, a single result is returned in the <code>msg</code> property specified by "Output property". When true, 1 result per item is returned in the <code>msg</code> property specified by "Output property"</span></li> <li><span class="buffer-parser-prop-name">set topic</span> <span class="buffer-parser-prop-desc"> When false, <code>msg.topic</code> will be unmodified. When true, <code>msg.topic</code> will be set to the value of <code>name</code>. </span></li> <li><span class="buffer-parser-prop-name">fan out</span> <span class="buffer-parser-prop-desc"> When true, multiple output pins will be added to the node and each result will be sent out of its own pin. </span></li> </ul> <div> <h4><b>Output...</b></h4> <div class="buffer-parser-node-help-indent"> <ul> <li><span class="buffer-parser-prop-name">key/value</span> <span class="buffer-parser-prop-desc"> When key/value is selected, the payload will be the value only</span>. Use a fat arrow <code>=></code> in the name to create object.properties e.g. <code>motor1=>power</code> will end up in <code>msg.payload.motor1.power</code>. </li> <li><span class="buffer-parser-prop-name">key/object</span> <span class="buffer-parser-prop-desc"> When key/object is selected, the payload will be an object</span>. Use a fat arrow <code>=></code> in the name to create object.properties e.g. <code>motor1=>power</code> will end up in <code>msg.payload.motor1.power</code>. </li> <li><span class="buffer-parser-prop-name">value only</span> <span class="buffer-parser-prop-desc"> When value only is selected, the payload will be an array of values</span> </li> <li><span class="buffer-parser-prop-name">array</span> <span class="buffer-parser-prop-desc"> When array is selected, the payload will be an array of objects</span> </li> <li><span class="buffer-parser-prop-name">buffer</span> <span class="buffer-parser-prop-desc"> When value is selected, the payload will be a buffer. NOTE: No items will be processed (but byteswaps will be applied).</span> </li> </ul> <div> <h4><b>items...</b></h4> <div class="buffer-parser-node-help-indent"> <ul> <li><span class="buffer-parser-prop-name">name</span> <span class="buffer-parser-prop-desc"> A name to identify the resulting data. This can also be used as the topic when returning multiple values.</span></li> <li><span class="buffer-parser-prop-name">type</span> <span class="buffer-parser-prop-desc"> The type that output data should be changed to (see Allowable types below)</span></li> <li><span class="buffer-parser-prop-name">offset</span> <span class="buffer-parser-prop-desc"> The <span class="buffer-parser-prop-name">offset</span> value denotes which byte to start reading data from. This is regardless of data source being a buffer or an integer array e.g. when data source is an integer array, specifying 3 means item source data will start from the middle of the 2nd WORD</span></li> <li><span class="buffer-parser-prop-name">length</span> <span class="buffer-parser-prop-desc"> The quantity of items to be returned. e.g. 6 bools or 12 floats or 34 int32s. NOTE: setting <code>length</code> to -1 will attempt to read all bytes from offset to the end. NOTE: If source data does not have enough bytes, the operation will fail.</span></li> <li><span class="buffer-parser-prop-name">offsetbit</span> <span class="buffer-parser-prop-desc"> The first bit to return when specifying <span class="buffer-parser-prop-name">type</span> bool. e.g. requesting <span class="buffer-parser-prop-name">offsetbit</span> 6 and a <span class="buffer-parser-prop-name">length</span> of 3 will return byte0-bit6, byte0-bit7 and byte1-bit0 </span></li> <li><span class="buffer-parser-prop-name">mask</span> <span class="buffer-parser-prop-desc"> An optional mask to apply to the final data value. Enter a decimal mask e.g 65535 or a hexadecimal mask e.g. 0xffff</span></li> <li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAxUlEQVRYR+2XUQ6CMAyGP+4hiZ7MBw+CHsQHT6YJ3kNTEpNSDUW24RK7N9bQfvzZWv6G8ToCe2Br9nM93oALIHWG1ajMstnlquTkOb0gNEAPbFYCuAOtVeCxUvGR+loBC6BjOdg+5g+AUCAUqFqBHHd/Ksfw8VMKBMDPFbCzwJsVi+Lf3IJFBZSMycPo/wBSD+GsM1SyDwRAsgKzEnjXzItHH6hagdQ+4L3/9j9wLWhKLYyY1F115lSAxCEfCppUMaVnbc+fO91MIQGBj78AAAAASUVORK5CYII=" style="height:16px;" > <span class="buffer-parser-prop-name">scale</span> <span class="buffer-parser-prop-desc"> A multiplier or a simple Scale Equation to apply to the final value (after the mask). Supported Scaling Equations are...</span> <ul> <li class="buffer-parser-help-text"><code>&lt;&lt;</code> <span>e.g. <code>&lt;&lt;2</code> would left shift the parsed value 2 places</span></li> <li class="buffer-parser-help-text"><code>&gt;&gt;</code> <span>e.g. <code>&gt;&gt;2</code> would right shift the parsed value 2 places</span></li> <li class="buffer-parser-help-text"><code>&gt;&gt;&gt;</code> <span>e.g. <code>&gt;&gt;&gt;2</code> would zero-fill right shift the parsed value 2 places (returns a 32bit unsigned value)</span></li> <li class="buffer-parser-help-text"><code>+</code> <span>e.g. <code>+10</code> would add 10 to the parsed value </span></li> <li class="buffer-parser-help-text"><code>-</code> <span>e.g. <code>-10</code> would deduct 10 from the parsed value </span></li> <li class="buffer-parser-help-text"><code>/</code> <span>e.g. <code>/10</code> would divide the parsed value by 10</span></li> <li class="buffer-parser-help-text"><code>*</code> <span>e.g. <code>*10</code> would multiply the parsed value by 10</span></li> <li class="buffer-parser-help-text"><code>**</code> <span>e.g. <code>**2</code> would raise the parsed value to the power of 2</span></li> <li class="buffer-parser-help-text"><code>^</code> <span>e.g. <code>^0xf0</code> would XOR the parsed value with 0xf0</span></li> <li class="buffer-parser-help-text"><code>==</code> <span>e.g. <code>==10</code> would result in <code>true</code> if the parsed value was equal to 10</span></li> <li class="buffer-parser-help-text"><code>!=</code> <span>e.g. <code>!=10</code> would result in <code>false</code> if the parsed value was equal to 10</span></li> <!-- <li class="buffer-parser-help-text"><code>!</code> <span>e.g. <code>!</code> would result in <code>true</code> if the parsed value was <code>0</code> or <code>false</code> if the parsed value was not <code>0</code></span></li> --> <li class="buffer-parser-help-text"><code>!!</code> <span>e.g. <code>!!</code> would result in <code>true</code> if the parsed value was <code>1</code> (same as <code>!!1 == true</code>) </span></li> <li class="buffer-parser-help-text"><code>&gt;</code> <span>e.g. <code>&gt;10</code> would result in <code>true</code> if the parsed value was greater than 10</span></li> <li class="buffer-parser-help-text"><code>&lt;</code> <span>e.g. <code>&lt;10</code> would result in <code>true</code> if the parsed value was less than 10</span></li> </ul> </li> </ul> <div> <h3 id="buffer-parser-help-dyn-spec">Dynamic specification</h3> <div class="buffer-parser-node-help-indent">The <code>specification</code> can be passed in via msg, flow or global instead of being configured by the UI. The dynamic specification must be an object with the following properties... <ul> <li><span class="buffer-parser-prop-name">options</span> <span class="buffer-parser-prop-type">(Object)</span> <span class="buffer-parser-prop-desc"> processing options</span></li> <li><span class="buffer-parser-prop-name">items</span> <span class="buffer-parser-prop-type">(Array)</span> <span