From 42bedfab0ec4a433f4f6ea4312e8869fffc05098 Mon Sep 17 00:00:00 2001 From: Peter Hall <33176108+IamPete1@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:53:41 +0100 Subject: [PATCH 01/33] Add MAVLinkDashboard --- MAVLinkDashboard/Default_Layout.json | 65 + .../MAVLink/local_modules/README.md | 6 + .../MAVLink/local_modules/jspack/.npmignore | 19 + .../MAVLink/local_modules/jspack/LICENSE | 26 + .../MAVLink/local_modules/jspack/README.md | 147 + .../MAVLink/local_modules/jspack/jspack.js | 891 + .../MAVLink/local_modules/jspack/package.json | 64 + .../local_modules/jspack/test/int64.js | 456 + .../MAVLink/local_modules/long/LICENSE | 202 + .../MAVLink/local_modules/long/README.md | 246 + .../MAVLink/local_modules/long/index.js | 1 + .../MAVLink/local_modules/long/package.json | 64 + .../MAVLink/local_modules/long/src/long.js | 1325 ++ MAVLinkDashboard/MAVLink/mavlink.js | 18848 ++++++++++++++++ MAVLinkDashboard/MAVLinkDashboard.js | 571 + MAVLinkDashboard/Readme.md | 7 + MAVLinkDashboard/SandBoxWidgets/Attitude.json | 23 + MAVLinkDashboard/SandBoxWidgets/Graph.json | 1958 ++ .../SandBoxWidgets/MAVLink_Inspector.json | 23 + MAVLinkDashboard/SandBoxWidgets/Messages.json | 1946 ++ MAVLinkDashboard/SandBoxWidgets/Value.json | 1873 ++ MAVLinkDashboard/WidgetEdit.js | 505 + MAVLinkDashboard/Widgets/Base_Class.js | 215 + MAVLinkDashboard/Widgets/Menu.js | 385 + MAVLinkDashboard/Widgets/SandBox.html | 181 + MAVLinkDashboard/Widgets/SandBox.js | 124 + MAVLinkDashboard/Widgets/SubGrid.js | 351 + MAVLinkDashboard/index.html | 204 + images/AP_Square.png | Bin 0 -> 16070 bytes 29 files changed, 30726 insertions(+) create mode 100644 MAVLinkDashboard/Default_Layout.json create mode 100644 MAVLinkDashboard/MAVLink/local_modules/README.md create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/.npmignore create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/LICENSE create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/README.md create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/jspack.js create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/package.json create mode 100644 MAVLinkDashboard/MAVLink/local_modules/jspack/test/int64.js create mode 100644 MAVLinkDashboard/MAVLink/local_modules/long/LICENSE create mode 100644 MAVLinkDashboard/MAVLink/local_modules/long/README.md create mode 100644 MAVLinkDashboard/MAVLink/local_modules/long/index.js create mode 100644 MAVLinkDashboard/MAVLink/local_modules/long/package.json create mode 100644 MAVLinkDashboard/MAVLink/local_modules/long/src/long.js create mode 100644 MAVLinkDashboard/MAVLink/mavlink.js create mode 100644 MAVLinkDashboard/MAVLinkDashboard.js create mode 100644 MAVLinkDashboard/Readme.md create mode 100644 MAVLinkDashboard/SandBoxWidgets/Attitude.json create mode 100644 MAVLinkDashboard/SandBoxWidgets/Graph.json create mode 100644 MAVLinkDashboard/SandBoxWidgets/MAVLink_Inspector.json create mode 100644 MAVLinkDashboard/SandBoxWidgets/Messages.json create mode 100644 MAVLinkDashboard/SandBoxWidgets/Value.json create mode 100644 MAVLinkDashboard/WidgetEdit.js create mode 100644 MAVLinkDashboard/Widgets/Base_Class.js create mode 100644 MAVLinkDashboard/Widgets/Menu.js create mode 100644 MAVLinkDashboard/Widgets/SandBox.html create mode 100644 MAVLinkDashboard/Widgets/SandBox.js create mode 100644 MAVLinkDashboard/Widgets/SubGrid.js create mode 100644 MAVLinkDashboard/index.html create mode 100644 images/AP_Square.png diff --git a/MAVLinkDashboard/Default_Layout.json b/MAVLinkDashboard/Default_Layout.json new file mode 100644 index 00000000..b58a496e --- /dev/null +++ b/MAVLinkDashboard/Default_Layout.json @@ -0,0 +1,65 @@ +{ + "header": { + "version": 1 + }, + "grid": { + "columns": 12, + "rows": 6, + "color": "rgb(255, 255, 255)" + }, + "widgets": { + "0": { + "x": "11", + "y": "0", + "w": null, + "h": null, + "type": "WidgetMenu", + "options": { + "form_content": { + "borderColor": "#c8c8c8", + "backgroundColor": "#ffffff" + } + } + }, + "1": { + "x": "0", + "y": "0", + "w": null, + "h": null, + "type": "WidgetSandBox", + "options": { + "form": {}, + "form_content": {}, + "sandbox": "// Initialization\ndiv.appendChild(document.createTextNode(\"Widget Example:\"))\ndiv.appendChild(document.createElement(\"br\"))\n\nmessage_report = document.createTextNode(\"No Data\")\ndiv.appendChild(message_report)\n\n// Runtime function\nhandle_msg = function (msg) {\n message_report.nodeValue = \"Got: \" + msg._name\n}\n" + } + }, + "2": { + "x": "1", + "y": "0", + "w": null, + "h": null, + "type": "WidgetSandBox", + "options": { + "form": {}, + "form_content": {}, + "sandbox": "// Initialization\ndiv.appendChild(document.createTextNode(\"Widget Example:\"))\ndiv.appendChild(document.createElement(\"br\"))\n\nmessage_report = document.createTextNode(\"No Data\")\ndiv.appendChild(message_report)\n\n// Runtime function\nhandle_msg = function (msg) {\n message_report.nodeValue = \"Got: \" + msg._name\n}\n" + } + }, + "3": { + "x": "2", + "y": "0", + "w": null, + "h": null, + "type": "WidgetSubGrid", + "options": { + "form_content": { + "rows": 2, + "columns": 2, + "borderColor": "#c8c8c8", + "backgroundColor": "#ffffff" + }, + "widgets": {} + } + } + } +} \ No newline at end of file diff --git a/MAVLinkDashboard/MAVLink/local_modules/README.md b/MAVLinkDashboard/MAVLink/local_modules/README.md new file mode 100644 index 00000000..ad5a1382 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/README.md @@ -0,0 +1,6 @@ +This folder is a locally modified copy of some Node/npm packages 'jspack' and 'long'. we have copied them here and tweaked them to be compatible with our needs, please see their respective README.md file for their original info, which we have not changed. + +This README.md serves to make you aware that these two packages as stored here in the 'jspack' and 'long' folders ARE MODIFIED from the originals. +By placing this statement here, and putting a notice in long.js as well, we feel are in compliance with the LICENSE file of 'long' , which requires us to tell you they are modified. + +We have included their original license files, in compliance with them, as both license/s permit distribution of derived works in source and/or binary form. diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/.npmignore b/MAVLinkDashboard/MAVLink/local_modules/jspack/.npmignore new file mode 100644 index 00000000..be1ea81a --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/.npmignore @@ -0,0 +1,19 @@ +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz +*.pyc + +pids +logs +results + +npm-debug.log +node_modules + +**~ +**.swp diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/LICENSE b/MAVLinkDashboard/MAVLink/local_modules/jspack/LICENSE new file mode 100644 index 00000000..d646dd72 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2008, Fair Oaks Labs, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + + * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/README.md b/MAVLinkDashboard/MAVLink/local_modules/jspack/README.md new file mode 100644 index 00000000..fee0cdc3 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/README.md @@ -0,0 +1,147 @@ +jspack - library to pack primitives to octet arrays +==================================================== + +[![Build status](https://travis-ci.org/birchroad/node-jspack.svg?branch=master)](https://travis-ci.org/birchroad/node-jspack) + +## Disclaimer +The jspack module and documentation are essentially ports of the +Python struct module and documentation, with such changes as were necessary. The port was originally made by Fair Oaks Labs, Inc. and published at http://code.google.com/p/jspack/ +If any Python people are miffed that their documentation got ripped off, let me know, +and I'll gladly revise them. + +This module performs conversions between JavaScript values and C structs +represented as octet arrays (i.e. JavaScript arrays of integral numbers +between 0 and 255, inclusive). It uses format strings (explained below) as +compact descriptions of the layout of the C structs and the intended conversion +to/from JavaScript values. This can be used to handle binary data stored in +files, or received from network connections or other sources. + +## Install + npm install jspack + +## Reference + +The module defines the following functions: + +### Unpack(fmt, a, p) +Return an array containing values unpacked from the octet array a, +beginning at position p, according to the supplied format string. If there +are more octets in a than required by the format string, the excess is +ignored. If there are fewer octets than required, Unpack() will return +undefined. If no value is supplied for the p argument, zero is assumed. + +### PackTo(fmt, a, p, values) +Pack and store the values array into the supplied octet array a, beginning +at position p. If there are more values supplied than are specified in the +format string, the excess is ignored. If there are fewer values supplied, +PackTo() will return false. If there is insufficient space in a to store +the packed values, PackTo() will return false. On success, PackTo() returns +the a argument. If any value is of an inappropriate type, the results are +undefined. + +### Pack(fmt, values) +Return an octet array containing the packed values array. If there are +more values supplied than are specified in the format string, the excess is +ignored. If there are fewer values supplied, Pack() will return false. If +any value is of an inappropriate type, the results are undefined. + +### CalcLength(fmt) +Return the number of octets required to store the given format string. + + +## Formats +Format characters have the following meanings; the conversion between C and +JavaScript values should be obvious given their types: + + Format | C Type | JavaScript Type | Size (octets) | Notes + ------------------------------------------------------------------- + A | char[] | Array | Length | (1) + x | pad byte | N/A | 1 | + c | char | string (length 1) | 1 | (2) + b | signed char | number | 1 | (3) + B | unsigned char | number | 1 | (3) + h | signed short | number | 2 | (3) + H | unsigned short | number | 2 | (3) + i | signed int | number | 4 | (3) + I | unsigned int | number | 4 | (3) + l | signed long | number | 4 | (3) + L | unsigned long | number | 4 | (3) + q | signed long | number | 8 | (6) + Q | unsigned long | number | 8 | (6) + s | char[] | string | Length | (2) + f | float | number | 4 | (4) + d | double | number | 8 | (5) + +*Notes:* + + **(1)** The "A" code simply returns a slice of the source octet array. This is + primarily useful when a data structure contains bytes which are subject to + multiple interpretations (e.g. unions), and the data structure is being + decoded in multiple passes. + + **(2)** The "c" and "s" codes handle strings with codepoints between 0 and 255, + inclusive. The data are not bounds-checked, so strings containing characters + with codepoints outside this range will encode to "octet" arrays that contain + values outside the range of an octet. Furthermore, since these codes decode + octet arrays by assuming the octets represent UNICODE codepoints, they may + not "correctly" decode bytes in the range 128-255, since that range is subject + to multiple interpretations. Caveat coder! + + **(3)** The 8 "integer" codes clip their encoded values to the minima and maxmima + of their respective types: If you invoke Struct.Pack('b', [-129]), for + instance, the result will be [128], which is the octet encoding of -128, + which is the minima of a signed char. Similarly, Struct.Pack('h', [-32769]) + returns [128, 0]. Fractions are truncated. + + **(4)** Since JavaScript doesn't natively support 32-bit floats, whenever a float + is stored, the source JavaScript number must be rounded. This module applies + correct rounding during this process. Numbers with magnitude greater than or + equal to 2^128-2^103 round to either positive or negative Infinity. The + rounding algorithm assumes that JavaScript is using exactly 64 bits of + floating point precision; 128-bit floating point will result in subtle errors. + + **(5)** This module assumes that JavaScript is using 64 bits of floating point + precision, so the "d" code performs no rounding. 128-bit floating point will + cause the "d" code to simply truncate significands to 52 bits. + + **(6)** Since 64bit longs cannot be represented by numbers JavaScript, this version of + jspack will process longs as arrays in the form: ```[lowBits, hightBits]```. The + decoded long array contains a third element, the unsigned flag, which is ```false``` for signed + and ```true``` for unsigned values. + This representation is similar to what [Long.js](https://github.com/dcodeIO/Long.js), and + therefore the [Google Closure Libaray](https://github.com/google/closure-library), uses. + See [test/int64.js](test/int64.js) for examples how to work with Long.js. + +A format character may be preceded by an integral repeat count. For example, +the format string "4h" means exactly the same thing as "hhhh". + +Whitespace characters between formats are ignored; a count and its format must +not be separated by whitespace, however. + +For the "A" format character, the count is interpreted as the size of the +array, not a repeat count as for the other format characters; for example, "10A" +means a single 10-octet array. When packing, the Array is truncated or padded +with 0 bytes as appropriate to make it conform to the specified length. When +unpacking, the resulting Array always has exactly the specified number of bytes. +As a special case, "0A" means a single, empty Array. + +For the "s" format character, the count is interpreted as the size of the +string, not a repeat count as for the other format characters; for example, +"10s" means a single 10-byte string, while "10c" means 10 characters. When +packing, the string is truncated or padded with 0 bytes as appropriate to make +it conform to the specified length. When unpacking, the resulting string always +has exactly the specified number of bytes. As a special case, "0s" means a +single, empty string (while "0c" means 0 characters). + + +By default, C numbers are represented in network (or big-endian) byte order. +Alternatively, the first character of the format string can be used to indicate +byte order of the packed data, according to the following table: + + Character | Byte Order + ---------------------------------- + < | little-endian + > | big-endian + ! | network (= big-endian) + +If the first character is not one of these, "!" is assumed. diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/jspack.js b/MAVLinkDashboard/MAVLink/local_modules/jspack/jspack.js new file mode 100644 index 00000000..20095fed --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/jspack.js @@ -0,0 +1,891 @@ +/** + * @license + + Copyright © 2008 Fair Oaks Labs, Inc. + All rights reserved. + + This file is Modified from the original, by buzz 2020: + - ran thru http://www.jsnice.org/ and manually renamed the variables to be clearer + - added optionally enabled debugging/verbose/printfs throughout + - bugfixes and integration so it now passes our mavlink.js testsuite/s + - please see README.md in the upper level folder. +*/ +'use strict'; + +//var Long = require('long'); + +let DEBUG = false; + +/** + * @return {undefined} + */ +function JSPack() { + var el; + /** @type {boolean} */ + var booleanIsBigEndian = false; + var m = this; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @param {number} len + * @return {?} + */ + //Raw byte arrays + // m._DeArray = function(octet_array_a, offset_p, len) { + // if (DEBUG) console.log("zzz1"); + // return [octet_array_a.slice(offset_p, offset_p + len)]; + //}; + + /** + * @param {!Array} to_octet_array_a + * @param {number} offset_p + * @param {number} len + * @param {!NodeList} from_array_v + * @return {undefined} + */ + // m._EnArray = function(to_octet_array_a, offset_p, len, from_array_v) { + // if (DEBUG) console.log("zzz2"); + /** @type {number} */ + // var i = 0; + // for (; i < len; to_octet_array_a[offset_p + i] = from_array_v[i] ? from_array_v[i] : 0, i++) { + // } + //}; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // ASCII characters + m._DeChar = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz3"); + return String.fromCharCode(octet_array_a[offset_p]); + }; + /** + * @param {!Array} to_octet_array_a + * @param {number} offset_p + * @param {string} from_str_array_v + * @return {undefined} + */ + // m._EnChar = function(to_octet_array_a, offset_p, from_str_array_v) { + // if (DEBUG) console.log("zzz4"); + // /** @type {number} */ + // to_octet_array_a[offset_p] = from_str_array_v.charCodeAt(0); + // }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + //Little-endian (un)signed N-byte integers + m._DeInt = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz5"); + /** @type {number} */ + var lsb = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + var nsb = booleanIsBigEndian ? -1 : 1; + /** @type {number} */ + var stop = lsb + nsb * el.len; + var rv; + var i; + var f; + /** @type {number} */ + rv = 0; + /** @type {number} */ + i = lsb; + /** @type {number} */ + f = 1; + for (; i != stop; rv = rv + octet_array_a[offset_p + i] * f, i = i + nsb, f = f * 256) { + } + if (el.bSigned && rv & Math.pow(2, el.len * 8 - 1)) { + /** @type {number} */ + rv = rv - Math.pow(2, el.len * 8); + } + return rv; + }; + + + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} val + * @return {undefined} + */ + m._EnInt = function(octet_array_a, offset_p, val) { + if (DEBUG) console.log("chunk-from: "+val); + /** @type {number} */ + var lsb = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + var nsb = booleanIsBigEndian ? -1 : 1; + /** @type {number} */ + var stop = lsb + nsb * el.len; + var i; + // range limit: + if (val < el.min ) { + val = el.min; + console.log("value limited to MIN:"+val); + } + if (val > el.max ) { + val = el.max; + console.log("value limited to MAX:"+val); + } + /** @type {number} */ + i = lsb; + if (DEBUG) console.log("booleanIsBigEndian:"+booleanIsBigEndian); + if (DEBUG) console.log("el.len:"+el.len); + if (DEBUG) console.log("lsb:"+lsb); + if (DEBUG) console.log("nsb:"+nsb); + if (DEBUG) console.log("i:"+i); + if (DEBUG) console.log("stop:"+stop); + for (; i != stop; ) { + + var to = JSON.stringify(val&255); + if (DEBUG) console.log("chunk as bytes: "+to); + + octet_array_a[offset_p + i] = val & 255; + i = i + nsb; + val = val >> 8; + + + } + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @param {number} len + * @return {?} + */ + // ASCII character strings + m._DeString = function(octet_array_a, offset_p, len) { + if (DEBUG) console.log("zzz7"); + /** @type {!Array} */ + var retval = new Array(len); + /** @type {number} */ + var i = 0; + for (; i < len; retval[i] = String.fromCharCode(octet_array_a[offset_p + i]), i++) { + } + return retval.join(""); + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} len + * @param {string} strval + * @return {undefined} + */ + m._EnString = function(octet_array_a, offset_p, len, strval) { + if (DEBUG) console.log("zzz8"); + var t; + /** @type {number} */ + if ( DEBUG ) console.log("strencode before: "+octet_array_a+"\np:"+offset_p+" len:"+len+" strval:"+strval) + var i = 0; + //if (DEBUG) console.log("strval:"+strval); +//console.trace("Here I am!") + + // we all strings to be passed in as a string of characters, or a an array or buffer of them is ok too + + if (typeof strval.charCodeAt === "function") { + for (; i < len; octet_array_a[offset_p + i] = (t = strval.charCodeAt(i)) ? t : 0, i++) { + if ( t > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc to jspack, not 8 bit ascii. please use .toString('binary'); not .toString();"); + } + if ( DEBUG ) console.log("strencode from CHAR-string."); + + } else if (Array.isArray(strval)) { + for (; i < len; octet_array_a[offset_p + i] = (t = strval[i]) ? t : 0, i++) { + // referring directly to 't' inside this loop is bad, seems delayed by an iteration, but strval[i] is ok. + if ( strval[i] > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc, or array data with values > 255, to jspack, not 8 bit ascii.\n(bad Array data)"+strval[i]); + } + if ( DEBUG ) console.log("strencode from ARRAY."); + + } else if (Buffer.isBuffer(strval)) { + for (; i < len; octet_array_a[offset_p + i] = (t = strval[i]) ? t : 0, i++) { + if ( strval[i] > 255 ) console.log("ERROR ERROR ERROR ERROR ERROR ERROR - It seems u passed unicode/utf-8/etc to jspack, not 8 bit ascii. \n(bad Buffer data)"+strval[i]); + } + if ( DEBUG ) console.log("strencode from Buffer."); + + } else { + console.log("ERROR encoding string _EnString: array:"+octet_array_a+" p:"+offset_p+" len:"+len+" strval:"+JSON.stringify(strval)) +} + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Little-endian N-bit IEEE 754 floating point + m._De754 = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz9"); + var bool_s; + var exponent; + var mantissa; + var i; + var d; + var nBits; + var mantissaLen; + var exponentLen; + var eBias; + var eMax; + mantissaLen = el.mLen; + /** @type {number} */ + exponentLen = el.len * 8 - el.mLen - 1; + /** @type {number} */ + eMax = (1 << exponentLen) - 1; + /** @type {number} */ + eBias = eMax >> 1; + /** @type {number} */ + i = booleanIsBigEndian ? 0 : el.len - 1; + /** @type {number} */ + d = booleanIsBigEndian ? 1 : -1; + bool_s = octet_array_a[offset_p + i]; + /** @type {number} */ + i = i + d; + /** @type {number} */ + nBits = -7; + /** @type {number} */ + exponent = bool_s & (1 << -nBits) - 1; + /** @type {number} */ + bool_s = bool_s >> -nBits; + /** @type {number} */ + nBits = nBits + exponentLen; + for (; nBits > 0; exponent = exponent * 256 + octet_array_a[offset_p + i], i = i + d, nBits = nBits - 8) { + } + /** @type {number} */ + mantissa = exponent & (1 << -nBits) - 1; + /** @type {number} */ + exponent = exponent >> -nBits; + nBits = nBits + mantissaLen; + for (; nBits > 0; mantissa = mantissa * 256 + octet_array_a[offset_p + i], i = i + d, nBits = nBits - 8) { + } + switch(exponent) { + case 0: + /** @type {number} */ + // Zero, or denormalized number + exponent = 1 - eBias; + break; + case eMax: + // NaN, or +/-Infinity + return mantissa ? NaN : (bool_s ? -1 : 1) * Infinity; + default: + // Normalized number + mantissa = mantissa + Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = exponent - eBias; + break; + } + return (bool_s ? -1 : 1) * mantissa * Math.pow(2, exponent - mantissaLen); + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {number} v + * @return {undefined} + */ + m._En754 = function(octet_array_a, offset_p, v) { + if (DEBUG) console.log("zzz_10"); + var bool_s; + var exponent; + var mantissa; + var i; + var d; + var c; + var mantissaLen; + var exponentLen; + var eBias; + var eMax; + mantissaLen = el.mLen; + /** @type {number} */ + exponentLen = el.len * 8 - el.mLen - 1; + /** @type {number} */ + eMax = (1 << exponentLen) - 1; + /** @type {number} */ + eBias = eMax >> 1; + /** @type {number} */ + bool_s = v < 0 ? 1 : 0; + /** @type {number} */ + v = Math.abs(v); + if (isNaN(v) || v == Infinity) { + /** @type {number} */ + mantissa = isNaN(v) ? 1 : 0; + /** @type {number} */ + exponent = eMax; + } else { + /** @type {number} */ + exponent = Math.floor(Math.log(v) / Math.LN2);// Calculate log2 of the value + if (v * (c = Math.pow(2, -exponent)) < 1) { // Math.log() isn't 100% reliable + exponent--; + /** @type {number} */ + c = c * 2; + } + // Round by adding 1/2 the significand's LSD + if (exponent + eBias >= 1) { + /** @type {number} */ + v = v + el.rt / c; // Normalized: mLen significand digits + } else { + /** @type {number} */ + v = v + el.rt * Math.pow(2, 1 - eBias);// Denormalized: <= mLen significand digits + } + if (v * c >= 2) { + exponent++; + /** @type {number} */ + c = c / 2; // Rounding can increment the exponent + } + if (exponent + eBias >= eMax) { + // Overflow + /** @type {number} */ + mantissa = 0; + /** @type {number} */ + exponent = eMax; + } else { + if (exponent + eBias >= 1) { + // Normalized - term order matters, as Math.pow(2, 52-e) and v*Math.pow(2, 52) can overflow + /** @type {number} */ + mantissa = (v * c - 1) * Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = exponent + eBias; + } else { + // Denormalized - also catches the '0' case, somewhat by chance + /** @type {number} */ + mantissa = v * Math.pow(2, eBias - 1) * Math.pow(2, mantissaLen); + /** @type {number} */ + exponent = 0; + } + } + } + /** @type {number} */ + i = booleanIsBigEndian ? el.len - 1 : 0; + /** @type {number} */ + d = booleanIsBigEndian ? -1 : 1; + for (; mantissaLen >= 8; octet_array_a[offset_p + i] = mantissa & 255, i = i + d, mantissa = mantissa / 256, mantissaLen = mantissaLen - 8) { + } + /** @type {number} */ + exponent = exponent << mantissaLen | mantissa; + exponentLen = exponentLen + mantissaLen; + for (; exponentLen > 0; octet_array_a[offset_p + i] = exponent & 255, i = i + d, exponent = exponent / 256, exponentLen = exponentLen - 8) { + } + octet_array_a[offset_p + i - d] |= bool_s * 128; + }; + + + /** + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Convert int64 to array with 3 elements: [lowBits, highBits, unsignedFlag] + // '>>>' trick to convert signed 32bit int to unsigned int (because << always results in a signed 32bit int) + m._DeInt64 = function(octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_11"); + /** @type {number} */ + var lsb = booleanIsBigEndian ? 0 : 7; + /** @type {number} */ + var nsb = booleanIsBigEndian ? 1 : -1; + /** @type {number} */ + var stop = lsb + nsb * 8; + /** @type {!Array} */ + var nextIdLookup = [0, 0, !el.bSigned]; + var i; + var f; + var indexLookupKey; + /** @type {number} */ + i = lsb; + /** @type {number} */ + indexLookupKey = 1; + /** @type {number} */ + f = 0; + for (; i != stop; nextIdLookup[indexLookupKey] = (nextIdLookup[indexLookupKey] << 8 >>> 0) + octet_array_a[offset_p + i], i = i + nsb, f++, indexLookupKey = f < 4 ? 1 : 0) { + + if ( DEBUG ) console.log("jsPacking int64:"+octet_array_a[offset_p + i]); + + } + return nextIdLookup; + }; + /** + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {!Object} v + * @return {undefined} + */ + m._EnInt64 = function(octet_array_a, offset_p, v) { + + if (v.length != 2) { //todo put this error back + console.log("ERROR ERROR: jspack needs an array of at least length TWO to pack an int64 "+v+' len:'+v.length); + } +// if (DEBUG) console.log("zzz_12 v:"+v); + /** @type {number} */ + var lsb = booleanIsBigEndian ? 0 : 7; + /** @type {number} */ + var nsb = booleanIsBigEndian ? 1 : -1; + /** @type {number} */ + var stop = lsb + nsb * 8; + var i; + var f; + var j; + var shift; + /** @type {number} */ + i = lsb; + /** @type {number} */ + j = 1; + /** @type {number} */ + f = 0; + /** @type {number} */ + shift = 24; + + for (; i != stop; octet_array_a[offset_p + i] = v[j] >> shift & 255, i = i + nsb, f++, j = f < 4 ? 1 : 0, shift = 24 - 8 * (f % 4)) { + var x = v[j] >> shift & 255 ; + var vj = v[j]; + + if ( DEBUG ) console.log('js qqqq vj:'+vj+' j:'+j+' x:'+x+' a:'+octet_array_a+' i:'+i+" offset_p:"+offset_p+" v:"+v); + } + }; + + + + // Class data + /** @type {string} */ + m._sPattern = "(\\d+)?([AxcbBhHsfdiIlLqQ])"; + + m._lenLut = {'A':1, 'x':1, 'c':1, 'b':1, 'B':1, 'h':2, 'H':2, 's':1, 'f':4, 'd':8, 'i':4, 'I':4, 'l':4, 'L':4, 'q':8, 'Q':8}; + + m._elLookUpTable = { 'A': {en:m._EnArray, de:m._DeArray}, + 's': {en:m._EnString, de:m._DeString}, + 'c': {en:m._EnChar, de:m._DeChar}, + 'b': {en:m._EnInt, de:m._DeInt, len:1, bSigned:true, min:-Math.pow(2, 7), max:Math.pow(2, 7)-1}, + 'B': {en:m._EnInt, de:m._DeInt, len:1, bSigned:false, min:0, max:Math.pow(2, 8)-1}, + 'h': {en:m._EnInt, de:m._DeInt, len:2, bSigned:true, min:-Math.pow(2, 15), max:Math.pow(2, 15)-1}, + 'H': {en:m._EnInt, de:m._DeInt, len:2, bSigned:false, min:0, max:Math.pow(2, 16)-1}, + 'i': {en:m._EnInt, de:m._DeInt, len:4, bSigned:true, min:-Math.pow(2, 31), max:Math.pow(2, 31)-1}, + 'I': {en:m._EnInt, de:m._DeInt, len:4, bSigned:false, min:0, max:Math.pow(2, 32)-1}, + 'l': {en:m._EnInt, de:m._DeInt, len:4, bSigned:true, min:-Math.pow(2, 31), max:Math.pow(2, 31)-1}, + 'L': {en:m._EnInt, de:m._DeInt, len:4, bSigned:false, min:0, max:Math.pow(2, 32)-1}, + 'f': {en:m._En754, de:m._De754, len:4, mLen:23, rt:Math.pow(2, -24)-Math.pow(2, -77)}, + 'd': {en:m._En754, de:m._De754, len:8, mLen:52, rt:0}, + 'q': {en:m._EnInt64, de:m._DeInt64, bSigned:true, len:8 }, // 64bit fields need 8 bytes.. + 'Q': {en:m._EnInt64, de:m._DeInt64, bSigned:false, len:8 }}; // quirk of longs is they come in with a length of 2 in an array + + + /** + * @param {number} num_elements_n + * @param {number} size_s + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Unpack a series of n elements of size s from array a at offset p with fxn + m._UnpackSeries = function(num_elements_n, size_s, octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_13"); + var fxn = el.de; + /** @type {!Array} */ + var rv = []; + /** @type {number} */ + var o = 0; + for (; o < num_elements_n; rv.push(fxn(octet_array_a, offset_p + o * size_s)), o++) { + } + return rv; + }; + /** + * @param {number} num_elements_n + * @param {number} size_s + * @param {!Array} to_octet_array_a + * @param {number} array_a_offset_p + * @param {(Array|NodeList|null)} from_array_v + * @param {number} array_v_offset_i + * @return {undefined} + */ + // Pack a series of n elements of size s from array v at offset i to array a at offset p with fxn + + m._PackSeries = function(num_elements_n, size_s, to_octet_array_a, array_a_offset_p, from_array_v, array_v_offset_i) { + if (DEBUG) console.log("pack-series: "); + + + if ( DEBUG ) console.log('js before 0:'+0+' num_elements_n:'+num_elements_n+' size_s:'+size_s+' to_a:'+to_octet_array_a+' i:'+array_v_offset_i+" offset_p:"+array_a_offset_p+" v:"+from_array_v); + var fxn = el.en; + /** @type {number} */ + var o = 0; + for (; o < num_elements_n; o++) { + //if (DEBUG) console.log("14 called fxn with o:"+o); + var z = from_array_v[array_v_offset_i + o]; + var to = JSON.stringify(z); + var too = JSON.stringify(from_array_v); + if (DEBUG) console.log('js pre-ffff z:'+z+' to:'+to+' too:'+too+''); + // handle flattened arrays - non-array things don't have a .length + try { + if (z.length == undefined ) { + //from_array_v = [ from_array_v ] ; + if (DEBUG) console.log('Z FIX'); + }} catch (e){} + var z = from_array_v[array_v_offset_i + o]; + var to = JSON.stringify(z); + var too = JSON.stringify(from_array_v); + + // if we only have one thing to back and its got an 8 byte target len ( it's a 64bit long), and length of source array is 2 ( low and high bits ) + // we treat it as a singular thing... we use this for Q type, which gets passed in as [lowBits, hightBits] + if (( num_elements_n == 1 ) && (size_s == 8) && (from_array_v.length == 2) ) { + z = from_array_v; + if (DEBUG) console.log("js handling Q 64bit array"); + } + + + if (DEBUG) console.log('js partial z:'+z+' to:'+to+' too:'+too+' num_elements_n:'+num_elements_n+' size_s:'+size_s+' to_a:'+to_octet_array_a+' v_offset_i:'+array_v_offset_i+" a_offset_p:"+array_a_offset_p+" from_v:"+from_array_v); + + fxn(to_octet_array_a, array_a_offset_p + o * size_s, z); + + } + if (DEBUG) console.log('js after to_a:'+to_octet_array_a); + }; + + + /** + * @param {string} fmt + * @param {!Object} octet_array_a + * @param {number} offset_p + * @return {?} + */ + // Unpack the octet array a, beginning at offset p, according to the fmt string + m.Unpack = function(fmt, octet_array_a, offset_p) { + if (DEBUG) console.log("zzz_15"); + /** @type {boolean} */ + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + /** @type {number} */ + offset_p = offset_p ? offset_p : 0; + /** @type {!RegExp} */ + var re = new RegExp(this._sPattern, "g"); + var re_match; + var repeat_count_n; + var element_size_s; + /** @type {!Array} */ + var rv = []; + + //loop over chars in the format string with regex due to optional digits + for (; re_match = re.exec(fmt);) { + /** @type {number} */ + repeat_count_n = re_match[1] == undefined || re_match[1] == "" ? 1 : parseInt(re_match[1]); + element_size_s = this._lenLut[re_match[2]]; + if (offset_p + repeat_count_n * element_size_s > octet_array_a.length) { + return undefined; + } + switch(re_match[2]) { + case "A": + case "s": + rv.push(this._elLookUpTable[re_match[2]].de(octet_array_a, offset_p, repeat_count_n)); + break; + case "c": + case "b": + case "B": + case "h": + case "H": + case "i": + case "I": + case "l": + case "L": + case "f": + case "d": + case "q": + case "Q": + el = this._elLookUpTable[re_match[2]]; + + //rv.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + + // unpack arrays to an actual array type within the field array result: + // https://github.com/AndreasAntener/node-jspack/commit/4f16680101303a6b4a1b0deba8cf7d20fc68213e + if (repeat_count_n > 1) { + // Field is array, unpack into separate array and push as such + var arr = []; + arr.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + rv.push(arr); + } else { + rv.push(this._UnpackSeries(repeat_count_n, element_size_s, octet_array_a, offset_p)); + } + + break; + } + /** @type {number} */ + offset_p = offset_p + repeat_count_n * element_size_s; + } + return Array.prototype.concat.apply([], rv); + }; + + + // cross check the list of input data matches the size of bytes we'll be assembling + // this is a slightly tweaked implementation of the previous 'PackTo' commented out below. + // it has a more-consistent approach to input and output arrays, paying particular attention to Q,q, long, etc + m.WouldPack = function(fmt, octet_array_a, offset_p, values) { + //if (DEBUG) console.log("zzz_16 fmt:"+JSON.stringify(fmt)+" values:"+JSON.stringify(values)); + // @type {boolean} / + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + // @type {!RegExp} / + var re = new RegExp(this._sPattern, "g"); + + var m; + var n; + var s; + var values_i = 0; // current index into the values[] + + var j; + for (; m = re.exec(fmt);) { + + // leading optional prefix num or 1 + n = m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1]); + + s = this._lenLut[m[2]]; + + + if (DEBUG) console.log("character: "+m[2]+" how many(n)?: "+n); + el = this._elLookUpTable[m[2]]; + + //if (DEBUG) console.log("using lookup table:"+JSON.stringify(el)); + var bytes_consumed_per_element = el["len"]; + bytes_consumed_per_element = bytes_consumed_per_element == undefined ? 1 : bytes_consumed_per_element ; // undefined means 1 + if (DEBUG) console.log("bytes_consumed_per_element:"+JSON.stringify(bytes_consumed_per_element)); + if (DEBUG) console.log("current_values_idx:"+JSON.stringify(values_i) +" values:"+JSON.stringify(values[values_i]) ) ; + + + // do per-case behaviours 'A' , 's' and 'x' are special, everything else gets the same + switch(m[2]) { + //------------------------------------------ + case "A": + case "s": + if (values_i + 1 > values.length) { + console.log("JSPACK-ERROR: values_i + 1 > values.length values_i:"+values_i+" values.length:"+values.length); + //return false; + } + if (DEBUG) console.log("all values:"+JSON.stringify(values)); + this._elLookUpTable[m[2]].en(octet_array_a, offset_p, n, values[values_i]); + // @type {number} / + values_i = values_i + 1; + break; + //------------------------------------------ + case "x": + // @type {number} / + j = 0; + for (; j < n; j++) { + // @type {number} / + octet_array_a[offset_p + j] = 0; + } + break; + //------------------------------------------ + // everything else + default: + + // if n > 1 , ie it's multiple occurrences of a 'thing' + if (n > 1 ) { + + // if we were handed an array at this idx, we need the array to be the same length as n + if (Array.isArray(values[values_i])) { + + // Value series is array, iterate through that, only increment by 1 + if ((values_i + 1) > values.length) { + if (DEBUG) console.log("JSPACK-ERROR: value series is array but (values_i + 1) > values.length. i:"+values_i+" values.length:"+values.length); + //return false; + } + if (DEBUG) console.log("(dst IS array) (source IS array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values[values_i], 0); + values_i += 1; + } + else { + if (DEBUG) console.log("ERROR: (dst IS array) (source is not array)"); + } + } + + // if n == 1, it just one of a thing + if (n == 1 ) { + + // type Q can have the source as an array when there is only 1 of them. + if (Array.isArray(values[values_i]) ) { + + if (( m[2] == 'Q' ) || ( m[2] == 'q' ) ) { + this._PackSeries(n, s, octet_array_a, offset_p, values[values_i], 0); + values_i += 1; + } + if (DEBUG) console.log("(dst is not array) (source IS array)"); + + } else { + if ((values_i + n ) > values.length) { + if (DEBUG) console.log("JSPACK-ERROR: value series NOT array but (values_i + n ) > values.length. i:"+values_i+" n:"+n+" values.length:"+values.length+" values:"+JSON.stringify(values)); + //return false; + } + if (DEBUG) console.log("(dst is not array) (source is not array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values, values_i); + values_i += n; + } + } + + if (DEBUG) console.log(""); + break; + //------------------------------------------ + } + + offset_p = offset_p + n * s; + + } + if (DEBUG) console.log("wouldpack completed, result array_a is:"+JSON.stringify(octet_array_a)); + return octet_array_a + } + + + + /** + * @param {string} fmt + * @param {!Array} octet_array_a + * @param {number} offset_p + * @param {!NodeList} values + * @return {?} + */ +/* + // Pack the supplied values into the octet array a, beginning at offset p, according to the fmt string + m.PackTo = function(fmt, octet_array_a, offset_p, values) { + if (DEBUG) console.log("zzz_16 fmt:"+JSON.stringify(fmt)+" values:"+JSON.stringify(values)); + // @type {boolean} / + // Set the private bBE flag based on the format string - assume big-endianness + booleanIsBigEndian = fmt.charAt(0) != "<"; + // @type {!RegExp} / + var re = new RegExp(this._sPattern, "g"); + var m; + var n; + var s; + // @type {number} / + var i = 0; + var j; + for (; m = re.exec(fmt);) { + // @type {number} / + n = m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1]); + s = this._lenLut[m[2]]; + if (offset_p + n * s > octet_array_a.length) { + console.log("JSPACK-ERROR: offset_p + n * s > octet_array_a.length offset_p:"+offset_p+" n:"+n+" s:"+s+" octet_array_a.length:"+octet_array_a.length+" octet_array_a:"+JSON.stringify(octet_array_a)); + return false; + } + if (DEBUG) console.log("\n---------------------------------------------\n"); + if (DEBUG) console.log("handling format specifier:"+m[2]+" how many:"+n); + switch(m[2]) { + case "A": + case "s": + if (i + 1 > values.length) { + console.log("JSPACK-ERROR: i + 1 > values.length i:"+i+" values.length:"+values.length); + return false; + } + if (DEBUG) console.log("zzz_16A values:"+JSON.stringify(values)); + this._elLookUpTable[m[2]].en(octet_array_a, offset_p, n, values[i]); + // @type {number} / + i = i + 1; + break; + case "c": + case "b": + case "B": + case "h": + case "H": + case "i": + case "I": + case "l": + case "L": + case "f": + case "d": + case "q": + case "Q": + if (DEBUG) console.log("16 blerg"); + el = this._elLookUpTable[m[2]]; + if (DEBUG) console.log("using lookup table:"+JSON.stringify(el)); + //if (i + n > values.length) { return false; } + //this._PackSeries(n, s, octet_array_a, offset_p, values, i); + //i = i + n; + //added support for packing value series when they are supplied as arrays within the values array + // https://github.com/AndreasAntener/node-jspack/commit/8de80d20aa06dea15527b3073c6c8631abda0f17 + if (n > 1 && Array.isArray(values[i])) { + // Value series is array, iterate through that, only increment by 1 + if ((i + 1) > values.length) { + console.log("JSPACK-ERROR: value series is array but (i + 1) > values.length. i:"+i+" values.length:"+values.length); + return false; + } + if (DEBUG) console.log("zzz_16 option 1 (source is array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values[i], 0); + i += 1; + } else { + if ((i + n) > values.length) { + console.log("JSPACK-ERROR: value series NOT array but (i + n) > values.length. i:"+i+" n:"+n+" values.length:"+values.length+" values:"+JSON.stringify(values)); + //return false; + } + if (DEBUG) console.log("zzz_16 option 2 (source is not array)"); + this._PackSeries(n, s, octet_array_a, offset_p, values, i); + i += n; + } + + if (DEBUG) console.log("end case"); + break; + case "x": + // @type {number} / + j = 0; + for (; j < n; j++) { + // @type {number} / + octet_array_a[offset_p + j] = 0; + } + break; + } + // @type {number} / + offset_p = offset_p + n * s; + } + console.log("pack completed, result array_a is:"+JSON.stringify(octet_array_a)); + return octet_array_a; + }; + */ + + /** + * @param {string} fmt + * @param {(Node|NodeList|null|string)} values + * @return {?} + */ + // Pack the supplied values into a new octet array, according to the fmt string + m.Pack = function(fmt, values) { + if (DEBUG) console.log("\n\n------------------------------------------------------------------------------------------------------------\n\n"); + if (DEBUG) console.log("initial unpacked values:"+JSON.stringify(values)); + if (DEBUG) console.log("initial format string:"+JSON.stringify(fmt)); + if (DEBUG) console.log("\n\nwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww\n\n"); + return this.WouldPack(fmt, new Array(this.CalcLength(fmt)), 0, values); + //if (DEBUG) console.log("\n\nmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm\n\n"); + // return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); + }; + + /** + * @param {string} fmt + * @param {(Node|NodeList|null|string)} values + * @return {?} + */ + // Pack the supplied values into a new octet array, according to the fmt string + m.oldPack = function(fmt, values) { + if (DEBUG) console.log("\n\n------------------------------------------------------------------------------------------------------------\n\n"); + if (DEBUG) console.log("initial unpacked values:"+JSON.stringify(values)); + if (DEBUG) console.log("initial format string:"+JSON.stringify(fmt)); + return this.PackTo(fmt, new Array(this.CalcLength(fmt)), 0, values); + }; + + /** + * @param {string} fmt + * @return {?} + */ + // Determine the number of bytes represented by the format string + m.CalcLength = function(fmt) { + + /** @type {!RegExp} */ + var re = new RegExp(this._sPattern, "g"); + var m; + /** @type {number} */ + var value = 0; + for (; m = re.exec(fmt);) { + /** @type {number} */ + value = value + (m[1] == undefined || m[1] == "" ? 1 : parseInt(m[1])) * this._lenLut[m[2]]; + } + if (DEBUG) console.log("number of bytes in format string?: "+value+"\n"); + return value; + }; +} + +export default JSPack + diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/package.json b/MAVLinkDashboard/MAVLink/local_modules/jspack/package.json new file mode 100644 index 00000000..f9c36896 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/package.json @@ -0,0 +1,64 @@ +{ + "_from": "jspack@0.0.4", + "_id": "jspack@0.0.4", + "_inBundle": false, + "_integrity": "sha1-Mt01x/3LPjRWwY+7fvntC8YjgXc=", + "_location": "/jspack", + "_phantomChildren": {}, + "_requested": { + "type": "version", + "registry": false, + "raw": "jspack@0.0.4", + "name": "jspack", + "escapedName": "jspack", + "rawSpec": "0.0.4", + "saveSpec": null, + "fetchSpec": "0.0.4" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "local_modules/jspack", + "_shasum": "32dd35c7fdcb3e3456c18fbb7ef9ed0bc6238177", + "_spec": "jspack@0.0.4", + "_where": "/home/buzz/GCS/mavlink/pymavlink/generator/javascript", + "author": { + "name": "https://github.com/pgriess" + }, + "bugs": { + "url": "https://github.com/birchroad/node-jspack/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "JavaScript library to pack primitives to octet arrays, including int64 support, packaged for NodeJS.", + "devDependencies": { + "long": "", + "mocha": "", + "should": "", + "sinon": "" + }, + "homepage": "https://github.com/birchroad/node-jspack", + "main": "./jspack.js", + "maintainers": [ + { + "name": "Peter Magnusson", + "email": "peter@birchroad.net", + "url": "http://github.com/birchroad/node-jspack" + }, + { + "name": "Andreas Antener", + "url": "https://github.com/AndreasAntener/node-jspack" + } + ], + "name": "jspack", + "repository": { + "type": "git", + "url": "https://github.com/birchroad/node-jspack.git" + }, + "scripts": { + "pretest": "npm install", + "test": "mocha test" + }, + "version": "0.0.4" +} diff --git a/MAVLinkDashboard/MAVLink/local_modules/jspack/test/int64.js b/MAVLinkDashboard/MAVLink/local_modules/jspack/test/int64.js new file mode 100644 index 00000000..fc72eb49 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/jspack/test/int64.js @@ -0,0 +1,456 @@ +// This file is MODIFIED from the original, by buzz 2020, please see README.md in the upper level folder for more details. +var should = require('should'); +var jspack = require('../jspack.js').jspack; +var Long = require('long'); + +describe('Test long integration (examples):', function() { + + // Demonstrating the use together with Long.js (https://github.com/dcodeIO/Long.js) + // + // Packing a long requires the input of a 2 part array containing the [low, high] bits + // of the specific long value. + // Unpacking a long results in a 3 part array containing [low, high, unsigned] bits and flag. + // The decoded value can be applied directly to Long.fromBits() + // + // Test number u 228290380562207 (BE: 0x00, 0x00, 0xcf, 0xa0, 0xff, 0x09, 0xff, 0x1f) + // (LE: 0x1f, 0xff, 0x09, 0xff, 0xa0, 0xcf, 0x00, 0x00) + // Test number s -228290380562207 (BE: 0xff, 0xff, 0x30, 0x5f, 0x00, 0xf6, 0x00, 0xe1) + // (LE: 0xe1, 0x00, 0xf6, 0x00, 0x5f, 0x30, 0xff, 0xff) + + it('pack Q', function() { + var buf = jspack.Pack('>Q', [[0xffe1ffff, 0xffa0]]); + buf.should.be.eql([0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + }); + + it('unpack Q', function() { + var buf = jspack.Unpack('>Q', [0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xffe1ffff); + buf[0][1].should.be.eql(0xffa0); + buf[0][2].should.be.true; + }); + + // Test lower-case q as well. This only test the matching of the character and the unsigned bit, + // the parsing is the same as for upper-case Q (since we don't actually convert to a number). + it('pack >q (signed)', function() { + var buf = jspack.Pack('>q', [[0xffe1ffff, 0xffa0]]); + buf.should.be.eql([0x00, 0x00, 0xff, 0xa0, 0xff, 0xe1, 0xff, 0xff]); + }); + + it('unpack >> 0).toString(2); + y = ("00000000000000000000000000000000" + x).slice(-32) + y1 = y.substring(0,8); + y2 = y.substring(8,16); + y3 = y.substring(16,24); + y4 = y.substring(24,32); + return [y,y1,y2,y3,y4]; +} +function dec2bin_ws(dec) { + var str = dec2bin(dec); + var bb = str.slice(1); //1-4 skipping zero + var bbj = bb.join(' '); + return bbj; +} + +describe('ASCII Boundary tests:', function() { + + it('pack <4s correctly over the ascii 127->128->129 boundary', function() { // should work in range 0-255 if u use 'binary' encoding + + this.format = '<4s'; + + this.ascii_bytes = new Buffer.from([ 126, 127, 128, 129]).toString('binary'); // 'binary' encoding is important here, as without it values above 128 are treated as unicode. + var buf = jspack.Pack(this.format, [ this.ascii_bytes]); + body = [ 0x7e, 0x7f, 0x80, 0x81]; // expected result + buf.should.be.eql(body); + + }); + + it('long Q buzz', function() { // should work in range 0-255 if u use 'binary' encoding + +//from aoa_ssa + + this.format = '> 8) & 0xFF), this.msgId>>16]; + + this.msgId = 130; + + var v1 = ((this.msgId & 0xFF) << 8) | ((this.msgId >> 8) & 0xFF); + var v2 = this.msgId>>16; + + v1.should.be.eql(33280); + v2.should.be.eql(0); + + var orderedfields = [253,13,0,0,40,11,10,33280,0]; + + console.log("------------------------------------------------------------------------\nmavheader:"+JSON.stringify(orderedfields)); + var hdr = jspack.Pack('BBBBBBBHB',orderedfields); + + buf = [0xfd, 0x0d, 0x00, 0x00, 0x28, 0x0b, 0x0a, 0x82, 0x00, 0x00]; + + buf.should.be.eql(hdr); + }); + +}); + +describe('Q Boundary tests:', function() { + + it('unpack >Q full', function() { + var buf = jspack.Unpack('>Q', [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xffffffff); + buf[0][1].should.be.eql(0xffffffff); + buf[0][2].should.be.true; + }); + + it('pack >Q full', function() { + var buf = jspack.Pack('>Q', [[0xffffffff, 0xffffffff]]); + buf.should.be.eql([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + }); + + it('unpack Q zero', function() { + var buf = jspack.Unpack('>Q', [0, 0, 0, 0, 0, 0, 0, 0]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0); + buf[0][1].should.be.eql(0); + buf[0][2].should.be.true; + }); + + it('pack >Q zero', function() { + var buf = jspack.Pack('>Q', [[0, 0]]); + buf.should.be.eql([0, 0, 0, 0, 0, 0, 0, 0]); + }); + + it('unpack Q one', function() { + var buf = jspack.Unpack('>Q', [1, 1, 1, 1, 1, 1, 1, 1]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0x01010101); + buf[0][1].should.be.eql(0x01010101); + buf[0][2].should.be.true; + }); + + it('pack >Q one', function() { + var buf = jspack.Pack('>Q', [[0x01010101, 0x01010101]]); + buf.should.be.eql([1, 1, 1, 1, 1, 1, 1, 1]); + }); + + it('unpack Q 0xfe', function() { + var buf = jspack.Unpack('>Q', [0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe]); + buf.length.should.be.eql(1); + buf[0].length.should.be.eql(3); + buf[0][0].should.be.eql(0xfefefefe); + buf[0][1].should.be.eql(0xfefefefe); + buf[0][2].should.be.true; + }); + + it('pack >Q 0xfe', function() { + var buf = jspack.Pack('>Q', [[0xfefefefe, 0xfefefefe]]); + buf.should.be.eql([0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe]); + }); + + it('unpack 53 are representable in the Number type", which is "representing the +doubleprecision 64-bit format IEEE 754 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic". +The [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +in JavaScript is 253-1. + +Example: 264-1 is 1844674407370955**1615** but in JavaScript it evaluates to 1844674407370955**2000**. + +Furthermore, bitwise operators in JavaScript "deal only with integers in the range −231 through +231−1, inclusive, or in the range 0 through 232−1, inclusive. These operators accept any value of +the Number type but first convert each such value to one of 232 integer values." + +In some use cases, however, it is required to be able to reliably work with and perform bitwise operations on the full +64 bits. This is where long.js comes into play. + +Usage +----- + +The class is compatible with CommonJS and AMD loaders and is exposed globally as `Long` if neither is available. + +```javascript +var Long = require("long"); + +var longVal = new Long(0xFFFFFFFF, 0x7FFFFFFF); + +console.log(longVal.toString()); +... +``` + +API +--- + +### Constructor + +* new **Long**(low: `number`, high: `number`, unsigned?: `boolean`)
+ Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. See the from* functions below for more convenient ways of constructing Longs. + +### Fields + +* Long#**low**: `number`
+ The low 32 bits as a signed value. + +* Long#**high**: `number`
+ The high 32 bits as a signed value. + +* Long#**unsigned**: `boolean`
+ Whether unsigned or not. + +### Constants + +* Long.**ZERO**: `Long`
+ Signed zero. + +* Long.**ONE**: `Long`
+ Signed one. + +* Long.**NEG_ONE**: `Long`
+ Signed negative one. + +* Long.**UZERO**: `Long`
+ Unsigned zero. + +* Long.**UONE**: `Long`
+ Unsigned one. + +* Long.**MAX_VALUE**: `Long`
+ Maximum signed value. + +* Long.**MIN_VALUE**: `Long`
+ Minimum signed value. + +* Long.**MAX_UNSIGNED_VALUE**: `Long`
+ Maximum unsigned value. + +### Utility + +* Long.**isLong**(obj: `*`): `boolean`
+ Tests if the specified object is a Long. + +* Long.**fromBits**(lowBits: `number`, highBits: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is assumed to use 32 bits. + +* Long.**fromBytes**(bytes: `number[]`, unsigned?: `boolean`, le?: `boolean`): `Long`
+ Creates a Long from its byte representation. + +* Long.**fromBytesLE**(bytes: `number[]`, unsigned?: `boolean`): `Long`
+ Creates a Long from its little endian byte representation. + +* Long.**fromBytesBE**(bytes: `number[]`, unsigned?: `boolean`): `Long`
+ Creates a Long from its big endian byte representation. + +* Long.**fromInt**(value: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the given 32 bit integer value. + +* Long.**fromNumber**(value: `number`, unsigned?: `boolean`): `Long`
+ Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + +* Long.**fromString**(str: `string`, unsigned?: `boolean`, radix?: `number`)
+ Long.**fromString**(str: `string`, radix: `number`)
+ Returns a Long representation of the given string, written using the specified radix. + +* Long.**fromValue**(val: `*`, unsigned?: `boolean`): `Long`
+ Converts the specified value to a Long using the appropriate from* function for its type. + +### Methods + +* Long#**add**(addend: `Long | number | string`): `Long`
+ Returns the sum of this and the specified Long. + +* Long#**and**(other: `Long | number | string`): `Long`
+ Returns the bitwise AND of this Long and the specified. + +* Long#**compare**/**comp**(other: `Long | number | string`): `number`
+ Compares this Long's value with the specified's. Returns `0` if they are the same, `1` if the this is greater and `-1` if the given one is greater. + +* Long#**divide**/**div**(divisor: `Long | number | string`): `Long`
+ Returns this Long divided by the specified. + +* Long#**equals**/**eq**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value equals the specified's. + +* Long#**getHighBits**(): `number`
+ Gets the high 32 bits as a signed integer. + +* Long#**getHighBitsUnsigned**(): `number`
+ Gets the high 32 bits as an unsigned integer. + +* Long#**getLowBits**(): `number`
+ Gets the low 32 bits as a signed integer. + +* Long#**getLowBitsUnsigned**(): `number`
+ Gets the low 32 bits as an unsigned integer. + +* Long#**getNumBitsAbs**(): `number`
+ Gets the number of bits needed to represent the absolute value of this Long. + +* Long#**greaterThan**/**gt**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is greater than the specified's. + +* Long#**greaterThanOrEqual**/**gte**/**ge**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is greater than or equal the specified's. + +* Long#**isEven**(): `boolean`
+ Tests if this Long's value is even. + +* Long#**isNegative**(): `boolean`
+ Tests if this Long's value is negative. + +* Long#**isOdd**(): `boolean`
+ Tests if this Long's value is odd. + +* Long#**isPositive**(): `boolean`
+ Tests if this Long's value is positive. + +* Long#**isZero**/**eqz**(): `boolean`
+ Tests if this Long's value equals zero. + +* Long#**lessThan**/**lt**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is less than the specified's. + +* Long#**lessThanOrEqual**/**lte**/**le**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value is less than or equal the specified's. + +* Long#**modulo**/**mod**/**rem**(divisor: `Long | number | string`): `Long`
+ Returns this Long modulo the specified. + +* Long#**multiply**/**mul**(multiplier: `Long | number | string`): `Long`
+ Returns the product of this and the specified Long. + +* Long#**negate**/**neg**(): `Long`
+ Negates this Long's value. + +* Long#**not**(): `Long`
+ Returns the bitwise NOT of this Long. + +* Long#**notEquals**/**neq**/**ne**(other: `Long | number | string`): `boolean`
+ Tests if this Long's value differs from the specified's. + +* Long#**or**(other: `Long | number | string`): `Long`
+ Returns the bitwise OR of this Long and the specified. + +* Long#**shiftLeft**/**shl**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits shifted to the left by the given amount. + +* Long#**shiftRight**/**shr**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits arithmetically shifted to the right by the given amount. + +* Long#**shiftRightUnsigned**/**shru**/**shr_u**(numBits: `Long | number | string`): `Long`
+ Returns this Long with bits logically shifted to the right by the given amount. + +* Long#**subtract**/**sub**(subtrahend: `Long | number | string`): `Long`
+ Returns the difference of this and the specified Long. + +* Long#**toBytes**(le?: `boolean`): `number[]`
+ Converts this Long to its byte representation. + +* Long#**toBytesLE**(): `number[]`
+ Converts this Long to its little endian byte representation. + +* Long#**toBytesBE**(): `number[]`
+ Converts this Long to its big endian byte representation. + +* Long#**toInt**(): `number`
+ Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + +* Long#**toNumber**(): `number`
+ Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + +* Long#**toSigned**(): `Long`
+ Converts this Long to signed. + +* Long#**toString**(radix?: `number`): `string`
+ Converts the Long to a string written in the specified radix. + +* Long#**toUnsigned**(): `Long`
+ Converts this Long to unsigned. + +* Long#**xor**(other: `Long | number | string`): `Long`
+ Returns the bitwise XOR of this Long and the given one. + +Building +-------- + +To build an UMD bundle to `dist/long.js`, run: + +``` +$> npm install +$> npm run build +``` + +Running the [tests](./tests): + +``` +$> npm test +``` diff --git a/MAVLinkDashboard/MAVLink/local_modules/long/index.js b/MAVLinkDashboard/MAVLink/local_modules/long/index.js new file mode 100644 index 00000000..e16857a1 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/long/index.js @@ -0,0 +1 @@ +module.exports = require("./src/long"); diff --git a/MAVLinkDashboard/MAVLink/local_modules/long/package.json b/MAVLinkDashboard/MAVLink/local_modules/long/package.json new file mode 100644 index 00000000..532202f4 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/long/package.json @@ -0,0 +1,64 @@ +{ + "_from": "long@^4.0.0", + "_id": "long@4.0.0", + "_inBundle": false, + "_integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "_location": "/long", + "_phantomChildren": {}, + "_requested": { + "type": "range", + "registry": false, + "raw": "long@^4.0.0", + "name": "long", + "escapedName": "long", + "rawSpec": "^4.0.0", + "saveSpec": null, + "fetchSpec": "^4.0.0" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "local_modules/long", + "_shasum": "9a7b71cfb7d361a194ea555241c92f7468d5bf28", + "_spec": "long@^4.0.0", + "_where": "/home/buzz/GCS/mavlink/pymavlink/generator/javascript", + "author": { + "name": "Daniel Wirtz", + "email": "dcode@dcode.io" + }, + "bugs": { + "url": "https://github.com/dcodeIO/long.js/issues" + }, + "bundleDependencies": false, + "dependencies": {}, + "deprecated": false, + "description": "A Long class for representing a 64-bit two's-complement integer value.", + "devDependencies": { + "webpack": "^3.10.0" + }, + "files": [ + "index.js", + "LICENSE", + "README.md", + "src/long.js", + "dist/long.js", + "dist/long.js.map" + ], + "homepage": "https://github.com/dcodeIO/long.js#readme", + "keywords": [ + "math" + ], + "license": "Apache-2.0", + "main": "src/long.js", + "name": "long", + "repository": { + "type": "git", + "url": "git+https://github.com/dcodeIO/long.js.git" + }, + "scripts": { + "build": "webpack", + "test": "node tests" + }, + "version": "4.0.0" +} diff --git a/MAVLinkDashboard/MAVLink/local_modules/long/src/long.js b/MAVLinkDashboard/MAVLink/local_modules/long/src/long.js new file mode 100644 index 00000000..6eec276c --- /dev/null +++ b/MAVLinkDashboard/MAVLink/local_modules/long/src/long.js @@ -0,0 +1,1325 @@ +// This file is MODIFIED from the original, by buzz 2020, please see README.md in the upper level folder for more details. + +module.exports = Long; + +/** + * wasm optimizations, to do native i64 multiplication and divide + */ +var wasm = null; + +try { + wasm = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11 + ])), {}).exports; +} catch (e) { + // no wasm support :( +} + +/** + * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. + * See the from* functions below for more convenient ways of constructing Longs. + * @exports Long + * @class A Long class for representing a 64 bit two's-complement integer value. + * @param {number} low The low (signed) 32 bits of the long + * @param {number} high The high (signed) 32 bits of the long + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @constructor + */ +function Long(low, high, unsigned) { + + /** + * The low 32 bits as a signed value. + * @type {number} + */ + this.low = low | 0; + + /** + * The high 32 bits as a signed value. + * @type {number} + */ + this.high = high | 0; + + /** + * Whether unsigned or not. + * @type {boolean} + */ + this.unsigned = !!unsigned; +} + +// The internal representation of a long is the two given signed, 32-bit values. +// We use 32-bit pieces because these are the size of integers on which +// Javascript performs bit-operations. For operations like addition and +// multiplication, we split each number into 16 bit pieces, which can easily be +// multiplied within Javascript's floating-point representation without overflow +// or change in sign. +// +// In the algorithms below, we frequently reduce the negative case to the +// positive case by negating the input(s) and then post-processing the result. +// Note that we must ALWAYS check specially whether those values are MIN_VALUE +// (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as +// a positive number, it overflows back into a negative). Not handling this +// case would often result in infinite recursion. +// +// Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from* +// methods on which they depend. + +/** + * An indicator used to reliably determine if an object is a Long or not. + * @type {boolean} + * @const + * @private + */ +Long.prototype.__isLong__; + +Object.defineProperty(Long.prototype, "__isLong__", { value: true }); + +/** + * @function + * @param {*} obj Object + * @returns {boolean} + * @inner + */ +function isLong(obj) { + return (obj && obj["__isLong__"]) === true; +} + +/** + * Tests if the specified object is a Long. + * @function + * @param {*} obj Object + * @returns {boolean} + */ +Long.isLong = isLong; + +/** + * A cache of the Long representations of small integer values. + * @type {!Object} + * @inner + */ +var INT_CACHE = {}; + +/** + * A cache of the Long representations of small unsigned integer values. + * @type {!Object} + * @inner + */ +var UINT_CACHE = {}; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromInt(value, unsigned) { + var obj, cachedObj, cache; + if (unsigned) { + value >>>= 0; + if (cache = (0 <= value && value < 256)) { + cachedObj = UINT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); + if (cache) + UINT_CACHE[value] = obj; + return obj; + } else { + value |= 0; + if (cache = (-128 <= value && value < 128)) { + cachedObj = INT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, value < 0 ? -1 : 0, false); + if (cache) + INT_CACHE[value] = obj; + return obj; + } +} + +/** + * Returns a Long representing the given 32 bit integer value. + * @function + * @param {number} value The 32 bit integer in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromInt = fromInt; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromNumber(value, unsigned) { + if (isNaN(value)) + return unsigned ? UZERO : ZERO; + if (unsigned) { + if (value < 0) + return UZERO; + if (value >= TWO_PWR_64_DBL) + return MAX_UNSIGNED_VALUE; + } else { + if (value <= -TWO_PWR_63_DBL) + return MIN_VALUE; + if (value + 1 >= TWO_PWR_63_DBL) + return MAX_VALUE; + } + if (value < 0) + return fromNumber(-value, unsigned).neg(); + return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); +} + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * @function + * @param {number} value The number in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromNumber = fromNumber; + +/** + * @param {number} lowBits + * @param {number} highBits + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromBits(lowBits, highBits, unsigned) { + return new Long(lowBits, highBits, unsigned); +} + +/** + * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is + * assumed to use 32 bits. + * @function + * @param {number} lowBits The low 32 bits + * @param {number} highBits The high 32 bits + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromBits = fromBits; + +/** + * @function + * @param {number} base + * @param {number} exponent + * @returns {number} + * @inner + */ +var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4) + +/** + * @param {string} str + * @param {(boolean|number)=} unsigned + * @param {number=} radix + * @returns {!Long} + * @inner + */ +function fromString(str, unsigned, radix) { + if (str.length === 0) + throw Error('empty string'); + if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") + return ZERO; + if (typeof unsigned === 'number') { + // For goog.math.long compatibility + radix = unsigned, + unsigned = false; + } else { + unsigned = !! unsigned; + } + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + + var p; + if ((p = str.indexOf('-')) > 0) + throw Error('interior hyphen'); + else if (p === 0) { + return fromString(str.substring(1), unsigned, radix).neg(); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 8)); + + var result = ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i), + value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = fromNumber(pow_dbl(radix, size)); + result = result.mul(power).add(fromNumber(value)); + } else { + result = result.mul(radixToPower); + result = result.add(fromNumber(value)); + } + } + result.unsigned = unsigned; + return result; +} + +/** + * Returns a Long representation of the given string, written using the specified radix. + * @function + * @param {string} str The textual representation of the Long + * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to signed + * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 + * @returns {!Long} The corresponding Long value + */ +Long.fromString = fromString; + +/** + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromValue(val, unsigned) { + if (typeof val === 'number') + return fromNumber(val, unsigned); + if (typeof val === 'string') + return fromString(val, unsigned); + // Throws for non-objects, converts non-instanceof Long: + return fromBits(val.low, val.high, typeof unsigned === 'boolean' ? unsigned : val.unsigned); +} + +/** + * Converts the specified value to a Long using the appropriate from* function for its type. + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} + */ +Long.fromValue = fromValue; + +// NOTE: the compiler should inline these constant values below and then remove these variables, so there should be +// no runtime penalty for these. + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_16_DBL = 1 << 16; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_24_DBL = 1 << 24; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2; + +/** + * @type {!Long} + * @const + * @inner + */ +var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL); + +/** + * @type {!Long} + * @inner + */ +var ZERO = fromInt(0); + +/** + * Signed zero. + * @type {!Long} + */ +Long.ZERO = ZERO; + +/** + * @type {!Long} + * @inner + */ +var UZERO = fromInt(0, true); + +/** + * Unsigned zero. + * @type {!Long} + */ +Long.UZERO = UZERO; + +/** + * @type {!Long} + * @inner + */ +var ONE = fromInt(1); + +/** + * Signed one. + * @type {!Long} + */ +Long.ONE = ONE; + +/** + * @type {!Long} + * @inner + */ +var UONE = fromInt(1, true); + +/** + * Unsigned one. + * @type {!Long} + */ +Long.UONE = UONE; + +/** + * @type {!Long} + * @inner + */ +var NEG_ONE = fromInt(-1); + +/** + * Signed negative one. + * @type {!Long} + */ +Long.NEG_ONE = NEG_ONE; + +/** + * @type {!Long} + * @inner + */ +var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false); + +/** + * Maximum signed value. + * @type {!Long} + */ +Long.MAX_VALUE = MAX_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true); + +/** + * Maximum unsigned value. + * @type {!Long} + */ +Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MIN_VALUE = fromBits(0, 0x80000000|0, false); + +/** + * Minimum signed value. + * @type {!Long} + */ +Long.MIN_VALUE = MIN_VALUE; + +/** + * @alias Long.prototype + * @inner + */ +var LongPrototype = Long.prototype; + +/** + * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + * @returns {number} + */ +LongPrototype.toInt = function toInt() { + return this.unsigned ? this.low >>> 0 : this.low; +}; + +/** + * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + * @returns {number} + */ +LongPrototype.toNumber = function toNumber() { + if (this.unsigned) + return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0); + return this.high * TWO_PWR_32_DBL + (this.low >>> 0); +}; + +/** + * Converts the Long to a string written in the specified radix. + * @param {number=} radix Radix (2-36), defaults to 10 + * @returns {string} + * @override + * @throws {RangeError} If `radix` is out of range + */ +LongPrototype.toString = function toString(radix) { + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + if (this.isZero()) + return '0'; + if (this.isNegative()) { // Unsigned Longs are never negative + if (this.eq(MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = fromNumber(radix), + div = this.div(radixLong), + rem1 = div.mul(radixLong).sub(this); + return div.toString(radix) + rem1.toInt().toString(radix); + } else + return '-' + this.neg().toString(radix); + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), + rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower), + intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, + digits = intval.toString(radix); + rem = remDiv; + if (rem.isZero()) + return digits + result; + else { + while (digits.length < 6) + digits = '0' + digits; + result = '' + digits + result; + } + } +}; + +/** + * Gets the high 32 bits as a signed integer. + * @returns {number} Signed high bits + */ +LongPrototype.getHighBits = function getHighBits() { + return this.high; +}; + +/** + * Gets the high 32 bits as an unsigned integer. + * @returns {number} Unsigned high bits + */ +LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { + return this.high >>> 0; +}; + +/** + * Gets the low 32 bits as a signed integer. + * @returns {number} Signed low bits + */ +LongPrototype.getLowBits = function getLowBits() { + return this.low; +}; + +/** + * Gets the low 32 bits as an unsigned integer. + * @returns {number} Unsigned low bits + */ +LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { + return this.low >>> 0; +}; + +/** + * Gets the number of bits needed to represent the absolute value of this Long. + * @returns {number} + */ +LongPrototype.getNumBitsAbs = function getNumBitsAbs() { + if (this.isNegative()) // Unsigned Longs are never negative + return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs(); + var val = this.high != 0 ? this.high : this.low; + for (var bit = 31; bit > 0; bit--) + if ((val & (1 << bit)) != 0) + break; + return this.high != 0 ? bit + 33 : bit + 1; +}; + +/** + * Tests if this Long's value equals zero. + * @returns {boolean} + */ +LongPrototype.isZero = function isZero() { + return this.high === 0 && this.low === 0; +}; + +/** + * Tests if this Long's value equals zero. This is an alias of {@link Long#isZero}. + * @returns {boolean} + */ +LongPrototype.eqz = LongPrototype.isZero; + +/** + * Tests if this Long's value is negative. + * @returns {boolean} + */ +LongPrototype.isNegative = function isNegative() { + return !this.unsigned && this.high < 0; +}; + +/** + * Tests if this Long's value is positive. + * @returns {boolean} + */ +LongPrototype.isPositive = function isPositive() { + return this.unsigned || this.high >= 0; +}; + +/** + * Tests if this Long's value is odd. + * @returns {boolean} + */ +LongPrototype.isOdd = function isOdd() { + return (this.low & 1) === 1; +}; + +/** + * Tests if this Long's value is even. + * @returns {boolean} + */ +LongPrototype.isEven = function isEven() { + return (this.low & 1) === 0; +}; + +/** + * Tests if this Long's value equals the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.equals = function equals(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1) + return false; + return this.high === other.high && this.low === other.low; +}; + +/** + * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.eq = LongPrototype.equals; + +/** + * Tests if this Long's value differs from the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.notEquals = function notEquals(other) { + return !this.eq(/* validates */ other); +}; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.neq = LongPrototype.notEquals; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ne = LongPrototype.notEquals; + +/** + * Tests if this Long's value is less than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThan = function lessThan(other) { + return this.comp(/* validates */ other) < 0; +}; + +/** + * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lt = LongPrototype.lessThan; + +/** + * Tests if this Long's value is less than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { + return this.comp(/* validates */ other) <= 0; +}; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lte = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.le = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is greater than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThan = function greaterThan(other) { + return this.comp(/* validates */ other) > 0; +}; + +/** + * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gt = LongPrototype.greaterThan; + +/** + * Tests if this Long's value is greater than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { + return this.comp(/* validates */ other) >= 0; +}; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gte = LongPrototype.greaterThanOrEqual; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ge = LongPrototype.greaterThanOrEqual; + +/** + * Compares this Long's value with the specified's. + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.compare = function compare(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.eq(other)) + return 0; + var thisNeg = this.isNegative(), + otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) + return -1; + if (!thisNeg && otherNeg) + return 1; + // At this point the sign bits are the same + if (!this.unsigned) + return this.sub(other).isNegative() ? -1 : 1; + // Both are positive if at least one is unsigned + return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1; +}; + +/** + * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}. + * @function + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.comp = LongPrototype.compare; + +/** + * Negates this Long's value. + * @returns {!Long} Negated Long + */ +LongPrototype.negate = function negate() { + if (!this.unsigned && this.eq(MIN_VALUE)) + return MIN_VALUE; + return this.not().add(ONE); +}; + +/** + * Negates this Long's value. This is an alias of {@link Long#negate}. + * @function + * @returns {!Long} Negated Long + */ +LongPrototype.neg = LongPrototype.negate; + +/** + * Returns the sum of this and the specified Long. + * @param {!Long|number|string} addend Addend + * @returns {!Long} Sum + */ +LongPrototype.add = function add(addend) { + if (!isLong(addend)) + addend = fromValue(addend); + + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = addend.high >>> 16; + var b32 = addend.high & 0xFFFF; + var b16 = addend.low >>> 16; + var b00 = addend.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the difference of this and the specified Long. + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.subtract = function subtract(subtrahend) { + if (!isLong(subtrahend)) + subtrahend = fromValue(subtrahend); + return this.add(subtrahend.neg()); +}; + +/** + * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}. + * @function + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.sub = LongPrototype.subtract; + +/** + * Returns the product of this and the specified Long. + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.multiply = function multiply(multiplier) { + if (this.isZero()) + return this; + if (!isLong(multiplier)) + multiplier = fromValue(multiplier); + + // use wasm support if present + if (wasm) { + var low = wasm.mul(this.low, + this.high, + multiplier.low, + multiplier.high); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (multiplier.isZero()) + return (this.unsigned?UZERO:ZERO); + if (this.eq(MIN_VALUE)) + return multiplier.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO); + if (multiplier.eq(MIN_VALUE)) + return this.isOdd() ? MIN_VALUE : (this.unsigned?UZERO:ZERO); + + if (this.isNegative()) { + if (multiplier.isNegative()) + return this.neg().mul(multiplier.neg()); + else + return this.neg().mul(multiplier).neg(); + } else if (multiplier.isNegative()) + return this.mul(multiplier.neg()).neg(); + + // If both longs are small, use float multiplication + if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24)) + return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned); + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = multiplier.high >>> 16; + var b32 = multiplier.high & 0xFFFF; + var b16 = multiplier.low >>> 16; + var b00 = multiplier.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}. + * @function + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.mul = LongPrototype.multiply; + +/** + * Returns this Long divided by the specified. The result is signed if this Long is signed or + * unsigned if this Long is unsigned. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.divide = function divide(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + if (divisor.isZero()) + throw Error('division by zero'); + + // use wasm support if present + if (wasm) { + // guard against signed division overflow: the largest + // negative number / -1 would be 1 larger than the largest + // positive number, due to two's complement. + if (!this.unsigned && + this.high === -0x80000000 && + divisor.low === -1 && divisor.high === -1) { + // be consistent with non-wasm code path + return this; + } + var low = (this.unsigned ? wasm.div_u : wasm.div_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (this.isZero()) + return this.unsigned ? UZERO : ZERO; + var approx, rem, res; + if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. + if (this.eq(MIN_VALUE)) { + if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) + return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + else if (divisor.eq(MIN_VALUE)) + return ONE; + else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shr(1); + approx = halfThis.div(divisor).shl(1); + if (approx.eq(ZERO)) { + return divisor.isNegative() ? ONE : NEG_ONE; + } else { + rem = this.sub(divisor.mul(approx)); + res = approx.add(rem.div(divisor)); + return res; + } + } + } else if (divisor.eq(MIN_VALUE)) + return this.unsigned ? UZERO : ZERO; + if (this.isNegative()) { + if (divisor.isNegative()) + return this.neg().div(divisor.neg()); + return this.neg().div(divisor).neg(); + } else if (divisor.isNegative()) + return this.div(divisor.neg()).neg(); + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); + if (divisor.gt(this)) + return UZERO; + if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true + return UONE; + res = UZERO; + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + rem = this; + while (rem.gte(divisor)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2), + delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48), + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + approxRes = fromNumber(approx), + approxRem = approxRes.mul(divisor); + while (approxRem.isNegative() || approxRem.gt(rem)) { + approx -= delta; + approxRes = fromNumber(approx, this.unsigned); + approxRem = approxRes.mul(divisor); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) + approxRes = ONE; + + res = res.add(approxRes); + rem = rem.sub(approxRem); + } + return res; +}; + +/** + * Returns this Long divided by the specified. This is an alias of {@link Long#divide}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.div = LongPrototype.divide; + +/** + * Returns this Long modulo the specified. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.modulo = function modulo(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + + // use wasm support if present + if (wasm) { + var low = (this.unsigned ? wasm.rem_u : wasm.rem_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + return this.sub(this.div(divisor).mul(divisor)); +}; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.mod = LongPrototype.modulo; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.rem = LongPrototype.modulo; + +/** + * Returns the bitwise NOT of this Long. + * @returns {!Long} + */ +LongPrototype.not = function not() { + return fromBits(~this.low, ~this.high, this.unsigned); +}; + +/** + * Returns the bitwise AND of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.and = function and(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low & other.low, this.high & other.high, this.unsigned); +}; + +/** + * Returns the bitwise OR of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.or = function or(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low | other.low, this.high | other.high, this.unsigned); +}; + +/** + * Returns the bitwise XOR of this Long and the given one. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.xor = function xor(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftLeft = function shiftLeft(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned); + else + return fromBits(0, this.low << (numBits - 32), this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shl = LongPrototype.shiftLeft; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRight = function shiftRight(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned); + else + return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned); +}; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr = LongPrototype.shiftRight; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + numBits &= 63; + if (numBits === 0) + return this; + else { + var high = this.high; + if (numBits < 32) { + var low = this.low; + return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned); + } else if (numBits === 32) + return fromBits(high, 0, this.unsigned); + else + return fromBits(high >>> (numBits - 32), 0, this.unsigned); + } +}; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shru = LongPrototype.shiftRightUnsigned; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr_u = LongPrototype.shiftRightUnsigned; + +/** + * Converts this Long to signed. + * @returns {!Long} Signed long + */ +LongPrototype.toSigned = function toSigned() { + if (!this.unsigned) + return this; + return fromBits(this.low, this.high, false); +}; + +/** + * Converts this Long to unsigned. + * @returns {!Long} Unsigned long + */ +LongPrototype.toUnsigned = function toUnsigned() { + if (this.unsigned) + return this; + return fromBits(this.low, this.high, true); +}; + +/** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ +LongPrototype.toBytes = function toBytes(le) { + return le ? this.toBytesLE() : this.toBytesBE(); +}; + +/** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ +LongPrototype.toBytesLE = function toBytesLE() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + lo >>> 8 & 0xff, + lo >>> 16 & 0xff, + lo >>> 24 , + hi & 0xff, + hi >>> 8 & 0xff, + hi >>> 16 & 0xff, + hi >>> 24 + ]; +}; + +/** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ +LongPrototype.toBytesBE = function toBytesBE() { + var hi = this.high, + lo = this.low; + return [ + hi >>> 24 , + hi >>> 16 & 0xff, + hi >>> 8 & 0xff, + hi & 0xff, + lo >>> 24 , + lo >>> 16 & 0xff, + lo >>> 8 & 0xff, + lo & 0xff + ]; +}; + +/** + * Creates a Long from its byte representation. + * @param {!Array.} bytes Byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {Long} The corresponding Long value + */ +Long.fromBytes = function fromBytes(bytes, unsigned, le) { + return le ? Long.fromBytesLE(bytes, unsigned) : Long.fromBytesBE(bytes, unsigned); +}; + +/** + * Creates a Long from its little endian byte representation. + * @param {!Array.} bytes Little endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesLE = function fromBytesLE(bytes, unsigned) { + return new Long( + bytes[0] | + bytes[1] << 8 | + bytes[2] << 16 | + bytes[3] << 24, + bytes[4] | + bytes[5] << 8 | + bytes[6] << 16 | + bytes[7] << 24, + unsigned + ); +}; + +/** + * Creates a Long from its big endian byte representation. + * @param {!Array.} bytes Big endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesBE = function fromBytesBE(bytes, unsigned) { + return new Long( + bytes[4] << 24 | + bytes[5] << 16 | + bytes[6] << 8 | + bytes[7], + bytes[0] << 24 | + bytes[1] << 16 | + bytes[2] << 8 | + bytes[3], + unsigned + ); +}; diff --git a/MAVLinkDashboard/MAVLink/mavlink.js b/MAVLinkDashboard/MAVLink/mavlink.js new file mode 100644 index 00000000..5e3e2824 --- /dev/null +++ b/MAVLinkDashboard/MAVLink/mavlink.js @@ -0,0 +1,18848 @@ +/* +MAVLink protocol implementation for node.js (auto-generated by mavgen_javascript.py) + +Generated from: all.xml,ardupilotmega.xml,ASLUAV.xml,common.xml,development.xml,icarous.xml,minimal.xml,python_array_test.xml,standard.xml,test.xml,ualberta.xml,uAvionix.xml,loweheiser.xml,storm32.xml,AVSSUAS.xml,cubepilot.xml,csAirLink.xml + +Note: this file has been auto-generated. DO NOT EDIT +*/ + +var jspack +import("./local_modules/jspack/jspack.js").then((mod) => { + jspack = new mod.default() +}).catch((e) => { + // Discard annoying error. + // This fails in the sandbox because of CORS + // Only really want the message definitions in that case, not the parser, so its OK. +}) + +mavlink20 = function(){}; + +// Implement the CRC-16/MCRF4XX function (present in the Python version through the mavutil.py package) +mavlink20.x25Crc = function(buffer, crcIN) { + + var bytes = buffer; + var crcOUT = crcIN === undefined ? 0xffff : crcIN; + bytes.forEach(function(e) { + var tmp = e ^ (crcOUT & 0xff); + tmp = (tmp ^ (tmp << 4)) & 0xff; + crcOUT = (crcOUT >> 8) ^ (tmp << 8) ^ (tmp << 3) ^ (tmp >> 4); + crcOUT = crcOUT & 0xffff; + }); + return crcOUT; + +} + +mavlink20.WIRE_PROTOCOL_VERSION = "2.0"; +mavlink20.PROTOCOL_MARKER_V1 = 0xFE +mavlink20.PROTOCOL_MARKER_V2 = 0xFD +mavlink20.HEADER_LEN_V1 = 6 +mavlink20.HEADER_LEN_V2 = 10 +mavlink20.HEADER_LEN = 10; + +mavlink20.MAVLINK_TYPE_CHAR = 0 +mavlink20.MAVLINK_TYPE_UINT8_T = 1 +mavlink20.MAVLINK_TYPE_INT8_T = 2 +mavlink20.MAVLINK_TYPE_UINT16_T = 3 +mavlink20.MAVLINK_TYPE_INT16_T = 4 +mavlink20.MAVLINK_TYPE_UINT32_T = 5 +mavlink20.MAVLINK_TYPE_INT32_T = 6 +mavlink20.MAVLINK_TYPE_UINT64_T = 7 +mavlink20.MAVLINK_TYPE_INT64_T = 8 +mavlink20.MAVLINK_TYPE_FLOAT = 9 +mavlink20.MAVLINK_TYPE_DOUBLE = 10 + +mavlink20.MAVLINK_IFLAG_SIGNED = 0x01 +mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN = 13 + +// Mavlink headers incorporate sequence, source system (platform) and source component. +mavlink20.header = function(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags=0, compat_flags=0,) { + + this.mlen = ( typeof mlen === 'undefined' ) ? 0 : mlen; + this.seq = ( typeof seq === 'undefined' ) ? 0 : seq; + this.srcSystem = ( typeof srcSystem === 'undefined' ) ? 0 : srcSystem; + this.srcComponent = ( typeof srcComponent === 'undefined' ) ? 0 : srcComponent; + this.msgId = msgId + this.incompat_flags = incompat_flags + this.compat_flags = compat_flags + +} +mavlink20.header.prototype.pack = function() { + return jspack.Pack('BBBBBBBHB', [253, this.mlen, this.incompat_flags, this.compat_flags, this.seq, this.srcSystem, this.srcComponent, ((this.msgId & 0xFF) << 8) | ((this.msgId >> 8) & 0xFF), this.msgId>>16]); +} + +// Base class declaration: mavlink.message will be the parent class for each +// concrete implementation in mavlink.messages. +mavlink20.message = function() {}; + +// Convenience setter to facilitate turning the unpacked array of data into member properties +mavlink20.message.prototype.set = function(args,verbose) { + // inspect + this.fieldnames.forEach(function(e, i) { + var num = parseInt(i,10); + if (this.hasOwnProperty(e) && isNaN(num) ){ // asking for an attribute that's non-numeric is ok unless its already an attribute we have + if ( verbose >= 1) { console.log("WARNING, overwriting an existing property is DANGEROUS:"+e+" ==>"+i+"==>"+args[i]+" -> "+JSON.stringify(this)); } + } + }, this); + + // then modify + this.fieldnames.forEach(function(e, i) { + this[e] = args[i]; + }, this); +}; + +// trying to be the same-ish as the python function of the same name +mavlink20.message.prototype.sign_packet = function( mav) { + var crypto= require('crypto'); + var h = crypto.createHash('sha256'); + + //mav.signing.timestamp is a 48bit number, or 6 bytes. + + // due to js not being able to shift numbers more than 32, we'll use this instead.. + // js stores all its numbers as a 64bit float with 53 bits of mantissa, so have room for 48 ok. + // positive shifts left, negative shifts right + function shift(number, shift) { + return number * Math.pow(2, shift); + } + + var thigh = shift(mav.signing.timestamp,-32) // 2 bytes from the top, shifted right by 32 bits + var tlow = (mav.signing.timestamp & 0xfffffff ) // 4 bytes from the bottom + + // I means unsigned 4bytes, H means unsigned 2 bytes + // first add the linkid(1 byte) and timestamp(6 bytes) that start the signature + this._msgbuf = this._msgbuf.concat(jspack.Pack(' 1) && ( (this._payload[plen-1] == 0) || (this._payload[plen-1] == null) ) ) { + plen = plen - 1; + } + this._payload = this._payload.slice(0, plen); + // signing is our first incompat flag. + var incompat_flags = 0; + if (mav.signing.sign_outgoing){ + incompat_flags |= mavlink20.MAVLINK_IFLAG_SIGNED + } + // header + this._header = new mavlink20.header(this._id, this._payload.length, mav.seq, mav.srcSystem, mav.srcComponent, incompat_flags, 0,); + // payload + this._msgbuf = this._header.pack().concat(this._payload); + // crc - for now, assume always using crc_extra = True. TODO: check/fix this. + var crc = mavlink20.x25Crc(this._msgbuf.slice(1)); + crc = mavlink20.x25Crc([crc_extra], crc); + this._msgbuf = this._msgbuf.concat(jspack.Pack(' MAV. Also used to +return a point from MAV -> GCS. + + target_system : System ID. (uint8_t) + target_component : Component ID. (uint8_t) + idx : Point index (first point is 1, 0 is for return point). (uint8_t) + count : Total number of points (for sanity checking). (uint8_t) + lat : Latitude of point. (float) + lng : Longitude of point. (float) + +*/ + mavlink20.messages.fence_point = function( ...moreargs ) { + [ this.target_system , this.target_component , this.idx , this.count , this.lat , this.lng ] = moreargs; + + this._format = ' MAV. Also used to +return a point from MAV -> GCS. + + target_system : System ID. (uint8_t) + target_component : Component ID. (uint8_t) + idx : Point index (first point is 0). (uint8_t) + count : Total number of points (for sanity checking). (uint8_t) + lat : Latitude of point. (int32_t) + lng : Longitude of point. (int32_t) + alt : Transit / loiter altitude relative to home. (int16_t) + break_alt : Break altitude relative to home. (int16_t) + land_dir : Heading to aim for when landing. (uint16_t) + flags : Configuration flags. (uint8_t) + +*/ + mavlink20.messages.rally_point = function( ...moreargs ) { + [ this.target_system , this.target_component , this.idx , this.count , this.lat , this.lng , this.alt , this.break_alt , this.land_dir , this.flags ] = moreargs; + + this._format = ' value[float]. +This allows to send a parameter to any other component (such as the +GCS) without the need of previous knowledge of possible parameter +names. Thus the same GCS can store different parameters for different +autopilots. See also https://mavlink.io/en/services/parameter.html for +a full documentation of QGroundControl and IMU code. + + target_system : System ID (uint8_t) + target_component : Component ID (uint8_t) + param_id : Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars - applications have to provide 16+1 bytes storage if the ID is stored as string (char) + param_index : Parameter index. Send -1 to use the param ID field as identifier (else the param id will be ignored) (int16_t) + +*/ + mavlink20.messages.param_request_read = function( ...moreargs ) { + [ this.target_system , this.target_component , this.param_id , this.param_index ] = moreargs; + + this._format = ' 0 indicates the interval at which it is sent. (int32_t) + +*/ + mavlink20.messages.message_interval = function( ...moreargs ) { + [ this.message_id , this.interval_us ] = moreargs; + + this._format = '= 1 && this.buf[0] != this.protocol_marker ) { + + // Strip the offending initial byte and throw an error. + var badPrefix = this.buf[0]; + this.bufInError = this.buf.slice(0,1); + this.buf = this.buf.slice(1); + this.expected_length = mavlink20.HEADER_LEN; //initially we 'expect' at least the length of the header, later parseLength corrects for this. + throw new Error("Bad prefix ("+badPrefix+")"); + } + +} + +// Determine the length. Leaves buffer untouched. +// Although the 'len' of a packet is available as of the second byte, the third byte with 'incompat_flags' lets +// us know if we have signing enabled, which affects the real-world length by the signature-block length of 13 bytes. +// once successful, 'this.expected_length' is correctly set for the whole packet. +MAVLink20Processor.prototype.parseLength = function() { + + if( this.buf.length >= 3 ) { + var unpacked = jspack.Unpack('BBB', this.buf.slice(0, 3)); + var magic = unpacked[0]; // stx ie fd or fe etc + this.expected_length = unpacked[1] + mavlink20.HEADER_LEN + 2 // length of message + header + CRC (ie non-signed length) + this.incompat_flags = unpacked[2]; + // mavlink2 only.. in mavlink1, incompat_flags var above is actually the 'seq', but for this test its ok. + if ((magic == mavlink20.PROTOCOL_MARKER_V2 ) && ( this.incompat_flags & mavlink20.MAVLINK_IFLAG_SIGNED )){ + this.expected_length += mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN; + } + } + +} + +// input some data bytes, possibly returning a new message - python equiv function is called parse_char / __parse_char_legacy +MAVLink20Processor.prototype.parseChar = function(c) { + + if (c == null) { + return + } + + var m = null; + + try { + this.pushBuffer(c); + this.parsePrefix(); + this.parseLength(); + m = this.parsePayload(); + + } catch(e) { + this.log('error', e.message); + this.total_receive_errors += 1; + m = new mavlink20.messages.bad_data(this.bufInError, e.message); + this.bufInError = new Uint8Array(); + + } + + // emit a packet-specific message as well as a generic message, user/s can choose to use either or both of these. + //if(null != m) { + // this.emit(m._name, m); + // this.emit('message', m); + //} + + return m; + +} + +// continuation of python's __parse_char_legacy +MAVLink20Processor.prototype.parsePayload = function() { + + var m = null; + + // tip: this.expected_length and this.incompat_flags both already set correctly by parseLength(..) above + + // If we have enough bytes to try and read it, read it. + // shortest packet is header+checksum(2) with no payload, so we need at least that many + // but once we have a longer 'expected length' we have to read all of it. + if(( this.expected_length >= mavlink20.HEADER_LEN+2) && (this.buf.length >= this.expected_length) ) { + + // Slice off the expected packet length, reset expectation to be to find a header. + var mbuf = this.buf.slice(0, this.expected_length); + + // TODO: slicing off the buffer should depend on the error produced by the decode() function + // - if we find a well formed message, cut-off the expected_length + // - if the message is not well formed (correct prefix by accident), cut-off 1 char only + this.buf = this.buf.slice(this.expected_length); + this.expected_length = mavlink20.HEADER_LEN; // after attempting a parse, we'll next expect to find just a header. + + try { + m = this.decode(mbuf); + this.total_packets_received += 1; + } + catch(e) { + // Set buffer in question and re-throw to generic error handling + this.bufInError = mbuf; + throw e; + } + } + + return m; + +} + +// input some data bytes, possibly returning an array of new messages +MAVLink20Processor.prototype.parseBuffer = function(s) { + + // Get a message, if one is available in the stream. + var m = this.parseChar(s); + + // No messages available, bail. + if ( null === m ) { + return null; + } + + // While more valid messages can be read from the existing buffer, add + // them to the array of new messages and return them. + var ret = [m]; + while(true) { + m = this.parseChar(); + if ( null === m ) { + // No more messages left. + return ret; + } + ret.push(m); + } + +} + +//check signature on incoming message , many of the comments in this file come from the python impl +MAVLink20Processor.prototype.check_signature = function(msgbuf, srcSystem, srcComponent) { + + //timestamp_buf = msgbuf[-12:-6] + var timestamp_buf= msgbuf.slice(-12,-6); + + //link_id = msgbuf[-13] + var link_id= new Buffer.from(msgbuf.slice(-13,-12)); // just a single byte really, but returned as a buffer + link_id = link_id[0]; // get the first byte. + + //self.mav_sign_unpacker = jspack.Unpack('> 8) & 0xFF); // first-two msgid bytes + var msgIDhigh = unpacked[8]; // the 3rd msgid byte + msgId = msgIDlow | (msgIDhigh<<16); // combined result. 0 - 16777215 24bit number + } + catch(e) { + throw new Error('Unable to unpack MAVLink header: ' + e.message); + } + + // TODO allow full parsing of 1.0 inside the 2.0 parser, this is just a start + if (magic == mavlink20.PROTOCOL_MARKER_V1){ + //headerlen = 6; + + // these two are in the same place in both v1 and v2 so no change needed: + //magic = magic; + //mlen = mlen; + + // grab mavlink-v1 header position info from v2 unpacked position + seq1 = incompat_flags; + srcSystem1 = compat_flags; + srcComponent1 = seq; + msgId1 = srcSystem; + // override the v1 vs v2 offsets so we get the correct data either way... + seq = seq1; + srcSystem = srcSystem1; + srcComponent = srcComponent1; + msgId = msgId1; + // don't exist in mavlink1, so zero-them + incompat_flags = 0; + compat_flags = 0; + signature_len = 0; + // todo add more v1 here and don't just return + return; + } + + if (magic != this.protocol_marker) { + throw new Error("Invalid MAVLink prefix ("+magic+")"); + } + + // is packet supposed to be signed? + if ( incompat_flags & mavlink20.MAVLINK_IFLAG_SIGNED ){ + signature_len = mavlink20.MAVLINK_SIGNATURE_BLOCK_LEN; + } else { + signature_len = 0; + } + + // header's declared len compared to packets actual len + var actual_len = (msgbuf.length - (mavlink20.HEADER_LEN + 2 + signature_len)); + var actual_len_nosign = (msgbuf.length - (mavlink20.HEADER_LEN + 2 )); + + if ((mlen == actual_len) && (signature_len > 0)){ + var len_if_signed = mlen+signature_len; + //console.log("Packet appears signed && labeled as signed, OK. msgId=" + msgId); + + } else if ((mlen == actual_len_nosign) && (signature_len > 0)){ + + var len_if_signed = mlen+signature_len; + throw new Error("Packet appears unsigned when labeled as signed. Got actual_len "+actual_len_nosign+" expected " + len_if_signed + ", msgId=" + msgId); + + } else if( mlen != actual_len) { + throw new Error("Invalid MAVLink message length. Got " + (msgbuf.length - (mavlink20.HEADER_LEN + 2)) + " expected " + mlen + ", msgId=" + msgId); + + } + + if (!(msgId in mavlink20.map)) { + throw new Error("Unknown MAVLink message ID (" + msgId + ")"); + } + + // here's the common chunks of packet we want to work with below.. + var headerBuf= msgbuf.slice(mavlink20.HEADER_LEN); // first10 + var sigBuf = msgbuf.slice(-signature_len); // last 13 or nothing + var crcBuf1 = msgbuf.slice(-2); // either last-2 or last-2-prior-to-signature + var crcBuf2 = msgbuf.slice(-15,-13); // either last-2 or last-2-prior-to-signature + var payloadBuf = msgbuf.slice(mavlink20.HEADER_LEN, -(signature_len+2)); // the remaining bit between the header and the crc + var crcCheckBuf = msgbuf.slice(1, -(signature_len+2)); // the part uses to calculate the crc - ie between the magic and signature, + + // decode the payload + // refs: (fmt, type, order_map, crc_extra) = mavlink20.map[msgId] + var decoder = mavlink20.map[msgId]; + + // decode the checksum + var receivedChecksum = undefined; + if ( signature_len == 0 ) { // unsigned + try { + receivedChecksum = jspack.Unpack(' payloadBuf.length) { + payloadBuf = this.concat_buffer(payloadBuf, new Uint8Array(paylen - payloadBuf.length).fill(0)); + } + // Decode the payload and reorder the fields to match the order map. + try { + var t = jspack.Unpack(decoder.format, payloadBuf); + } + catch (e) { + throw new Error('Unable to unpack MAVLink payload type='+decoder.type+' format='+decoder.format+' payloadLength='+ payloadBuf +': '+ e.message); + } + + // Need to check if the message contains arrays + var args = {}; + const elementsInMsg = decoder.order_map.length; + const actualElementsInMsg = JSON.parse(JSON.stringify(t)).length; + + if (elementsInMsg == actualElementsInMsg) { + // Reorder the fields to match the order map + t.forEach(function(e, i, l) { + args[i] = t[decoder.order_map[i]] + }); + } else { + // This message contains arrays + var typeIndex = 1; + var orderIndex = 0; + var memberIndex = 0; + var tempArgs = {}; + + // Walk through the fields + for(var i = 0, size = decoder.format.length-1; i <= size; ++i) { + var order = decoder.order_map[orderIndex]; + var currentType = decoder.format[typeIndex]; + + if (isNaN(parseInt(currentType))) { + // This field is not an array check the type and add it to the args + tempArgs[orderIndex] = t[memberIndex]; + memberIndex++; + } else { + // This field is part of an array, need to find the length of the array + var arraySize = '' + var newArray = [] + while (!isNaN(decoder.format[typeIndex])) { + arraySize = arraySize + decoder.format[typeIndex]; + typeIndex++; + } + + // Now that we know how long the array is, create an array with the values + for(var j = 0, size = parseInt(arraySize); j < size; ++j){ + newArray.push(t[j+orderIndex]); + memberIndex++; + } + + // Add the array to the args object + arraySize = arraySize + decoder.format[typeIndex]; + currentType = arraySize; + tempArgs[orderIndex] = newArray; + } + orderIndex++; + typeIndex++; + } + + // Finally reorder the fields to match the order map + t.forEach(function(e, i, l) { + args[i] = tempArgs[decoder.order_map[i]] + }); + } + + // construct the message object + try { + // args at this point might look like: { '0': 6, '1': 8, '2': 0, '3': 0, '4': 3, '5': 3 } + var m = new decoder.type; // make a new 'empty' instance of the right class, + m.set(args,false); // associate ordered-field-numbers to names, after construction not during. + } + catch (e) { + throw new Error('Unable to instantiate MAVLink message of type '+decoder.type+' : ' + e.message); + } + + m._signed = sig_ok; + if (m._signed) { m._link_id = msgbuf[-13]; } + + m._msgbuf = msgbuf; + m._payload = payloadBuf + m.crc = receivedChecksum; + m._header = new mavlink20.header(msgId, mlen, seq, srcSystem, srcComponent, incompat_flags, compat_flags); + this.log(m); + return m; +} + + +// allow loading as both common.js (Node), and/or vanilla javascript in-browser +if(typeof module === "object" && module.exports) { + module.exports = {mavlink20, MAVLink20Processor}; +} + diff --git a/MAVLinkDashboard/MAVLinkDashboard.js b/MAVLinkDashboard/MAVLinkDashboard.js new file mode 100644 index 00000000..1105d9fc --- /dev/null +++ b/MAVLinkDashboard/MAVLinkDashboard.js @@ -0,0 +1,571 @@ +// Setup connect button in menu widget, this handles WebSocket and incoming MAVLink +function setup_connect(button_svg, button_color) { + + const tip_div = document.createElement("div") + tip_div.appendChild(document.importNode(document.getElementById('connection_tip_template').content, true)) + const tip = tippy(button_svg, { + content: tip_div, + interactive: true, + trigger: 'manual', + maxWidth: "1000px", + appendTo: () => document.body, + popperOptions: { + strategy: 'fixed', + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom', 'right'], + }, + }, + { + name: 'preventOverflow', + options: { + altAxis: true, + tether: false, + }, + }, + ], + }, + }) + button_svg.onclick = () => { tip.show() } + + // Close button + tip_div.querySelector(`svg[id="Close"]`).onclick = () => { + tip.hide() + } + + const url_input = tip_div.querySelector(`input[id="target_url"]`) + + const connect_button = tip_div.querySelector(`input[id="connection_button"]`) + const disconnect_button = tip_div.querySelector(`input[id="disconnection_button"]`) + + // Websocket object + let ws = null + let expecting_close = false + + function set_inputs(connected) { + // Disable connect button and url input, enable disconnect button + connect_button.disabled = connected + url_input.disabled = connected + + disconnect_button.disabled = !connected + } + set_inputs(false) + + // Connect to WebSocket server + function connect(target) { + // Make sure we are not connected to something else + disconnect() + + // Can't connect twice + set_inputs(true) + + // Set orange for connecting + button_color("orange") + + ws = new WebSocket(target) + ws.binaryType = "arraybuffer" + + expecting_close = false + + ws.onopen = () => { + button_color("green") + + // Hide tip + tip.hide() + + // Allow disconnect + disconnect_button.disabled = false + } + + ws.onclose = () => { + if (!expecting_close) { + button_color("red") + } + + // Enable connect buttons + set_inputs(false) + } + + ws.onerror = (e) => { + console.log(e) + ws.close() + } + + ws.onmessage = (msg) => { + // Feed data to MAVLink parser and forward messages + for (const char of new Uint8Array(msg.data)) { + const m = MAVLink.parseChar(char) + if (m != null) { + // Sent to each Widget + for (const widget of grid.getGridItems()) { + widget.MAVLink_msg_handler(m) + } + for (const widget of test_grid.getGridItems()) { + widget.MAVLink_msg_handler(m) + } + } + } + } + + } + + // Disconnect from WebSocket server + function disconnect() { + // Close socket + if (ws != null) { + expecting_close = true + ws.close() + } + + // Return button to black + button_color("black") + url_input.disabled = false + + // Enable connect buttons + set_inputs(false) + } + + + connect_button.onclick = () => { + const in_progress = (ws != null) && ((ws.readyState == WebSocket.CONNECTING) || (ws.readyState == WebSocket.CLOSING)) + if (in_progress) { + // Don't do anything if the socket is connecting or closing a connection + return + } + + if (!url_input.checkValidity()) { + // Invalid address, re-fire the tip and focus the url + tip.show() + url_input.focus() + return + } + + url_input.disabled = true + connect(url_input.value) + } + + disconnect_button.onclick = () => { + + if ((ws != null) && (ws.readyState == WebSocket.CLOSING)) { + // Don't do anything if the socket is already or closing a connection + return + } + + disconnect() + } + +} + +// Get the details of the passed in widget for copy or save +function get_widget_object(widget) { + return { + x: widget.getAttribute("gs-x"), + y: widget.getAttribute("gs-y"), + w: widget.getAttribute("gs-w"), + h: widget.getAttribute("gs-h"), + type: widget.constructor.name, + options: widget.get_options() + } +} + +// Get array of widgets from the target grid for saving +function get_widgets(target_grid) { + const save_widgets = {} + + const widgets = target_grid.getGridItems() + for (let i = 0; i { + return res.json() + }).then((obj) => { + load_layout(obj.grid, obj.widgets) + }) + +} + +function new_widget(type, options) { + + switch (type) { + case "WidgetMenu": + return new WidgetMenu(options) + + case "WidgetSandBox": + return new WidgetSandBox(options) + + case "WidgetSubGrid": + return new WidgetSubGrid(options) + } + + throw new Error("Unknown widget type: " + type) +} + +// Add a widget checking if it will fit +function add_widget(target_grid, obj) { + + const pos_opts = { + x: (obj.x == null) ? null : parseInt(obj.x), + y: (obj.y == null) ? null : parseInt(obj.y), + w: (obj.w == null) ? null : parseInt(obj.w), + h: (obj.h == null) ? null : parseInt(obj.h), + autoPosition: false + } + + // See if there is in the closest position + if (!target_grid.willItFit(pos_opts)) { + // See if it would fit with auto-position + pos_opts.autoPosition = true + if (!target_grid.willItFit(pos_opts)) { + alert("Widget won't fit on Grid") + return + } + } + + let widget = new_widget(obj.type, obj.options) + + target_grid.addWidget(widget, pos_opts) + + const editable = !((target_grid.opts.disableDrag === true) && (target_grid.opts.disableResize === true)) + widget.set_edit(editable) + + return widget +} + +// Load widget object to target grid +function load_widgets(target_grid, widgets) { + + target_grid.batchUpdate(true) + + for (const widget of Object.values(widgets)) { + add_widget(target_grid, widget) + } + + target_grid.batchUpdate(false) + + // Call init on each widget after grid has updated + for (const widget of target_grid.getGridItems()) { + widget.init() + } +} + +function load_layout(grid_layout, widgets) { + + try { + // Set background color + const dashboard_div = document.getElementById("dashboard") + dashboard_div.style.backgroundColor = grid_layout.color + + // Reload grid + init_grid(parseInt(grid_layout.columns), parseInt(grid_layout.rows)) + + load_widgets(grid, widgets) + + } catch (error) { + load_default_grid() + + alert('Grid load failed\n' + error.message) + } + +} + +async function load_file(e) { + + const file = e.files[0] + if (file == null) { + return + } + + let reader = new FileReader() + reader.onload = function (e) { + const obj = JSON.parse(reader.result) + if ("widgets" in obj) { + load_layout(obj.grid, obj.widgets) + + } else if ("widget" in obj) { + const widget = add_widget(grid, obj.widget) + if (widget != null) { + widget.init() + } + + } else { + alert("Unable to load from: " + file) + } + + } + reader.readAsText(file) + +} + +// Pallet for user to add widgets +function init_pallet() { + + const dashboard = document.getElementById("dashboard") + + const tip_div = document.createElement("div") + tip_div.style.width = "600px" + tip_div.style.height = "400px" + tip_div.style.padding = "10px" + tip_div.style.borderRadius = "10px" + + const grid_div = document.createElement("div") + grid_div.classList.add("grid-stack-item-content") + grid_div.style.overflow = "auto" + tip_div.appendChild(grid_div) + + // Pallet grid object + let palette + + // True is the tip is currently being shown + let tip_shown = false + + // Add grid at run time + function tippy_mount(instance) { + + const columns = 6 + const rows = 4 + + palette = GridStack.init({ + float: true, + disableOneColumnMode: true, + column: columns, + row: rows, + cellHeight: (100 / rows) + "%", + disableResize: true, + }, grid_div) + + palette.batchUpdate(true) + + // Add pure JS widgets + const subgrid = add_widget(palette, { type: "WidgetSubGrid" }) + const sandbox = add_widget(palette, { type: "WidgetSandBox" }) + + // Load in json definitions + const sandbox_files = [ + "SandBoxWidgets/Attitude.json", + "SandBoxWidgets/Graph.json", + "SandBoxWidgets/MAVLink_Inspector.json", + "SandBoxWidgets/Messages.json", + "SandBoxWidgets/Value.json", + ] + + let import_done = [] + + // Add widget for each file + for (const file of sandbox_files) { + import_done.push( + new Promise((resolve, reject) => { + fetch(file).then((res) => { + return res.json() + }).then((obj) => { + add_widget(palette, obj.widget) + resolve() + }) + }) + ) + } + + // Wait for all files to load + Promise.allSettled(import_done).then(() => { + palette.batchUpdate(false) + + // Call init on each widget after grid has updated + for (const widget of palette.getGridItems()) { + widget.init() + } + + // Add tip to each widget to give more information + for (const widget of palette.getGridItems()) { + const about = widget.get_about() + + const widget_tip_div = document.createElement("div") + + const heading = document.createElement("h6") + heading.innerText = about.name + widget_tip_div.appendChild(heading) + + if ("info" in about) { + widget_tip_div.appendChild(document.createTextNode(about.info)) + } + + const tip = tippy(widget, { + content: widget_tip_div, + appendTo: () => document.body, + theme: 'light-border', // differentiate from the interactive tip were in already + }) + + } + }) + + palette.on('removed', () => { + // Hide tip once item has been removed + // when the user re-triggers grid is re-generated replacing the removed item + instance.hide() + tip_shown = false + }) + } + + // Clear grid on tip hide + function tippy_hidden(instance) { + clear_grid(palette) + palette = null + } + + const tip = tippy(dashboard, { + content: tip_div, + interactive: true, + trigger: 'manual', + maxWidth: "1000px", + followCursor: "initial", + appendTo: () => document.body, + onMount: tippy_mount, + onHidden: tippy_hidden, + arrow: false + }) + + // Clicks toggle tip + dashboard.onclick = (e) => { + if (e.target != e.currentTarget ) { + // Only trigger on direct clicks + // Reset toggle to allow clicking off menu + tip_shown = true + return + } + + const edit_enabled = !((grid.opts.disableDrag === true) && (grid.opts.disableResize === true)) + if (!edit_enabled) { + // only trigger if editing of the base grid is enabled + return + } + + if (!tip_shown) { + if (palette != null) { + // Don't allow rapid triggers + // Old palette must be cleared before new one is added + // Note toggle is not updated + return + } + tip.show() + } + tip_shown = !tip_shown + } + +} + +// Called when a widget is dropped, sub grids don't like being moved for some reason. +// The fix seems to be to delete and re-create them. +function widget_dropped(event, previousWidget, newWidget) { + + // Copy + const obj = get_widget_object(newWidget.el) + const target = newWidget.grid + + // Remove ordinal + newWidget.el.destroy() + target.removeWidget(newWidget.el) + + // Add copy + const copy = add_widget(target, obj) + if (copy != null) { + copy.init() + } + +} + diff --git a/MAVLinkDashboard/Readme.md b/MAVLinkDashboard/Readme.md new file mode 100644 index 00000000..d9ec9f13 --- /dev/null +++ b/MAVLinkDashboard/Readme.md @@ -0,0 +1,7 @@ +## MAVLink Dashboard + +This is a display only tool to help visualizing incoming MAVLink telemetry data. + +This is not a GCS! It should be used in addition to a GCS. + +Focus is on flexibility and user customization. diff --git a/MAVLinkDashboard/SandBoxWidgets/Attitude.json b/MAVLinkDashboard/SandBoxWidgets/Attitude.json new file mode 100644 index 00000000..f08a9d8b --- /dev/null +++ b/MAVLinkDashboard/SandBoxWidgets/Attitude.json @@ -0,0 +1,23 @@ +{ + "header": { + "version": 1 + }, + "widget": { + "x": "3", + "y": "0", + "w": "2", + "h": "2", + "type": "WidgetSandBox", + "options": { + "form": { + "components": [] + }, + "form_content": {}, + "sandbox": "\n// Import Gauges from https://github.com/teocci/js-module-flight-indicators\n// Add ccs with link tag\nconst ccs = document.createElement('link')\nccs.rel = \"stylesheet\"\nccs.href = \"https://unpkg.com/flight-indicators-js@1.0.5/css/flight-indicators.css\"\ndocument.body.appendChild(ccs)\n\nlet attitude\nimport(\"https://unpkg.com/flight-indicators-js@1.0.5/esm/module-flight-indicators.mjs\").then((mod) => {\n const FlightIndicators = mod.default\n\n attitude = new FlightIndicators(\n div,\n FlightIndicators.TYPE_ATTITUDE\n )\n\n // This is a dirty hack to switch to remote copy's of images\n let images = div.querySelectorAll(\"img\")\n for (const image of images) {\n let src = image.src\n\n // Hide box is broken, hide manually\n // see: https://github.com/teocci/js-module-flight-indicators/pull/1\n if (src.endsWith(\"fi_box.svg\")) {\n image.style.display = \"none\"\n continue\n }\n\n var lastIndex = src.lastIndexOf(\"/img/\")\n image.src = \"https://unpkg.com/flight-indicators-js@1.0.5\" + src.substr(lastIndex)\n }\n\n resize()\n})\n\n// Remove margin and border to give more room\ndiv.style.margin = 0\ndiv.style.border = 0\ndiv.style.padding = 0\n\n// Center gauge\ndiv.style.display = \"flex\" \ndiv.style.justifyContent = \"center\" \ndiv.style.alignItems = \"center\" \n\nfunction resize() {\n\n if (attitude == null) {\n return\n }\n\n // Get width and height of widget\n const width = div.offsetWidth\n const height = div.offsetHeight\n\n const max_size = Math.min(width, height)\n attitude.resize(max_size)\n}\n\n// Watch for size changes\nnew ResizeObserver(() => { resize() }).observe(div)\n\nconst ATTITUDE_id = 30\n\n// Runtime function\nhandle_msg = function(msg) {\n\n if (msg._id != ATTITUDE_id) {\n return\n }\n\n if (attitude == null) {\n return\n }\n\n function rad2deg(rad) {\n return rad * (180.0 / Math.PI)\n }\n\n // Roll is backwards for some reason...\n attitude.updateRoll(-rad2deg(msg.roll))\n\n attitude.updatePitch(rad2deg(msg.pitch))\n}\n\n", + "about" : { + "name": "Attitude gauge", + "info": "Attitude gauge example built using the Sandbox widget. Reads ATTITUDE MAVLink message." + } + } + } +} \ No newline at end of file diff --git a/MAVLinkDashboard/SandBoxWidgets/Graph.json b/MAVLinkDashboard/SandBoxWidgets/Graph.json new file mode 100644 index 00000000..15c3d039 --- /dev/null +++ b/MAVLinkDashboard/SandBoxWidgets/Graph.json @@ -0,0 +1,1958 @@ +{ + "header": { + "version": 1 + }, + "widget": { + "x": "0", + "y": "0", + "w": "3", + "h": "1", + "type": "WidgetSandBox", + "options": { + "form": { + "components": [ + { + "label": "Plot title", + "tooltip": "Title for plot", + "defaultValue": "Ground speed", + "key": "title", + "type": "textfield", + "input": true, + "tableView": true, + "id": "e9r0rai", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "minLength": "", + "maxLength": "", + "pattern": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "mask": false, + "inputType": "text", + "inputFormat": "plain", + "inputMask": "", + "displayMask": "", + "spellcheck": true, + "truncateMultipleSpaces": false + }, + { + "label": "Axis label", + "tooltip": "The label to show for the selected value.", + "defaultValue": "Ground speed (m/s)", + "key": "label", + "type": "textfield", + "input": true, + "tableView": true, + "id": "e2920zg", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "minLength": "", + "maxLength": "", + "pattern": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "mask": false, + "inputType": "text", + "inputFormat": "plain", + "inputMask": "", + "displayMask": "", + "spellcheck": true, + "truncateMultipleSpaces": false + }, + { + "label": "color", + "tooltip": "Text color", + "key": "color", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "emqmow4a", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Message", + "tooltip": "Message to look for", + "key": "message", + "type": "mavlinkmsg", + "input": true, + "tableView": true, + "data": { + "values": [ + { + "label": "ACTUATOR_CONTROL_TARGET (140)", + "value": "140" + }, + { + "value": "375", + "label": "ACTUATOR_OUTPUT_STATUS (375)" + }, + { + "value": "11010", + "label": "ADAP_TUNING (11010)" + }, + { + "value": "246", + "label": "ADSB_VEHICLE (246)" + }, + { + "value": "163", + "label": "AHRS (163)" + }, + { + "value": "178", + "label": "AHRS2 (178)" + }, + { + "value": "182", + "label": "AHRS3 (182)" + }, + { + "value": "52000", + "label": "AIRLINK_AUTH (52000)" + }, + { + "value": "52001", + "label": "AIRLINK_AUTH_RESPONSE (52001)" + }, + { + "value": "295", + "label": "AIRSPEED (295)" + }, + { + "value": "174", + "label": "AIRSPEED_AUTOCAL (174)" + }, + { + "value": "301", + "label": "AIS_VESSEL (301)" + }, + { + "value": "141", + "label": "ALTITUDE (141)" + }, + { + "value": "11020", + "label": "AOA_SSA (11020)" + }, + { + "value": "153", + "label": "AP_ADC (153)" + }, + { + "value": "17150", + "label": "ARRAY_TEST_0 (17150)" + }, + { + "value": "17151", + "label": "ARRAY_TEST_1 (17151)" + }, + { + "value": "17153", + "label": "ARRAY_TEST_3 (17153)" + }, + { + "value": "17154", + "label": "ARRAY_TEST_4 (17154)" + }, + { + "value": "17155", + "label": "ARRAY_TEST_5 (17155)" + }, + { + "value": "17156", + "label": "ARRAY_TEST_6 (17156)" + }, + { + "value": "17157", + "label": "ARRAY_TEST_7 (17157)" + }, + { + "value": "17158", + "label": "ARRAY_TEST_8 (17158)" + }, + { + "value": "8008", + "label": "ASL_OBCTRL (8008)" + }, + { + "value": "8004", + "label": "ASLCTRL_DATA (8004)" + }, + { + "value": "8005", + "label": "ASLCTRL_DEBUG (8005)" + }, + { + "value": "8006", + "label": "ASLUAV_STATUS (8006)" + }, + { + "value": "138", + "label": "ATT_POS_MOCAP (138)" + }, + { + "value": "30", + "label": "ATTITUDE (30)" + }, + { + "value": "31", + "label": "ATTITUDE_QUATERNION (31)" + }, + { + "value": "61", + "label": "ATTITUDE_QUATERNION_COV (61)" + }, + { + "value": "83", + "label": "ATTITUDE_TARGET (83)" + }, + { + "value": "7", + "label": "AUTH_KEY (7)" + }, + { + "value": "286", + "label": "AUTOPILOT_STATE_FOR_GIMBAL_DEVICE (286)" + }, + { + "value": "148", + "label": "AUTOPILOT_VERSION (148)" + }, + { + "value": "183", + "label": "AUTOPILOT_VERSION_REQUEST (183)" + }, + { + "value": "60052", + "label": "AVSS_DRONE_IMU (60052)" + }, + { + "value": "60053", + "label": "AVSS_DRONE_OPERATION_MODE (60053)" + }, + { + "value": "60051", + "label": "AVSS_DRONE_POSITION (60051)" + }, + { + "value": "60050", + "label": "AVSS_PRS_SYS_STATUS (60050)" + }, + { + "value": "147", + "label": "BATTERY_STATUS (147)" + }, + { + "value": "181", + "label": "BATTERY2 (181)" + }, + { + "value": "257", + "label": "BUTTON_CHANGE (257)" + }, + { + "value": "262", + "label": "CAMERA_CAPTURE_STATUS (262)" + }, + { + "value": "180", + "label": "CAMERA_FEEDBACK (180)" + }, + { + "value": "271", + "label": "CAMERA_FOV_STATUS (271)" + }, + { + "value": "263", + "label": "CAMERA_IMAGE_CAPTURED (263)" + }, + { + "value": "259", + "label": "CAMERA_INFORMATION (259)" + }, + { + "value": "260", + "label": "CAMERA_SETTINGS (260)" + }, + { + "value": "179", + "label": "CAMERA_STATUS (179)" + }, + { + "value": "276", + "label": "CAMERA_TRACKING_GEO_STATUS (276)" + }, + { + "value": "275", + "label": "CAMERA_TRACKING_IMAGE_STATUS (275)" + }, + { + "value": "112", + "label": "CAMERA_TRIGGER (112)" + }, + { + "value": "388", + "label": "CAN_FILTER_MODIFY (388)" + }, + { + "value": "386", + "label": "CAN_FRAME (386)" + }, + { + "value": "387", + "label": "CANFD_FRAME (387)" + }, + { + "value": "5", + "label": "CHANGE_OPERATOR_CONTROL (5)" + }, + { + "value": "6", + "label": "CHANGE_OPERATOR_CONTROL_ACK (6)" + }, + { + "value": "247", + "label": "COLLISION (247)" + }, + { + "value": "77", + "label": "COMMAND_ACK (77)" + }, + { + "value": "75", + "label": "COMMAND_INT (75)" + }, + { + "value": "223", + "label": "COMMAND_INT_STAMPED (223)" + }, + { + "value": "76", + "label": "COMMAND_LONG (76)" + }, + { + "value": "224", + "label": "COMMAND_LONG_STAMPED (224)" + }, + { + "value": "177", + "label": "COMPASSMOT_STATUS (177)" + }, + { + "value": "60025", + "label": "COMPONENT_PREARM_STATUS (60025)" + }, + { + "value": "146", + "label": "CONTROL_SYSTEM_STATE (146)" + }, + { + "value": "50005", + "label": "CUBEPILOT_FIRMWARE_UPDATE_RESP (50005)" + }, + { + "value": "50004", + "label": "CUBEPILOT_FIRMWARE_UPDATE_START (50004)" + }, + { + "value": "50001", + "label": "CUBEPILOT_RAW_RC (50001)" + }, + { + "value": "67", + "label": "DATA_STREAM (67)" + }, + { + "value": "130", + "label": "DATA_TRANSMISSION_HANDSHAKE (130)" + }, + { + "value": "169", + "label": "DATA16 (169)" + }, + { + "value": "170", + "label": "DATA32 (170)" + }, + { + "value": "171", + "label": "DATA64 (171)" + }, + { + "value": "172", + "label": "DATA96 (172)" + }, + { + "value": "254", + "label": "DEBUG (254)" + }, + { + "value": "350", + "label": "DEBUG_FLOAT_ARRAY (350)" + }, + { + "value": "250", + "label": "DEBUG_VECT (250)" + }, + { + "value": "195", + "label": "DEEPSTALL (195)" + }, + { + "value": "11000", + "label": "DEVICE_OP_READ (11000)" + }, + { + "value": "11001", + "label": "DEVICE_OP_READ_REPLY (11001)" + }, + { + "value": "11002", + "label": "DEVICE_OP_WRITE (11002)" + }, + { + "value": "11003", + "label": "DEVICE_OP_WRITE_REPLY (11003)" + }, + { + "value": "154", + "label": "DIGICAM_CONFIGURE (154)" + }, + { + "value": "155", + "label": "DIGICAM_CONTROL (155)" + }, + { + "value": "132", + "label": "DISTANCE_SENSOR (132)" + }, + { + "value": "225", + "label": "EFI_STATUS (225)" + }, + { + "value": "8007", + "label": "EKF_EXT (8007)" + }, + { + "value": "193", + "label": "EKF_STATUS_REPORT (193)" + }, + { + "value": "131", + "label": "ENCAPSULATED_DATA (131)" + }, + { + "value": "11030", + "label": "ESC_TELEMETRY_1_TO_4 (11030)" + }, + { + "value": "11040", + "label": "ESC_TELEMETRY_13_TO_16 (11040)" + }, + { + "value": "11041", + "label": "ESC_TELEMETRY_17_TO_20 (11041)" + }, + { + "value": "11042", + "label": "ESC_TELEMETRY_21_TO_24 (11042)" + }, + { + "value": "11043", + "label": "ESC_TELEMETRY_25_TO_28 (11043)" + }, + { + "value": "11044", + "label": "ESC_TELEMETRY_29_TO_32 (11044)" + }, + { + "value": "11031", + "label": "ESC_TELEMETRY_5_TO_8 (11031)" + }, + { + "value": "11032", + "label": "ESC_TELEMETRY_9_TO_12 (11032)" + }, + { + "value": "230", + "label": "ESTIMATOR_STATUS (230)" + }, + { + "value": "245", + "label": "EXTENDED_SYS_STATE (245)" + }, + { + "value": "161", + "label": "FENCE_FETCH_POINT (161)" + }, + { + "value": "160", + "label": "FENCE_POINT (160)" + }, + { + "value": "162", + "label": "FENCE_STATUS (162)" + }, + { + "value": "110", + "label": "FILE_TRANSFER_PROTOCOL (110)" + }, + { + "value": "264", + "label": "FLIGHT_INFORMATION (264)" + }, + { + "value": "144", + "label": "FOLLOW_TARGET (144)" + }, + { + "value": "8011", + "label": "FW_SOARING_DATA (8011)" + }, + { + "value": "373", + "label": "GENERATOR_STATUS (373)" + }, + { + "value": "201", + "label": "GIMBAL_CONTROL (201)" + }, + { + "value": "285", + "label": "GIMBAL_DEVICE_ATTITUDE_STATUS (285)" + }, + { + "value": "283", + "label": "GIMBAL_DEVICE_INFORMATION (283)" + }, + { + "value": "284", + "label": "GIMBAL_DEVICE_SET_ATTITUDE (284)" + }, + { + "value": "280", + "label": "GIMBAL_MANAGER_INFORMATION (280)" + }, + { + "value": "282", + "label": "GIMBAL_MANAGER_SET_ATTITUDE (282)" + }, + { + "value": "288", + "label": "GIMBAL_MANAGER_SET_MANUAL_CONTROL (288)" + }, + { + "value": "287", + "label": "GIMBAL_MANAGER_SET_PITCHYAW (287)" + }, + { + "value": "281", + "label": "GIMBAL_MANAGER_STATUS (281)" + }, + { + "value": "200", + "label": "GIMBAL_REPORT (200)" + }, + { + "value": "214", + "label": "GIMBAL_TORQUE_CMD_REPORT (214)" + }, + { + "value": "33", + "label": "GLOBAL_POSITION_INT (33)" + }, + { + "value": "63", + "label": "GLOBAL_POSITION_INT_COV (63)" + }, + { + "value": "101", + "label": "GLOBAL_VISION_POSITION_ESTIMATE (101)" + }, + { + "value": "216", + "label": "GOPRO_GET_REQUEST (216)" + }, + { + "value": "217", + "label": "GOPRO_GET_RESPONSE (217)" + }, + { + "value": "215", + "label": "GOPRO_HEARTBEAT (215)" + }, + { + "value": "218", + "label": "GOPRO_SET_REQUEST (218)" + }, + { + "value": "219", + "label": "GOPRO_SET_RESPONSE (219)" + }, + { + "value": "49", + "label": "GPS_GLOBAL_ORIGIN (49)" + }, + { + "value": "123", + "label": "GPS_INJECT_DATA (123)" + }, + { + "value": "232", + "label": "GPS_INPUT (232)" + }, + { + "value": "24", + "label": "GPS_RAW_INT (24)" + }, + { + "value": "233", + "label": "GPS_RTCM_DATA (233)" + }, + { + "value": "127", + "label": "GPS_RTK (127)" + }, + { + "value": "25", + "label": "GPS_STATUS (25)" + }, + { + "value": "124", + "label": "GPS2_RAW (124)" + }, + { + "value": "128", + "label": "GPS2_RTK (128)" + }, + { + "value": "8014", + "label": "GSM_LINK_STATUS (8014)" + }, + { + "value": "0", + "label": "HEARTBEAT (0)" + }, + { + "value": "50003", + "label": "HERELINK_TELEM (50003)" + }, + { + "value": "50002", + "label": "HERELINK_VIDEO_STREAM_INFORMATION (50002)" + }, + { + "value": "234", + "label": "HIGH_LATENCY (234)" + }, + { + "value": "235", + "label": "HIGH_LATENCY2 (235)" + }, + { + "value": "105", + "label": "HIGHRES_IMU (105)" + }, + { + "value": "93", + "label": "HIL_ACTUATOR_CONTROLS (93)" + }, + { + "value": "91", + "label": "HIL_CONTROLS (91)" + }, + { + "value": "113", + "label": "HIL_GPS (113)" + }, + { + "value": "114", + "label": "HIL_OPTICAL_FLOW (114)" + }, + { + "value": "92", + "label": "HIL_RC_INPUTS_RAW (92)" + }, + { + "value": "107", + "label": "HIL_SENSOR (107)" + }, + { + "value": "90", + "label": "HIL_STATE (90)" + }, + { + "value": "115", + "label": "HIL_STATE_QUATERNION (115)" + }, + { + "value": "242", + "label": "HOME_POSITION (242)" + }, + { + "value": "165", + "label": "HWSTATUS (165)" + }, + { + "value": "12920", + "label": "HYGROMETER_SENSOR (12920)" + }, + { + "value": "42000", + "label": "ICAROUS_HEARTBEAT (42000)" + }, + { + "value": "42001", + "label": "ICAROUS_KINEMATIC_BANDS (42001)" + }, + { + "value": "335", + "label": "ISBD_LINK_STATUS (335)" + }, + { + "value": "149", + "label": "LANDING_TARGET (149)" + }, + { + "value": "186", + "label": "LED_CONTROL (186)" + }, + { + "value": "167", + "label": "LIMITS_STATUS (167)" + }, + { + "value": "32", + "label": "LOCAL_POSITION_NED (32)" + }, + { + "value": "64", + "label": "LOCAL_POSITION_NED_COV (64)" + }, + { + "value": "89", + "label": "LOCAL_POSITION_NED_SYSTEM_GLOBAL_OFFSET (89)" + }, + { + "value": "120", + "label": "LOG_DATA (120)" + }, + { + "value": "118", + "label": "LOG_ENTRY (118)" + }, + { + "value": "121", + "label": "LOG_ERASE (121)" + }, + { + "value": "119", + "label": "LOG_REQUEST_DATA (119)" + }, + { + "value": "122", + "label": "LOG_REQUEST_END (122)" + }, + { + "value": "117", + "label": "LOG_REQUEST_LIST (117)" + }, + { + "value": "268", + "label": "LOGGING_ACK (268)" + }, + { + "value": "266", + "label": "LOGGING_DATA (266)" + }, + { + "value": "267", + "label": "LOGGING_DATA_ACKED (267)" + }, + { + "value": "10151", + "label": "LOWEHEISER_GOV_EFI (10151)" + }, + { + "value": "191", + "label": "MAG_CAL_PROGRESS (191)" + }, + { + "value": "192", + "label": "MAG_CAL_REPORT (192)" + }, + { + "value": "69", + "label": "MANUAL_CONTROL (69)" + }, + { + "value": "81", + "label": "MANUAL_SETPOINT (81)" + }, + { + "value": "11039", + "label": "MCU_STATUS (11039)" + }, + { + "value": "152", + "label": "MEMINFO (152)" + }, + { + "value": "249", + "label": "MEMORY_VECT (249)" + }, + { + "value": "244", + "label": "MESSAGE_INTERVAL (244)" + }, + { + "value": "47", + "label": "MISSION_ACK (47)" + }, + { + "value": "53", + "label": "MISSION_CHECKSUM (53)" + }, + { + "value": "45", + "label": "MISSION_CLEAR_ALL (45)" + }, + { + "value": "44", + "label": "MISSION_COUNT (44)" + }, + { + "value": "42", + "label": "MISSION_CURRENT (42)" + }, + { + "value": "39", + "label": "MISSION_ITEM (39)" + }, + { + "value": "73", + "label": "MISSION_ITEM_INT (73)" + }, + { + "value": "46", + "label": "MISSION_ITEM_REACHED (46)" + }, + { + "value": "40", + "label": "MISSION_REQUEST (40)" + }, + { + "value": "51", + "label": "MISSION_REQUEST_INT (51)" + }, + { + "value": "43", + "label": "MISSION_REQUEST_LIST (43)" + }, + { + "value": "37", + "label": "MISSION_REQUEST_PARTIAL_LIST (37)" + }, + { + "value": "41", + "label": "MISSION_SET_CURRENT (41)" + }, + { + "value": "38", + "label": "MISSION_WRITE_PARTIAL_LIST (38)" + }, + { + "value": "156", + "label": "MOUNT_CONFIGURE (156)" + }, + { + "value": "157", + "label": "MOUNT_CONTROL (157)" + }, + { + "value": "265", + "label": "MOUNT_ORIENTATION (265)" + }, + { + "value": "158", + "label": "MOUNT_STATUS (158)" + }, + { + "value": "251", + "label": "NAMED_VALUE_FLOAT (251)" + }, + { + "value": "252", + "label": "NAMED_VALUE_INT (252)" + }, + { + "value": "62", + "label": "NAV_CONTROLLER_OUTPUT (62)" + }, + { + "value": "220", + "label": "NAV_FILTER_BIAS (220)" + }, + { + "value": "330", + "label": "OBSTACLE_DISTANCE (330)" + }, + { + "value": "11037", + "label": "OBSTACLE_DISTANCE_3D (11037)" + }, + { + "value": "331", + "label": "ODOMETRY (331)" + }, + { + "value": "12918", + "label": "OPEN_DRONE_ID_ARM_STATUS (12918)" + }, + { + "value": "12902", + "label": "OPEN_DRONE_ID_AUTHENTICATION (12902)" + }, + { + "value": "12900", + "label": "OPEN_DRONE_ID_BASIC_ID (12900)" + }, + { + "value": "12901", + "label": "OPEN_DRONE_ID_LOCATION (12901)" + }, + { + "value": "12915", + "label": "OPEN_DRONE_ID_MESSAGE_PACK (12915)" + }, + { + "value": "12905", + "label": "OPEN_DRONE_ID_OPERATOR_ID (12905)" + }, + { + "value": "12903", + "label": "OPEN_DRONE_ID_SELF_ID (12903)" + }, + { + "value": "12904", + "label": "OPEN_DRONE_ID_SYSTEM (12904)" + }, + { + "value": "12919", + "label": "OPEN_DRONE_ID_SYSTEM_UPDATE (12919)" + }, + { + "value": "100", + "label": "OPTICAL_FLOW (100)" + }, + { + "value": "106", + "label": "OPTICAL_FLOW_RAD (106)" + }, + { + "value": "11033", + "label": "OSD_PARAM_CONFIG (11033)" + }, + { + "value": "11034", + "label": "OSD_PARAM_CONFIG_REPLY (11034)" + }, + { + "value": "11035", + "label": "OSD_PARAM_SHOW_CONFIG (11035)" + }, + { + "value": "11036", + "label": "OSD_PARAM_SHOW_CONFIG_REPLY (11036)" + }, + { + "value": "324", + "label": "PARAM_EXT_ACK (324)" + }, + { + "value": "321", + "label": "PARAM_EXT_REQUEST_LIST (321)" + }, + { + "value": "320", + "label": "PARAM_EXT_REQUEST_READ (320)" + }, + { + "value": "323", + "label": "PARAM_EXT_SET (323)" + }, + { + "value": "322", + "label": "PARAM_EXT_VALUE (322)" + }, + { + "value": "50", + "label": "PARAM_MAP_RC (50)" + }, + { + "value": "21", + "label": "PARAM_REQUEST_LIST (21)" + }, + { + "value": "20", + "label": "PARAM_REQUEST_READ (20)" + }, + { + "value": "23", + "label": "PARAM_SET (23)" + }, + { + "value": "22", + "label": "PARAM_VALUE (22)" + }, + { + "value": "194", + "label": "PID_TUNING (194)" + }, + { + "value": "4", + "label": "PING (4)" + }, + { + "value": "258", + "label": "PLAY_TUNE (258)" + }, + { + "value": "87", + "label": "POSITION_TARGET_GLOBAL_INT (87)" + }, + { + "value": "85", + "label": "POSITION_TARGET_LOCAL_NED (85)" + }, + { + "value": "125", + "label": "POWER_STATUS (125)" + }, + { + "value": "60020", + "label": "QSHOT_STATUS (60020)" + }, + { + "value": "166", + "label": "RADIO (166)" + }, + { + "value": "221", + "label": "RADIO_CALIBRATION (221)" + }, + { + "value": "420", + "label": "RADIO_RC_CHANNELS (420)" + }, + { + "value": "109", + "label": "RADIO_STATUS (109)" + }, + { + "value": "176", + "label": "RALLY_FETCH_POINT (176)" + }, + { + "value": "175", + "label": "RALLY_POINT (175)" + }, + { + "value": "173", + "label": "RANGEFINDER (173)" + }, + { + "value": "27", + "label": "RAW_IMU (27)" + }, + { + "value": "28", + "label": "RAW_PRESSURE (28)" + }, + { + "value": "339", + "label": "RAW_RPM (339)" + }, + { + "value": "65", + "label": "RC_CHANNELS (65)" + }, + { + "value": "70", + "label": "RC_CHANNELS_OVERRIDE (70)" + }, + { + "value": "35", + "label": "RC_CHANNELS_RAW (35)" + }, + { + "value": "34", + "label": "RC_CHANNELS_SCALED (34)" + }, + { + "value": "376", + "label": "RELAY_STATUS (376)" + }, + { + "value": "185", + "label": "REMOTE_LOG_BLOCK_STATUS (185)" + }, + { + "value": "184", + "label": "REMOTE_LOG_DATA_BLOCK (184)" + }, + { + "value": "66", + "label": "REQUEST_DATA_STREAM (66)" + }, + { + "value": "142", + "label": "RESOURCE_REQUEST (142)" + }, + { + "value": "226", + "label": "RPM (226)" + }, + { + "value": "55", + "label": "SAFETY_ALLOWED_AREA (55)" + }, + { + "value": "54", + "label": "SAFETY_SET_ALLOWED_AREA (54)" + }, + { + "value": "8015", + "label": "SATCOM_LINK_STATUS (8015)" + }, + { + "value": "26", + "label": "SCALED_IMU (26)" + }, + { + "value": "116", + "label": "SCALED_IMU2 (116)" + }, + { + "value": "129", + "label": "SCALED_IMU3 (129)" + }, + { + "value": "29", + "label": "SCALED_PRESSURE (29)" + }, + { + "value": "137", + "label": "SCALED_PRESSURE2 (137)" + }, + { + "value": "143", + "label": "SCALED_PRESSURE3 (143)" + }, + { + "value": "11004", + "label": "SECURE_COMMAND (11004)" + }, + { + "value": "11005", + "label": "SECURE_COMMAND_REPLY (11005)" + }, + { + "value": "8009", + "label": "SENS_ATMOS (8009)" + }, + { + "value": "8010", + "label": "SENS_BATMON (8010)" + }, + { + "value": "8003", + "label": "SENS_MPPT (8003)" + }, + { + "value": "8002", + "label": "SENS_POWER (8002)" + }, + { + "value": "8013", + "label": "SENS_POWER_BOARD (8013)" + }, + { + "value": "8016", + "label": "SENSOR_AIRFLOW_ANGLES (8016)" + }, + { + "value": "150", + "label": "SENSOR_OFFSETS (150)" + }, + { + "value": "8012", + "label": "SENSORPOD_STATUS (8012)" + }, + { + "value": "126", + "label": "SERIAL_CONTROL (126)" + }, + { + "value": "36", + "label": "SERVO_OUTPUT_RAW (36)" + }, + { + "value": "139", + "label": "SET_ACTUATOR_CONTROL_TARGET (139)" + }, + { + "value": "82", + "label": "SET_ATTITUDE_TARGET (82)" + }, + { + "value": "48", + "label": "SET_GPS_GLOBAL_ORIGIN (48)" + }, + { + "value": "243", + "label": "SET_HOME_POSITION (243)" + }, + { + "value": "151", + "label": "SET_MAG_OFFSETS (151)" + }, + { + "value": "11", + "label": "SET_MODE (11)" + }, + { + "value": "86", + "label": "SET_POSITION_TARGET_GLOBAL_INT (86)" + }, + { + "value": "84", + "label": "SET_POSITION_TARGET_LOCAL_NED (84)" + }, + { + "value": "256", + "label": "SETUP_SIGNING (256)" + }, + { + "value": "108", + "label": "SIM_STATE (108)" + }, + { + "value": "164", + "label": "SIMSTATE (164)" + }, + { + "value": "370", + "label": "SMART_BATTERY_INFO (370)" + }, + { + "value": "253", + "label": "STATUSTEXT (253)" + }, + { + "value": "261", + "label": "STORAGE_INFORMATION (261)" + }, + { + "value": "60002", + "label": "STORM32_GIMBAL_DEVICE_CONTROL (60002)" + }, + { + "value": "60001", + "label": "STORM32_GIMBAL_DEVICE_STATUS (60001)" + }, + { + "value": "60012", + "label": "STORM32_GIMBAL_MANAGER_CONTROL (60012)" + }, + { + "value": "60013", + "label": "STORM32_GIMBAL_MANAGER_CONTROL_PITCHYAW (60013)" + }, + { + "value": "60014", + "label": "STORM32_GIMBAL_MANAGER_CORRECT_ROLL (60014)" + }, + { + "value": "60010", + "label": "STORM32_GIMBAL_MANAGER_INFORMATION (60010)" + }, + { + "value": "60015", + "label": "STORM32_GIMBAL_MANAGER_PROFILE (60015)" + }, + { + "value": "60011", + "label": "STORM32_GIMBAL_MANAGER_STATUS (60011)" + }, + { + "value": "1", + "label": "SYS_STATUS (1)" + }, + { + "value": "2", + "label": "SYSTEM_TIME (2)" + }, + { + "value": "135", + "label": "TERRAIN_CHECK (135)" + }, + { + "value": "134", + "label": "TERRAIN_DATA (134)" + }, + { + "value": "136", + "label": "TERRAIN_REPORT (136)" + }, + { + "value": "133", + "label": "TERRAIN_REQUEST (133)" + }, + { + "value": "17000", + "label": "TEST_TYPES (17000)" + }, + { + "value": "111", + "label": "TIMESYNC (111)" + }, + { + "value": "385", + "label": "TUNNEL (385)" + }, + { + "value": "222", + "label": "UALBERTA_SYS_STATUS (222)" + }, + { + "value": "311", + "label": "UAVCAN_NODE_INFO (311)" + }, + { + "value": "310", + "label": "UAVCAN_NODE_STATUS (310)" + }, + { + "value": "10006", + "label": "UAVIONIX_ADSB_GET (10006)" + }, + { + "value": "10001", + "label": "UAVIONIX_ADSB_OUT_CFG (10001)" + }, + { + "value": "10005", + "label": "UAVIONIX_ADSB_OUT_CFG_FLIGHTID (10005)" + }, + { + "value": "10004", + "label": "UAVIONIX_ADSB_OUT_CFG_REGISTRATION (10004)" + }, + { + "value": "10007", + "label": "UAVIONIX_ADSB_OUT_CONTROL (10007)" + }, + { + "value": "10002", + "label": "UAVIONIX_ADSB_OUT_DYNAMIC (10002)" + }, + { + "value": "10008", + "label": "UAVIONIX_ADSB_OUT_STATUS (10008)" + }, + { + "value": "10003", + "label": "UAVIONIX_ADSB_TRANSCEIVER_HEALTH_REPORT (10003)" + }, + { + "value": "340", + "label": "UTM_GLOBAL_POSITION (340)" + }, + { + "value": "248", + "label": "V2_EXTENSION (248)" + }, + { + "value": "74", + "label": "VFR_HUD (74)" + }, + { + "value": "241", + "label": "VIBRATION (241)" + }, + { + "value": "104", + "label": "VICON_POSITION_ESTIMATE (104)" + }, + { + "value": "269", + "label": "VIDEO_STREAM_INFORMATION (269)" + }, + { + "value": "270", + "label": "VIDEO_STREAM_STATUS (270)" + }, + { + "value": "11011", + "label": "VISION_POSITION_DELTA (11011)" + }, + { + "value": "102", + "label": "VISION_POSITION_ESTIMATE (102)" + }, + { + "value": "103", + "label": "VISION_SPEED_ESTIMATE (103)" + }, + { + "value": "11038", + "label": "WATER_DEPTH (11038)" + }, + { + "value": "9000", + "label": "WHEEL_DISTANCE (9000)" + }, + { + "value": "299", + "label": "WIFI_CONFIG_AP (299)" + }, + { + "value": "9005", + "label": "WINCH_STATUS (9005)" + }, + { + "value": "168", + "label": "WIND (168)" + }, + { + "value": "231", + "label": "WIND_COV (231)" + } + ], + "json": "", + "url": "", + "resource": "", + "custom": "" + }, + "defaultValue": 74, + "id": "e82ci7c", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "onlyAvailableItems": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "dataSrc": "values", + "authenticate": false, + "ignoreCache": false, + "template": "{{ item.label }}", + "idPath": "id", + "clearOnRefresh": false, + "limit": 100, + "valueProperty": "", + "lazyLoad": true, + "filter": "", + "searchEnabled": true, + "searchDebounce": 0.3, + "searchField": "", + "minSearch": 0, + "readOnlyValue": false, + "selectFields": "", + "selectThreshold": 0.3, + "uniqueOptions": false, + "fuseOptions": { + "include": "score", + "threshold": 0.3 + }, + "indexeddb": { + "filter": {} + }, + "customOptions": {}, + "useExactSearch": false + }, + { + "label": "Field", + "tooltip": "Mesage feild to display", + "MAVLinkMsgSelect": "message", + "defaultValue": "groundspeed", + "key": "field", + "type": "mavlinkfield", + "input": true, + "tableView": true, + "dataSrc": "custom", + "data": { + "custom": "\n// Get the target key\nif (component.MAVLinkMsgSelect == undefined) {\n return [ \"Invalid MAVLink message item key\" ]\n}\nconst key = component.MAVLinkMsgSelect\n\n// Get the value of form item with that key\nconst id = submission.data[component.MAVLinkMsgSelect]\n\n// Function to get fields for given message id\nfunction get_fields(id) {\n for (const msg_map of Object.values(mavlink20.map)) {\n const msg = new msg_map.type\n if (String(msg._id) == id) {\n return msg.fieldnames\n }\n }\n return [ \"Unknown message\" ]\n}\n\n// Get the fields for the give message id\nvalues = get_fields(id)\n", + "values": [ + { + "label": "", + "value": "" + } + ], + "json": "", + "url": "", + "resource": "" + }, + "id": "enf0q0k", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "onlyAvailableItems": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "authenticate": false, + "ignoreCache": false, + "template": "{{ item.label }}", + "idPath": "id", + "clearOnRefresh": false, + "limit": 100, + "valueProperty": "", + "lazyLoad": true, + "filter": "", + "searchEnabled": true, + "searchDebounce": 0.3, + "searchField": "", + "minSearch": 0, + "readOnlyValue": false, + "selectFields": "", + "selectThreshold": 0.3, + "uniqueOptions": false, + "fuseOptions": { + "include": "score", + "threshold": 0.3 + }, + "indexeddb": { + "filter": {} + }, + "customOptions": {}, + "useExactSearch": false + }, + { + "label": "Scale factor", + "tooltip": "Scale factor applyed to value, for example to change units", + "key": "scaleFactor", + "type": "number", + "input": true, + "tableView": false, + "defaultValue": 1, + "id": "ev161x", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "min": "", + "max": "", + "step": "any", + "integer": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Period (s)", + "tooltip": "Time period to plot over", + "defaultValue": 60, + "key": "time", + "type": "number", + "input": true, + "tableView": false, + "id": "eva13ua", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "min": "", + "max": "", + "step": "any", + "integer": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + } + ] + }, + "form_content": { + "title": "Ground speed", + "label": "Ground speed (m/s)", + "message": 74, + "field": "groundspeed", + "scaleFactor": 1, + "time": 60, + "color": "#000000", + "periodS": 60 + }, + "sandbox": "// Include plotly\nconst script = document.createElement(\"script\")\nscript.src = \"https://cdn.plot.ly/plotly-2.35.0.min.js\"\ndocument.body.appendChild(script)\n\n// Setup layout\nconst plot_layout = { \n title: { text: options.title },\n legend: { itemclick: false, itemdoubleclick: false }, \n margin: { b: 50, l: 65, r: 50, t: 50 },\n xaxis: { title: { text: \"time (s)\" }, range: [-options.time, 0], zeroline: false, showline: true, mirror: true },\n yaxis: { title: { text: options.label }, zeroline: false, showline: true, mirror: true }\n}\n\nconst plot_data = [\n { mode: 'lines', x: [], y:[], line: { color: options.color } }\n]\n\ndata = {\n time: [],\n value: []\n}\n\nlet plot_created = false\n\n// Update plot\nfunction update_data() {\n\n // Caculate time since sample\n const now = Date.now()\n const len = data.time.length\n const dt = new Array(len)\n for (let i = 0; i -x > options.time)\n if (last != -1) {\n data.time.splice(0, last)\n data.value.splice(0, last)\n dt.splice(0, last)\n }\n\n // Update plot\n plot_data[0].x = dt\n plot_data[0].y = data.value\n\n // Make sure plotly is loaded\n if (window.Plotly !== undefined) {\n if (!plot_created) {\n replot()\n }\n Plotly.redraw(div)\n }\n}\n\n\nfunction replot() {\n // Clear plot and redrae to cope with change in size or options\n plot_layout.title.text = options.title\n plot_layout.xaxis.range[0] = -options.time\n plot_layout.yaxis.title.text = options.label\n plot_data[0].line.color = options.color\n\n if (window.Plotly !== undefined) {\n Plotly.purge(div)\n Plotly.newPlot(div, plot_data, plot_layout, {displaylogo: false})\n plot_created = true\n }\n}\n\n// Watch for size changes\nnew ResizeObserver(() => { replot() }).observe(div)\n\n// Runtime function\nhandle_msg = function (msg) {\n\n // Check message ID\n if (msg._id != options.message) {\n return\n }\n\n // Check for field\n if (!(options.field in msg)) {\n throw new Error(\"No feild \" + options.field + \" in \" + msg._name)\n }\n\n let value = msg[options.field]\n value *= options.scaleFactor\n\n // Add data\n data.value.push(value)\n data.time.push(Date.now())\n \n // Plot\n update_data()\n}\n\n// Add 10Hz update plot\nsetInterval(update_data, 100)\n\n// Optional function to allow run-time update of options\nhandle_options = function (opts) {\n options = opts\n replot()\n}\n", + "about" : { + "name": "Graph", + "info": "Graph example built using the Sandbox widget. User customsable plot options." + } + } + } +} \ No newline at end of file diff --git a/MAVLinkDashboard/SandBoxWidgets/MAVLink_Inspector.json b/MAVLinkDashboard/SandBoxWidgets/MAVLink_Inspector.json new file mode 100644 index 00000000..77e4eada --- /dev/null +++ b/MAVLinkDashboard/SandBoxWidgets/MAVLink_Inspector.json @@ -0,0 +1,23 @@ +{ + "header": { + "version": 1 + }, + "widget": { + "x": "1", + "y": "0", + "w": "3", + "h": "2", + "type": "WidgetSandBox", + "options": { + "form": { + "components": [] + }, + "form_content": {}, + "sandb0ox": "// Build component ID lookup\nlet comp_id = {}\nfor (const [key, value] of Object.entries(mavlink20)) {\n if (key.startsWith(\"MAV_COMP_ID\")) {\n comp_id[value] = key\n }\n}\n\n// Add a heading\nconst heading = document.createElement(\"h3\")\nheading.appendChild(document.createTextNode(\"MAVLink Inspector\"))\nheading.style.margin = 0\ndiv.appendChild(heading)\n\n// Use flex to allow the tree to take up the remaining space\ndiv.style.display = \"flex\"\ndiv.style.flexDirection = \"column\"\n\n// Add a div to hold the tree\nconst tree_div = document.createElement(\"div\")\ntree_div.style.height = \"100%\"\ndiv.appendChild(tree_div)\n\n// Allow scrolling if needed\ntree_div.style.overflow = \"auto\"\n\n// List for any system IDs\nlet ids = {}\n\n// Create a new details elememnt with summary\nfunction create_details(summary_text, indent = false, open = true) {\n // Create new details item\n const details = document.createElement(\"details\")\n\n // Add text\n const summary = document.createElement(\"summary\")\n summary.appendChild(document.createTextNode(summary_text))\n details.appendChild(summary)\n\n if (indent) {\n details.style.marginLeft = \"1em\"\n }\n details.open = open\n\n return details\n}\n\n// Add a new item to a tree\nfunction add_to_tree(tree, id, parent, item) {\n\n // Find any existing id that should come before this one\n let prior_item = null\n for (const existing_id of Object.keys(tree)) {\n if (parseInt(existing_id) < id) {\n prior_item = tree[existing_id]\n }\n }\n\n if (prior_item == null) {\n // No prior element, add to start of tree\n parent.append(item)\n } else {\n // Add affter the prior element\n prior_item.ele.after(item)\n }\n\n tree[id] = { ele: item, content: {} }\n}\n\n// Runtime function\nhandle_msg = function (msg) {\n\n const id = msg._header.srcSystem\n const comp = msg._header.srcComponent\n const msg_id = msg._id\n\n // Add new ID to tree if not already there\n if (!(id in ids)) {\n add_to_tree(ids, id, tree_div, create_details(\"System ID: \" + id))\n }\n\n const id_branch = ids[id]\n\n // Add new component to tree if not already there\n if (!(comp in id_branch.content)) {\n let comp_str = \"Component ID:\" + comp\n if (comp in comp_id) {\n comp_str += \" \" + comp_id[comp]\n }\n\n add_to_tree(id_branch.content, comp, id_branch.ele, create_details(comp_str, true))\n }\n\n const component_branch = id_branch.content[comp]\n\n // Add new message to tree if not already there\n if (!(msg_id in component_branch.content)) {\n let msg_str\n let type = null\n if (msg_id in mavlink20.map) {\n type = new mavlink20.map[msg_id].type\n msg_str = type._name + \" (\" + msg_id + \")\"\n } else {\n msg_str = \"\" + msg_id\n }\n \n add_to_tree(component_branch.content, msg_id, component_branch.ele, create_details(msg_str, true, false))\n\n const msg_item = component_branch.content[msg_id]\n msg_item.type = type\n\n if (type != null) {\n // Add line for each feild\n for (const feild of type.fieldnames) {\n const line = document.createElement(\"li\")\n line.style.marginLeft = \"1em\"\n line.appendChild(document.createTextNode(feild + \": \"))\n msg_item.ele.appendChild(line)\n\n const value = document.createTextNode(\"?\")\n line.appendChild(value)\n msg_item.content[feild] = value\n }\n } \n }\n\n // Update the feild values\n const msg_item = component_branch.content[msg_id]\n if (msg_item.type != null) {\n for (const [feild, text] of Object.entries(msg_item.content)) {\n text.nodeValue = msg[feild]\n }\n }\n\n}\n", + "about" : { + "name": "MAVLink inspector", + "info": "MAVLink inspector example built using the Sandbox widget. Useful for checking the messages are available for use in other widgets." + } + } + } +} \ No newline at end of file diff --git a/MAVLinkDashboard/SandBoxWidgets/Messages.json b/MAVLinkDashboard/SandBoxWidgets/Messages.json new file mode 100644 index 00000000..b85e5c50 --- /dev/null +++ b/MAVLinkDashboard/SandBoxWidgets/Messages.json @@ -0,0 +1,1946 @@ +{ + "header": { + "version": 1 + }, + "widget": { + "x": "3", + "y": "0", + "w": "2", + "h": "2", + "type": "WidgetSandBox", + "options": { + "form": { + "components": [ + { + "label": "Line history", + "tooltip": "This is the number of lines kept in history.", + "key": "lineHistory", + "type": "number", + "input": true, + "tableView": false, + "defaultValue": 200, + "id": "e1rimpc", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "min": "", + "max": "", + "step": "any", + "integer": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "HTML", + "content": "Options for message severity levels", + "key": "html", + "type": "htmlelement", + "input": false, + "tableView": false, + "id": "eywu51a", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": false, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "tag": "p", + "attrs": [] + }, + { + "label": "Severity levels", + "components": [ + { + "label": "Emergency", + "key": "emergency", + "components": [ + { + "label": "Text color", + "defaultValue": "#ffffff", + "key": "textColor0", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ekhgxf", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#ff0000", + "key": "backgroundColor0", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "eicf206", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": true, + "key": "speech0", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "elzs3sc", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Alert", + "key": "alert", + "components": [ + { + "label": "Text color", + "defaultValue": "#ffffff", + "key": "textColor1", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ewsux5a4", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#ff0000", + "key": "backgroundColor1", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ew1larx", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "key": "speech1", + "type": "checkbox", + "input": true, + "tableView": false, + "defaultValue": false, + "id": "eyfvsm", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Critical", + "key": "critical", + "components": [ + { + "label": "Text color", + "defaultValue": "#ffffff", + "key": "textColor2", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ecmprmo", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#ff0000", + "key": "backgroundColor2", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "e2po1gi", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "key": "speech2", + "type": "checkbox", + "input": true, + "tableView": false, + "defaultValue": false, + "id": "e06f12t", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Error", + "key": "error", + "components": [ + { + "label": "Text color", + "key": "textColor3", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "e8aq8h", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "key": "backgroundColor3", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "defaultValue": "#ffa500", + "id": "es5hj8", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": false, + "key": "speech3", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "ebkvpx7", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Warning", + "key": "warning", + "components": [ + { + "label": "Text color", + "key": "textColor4", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ekrucxw", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#ffa500", + "key": "backgroundColor4", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "etmahcu", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": false, + "key": "speech4", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "elajtmk", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Notice", + "key": "notice", + "components": [ + { + "label": "Text color", + "key": "textColor5", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ei4mo3m", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#ffff00", + "key": "backgroundColor5", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "e61nxsh", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": false, + "key": "speech5", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "ehwtmxf", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Info", + "key": "info", + "components": [ + { + "label": "Text color", + "defaultValue": "#ffffff", + "key": "textColor6", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "e7h9z6", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#00ff00", + "key": "backgroundColor6", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "elut6zc", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": false, + "key": "speech6", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "e0u3lp", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + }, + { + "label": "Debug", + "key": "debug", + "components": [ + { + "label": "Text color", + "defaultValue": "#ffffff", + "key": "textColor7", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "ewvzi7", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Background color", + "defaultValue": "#00ff00", + "key": "backgroundColor7", + "type": "color", + "input": true, + "tableView": false, + "widget": { + "type": "input" + }, + "inputType": "color", + "mask": false, + "data": "#000000", + "id": "evvhm5j", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + }, + { + "label": "Enable speech", + "defaultValue": false, + "key": "speech7", + "type": "checkbox", + "input": true, + "tableView": false, + "id": "eivks8d", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": true, + "labelPosition": "right", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "inputType": "checkbox", + "value": "", + "name": "" + } + ] + } + ], + "key": "severityLevels", + "type": "tabs", + "input": false, + "tableView": false, + "id": "ezvo3ye", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "defaultValue": null, + "protected": false, + "unique": false, + "persistent": false, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "tooltip": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "tree": false, + "lazyLoad": false, + "verticalLayout": false + } + ] + }, + "form_content": { + }, + "sandbox": "// Add a heading\nconst heading = document.createElement(\"h3\")\nheading.appendChild(document.createTextNode(\"Messages\"))\nheading.style.margin = 0\ndiv.appendChild(heading)\n\n// Use flex to allow the tree to take up the remaining space\ndiv.style.display = \"flex\"\ndiv.style.flexDirection = \"column\"\n\n// Add a div to hold the tree\nconst msg_div = document.createElement(\"div\")\nmsg_div.style.height = \"100%\"\ndiv.appendChild(msg_div)\n\n// Allow scrolling if needed\nmsg_div.style.overflow = \"auto\"\n\nconst speech_msg = new SpeechSynthesisUtterance()\n\nfunction print(text, severity) {\n\n const text_color = options[\"textColor\" + severity]\n const background_color = options[\"backgroundColor\" + severity]\n const speech = options[\"speech\" + severity]\n\n const div = document.createElement(\"div\")\n if (text_color != null) {\n div.style.color = text_color \n }\n if (background_color != null) {\n div.style.backgroundColor = background_color\n }\n \n div.innerText = text\n\n // Add item\n msg_div.appendChild(div)\n\n // Remove any item over the history\n while (msg_div.childElementCount > options.lineHistory) {\n msg_div.removeChild(msg_div.firstElementChild)\n }\n \n // Move scroll to bottom\n msg_div.scrollTop = msg_div.scrollHeight\n\n // Say if enabled\n if (speech) {\n speech_msg.text = text\n window.speechSynthesis.speak(speech_msg)\n }\n\n}\n\n// Class for accumulating status texts\nclass status_text {\n\n constructor(msg) {\n this.chunks = []\n this.expected_chunks = 1\n this.severity = null\n this.id = null\n\n this.add(msg)\n }\n\n add(msg) {\n if ((this.severity == null) || (this.id == null)) {\n // First message\n this.severity = msg.severity\n this.id = msg.id\n\n } else if ((msg.severity != this.severity) || (msg.id != this.id)) {\n // New message does not belong in this set\n return false\n }\n\n // Remove null chars\n this.chunks[msg.chunk_seq] = msg.text.replace(/\\0.*$/g,'')\n\n // If this message does not contain a null then another is expected\n const text_max_length = 50\n if (this.chunks[msg.chunk_seq].length == text_max_length) {\n this.expected_chunks = msg.chunk_seq + 1\n }\n\n // Record the time\n this.last_chunk = Date.now()\n\n return true\n }\n\n get_text() {\n let text = \"\"\n for (const chunk of this.chunks) {\n if (chunk != null) {\n text += chunk\n } else {\n // Indicate the missing chunk\n text += \" ... \"\n }\n }\n return text\n }\n\n get_msg() {\n if (this.id == 0) {\n // Id of 0 means single chunk message\n return { text: this.get_text(), severity: this.severity }\n }\n\n // Multi chunk, count chunks\n let chunk_count = 0\n for (const chunk of this.chunks) {\n if (chunk != null) {\n chunk_count++\n }\n }\n\n if (chunk_count == this.expected_chunks) {\n // Got all the expected chunks\n return { text: this.get_text(), severity: this.severity }\n }\n\n if ((Date.now() - this.last_chunk) > 1000) {\n // More than 1 second since last chunk, assume its lost and return what we have\n return { text: this.get_text(), severity: this.severity }\n }\n\n return null\n }\n}\n\n// Object for each system ID and component ID\nlet systems = {}\n\n// Print any messagesd from message array and remove\nfunction print_message(messages) {\n for (let i = 0; i{{ item.label }}", + "idPath": "id", + "clearOnRefresh": false, + "limit": 100, + "valueProperty": "", + "lazyLoad": true, + "filter": "", + "searchEnabled": true, + "searchDebounce": 0.3, + "searchField": "", + "minSearch": 0, + "readOnlyValue": false, + "selectFields": "", + "selectThreshold": 0.3, + "uniqueOptions": false, + "fuseOptions": { + "include": "score", + "threshold": 0.3 + }, + "indexeddb": { + "filter": {} + }, + "customOptions": {}, + "useExactSearch": false + }, + { + "label": "Field", + "tooltip": "Mesage feild to display", + "MAVLinkMsgSelect": "message", + "defaultValue": "groundspeed", + "key": "field", + "type": "mavlinkfield", + "input": true, + "tableView": true, + "dataSrc": "custom", + "data": { + "custom": "\n// Get the target key\nif (component.MAVLinkMsgSelect == undefined) {\n return [ \"Invalid MAVLink message item key\" ]\n}\nconst key = component.MAVLinkMsgSelect\n\n// Get the value of form item with that key\nconst id = submission.data[component.MAVLinkMsgSelect]\n\n// Function to get fields for given message id\nfunction get_fields(id) {\n for (const msg_map of Object.values(mavlink20.map)) {\n const msg = new msg_map.type\n if (String(msg._id) == id) {\n return msg.fieldnames\n }\n }\n return [ \"Unknown message\" ]\n}\n\n// Get the fields for the give message id\nvalues = get_fields(id)\n", + "values": [ + { + "label": "", + "value": "" + } + ], + "json": "", + "url": "", + "resource": "" + }, + "id": "e89jpto", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": null, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "onlyAvailableItems": false + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [], + "authenticate": false, + "ignoreCache": false, + "template": "{{ item.label }}", + "idPath": "id", + "clearOnRefresh": false, + "limit": 100, + "valueProperty": "", + "lazyLoad": true, + "filter": "", + "searchEnabled": true, + "searchDebounce": 0.3, + "searchField": "", + "minSearch": 0, + "readOnlyValue": false, + "selectFields": "", + "selectThreshold": 0.3, + "uniqueOptions": false, + "fuseOptions": { + "include": "score", + "threshold": 0.3 + }, + "indexeddb": { + "filter": {} + }, + "customOptions": {}, + "useExactSearch": false + }, + { + "label": "Scale factor", + "tooltip": "Scale factor applyed to value, for example to change units", + "key": "scaleFactor", + "type": "number", + "input": true, + "tableView": false, + "defaultValue": 1, + "id": "ecvia63", + "placeholder": "", + "prefix": "", + "customClass": "", + "suffix": "", + "multiple": false, + "protected": false, + "unique": false, + "persistent": true, + "hidden": false, + "clearOnHide": true, + "refreshOn": "", + "redrawOn": "", + "modalEdit": false, + "dataGridLabel": false, + "labelPosition": "top", + "description": "", + "errorLabel": "", + "hideLabel": false, + "tabindex": "", + "disabled": false, + "autofocus": false, + "dbIndex": false, + "customDefaultValue": "", + "calculateValue": "", + "calculateServer": false, + "widget": { + "type": "input" + }, + "attributes": {}, + "validateOn": "change", + "validate": { + "required": false, + "custom": "", + "customPrivate": false, + "strictDateValidation": false, + "multiple": false, + "unique": false, + "min": "", + "max": "", + "step": "any", + "integer": "" + }, + "conditional": { + "show": null, + "when": null, + "eq": "" + }, + "overlay": { + "style": "", + "left": "", + "top": "", + "width": "", + "height": "" + }, + "allowCalculateOverride": false, + "encrypted": false, + "showCharCount": false, + "showWordCount": false, + "properties": {}, + "allowMultipleMasks": false, + "addons": [] + } + ] + }, + "form_content": { + }, + "sandbox": "// Add label\nconst label = document.createElement(\"span\")\nlabel.appendChild(document.createTextNode(options.label))\ndiv.appendChild(label)\ndiv.style.textAlign = \"center\"\n\n// Div for main value\nconst value_div = document.createElement(\"div\")\nvalue_div.style.position = \"absolute\"\nvalue_div.style.top = 0\nvalue_div.style.bottom = 0\nvalue_div.style.left = 0\nvalue_div.style.right = 0\ndiv.appendChild(value_div)\n\n// Svg for value\nconst svg = document.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\")\nsvg.style.height = \"100%\"\nsvg.style.width = \"100%\"\nsvg.style.fill = options.color\nvalue_div.appendChild(svg)\n\nconst text = document.createElementNS(\"http://www.w3.org/2000/svg\", \"text\")\ntext.innerHTML = \"-\"\nsvg.appendChild(text)\n\nfunction resize() {\n const bbox = text.getBBox()\n svg.setAttribute('viewBox', [bbox.x, bbox.y, bbox.width, bbox.height].join(' '));\n\n value_div.style.top = label.getBoundingClientRect().height + \"px\"\n}\n\nresize()\n\n// Runtime function\nhandle_msg = function (msg) {\n\n // Check message ID\n if (msg._id != options.message) {\n return\n }\n\n // Check for field\n if (!(options.field in msg)) {\n throw new Error(\"No feild \" + options.field + \" in \" + msg._name)\n }\n\n let value = msg[options.field]\n value *= options.scaleFactor\n\n text.innerHTML = value.toFixed(Math.round(options.decimalPlaces))\n\n resize()\n\n}\n\n// Optional function to allow run-time update of options\nhandle_options = function (opts) {\n // update color and text\n svg.style.fill = opts.color\n label.innerText = opts.label\n\n // Update the runtime value of options\n options = opts\n}\n", + "about" : { + "name": "Value", + "info": "Value example built using the Sandbox widget. User customsable options." + } + } + } +} \ No newline at end of file diff --git a/MAVLinkDashboard/WidgetEdit.js b/MAVLinkDashboard/WidgetEdit.js new file mode 100644 index 00000000..03153ae8 --- /dev/null +++ b/MAVLinkDashboard/WidgetEdit.js @@ -0,0 +1,505 @@ + +let editor = null +let form_builder = null +let test_grid = null + +function init_editor() { + + const script_tab = document.getElementById('JSEditor') + const form_tab = document.getElementById('FormEditor') + + require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@latest/min/vs' }}); + + window.MonacoEnvironment = { + getWorkerUrl: function(workerId, label) { + return `data:text/javascript;charset=utf-8,${encodeURIComponent(` + self.MonacoEnvironment = { + baseUrl: 'https://unpkg.com/monaco-editor@latest/min/' + }; + importScripts('https://unpkg.com/monaco-editor@latest/min/vs/base/worker/workerMain.js');` + )}` + } + } + + require(["vs/editor/editor.main"], function () { + editor = monaco.editor.create(script_tab, { + language: 'javascript', + theme: 'vs-dark', + }) + }) + + // Test grid + const columns = 5 + const rows = 5 + test_grid = GridStack.init({ + float: true, + disableDrag: true, + disableResize: true, + column: columns, + row: rows, + cellHeight: (100 / rows) + "%", + disableOneColumnMode: true, + alwaysShowResizeHandle: true + }, document.getElementById("TestGrid")) + + // Edit buttons + const edit_overlay = document.getElementById("edit_overlay") + const edit_icon = edit_overlay.querySelector(`svg[name="edit"]`) + const lock_icon = edit_overlay.querySelector(`svg[name="lock"]`) + + // Handle edit button clicks + function edit_click(b) { + edit_icon.setAttribute("display", !b ? "block" : "none") + lock_icon.setAttribute("display", b ? "block" : "none") + + if (b) { + test_grid.enable() + } else { + test_grid.disable() + } + + for (const widget of test_grid.getGridItems()) { + widget.set_edit(b) + } + } + + edit_icon.onclick = () => { edit_click(true) } + lock_icon.onclick = () => { edit_click(false) } + + // Load MAVlink messages for select + const MAVLink_select = [] + for (const msg_map of Object.values(mavlink20.map)) { + const msg = new msg_map.type + const id = String(msg._id) + MAVLink_select.push({ value: id, label: msg._name + " (" + id + ")"}) + } + + // Sort alphabetically + MAVLink_select.sort((a, b) => + a.label.localeCompare(b.label) + ) + + // Custom select for MAVLink messages + const msg_select_component = Formio.Components.components.select + const msg_select_edit_form = Formio.Components.components.select.editForm() + + class Mavlinkmsg extends msg_select_component { + static schema(...extend) { + return msg_select_component.schema({ + type: 'mavlinkmsg', + label: 'mavlinkmsg', + key: 'mavlinkmsg', + data: { + values: MAVLink_select + } + }, ...extend) + } + + static get builderInfo() { + return { + title: 'MAVLink message', + icon: 'envelope', + group: 'basic', + documentation: '/userguide/#textfield', + weight: 0, + schema: Mavlinkmsg.schema() + } + } + + static editForm = function () { + return msg_select_edit_form + } + } + + // Custom select for MAVLink fields + const field_select_component = Formio.Components.components.select + const field_select_edit_form = Formio.Components.components.select.editForm() + + field_select_edit_form.components[0].components[1].components.unshift( + { + label: "MAVLink message input key", + widget: "choicesjs", + description: "Key for a MAVLink message item, field options are populated from this item", + tableView: true, + dataSrc: "custom", + data: { + custom: +`// Search the form and add the key of any mavlinkmsg items +values = [] + +function recursive_search(obj) { + if (obj.type == "mavlinkmsg") { + values.push(obj.key) + } + + if (!("components" in obj)) { + return + } + for (let comp of obj.components) { + recursive_search(comp) + } +} + +recursive_search(instance.options.editForm) + +if (values.length == 0) { + values = ["No MAVLink message items found"] +} +` + }, + validateWhenHidden: false, + key: "MAVLinkMsgSelect", + type: "select", + input: true + } + ) + + class Mavlinkfield extends field_select_component { + static schema(...extend) { + return field_select_component.schema({ + type: 'mavlinkfield', + label: 'mavlinkfield', + key: 'mavlinkfield', + dataSrc: "custom", + data: { + custom: +` +// Get the target key +if (component.MAVLinkMsgSelect == undefined) { + return [ "Invalid MAVLink message item key" ] +} +const key = component.MAVLinkMsgSelect + +// Get the value of form item with that key +const id = submission.data[component.MAVLinkMsgSelect] + +// Function to get fields for given message id +function get_fields(id) { + for (const msg_map of Object.values(mavlink20.map)) { + const msg = new msg_map.type + if (String(msg._id) == id) { + return msg.fieldnames + } + } + return [ "Unknown message" ] +} + +// Get the fields for the give message id +values = get_fields(id) +` + }, + }, ...extend) + } + + static get builderInfo() { + return { + title: 'MAVLink field', + icon: 'envelope', + group: 'basic', + documentation: '/userguide/#textfield', + weight: 0, + schema: Mavlinkfield.schema() + } + } + + static editForm = function () { + return field_select_edit_form + } + } + + // Custom input for color + const input_component = Formio.Components.components.input + const input_edit_form = Formio.Components.components.input.editForm() + + class Color extends input_component { + static schema(...extend) { + return input_component.schema({ + type: 'color', + label: 'color', + key: 'color', + inputType: 'color', + mask: false, + data: "#000000" + }, ...extend) + } + + static get builderInfo() { + return { + title: 'Color picker', + icon: 'palette', + group: 'basic', + documentation: '/userguide/#textfield', + weight: 0, + schema: Color.schema() + }; + } + + static editForm = function () { + return input_edit_form + } + + // Fix annoying warning about value="" being invalid for color inputs + setValue(e) { + if (e == "") { + e = "#000000" + } + super.setValue(e) + } + + renderElement(value, index) { + let ret = super.renderElement(value, index) + return ret.replace('value=""', 'value="#000000"') + } + } + + Formio.use({ + components: { + mavlinkmsg: Mavlinkmsg, + mavlinkfield: Mavlinkfield, + color: Color + } + }) + + // Setup FormIO + const options = { + noDefaultSubmitButton: true, + builder: { + advanced: false, + premium: false, + data: false, + basic: { + title: 'Inputs', + default: true, + components: { + password: false, + button: false, + textarea: false, + file: { + title: 'file', + key: 'file', + icon: 'file', + schema: { + label: 'Upload', + type: 'file', + key: 'file', + input: true, + storage: 'base64', + } + }, + } + }, + layout: { + default: true, + components: { + content: false, + well: false, + } + }, + }, + } + + // Strip formio builder components to remove lots of the options + // Only include what is given in the white list object + function strip_component(obj, white_list, black_list) { + + function strip_array(obj, list) { + return obj.filter(comp => list.includes(comp.key)) + } + + // Get display tab of edit form + const item = obj.editForm() + + // Strip tabs + const tabs = Object.keys(white_list) + item.components[0].components = strip_array(item.components[0].components, tabs) + + // Strip items from tabs + for (let comp of item.components[0].components) { + if (white_list[comp.key] == null) { + continue + } + comp.components = strip_array(comp.components, white_list[comp.key]) + } + + // Strip any component with a key value in the list + function recursive_strip(obj, keys_to_remove) { + if (!("components" in obj)) { + return + } + obj.components = obj.components.filter(comp => !keys_to_remove.includes(comp.key)) + + for (let comp of obj.components) { + recursive_strip(comp, keys_to_remove) + } + } + + if (black_list != null) { + recursive_strip(item, black_list) + } + + // Replace function to return striped object + obj.editForm = function () { + return item + } + + } + + strip_component(Formio.Components.components.textfield, { display: ['label', 'description', 'tooltip'], data: ["defaultValue"], api: ["key"] }) + strip_component(Formio.Components.components.number, { display: ['label', 'description', 'tooltip'], data: ["defaultValue"], api: ["key"] }) + strip_component(Formio.Components.components.checkbox, { display: ['label', 'description', 'tooltip'], data: ["defaultValue"], api: ["key"] }) + strip_component(Formio.Components.components.selectboxes, { display: ['label', 'description', 'tooltip'], data: ["defaultValue", "values"], api: ["key"] }, "shortcut") + strip_component(Formio.Components.components.select, { display: ['label', 'description', 'tooltip'], data: ["defaultValue", "data.values"], api: ["key"] }) + strip_component(Formio.Components.components.file, { display: ['label', 'description', 'tooltip'], api: ["key"] }) + strip_component(Formio.Components.components.radio, { display: ['label', 'description', 'tooltip'], data: ["defaultValue", "values"], api: ["key"] }, "shortcut") + strip_component(Formio.Components.components.mavlinkmsg, { display: ['label', 'description', 'tooltip'], data: ["defaultValue"], api: ["key"] }) + strip_component(Formio.Components.components.mavlinkfield, { display: ['label', 'description', 'tooltip'], data: ["defaultValue", "MAVLinkMsgSelect"], api: ["key"] }) + strip_component(Formio.Components.components.color, { display: ['label', 'description', 'tooltip'], data: ["defaultValue"], api: ["key"] }) + + strip_component(Formio.Components.components.htmlelement, { display: ['label', 'tag', 'content'], api: ["key"] }) + strip_component(Formio.Components.components.columns, { display: ['label', 'columns', 'tooltip'], api: ["key"] }) + strip_component(Formio.Components.components.fieldset, { display: ['legend', 'tooltip'], api: ["key"] }) + strip_component(Formio.Components.components.panel, { display: ['title', 'tooltip'], api: ["key"] }) + strip_component(Formio.Components.components.table, { display: ['label', "numRows", "numCols"], api: ["key"] }) + strip_component(Formio.Components.components.tabs, { display: ['label', "components"], api: ["key"] }) + + Formio.builder(form_tab, {}, options).then((builder) => { form_builder = builder } ) + + // Tab buttons + const script_tab_button = edit_overlay.querySelector(`input[name="script"]`) + const form_tab_button = edit_overlay.querySelector(`input[name="form"]`) + + function tab_click(button) { + + // Reset everything + script_tab.style.display = "none" + form_tab.style.display = "none" + + script_tab_button.style.backgroundColor = "inherit" + form_tab_button.style.backgroundColor = "inherit" + + script_tab_button.style.color = "#000" + form_tab_button.style.color = "#000" + + + // Select the correct tab + if (button.name == "script") { + script_tab.style.display = "block" + + // Re-enable widget tool tip + for (const widget of test_grid.getGridItems()) { + widget.edit_tip.setProps({ + hideOnClick: true + }) + } + + } else if (button.name == "form") { + form_tab.style.display = "block" + + // Click the edit button of the test grid + edit_icon.onclick() + + // Pop the options menu of the test widget + for (const widget of test_grid.getGridItems()) { + // Show widget and prevent hide + widget.edit_tip.show() + widget.edit_tip.setProps({ + hideOnClick: false + }) + } + + } else { + throw new Error("Unknown tab") + } + + button.style.backgroundColor = "#000" + button.style.color = "#fff" + + } + + script_tab_button.onclick = (e) => { tab_click(e.target) } + form_tab_button.onclick = (e) => { tab_click(e.target) } + +} + +function load_editor(widget) { + + if ((editor == null) || (test_grid == null)) { + return + } + + // Show editor + let edit_overlay = document.getElementById("edit_overlay") + edit_overlay.style.visibility = "" + + // Select script tab + edit_overlay.querySelector(`input[name="script"]`).click() + + // Reset test grid + test_grid.removeAll() + test_grid.disable() + edit_overlay.querySelector(`svg[name="edit"]`).setAttribute("display", "block") + edit_overlay.querySelector(`svg[name="lock"]`).setAttribute("display", "none") + + // Load a copy of the widget onto the test grid + const obj = get_widget_object(widget) + const pos_opts = { + autoPosition: true, + w: obj.w, + h: obj.h + } + + if (!test_grid.willItFit(pos_opts)) { + // If the item won't fit at original size then allow any size + pos_opts.w = null + pos_opts.h = null + } + + let test_widget = new_widget(obj.type, obj.options) + + // Don't allow delete, copy, save or edit in the editor + test_widget.disable_buttons_for_edit() + + test_grid.addWidget(test_widget, pos_opts) + + // Load edit text into editor + editor.setValue(test_widget.get_edit_text()) + + // Update test widget in real time + editor.onDidChangeModelContent(() => { + test_widget.set_edited_text(editor.getValue()) + }) + + // Load form into builder, note that we use the original widget here as the form may not have loaded yet in the new one + form_builder.setForm(widget.get_form_definition()) + + // Update test widget form in real time + form_builder.on('updateComponent', function() { + test_widget.set_form_definition(form_builder.schema) + }) + form_builder.on('removeComponent', function() { + test_widget.set_form_definition(form_builder.schema) + }) + + + // Close edit button + edit_overlay.querySelector(`svg[id="Close"]`).onclick = () => { + + // Call the destroy method on each widget being removed + for (const widget of test_grid.getGridItems()) { + widget.destroy() + test_grid.removeWidget(widget) + } + + // Make sure nothing is left + test_grid.removeAll() + + // Hide overlay + edit_overlay.style.visibility = "hidden" + + // Update original widget + widget.set_edited_text(editor.getValue()) + widget.set_form_definition(form_builder.schema) + + } + +} diff --git a/MAVLinkDashboard/Widgets/Base_Class.js b/MAVLinkDashboard/Widgets/Base_Class.js new file mode 100644 index 00000000..998c47c5 --- /dev/null +++ b/MAVLinkDashboard/Widgets/Base_Class.js @@ -0,0 +1,215 @@ +// Base class for widget +// Adds form in tool tip + +class WidgetBase extends HTMLElement { + + constructor(options, editable) { + super() + + // Stash info used for widget palette + this.about = null + if ((options != null) && ("about" in options)) { + this.about = options.about + } else { + this.about = { name: this.constructor.name } + } + + let form_definition = {} + let form_content = {} + + if ((options != null) && ("form" in options)) { + form_definition = options.form + if ("form_content" in options) { + form_content = options.form_content + } + } + + this.style.display = "flex" + this.edit_enabled = false + + // Popup to show on double click when edit is enabled + this.tippy_div = document.createElement("div") + this.tippy_div.appendChild(document.importNode(document.getElementById('widget_tip_template').content, true)) + + // Add name + this.tippy_div.querySelector(`span[id="NameSpan"]`).innerHTML = this.constructor.name + + // Copy button + this.tippy_div.querySelector(`svg[id="Copy"]`).onclick = () => { + const copy = add_widget(this.gridstackNode.grid, get_widget_object(this)) + if (copy != null) { + copy.init() + } + } + + // Remove button + this.tippy_div.querySelector(`svg[id="Delete"]`).onclick = () => { + this.destroy() + this.gridstackNode.grid.removeWidget(this) + } + + // Close button + this.tippy_div.querySelector(`svg[id="Close"]`).onclick = () => { + this.edit_tip.hide() + } + + // Edit button + let edit_button = this.tippy_div.querySelector(`svg[id="Edit"]`) + if (editable) { + edit_button.onclick = () => { + this.edit_tip.hide() + load_editor(this) + } + } else { + edit_button.style.display = "none" + } + + // Save button + let save_button = this.tippy_div.querySelector(`svg[id="Save"]`) + if (editable) { + save_button.onclick = () => { + this.edit_tip.hide() + save_widget(this) + } + } else { + save_button.style.display = "none" + } + + // Add form + const form_div = this.tippy_div.querySelector(`div[id="form"]`) + Formio.createForm(form_div, form_definition).then((form) => { + // Populate form object and add changed callback + this.form = form + this.form.setForm(form_definition).then(() => { + this.form.setSubmission( { data: form_content } ) + }) + + this.form.on('change', () => { + if (this.form.checkValidity(this.form.submission.data)) { + this.form_changed() + } + }) + }) + + // Hide form with no options + if (Object.values(form_definition).length == 0) { + form_div.style.display = "none" + } + + this.edit_tip = tippy(this, { + content: this.tippy_div, + interactive: true, + trigger: 'manual', + maxWidth: "500px", + appendTo: () => document.body, + popperOptions: { + strategy: 'fixed', + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom', 'right'], + }, + }, + { + name: 'preventOverflow', + options: { + altAxis: true, + tether: false, + }, + }, + ], + }, + }) + + this.ondblclick = (e) => { + if (this.edit_enabled) { + this.edit_tip.show() + } + + // Don't propagate events, this prevents triggering the a sub grid event + e.stopPropagation() + } + + } + + // Handle incoming MAVLink message + MAVLink_msg_handler(msg) {} + + // Enable or disable editing + set_edit(b) { + this.edit_enabled = b + + // Show "move" pointer to user on hover over + this.style.cursor = b ? "move" : "auto" + } + + get_about() { + return this.about + } + + // Get text value to be edited in the editor + get_edit_text() {} + + // Set text that has been edited by the editor + set_edited_text(text) {} + + // Don't want all buttons to work in the editor + disable_buttons_for_edit() { + this.tippy_div.querySelector(`svg[id="Delete"]`).style.display = "none" + this.tippy_div.querySelector(`svg[id="Copy"]`).style.display = "none" + this.tippy_div.querySelector(`svg[id="Save"]`).style.display = "none" + this.tippy_div.querySelector(`svg[id="Edit"]`).style.display = "none" + } + + // Clean up + destroy() { + this.edit_tip.destroy() + } + + // Update form definition + set_form_definition(new_def) { + this.form.setForm(new_def) + + // Hide form with no options + let have_content = true + if ((Object.values(new_def).length == 0) || + !("components" in new_def) || + (new_def.components.length == 0)) { + have_content = false + } + + this.tippy_div.querySelector(`div[id="form"]`).style.display = have_content ? "block" : "none" + } + + // Get current form definition + get_form_definition() { + if (this.form == null) { + return + } + + return this.form.form + } + + // Get options to save + get_options() { + return { form: this.get_form_definition(), form_content: this.get_form_content() } + } + + // Form changed due to user input + form_changed() {} + + // Get the user submission to the form + get_form_content() { + if (this.form == null) { + return + } + + return this.form.submission.data + } + + // Called after the widget has been added its parent main grid + init() {} + +} +customElements.define('widget-base', WidgetBase) diff --git a/MAVLinkDashboard/Widgets/Menu.js b/MAVLinkDashboard/Widgets/Menu.js new file mode 100644 index 00000000..42d9ff01 --- /dev/null +++ b/MAVLinkDashboard/Widgets/Menu.js @@ -0,0 +1,385 @@ +// Menu widget +// Sub grid containing clickable icons + +class WidgetMenu extends WidgetBase { + constructor(options) { + + if (options == null) { + options = {} + } + + // Add a predefined form, color input for border and background + options.form = { + components: + [{ + label: "Border color", + key: "borderColor", + type: "color", + input: true, + tableView: false, + widget: { type: "input" }, + inputType: "color", + mask: false, + data: "#000000", + defaultValue: "#c8c8c8", + id: "ebao4j", + placeholder: "", + prefix: "", + customClass: "", + suffix: "", + multiple: false, + protected: false, + unique: false, + persistent: true, + hidden: false, + clearOnHide: true, + refreshOn: "", + redrawOn: "", + modalEdit: false, + dataGridLabel: false, + labelPosition: "top", + description: "", + errorLabel: "", + tooltip: "", + hideLabel: false, + tabindex: "", + disabled: false, + autofocus: false, + dbIndex: false, + customDefaultValue: "", + calculateValue: "", + calculateServer: false, + attributes: {}, + validateOn: "change", + validate: { + required: false, + custom: "", + customPrivate: false, + strictDateValidation: false, + multiple: false, + unique: false + }, + conditional: { + show: null, + when: null, + eq: "" + }, + overlay: { + style: "", + left: "", + top: "", + width: "", + height: "" + }, + allowCalculateOverride: false, + encrypted: false, + showCharCount: false, + showWordCount: false, + properties: {}, + allowMultipleMasks: false, + addons: [] + }, + { + label: "Background color", + tooltip: "Note that the icons may not show up if the background is too dark.", + key: "backgroundColor", + type: "color", + input: true, + tableView: false, + widget: { type: "input" }, + inputType: "color", + mask: false, + data: "#000000", + defaultValue: "#ffffff", + id: "e6byhel", + placeholder: "", + prefix: "", + customClass: "", + suffix: "", + multiple: false, + protected: false, + unique: false, + persistent: true, + hidden: false, + clearOnHide: true, + refreshOn: "", + redrawOn: "", + modalEdit: false, + dataGridLabel: false, + labelPosition: "top", + description: "", + errorLabel: "", + hideLabel: false, + tabindex: "", + disabled: false, + autofocus: false, + dbIndex: false, + customDefaultValue: "", + calculateValue: "", + calculateServer: false, + attributes: {}, + validateOn: "change", + validate: { + required: false, + custom: "", + customPrivate: false, + strictDateValidation: false, + multiple: false, + unique: false + }, + conditional: { + show: null, + when: null, + eq: "" + }, + overlay: { + style: "", + left: "", + top: "", + width: "", + height: "" + }, + allowCalculateOverride: false, + encrypted: false, + showCharCount: false, + showWordCount: false, + properties: {}, + allowMultipleMasks: false, + addons: [] + }] + } + + super(options, false) + + this.classList.add("grid-stack-item") + this.classList.add("grid-stack-draggable-item") + this.classList.add("grid-stack-sub-grid") + + this.widget_div = document.createElement("div") + this.widget_div.style.border = "5px solid" + this.widget_div.style.borderRadius = "10px" + this.widget_div.style.borderColor = "#c8c8c8" + this.widget_div.style.padding = "5px" + this.widget_div.style.flex = 1 + this.widget_div.style.overflow = "hidden" + + this.widget_div.classList.add("grid-stack-item-content") + this.appendChild(this.widget_div) + + this.size_div = document.createElement("div") + this.size_div.style.position = "absolute" + this.size_div.style.top = 0 + this.size_div.style.left = 0 + this.size_div.style.bottom = 0 + this.size_div.style.right = 0 + this.widget_div.appendChild(this.size_div) + + this.grid_div = document.createElement("div") + this.grid_div.style.border = "none" + this.grid_div.style.width = "100%" + this.grid_div.style.height = "100%" + this.grid_div.style.overflow = "hidden" + + this.size_div.appendChild(this.grid_div) + + this.grid = GridStack.init({ + float: true, + disableOneColumnMode: true, + staticGrid: true + }, this.grid_div) + + } + + init() { + + // AP link + let AP_div = document.createElement("div") + AP_div.appendChild(document.importNode(document.getElementById('AP_link').content, true)) + this.grid.addWidget(AP_div) + + // Github repo link + let GH_div = document.createElement("div") + GH_div.appendChild(document.importNode(document.getElementById('GH_link').content, true)) + this.grid.addWidget(GH_div) + + // Connect button + let Connect_div = document.createElement("div") + Connect_div.appendChild(document.importNode(document.getElementById('Connect_icon_template').content, true)) + this.grid.addWidget(Connect_div) + + // Setting button + let settings_div = document.createElement("div") + settings_div.appendChild(document.importNode(document.getElementById('settings_icon_template').content, true)) + this.grid.addWidget(settings_div) + + // Tip for settings + const settings_icon = settings_div.querySelector(`svg[name="gear"]`) + const settings_tip_div = document.createElement("div") + settings_tip_div.id = "settings_tip_div" + settings_tip_div.appendChild(document.importNode(document.getElementById('settings_tip_template').content, true)) + const settings_tip = tippy(settings_icon, { + content: settings_tip_div, + interactive: true, + trigger: 'manual', + maxWidth: "1000px", + appendTo: () => document.body, + popperOptions: { + strategy: 'fixed', + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['bottom', 'right'], + }, + }, + { + name: 'preventOverflow', + options: { + altAxis: true, + tether: false, + }, + }, + ], + }, + }) + settings_icon.onclick = () => { settings_tip.show() } + + // Close button + settings_tip_div.querySelector(`svg[id="Close"]`).onclick = () => { + settings_tip.hide() + } + + // Edit checkbox + const edit_checkbox = settings_tip_div.querySelector(`input[id="edit_enabled"]`) + edit_checkbox.onclick = () => { + const b = edit_checkbox.checked + + if (b) { + grid.enable() + } else { + grid.disable() + } + + for (const widget of grid.getGridItems()) { + widget.set_edit(b) + } + } + + // Grid dimension inputs + const num_columns = settings_tip_div.querySelector(`input[id="num_columns"]`) + num_columns.value = grid.opts.column + num_columns.onchange = () => { + grid.column(parseInt(num_columns.value), 'list') + } + + const num_rows = settings_tip_div.querySelector(`input[id="num_rows"]`) + num_rows.value = grid.opts.maxRow + num_rows.onchange = () => { + // Can't dynamically change the number of rows, get layout, update rows and re-load + const layout = get_layout() + layout.grid.rows = num_rows.value + load_layout(layout.grid, layout.widgets) + } + + // Background color input + const background_color = settings_tip_div.querySelector(`input[id="background_color"]`) + + // Helper to go from RGB to hex as used by input value + function rgbToHex(rgb) { + const sep = rgb.indexOf(",") > -1 ? "," : " " + rgb = rgb.substr(4).split(")")[0].split(sep) + + function componentToHex(c) { + const hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex + } + return "#" + componentToHex(+rgb[0]) + componentToHex(+rgb[1]) + componentToHex(+rgb[2]) + } + + const dashboard_div = document.getElementById("dashboard") + background_color.value = rgbToHex(dashboard_div.style.backgroundColor) + background_color.onchange = () => { + dashboard_div.style.backgroundColor = background_color.value + } + + // Save button + settings_tip_div.querySelector(`input[id="save_button"]`).onclick = () => { + save_layout() + } + + // Load button + settings_tip_div.querySelector(`input[id="load"]`).onchange = (e) => { + settings_tip.hide() + load_file(e.target) + } + + // Pass the path to be used as the button for connection + let connect_icon = Connect_div.querySelector('svg') + let connect_path = connect_icon.querySelector('path') + let set_color = function(c) { + connect_path.setAttribute("fill", c) + } + setup_connect(connect_icon, set_color) + + // watch for size changes + new ResizeObserver(() => { this.#update_size() }).observe(this) + + // Don't show edit buttons on tool tip + this.disable_buttons_for_edit() + + } + + #update_size() { + if (this.grid == null) { + return + } + + const height = this.size_div.clientHeight + const width = this.size_div.clientWidth + + const max_ar = 1.5 + const min_ar = 1 / max_ar + + const ar = height / width + let columns + if (ar < min_ar) { + // Wide and thin + columns = 4 + } else if (ar > max_ar) { + // Tall and narrow + columns = 1 + } else { + // Square ish + columns = 2 + } + + // Set number of columns and row height + this.grid.column(columns) + this.grid.cellHeight(Math.floor((height * columns) / 4) + "px") + + // Place each item + const widgets = this.grid.getGridItems() + for (let i = 0; i + + + + + Sandbox + + + + + + diff --git a/MAVLinkDashboard/Widgets/SandBox.js b/MAVLinkDashboard/Widgets/SandBox.js new file mode 100644 index 00000000..04248acf --- /dev/null +++ b/MAVLinkDashboard/Widgets/SandBox.js @@ -0,0 +1,124 @@ +// Sandbox widget +// Loads iframe and sends messages to it + +class WidgetSandBox extends WidgetBase { + constructor(options) { + + if (options == null) { + options = {} + } + + // Add info used in palette tool tip + // This can be added manual for custom widgets by editing the JSON + if (!("about" in options)) { + options.about = { + name: "Sandbox", + info: "Sandboxed widget allowing user defined functionality with JavaScript. User input using Formio form." + } + } + + super(options, true) + + // Simple example by default + this.script_text = `// Initialization +div.appendChild(document.createTextNode("Widget Example:")) +div.appendChild(document.createElement("br")) + +message_report = document.createTextNode("No Data") +div.appendChild(message_report) + +// Runtime function +handle_msg = function (msg) { + message_report.nodeValue = "Got: " + msg._name +} +` + + // Load provided script if available + if ((options != null) && ("sandbox" in options)) { + this.script_text = options.sandbox + } + + // Sandboxed iframe for user content + this.iframe = document.createElement("iframe") + this.iframe.sandbox = 'allow-scripts' + this.iframe.src = 'Widgets/SandBox.html' + this.iframe.scrolling="no" + this.iframe.style.border = "none" + this.iframe.style.width = "100%" + this.iframe.style.height = "100%" + this.iframe.style.overflow = "hidden" + + // Send over user config as soon as iframe is loaded + this.iframe.addEventListener("load", (e) => { + this.init() + }) + + this.appendChild(this.iframe) + + } + + // Init the iframe with the user script and options + init() { + if (this.iframe.contentWindow == null) { + return + } + + const data = { + script: this.script_text, + options: this.get_form_content() + } + this.iframe.contentWindow.postMessage(data, '*') + } + + MAVLink_msg_handler(msg) { + if (this.iframe.contentWindow == null) { + return + } + this.iframe.contentWindow.postMessage( { MAVLink: msg }, '*') + } + + set_edit(b) { + super.set_edit(b) + + // Disable pointer events when editing, this allows the boxes to be dragged + this.iframe.style.pointerEvents = this.edit_enabled ? "none" : "auto" + + } + + get_options() { + const options = super.get_options() + options.sandbox = this.script_text + return options + } + + // Get text value to be edited in the editor + get_edit_text() { + return this.script_text + } + + // Set text that has been edited by the editor + set_edited_text(text) { + this.script_text = text + this.init() + } + + // Clean up + destroy() { + this.edit_tip.destroy() + this.removeChild(this.iframe) + super.destroy() + } + + // Form changed due to user input, send to iframe + form_changed() { + if (this.iframe.contentWindow == null) { + return + } + const data = { + options: this.get_form_content() + } + this.iframe.contentWindow.postMessage(data, '*') + } + +} +customElements.define('widget-sand-box', WidgetSandBox) diff --git a/MAVLinkDashboard/Widgets/SubGrid.js b/MAVLinkDashboard/Widgets/SubGrid.js new file mode 100644 index 00000000..7f3aba10 --- /dev/null +++ b/MAVLinkDashboard/Widgets/SubGrid.js @@ -0,0 +1,351 @@ +// SubGrid widget +// Adds sub grid + +class WidgetSubGrid extends WidgetBase { + constructor(options) { + + if (options == null) { + options = {} + } + + // Add a predefined form, grid size and color input for border and background + options.form = { + components: + [{ + label: "Rows", + tooltip: "Number of rows in this subgrid.", + applyMaskOn: "change", + mask: false, + tableView: false, + delimiter: false, + requireDecimal: false, + inputFormat: "plain", + truncateMultipleSpaces: false, + validate: { + min: 1, + max: 12 + }, + validateWhenHidden: false, + key: "rows", + type: "number", + input: true, + defaultValue: 2, + decimalLimit: 0 + }, + { + label: "Columns", + tooltip: "Number of columns in this subgrid.", + applyMaskOn: "change", + mask: false, + tableView: false, + delimiter: false, + requireDecimal: false, + inputFormat: "plain", + truncateMultipleSpaces: false, + validate: { + min: 1, + max: 12 + }, + validateWhenHidden: false, + key: "columns", + type: "number", + input: true, + defaultValue: 2, + decimalLimit: 0 + }, + { + label: "Border color", + key: "borderColor", + type: "color", + input: true, + tableView: false, + widget: { type: "input" }, + inputType: "color", + mask: false, + data: "#000000", + defaultValue: "#c8c8c8", + id: "ebao4j", + placeholder: "", + prefix: "", + customClass: "", + suffix: "", + multiple: false, + protected: false, + unique: false, + persistent: true, + hidden: false, + clearOnHide: true, + refreshOn: "", + redrawOn: "", + modalEdit: false, + dataGridLabel: false, + labelPosition: "top", + description: "", + errorLabel: "", + tooltip: "", + hideLabel: false, + tabindex: "", + disabled: false, + autofocus: false, + dbIndex: false, + customDefaultValue: "", + calculateValue: "", + calculateServer: false, + attributes: {}, + validateOn: "change", + validate: { + required: false, + custom: "", + customPrivate: false, + strictDateValidation: false, + multiple: false, + unique: false + }, + conditional: { + show: null, + when: null, + eq: "" + }, + overlay: { + style: "", + left: "", + top: "", + width: "", + height: "" + }, + allowCalculateOverride: false, + encrypted: false, + showCharCount: false, + showWordCount: false, + properties: {}, + allowMultipleMasks: false, + addons: [] + }, + { + label: "Background color", + tooltip: "", + key: "backgroundColor", + type: "color", + input: true, + tableView: false, + widget: { type: "input" }, + inputType: "color", + mask: false, + data: "#000000", + defaultValue: "#ffffff", + id: "e6byhel", + placeholder: "", + prefix: "", + customClass: "", + suffix: "", + multiple: false, + protected: false, + unique: false, + persistent: true, + hidden: false, + clearOnHide: true, + refreshOn: "", + redrawOn: "", + modalEdit: false, + dataGridLabel: false, + labelPosition: "top", + description: "", + errorLabel: "", + hideLabel: false, + tabindex: "", + disabled: false, + autofocus: false, + dbIndex: false, + customDefaultValue: "", + calculateValue: "", + calculateServer: false, + attributes: {}, + validateOn: "change", + validate: { + required: false, + custom: "", + customPrivate: false, + strictDateValidation: false, + multiple: false, + unique: false + }, + conditional: { + show: null, + when: null, + eq: "" + }, + overlay: { + style: "", + left: "", + top: "", + width: "", + height: "" + }, + allowCalculateOverride: false, + encrypted: false, + showCharCount: false, + showWordCount: false, + properties: {}, + allowMultipleMasks: false, + addons: [] + }] + } + + // Add info used in palette tool tip + options.about = { + name: "Subgrid", + info: "Nestable sub grid widget" + } + + super(options, true) + + this.classList.add("grid-stack-item") + this.classList.add("grid-stack-draggable-item") + this.classList.add("grid-stack-sub-grid") + + this.widget_div = document.createElement("div") + this.widget_div.style.border = "5px solid" + this.widget_div.style.borderRadius = "10px" + this.widget_div.style.borderColor = "#c8c8c8" + this.widget_div.style.padding = "5px" + this.widget_div.style.flex = 1 + this.widget_div.style.overflow = "hidden" + + this.widget_div.classList.add("grid-stack-item-content") + this.appendChild(this.widget_div) + + this.size_div = document.createElement("div") + this.size_div.style.position = "absolute" + this.size_div.style.top = 0 + this.size_div.style.left = 0 + this.size_div.style.bottom = 0 + this.size_div.style.right = 0 + this.widget_div.appendChild(this.size_div) + + this.grid_div = document.createElement("div") + this.grid_div.style.border = "none" + this.grid_div.style.width = "100%" + this.grid_div.style.height = "100%" + this.grid_div.style.overflow = "hidden" + + this.size_div.appendChild(this.grid_div) + + // Don't show edit button on tool tip + this.tippy_div.querySelector(`svg[id="Edit"]`).style.display = "none" + + // Load grid if size options given + this.widgets_to_load = null + if (("form_content" in options) && ("rows" in options.form_content) && ("columns" in options.form_content)) { + this.grid_rows = options.form_content.rows + this.grid_columns = options.form_content.columns + this.load_grid() + + // Load provided widgets + if ("widgets" in options) { + this.widgets_to_load = options.widgets + } + } + } + + init() { + super.init() + // Widgets are loaded after this widget has been loaded into its grid + // This guarantees this widget has some size which the the sub widgets can inherit + if (this.widgets_to_load != null) { + load_widgets(this.grid, this.widgets_to_load) + this.widgets_to_load = null + } + } + + + load_grid() { + + // Stash widgets for reload after resize + let widgets + if (this.grid != null) { + widgets = get_widgets(this.grid) + } + + // Clear existing grid + clear_grid(this.grid) + + // Create new grid + this.grid = GridStack.init({ + float: true, + disableDrag: true, + disableResize: true, + column: this.grid_columns, + row: this.grid_rows, + cellHeight: (100 / this.grid_rows) + "%", + disableOneColumnMode: true, + alwaysShowResizeHandle: true, + acceptWidgets: function(widget) { + const name = widget.constructor.name + if (name == "WidgetMenu") { + // Don't allow the menu to be moved into sub grid + return false + } + return true + } + }, this.grid_div) + + // Replace widgets + if (widgets != null) { + load_widgets(this.grid, widgets) + } + + // Re-apply edit to grid and widgets + this.set_edit(this.edit_enabled) + + // Bind dropped callback + this.grid.on('dropped', widget_dropped) + } + + // Set edit enable / disable + set_edit(b) { + super.set_edit(b) + + if (this.grid == null) { + return + } + + // Sub grid and its sub widgets inherit the enable of the widget + if (b) { + this.grid.enable() + } else { + this.grid.disable() + } + + for (const widget of this.grid.getGridItems()) { + widget.set_edit(b) + } + } + + get_options() { + return { + form_content: this.get_form_content(), + widgets: get_widgets(this.grid) + } + } + + // Form changed due to user input + form_changed() { + const options = this.get_form_content() + this.widget_div.style.borderColor = options.borderColor + this.widget_div.style.backgroundColor = options.backgroundColor + + // Reload grid if size changed + if (("rows" in options) && ("columns" in options) && ((options.rows != this.grid_rows) || (options.columns != this.grid_columns))) { + this.grid_rows = options.rows + this.grid_columns = options.columns + this.load_grid() + } + } + + destroy() { + clear_grid(this.grid) + super.destroy() + } + +} +customElements.define('widget-subgrid', WidgetSubGrid) diff --git a/MAVLinkDashboard/index.html b/MAVLinkDashboard/index.html new file mode 100644 index 00000000..40a4a5a9 --- /dev/null +++ b/MAVLinkDashboard/index.html @@ -0,0 +1,204 @@ + + + + + + + MAVLink Dashboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + diff --git a/images/AP_Square.png b/images/AP_Square.png new file mode 100644 index 0000000000000000000000000000000000000000..e1261ddd71b6417cbb9bb7b2cd23ba29295c861f GIT binary patch literal 16070 zcmeIZbx>SS6E?c|;_kLA?oM!*MS~`ISYUB?cMA|K5G=S`AZTz14he3-1Hql(ddctg zx?k0;x9WcXy?biq^mISd^Gr{lJ=42y)zuU*(a6vM005>UL{{_p{mt`}3yA#u>UmV` z0|1cm_-O08X+k|gPA-lX)^;$Eo3|4T1oN`C006uesxtMG44OUUp19Gl;ku+mDC%*u zgCma{x>ZsNCtbf}SFt4$yl(@j)se4#5P7=DnR`G9R5}lb+TaBFmZ;cxlo_-@ePc3_UY%FqAt?%`wuKBuI+;(jW8;r6!g{(@M{->CD! zBk$Uq=R2hOYA*bCE}ShO0JnpKOMK+4sOk1M-qwxi@o`H1*qf5b>42=u)kmYZE2FVy z+*IgW-h44RsA-Q3mZ8M&228?22S)eJ!w0NZmBFCE=&5*#EK(y?{)bbUPV>!yiq+bg zj?f3+M$P-%s0X57XB#*1$E^v0ci|5t5|5%THgBre9gpYKC3tVyZ*P5%9j>a8aNij;8m142`21Uh1tH)0l7+<1xOwo7UaS8`Ke-w^4orSc8>*-AD#i_7L?Sg! z=sq0r63^a`>-YU`xBAr_5)fC$l0oKWq2z4Hrg}&<_bd+3oK`BdY)aBODLBD(Nri`@ zPTE0#dfcxl;%Xv|)=^{y<=dcaaVJZ2-W=qWQu=LKnT8Clftt{ElC4AYwnk-Pk}`7x zkU*g!!|;2sstW<117xc$W1qvSD{*8qtWToXmum=S-vsT8)0f%m zMg{IDV$SreH+5ba*qm2~?jpXWIj`6Fe5bC!gy<>v`T$B*nD%>n=7~ixoo1eAPBSw)?D# zR+`C*Y^i$rcD^dwH&=lzD_T}?b zJ21OTW?Yq+jWjaHFKv8~5~-?lTpO|(qW$n$M%=g3_yHzpHf)_?$&^}J;==PrLh0`C zaLawt97VVSv@)%;ss-R z@|=wX#u*JcsJfanmMI6Mtjql~yLZWkj1pC2qFed9>)AgXMFh;M4Ly_79`fhq zG6K6#U41ftewt7mwJB`du1#aO1!+0*9!zNIYA+LAew+1#I8eSJ#yFl*ZG$*8R)r4r3j^y*d@nEhiH|JeCW#RrmL{T8SqmMvTxEiBB->wEdCx}` z%Vl0mP2qNSaKajt=#y3OMm0FO5We~Zw>mGCBSb3^Af4qR42kK`{_xwm<%cibP31b5 zhK7+a7r7VjihR*05-6lgl98G$+q!C*i7^OjuR(7vhK~*D?%`n=wE&cxsEbd4qL@W# zJ?Yh6_8Pd6%I0XL1MX6*{md!Ir?c&qI12VKR8?gY9Q^7F#G2vjki5VZLztJ*L)=S2 zf^@4UV?NfX!)WTV$VqUyxI&#h!Zos;+2+SG4HL|sUEXYn2JVr~Dq&rTC7FO|KEMY0WfXjsApt!fm| z{O1NB2|IYtknsK4+F+;U)x-MLcC^yTh0jPYr7Y)X>O%SlGu8R&-Tir~&s1a}_Ls`U zdE$GBtj!R6C`YI5<5z(bLLvBae77BVuOg-eNfTt>q`Un^Y#W;VDbxZ9;9~6rUpiU# z$qS!MmJB9JZPm$uI0A+)EY_E|rL?_REf(g)4_SqWa4^}rq$dx%*Tqfg{69#$I1eGD zuSfvVZO-@=1GA<`Q5a4h&5A2FEVBv2Re77%$Q<~Z3cPs}=rO^}PKfU^5P8>BIG4U_ z1KVT7OGoTa#aWdRW$TepCl^WWgsr^vQ3t0uCc$?xeo1e2a1^?OIJiXW(FW=dSSX#h z@|nII$#F&8B0MpCW4)>G>i2hyz|Yg2b?qZ24^F~>&hqrXsYOmix@`im?$DC!Dc+;N z3TDQK=J43+c3D-v=~QEQk;|m$qKvl|xc}mw(`}WGPCz5WV+Cx}0vS5s+>p9+t0JQ0 zj==PxtDjS1cyZCr!R-?qh^Pypt19X^z>uD}hdMr4L#amqS2$5)cZOpYfXo$)Xz=Hj#MsVd=xq8B3xjC{kly_pI$?jE8^epN7Bnx(C4dOtLZLZz3 z9bL-zu$A<-i1)H7#fqZPfWUJ!0##(c6}Rpi5wO~GoJGhY&ueqoOCA!$;I(iB6D+8; z>n*+u9jYA`pDJiYH#TGDYIy1V9f2w${#Kge;q0KpSRH)AT*T&9qG>hQ8^bLjXz}&) z34WZ^Z^~N4lZ3DD1Y}~r0&(RxO=UyQ;E{P@No0+{%#X3-6$VqYMAMJ^cPdW4^%!C^ zaRq+7g)jSlmB7UYN{%xXMhD~l;{F0 z&U|9I%sEGF#bY=nZ-~i7Z8O)i0iWE^c(iq5cDSMa9wRbr3b-^*3EJNfB!5?J6xlXZ z;pOF-OfH2mUhE{&r?z861no+A{2tGs;+q}|0o33oI_B|V_(t0p=2rsonNJX|B00KM zRKi{{Fa-Hc^?S^&xK+*8z8701%h)9#;U2`=aWdnNaBx3T_w^cu zpEWfoaez0^ovBxwCKUVIR~8nvgU?|R^J2oaK~CYDxYyH5aA(u>e3U(-`C0G74wZ0M zZyQV18B93p9SuYg6W)c-xt$)2zK&F!r#s;P1=y?g`NnYDBK{sY7Q&gjvle9;mOM`L z@I4L*)!vW>42bQ{mImI$`V;z0+7sro@ zT66GBm@dB?Seu?W+^OeKuS88gi9*f`L3&3Z@+vP5XIK&tuChj-DGXs+2jczY%N{d~ zYba3<&Xh4wz!1+*os4Eew5P_Krxm9^|8fwzJaCHh20|7YMlsv*u@Cn- z=kv#cuc*;xBSVs36c77%=O)~}prFl>IG@KBk_VUhk|1cLcWvr*2L-jd2{NHOuL+kT zXw7TKIO)hDO2VjIB2%4COT9V(=qO8q+&C`>XE_A1Gx-_fJ~kHgV)fV1$lta=wpGP? z>uca;y=MC$!MOS_nRO03Wf0R`8VDs9*7yMih53qNw@kQ3vDqXQkqv2Gdu!HnWrW@7 zCYoUQ25?2Z^#E#_am=DVTx#8yTYMTX;Tm9VYbTCcqJ_ zAsT5=-VT@s+xWs(tRV5(yJBYL{-?~M(Jw@?k4TdHO~K!dv0+QeWVrfqjAE=lI0d0c zn6b4D0r3XiHW*=ivPMOvOoRvUbv3a#3?$ZyQyw>gYdOX}zjB>v`!Lid@(ao1hhuMI z8;`!`qk|0NVN{dZ`MyFk-<1r_X7`N^;VlqwqM3+syCDa9X7K)4wCgYNoTnH!V28 zZPi!7tknpANan7MyLRbJ)xx4X-MA!!MP9*GYQimeWiQmoTsV9w`j+=0SBUqT9AGtm zL}Hz~PvsPF{^P!Ip@!ytW!{I=SLZ!H(916uc!1gT$cNqVIe0tM2&NwpG3>M0%be_^ zQ|bm0&~Ah~B-_7>6|$%_>dsctoDX+xbYm|TxEm}w!&M-3B4!F;L&}qp6lK;aMajIH z3`ah za3n=y@F1PHO1Zh(`Ig$H^0QDi|Mf z8!Bm5@zkU9CoO7NvYPQst2gLAiR0j^kC#)$%pr%9hkc?OS4Lp;d?nu^mh9XQv{evW-Z&w8-6;yh@lJ`&?w`9HouOA7XU#$-V*K@Yp>RR1tN^M zQe;s{wOgCWA95J6cb67FG>gTg(7B=O~LGX zK*5loN~=oB<-As%Sg%3&z9;=dnj$s4tC7+P%_;^&ULanG;Z_Cz+8AsN9v*_F2Hm{_2t;HKnT zEQgEsMyFIs*@C_5ZCmrIgwBG3wJLHCbz#3a@K_7^ul4uT@#tB*DqJJ{WVeI$l=pyN z<0Yq2Ungdx;wSkQSS`zNOHO2qhFpoWm}6{eBHU1ptY*K|=b;d)F(AhkePT~cq!SKB zHWpsieG5N^mn7T<=EaYc*U+2%uDV51k28Pj$MWD4ITo2PijJpKO;)*vLzH6ORYste zI+%stM=B{bh)5G+GSK7h^Rg(OzX)?zlU$f?%0lA+5#xOrO=Rc<3zPP%FYfrMf~lvd zC~DuAo84Z8Y)lQ=Q+gdir^h-}L^+pb>!x1XBhD49;?4^?Dn%Sj__`#_bAH84+s!5B zo7{EChOtU-jJ>>grfi0uR7Oq?EYCVQUBUKOqYpq zk9y8wUKqkaY?Y%%DSU!`1~MO4#N@DFv-gVkc1%arQ#n+0Y<8%RP8pb`oS();uL`ec z7Dh;P&hkn)LR{BF3VCBwKEdQDMOAmXWfc>y@5R?e7Eb?n& z`c!=qhXk|tg`j|c0inh!M4zJ9=WcY(&TA2C$7~;fc*e#BpFP7Fi9GWlKu7RV0nXCN z+%js?FH)LoDzbQcF97!f#5)cNi35jt*jOR>5FIJtitCXk(C$Q3rD^F1-Z(&jYv0`Z zr6gm&Xq-lD%<0h^iCVG46XOof>>e!B+1^aX5hkKy<&Nmt=rn8S)oztemVXLH7p0Hx zloh9W9fDOCoGe?t`|*h|$YUl2Ko`%)8{*^Wtk}h6-6MOg%vV4Gz{BA5MOLFgY8r4i zApO|{=`GD%^id9am6gO9TIK+KuxS;b?>Kd$|XHIIHi$YG!^|cjC1TF^xYB(6P~$8JGgF zjAfT%WXh1Q6aBD__LqW-$xd;EiSj~A0M3){H?B04-udGKiWPWH2HWKGMqc)LX+iEX zjv1GXN@#Q>%s!g2bYNaPDVCJ#V3p^p}yM-AW_9dZh(mseg%C;O>CV=MGcW%$aa5FufG=LtDZ?I4fsQ>jhggDbewBB_BBHX zZcwSnYPEG=g_dOkk$2YS2NxO#WYx2H_Cbj;l3j|Z4riV-rmxOpUFF}u^|>G3(Dzyc zZSo11g}M#C8d~J6{K9X+Fq^J_H)$x8wb1ir2ePjO6d+SYAItf!Jh;i+P#%o}`>C?b zesXho_M6w%?!!4Mk^RQ7?{M;uYb<15c#aH`FtR&>LM+}K5aOV=mbrH+PA zTNnivl9|y8_949L%l!7D2*)7GYp-*A}x*;U|OsllDq2X2HfDdHTl*d2W{? zVFyF?GkCm^@QLUz3~l&??1iCh4RvIaM6yf8lZjc1aQR1zWxBu}J8NYWi3UC6(|2oh z%S(9^d@AxQZ3Yj6VoCfB3Jgf?=X!;sK*KLl709}~gVYXPNPGQjDq`eb4b={qQ|t;{ zunBffAb@7B8JAL}hhqWTq%V4pjXOxKi7FOTE2b;KVTMIGX;4PDV*(ziT~F{3V?T%w zSE^|H`{g3{P}a6jf+Ir3C_&Fdl8!U&zPm`i<*?`KiUeV#t?!uLl`nlt_!1r#Pj9@n z<`P2!+&b=4ZtGis*2D#kxlWVBlY7}oBlSADCT{0Q9(_k3rj1D_zDu-6s8++x-3)xW z8Np)(uXiw4PK%2Cb|UZ_Vn@zO1fBT67j=}>sQ_wqT>vX&FQ&@E0%kRYRj22>q1|+_Smvzmz+~KATtob`o zTvI!`-M*1{@+F>JNqA%oenO777L`MN%b8A|O`iZdh9zFrDe{(|e8bS_6VE1luDrA>*cf*?|DV#c?`%iwkZF>4Of~0qchXses4tBYK$w%iHsxF)T<(v zt*_GK>bKl8FxLtkI}^X!-qCVlK=1>fQcPjSc!hddOicozl+1 zQdb3uDTur*1|f{wZ)JK-^WJR=SjfrHXER{2mdoEr_rt<}6InJG!oK^S5hJ@UC`Iq{ zDd6;q%rjanrOEEZP>8kYs(?#H@PX3t*lbsaG7HhdrwhrewKj@kdfa156!(l!2` zgEWcu#v1KL^dvxjH5m!nF*uvQscB1GjXH?Wy}=KGf&$(QY1sJ6%QLsVDR7MM?hej# z)uk-%Q-#WNna{)extyn~sv>OWXwLyPcQl1@c-cEWm-_$!Q3)?6sF^Ly4P*+lv~~~! zAGdabLDuGC;Me@BT&hkoFe___j|)u8M@`$z$JR{994sM@Ch8^p3}6p)gMz&5?HpW% zy~MzO;R-*0{v+lDgZ@%+vlRpDs;Yxz99>`_J`O$(E_OLDYY!fel8gDdSHh`(XT z!d%T>J-IH~Xi(le>%EU&ApszAHMx*`D-};?#Q$GKXLy< z`yXTfOZiz#RaIEl(aimidy2AR;6MEfn>(6Wn+yN-kz0t@49W}TW#{MT=3?hF<%hBh zahsX53&Qv_gR2|V!3_2X>KUBF`WeSUNWj$86vo9a$S1_d&c`nx z$Zjei1Y?JC^Fn!EK!pSa1o{6B;-!oAb5ug@{@trTQ0C82FJPuzra~4k*iB!&uwduo z;}v2T5;BFc!(Lc$3A`{jwRpiT@E4T1nXtU0i#_ytI<4)YmM~5y2g|=2{tzxKrLHIj z=HcM_mqgtT>Spn5@Vo}B9n2j)UH{djZEX+Ja)bWilluh^FCQ1TkRY#sfY9^tFQr#7 z7uV-l{DaEP#lg$-SIeJ?5q=)dGqun^k@^hqm;8A&!ZI!}sGFmUwxgq+82FD=6a{%bLTKz}U@VW`>PhTsbIfSLd0=QGydn#`=A z4wkUz{PFjI{fFH8{}2p9FSz-+P0cLWUzkIo?0j4nyzD~ed;;uHJ^>3pGeJQZ7Zm!R z=&p_yZk|vVn3Ux+kI!5^2k2j1ftdb2Rm}gX?`Z}5GY`*{v2zKs{~Kjs&c6?q^H0Y3 z$7n@4|6hEF{-yA5DdySkZ?fmq^_&Sg|CtK^&exx$^MCR2_j&lg*uyjQ|2p}v`28PU z|D)@_V&K10{$F(ckFNiUf&WVRf6?{-8(nDsx|xDGJbwf7e7;q3f>#rNzRf~5RaTG% zJpDOx+e_X*OHiF4daeKfI{u$093bN($+Hl}O;J@2We13k$3ZOLi}?IgOhR}?St)I= zg+m|Tnj#(dZO6lbE5Yi!qsRz(^(ewnZWu&0sqFKjf@Y~MSrMEFiODcTZ%A-m1)gTu zJxSMF&@A=?g9%lVRgDHh=yPqNI9!wL1Q$HW8oexeb?AF1K6ZR=%$@T3NXZ44UI}y3dadIF!xt4+S5)T2u z2}2&k34;d2grWf#&WhXAKThiV(^ERFL-|x$91nB$AxO>V(E*+k*5$cgfAQ8XA*-lC zEm_mVLXs5+A08f%`|YfC#chi`;57YUH}&bDO8CIrvhn_8pMXf4wd`j(eL^+V4smW8 zM^?QNff_#9*#w}(2&}QvEx5F|YTaEmSWflh$ujh$p9tKQpx)+JNUX3hsYM#SOb1A>roi^jgq_}%k6+=v{%n!mH@^Mx{penBH}VIm3=t3;B-gujqFfO8&K zg6jw=BGH$t(I3C|=e%q-{HCE1<+pB=@5%Wf8x%M|>3I&g-3h*FuIf*ViUNTPT89em zg#2F0g6MCprBV;cZh~Fu?pAI-t^BshrziO}&H|v2lvRH#uQdm%0TK6crDS->nn0oVjZ;)X&c&4Z+a3(?;>he@7X@F4QX;xXCe zx}kzGQ4tw2Wl^lAx`xp9iZd<)wkU?geMQ_?#649)o;aV*W&$fm_P()=`D)nZHprJu zNg2((Oh=6Zc6C=@*xe+?XVLYAZodyW$C&nodLhZ%NQxqcb)hj>(>Q6POz0;}4tIIU zB?c?iPh!n-8P-J&&qT3FN=us5@Bws!@T}l7{Kf+Lwm!dr`*N0s5V$>>MU6?*Mx(l6 ztV6it(K=X*p`#-un+pPI0_H8{3Y(n)gKhmH5UuJrJb2PC-WX3yBSli^w>( zccO*!%Yx{jfOp%>j|5kX=S}C#!CTeQiS32PDY_R&o>}vQ-tX9PaIdxZzathS^3a^=BQgGn5u* z{bqd(gb(aY040nNMlMzyO~fOoLy?yw=&D&~pB3JF9T-Cl0mx_=eHes@ELK+4(2(G8 zxx{Vca^c(V&dm=7kD9g*vvi8WBO@bG`G`uS^@~Q+4I*g@E6lkPrfXstnq=plyy0z$3Ig|F5By#=C6!l&0(h4cnHDpp%J?*aB4u+&GG%J zKsWV7G^o$)&0VZkpqfJO};adg6lc^dY16JaFn$lXK-a2n;p z5R@F04F4WZD{N@x7edFS=RE!xTU>citLt{$$Oj@KPWuM^jZvZcMSH<*ldABj+;XEV z;u)F3oh-|br!Ab~-tGLw^kBhc$pnpDLU^xtw{xjR(6g^BTv6Q^Ojaaj0PuA`98Fh7 zF72lQ{ZfX7VX2uID^ir#R6!%DCn4#IOcn{2U92D`buOGZKpZYz$a}s*-jXH5tHVx8 zo!*5x$ESrmnxFpo8_OQq7trqs?}>4l?DOis+Z=Ze=|BIk+wX-TH37`sJ z1SbWc1t@{^tL<;XK))iFnc~*6LLfod^eRgsH5#S3>BYNY7TwV~C{V!bD#^CcSu&l( zcjZ<926*()b;N8o7Xe9I_>`<>%TH-7Wv&Y_*D)m4-}lIKS*f1~`)I;WJtpPxMbAVd zC27?32P-0egIixFdLJ8%if#mR`u&W^xjo>2bDtAE$dnI~JbRJTVRP1-BmA?k&}yAa zf)fKLI2-(0=){D8VOHoZZ6ppn0EsgMgoT4(A#Vkrmx806&z!)(4k9hC`-X@_cIyEb zM)<|KheI0EYfAnI5gjGtIP1){`g$iRaQ8t0v%>rCuqm)h=*oC|$*Up-%*tYAYf^wH zpfrv+TI;u&-QO%h-DcI(BoVmm&WW9O^Ts;EPCJ(rH9Lupn|$tjAETTHM2n2pba8O21WsM0l{6p z&&k$88kdj7(t9hM^HE+@;9(%fb+1*c^NagtDIjnlF3ZwV?=;&(gp#`P5kRg0#1P!p zXvt?8-Vh1-2|AHYD@yjymmeqJp z#RLYt9@09icpG4vM`vdI;yn!?d7i{ zHP0UVa=SqP)wAg)>dDjL4|8L;+am^|j3>`Cl`65a>mK+%T_m*o`I7n*WVxZcW;CPi z{=4dHvxHsuYPs!iUL9uYgL;GND85;R{x03{^75{46+aTVk-@fe9CSfk)HhDQ?M~*} zajV=gKBa{Qhsy_`{)FZY$K&~^>do~5o5bqSP(0@2YtMr=y6E*)wz{?{8+u8K9$)OO#2MSHv9aUM zf|=ci&8v>){eCSqk%zFTYlPW_*Eos%@&)BA>a-QT`an2$BigtP9W6`oH1l$(C3*;; zEZ&Y&`tshDZh`wI1DZ~=o=1ovU*HTs%S=AQZZ$%P4*1l0welk~-zv!%e(lt|GvnsF zoc$Ae=NZRUVC(JE=dM2&$W;jv3=TQgLNhZXKGiyXQR}M2ZEua&k5okNyX1ivGoEl= z+n*lJe9K!hGCrpH8OqRcDw7*b@n)!)L}0^#YTkh-`xCCHLIC3li-{U~SsZB-{Y6H8ekP)I7xf)FYnhKId~5!a6z-q3 zb5kG`KbaCOkseb!{dyRE+gH40r#VI_6q9n(zN6;z?wPkCr6qg3qqvF(ZIn2mW2fKhZ++!m}=`IRd-3r(1f5% z_5pq%I*aBd+m{s}`CWWte=Lsgb#|e`ZKz`-C!#F?36Wrcz^YSxZ$@el5EdKl@3!I# zz8x@?vJX{;*tqFigqM zw^A~r=GEYXRUQF+Am0#D_`dU`&7I6xc9*p#^X%6MGEGF|Gs*Q#H&J#g$4Z6E^KA}b zT+bQbbxPpK4fV|~I85Tw;`hqqWor(0(MMMV@}^@R z22)IbQ=B-0LDZTz@0V;h&Z8nOW~&T(dR{#CZH(T@nK3Z*=})iYN(Tw_G>3s~JX-^+H`^LtMK1PyfVJRjMZ zrKL&g@t20OabCXeSf>m;3vpd_*)NgsTqeSOVz50Z=2J{f(RiO%w)oC<_uILOFv+>u zR&LcSl64WkZJAiitbTN@n?}ZS75=8{v&MqFGvAy;17kQW38+}8>NL8!(oDErj#MOs z=%JY5)@nk{x~g)eDF%t%KUi+^ZF#|u`xzLHgQ!9i%$_@m#F-`a^XEF>TR1mGXYe|K zmJ{20TbGDBAod!tEw(LNnjF?*O(p=0hFi3c@54wztK*kvhOhTjty$pH(fiQh>?*w% z>KO2Q|Je7{THs^MoKa!QW5GM}7iw6@2w*Y+?t{Ux$e{4%qCNvM#?-dolm>w(O;2uz zsQRZUt?esg^a3luB#15k`y@Jn*J(5L4HA~@JDJ=|~1~waNkdlcTVgZ5Qs}Pffdd6;o zyfT|0(I}E!3rHz0Es!PwIR=?o#9W~rTVCtrleEZZ2EkWs7W2I7K{LoYMqov}8gzA~ zXB$TF)hqXhJjX!aIbw^s(uCcUSg3D{_Q?&oKxM0}Z98+ESv2=0Tc{^>J(c-n=|Bn2n7Mc;}dHyT3?Io73 z$PO$deJs*;Z=UNO-cstHoHKjCv2K10N!W z)qoX5d@7o+g#>_1>^vnQ&*Q|=)a9k@Qs`bO{djDb(P zGefxQ;VWPNie+(TF9}#|9*qzK-<~ORca4ZFVk8Yar))I#KAyBWUk1xrZ}+C+ZQR0q zy>Q?2o(;_l3(_NJ9Ki_5h#aZwgvH{jyjnJs*q>ciKuznaiu}R-wUCXUz416!Rwsxw zOkgC;U~9S0xP2dbb-}ve$-;;oR-Cl-ss?BRVYsc~R>+&7+JnczwXv0pPo9L4!GW78 zIYz4vo}pLDn`h_s66~r7fcsKlhyUH>PG#Mwi!7ap#{pM7pjb@e_&JFI0mHS^dLPOJ z<1l&mPft(n0s>Zwt(CIWELDqZ*p%Ud*haEPZz$xsm!9_l75uKwMmPh9FXjwg-wC@N pZl=EB7-P^9`rEFfnffDkbtRCH;pbcZ=M7STqMVxSCux(A{|D0bgY*CZ literal 0 HcmV?d00001 From 2d68e8d5bbaffc7b9045120e4cf9b8f284c15f44 Mon Sep 17 00:00:00 2001 From: Peter Hall <33176108+IamPete1@users.noreply.github.com> Date: Sat, 7 Sep 2024 22:51:46 +0100 Subject: [PATCH 02/33] MAVLInkDashboard: make popup toolbar larger --- MAVLinkDashboard/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MAVLinkDashboard/index.html b/MAVLinkDashboard/index.html index 40a4a5a9..2fce1d1f 100644 --- a/MAVLinkDashboard/index.html +++ b/MAVLinkDashboard/index.html @@ -49,7 +49,7 @@