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

830 lines (776 loc) 55.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, types, def) { let varSel = '#node-input-' + varName; let typeSel = varSel + 'Type'; let varVal = this[varName]; let typeVal = this[varName + 'Type']; if (typeVal == null || typeVal === 'none') { typeVal = def; } else if (typeVal === 'string') { typeVal = "str"; } else if (typeVal === 'number') { typeVal = "num"; } $(typeSel).val(typeVal); $(varSel).typedInput({ default: def, typeField: $(typeSel), types: types }); return $(varSel).typedInput('type', typeVal); } 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"' }, ], default: "value" } 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 //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") } else { $(".ui-row").hide(); } }); 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) { $("#node-input-resultType").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 { $("#node-input-resultType").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") { sw1.typedInput("width", "70%"); sw2.typedInput('hide'); } else { sw1.typedInput("width", "23%"); 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) } } multipleResultField.change();//cause setTopic to be enabled/disabled appropriately resultTypeField.change();//cause the items list to show/hide depending on selection $('#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;", class: "buffer-parser-row-item" }).appendTo(row1); let nameField = $('<input/>', { class: "node-input-item-property-name", type: "text", width: "145px" }) .appendTo(row1_2) .typedInput({ types: [{ label: "Name", value: "str" }] }) let row1_3 = $('<div/>', { style: "display:flex;", class: "buffer-parser-row-item" }).appendTo(row1); let lengthField = $('<input/>', { class: "node-input-item-property-length", type: "number", min: "0", width: "110px" }) .appendTo(row1_3) .typedInput({ types: [{ label: "Length", value: "num" }] }) let row1_4 = $('<div/>', { style: "display:flex;", 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;", class: "buffer-parser-row-item" }).appendTo(row1); let maskField = $('<input/>', { class: "node-input-item-property-mask", min: "0", width: "152px" }) .appendTo(row1_5) .typedInput({ types: [ { value: "", label: "No mask", hasValue: false }, { label: "Mask", value: "str" } ], default: "" }) let row1_6 = $('<div/>', { style: "display:flex;", class: "buffer-parser-row-item" }).appendTo(row1); let offsetbitField = $('<input/>', { class: "node-input-item-property-offsetbit", type: "number", min: "0", width: "110px" }) .appendTo(row1_6) .typedInput({ types: [{ label: "bit offset", value: "num" }] }) let row1_7 = $('<div/>', { style: "display:flex;", class: "buffer-parser-row-item" }).appendTo(row1); let scaleField = $('<input/>', { class: "node-input-item-property-scale", width: "110px" }) .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); } }, 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); }); 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" 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: 70%" 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: 70%" 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: 23%" type="text" id="node-input-swap1" placeholder=""> <input type="hidden" id="node-input-swap2Type"> <input style="width: 23%" type="text" id="node-input-swap2" placeholder=""> <input type="hidden" id="node-input-swap3Type"> <input style="width: 23%" 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: 70%" 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: 70%" 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 class="buffer-parser-prop-desc"> array of items (see below) </span> </ul> <div>The <span class="buffer-parser-prop-name">options</span> object can have the following properties... <ul> <li><span class="buffer-parser-prop-name">byteSwap</span> <span class="buffer-parser-prop-type">(Boolean|Array|optional)</span> <span class="buffer-parser-prop-desc"> swap all bytes before processing items. If <code>true</code>, then <code>swap16</code> will be performed. If <code>byteSwap</code> is an Array (e.g. <code>["swap64", "swap32", "swap64", "swap16"]</code>) multiple swaps can be performed in the specified order </span> </li> <li><span class="buffer-parser-prop-name">resultType</span> <span class="buffer-parser-prop-type">(String|optional)</span> <span class="buffer-parser-prop-desc"> How to return data. Valid options are "object", "keyvalue", "array", "value" or "buffer"</span></li> <li><span class="buffer-parser-prop-name">msgProperty</span> <span class="buffer-parser-prop-type">(String|optional)</span> <span class="buffer-parser-prop-desc"> How to return data. By default, data will be sent in <code>msg.payload</code> - this can be changed as required. e.g. set <span class="buffer-parser-prop-name">msgProperty</span> to <b>newPayload.data</b> to have results sent to <code>msg.newPayload.data</code> </span></li> <li><span class="buffer-parser-prop-name">multipleResult</span> <span class="buffer-parser-prop-type">(Boolean|optional)</span> <span class="buffer-parser-prop-desc"> By default, a single result will be sent at the end of processing. Set <span class="buffer-parser-prop-name">multipleResult</span> to <code>true</code> to have individual messages sent per item.</span></li> <li><span class="buffer-parser-prop-name">setTopic</span> <span class="buffer-parser-prop-type">(Boolean|optional)</span> <span class="buffer-parser-prop-desc"> when <span class="buffer-parser-prop-name">multipleResult</span> is set to <code>true</code>, the individual messages topic will be set to the value of item.name (or the items index if not set). To prevent the topic being overwritten, set <span class="buffer-parser-prop-name">setTopic</span> to <code>false</code> </span></li> </ul> </div> <div>The <span class="buffer-parser-prop-name">items</span> array must contain 1 or more objects in the following format (not used when <code>resultType</code>="buffer")... <ul> <li><span class="buffer-parser-prop-name">name</span> <span class="buffer-parser-prop-type">(String)</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-type">(String)</span> <span class="buffer-parser-prop-desc"> The type that output data should