diff --git a/package.json b/package.json
index 8e97138e..336197b0 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,10 @@
"scripts": {
"start": "vite",
"build": "vite build",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "lint": "eslint src",
+ "lint:fix": "eslint src --fix",
+ "format": "prettier --write src"
},
"repository": {
"type": "git",
@@ -20,14 +23,17 @@
"dependencies": {
"Leaflet.MultiOptionsPolyline": "hgoebl/Leaflet.MultiOptionsPolyline",
"bootstrap": "~3.4.1",
+ "eslint": "^8.24.0",
+ "eslint-config-prettier": "^8.5.0",
"html2canvas": "^1.0.0-rc.5",
"jquery": "^3.7.1",
"jquery-ui": "^1.13.2",
"leaflet": "^1.9.3",
"leaflet-marker-rotation": "^0.4.0",
"lodash": "^4.17.21",
+ "prettier": "^2.8.1",
"throttle-debounce": "^5.0.0",
- "vite": "^5.2.6",
+ "vite": "^5.4.12",
"vite-plugin-pwa": "^0.19.7"
},
"devDependencies": {
diff --git a/public/js/webworkers/csv-export-worker.js b/public/js/webworkers/csv-export-worker.js
index 3790e961..e09b436e 100644
--- a/public/js/webworkers/csv-export-worker.js
+++ b/public/js/webworkers/csv-export-worker.js
@@ -2,59 +2,59 @@ importScripts("/js/lodash.min.js");
onmessage = function(event) {
- /**
- * Converts `null` and other empty non-numeric values to empty string.
- *
- * @param {object} value is not a number
- * @returns {string}
- */
- function normalizeEmpty(value) {
- return !!value ? value : "";
- }
+ /**
+ * Converts `null` and other empty non-numeric values to empty string.
+ *
+ * @param {object} value is not a number
+ * @returns {string}
+ */
+ function normalizeEmpty(value) {
+ return !!value ? value : "";
+ }
- /**
- * @param {array} columns
- * @returns {string}
- */
- function joinColumns(columns) {
- return _(columns)
- .map(value =>
- _.isNumber(value)
- ? value
- : stringDelim + normalizeEmpty(value) + stringDelim)
- .join(opts.columnDelimiter);
- }
+ /**
+ * @param {array} columns
+ * @returns {string}
+ */
+ function joinColumns(columns) {
+ return _(columns)
+ .map(value =>
+ _.isNumber(value)
+ ? value
+ : stringDelim + normalizeEmpty(value) + stringDelim)
+ .join(opts.columnDelimiter);
+ }
- /**
- * Converts `null` entries in columns and other empty non-numeric values to NaN value string.
- *
- * @param {array} columns
- * @returns {string}
- */
- function joinColumnValues(columns) {
- return _(columns)
- .map(value =>
- (_.isNumber(value) || _.value)
- ? value
- : "NaN")
- .join(opts.columnDelimiter);
- }
+ /**
+ * Converts `null` entries in columns and other empty non-numeric values to NaN value string.
+ *
+ * @param {array} columns
+ * @returns {string}
+ */
+ function joinColumnValues(columns) {
+ return _(columns)
+ .map(value =>
+ (_.isNumber(value) || _.value)
+ ? value
+ : "NaN")
+ .join(opts.columnDelimiter);
+ }
- let opts = event.data.opts,
- stringDelim = opts.quoteStrings
- ? opts.stringDelimiter
- : "",
- mainFields = _([joinColumns(event.data.fieldNames)])
- .concat(_(event.data.frames)
- .flatten()
- .map(row => joinColumnValues(row))
- .value())
- .join("\n"),
- headers = _(event.data.sysConfig)
- .map((value, key) => joinColumns([key, value]))
- .join("\n"),
- result = headers + "\n" + mainFields;
+ let opts = event.data.opts,
+ stringDelim = opts.quoteStrings
+ ? opts.stringDelimiter
+ : "",
+ mainFields = _([joinColumns(event.data.fieldNames)])
+ .concat(_(event.data.frames)
+ .flatten()
+ .map(row => joinColumnValues(row))
+ .value())
+ .join("\n"),
+ headers = _(event.data.sysConfig)
+ .map((value, key) => joinColumns([key, value]))
+ .join("\n"),
+ result = headers + "\n" + mainFields;
+
+ postMessage(result);
- postMessage(result);
-
};
diff --git a/public/js/webworkers/spectrum-export-worker.js b/public/js/webworkers/spectrum-export-worker.js
new file mode 100644
index 00000000..124ba8b7
--- /dev/null
+++ b/public/js/webworkers/spectrum-export-worker.js
@@ -0,0 +1,14 @@
+onmessage = function(event) {
+ const columnDelimiter = event.data.opts.columnDelimiter;
+ const fftOutput = event.data.fftOutput;
+ const spectrumDataLength = fftOutput.length / 2;
+ const frequencyStep = 0.5 * event.data.blackBoxRate / spectrumDataLength;
+
+ let outText = "freq" + columnDelimiter + "value" + "\n";
+ for (let index = 0; index < spectrumDataLength; index += 10) {
+ const frequency = frequencyStep * index;
+ outText += frequency.toString() + columnDelimiter + fftOutput[index].toString() + "\n";
+ }
+
+ postMessage(outText);
+};
diff --git a/src/cache.js b/src/cache.js
index 79b559d6..f7a6829b 100644
--- a/src/cache.js
+++ b/src/cache.js
@@ -1,91 +1,88 @@
/**
* A FIFO cache to hold key-pair mappings. Its capacity will be at least the initialCapacity
- * supplied on creation, which you can increase by increasing the "capacity" property.
- *
+ * supplied on creation, which you can increase by increasing the "capacity" property.
+ *
* One extra element beyond the set capacity will be stored which can be fetched by calling "recycle()".
- * This allows the oldest value to be removed in order to be reused, instead of leaving it to be collected
+ * This allows the oldest value to be removed in order to be reused, instead of leaving it to be collected
* by the garbage collector.
- *
+ *
* Element age is determined by the time it was added or last get()'d from the cache.
*/
export function FIFOCache(initialCapacity) {
- //Private:
- var
- queue = [],
- items = {};
+ //Private:
+ let queue = [],
+ items = {};
- function removeFromQueue(key) {
- for (var i = 0; i < queue.length; i++) {
- if (queue[i] == key) {
- //Assume there's only one copy to remove:
- for (var j = i; j < queue.length - 1; j++) {
- queue[j] = queue[j + 1];
- }
-
- queue.length--;
- break;
- }
+ function removeFromQueue(key) {
+ for (let i = 0; i < queue.length; i++) {
+ if (queue[i] == key) {
+ //Assume there's only one copy to remove:
+ for (let j = i; j < queue.length - 1; j++) {
+ queue[j] = queue[j + 1];
}
+
+ queue.length--;
+ break;
+ }
}
-
- //Public:
- this.capacity = initialCapacity;
-
- /**
- * Remove and return the oldest value from the cache to be reused, or null if the cache wasn't full.
- */
- this.recycle = function() {
- if (queue.length > this.capacity) {
- var
- key = queue.shift(),
- result = items[key];
-
- delete items[key];
-
- return result;
- }
-
- return null;
- };
-
- /**
- * Add a mapping for the given key to the cache. If an existing value with that key was
- * present, it will be overwritten.
- */
- this.add = function(key, value) {
- // Was this already cached? Bump it back up to the end of the queue
- if (items[key] !== undefined)
- removeFromQueue(key);
-
- queue.push(key);
-
- items[key] = value;
-
- while (queue.length > this.capacity + 1) {
- delete items[queue.shift()];
- }
- };
-
- /**
- * Return the value in the cache that corresponds to the given key, or undefined if it has
- * expired or had never been stored.
- */
- this.get = function(key) {
- var item = items[key];
-
- if (item) {
- removeFromQueue(key);
- queue.push(key);
- }
-
- return item;
- };
-
- /**
- * Erase the entire content of the cache
- */
- this.clear = function() {
- queue = [];
- items = {};
- };
-}
\ No newline at end of file
+ }
+
+ //Public:
+ this.capacity = initialCapacity;
+
+ /**
+ * Remove and return the oldest value from the cache to be reused, or null if the cache wasn't full.
+ */
+ this.recycle = function () {
+ if (queue.length > this.capacity) {
+ let key = queue.shift(),
+ result = items[key];
+
+ delete items[key];
+
+ return result;
+ }
+
+ return null;
+ };
+
+ /**
+ * Add a mapping for the given key to the cache. If an existing value with that key was
+ * present, it will be overwritten.
+ */
+ this.add = function (key, value) {
+ // Was this already cached? Bump it back up to the end of the queue
+ if (items[key] !== undefined) removeFromQueue(key);
+
+ queue.push(key);
+
+ items[key] = value;
+
+ while (queue.length > this.capacity + 1) {
+ delete items[queue.shift()];
+ }
+ };
+
+ /**
+ * Return the value in the cache that corresponds to the given key, or undefined if it has
+ * expired or had never been stored.
+ */
+ this.get = function (key) {
+ let item = items[key];
+
+ if (item) {
+ removeFromQueue(key);
+ queue.push(key);
+ }
+
+ return item;
+ };
+
+ /**
+ * Erase the entire content of the cache
+ */
+ this.clear = function () {
+ queue = [];
+ items = {};
+ };
+}
diff --git a/src/configuration.js b/src/configuration.js
index b57049d4..c4933ccd 100644
--- a/src/configuration.js
+++ b/src/configuration.js
@@ -1,186 +1,176 @@
/**
* Configuration
- *
+ *
* Handle loading and display of configuration file
- *
+ *
*/
export function Configuration(file, configurationDefaults, showConfigFile) {
-
- // Private Variables
- var that = this; // generic pointer back to this function
- var fileData; // configuration file information
- var fileLinesArray; // Store the contents of the file globally
-
- function renderFileContentList(configurationList, filter) {
- var li;
-
- // Clear the contents of the list
- $('li',configurationList).remove();
-
- for(var i=0; i
' + fileLinesArray[i] + '');
-
- li = $('' + ((fileLinesArray[i].length==0)?' ':fileLinesArray[i]) + ' '); // Removed default syntax highlighting
- configurationList.append(li);
-
- } else {
- try {
- var regFilter = new RegExp('(.*)(' + filter + ')(.*)','i');
- var highLight = fileLinesArray[i].match(regFilter);
- if(highLight!=null) { // don't include blank lines
- li = $('' + highLight[1] + '' + highLight[2] + ' ' + highLight[3] + ' '); // Removed default syntax highlighting
- configurationList.append(li);
- }
- } catch(e) {
- continue;
- }
- }
- }
- }
-
- function renderFileContents(filter) {
-
- var
- configurationElem = ('.configuration-file'), // point to the actual element in index.html
- configurationDiv = $(''),
- configurationTitle = $('h3', configurationDiv),
- li;
-
- // now replace the element in the index.html with the loaded file information
- $(configurationElem).replaceWith(configurationDiv);
-
- var configurationList = $('.configuration-list');
- renderFileContentList(configurationList, null);
-
- //configurationTitle.text(file.name);
- $("#status-bar .configuration-file-name").text(file.name);
-
- // now replace the element in the index.html with the loaded file information
- $(configurationElem).replaceWith(configurationDiv);
-
-
- // Add close icon
- $(".configuration-close").click(function() {
- if(showConfigFile) showConfigFile(false); // hide the config file
- });
-
-
- }
-
- function loadFile(file) {
-
- var reader = new FileReader();
- fileData = file; // Store the data locally;
-
-
- reader.onload = function(e) {
-
- var data = e.target.result; // all the data
-
- fileLinesArray = data.split('\n'); // separated into lines
-
- renderFileContents();
-
- // Add user configurable file filter
- $(".configuration-filter").keyup(function() {
-
- var newFilter = $(".configuration-filter").val();
-
- var configurationList = $('.configuration-list');
- renderFileContentList(configurationList, newFilter);
-
- });
-
- };
-
- reader.readAsText(file);
+ // Private Variables
+ let that = this; // generic pointer back to this function
+ let fileData; // configuration file information
+ let fileLinesArray; // Store the contents of the file globally
+
+ function renderFileContentList(configurationList, filter) {
+ let li;
+
+ // Clear the contents of the list
+ $("li", configurationList).remove();
+
+ for (let i = 0; i < fileLinesArray.length; i++) {
+ if (!filter || filter.length < 1) {
+ //Everything
+ // li = $('' + fileLinesArray[i] + ' ');
+
+ li = $(
+ `${fileLinesArray[i].length == 0 ? " " : fileLinesArray[i]} `
+ ); // Removed default syntax highlighting
+ configurationList.append(li);
+ } else {
+ try {
+ let regFilter = new RegExp(`(.*)(${filter})(.*)`, "i");
+ let highLight = fileLinesArray[i].match(regFilter);
+ if (highLight != null) {
+ // don't include blank lines
+ li = $(
+ `${highLight[1]}${highLight[2]} ${highLight[3]} `
+ ); // Removed default syntax highlighting
+ configurationList.append(li);
+ }
+ } catch (e) {
+ continue;
+ }
+ }
}
+ }
+
+ function renderFileContents(filter) {
+ let configurationElem = ".configuration-file", // point to the actual element in index.html
+ configurationDiv = $(
+ ``
+ ),
+ configurationTitle = $("h3", configurationDiv),
+ li;
+
+ // now replace the element in the index.html with the loaded file information
+ $(configurationElem).replaceWith(configurationDiv);
+
+ let configurationList = $(".configuration-list");
+ renderFileContentList(configurationList, null);
+
+ //configurationTitle.text(file.name);
+ $("#status-bar .configuration-file-name").text(file.name);
+
+ // now replace the element in the index.html with the loaded file information
+ $(configurationElem).replaceWith(configurationDiv);
+
+ // Add close icon
+ $(".configuration-close").click(function () {
+ if (showConfigFile) showConfigFile(false); // hide the config file
+ });
+ }
+
+ function loadFile(file) {
+ let reader = new FileReader();
+ fileData = file; // Store the data locally;
- // Public variables and functions
- this.getFile = function() {
- return fileData;
- };
+ reader.onload = function (e) {
+ let data = e.target.result; // all the data
- loadFile(file); // configuration file loaded
-
- // Add filter
-
+ fileLinesArray = data.split("\n"); // separated into lines
+
+ renderFileContents();
+
+ // Add user configurable file filter
+ $(".configuration-filter").keyup(function () {
+ let newFilter = $(".configuration-filter").val();
+
+ let configurationList = $(".configuration-list");
+ renderFileContentList(configurationList, newFilter);
+ });
+ };
+
+ reader.readAsText(file);
+ }
+
+ // Public variables and functions
+ this.getFile = function () {
+ return fileData;
+ };
+
+ loadFile(file); // configuration file loaded
+
+ // Add filter
}
export function ConfigurationDefaults(prefs) {
-
- // Special configuration file that handles default values only
-
- // Private Variables
- var that = this; // generic pointer back to this function
- var fileData; // configuration file information
- var fileLinesArray = null; // Store the contents of the file globally
-
- function loadFileFromCache() {
-
- // Get the file from the cache if it exists
- prefs.get('configurationDefaults', function(item) {
- if (item) {
- fileLinesArray = item;
- } else {
- fileLinesArray = null;
- }
- });
- }
-
- this.loadFile = function(file) {
-
- var reader = new FileReader();
- fileData = file; // Store the data locally;
-
- reader.onload = function(e) {
-
- var data = e.target.result; // all the data
- fileLinesArray = data.split('\n'); // separated into lines
-
- prefs.set('configurationDefaults', fileLinesArray); // and store it to the cache
-
- };
-
- reader.readAsText(file);
+ // Special configuration file that handles default values only
+
+ // Private Variables
+ let that = this; // generic pointer back to this function
+ let fileData; // configuration file information
+ let fileLinesArray = null; // Store the contents of the file globally
+
+ function loadFileFromCache() {
+ // Get the file from the cache if it exists
+ prefs.get("configurationDefaults", function (item) {
+ if (item) {
+ fileLinesArray = item;
+ } else {
+ fileLinesArray = null;
+ }
+ });
+ }
+
+ this.loadFile = function (file) {
+ let reader = new FileReader();
+ fileData = file; // Store the data locally;
+
+ reader.onload = function (e) {
+ let data = e.target.result; // all the data
+ fileLinesArray = data.split("\n"); // separated into lines
+
+ prefs.set("configurationDefaults", fileLinesArray); // and store it to the cache
+ };
+
+ reader.readAsText(file);
+ };
+
+ // Public variables and functions
+ this.getFile = function () {
+ return fileData;
+ };
+
+ this.getLines = function () {
+ return fileLinesArray;
+ };
+
+ this.hasDefaults = function () {
+ return fileLinesArray != null; // is there a default file array
+ };
+
+ this.isDefault = function (line) {
+ // Returns the default line equivalent
+
+ if (!fileLinesArray) return true; // by default, lines are the same if there is no default file loaded
+
+ for (let i = 0; i < fileLinesArray.length; i++) {
+ if (line != fileLinesArray[i]) continue; // not the same line, keep looking
+ return true; // line is same as default
}
+ return false; // line not the same as default or not found
+ };
- // Public variables and functions
- this.getFile = function() {
- return fileData;
- };
-
- this.getLines = function() {
- return fileLinesArray;
- }
-
- this.hasDefaults = function() {
- return (fileLinesArray!=null); // is there a default file array
- }
-
- this.isDefault = function(line) {
- // Returns the default line equivalent
-
- if(!fileLinesArray) return true; // by default, lines are the same if there is no default file loaded
-
- for(var i=0; i> 16) & 0xFF) + "," + ((color >> 8) & 0xFF) + "," + (color & 0xFF) + ",0.5)";
- }
-
- /**
- * Examine the log metadata to determine the layout of motors for the 2D craft model. Returns the craft parameters
- * object.
- */
- function decide2DCraftParameters() {
-
- switch (numMotors) {
- case 2:
- craftParameters.motors = [
- {
- x: -1,
- y: 0,
- direction: -1,
- color: propColors[motorOrder[0]]
- }, {
- x: 1,
- y: 0,
- direction: -1,
- color: propColors[motorOrder[1]]
- }
- ];
- break;
- case 3:
- craftParameters.motors = [
- {
- x: 1,
- y: 0,
- direction: -1,
- color: propColors[motorOrder[0]]
- }, {
- x: -0.71,
- y: -0.71,
- direction: -1,
- color: propColors[motorOrder[1]]
- }, {
- x: -0.71,
- y: +0.71,
- direction: -1,
- color: propColors[motorOrder[2]]
- }
- ];
- break;
- case 4: // Classic '+' quad, yawOffset rotates it into an X
- craftParameters.motors = [
- {
- x: 1, /*0.71,*/
- y: 0, /*-0.71,*/
- direction: -1,
- color: propColors[motorOrder[1]]
- },
- {
- x: 0, /*-0.71,*/
- y: -1, /*-0.71,*/
- direction: 1,
- color: propColors[motorOrder[3]]
- },
- {
- x: -1,/*-0.71,*/
- y: 0, /*0.71,*/
- direction: -1,
- color: propColors[motorOrder[2]]
- },
- {
- x: 0, /*0.71,*/
- y: 1, /*0.71,*/
- direction: 1,
- color: propColors[motorOrder[0]]
- },
- ];
- break;
- default:
- craftParameters.motors = [];
-
- for (var i = 0; i < numMotors; i++) {
- craftParameters.motors.push({
- x: Math.cos(i / numMotors * Math.PI * 2),
- y: Math.sin(i / numMotors * Math.PI * 2),
- direction: Math.pow(-1, i),
- color: propColors[i]
- });
- }
- break;
+ function makeColorHalfStrength(color) {
+ color = parseInt(color.substring(1), 16);
+
+ return `rgba(${(color >> 16) & 0xff},${(color >> 8) & 0xff},${
+ color & 0xff
+ },0.5)`;
+ }
+
+ /**
+ * Examine the log metadata to determine the layout of motors for the 2D craft model. Returns the craft parameters
+ * object.
+ */
+ function decide2DCraftParameters() {
+ switch (numMotors) {
+ case 2:
+ craftParameters.motors = [
+ {
+ x: -1,
+ y: 0,
+ direction: -1,
+ color: propColors[motorOrder[0]],
+ },
+ {
+ x: 1,
+ y: 0,
+ direction: -1,
+ color: propColors[motorOrder[1]],
+ },
+ ];
+ break;
+ case 3:
+ craftParameters.motors = [
+ {
+ x: 1,
+ y: 0,
+ direction: -1,
+ color: propColors[motorOrder[0]],
+ },
+ {
+ x: -0.71,
+ y: -0.71,
+ direction: -1,
+ color: propColors[motorOrder[1]],
+ },
+ {
+ x: -0.71,
+ y: +0.71,
+ direction: -1,
+ color: propColors[motorOrder[2]],
+ },
+ ];
+ break;
+ case 4: // Classic '+' quad, yawOffset rotates it into an X
+ craftParameters.motors = [
+ {
+ x: 1 /*0.71,*/,
+ y: 0 /*-0.71,*/,
+ direction: -1,
+ color: propColors[motorOrder[1]],
+ },
+ {
+ x: 0 /*-0.71,*/,
+ y: -1 /*-0.71,*/,
+ direction: 1,
+ color: propColors[motorOrder[3]],
+ },
+ {
+ x: -1 /*-0.71,*/,
+ y: 0 /*0.71,*/,
+ direction: -1,
+ color: propColors[motorOrder[2]],
+ },
+ {
+ x: 0 /*0.71,*/,
+ y: 1 /*0.71,*/,
+ direction: 1,
+ color: propColors[motorOrder[0]],
+ },
+ ];
+ break;
+ default:
+ craftParameters.motors = [];
+
+ for (let i = 0; i < numMotors; i++) {
+ craftParameters.motors.push({
+ x: Math.cos((i / numMotors) * Math.PI * 2),
+ y: Math.sin((i / numMotors) * Math.PI * 2),
+ direction: Math.pow(-1, i),
+ color: propColors[i],
+ });
}
-
- return craftParameters;
+ break;
}
-
- this.render = function(frame, frameFieldIndexes) {
- var
- motorIndex,
- sysConfig = flightLog.getSysConfig();
- canvasContext.save();
+ return craftParameters;
+ }
+
+ this.render = function (frame, frameFieldIndexes) {
+ let motorIndex,
+ sysConfig = flightLog.getSysConfig();
+
+ canvasContext.save();
+
+ canvasContext.clearRect(0, 0, canvas.width, canvas.height); // clear the craft
+ canvasContext.translate(canvas.width * 0.5, canvas.height * 0.5);
+ canvasContext.rotate(-yawOffset);
+ canvasContext.scale(0.5, 0.5); // scale to fit
+
+ //Draw arms
+ canvasContext.lineWidth = armLength * ARM_THICKNESS_MULTIPLIER;
+
+ canvasContext.lineCap = "round";
+ canvasContext.strokeStyle = craftColor;
+
+ canvasContext.beginPath();
+
+ for (i = 0; i < numMotors; i++) {
+ canvasContext.moveTo(0, 0);
+
+ canvasContext.lineTo(
+ armLength *
+ ARM_EXTEND_BEYOND_MOTOR_MULTIPLIER *
+ craftParameters.motors[i].x,
+ armLength *
+ ARM_EXTEND_BEYOND_MOTOR_MULTIPLIER *
+ craftParameters.motors[i].y
+ );
+ }
+
+ canvasContext.stroke();
+
+ //Draw the central hub
+ canvasContext.beginPath();
+
+ canvasContext.moveTo(0, 0);
+ canvasContext.arc(
+ 0,
+ 0,
+ armLength * CENTRAL_HUB_SIZE_MULTIPLIER,
+ 0,
+ 2 * Math.PI
+ );
+
+ canvasContext.fillStyle = craftColor;
+ canvasContext.fill();
+
+ for (i = 0; i < numMotors; i++) {
+ let motorValue = frame[frameFieldIndexes[`motor[${motorOrder[i]}]`]];
+
+ canvasContext.save();
+ {
+ //Move to the motor center
+ canvasContext.translate(
+ armLength * craftParameters.motors[i].x,
+ armLength * craftParameters.motors[i].y
+ );
+
+ canvasContext.fillStyle = shadeColors[motorOrder[i]];
- canvasContext.clearRect(0, 0, canvas.width, canvas.height); // clear the craft
- canvasContext.translate(canvas.width*0.5, canvas.height*0.5);
- canvasContext.rotate(-yawOffset);
- canvasContext.scale(0.5, 0.5); // scale to fit
-
- //Draw arms
- canvasContext.lineWidth = armLength * ARM_THICKNESS_MULTIPLIER;
-
- canvasContext.lineCap = "round";
- canvasContext.strokeStyle = craftColor;
-
canvasContext.beginPath();
-
- for (i = 0; i < numMotors; i++) {
- canvasContext.moveTo(0, 0);
-
- canvasContext.lineTo(
- (armLength * ARM_EXTEND_BEYOND_MOTOR_MULTIPLIER) * craftParameters.motors[i].x,
- (armLength * ARM_EXTEND_BEYOND_MOTOR_MULTIPLIER) * craftParameters.motors[i].y
- );
- }
-
- canvasContext.stroke();
-
- //Draw the central hub
+
+ canvasContext.moveTo(0, 0);
+ canvasContext.arc(0, 0, bladeRadius, 0, Math.PI * 2, false);
+
+ canvasContext.fill();
+
+ canvasContext.fillStyle = propColors[motorOrder[i]];
+
canvasContext.beginPath();
-
+
canvasContext.moveTo(0, 0);
- canvasContext.arc(0, 0, armLength * CENTRAL_HUB_SIZE_MULTIPLIER, 0, 2 * Math.PI);
-
- canvasContext.fillStyle = craftColor;
+ canvasContext.arc(
+ 0,
+ 0,
+ bladeRadius,
+ -Math.PI / 2,
+ -Math.PI / 2 +
+ (Math.PI * 2 * Math.max(motorValue - sysConfig.motorOutput[0], 0)) /
+ (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]),
+ false
+ );
+
canvasContext.fill();
-
- for (i = 0; i < numMotors; i++) {
- var motorValue = frame[frameFieldIndexes["motor[" + motorOrder[i] + "]"]];
-
- canvasContext.save();
- {
- //Move to the motor center
- canvasContext.translate(
- armLength * craftParameters.motors[i].x,
- armLength * craftParameters.motors[i].y
- );
-
- canvasContext.fillStyle = shadeColors[motorOrder[i]];
-
- canvasContext.beginPath();
-
- canvasContext.moveTo(0, 0);
- canvasContext.arc(0, 0, bladeRadius, 0, Math.PI * 2, false);
-
- canvasContext.fill();
-
- canvasContext.fillStyle = propColors[motorOrder[i]];
-
- canvasContext.beginPath();
-
- canvasContext.moveTo(0, 0);
- canvasContext.arc(0, 0, bladeRadius, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2
- * Math.max(motorValue - sysConfig.motorOutput[0], 0) / (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]), false);
-
- canvasContext.fill();
-
- /* Disable motor value markers
+
+ /* Disable motor value markers
var
motorLabel = "" + motorValue;
@@ -241,34 +257,33 @@ export function Craft2D(flightLog, canvas, propColors) {
canvasContext.fillText(motorLabel, -(bladeRadius + MOTOR_LABEL_SPACING), 0);
}
*/
-
- }
- canvasContext.restore();
- }
- canvasContext.restore();
- };
-
- for (var i = 0; i < propColors.length; i++) {
- shadeColors.push(makeColorHalfStrength(propColors[i]));
+ }
+ canvasContext.restore();
}
-
- decide2DCraftParameters();
-
- this.resize = function(width, height) {
- if (canvas.width != width || canvas.height != height) {
- canvas.width = width;
- canvas.height = height;
- }
-
- armLength = 0.5 * height;
-
- if (numMotors >= 6) {
- bladeRadius = armLength * 0.4;
- } else {
- bladeRadius = armLength * 0.6;
- }
+ canvasContext.restore();
+ };
+
+ for (var i = 0; i < propColors.length; i++) {
+ shadeColors.push(makeColorHalfStrength(propColors[i]));
+ }
+
+ decide2DCraftParameters();
+
+ this.resize = function (width, height) {
+ if (canvas.width != width || canvas.height != height) {
+ canvas.width = width;
+ canvas.height = height;
}
-
- // Assume we're to fill the entire canvas until we're told otherwise by .resize()
- this.resize(canvas.width, canvas.height);
-}
\ No newline at end of file
+
+ armLength = 0.5 * height;
+
+ if (numMotors >= 6) {
+ bladeRadius = armLength * 0.4;
+ } else {
+ bladeRadius = armLength * 0.6;
+ }
+ };
+
+ // Assume we're to fill the entire canvas until we're told otherwise by .resize()
+ this.resize(canvas.width, canvas.height);
+}
diff --git a/src/craft_3d.js b/src/craft_3d.js
index 94eea37c..0264a8c2 100644
--- a/src/craft_3d.js
+++ b/src/craft_3d.js
@@ -1,324 +1,334 @@
export function Craft3D(flightLog, canvas, propColors) {
- var
- // Sets the distance between the center point and the center of the motor mount
- ARM_LENGTH = 1,
- NUM_PROP_LEVELS = 100,
-
- HUB_RADIUS = ARM_LENGTH * 0.3,
-
- CRAFT_DEPTH = ARM_LENGTH * 0.08,
- ARROW_DEPTH = CRAFT_DEPTH * 0.5;
-
- var customMix;
-
- if(userSettings != null) {
- customMix = userSettings.customMix;
- } else {
- customMix = null;
- }
+ let // Sets the distance between the center point and the center of the motor mount
+ ARM_LENGTH = 1,
+ NUM_PROP_LEVELS = 100,
+ HUB_RADIUS = ARM_LENGTH * 0.3,
+ CRAFT_DEPTH = ARM_LENGTH * 0.08,
+ ARROW_DEPTH = CRAFT_DEPTH * 0.5;
+
+ let customMix;
+
+ if (userSettings != null) {
+ customMix = userSettings.customMix;
+ } else {
+ customMix = null;
+ }
+
+ let numMotors;
+ if (customMix === null) {
+ numMotors = propColors.length;
+ } else {
+ numMotors = customMix.motorOrder.length;
+ }
+
+ let propRadius = numMotors == 8 ? 0.37 * ARM_LENGTH : 0.5 * ARM_LENGTH,
+ craftMaterial = new THREE.MeshLambertMaterial({ color: 0xa0a0a0 }),
+ arrowMaterial = new THREE.MeshLambertMaterial({ color: 0x404040 }),
+ propMaterials = new Array(propColors),
+ propShellMaterial = new THREE.MeshLambertMaterial({
+ color: 0xffffff,
+ opacity: 0.2,
+ transparent: true,
+ });
+
+ function buildPropGeometry() {
+ let props = new Array(NUM_PROP_LEVELS),
+ extrudeSettings = {
+ amount: 0.1 * propRadius,
+ steps: 1,
+ bevelEnabled: false,
+ };
+
+ for (let i = 0; i < NUM_PROP_LEVELS; i++) {
+ if (i === 0) {
+ props[i] = new THREE.Geometry();
+ } else {
+ let shape = new THREE.Shape();
- var numMotors;
- if(customMix===null) {
- numMotors = propColors.length;
+ if (i == NUM_PROP_LEVELS - 1) {
+ //work around three.js bug that requires the initial point to be on the radius to complete a full circle
+ shape.moveTo(propRadius, 0);
+ shape.absarc(
+ 0,
+ 0,
+ propRadius,
+ 0,
+ (Math.PI * 2 * i) / (NUM_PROP_LEVELS - 1)
+ );
} else {
- numMotors = customMix.motorOrder.length;
- }
-
- var
- propRadius = numMotors == 8 ? 0.37 * ARM_LENGTH : 0.5 * ARM_LENGTH,
-
- craftMaterial = new THREE.MeshLambertMaterial({ color : 0xA0A0A0 }),
- arrowMaterial = new THREE.MeshLambertMaterial({ color : 0x404040 }),
-
- propMaterials = new Array(propColors),
- propShellMaterial = new THREE.MeshLambertMaterial({ color: 0xFFFFFF, opacity: 0.20, transparent: true});
-
- function buildPropGeometry() {
- var
- props = new Array(NUM_PROP_LEVELS),
- extrudeSettings = {
- amount: 0.1 * propRadius,
- steps: 1,
- bevelEnabled: false
- };
-
- for (var i = 0; i < NUM_PROP_LEVELS; i++) {
- if (i === 0) {
- props[i] = new THREE.Geometry();
- } else {
- var
- shape = new THREE.Shape();
-
- if (i == NUM_PROP_LEVELS - 1) {
- //work around three.js bug that requires the initial point to be on the radius to complete a full circle
- shape.moveTo(propRadius, 0);
- shape.absarc(0, 0, propRadius, 0, Math.PI * 2 * i / (NUM_PROP_LEVELS - 1));
- } else {
- shape.moveTo(0, 0);
- shape.absarc(0, 0, propRadius, 0, Math.PI * 2 * i / (NUM_PROP_LEVELS - 1));
- }
-
- props[i] = new THREE.ExtrudeGeometry(shape, extrudeSettings);
- }
+ shape.moveTo(0, 0);
+ shape.absarc(
+ 0,
+ 0,
+ propRadius,
+ 0,
+ (Math.PI * 2 * i) / (NUM_PROP_LEVELS - 1)
+ );
}
-
- return props;
- }
-
- // Build a direction arrow to go on top of the craft
- function buildArrow() {
- var
- ARROW_STALK_RADIUS = HUB_RADIUS * 0.15,
- ARROW_STALK_LENGTH = HUB_RADIUS * 0.8,
- ARROW_HEAD_RADIUS = HUB_RADIUS * 0.55,
- ARROW_HEAD_LENGTH = HUB_RADIUS * 0.55,
-
- ARROW_LENGTH = ARROW_STALK_LENGTH + ARROW_HEAD_LENGTH;
-
- var
- path = new THREE.Path(),
- offset = -ARROW_LENGTH / 2;
-
- path.moveTo(-ARROW_STALK_RADIUS, 0 + offset);
- path.lineTo(-ARROW_STALK_RADIUS, ARROW_STALK_LENGTH + offset);
- path.lineTo(-ARROW_HEAD_RADIUS, ARROW_STALK_LENGTH + offset);
- path.lineTo(0, ARROW_LENGTH + offset);
- path.lineTo(ARROW_HEAD_RADIUS, ARROW_STALK_LENGTH + offset);
- path.lineTo(ARROW_STALK_RADIUS, ARROW_STALK_LENGTH + offset);
- path.lineTo(ARROW_STALK_RADIUS, 0 + offset);
-
- var
- shape = path.toShapes(true, false),
-
- extrudeSettings = {
- amount: ARROW_DEPTH,
- steps: 1,
- bevelEnabled: false
- },
-
- geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings),
-
- arrowMesh = new THREE.Mesh(geometry, arrowMaterial);
-
- return arrowMesh;
- }
-
- function buildCraft() {
- var
- path = new THREE.Path(),
-
- ARM_WIDTH_RADIANS = 0.15,
-
- //How much wider is the motor mount than the arm
- MOTOR_MOUNT_WIDTH_RATIO = 2.0,
-
- //What portion of the arm length is motor mount
- MOTOR_MOUNT_LENGTH_RATIO = 0.1,
-
- //What portion of the arm length is the bevel at the beginning and end of the motor mount
- MOTOR_BEVEL_DEPTH_RATIO = 0.04,
-
- ARM_WIDTH = 2 * Math.sin(ARM_WIDTH_RADIANS) * HUB_RADIUS;
-
- for (i = 0; i < numMotors; i++) {
- var
- armStart = i / numMotors * Math.PI * 2 - ARM_WIDTH_RADIANS,
- armEnd = armStart + ARM_WIDTH_RADIANS * 2;
-
- if (i === 0) {
- path.moveTo(Math.cos(armStart) * HUB_RADIUS, Math.sin(armStart) * HUB_RADIUS);
- } else {
- path.lineTo(Math.cos(armStart) * HUB_RADIUS, Math.sin(armStart) * HUB_RADIUS);
- }
-
- var
- // Unit vector pointing through the center of the arm
- armVectorX = Math.cos(armStart + ARM_WIDTH_RADIANS),
- armVectorY = Math.sin(armStart + ARM_WIDTH_RADIANS),
-
- // Vector at right angles scaled for the arm width
- crossArmX = -armVectorY * ARM_WIDTH * 0.5,
- crossArmY = armVectorX * ARM_WIDTH * 0.5,
-
- armPoints = [
- // Make the first part of the arms parallel by spacing the ends the same amount as the beginnings
- {length:1 - MOTOR_MOUNT_LENGTH_RATIO - MOTOR_BEVEL_DEPTH_RATIO, width:1},
- {length:1 - MOTOR_MOUNT_LENGTH_RATIO, width:MOTOR_MOUNT_WIDTH_RATIO},
- {length:1 + MOTOR_MOUNT_LENGTH_RATIO, width:MOTOR_MOUNT_WIDTH_RATIO},
- // Bevel after end of motor mount
- {length:1 + MOTOR_MOUNT_LENGTH_RATIO + MOTOR_BEVEL_DEPTH_RATIO, width: 1}
- ];
-
- armVectorX *= ARM_LENGTH;
- armVectorY *= ARM_LENGTH;
-
- // Draw one half of the arm:
- for (var j = 0; j < armPoints.length; j++) {
- var point = armPoints[j];
- path.lineTo(point.length * armVectorX - point.width * crossArmX, point.length * armVectorY - point.width * crossArmY);
- }
-
- // And flip the points to draw the other half:
- for (var j = armPoints.length - 1; j >= 0; j--) {
- var point = armPoints[j];
- path.lineTo(point.length * armVectorX + point.width * crossArmX, point.length * armVectorY + point.width * crossArmY);
- }
-
- path.lineTo(
- Math.cos(armEnd) * HUB_RADIUS,
- Math.sin(armEnd) * HUB_RADIUS
- );
- }
-
- var
- shape = path.toShapes(true, false),
-
- extrudeSettings = {
- amount: CRAFT_DEPTH,
- steps: 1,
- bevelEnabled: false
- },
-
- geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings),
-
- craftMesh = new THREE.Mesh(geometry, craftMaterial);
-
- return craftMesh;
- }
-
- var
- scene = new THREE.Scene(),
- camera = new THREE.PerspectiveCamera(40, 1, 0.1, 1000),
-
- renderer = new THREE.WebGLRenderer({canvas : canvas, alpha: true}),
-
- light = new THREE.HemisphereLight(0xe4e4ff, 0x405040, 1.1),
-
- craft = new THREE.Object3D(),
- craftParent = new THREE.Object3D(),
-
- craftMesh = buildCraft(),
- arrowMesh = buildArrow(),
- propGeometry = buildPropGeometry(),
-
- props = new Array(numMotors),
- propShells = new Array(numMotors),
-
- motorOrder,
- sysInfo = flightLog.getSysConfig(),
- yawOffset;
-
- // The craft object will hold the props and craft body
- // We'll rotate this to bring the front direction of the model to the correct position
- craft.add(craftMesh);
-
- // We put that in a container that we'll rotate based on the craft attitude
- craftParent.add(craft);
-
- // This allows us to add a craft directional arrow that'll point the same way as the craftMesh
- arrowMesh.position.z = CRAFT_DEPTH;
-
- craftParent.add(arrowMesh);
-
- scene.add(craftParent);
-
- light.position.set(1, 1, 1).normalize();
- scene.add(light);
-
- camera.position.y = 0;
- camera.position.z = 5;
-
- for (var i = 0; i < propColors.length; i++) {
- propMaterials[i] = new THREE.MeshLambertMaterial({color: propColors[i]});
+
+ props[i] = new THREE.ExtrudeGeometry(shape, extrudeSettings);
+ }
}
-
- for (var i = 0; i < numMotors; i++) {
-
- var propShell = new THREE.Mesh(propGeometry[propGeometry.length - 1], propShellMaterial);
-
- propShells[i] = propShell;
-
- propShell.translateX(Math.cos(i / numMotors * Math.PI * 2) * ARM_LENGTH);
- propShell.translateY(Math.sin(i / numMotors * Math.PI * 2) * ARM_LENGTH);
- propShell.translateZ(0.10);
-
- craft.add(propShell);
+
+ return props;
+ }
+
+ // Build a direction arrow to go on top of the craft
+ function buildArrow() {
+ let ARROW_STALK_RADIUS = HUB_RADIUS * 0.15,
+ ARROW_STALK_LENGTH = HUB_RADIUS * 0.8,
+ ARROW_HEAD_RADIUS = HUB_RADIUS * 0.55,
+ ARROW_HEAD_LENGTH = HUB_RADIUS * 0.55,
+ ARROW_LENGTH = ARROW_STALK_LENGTH + ARROW_HEAD_LENGTH;
+
+ let path = new THREE.Path(),
+ offset = -ARROW_LENGTH / 2;
+
+ path.moveTo(-ARROW_STALK_RADIUS, 0 + offset);
+ path.lineTo(-ARROW_STALK_RADIUS, ARROW_STALK_LENGTH + offset);
+ path.lineTo(-ARROW_HEAD_RADIUS, ARROW_STALK_LENGTH + offset);
+ path.lineTo(0, ARROW_LENGTH + offset);
+ path.lineTo(ARROW_HEAD_RADIUS, ARROW_STALK_LENGTH + offset);
+ path.lineTo(ARROW_STALK_RADIUS, ARROW_STALK_LENGTH + offset);
+ path.lineTo(ARROW_STALK_RADIUS, 0 + offset);
+
+ let shape = path.toShapes(true, false),
+ extrudeSettings = {
+ amount: ARROW_DEPTH,
+ steps: 1,
+ bevelEnabled: false,
+ },
+ geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings),
+ arrowMesh = new THREE.Mesh(geometry, arrowMaterial);
+
+ return arrowMesh;
+ }
+
+ function buildCraft() {
+ let path = new THREE.Path(),
+ ARM_WIDTH_RADIANS = 0.15,
+ //How much wider is the motor mount than the arm
+ MOTOR_MOUNT_WIDTH_RATIO = 2.0,
+ //What portion of the arm length is motor mount
+ MOTOR_MOUNT_LENGTH_RATIO = 0.1,
+ //What portion of the arm length is the bevel at the beginning and end of the motor mount
+ MOTOR_BEVEL_DEPTH_RATIO = 0.04,
+ ARM_WIDTH = 2 * Math.sin(ARM_WIDTH_RADIANS) * HUB_RADIUS;
+
+ for (i = 0; i < numMotors; i++) {
+ let armStart = (i / numMotors) * Math.PI * 2 - ARM_WIDTH_RADIANS,
+ armEnd = armStart + ARM_WIDTH_RADIANS * 2;
+
+ if (i === 0) {
+ path.moveTo(
+ Math.cos(armStart) * HUB_RADIUS,
+ Math.sin(armStart) * HUB_RADIUS
+ );
+ } else {
+ path.lineTo(
+ Math.cos(armStart) * HUB_RADIUS,
+ Math.sin(armStart) * HUB_RADIUS
+ );
+ }
+
+ let // Unit vector pointing through the center of the arm
+ armVectorX = Math.cos(armStart + ARM_WIDTH_RADIANS),
+ armVectorY = Math.sin(armStart + ARM_WIDTH_RADIANS),
+ // Vector at right angles scaled for the arm width
+ crossArmX = -armVectorY * ARM_WIDTH * 0.5,
+ crossArmY = armVectorX * ARM_WIDTH * 0.5,
+ armPoints = [
+ // Make the first part of the arms parallel by spacing the ends the same amount as the beginnings
+ {
+ length: 1 - MOTOR_MOUNT_LENGTH_RATIO - MOTOR_BEVEL_DEPTH_RATIO,
+ width: 1,
+ },
+ {
+ length: 1 - MOTOR_MOUNT_LENGTH_RATIO,
+ width: MOTOR_MOUNT_WIDTH_RATIO,
+ },
+ {
+ length: 1 + MOTOR_MOUNT_LENGTH_RATIO,
+ width: MOTOR_MOUNT_WIDTH_RATIO,
+ },
+ // Bevel after end of motor mount
+ {
+ length: 1 + MOTOR_MOUNT_LENGTH_RATIO + MOTOR_BEVEL_DEPTH_RATIO,
+ width: 1,
+ },
+ ];
+
+ armVectorX *= ARM_LENGTH;
+ armVectorY *= ARM_LENGTH;
+
+ // Draw one half of the arm:
+ for (var j = 0; j < armPoints.length; j++) {
+ var point = armPoints[j];
+ path.lineTo(
+ point.length * armVectorX - point.width * crossArmX,
+ point.length * armVectorY - point.width * crossArmY
+ );
+ }
+
+ // And flip the points to draw the other half:
+ for (var j = armPoints.length - 1; j >= 0; j--) {
+ var point = armPoints[j];
+ path.lineTo(
+ point.length * armVectorX + point.width * crossArmX,
+ point.length * armVectorY + point.width * crossArmY
+ );
+ }
+
+ path.lineTo(Math.cos(armEnd) * HUB_RADIUS, Math.sin(armEnd) * HUB_RADIUS);
}
-
- // Motor numbering in counter-clockwise order starting from the 3 o'clock position
- if(customMix===null) {
- switch (numMotors) {
- case 3:
- motorOrder = [0, 1, 2]; // Put motor 1 at the right
- yawOffset = -Math.PI / 2;
- break;
- case 4:
- motorOrder = [1, 3, 2, 0]; // Numbering for quad-plus
- yawOffset = Math.PI / 4; // Change from "plus" orientation to "X"
- break;
- case 6:
- motorOrder = [4, 1, 3, 5, 2, 0];
- yawOffset = 0;
- break;
- case 8:
- motorOrder = [5, 1, 4, 0, 7, 3, 6, 2];
- yawOffset = Math.PI / 8; // Put two motors at the front
- break;
- default:
- motorOrder = new Array(numMotors);
- for (var i = 0; i < numMotors; i++) {
- motorOrder[i] = i;
- }
- yawOffset = 0;
+
+ let shape = path.toShapes(true, false),
+ extrudeSettings = {
+ amount: CRAFT_DEPTH,
+ steps: 1,
+ bevelEnabled: false,
+ },
+ geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings),
+ craftMesh = new THREE.Mesh(geometry, craftMaterial);
+
+ return craftMesh;
+ }
+
+ let scene = new THREE.Scene(),
+ camera = new THREE.PerspectiveCamera(40, 1, 0.1, 1000),
+ renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true }),
+ light = new THREE.HemisphereLight(0xe4e4ff, 0x405040, 1.1),
+ craft = new THREE.Object3D(),
+ craftParent = new THREE.Object3D(),
+ craftMesh = buildCraft(),
+ arrowMesh = buildArrow(),
+ propGeometry = buildPropGeometry(),
+ props = new Array(numMotors),
+ propShells = new Array(numMotors),
+ motorOrder,
+ sysInfo = flightLog.getSysConfig(),
+ yawOffset;
+
+ // The craft object will hold the props and craft body
+ // We'll rotate this to bring the front direction of the model to the correct position
+ craft.add(craftMesh);
+
+ // We put that in a container that we'll rotate based on the craft attitude
+ craftParent.add(craft);
+
+ // This allows us to add a craft directional arrow that'll point the same way as the craftMesh
+ arrowMesh.position.z = CRAFT_DEPTH;
+
+ craftParent.add(arrowMesh);
+
+ scene.add(craftParent);
+
+ light.position.set(1, 1, 1).normalize();
+ scene.add(light);
+
+ camera.position.y = 0;
+ camera.position.z = 5;
+
+ for (var i = 0; i < propColors.length; i++) {
+ propMaterials[i] = new THREE.MeshLambertMaterial({ color: propColors[i] });
+ }
+
+ for (var i = 0; i < numMotors; i++) {
+ let propShell = new THREE.Mesh(
+ propGeometry[propGeometry.length - 1],
+ propShellMaterial
+ );
+
+ propShells[i] = propShell;
+
+ propShell.translateX(Math.cos((i / numMotors) * Math.PI * 2) * ARM_LENGTH);
+ propShell.translateY(Math.sin((i / numMotors) * Math.PI * 2) * ARM_LENGTH);
+ propShell.translateZ(0.1);
+
+ craft.add(propShell);
+ }
+
+ // Motor numbering in counter-clockwise order starting from the 3 o'clock position
+ if (customMix === null) {
+ switch (numMotors) {
+ case 3:
+ motorOrder = [0, 1, 2]; // Put motor 1 at the right
+ yawOffset = -Math.PI / 2;
+ break;
+ case 4:
+ motorOrder = [1, 3, 2, 0]; // Numbering for quad-plus
+ yawOffset = Math.PI / 4; // Change from "plus" orientation to "X"
+ break;
+ case 6:
+ motorOrder = [4, 1, 3, 5, 2, 0];
+ yawOffset = 0;
+ break;
+ case 8:
+ motorOrder = [5, 1, 4, 0, 7, 3, 6, 2];
+ yawOffset = Math.PI / 8; // Put two motors at the front
+ break;
+ default:
+ motorOrder = new Array(numMotors);
+ for (var i = 0; i < numMotors; i++) {
+ motorOrder[i] = i;
}
- } else {
- motorOrder = customMix.motorOrder;
- yawOffset = customMix.yawOffset;
+ yawOffset = 0;
}
-
- // Rotate the craft mesh and props to bring the board's direction arrow to the right direction
- craft.rotation.z = yawOffset;
-
- this.render = function(frame, frameFieldIndexes) {
- for (var i = 0; i < numMotors; i++) {
- if (props[i])
- propShells[i].remove(props[i]);
-
- var
- throttlePos = Math.min(Math.max(frame[frameFieldIndexes["motor[" + motorOrder[i] + "]"]] - sysInfo.motorOutput[0], 0) / (sysInfo.motorOutput[1] - sysInfo.motorOutput[0]), 1.0),
- propLevel = Math.round(throttlePos * (NUM_PROP_LEVELS - 1)),
- geometry = propGeometry[propLevel],
- prop = new THREE.Mesh(geometry, propMaterials[motorOrder[i]]);
-
- prop.scale.set(0.95, 0.95, 0.95);
-
- // Tricopter tail servo
- /*if (i == 0 && numMotors == 3 && frameFieldIndexes["servo[5]"] !== undefined) {
+ } else {
+ motorOrder = customMix.motorOrder;
+ yawOffset = customMix.yawOffset;
+ }
+
+ // Rotate the craft mesh and props to bring the board's direction arrow to the right direction
+ craft.rotation.z = yawOffset;
+
+ this.render = function (frame, frameFieldIndexes) {
+ for (let i = 0; i < numMotors; i++) {
+ if (props[i]) propShells[i].remove(props[i]);
+
+ let throttlePos = Math.min(
+ Math.max(
+ frame[frameFieldIndexes[`motor[${motorOrder[i]}]`]] -
+ sysInfo.motorOutput[0],
+ 0
+ ) /
+ (sysInfo.motorOutput[1] - sysInfo.motorOutput[0]),
+ 1.0
+ ),
+ propLevel = Math.round(throttlePos * (NUM_PROP_LEVELS - 1)),
+ geometry = propGeometry[propLevel],
+ prop = new THREE.Mesh(geometry, propMaterials[motorOrder[i]]);
+
+ prop.scale.set(0.95, 0.95, 0.95);
+
+ // Tricopter tail servo
+ /*if (i == 0 && numMotors == 3 && frameFieldIndexes["servo[5]"] !== undefined) {
propShells[i].rotation.x = -(frame[frameFieldIndexes["servo[5]"]] - 1500) / 1000 * Math.PI;
}*/
-
- propShells[i].add(prop);
-
- props[i] = prop;
- }
-
- // Display the craft's attitude
- craftParent.rotation.x = -frame[frameFieldIndexes['heading[1]']] /*- Math.PI / 2*/; // pitch
- craftParent.rotation.y = frame[frameFieldIndexes['heading[0]']]; // roll
-
- //craftParent.rotation.z = -frame[frameFieldIndexes['heading[2]']]; // yaw
-
- renderer.render(scene, camera);
- };
-
- this.resize = function(width, height) {
- if (canvas.width != width || canvas.height != height) {
- canvas.width = width;
- canvas.height = height;
-
- renderer.setViewport(0, 0, width, height);
-
- camera.updateProjectionMatrix();
- }
+
+ propShells[i].add(prop);
+
+ props[i] = prop;
+ }
+
+ // Display the craft's attitude
+ craftParent.rotation.x =
+ -frame[frameFieldIndexes["heading[1]"]] /*- Math.PI / 2*/; // pitch
+ craftParent.rotation.y = frame[frameFieldIndexes["heading[0]"]]; // roll
+
+ //craftParent.rotation.z = -frame[frameFieldIndexes['heading[2]']]; // yaw
+
+ renderer.render(scene, camera);
+ };
+
+ this.resize = function (width, height) {
+ if (canvas.width != width || canvas.height != height) {
+ canvas.width = width;
+ canvas.height = height;
+
+ renderer.setViewport(0, 0, width, height);
+
+ camera.updateProjectionMatrix();
}
-}
\ No newline at end of file
+ };
+}
diff --git a/src/css/header_dialog.css b/src/css/header_dialog.css
index cdf3d1fc..7c0d791d 100644
--- a/src/css/header_dialog.css
+++ b/src/css/header_dialog.css
@@ -1,60 +1,60 @@
/* Columns START> */
.cf_column {
- min-height: 18px;
- margin-bottom: 0;
+ min-height: 18px;
+ margin-bottom: 0;
}
.full {
- float: left;
- width: 100%;
+ float: left;
+ width: 100%;
}
.half {
- float: left;
- width: 50%;
+ float: left;
+ width: 50%;
}
.third_left {
- float: left;
- width: 33%;
+ float: left;
+ width: 33%;
}
.third_center {
- display: inline-block;
- width: 34%;
+ display: inline-block;
+ width: 34%;
}
.third_right {
- float: right;
- width: 33%;
+ float: right;
+ width: 33%;
}
.fourth {
- float: left;
- width: 25%;
+ float: left;
+ width: 25%;
}
.threefourth_right {
- float: right;
- width: 75%;
+ float: right;
+ width: 75%;
}
.threefourth_left {
- float: left;
- width: 75%;
+ float: left;
+ width: 75%;
}
.twothird {
- float: left;
- width: 67%;
+ float: left;
+ width: 67%;
}
/* Columns END> */
/* Default Parameter Entry Field */
input[type="number"]::-webkit-inner-spin-button {
- opacity: 0; /* 1 is required for chromium 33+ beta to show the arrows / spinners */
-/* margin-left: 5px;*/
+ opacity: 0; /* 1 is required for chromium 33+ beta to show the arrows / spinners */
+ /* margin-left: 5px;*/
}
/* Hide select dropdown arrow
@@ -65,350 +65,351 @@ select {
*/
.clear-both {
- clear: both;
+ clear: both;
}
.left {
- float: left;
+ float: left;
}
.right {
- float: right;
+ float: right;
}
/* Standard GUI BOX */
.gui_box {
- border: 1px solid #ccc;
- border-radius: 4px;
- background-color: #ccc;
- float: left;
- width: calc(100% - 2px);
- margin-bottom: 10px;
- font-family: 'open_sansregular', Arial, serif;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-color: #ccc;
+ float: left;
+ width: calc(100% - 2px);
+ margin-bottom: 10px;
+ font-family: "open_sansregular", Arial, serif;
}
.gui_box_titlebar {
- background-color: #808080;
- border-radius: 3px 3px 0 0;
- font-size: 14px;
- width: 100%;
- height: 27px;
- padding-bottom: 0;
- float: left;
- color: white;
- margin-bottom: 0px;
- font-family: 'open_sanssemibold', Arial, serif;
+ background-color: #808080;
+ border-radius: 3px 3px 0 0;
+ font-size: 14px;
+ width: 100%;
+ height: 27px;
+ padding-bottom: 0;
+ float: left;
+ color: white;
+ margin-bottom: 0px;
+ font-family: "open_sanssemibold", Arial, serif;
}
.h5 {
- color: red;
- margin: 0;
+ color: red;
+ margin: 0;
}
/* Spacers */
.spacer {
- padding-left: 7px;
- padding-right: 7px;
- width: calc(100% - 14px);
- float: left;
+ padding-left: 7px;
+ padding-right: 7px;
+ width: calc(100% - 14px);
+ float: left;
}
.spacer_box {
- padding: 0px;
- margin: 0px;
- width: 100%;
+ padding: 0px;
+ margin: 0px;
+ width: 100%;
}
/* main title for each div*/
.spacer_box_title {
- padding-left: 10px;
- padding-right: 10px;
- padding-top: 4px;
- margin-bottom: 0;
- float: left;
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 4px;
+ margin-bottom: 0;
+ float: left;
}
/* space around each div*/
.spacer_left {
- padding-left: 0px;
- float: left;
- width: calc(100%);
+ padding-left: 0px;
+ float: left;
+ width: calc(100%);
}
.spacer_right {
- padding-right: 5px;
- width: calc(100% - 5px);
- float: left;
+ padding-right: 5px;
+ width: calc(100% - 5px);
+ float: left;
}
.header-dialog .parameter td:first-child label {
- border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
}
.header-dialog .parameter td:last-child label {
- border-top-right-radius: 3px;
+ border-top-right-radius: 3px;
}
/* Parameter box titles */
.header-dialog .parameter th {
- background-color: #b0b0b0;
- padding: 2px;
- padding-left: 10px;
- border-left: 0 solid #ccc;
- font-weight: bold;
- font-size: 14px;
- color: #505050;
- text-align: left;
+ background-color: #b0b0b0;
+ padding: 2px;
+ padding-left: 10px;
+ border-left: 0 solid #ccc;
+ font-weight: bold;
+ font-size: 14px;
+ color: #505050;
+ text-align: left;
}
/* Parameter box other lines */
.header-dialog .parameter label {
- background-color: #d8d8d8;
- padding: 0px;
- margin: 0px;
- border-left: 0 solid #ccc;
- font-weight: normal;
- font-size: 13px;
- color: #505050;
- text-align: center;
+ background-color: #d8d8d8;
+ padding: 0px;
+ margin: 0px;
+ border-left: 0 solid #ccc;
+ font-weight: normal;
+ font-size: 13px;
+ color: #505050;
+ text-align: center;
}
/* Parameter box other rows */
.header-dialog .cf tr {
- background-color: #d8d8d8;
- text-align: center;
+ background-color: #d8d8d8;
+ text-align: center;
}
.header-dialog .modal-dialog {
- width:75%;
- min-width:850px;
+ width: 75%;
+ min-width: 850px;
}
.header-dialog .cf th {
- border-right: solid 1px silver;
- font-weight: normal;
+ border-right: solid 1px silver;
+ font-weight: normal;
}
.header-dialog .cf th:last-child {
- border-right: 0;
+ border-right: 0;
}
.header-dialog .cf td:first-child {
- border-bottom-left-radius: 3px;
- border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ border-top-left-radius: 3px;
}
.header-dialog .cf td:last-child {
- border-bottom-right-radius: 3px;
- border-right: 0;
- border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ border-right: 0;
+ border-top-right-radius: 3px;
}
/* Box holding numeric inputs */
.header-dialog .cf input {
- width: calc(100% - 10px);
- border: 1px solid silver;
- border-radius: 3px;
- margin-left: 2px;
- text-align: center;
+ width: calc(100% - 10px);
+ border: 1px solid silver;
+ border-radius: 3px;
+ margin-left: 2px;
+ text-align: center;
}
/* Box holding text down elements */
.header-dialog .cf select {
- appearance: none; /* hide arrow at right*/
- width: calc(100% - 10px);
- border: 1px solid silver;
- background-color: #FFFFFF;
- color: black;
- padding-top: 1px;
- padding-left: 2px;
- text-align: center;
- text-align-last: center;
+ appearance: none; /* hide arrow at right*/
+ width: calc(100% - 10px);
+ border: 1px solid silver;
+ background-color: #ffffff;
+ color: black;
+ padding-top: 1px;
+ padding-left: 2px;
+ text-align: center;
+ text-align-last: center;
}
.header-dialog .parameter th:nth-child(2) {
- border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
}
.header-dialog .parameter th:first-child {
- border-top-left-radius: 3px;
+ border-top-left-radius: 3px;
}
.header-dialog .parameter th:last-child {
- border-top-right-radius: 3px;
+ border-top-right-radius: 3px;
}
.header-dialog input[type="number"]::-webkit-inner-spin-button {
- border: 0;
+ border: 0;
}
.header-dialog table {
- float: center;
- margin: 0;
- border-collapse: collapse;
- width: calc(100% - 1px);
+ float: center;
+ margin: 0;
+ border-collapse: collapse;
+ width: calc(100% - 1px);
}
.header-dialog .gui_box {
- /*margin-bottom: 0;*/
+ /*margin-bottom: 0;*/
}
-.header-dialog .parameter td+input {
- padding: 1px;
- border-bottom: 0 solid #ccc;
+.header-dialog .parameter td + input {
+ padding: 1px;
+ border-bottom: 0 solid #ccc;
}
-.header-dialog table, .header-dialog table td {
- padding: 1px;
+.header-dialog table,
+.header-dialog table td {
+ padding: 1px;
}
.header-dialog table th {
- padding: 0;
- border: 0;
- font-weight: normal;
+ padding: 0;
+ border: 0;
+ font-weight: normal;
}
.header-dialog .pid_titlebar th {
- padding: 5px;
- text-align: left;
- border-right: 1px solid #ccc;
+ padding: 5px;
+ text-align: left;
+ border-right: 1px solid #ccc;
}
.header-dialog .pid_titlebar th:first-child {
- text-align: left;
+ text-align: left;
}
.header-dialog .pid_titlebar th:last-child {
- border-right: none;
+ border-right: none;
}
/* for labels in PID rows */
.header-dialog table:not(.parameter) tr td:first-child {
- text-align: center;
- padding-left: 5px;
+ text-align: center;
+ padding-left: 5px;
}
.header-dialog table tr td:last-child {
- border-right: 0 solid #ccc;
+ border-right: 0 solid #ccc;
}
.header-dialog table td,
.header-dialog .pid_titlebar th {
- padding: 1px;
- width: 15%;
- border-right: 1px solid #ccc;
- border-bottom: 1px solid #ccc;
+ padding: 1px;
+ width: 15%;
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
}
.header-dialog table tr td {
- text-align: center;
- padding-left: ;
+ text-align: center;
+ padding-left: ;
}
/* numeric inputs in tables */
.header-dialog table input {
- display: block;
- width: calc(100% - 4px);
- text-align: center;
- border: 0 solid #ccc;
- border-radius: 0;
- background-color: #F5F5F5;
+ display: block;
+ width: calc(100% - 4px);
+ text-align: center;
+ border: 0 solid #ccc;
+ border-radius: 0;
+ background-color: #f5f5f5;
}
.header-dialog .missing {
- background:rgba(255,0,0,0.2);
+ background: rgba(255, 0, 0, 0.2);
}
.header-dialog .static-features span {
- /* background-color:yellow; */
+ /* background-color:yellow; */
}
.header-dialog .static-features table {
- padding: 1px;
- border-right: 1px solid #ccc;
+ padding: 1px;
+ border-right: 1px solid #ccc;
}
.header-dialog .controller {
- float: left;
- width: calc(100% - 10px);
- margin-left: 0;
- margin-bottom: 10px;
- border-radius: 4px;
- border: 1px solid #ccc;
+ float: left;
+ width: calc(100% - 10px);
+ margin-left: 0;
+ margin-bottom: 10px;
+ border-radius: 4px;
+ border: 1px solid #ccc;
}
.header-dialog .controller select {
- width: calc(100% - 10px);
- height: 18px;
- line-height: 18px;
+ width: calc(100% - 10px);
+ height: 18px;
+ line-height: 18px;
}
.header-dialog .profile {
- float: left;
- width: calc(25% - 2px); /* - border*/
- border: 1px solid #ccc;
- border-radius: 3px;
+ float: left;
+ width: calc(25% - 2px); /* - border*/
+ border: 1px solid #ccc;
+ border-radius: 3px;
}
.header-dialog .profile .head {
- display: block;
- text-align: left;
- line-height: 18px;
- border-bottom: 1px solid #ccc;
- background-color: #828885;
- color: white;
- height: 19px;
- font-weight: normal;
- padding: 2px 2px 3px 6px;
+ display: block;
+ text-align: left;
+ line-height: 18px;
+ border-bottom: 1px solid #ccc;
+ background-color: #828885;
+ color: white;
+ height: 19px;
+ font-weight: normal;
+ padding: 2px 2px 3px 6px;
}
.header-dialog .profile select {
- width: 100%;
- padding-left: calc(100% - 35px);
- height: 18px;
- line-height: 18px;
+ width: 100%;
+ padding-left: calc(100% - 35px);
+ height: 18px;
+ line-height: 18px;
}
.header-dialog .pid_tuning .name {
- width: 25%;
+ width: 25%;
}
.header-dialog .proportional {
- width: 25%;
+ width: 25%;
}
.header-dialog .integral {
- width: 25%;
+ width: 25%;
}
.header-dialog .derivative {
- width: 25%;
+ width: 25%;
}
.header-dialog .cbox {
- width: 5%;
+ width: 5%;
}
.header-dialog .cbox-label {
- width: 20%;
+ width: 20%;
}
.header-dialog .cbox-description {
- width: 80%;
+ width: 80%;
}
/* text in parameter boxes*/
.header-dialog .parameter {
- float: right;
- /* padding-left: 0; */
+ float: right;
+ /* padding-left: 0; */
}
/*drop-down names display text*/
html:not(.isCF) .cf-only,
html:not(.isBF) .bf-only {
- /*display:none;*/
- /*visibility:hidden;*/
- /*opacity:0.25;*/
+ /*display:none;*/
+ /*visibility:hidden;*/
+ /*opacity:0.25;*/
}
/*
html:not(.isBF28) .bf-only {
@@ -417,207 +418,225 @@ html:not(.isBF28) .bf-only {
*/
.header-dialog .top-buttons {
- float: right;
+ float: right;
}
.header-dialog .fixed_band {
- position: absolute;
- width: 100%;
- bottom: 0;
+ position: absolute;
+ width: 100%;
+ bottom: 0;
}
.header-dialog .spacer_box {
- float: left;
+ float: left;
}
-.pid_labels th{
- text-align: center;
+.pid_labels th {
+ text-align: center;
}
-.pid_labels th label{
- font-weight: normal;
- font-size: 12px;
+.pid_labels th label {
+ font-weight: normal;
+ font-size: 12px;
}
.pid_mode {
- width: 100%;
- height: 25px;
- float: left;
- margin: 0;
- text-align: left;
- line-height: 13px;
- padding: 8px 0 0 5px;
- font-size: 12px;
- border-bottom: 1px solid #ccc;
- color: #202020;
- font-family: 'open_sans', Arial, serif;
- background: #D6D6D6 linear-gradient(315deg, rgba(255, 255, 255, .2) 10%, transparent 10%, transparent 20%,
- rgba(255, 255, 255, .2) 20%, rgba(255, 255, 255, .2) 30%, transparent 30%, transparent 40%,
- rgba(255, 255, 255, .2) 40%, rgba(255, 255, 255, .2) 50%, transparent 50%, transparent 60%,
- rgba(255, 255, 255, .2) 60%, rgba(255, 255, 255, .2) 70%, transparent 70%, transparent 80%,
- rgba(255, 255, 255, .2) 80%, rgba(255, 255, 255, .2) 90%, transparent 90%, transparent 100%,
- rgba(255, 255, 255, .2) 100%, transparent);
+ width: 100%;
+ height: 25px;
+ float: left;
+ margin: 0;
+ text-align: left;
+ line-height: 13px;
+ padding: 8px 0 0 5px;
+ font-size: 12px;
+ border-bottom: 1px solid #ccc;
+ color: #202020;
+ font-family: "open_sans", Arial, serif;
+ background: #d6d6d6
+ linear-gradient(
+ 315deg,
+ rgba(255, 255, 255, 0.2) 10%,
+ transparent 10%,
+ transparent 20%,
+ rgba(255, 255, 255, 0.2) 20%,
+ rgba(255, 255, 255, 0.2) 30%,
+ transparent 30%,
+ transparent 40%,
+ rgba(255, 255, 255, 0.2) 40%,
+ rgba(255, 255, 255, 0.2) 50%,
+ transparent 50%,
+ transparent 60%,
+ rgba(255, 255, 255, 0.2) 60%,
+ rgba(255, 255, 255, 0.2) 70%,
+ transparent 70%,
+ transparent 80%,
+ rgba(255, 255, 255, 0.2) 80%,
+ rgba(255, 255, 255, 0.2) 90%,
+ transparent 90%,
+ transparent 100%,
+ rgba(255, 255, 255, 0.2) 100%,
+ transparent
+ );
}
.pid_titlebar {
- color: #fff;
- background-color: #828885;
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
- height: 18px;
- padding: 30px;
+ color: #fff;
+ background-color: #828885;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ height: 18px;
+ padding: 30px;
}
.pid_titlebar td:first-child {
- text-align: left;
+ text-align: left;
}
.show {
- width:110px;
- float:right;
- margin-right:3px;
+ width: 110px;
+ float: right;
+ margin-right: 3px;
}
.show a {
- margin-left: 10px;
- width: calc(100% - 10px);
+ margin-left: 10px;
+ width: calc(100% - 10px);
}
.header-dialog .helpicon {
- margin-top: -1px;
+ margin-top: -1px;
}
.header-dialog .number .helpicon {
- margin-top: 3px;
- margin-right: 0;
+ margin-top: 3px;
+ margin-right: 0;
}
.header-dialog .gui_box_titlebar .helpicon {
- margin-top: 5px;
- margin-right: 5px;
+ margin-top: 5px;
+ margin-right: 5px;
}
.header-dialog .numberspacer {
- float: left;
- width: 65px;
- height: 21px;
+ float: left;
+ width: 65px;
+ height: 21px;
}
.header-dialog .number {
- margin-bottom: 5px;
- clear: left;
- padding-bottom: 5px;
- border-bottom: 1px solid #ddd;
- width: 100%;
- float: left;
+ margin-bottom: 5px;
+ clear: left;
+ padding-bottom: 5px;
+ border-bottom: 1px solid #ddd;
+ width: 100%;
+ float: left;
}
.header-dialog .number:last-child {
- padding-bottom: 5px;
- border-bottom: 0;
+ padding-bottom: 5px;
+ border-bottom: 0;
}
.header-dialog .number input {
- width: 50px;
- padding-left: 3px;
- text-align: center;
- border: 1px solid silver;
- border-radius: 3px;
- margin-right: 11px;
- font-weight: normal;
+ width: 50px;
+ padding-left: 3px;
+ text-align: center;
+ border: 1px solid silver;
+ border-radius: 3px;
+ margin-right: 11px;
+ font-weight: normal;
}
-.header-dialog .other table td,
+.header-dialog .other table td,
.header-dialog .rxMode table td,
-.header-dialog .misc table td
- {
- padding: 1px;
- border: 0 solid #ccc;
+.header-dialog .misc table td {
+ padding: 1px;
+ border: 0 solid #ccc;
}
.header-dialog .noline td {
- border: 0 solid #ccc;
+ border: 0 solid #ccc;
}
.header-dialog .features label,
-.header-dialog .static-features label{
- /* width: 70px; */
- /* padding-left: 3px; */
- text-align: left;
- border: 0 solid silver;
- border-radius: 3px;
- margin-right: 11px;
- font-weight: normal;
- font-size: 13px;
- width:90%;
+.header-dialog .static-features label {
+ /* width: 70px; */
+ /* padding-left: 3px; */
+ text-align: left;
+ border: 0 solid silver;
+ border-radius: 3px;
+ margin-right: 11px;
+ font-weight: normal;
+ font-size: 13px;
+ width: 90%;
}
.header-dialog .features span,
.header-dialog .static-features span {
- padding-right: 3px;
- height: 18px;
- line-height: 18px;
- text-align: left;
- border: 0 solid silver;
- border-radius: 3px;
- margin-right: 11px;
- font-weight: normal;
- font-size: 11px;
+ padding-right: 3px;
+ height: 18px;
+ line-height: 18px;
+ text-align: left;
+ border: 0 solid silver;
+ border-radius: 3px;
+ margin-right: 11px;
+ font-weight: normal;
+ font-size: 11px;
}
.header-dialog .gui_box span {
- font-style: normal;
- font-family: 'open_sansregular', Arial, sans-serif;
- line-height: 19px;
- color: #404040;
- font-size: 11px;
+ font-style: normal;
+ font-family: "open_sansregular", Arial, sans-serif;
+ line-height: 19px;
+ color: #404040;
+ font-size: 11px;
}
.noline {
- border:0;
+ border: 0;
}
.header-dialog .resetbt {
- width: 140px;
- float: right;
+ width: 140px;
+ float: right;
}
.header-dialog .right {
- float: right;
+ float: right;
}
.header-dialog .pids {
- float: left;
- width: 25%;
+ float: left;
+ width: 25%;
}
.header-dialog .leftzero {
- padding-left: 0;
+ padding-left: 0;
}
.header-dialog .tpa-breakpoint {
- border-top-left-radius: 0;
+ border-top-left-radius: 0;
}
.header-dialog .roll {
- border-bottom-left-radius: 3px;
+ border-bottom-left-radius: 3px;
}
.header-dialog .pidTuningLevel {
- float: left;
+ float: left;
}
.header-dialog .borderleft {
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
}
.header-dialog .textleft {
- width: 25%;
- float: left;
- text-align: left;
+ width: 25%;
+ float: left;
+ text-align: left;
}
.header-dialog .topspacer {
- margin-top:15px;
+ margin-top: 15px;
}
diff --git a/src/css/keys_dialog.css b/src/css/keys_dialog.css
index 12345b77..a3ea668f 100644
--- a/src/css/keys_dialog.css
+++ b/src/css/keys_dialog.css
@@ -1,137 +1,133 @@
.keys-dialog .parameter th {
- background-color: #828885;
- padding: 4px;
- border-left: 0 solid #ccc;
- border-bottom: 1px solid #ccc;
- font-weight: bold;
- color: white;
- text-align: left;
+ background-color: #828885;
+ padding: 4px;
+ border-left: 0 solid #ccc;
+ border-bottom: 1px solid #ccc;
+ font-weight: bold;
+ color: white;
+ text-align: left;
}
.keys-dialog .modal-dialog {
- width: 90%;
- min-width: 800px;
+ width: 90%;
+ min-width: 800px;
}
.keys-dialog .gui_box {
- margin-bottom: 0;
+ margin-bottom: 0;
}
.keys-dialog .spacer_box {
- padding-bottom: 10px;
- float: left;
- width: calc(100% - 5px);
+ padding-bottom: 10px;
+ float: left;
+ width: calc(100% - 5px);
}
.keys-dialog .show {
- width:110px;
- float:right;
- margin-right:3px;
+ width: 110px;
+ float: right;
+ margin-right: 3px;
}
-
+
.keys-dialog .show a {
- margin-left: 10px;
- width: calc(100% - 10px);
+ margin-left: 10px;
+ width: calc(100% - 10px);
}
.keys-dialog .helpicon {
- margin-top: -1px;
+ margin-top: -1px;
}
-
-
-.keys-dialog li{
- display: flex;
- position: static;
- margin-bottom: 0.05em;
- height: 39px;
+.keys-dialog li {
+ display: flex;
+ position: static;
+ margin-bottom: 0.05em;
+ height: 39px;
}
-.keys-dialog ul{
- list-style-type: none;
- padding: initial;
- float: left;
- width: calc(100% - 4px);
+.keys-dialog ul {
+ list-style-type: none;
+ padding: initial;
+ float: left;
+ width: calc(100% - 4px);
}
.keys-dialog .keys {
- text-align: left;
+ text-align: left;
}
.keys-dialog .key {
- margin: 3px;
- background-color: beige;
- display: inline-block;
- border-radius: 8px;
- border-style: groove;
- padding: 5px 10px;
- vertical-align: middle;
+ margin: 3px;
+ background-color: beige;
+ display: inline-block;
+ border-radius: 8px;
+ border-style: groove;
+ padding: 5px 10px;
+ vertical-align: middle;
}
.keys-dialog .normal {
- margin: 3px;
- display: inline-block;
- border-radius: 8px;
- border-style: none;
- padding: 8px 0;
- vertical-align: middle;
+ margin: 3px;
+ display: inline-block;
+ border-radius: 8px;
+ border-style: none;
+ padding: 8px 0;
+ vertical-align: middle;
}
.keys-dialog div.description {
- flex: 1;
- padding-left: 10px;
- color: #7d7d7d;
- text-align: justify;
- position: relative;
- align-self: center;
- font-size: 11px;
+ flex: 1;
+ padding-left: 10px;
+ color: #7d7d7d;
+ text-align: justify;
+ position: relative;
+ align-self: center;
+ font-size: 11px;
}
-
-
.keys-dialog .gui_box_titlebar .helpicon {
- margin-top: 5px;
- margin-right: 5px;
+ margin-top: 5px;
+ margin-right: 5px;
}
.keys-dialog .noline td {
- border: 0 solid #ccc;
+ border: 0 solid #ccc;
}
.keys-dialog .gui_box span {
- font-style: normal;
- font-family: 'open_sansregular', Arial, sans-serif;
- line-height: 19px;
- color: #7d7d7d;
- font-size: 11px;
- text-align: left;
- min-width: 165px;
- vertical-align: middle;
+ font-style: normal;
+ font-family: "open_sansregular", Arial, sans-serif;
+ line-height: 19px;
+ color: #7d7d7d;
+ font-size: 11px;
+ text-align: left;
+ min-width: 165px;
+ vertical-align: middle;
}
.noline {
- border:0;
+ border: 0;
}
.keys-dialog .right {
- float: right;
+ float: right;
}
.keys-dialog .leftzero {
- padding-left: 0;
+ padding-left: 0;
}
.keys-dialog .borderleft {
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
}
.keys-dialog .textleft {
- width: 25%;
- float: left;
- text-align: left;
+ width: 25%;
+ float: left;
+ text-align: left;
}
.keys-dialog .topspacer {
- margin-top:15px;
-}
\ No newline at end of file
+ margin-top: 15px;
+}
diff --git a/src/css/main.css b/src/css/main.css
index 74c62483..37a37bfd 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -3,850 +3,886 @@
@import "leaflet/dist/leaflet.css";
body {
- padding-bottom:2em;
+ padding-bottom: 2em;
}
-html, body {
- height:100%;
- overflow-y: visible; /* Show page scrollbar when packaged as a Chrome app */
+html,
+body {
+ height: 100%;
+ overflow-y: visible; /* Show page scrollbar when packaged as a Chrome app */
}
a:hover {
- text-decoration: none;
- color: inherit;
+ text-decoration: none;
+ color: inherit;
}
a.disabled {
- pointer-events: none;
- cursor: default;
- color: #999;
+ pointer-events: none;
+ cursor: default;
+ color: #999;
}
/* Add an extended wide size to the page container for large monitors */
@media (min-width: 1400px) {
- .container.main-pane {
- width:90%;
- }
+ .container.main-pane {
+ width: 90%;
+ }
}
@media (max-width: 1140px) {
- .navbar-header {
- float: none;
- }
- .navbar-toggle {
- display: block;
- }
- .navbar-collapse {
- border-top: 1px solid transparent;
- box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
- }
- .navbar-collapse.collapse {
- display: none!important;
- }
- .navbar-nav {
- float: none!important;
- margin: 7px -15px;
- }
- .navbar-nav>li {
- float: none;
- }
- .navbar-nav>li>a {
- padding-top: 10px;
- padding-bottom: 10px;
- }
- .navbar-text {
- float: none;
- margin: 15px 0;
- }
- /* since 3.1.0 */
- .navbar-collapse.collapse.in {
- display: block!important;
- }
- .collapsing {
- overflow: hidden!important;
- }
+ .navbar-header {
+ float: none;
+ }
+ .navbar-toggle {
+ display: block;
+ }
+ .navbar-collapse {
+ border-top: 1px solid transparent;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ }
+ .navbar-collapse.collapse {
+ display: none !important;
+ }
+ .navbar-nav {
+ float: none !important;
+ margin: 7px -15px;
+ }
+ .navbar-nav > li {
+ float: none;
+ }
+ .navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+ .navbar-text {
+ float: none;
+ margin: 15px 0;
+ }
+ /* since 3.1.0 */
+ .navbar-collapse.collapse.in {
+ display: block !important;
+ }
+ .collapsing {
+ overflow: hidden !important;
+ }
}
@media (min-width: 768px) {
- .welcome-pane .panel-body {
- min-height: 100px;
- }
-
- .log-workspace-panel {
- width: 200px;
- }
- .dropdown-menu{
- width:200px;
- }
- .workspace-selector-title {
- width: 130px
- }
+ .welcome-pane .panel-body {
+ min-height: 100px;
+ }
+ .log-workspace-panel {
+ width: 200px;
+ }
+ .dropdown-menu {
+ width: 200px;
+ }
+ .workspace-selector-title {
+ width: 130px;
+ }
}
@media (max-width: 768px) {
- .workspace-selector-title {
- width: 94px
- }
+ .workspace-selector-title {
+ width: 94px;
+ }
}
/*** toolbar expansion ***/
@media (max-width: 1020px) {
- .video-top-controls * h4 {
- display: none!important;
- }
+ .video-top-controls * h4 {
+ display: none !important;
+ }
- .video-top-controls {
- height: 50px;
- padding-top: 10px;
- }
+ .video-top-controls {
+ height: 50px;
+ padding-top: 10px;
+ }
- .graph-row,
- .log-field-values {
- top: 86px!important;
- }
+ .graph-row,
+ .log-field-values {
+ top: 86px !important;
+ }
}
-
@media (max-width: 675px) {
- .container-fluid.main-pane,
- html.has-log div.log-seek-bar {
- padding-left:0;
- padding-right:0;
- }
-
- .container-fluid.main-pane .graph-row {
- width:100%;
- }
-
- div.log-graph-config{
- padding: 1em 0;
- line-height: 1.1;
- }
-
- div.log-graph-config h2 {
- margin: 0;
- display: none;
- }
-
- div.log-graph-config h3 {
- margin-top: 0.25em;
- margin-bottom: 0.25em;
- font-size: 115%;
- }
-
- div.log-index div.form-control-static {
- min-height: 0px;
- padding-top: 0px;
- padding-bottom: 0px;
- }
-
- button.btn {
- padding: 3px 6px;
- font-size: 12px;
- }
-
- ul.video-top-controls {
- height: 30px;
- padding-top: 5px;
- }
-
- .video-top-controls li {
- margin-right: 1px;
- }
-
- div.graph-row {
- top: 70px!important;
- }
-
- div.navbar-logo .log-filename {
- font-size: 12px;
- }
-
- div.navbar-logo img {
- height: 25px;
- }
-
- div.navbar-logo span {
- top: 25px;
- line-height: 1.5;
- }
-
- html.has-log button.log-jump-start,
- html.has-log button.log-jump-back,
- html.has-log button.log-jump-forward,
- html.has-log button.log-jump-end {
- display:none;
- }
+ .container-fluid.main-pane,
+ html.has-log div.log-seek-bar {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ .container-fluid.main-pane .graph-row {
+ width: 100%;
+ }
+
+ div.log-graph-config {
+ padding: 1em 0;
+ line-height: 1.1;
+ }
+
+ div.log-graph-config h2 {
+ margin: 0;
+ display: none;
+ }
+
+ div.log-graph-config h3 {
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
+ font-size: 115%;
+ }
+
+ div.log-index div.form-control-static {
+ min-height: 0px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ }
+
+ button.btn {
+ padding: 3px 6px;
+ font-size: 12px;
+ }
+
+ ul.video-top-controls {
+ height: 30px;
+ padding-top: 5px;
+ }
+
+ .video-top-controls li {
+ margin-right: 1px;
+ }
+
+ div.graph-row {
+ top: 70px !important;
+ }
+
+ div.navbar-logo .log-filename {
+ font-size: 12px;
+ }
+
+ div.navbar-logo img {
+ height: 25px;
+ }
+
+ div.navbar-logo span {
+ top: 25px;
+ line-height: 1.5;
+ }
+
+ html.has-log button.log-jump-start,
+ html.has-log button.log-jump-back,
+ html.has-log button.log-jump-forward,
+ html.has-log button.log-jump-end {
+ display: none;
+ }
}
/* With video */
@media (max-width: 920px) {
- html.has-video .video-top-controls li {
- margin-right: 1px;
- }
-
- html.has-video button.log-jump-start,
- html.has-video button.log-jump-back,
- html.has-video button.log-jump-forward,
- html.has-video button.log-jump-end {
- display:none;
- }
+ html.has-video .video-top-controls li {
+ margin-right: 1px;
+ }
+
+ html.has-video button.log-jump-start,
+ html.has-video button.log-jump-back,
+ html.has-video button.log-jump-forward,
+ html.has-video button.log-jump-end {
+ display: none;
+ }
}
@media (max-width: 1150px) {
- html.has-video .view-buttons-expanded {
- display: none!important;
- }
+ html.has-video .view-buttons-expanded {
+ display: none !important;
+ }
}
@media (min-width: 1151px) {
- html.has-video .view-buttons {
- display: none!important;
- }
+ html.has-video .view-buttons {
+ display: none !important;
+ }
}
@media (max-width: 1404px) {
- html.has-video .playback-rate-expanded,
- html.has-video .graph-zoom-expanded,
- html.has-video .log-sync-expanded {
- display: none !important;
- }
+ html.has-video .playback-rate-expanded,
+ html.has-video .graph-zoom-expanded,
+ html.has-video .log-sync-expanded {
+ display: none !important;
+ }
}
@media (min-width: 1405px) {
- html.has-video .playback-rate,
- html.has-video .zoom-menu,
- html.has-video .sync-menu {
- display: none!important;
- }
+ html.has-video .playback-rate,
+ html.has-video .zoom-menu,
+ html.has-video .sync-menu {
+ display: none !important;
+ }
}
@media (max-width: 1537px) {
- html.has-video .log-chart-time-panel {
- display: none!important;
- }
+ html.has-video .log-chart-time-panel {
+ display: none !important;
+ }
}
/* Without video */
@media (max-width: 886px) {
- html:not(.has-video) .view-buttons-expanded {
- display: none!important;
- }
+ html:not(.has-video) .view-buttons-expanded {
+ display: none !important;
+ }
}
@media (min-width: 887px) {
- html:not(.has-video) .view-buttons {
- display: none!important;
- }
+ html:not(.has-video) .view-buttons {
+ display: none !important;
+ }
}
@media (max-width: 1020px) {
- html:not(.has-video) .playback-rate-expanded,
- html:not(.has-video) .graph-zoom-expanded,
- html:not(.has-video) .log-sync-expanded {
- display: none !important;
- }
+ html:not(.has-video) .playback-rate-expanded,
+ html:not(.has-video) .graph-zoom-expanded,
+ html:not(.has-video) .log-sync-expanded {
+ display: none !important;
+ }
}
@media (min-width: 1021px) {
- html:not(.has-video) .playback-rate,
- html:not(.has-video) .zoom-menu,
- html:not(.has-video) .sync-menu {
- display: none!important;
- }
+ html:not(.has-video) .playback-rate,
+ html:not(.has-video) .zoom-menu,
+ html:not(.has-video) .sync-menu {
+ display: none !important;
+ }
}
@media (max-width: 1153px) {
- html:not(.has-video) .log-chart-time-panel {
- display: none!important;
- }
+ html:not(.has-video) .log-chart-time-panel {
+ display: none !important;
+ }
}
/*** end toolbar expansion ***/
input.video-offset {
- width: 65px!important;
+ width: 65px !important;
}
input.graph-time {
- text-align: right;
+ text-align: right;
}
-.welcome-pane, .main-pane {
- position: fixed;
- top: 15px;
- bottom: 100px;
- left: 0;
- right: 0;
- display: contents;
+.welcome-pane,
+.main-pane {
+ position: fixed;
+ top: 15px;
+ bottom: 100px;
+ left: 0;
+ right: 0;
+ display: contents;
}
.header-pane {
- width: 100%;
+ width: 100%;
}
html:not(.has-log):not(.has-video) .header-pane {
- visibility:hidden;
+ visibility: hidden;
}
.log-info {
- max-width: 30em;
+ max-width: 30em;
}
.log-info .form-group {
- margin-bottom:10px;
+ margin-bottom: 10px;
}
/* Bootstrap-styled file inputs by abeautifulsite http://www.abeautifulsite.net/whipping-file-inputs-into-shape-with-bootstrap-3/ */
.btn-file {
- position: relative;
- overflow: hidden;
-}
-.btn-file input[type=file] {
- position: absolute;
- top: 0;
- right: 0;
- min-width: 100%;
- min-height: 100%;
- font-size: 100px;
- text-align: right;
- filter: alpha(opacity=0);
- opacity: 0;
- outline: none;
- background: white;
- cursor: inherit;
- display: block;
+ position: relative;
+ overflow: hidden;
+}
+.btn-file input[type="file"] {
+ position: absolute;
+ top: 0;
+ right: 0;
+ min-width: 100%;
+ min-height: 100%;
+ font-size: 100px;
+ text-align: right;
+ filter: alpha(opacity=0);
+ opacity: 0;
+ outline: none;
+ background: white;
+ cursor: inherit;
+ display: block;
}
.log-seek-bar canvas {
- width:100%;
- height:50px;
- margin-top:0.5em;
+ width: 100%;
+ height: 50px;
+ margin-top: 0.5em;
}
.graph-row {
- margin-top:1em;
- display:none;
+ margin-top: 1em;
+ display: none;
}
#main-page {
- position: static;
- top: 60px;
+ position: static;
+ top: 60px;
}
.log-close-legend-dialog {
- float: right;
- font-size: 14px;
- color: #bbb;
- position: relative;
- top: -10px;
- cursor: pointer;
+ float: right;
+ font-size: 14px;
+ color: #bbb;
+ position: relative;
+ top: -10px;
+ cursor: pointer;
}
.log-open-legend-dialog {
- float: right;
- color: #bbb;
- padding-right: 14px;
- padding-top: 8px;
- font-size: 14px;
- display: none;
- cursor: pointer;
+ float: right;
+ color: #bbb;
+ padding-right: 14px;
+ padding-top: 8px;
+ font-size: 14px;
+ display: none;
+ cursor: pointer;
}
.log-graph-legend {
- font-size:85%;
- -webkit-flex-grow:1;
- flex-grow:1;
- overflow-y: auto;
- overflow-x: hidden;
- margin-bottom:1em;
+ font-size: 85%;
+ -webkit-flex-grow: 1;
+ flex-grow: 1;
+ overflow-y: auto;
+ overflow-x: hidden;
+ margin-bottom: 1em;
}
.log-graph {
- position:relative;
- -webkit-flex-grow: 1;
- flex-grow: 1;
- background-color: black;
+ position: relative;
+ -webkit-flex-grow: 1;
+ flex-grow: 1;
+ background-color: black;
}
.log-graph-config {
- background-color:#222;
- color:#bbb;
- padding:1em;
-
- min-width: 135px;
-
- -webkit-flex-grow: 0.02;
- flex-grow: 0.02;
-
- -webkit-flex-direction:column;
- flex-direction:column;
-
- display:none;
+ background-color: #222;
+ color: #bbb;
+ padding: 1em;
+
+ min-width: 135px;
+
+ -webkit-flex-grow: 0.02;
+ flex-grow: 0.02;
+
+ -webkit-flex-direction: column;
+ flex-direction: column;
+
+ display: none;
}
.log-graph-config h2 {
- font-size:160%;
- margin-top:5px;
+ font-size: 160%;
+ margin-top: 5px;
}
.log-graph-config h3 {
- font-size:125%;
- margin-top: 1em;
- margin-bottom: 0.5em;
- cursor: pointer;
+ font-size: 125%;
+ margin-top: 1em;
+ margin-bottom: 0.5em;
+ cursor: pointer;
}
html.has-log .log-graph-config {
- display:-webkit-flex;
- display:flex;
+ display: -webkit-flex;
+ display: flex;
}
.config-graph {
- margin:0.5em 0;
- background-color: white;
+ margin: 0.5em 0;
+ background-color: white;
}
.config-graph dd {
- margin-top:0.5em;
+ margin-top: 0.5em;
}
.config-graph-header {
- margin-bottom: 0;
- margin-right: 0;
- width: 100%;
+ margin-bottom: 0;
+ margin-right: 0;
+ width: 100%;
}
.config-graph-field {
- margin-bottom: 0.5em;
- margin-top: 0.2em;
- font-size: 12px;
+ margin-bottom: 0.5em;
+ margin-top: 0.2em;
+ font-size: 12px;
}
.config-graph-field-list {
- margin-top: 8px;
- margin-bottom: 5px;
+ margin-top: 8px;
+ margin-bottom: 5px;
}
.config-graph-field .form-control {
- font-size: 12px;
- height: 30px;
+ font-size: 12px;
+ height: 30px;
}
.config-graph-field-header {
- margin-bottom: 0;
- font-size: 12px;
+ margin-bottom: 0;
+ font-size: 12px;
}
.config-graph-field-list td,
.config-graph-field-list th {
- padding: 2px 2px;
+ padding: 2px 2px;
}
.config-graph-field input {
- padding: 5px 5px;
- text-align: right;
- max-width: 45px;
+ padding: 5px 5px;
+ text-align: right;
+ max-width: 45px;
}
-.config-graph-field input[name=grid] {
- width: 25px;
+.config-graph-field input[name="grid"] {
+ width: 25px;
}
.config-graph-field select.form-control {
- padding-left: 2px;
- max-width: 150px;
+ padding-left: 2px;
+ max-width: 180px;
}
-.config-graph-field input[name=linewidth],
+.config-graph-field input[name="linewidth"],
.config-graph-field select.color-picker {
- height:30px;
- max-width: 30px;
- border-radius: 4px;
+ height: 30px;
+ max-width: 30px;
+ border-radius: 4px;
}
.config-graph h4 button {
- margin-left:0.5em;
+ margin-left: 0.5em;
}
-.config-graph table tr{
- line-height: 10px;
+.config-graph table tr {
+ line-height: 10px;
}
.setup-parameters {
- margin:0.5em 0;
+ margin: 0.5em 0;
}
.setup-parameters dd {
- margin-top:0.5em;
+ margin-top: 0.5em;
}
.setup-parameter {
- margin:0.5em 0;
+ margin: 0.5em 0;
}
#graphCanvas {
- position:absolute;
- top:0;
- left:0;
- width:100%;
- height:100%;
- background-color: black;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: black;
}
#craftCanvas,
#analyserCanvas,
#mapContainer,
#stickCanvas {
- position:absolute;
- top:0;
- left:0;
- /* pointer-events:none; */ /* Allow the user to drag the graph lines instead of hitting the craft */
- display:none;
+ position: absolute;
+ top: 0;
+ left: 0;
+ /* pointer-events:none; */ /* Allow the user to drag the graph lines instead of hitting the craft */
+ display: none;
}
html.has-craft #craftCanvas,
html.has-analyser #analyserCanvas,
html.has-map #mapContainer,
html.has-sticks #stickCanvas {
- display:block;
+ display: block;
}
#mapContainer.no-gps-data:before {
- position: absolute;
- display: block;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 9999;
- content: "";
- background-color: gray;
- opacity: .5;
+ position: absolute;
+ display: block;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 9999;
+ content: "";
+ background-color: gray;
+ opacity: 0.5;
}
#mapContainer.no-gps-data:after {
- position: absolute;
- display: block;
- top: calc(30%);
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 10001;
- content: "No GPS Data";
- text-align: center;
- font-size: 3vw;
+ position: absolute;
+ display: block;
+ top: calc(30%);
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 10001;
+ content: "No GPS Data";
+ text-align: center;
+ font-size: 3vw;
}
-html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenException) {
- display:block;
+html.has-analyser-fullscreen.has-analyser
+ .analyser
+ input:not(.onlyFullScreenException) {
+ display: block;
}
-#analyser ,#log-seek-bar {
- z-index: 10;
+#analyser,
+#log-seek-bar {
+ z-index: 10;
}
/* This filters change the color of a black png image. For new colors check: https://codepen.io/sosuke/pen/Pjoqqp */
.isBF #mapContainer .icon {
- filter: invert(36%) sepia(28%) saturate(3957%) hue-rotate(28deg) brightness(93%) contrast(103%);
+ filter: invert(36%) sepia(28%) saturate(3957%) hue-rotate(28deg)
+ brightness(93%) contrast(103%);
}
.isCF #mapContainer .icon {
- filter: invert(28%) sepia(100%) saturate(2050%) hue-rotate(134deg) brightness(100%) contrast(104%);
+ filter: invert(28%) sepia(100%) saturate(2050%) hue-rotate(134deg)
+ brightness(100%) contrast(104%);
}
.isINAV #mapContainer .icon {
- filter: invert(14%) sepia(100%) saturate(4698%) hue-rotate(244deg) brightness(64%) contrast(130%);
+ filter: invert(14%) sepia(100%) saturate(4698%) hue-rotate(244deg)
+ brightness(64%) contrast(130%);
}
.analyser:hover .non-shift #analyserResize {
- opacity: 1;
- height: auto;
- transition: opacity 500ms ease-in;
+ opacity: 1;
+ height: auto;
+ transition: opacity 500ms ease-in;
}
.analyser #analyserResize {
- color: #bbb;
- height: 0;
- overflow: hidden;
- opacity: 0;
- top: 5px;
- left: 975px;
- float: right;
- z-index: 9;
- position: absolute;
- font-size: 18px;
+ color: #bbb;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ top: 5px;
+ left: 975px;
+ float: right;
+ z-index: 9;
+ position: absolute;
+ font-size: 18px;
}
.analyser:hover .non-shift #spectrumType {
- opacity: 1;
- height: auto;
- transition: opacity 500ms ease-in;
+ opacity: 1;
+ height: auto;
+ transition: opacity 500ms ease-in;
}
.analyser #spectrumType {
- color: #bbb;
- height: 0;
- overflow: hidden;
- opacity: 0;
- left: 5px;
- float: left;
- z-index: 9;
- position: absolute;
- font-size: 9px;
+ color: #bbb;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ left: 5px;
+ float: left;
+ z-index: 9;
+ position: absolute;
+ font-size: 9px;
}
.analyser #spectrumType select {
- border-radius: 3px;
- padding: 0px 5px;
- color: black;
+ border-radius: 3px;
+ padding: 0px 5px;
+ color: black;
}
.analyser:hover .non-shift #overdrawSpectrumType {
- opacity: 1;
- height: auto;
- transition: opacity 500ms ease-in;
+ opacity: 1;
+ height: auto;
+ transition: opacity 500ms ease-in;
}
.analyser #overdrawSpectrumType {
- height: 0;
- overflow: hidden;
- opacity: 0;
- left: 120px;
- float: left;
- z-index: 9;
- position: absolute;
- font-size: 9px;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ left: 120px;
+ float: left;
+ z-index: 9;
+ position: absolute;
+ font-size: 9px;
}
.analyser #overdrawSpectrumType select {
- border-radius: 3px;
- padding: 0px 5px;
- color: black;
+ border-radius: 3px;
+ padding: 0px 5px;
+ color: black;
+}
+
+.analyser:hover .non-shift #spectrumComparison {
+ opacity: 1;
+ height: auto;
+ transition: opacity 500ms ease-in;
}
-.analyser #analyserResize:hover {
- color: white;
- cursor: pointer;
- animation: ease-in 500ms;
+.analyser #spectrumComparison {
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ left: 260px;
+ float: left;
+ z-index: 9;
+ position: absolute;
+ font-size: 9px;
+ border: gray;
+ border-style: solid;
+ border-width: 1px;
+ display: flex;
+}
+
+.analyser #spectrumComparison select {
+ border-radius: 3px;
+ padding: 0px 5px;
+ color: black;
+}
+
+#spectrumComparison button {
+ width: auto;
+ height: auto;
+ border-radius: 3px;
+ float: left;
}
.analyser input#analyserZoomX {
- width: 100px;
- height : 10px;
- left: 975px;
- top: 10px;
- float: right;
+ width: 100px;
+ height: 10px;
+ left: 975px;
+ top: 10px;
+ float: right;
}
.analyser input#analyserZoomY {
- width: 10px;
- height: 100px;
- -webkit-appearance: slider-vertical;
- left: 1085px;
- top: 30px;
+ width: 10px;
+ height: 100px;
+ -webkit-appearance: slider-vertical;
+ left: 1085px;
+ top: 30px;
}
.analyser input.onlyFullScreen {
- display: none;
- padding: 3px;
- margin-right: 3px;
- z-index: 9;
- position: absolute;
+ display: none;
+ padding: 3px;
+ margin-right: 3px;
+ z-index: 9;
+ position: absolute;
}
-.analyser, .map-container, .log-seek-bar {
- position: absolute;
+.analyser,
+.map-container,
+.log-seek-bar {
+ position: absolute;
}
#log-seek-bar {
- width: 100%;
+ width: 100%;
}
.log-seek-bar:hover .non-shift #seekbarTypeSelect {
- opacity: 1;
- height: auto;
- transition: opacity 500ms ease-in;
+ opacity: 1;
+ height: auto;
+ transition: opacity 500ms ease-in;
}
#seekbarToolbar {
- position: absolute;
- top: 8px;
- left: 20px;
+ position: absolute;
+ top: 8px;
+ left: 20px;
}
.log-seek-bar #seekbarTypeSelect {
- height: 0;
- overflow: hidden;
- opacity: 0;
- left: 5px;
- float: left;
- z-index: 9;
- position: absolute;
- font-size: 9px;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ left: 5px;
+ float: left;
+ z-index: 9;
+ position: absolute;
+ font-size: 9px;
}
.log-seek-bar #seekbarTypeSelect select {
- border-radius: 3px;
- padding: 0px 5px;
- color: black;
+ border-radius: 3px;
+ padding: 0px 5px;
+ color: black;
}
.log-graph video {
- position:absolute;
- top:0;
- left:0;
- width:100%;
- height:100%;
- display:block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: block;
}
/* Prevent field value columns from jumping as their contents change */
.log-field-values th,
.log-field-values td {
- width:15%;
+ width: 15%;
}
.log-field-values td,
.configuration-file li {
- font-family: monospace;
- font-size: small;
- margin-left: 10px;
+ font-family: monospace;
+ font-size: small;
+ margin-left: 10px;
}
.configuration-file li:nth-child(even) {
- background-color: #EBF3F7;
+ background-color: #ebf3f7;
}
.log-field-values .raw-value {
- max-width:5em;
+ max-width: 5em;
}
.log-field-values table,
.log-field-values h4 {
- background-color: #FFFFFF;
- overflow-y: auto;
- height: 20px;
+ background-color: #ffffff;
+ overflow-y: auto;
+ height: 20px;
}
.log-field-values tr:nth-child(even) {
- background-color: #EBF3F7;
+ background-color: #ebf3f7;
}
html.has-table-overlay .log-field-values {
- position: fixed;
- top: 110px;
- left: 15px;
- width: calc(100% - 30px);
- display:block;
- padding: 9px;
- font-size: small;
- background-color: white;
- border: black;
- /*z-index: 10;*/
- bottom: 80px;
- overflow-y: auto;
- border-style: solid;
- border-width: 1px;
+ position: fixed;
+ top: 110px;
+ left: 15px;
+ width: calc(100% - 30px);
+ display: block;
+ padding: 9px;
+ font-size: small;
+ background-color: white;
+ border: black;
+ /*z-index: 10;*/
+ bottom: 80px;
+ overflow-y: auto;
+ border-style: solid;
+ border-width: 1px;
}
html:not(.has-config-overlay) .configuration-file,
html:not(.has-marker) .marker-offset {
- display:none;
+ display: none;
}
.configuration-changed {
- background-color: #f2dede;
+ background-color: #f2dede;
}
.configuration-file h3 {
- background-color: #d9edf7;
- margin-top: 2px;
- padding: 5px;
+ background-color: #d9edf7;
+ margin-top: 2px;
+ padding: 5px;
}
.configuration-close {
- float: right;
- color: #bbb;
- cursor: pointer;
+ float: right;
+ color: #bbb;
+ cursor: pointer;
}
html.has-config .configuration-file {
- position: fixed;
- top: 110px;
- left: 15px;
- width: calc(100% - 30px);
- padding: 9px;
- background-color: white;
- border: black;
- /* z-index: 10; */
- bottom: 80px;
- overflow-y: hidden;
- border-style: solid;
- border-width: 1px;
+ position: fixed;
+ top: 110px;
+ left: 15px;
+ width: calc(100% - 30px);
+ padding: 9px;
+ background-color: white;
+ border: black;
+ /* z-index: 10; */
+ bottom: 80px;
+ overflow-y: hidden;
+ border-style: solid;
+ border-width: 1px;
}
html.has-config .configuration-list {
- height:580px;
- overflow:hidden;
- overflow-y:scroll;
- margin-top: 5px;
- min-width:13em;
+ height: 580px;
+ overflow: hidden;
+ overflow-y: scroll;
+ margin-top: 5px;
+ min-width: 13em;
}
.graph-legend-field {
- margin-bottom: 0.5em;
- cursor:pointer;
- padding-left: 0.7em;
- padding-right: 0.7em;
+ margin-bottom: 0.5em;
+ cursor: pointer;
+ padding-left: 0.7em;
+ padding-right: 0.7em;
}
.graph-legend-field-name:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
.graph-legend-field-value {
- float: right;
+ float: right;
}
.graph-legend-field-settings {
- font-size: 8px;
- color: black;
- opacity: 0.8;
- font-weight: bold;
- padding-left: 0.3em;
+ font-size: 8px;
+ color: black;
+ opacity: 0.8;
+ font-weight: bold;
+ padding-left: 0.3em;
}
.graph-legend-field.highlight > .graph-legend-field-settings {
- opacity: 1.0;
- box-shadow: 0px 0px 2px white;
+ opacity: 1;
+ box-shadow: 0px 0px 2px white;
}
.graph-legend-field > .glyphicon-equalizer {
- float: right;
- margin-left: 0.7em;
+ float: right;
+ margin-left: 0.7em;
}
.graph-legend-field-visibility {
- float: right;
- margin-left: 0.7em;
+ float: right;
+ margin-left: 0.7em;
}
html.has-video .graph-row,
html.has-log .graph-row {
- display:-webkit-flex;
- display:flex;
- position: fixed;
- top: 100px;
- width: calc(100% - 30px);
- bottom: 80px;
+ display: -webkit-flex;
+ display: flex;
+ position: fixed;
+ top: 100px;
+ width: calc(100% - 30px);
+ bottom: 80px;
}
html.has-video .log-graph {
- height:auto;
+ height: auto;
}
html.has-video .log-graph video {
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
}
html.has-video #graphCanvas {
- background-color:rgba(0,0,0,0.25);
+ background-color: rgba(0, 0, 0, 0.25);
}
html .view-video,
@@ -856,7 +892,7 @@ html .view-table,
html .view-analyser,
html .view-analyser-sticks,
html .view-analyser-fullscreen {
- color:black;
+ color: black;
}
html.has-analyser-sticks.isBF .view-analyser-sticks,
@@ -866,7 +902,7 @@ html.has-table.isBF .view-table,
html.has-sticks.isBF .view-sticks,
html.has-craft.isBF .view-craft,
html:not(.video-hidden).isBF .view-video {
- color: rgba(158, 100, 0,1);
+ color: rgba(158, 100, 0, 1);
}
html.has-analyser-sticks.isCF .view-analyser-sticks,
@@ -876,7 +912,7 @@ html.has-table.isCF .view-table,
html.has-sticks.isCF .view-sticks,
html.has-craft.isCF .view-craft,
html:not(.video-hidden).isCF .view-video {
- color: rgba(0, 162, 63,1);
+ color: rgba(0, 162, 63, 1);
}
html.has-analyser-sticks.isINAV .view-analyser-sticks,
@@ -886,227 +922,231 @@ html.has-table.isINAV .view-table,
html.has-sticks.isINAV .view-sticks,
html.has-craft.isINAV .view-craft,
html:not(.video-hidden).isINAV .view-video {
- color: rgba(8, 80, 172,1.0);
+ color: rgba(8, 80, 172, 1);
}
-
html:not(.has-analyser-fullscreen) .glyphicon-resize-small {
- display:none;
+ display: none;
}
html.has-analyser-fullscreen .glyphicon-resize-full {
- display:none;
+ display: none;
}
html.has-analyser .view-analyser-fullscreen {
- visibility:inherit;
+ visibility: inherit;
}
html:not(.has-analyser) .view-analyser-fullscreen {
- visibility:hidden;
+ visibility: hidden;
}
.video-top-controls > li {
- display:inline-block;
- margin-right: 17px;
- vertical-align: top;
- /* height: 80px; */
+ display: inline-block;
+ margin-right: 17px;
+ vertical-align: top;
+ /* height: 80px; */
}
.video-top-controls h4 {
- font-size: 11px;
- color: #757575;
- margin-top: 7px;
- margin-bottom: 4px;
- font-weight: bold;
+ font-size: 11px;
+ color: #757575;
+ margin-top: 7px;
+ margin-bottom: 4px;
+ font-weight: bold;
}
-html.has-log .video-top-controls > li.log-view-panel, html.has-log .video-top-controls > li.log-chart-zoom-panel, html.has-log .video-top-controls > li.log-chart-time-panel {
- display: inline-block;
- /* overflow-y: hidden; */
+html.has-log .video-top-controls > li.log-view-panel,
+html.has-log .video-top-controls > li.log-chart-zoom-panel,
+html.has-log .video-top-controls > li.log-chart-time-panel {
+ display: inline-block;
+ /* overflow-y: hidden; */
}
-.video-top-controls > li.log-view-panel, .video-top-controls > li.log-chart-zoom-panel, .video-top-controls > li.log-chart-time-panel {
- display: none;
+.video-top-controls > li.log-view-panel,
+.video-top-controls > li.log-chart-zoom-panel,
+.video-top-controls > li.log-chart-time-panel {
+ display: none;
}
.video-top-controls > .log-sync-panel {
- display:none;
+ display: none;
}
html.has-video.has-log .video-top-controls > .log-sync-panel {
- display: inline-block;
+ display: inline-block;
}
html .video-jump-start,
html .video-jump-end,
html .log-jump-start,
html .log-jump-end {
- display:none;
+ display: none;
}
html.has-video .video-jump-start,
html.has-video .video-jump-end {
- display:block;
+ display: block;
}
html.has-log .log-jump-start,
html.has-log .log-jump-end {
- display:block;
+ display: block;
}
.log-seek-bar {
- display:none;
+ display: none;
}
html.has-video .log-seek-bar,
html.has-log .log-seek-bar {
- display:block;
- position: fixed;
- bottom: 20px;
- padding-left: 15px;
- padding-right: 15px;
+ display: block;
+ position: fixed;
+ bottom: 20px;
+ padding-left: 15px;
+ padding-right: 15px;
}
.log-workspace-panel {
- width: 200px;
+ width: 200px;
}
.log-workspace-panel button {
- width: 100%;
- text-align: left;
+ width: 100%;
+ text-align: left;
}
.log-workspace-panel .caret {
- float: right;
- margin-top: 8px;
+ float: right;
+ margin-top: 8px;
}
.log-workspace-panel .glyphicon {
- opacity: 0.4;
- transition: 250ms;
- vertical-align: middle;
- top: 0px;
+ opacity: 0.4;
+ transition: 250ms;
+ vertical-align: middle;
+ top: 0px;
}
.log-workspace-panel .glyphicon:hover {
- opacity: 1.0;
- transition: 0ms;
+ opacity: 1;
+ transition: 0ms;
}
-.log-workspace-panel .open .workspace-selector-editButton{
- display: none;
+.log-workspace-panel .open .workspace-selector-editButton {
+ display: none;
}
-.log-workspace-panel li>a {
- padding: 3px 5px 3px 5px;
+.log-workspace-panel li > a {
+ padding: 3px 5px 3px 5px;
}
.log-workspace-panel .workspace-selector-index {
- padding-right: 5px;
- opacity: 0.9;
- vertical-align: middle;
+ padding-right: 5px;
+ opacity: 0.9;
+ vertical-align: middle;
}
.log-workspace-panel .faded {
- opacity: 0.4;
+ opacity: 0.4;
}
.log-workspace-panel .workspace-selector-title {
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- display:inline-block;
- vertical-align:middle;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ display: inline-block;
+ vertical-align: middle;
}
.log-workspace-panel input {
- padding-top: 2px;
- margin-left: -2px;
- width: 108px;
- height: 18px;
+ padding-top: 2px;
+ margin-left: -2px;
+ width: 108px;
+ height: 18px;
}
html .graph-time-marker-group > .graph-time-marker {
- display:none;
+ display: none;
}
html.has-marker .graph-time-marker-group > .graph-time-marker {
- display:inline-block;
+ display: inline-block;
}
-.log-metadata, .log-field-values {
- display:none;
+.log-metadata,
+.log-field-values {
+ display: none;
}
html.has-log:not(.is-fullscreen) .log-metadata,
html.has-log:not(.is-fullscreen).has-table .log-field-values {
- display:block;
+ display: block;
}
.video-top-controls,
.log-graph {
- display:none;
+ display: none;
}
.nowrap {
- /* overflow-y: hidden; */
- /* width: 40px; */
- /* white-space: nowrap; */
+ /* overflow-y: hidden; */
+ /* width: 40px; */
+ /* white-space: nowrap; */
}
html.has-video .video-top-controls,
html.has-log .video-top-controls,
html.has-video .log-graph,
html.has-log .log-graph {
- display:block;
+ display: block;
}
html.has-video .video-top-controls,
html.has-log .video-top-controls {
- top:50px;
- position:fixed;
- z-index:10;
- background-color:white;
- width: 100%;
- min-width: 1390px;
- white-space: nowrap;
+ top: 50px;
+ position: fixed;
+ z-index: 10;
+ background-color: white;
+ width: 100%;
+ min-width: 1390px;
+ white-space: nowrap;
}
.playback-rate-control,
-.graph-zoom-control {
- width: 200px;
- margin-right: 13px;
- margin-top: 6px;
+.graph-zoom-control {
+ width: 200px;
+ margin-right: 13px;
+ margin-top: 6px;
}
-.noUi-horizontal .noUi-handle{
- text-align: center;
- padding: 3px;
- width: fit-content;
+.noUi-horizontal .noUi-handle {
+ text-align: center;
+ padding: 3px;
+ width: fit-content;
}
-.noUi-handle:after,.noUi-handle:before {
- visibility: hidden;
+.noUi-handle:after,
+.noUi-handle:before {
+ visibility: hidden;
}
.override-button-group button {
- width: 33%;
- padding-bottom: 8px;
+ width: 33%;
+ padding-bottom: 8px;
}
html.has-scaling-override .toggle-scaling,
html.has-expo-override .toggle-expo,
html.has-smoothing-override .toggle-smoothing,
-html.has-grid-override .toggle-grid
-{
- stroke-opacity: 0.2;
- fill-opacity: 0.2;
+html.has-grid-override .toggle-grid {
+ stroke-opacity: 0.2;
+ fill-opacity: 0.2;
}
.main-pane {
- display:none;
+ display: none;
}
html.video-hidden video {
- display:none;
+ display: none;
}
html:not(.has-video) .view-video {
- display:none;
+ display: none;
}
html:not(.has-video) a.view-video,
@@ -1115,82 +1155,87 @@ html:not(.has-analyser) a.view-analyser,
html:not(.has-analyser) a.view-analyser-fullscreen,
html:not(.has-analyser) li.has-analyser,
html:not(.has-marker) li.has-marker {
- display:none;
+ display: none;
}
html.has-log .main-pane,
html.has-video .main-pane {
- display:block;
+ display: block;
}
html.has-log .welcome-pane,
html.has-video .welcome-pane {
- display:none;
+ display: none;
}
.btn-video-export.disabled {
- pointer-events:all !important;
+ pointer-events: all !important;
}
.btn-video-export {
- display:none;
+ display: none;
}
html.has-log .btn-video-export {
- display:inline-block;
+ display: inline-block;
}
-.pane-video-settings, .pane-video-progress, .pane-video-complete {
- display:none;
+.pane-video-settings,
+.pane-video-progress,
+.pane-video-complete {
+ display: none;
}
.video-export-mode-settings .pane-video-settings,
.video-export-mode-progress .pane-video-progress,
.video-export-mode-complete .pane-video-complete {
- display:block;
+ display: block;
}
.video-dim-section {
- display:none;
+ display: none;
}
html.has-video .video-dim-section {
- display:block;
+ display: block;
}
progress {
- width:100%;
- height:20px;
+ width: 100%;
+ height: 20px;
}
.jumbotron {
- padding: 15px;
+ padding: 15px;
}
.hiddenElement {
- display: none;
+ display: none;
}
.loading-message {
- padding-left: 30px;
- font-weight: bold;
- font-size: 1.5em;
+ padding-left: 30px;
+ font-weight: bold;
+ font-size: 1.5em;
}
.navbar-inverse .navbar-brand {
- color: #EAEAEA;
+ color: #eaeaea;
}
.navbar {
- margin-bottom:5px;
+ margin-bottom: 5px;
}
-.btn-group .btn+.btn, .btn-group .btn+.btn-group, .btn-group .btn-group+.btn, .btn-group .btn-group+.btn-group {
- margin-left: 0px;
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+ margin-left: 0px;
}
.btn-nobg {
- color: #BBB;
- background: none;
+ color: #bbb;
+ background: none;
}
.btn-nobg:hover {
- color: white;
- text-shadow: 1px 1px 2px gray, 0 0 5px blue, 0 0 10px white;
+ color: white;
+ text-shadow: 1px 1px 2px gray, 0 0 5px blue, 0 0 10px white;
}
.btn-nobg:focus,
@@ -1203,102 +1248,102 @@ progress {
}
.btn-svg-icon {
- padding: 3px 7px;
- line-height: 1em;
+ padding: 3px 7px;
+ line-height: 1em;
}
.navbar-logo {
- position: absolute;
- display:block;
- left: -40px;
+ position: absolute;
+ display: block;
+ left: -40px;
}
html.isCF .navbar-logo {
- left: 16px;
+ left: 16px;
}
html.isINAV .navbar-logo {
- left: 34px;
+ left: 34px;
}
html.isCF .navbar-logo img {
- content: url("/images/cf_logo_white.svg");
- width: 160px;
+ content: url(".././images/cf_logo_white.svg");
+ width: 160px;
}
html.isBF .navbar-logo img {
- content: url("/images/light-wide-2.svg");
- width: 282px;
- height: 50px;
- margin-left: 0 !important;
+ content: url("/images/light-wide-2.svg");
+ width: 282px;
+ height: 50px;
+ margin-left: 0 !important;
}
html.isINAV .navbar-logo img {
- content: url("/images/inav_logo_white.png");
- width: auto;
- left: -16px;
- top: 3px;
+ content: url(".././images/inav_logo_white.png");
+ width: auto;
+ left: -16px;
+ top: 3px;
}
.navbar-logo img {
- position:relative;
- height: 35px;
- width: 210px;
- padding: 0;
- display:block;
+ position: relative;
+ height: 35px;
+ width: 210px;
+ padding: 0;
+ display: block;
}
.navbar-logo a {
- padding: 0;
- position: absolute;
- top: 32%;
- left: 75%;
- width: 100%;
+ padding: 0;
+ position: absolute;
+ top: 32%;
+ left: 75%;
+ width: 100%;
}
.navbar-logo span {
- padding-left: 0;
- position: relative;
- left: -150px;
- top: 20px;
- color: antiquewhite;
+ padding-left: 0;
+ position: relative;
+ left: -150px;
+ top: 20px;
+ color: antiquewhite;
}
html.isBF .navbar-logo span {
- left: -181px;
- top: 30px;
+ left: -181px;
+ top: 30px;
}
html.has-video .navbar-static-top,
html.has-log .navbar-static-top {
- display:block;
+ display: block;
}
.navbar-static-top {
- display:none;
- position: fixed;
- width: 100%;
+ display: none;
+ position: fixed;
+ width: 100%;
}
#status-bar {
- position: fixed;
- bottom: 0;
- width: 100%;
- height: 20px;
- line-height: 20px;
- padding: 0 10px 0 10px;
- border-top: 1px solid #7d7d79;
- background-color: #bfbeb5;
+ position: fixed;
+ bottom: 0;
+ width: 100%;
+ height: 20px;
+ line-height: 20px;
+ padding: 0 10px 0 10px;
+ border-top: 1px solid #7d7d79;
+ background-color: #bfbeb5;
}
html:not(.has-log) #status-bar {
- display: none;
+ display: none;
}
html:not(.has-gps) .view-map,
html:not(.has-gps) .map-container,
html:not(.has-gps) .btn-gpx-export {
- display: none !important;
+ display: none !important;
}
#status-bar .bookmark-1,
@@ -1310,253 +1355,256 @@ html:not(.has-gps) .btn-gpx-export {
#status-bar .bookmark-7,
#status-bar .bookmark-8,
#status-bar .bookmark-9 {
- background-color: red;
- color : white;
- /* padding-left: 5px; */
- /* padding-right: 0; */
- width: 20px;
- /* margin-right: 0; */
- /* margin-left: 0; */
- text-align: center;
- visibility: hidden;
- cursor: pointer;
+ background-color: red;
+ color: white;
+ /* padding-left: 5px; */
+ /* padding-right: 0; */
+ width: 20px;
+ /* margin-right: 0; */
+ /* margin-left: 0; */
+ text-align: center;
+ visibility: hidden;
+ cursor: pointer;
}
#status-bar .marker-offset {
- visibility: hidden;
- cursor: pointer;
+ visibility: hidden;
+ cursor: pointer;
}
#status-bar .bookmark-clear {
- background-color: blue;
- color : white;
- padding-left: 0;
- padding-right: 0;
- width: 60px;
- margin-right: 0;
- margin-left: 0;
- text-align: center;
- visibility: hidden;
- cursor: pointer;
+ background-color: blue;
+ color: white;
+ padding-left: 0;
+ padding-right: 0;
+ width: 60px;
+ margin-right: 0;
+ margin-left: 0;
+ text-align: center;
+ visibility: hidden;
+ cursor: pointer;
}
#status-bar .configuration-file-name {
- cursor: pointer;
+ cursor: pointer;
}
html:not(.has-config) #status-bar .configuration-file-name {
- display: none;
+ display: none;
}
#status-bar div:not(:last-child) {
- float: left;
- padding-right: 5px;
- margin-right: 5px;
- border-right: 1px solid #7d7d79;
+ float: left;
+ padding-right: 5px;
+ margin-right: 5px;
+ border-right: 1px solid #7d7d79;
}
#status-bar div:first-child {
- margin-left: 10px;
+ margin-left: 10px;
}
#status-bar div:last-child {
- float: right;
+ float: right;
}
/***
- IOS Style Checkboxes common code
+ IOS Style Checkboxes common code
***/
-input[type="checkbox"].ios-switch {
- position: absolute;
- opacity: 0;
+input[type="checkbox"].ios-switch {
+ position: absolute;
+ opacity: 0;
}
/* Normal Track */
input[type="checkbox"].ios-switch + div {
- vertical-align: middle;
- width: 40px;
- height: 20px;
- border: 1px solid rgba(0,0,0,.4);
- border-radius: 999px;
- background-color: rgba(0, 0, 0, 0.1);
- -webkit-transition-duration: .4s;
- -webkit-transition-property: background-color, box-shadow;
- box-shadow: inset 0 0 0 0 rgba(0,0,0,0.4);
- margin: 0 0 0 2.5em;
- display: inherit;
+ vertical-align: middle;
+ width: 40px;
+ height: 20px;
+ border: 1px solid rgba(0, 0, 0, 0.4);
+ border-radius: 999px;
+ background-color: rgba(0, 0, 0, 0.1);
+ -webkit-transition-duration: 0.4s;
+ -webkit-transition-property: background-color, box-shadow;
+ box-shadow: inset 0 0 0 0 rgba(0, 0, 0, 0.4);
+ margin: 0 0 0 2.5em;
+ display: inherit;
}
/* Checked Track (Blue) */
input[type="checkbox"].ios-switch:checked + div {
- width: 40px;
- background: #3b89ec 0 0;
- border: 1px solid #0e62cd;
- box-shadow: inset 0 0 0 10px rgba(59,137,259,1);
+ width: 40px;
+ background: #3b89ec 0 0;
+ border: 1px solid #0e62cd;
+ box-shadow: inset 0 0 0 10px rgba(59, 137, 259, 1);
}
/* Tiny Track */
input[type="checkbox"].tinyswitch.ios-switch + div {
- width: 34px; height: 18px;
+ width: 34px;
+ height: 18px;
}
/* Big Track */
input[type="checkbox"].bigswitch.ios-switch + div {
- width: 50px; height: 25px;
+ width: 50px;
+ height: 25px;
}
/* Green Track */
input[type="checkbox"].green.ios-switch:checked + div,
html.isCF input[type="checkbox"].ios-switch:checked + div {
- background-color: #00e359;
- border: 1px solid rgba(0, 162, 63,1);
- box-shadow: inset 0 0 0 10px rgba(0,227,89,1);
+ background-color: #00e359;
+ border: 1px solid rgba(0, 162, 63, 1);
+ box-shadow: inset 0 0 0 10px rgba(0, 227, 89, 1);
}
/* Orange Track */
-input[type="checkbox"].orange.ios-switch:checked + div,
+input[type="checkbox"].orange.ios-switch:checked + div,
html.isBF input[type="checkbox"].ios-switch:checked + div {
- background-color: #FFC638;
- border: 1px solid rgba(158, 100, 0, 1);
- box-shadow: inset 0 0 0 10px rgba(224,168,0,1);
+ background-color: #ffc638;
+ border: 1px solid rgba(158, 100, 0, 1);
+ box-shadow: inset 0 0 0 10px rgba(224, 168, 0, 1);
}
/* Normal Knob */
input[type="checkbox"].ios-switch + div > div {
- float: left;
- width: 18px;
- height: 18px;
- border-radius: inherit;
- background: #ffffff;
- -webkit-transition-timing-function: cubic-bezier(.54,1.85,.5,1);
- -webkit-transition-duration: 0.4s;
- -webkit-transition-property: transform, background-color, box-shadow;
- -moz-transition-timing-function: cubic-bezier(.54,1.85,.5,1);
- -moz-transition-duration: 0.4s;
- -moz-transition-property: transform, background-color;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.4);
- pointer-events: none;
- margin-left: 0;
+ float: left;
+ width: 18px;
+ height: 18px;
+ border-radius: inherit;
+ background: #ffffff;
+ -webkit-transition-timing-function: cubic-bezier(0.54, 1.85, 0.5, 1);
+ -webkit-transition-duration: 0.4s;
+ -webkit-transition-property: transform, background-color, box-shadow;
+ -moz-transition-timing-function: cubic-bezier(0.54, 1.85, 0.5, 1);
+ -moz-transition-duration: 0.4s;
+ -moz-transition-property: transform, background-color;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.4);
+ pointer-events: none;
+ margin-left: 0;
}
/* Checked Knob (Blue Style) */
input[type="checkbox"].ios-switch:checked + div > div {
- -webkit-transform: translate3d(20px, 0, 0);
- -moz-transform: translate3d(20px, 0, 0);
- background-color: #ffffff;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172,1);
+ -webkit-transform: translate3d(20px, 0, 0);
+ -moz-transform: translate3d(20px, 0, 0);
+ background-color: #ffffff;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172, 1);
}
/* Tiny Knob */
input[type="checkbox"].tinyswitch.ios-switch + div > div {
- width: 16px; height: 16px;
- margin-top: 1px;
+ width: 16px;
+ height: 16px;
+ margin-top: 1px;
}
/* Checked Tiny Knob (Blue Style) */
input[type="checkbox"].tinyswitch.ios-switch:checked + div > div {
- -webkit-transform: translate3d(16px, 0, 0);
- -moz-transform: translate3d(16px, 0, 0);
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172,1);
+ -webkit-transform: translate3d(16px, 0, 0);
+ -moz-transform: translate3d(16px, 0, 0);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172, 1);
}
/* Big Knob */
input[type="checkbox"].bigswitch.ios-switch + div > div {
- width: 23px; height: 23px;
- margin-top: 1px;
+ width: 23px;
+ height: 23px;
+ margin-top: 1px;
}
/* Checked Big Knob (Blue Style) */
input[type="checkbox"].bigswitch.ios-switch:checked + div > div {
- -webkit-transform: translate3d(25px, 0, 0);
- -moz-transform: translate3d(16px, 0, 0);
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172,1);
+ -webkit-transform: translate3d(25px, 0, 0);
+ -moz-transform: translate3d(16px, 0, 0);
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172, 1);
}
/* Green Knob */
-input[type="checkbox"].green.ios-switch:checked + div > div ,
+input[type="checkbox"].green.ios-switch:checked + div > div,
html.isCF input[type="checkbox"].ios-switch:checked + div > div {
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 162, 63,1);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 162, 63, 1);
}
/* Orange Knob */
-input[type="checkbox"].orange.ios-switch:checked + div > div ,
+input[type="checkbox"].orange.ios-switch:checked + div > div,
html.isBF input[type="checkbox"].ios-switch:checked + div > div {
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(158, 100, 0,1);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(158, 100, 0, 1);
}
/* Blue Knob */
-input[type="checkbox"].blue.ios-switch:checked + div > div ,
+input[type="checkbox"].blue.ios-switch:checked + div > div,
html.isINAV input[type="checkbox"].ios-switch:checked + div > div {
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172,1);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(8, 80, 172, 1);
}
html.isBF .config-graph h4 span:first-child,
html.isBF .log-graph-config h3 span {
- background-image: linear-gradient(to bottom,#fff8e9 0,#ffbb00 100%);
+ background-image: linear-gradient(to bottom, #fff8e9 0, #ffbb00 100%);
}
html.isCF .config-graph h4 span:first-child,
html.isCF .log-graph-config h3 span {
- background-image: linear-gradient(to bottom,#fff8e9 0,#00a23f 100%);
+ background-image: linear-gradient(to bottom, #fff8e9 0, #00a23f 100%);
}
html.isINAV .config-graph h4 span:first-child,
html.isINAV .log-graph-config h3 span {
- background-image: linear-gradient(to bottom,#fff8e9 0,#0850ac 100%);
+ background-image: linear-gradient(to bottom, #fff8e9 0, #0850ac 100%);
}
.config-graph h4 span,
.log-graph-config h3 span {
- /* background-color: rgba(239,187,0,0.5); */
- padding: 2px 4px 4px;
- border-radius: 5px;
- margin-right:3px;
+ /* background-color: rgba(239,187,0,0.5); */
+ padding: 2px 4px 4px;
+ border-radius: 5px;
+ margin-right: 3px;
}
div#lap-timer {
- position: absolute;
- background-color: rgb(34, 34, 34);
- z-index: 10;
- top: 5%;
- left: calc(90% - 233px);
- border-radius: 5px;
- padding: 5px;
- color: #bbb;
- border: thin solid;
+ position: absolute;
+ background-color: rgb(34, 34, 34);
+ z-index: 10;
+ top: 5%;
+ left: calc(90% - 233px);
+ border-radius: 5px;
+ padding: 5px;
+ color: #bbb;
+ border: thin solid;
}
tr.lap-timer-heading td {
- font-style: italic;
- /* text-decoration: underline; */
- border-bottom: thin solid white;
+ font-style: italic;
+ /* text-decoration: underline; */
+ border-bottom: thin solid white;
}
table#lap-timer-laps {
- display: none;
+ display: none;
}
html.hasLaps table#lap-timer-laps {
- display: table;
+ display: table;
}
div#lap-timer td:first-child {
- padding-right: 10px;
+ padding-right: 10px;
}
-
.viewer-download {
- display: none;
+ display: none;
}
/*
Bootstrap Tooltip Overrides
*/
.tooltip {
- --tooltip-color: #363636;
-}
+ --tooltip-color: #363636;
+}
.tooltip-inner {
background-color: var(--tooltip-color);
}
@@ -1570,7 +1618,7 @@ div#lap-timer td:first-child {
border-top-color: var(--tooltip-color);
}
.tooltip.right .tooltip-arrow {
- border-right-color: var(--tooltip-color);
+ border-right-color: var(--tooltip-color);
}
.tooltip.left .tooltip-arrow {
border-left-color: var(--tooltip-color);
@@ -1589,134 +1637,74 @@ div#lap-timer td:first-child {
Mouse Notification styling
*/
.mouseNotification {
- position: absolute;
- margin: 0 auto;
- white-space: pre-wrap;
+ position: absolute;
+ margin: 0 auto;
+ white-space: pre-wrap;
}
.mouseNotification-box {
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- font-size: 12px;
- font-style: normal;
- font-weight: normal;
- line-height: 1.42857143;
- text-decoration: none;
- text-shadow: none;
- text-transform: none;
- letter-spacing: normal;
- word-break: normal;
- word-spacing: normal;
- word-wrap: normal;
- z-index: 10;
- padding: 4px;
- color: white;
- background-color: #363636;
- border: 2px;
- border-radius: 3px;
-}
-
-/* changelog block */
-#changelog {
- width: 700px;
- height: 100%;
- position: fixed;
- right: -695px;
- top: 0px;
-}
-
-#changelog .wrapper {
- height: 100%;
- padding: 0 0;
- border-left: 5px solid #ffbb00;
- overflow: hidden;
- display: none;
-}
-
-#changelog .button {
- transform: rotate(270deg);
- top: 50px;
- right: 665px;
- position: absolute;
- background: #ffbb00;
- border-radius: 5px 5px 0 0;
- border-bottom: none;
- height: 30px;
-}
-
-#changelog .button a {
- display: block;
- padding: 5px 10px;
- width: 90px;
- text-align: center;
- color: #000;
-}
-
-#changelog .title {
- margin: 20px 0;
- font-size: 16px;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-decoration: none;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: normal;
+ word-break: normal;
+ word-spacing: normal;
+ word-wrap: normal;
+ z-index: 10;
+ padding: 4px;
+ color: white;
+ background-color: #363636;
+ border: 2px;
+ border-radius: 3px;
}
-html.log_open #changelog {
- right: 0px;
- background: white;
-}
-
-html.log_open #changelog .wrapper {
- display: block;
-}
-
-/* changelog content */
-#changelog iframe {
- border: 1px;
- width: calc(100% - 20px);
- border-style: solid;
- height: calc(100% - 20px);
- margin: 10px 10px 10px 10px;
-}
-
-
dialog {
- background-color: white;
- padding: 1em;
- height: auto;
- margin: auto auto;
- position: absolute;
- width: 50%;
- border-radius: 3px;
- border: 1px solid silver;
+ background-color: white;
+ padding: 1em;
+ height: auto;
+ margin: auto auto;
+ position: absolute;
+ width: 50%;
+ border-radius: 3px;
+ border: 1px solid silver;
}
dialog h3 {
- display: block;
- font-size: 1.17em;
- -webkit-margin-before: 1em;
- -webkit-margin-after: 1em;
- -webkit-margin-start: 0px;
- -webkit-margin-end: 0px;
- font-weight: bold;
+ display: block;
+ font-size: 1.17em;
+ -webkit-margin-before: 1em;
+ -webkit-margin-after: 1em;
+ -webkit-margin-start: 0px;
+ -webkit-margin-end: 0px;
+ font-weight: bold;
}
.regular-button {
- margin-top: 8px;
- margin-bottom: 8px;
- margin-right: 10px;
- background-color: #ffbb00;
- border-radius: 3px;
- border: 1px solid #dba718;
- color: #000;
- font-family: 'open_sansbold', Arial;
- font-size: 12px;
- font-weight: bold;
- text-shadow: 0px 1px rgba(255, 255, 255, 0.25);
- display: inline-block;
- cursor: pointer;
- transition: all ease 0.2s;
- padding: 0px;
- padding-left: 9px;
- padding-right: 9px;
- line-height: 28px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+ margin-right: 10px;
+ background-color: #ffbb00;
+ border-radius: 3px;
+ border: 1px solid #dba718;
+ color: #000;
+ font-family: "open_sansbold", Arial;
+ font-size: 12px;
+ font-weight: bold;
+ text-shadow: 0px 1px rgba(255, 255, 255, 0.25);
+ display: inline-block;
+ cursor: pointer;
+ transition: all ease 0.2s;
+ padding: 0px;
+ padding-left: 9px;
+ padding-right: 9px;
+ line-height: 28px;
}
.actual-lograte {
- color: red;
+ color: red;
}
diff --git a/src/css/menu.css b/src/css/menu.css
new file mode 100644
index 00000000..e0cf3b07
--- /dev/null
+++ b/src/css/menu.css
@@ -0,0 +1,102 @@
+:root {
+ --bgColor: #0fddaf;
+ --txtColor: #ffffff;
+ --borColor: rgba(0, 0, 0, 0);
+ --sizeVar: 8px;
+ --textPrimary: #4b4760;
+ --textSecondary: #7f7989;
+ --borderColor: #cccccc;
+}
+
+.flexDiv {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ width: fit-content;
+}
+
+.selectWrapper {
+ width: 100%;
+ position: relative;
+ padding-top: calc(var(--sizeVar) / 2);
+}
+
+.dropdown-content {
+ display: none;
+ border: 1px solid var(--borderColor);
+ box-sizing: border-box;
+ border-radius: calc(var(--sizeVar) / 2);
+ position: absolute;
+ width: auto;
+ left: 0;
+ right: 0;
+ overflow: visible;
+ background: #ffffff;
+ z-index: 300;
+ min-width: fit-content;
+}
+.dropdown-content div {
+ color: var(--textPrimary);
+ padding: 2px;
+ cursor: pointer;
+ white-space: nowrap;
+ width: auto;
+}
+
+.show {
+ display: block;
+}
+
+.dropdown-content div:hover {
+ background-color: #f6f6f6;
+}
+
+.dropdown-content div:active {
+ font-style: italic;
+}
+
+.bottomBorder {
+ border-bottom: 1px solid var(--borderColor);
+}
+.topBorder {
+ border-top: 1px solid var(--borderColor);
+}
+.iconDiv {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.noSpace {
+ justify-content: flex-start;
+ gap: 6px;
+}
+.titleDiv {
+ pointer-events: none;
+ font-weight: 600;
+}
+.justHover i {
+ opacity: 0;
+}
+.justHover:hover i {
+ opacity: 1;
+}
+.dropdown-content .placeholder {
+ color: var(--textSecondary);
+ font-style: italic;
+}
+.dropdown-content .narrow {
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+.dropdown-content i {
+ color: var(--textSecondary);
+}
+.menu-button {
+ background-color: #f6f6f6;
+ font-weight: 700;
+ border: 1px solid black;
+ border-radius: 3px;
+}
+.minmax-control {
+ min-width: 60px;
+}
diff --git a/src/css/user_settings_dialog.css b/src/css/user_settings_dialog.css
index f2918766..9ca1e3e5 100644
--- a/src/css/user_settings_dialog.css
+++ b/src/css/user_settings_dialog.css
@@ -1,123 +1,119 @@
-
.user-settings-dialog .modal-dialog {
- width:75%;
- min-width:954px;
+ width: 75%;
+ min-width: 954px;
}
-.user-settings-dialog input[type=number] {
- margin-right:2px;
- min-width: 48px;
+.user-settings-dialog input[type="number"] {
+ margin-right: 2px;
+ min-width: 48px;
}
.user-settings-dialog .custom-mixes-group img {
- width: 125px;
- max-height: 125px;
+ width: 125px;
+ max-height: 125px;
}
-.user-settings-dialog .stick-mode-group img {
- width: 200px;
- max-height: 125px;
+.user-settings-dialog .stick-mode-group img {
+ width: 200px;
+ max-height: 125px;
}
-
.user-settings-dialog .watermark-logo-image {
- background-image: url('/images/logo-background.png');
- height: 100px;
- width: 100px;
- background-repeat:no-repeat;
- background-size:100px 100px;
+ background-image: url("/images/logo-background.png");
+ height: 100px;
+ width: 100px;
+ background-repeat: no-repeat;
+ background-size: 100px 100px;
}
-.user-settings-dialog .watermark-group img {
- width: 100px;
- height: 100px;
- /* background-color:aquamarine; */
- padding: 2px;
- border:1px solid #021a40;
- opacity: 1.0;
+.user-settings-dialog .watermark-group img {
+ width: 100px;
+ height: 100px;
+ /* background-color:aquamarine; */
+ padding: 2px;
+ border: 1px solid #021a40;
+ opacity: 1;
}
-
-.user-settings-dialog .watermark-group input[type=file] {
- color: #7D7D7D;
- margin: 2px;
+.user-settings-dialog .watermark-group input[type="file"] {
+ color: #7d7d7d;
+ margin: 2px;
}
.user-settings-dialog table {
- width:calc(100% - 2px);
+ width: calc(100% - 2px);
}
.user-settings-dialog select {
- width:100px;
- margin-left:10px;
- margin-bottom:5px
+ width: 100px;
+ margin-left: 10px;
+ margin-bottom: 5px;
}
-.user-settings-dialog label.option {
- width: 100%;
- margin-bottom: 5px;
- position: relative;
+.user-settings-dialog label.option {
+ width: 100%;
+ margin-bottom: 5px;
+ position: relative;
}
.user-settings-dialog span {
- /* display: block; */
- margin-left: 10px;
- position: absolute;
- left: 125px;
+ /* display: block; */
+ margin-left: 10px;
+ position: absolute;
+ left: 125px;
}
-.user-settings-dialog label + input[type=radio] {
- margin-left: 6px;
- margin-right: 10px;
- margin-bottom: 6px;
+.user-settings-dialog label + input[type="radio"] {
+ margin-left: 6px;
+ margin-right: 10px;
+ margin-bottom: 6px;
}
-.user-settings-dialog input[type=radio] {
- margin-left: 85px;
- margin-bottom: 5px;
- margin-right: 10px;
+.user-settings-dialog input[type="radio"] {
+ margin-left: 85px;
+ margin-bottom: 5px;
+ margin-right: 10px;
}
.user-settings-dialog label {
- width: 75px;
- margin-bottom:0px;
- padding-top: 0px;
+ width: 75px;
+ margin-bottom: 0px;
+ padding-top: 0px;
}
.user-settings-dialog table {
- margin-top: 10px;
+ margin-top: 10px;
}
.user-settings-dialog td.position {
- text-align-last: right;
+ text-align-last: right;
}
.user-settings-dialog td.position label {
- width: 41px;
- font-style:oblique;
- margin-right: 6px;
- padding-top: 0px;
+ width: 41px;
+ font-style: oblique;
+ margin-right: 6px;
+ padding-top: 0px;
}
-.user-settings-dialog input[type=number] + span {
- position: static;
- display: inline;
+.user-settings-dialog input[type="number"] + span {
+ position: static;
+ display: inline;
}
-
-.user-settings-dialog p, .user-settings-dialog span
- {
- font-style: normal;
- font-family: 'open_sansregular', Arial;
- line-height: 19px;
- color: #7d7d7d;
- font-size: 11px;
- display: inline;
- margin-left: 2px;
+.user-settings-dialog p,
+.user-settings-dialog span {
+ font-style: normal;
+ font-family: "open_sansregular", Arial;
+ line-height: 19px;
+ color: #7d7d7d;
+ font-size: 11px;
+ display: inline;
+ margin-left: 2px;
}
/* Normal Track Override positioning*/
.user-settings-dialog input[type="checkbox"].ios-switch + div {
- position: absolute;
- left: 50px;
-}
\ No newline at end of file
+ position: absolute;
+ left: 50px;
+}
diff --git a/src/csv-exporter.js b/src/csv-exporter.js
index 7dae6bcd..b59b2a45 100644
--- a/src/csv-exporter.js
+++ b/src/csv-exporter.js
@@ -7,39 +7,47 @@
/**
* @constructor
- * @param {FlightLog} flightLog
+ * @param {FlightLog} flightLog
* @param {ExportOptions} [opts={}]
*/
-export function CsvExporter(flightLog, opts={}) {
+export function CsvExporter(flightLog, opts = {}) {
+ var opts = _.merge(
+ {
+ columnDelimiter: ",",
+ stringDelimiter: '"',
+ quoteStrings: true,
+ },
+ opts
+ );
- var opts = _.merge({
- columnDelimiter: ",",
- stringDelimiter: "\"",
- quoteStrings: true,
- }, opts);
+ /**
+ * @param {function} success is a callback triggered when export is done
+ */
+ function dump(success) {
+ let frames = _(
+ flightLog.getChunksInTimeRange(
+ flightLog.getMinTime(),
+ flightLog.getMaxTime()
+ )
+ )
+ .map((chunk) => chunk.frames)
+ .value(),
+ worker = new Worker("/js/webworkers/csv-export-worker.js");
- /**
- * @param {function} success is a callback triggered when export is done
- */
- function dump(success) {
- let frames = _(flightLog.getChunksInTimeRange(flightLog.getMinTime(), flightLog.getMaxTime()))
- .map(chunk => chunk.frames).value(),
- worker = new Worker("/js/webworkers/csv-export-worker.js");
-
- worker.onmessage = event => {
- success(event.data);
- worker.terminate();
- };
- worker.postMessage({
- sysConfig: flightLog.getSysConfig(),
- fieldNames: flightLog.getMainFieldNames(),
- frames: frames,
- opts: opts,
- });
- }
-
- // exposed functions
- return {
- dump: dump,
+ worker.onmessage = (event) => {
+ success(event.data);
+ worker.terminate();
};
-};
+ worker.postMessage({
+ sysConfig: flightLog.getSysConfig(),
+ fieldNames: flightLog.getMainFieldNames(),
+ frames: frames,
+ opts: opts,
+ });
+ }
+
+ // exposed functions
+ return {
+ dump: dump,
+ };
+}
diff --git a/src/datastream.js b/src/datastream.js
index f043a6df..869cc7aa 100644
--- a/src/datastream.js
+++ b/src/datastream.js
@@ -1,171 +1,159 @@
-import {
- signExtend16Bit,
- signExtend8Bit,
-} from "./tools";
+import { signExtend16Bit, signExtend8Bit } from "./tools";
-var EOF = -1;
+let EOF = -1;
/*
- * Take an array of unsigned byte data and present it as a stream with various methods
- * for reading data in different formats.
- */
-export const ArrayDataStream = function(data, start, end) {
- this.data = data;
- this.eof = false;
- this.start = start === undefined ? 0 : start;
- this.end = end === undefined ? data.length : end;
- this.pos = this.start;
+ * Take an array of unsigned byte data and present it as a stream with various methods
+ * for reading data in different formats.
+ */
+export const ArrayDataStream = function (data, start, end) {
+ this.data = data;
+ this.eof = false;
+ this.start = start === undefined ? 0 : start;
+ this.end = end === undefined ? data.length : end;
+ this.pos = this.start;
};
/**
* Read a single byte from the string and turn it into a JavaScript string (assuming ASCII).
- *
+ *
* @returns String containing one character, or EOF if the end of file was reached (eof flag
* is set).
*/
-ArrayDataStream.prototype.readChar = function() {
- if (this.pos < this.end)
- return String.fromCharCode(this.data[this.pos++]);
+ArrayDataStream.prototype.readChar = function () {
+ if (this.pos < this.end) return String.fromCharCode(this.data[this.pos++]);
- this.eof = true;
- return EOF;
+ this.eof = true;
+ return EOF;
};
/**
* Read one unsigned byte from the stream
- *
+ *
* @returns Unsigned byte, or EOF if the end of file was reached (eof flag is set).
*/
-ArrayDataStream.prototype.readByte = function() {
- if (this.pos < this.end)
- return this.data[this.pos++];
+ArrayDataStream.prototype.readByte = function () {
+ if (this.pos < this.end) return this.data[this.pos++];
- this.eof = true;
- return EOF;
+ this.eof = true;
+ return EOF;
};
//Synonym:
ArrayDataStream.prototype.readU8 = ArrayDataStream.prototype.readByte;
-ArrayDataStream.prototype.readS8 = function() {
- return signExtend8Bit(this.readByte());
+ArrayDataStream.prototype.readS8 = function () {
+ return signExtend8Bit(this.readByte());
};
-ArrayDataStream.prototype.unreadChar = function(c) {
- this.pos--;
+ArrayDataStream.prototype.unreadChar = function (c) {
+ this.pos--;
};
-
-ArrayDataStream.prototype.peekChar = function() {
- if (this.pos < this.end)
- return String.fromCharCode(this.data[this.pos]);
- this.eof = true;
- return EOF;
+ArrayDataStream.prototype.peekChar = function () {
+ if (this.pos < this.end) return String.fromCharCode(this.data[this.pos]);
+
+ this.eof = true;
+ return EOF;
};
/**
* Read a (maximally 32-bit) unsigned integer from the stream which was encoded in Variable Byte format.
- *
+ *
* @returns the unsigned integer, or 0 if a valid integer could not be read (EOF was reached or integer format
* was invalid).
*/
-ArrayDataStream.prototype.readUnsignedVB = function() {
- var
- i, b,
- shift = 0, result = 0;
-
- // 5 bytes is enough to encode 32-bit unsigned quantities
- for (i = 0; i < 5; i++) {
- b = this.readByte();
-
- if (b == EOF)
- return 0;
-
- result = result | ((b & ~0x80) << shift);
-
- // Final byte?
- if (b < 128) {
- /*
- * Force the 32-bit integer to be reinterpreted as unsigned by doing an unsigned right shift, so that
- * the top bit being set doesn't cause it to interpreted as a negative number.
- */
- return result >>> 0;
- }
-
- shift += 7;
+ArrayDataStream.prototype.readUnsignedVB = function () {
+ let i,
+ b,
+ shift = 0,
+ result = 0;
+
+ // 5 bytes is enough to encode 32-bit unsigned quantities
+ for (i = 0; i < 5; i++) {
+ b = this.readByte();
+
+ if (b == EOF) return 0;
+
+ result = result | ((b & ~0x80) << shift);
+
+ // Final byte?
+ if (b < 128) {
+ /*
+ * Force the 32-bit integer to be reinterpreted as unsigned by doing an unsigned right shift, so that
+ * the top bit being set doesn't cause it to interpreted as a negative number.
+ */
+ return result >>> 0;
}
- // This VB-encoded int is too long!
- return 0;
+ shift += 7;
+ }
+
+ // This VB-encoded int is too long!
+ return 0;
};
-ArrayDataStream.prototype.readSignedVB = function() {
- var unsigned = this.readUnsignedVB();
+ArrayDataStream.prototype.readSignedVB = function () {
+ let unsigned = this.readUnsignedVB();
- // Apply ZigZag decoding to recover the signed value
- return (unsigned >>> 1) ^ -(unsigned & 1);
+ // Apply ZigZag decoding to recover the signed value
+ return (unsigned >>> 1) ^ -(unsigned & 1);
};
-ArrayDataStream.prototype.readString = function(length) {
- var
- chars = new Array(length),
- i;
-
- for (i = 0; i < length; i++) {
- chars[i] = this.readChar();
- }
-
- return chars.join("");
+ArrayDataStream.prototype.readString = function (length) {
+ let chars = new Array(length),
+ i;
+
+ for (i = 0; i < length; i++) {
+ chars[i] = this.readChar();
+ }
+
+ return chars.join("");
};
-ArrayDataStream.prototype.readS16 = function() {
- var
- b1 = this.readByte(),
- b2 = this.readByte();
-
- return signExtend16Bit(b1 | (b2 << 8));
+ArrayDataStream.prototype.readS16 = function () {
+ let b1 = this.readByte(),
+ b2 = this.readByte();
+
+ return signExtend16Bit(b1 | (b2 << 8));
};
-ArrayDataStream.prototype.readU16 = function() {
- var
- b1 = this.readByte(),
- b2 = this.readByte();
-
- return b1 | (b2 << 8);
+ArrayDataStream.prototype.readU16 = function () {
+ let b1 = this.readByte(),
+ b2 = this.readByte();
+
+ return b1 | (b2 << 8);
};
-ArrayDataStream.prototype.readU32 = function() {
- var
- b1 = this.readByte(),
- b2 = this.readByte(),
- b3 = this.readByte(),
- b4 = this.readByte();
- return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
+ArrayDataStream.prototype.readU32 = function () {
+ let b1 = this.readByte(),
+ b2 = this.readByte(),
+ b3 = this.readByte(),
+ b4 = this.readByte();
+ return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
};
/**
* Search for the string 'needle' beginning from the current stream position up
* to the end position. Return the offset of the first occurrence found.
- *
+ *
* @param needle
* String to search for
* @returns Position of the start of needle in the stream, or -1 if it wasn't
* found
*/
-ArrayDataStream.prototype.nextOffsetOf = function(needle) {
- var i, j;
-
- for (i = this.pos; i <= this.end - needle.length; i++) {
- if (this.data[i] == needle[0]) {
- for (j = 1; j < needle.length && this.data[i + j] == needle[j]; j++)
- ;
-
- if (j == needle.length)
- return i;
- }
+ArrayDataStream.prototype.nextOffsetOf = function (needle) {
+ let i, j;
+
+ for (i = this.pos; i <= this.end - needle.length; i++) {
+ if (this.data[i] == needle[0]) {
+ for (j = 1; j < needle.length && this.data[i + j] == needle[j]; j++);
+
+ if (j == needle.length) return i;
}
-
- return -1;
+ }
+
+ return -1;
};
ArrayDataStream.prototype.EOF = EOF;
diff --git a/src/decoders.js b/src/decoders.js
index f7767076..0eeb7dcd 100644
--- a/src/decoders.js
+++ b/src/decoders.js
@@ -1,292 +1,293 @@
-import { ArrayDataStream } from './datastream.js';
+import { ArrayDataStream } from "./datastream.js";
import {
- signExtend24Bit,
- signExtend16Bit,
- signExtend8Bit,
- signExtend7Bit,
- signExtend6Bit,
- signExtend5Bit,
- signExtend4Bit,
- signExtend2Bit,
-} from './tools.js';
+ signExtend24Bit,
+ signExtend16Bit,
+ signExtend8Bit,
+ signExtend7Bit,
+ signExtend6Bit,
+ signExtend5Bit,
+ signExtend4Bit,
+ signExtend2Bit,
+} from "./tools.js";
/**
* Extend ArrayDataStream with decoders for advanced formats.
*/
-ArrayDataStream.prototype.readTag2_3S32 = function(values) {
- var
- leadByte,
- byte1, byte2, byte3, byte4,
- i;
-
- leadByte = this.readByte();
-
- // Check the selector in the top two bits to determine the field layout
- switch (leadByte >> 6) {
- case 0:
- // 2-bit fields
- values[0] = signExtend2Bit((leadByte >> 4) & 0x03);
- values[1] = signExtend2Bit((leadByte >> 2) & 0x03);
- values[2] = signExtend2Bit(leadByte & 0x03);
- break;
- case 1:
- // 4-bit fields
- values[0] = signExtend4Bit(leadByte & 0x0F);
+ArrayDataStream.prototype.readTag2_3S32 = function (values) {
+ let leadByte, byte1, byte2, byte3, byte4, i;
+
+ leadByte = this.readByte();
+
+ // Check the selector in the top two bits to determine the field layout
+ switch (leadByte >> 6) {
+ case 0:
+ // 2-bit fields
+ values[0] = signExtend2Bit((leadByte >> 4) & 0x03);
+ values[1] = signExtend2Bit((leadByte >> 2) & 0x03);
+ values[2] = signExtend2Bit(leadByte & 0x03);
+ break;
+ case 1:
+ // 4-bit fields
+ values[0] = signExtend4Bit(leadByte & 0x0f);
+
+ leadByte = this.readByte();
+
+ values[1] = signExtend4Bit(leadByte >> 4);
+ values[2] = signExtend4Bit(leadByte & 0x0f);
+ break;
+ case 2:
+ // 6-bit fields
+ values[0] = signExtend6Bit(leadByte & 0x3f);
+
+ leadByte = this.readByte();
+ values[1] = signExtend6Bit(leadByte & 0x3f);
+
+ leadByte = this.readByte();
+ values[2] = signExtend6Bit(leadByte & 0x3f);
+ break;
+ case 3:
+ // Fields are 8, 16 or 24 bits, read selector to figure out which field is which size
+
+ for (i = 0; i < 3; i++) {
+ switch (leadByte & 0x03) {
+ case 0: // 8-bit
+ byte1 = this.readByte();
+
+ // Sign extend to 32 bits
+ values[i] = signExtend8Bit(byte1);
+ break;
+ case 1: // 16-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
- leadByte = this.readByte();
+ // Sign extend to 32 bits
+ values[i] = signExtend16Bit(byte1 | (byte2 << 8));
+ break;
+ case 2: // 24-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
+ byte3 = this.readByte();
- values[1] = signExtend4Bit(leadByte >> 4);
- values[2] = signExtend4Bit(leadByte & 0x0F);
- break;
- case 2:
- // 6-bit fields
- values[0] = signExtend6Bit(leadByte & 0x3F);
+ values[i] = signExtend24Bit(byte1 | (byte2 << 8) | (byte3 << 16));
+ break;
+ case 3: // 32-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
+ byte3 = this.readByte();
+ byte4 = this.readByte();
- leadByte = this.readByte();
- values[1] = signExtend6Bit(leadByte & 0x3F);
+ values[i] = byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24);
+ break;
+ }
- leadByte = this.readByte();
- values[2] = signExtend6Bit(leadByte & 0x3F);
- break;
- case 3:
- // Fields are 8, 16 or 24 bits, read selector to figure out which field is which size
-
- for (i = 0; i < 3; i++) {
- switch (leadByte & 0x03) {
- case 0: // 8-bit
- byte1 = this.readByte();
-
- // Sign extend to 32 bits
- values[i] = signExtend8Bit(byte1);
- break;
- case 1: // 16-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
-
- // Sign extend to 32 bits
- values[i] = signExtend16Bit(byte1 | (byte2 << 8));
- break;
- case 2: // 24-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
- byte3 = this.readByte();
-
- values[i] = signExtend24Bit(byte1 | (byte2 << 8) | (byte3 << 16));
- break;
- case 3: // 32-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
- byte3 = this.readByte();
- byte4 = this.readByte();
-
- values[i] = (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24));
- break;
- }
-
- leadByte >>= 2;
- }
- break;
- }
+ leadByte >>= 2;
+ }
+ break;
+ }
};
-ArrayDataStream.prototype.readTag2_3SVariable = function(values) {
- var
- leadByte, leadByte2, leadByte3,
- byte1, byte2, byte3, byte4,
- i;
-
- leadByte = this.readByte();
-
- // Check the selector in the top two bits to determine the field layout
- switch (leadByte >> 6) {
- case 0:
- // 2 bits per field ss11 2233,
- values[0] = signExtend2Bit((leadByte >> 4) & 0x03);
- values[1] = signExtend2Bit((leadByte >> 2) & 0x03);
- values[2] = signExtend2Bit(leadByte & 0x03);
- break;
- case 1:
- // 554 bits per field ss11 1112 2222 3333
- values[0] = signExtend5Bit((leadByte & 0x3E) >> 1);
+ArrayDataStream.prototype.readTag2_3SVariable = function (values) {
+ let leadByte, leadByte2, leadByte3, byte1, byte2, byte3, byte4, i;
+
+ leadByte = this.readByte();
+
+ // Check the selector in the top two bits to determine the field layout
+ switch (leadByte >> 6) {
+ case 0:
+ // 2 bits per field ss11 2233,
+ values[0] = signExtend2Bit((leadByte >> 4) & 0x03);
+ values[1] = signExtend2Bit((leadByte >> 2) & 0x03);
+ values[2] = signExtend2Bit(leadByte & 0x03);
+ break;
+ case 1:
+ // 554 bits per field ss11 1112 2222 3333
+ values[0] = signExtend5Bit((leadByte & 0x3e) >> 1);
+
+ leadByte2 = this.readByte();
+
+ values[1] = signExtend5Bit(
+ ((leadByte & 0x01) << 5) | ((leadByte2 & 0x0f) >> 4)
+ );
+ values[2] = signExtend4Bit(leadByte2 & 0x0f);
+ break;
+ case 2:
+ // 877 bits per field ss11 1111 1122 2222 2333 3333
+ leadByte2 = this.readByte();
+ values[1] = signExtend8Bit(
+ ((leadByte & 0x3f) << 2) | ((leadByte2 & 0xc0) >> 6)
+ );
+
+ leadByte3 = this.readByte();
+ values[1] = signExtend7Bit(
+ ((leadByte2 & 0x3f) << 1) | ((leadByte2 & 0x80) >> 7)
+ );
+
+ values[2] = signExtend7Bit(leadByte3 & 0x7f);
+ break;
+ case 3:
+ // Fields are 8, 16 or 24 bits, read selector to figure out which field is which size
+
+ for (i = 0; i < 3; i++) {
+ switch (leadByte & 0x03) {
+ case 0: // 8-bit
+ byte1 = this.readByte();
+
+ // Sign extend to 32 bits
+ values[i] = signExtend8Bit(byte1);
+ break;
+ case 1: // 16-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
- leadByte2 = this.readByte();
+ // Sign extend to 32 bits
+ values[i] = signExtend16Bit(byte1 | (byte2 << 8));
+ break;
+ case 2: // 24-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
+ byte3 = this.readByte();
- values[1] = signExtend5Bit(((leadByte & 0x01) << 5) | ((leadByte2 & 0x0F) >> 4));
- values[2] = signExtend4Bit(leadByte2 & 0x0F);
- break;
- case 2:
- // 877 bits per field ss11 1111 1122 2222 2333 3333
- leadByte2 = this.readByte();
- values[1] = signExtend8Bit(((leadByte & 0x3F) << 2) | ((leadByte2 & 0xC0) >> 6));
+ values[i] = signExtend24Bit(byte1 | (byte2 << 8) | (byte3 << 16));
+ break;
+ case 3: // 32-bit
+ byte1 = this.readByte();
+ byte2 = this.readByte();
+ byte3 = this.readByte();
+ byte4 = this.readByte();
- leadByte3 = this.readByte();
- values[1] = signExtend7Bit(((leadByte2 & 0x3F) << 1) | ((leadByte2 & 0x80) >> 7));
+ values[i] = byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24);
+ break;
+ }
- values[2] = signExtend7Bit(leadByte3 & 0x7F);
- break;
- case 3:
- // Fields are 8, 16 or 24 bits, read selector to figure out which field is which size
-
- for (i = 0; i < 3; i++) {
- switch (leadByte & 0x03) {
- case 0: // 8-bit
- byte1 = this.readByte();
-
- // Sign extend to 32 bits
- values[i] = signExtend8Bit(byte1);
- break;
- case 1: // 16-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
-
- // Sign extend to 32 bits
- values[i] = signExtend16Bit(byte1 | (byte2 << 8));
- break;
- case 2: // 24-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
- byte3 = this.readByte();
-
- values[i] = signExtend24Bit(byte1 | (byte2 << 8) | (byte3 << 16));
- break;
- case 3: // 32-bit
- byte1 = this.readByte();
- byte2 = this.readByte();
- byte3 = this.readByte();
- byte4 = this.readByte();
-
- values[i] = (byte1 | (byte2 << 8) | (byte3 << 16) | (byte4 << 24));
- break;
- }
-
- leadByte >>= 2;
- }
- break;
- }
+ leadByte >>= 2;
+ }
+ break;
+ }
};
-ArrayDataStream.prototype.readTag8_4S16_v1 = function(values) {
- var
- selector, combinedChar,
- char1, char2,
- i,
-
- FIELD_ZERO = 0,
- FIELD_4BIT = 1,
- FIELD_8BIT = 2,
- FIELD_16BIT = 3;
-
- selector = this.readByte();
-
- //Read the 4 values from the stream
- for (i = 0; i < 4; i++) {
- switch (selector & 0x03) {
- case FIELD_ZERO:
- values[i] = 0;
- break;
- case FIELD_4BIT: // Two 4-bit fields
- combinedChar = this.readByte();
-
- values[i] = signExtend4Bit(combinedChar & 0x0F);
+ArrayDataStream.prototype.readTag8_4S16_v1 = function (values) {
+ let selector,
+ combinedChar,
+ char1,
+ char2,
+ i,
+ FIELD_ZERO = 0,
+ FIELD_4BIT = 1,
+ FIELD_8BIT = 2,
+ FIELD_16BIT = 3;
+
+ selector = this.readByte();
+
+ //Read the 4 values from the stream
+ for (i = 0; i < 4; i++) {
+ switch (selector & 0x03) {
+ case FIELD_ZERO:
+ values[i] = 0;
+ break;
+ case FIELD_4BIT: // Two 4-bit fields
+ combinedChar = this.readByte();
- i++;
- selector >>= 2;
+ values[i] = signExtend4Bit(combinedChar & 0x0f);
- values[i] = signExtend4Bit(combinedChar >> 4);
- break;
- case FIELD_8BIT: // 8-bit field
- values[i] = signExtend8Bit(this.readByte());
- break;
- case FIELD_16BIT: // 16-bit field
- char1 = this.readByte();
- char2 = this.readByte();
+ i++;
+ selector >>= 2;
- values[i] = signExtend16Bit(char1 | (char2 << 8));
- break;
- }
+ values[i] = signExtend4Bit(combinedChar >> 4);
+ break;
+ case FIELD_8BIT: // 8-bit field
+ values[i] = signExtend8Bit(this.readByte());
+ break;
+ case FIELD_16BIT: // 16-bit field
+ char1 = this.readByte();
+ char2 = this.readByte();
- selector >>= 2;
+ values[i] = signExtend16Bit(char1 | (char2 << 8));
+ break;
}
+
+ selector >>= 2;
+ }
};
-ArrayDataStream.prototype.readTag8_4S16_v2 = function(values) {
- var
- selector, i,
- char1, char2,
- buffer,
- nibbleIndex,
-
- FIELD_ZERO = 0,
- FIELD_4BIT = 1,
- FIELD_8BIT = 2,
- FIELD_16BIT = 3;
-
- selector = this.readByte();
-
- //Read the 4 values from the stream
- nibbleIndex = 0;
- for (i = 0; i < 4; i++) {
- switch (selector & 0x03) {
- case FIELD_ZERO:
- values[i] = 0;
- break;
- case FIELD_4BIT:
- if (nibbleIndex === 0) {
- buffer = this.readByte();
- values[i] = signExtend4Bit(buffer >> 4);
- nibbleIndex = 1;
- } else {
- values[i] = signExtend4Bit(buffer & 0x0F);
- nibbleIndex = 0;
- }
- break;
- case FIELD_8BIT:
- if (nibbleIndex === 0) {
- values[i] = signExtend8Bit(this.readByte());
- } else {
- char1 = (buffer & 0x0F) << 4;
- buffer = this.readByte();
-
- char1 |= buffer >> 4;
- values[i] = signExtend8Bit(char1);
- }
- break;
- case FIELD_16BIT:
- if (nibbleIndex === 0) {
- char1 = this.readByte();
- char2 = this.readByte();
-
- //Sign extend...
- values[i] = signExtend16Bit((char1 << 8) | char2);
- } else {
- /*
- * We're in the low 4 bits of the current buffer, then one byte, then the high 4 bits of the next
- * buffer.
- */
- char1 = this.readByte();
- char2 = this.readByte();
-
- values[i] = signExtend16Bit(((buffer & 0x0F) << 12) | (char1 << 4) | (char2 >> 4));
-
- buffer = char2;
- }
- break;
+ArrayDataStream.prototype.readTag8_4S16_v2 = function (values) {
+ let selector,
+ i,
+ char1,
+ char2,
+ buffer,
+ nibbleIndex,
+ FIELD_ZERO = 0,
+ FIELD_4BIT = 1,
+ FIELD_8BIT = 2,
+ FIELD_16BIT = 3;
+
+ selector = this.readByte();
+
+ //Read the 4 values from the stream
+ nibbleIndex = 0;
+ for (i = 0; i < 4; i++) {
+ switch (selector & 0x03) {
+ case FIELD_ZERO:
+ values[i] = 0;
+ break;
+ case FIELD_4BIT:
+ if (nibbleIndex === 0) {
+ buffer = this.readByte();
+ values[i] = signExtend4Bit(buffer >> 4);
+ nibbleIndex = 1;
+ } else {
+ values[i] = signExtend4Bit(buffer & 0x0f);
+ nibbleIndex = 0;
}
-
- selector >>= 2;
+ break;
+ case FIELD_8BIT:
+ if (nibbleIndex === 0) {
+ values[i] = signExtend8Bit(this.readByte());
+ } else {
+ char1 = (buffer & 0x0f) << 4;
+ buffer = this.readByte();
+
+ char1 |= buffer >> 4;
+ values[i] = signExtend8Bit(char1);
+ }
+ break;
+ case FIELD_16BIT:
+ if (nibbleIndex === 0) {
+ char1 = this.readByte();
+ char2 = this.readByte();
+
+ //Sign extend...
+ values[i] = signExtend16Bit((char1 << 8) | char2);
+ } else {
+ /*
+ * We're in the low 4 bits of the current buffer, then one byte, then the high 4 bits of the next
+ * buffer.
+ */
+ char1 = this.readByte();
+ char2 = this.readByte();
+
+ values[i] = signExtend16Bit(
+ ((buffer & 0x0f) << 12) | (char1 << 4) | (char2 >> 4)
+ );
+
+ buffer = char2;
+ }
+ break;
}
+
+ selector >>= 2;
+ }
};
-ArrayDataStream.prototype.readTag8_8SVB = function(values, valueCount) {
- var
- i, header;
+ArrayDataStream.prototype.readTag8_8SVB = function (values, valueCount) {
+ let i, header;
- if (valueCount == 1) {
- values[0] = this.readSignedVB();
- } else {
- header = this.readByte();
+ if (valueCount == 1) {
+ values[0] = this.readSignedVB();
+ } else {
+ header = this.readByte();
- for (i = 0; i < 8; i++, header >>= 1)
- values[i] = (header & 0x01) ? this.readSignedVB() : 0;
- }
-};
\ No newline at end of file
+ for (i = 0; i < 8; i++, header >>= 1)
+ values[i] = header & 0x01 ? this.readSignedVB() : 0;
+ }
+};
diff --git a/src/expo.js b/src/expo.js
index ccac92fb..3ba29be3 100644
--- a/src/expo.js
+++ b/src/expo.js
@@ -1,100 +1,94 @@
/**
* Creates a lookup-table based expo curve, which takes values that range between -inputrange and +inputRange, and
* scales them to -outputRange to +outputRange with the given power curve (curve <1.0 exaggerates values near the origin,
- * curve = 1.0 is a straight line mapping).
+ * curve = 1.0 is a straight line mapping).
*/
export function ExpoCurve(offset, power, inputRange, outputRange, steps) {
- var
- curve, inputScale, rawInputScale;
-
- function lookupStraightLine(input) {
- return (input + offset) * inputScale;
- }
+ let curve, inputScale, rawInputScale;
- this.lookupRaw = function(input) {
- return (input + offset) * rawInputScale;
- }
+ function lookupStraightLine(input) {
+ return (input + offset) * inputScale;
+ }
- this.getCurve = function() {
- return {
- offset: offset,
- power: power,
- inputRange: inputRange,
- outputRange: outputRange,
- steps: steps,
- };
- }
+ this.lookupRaw = function (input) {
+ return (input + offset) * rawInputScale;
+ };
+
+ this.getCurve = function () {
+ return {
+ offset: offset,
+ power: power,
+ inputRange: inputRange,
+ outputRange: outputRange,
+ steps: steps,
+ };
+ };
+
+ /**
+ * An approximation of lookupMathPow by precomputing several expo curve points and interpolating between those
+ * points using straight line interpolation.
+ *
+ * The error will be largest in the area of the curve where the slope changes the fastest with respect to input
+ * (e.g. the approximation will be too straight near the origin when power < 1.0, but a good fit far from the origin)
+ */
+ function lookupInterpolatedCurve(input) {
+ let valueInCurve, prevStepIndex;
+
+ input += offset;
- /**
- * An approximation of lookupMathPow by precomputing several expo curve points and interpolating between those
- * points using straight line interpolation.
- *
- * The error will be largest in the area of the curve where the slope changes the fastest with respect to input
- * (e.g. the approximation will be too straight near the origin when power < 1.0, but a good fit far from the origin)
+ valueInCurve = Math.abs(input * inputScale);
+ prevStepIndex = Math.floor(valueInCurve);
+
+ /* If the input value lies beyond the stated input range, use the final
+ * two points of the curve to extrapolate out (the "curve" out there is a straight line, though)
*/
- function lookupInterpolatedCurve(input) {
- var
- valueInCurve,
- prevStepIndex;
-
- input += offset;
-
- valueInCurve = Math.abs(input * inputScale);
- prevStepIndex = Math.floor(valueInCurve);
-
- /* If the input value lies beyond the stated input range, use the final
- * two points of the curve to extrapolate out (the "curve" out there is a straight line, though)
- */
- if (prevStepIndex > steps - 2) {
- prevStepIndex = steps - 2;
- }
-
- //Straight-line interpolation between the two curve points
- var
- proportion = valueInCurve - prevStepIndex,
- result = curve[prevStepIndex] + (curve[prevStepIndex + 1] - curve[prevStepIndex]) * proportion;
-
- if (input < 0)
- return -result;
- return result;
- }
-
- function lookupMathPow(input) {
- input += offset;
-
- var
- result = Math.pow(Math.abs(input) / inputRange, power) * outputRange;
-
- if (input < 0)
- return -result;
- return result;
+ if (prevStepIndex > steps - 2) {
+ prevStepIndex = steps - 2;
}
-
- rawInputScale = outputRange / inputRange;
- // If steps argument isn't supplied, use a reasonable default
- if (steps === undefined) {
- steps = 12;
- }
-
- if (steps <= 2 || power == 1.0) {
- //Curve is actually a straight line
- inputScale = outputRange / inputRange;
-
- this.lookup = lookupStraightLine;
- } else {
- var
- stepSize = 1.0 / (steps - 1),
- i;
-
- curve = new Array(steps);
-
- inputScale = (steps - 1) / inputRange;
-
- for (i = 0; i < steps; i++) {
- curve[i] = Math.pow(i * stepSize, power) * outputRange;
- }
-
- this.lookup = lookupInterpolatedCurve;
+ //Straight-line interpolation between the two curve points
+ let proportion = valueInCurve - prevStepIndex,
+ result =
+ curve[prevStepIndex] +
+ (curve[prevStepIndex + 1] - curve[prevStepIndex]) * proportion;
+
+ if (input < 0) return -result;
+ return result;
+ }
+
+ function lookupMathPow(input) {
+ input += offset;
+
+ let result = Math.pow(Math.abs(input) / inputRange, power) * outputRange;
+
+ if (input < 0) return -result;
+ return result;
+ }
+
+ rawInputScale = outputRange / inputRange;
+
+ // If steps argument isn't supplied, use a reasonable default
+ if (steps === undefined) {
+ steps = 12;
+ }
+
+ if (steps <= 2 || power == 1.0) {
+ //Curve is actually a straight line
+ inputScale = outputRange / inputRange;
+
+ this.lookup = lookupStraightLine;
+ } else {
+ let stepSize = 1.0 / (steps - 1),
+ i;
+
+ curve = new Array(steps);
+
+ inputScale = (steps - 1) / inputRange;
+
+ for (i = 0; i < steps; i++) {
+ curve[i] = Math.pow(i * stepSize, power) * outputRange;
}
+
+ this.lookup = lookupInterpolatedCurve;
+ }
}
diff --git a/src/flightlog.js b/src/flightlog.js
index 4c27b792..9afafb27 100644
--- a/src/flightlog.js
+++ b/src/flightlog.js
@@ -1,22 +1,23 @@
import { FlightLogIndex } from "./flightlog_index";
import { FlightLogParser } from "./flightlog_parser";
+import { GPS_transform } from "./gps_transform";
import {
- MAX_MOTOR_NUMBER,
- DSHOT_MIN_VALUE,
- DSHOT_RANGE,
- FlightLogEvent,
- AXIS,
- FAST_PROTOCOL,
- SUPER_EXPO_YAW,
+ MAX_MOTOR_NUMBER,
+ DSHOT_MIN_VALUE,
+ DSHOT_RANGE,
+ FlightLogEvent,
+ AXIS,
+ FAST_PROTOCOL,
+ SUPER_EXPO_YAW,
} from "./flightlog_fielddefs";
import { IMU } from "./imu";
import { FIFOCache } from "./cache";
import {
- binarySearchOrPrevious,
- binarySearchOrNext,
- constrain,
- validate,
- firmwareGreaterOrEqual,
+ binarySearchOrPrevious,
+ binarySearchOrNext,
+ constrain,
+ validate,
+ firmwareGreaterOrEqual,
} from "./tools";
/**
@@ -29,1052 +30,1380 @@ import {
* Window based smoothing of fields is offered.
*/
export function FlightLog(logData) {
- var
- ADDITIONAL_COMPUTED_FIELD_COUNT = 15, /** attitude + PID_SUM + PID_ERROR + RCCOMMAND_SCALED **/
-
- that = this,
- logIndex = false,
- logIndexes = new FlightLogIndex(logData),
- parser = new FlightLogParser(logData),
-
- iframeDirectory,
-
- // We cache these details so they don't have to be recomputed on every request:
- numCells = false, numMotors = false,
-
- fieldNames = [],
- fieldNameToIndex = {},
-
- chunkCache = new FIFOCache(2),
-
- // Map from field indexes to smoothing window size in microseconds
- fieldSmoothing = {},
- maxSmoothing = 0,
-
- smoothedCache = new FIFOCache(2);
-
-
- //Public fields:
- this.parser = parser;
-
- this.getMainFieldCount = function() {
- return fieldNames.length;
- };
-
- this.getMainFieldNames = function() {
- return fieldNames;
- };
-
- /**
- * Get the fatal parse error encountered when reading the log with the given index, or false if no error
- * was encountered.
- */
- this.getLogError = function(logIndex) {
- var
- error = logIndexes.getIntraframeDirectory(logIndex).error;
-
- if (error)
- return error;
-
- return false;
- };
-
- /**
- * Get the stats for the log of the given index, or leave off the logIndex argument to fetch the stats
- * for the current log.
- */
- function getRawStats(logIndex) {
- if (logIndex === undefined) {
- return iframeDirectory.stats;
- } else {
- return logIndexes.getIntraframeDirectory(logIndex).stats;
- }
- };
-
- /**
- * Get the stats for the log of the given index, or leave off the logIndex argument to fetch the stats
- * for the current log.
- *
- * Stats are modified to add a global field[] array which contains merged field stats for the different frame types
- * that the flightlog presents as one merged frame.
- */
- this.getStats = function(logIndex) {
- var
- rawStats = getRawStats(logIndex);
-
- // Just modify the raw stats variable to add this field, the parser won't mind the extra field appearing:
- if (rawStats.frame.S) {
- rawStats.field = rawStats.frame.I.field.concat(rawStats.frame.S.field);
- } else {
- rawStats.field = rawStats.frame.I.field;
- }
-
- return rawStats;
- };
-
- /**
- * Get the earliest time seen in the log of the given index (in microseconds), or leave off the logIndex
- * argument to fetch details for the current log.
- */
- this.getMinTime = function(logIndex) {
- return getRawStats(logIndex).frame["I"].field[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME].min;
- };
-
- /**
- * Get the latest time seen in the log of the given index (in microseconds), or leave off the logIndex
- * argument to fetch details for the current log.
- */
- this.getMaxTime = function(logIndex) {
- return getRawStats(logIndex).frame["I"].field[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME].max;
- };
-
- /**
- * Get the flight controller system information that was parsed for the current log file.
- */
- this.getSysConfig = function() {
- return parser.sysConfig;
- };
-
- this.setSysConfig = function(newSysConfig) {
- $.extend(true, parser.sysConfig, newSysConfig);
- };
-
-
- /**
- * Get the index of the currently selected log.
- */
- this.getLogIndex = function() {
- return logIndex;
- };
-
- this.getLogCount = function() {
- return logIndexes.getLogCount();
- };
-
- /**
- * Return a coarse summary of throttle position and events across the entire log.
- */
- this.getActivitySummary = function() {
- var directory = logIndexes.getIntraframeDirectory(logIndex);
-
- return {
- times: directory.times,
- avgThrottle: directory.avgThrottle,
- maxMotorDiff: directory.maxMotorDiff,
- maxRC: directory.maxRC,
- hasEvent: directory.hasEvent
+ let ADDITIONAL_COMPUTED_FIELD_COUNT = 20 /** attitude + PID_SUM + PID_ERROR + RCCOMMAND_SCALED + GPS coord, distance and azimuth **/,
+ that = this,
+ logIndex = 0,
+ logIndexes = new FlightLogIndex(logData),
+ parser = new FlightLogParser(logData),
+ iframeDirectory,
+ // We cache these details so they don't have to be recomputed on every request:
+ numCells = false,
+ numMotors = false,
+ fieldNames = [],
+ fieldNameToIndex = {},
+ chunkCache = new FIFOCache(2),
+ // Map from field indexes to smoothing window size in microseconds
+ fieldSmoothing = {},
+ maxSmoothing = 0,
+ smoothedCache = new FIFOCache(2),
+ gpsTransform = null;
+
+ //Public fields:
+ this.parser = parser;
+
+ this.getMainFieldCount = function () {
+ return fieldNames.length;
+ };
+
+ this.getMainFieldNames = function () {
+ return fieldNames;
+ };
+
+ /**
+ * Get the fatal parse error encountered when reading the log with the given index, or false if no error
+ * was encountered.
+ */
+ this.getLogError = function (logIndex) {
+ let error = logIndexes.getIntraframeDirectory(logIndex).error;
+
+ if (error) return error;
+
+ return false;
+ };
+
+ /**
+ * Get the stats for the log of the given index, or leave off the logIndex argument to fetch the stats
+ * for the current log.
+ */
+ function getRawStats(logIndex) {
+ if (logIndex === undefined) {
+ return iframeDirectory.stats;
+ } else {
+ return logIndexes.getIntraframeDirectory(logIndex).stats;
+ }
+ }
+
+ /**
+ * Get the stats for the log of the given index, or leave off the logIndex argument to fetch the stats
+ * for the current log.
+ *
+ * Stats are modified to add a global field[] array which contains merged field stats for the different frame types
+ * that the flightlog presents as one merged frame.
+ */
+ this.getStats = function (logIndex) {
+ let rawStats = getRawStats(logIndex);
+
+ if (rawStats.field === undefined) {
+ rawStats.field = [];
+ for (let i = 0; i < rawStats.frame.I.field.length; ++i) {
+ rawStats.field[i] = {
+ min: Math.min(
+ rawStats.frame.I.field[i].min,
+ rawStats.frame.P.field[i].min
+ ),
+ max: Math.max(
+ rawStats.frame.I.field[i].max,
+ rawStats.frame.P.field[i].max
+ ),
};
- };
+ }
- /**
- * Get the index of the field with the given name, or undefined if that field doesn't exist in the log.
- */
- this.getMainFieldIndexByName = function(name) {
- return fieldNameToIndex[name];
+ if (rawStats.frame.S) {
+ rawStats.field = rawStats.field.concat(rawStats.frame.S.field);
+ }
+ }
+ return rawStats;
+ };
+
+ /**
+ * Get the earliest time seen in the log of the given index (in microseconds), or leave off the logIndex
+ * argument to fetch details for the current log.
+ */
+ this.getMinTime = function (index) {
+ index = index ?? logIndex;
+ return logIndexes.getIntraframeDirectory(index).minTime;
+ };
+
+ /**
+ * Get the latest time seen in the log of the given index (in microseconds), or leave off the logIndex
+ * argument to fetch details for the current log.
+ */
+ this.getMaxTime = function (index) {
+ index = index ?? logIndex;
+ return logIndexes.getIntraframeDirectory(index).maxTime;
+ };
+
+ this.getActualLoggedTime = function (index) {
+ index = index ?? logIndex;
+ const directory = logIndexes.getIntraframeDirectory(index);
+ return directory.maxTime - directory.minTime - directory.unLoggedTime;
+ };
+
+ /**
+ * Get the flight controller system information that was parsed for the current log file.
+ */
+ this.getSysConfig = function () {
+ return parser.sysConfig;
+ };
+
+ this.setSysConfig = function (newSysConfig) {
+ $.extend(true, parser.sysConfig, newSysConfig);
+ };
+
+ /**
+ * Get the index of the currently selected log.
+ */
+ this.getLogIndex = function () {
+ return logIndex;
+ };
+
+ this.getLogCount = function () {
+ return logIndexes.getLogCount();
+ };
+
+ /**
+ * Return a coarse summary of throttle position and events across the entire log.
+ */
+ this.getActivitySummary = function () {
+ let directory = logIndexes.getIntraframeDirectory(logIndex);
+
+ return {
+ times: directory.times,
+ avgThrottle: directory.avgThrottle,
+ maxMotorDiff: directory.maxMotorDiff,
+ maxRC: directory.maxRC,
+ hasEvent: directory.hasEvent,
};
+ };
+
+ /**
+ * Get the index of the field with the given name, or undefined if that field doesn't exist in the log.
+ */
+ this.getMainFieldIndexByName = function (name) {
+ return fieldNameToIndex[name];
+ };
+
+ this.getMainFieldIndexes = function (name) {
+ return fieldNameToIndex;
+ };
+
+ this.getFrameAtTime = function (startTime) {
+ let chunks = this.getChunksInTimeRange(startTime, startTime),
+ chunk = chunks[0];
+
+ if (chunk) {
+ for (var i = 0; i < chunk.frames.length; i++) {
+ if (
+ chunk.frames[i][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ] > startTime
+ )
+ break;
+ }
+
+ return chunk.frames[i - 1];
+ } else return false;
+ };
+
+ this.getSmoothedFrameAtTime = function (startTime) {
+ let chunks = this.getSmoothedChunksInTimeRange(startTime, startTime),
+ chunk = chunks[0];
+
+ if (chunk) {
+ for (var i = 0; i < chunk.frames.length; i++) {
+ if (
+ chunk.frames[i][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ] > startTime
+ )
+ break;
+ }
+
+ return chunk.frames[i - 1];
+ } else return false;
+ };
+
+ this.getCurrentFrameAtTime = function (startTime) {
+ let chunks = this.getSmoothedChunksInTimeRange(startTime, startTime),
+ chunk = chunks[0];
+
+ if (chunk) {
+ for (var i = 0; i < chunk.frames.length; i++) {
+ if (
+ chunk.frames[i][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ] > startTime
+ )
+ break;
+ }
+
+ return {
+ previous: i >= 2 ? chunk.frames[i - 2] : null,
+ current: i >= 1 ? chunk.frames[i - 1] : null,
+ next: i >= 0 ? chunk.frames[i] : null,
+ };
+ } else return false;
+ };
+
+ function buildFieldNames() {
+ // Make an independent copy
+ fieldNames = parser.frameDefs.I.name.slice(0);
+
+ // Add names of slow fields which we'll merge into the main stream
+ if (parser.frameDefs.S) {
+ for (const name of parser.frameDefs.S.name) {
+ fieldNames.push(name);
+ }
+ }
+ // Add names of gps fields which we'll merge into the main stream
+ if (parser.frameDefs.G) {
+ for (const name of parser.frameDefs.G.name) {
+ if (name !== "time") {
+ // remove duplicate time field
+ fieldNames.push(name);
+ }
+ }
+ }
- this.getMainFieldIndexes = function(name) {
- return fieldNameToIndex;
- };
+ // Add names for our ADDITIONAL_COMPUTED_FIELDS
+ if (!that.isFieldDisabled().GYRO) {
+ fieldNames.push("heading[0]", "heading[1]", "heading[2]");
+ }
+ if (!that.isFieldDisabled().PID) {
+ fieldNames.push("axisSum[0]", "axisSum[1]", "axisSum[2]");
+ }
+ if (!that.isFieldDisabled().SETPOINT) {
+ fieldNames.push(
+ "rcCommands[0]",
+ "rcCommands[1]",
+ "rcCommands[2]",
+ "rcCommands[3]"
+ ); // Custom calculated scaled rccommand
+ }
+ if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().SETPOINT) {
+ fieldNames.push("axisError[0]", "axisError[1]", "axisError[2]"); // Custom calculated error field
+ }
+ if (!that.isFieldDisabled().GPS) {
+ fieldNames.push("gpsCartesianCoords[0]", "gpsCartesianCoords[1]", "gpsCartesianCoords[2]", "gpsDistance", "gpsHomeAzimuth"); // GPS coords in cartesian system
+ }
- this.getFrameAtTime = function(startTime) {
- var
- chunks = this.getChunksInTimeRange(startTime, startTime),
- chunk = chunks[0];
+ fieldNameToIndex = {};
+ for (let i = 0; i < fieldNames.length; i++) {
+ fieldNameToIndex[fieldNames[i]] = i;
+ }
+ }
- if (chunk) {
- for (var i = 0; i < chunk.frames.length; i++) {
- if (chunk.frames[i][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] > startTime)
- break;
- }
+ function estimateNumMotors() {
+ let count = 0;
- return chunk.frames[i - 1];
- } else
- return false;
- };
+ for (let j = 0; j < MAX_MOTOR_NUMBER; j++) {
+ if (that.getMainFieldIndexByName(`motor[${j}]`) !== undefined) {
+ count++;
+ }
+ }
- this.getSmoothedFrameAtTime = function(startTime) {
- var
- chunks = this.getSmoothedChunksInTimeRange(startTime, startTime),
- chunk = chunks[0];
+ numMotors = count;
+ }
- if (chunk) {
- for (var i = 0; i < chunk.frames.length; i++) {
- if (chunk.frames[i][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] > startTime)
- break;
- }
+ function estimateNumCells() {
+ let i,
+ fieldNames = that.getMainFieldNames(),
+ sysConfig = that.getSysConfig(),
+ found = false;
- return chunk.frames[i - 1];
- } else
- return false;
- };
+ let refVoltage;
+ if (firmwareGreaterOrEqual(sysConfig, "3.1.0", "2.0.0")) {
+ refVoltage = sysConfig.vbatref;
+ } else {
+ refVoltage = that.vbatADCToMillivolts(sysConfig.vbatref) / 100;
+ }
- this.getCurrentFrameAtTime = function(startTime) {
- var
- chunks = this.getSmoothedChunksInTimeRange(startTime, startTime),
- chunk = chunks[0];
+ //Are we even logging VBAT?
+ if (!fieldNameToIndex.vbatLatest) {
+ numCells = false;
+ } else {
+ for (i = 1; i < 8; i++) {
+ if (refVoltage < i * sysConfig.vbatmaxcellvoltage) break;
+ }
- if (chunk) {
- for (var i = 0; i < chunk.frames.length; i++) {
- if (chunk.frames[i][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] > startTime)
- break;
- }
+ numCells = i;
+ }
+ }
- return {
- previous:(i>=2)?chunk.frames[i - 2]:null,
- current:(i>=1)?chunk.frames[i - 1]:null,
- next:(i>=0)?chunk.frames[i]:null,
- };
- } else
- return false;
- };
+ this.getNumCellsEstimate = function () {
+ return numCells;
+ };
- function buildFieldNames() {
- // Make an independent copy
- fieldNames = parser.frameDefs.I.name.slice(0);
+ this.getNumMotors = function () {
+ return numMotors;
+ };
- // Add names of slow fields which we'll merge into the main stream
- if (parser.frameDefs.S) {
- for (const name of parser.frameDefs.S.name) {
- fieldNames.push(name);
- }
- }
- // Add names of gps fields which we'll merge into the main stream
- if (parser.frameDefs.G) {
- for (const name of parser.frameDefs.G.name) {
- if (name !== 'time') { // remove duplicate time field
- fieldNames.push(name);
- }
- }
- }
+ /**
+ * Get the raw chunks in the range [startIndex...endIndex] (inclusive)
+ *
+ * When the cache misses, this will result in parsing the original log file to create chunks.
+ */
+ function getChunksInIndexRange(startIndex, endIndex) {
+ let resultChunks = [],
+ eventNeedsTimestamp = [];
- // Add names for our ADDITIONAL_COMPUTED_FIELDS
- if (!that.isFieldDisabled().GYRO) {
- fieldNames.push("heading[0]", "heading[1]", "heading[2]");
- }
- if (!that.isFieldDisabled().PID) {
- fieldNames.push("axisSum[0]", "axisSum[1]", "axisSum[2]");
- }
- if (!that.isFieldDisabled().SETPOINT) {
- fieldNames.push("rcCommands[0]", "rcCommands[1]", "rcCommands[2]", "rcCommands[3]"); // Custom calculated scaled rccommand
- }
- if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) {
- fieldNames.push("axisError[0]", "axisError[1]", "axisError[2]"); // Custom calculated error field
- }
+ if (startIndex < 0) startIndex = 0;
- fieldNameToIndex = {};
- for (let i = 0; i < fieldNames.length; i++) {
- fieldNameToIndex[fieldNames[i]] = i;
- }
- }
+ if (endIndex > iframeDirectory.offsets.length - 1)
+ endIndex = iframeDirectory.offsets.length - 1;
- function estimateNumMotors() {
- let count = 0;
+ if (endIndex < startIndex) return [];
- for (let j = 0; j < MAX_MOTOR_NUMBER; j++) {
- if (that.getMainFieldIndexByName(`motor[${j}]`) !== undefined) {
- count++;
- }
- }
+ //Assume caller asked for about a screen-full. Try to cache about three screens worth.
+ if (chunkCache.capacity < (endIndex - startIndex + 1) * 3 + 1) {
+ chunkCache.capacity = (endIndex - startIndex + 1) * 3 + 1;
- numMotors = count;
+ //And while we're here, use the same size for the smoothed cache
+ smoothedCache.capacity = chunkCache.capacity;
}
- function estimateNumCells() {
- var
- i,
- fieldNames = that.getMainFieldNames(),
- sysConfig = that.getSysConfig(),
- found = false;
+ for (let chunkIndex = startIndex; chunkIndex <= endIndex; chunkIndex++) {
+ var chunkStartOffset,
+ chunkEndOffset,
+ chunk = chunkCache.get(chunkIndex);
- var refVoltage;
- if(firmwareGreaterOrEqual(sysConfig, '3.1.0', '2.0.0')) {
- refVoltage = sysConfig.vbatref;
- } else {
- refVoltage = that.vbatADCToMillivolts(sysConfig.vbatref) / 100;
- }
+ // Did we cache this chunk already?
+ if (chunk) {
+ // Use the first event in the chunk to fill in event times at the trailing end of the previous one
+ let frame = chunk.frames[0];
- //Are we even logging VBAT?
- if (!fieldNameToIndex.vbatLatest) {
- numCells = false;
- } else {
- for (i = 1; i < 8; i++) {
- if (refVoltage < i * sysConfig.vbatmaxcellvoltage)
- break;
- }
-
- numCells = i;
+ for (let i = 0; i < eventNeedsTimestamp.length; i++) {
+ eventNeedsTimestamp[i].time =
+ frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
}
- }
+ eventNeedsTimestamp.length = 0;
+ } else {
+ // Parse the log file to create this chunk since it wasn't cached
+ chunkStartOffset = iframeDirectory.offsets[chunkIndex];
- this.getNumCellsEstimate = function() {
- return numCells;
- };
+ if (chunkIndex + 1 < iframeDirectory.offsets.length)
+ chunkEndOffset = iframeDirectory.offsets[chunkIndex + 1];
+ // We're at the end so parse till end-of-log
+ else chunkEndOffset = logIndexes.getLogBeginOffset(logIndex + 1);
- this.getNumMotors = function() {
- return numMotors;
- };
-
- /**
- * Get the raw chunks in the range [startIndex...endIndex] (inclusive)
- *
- * When the cache misses, this will result in parsing the original log file to create chunks.
- */
- function getChunksInIndexRange(startIndex, endIndex) {
- var
- resultChunks = [],
- eventNeedsTimestamp = [];
-
- if (startIndex < 0)
- startIndex = 0;
+ chunk = chunkCache.recycle();
- if (endIndex > iframeDirectory.offsets.length - 1)
- endIndex = iframeDirectory.offsets.length - 1;
-
- if (endIndex < startIndex)
- return [];
-
- //Assume caller asked for about a screen-full. Try to cache about three screens worth.
- if (chunkCache.capacity < (endIndex - startIndex + 1) * 3 + 1) {
- chunkCache.capacity = (endIndex - startIndex + 1) * 3 + 1;
-
- //And while we're here, use the same size for the smoothed cache
- smoothedCache.capacity = chunkCache.capacity;
+ // Were we able to reuse memory from an expired chunk?
+ if (chunk) {
+ chunk.index = chunkIndex;
+ /*
+ * getSmoothedChunks would like to share this data, so we can't reuse the old arrays without
+ * accidentally changing data that it might still want to reference:
+ */
+ chunk.gapStartsHere = {};
+ chunk.events = [];
+ delete chunk.hasAdditionalFields;
+ delete chunk.needsEventTimes;
+
+ //But reuse the old chunk's frames array since getSmoothedChunks has an independent copy
+ } else {
+ chunk = {
+ index: chunkIndex,
+ frames: [],
+ gapStartsHere: {},
+ events: [],
+ };
}
- for (var chunkIndex = startIndex; chunkIndex <= endIndex; chunkIndex++) {
- var
- chunkStartOffset, chunkEndOffset,
- chunk = chunkCache.get(chunkIndex);
+ // We need to store this in the chunk so we can refer to it later when we inject computed fields
+ chunk.initialIMU = iframeDirectory.initialIMU[chunkIndex];
+
+ var mainFrameIndex = 0,
+ slowFrameLength = parser.frameDefs.S ? parser.frameDefs.S.count : 0,
+ lastSlow = parser.frameDefs.S
+ ? iframeDirectory.initialSlow[chunkIndex].slice(0)
+ : [],
+ lastGPSLength = parser.frameDefs.G ? parser.frameDefs.G.count - 1 : 0, // -1 since we exclude the time field
+ lastGPS = parser.frameDefs.G
+ ? iframeDirectory.initialGPS[chunkIndex].slice(0)
+ : [];
+
+ parser.onFrameReady = function (
+ frameValid,
+ frame,
+ frameType,
+ frameOffset,
+ frameSize
+ ) {
+ let destFrame, destFrame_currentIndex;
+
+ // The G frames need to be processed always. They are "invalid" if not H (Home) has been detected
+ // before, but if not processed the viewer shows cuts and gaps. This happens if the quad takes off before
+ // fixing enough satellites.
+ if (frameValid || (frameType == "G" && frame)) {
+ switch (frameType) {
+ case "P":
+ case "I":
+ //The parser re-uses the "frame" array so we must copy that data somewhere else
+
+ var numOutputFields =
+ frame.length +
+ slowFrameLength +
+ lastGPSLength +
+ ADDITIONAL_COMPUTED_FIELD_COUNT;
+
+ //Do we have a recycled chunk to copy on top of?
+ if (chunk.frames[mainFrameIndex]) {
+ destFrame = chunk.frames[mainFrameIndex];
+ destFrame.length = numOutputFields;
+ } else {
+ // Otherwise allocate a new array
+ destFrame = new Array(numOutputFields);
+ chunk.frames.push(destFrame);
+ }
- // Did we cache this chunk already?
- if (chunk) {
- // Use the first event in the chunk to fill in event times at the trailing end of the previous one
- var frame = chunk.frames[0];
+ // Copy the main frame data in
+ for (var i = 0; i < frame.length; i++) {
+ destFrame[i] = frame[i];
+ }
- for (var i = 0; i < eventNeedsTimestamp.length; i++) {
- eventNeedsTimestamp[i].time = frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
+ destFrame_currentIndex = frame.length; // Keeps track of where to place direct data in the destFrame.
+ // Then merge in the last seen slow-frame data
+ for (
+ let slowFrameIndex = 0;
+ slowFrameIndex < slowFrameLength;
+ slowFrameIndex++
+ ) {
+ destFrame[slowFrameIndex + destFrame_currentIndex] =
+ lastSlow[slowFrameIndex] === undefined
+ ? null
+ : lastSlow[slowFrameIndex];
}
- eventNeedsTimestamp.length = 0;
- } else {
- // Parse the log file to create this chunk since it wasn't cached
- chunkStartOffset = iframeDirectory.offsets[chunkIndex];
-
- if (chunkIndex + 1 < iframeDirectory.offsets.length)
- chunkEndOffset = iframeDirectory.offsets[chunkIndex + 1];
- else // We're at the end so parse till end-of-log
- chunkEndOffset = logIndexes.getLogBeginOffset(logIndex + 1);
-
- chunk = chunkCache.recycle();
-
- // Were we able to reuse memory from an expired chunk?
- if (chunk) {
- chunk.index = chunkIndex;
- /*
- * getSmoothedChunks would like to share this data, so we can't reuse the old arrays without
- * accidentally changing data that it might still want to reference:
- */
- chunk.gapStartsHere = {};
- chunk.events = [];
- delete chunk.hasAdditionalFields;
- delete chunk.needsEventTimes;
-
- //But reuse the old chunk's frames array since getSmoothedChunks has an independent copy
- } else {
- chunk = {
- index: chunkIndex,
- frames: [],
- gapStartsHere: {},
- events: []
- };
+ destFrame_currentIndex += slowFrameLength;
+
+ // Also merge last seen gps-frame data
+ for (
+ let gpsFrameIndex = 0;
+ gpsFrameIndex < lastGPSLength;
+ gpsFrameIndex++
+ ) {
+ destFrame[gpsFrameIndex + destFrame_currentIndex] =
+ lastGPS[gpsFrameIndex] === undefined
+ ? null
+ : lastGPS[gpsFrameIndex];
}
+ // destFrame_currentIndex += lastGPSLength; Add this line if you wish to add more fields.
- // We need to store this in the chunk so we can refer to it later when we inject computed fields
- chunk.initialIMU = iframeDirectory.initialIMU[chunkIndex];
-
- var
- mainFrameIndex = 0,
- slowFrameLength = parser.frameDefs.S ? parser.frameDefs.S.count : 0,
- lastSlow = parser.frameDefs.S ? iframeDirectory.initialSlow[chunkIndex].slice(0) : [],
- lastGPSLength = parser.frameDefs.G ? parser.frameDefs.G.count-1 : 0, // -1 since we exclude the time field
- lastGPS = parser.frameDefs.G ? iframeDirectory.initialGPS[chunkIndex].slice(0) : [];
-
- parser.onFrameReady = function(frameValid, frame, frameType, frameOffset, frameSize) {
- var
- destFrame,
- destFrame_currentIndex;
-
- // The G frames need to be processed always. They are "invalid" if not H (Home) has been detected
- // before, but if not processed the viewer shows cuts and gaps. This happens if the quad takes off before
- // fixing enough satellites.
- if (frameValid || (frameType == 'G' && frame)) {
- switch (frameType) {
- case 'P':
- case 'I':
-
- //The parser re-uses the "frame" array so we must copy that data somewhere else
-
- var
- numOutputFields = frame.length + slowFrameLength + lastGPSLength + ADDITIONAL_COMPUTED_FIELD_COUNT;
-
- //Do we have a recycled chunk to copy on top of?
- if (chunk.frames[mainFrameIndex]) {
- destFrame = chunk.frames[mainFrameIndex];
- destFrame.length = numOutputFields;
- } else {
- // Otherwise allocate a new array
- destFrame = new Array(numOutputFields);
- chunk.frames.push(destFrame);
- }
-
- // Copy the main frame data in
- for (var i = 0; i < frame.length; i++) {
- destFrame[i] = frame[i];
- }
-
- destFrame_currentIndex = frame.length; // Keeps track of where to place direct data in the destFrame.
- // Then merge in the last seen slow-frame data
- for (let slowFrameIndex = 0; slowFrameIndex < slowFrameLength; slowFrameIndex++) {
- destFrame[slowFrameIndex + destFrame_currentIndex] = lastSlow[slowFrameIndex] === undefined ? null : lastSlow[slowFrameIndex];
- }
- destFrame_currentIndex += slowFrameLength;
-
- // Also merge last seen gps-frame data
- for (let gpsFrameIndex = 0; gpsFrameIndex < lastGPSLength; gpsFrameIndex++) {
- destFrame[gpsFrameIndex + destFrame_currentIndex] = lastGPS[gpsFrameIndex] === undefined ? null : lastGPS[gpsFrameIndex];
- }
- // destFrame_currentIndex += lastGPSLength; Add this line if you wish to add more fields.
-
- for (var i = 0; i < eventNeedsTimestamp.length; i++) {
- eventNeedsTimestamp[i].time = frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- }
- eventNeedsTimestamp.length = 0;
-
- mainFrameIndex++;
-
- break;
- case 'E':
- if (frame.event == FlightLogEvent.LOGGING_RESUME) {
- chunk.gapStartsHere[mainFrameIndex - 1] = true;
- }
-
- /*
- * If the event was logged during a loop iteration, it will appear in the log
- * before that loop iteration does (since the main log stream is logged at the very
- * end of the loop).
- *
- * So we want to use the timestamp of that later frame as the timestamp of the loop
- * iteration this event was logged in.
- */
- if (!frame.time) {
- eventNeedsTimestamp.push(frame);
- }
- chunk.events.push(frame);
- break;
- case 'S':
- for (var i = 0; i < frame.length; i++) {
- lastSlow[i] = frame[i];
- }
- break;
- case 'H':
- // TODO
- // contains coordinates only
- // should be handled separately
- case 'G':
- // The frameValid can be false, when no GPS home (the G frames contains GPS position as diff of GPS Home position).
- // But other data from the G frame can be valid (time, num sats)
-
- //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course
- frame.shift(); // remove time
- for (let i = 0; i < frame.length; i++) {
- lastGPS[i] = frame[i];
- }
- break;
- }
- } else {
- chunk.gapStartsHere[mainFrameIndex - 1] = true;
- }
- };
-
- parser.resetDataState();
-
- //Prime the parser with the previous state we get from the flightlog index, so it can base deltas off that data
- if (iframeDirectory.initialGPSHome) {
- parser.setGPSHomeHistory(iframeDirectory.initialGPSHome[chunkIndex]);
+ for (var i = 0; i < eventNeedsTimestamp.length; i++) {
+ eventNeedsTimestamp[i].time =
+ frame[
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ];
}
+ eventNeedsTimestamp.length = 0;
- parser.parseLogData(false, chunkStartOffset, chunkEndOffset);
+ mainFrameIndex++;
- //Truncate the array to fit just in case it was recycled and the new one is shorter
- chunk.frames.length = mainFrameIndex;
+ break;
+ case "E":
+ if (frame.event == FlightLogEvent.LOGGING_RESUME) {
+ chunk.gapStartsHere[mainFrameIndex - 1] = true;
+ }
- chunkCache.add(chunkIndex, chunk);
+ /*
+ * If the event was logged during a loop iteration, it will appear in the log
+ * before that loop iteration does (since the main log stream is logged at the very
+ * end of the loop).
+ *
+ * So we want to use the timestamp of that later frame as the timestamp of the loop
+ * iteration this event was logged in.
+ */
+ if (!frame.time) {
+ eventNeedsTimestamp.push(frame);
+ }
+ chunk.events.push(frame);
+ break;
+ case "S":
+ for (var i = 0; i < frame.length; i++) {
+ lastSlow[i] = frame[i];
+ }
+ break;
+ case "H": {
+ const homeAltitude = frame.length > 2 ? frame[2] / 10 : 0; // will work after BF firmware improvement
+ gpsTransform = new GPS_transform(frame[0] / 10000000, frame[1] / 10000000, homeAltitude, 0.0);
+ break;
+ }
+ case "G":
+ // The frameValid can be false, when no GPS home (the G frames contains GPS position as diff of GPS Home position).
+ // But other data from the G frame can be valid (time, num sats)
+
+ //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course
+ frame.shift(); // remove time
+ for (let i = 0; i < frame.length; i++) {
+ lastGPS[i] = frame[i];
+ }
+ break;
}
+ } else {
+ chunk.gapStartsHere[mainFrameIndex - 1] = true;
+ }
+ };
- resultChunks.push(chunk);
- }
+ parser.resetDataState();
- /*
- * If there is an event that trailed the all the chunks we were decoding, we can't give it an event time field
- * because we didn't get to see the time of the next frame.
- */
- if (eventNeedsTimestamp.length > 0) {
- resultChunks[resultChunks.length - 1].needsEventTimes = true;
+ //Prime the parser with the previous state we get from the flightlog index, so it can base deltas off that data
+ if (iframeDirectory.initialGPSHome) {
+ parser.setGPSHomeHistory(iframeDirectory.initialGPSHome[chunkIndex]);
}
- injectComputedFields(resultChunks, resultChunks);
+ parser.parseLogData(false, chunkStartOffset, chunkEndOffset);
- return resultChunks;
- }
+ //Truncate the array to fit just in case it was recycled and the new one is shorter
+ chunk.frames.length = mainFrameIndex;
- /**
- * Get an array of chunks which span times from the given start to end time.
- * Each chunk is an array of log frames.
- */
- this.getChunksInTimeRange = function(startTime, endTime) {
- var
- startIndex = binarySearchOrPrevious(iframeDirectory.times, startTime),
- endIndex = binarySearchOrPrevious(iframeDirectory.times, endTime);
+ chunkCache.add(chunkIndex, chunk);
+ }
- return getChunksInIndexRange(startIndex, endIndex);
- };
+ resultChunks.push(chunk);
+ }
/*
- * Smoothing is map from field index to smoothing radius, where radius is in us. You only need to specify fields
- * which need to be smoothed.
+ * If there is an event that trailed the all the chunks we were decoding, we can't give it an event time field
+ * because we didn't get to see the time of the next frame.
*/
- this.setFieldSmoothing = function(newSmoothing) {
- smoothedCache.clear();
- fieldSmoothing = newSmoothing;
+ if (eventNeedsTimestamp.length > 0) {
+ resultChunks[resultChunks.length - 1].needsEventTimes = true;
+ }
- maxSmoothing = 0;
+ injectComputedFields(resultChunks, resultChunks);
- for (var fieldIndex in newSmoothing) {
- if (newSmoothing[fieldIndex] > maxSmoothing) {
- maxSmoothing = newSmoothing[fieldIndex];
- }
- }
- };
+ return resultChunks;
+ }
- /**
- * Use the data in sourceChunks to compute additional fields (like IMU attitude) and add those into the
- * resultChunks.
- *
- * sourceChunks and destChunks can be the same array.
- */
- function injectComputedFields(sourceChunks, destChunks) {
+ /**
+ * Get an array of chunks which span times from the given start to end time.
+ * Each chunk is an array of log frames.
+ */
+ this.getChunksInTimeRange = function (startTime, endTime) {
+ let startIndex = binarySearchOrPrevious(iframeDirectory.times, startTime),
+ endIndex = binarySearchOrPrevious(iframeDirectory.times, endTime);
- let gyroADC = [fieldNameToIndex["gyroADC[0]"], fieldNameToIndex["gyroADC[1]"], fieldNameToIndex["gyroADC[2]"]];
- let accSmooth = [fieldNameToIndex["accSmooth[0]"], fieldNameToIndex["accSmooth[1]"], fieldNameToIndex["accSmooth[2]"]];
- let magADC = [fieldNameToIndex["magADC[0]"], fieldNameToIndex["magADC[1]"], fieldNameToIndex["magADC[2]"]];
- let rcCommand = [fieldNameToIndex["rcCommand[0]"], fieldNameToIndex["rcCommand[1]"], fieldNameToIndex["rcCommand[2]"], fieldNameToIndex["rcCommand[3]"]];
- let setpoint = [fieldNameToIndex["setpoint[0]"], fieldNameToIndex["setpoint[1]"], fieldNameToIndex["setpoint[2]"], fieldNameToIndex["setpoint[3]"]];
+ return getChunksInIndexRange(startIndex, endIndex);
+ };
- const flightModeFlagsIndex = fieldNameToIndex["flightModeFlags"]; // This points to the flightmode data
+ /*
+ * Smoothing is map from field index to smoothing radius, where radius is in us. You only need to specify fields
+ * which need to be smoothed.
+ */
+ this.setFieldSmoothing = function (newSmoothing) {
+ smoothedCache.clear();
+ fieldSmoothing = newSmoothing;
- let axisPID = [[fieldNameToIndex["axisP[0]"], fieldNameToIndex["axisI[0]"], fieldNameToIndex["axisD[0]"], fieldNameToIndex["axisF[0]"]],
- [fieldNameToIndex["axisP[1]"], fieldNameToIndex["axisI[1]"], fieldNameToIndex["axisD[1]"], fieldNameToIndex["axisF[1]"]],
- [fieldNameToIndex["axisP[2]"], fieldNameToIndex["axisI[2]"], fieldNameToIndex["axisD[2]"], fieldNameToIndex["axisF[2]"]]];
+ maxSmoothing = 0;
- let sourceChunkIndex;
- let destChunkIndex;
- let attitude;
+ for (let fieldIndex in newSmoothing) {
+ if (newSmoothing[fieldIndex] > maxSmoothing) {
+ maxSmoothing = newSmoothing[fieldIndex];
+ }
+ }
+ };
+
+ /**
+ * Use the data in sourceChunks to compute additional fields (like IMU attitude) and add those into the
+ * resultChunks.
+ *
+ * sourceChunks and destChunks can be the same array.
+ */
+ function injectComputedFields(sourceChunks, destChunks) {
+ let gyroADC = [fieldNameToIndex["gyroADC[0]"], fieldNameToIndex["gyroADC[1]"], fieldNameToIndex["gyroADC[2]"]];
+ let accSmooth = [fieldNameToIndex["accSmooth[0]"], fieldNameToIndex["accSmooth[1]"], fieldNameToIndex["accSmooth[2]"]];
+ let magADC = [fieldNameToIndex["magADC[0]"], fieldNameToIndex["magADC[1]"], fieldNameToIndex["magADC[2]"]];
+ let imuQuaternion = [
+ fieldNameToIndex["imuQuaternion[0]"],
+ fieldNameToIndex["imuQuaternion[1]"],
+ fieldNameToIndex["imuQuaternion[2]"],
+ ];
+ let rcCommand = [
+ fieldNameToIndex["rcCommand[0]"],
+ fieldNameToIndex["rcCommand[1]"],
+ fieldNameToIndex["rcCommand[2]"],
+ fieldNameToIndex["rcCommand[3]"],
+ ];
+ let setpoint = [
+ fieldNameToIndex["setpoint[0]"],
+ fieldNameToIndex["setpoint[1]"],
+ fieldNameToIndex["setpoint[2]"],
+ fieldNameToIndex["setpoint[3]"],
+ ];
+ let gpsCoord = [
+ fieldNameToIndex["GPS_coord[0]"],
+ fieldNameToIndex["GPS_coord[1]"],
+ fieldNameToIndex["GPS_altitude"],
+ ];
+
+ const flightModeFlagsIndex = fieldNameToIndex["flightModeFlags"]; // This points to the flightmode data
+
+ let axisPID = [
+ [
+ fieldNameToIndex["axisP[0]"],
+ fieldNameToIndex["axisI[0]"],
+ fieldNameToIndex["axisD[0]"],
+ fieldNameToIndex["axisF[0]"],
+ ],
+ [
+ fieldNameToIndex["axisP[1]"],
+ fieldNameToIndex["axisI[1]"],
+ fieldNameToIndex["axisD[1]"],
+ fieldNameToIndex["axisF[1]"],
+ ],
+ [
+ fieldNameToIndex["axisP[2]"],
+ fieldNameToIndex["axisI[2]"],
+ fieldNameToIndex["axisD[2]"],
+ fieldNameToIndex["axisF[2]"],
+ ],
+ ];
+
+ let sourceChunkIndex;
+ let destChunkIndex;
+
+ const sysConfig = that.getSysConfig();
+
+ if (destChunks.length === 0) {
+ return;
+ }
- const sysConfig = that.getSysConfig();
+// Do we have mag fields? If not mark that data as absent
+ if (!magADC[0]) {
+ magADC = false;
+ }
- if (destChunks.length === 0) {
- return;
- }
+ if (!gyroADC[0]) {
+ gyroADC = false;
+ }
- // Do we have mag fields? If not mark that data as absent
- if (!magADC[0]) {
- magADC = false;
- }
+ if (!accSmooth[0]) {
+ accSmooth = false;
+ }
- if (!gyroADC[0]) {
- gyroADC = false;
- }
+ if (!imuQuaternion[0]) {
+ imuQuaternion = false;
+ }
- if (!accSmooth[0]) {
- accSmooth = false;
- }
+ if (!rcCommand[0]) {
+ rcCommand = false;
+ }
- if (!rcCommand[0]) {
- rcCommand = false;
- }
+ if (!setpoint[0]) {
+ setpoint = false;
+ }
- if (!setpoint[0]) {
- setpoint = false;
- }
+ if (!axisPID[0]) {
+ axisPID = false;
+ }
- if (!axisPID[0]) {
- axisPID = false;
- }
+ if (!gpsCoord[0]) {
+ gpsCoord = false;
+ }
- sourceChunkIndex = 0;
- destChunkIndex = 0;
- // Skip leading source chunks that don't appear in the destination
- while (sourceChunks[sourceChunkIndex].index < destChunks[destChunkIndex].index) {
- sourceChunkIndex++;
- }
+ sourceChunkIndex = 0;
+ destChunkIndex = 0;
- for (; destChunkIndex < destChunks.length; sourceChunkIndex++, destChunkIndex++) {
- var
- destChunk = destChunks[destChunkIndex],
- sourceChunk = sourceChunks[sourceChunkIndex];
-
- if (!destChunk.hasAdditionalFields) {
- destChunk.hasAdditionalFields = true;
-
- var
- chunkIMU = new IMU(sourceChunks[sourceChunkIndex].initialIMU);
-
- for (var i = 0; i < sourceChunk.frames.length; i++) {
- var
- srcFrame = sourceChunk.frames[i],
- destFrame = destChunk.frames[i],
- fieldIndex = destFrame.length - ADDITIONAL_COMPUTED_FIELD_COUNT;
-
- if (!that.isFieldDisabled().GYRO) { //don't calculate attitude if no gyro data
- attitude = chunkIMU.updateEstimatedAttitude(
- [srcFrame[gyroADC[0]], srcFrame[gyroADC[1]], srcFrame[gyroADC[2]]],
- [srcFrame[accSmooth[0]], srcFrame[accSmooth[1]], srcFrame[accSmooth[2]]],
- srcFrame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME],
- sysConfig.acc_1G,
- sysConfig.gyroScale,
- magADC);
-
- destFrame[fieldIndex++] = attitude.roll;
- destFrame[fieldIndex++] = attitude.pitch;
- destFrame[fieldIndex++] = attitude.heading;
- }
-
- // Add the Feedforward PID sum (P+I+D+F)
- if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) {
- for (var axis = 0; axis < 3; axis++) {
- let pidSum =
- (axisPID[axis][0] !== undefined ? srcFrame[axisPID[axis][0]] : 0) +
- (axisPID[axis][1] !== undefined ? srcFrame[axisPID[axis][1]] : 0) +
- (axisPID[axis][2] !== undefined ? srcFrame[axisPID[axis][2]] : 0) +
- (axisPID[axis][3] !== undefined ? srcFrame[axisPID[axis][3]] : 0);
-
- // Limit the PID sum by the limits defined in the header
- let pidLimit = axis < AXIS.YAW ? sysConfig.pidSumLimit : sysConfig.pidSumLimitYaw;
- if (pidLimit != null && pidLimit > 0) {
- pidSum = constrain(pidSum, -pidLimit, pidLimit);
- }
-
- // Assign value
- destFrame[fieldIndex++] = pidSum;
- }
- }
-
- // Check the current flightmode (we need to know this so that we can correctly calculate the rates)
- var currentFlightMode = srcFrame[flightModeFlagsIndex];
-
- // Calculate the Scaled rcCommand (setpoint) (in deg/s, % for throttle)
- var fieldIndexRcCommands = fieldIndex;
-
- if (!that.isFieldDisabled().SETPOINT) {
-
- // Since version 4.0 is not more a virtual field. Copy the real field to the virtual one to maintain the name, workspaces, etc.
- if (sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(sysConfig.firmwareVersion, '4.0.0')) {
- // Roll, pitch and yaw
- for (let axis = 0; axis <= AXIS.YAW; axis++) {
- destFrame[fieldIndex++] = srcFrame[setpoint[axis]];
- }
- // Throttle
- destFrame[fieldIndex++] = srcFrame[setpoint[AXIS.YAW + 1]]/10;
-
- // Versions earlier to 4.0 we must calculate the expected setpoint
- } else {
- // Roll, pitch and yaw
- for (let axis = 0; axis <= AXIS.YAW; axis++) {
- destFrame[fieldIndex++] = rcCommand[axis] !== undefined ? that.rcCommandRawToDegreesPerSecond(srcFrame[rcCommand[axis]], axis, currentFlightMode) : 0;
- }
- // Throttle
- destFrame[fieldIndex++] =
- (rcCommand[AXIS.YAW + 1] !== undefined ? that.rcCommandRawToThrottle(srcFrame[rcCommand[AXIS.YAW + 1]]) : 0);
- }
- }
-
- // Calculate the PID Error
- if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) {
- for (var axis = 0; axis < 3; axis++) {
- let gyroADCdegrees = (gyroADC[axis] !== undefined ? that.gyroRawToDegreesPerSecond(srcFrame[gyroADC[axis]]) : 0);
- destFrame[fieldIndex++] = destFrame[fieldIndexRcCommands + axis] - gyroADCdegrees;
- }
- }
-
- // Remove empty fields at the end
- destFrame.splice(fieldIndex);
+ // Skip leading source chunks that don't appear in the destination
+ while (
+ sourceChunks[sourceChunkIndex].index < destChunks[destChunkIndex].index
+ ) {
+ sourceChunkIndex++;
+ }
- }
+ for (
+ ;
+ destChunkIndex < destChunks.length;
+ sourceChunkIndex++, destChunkIndex++
+ ) {
+ let destChunk = destChunks[destChunkIndex],
+ sourceChunk = sourceChunks[sourceChunkIndex];
+
+ if (!destChunk.hasAdditionalFields) {
+ destChunk.hasAdditionalFields = true;
+ const chunkIMU = new IMU(sourceChunk.initialIMU);
+
+ for (let i = 0; i < sourceChunk.frames.length; i++) {
+ let srcFrame = sourceChunk.frames[i],
+ destFrame = destChunk.frames[i],
+ fieldIndex = destFrame.length - ADDITIONAL_COMPUTED_FIELD_COUNT;
+
+ if (imuQuaternion) {
+ const scaleFromFixedInt16 = 0x7FFF; // 0x7FFF = 2^15 - 1
+ const q = {
+ x: srcFrame[imuQuaternion[0]] / scaleFromFixedInt16,
+ y: srcFrame[imuQuaternion[1]] / scaleFromFixedInt16,
+ z: srcFrame[imuQuaternion[2]] / scaleFromFixedInt16,
+ w: 1.0,
+ };
+ q.w = Math.sqrt(1.0 - (q.x ** 2 + q.y ** 2 + q.z ** 2));
+ const xx = q.x ** 2,
+ xy = q.x * q.y,
+ xz = q.x * q.z,
+ wx = q.w * q.x,
+ yy = q.y ** 2,
+ yz = q.y * q.z,
+ wy = q.w * q.y,
+ zz = q.z ** 2,
+ wz = q.w * q.z;
+ let roll = Math.atan2((+2.0 * (wx + yz)), (+1.0 - 2.0 * (xx + yy)));
+ let pitch = ((0.5 * Math.PI) - Math.acos(+2.0 * (wy - xz)));
+ let heading = -Math.atan2((+2.0 * (wz + xy)), (+1.0 - 2.0 * (yy + zz)));
+ if (heading < 0) {
+ heading += 2.0 * Math.PI;
+ }
+
+ destFrame[fieldIndex++] = roll;
+ destFrame[fieldIndex++] = pitch;
+ destFrame[fieldIndex++] = heading;
+ } else {
+ const attitude = chunkIMU.updateEstimatedAttitude(
+ [srcFrame[gyroADC[0]], srcFrame[gyroADC[1]], srcFrame[gyroADC[2]]],
+ [srcFrame[accSmooth[0]], srcFrame[accSmooth[1]], srcFrame[accSmooth[2]]],
+ srcFrame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME],
+ sysConfig.acc_1G,
+ sysConfig.gyroScale,
+ magADC);
+ destFrame[fieldIndex++] = attitude.roll;
+ destFrame[fieldIndex++] = attitude.pitch;
+ destFrame[fieldIndex++] = attitude.heading;
+ }
+
+ // Add the Feedforward PID sum (P+I+D+F)
+ if (!that.isFieldDisabled().PID) {
+ for (var axis = 0; axis < 3; axis++) {
+ let pidSum =
+ (axisPID[axis][0] !== undefined
+ ? srcFrame[axisPID[axis][0]]
+ : 0) +
+ (axisPID[axis][1] !== undefined
+ ? srcFrame[axisPID[axis][1]]
+ : 0) +
+ (axisPID[axis][2] !== undefined
+ ? srcFrame[axisPID[axis][2]]
+ : 0) +
+ (axisPID[axis][3] !== undefined
+ ? srcFrame[axisPID[axis][3]]
+ : 0);
+
+ // Limit the PID sum by the limits defined in the header
+ let pidLimit =
+ axis < AXIS.YAW
+ ? sysConfig.pidSumLimit
+ : sysConfig.pidSumLimitYaw;
+ if (pidLimit != null && pidLimit > 0) {
+ pidSum = constrain(pidSum, -pidLimit, pidLimit);
+ }
+
+ // Assign value
+ destFrame[fieldIndex++] = pidSum;
}
+ }
+
+ // Check the current flightmode (we need to know this so that we can correctly calculate the rates)
+ let currentFlightMode = srcFrame[flightModeFlagsIndex];
+
+ // Calculate the Scaled rcCommand (setpoint) (in deg/s, % for throttle)
+ let fieldIndexRcCommands = fieldIndex;
+
+ if (!that.isFieldDisabled().SETPOINT) {
+ // Since version 4.0 is not more a virtual field. Copy the real field to the virtual one to maintain the name, workspaces, etc.
+ if (
+ sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(sysConfig.firmwareVersion, "4.0.0")
+ ) {
+ // Roll, pitch and yaw
+ for (let axis = 0; axis <= AXIS.YAW; axis++) {
+ destFrame[fieldIndex++] = srcFrame[setpoint[axis]];
+ }
+ // Throttle
+ destFrame[fieldIndex++] = srcFrame[setpoint[AXIS.YAW + 1]] / 10;
+
+ // Versions earlier to 4.0 we must calculate the expected setpoint
+ } else {
+ // Roll, pitch and yaw
+ for (let axis = 0; axis <= AXIS.YAW; axis++) {
+ destFrame[fieldIndex++] =
+ rcCommand[axis] !== undefined
+ ? that.rcCommandRawToDegreesPerSecond(
+ srcFrame[rcCommand[axis]],
+ axis,
+ currentFlightMode
+ )
+ : 0;
+ }
+ // Throttle
+ destFrame[fieldIndex++] =
+ rcCommand[AXIS.YAW + 1] !== undefined
+ ? that.rcCommandRawToThrottle(
+ srcFrame[rcCommand[AXIS.YAW + 1]]
+ )
+ : 0;
+ }
+ }
+
+ // Calculate the PID Error
+ if (
+ !that.isFieldDisabled().GYRO &&
+ !that.isFieldDisabled().SETPOINT
+ ) {
+ for (var axis = 0; axis < 3; axis++) {
+ let gyroADCdegrees =
+ gyroADC[axis] !== undefined
+ ? that.gyroRawToDegreesPerSecond(srcFrame[gyroADC[axis]])
+ : 0;
+ destFrame[fieldIndex++] =
+ destFrame[fieldIndexRcCommands + axis] - gyroADCdegrees;
+ }
+ }
+
+ // Calculate cartesian coords by GPS
+ if (!that.isFieldDisabled().GPS) {
+ if (gpsTransform && gpsCoord && srcFrame[gpsCoord[0]]) {
+ const gpsCartesianCoords = gpsTransform.WGS_BS(srcFrame[gpsCoord[0]] / 10000000, srcFrame[gpsCoord[1]] / 10000000, srcFrame[gpsCoord[2]] / 10);
+ destFrame[fieldIndex++] = gpsCartesianCoords.x;
+ destFrame[fieldIndex++] = gpsCartesianCoords.y;
+ destFrame[fieldIndex++] = gpsCartesianCoords.z;
+ destFrame[fieldIndex++] = Math.sqrt(gpsCartesianCoords.x * gpsCartesianCoords.x + gpsCartesianCoords.z * gpsCartesianCoords.z);
+
+ let homeAzimuth = Math.atan2(-gpsCartesianCoords.z, -gpsCartesianCoords.x) * 180 / Math.PI;
+ if (homeAzimuth < 0) {
+ homeAzimuth += 360;
+ }
+ destFrame[fieldIndex++] = homeAzimuth;
+ } else {
+ destFrame[fieldIndex++] = 0;
+ destFrame[fieldIndex++] = 0;
+ destFrame[fieldIndex++] = 0;
+ destFrame[fieldIndex++] = 0;
+ destFrame[fieldIndex++] = 0;
+ }
+ }
+
+ // Remove empty fields at the end
+ destFrame.splice(fieldIndex);
}
+ }
}
-
- /**
- * Add timestamps to events that getChunksInRange was unable to compute, because at the time it had trailing
- * events in its chunk array but no next-chunk to take the times from for those events.
- *
- * Set processLastChunk to true if the last chunk of this array is the final chunk in the file.
+ }
+
+ /**
+ * Add timestamps to events that getChunksInRange was unable to compute, because at the time it had trailing
+ * events in its chunk array but no next-chunk to take the times from for those events.
+ *
+ * Set processLastChunk to true if the last chunk of this array is the final chunk in the file.
+ */
+ function addMissingEventTimes(chunks, processLastChunk) {
+ /*
+ * If we're at the end of the file then we will compute event times for the last chunk, otherwise we'll
+ * wait until we have the next chunk to fill in times for this last chunk.
*/
- function addMissingEventTimes(chunks, processLastChunk) {
- /*
- * If we're at the end of the file then we will compute event times for the last chunk, otherwise we'll
- * wait until we have the next chunk to fill in times for this last chunk.
- */
- var
- endChunk = processLastChunk ? chunks.length : chunks.length - 1;
-
- for (var i = 0; i < endChunk; i++) {
- var chunk = chunks[i];
+ let endChunk = processLastChunk ? chunks.length : chunks.length - 1;
- if (chunk.needsEventTimes) {
- // What is the time of the next frame after the chunk with the trailing events? We'll use that for the event times
- var nextTime;
+ for (let i = 0; i < endChunk; i++) {
+ let chunk = chunks[i];
- if (i + 1 < chunks.length) {
- var nextChunk = chunks[i + 1];
+ if (chunk.needsEventTimes) {
+ // What is the time of the next frame after the chunk with the trailing events? We'll use that for the event times
+ var nextTime;
- nextTime = nextChunk.frames[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- } else {
- //Otherwise we're at the end of the log so assume this event was logged sometime after the final frame
- nextTime = chunk.frames[chunk.frames.length - 1][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- }
+ if (i + 1 < chunks.length) {
+ let nextChunk = chunks[i + 1];
- for (var j = chunk.events.length - 1; j >= 0; j--) {
- if (chunk.events[j].time === undefined) {
- chunk.events[j].time = nextTime;
- } else {
- // All events with missing timestamps should appear at the end of the chunk, so we're done
- break;
- }
- }
+ nextTime =
+ nextChunk.frames[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ];
+ } else {
+ //Otherwise we're at the end of the log so assume this event was logged sometime after the final frame
+ nextTime =
+ chunk.frames[chunk.frames.length - 1][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ];
+ }
- delete chunk.needsEventTimes;
- }
+ for (let j = chunk.events.length - 1; j >= 0; j--) {
+ if (chunk.events[j].time === undefined) {
+ chunk.events[j].time = nextTime;
+ } else {
+ // All events with missing timestamps should appear at the end of the chunk, so we're done
+ break;
+ }
}
+
+ delete chunk.needsEventTimes;
+ }
}
+ }
+ /*
+ * Double check that the indexes of each chunk in the array are in increasing order (bugcheck).
+ */
+ function verifyChunkIndexes(chunks) {
+ // Uncomment for debugging...
/*
- * Double check that the indexes of each chunk in the array are in increasing order (bugcheck).
- */
- function verifyChunkIndexes(chunks) {
- // Uncomment for debugging...
- /*
for (var i = 0; i < chunks.length - 1; i++) {
if (chunks[i].index + 1 != chunks[i+1].index) {
console.log("Bad chunk index, bug in chunk caching");
}
}*/
- }
+ }
+
+ /**
+ * Get an array of chunk data which has been smoothed by the previously-configured smoothing settings. The frames
+ * in the chunks will at least span the range given by [startTime...endTime].
+ */
+ this.getSmoothedChunksInTimeRange = function (startTime, endTime) {
+ let sourceChunks,
+ resultChunks,
+ chunkAlreadyDone,
+ allDone,
+ timeFieldIndex = FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME;
+
+ //if (maxSmoothing == 0) // TODO We can't bail early because we do things like add fields to the chunks at the end
+ // return this.getChunksInTimeRange(startTime, endTime);
+
+ let /*
+ * Ensure that the range that the caller asked for can be fully smoothed by expanding the request
+ * for source chunks on either side of the range asked for (to smooth the chunks on the edges, we
+ * need to be able to see their neighbors)
+ */
+ leadingROChunks = 1,
+ trailingROChunks = 1,
+ startIndex =
+ binarySearchOrPrevious(
+ iframeDirectory.times,
+ startTime - maxSmoothing
+ ) - leadingROChunks,
+ endIndex =
+ binarySearchOrNext(iframeDirectory.times, endTime + maxSmoothing) +
+ trailingROChunks;
- /**
- * Get an array of chunk data which has been smoothed by the previously-configured smoothing settings. The frames
- * in the chunks will at least span the range given by [startTime...endTime].
+ /*
+ * If our expanded source chunk range exceeds the actual source chunks available, trim down our leadingROChunks
+ * and trailingROChunks to match (i.e. we are allowed to smooth the first and last chunks of the file despite
+ * there not being a chunk past them to smooth against on one side).
*/
- this.getSmoothedChunksInTimeRange = function(startTime, endTime) {
- var
- sourceChunks,
- resultChunks,
- chunkAlreadyDone, allDone,
- timeFieldIndex = FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME;
+ if (startIndex < 0) {
+ leadingROChunks += startIndex;
+ startIndex = 0;
+ }
- //if (maxSmoothing == 0) // TODO We can't bail early because we do things like add fields to the chunks at the end
- // return this.getChunksInTimeRange(startTime, endTime);
+ if (endIndex > iframeDirectory.offsets.length - 1) {
+ trailingROChunks -= endIndex - (iframeDirectory.offsets.length - 1);
+ endIndex = iframeDirectory.offsets.length - 1;
+ }
- var
- /*
- * Ensure that the range that the caller asked for can be fully smoothed by expanding the request
- * for source chunks on either side of the range asked for (to smooth the chunks on the edges, we
- * need to be able to see their neighbors)
- */
- leadingROChunks = 1, trailingROChunks = 1,
-
- startIndex = binarySearchOrPrevious(iframeDirectory.times, startTime - maxSmoothing) - leadingROChunks,
- endIndex = binarySearchOrNext(iframeDirectory.times, endTime + maxSmoothing) + trailingROChunks;
-
- /*
- * If our expanded source chunk range exceeds the actual source chunks available, trim down our leadingROChunks
- * and trailingROChunks to match (i.e. we are allowed to smooth the first and last chunks of the file despite
- * there not being a chunk past them to smooth against on one side).
- */
- if (startIndex < 0) {
- leadingROChunks += startIndex;
- startIndex = 0;
- }
+ sourceChunks = getChunksInIndexRange(startIndex, endIndex);
- if (endIndex > iframeDirectory.offsets.length - 1) {
- trailingROChunks -= endIndex - (iframeDirectory.offsets.length - 1);
- endIndex = iframeDirectory.offsets.length - 1;
- }
+ verifyChunkIndexes(sourceChunks);
+
+ //Create an independent copy of the raw frame data to smooth out:
+ resultChunks = new Array(
+ sourceChunks.length - leadingROChunks - trailingROChunks
+ );
+ chunkAlreadyDone = new Array(sourceChunks.length);
+
+ allDone = true;
- sourceChunks = getChunksInIndexRange(startIndex, endIndex);
+ //Don't smooth the edge chunks since they can't be fully smoothed
+ for (
+ let i = leadingROChunks;
+ i < sourceChunks.length - trailingROChunks;
+ i++
+ ) {
+ let sourceChunk = sourceChunks[i],
+ resultChunk = smoothedCache.get(sourceChunk.index);
- verifyChunkIndexes(sourceChunks);
+ chunkAlreadyDone[i] = resultChunk ? true : false;
- //Create an independent copy of the raw frame data to smooth out:
- resultChunks = new Array(sourceChunks.length - leadingROChunks - trailingROChunks);
- chunkAlreadyDone = new Array(sourceChunks.length);
+ //If we haven't already smoothed this chunk
+ if (!chunkAlreadyDone[i]) {
+ allDone = false;
- allDone = true;
+ resultChunk = smoothedCache.recycle();
- //Don't smooth the edge chunks since they can't be fully smoothed
- for (var i = leadingROChunks; i < sourceChunks.length - trailingROChunks; i++) {
- var
- sourceChunk = sourceChunks[i],
- resultChunk = smoothedCache.get(sourceChunk.index);
+ if (resultChunk) {
+ //Reuse the memory from the expired chunk to reduce garbage
+ resultChunk.index = sourceChunk.index;
+ resultChunk.frames.length = sourceChunk.frames.length;
+ resultChunk.gapStartsHere = sourceChunk.gapStartsHere;
+ resultChunk.events = sourceChunk.events;
- chunkAlreadyDone[i] = resultChunk ? true : false;
+ //Copy frames onto the expired chunk:
+ for (var j = 0; j < resultChunk.frames.length; j++) {
+ if (resultChunk.frames[j]) {
+ //Copy on top of the recycled array:
+ resultChunk.frames[j].length = sourceChunk.frames[j].length;
- //If we haven't already smoothed this chunk
- if (!chunkAlreadyDone[i]) {
- allDone = false;
+ for (let k = 0; k < sourceChunk.frames[j].length; k++) {
+ resultChunk.frames[j][k] = sourceChunk.frames[j][k];
+ }
+ } else {
+ //Allocate a new copy of the raw array:
+ resultChunk.frames[j] = sourceChunk.frames[j].slice(0);
+ }
+ }
+ } else {
+ //Allocate a new chunk
+ resultChunk = {
+ index: sourceChunk.index,
+ frames: new Array(sourceChunk.frames.length),
+ gapStartsHere: sourceChunk.gapStartsHere,
+ events: sourceChunk.events,
+ };
+
+ for (var j = 0; j < resultChunk.frames.length; j++) {
+ resultChunk.frames[j] = sourceChunk.frames[j].slice(0);
+ }
+ }
- resultChunk = smoothedCache.recycle();
+ smoothedCache.add(resultChunk.index, resultChunk);
+ }
- if (resultChunk) {
- //Reuse the memory from the expired chunk to reduce garbage
- resultChunk.index = sourceChunk.index;
- resultChunk.frames.length = sourceChunk.frames.length;
- resultChunk.gapStartsHere = sourceChunk.gapStartsHere;
- resultChunk.events = sourceChunk.events;
+ resultChunks[i - leadingROChunks] = resultChunk;
+ }
- //Copy frames onto the expired chunk:
- for (var j = 0; j < resultChunk.frames.length; j++) {
- if (resultChunk.frames[j]) {
- //Copy on top of the recycled array:
- resultChunk.frames[j].length = sourceChunk.frames[j].length;
+ if (!allDone) {
+ for (let fieldIndex in fieldSmoothing) {
+ var radius = fieldSmoothing[fieldIndex],
+ //The position we're currently computing the smoothed value for:
+ centerChunkIndex,
+ centerFrameIndex;
+
+ //The outer two loops are used to begin a new partition to smooth within
+ // Don't bother to smooth the first and last source chunks, since we can't smooth them completely
+ mainLoop: for (
+ centerChunkIndex = leadingROChunks;
+ centerChunkIndex < sourceChunks.length - trailingROChunks;
+ centerChunkIndex++
+ ) {
+ if (chunkAlreadyDone[centerChunkIndex]) continue;
+
+ for (
+ centerFrameIndex = 0;
+ centerFrameIndex < sourceChunks[centerChunkIndex].frames.length;
+
+ ) {
+ var //Current beginning & end of the smoothing window:
+ leftChunkIndex = centerChunkIndex,
+ leftFrameIndex = centerFrameIndex,
+ rightChunkIndex,
+ rightFrameIndex,
+ /*
+ * The end of the current partition to be smoothed (exclusive, so the partition doesn't
+ * contain the value pointed to by chunks[endChunkIndex][endFrameIndex]).
+ *
+ * We'll refine this guess for the end of the partition later if we find discontinuities:
+ */
+ endChunkIndex = sourceChunks.length - 1 - trailingROChunks,
+ endFrameIndex = sourceChunks[endChunkIndex].frames.length,
+ partitionEnded = false,
+ accumulator = 0,
+ valuesInHistory = 0,
+ centerTime =
+ sourceChunks[centerChunkIndex].frames[centerFrameIndex][
+ timeFieldIndex
+ ];
- for (var k = 0; k < sourceChunk.frames[j].length; k++) {
- resultChunk.frames[j][k] = sourceChunk.frames[j][k];
- }
- } else {
- //Allocate a new copy of the raw array:
- resultChunk.frames[j] = sourceChunk.frames[j].slice(0);
- }
- }
- } else {
- //Allocate a new chunk
- resultChunk = {
- index: sourceChunk.index,
- frames: new Array(sourceChunk.frames.length),
- gapStartsHere: sourceChunk.gapStartsHere,
- events: sourceChunk.events
- };
-
- for (var j = 0; j < resultChunk.frames.length; j++) {
- resultChunk.frames[j] = sourceChunk.frames[j].slice(0);
- }
- }
+ /*
+ * This may not be the left edge of a partition, we may just have skipped the previous chunk due to
+ * it having already been cached. If so, we can read the values from the previous chunk in order
+ * to prime our history window. Move the left&right indexes to the left so the main loop will read
+ * those earlier values.
+ */
+ while (
+ leftFrameIndex > 0 ||
+ (leftFrameIndex === 0 && leftChunkIndex > 0)
+ ) {
+ let oldleftChunkIndex = leftChunkIndex,
+ oldleftFrameIndex = leftFrameIndex;
+
+ //Try moving it left
+ if (leftFrameIndex === 0) {
+ leftChunkIndex--;
+ leftFrameIndex = sourceChunks[leftChunkIndex].frames.length - 1;
+ } else {
+ leftFrameIndex--;
+ }
+
+ if (
+ sourceChunks[leftChunkIndex].gapStartsHere[leftFrameIndex] ||
+ sourceChunks[leftChunkIndex].frames[leftFrameIndex][
+ timeFieldIndex
+ ] <
+ centerTime - radius
+ ) {
+ //We moved the left index one step too far, shift it back
+ leftChunkIndex = oldleftChunkIndex;
+ leftFrameIndex = oldleftFrameIndex;
- smoothedCache.add(resultChunk.index, resultChunk);
+ break;
+ }
}
- resultChunks[i - leadingROChunks] = resultChunk;
- }
+ rightChunkIndex = leftChunkIndex;
+ rightFrameIndex = leftFrameIndex;
+
+ //The main loop, where we march our smoothing window along until we exhaust this partition
+ while (
+ centerChunkIndex < endChunkIndex ||
+ (centerChunkIndex == endChunkIndex &&
+ centerFrameIndex < endFrameIndex)
+ ) {
+ // Old values fall out of the window
+ while (
+ sourceChunks[leftChunkIndex].frames[leftFrameIndex][
+ timeFieldIndex
+ ] <
+ centerTime - radius
+ ) {
+ accumulator -=
+ sourceChunks[leftChunkIndex].frames[leftFrameIndex][
+ fieldIndex
+ ];
+ valuesInHistory--;
+
+ leftFrameIndex++;
+ if (
+ leftFrameIndex == sourceChunks[leftChunkIndex].frames.length
+ ) {
+ leftFrameIndex = 0;
+ leftChunkIndex++;
+ }
+ }
+
+ //New values are added to the window
+ while (
+ !partitionEnded &&
+ sourceChunks[rightChunkIndex].frames[rightFrameIndex][
+ timeFieldIndex
+ ] <=
+ centerTime + radius
+ ) {
+ accumulator +=
+ sourceChunks[rightChunkIndex].frames[rightFrameIndex][
+ fieldIndex
+ ];
+ valuesInHistory++;
+
+ //If there is a discontinuity after this point, stop trying to add further values
+ if (
+ sourceChunks[rightChunkIndex].gapStartsHere[rightFrameIndex]
+ ) {
+ partitionEnded = true;
+ }
- if (!allDone) {
- for (var fieldIndex in fieldSmoothing) {
- var
- radius = fieldSmoothing[fieldIndex],
-
- //The position we're currently computing the smoothed value for:
- centerChunkIndex, centerFrameIndex;
-
- //The outer two loops are used to begin a new partition to smooth within
- mainLoop:
-
- // Don't bother to smooth the first and last source chunks, since we can't smooth them completely
- for (centerChunkIndex = leadingROChunks; centerChunkIndex < sourceChunks.length - trailingROChunks; centerChunkIndex++) {
- if (chunkAlreadyDone[centerChunkIndex])
- continue;
-
- for (centerFrameIndex = 0; centerFrameIndex < sourceChunks[centerChunkIndex].frames.length; ) {
- var
- //Current beginning & end of the smoothing window:
- leftChunkIndex = centerChunkIndex,
- leftFrameIndex = centerFrameIndex,
-
- rightChunkIndex, rightFrameIndex,
-
- /*
- * The end of the current partition to be smoothed (exclusive, so the partition doesn't
- * contain the value pointed to by chunks[endChunkIndex][endFrameIndex]).
- *
- * We'll refine this guess for the end of the partition later if we find discontinuities:
- */
- endChunkIndex = sourceChunks.length - 1 - trailingROChunks,
- endFrameIndex = sourceChunks[endChunkIndex].frames.length,
-
- partitionEnded = false,
- accumulator = 0,
- valuesInHistory = 0,
-
- centerTime = sourceChunks[centerChunkIndex].frames[centerFrameIndex][timeFieldIndex];
-
- /*
- * This may not be the left edge of a partition, we may just have skipped the previous chunk due to
- * it having already been cached. If so, we can read the values from the previous chunk in order
- * to prime our history window. Move the left&right indexes to the left so the main loop will read
- * those earlier values.
- */
- while (leftFrameIndex > 0 || leftFrameIndex === 0 && leftChunkIndex > 0) {
- var
- oldleftChunkIndex = leftChunkIndex,
- oldleftFrameIndex = leftFrameIndex;
-
- //Try moving it left
- if (leftFrameIndex === 0) {
- leftChunkIndex--;
- leftFrameIndex = sourceChunks[leftChunkIndex].frames.length - 1;
- } else {
- leftFrameIndex--;
- }
-
- if (sourceChunks[leftChunkIndex].gapStartsHere[leftFrameIndex] || sourceChunks[leftChunkIndex].frames[leftFrameIndex][timeFieldIndex] < centerTime - radius) {
- //We moved the left index one step too far, shift it back
- leftChunkIndex = oldleftChunkIndex;
- leftFrameIndex = oldleftFrameIndex;
-
- break;
- }
- }
-
- rightChunkIndex = leftChunkIndex;
- rightFrameIndex = leftFrameIndex;
-
- //The main loop, where we march our smoothing window along until we exhaust this partition
- while (centerChunkIndex < endChunkIndex || centerChunkIndex == endChunkIndex && centerFrameIndex < endFrameIndex) {
- // Old values fall out of the window
- while (sourceChunks[leftChunkIndex].frames[leftFrameIndex][timeFieldIndex] < centerTime - radius) {
- accumulator -= sourceChunks[leftChunkIndex].frames[leftFrameIndex][fieldIndex];
- valuesInHistory--;
-
- leftFrameIndex++;
- if (leftFrameIndex == sourceChunks[leftChunkIndex].frames.length) {
- leftFrameIndex = 0;
- leftChunkIndex++;
- }
- }
-
- //New values are added to the window
- while (!partitionEnded && sourceChunks[rightChunkIndex].frames[rightFrameIndex][timeFieldIndex] <= centerTime + radius) {
- accumulator += sourceChunks[rightChunkIndex].frames[rightFrameIndex][fieldIndex];
- valuesInHistory++;
-
- //If there is a discontinuity after this point, stop trying to add further values
- if (sourceChunks[rightChunkIndex].gapStartsHere[rightFrameIndex]) {
- partitionEnded = true;
- }
-
- //Advance the right index onward since we read a value
- rightFrameIndex++;
- if (rightFrameIndex == sourceChunks[rightChunkIndex].frames.length) {
- rightFrameIndex = 0;
- rightChunkIndex++;
-
- if (rightChunkIndex == sourceChunks.length) {
- //We reached the end of the region of interest!
- partitionEnded = true;
- }
- }
-
- if (partitionEnded) {
- //Let the center-storing loop know not to advance the center to this position:
- endChunkIndex = rightChunkIndex;
- endFrameIndex = rightFrameIndex;
- }
- }
-
- // Store the average of the history window into the frame in the center of the window
- resultChunks[centerChunkIndex - leadingROChunks].frames[centerFrameIndex][fieldIndex] = Math.round(accumulator / valuesInHistory);
-
- // Advance the center so we can start computing the next value
- centerFrameIndex++;
- if (centerFrameIndex == sourceChunks[centerChunkIndex].frames.length) {
- centerFrameIndex = 0;
- centerChunkIndex++;
-
- //Is the next chunk already cached? Then we have nothing to write into there
- if (chunkAlreadyDone[centerChunkIndex])
- continue mainLoop;
-
- //Have we covered the whole ROI?
- if (centerChunkIndex == sourceChunks.length - trailingROChunks)
- break mainLoop;
- }
-
- centerTime = sourceChunks[centerChunkIndex].frames[centerFrameIndex][timeFieldIndex];
- }
- }
+ //Advance the right index onward since we read a value
+ rightFrameIndex++;
+ if (
+ rightFrameIndex == sourceChunks[rightChunkIndex].frames.length
+ ) {
+ rightFrameIndex = 0;
+ rightChunkIndex++;
+
+ if (rightChunkIndex == sourceChunks.length) {
+ //We reached the end of the region of interest!
+ partitionEnded = true;
+ }
}
+
+ if (partitionEnded) {
+ //Let the center-storing loop know not to advance the center to this position:
+ endChunkIndex = rightChunkIndex;
+ endFrameIndex = rightFrameIndex;
+ }
+ }
+
+ // Store the average of the history window into the frame in the center of the window
+ resultChunks[centerChunkIndex - leadingROChunks].frames[
+ centerFrameIndex
+ ][fieldIndex] = Math.round(accumulator / valuesInHistory);
+
+ // Advance the center so we can start computing the next value
+ centerFrameIndex++;
+ if (
+ centerFrameIndex == sourceChunks[centerChunkIndex].frames.length
+ ) {
+ centerFrameIndex = 0;
+ centerChunkIndex++;
+
+ //Is the next chunk already cached? Then we have nothing to write into there
+ if (chunkAlreadyDone[centerChunkIndex]) continue mainLoop;
+
+ //Have we covered the whole ROI?
+ if (centerChunkIndex == sourceChunks.length - trailingROChunks)
+ break mainLoop;
+ }
+
+ centerTime =
+ sourceChunks[centerChunkIndex].frames[centerFrameIndex][
+ timeFieldIndex
+ ];
}
+ }
}
+ }
+ }
- addMissingEventTimes(sourceChunks, trailingROChunks === 0);
+ addMissingEventTimes(sourceChunks, trailingROChunks === 0);
- verifyChunkIndexes(sourceChunks);
- verifyChunkIndexes(resultChunks);
+ verifyChunkIndexes(sourceChunks);
+ verifyChunkIndexes(resultChunks);
- return resultChunks;
- };
+ return resultChunks;
+ };
- /**
- * Attempt to open the log with the given index, returning true on success.
- */
- this.openLog = function(index) {
- if (this.getLogError(index)) {
- return false;
- }
+ /**
+ * Attempt to open the log with the given index, returning true on success.
+ */
+ this.openLog = function (index) {
+ if (this.getLogError(index)) {
+ return false;
+ }
- logIndex = index;
+ logIndex = index;
- chunkCache.clear();
- smoothedCache.clear();
+ chunkCache.clear();
+ smoothedCache.clear();
- iframeDirectory = logIndexes.getIntraframeDirectory(index);
+ iframeDirectory = logIndexes.getIntraframeDirectory(index);
- parser.parseHeader(logIndexes.getLogBeginOffset(index), logIndexes.getLogBeginOffset(index + 1));
+ parser.parseHeader(
+ logIndexes.getLogBeginOffset(index),
+ logIndexes.getLogBeginOffset(index + 1)
+ );
- // Hide the header button if we are not using betaflight
- switch (this.getSysConfig().firmwareType) {
- case FIRMWARE_TYPE_BETAFLIGHT:
- case FIRMWARE_TYPE_INAV:
- $(".open-header-dialog").show()
- break;
+ // Hide the header button if we are not using betaflight
+ switch (this.getSysConfig().firmwareType) {
+ case FIRMWARE_TYPE_BETAFLIGHT:
+ case FIRMWARE_TYPE_INAV:
+ $(".open-header-dialog").show();
+ break;
- default:
- $(".open-header-dialog").hide()
- break;
- }
+ default:
+ $(".open-header-dialog").hide();
+ break;
+ }
- buildFieldNames();
+ buildFieldNames();
- estimateNumMotors();
- estimateNumCells();
+ estimateNumMotors();
+ estimateNumCells();
- return true;
- };
+ return true;
+ };
+
+ this.hasGpsData = function () {
+ return this.getStats()?.frame?.G ? true : false;
+ };
+
+ this.getMinMaxForFieldDuringAllTime = function (field_name) {
+ let stats = this.getStats(),
+ min = Number.MAX_VALUE,
+ max = -Number.MAX_VALUE;
+
+ let fieldIndex = this.getMainFieldIndexByName(field_name),
+ fieldStat = fieldIndex !== undefined ? stats.field[fieldIndex] : false;
+
+ if (fieldStat) {
+ min = Math.min(min, fieldStat.min);
+ max = Math.max(max, fieldStat.max);
+ } else {
+ const mm = this.getMinMaxForFieldDuringTimeInterval(
+ field_name,
+ this.getMinTime(),
+ this.getMaxTime()
+ );
+ if (mm !== undefined) {
+ min = Math.min(mm.min, min);
+ max = Math.max(mm.max, max);
+ }
+ }
+
+ return { min: min, max: max };
+ };
+
+ /**
+ * Function to compute of min and max curve values during time interval.
+ * @param field_name String: Curve fields name.
+ * @param start_time Integer: The interval start time .
+ * @end_time start_time Integer: The interval end time .
+ * @returns {min: MinValue, max: MaxValue} if success, or {min: Number.MAX_VALUE, max: Number.MAX_VALUE} if error
+ */
+ this.getMinMaxForFieldDuringTimeInterval = function (
+ field_name,
+ start_time,
+ end_time
+ ) {
+ let chunks = this.getSmoothedChunksInTimeRange(start_time, end_time);
+ let startFrameIndex;
+ let minValue = Number.MAX_VALUE,
+ maxValue = -Number.MAX_VALUE;
+
+ const fieldIndex = this.getMainFieldIndexByName(field_name);
+ if (chunks.length == 0 || fieldIndex == undefined) return undefined;
+
+ //Find the first sample that lies inside the window
+ for (
+ startFrameIndex = 0;
+ startFrameIndex < chunks[0].frames.length;
+ startFrameIndex++
+ ) {
+ if (
+ chunks[0].frames[startFrameIndex][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ] >= start_time
+ ) {
+ break;
+ }
+ }
- this.hasGpsData = function() {
- return this.getStats()?.frame?.G ? true : false;;
+ // Pick the sample before that to begin plotting from
+ if (startFrameIndex > 0) startFrameIndex--;
+
+ let frameIndex = startFrameIndex;
+ findingLoop: for (
+ let chunkIndex = 0;
+ chunkIndex < chunks.length;
+ chunkIndex++
+ ) {
+ const chunk = chunks[chunkIndex];
+ for (; frameIndex < chunk.frames.length; frameIndex++) {
+ const fieldValue = chunk.frames[frameIndex][fieldIndex];
+ const frameTime =
+ chunk.frames[frameIndex][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ];
+ minValue = Math.min(minValue, fieldValue);
+ maxValue = Math.max(maxValue, fieldValue);
+ if (frameTime > end_time) break findingLoop;
+ }
+ frameIndex = 0;
+ }
+ return {
+ min: minValue,
+ max: maxValue,
};
+ };
+
+ this.getCurrentLogRowsCount = function () {
+ const stats = this.getStats(this.getLogIndex());
+ return stats.frame["I"].validCount + stats.frame["P"].validCount;
+ };
}
-FlightLog.prototype.accRawToGs = function(value) {
- return value / this.getSysConfig().acc_1G;
+FlightLog.prototype.accRawToGs = function (value) {
+ return value / this.getSysConfig().acc_1G;
};
-FlightLog.prototype.gyroRawToDegreesPerSecond = function(value) {
- return this.getSysConfig().gyroScale * 1000000 / (Math.PI / 180.0) * value;
+FlightLog.prototype.gyroRawToDegreesPerSecond = function (value) {
+ return (
+ ((this.getSysConfig().gyroScale * 1000000) / (Math.PI / 180.0)) * value
+ );
};
-
-
/***
The rcCommandToDegreesPerSecond function is betaflight version specific
@@ -1083,281 +1412,369 @@ FlightLog.prototype.gyroRawToDegreesPerSecond = function(value) {
**/
// Convert rcCommand to degrees per second
-FlightLog.prototype.rcCommandRawToDegreesPerSecond = function(value, axis, currentFlightMode) {
-
- var sysConfig = this.getSysConfig();
-
- if(firmwareGreaterOrEqual(sysConfig, '3.0.0', '2.0.0')) {
-
- const RC_RATE_INCREMENTAL = 14.54;
- const RC_EXPO_POWER = 3;
-
- var rcInput;
- var that = this;
-
- var calculateSetpointRate = function(axis, rc) {
-
- var rcCommandf = rc / 500.0;
- var rcCommandfAbs = Math.abs(rcCommandf);
-
- if (sysConfig["rc_expo"][axis]) {
- var expof = sysConfig["rc_expo"][axis] / 100;
- rcCommandf = rcCommandf * Math.pow(rcCommandfAbs, RC_EXPO_POWER) * expof + rcCommandf * (1-expof);
- }
-
- var rcRate = sysConfig["rc_rates"][axis] / 100.0;
- if (rcRate > 2.0) {
- rcRate += RC_RATE_INCREMENTAL * (rcRate - 2.0);
- }
-
- var angleRate = 200.0 * rcRate * rcCommandf;
- if (sysConfig.rates[axis]) {
- var rcSuperfactor = 1.0 / (constrain(1.0 - (rcCommandfAbs * (sysConfig.rates[axis] / 100.0)), 0.01, 1.00));
- angleRate *= rcSuperfactor;
- }
-
- /*
+FlightLog.prototype.rcCommandRawToDegreesPerSecond = function (
+ value,
+ axis,
+ currentFlightMode
+) {
+ let sysConfig = this.getSysConfig();
+
+ if (firmwareGreaterOrEqual(sysConfig, "3.0.0", "2.0.0")) {
+ const RC_RATE_INCREMENTAL = 14.54;
+ const RC_EXPO_POWER = 3;
+
+ let rcInput;
+ var that = this;
+
+ let calculateSetpointRate = function (axis, rc) {
+ let rcCommandf = rc / 500.0;
+ let rcCommandfAbs = Math.abs(rcCommandf);
+
+ if (sysConfig["rc_expo"][axis]) {
+ let expof = sysConfig["rc_expo"][axis] / 100;
+ rcCommandf =
+ rcCommandf * Math.pow(rcCommandfAbs, RC_EXPO_POWER) * expof +
+ rcCommandf * (1 - expof);
+ }
+
+ let rcRate = sysConfig["rc_rates"][axis] / 100.0;
+ if (rcRate > 2.0) {
+ rcRate += RC_RATE_INCREMENTAL * (rcRate - 2.0);
+ }
+
+ let angleRate = 200.0 * rcRate * rcCommandf;
+ if (sysConfig.rates[axis]) {
+ let rcSuperfactor =
+ 1.0 /
+ constrain(
+ 1.0 - rcCommandfAbs * (sysConfig.rates[axis] / 100.0),
+ 0.01,
+ 1.0
+ );
+ angleRate *= rcSuperfactor;
+ }
+
+ /*
if (debugMode == DEBUG_ANGLERATE) {
debug[axis] = angleRate;
}
*/
- var limit = sysConfig["rate_limits"][axis];
- if (sysConfig.pidController == 0 || limit == null) { /* LEGACY */
- return constrain(angleRate * 4.1, -8190.0, 8190.0) >> 2; // Rate limit protection
- } else {
- return constrain(angleRate, -1.0 * limit, limit); // Rate limit protection (deg/sec)
- }
- };
-
- return calculateSetpointRate(axis, value);
-
- }
- else if(firmwareGreaterOrEqual(sysConfig, '2.8.0')) {
-
- var that = this;
-
- var isSuperExpoActive = function() {
- var FEATURE_SUPEREXPO_RATES = 1 << 23;
-
- return (sysConfig.features & FEATURE_SUPEREXPO_RATES);
- }
-
- var calculateRate = function(value, axis) {
- var angleRate;
-
- if (isSuperExpoActive()) {
- var rcFactor = (axis === AXIS.YAW) ? (Math.abs(value) / (500.0 * (validate(sysConfig.rc_rates[2],100) / 100.0))) : (Math.abs(value) / (500.0 * (validate(sysConfig.rc_rates[0],100) / 100.0)));
- rcFactor = 1.0 / (constrain(1.0 - (rcFactor * (validate(sysConfig.rates[axis],100) / 100.0)), 0.01, 1.00));
-
- angleRate = rcFactor * ((27 * value) / 16.0);
- } else {
- angleRate = ((validate(sysConfig.rates[axis],100) + 27) * value) / 16.0;
- }
-
- return constrain(angleRate, -8190.0, 8190.0); // Rate limit protection
- };
+ let limit = sysConfig["rate_limits"][axis];
+ if (sysConfig.pidController == 0 || limit == null) {
+ /* LEGACY */
+ return constrain(angleRate * 4.1, -8190.0, 8190.0) >> 2; // Rate limit protection
+ } else {
+ return constrain(angleRate, -1.0 * limit, limit); // Rate limit protection (deg/sec)
+ }
+ };
- return calculateRate(value, axis) >> 2; // the shift by 2 is to counterbalance the divide by 4 that occurs on the gyro to calculate the error
+ return calculateSetpointRate(axis, value);
+ } else if (firmwareGreaterOrEqual(sysConfig, "2.8.0")) {
+ var that = this;
- } else { // earlier version of betaflight
+ let isSuperExpoActive = function () {
+ let FEATURE_SUPEREXPO_RATES = 1 << 23;
- var that = this;
+ return sysConfig.features & FEATURE_SUPEREXPO_RATES;
+ };
- var calculateExpoPlus = function(value, axis) {
- var propFactor;
- var superExpoFactor;
+ let calculateRate = function (value, axis) {
+ let angleRate;
+
+ if (isSuperExpoActive()) {
+ let rcFactor =
+ axis === AXIS.YAW
+ ? Math.abs(value) /
+ (500.0 * (validate(sysConfig.rc_rates[2], 100) / 100.0))
+ : Math.abs(value) /
+ (500.0 * (validate(sysConfig.rc_rates[0], 100) / 100.0));
+ rcFactor =
+ 1.0 /
+ constrain(
+ 1.0 - rcFactor * (validate(sysConfig.rates[axis], 100) / 100.0),
+ 0.01,
+ 1.0
+ );
+
+ angleRate = rcFactor * ((27 * value) / 16.0);
+ } else {
+ angleRate =
+ ((validate(sysConfig.rates[axis], 100) + 27) * value) / 16.0;
+ }
+
+ return constrain(angleRate, -8190.0, 8190.0); // Rate limit protection
+ };
- if (axis == AXIS.YAW && !that.getSysConfig().superExpoYawMode) {
- propFactor = 1.0;
- } else {
- superExpoFactor = (axis == AXIS.YAW) ? that.getSysConfig().superExpoFactorYaw : that.getSysConfig().superExpoFactor;
- propFactor = 1.0 - ((superExpoFactor / 100.0) * (Math.abs(value) / 500.0));
- }
+ return calculateRate(value, axis) >> 2; // the shift by 2 is to counterbalance the divide by 4 that occurs on the gyro to calculate the error
+ } else {
+ // earlier version of betaflight
- return propFactor;
- }
+ var that = this;
- var superExpoFactor = 1.0/calculateExpoPlus(value, axis);
+ let calculateExpoPlus = function (value, axis) {
+ let propFactor;
+ let superExpoFactor;
+ if (axis == AXIS.YAW && !that.getSysConfig().superExpoYawMode) {
+ propFactor = 1.0;
+ } else {
+ superExpoFactor =
+ axis == AXIS.YAW
+ ? that.getSysConfig().superExpoFactorYaw
+ : that.getSysConfig().superExpoFactor;
+ propFactor =
+ 1.0 - (superExpoFactor / 100.0) * (Math.abs(value) / 500.0);
+ }
- if(axis===AXIS.YAW /*YAW*/) {
- if(sysConfig.superExpoYawMode==SUPER_EXPO_YAW.ON && currentFlightMode==null) superExpoFactor = 1.0; // If we don't know the flight mode, then reset the super expo mode.
- if((sysConfig.superExpoYawMode==SUPER_EXPO_YAW.ALWAYS)||(sysConfig.superExpoYawMode==SUPER_EXPO_YAW.ON && this.getFlightMode(currentFlightMode).SuperExpo)) {
- return superExpoFactor * ((sysConfig.rates[AXIS.YAW] + 47) * value ) >> 7;
- } else {
- return ((sysConfig.rates[AXIS.YAW] + 47) * value ) >> 7;
- }
+ return propFactor;
+ };
- } else { /*ROLL or PITCH */
- if(currentFlightMode==null) superExpoFactor = 1.0; // If we don't know the flight mode, then reset the super expo mode.
- return superExpoFactor * ((((axis===AXIS.ROLL)?sysConfig.rates[AXIS.ROLL]:sysConfig.rates[AXIS.PITCH]) + 27) * value ) >> 6;
- }
+ let superExpoFactor = 1.0 / calculateExpoPlus(value, axis);
+
+ if (axis === AXIS.YAW /*YAW*/) {
+ if (
+ sysConfig.superExpoYawMode == SUPER_EXPO_YAW.ON &&
+ currentFlightMode == null
+ )
+ superExpoFactor = 1.0; // If we don't know the flight mode, then reset the super expo mode.
+ if (
+ sysConfig.superExpoYawMode == SUPER_EXPO_YAW.ALWAYS ||
+ (sysConfig.superExpoYawMode == SUPER_EXPO_YAW.ON &&
+ this.getFlightMode(currentFlightMode).SuperExpo)
+ ) {
+ return (
+ (superExpoFactor * ((sysConfig.rates[AXIS.YAW] + 47) * value)) >> 7
+ );
+ } else {
+ return ((sysConfig.rates[AXIS.YAW] + 47) * value) >> 7;
+ }
+ } else {
+ /*ROLL or PITCH */
+ if (currentFlightMode == null) superExpoFactor = 1.0; // If we don't know the flight mode, then reset the super expo mode.
+ return (
+ (superExpoFactor *
+ (((axis === AXIS.ROLL
+ ? sysConfig.rates[AXIS.ROLL]
+ : sysConfig.rates[AXIS.PITCH]) +
+ 27) *
+ value)) >>
+ 6
+ );
}
+ }
};
-FlightLog.prototype.rcCommandRawToThrottle = function(value) {
- // Throttle displayed as percentage
- return Math.min(Math.max(((value - this.getSysConfig().minthrottle) / (this.getSysConfig().maxthrottle - this.getSysConfig().minthrottle)) * 100.0, 0.0),100.0);
+FlightLog.prototype.rcCommandRawToThrottle = function (value) {
+ // Throttle displayed as percentage
+ return Math.min(
+ Math.max(
+ ((value - this.getSysConfig().minthrottle) /
+ (this.getSysConfig().maxthrottle - this.getSysConfig().minthrottle)) *
+ 100.0,
+ 0.0
+ ),
+ 100.0
+ );
};
-FlightLog.prototype.rcMotorRawToPctPhysical = function(value) {
-
- // Motor displayed as percentage
- let motorPct;
- if (this.isDigitalProtocol()) {
- motorPct = ((value - DSHOT_MIN_VALUE) / DSHOT_RANGE) * 100;
- } else {
- const MAX_ANALOG_VALUE = this.getSysConfig().maxthrottle;
- const MIN_ANALOG_VALUE = this.getSysConfig().minthrottle;
- const ANALOG_RANGE = MAX_ANALOG_VALUE - MIN_ANALOG_VALUE;
- motorPct = ((value - MIN_ANALOG_VALUE) / ANALOG_RANGE) * 100;
- }
- return Math.min(Math.max(motorPct, 0.0), 100.0);
+// rcCommandThrottle back transform function
+FlightLog.prototype.ThrottleTorcCommandRaw = function (value) {
+ // Throttle displayed as percentage
+ return (
+ (value / 100) *
+ (this.getSysConfig().maxthrottle - this.getSysConfig().minthrottle) +
+ this.getSysConfig().minthrottle
+ );
+};
+FlightLog.prototype.rcMotorRawToPctPhysical = function (value) {
+ // Motor displayed as percentage
+ let motorPct;
+ if (this.isDigitalProtocol()) {
+ motorPct = ((value - DSHOT_MIN_VALUE) / DSHOT_RANGE) * 100;
+ } else {
+ const MAX_ANALOG_VALUE = this.getSysConfig().maxthrottle;
+ const MIN_ANALOG_VALUE = this.getSysConfig().minthrottle;
+ const ANALOG_RANGE = MAX_ANALOG_VALUE - MIN_ANALOG_VALUE;
+ motorPct = ((value - MIN_ANALOG_VALUE) / ANALOG_RANGE) * 100;
+ }
+ return Math.min(Math.max(motorPct, 0.0), 100.0);
+};
+// rcMotorRaw back transform function
+FlightLog.prototype.PctPhysicalTorcMotorRaw = function (value) {
+ // Motor displayed as percentage
+ let motorRaw;
+ if (this.isDigitalProtocol()) {
+ motorRaw = (value / 100) * DSHOT_RANGE + DSHOT_MIN_VALUE;
+ } else {
+ const MAX_ANALOG_VALUE = this.getSysConfig().maxthrottle;
+ const MIN_ANALOG_VALUE = this.getSysConfig().minthrottle;
+ const ANALOG_RANGE = MAX_ANALOG_VALUE - MIN_ANALOG_VALUE;
+ motorRaw = (value / 100) * ANALOG_RANGE + MIN_ANALOG_VALUE;
+ }
+ return motorRaw;
};
-FlightLog.prototype.isDigitalProtocol = function() {
- let digitalProtocol;
- switch(FAST_PROTOCOL[this.getSysConfig().fast_pwm_protocol]) {
+FlightLog.prototype.isDigitalProtocol = function () {
+ let digitalProtocol;
+ switch (FAST_PROTOCOL[this.getSysConfig().fast_pwm_protocol]) {
case "PWM":
case "ONESHOT125":
case "ONESHOT42":
case "MULTISHOT":
case "BRUSHED":
- digitalProtocol = false;
- break;
+ digitalProtocol = false;
+ break;
case "DSHOT150":
case "DSHOT300":
case "DSHOT600":
case "DSHOT1200":
case "PROSHOT1000":
default:
- digitalProtocol = true;
- break;
- }
- return digitalProtocol;
+ digitalProtocol = true;
+ break;
+ }
+ return digitalProtocol;
};
-FlightLog.prototype.getPIDPercentage = function(value) {
- // PID components and outputs are displayed as percentage (raw value is 0-1000)
- return (value / 10.0);
+FlightLog.prototype.getPIDPercentage = function (value) {
+ // PID components and outputs are displayed as percentage (raw value is 0-1000)
+ return value / 10.0;
};
-
-FlightLog.prototype.getReferenceVoltageMillivolts = function() {
- if(this.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(this.getSysConfig().firmwareVersion, '4.0.0')) {
- return this.getSysConfig().vbatref * 10;
- } else if((this.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(this.getSysConfig().firmwareVersion, '3.1.0')) ||
- (this.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(this.getSysConfig().firmwareVersion, '2.0.0'))) {
- return this.getSysConfig().vbatref * 100;
- } else {
- return this.vbatADCToMillivolts(this.getSysConfig().vbatref);
- }
-
+FlightLog.prototype.getReferenceVoltageMillivolts = function () {
+ if (
+ this.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(this.getSysConfig().firmwareVersion, "4.0.0")
+ ) {
+ return this.getSysConfig().vbatref * 10;
+ } else if (
+ (this.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(this.getSysConfig().firmwareVersion, "3.1.0")) ||
+ (this.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(this.getSysConfig().firmwareVersion, "2.0.0"))
+ ) {
+ return this.getSysConfig().vbatref * 100;
+ } else {
+ return this.vbatADCToMillivolts(this.getSysConfig().vbatref);
+ }
};
-FlightLog.prototype.vbatADCToMillivolts = function(vbatADC) {
- var
- ADCVREF = 33;
+FlightLog.prototype.vbatADCToMillivolts = function (vbatADC) {
+ let ADCVREF = 33;
- // ADC is 12 bit (i.e. max 0xFFF), voltage reference is 3.3V, vbatscale is premultiplied by 100
- return (vbatADC * ADCVREF * 10 * this.getSysConfig().vbatscale) / 0xFFF;
+ // ADC is 12 bit (i.e. max 0xFFF), voltage reference is 3.3V, vbatscale is premultiplied by 100
+ return (vbatADC * ADCVREF * 10 * this.getSysConfig().vbatscale) / 0xfff;
};
-FlightLog.prototype.amperageADCToMillivolts = function(amperageADC) {
- var
- ADCVREF = 33,
- millivolts = (amperageADC * ADCVREF * 100) / 4095;
+FlightLog.prototype.amperageADCToMillivolts = function (amperageADC) {
+ let ADCVREF = 33,
+ millivolts = (amperageADC * ADCVREF * 100) / 4095;
- millivolts -= this.getSysConfig().currentMeterOffset;
+ millivolts -= this.getSysConfig().currentMeterOffset;
- return millivolts * 10000 / this.getSysConfig().currentMeterScale;
+ return (millivolts * 10000) / this.getSysConfig().currentMeterScale;
};
-
-FlightLog.prototype.getFlightMode = function(currentFlightMode) {
- return {
- Arm: (currentFlightMode & (1<<0))!=0,
- Angle: (currentFlightMode & (1<<1))!=0,
- Horizon: (currentFlightMode & (1<<2))!=0,
- Baro: (currentFlightMode & (1<<3))!=0,
- AntiGravity: (currentFlightMode & (1<<4))!=0,
- Headfree: (currentFlightMode & (1<<5))!=0,
- HeadAdj: (currentFlightMode & (1<<6))!=0,
- CamStab: (currentFlightMode & (1<<7))!=0,
- CamTrig: (currentFlightMode & (1<<8))!=0,
- GPSHome: (currentFlightMode & (1<<9))!=0,
- GPSHold: (currentFlightMode & (1<<10))!=0,
- Passthrough: (currentFlightMode & (1<<11))!=0,
- Beeper: (currentFlightMode & (1<<12))!=0,
- LEDMax: (currentFlightMode & (1<<13))!=0,
- LEDLow: (currentFlightMode & (1<<14))!=0,
- LLights: (currentFlightMode & (1<<15))!=0,
- Calib: (currentFlightMode & (1<<16))!=0,
- GOV: (currentFlightMode & (1<<17))!=0,
- OSD: (currentFlightMode & (1<<18))!=0,
- Telemetry: (currentFlightMode & (1<<19))!=0,
- GTune: (currentFlightMode & (1<<20))!=0,
- Sonar: (currentFlightMode & (1<<21))!=0,
- Servo1: (currentFlightMode & (1<<22))!=0,
- Servo2: (currentFlightMode & (1<<23))!=0,
- Servo3: (currentFlightMode & (1<<24))!=0,
- Blackbox: (currentFlightMode & (1<<25))!=0,
- Failsafe: (currentFlightMode & (1<<26))!=0,
- Airmode: (currentFlightMode & (1<<27))!=0,
- SuperExpo: (currentFlightMode & (1<<28))!=0,
- _3DDisableSwitch: (currentFlightMode & (1<<29))!=0,
- CheckboxItemCount: (currentFlightMode & (1<<30))!=0,
- };
+FlightLog.prototype.getFlightMode = function (currentFlightMode) {
+ return {
+ Arm: (currentFlightMode & (1 << 0)) != 0,
+ Angle: (currentFlightMode & (1 << 1)) != 0,
+ Horizon: (currentFlightMode & (1 << 2)) != 0,
+ Baro: (currentFlightMode & (1 << 3)) != 0,
+ AntiGravity: (currentFlightMode & (1 << 4)) != 0,
+ Headfree: (currentFlightMode & (1 << 5)) != 0,
+ HeadAdj: (currentFlightMode & (1 << 6)) != 0,
+ CamStab: (currentFlightMode & (1 << 7)) != 0,
+ CamTrig: (currentFlightMode & (1 << 8)) != 0,
+ GPSHome: (currentFlightMode & (1 << 9)) != 0,
+ GPSHold: (currentFlightMode & (1 << 10)) != 0,
+ Passthrough: (currentFlightMode & (1 << 11)) != 0,
+ Beeper: (currentFlightMode & (1 << 12)) != 0,
+ LEDMax: (currentFlightMode & (1 << 13)) != 0,
+ LEDLow: (currentFlightMode & (1 << 14)) != 0,
+ LLights: (currentFlightMode & (1 << 15)) != 0,
+ Calib: (currentFlightMode & (1 << 16)) != 0,
+ GOV: (currentFlightMode & (1 << 17)) != 0,
+ OSD: (currentFlightMode & (1 << 18)) != 0,
+ Telemetry: (currentFlightMode & (1 << 19)) != 0,
+ GTune: (currentFlightMode & (1 << 20)) != 0,
+ Sonar: (currentFlightMode & (1 << 21)) != 0,
+ Servo1: (currentFlightMode & (1 << 22)) != 0,
+ Servo2: (currentFlightMode & (1 << 23)) != 0,
+ Servo3: (currentFlightMode & (1 << 24)) != 0,
+ Blackbox: (currentFlightMode & (1 << 25)) != 0,
+ Failsafe: (currentFlightMode & (1 << 26)) != 0,
+ Airmode: (currentFlightMode & (1 << 27)) != 0,
+ SuperExpo: (currentFlightMode & (1 << 28)) != 0,
+ _3DDisableSwitch: (currentFlightMode & (1 << 29)) != 0,
+ CheckboxItemCount: (currentFlightMode & (1 << 30)) != 0,
+ };
};
-FlightLog.prototype.getFeatures = function(enabledFeatures) {
- return {
- RX_PPM : (enabledFeatures & (1 << 0))!=0,
- VBAT : (enabledFeatures & (1 << 1))!=0,
- INFLIGHT_ACC_CAL : (enabledFeatures & (1 << 2))!=0,
- RX_SERIAL : (enabledFeatures & (1 << 3))!=0,
- MOTOR_STOP : (enabledFeatures & (1 << 4))!=0,
- SERVO_TILT : (enabledFeatures & (1 << 5))!=0,
- SOFTSERIAL : (enabledFeatures & (1 << 6))!=0,
- GPS : (enabledFeatures & (1 << 7))!=0,
- FAILSAFE : (enabledFeatures & (1 << 8))!=0,
- SONAR : (enabledFeatures & (1 << 9))!=0,
- TELEMETRY : (enabledFeatures & (1 << 10))!=0,
- CURRENT_METER : (enabledFeatures & (1 << 11))!=0,
- _3D : (enabledFeatures & (1 << 12))!=0,
- RX_PARALLEL_PWM : (enabledFeatures & (1 << 13))!=0,
- RX_MSP : (enabledFeatures & (1 << 14))!=0,
- RSSI_ADC : (enabledFeatures & (1 << 15))!=0,
- LED_STRIP : (enabledFeatures & (1 << 16))!=0,
- DISPLAY : (enabledFeatures & (1 << 17))!=0,
- ONESHOT125 : (enabledFeatures & (1 << 18))!=0,
- BLACKBOX : (enabledFeatures & (1 << 19))!=0,
- CHANNEL_FORWARDING : (enabledFeatures & (1 << 20))!=0,
- TRANSPONDER : (enabledFeatures & (1 << 21))!=0,
- AIRMODE : (enabledFeatures & (1 << 22))!=0,
- SUPEREXPO_RATES : (enabledFeatures & (1 << 23))!=0,
- ANTI_GRAVITY : (enabledFeatures & (1 << 24))!=0,
- };
+FlightLog.prototype.getFeatures = function (enabledFeatures) {
+ return {
+ RX_PPM: (enabledFeatures & (1 << 0)) != 0,
+ VBAT: (enabledFeatures & (1 << 1)) != 0,
+ INFLIGHT_ACC_CAL: (enabledFeatures & (1 << 2)) != 0,
+ RX_SERIAL: (enabledFeatures & (1 << 3)) != 0,
+ MOTOR_STOP: (enabledFeatures & (1 << 4)) != 0,
+ SERVO_TILT: (enabledFeatures & (1 << 5)) != 0,
+ SOFTSERIAL: (enabledFeatures & (1 << 6)) != 0,
+ GPS: (enabledFeatures & (1 << 7)) != 0,
+ FAILSAFE: (enabledFeatures & (1 << 8)) != 0,
+ SONAR: (enabledFeatures & (1 << 9)) != 0,
+ TELEMETRY: (enabledFeatures & (1 << 10)) != 0,
+ CURRENT_METER: (enabledFeatures & (1 << 11)) != 0,
+ _3D: (enabledFeatures & (1 << 12)) != 0,
+ RX_PARALLEL_PWM: (enabledFeatures & (1 << 13)) != 0,
+ RX_MSP: (enabledFeatures & (1 << 14)) != 0,
+ RSSI_ADC: (enabledFeatures & (1 << 15)) != 0,
+ LED_STRIP: (enabledFeatures & (1 << 16)) != 0,
+ DISPLAY: (enabledFeatures & (1 << 17)) != 0,
+ ONESHOT125: (enabledFeatures & (1 << 18)) != 0,
+ BLACKBOX: (enabledFeatures & (1 << 19)) != 0,
+ CHANNEL_FORWARDING: (enabledFeatures & (1 << 20)) != 0,
+ TRANSPONDER: (enabledFeatures & (1 << 21)) != 0,
+ AIRMODE: (enabledFeatures & (1 << 22)) != 0,
+ SUPEREXPO_RATES: (enabledFeatures & (1 << 23)) != 0,
+ ANTI_GRAVITY: (enabledFeatures & (1 << 24)) != 0,
+ };
};
-FlightLog.prototype.isFieldDisabled = function() {
- const disabledFields=this.getSysConfig().fields_disabled_mask;
- return {
- PID : (disabledFields & (1 << 0))!==0,
- RC_COMMANDS : (disabledFields & (1 << 1))!==0,
- SETPOINT : (disabledFields & (1 << 2))!==0,
- BATTERY : (disabledFields & (1 << 3))!==0,
- MAGNETOMETER : (disabledFields & (1 << 4))!==0,
- ALTITUDE : (disabledFields & (1 << 5))!==0,
- RSSI : (disabledFields & (1 << 6))!==0,
- GYRO : (disabledFields & (1 << 7))!==0,
- ACC : (disabledFields & (1 << 8))!==0,
- DEBUG : (disabledFields & (1 << 9))!==0,
- MOTORS : (disabledFields & (1 << 10))!==0,
- GPS : (disabledFields & (1 << 11))!==0,
- RPM : (disabledFields & (1 << 12))!==0,
- GYROUNFILT : (disabledFields & (1 << 13))!==0,
- };
+FlightLog.prototype.isFieldDisabled = function () {
+ const disabledFields = this.getSysConfig().fields_disabled_mask;
+ const disabledFieldsFlags = {
+ PID: (disabledFields & (1 << 0)) !== 0,
+ RC_COMMANDS: (disabledFields & (1 << 1)) !== 0,
+ SETPOINT: (disabledFields & (1 << 2)) !== 0,
+ BATTERY: (disabledFields & (1 << 3)) !== 0,
+ MAGNETOMETER: (disabledFields & (1 << 4)) !== 0,
+ ALTITUDE: (disabledFields & (1 << 5)) !== 0,
+ RSSI: (disabledFields & (1 << 6)) !== 0,
+ GYRO: (disabledFields & (1 << 7)) !== 0,
+ ACC: (disabledFields & (1 << 8)) !== 0,
+ DEBUG: (disabledFields & (1 << 9)) !== 0,
+ MOTORS: (disabledFields & (1 << 10)) !== 0,
+ GPS: (disabledFields & (1 << 11)) !== 0,
+ RPM: (disabledFields & (1 << 12)) !== 0,
+ GYROUNFILT: (disabledFields & (1 << 13)) !== 0,
+ };
+
+ if (
+ this.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(this.getSysConfig().firmwareVersion, "4.6.0")
+ ) {
+ disabledFieldsFlags.ATTITUDE = (disabledFields & (1 << 8)) !== 0;
+ disabledFieldsFlags.ACC = (disabledFields & (1 << 9)) !== 0;
+ disabledFieldsFlags.DEBUG = (disabledFields & (1 << 10)) !== 0;
+ disabledFieldsFlags.MOTORS = (disabledFields & (1 << 11)) !== 0;
+ disabledFieldsFlags.GPS = (disabledFields & (1 << 12)) !== 0;
+ disabledFieldsFlags.RPM = (disabledFields & (1 << 13)) !== 0;
+ disabledFieldsFlags.GYROUNFILT = (disabledFields & (1 << 14)) !== 0;
+ disabledFieldsFlags.SERVO = (disabledFields & (1 << 15)) !== 0;
+ }
+
+ return disabledFieldsFlags;
};
diff --git a/src/flightlog_fielddefs.js b/src/flightlog_fielddefs.js
index c60b4400..3134a392 100644
--- a/src/flightlog_fielddefs.js
+++ b/src/flightlog_fielddefs.js
@@ -1,11 +1,11 @@
function makeReadOnly(x) {
- // Make read-only if browser supports it:
- if (Object.freeze) {
- return Object.freeze(x);
- }
-
- // Otherwise a no-op
- return x;
+ // Make read-only if browser supports it:
+ if (Object.freeze) {
+ return Object.freeze(x);
+ }
+
+ // Otherwise a no-op
+ return x;
}
// Some constants used at different places
@@ -16,538 +16,612 @@ export const DSHOT_RANGE = DSHOT_MAX_VALUE - DSHOT_MIN_VALUE;
// Fields definitions for lists
export const FlightLogEvent = makeReadOnly({
- SYNC_BEEP: 0,
-
- AUTOTUNE_CYCLE_START: 10,
- AUTOTUNE_CYCLE_RESULT: 11,
- AUTOTUNE_TARGETS: 12,
- INFLIGHT_ADJUSTMENT: 13,
- LOGGING_RESUME: 14,
- DISARM: 15,
-
- GTUNE_CYCLE_RESULT: 20,
- FLIGHT_MODE: 30,
- TWITCH_TEST: 40, // Feature for latency testing
-
- CUSTOM : 250, // Virtual Event Code - Never part of Log File.
- CUSTOM_BLANK : 251, // Virtual Event Code - Never part of Log File. - No line shown
- LOG_END: 255,
+ SYNC_BEEP: 0,
+
+ AUTOTUNE_CYCLE_START: 10,
+ AUTOTUNE_CYCLE_RESULT: 11,
+ AUTOTUNE_TARGETS: 12,
+ INFLIGHT_ADJUSTMENT: 13,
+ LOGGING_RESUME: 14,
+ DISARM: 15,
+
+ GTUNE_CYCLE_RESULT: 20,
+ FLIGHT_MODE: 30,
+ TWITCH_TEST: 40, // Feature for latency testing
+
+ CUSTOM: 250, // Virtual Event Code - Never part of Log File.
+ CUSTOM_BLANK: 251, // Virtual Event Code - Never part of Log File. - No line shown
+ LOG_END: 255,
});
// Add a general axis index.
export const AXIS = makeReadOnly({
- ROLL: 0,
- PITCH: 1,
- YAW: 2,
+ ROLL: 0,
+ PITCH: 1,
+ YAW: 2,
});
export let FLIGHT_LOG_FLIGHT_MODE_NAME = [];
export const FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3 = makeReadOnly([
- 'ARM',
- 'ANGLE',
- 'HORIZON',
- 'BARO',
- 'ANTIGRAVITY',
- 'MAG',
- 'HEADFREE',
- 'HEADADJ',
- 'CAMSTAB',
- 'CAMTRIG',
- 'GPSHOME',
- 'GPSHOLD',
- 'PASSTHRU',
- 'BEEPER',
- 'LEDMAX',
- 'LEDLOW',
- 'LLIGHTS',
- 'CALIB',
- 'GOV',
- 'OSD',
- 'TELEMETRY',
- 'GTUNE',
- 'SONAR',
- 'SERVO1',
- 'SERVO2',
- 'SERVO3',
- 'BLACKBOX',
- 'FAILSAFE',
- 'AIRMODE',
- '3DDISABLE',
- 'FPVANGLEMIX',
- 'BLACKBOXERASE',
- 'CAMERA1',
- 'CAMERA2',
- 'CAMERA3',
- 'FLIPOVERAFTERCRASH',
- 'PREARM',
+ "ARM",
+ "ANGLE",
+ "HORIZON",
+ "BARO",
+ "ANTIGRAVITY",
+ "MAG",
+ "HEADFREE",
+ "HEADADJ",
+ "CAMSTAB",
+ "CAMTRIG",
+ "GPSHOME",
+ "GPSHOLD",
+ "PASSTHRU",
+ "BEEPER",
+ "LEDMAX",
+ "LEDLOW",
+ "LLIGHTS",
+ "CALIB",
+ "GOV",
+ "OSD",
+ "TELEMETRY",
+ "GTUNE",
+ "SONAR",
+ "SERVO1",
+ "SERVO2",
+ "SERVO3",
+ "BLACKBOX",
+ "FAILSAFE",
+ "AIRMODE",
+ "3DDISABLE",
+ "FPVANGLEMIX",
+ "BLACKBOXERASE",
+ "CAMERA1",
+ "CAMERA2",
+ "CAMERA3",
+ "FLIPOVERAFTERCRASH",
+ "PREARM",
]);
export const FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3 = makeReadOnly([
- 'ARM',
- 'ANGLE',
- 'HORIZON',
- 'MAG',
- 'BARO',
- 'GPSHOME',
- 'GPSHOLD',
- 'HEADFREE',
- 'PASSTHRU',
- 'RANGEFINDER',
- 'FAILSAFE',
- 'GPSRESCUE',
- 'ANTIGRAVITY',
- 'HEADADJ',
- 'CAMSTAB',
- 'CAMTRIG',
- 'BEEPER',
- 'LEDMAX',
- 'LEDLOW',
- 'LLIGHTS',
- 'CALIB',
- 'GOV',
- 'OSD',
- 'TELEMETRY',
- 'GTUNE',
- 'SERVO1',
- 'SERVO2',
- 'SERVO3',
- 'BLACKBOX',
- 'AIRMODE',
- '3D',
- 'FPVANGLEMIX',
- 'BLACKBOXERASE',
- 'CAMERA1',
- 'CAMERA2',
- 'CAMERA3',
- 'FLIPOVERAFTERCRASH',
- 'PREARM',
- 'BEEPGPSCOUNT',
- 'VTXPITMODE',
- 'USER1',
- 'USER2',
- 'USER3',
- 'USER4',
- 'PIDAUDIO',
- 'ACROTRAINER',
- 'VTXCONTROLDISABLE',
- 'LAUNCHCONTROL',
+ "ARM",
+ "ANGLE",
+ "HORIZON",
+ "MAG",
+ "BARO",
+ "GPSHOME",
+ "GPSHOLD",
+ "HEADFREE",
+ "PASSTHRU",
+ "RANGEFINDER",
+ "FAILSAFE",
+ "GPSRESCUE",
+ "ANTIGRAVITY",
+ "HEADADJ",
+ "CAMSTAB",
+ "CAMTRIG",
+ "BEEPER",
+ "LEDMAX",
+ "LEDLOW",
+ "LLIGHTS",
+ "CALIB",
+ "GOV",
+ "OSD",
+ "TELEMETRY",
+ "GTUNE",
+ "SERVO1",
+ "SERVO2",
+ "SERVO3",
+ "BLACKBOX",
+ "AIRMODE",
+ "3D",
+ "FPVANGLEMIX",
+ "BLACKBOXERASE",
+ "CAMERA1",
+ "CAMERA2",
+ "CAMERA3",
+ "FLIPOVERAFTERCRASH",
+ "PREARM",
+ "BEEPGPSCOUNT",
+ "VTXPITMODE",
+ "USER1",
+ "USER2",
+ "USER3",
+ "USER4",
+ "PIDAUDIO",
+ "ACROTRAINER",
+ "VTXCONTROLDISABLE",
+ "LAUNCHCONTROL",
]);
-export const FLIGHT_LOG_FEATURES = makeReadOnly([
- 'RX_PPM',
- 'VBAT',
- 'INFLIGHT_ACC_CAL',
- 'RX_SERIAL',
- 'MOTOR_STOP',
- 'SERVO_TILT',
- 'SOFTSERIAL',
- 'GPS',
- 'FAILSAFE',
- 'SONAR',
- 'TELEMETRY',
- 'CURRENT_METER',
- '3D',
- 'RX_PARALLEL_PWM',
- 'RX_MSP',
- 'RSSI_ADC',
- 'LED_STRIP',
- 'DISPLAY',
- 'ONESHOT125',
- 'BLACKBOX',
- 'CHANNEL_FORWARDING',
- 'TRANSPONDER',
- 'AIRMODE',
- 'SUPEREXPO_RATES',
+export const FLIGHT_LOG_FLIGHT_MODE_NAME_POST_4_5 = makeReadOnly([
+ "ARM",
+ "ANGLE",
+ "HORIZON",
+ "MAG",
+ "ALTHOLD",
+ "HEADFREE",
+ "PASSTHRU",
+ "FAILSAFE",
+ "POSHOLD",
+ "GPSRESCUE",
+ "ANTIGRAVITY",
+ "HEADADJ",
+ "CAMSTAB",
+ "BEEPER",
+ "LEDLOW",
+ "CALIB",
+ "OSD",
+ "TELEMETRY",
+ "SERVO1",
+ "SERVO2",
+ "SERVO3",
+ "BLACKBOX",
+ "AIRMODE",
+ "3D",
+ "FPVANGLEMIX",
+ "BLACKBOXERASE",
+ "CAMERA1",
+ "CAMERA2",
+ "CAMERA3",
+ "FLIPOVERAFTERCRASH",
+ "PREARM",
+ "BEEPGPSCOUNT",
+ "VTXPITMODE",
+ "USER1",
+ "USER2",
+ "USER3",
+ "USER4",
+ "PIDAUDIO",
+ "ACROTRAINER",
+ "VTXCONTROLDISABLE",
+ "LAUNCHCONTROL",
]);
-export const OFF_ON = makeReadOnly([
- "OFF",
- "ON",
+export const FLIGHT_LOG_FEATURES = makeReadOnly([
+ "RX_PPM",
+ "VBAT",
+ "INFLIGHT_ACC_CAL",
+ "RX_SERIAL",
+ "MOTOR_STOP",
+ "SERVO_TILT",
+ "SOFTSERIAL",
+ "GPS",
+ "FAILSAFE",
+ "SONAR",
+ "TELEMETRY",
+ "CURRENT_METER",
+ "3D",
+ "RX_PARALLEL_PWM",
+ "RX_MSP",
+ "RSSI_ADC",
+ "LED_STRIP",
+ "DISPLAY",
+ "ONESHOT125",
+ "BLACKBOX",
+ "CHANNEL_FORWARDING",
+ "TRANSPONDER",
+ "AIRMODE",
+ "SUPEREXPO_RATES",
]);
+export const OFF_ON = makeReadOnly(["OFF", "ON"]);
+
export const FAST_PROTOCOL = makeReadOnly([
- "PWM",
- "ONESHOT125",
- "ONESHOT42",
- "MULTISHOT",
- "BRUSHED",
- "DSHOT150",
- "DSHOT300",
- "DSHOT600",
- "DSHOT1200", //deprecated
- "PROSHOT1000",
+ "PWM",
+ "ONESHOT125",
+ "ONESHOT42",
+ "MULTISHOT",
+ "BRUSHED",
+ "DSHOT150",
+ "DSHOT300",
+ "DSHOT600",
+ "DSHOT1200", //deprecated
+ "PROSHOT1000",
]);
-export const MOTOR_SYNC = makeReadOnly([
- "SYNCED",
- "UNSYNCED",
-]);
+export const MOTOR_SYNC = makeReadOnly(["SYNCED", "UNSYNCED"]);
export const SERIALRX_PROVIDER = makeReadOnly([
- "SPEK1024",
- "SPEK2048",
- "SBUS",
- "SUMD",
- "SUMH",
- "XB-B",
- "XB-B-RJ01",
- "IBUS",
- "JETIEXBUS",
- "CRSF",
- "SRXL",
- "CUSTOM",
- "FPORT",
- "SRXL2",
- "GHST",
+ "SPEK1024",
+ "SPEK2048",
+ "SBUS",
+ "SUMD",
+ "SUMH",
+ "XB-B",
+ "XB-B-RJ01",
+ "IBUS",
+ "JETIEXBUS",
+ "CRSF",
+ "SRXL",
+ "CUSTOM",
+ "FPORT",
+ "SRXL2",
+ "GHST",
]);
-export const ANTI_GRAVITY_MODE = makeReadOnly([
- "SMOOTH",
- "STEP",
-]);
+export const ANTI_GRAVITY_MODE = makeReadOnly(["SMOOTH", "STEP"]);
-export const RC_SMOOTHING_TYPE = makeReadOnly([
- "INTERPOLATION",
- "FILTER",
-]);
+export const RC_SMOOTHING_TYPE = makeReadOnly(["INTERPOLATION", "FILTER"]);
-export const RC_SMOOTHING_MODE = makeReadOnly([
- "OFF",
- "ON",
-]);
+export const RC_SMOOTHING_MODE = makeReadOnly(["OFF", "ON"]);
export const RC_SMOOTHING_DEBUG_AXIS = makeReadOnly([
- "ROLL",
- "PITCH",
- "YAW",
- "THROTTLE",
+ "ROLL",
+ "PITCH",
+ "YAW",
+ "THROTTLE",
]);
-export const FILTER_TYPE = makeReadOnly([
- "PT1",
- "BIQUAD",
- "PT2",
- "PT3",
-]);
+export const FILTER_TYPE = makeReadOnly(["PT1", "BIQUAD", "PT2", "PT3"]);
export let DEBUG_MODE = [];
export const DEBUG_MODE_COMPLETE = makeReadOnly([
- "NONE",
- "CYCLETIME",
- "BATTERY",
- "GYRO", // deprecated (GYRO_FILTERED)
- "ACCELEROMETER",
- "MIXER", // deprecated
- "AIRMODE", // deprecated
- "PIDLOOP",
- "NOTCH", // deprecated (GYRO_SCALED)
- "RC_INTERPOLATION",
- "VELOCITY", // deprecated
- "DTERM_FILTER", // deprecated
- "ANGLERATE",
- "ESC_SENSOR",
- "SCHEDULER",
- "STACK",
- "ESC_SENSOR_RPM",
- "ESC_SENSOR_TMP",
- "ALTITUDE",
- "FFT",
- "FFT_TIME",
- "FFT_FREQ",
- "RX_FRSKY_SPI",
- "GYRO_RAW", // deprecated
- "DUAL_GYRO", // deprecated
- "DUAL_GYRO_RAW",
- "DUAL_GYRO_COMBINED", // deprecated
- "DUAL_GYRO_DIFF",
- "MAX7456_SIGNAL",
- "MAX7456_SPICLOCK",
- "SBUS",
- "FPORT",
- "RANGEFINDER",
- "RANGEFINDER_QUALITY",
- "LIDAR_TF",
- "ADC_INTERNAL",
- "RUNAWAY_TAKEOFF",
- "SDIO",
- "CURRENT_SENSOR",
- "USB",
- "SMARTAUDIO",
- "RTH",
- "ITERM_RELAX",
- "ACRO_TRAINER",
- "RC_SMOOTHING",
- "RX_SIGNAL_LOSS",
- "RC_SMOOTHING_RATE",
- "ANTI_GRAVITY",
- "DYN_LPF",
- "RX_SPECTRUM_SPI",
- "DSHOT_RPM_TELEMETRY",
- "RPM_FILTER",
- "D_MIN",
- "AC_CORRECTION",
- "AC_ERROR",
- "DUAL_GYRO_SCALED",
- "DSHOT_RPM_ERRORS",
- "CRSF_LINK_STATISTICS_UPLINK",
- "CRSF_LINK_STATISTICS_PWR",
- "CRSF_LINK_STATISTICS_DOWN",
- "BARO",
- "GPS_RESCUE_THROTTLE_PID",
- "DYN_IDLE",
- "FF_LIMIT", // deprecated (FEEDFORWARD_LIMIT)
- "FF_INTERPOLATED", // deprecated (FEEDFORWARD)
- "BLACKBOX_OUTPUT",
- "GYRO_SAMPLE",
- "RX_TIMING",
- "D_LPF",
- "VTX_TRAMP",
- "GHST",
- "GHST_MSP",
- "SCHEDULER_DETERMINISM",
- "TIMING_ACCURACY",
- "RX_EXPRESSLRS_SPI",
- "RX_EXPRESSLRS_PHASELOCK",
- "RX_STATE_TIME",
- "GPS_RESCUE_VELOCITY",
- "GPS_RESCUE_HEADING",
- "GPS_RESCUE_TRACKING",
- "GPS_CONNECTION",
- "ATTITUDE",
- "VTX_MSP",
- "GPS_DOP",
- "FAILSAFE",
- "GYRO_CALIBRATION",
- "ANGLE_MODE",
- "ANGLE_TARGET",
- "CURRENT_ANGLE",
- "DSHOT_TELEMETRY_COUNTS",
- "RPM_LIMIT",
- "RC_STATS",
- "MAG_CALIB",
- "MAG_TASK_RATE",
- "EZLANDING",
+ "NONE",
+ "CYCLETIME",
+ "BATTERY",
+ "GYRO", // deprecated (GYRO_FILTERED)
+ "ACCELEROMETER",
+ "MIXER", // deprecated
+ "AIRMODE", // deprecated
+ "PIDLOOP",
+ "NOTCH", // deprecated (GYRO_SCALED)
+ "RC_INTERPOLATION",
+ "VELOCITY", // deprecated
+ "DTERM_FILTER", // deprecated
+ "ANGLERATE",
+ "ESC_SENSOR",
+ "SCHEDULER",
+ "STACK",
+ "ESC_SENSOR_RPM",
+ "ESC_SENSOR_TMP",
+ "ALTITUDE",
+ "FFT",
+ "FFT_TIME",
+ "FFT_FREQ",
+ "RX_FRSKY_SPI",
+ "GYRO_RAW", // deprecated
+ "DUAL_GYRO", // deprecated
+ "DUAL_GYRO_RAW",
+ "DUAL_GYRO_COMBINED", // deprecated
+ "DUAL_GYRO_DIFF",
+ "MAX7456_SIGNAL",
+ "MAX7456_SPICLOCK",
+ "SBUS",
+ "FPORT",
+ "RANGEFINDER",
+ "RANGEFINDER_QUALITY",
+ "LIDAR_TF",
+ "ADC_INTERNAL",
+ "RUNAWAY_TAKEOFF",
+ "SDIO",
+ "CURRENT_SENSOR",
+ "USB",
+ "SMARTAUDIO",
+ "RTH",
+ "ITERM_RELAX",
+ "ACRO_TRAINER",
+ "RC_SMOOTHING",
+ "RX_SIGNAL_LOSS",
+ "RC_SMOOTHING_RATE",
+ "ANTI_GRAVITY",
+ "DYN_LPF",
+ "RX_SPECTRUM_SPI",
+ "DSHOT_RPM_TELEMETRY",
+ "RPM_FILTER",
+ "D_MAX",
+ "AC_CORRECTION",
+ "AC_ERROR",
+ "DUAL_GYRO_SCALED",
+ "DSHOT_RPM_ERRORS",
+ "CRSF_LINK_STATISTICS_UPLINK",
+ "CRSF_LINK_STATISTICS_PWR",
+ "CRSF_LINK_STATISTICS_DOWN",
+ "BARO",
+ "GPS_RESCUE_THROTTLE_PID",
+ "DYN_IDLE",
+ "FF_LIMIT", // deprecated (FEEDFORWARD_LIMIT)
+ "FF_INTERPOLATED", // deprecated (FEEDFORWARD)
+ "BLACKBOX_OUTPUT",
+ "GYRO_SAMPLE",
+ "RX_TIMING",
+ "D_LPF",
+ "VTX_TRAMP",
+ "GHST",
+ "GHST_MSP",
+ "SCHEDULER_DETERMINISM",
+ "TIMING_ACCURACY",
+ "RX_EXPRESSLRS_SPI",
+ "RX_EXPRESSLRS_PHASELOCK",
+ "RX_STATE_TIME",
+ "GPS_RESCUE_VELOCITY",
+ "GPS_RESCUE_HEADING",
+ "GPS_RESCUE_TRACKING",
+ "GPS_CONNECTION",
+ "ATTITUDE",
+ "VTX_MSP",
+ "GPS_DOP",
+ "FAILSAFE",
+ "GYRO_CALIBRATION",
+ "ANGLE_MODE",
+ "ANGLE_TARGET",
+ "CURRENT_ANGLE",
+ "DSHOT_TELEMETRY_COUNTS",
+ "RPM_LIMIT",
+ "RC_STATS",
+ "MAG_CALIB",
+ "MAG_TASK_RATE",
+ "EZLANDING",
]);
-export const SUPER_EXPO_YAW = makeReadOnly([
- "OFF",
- "ON",
- "ALWAYS",
-]);
+export const SUPER_EXPO_YAW = makeReadOnly(["OFF", "ON", "ALWAYS"]);
export const GYRO_LPF = makeReadOnly([
- "OFF",
- "188HZ",
- "98HZ",
- "42HZ",
- "20HZ",
- "10HZ",
- "5HZ",
- "EXPERIMENTAL",
+ "OFF",
+ "188HZ",
+ "98HZ",
+ "42HZ",
+ "20HZ",
+ "10HZ",
+ "5HZ",
+ "EXPERIMENTAL",
]);
export const GYRO_HARDWARE_LPF = makeReadOnly([
- "NORMAL",
- "EXPERIMENTAL",
- "1KHZ_SAMPLING",
-]);
-
-export const GYRO_32KHZ_HARDWARE_LPF = makeReadOnly([
- "NORMAL",
- "EXPERIMENTAL",
+ "NORMAL",
+ "EXPERIMENTAL",
+ "1KHZ_SAMPLING",
]);
-export const ACC_HARDWARE = makeReadOnly([
- "AUTO",
- "NONE",
- "ADXL345",
- "MPU6050",
- "MMA8452",
- "BMA280",
- "LSM303DLHC",
- "MPU6000",
- "MPU6500",
- "MPU9250",
- "ICM20601",
- "ICM20602",
- "ICM20608G",
- "ICM20649",
- "ICM20689",
- "ICM42605",
- "ICM42688P",
- "BMI160",
- "BMI270",
- "LSM6DSO",
- "LSM6DSV16X",
- "VIRTUAL",
+export const GYRO_32KHZ_HARDWARE_LPF = makeReadOnly(["NORMAL", "EXPERIMENTAL"]);
+
+export let ACC_HARDWARE = [];
+
+export const ACC_HARDWARE_COMPLETE = makeReadOnly([
+ "AUTO",
+ "NONE",
+ "ADXL345",
+ "MPU6050",
+ "MMA8452",
+ "BMA280",
+ "LSM303DLHC",
+ "MPU6000",
+ "MPU6500",
+ "MPU9250",
+ "ICM20601",
+ "ICM20602",
+ "ICM20608G",
+ "ICM20649",
+ "ICM20689",
+ "ICM42605",
+ "ICM42688P",
+ "BMI160",
+ "BMI270",
+ "LSM6DSO",
+ "LSM6DSV16X",
+ "VIRTUAL",
]);
export const BARO_HARDWARE = makeReadOnly([
- "AUTO",
- "NONE",
- "BMP085",
- "MS5611",
- "BMP280",
- "LPS",
- "QMP6988",
- "BMP388",
- "DPS310",
- "2SMPB_02B",
+ "AUTO",
+ "NONE",
+ "BMP085",
+ "MS5611",
+ "BMP280",
+ "LPS",
+ "QMP6988",
+ "BMP388",
+ "DPS310",
+ "2SMPB_02B",
]);
export const MAG_HARDWARE = makeReadOnly([
- "AUTO",
- "NONE",
- "HMC5883",
- "AK8975",
- "AK8963",
- "QMC5883",
- "LIS3MDL",
- "MAG_MPU925X_AK8963",
+ "AUTO",
+ "NONE",
+ "HMC5883",
+ "AK8975",
+ "AK8963",
+ "QMC5883",
+ "LIS3MDL",
+ "MAG_MPU925X_AK8963",
]);
export const FLIGHT_LOG_FLIGHT_STATE_NAME = makeReadOnly([
- "GPS_FIX_HOME",
- "GPS_FIX",
- "CALIBRATE_MAG",
- "SMALL_ANGLE",
- "FIXED_WING",
+ "GPS_FIX_HOME",
+ "GPS_FIX",
+ "CALIBRATE_MAG",
+ "SMALL_ANGLE",
+ "FIXED_WING",
]);
export const FLIGHT_LOG_FAILSAFE_PHASE_NAME = makeReadOnly([
- "IDLE",
- "RX_LOSS_DETECTED",
- "LANDING",
- "LANDED",
+ "IDLE",
+ "RX_LOSS_DETECTED",
+ "LANDING",
+ "LANDED",
]);
export const FFT_CALC_STEPS = makeReadOnly([
- "ARM_CFFT_F32",
- "BITREVERSAL",
- "STAGE_RFFT_F32",
- "ARM_CMPLX_MAG_F32",
- "CALC_FREQUENCIES",
- "UPDATE_FILTERS",
- "HANNING",
+ "ARM_CFFT_F32",
+ "BITREVERSAL",
+ "STAGE_RFFT_F32",
+ "ARM_CMPLX_MAG_F32",
+ "CALC_FREQUENCIES",
+ "UPDATE_FILTERS",
+ "HANNING",
]);
export const ITERM_RELAX = makeReadOnly([
- "OFF",
- "RP",
- "RPY",
- "RP_INC",
- "RPY_INC",
+ "OFF",
+ "RP",
+ "RPY",
+ "RP_INC",
+ "RPY_INC",
]);
-export const ITERM_RELAX_TYPE = makeReadOnly([
- "GYRO",
- "SETPOINT",
-]);
+export const ITERM_RELAX_TYPE = makeReadOnly(["GYRO", "SETPOINT"]);
export const FLIGHT_LOG_DISARM_REASON = makeReadOnly([
- "ARMING_DISABLED",
- "FAILSAFE",
- "THROTTLE_TIMEOUT",
- "STICKS",
- "SWITCH",
- "CRASH_PROTECTION",
- "RUNAWAY_TAKEOFF",
- "GPS_RESCUE",
- "SERIAL_IO",
+ "ARMING_DISABLED",
+ "FAILSAFE",
+ "THROTTLE_TIMEOUT",
+ "STICKS",
+ "SWITCH",
+ "CRASH_PROTECTION",
+ "RUNAWAY_TAKEOFF",
+ "GPS_RESCUE",
+ "SERIAL_IO",
]);
export const RATES_TYPE = makeReadOnly([
- "BETAFLIGHT",
- "RACEFLIGHT",
- "KISS",
- "ACTUAL",
- "QUICK",
+ "BETAFLIGHT",
+ "RACEFLIGHT",
+ "KISS",
+ "ACTUAL",
+ "QUICK",
]);
-export const GYRO_TO_USE = makeReadOnly([
- "FIRST",
- "SECOND",
- "BOTH",
-]);
+export const GYRO_TO_USE = makeReadOnly(["FIRST", "SECOND", "BOTH"]);
export const FF_AVERAGING = makeReadOnly([
- "OFF",
- "2_POINT",
- "3_POINT",
- "4_POINT",
+ "OFF",
+ "2_POINT",
+ "3_POINT",
+ "4_POINT",
]);
export const SIMPLIFIED_PIDS_MODE = makeReadOnly([
- "OFF",
- "ON - RP",
- "ON - RPY",
+ "OFF",
+ "ON - RP",
+ "ON - RPY",
]);
-export const THROTTLE_LIMIT_TYPE = makeReadOnly([
- "OFF",
- "SCALE",
- "CLIP",
-]);
+export const THROTTLE_LIMIT_TYPE = makeReadOnly(["OFF", "SCALE", "CLIP"]);
export function adjustFieldDefsList(firmwareType, firmwareVersion) {
- if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(firmwareVersion, '3.3.0')) {
-
- // Debug names
- DEBUG_MODE = DEBUG_MODE_COMPLETE.slice(0);
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('MIXER'), 1);
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('AIRMODE'), 1);
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('VELOCITY'), 1);
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('DTERM_FILTER'), 1);
-
- if (semver.gte(firmwareVersion, '3.4.0')) {
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO'), 1, 'GYRO_FILTERED');
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('NOTCH'), 1, 'GYRO_SCALED');
- }
- if (semver.gte(firmwareVersion, '4.0.0')) {
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO_RAW'), 0, 'RX_SFHSS_SPI');
- }
- if (semver.gte(firmwareVersion, '4.1.0')) {
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO'), 1);
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO_COMBINED'), 1);
- }
- if (semver.gte(firmwareVersion, '4.3.0')) {
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_INTERPOLATED'), 1, 'FEEDFORWARD');
- DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_LIMIT'), 1, 'FEEDFORWARD_LIMIT');
- }
-
- DEBUG_MODE = makeReadOnly(DEBUG_MODE);
-
- // Flight mode names
- FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3.slice(0);
- if (semver.lt(firmwareVersion, '3.4.0')) {
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSRESCUE'), 1);
- }
- if (semver.gte(firmwareVersion, '3.5.0')) {
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('RANGEFINDER'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('CAMTRIG'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LEDMAX'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LLIGHTS'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GOV'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GTUNE'), 1);
- }
- if (semver.gte(firmwareVersion, '4.0.0')) {
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('BARO'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOME'), 1);
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOLD'), 1);
- }
- FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME);
-
- } else {
- DEBUG_MODE = DEBUG_MODE_COMPLETE;
-
- FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3.slice(0);
+ if (
+ firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(firmwareVersion, "3.3.0")
+ ) {
+ // ACC hardware names
+ ACC_HARDWARE = ACC_HARDWARE_COMPLETE.slice(0);
+ // Debug names
+ DEBUG_MODE = DEBUG_MODE_COMPLETE.slice(0);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("MIXER"), 1);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("AIRMODE"), 1);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("VELOCITY"), 1);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("DTERM_FILTER"), 1);
+
+ if (semver.gte(firmwareVersion, "3.4.0")) {
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("GYRO"), 1, "GYRO_FILTERED");
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("NOTCH"), 1, "GYRO_SCALED");
+ }
+ if (semver.gte(firmwareVersion, "4.0.0")) {
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("GYRO_RAW"), 0, "RX_SFHSS_SPI");
+ }
+ if (semver.gte(firmwareVersion, "4.1.0")) {
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("DUAL_GYRO"), 1);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("DUAL_GYRO_COMBINED"), 1);
+ }
+ if (semver.gte(firmwareVersion, "4.3.0")) {
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("FF_INTERPOLATED"), 1, "FEEDFORWARD");
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("FF_LIMIT"), 1, "FEEDFORWARD_LIMIT");
+ }
+ if (semver.lt(firmwareVersion, "4.6.0")) {
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("D_MAX"), 1, "D_MIN");
+ }
+ if (semver.gte(firmwareVersion, "4.6.0")) {
+ ACC_HARDWARE.splice(ACC_HARDWARE.indexOf("ADXL345"), 1);
+ ACC_HARDWARE.splice(ACC_HARDWARE.indexOf("MMA8452"), 1);
+ ACC_HARDWARE.splice(ACC_HARDWARE.indexOf("BMA280"), 1);
+ ACC_HARDWARE.splice(ACC_HARDWARE.indexOf("LSM303DLHC"), 1);
+ ACC_HARDWARE.splice(ACC_HARDWARE.indexOf("LSM6DSV16X") + 1, 0, "IIM42653");
+
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf('GPS_RESCUE_THROTTLE_PID'), 1, 'AUTOPILOT_ALTITUDE');
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("GYRO_SCALED"), 1);
+ DEBUG_MODE.splice(DEBUG_MODE.indexOf("RANGEFINDER_QUALITY") + 1, 0, "OPTICALFLOW");
+ DEBUG_MODE.push('TPA');
+ DEBUG_MODE.push('S_TERM');
+ DEBUG_MODE.push('SPA');
+ DEBUG_MODE.push('TASK');
+ DEBUG_MODE.push('GIMBAL');
+ DEBUG_MODE.push('WING_SETPOINT');
+ DEBUG_MODE.push('AUTOPILOT_POSITION');
+ }
- if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.lte(firmwareVersion, '3.1.6')) {
- FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('ANTIGRAVITY'), 1);
- }
+ ACC_HARDWARE = makeReadOnly(ACC_HARDWARE);
+ DEBUG_MODE = makeReadOnly(DEBUG_MODE);
- FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME);
+ // Flight mode names
+ if (semver.gte(firmwareVersion, "4.6.0")) {
+ FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_POST_4_5.slice(0);
+ } else {
+ FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3.slice(0);
+ if (semver.lt(firmwareVersion, "3.4.0")) {
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("GPSRESCUE"),
+ 1
+ );
+ }
+ if (semver.gte(firmwareVersion, "3.5.0")) {
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("RANGEFINDER"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("CAMTRIG"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("LEDMAX"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("LLIGHTS"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("GOV"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("GTUNE"),
+ 1
+ );
+ }
+ if (semver.gte(firmwareVersion, "4.0.0")) {
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("BARO"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("GPSHOME"),
+ 1
+ );
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("GPSHOLD"),
+ 1
+ );
+ }
+ }
+
+ FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME);
+ } else {
+ DEBUG_MODE = DEBUG_MODE_COMPLETE;
+
+ FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3.slice(0);
+
+ if (
+ firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.lte(firmwareVersion, "3.1.6")
+ ) {
+ FLIGHT_LOG_FLIGHT_MODE_NAME.splice(
+ FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf("ANTIGRAVITY"),
+ 1
+ );
}
+
+ FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME);
+ }
}
diff --git a/src/flightlog_fields_presenter.js b/src/flightlog_fields_presenter.js
index eb3ea01c..d839fd42 100644
--- a/src/flightlog_fields_presenter.js
+++ b/src/flightlog_fields_presenter.js
@@ -1,1346 +1,1476 @@
import {
- FLIGHT_LOG_FLIGHT_MODE_NAME,
- FLIGHT_LOG_FEATURES,
- DEBUG_MODE,
- FLIGHT_LOG_FLIGHT_STATE_NAME,
- FLIGHT_LOG_FAILSAFE_PHASE_NAME,
- FFT_CALC_STEPS,
+ FLIGHT_LOG_FLIGHT_MODE_NAME,
+ FLIGHT_LOG_FEATURES,
+ DEBUG_MODE,
+ FLIGHT_LOG_FLIGHT_STATE_NAME,
+ FLIGHT_LOG_FAILSAFE_PHASE_NAME,
+ FFT_CALC_STEPS,
} from "./flightlog_fielddefs";
import { formatTime } from "./tools";
export function FlightLogFieldPresenter() {
- // this is intentional
+ // this is intentional
}
const FRIENDLY_FIELD_NAMES = {
-
- 'axisP[all]': 'PID P',
- 'axisP[0]': 'PID P [roll]',
- 'axisP[1]': 'PID P [pitch]',
- 'axisP[2]': 'PID P [yaw]',
-
- 'axisI[all]': 'PID I',
- 'axisI[0]': 'PID I [roll]',
- 'axisI[1]': 'PID I [pitch]',
- 'axisI[2]': 'PID I [yaw]',
-
- 'axisD[all]': 'PID D',
- 'axisD[0]': 'PID D [roll]',
- 'axisD[1]': 'PID D [pitch]',
- 'axisD[2]': 'PID D [yaw]',
-
- 'axisF[all]': 'PID Feedforward',
- 'axisF[0]': 'PID Feedforward [roll]',
- 'axisF[1]': 'PID Feedforward [pitch]',
- 'axisF[2]': 'PID Feedforward [yaw]',
-
- //Virtual field
- 'axisSum[all]': 'PID Sum',
- 'axisSum[0]' : 'PID Sum [roll]',
- 'axisSum[1]' : 'PID Sum [pitch]',
- 'axisSum[2]' : 'PID Sum [yaw]',
-
- //Virtual field
- 'axisError[all]': 'PID Error',
- 'axisError[0]' : 'PID Error [roll]',
- 'axisError[1]' : 'PID Error [pitch]',
- 'axisError[2]' : 'PID Error [yaw]',
-
- //Virtual field
- 'rcCommands[all]': 'Setpoints',
- 'rcCommands[0]' : 'Setpoint [roll]',
- 'rcCommands[1]' : 'Setpoint [pitch]',
- 'rcCommands[2]' : 'Setpoint [yaw]',
- 'rcCommands[3]' : 'Setpoint [throttle]',
-
- 'rcCommand[all]': 'RC Commands',
- 'rcCommand[0]': 'RC Command [roll]',
- 'rcCommand[1]': 'RC Command [pitch]',
- 'rcCommand[2]': 'RC Command [yaw]',
- 'rcCommand[3]': 'RC Command [throttle]',
-
- 'gyroADC[all]': 'Gyros',
- 'gyroADC[0]': 'Gyro [roll]',
- 'gyroADC[1]': 'Gyro [pitch]',
- 'gyroADC[2]': 'Gyro [yaw]',
-
- 'gyroUnfilt[all]': 'Unfiltered Gyros',
- 'gyroUnfilt[0]': 'Unfiltered Gyro [roll]',
- 'gyroUnfilt[1]': 'Unfiltered Gyro [pitch]',
- 'gyroUnfilt[2]': 'Unfiltered Gyro [yaw]',
-
- //End-users prefer 1-based indexing
- 'motor[all]': 'Motors',
- 'motor[0]': 'Motor [1]',
- 'motor[1]': 'Motor [2]',
- 'motor[2]': 'Motor [3]',
- 'motor[3]': 'Motor [4]',
- 'motor[4]': 'Motor [5]',
- 'motor[5]': 'Motor [6]',
- 'motor[6]': 'Motor [7]',
- 'motor[7]': 'Motor [8]',
-
- 'eRPM[all]': 'RPM',
- 'eRPM[0]': 'RPM [1]',
- 'eRPM[1]': 'RPM [2]',
- 'eRPM[2]': 'RPM [3]',
- 'eRPM[3]': 'RPM [4]',
- 'eRPM[4]': 'RPM [5]',
- 'eRPM[5]': 'RPM [6]',
- 'eRPM[6]': 'RPM [7]',
- 'eRPM[7]': 'RPM [8]',
-
- 'servo[all]': 'Servos',
- 'servo[5]': 'Servo Tail',
-
- 'vbatLatest': 'Battery volt.',
- 'amperageLatest': 'Amperage',
- 'baroAlt': 'Barometer',
-
- 'heading[all]': 'Heading',
- 'heading[0]': 'Heading [roll]',
- 'heading[1]': 'Heading [pitch]',
- 'heading[2]': 'Heading [yaw]',
-
- 'accSmooth[all]': 'Accel.',
- 'accSmooth[0]': 'Accel. [X]',
- 'accSmooth[1]': 'Accel. [Y]',
- 'accSmooth[2]': 'Accel. [Z]',
-
- 'magADC[all]': 'Compass',
- 'magADC[0]': 'Compass [X]',
- 'magADC[1]': 'Compass [Y]',
- 'magADC[2]': 'Compass [Z]',
-
- 'flightModeFlags': 'Flight Mode Flags',
- 'stateFlags': 'State Flags',
- 'failsafePhase': 'Failsafe Phase',
- 'rxSignalReceived': 'RX Signal Received',
- 'rxFlightChannelsValid': 'RX Flight Ch. Valid',
- 'rssi': 'RSSI',
-
- 'GPS_numSat': "GPS Sat Count",
- 'GPS_coord[0]': "GPS Latitude",
- 'GPS_coord[1]': "GPS Longitude",
- 'GPS_altitude': "GPS Altitude ASL",
- 'GPS_speed': "GPS Speed",
- 'GPS_ground_course': "GPS Heading",
+ "axisP[all]": "PID P",
+ "axisP[0]": "PID P [roll]",
+ "axisP[1]": "PID P [pitch]",
+ "axisP[2]": "PID P [yaw]",
+
+ "axisI[all]": "PID I",
+ "axisI[0]": "PID I [roll]",
+ "axisI[1]": "PID I [pitch]",
+ "axisI[2]": "PID I [yaw]",
+
+ "axisD[all]": "PID D",
+ "axisD[0]": "PID D [roll]",
+ "axisD[1]": "PID D [pitch]",
+ "axisD[2]": "PID D [yaw]",
+
+ "axisF[all]": "PID Feedforward",
+ "axisF[0]": "PID Feedforward [roll]",
+ "axisF[1]": "PID Feedforward [pitch]",
+ "axisF[2]": "PID Feedforward [yaw]",
+
+ "axisS[all]": "PID S",
+ "axisS[0]": "PID S [roll]",
+ "axisS[1]": "PID S [pitch]",
+ "axisS[2]": "PID S [yaw]",
+
+ //Virtual field
+ "axisSum[all]": "PID Sum",
+ "axisSum[0]": "PID Sum [roll]",
+ "axisSum[1]": "PID Sum [pitch]",
+ "axisSum[2]": "PID Sum [yaw]",
+
+ //Virtual field
+ "axisError[all]": "PID Error",
+ "axisError[0]": "PID Error [roll]",
+ "axisError[1]": "PID Error [pitch]",
+ "axisError[2]": "PID Error [yaw]",
+
+ //Virtual field
+ "rcCommands[all]": "Setpoints",
+ "rcCommands[0]": "Setpoint [roll]",
+ "rcCommands[1]": "Setpoint [pitch]",
+ "rcCommands[2]": "Setpoint [yaw]",
+ "rcCommands[3]": "Setpoint [throttle]",
+
+ "rcCommand[all]": "RC Commands",
+ "rcCommand[0]": "RC Command [roll]",
+ "rcCommand[1]": "RC Command [pitch]",
+ "rcCommand[2]": "RC Command [yaw]",
+ "rcCommand[3]": "RC Command [throttle]",
+
+ "gyroADC[all]": "Gyros",
+ "gyroADC[0]": "Gyro [roll]",
+ "gyroADC[1]": "Gyro [pitch]",
+ "gyroADC[2]": "Gyro [yaw]",
+
+ "gyroUnfilt[all]": "Unfiltered Gyros",
+ "gyroUnfilt[0]": "Unfiltered Gyro [roll]",
+ "gyroUnfilt[1]": "Unfiltered Gyro [pitch]",
+ "gyroUnfilt[2]": "Unfiltered Gyro [yaw]",
+
+ //End-users prefer 1-based indexing
+ "motor[all]": "Motors",
+ "motor[0]": "Motor [1]",
+ "motor[1]": "Motor [2]",
+ "motor[2]": "Motor [3]",
+ "motor[3]": "Motor [4]",
+ "motor[4]": "Motor [5]",
+ "motor[5]": "Motor [6]",
+ "motor[6]": "Motor [7]",
+ "motor[7]": "Motor [8]",
+
+ "eRPM[all]": "RPM",
+ "eRPM[0]": "RPM [1]",
+ "eRPM[1]": "RPM [2]",
+ "eRPM[2]": "RPM [3]",
+ "eRPM[3]": "RPM [4]",
+ "eRPM[4]": "RPM [5]",
+ "eRPM[5]": "RPM [6]",
+ "eRPM[6]": "RPM [7]",
+ "eRPM[7]": "RPM [8]",
+
+ "servo[all]": "Servos",
+ "servo[5]": "Servo Tail",
+
+ vbatLatest: "Battery volt.",
+ amperageLatest: "Amperage",
+ baroAlt: "Barometer",
+
+ "heading[all]": "Heading",
+ "heading[0]": "Heading [roll]",
+ "heading[1]": "Heading [pitch]",
+ "heading[2]": "Heading [yaw]",
+
+ "accSmooth[all]": "Accel.",
+ "accSmooth[0]": "Accel. [X]",
+ "accSmooth[1]": "Accel. [Y]",
+ "accSmooth[2]": "Accel. [Z]",
+
+ "magADC[all]": "Compass",
+ "magADC[0]": "Compass [X]",
+ "magADC[1]": "Compass [Y]",
+ "magADC[2]": "Compass [Z]",
+
+ flightModeFlags: "Flight Mode Flags",
+ stateFlags: "State Flags",
+ failsafePhase: "Failsafe Phase",
+ rxSignalReceived: "RX Signal Received",
+ rxFlightChannelsValid: "RX Flight Ch. Valid",
+ rssi: "RSSI",
+
+ GPS_numSat: "GPS Sat Count",
+ "GPS_coord[0]": "GPS Latitude",
+ "GPS_coord[1]": "GPS Longitude",
+ GPS_altitude: "GPS Altitude ASL",
+ GPS_speed: "GPS Speed",
+ GPS_ground_course: "GPS Heading",
+
+ "gpsCartesianCoords[all]": "GPS Coords",
+ "gpsCartesianCoords[0]": "GPS Coords [X]",
+ "gpsCartesianCoords[1]": "GPS Coords [Y]",
+ "gpsCartesianCoords[2]": "GPS Coords [Z]",
+ gpsDistance: "GPS Home distance",
+ gpsHomeAzimuth: "GPS Home azimuth",
};
const DEBUG_FRIENDLY_FIELD_NAMES_INITIAL = {
- 'NONE' : {
- 'debug[all]':'Debug [all]',
- 'debug[0]':'Debug [0]',
- 'debug[1]':'Debug [1]',
- 'debug[2]':'Debug [2]',
- 'debug[3]':'Debug [3]',
- 'debug[4]':'Debug [4]',
- 'debug[5]':'Debug [5]',
- 'debug[6]':'Debug [6]',
- 'debug[7]':'Debug [7]',
- },
- 'CYCLETIME' : {
- 'debug[all]':'Debug Cycle Time',
- 'debug[0]':'Cycle Time',
- 'debug[1]':'CPU Load',
- 'debug[2]':'Motor Update',
- 'debug[3]':'Motor Deviation',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'BATTERY' : {
- 'debug[all]':'Debug Battery',
- 'debug[0]':'Battery Volt ADC',
- 'debug[1]':'Battery Volt',
- 'debug[2]':'Not Used',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GYRO' : {
- 'debug[all]':'Debug Gyro',
- 'debug[0]':'Gyro Raw [X]',
- 'debug[1]':'Gyro Raw [Y]',
- 'debug[2]':'Gyro Raw [Z]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GYRO_FILTERED' : {
- 'debug[all]':'Debug Gyro Filtered',
- 'debug[0]':'Gyro Filtered [X]',
- 'debug[1]':'Gyro Filtered [Y]',
- 'debug[2]':'Gyro Filtered [Z]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ACCELEROMETER' : {
- 'debug[all]':'Debug Accel.',
- 'debug[0]':'Accel. Raw [X]',
- 'debug[1]':'Accel. Raw [Y]',
- 'debug[2]':'Accel. Raw [Z]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'MIXER' : {
- 'debug[all]':'Debug Mixer',
- 'debug[0]':'Roll-Pitch-Yaw Mix [0]',
- 'debug[1]':'Roll-Pitch-Yaw Mix [1]',
- 'debug[2]':'Roll-Pitch-Yaw Mix [2]',
- 'debug[3]':'Roll-Pitch-Yaw Mix [3]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'PIDLOOP' : {
- 'debug[all]':'Debug PID',
- 'debug[0]':'Wait Time',
- 'debug[1]':'Sub Update Time',
- 'debug[2]':'PID Update Time',
- 'debug[3]':'Motor Update Time',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'NOTCH' : {
- 'debug[all]':'Debug Notch',
- 'debug[0]':'Gyro Pre-Notch [roll]',
- 'debug[1]':'Gyro Pre-Notch [pitch]',
- 'debug[2]':'Gyro Pre-Notch [yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GYRO_SCALED' : {
- 'debug[all]':'Debug Gyro Scaled',
- 'debug[0]':'Gyro Scaled [roll]',
- 'debug[1]':'Gyro Scaled [pitch]',
- 'debug[2]':'Gyro Scaled [yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RC_INTERPOLATION' : {
- 'debug[all]':'Debug RC Interpolation',
- 'debug[0]':'Raw RC Command [roll]',
- 'debug[1]':'Current RX Refresh Rate',
- 'debug[2]':'Interpolation Step Count',
- 'debug[3]':'RC Setpoint [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DTERM_FILTER' : {
- 'debug[all]':'Debug Filter',
- 'debug[0]':'DTerm Filter [roll]',
- 'debug[1]':'DTerm Filter [pitch]',
- 'debug[2]':'Not Used',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ANGLERATE' : {
- 'debug[all]':'Debug Angle Rate',
- 'debug[0]':'Angle Rate[roll]',
- 'debug[1]':'Angle Rate[pitch]',
- 'debug[2]':'Angle Rate[yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ESC_SENSOR' : {
- 'debug[all]':'ESC Sensor',
- 'debug[0]':'Motor Index',
- 'debug[1]':'Timeouts',
- 'debug[2]':'CNC errors',
- 'debug[3]':'Data age',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'SCHEDULER' : {
- 'debug[all]':'Scheduler',
- 'debug[0]':'Not Used',
- 'debug[1]':'Not Used',
- 'debug[2]':'Schedule Time',
- 'debug[3]':'Function Exec Time',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'STACK' : {
- 'debug[all]':'Stack',
- 'debug[0]':'Stack High Mem',
- 'debug[1]':'Stack Low Mem',
- 'debug[2]':'Stack Current',
- 'debug[3]':'Stack p',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ESC_SENSOR_RPM' : {
- 'debug[all]':'ESC Sensor RPM',
- 'debug[0]':'Motor 1',
- 'debug[1]':'Motor 2',
- 'debug[2]':'Motor 3',
- 'debug[3]':'Motor 4',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ESC_SENSOR_TMP' : {
- 'debug[all]':'ESC Sensor Temp',
- 'debug[0]':'Motor 1',
- 'debug[1]':'Motor 2',
- 'debug[2]':'Motor 3',
- 'debug[3]':'Motor 4',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ALTITUDE' : {
- 'debug[all]':'Altitude',
- 'debug[0]':'GPS Trust * 100',
- 'debug[1]':'Baro Altitude',
- 'debug[2]':'GPS Altitude',
- 'debug[3]':'Vario',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FFT' : {
- 'debug[all]':'Debug FFT',
- 'debug[0]':'Gyro Scaled [dbg-axis]',
- 'debug[1]':'Gyro Pre-Dyn [dbg-axis]',
- 'debug[2]':'Gyro Downsampled [roll]',
- 'debug[3]':'FFT Center Index [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FFT_TIME' : {
- 'debug[all]':'Debug FFT TIME',
- 'debug[0]':'Active calc step',
- 'debug[1]':'Step duration',
- 'debug[2]':'Additional steps',
- 'debug[3]':'Not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FFT_FREQ' : {
- 'debug[all]':'Debug FFT FREQ',
- 'debug[0]':'Center Freq [roll]',
- 'debug[1]':'Center Freq [pitch]',
- 'debug[2]':'Gyro Pre-Dyn [dbg-axis]',
- 'debug[3]':'Gyro Scaled [dbg-axis]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_FRSKY_SPI' : {
- 'debug[all]':'FrSky SPI Rx',
- 'debug[0]':'Looptime',
- 'debug[1]':'Packet',
- 'debug[2]':'Missing Packets',
- 'debug[3]':'State',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_SFHSS_SPI' : {
- 'debug[all]':'SFHSS SPI Rx',
- 'debug[0]':'State',
- 'debug[1]':'Missing Frame',
- 'debug[2]':'Offset Max',
- 'debug[3]':'Offset Min',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GYRO_RAW' : {
- 'debug[all]':'Debug Gyro Raw',
- 'debug[0]':'Gyro Raw [X]',
- 'debug[1]':'Gyro Raw [Y]',
- 'debug[2]':'Gyro Raw [Z]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DUAL_GYRO' : {
- 'debug[all]':'Debug Dual Gyro',
- 'debug[0]':'Gyro 1 Filtered [roll]',
- 'debug[1]':'Gyro 1 Filtered [pitch]',
- 'debug[2]':'Gyro 2 Filtered [roll]',
- 'debug[3]':'Gyro 2 Filtered [pitch]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DUAL_GYRO_RAW': {
- 'debug[all]':'Debug Dual Gyro Raw',
- 'debug[0]':'Gyro 1 Raw [roll]',
- 'debug[1]':'Gyro 1 Raw [pitch]',
- 'debug[2]':'Gyro 2 Raw [roll]',
- 'debug[3]':'Gyro 2 Raw [pitch]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DUAL_GYRO_COMBINED': {
- 'debug[all]':'Debug Dual Combined',
- 'debug[0]':'Not Used',
- 'debug[1]':'Gyro Filtered [roll]',
- 'debug[2]':'Gyro Filtered [pitch]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DUAL_GYRO_DIFF': {
- 'debug[all]':'Debug Dual Gyro Diff',
- 'debug[0]':'Gyro Diff [roll]',
- 'debug[1]':'Gyro Diff [pitch]',
- 'debug[2]':'Gyro Diff [yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'MAX7456_SIGNAL' : {
- 'debug[all]':'Max7456 Signal',
- 'debug[0]':'Mode Reg',
- 'debug[1]':'Sense',
- 'debug[2]':'ReInit',
- 'debug[3]':'Rows',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'MAX7456_SPICLOCK' : {
- 'debug[all]':'Max7456 SPI Clock',
- 'debug[0]':'Overclock',
- 'debug[1]':'DevType',
- 'debug[2]':'Divisor',
- 'debug[3]':'not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'SBUS' : {
- 'debug[all]':'SBus Rx',
- 'debug[0]':'Frame Flags',
- 'debug[1]':'State Flags',
- 'debug[2]':'Frame Time',
- 'debug[3]':'not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FPORT' : {
- 'debug[all]':'FPort Rx',
- 'debug[0]':'Frame Interval',
- 'debug[1]':'Frame Errors',
- 'debug[2]':'Last Error',
- 'debug[3]':'Telemetry Interval',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RANGEFINDER' : {
- 'debug[all]':'Rangefinder',
- 'debug[0]':'not used',
- 'debug[1]':'Raw Altitude',
- 'debug[2]':'Calc Altituded',
- 'debug[3]':'SNR',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RANGEFINDER_QUALITY' : {
- 'debug[all]':'Rangefinder Quality',
- 'debug[0]':'Raw Altitude',
- 'debug[1]':'SNR Threshold Reached',
- 'debug[2]':'Dyn Distance Threshold',
- 'debug[3]':'Is Surface Altitude Valid',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'LIDAR_TF' : {
- 'debug[all]':'Lidar TF',
- 'debug[0]':'Distance',
- 'debug[1]':'Strength',
- 'debug[2]':'TF Frame (4)',
- 'debug[3]':'TF Frame (5)',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ADC_INTERNAL' : {
- 'debug[all]':'ADC Internal',
- 'debug[0]':'Core Temp',
- 'debug[1]':'VRef Internal Sample',
- 'debug[2]':'Temp Sensor Sample',
- 'debug[3]':'Vref mV',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RUNAWAY_TAKEOFF' : {
- 'debug[all]':'Runaway Takeoff',
- 'debug[0]':'Enabled',
- 'debug[1]':'Activating Delay',
- 'debug[2]':'Deactivating Delay',
- 'debug[3]':'Deactivating Time',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'CURRENT_SENSOR' : {
- 'debug[all]':'Current Sensor',
- 'debug[0]':'milliVolts',
- 'debug[1]':'centiAmps',
- 'debug[2]':'Amps Latest',
- 'debug[3]':'mAh Drawn',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'USB' : {
- 'debug[all]':'USB',
- 'debug[0]':'Cable In',
- 'debug[1]':'VCP Connected',
- 'debug[2]':'not used',
- 'debug[3]':'not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'SMART AUDIO' : {
- 'debug[all]':'Smart Audio VTx',
- 'debug[0]':'Device + Version',
- 'debug[1]':'Channel',
- 'debug[2]':'Frequency',
- 'debug[3]':'Power',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RTH' : {
- 'debug[all]':'RTH',
- 'debug[0]':'Rescue Throttle',
- 'debug[1]':'Rescue Angle',
- 'debug[2]':'Altitude Adjustment',
- 'debug[3]':'Rescue State',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ITERM_RELAX' : {
- 'debug[all]':'I-term Relax',
- 'debug[0]':'Setpoint HPF [roll]',
- 'debug[1]':'I Relax Factor [roll]',
- 'debug[2]':'Relaxed I Error [roll]',
- 'debug[3]':'Axis Error [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ACRO_TRAINER' : {
- 'debug[all]':'Acro Trainer (a_t_axis)',
- 'debug[0]':'Current Angle * 10 [deg]',
- 'debug[1]':'Axis State',
- 'debug[2]':'Correction amount',
- 'debug[3]':'Projected Angle * 10 [deg]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RC_SMOOTHING' : {
- 'debug[all]':'Debug RC Smoothing',
- 'debug[0]':'Raw RC Command',
- 'debug[1]':'Raw RC Derivative',
- 'debug[2]':'Smoothed RC Derivative',
- 'debug[3]':'RX Refresh Rate',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_SIGNAL_LOSS' : {
- 'debug[all]':'Rx Signal Loss',
- 'debug[0]':'Signal Received',
- 'debug[1]':'Failsafe',
- 'debug[2]':'Not used',
- 'debug[3]':'Throttle',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RC_SMOOTHING_RATE' : {
- 'debug[all]':'Debug RC Smoothing Rate',
- 'debug[0]':'Current RX Refresh Rate',
- 'debug[1]':'Training Step Count',
- 'debug[2]':'Average RX Refresh Rate',
- 'debug[3]':'Sampling State',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'ANTI_GRAVITY' : {
- 'debug[all]':'I-term Relax',
- 'debug[0]':'Base I gain * 1000',
- 'debug[1]':'Final I gain * 1000',
- 'debug[2]':'P gain [roll] * 1000',
- 'debug[3]':'P gain [pitch] * 1000',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DYN_LPF' : {
- 'debug[all]':'Debug Dyn LPF',
- 'debug[0]':'Gyro Scaled [dbg-axis]',
- 'debug[1]':'Notch Center [roll]',
- 'debug[2]':'Lowpass Cutoff',
- 'debug[3]':'Gyro Pre-Dyn [dbg-axis]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DSHOT_RPM_TELEMETRY' : {
- 'debug[all]':'DShot Telemetry RPM',
- 'debug[0]':'Motor 1 - DShot',
- 'debug[1]':'Motor 2 - DShot',
- 'debug[2]':'Motor 3 - DShot',
- 'debug[3]':'Motor 4 - DShot',
- 'debug[4]':'Motor 5 - DShot',
- 'debug[5]':'Motor 6 - DShot',
- 'debug[6]':'Motor 7 - DShot',
- 'debug[7]':'Motor 8 - DShot',
- },
- 'RPM_FILTER' : {
- 'debug[all]':'RPM Filter',
- 'debug[0]':'Motor 1 - rpmFilter',
- 'debug[1]':'Motor 2 - rpmFilter',
- 'debug[2]':'Motor 3 - rpmFilter',
- 'debug[3]':'Motor 4 - rpmFilter',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'D_MIN' : {
- 'debug[all]':'D_MIN',
- 'debug[0]':'Gyro Factor [roll]',
- 'debug[1]':'Setpoint Factor [roll]',
- 'debug[2]':'Actual D [roll]',
- 'debug[3]':'Actual D [pitch]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'AC_CORRECTION' : {
- 'debug[all]':'AC Correction',
- 'debug[0]':'AC Correction [roll]',
- 'debug[1]':'AC Correction [pitch]',
- 'debug[2]':'AC Correction [yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'AC_ERROR' : {
- 'debug[all]':'AC Error',
- 'debug[0]':'AC Error [roll]',
- 'debug[1]':'AC Error [pitch]',
- 'debug[2]':'AC Error [yaw]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DUAL_GYRO_SCALED' : {
- 'debug[all]':'Dual Gyro Scaled',
- 'debug[0]':'Gyro 1 [roll]',
- 'debug[1]':'Gyro 1 [pitch]',
- 'debug[2]':'Gyro 2 [roll]',
- 'debug[3]':'Gyro 2 [pitch]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DSHOT_RPM_ERRORS' : {
- 'debug[all]':'DSHOT RPM Error',
- 'debug[0]':'DSHOT RPM Error [1]',
- 'debug[1]':'DSHOT RPM Error [2]',
- 'debug[2]':'DSHOT RPM Error [3]',
- 'debug[3]':'DSHOT RPM Error [4]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'CRSF_LINK_STATISTICS_UPLINK' : {
- 'debug[all]':'CRSF Stats Uplink',
- 'debug[0]':'Uplink RSSI 1',
- 'debug[1]':'Uplink RSSI 2',
- 'debug[2]':'Uplink Link Quality',
- 'debug[3]':'RF Mode',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'CRSF_LINK_STATISTICS_PWR' : {
- 'debug[all]':'CRSF Stats Power',
- 'debug[0]':'Antenna',
- 'debug[1]':'SNR',
- 'debug[2]':'TX Power',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'CRSF_LINK_STATISTICS_DOWN' : {
- 'debug[all]':'CRSF Stats Downlink',
- 'debug[0]':'Downlink RSSI',
- 'debug[1]':'Downlink Link Quality',
- 'debug[2]':'Downlink SNR',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'BARO' : {
- 'debug[all]':'Debug Barometer',
- 'debug[0]':'Baro State',
- 'debug[1]':'Baro Temperature',
- 'debug[2]':'Baro Pressure',
- 'debug[3]':'Baro Pressure Sum',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GPS_RESCUE_THROTTLE_PID' : {
- 'debug[all]':'GPS Rescue Throttle PID',
- 'debug[0]':'Throttle P',
- 'debug[1]':'Throttle I',
- 'debug[2]':'Throttle D',
- 'debug[3]':'Z Velocity',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'DYN_IDLE' : {
- 'debug[all]':'Dyn Idle',
- 'debug[0]':'Motor Range Min Inc',
- 'debug[1]':'Target RPS Change Rate',
- 'debug[2]':'Error',
- 'debug[3]':'Min RPM',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FF_LIMIT' : {
- 'debug[all]':'FF Limit',
- 'debug[0]':'FF input [roll]',
- 'debug[1]':'FF input [pitch]',
- 'debug[2]':'FF limited [roll]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FF_INTERPOLATED' : {
- 'debug[all]':'FF Interpolated [roll]',
- 'debug[0]':'Setpoint Delta Impl [roll]',
- 'debug[1]':'Boost amount [roll]',
- 'debug[2]':'Boost amount, clipped [roll]',
- 'debug[3]':'Clip amount [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'BLACKBOX_OUTPUT' : {
- 'debug[all]':'Blackbox Output',
- 'debug[0]':'Blackbox Rate',
- 'debug[1]':'Blackbox Max Rate',
- 'debug[2]':'Dropouts',
- 'debug[3]':'Tx Bytes Free',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GYRO_SAMPLE' : {
- 'debug[all]':'Gyro Sample',
- 'debug[0]':'Before downsampling',
- 'debug[1]':'After downsampling',
- 'debug[2]':'After RPM',
- 'debug[3]':'After all but Dyn Notch',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_TIMING' : {
- 'debug[all]':'Receiver Timing (us)',
- 'debug[0]':'Frame Delta',
- 'debug[1]':'Frame Age',
- 'debug[2]':'not used',
- 'debug[3]':'not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'D_LPF' : {
- 'debug[all]':'D-Term [D_LPF]',
- 'debug[0]':'Unfiltered D [roll]',
- 'debug[1]':'Unfiltered D [pitch]',
- 'debug[2]':'Filtered, with DMax [roll]',
- 'debug[3]':'Filtered, with DMax [pitch]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'VTX_TRAMP' : {
- 'debug[all]':'Tramp VTx',
- 'debug[0]':'Status',
- 'debug[1]':'Reply Code',
- 'debug[2]':'Pit Mode',
- 'debug[3]':'Retry Count',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GHST' : {
- 'debug[all]':'Ghost Rx',
- 'debug[0]':'CRC Error Count',
- 'debug[1]':'Unknown Frame Count',
- 'debug[2]':'RSSI',
- 'debug[3]':'Link Quality',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GHST_MSP' : {
- 'debug[all]':'Ghost MSP',
- 'debug[0]':'MSP Frame Count',
- 'debug[1]':'MSP Frame Counter',
- 'debug[2]':'Not used',
- 'debug[3]':'Not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'SCHEDULER_DETERMINISM' : {
- 'debug[all]':'Scheduler Determinism',
- 'debug[0]':'Cycle Start time',
- 'debug[1]':'ID of Late Task',
- 'debug[2]':'Task Delay Time',
- 'debug[3]':'Gyro Clock Skew',
- 'debug[4]':'Minimum Gyro period in 100th of a us',
- 'debug[5]':'Maximum Gyro period in 100th of a us',
- 'debug[6]':'Span of Gyro period in 100th of a us',
- 'debug[7]':'Gyro cycle deviation in 100th of a us',
- },
- 'TIMING_ACCURACY' : {
- 'debug[all]':'Timing Accuracy',
- 'debug[0]':'CPU Busy',
- 'debug[1]':'Late Tasks per second',
- 'debug[2]':'Total delay in last second',
- 'debug[3]':'Total Tasks per second',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_EXPRESSLRS_SPI' : {
- 'debug[all]':'ExpressLRS SPI Rx',
- 'debug[0]':'Lost Connection Count',
- 'debug[1]':'RSSI',
- 'debug[2]':'SNR',
- 'debug[3]':'Uplink LQ',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_EXPRESSLRS_PHASELOCK' : {
- 'debug[all]':'ExpressLRS SPI Phaselock',
- 'debug[0]':'Phase offset',
- 'debug[1]':'Filtered phase offset',
- 'debug[2]':'Frequency Offset',
- 'debug[3]':'Phase Shift',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'RX_STATE_TIME' : {
- 'debug[all]':'Rx State Time',
- 'debug[0]':'Time 0',
- 'debug[1]':'Time 1',
- 'debug[2]':'Time 2',
- 'debug[3]':'Time 3',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GPS_RESCUE_VELOCITY' : {
- 'debug[all]':'GPS Rescue Velocity',
- 'debug[0]':'Velocity P',
- 'debug[1]':'Velocity D',
- 'debug[2]':'Velocity to Home',
- 'debug[3]':'Target Velocity',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GPS_RESCUE_HEADING' : {
- 'debug[all]':'GPS Rescue Heading',
- 'debug[0]':'Ground Speed',
- 'debug[1]':'GPS Heading',
- 'debug[2]':'IMU Attitude',
- 'debug[3]':'Angle to home',
- 'debug[4]':'magYaw',
- 'debug[5]':'Roll MixAtt',
- 'debug[6]':'Roll Added',
- 'debug[7]':'Rescue Yaw Rate',
- },
- 'GPS_RESCUE_TRACKING' : {
- 'debug[all]':'GPS Rescue Tracking',
- 'debug[0]':'Velocity to home',
- 'debug[1]':'Target velocity',
- 'debug[2]':'Altitude',
- 'debug[3]':'Target altitude',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GPS_CONNECTION' : {
- 'debug[all]':'GPS Connection',
- 'debug[0]':'Nav Model',
- 'debug[1]':'GPS Nav interval',
- 'debug[2]':'Task timer',
- 'debug[3]':'Baud Rate / FC interval',
- 'debug[4]':'State*100 +SubState',
- 'debug[5]':'ExecuteTime',
- 'debug[6]':'Ack State',
- 'debug[7]':'Rx buffer size',
- },
- 'ATTITUDE' : {
- 'debug[all]':'Attitude',
- 'debug[0]':'IMU Gain',
- 'debug[1]':'EZ_EF',
- 'debug[2]':'GroundSpeedError',
- 'debug[3]':'VelocityFactor',
- },
- 'VTX_MSP' : {
- 'debug[all]': 'VTX MSP',
- 'debug[0]': 'packetCounter',
- 'debug[1]': 'isCrsfPortConfig',
- 'debug[2]': 'isLowPowerDisarmed',
- 'debug[3]': 'mspTelemetryDescriptor',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'GPS_DOP' : {
- 'debug[all]': 'GPS Dilution of Precision',
- 'debug[0]': 'Number of Satellites',
- 'debug[1]': 'pDOP (positional - 3D)',
- 'debug[2]': 'hDOP (horizontal - 2D)',
- 'debug[3]': 'vDOP (vertical - 1D)',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- },
- 'FAILSAFE' : {
- 'debug[all]': 'Failsafe',
- 'debug[0]': 'Failsafe Phase switch',
- 'debug[1]': 'Failsafe State',
- 'debug[2]': 'Receiving data from Rx',
- 'debug[3]': 'Failsafe Phase',
- },
- 'GYRO_CALIBRATION' : {
- 'debug[all]': 'Gyro Calibration',
- 'debug[0]': 'Gyro Calibration X',
- 'debug[1]': 'Gyro Calibration Y',
- 'debug[2]': 'Gyro Calibration Z',
- 'debug[3]': 'Calibration Cycles remaining',
- },
- 'ANGLE_MODE' : {
- 'debug[all]': 'Angle Mode',
- 'debug[0]': 'Target Angle',
- 'debug[1]': 'Error P correction',
- 'debug[2]': 'Feedforward correction',
- 'debug[3]': 'Angle Achieved',
- },
- 'ANGLE_TARGET' : {
- 'debug[all]': 'Angle Target',
- 'debug[0]': 'Angle Target',
- 'debug[1]': 'Sin Angle',
- 'debug[2]': 'Current PID Setpoint',
- 'debug[3]': 'Angle Current',
- },
- 'CURRENT_ANGLE' : {
- 'debug[all]': 'Current Angle',
- 'debug[0]': 'Current Angle X',
- 'debug[1]': 'Current Angle Y',
- 'debug[2]': 'Current Angle Z',
- },
- 'DSHOT_TELEMETRY_COUNTS' : {
- 'debug[all]': 'DShot Telemetry Counts',
- 'debug[0]': 'DShot Telemetry Debug[0] + 1',
- 'debug[1]': 'DShot Telemetry Debug[1] + 1',
- 'debug[2]': 'DShot Telemetry Debug[2] + 1',
- 'debug[3]': 'Preamble Skip',
- },
- 'RPM_LIMIT' : {
- 'debug[all]': 'RPM Limit',
- 'debug[0]': 'Average RPM',
- 'debug[1]': 'Average RPM (unsmoothed)',
- 'debug[2]': 'RPM Limit throttle scale',
- 'debug[3]': 'Throttle',
- 'debug[4]': 'Error',
- 'debug[5]': 'Proportional',
- 'debug[6]': 'Integral',
- 'debug[7]': 'Derivative',
- },
- 'RC_STATS' : {
- 'debug[all]': 'RC Stats',
- 'debug[0]': 'Average Throttle',
- },
- 'MAG_CALIB' : {
- 'debug[all]': 'Mag Calibration',
- 'debug[0]': 'Mag X',
- 'debug[1]': 'Mag Y',
- 'debug[2]': 'Mag Z',
- 'debug[3]': 'Field Strength',
- 'debug[4]': 'Estimated Mag Bias X',
- 'debug[5]': 'Estimated Mag Bias Y',
- 'debug[6]': 'Estimated Mag Bias Z',
- 'debug[7]': 'Lambda',
- },
- 'MAG_TASK_RATE' : {
- 'debug[all]': 'Mag Task Rate',
- 'debug[0]': 'Task Rate (Hz)',
- 'debug[1]': 'Actual Data Rate (Hz)',
- 'debug[2]': 'Data Interval (Us)',
- 'debug[3]': 'Execute Time (Us)',
- 'debug[4]': 'Bus Busy',
- 'debug[5]': 'Read State',
- 'debug[6]': 'Task Time (Us)',
- },
- 'EZLANDING' : {
- 'debug[all]': 'EZ Landing',
- 'debug[0]': 'EZ Land Factor',
- 'debug[1]': 'Adjusted Throttle',
- 'debug[2]': 'Upper Limit',
- 'debug[3]': 'EZ Land Limit',
- 'debug[4]': 'Stick Limit',
- 'debug[5]': 'Speed Limit',
- },
+ NONE: {
+ "debug[all]": "Debug [all]",
+ "debug[0]": "Debug [0]",
+ "debug[1]": "Debug [1]",
+ "debug[2]": "Debug [2]",
+ "debug[3]": "Debug [3]",
+ "debug[4]": "Debug [4]",
+ "debug[5]": "Debug [5]",
+ "debug[6]": "Debug [6]",
+ "debug[7]": "Debug [7]",
+ },
+ CYCLETIME: {
+ "debug[all]": "Debug Cycle Time",
+ "debug[0]": "Cycle Time",
+ "debug[1]": "CPU Load",
+ "debug[2]": "Motor Update",
+ "debug[3]": "Motor Deviation",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ BATTERY: {
+ "debug[all]": "Debug Battery",
+ "debug[0]": "Battery Volt ADC",
+ "debug[1]": "Battery Volt",
+ "debug[2]": "Not Used",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GYRO: {
+ "debug[all]": "Debug Gyro",
+ "debug[0]": "Gyro Raw [X]",
+ "debug[1]": "Gyro Raw [Y]",
+ "debug[2]": "Gyro Raw [Z]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GYRO_FILTERED: {
+ "debug[all]": "Debug Gyro Filtered",
+ "debug[0]": "Gyro Filtered [X]",
+ "debug[1]": "Gyro Filtered [Y]",
+ "debug[2]": "Gyro Filtered [Z]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ACCELEROMETER: {
+ "debug[all]": "Debug Accel.",
+ "debug[0]": "Accel. Raw [X]",
+ "debug[1]": "Accel. Raw [Y]",
+ "debug[2]": "Accel. Raw [Z]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ MIXER: {
+ "debug[all]": "Debug Mixer",
+ "debug[0]": "Roll-Pitch-Yaw Mix [0]",
+ "debug[1]": "Roll-Pitch-Yaw Mix [1]",
+ "debug[2]": "Roll-Pitch-Yaw Mix [2]",
+ "debug[3]": "Roll-Pitch-Yaw Mix [3]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ PIDLOOP: {
+ "debug[all]": "Debug PID",
+ "debug[0]": "Wait Time",
+ "debug[1]": "Sub Update Time",
+ "debug[2]": "PID Update Time",
+ "debug[3]": "Motor Update Time",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ NOTCH: {
+ "debug[all]": "Debug Notch",
+ "debug[0]": "Gyro Pre-Notch [roll]",
+ "debug[1]": "Gyro Pre-Notch [pitch]",
+ "debug[2]": "Gyro Pre-Notch [yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GYRO_SCALED: {
+ "debug[all]": "Debug Gyro Scaled",
+ "debug[0]": "Gyro Scaled [roll]",
+ "debug[1]": "Gyro Scaled [pitch]",
+ "debug[2]": "Gyro Scaled [yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RC_INTERPOLATION: {
+ "debug[all]": "Debug RC Interpolation",
+ "debug[0]": "Raw RC Command [roll]",
+ "debug[1]": "Current RX Refresh Rate",
+ "debug[2]": "Interpolation Step Count",
+ "debug[3]": "RC Setpoint [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DTERM_FILTER: {
+ "debug[all]": "Debug Filter",
+ "debug[0]": "DTerm Filter [roll]",
+ "debug[1]": "DTerm Filter [pitch]",
+ "debug[2]": "Not Used",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ANGLERATE: {
+ "debug[all]": "Debug Angle Rate",
+ "debug[0]": "Angle Rate[roll]",
+ "debug[1]": "Angle Rate[pitch]",
+ "debug[2]": "Angle Rate[yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ESC_SENSOR: {
+ "debug[all]": "ESC Sensor",
+ "debug[0]": "Motor Index",
+ "debug[1]": "Timeouts",
+ "debug[2]": "CNC errors",
+ "debug[3]": "Data age",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ SCHEDULER: {
+ "debug[all]": "Scheduler",
+ "debug[0]": "Not Used",
+ "debug[1]": "Not Used",
+ "debug[2]": "Schedule Time",
+ "debug[3]": "Function Exec Time",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ STACK: {
+ "debug[all]": "Stack",
+ "debug[0]": "Stack High Mem",
+ "debug[1]": "Stack Low Mem",
+ "debug[2]": "Stack Current",
+ "debug[3]": "Stack p",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ESC_SENSOR_RPM: {
+ "debug[all]": "ESC Sensor RPM",
+ "debug[0]": "Motor 1",
+ "debug[1]": "Motor 2",
+ "debug[2]": "Motor 3",
+ "debug[3]": "Motor 4",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ESC_SENSOR_TMP: {
+ "debug[all]": "ESC Sensor Temp",
+ "debug[0]": "Motor 1",
+ "debug[1]": "Motor 2",
+ "debug[2]": "Motor 3",
+ "debug[3]": "Motor 4",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ALTITUDE: {
+ "debug[all]": "Altitude",
+ "debug[0]": "GPS Trust * 100",
+ "debug[1]": "Baro Altitude",
+ "debug[2]": "GPS Altitude",
+ "debug[3]": "Vario",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FFT: {
+ "debug[all]": "Debug FFT",
+ "debug[0]": "Gyro Scaled [dbg-axis]",
+ "debug[1]": "Gyro Pre-Dyn [dbg-axis]",
+ "debug[2]": "Gyro Downsampled [roll]",
+ "debug[3]": "FFT Center Index [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FFT_TIME: {
+ "debug[all]": "Debug FFT TIME",
+ "debug[0]": "Active calc step",
+ "debug[1]": "Step duration",
+ "debug[2]": "Additional steps",
+ "debug[3]": "Not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FFT_FREQ: {
+ "debug[all]": "Debug FFT FREQ",
+ "debug[0]": "Center Freq [roll]",
+ "debug[1]": "Center Freq [pitch]",
+ "debug[2]": "Gyro Pre-Dyn [dbg-axis]",
+ "debug[3]": "Gyro Scaled [dbg-axis]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_FRSKY_SPI: {
+ "debug[all]": "FrSky SPI Rx",
+ "debug[0]": "Looptime",
+ "debug[1]": "Packet",
+ "debug[2]": "Missing Packets",
+ "debug[3]": "State",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_SFHSS_SPI: {
+ "debug[all]": "SFHSS SPI Rx",
+ "debug[0]": "State",
+ "debug[1]": "Missing Frame",
+ "debug[2]": "Offset Max",
+ "debug[3]": "Offset Min",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GYRO_RAW: {
+ "debug[all]": "Debug Gyro Raw",
+ "debug[0]": "Gyro Raw [X]",
+ "debug[1]": "Gyro Raw [Y]",
+ "debug[2]": "Gyro Raw [Z]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DUAL_GYRO: {
+ "debug[all]": "Debug Dual Gyro",
+ "debug[0]": "Gyro 1 Filtered [roll]",
+ "debug[1]": "Gyro 1 Filtered [pitch]",
+ "debug[2]": "Gyro 2 Filtered [roll]",
+ "debug[3]": "Gyro 2 Filtered [pitch]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DUAL_GYRO_RAW: {
+ "debug[all]": "Debug Dual Gyro Raw",
+ "debug[0]": "Gyro 1 Raw [roll]",
+ "debug[1]": "Gyro 1 Raw [pitch]",
+ "debug[2]": "Gyro 2 Raw [roll]",
+ "debug[3]": "Gyro 2 Raw [pitch]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DUAL_GYRO_COMBINED: {
+ "debug[all]": "Debug Dual Combined",
+ "debug[0]": "Not Used",
+ "debug[1]": "Gyro Filtered [roll]",
+ "debug[2]": "Gyro Filtered [pitch]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DUAL_GYRO_DIFF: {
+ "debug[all]": "Debug Dual Gyro Diff",
+ "debug[0]": "Gyro Diff [roll]",
+ "debug[1]": "Gyro Diff [pitch]",
+ "debug[2]": "Gyro Diff [yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ MAX7456_SIGNAL: {
+ "debug[all]": "Max7456 Signal",
+ "debug[0]": "Mode Reg",
+ "debug[1]": "Sense",
+ "debug[2]": "ReInit",
+ "debug[3]": "Rows",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ MAX7456_SPICLOCK: {
+ "debug[all]": "Max7456 SPI Clock",
+ "debug[0]": "Overclock",
+ "debug[1]": "DevType",
+ "debug[2]": "Divisor",
+ "debug[3]": "not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ SBUS: {
+ "debug[all]": "SBus Rx",
+ "debug[0]": "Frame Flags",
+ "debug[1]": "State Flags",
+ "debug[2]": "Frame Time",
+ "debug[3]": "not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FPORT: {
+ "debug[all]": "FPort Rx",
+ "debug[0]": "Frame Interval",
+ "debug[1]": "Frame Errors",
+ "debug[2]": "Last Error",
+ "debug[3]": "Telemetry Interval",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RANGEFINDER: {
+ "debug[all]": "Rangefinder",
+ "debug[0]": "not used",
+ "debug[1]": "Raw Altitude",
+ "debug[2]": "Calc Altituded",
+ "debug[3]": "SNR",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RANGEFINDER_QUALITY: {
+ "debug[all]": "Rangefinder Quality",
+ "debug[0]": "Raw Altitude",
+ "debug[1]": "SNR Threshold Reached",
+ "debug[2]": "Dyn Distance Threshold",
+ "debug[3]": "Is Surface Altitude Valid",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ LIDAR_TF: {
+ "debug[all]": "Lidar TF",
+ "debug[0]": "Distance",
+ "debug[1]": "Strength",
+ "debug[2]": "TF Frame (4)",
+ "debug[3]": "TF Frame (5)",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ADC_INTERNAL: {
+ "debug[all]": "ADC Internal",
+ "debug[0]": "Core Temp",
+ "debug[1]": "VRef Internal Sample",
+ "debug[2]": "Temp Sensor Sample",
+ "debug[3]": "Vref mV",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RUNAWAY_TAKEOFF: {
+ "debug[all]": "Runaway Takeoff",
+ "debug[0]": "Enabled",
+ "debug[1]": "Activating Delay",
+ "debug[2]": "Deactivating Delay",
+ "debug[3]": "Deactivating Time",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ CURRENT_SENSOR: {
+ "debug[all]": "Current Sensor",
+ "debug[0]": "milliVolts",
+ "debug[1]": "centiAmps",
+ "debug[2]": "Amps Latest",
+ "debug[3]": "mAh Drawn",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ USB: {
+ "debug[all]": "USB",
+ "debug[0]": "Cable In",
+ "debug[1]": "VCP Connected",
+ "debug[2]": "not used",
+ "debug[3]": "not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ "SMART AUDIO": {
+ "debug[all]": "Smart Audio VTx",
+ "debug[0]": "Device + Version",
+ "debug[1]": "Channel",
+ "debug[2]": "Frequency",
+ "debug[3]": "Power",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RTH: {
+ "debug[all]": "RTH",
+ "debug[0]": "Rescue Throttle",
+ "debug[1]": "Rescue Angle",
+ "debug[2]": "Altitude Adjustment",
+ "debug[3]": "Rescue State",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ITERM_RELAX: {
+ "debug[all]": "I-term Relax",
+ "debug[0]": "Setpoint HPF [roll]",
+ "debug[1]": "I Relax Factor [roll]",
+ "debug[2]": "Relaxed I Error [roll]",
+ "debug[3]": "Axis Error [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ACRO_TRAINER: {
+ "debug[all]": "Acro Trainer (a_t_axis)",
+ "debug[0]": "Current Angle * 10 [deg]",
+ "debug[1]": "Axis State",
+ "debug[2]": "Correction amount",
+ "debug[3]": "Projected Angle * 10 [deg]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RC_SMOOTHING: {
+ "debug[all]": "Debug RC Smoothing",
+ "debug[0]": "Raw RC Command",
+ "debug[1]": "Raw RC Derivative",
+ "debug[2]": "Smoothed RC Derivative",
+ "debug[3]": "RX Refresh Rate",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_SIGNAL_LOSS: {
+ "debug[all]": "Rx Signal Loss",
+ "debug[0]": "Signal Received",
+ "debug[1]": "Failsafe",
+ "debug[2]": "Not used",
+ "debug[3]": "Throttle",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RC_SMOOTHING_RATE: {
+ "debug[all]": "Debug RC Smoothing Rate",
+ "debug[0]": "Current RX Refresh Rate",
+ "debug[1]": "Training Step Count",
+ "debug[2]": "Average RX Refresh Rate",
+ "debug[3]": "Sampling State",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ ANTI_GRAVITY: {
+ "debug[all]": "I-term Relax",
+ "debug[0]": "Base I gain * 1000",
+ "debug[1]": "Final I gain * 1000",
+ "debug[2]": "P gain [roll] * 1000",
+ "debug[3]": "P gain [pitch] * 1000",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DYN_LPF: {
+ "debug[all]": "Debug Dyn LPF",
+ "debug[0]": "Gyro Scaled [dbg-axis]",
+ "debug[1]": "Notch Center [roll]",
+ "debug[2]": "Lowpass Cutoff",
+ "debug[3]": "Gyro Pre-Dyn [dbg-axis]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DSHOT_RPM_TELEMETRY: {
+ "debug[all]": "DShot Telemetry RPM",
+ "debug[0]": "Motor 1 - DShot",
+ "debug[1]": "Motor 2 - DShot",
+ "debug[2]": "Motor 3 - DShot",
+ "debug[3]": "Motor 4 - DShot",
+ "debug[4]": "Motor 5 - DShot",
+ "debug[5]": "Motor 6 - DShot",
+ "debug[6]": "Motor 7 - DShot",
+ "debug[7]": "Motor 8 - DShot",
+ },
+ RPM_FILTER: {
+ "debug[all]": "RPM Filter",
+ "debug[0]": "Motor 1 - rpmFilter",
+ "debug[1]": "Motor 2 - rpmFilter",
+ "debug[2]": "Motor 3 - rpmFilter",
+ "debug[3]": "Motor 4 - rpmFilter",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ D_MAX: {
+ "debug[all]": "D_MAX",
+ "debug[0]": "Gyro Factor [roll]",
+ "debug[1]": "Setpoint Factor [roll]",
+ "debug[2]": "Actual D [roll]",
+ "debug[3]": "Actual D [pitch]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ AC_CORRECTION: {
+ "debug[all]": "AC Correction",
+ "debug[0]": "AC Correction [roll]",
+ "debug[1]": "AC Correction [pitch]",
+ "debug[2]": "AC Correction [yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ AC_ERROR: {
+ "debug[all]": "AC Error",
+ "debug[0]": "AC Error [roll]",
+ "debug[1]": "AC Error [pitch]",
+ "debug[2]": "AC Error [yaw]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DUAL_GYRO_SCALED: {
+ "debug[all]": "Dual Gyro Scaled",
+ "debug[0]": "Gyro 1 [roll]",
+ "debug[1]": "Gyro 1 [pitch]",
+ "debug[2]": "Gyro 2 [roll]",
+ "debug[3]": "Gyro 2 [pitch]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DSHOT_RPM_ERRORS: {
+ "debug[all]": "DSHOT RPM Error",
+ "debug[0]": "DSHOT RPM Error [1]",
+ "debug[1]": "DSHOT RPM Error [2]",
+ "debug[2]": "DSHOT RPM Error [3]",
+ "debug[3]": "DSHOT RPM Error [4]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ CRSF_LINK_STATISTICS_UPLINK: {
+ "debug[all]": "CRSF Stats Uplink",
+ "debug[0]": "Uplink RSSI 1",
+ "debug[1]": "Uplink RSSI 2",
+ "debug[2]": "Uplink Link Quality",
+ "debug[3]": "RF Mode",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ CRSF_LINK_STATISTICS_PWR: {
+ "debug[all]": "CRSF Stats Power",
+ "debug[0]": "Antenna",
+ "debug[1]": "SNR",
+ "debug[2]": "TX Power",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ CRSF_LINK_STATISTICS_DOWN: {
+ "debug[all]": "CRSF Stats Downlink",
+ "debug[0]": "Downlink RSSI",
+ "debug[1]": "Downlink Link Quality",
+ "debug[2]": "Downlink SNR",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ BARO: {
+ "debug[all]": "Debug Barometer",
+ "debug[0]": "Baro State",
+ "debug[1]": "Baro Temperature",
+ "debug[2]": "Baro Pressure",
+ "debug[3]": "Baro Pressure Sum",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GPS_RESCUE_THROTTLE_PID: {
+ "debug[all]": "GPS Rescue Throttle PID",
+ "debug[0]": "Throttle P",
+ "debug[1]": "Throttle I",
+ "debug[2]": "Throttle D",
+ "debug[3]": "Z Velocity",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ DYN_IDLE: {
+ "debug[all]": "Dyn Idle",
+ "debug[0]": "Motor Range Min Inc",
+ "debug[1]": "Target RPS Change Rate",
+ "debug[2]": "Error",
+ "debug[3]": "Min RPM",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FF_LIMIT: {
+ "debug[all]": "FF Limit",
+ "debug[0]": "FF input [roll]",
+ "debug[1]": "FF input [pitch]",
+ "debug[2]": "FF limited [roll]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FF_INTERPOLATED: {
+ "debug[all]": "FF Interpolated [roll]",
+ "debug[0]": "Setpoint Delta Impl [roll]",
+ "debug[1]": "Boost amount [roll]",
+ "debug[2]": "Boost amount, clipped [roll]",
+ "debug[3]": "Clip amount [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ BLACKBOX_OUTPUT: {
+ "debug[all]": "Blackbox Output",
+ "debug[0]": "Blackbox Rate",
+ "debug[1]": "Blackbox Max Rate",
+ "debug[2]": "Dropouts",
+ "debug[3]": "Tx Bytes Free",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GYRO_SAMPLE: {
+ "debug[all]": "Gyro Sample",
+ "debug[0]": "Before downsampling",
+ "debug[1]": "After downsampling",
+ "debug[2]": "After RPM",
+ "debug[3]": "After all but Dyn Notch",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_TIMING: {
+ "debug[all]": "Receiver Timing (us)",
+ "debug[0]": "Frame Delta",
+ "debug[1]": "Frame Age",
+ "debug[2]": "not used",
+ "debug[3]": "not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ D_LPF: {
+ "debug[all]": "D-Term [D_LPF]",
+ "debug[0]": "Unfiltered D [roll]",
+ "debug[1]": "Unfiltered D [pitch]",
+ "debug[2]": "Filtered, with DMax [roll]",
+ "debug[3]": "Filtered, with DMax [pitch]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ VTX_TRAMP: {
+ "debug[all]": "Tramp VTx",
+ "debug[0]": "Status",
+ "debug[1]": "Reply Code",
+ "debug[2]": "Pit Mode",
+ "debug[3]": "Retry Count",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GHST: {
+ "debug[all]": "Ghost Rx",
+ "debug[0]": "CRC Error Count",
+ "debug[1]": "Unknown Frame Count",
+ "debug[2]": "RSSI",
+ "debug[3]": "Link Quality",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GHST_MSP: {
+ "debug[all]": "Ghost MSP",
+ "debug[0]": "MSP Frame Count",
+ "debug[1]": "MSP Frame Counter",
+ "debug[2]": "Not used",
+ "debug[3]": "Not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ SCHEDULER_DETERMINISM: {
+ "debug[all]": "Scheduler Determinism",
+ "debug[0]": "Cycle Start time",
+ "debug[1]": "ID of Late Task",
+ "debug[2]": "Task Delay Time",
+ "debug[3]": "Gyro Clock Skew",
+ "debug[4]": "Minimum Gyro period in 100th of a us",
+ "debug[5]": "Maximum Gyro period in 100th of a us",
+ "debug[6]": "Span of Gyro period in 100th of a us",
+ "debug[7]": "Gyro cycle deviation in 100th of a us",
+ },
+ TIMING_ACCURACY: {
+ "debug[all]": "Timing Accuracy",
+ "debug[0]": "CPU Busy",
+ "debug[1]": "Late Tasks per second",
+ "debug[2]": "Total delay in last second",
+ "debug[3]": "Total Tasks per second",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_EXPRESSLRS_SPI: {
+ "debug[all]": "ExpressLRS SPI Rx",
+ "debug[0]": "Lost Connection Count",
+ "debug[1]": "RSSI",
+ "debug[2]": "SNR",
+ "debug[3]": "Uplink LQ",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_EXPRESSLRS_PHASELOCK: {
+ "debug[all]": "ExpressLRS SPI Phaselock",
+ "debug[0]": "Phase offset",
+ "debug[1]": "Filtered phase offset",
+ "debug[2]": "Frequency Offset",
+ "debug[3]": "Phase Shift",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ RX_STATE_TIME: {
+ "debug[all]": "Rx State Time",
+ "debug[0]": "Time 0",
+ "debug[1]": "Time 1",
+ "debug[2]": "Time 2",
+ "debug[3]": "Time 3",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GPS_RESCUE_VELOCITY: {
+ "debug[all]": "GPS Rescue Velocity",
+ "debug[0]": "Velocity P",
+ "debug[1]": "Velocity D",
+ "debug[2]": "Velocity to Home",
+ "debug[3]": "Target Velocity",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GPS_RESCUE_HEADING: {
+ "debug[all]": "GPS Rescue Heading",
+ "debug[0]": "Ground Speed",
+ "debug[1]": "GPS Heading",
+ "debug[2]": "IMU Attitude",
+ "debug[3]": "Angle to home",
+ "debug[4]": "magYaw",
+ "debug[5]": "Roll MixAtt",
+ "debug[6]": "Roll Added",
+ "debug[7]": "Rescue Yaw Rate",
+ },
+ GPS_RESCUE_TRACKING: {
+ "debug[all]": "GPS Rescue Tracking",
+ "debug[0]": "Velocity to home",
+ "debug[1]": "Target velocity",
+ "debug[2]": "Altitude",
+ "debug[3]": "Target altitude",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GPS_CONNECTION: {
+ "debug[all]": "GPS Connection",
+ "debug[0]": "Nav Model",
+ "debug[1]": "GPS Nav interval",
+ "debug[2]": "Task timer",
+ "debug[3]": "Baud Rate / FC interval",
+ "debug[4]": "State*100 +SubState",
+ "debug[5]": "ExecuteTime",
+ "debug[6]": "Ack State",
+ "debug[7]": "Rx buffer size",
+ },
+ ATTITUDE: {
+ "debug[all]": "Attitude",
+ "debug[0]": "IMU Gain",
+ "debug[1]": "EZ_EF",
+ "debug[2]": "GroundSpeedError",
+ "debug[3]": "VelocityFactor",
+ },
+ VTX_MSP: {
+ "debug[all]": "VTX MSP",
+ "debug[0]": "packetCounter",
+ "debug[1]": "isCrsfPortConfig",
+ "debug[2]": "isLowPowerDisarmed",
+ "debug[3]": "mspTelemetryDescriptor",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ GPS_DOP: {
+ "debug[all]": "GPS Dilution of Precision",
+ "debug[0]": "Number of Satellites",
+ "debug[1]": "pDOP (positional - 3D)",
+ "debug[2]": "hDOP (horizontal - 2D)",
+ "debug[3]": "vDOP (vertical - 1D)",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ },
+ FAILSAFE: {
+ "debug[all]": "Failsafe",
+ "debug[0]": "Failsafe Phase switch",
+ "debug[1]": "Failsafe State",
+ "debug[2]": "Receiving data from Rx",
+ "debug[3]": "Failsafe Phase",
+ },
+ GYRO_CALIBRATION: {
+ "debug[all]": "Gyro Calibration",
+ "debug[0]": "Gyro Calibration X",
+ "debug[1]": "Gyro Calibration Y",
+ "debug[2]": "Gyro Calibration Z",
+ "debug[3]": "Calibration Cycles remaining",
+ },
+ ANGLE_MODE: {
+ "debug[all]": "Angle Mode",
+ "debug[0]": "Target Angle",
+ "debug[1]": "Error P correction",
+ "debug[2]": "Feedforward correction",
+ "debug[3]": "Angle Achieved",
+ },
+ ANGLE_TARGET: {
+ "debug[all]": "Angle Target",
+ "debug[0]": "Angle Target",
+ "debug[1]": "Sin Angle",
+ "debug[2]": "Current PID Setpoint",
+ "debug[3]": "Angle Current",
+ },
+ CURRENT_ANGLE: {
+ "debug[all]": "Current Angle",
+ "debug[0]": "Current Angle X",
+ "debug[1]": "Current Angle Y",
+ "debug[2]": "Current Angle Z",
+ },
+ DSHOT_TELEMETRY_COUNTS: {
+ "debug[all]": "DShot Telemetry Counts",
+ "debug[0]": "DShot Telemetry Debug[0] + 1",
+ "debug[1]": "DShot Telemetry Debug[1] + 1",
+ "debug[2]": "DShot Telemetry Debug[2] + 1",
+ "debug[3]": "Preamble Skip",
+ },
+ RPM_LIMIT: {
+ "debug[all]": "RPM Limit",
+ "debug[0]": "Average RPM",
+ "debug[1]": "Average RPM (unsmoothed)",
+ "debug[2]": "RPM Limit throttle scale",
+ "debug[3]": "Throttle",
+ "debug[4]": "Error",
+ "debug[5]": "Proportional",
+ "debug[6]": "Integral",
+ "debug[7]": "Derivative",
+ },
+ RC_STATS: {
+ "debug[all]": "RC Stats",
+ "debug[0]": "Average Throttle",
+ },
+ MAG_CALIB: {
+ "debug[all]": "Mag Calibration",
+ "debug[0]": "Mag X",
+ "debug[1]": "Mag Y",
+ "debug[2]": "Mag Z",
+ "debug[3]": "Field Strength",
+ "debug[4]": "Estimated Mag Bias X",
+ "debug[5]": "Estimated Mag Bias Y",
+ "debug[6]": "Estimated Mag Bias Z",
+ "debug[7]": "Lambda",
+ },
+ MAG_TASK_RATE: {
+ "debug[all]": "Mag Task Rate",
+ "debug[0]": "Task Rate (Hz)",
+ "debug[1]": "Actual Data Rate (Hz)",
+ "debug[2]": "Data Interval (Us)",
+ "debug[3]": "Execute Time (Us)",
+ "debug[4]": "Bus Busy",
+ "debug[5]": "Read State",
+ "debug[6]": "Task Time (Us)",
+ },
+ EZLANDING: {
+ "debug[all]": "EZ Landing",
+ "debug[0]": "EZ Land Factor",
+ "debug[1]": "Adjusted Throttle",
+ "debug[2]": "Upper Limit",
+ "debug[3]": "EZ Land Limit",
+ "debug[4]": "Stick Limit",
+ "debug[5]": "Speed Limit",
+ },
};
let DEBUG_FRIENDLY_FIELD_NAMES = null;
-FlightLogFieldPresenter.adjustDebugDefsList = function(firmwareType, firmwareVersion) {
-
- DEBUG_FRIENDLY_FIELD_NAMES = {...DEBUG_FRIENDLY_FIELD_NAMES_INITIAL};
-
- if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT) {
-
- if (semver.gte(firmwareVersion, '4.1.0')) {
- DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = {
- 'debug[all]':'Feedforward [roll]',
- 'debug[0]':'Setpoint Delta [roll]',
- 'debug[1]':'Boost [roll]',
- 'debug[2]':'Boost, clipped [roll]',
- 'debug[3]':'Duplicate Counter [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.FF_LIMIT = {
- 'debug[all]':'Feedforward Limit [roll]',
- 'debug[0]':'FF limit input [roll]',
- 'debug[1]':'FF limit input [pitch]',
- 'debug[2]':'FF limited [roll]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- }
-
- if (semver.gte(firmwareVersion, '4.2.0')) {
- DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = {
- 'debug[all]':'Feedforward [roll]',
- 'debug[0]':'Setpoint Delta [roll]',
- 'debug[1]':'Acceleration [roll]',
- 'debug[2]':'Acceleration, clipped [roll]',
- 'debug[3]':'Duplicate Counter [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- }
-
- if (semver.gte(firmwareVersion, '4.3.0')) {
- DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD = {
- 'debug[all]':'Feedforward [roll]',
- 'debug[0]':'Setpoint, un-smoothed [roll]',
- 'debug[1]':'Delta, smoothed [roll]',
- 'debug[2]':'Boost, smoothed [roll]',
- 'debug[3]':'rcCommand Delta [roll]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD_LIMIT = {
- 'debug[all]':'Feedforward Limit [roll]',
- 'debug[0]':'Feedforward input [roll]',
- 'debug[1]':'Feedforward input [pitch]',
- 'debug[2]':'Feedforward limited [roll]',
- 'debug[3]':'Not Used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.DYN_IDLE = {
- 'debug[all]':'Dyn Idle',
- 'debug[0]':'Dyn Idle P [roll]',
- 'debug[1]':'Dyn Idle I [roll]',
- 'debug[2]':'Dyn Idle D [roll]',
- 'debug[3]':'Min RPM',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.FFT = {
- 'debug[all]':'Debug FFT',
- 'debug[0]':'Gyro Pre Dyn Notch [dbg-axis]',
- 'debug[1]':'Gyro Post Dyn Notch [dbg-axis]',
- 'debug[2]':'Gyro Downsampled [dbg-axis]',
- 'debug[3]':'Not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.FFT_TIME = {
- 'debug[all]':'Debug FFT TIME',
- 'debug[0]':'Active calc step',
- 'debug[1]':'Step duration',
- 'debug[2]':'Not used',
- 'debug[3]':'Not used',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.FFT_FREQ = {
- 'debug[all]':'Debug FFT FREQ',
- 'debug[0]':'Notch 1 Center Freq [dbg-axis]',
- 'debug[1]':'Notch 2 Center Freq [dbg-axis]',
- 'debug[2]':'Notch 3 Center Freq [dbg-axis]',
- 'debug[3]':'Gyro Pre Dyn Notch [dbg-axis]',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = {
- 'debug[all]':'GPS Rescue Altitude',
- 'debug[0]':'Throttle P',
- 'debug[1]':'Throttle D',
- 'debug[2]':'Altitude',
- 'debug[3]':'Target Altitude',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- }
-
- if (semver.gte(firmwareVersion, '4.4.0')) {
- DEBUG_FRIENDLY_FIELD_NAMES.BARO = {
- 'debug[all]':'Debug Barometer',
- 'debug[0]':'Baro State',
- 'debug[1]':'Baro Pressure',
- 'debug[2]':'Baro Temperature',
- 'debug[3]':'Baro Altitude',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.RTH = {
- 'debug[all]':'RTH Rescue codes',
- 'debug[0]':'Pitch angle, deg',
- 'debug[1]':'Rescue Phase',
- 'debug[2]':'Failure code',
- 'debug[3]':'Failure timers',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = {
- 'debug[all]':'GPS Rescue throttle PIDs',
- 'debug[0]':'Throttle P',
- 'debug[1]':'Throttle D',
- 'debug[2]':'Altitude',
- 'debug[3]':'Target altitude',
- 'debug[4]':'Not Used',
- 'debug[5]':'Not Used',
- 'debug[6]':'Not Used',
- 'debug[7]':'Not Used',
- };
- }
+FlightLogFieldPresenter.adjustDebugDefsList = function (
+ firmwareType,
+ firmwareVersion
+) {
+ DEBUG_FRIENDLY_FIELD_NAMES = { ...DEBUG_FRIENDLY_FIELD_NAMES_INITIAL };
+
+ if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT) {
+ if (semver.gte(firmwareVersion, "4.1.0")) {
+ DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = {
+ "debug[all]": "Feedforward [roll]",
+ "debug[0]": "Setpoint Delta [roll]",
+ "debug[1]": "Boost [roll]",
+ "debug[2]": "Boost, clipped [roll]",
+ "debug[3]": "Duplicate Counter [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.FF_LIMIT = {
+ "debug[all]": "Feedforward Limit [roll]",
+ "debug[0]": "FF limit input [roll]",
+ "debug[1]": "FF limit input [pitch]",
+ "debug[2]": "FF limited [roll]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
}
-};
-FlightLogFieldPresenter.presentFlags = function(flags, flagNames) {
- let
- printedFlag = false,
- i,
- result = "";
+ if (semver.gte(firmwareVersion, "4.2.0")) {
+ DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = {
+ "debug[all]": "Feedforward [roll]",
+ "debug[0]": "Setpoint Delta [roll]",
+ "debug[1]": "Acceleration [roll]",
+ "debug[2]": "Acceleration, clipped [roll]",
+ "debug[3]": "Duplicate Counter [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ }
- i = 0;
+ if (semver.gte(firmwareVersion, "4.3.0")) {
+ DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD = {
+ "debug[all]": "Feedforward [roll]",
+ "debug[0]": "Setpoint, un-smoothed [roll]",
+ "debug[1]": "Delta, smoothed [roll]",
+ "debug[2]": "Boost, smoothed [roll]",
+ "debug[3]": "rcCommand Delta [roll]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD_LIMIT = {
+ "debug[all]": "Feedforward Limit [roll]",
+ "debug[0]": "Feedforward input [roll]",
+ "debug[1]": "Feedforward input [pitch]",
+ "debug[2]": "Feedforward limited [roll]",
+ "debug[3]": "Not Used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.DYN_IDLE = {
+ "debug[all]": "Dyn Idle",
+ "debug[0]": "Dyn Idle P [roll]",
+ "debug[1]": "Dyn Idle I [roll]",
+ "debug[2]": "Dyn Idle D [roll]",
+ "debug[3]": "Min RPM",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.FFT = {
+ "debug[all]": "Debug FFT",
+ "debug[0]": "Gyro Pre Dyn Notch [dbg-axis]",
+ "debug[1]": "Gyro Post Dyn Notch [dbg-axis]",
+ "debug[2]": "Gyro Downsampled [dbg-axis]",
+ "debug[3]": "Not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.FFT_TIME = {
+ "debug[all]": "Debug FFT TIME",
+ "debug[0]": "Active calc step",
+ "debug[1]": "Step duration",
+ "debug[2]": "Not used",
+ "debug[3]": "Not used",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.FFT_FREQ = {
+ "debug[all]": "Debug FFT FREQ",
+ "debug[0]": "Notch 1 Center Freq [dbg-axis]",
+ "debug[1]": "Notch 2 Center Freq [dbg-axis]",
+ "debug[2]": "Notch 3 Center Freq [dbg-axis]",
+ "debug[3]": "Gyro Pre Dyn Notch [dbg-axis]",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = {
+ "debug[all]": "GPS Rescue Altitude",
+ "debug[0]": "Throttle P",
+ "debug[1]": "Throttle D",
+ "debug[2]": "Altitude",
+ "debug[3]": "Target Altitude",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ }
- while (flags > 0) {
- if ((flags & 1) != 0) {
- if (printedFlag) {
- result += "|";
- } else {
- printedFlag = true;
- }
+ if (semver.gte(firmwareVersion, "4.4.0")) {
+ DEBUG_FRIENDLY_FIELD_NAMES.BARO = {
+ "debug[all]": "Debug Barometer",
+ "debug[0]": "Baro State",
+ "debug[1]": "Baro Pressure",
+ "debug[2]": "Baro Temperature",
+ "debug[3]": "Baro Altitude",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.RTH = {
+ "debug[all]": "RTH Rescue codes",
+ "debug[0]": "Pitch angle, deg",
+ "debug[1]": "Rescue Phase",
+ "debug[2]": "Failure code",
+ "debug[3]": "Failure timers",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = {
+ "debug[all]": "GPS Rescue throttle PIDs",
+ "debug[0]": "Throttle P",
+ "debug[1]": "Throttle D",
+ "debug[2]": "Altitude",
+ "debug[3]": "Target altitude",
+ "debug[4]": "Not Used",
+ "debug[5]": "Not Used",
+ "debug[6]": "Not Used",
+ "debug[7]": "Not Used",
+ };
+ }
- result += flagNames[i];
- }
+ if (semver.gte(firmwareVersion, '4.5.0')) {
+ DEBUG_FRIENDLY_FIELD_NAMES.ATTITUDE = {
+ "debug[all]": "Attitude",
+ "debug[0]": "Roll angle",
+ "debug[1]": "Pitch angle",
+ "debug[2]": "Ground speed factor",
+ "debug[3]": "Heading error",
+ "debug[4]": "Velocity to home",
+ "debug[5]": "Ground speed error ratio",
+ "debug[6]": "Pitch forward angle",
+ "debug[7]": "dcmKp gain",
+ };
+ }
- flags >>= 1;
- i++;
+ if (semver.gte(firmwareVersion, '4.6.0')) {
+ // FFT_FREQ updated in firmware #13750
+ DEBUG_FRIENDLY_FIELD_NAMES.FFT_FREQ = {
+ 'debug[all]':'Debug FFT FREQ',
+ 'debug[0]':'Gyro Pre Dyn Notch [dbg-axis]',
+ 'debug[1]':'Notch 1 Center Freq [dbg-axis]',
+ 'debug[2]':'Notch 2 Center Freq [dbg-axis]',
+ 'debug[3]':'Notch 3 Center Freq [dbg-axis]',
+ 'debug[4]':'Notch 4 Center Freq [dbg-axis]',
+ 'debug[5]':'Notch 5 Center Freq [dbg-axis]',
+ 'debug[6]':'Notch 6 Center Freq [dbg-axis]',
+ 'debug[7]':'Notch 7 Center Freq [dbg-axis]',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.AUTOPILOT_ALTITUDE = {
+ 'debug[all]': 'Autopilot Altitude',
+ 'debug[0]': 'Autopilot Throttle',
+ 'debug[1]': 'Tilt Multiplier',
+ 'debug[2]': 'Zero Altitude cm',
+ 'debug[3]': 'Altitude cm',
+ 'debug[4]': 'Altitude P',
+ 'debug[5]': 'Altitude I',
+ 'debug[6]': 'Altitude D',
+ 'debug[7]': 'Altitude F',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.TPA = {
+ 'debug[all]': 'TPA',
+ 'debug[0]': 'TPA Factor',
+ 'debug[1]': 'TPA Attitude Roll (Wing)',
+ 'debug[2]': 'TPA Attitude Pitch (Wing)',
+ 'debug[3]': 'TPA Calculated Throttle (Wing)',
+ 'debug[4]': 'TPA Speed (Wing)',
+ 'debug[5]': 'TPA Argument (Wing)',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.S_TERM = {
+ 'debug[all]': 'S Term',
+ 'debug[0]': 'S Term [roll]',
+ 'debug[1]': 'S Term [pitch]',
+ 'debug[2]': 'S Term [yaw]',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.SPA = {
+ 'debug[all': 'SPA',
+ 'debug[0]': 'Setpoint PID Attenuation [roll]',
+ 'debug[1]': 'Setpoint PID Attenuation [pitch]',
+ 'debug[2]': 'Setpoint PID Attenuation [yaw]',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.TASK = {
+ 'debug[all]': 'TASK',
+ 'debug[0]': 'Value',
+ 'debug[1]': 'Rate (Hz)',
+ 'debug[2]': 'Max (us)',
+ 'debug[3]': 'Average (us)',
+ 'debug[4]': 'Estimated execution time (us)',
+ 'debug[5]': 'Actual execution time (us)',
+ 'debug[6]': 'Difference estimated vs actual',
+ 'debug[7]': 'Late count',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.GIMBAL = {
+ 'debug[all]': 'Gimbal',
+ 'debug[0]': 'Headtracker Roll',
+ 'debug[1]': 'Headtracker Pitch',
+ 'debug[2]': 'Headtracker Yaw',
+ 'debug[3]': 'Gimbal Roll',
+ 'debug[4]': 'Gimbal Pitch',
+ 'debug[5]': 'Gimbal Yaw',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.WING_SETPOINT = {
+ 'debug[all]': 'Wing Setpoint',
+ 'debug[0]': 'Current Setpoint [roll]',
+ 'debug[1]': 'Adjusted Setpoint [roll]',
+ 'debug[2]': 'Current Setpoint [pitch]',
+ 'debug[3]': 'Adjusted Setpoint [pitch]',
+ 'debug[4]': 'Current Setpoint [yaw]',
+ 'debug[5]': 'Adjusted Setpoint [yaw]',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.OPTICALFLOW = {
+ 'debug[all]': 'Optical Flow',
+ 'debug[0]': 'Quality',
+ 'debug[1]': 'Raw flow rates X',
+ 'debug[2]': 'Raw flow rates Y',
+ 'debug[3]': 'Processed flow rates X',
+ 'debug[4]': 'Processed flow rates Y',
+ 'debug[5]': 'Delta time',
+ };
+ DEBUG_FRIENDLY_FIELD_NAMES.AUTOPILOT_POSITION = {
+ 'debug[all]': 'Autopilot Position',
+ 'debug[0]': 'Distance',
+ 'debug[1]': 'GPS Distance',
+ 'debug[2]': 'PID Sum EF',
+ 'debug[3]': 'Angle',
+ 'debug[4]': 'pidP',
+ 'debug[5]': 'pidI',
+ 'debug[6]': 'pidD',
+ 'debug[7]': 'pidA',
+ };
}
+ }
+};
+
+FlightLogFieldPresenter.presentFlags = function (flags, flagNames) {
+ let printedFlag = false,
+ i = 0,
+ result = "";
+
+ while (flags > 0) {
+ if ((flags & 1) != 0) {
+ if (printedFlag) {
+ result += "|";
+ } else {
+ printedFlag = true;
+ }
- if (printedFlag) {
- return result;
- } else {
- return "0"; //No flags set
+ result += flagNames[i];
}
+
+ flags >>= 1;
+ i++;
+ }
+
+ if (printedFlag) {
+ return result;
+ } else {
+ return "0"; //No flags set
+ }
};
// Only list events that have changed, flag with eirer go ON or OFF.
-FlightLogFieldPresenter.presentChangeEvent = function presentChangeEvent(flags, lastFlags, flagNames) {
- let eventState = '';
- let found = false;
-
- for (let i = 0; i < flagNames.length; i++) {
- if ((1 << i) & (flags ^ lastFlags)) { // State Changed
- eventState += '|' + flagNames[i] + ' ' + (((1 << i) & flags) ? 'ON' : 'OFF');
- found = true;
- }
+FlightLogFieldPresenter.presentChangeEvent = function presentChangeEvent(
+ flags,
+ lastFlags,
+ flagNames)
+{
+ let eventState = "";
+ let found = false;
+ const maxModeNumber = 32; // int has 32 bit only! We have not to roll bit shift 1< maxModeNumber) {
+ modesCount = maxModeNumber;
+ }
+ for (let i = 0; i < modesCount; i++) {
+ if ((1 << i) & (flags ^ lastFlags)) {
+ // State Changed
+ eventState += `${found ? "|" : ""}${flagNames[i]} ${(1 << i) & flags ? "ON" : "OFF"}`;
+ found = true;
}
- if (!found) { eventState += ' | ACRO'; } // Catch the state when all flags are off, which is ACRO of course
- return eventState;
+ }
+ if (!found) {
+ eventState += " | ACRO";
+ } // Catch the state when all flags are off, which is ACRO of course
+ return eventState;
};
FlightLogFieldPresenter.presentEnum = function presentEnum(value, enumNames) {
- if (enumNames[value] === undefined) {
- return value;
- }
+ if (enumNames[value] === undefined) {
+ return value;
+ }
- return enumNames[value];
+ return enumNames[value];
};
/**
@@ -1352,579 +1482,1424 @@ FlightLogFieldPresenter.presentEnum = function presentEnum(value, enumNames) {
* @returns String: readable meters in selected unit.
*/
-FlightLogFieldPresenter.decodeCorrectAltitude = function(altitude, altitudeUnits) {
- switch (altitudeUnits) {
- case 1: // Keep it in meters.
- return (altitude).toFixed(2) + " m";
- case 2: // Translate it into feet.
- return (altitude * 3.28).toFixed(2) + " ft";
- }
+FlightLogFieldPresenter.decodeCorrectAltitude = function (
+ altitude,
+ altitudeUnits
+) {
+ switch (altitudeUnits) {
+ case 1: // Keep it in meters.
+ return `${altitude.toFixed(2)} m`;
+ case 2: // Translate it into feet.
+ return `${(altitude * 3.28).toFixed(2)} ft`;
+ }
+};
+
+// Altitude back convertacion function
+FlightLogFieldPresenter.decodeAltitudeLogToChart = function (
+ altitude,
+ altitudeUnits
+) {
+ switch (altitudeUnits) {
+ case 1: // Keep it in meters.
+ return altitude;
+ case 2: // Translate it into feet.
+ return altitude * 3.28;
+ }
};
/**
* Attempt to decode the given raw logged value into something more human readable, or return an empty string if
* no better representation is available.
*
+ * @param flightLog The pointer to FlightLog object
* @param fieldName Name of the field
* @param value Value of the field
*/
-FlightLogFieldPresenter.decodeFieldToFriendly = function(flightLog, fieldName, value, currentFlightMode) {
- if (value === undefined) {
- return "";
- }
-
- const highResolutionScale = (flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0) ? 10 : 1;
- const highResolutionAddPrecision = (flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0) ? 1 : 0;
-
- switch (fieldName) {
- case 'time':
- return formatTime(value / 1000, true);
-
- case 'gyroADC[0]':
- case 'gyroADC[1]':
- case 'gyroADC[2]':
- case 'gyroUnfilt[0]':
- case 'gyroUnfilt[1]':
- case 'gyroUnfilt[2]':
- return flightLog.gyroRawToDegreesPerSecond(value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s";
-
- case 'gyroADCs[0]':
- case 'gyroADCs[1]':
- case 'gyroADCs[2]':
- return value.toFixed(0) + " °/s";
-
- case 'axisError[0]':
- case 'axisError[1]':
- case 'axisError[2]':
- return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s";
-
- case 'rcCommand[0]':
- case 'rcCommand[1]':
- case 'rcCommand[2]':
- return (value / highResolutionScale + 1500).toFixed(highResolutionAddPrecision) + " us";
- case 'rcCommand[3]':
- return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " us";
-
- case 'motor[0]':
- case 'motor[1]':
- case 'motor[2]':
- case 'motor[3]':
- case 'motor[4]':
- case 'motor[5]':
- case 'motor[6]':
- case 'motor[7]':
- return `${flightLog.rcMotorRawToPctPhysical(value).toFixed(2)} %`;
-
- case 'eRPM[0]':
- case 'eRPM[1]':
- case 'eRPM[2]':
- case 'eRPM[3]':
- case 'eRPM[4]':
- case 'eRPM[5]':
- case 'eRPM[6]':
- case 'eRPM[7]':
- let motor_poles = flightLog.getSysConfig()['motor_poles'];
- return (value * 200 / motor_poles).toFixed(0) + " rpm / " + (value * 3.333 / motor_poles).toFixed(1) + ' hz';
-
- case 'rcCommands[0]':
- case 'rcCommands[1]':
- case 'rcCommands[2]':
- return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s";
- case 'rcCommands[3]':
- return value.toFixed(1) + "%";
-
- case 'axisSum[0]':
- case 'axisSum[1]':
- case 'axisSum[2]':
- case 'axisP[0]':
- case 'axisP[1]':
- case 'axisP[2]':
- case 'axisI[0]':
- case 'axisI[1]':
- case 'axisI[2]':
- case 'axisD[0]':
- case 'axisD[1]':
- case 'axisD[2]':
- case 'axisF[0]':
- case 'axisF[1]':
- case 'axisF[2]':
- return flightLog.getPIDPercentage(value).toFixed(1) + " %";
-
- case 'accSmooth[0]':
- case 'accSmooth[1]':
- case 'accSmooth[2]':
- return flightLog.accRawToGs(value).toFixed(2 + highResolutionAddPrecision) + " g";
-
- case 'vbatLatest':
- if (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '4.0.0')) {
- return (value / 100).toFixed(2) + "V" + ", " + (value / 100 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell";
- } else if ((flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.0')) ||
- (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '2.0.0'))) {
- return (value / 10).toFixed(2) + "V" + ", " + (value / 10 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell";
- } else {
- return (flightLog.vbatADCToMillivolts(value) / 1000).toFixed(2) + "V" + ", " + (flightLog.vbatADCToMillivolts(value) / 1000 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell";
- }
-
- case 'amperageLatest':
- if ((flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.7')) ||
- (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '2.0.0'))) {
- return (value / 100).toFixed(2) + "A" + ", " + (value / 100 / flightLog.getNumMotors()).toFixed(2) + " A/motor";
- } else if (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.0')) {
- return (value / 100).toFixed(2) + "A" + ", " + (value / 100 / flightLog.getNumMotors()).toFixed(2) + " A/motor";
- } else {
- return (flightLog.amperageADCToMillivolts(value) / 1000).toFixed(2) + "A" + ", " + (flightLog.amperageADCToMillivolts(value) / 1000 / flightLog.getNumMotors()).toFixed(2) + " A/motor";
- }
-
- case 'heading[0]':
- case 'heading[1]':
- case 'heading[2]':
- return (value / Math.PI * 180).toFixed(1) + "°";
-
- case 'baroAlt':
- return FlightLogFieldPresenter.decodeCorrectAltitude((value/100), userSettings.altitudeUnits);
-
- case 'flightModeFlags':
- return FlightLogFieldPresenter.presentFlags(value, FLIGHT_LOG_FLIGHT_MODE_NAME);
-
- case 'stateFlags':
- return FlightLogFieldPresenter.presentFlags(value, FLIGHT_LOG_FLIGHT_STATE_NAME);
-
- case 'failsafePhase':
- return FlightLogFieldPresenter.presentEnum(value, FLIGHT_LOG_FAILSAFE_PHASE_NAME);
-
- case 'features':
- return FlightLogFieldPresenter.presentEnum(value, FLIGHT_LOG_FEATURES);
-
- case 'rssi':
- return (value / 1024 * 100).toFixed(2) + " %";
-
- //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course
- case 'GPS_numSat':
- return `${value}`;
- case 'GPS_coord[0]':
- case 'GPS_coord[1]':
- return `${(value/10000000).toFixed(5)}`;
- case 'GPS_altitude':
- return FlightLogFieldPresenter.decodeCorrectAltitude((value/10), userSettings.altitudeUnits);
- case 'GPS_speed':
- switch (userSettings.speedUnits) {
- case 1:
- return `${(value/100).toFixed(2)} m/s`;
- case 2:
- return `${((value/100) * 3.6).toFixed(2)} kph`;
- case 3:
- return `${((value/100) * 2.2369).toFixed(2)} mph`;
- }
- case 'GPS_ground_course':
- return `${(value/10).toFixed(1)} °`;
-
- case 'debug[0]':
- case 'debug[1]':
- case 'debug[2]':
- case 'debug[3]':
- case 'debug[4]':
- case 'debug[5]':
- case 'debug[6]':
- case 'debug[7]':
- return FlightLogFieldPresenter.decodeDebugFieldToFriendly(flightLog, fieldName, value, currentFlightMode);
-
- default:
- return "";
- }
+FlightLogFieldPresenter.decodeFieldToFriendly = function (
+ flightLog,
+ fieldName,
+ value,
+ currentFlightMode
+) {
+ if (value === undefined) {
+ return "";
+ }
+
+ const highResolutionScale =
+ flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0 ? 10 : 1;
+ const highResolutionAddPrecision =
+ flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0 ? 1 : 0;
+
+ switch (fieldName) {
+ case "time":
+ return formatTime(value / 1000, true);
+
+ case "gyroADC[0]":
+ case "gyroADC[1]":
+ case "gyroADC[2]":
+ case "gyroUnfilt[0]":
+ case "gyroUnfilt[1]":
+ case "gyroUnfilt[2]":
+ return `${flightLog
+ .gyroRawToDegreesPerSecond(value / highResolutionScale)
+ .toFixed(highResolutionAddPrecision)} °/s`;
+
+ case "gyroADCs[0]":
+ case "gyroADCs[1]":
+ case "gyroADCs[2]":
+ return `${value.toFixed(0)} °/s`;
+
+ case "axisError[0]":
+ case "axisError[1]":
+ case "axisError[2]":
+ return `${(value / highResolutionScale).toFixed(
+ highResolutionAddPrecision
+ )} °/s`;
+
+ case "rcCommand[0]":
+ case "rcCommand[1]":
+ case "rcCommand[2]":
+ return `${(value / highResolutionScale + 1500).toFixed(
+ highResolutionAddPrecision
+ )} us`;
+ case "rcCommand[3]":
+ return `${(value / highResolutionScale).toFixed(
+ highResolutionAddPrecision
+ )} us`;
+
+ case "motor[0]":
+ case "motor[1]":
+ case "motor[2]":
+ case "motor[3]":
+ case "motor[4]":
+ case "motor[5]":
+ case "motor[6]":
+ case "motor[7]":
+ return `${flightLog.rcMotorRawToPctPhysical(value).toFixed(2)} %`;
+
+ case "eRPM[0]":
+ case "eRPM[1]":
+ case "eRPM[2]":
+ case "eRPM[3]":
+ case "eRPM[4]":
+ case "eRPM[5]":
+ case "eRPM[6]":
+ case "eRPM[7]":
+ let motor_poles = flightLog.getSysConfig()["motor_poles"];
+ return `${((value * 200) / motor_poles).toFixed(0)} rpm / ${(
+ (value * 3.333) /
+ motor_poles
+ ).toFixed(1)} hz`;
+
+ case "rcCommands[0]":
+ case "rcCommands[1]":
+ case "rcCommands[2]":
+ return `${(value / highResolutionScale).toFixed(
+ highResolutionAddPrecision
+ )} °/s`;
+ case "rcCommands[3]":
+ return `${value.toFixed(1)}%`;
+
+ case "axisSum[0]":
+ case "axisSum[1]":
+ case "axisSum[2]":
+ case "axisP[0]":
+ case "axisP[1]":
+ case "axisP[2]":
+ case "axisI[0]":
+ case "axisI[1]":
+ case "axisI[2]":
+ case "axisD[0]":
+ case "axisD[1]":
+ case "axisD[2]":
+ case "axisF[0]":
+ case "axisF[1]":
+ case "axisF[2]":
+ case "axisS[0]":
+ case "axisS[1]":
+ case "axisS[2]":
+ return `${flightLog.getPIDPercentage(value).toFixed(1)} %`;
+
+ case "accSmooth[0]":
+ case "accSmooth[1]":
+ case "accSmooth[2]":
+ return `${flightLog
+ .accRawToGs(value)
+ .toFixed(2 + highResolutionAddPrecision)} g`;
+
+ case "vbatLatest":
+ if (
+ flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "4.0.0")
+ ) {
+ return (
+ `${(value / 100).toFixed(2)}V` +
+ `, ${(value / 100 / flightLog.getNumCellsEstimate()).toFixed(
+ 2
+ )} V/cell`
+ );
+ } else if (
+ (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.0")) ||
+ (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "2.0.0"))
+ ) {
+ return (
+ `${(value / 10).toFixed(2)}V` +
+ `, ${(value / 10 / flightLog.getNumCellsEstimate()).toFixed(
+ 2
+ )} V/cell`
+ );
+ } else {
+ return (
+ `${(flightLog.vbatADCToMillivolts(value) / 1000).toFixed(2)}V` +
+ `, ${(
+ flightLog.vbatADCToMillivolts(value) /
+ 1000 /
+ flightLog.getNumCellsEstimate()
+ ).toFixed(2)} V/cell`
+ );
+ }
+
+ case "amperageLatest":
+ if (
+ (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.7")) ||
+ (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "2.0.0"))
+ ) {
+ return (
+ `${(value / 100).toFixed(2)}A` +
+ `, ${(value / 100 / flightLog.getNumMotors()).toFixed(2)} A/motor`
+ );
+ } else if (
+ flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.0")
+ ) {
+ return (
+ `${(value / 100).toFixed(2)}A` +
+ `, ${(value / 100 / flightLog.getNumMotors()).toFixed(2)} A/motor`
+ );
+ } else {
+ return (
+ `${(flightLog.amperageADCToMillivolts(value) / 1000).toFixed(2)}A` +
+ `, ${(
+ flightLog.amperageADCToMillivolts(value) /
+ 1000 /
+ flightLog.getNumMotors()
+ ).toFixed(2)} A/motor`
+ );
+ }
+
+ case "heading[0]":
+ case "heading[1]":
+ case "heading[2]":
+ return `${((value / Math.PI) * 180).toFixed(1)}°`;
+
+ case "baroAlt":
+ return FlightLogFieldPresenter.decodeCorrectAltitude(
+ value / 100,
+ userSettings.altitudeUnits
+ );
+
+ case "flightModeFlags":
+ return FlightLogFieldPresenter.presentFlags(
+ value,
+ FLIGHT_LOG_FLIGHT_MODE_NAME
+ );
+
+ case "stateFlags":
+ return FlightLogFieldPresenter.presentFlags(
+ value,
+ FLIGHT_LOG_FLIGHT_STATE_NAME
+ );
+
+ case "failsafePhase":
+ return FlightLogFieldPresenter.presentEnum(
+ value,
+ FLIGHT_LOG_FAILSAFE_PHASE_NAME
+ );
+
+ case "features":
+ return FlightLogFieldPresenter.presentEnum(value, FLIGHT_LOG_FEATURES);
+
+ case "rssi":
+ return `${((value / 1024) * 100).toFixed(2)} %`;
+
+ //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course
+ case "GPS_numSat":
+ return `${value}`;
+ case "GPS_coord[0]":
+ case "GPS_coord[1]":
+ return `${(value / 10000000).toFixed(5)}`;
+ case "GPS_altitude":
+ return FlightLogFieldPresenter.decodeCorrectAltitude(
+ value / 10,
+ userSettings.altitudeUnits
+ );
+ case "GPS_speed":
+ switch (userSettings.speedUnits) {
+ case 1:
+ return `${(value / 100).toFixed(2)} m/s`;
+ case 2:
+ return `${((value / 100) * 3.6).toFixed(2)} kph`;
+ case 3:
+ return `${((value / 100) * 2.2369).toFixed(2)} mph`;
+ }
+ case "GPS_ground_course":
+ return `${(value / 10).toFixed(1)} °`;
+
+ case "gpsCartesianCoords[0]":
+ case "gpsCartesianCoords[1]":
+ case "gpsCartesianCoords[2]":
+ case "gpsDistance":
+ return `${value.toFixed(0)} m`;
+ case "gpsHomeAzimuth":
+ return `${value.toFixed(1)} °`;
+ case "magADC[0]":
+ case "magADC[1]":
+ case "magADC[2]":
+ return `${(value / 10).toFixed(1)} °`;
+
+ case "debug[0]":
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ case "debug[5]":
+ case "debug[6]":
+ case "debug[7]":
+ return FlightLogFieldPresenter.decodeDebugFieldToFriendly(
+ flightLog,
+ fieldName,
+ value
+ );
+
+ default:
+ return value?.toFixed(0);
+ }
};
-FlightLogFieldPresenter.decodeDebugFieldToFriendly = function(flightLog, fieldName, value) {
- if (flightLog) {
- const debugModeName = DEBUG_MODE[flightLog.getSysConfig().debug_mode]; // convert to recognisable name
- switch (debugModeName) {
- case 'NONE':
- case 'AIRMODE':
- case 'BARO':
- switch (fieldName) {
- case 'debug[1]':
- return `${value.toFixed(0)} hPa`;
- case 'debug[2]':
- return `${(value / 100).toFixed(2)} °C`;
- case 'debug[3]':
- return `${(value / 100).toFixed(2)} m`;
- default:
- return `${value.toFixed(0)}`;
- }
- case 'VELOCITY':
- case 'DFILTER':
- return "";
- case 'CYCLETIME':
- switch (fieldName) {
- case 'debug[1]':
- return value.toFixed(0) + " %";
- default:
- return value.toFixed(0) + "\u03BCS";
- }
- case 'BATTERY':
- switch (fieldName) {
- case 'debug[0]':
- return value.toFixed(0);
- default:
- return (value / 10).toFixed(1) + " V";
- }
- case 'ACCELEROMETER':
- return flightLog.accRawToGs(value).toFixed(2) + " g";
- case 'MIXER':
- return Math.round(flightLog.rcCommandRawToThrottle(value)) + " %";
- case 'PIDLOOP':
- return value.toFixed(0) + " \u03BCS";
- case 'RC_INTERPOLATION':
- switch (fieldName) {
- case 'debug[1]': // current RX refresh rate
- return value.toFixed(0) + ' ms';
- case 'debug[3]': // setpoint [roll]
- return value.toFixed(0) + " °/s";
- default:
- return value.toFixed(0);
- }
- case 'GYRO':
- case 'GYRO_FILTERED':
- case 'GYRO_SCALED':
- case 'DUAL_GYRO':
- case 'DUAL_GYRO_COMBINED':
- case 'DUAL_GYRO_DIFF':
- case 'DUAL_GYRO_RAW':
- case 'NOTCH':
- return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
- case 'ANGLERATE':
- return value.toFixed(0) + " °/s";
- case 'ESC_SENSOR':
- switch (fieldName) {
- case 'debug[3]':
- return value.toFixed(0) + " \u03BCS";
- default:
- return value.toFixed(0);
- }
- case 'SCHEDULER':
- return value.toFixed(0) + " \u03BCS";
- case 'STACK':
- return value.toFixed(0);
- case 'ESC_SENSOR_RPM':
- return value.toFixed(0) + " rpm";
- case 'ESC_SENSOR_TMP':
- return value.toFixed(0) + " °C";
- case 'ALTITUDE':
- switch (fieldName) {
- case 'debug[0]': // GPS Trust * 100
- return value.toFixed(0);
- case 'debug[1]': // GPS Altitude cm
- case 'debug[2]': // OSD Altitude cm
- case 'debug[3]': // Control Altitude
- return (value / 100).toFixed(2) + ' m';
- default:
- return value.toFixed(0);
- }
- case 'FFT':
- switch (fieldName) {
- case 'debug[0]': // gyro pre dyn notch [for gyro debug axis]
- case 'debug[1]': // gyro post dyn notch [for gyro debug axis]
- case 'debug[2]': // gyro pre dyn notch, downsampled for FFT [for gyro debug axis]
- return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
- // debug 3 = not used
- default:
- return value.toFixed(0);
- }
- case 'FFT_TIME':
- switch (fieldName) {
- case 'debug[0]':
- return FlightLogFieldPresenter.presentEnum(value, FFT_CALC_STEPS);
- case 'debug[1]':
- return value.toFixed(0) + " \u03BCs";
- // debug 2 = not used
- // debug 3 = not used
- default:
- return value.toFixed(0);
- }
- case 'FFT_FREQ':
- switch (fieldName) {
- case 'debug[3]': // gyro pre dyn notch [for gyro debug axis]
- return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
- default:
- return value.toFixed(0) + " Hz";
- }
- case 'RTH':
- switch (fieldName) {
-// temporarily, perhaps
-// case 'debug[0]': // pitch angle +/-4000 means +/- 40 deg
-// return (value / 100).toFixed(1) + " °";
- default:
- return value.toFixed(0);
- }
- case 'ITERM_RELAX':
- switch (fieldName) {
- case 'debug[0]': // roll setpoint high-pass filtered
- return value.toFixed(0) + " °/s";
- case 'debug[1]': // roll I-term relax factor
- return value.toFixed(0) + ' %';
- case 'debug[3]': // roll absolute control axis error
- return (value / 10).toFixed(1) + " °";
- default:
- return value.toFixed(0);
- }
- case 'RC_SMOOTHING':
- switch (fieldName) {
- case 'debug[0]':
- return (value + 1500).toFixed(0) + " us";
- case 'debug[3]': // rx frame rate [us]
- return (value / 1000).toFixed(1) + ' ms';
- default:
- return value.toFixed(0);
- }
- case 'RC_SMOOTHING_RATE':
- switch (fieldName) {
- case 'debug[0]': // current frame rate [us]
- case 'debug[2]': // average frame rate [us]
- return (value / 1000).toFixed(2) + ' ms';
- default:
- return value.toFixed(0);
- }
- case 'DSHOT_RPM_TELEMETRY':
- return (value * 200 / flightLog.getSysConfig()['motor_poles']).toFixed(0) + " rpm / " + (value * 3.333 / flightLog.getSysConfig()['motor_poles']).toFixed(0) + ' hz';
- case 'RPM_FILTER':
- return (value * 60).toFixed(0) + "rpm / " + value.toFixed(0) + " Hz";
- case 'D_MIN':
- switch (fieldName) {
- case 'debug[0]': // roll gyro factor
- case 'debug[1]': // roll setpoint Factor
- return value.toFixed(0) + ' %';
- case 'debug[2]': // roll actual D
- case 'debug[3]': // pitch actual D
- return (value / 10).toFixed(1);
- default:
- return value.toFixed(0);
- }
- case 'DYN_LPF':
- switch (fieldName) {
- case 'debug[0]': // gyro scaled [for selected axis]
- case 'debug[3]': // pre-dyn notch gyro [for selected axis]
- return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
- default:
- return value.toFixed(0) + " Hz";
- }
- case 'DYN_IDLE':
- switch (fieldName) {
- case 'debug[3]': // minRPS
- return (value * 6) + ' rpm / ' + (value / 10).toFixed(0) +' hz';
- default:
- return value.toFixed(0);
- }
- case 'AC_CORRECTION':
- return (value / 10).toFixed(1) + " °/s";
- case 'AC_ERROR':
- return (value / 10).toFixed(1) + " °";
- case 'RX_TIMING':
- switch (fieldName) {
- case 'debug[0]': // Frame delta us/10
- case 'debug[1]': // Frame age us/10
- return (value / 100).toFixed(2) + ' ms';
- default:
- return value.toFixed(0);
- }
- case 'GHST':
- switch (fieldName) {
- // debug 0 is CRC error count 0 to int16_t
- // debug 1 is unknown frame count 0 to int16_t
- // debug 2 is RSSI 0 to -128 -> 0 to 128
- case 'debug[3]': // LQ 0-100
- return value.toFixed(0) + ' %';
- default:
- return value.toFixed(0);
- }
- case 'GHST_MSP':
- switch (fieldName) {
- // debug 0 is msp frame count
- // debug 1 is msp frame count
- // debug 2 and 3 not used
- default:
- return value.toFixed(0);
- }
- case 'SCHEDULER_DETERMINISM':
- switch (fieldName) {
- case 'debug[0]': // cycle time in us*10
- case 'debug[2]': // task delay time in us*10
- case 'debug[3]': // task delay time in us*10
- return (value / 10).toFixed(1) + ' us';
- // debug 1 is task ID of late task
- default:
- return value.toFixed(0);
- }
- case 'TIMING_ACCURACY':
- switch (fieldName) {
- case 'debug[0]': // CPU Busy %
- return value.toFixed(1) + ' %';
- case 'debug[2]': // task delay time in us*10
- return (value / 10).toFixed(1) + ' us';
- default:
- return value.toFixed(0);
- }
- case 'RX_EXPRESSLRS_SPI':
- switch (fieldName) {
- case 'debug[3]': // uplink LQ %
- return value.toFixed(1) + ' %';
- // debug 0 = Lost connection count
- // debug 1 = RSSI
- // debug 2 = SNR
- default:
- return value.toFixed(0);
- }
- case 'RX_EXPRESSLRS_PHASELOCK':
- switch (fieldName) {
- case 'debug[2]': // Frequency offset in ticks
- return value.toFixed(0) + ' ticks';
- // debug 0 = Phase offset us
- // debug 1 = Filtered phase offset us
- // debug 3 = Pphase shift in us
- default:
- return value.toFixed(0) + ' us';
- }
- case 'GPS_RESCUE_THROTTLE_PID':
- switch (fieldName) {
- case 'debug[0]': // Throttle P added uS
- case 'debug[1]': // Throttle D added * uS
- return value.toFixed(0) + ' uS';
- case 'debug[2]': // current altitude in m
- case 'debug[3]': // TARGET altitude in m
- return (value / 100).toFixed(1) + ' m';
- default:
- return value.toFixed(0);
- }
- case 'GPS_RESCUE_VELOCITY':
- switch (fieldName) {
- case 'debug[0]': // Pitch P degrees * 100
- case 'debug[1]': // Pitch D degrees * 100
- return (value / 100).toFixed(1) + " °";
- case 'debug[2]': // velocity to home cm/s
- case 'debug[3]': // velocity target cm/s
- return (value / 100).toFixed(1) + ' m/s';
- default:
- return value.toFixed(0);
- }
- case 'GPS_RESCUE_HEADING':
- switch (fieldName) {
- case 'debug[0]': // Ground speed cm/s
- return (value / 100).toFixed(2) + ' m/s';
- case 'debug[1]': // GPS Ground course degrees * 10
- case 'debug[2]': // Attitude in degrees * 10
- case 'debug[3]': // Angle to home in degrees * 10
- case 'debug[4]': // magYaw in degrees * 10
- return (value / 10).toFixed(1) + " °";
- case 'debug[6]': // Roll Added deg * 100
- return (value / 100).toFixed(1) + " °";
- case 'debug[5]': // Roll Mix Att
- case 'debug[7]': // Rescue Yaw Rate
- default:
- return value.toFixed(0);
- }
- case 'GPS_RESCUE_TRACKING':
- switch (fieldName) {
- case 'debug[0]': // velocity to home cm/s
- case 'debug[1]': // velocity target cm/s
- return (value / 100).toFixed(1) + ' m/s';
- case 'debug[2]': // altitude cm
- case 'debug[3]': // altitude target cm
- return (value / 100).toFixed(1) + ' m';
- default:
- return value.toFixed(0);
- }
- case 'GPS__CONNECTION':
- switch (fieldName) {
- case 'debug[0]': // Flight model
- case 'debug[1]': // GPS Nav packet interval
- case 'debug[2]': // FC Nav data time
- return value.toFixed(0);
- case 'debug[3]': // Baud Rate / Nav interval
- return (value * 100).toFixed(0);
- case 'debug[4]': // main state * 100 + subState
- case 'debug[5]': // executeTimeUs
- case 'debug[6]': // ack state
- case 'debug[7]': // serial Rx buffer
- default:
- return value.toFixed(0);
- }
- case 'ATTITUDE':
- switch (fieldName) {
- case 'debug[0]': // accADC X
- case 'debug[1]': // accADC Y
- case 'debug[2]': // setpoint Roll
- case 'debug[3]': // setpoint Pitch
- default:
- return value.toFixed(0);
- }
- case 'VTX_MSP':
- switch (fieldName) {
- case 'debug[0]': // packetCounter
- case 'debug[1]': // isCrsfPortConfig
- case 'debug[2]': // isLowPowerDisarmed
- case 'debug[3]': // mspTelemetryDescriptor
- default:
- return value.toFixed(0);
- }
- case 'GPS_DOP':
- switch (fieldName) {
- case 'debug[0]': // Number of Satellites
- return value.toFixed(0);
- case 'debug[1]': // pDOP (positional - 3D)
- case 'debug[2]': // hDOP (horizontal - 2D)
- case 'debug[3]': // vDOP (vertical - 1D)
- default:
- return (value / 100).toFixed(2);
- }
- case 'FAILSAFE':
- return value.toFixed(0);
- case 'GYRO_CALIBRATION':
- return value.toFixed(0);
- case 'ANGLE_MODE':
- switch (fieldName) {
- case 'debug[0]': // target angle
- case 'debug[1]': // angle error
- case 'debug[2]': // angle feedforward
- case 'debug[3]': // angle achieved
- return (value / 10).toFixed(1) + " °";
- default:
- return value.toFixed(0);
- }
- case 'ANGLE_TARGET':
- return value.toFixed(0);
- case 'CURRENT_ANGLE':
- return value.toFixed(0);
- case 'DSHOT_TELEMETRY_COUNTS':
- return value.toFixed(0);
- case 'EZLANDING':
- return `${(value / 100.0).toFixed(2)} %`;
+FlightLogFieldPresenter.decodeDebugFieldToFriendly = function (
+ flightLog,
+ fieldName,
+ value
+) {
+ if (flightLog) {
+ const debugModeName = DEBUG_MODE[flightLog.getSysConfig().debug_mode]; // convert to recognisable name
+ switch (debugModeName) {
+ case "NONE":
+ case "AIRMODE":
+ case "BARO":
+ switch (fieldName) {
+ case "debug[1]":
+ return `${value.toFixed(0)} hPa`;
+ case "debug[2]":
+ return `${(value / 100).toFixed(2)} °C`;
+ case "debug[3]":
+ return `${(value / 100).toFixed(2)} m`;
+ default:
+ return `${value.toFixed(0)}`;
+ }
+ case "VELOCITY":
+ case "DFILTER":
+ return "";
+ case "CYCLETIME":
+ switch (fieldName) {
+ case "debug[1]":
+ return `${value.toFixed(0)} %`;
+ default:
+ return `${value.toFixed(0)}\u03BCS`;
}
+ case "BATTERY":
+ switch (fieldName) {
+ case "debug[0]":
+ return value.toFixed(0);
+ default:
+ return `${(value / 10).toFixed(1)} V`;
+ }
+ case "ACCELEROMETER":
+ return `${flightLog.accRawToGs(value).toFixed(2)} g`;
+ case "MIXER":
+ return `${Math.round(flightLog.rcCommandRawToThrottle(value))} %`;
+ case "PIDLOOP":
+ return `${value.toFixed(0)} \u03BCS`;
+ case "RC_INTERPOLATION":
+ switch (fieldName) {
+ case "debug[1]": // current RX refresh rate
+ return `${value.toFixed(0)} ms`;
+ case "debug[3]": // setpoint [roll]
+ return `${value.toFixed(0)} °/s`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GYRO":
+ case "GYRO_FILTERED":
+ case "GYRO_SCALED":
+ case "DUAL_GYRO":
+ case "DUAL_GYRO_COMBINED":
+ case "DUAL_GYRO_DIFF":
+ case "DUAL_GYRO_RAW":
+ case "NOTCH":
+ case "GYRO_SAMPLE":
+ return `${Math.round(flightLog.gyroRawToDegreesPerSecond(value))} °/s`;
+ case "ANGLERATE":
+ return `${value.toFixed(0)} °/s`;
+ case "ESC_SENSOR":
+ switch (fieldName) {
+ case "debug[3]":
+ return `${value.toFixed(0)} \u03BCS`;
+ default:
+ return value.toFixed(0);
+ }
+ case "SCHEDULER":
+ return `${value.toFixed(0)} \u03BCS`;
+ case "STACK":
return value.toFixed(0);
+ case "ESC_SENSOR_RPM":
+ return `${value.toFixed(0)} rpm`;
+ case "ESC_SENSOR_TMP":
+ return `${value.toFixed(0)} °C`;
+ case "ALTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // GPS Trust * 100
+ return value.toFixed(0);
+ case "debug[1]": // GPS Altitude cm
+ case "debug[2]": // OSD Altitude cm
+ case "debug[3]": // Control Altitude
+ return `${(value / 100).toFixed(2)} m`;
+ default:
+ return value.toFixed(0);
+ }
+ case "FFT":
+ switch (fieldName) {
+ case "debug[0]": // gyro pre dyn notch [for gyro debug axis]
+ case "debug[1]": // gyro post dyn notch [for gyro debug axis]
+ case "debug[2]": // gyro pre dyn notch, downsampled for FFT [for gyro debug axis]
+ return `${Math.round(
+ flightLog.gyroRawToDegreesPerSecond(value)
+ )} °/s`;
+ // debug 3 = not used
+ default:
+ return value.toFixed(0);
+ }
+ case "FFT_TIME":
+ switch (fieldName) {
+ case "debug[0]":
+ return FlightLogFieldPresenter.presentEnum(value, FFT_CALC_STEPS);
+ case "debug[1]":
+ return `${value.toFixed(0)} \u03BCs`;
+ // debug 2 = not used
+ // debug 3 = not used
+ default:
+ return value.toFixed(0);
+ }
+ case "FFT_FREQ":
+ if (semver.gte(flightLog.getSysConfig().firmwareVersion, '4.6.0')) {
+ switch (fieldName) {
+ case 'debug[0]': // gyro pre dyn notch [for gyro debug axis]
+ return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
+ default:
+ return value.toFixed(0) + " Hz";
+ }
+ } else {
+ switch (fieldName) {
+ case 'debug[3]': // gyro pre dyn notch [for gyro debug axis]
+ return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s";
+ default:
+ return value.toFixed(0) + " Hz";
+ }
+ }
+ case "RTH":
+ switch (fieldName) {
+ // temporarily, perhaps
+ // case 'debug[0]': // pitch angle +/-4000 means +/- 40 deg
+ // return (value / 100).toFixed(1) + " °";
+ default:
+ return value.toFixed(0);
+ }
+ case "ITERM_RELAX":
+ switch (fieldName) {
+ case "debug[0]": // roll setpoint high-pass filtered
+ return `${value.toFixed(0)} °/s`;
+ case "debug[1]": // roll I-term relax factor
+ return `${value.toFixed(0)} %`;
+ case "debug[3]": // roll absolute control axis error
+ return `${(value / 10).toFixed(1)} °`;
+ default:
+ return value.toFixed(0);
+ }
+ case "RC_SMOOTHING":
+ switch (fieldName) {
+ case "debug[0]":
+ return `${(value + 1500).toFixed(0)} us`;
+ case "debug[3]": // rx frame rate [us]
+ return `${(value / 1000).toFixed(1)} ms`;
+ default:
+ return value.toFixed(0);
+ }
+ case "RC_SMOOTHING_RATE":
+ switch (fieldName) {
+ case "debug[0]": // current frame rate [us]
+ case "debug[2]": // average frame rate [us]
+ return `${(value / 1000).toFixed(2)} ms`;
+ default:
+ return value.toFixed(0);
+ }
+ case "DSHOT_RPM_TELEMETRY":
+ return `${(
+ (value * 200) /
+ flightLog.getSysConfig()["motor_poles"]
+ ).toFixed(0)} rpm / ${(
+ (value * 3.333) /
+ flightLog.getSysConfig()["motor_poles"]
+ ).toFixed(0)} hz`;
+ case "RPM_FILTER":
+ return `${(value * 60).toFixed(0)}rpm / ${value.toFixed(0)} Hz`;
+ case "D_MAX":
+ switch (fieldName) {
+ case "debug[0]": // roll gyro factor
+ case "debug[1]": // roll setpoint Factor
+ return `${value.toFixed(0)} %`;
+ case "debug[2]": // roll actual D
+ case "debug[3]": // pitch actual D
+ return (value / 10).toFixed(1);
+ default:
+ return value.toFixed(0);
+ }
+ case "DYN_LPF":
+ switch (fieldName) {
+ case "debug[0]": // gyro scaled [for selected axis]
+ case "debug[3]": // pre-dyn notch gyro [for selected axis]
+ return `${Math.round(
+ flightLog.gyroRawToDegreesPerSecond(value)
+ )} °/s`;
+ default:
+ return `${value.toFixed(0)} Hz`;
+ }
+ case "DYN_IDLE":
+ switch (fieldName) {
+ case "debug[3]": // minRPS
+ return `${value * 6} rpm / ${(value / 10).toFixed(0)} hz`;
+ default:
+ return value.toFixed(0);
+ }
+ case "AC_CORRECTION":
+ return `${(value / 10).toFixed(1)} °/s`;
+ case "AC_ERROR":
+ return `${(value / 10).toFixed(1)} °`;
+ case "RX_TIMING":
+ switch (fieldName) {
+ case "debug[0]": // Frame delta us/10
+ case "debug[1]": // Frame age us/10
+ return `${(value / 100).toFixed(2)} ms`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GHST":
+ switch (fieldName) {
+ // debug 0 is CRC error count 0 to int16_t
+ // debug 1 is unknown frame count 0 to int16_t
+ // debug 2 is RSSI 0 to -128 -> 0 to 128
+ case "debug[3]": // LQ 0-100
+ return `${value.toFixed(0)} %`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GHST_MSP":
+ switch (fieldName) {
+ // debug 0 is msp frame count
+ // debug 1 is msp frame count
+ // debug 2 and 3 not used
+ default:
+ return value.toFixed(0);
+ }
+ case "SCHEDULER_DETERMINISM":
+ switch (fieldName) {
+ case "debug[0]": // cycle time in us*10
+ case "debug[2]": // task delay time in us*10
+ case "debug[3]": // task delay time in us*10
+ return `${(value / 10).toFixed(1)} us`;
+ // debug 1 is task ID of late task
+ default:
+ return value.toFixed(0);
+ }
+ case "TIMING_ACCURACY":
+ switch (fieldName) {
+ case "debug[0]": // CPU Busy %
+ return `${value.toFixed(1)} %`;
+ case "debug[2]": // task delay time in us*10
+ return `${(value / 10).toFixed(1)} us`;
+ default:
+ return value.toFixed(0);
+ }
+ case "RX_EXPRESSLRS_SPI":
+ switch (fieldName) {
+ case "debug[3]": // uplink LQ %
+ return `${value.toFixed(1)} %`;
+ // debug 0 = Lost connection count
+ // debug 1 = RSSI
+ // debug 2 = SNR
+ default:
+ return value.toFixed(0);
+ }
+ case "RX_EXPRESSLRS_PHASELOCK":
+ switch (fieldName) {
+ case "debug[2]": // Frequency offset in ticks
+ return `${value.toFixed(0)} ticks`;
+ // debug 0 = Phase offset us
+ // debug 1 = Filtered phase offset us
+ // debug 3 = Pphase shift in us
+ default:
+ return `${value.toFixed(0)} us`;
+ }
+ case "GPS_RESCUE_THROTTLE_PID":
+ switch (fieldName) {
+ case "debug[0]": // Throttle P added uS
+ case "debug[1]": // Throttle D added * uS
+ return `${value.toFixed(0)} uS`;
+ case "debug[2]": // current altitude in m
+ case "debug[3]": // TARGET altitude in m
+ return `${(value / 100).toFixed(1)} m`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GPS_RESCUE_VELOCITY":
+ switch (fieldName) {
+ case "debug[0]": // Pitch P degrees * 100
+ case "debug[1]": // Pitch D degrees * 100
+ return `${(value / 100).toFixed(1)} °`;
+ case "debug[2]": // velocity to home cm/s
+ case "debug[3]": // velocity target cm/s
+ return `${(value / 100).toFixed(1)} m/s`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GPS_RESCUE_HEADING":
+ switch (fieldName) {
+ case "debug[0]": // Ground speed cm/s
+ return `${(value / 100).toFixed(2)} m/s`;
+ case "debug[1]": // GPS Ground course degrees * 10
+ case "debug[2]": // Attitude in degrees * 10
+ case "debug[3]": // Angle to home in degrees * 10
+ case "debug[4]": // magYaw in degrees * 10
+ return `${(value / 10).toFixed(1)} °`;
+ case "debug[6]": // Roll Added deg * 100
+ return `${(value / 100).toFixed(1)} °`;
+ case "debug[5]": // Roll Mix Att
+ case "debug[7]": // Rescue Yaw Rate
+ default:
+ return value.toFixed(0);
+ }
+ case "GPS_RESCUE_TRACKING":
+ switch (fieldName) {
+ case "debug[0]": // velocity to home cm/s
+ case "debug[1]": // velocity target cm/s
+ return `${(value / 100).toFixed(1)} m/s`;
+ case "debug[2]": // altitude cm
+ case "debug[3]": // altitude target cm
+ return `${(value / 100).toFixed(1)} m`;
+ default:
+ return value.toFixed(0);
+ }
+ case "GPS__CONNECTION":
+ switch (fieldName) {
+ case "debug[0]": // Flight model
+ case "debug[1]": // GPS Nav packet interval
+ case "debug[2]": // FC Nav data time
+ return value.toFixed(0);
+ case "debug[3]": // Baud Rate / Nav interval
+ return (value * 100).toFixed(0);
+ case "debug[4]": // main state * 100 + subState
+ case "debug[5]": // executeTimeUs
+ case "debug[6]": // ack state
+ case "debug[7]": // serial Rx buffer
+ default:
+ return value.toFixed(0);
+ }
+ case "ATTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // Roll Angle
+ case "debug[1]": // Pitch Angle
+ case "debug[2]": // Ground speed factor
+ case "debug[3]": // Heading error
+ case "debug[4]": // Velocity to home
+ case "debug[5]": // Ground speed error ratio
+ case "debug[6]": // Pitch forward angle
+ case "debug[7]": // dcmKp gain
+ default:
+ return value.toFixed(0);
+ }
+ case "VTX_MSP":
+ switch (fieldName) {
+ case "debug[0]": // packetCounter
+ case "debug[1]": // isCrsfPortConfig
+ case "debug[2]": // isLowPowerDisarmed
+ case "debug[3]": // mspTelemetryDescriptor
+ default:
+ return value.toFixed(0);
+ }
+ case "GPS_DOP":
+ switch (fieldName) {
+ case "debug[0]": // Number of Satellites
+ return value.toFixed(0);
+ case "debug[1]": // pDOP (positional - 3D)
+ case "debug[2]": // hDOP (horizontal - 2D)
+ case "debug[3]": // vDOP (vertical - 1D)
+ default:
+ return (value / 100).toFixed(2);
+ }
+ case "FAILSAFE":
+ return value.toFixed(0);
+ case "GYRO_CALIBRATION":
+ return value.toFixed(0);
+ case "ANGLE_MODE":
+ switch (fieldName) {
+ case "debug[0]": // target angle
+ case "debug[1]": // angle error
+ case "debug[2]": // angle feedforward
+ case "debug[3]": // angle achieved
+ return `${(value / 10).toFixed(1)} °`;
+ default:
+ return value.toFixed(0);
+ }
+ case "ANGLE_TARGET":
+ return value.toFixed(0);
+ case "CURRENT_ANGLE":
+ return value.toFixed(0);
+ case "DSHOT_TELEMETRY_COUNTS":
+ return value.toFixed(0);
+ case "EZLANDING":
+ return `${(value / 100.0).toFixed(2)} %`;
+ case "OPTICALFLOW":
+ switch (fieldName) {
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ return `${(value / 1000).toFixed(1)}`;
+ default:
+ return value.toFixed(1);
+ }
+ case "AUTOPILOT_POSITION":
+ switch (fieldName) {
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ case "debug[5]":
+ case "debug[6]":
+ case "debug[7]":
+ return `${(value / 10).toFixed(1)}`;
+ default:
+ return value.toFixed(1);
+ }
+ case "TPA":
+ switch (fieldName) {
+ case "debug[1]":
+ case "debug[2]":
+ return `${(value / 10).toFixed(1)} °`;
+ case "debug[4]":
+ return `${(value / 10).toFixed(1)} m/s`;
+ default:
+ return value.toFixed(1);
+ }
}
- return "";
+ return value.toFixed(0);
+ }
+ return value.toFixed(0);
};
-FlightLogFieldPresenter.fieldNameToFriendly = function(fieldName, debugMode) {
- if (debugMode) {
- if (fieldName.includes('debug')) {
- let debugModeName = DEBUG_MODE[debugMode];
- let debugFields;
+FlightLogFieldPresenter.fieldNameToFriendly = function (fieldName, debugMode) {
+ if (debugMode) {
+ if (fieldName.includes("debug")) {
+ let debugModeName = DEBUG_MODE[debugMode];
+ let debugFields;
- if (debugModeName) {
- debugFields = DEBUG_FRIENDLY_FIELD_NAMES[debugModeName];
- }
+ if (debugModeName) {
+ debugFields = DEBUG_FRIENDLY_FIELD_NAMES[debugModeName];
+ }
- if (!debugFields) {
- if (fieldName === 'debug[all]') {
- return 'Debug (' + (debugModeName || debugMode) + ')';
- }
- debugFields = DEBUG_FRIENDLY_FIELD_NAMES[DEBUG_MODE[0]];
- }
-
- return debugFields[fieldName];
+ if (!debugFields) {
+ if (fieldName === "debug[all]") {
+ return `Debug (${debugModeName || debugMode})`;
}
+ debugFields = DEBUG_FRIENDLY_FIELD_NAMES[DEBUG_MODE[0]];
+ }
+
+ return debugFields[fieldName] ?? fieldName;
}
- if (FRIENDLY_FIELD_NAMES[fieldName]) {
- return FRIENDLY_FIELD_NAMES[fieldName];
- }
+ }
+ if (FRIENDLY_FIELD_NAMES[fieldName]) {
+ return FRIENDLY_FIELD_NAMES[fieldName];
+ }
+
+ return fieldName;
+};
- return fieldName;
+/**
+ * Attempt to decode fields values from log file to chart units and back.
+ *
+ * @param flightLog The pointer to FlightLog object
+ * @param fieldName Name of the field
+ * @param value Value of the field
+ * @param toFriendly If true then convert from log file units to charts, else - from charts units to log file
+ */
+FlightLogFieldPresenter.ConvertFieldValue = function (
+ flightLog,
+ fieldName,
+ toFriendly,
+ value
+) {
+ if (value === undefined) {
+ return 0;
+ }
+
+ const highResolutionScale =
+ flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0 ? 10 : 1;
+ const highResolutionAddPrecision =
+ flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0 ? 1 : 0;
+
+ switch (fieldName) {
+ case "time":
+ return toFriendly ? value / 1000 : value * 1000;
+
+ case "gyroADC[0]":
+ case "gyroADC[1]":
+ case "gyroADC[2]":
+ case "gyroUnfilt[0]":
+ case "gyroUnfilt[1]":
+ case "gyroUnfilt[2]":
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value / highResolutionScale)
+ : (value * highResolutionScale) /
+ flightLog.gyroRawToDegreesPerSecond(1.0);
+
+ case "axisError[0]":
+ case "axisError[1]":
+ case "axisError[2]":
+ return toFriendly
+ ? value / highResolutionScale
+ : value * highResolutionScale;
+
+ case "rcCommand[0]":
+ case "rcCommand[1]":
+ case "rcCommand[2]":
+ return toFriendly
+ ? value / highResolutionScale + 1500
+ : (value - 1500) * highResolutionScale;
+ case "rcCommand[3]":
+ return toFriendly
+ ? value / highResolutionScale
+ : value * highResolutionScale;
+
+ case "motor[0]":
+ case "motor[1]":
+ case "motor[2]":
+ case "motor[3]":
+ case "motor[4]":
+ case "motor[5]":
+ case "motor[6]":
+ case "motor[7]":
+ return toFriendly
+ ? flightLog.rcMotorRawToPctPhysical(value)
+ : flightLog.PctPhysicalTorcMotorRaw(value);
+
+ case "eRPM[0]":
+ case "eRPM[1]":
+ case "eRPM[2]":
+ case "eRPM[3]":
+ case "eRPM[4]":
+ case "eRPM[5]":
+ case "eRPM[6]":
+ case "eRPM[7]":
+ let motor_poles = flightLog.getSysConfig()["motor_poles"];
+ return toFriendly
+ ? (value * 200) / motor_poles
+ : (value * motor_poles) / 200;
+
+ case "axisSum[0]":
+ case "axisSum[1]":
+ case "axisSum[2]":
+ case "axisP[0]":
+ case "axisP[1]":
+ case "axisP[2]":
+ case "axisI[0]":
+ case "axisI[1]":
+ case "axisI[2]":
+ case "axisD[0]":
+ case "axisD[1]":
+ case "axisD[2]":
+ case "axisF[0]":
+ case "axisF[1]":
+ case "axisF[2]":
+ case "axisS[0]":
+ case "axisS[1]":
+ case "axisS[2]":
+ return toFriendly
+ ? flightLog.getPIDPercentage(value)
+ : value / flightLog.getPIDPercentage(1.0);
+
+ case "accSmooth[0]":
+ case "accSmooth[1]":
+ case "accSmooth[2]":
+ return toFriendly
+ ? flightLog.accRawToGs(value)
+ : value / flightLog.accRawToGs(1.0);
+
+ case "vbatLatest":
+ if (
+ flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "4.0.0")
+ ) {
+ return toFriendly ? value / 100 : value * 100;
+ } else if (
+ (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.0")) ||
+ (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "2.0.0"))
+ ) {
+ return toFriendly ? value / 10 : value * 10;
+ } else {
+ return toFriendly ? value / 1000 : value * 1000;
+ }
+
+ case "amperageLatest":
+ if (
+ (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.7")) ||
+ (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "2.0.0"))
+ ) {
+ return toFriendly ? value / 100 : value * 100;
+ } else if (
+ flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(flightLog.getSysConfig().firmwareVersion, "3.1.0")
+ ) {
+ return toFriendly ? value / 100 : value * 100;
+ } else {
+ return toFriendly ? value / 1000 : value * 1000;
+ }
+
+ case "heading[0]":
+ case "heading[1]":
+ case "heading[2]":
+ return toFriendly ? (value / Math.PI) * 180 : (value * Math.PI) / 180;
+
+ case "baroAlt":
+ return toFriendly
+ ? FlightLogFieldPresenter.decodeAltitudeLogToChart(
+ value / 100,
+ userSettings.altitudeUnits
+ )
+ : (value * 100) /
+ FlightLogFieldPresenter.decodeAltitudeLogToChart(
+ 1.0,
+ userSettings.altitudeUnits
+ );
+
+ case "flightModeFlags":
+ return value;
+
+ case "stateFlags":
+ return value;
+
+ case "failsafePhase":
+ return value;
+
+ case "features":
+ return value;
+
+ case "rssi":
+ return toFriendly ? (value / 1024) * 100 : (value * 1024) / 100;
+
+ //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course
+ case "GPS_numSat":
+ return value;
+ case "GPS_coord[0]":
+ case "GPS_coord[1]":
+ return toFriendly ? value / 10000000 : value * 10000000;
+ case "GPS_altitude":
+ return toFriendly
+ ? FlightLogFieldPresenter.decodeAltitudeLogToChart(
+ value / 10,
+ userSettings.altitudeUnits
+ )
+ : (value * 10) /
+ FlightLogFieldPresenter.decodeAltitudeLogToChart(
+ 1.0,
+ userSettings.altitudeUnits
+ );
+ case "GPS_speed":
+ switch (userSettings.speedUnits) {
+ case 1:
+ return toFriendly ? value / 100 : value * 100; // m/s
+ case 2:
+ return toFriendly ? (value / 100) * 3.6 : (100 * value) / 3.6; // kph
+ case 3:
+ return toFriendly ? (value / 100) * 2.2369 : (value * 100) / 2.2369; //mph
+ }
+ case "GPS_ground_course":
+ return toFriendly ? value / 10 : value * 10;
+ case "magADC[0]":
+ case "magADC[1]":
+ case "magADC[2]":
+ return toFriendly ? value / 10 : value * 10;
+
+ case "debug[0]":
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ case "debug[5]":
+ case "debug[6]":
+ case "debug[7]":
+ return FlightLogFieldPresenter.ConvertDebugFieldValue(
+ flightLog,
+ fieldName,
+ toFriendly,
+ value
+ );
+
+ default:
+ return value;
+ }
+};
+
+/**
+ * Attempt to decode debug fields values from log file to chart units and back.
+ *
+ * @param flightLog The pointer to FlightLog object
+ * @param fieldName Name of the field
+ * @param value Value of the field
+ * @param toFriendly If true then convert from log file units to charts, else - from charts units to log file
+ */
+FlightLogFieldPresenter.ConvertDebugFieldValue = function (
+ flightLog,
+ fieldName,
+ toFriendly,
+ value
+) {
+ if (flightLog) {
+ const debugModeName = DEBUG_MODE[flightLog.getSysConfig().debug_mode]; // convert to recognisable name
+ switch (debugModeName) {
+ case "NONE":
+ case "AIRMODE":
+ case "BARO":
+ switch (fieldName) {
+ case "debug[1]":
+ return value; // hPa
+ case "debug[2]":
+ return toFriendly ? value / 100 : value * 100; // °C`
+ case "debug[3]":
+ return toFriendly ? value / 100 : value * 100; //m`
+ default:
+ return value;
+ }
+ case "VELOCITY":
+ case "DFILTER":
+ return value;
+ case "CYCLETIME":
+ switch (fieldName) {
+ case "debug[1]":
+ return value;
+ default:
+ return value;
+ }
+ case "BATTERY":
+ switch (fieldName) {
+ case "debug[0]":
+ return value;
+ default:
+ return toFriendly ? value / 10 : value * 10; // " V";
+ }
+ case "ACCELEROMETER":
+ return toFriendly
+ ? flightLog.accRawToGs(value)
+ : value / flightLog.accRawToGs(1.0);
+ case "MIXER":
+ return toFriendly
+ ? flightLog.rcCommandRawToThrottle(value)
+ : flightLog.ThrottleTorcCommandRaw(value);
+ case "PIDLOOP":
+ return value;
+ case "RC_INTERPOLATION":
+ switch (fieldName) {
+ case "debug[1]": // current RX refresh rate
+ return value; // ms
+ case "debug[3]": // setpoint [roll]
+ return value; // °/s
+ default:
+ return value;
+ }
+ case "GYRO":
+ case "GYRO_FILTERED":
+ case "GYRO_SCALED":
+ case "DUAL_GYRO":
+ case "DUAL_GYRO_COMBINED":
+ case "DUAL_GYRO_DIFF":
+ case "DUAL_GYRO_RAW":
+ case "NOTCH":
+ case "GYRO_SAMPLE":
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value)
+ : value / flightLog.gyroRawToDegreesPerSecond(1.0); // °/s;
+ case "ANGLERATE":
+ return value; // °/s;
+ case "ESC_SENSOR":
+ switch (fieldName) {
+ case "debug[3]":
+ return value;
+ default:
+ return value;
+ }
+ case "SCHEDULER":
+ return value;
+ case "STACK":
+ return value;
+ case "ESC_SENSOR_RPM":
+ return value; // " rpm";
+ case "ESC_SENSOR_TMP":
+ return value; // " °C";
+ case "ALTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // GPS Trust * 100
+ return value;
+ case "debug[1]": // GPS Altitude cm
+ case "debug[2]": // OSD Altitude cm
+ case "debug[3]": // Control Altitude
+ return toFriendly ? value / 100 : value * 100; // m
+ default:
+ return value;
+ }
+ case "FFT":
+ switch (fieldName) {
+ case "debug[0]": // gyro pre dyn notch [for gyro debug axis]
+ case "debug[1]": // gyro post dyn notch [for gyro debug axis]
+ case "debug[2]": // gyro pre dyn notch, downsampled for FFT [for gyro debug axis]
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value)
+ : value / flightLog.gyroRawToDegreesPerSecond(1.0); // °/s;
+ // debug 3 = not used
+ default:
+ return value;
+ }
+ case "FFT_TIME":
+ switch (fieldName) {
+ case "debug[0]":
+ return value;
+ case "debug[1]":
+ return value;
+ // debug 2 = not used
+ // debug 3 = not used
+ default:
+ return value;
+ }
+ case "FFT_FREQ":
+ if (semver.gte(flightLog.getSysConfig().firmwareVersion, '4.6.0')) {
+ switch (fieldName) {
+ case 'debug[0]': // gyro pre dyn notch [for gyro debug axis]
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value)
+ : value / flightLog.gyroRawToDegreesPerSecond(1.0); // °/s;
+ default:
+ return value;
+ }
+ } else {
+ switch (fieldName) {
+ case "debug[3]": // gyro pre dyn notch [for gyro debug axis]
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value)
+ : value / flightLog.gyroRawToDegreesPerSecond(1.0); // °/s;
+ default:
+ return value;
+ }
+ }
+ case "RTH":
+ switch (fieldName) {
+ // temporarily, perhaps
+ // case 'debug[0]': // pitch angle +/-4000 means +/- 40 deg
+ // return (value / 100).toFixed(1) + " °";
+ default:
+ return value;
+ }
+ case "ITERM_RELAX":
+ switch (fieldName) {
+ case "debug[0]": // roll setpoint high-pass filtered
+ return value; // °/s
+ case "debug[1]": // roll I-term relax factor
+ return value; // %
+ case "debug[3]": // roll absolute control axis error
+ return toFriendly ? value / 10 : value * 10; // °
+ default:
+ return value;
+ }
+ case "RC_SMOOTHING":
+ switch (fieldName) {
+ case "debug[0]":
+ return toFriendly ? value + 1500 : value - 1500; // us
+ case "debug[3]": // rx frame rate [us]
+ return toFriendly ? value / 1000 : value * 1000; // ms
+ default:
+ return value;
+ }
+ case "RC_SMOOTHING_RATE":
+ switch (fieldName) {
+ case "debug[0]": // current frame rate [us]
+ case "debug[2]": // average frame rate [us]
+ return toFriendly ? value / 1000 : value * 1000; // ms
+ default:
+ return value;
+ }
+ case "DSHOT_RPM_TELEMETRY":
+ let pole = flightLog.getSysConfig()["motor_poles"];
+ return toFriendly ? (value * 200) / pole : (value * pole) / 200;
+ case "RPM_FILTER":
+ return toFriendly ? value * 60 : value / 60;
+ case "D_MAX":
+ switch (fieldName) {
+ case "debug[0]": // roll gyro factor
+ case "debug[1]": // roll setpoint Factor
+ return value;
+ case "debug[2]": // roll actual D
+ case "debug[3]": // pitch actual D
+ return toFriendly ? value / 10 : value * 10;
+ default:
+ return value;
+ }
+ case "DYN_LPF":
+ switch (fieldName) {
+ case "debug[0]": // gyro scaled [for selected axis]
+ case "debug[3]": // pre-dyn notch gyro [for selected axis]
+ return toFriendly
+ ? flightLog.gyroRawToDegreesPerSecond(value)
+ : value / flightLog.gyroRawToDegreesPerSecond(1.0); // °/s
+ default:
+ return value;
+ }
+ case "DYN_IDLE":
+ switch (fieldName) {
+ case "debug[3]": // minRPS
+ return toFriendly ? value * 6 : value / 6;
+ default:
+ return value;
+ }
+ case "AC_CORRECTION":
+ return toFriendly ? value / 10 : value * 10; // °/s
+ case "AC_ERROR":
+ return toFriendly ? value / 10 : value * 10; // °
+ case "RX_TIMING":
+ switch (fieldName) {
+ case "debug[0]": // Frame delta us/10
+ case "debug[1]": // Frame age us/10
+ return toFriendly ? value / 100 : value * 100; //ms
+ default:
+ return value;
+ }
+ case "GHST":
+ switch (fieldName) {
+ // debug 0 is CRC error count 0 to int16_t
+ // debug 1 is unknown frame count 0 to int16_t
+ // debug 2 is RSSI 0 to -128 -> 0 to 128
+ case "debug[3]": // LQ 0-100
+ return value;
+ default:
+ return value;
+ }
+ case "GHST_MSP":
+ switch (fieldName) {
+ // debug 0 is msp frame count
+ // debug 1 is msp frame count
+ // debug 2 and 3 not used
+ default:
+ return value;
+ }
+ case "SCHEDULER_DETERMINISM":
+ switch (fieldName) {
+ case "debug[0]": // cycle time in us*10
+ case "debug[2]": // task delay time in us*10
+ case "debug[3]": // task delay time in us*10
+ return toFriendly ? value / 10 : value * 10; // us
+ // debug 1 is task ID of late task
+ default:
+ return value;
+ }
+ case "TIMING_ACCURACY":
+ switch (fieldName) {
+ case "debug[0]": // CPU Busy %
+ return value; // %
+ case "debug[2]": // task delay time in us*10
+ return toFriendly ? value / 10 : value * 10; // us
+ default:
+ return value;
+ }
+ case "RX_EXPRESSLRS_SPI":
+ switch (fieldName) {
+ case "debug[3]": // uplink LQ %
+ return value;
+ // debug 0 = Lost connection count
+ // debug 1 = RSSI
+ // debug 2 = SNR
+ default:
+ return value;
+ }
+ case "RX_EXPRESSLRS_PHASELOCK":
+ switch (fieldName) {
+ case "debug[2]": // Frequency offset in ticks
+ return value; // ' ticks';
+ // debug 0 = Phase offset us
+ // debug 1 = Filtered phase offset us
+ // debug 3 = Pphase shift in us
+ default:
+ return value; // ' us';
+ }
+ case "GPS_RESCUE_THROTTLE_PID":
+ switch (fieldName) {
+ case "debug[0]": // Throttle P added uS
+ case "debug[1]": // Throttle D added * uS
+ return value; // ' uS';
+ case "debug[2]": // current altitude in m
+ case "debug[3]": // TARGET altitude in m
+ return toFriendly ? value / 100 : value * 100; // m
+ default:
+ return value;
+ }
+ case "GPS_RESCUE_VELOCITY":
+ switch (fieldName) {
+ case "debug[0]": // Pitch P degrees * 100
+ case "debug[1]": // Pitch D degrees * 100
+ return toFriendly ? value / 100 : value * 100; // °
+ case "debug[2]": // velocity to home cm/s
+ case "debug[3]": // velocity target cm/s
+ return toFriendly ? value / 100 : value * 100; // m/s
+ default:
+ return value;
+ }
+ case "GPS_RESCUE_HEADING":
+ switch (fieldName) {
+ case "debug[0]": // Ground speed cm/s
+ return toFriendly ? value / 100 : value * 100; // m/s
+ case "debug[1]": // GPS Ground course degrees * 10
+ case "debug[2]": // Attitude in degrees * 10
+ case "debug[3]": // Angle to home in degrees * 10
+ case "debug[4]": // magYaw in degrees * 10
+ return toFriendly ? value / 10 : value * 10; //°
+ case "debug[6]": // Roll Added deg * 100
+ return toFriendly ? value / 100 : value * 100; // °
+ case "debug[5]": // Roll Mix Att
+ case "debug[7]": // Rescue Yaw Rate
+ default:
+ return value;
+ }
+ case "GPS_RESCUE_TRACKING":
+ switch (fieldName) {
+ case "debug[0]": // velocity to home cm/s
+ case "debug[1]": // velocity target cm/s
+ return toFriendly ? value / 100 : value * 100; // m/s
+ case "debug[2]": // altitude cm
+ case "debug[3]": // altitude target cm
+ return toFriendly ? value / 100 : value * 100;
+ default:
+ return value;
+ }
+ case "GPS__CONNECTION":
+ switch (fieldName) {
+ case "debug[0]": // Flight model
+ case "debug[1]": // GPS Nav packet interval
+ case "debug[2]": // FC Nav data time
+ return value;
+ case "debug[3]": // Baud Rate / Nav interval
+ return toFriendly ? value * 100 : value / 100;
+ case "debug[4]": // main state * 100 + subState
+ case "debug[5]": // executeTimeUs
+ case "debug[6]": // ack state
+ case "debug[7]": // serial Rx buffer
+ default:
+ return value;
+ }
+ case "ATTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // Roll Angle
+ case "debug[1]": // Pitch Angle
+ case "debug[2]": // Ground speed factor
+ case "debug[3]": // Heading error
+ case "debug[4]": // Velocity to home
+ case "debug[5]": // Ground speed error ratio
+ case "debug[6]": // Pitch forward angle
+ case "debug[7]": // dcmKp gain
+ default:
+ return value;
+ }
+ case "VTX_MSP":
+ switch (fieldName) {
+ case "debug[0]": // packetCounter
+ case "debug[1]": // isCrsfPortConfig
+ case "debug[2]": // isLowPowerDisarmed
+ case "debug[3]": // mspTelemetryDescriptor
+ default:
+ return value;
+ }
+ case "GPS_DOP":
+ switch (fieldName) {
+ case "debug[0]": // Number of Satellites
+ return value;
+ case "debug[1]": // pDOP (positional - 3D)
+ case "debug[2]": // hDOP (horizontal - 2D)
+ case "debug[3]": // vDOP (vertical - 1D)
+ default:
+ return toFriendly ? value / 100 : value * 100;
+ }
+ case "FAILSAFE":
+ return value;
+ case "GYRO_CALIBRATION":
+ return value;
+ case "ANGLE_MODE":
+ switch (fieldName) {
+ case "debug[0]": // target angle
+ case "debug[1]": // angle error
+ case "debug[2]": // angle feedforward
+ case "debug[3]": // angle achieved
+ return toFriendly ? value / 10 : value * 10; // °
+ default:
+ return value;
+ }
+ case "ANGLE_TARGET":
+ return value;
+ case "CURRENT_ANGLE":
+ return value;
+ case "DSHOT_TELEMETRY_COUNTS":
+ return value;
+ case "OPTICALFLOW":
+ switch (fieldName) {
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ return toFriendly ? value / 1000 : value * 1000;
+ default:
+ return value;
+ }
+ case "AUTOPILOT_POSITION":
+ switch (fieldName) {
+ case "debug[2]":
+ case "debug[3]":
+ case "debug[4]":
+ case "debug[5]":
+ case "debug[6]":
+ case "debug[7]":
+ return toFriendly ? value / 10 : value * 10;
+ default:
+ return value;
+ }
+ case "TPA":
+ switch (fieldName) {
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[4]":
+ return toFriendly ? value / 10 : value * 10;
+ default:
+ return value;
+ }
+ }
+ }
+ return value;
};
diff --git a/src/flightlog_index.js b/src/flightlog_index.js
index 4c5db51c..e35a3809 100644
--- a/src/flightlog_index.js
+++ b/src/flightlog_index.js
@@ -2,330 +2,355 @@ import { FlightLogParser } from "./flightlog_parser";
import { FlightLogEvent } from "./flightlog_fielddefs";
import { IMU } from "./imu";
import { ArrayDataStream } from "./datastream";
-import './decoders';
+import "./decoders";
export function FlightLogIndex(logData) {
- //Private:
- var
- that = this,
- logBeginOffsets = false,
- logCount = false,
- intraframeDirectories = false;
-
- function buildLogOffsetsIndex() {
- var
- stream = new ArrayDataStream(logData),
- i, logStart;
-
- logBeginOffsets = [];
-
- for (i = 0; ; i++) {
- logStart = stream.nextOffsetOf(FlightLogParser.prototype.FLIGHT_LOG_START_MARKER);
-
- if (logStart == -1) {
- //No more logs found in the file
- logBeginOffsets.push(stream.end);
- break;
- }
-
- logBeginOffsets.push(logStart);
-
- //Restart the search after this header
- stream.pos = logStart + FlightLogParser.prototype.FLIGHT_LOG_START_MARKER.length;
- }
+ //Private:
+ let that = this,
+ logBeginOffsets = false,
+ logCount = false,
+ intraframeDirectories = false;
+
+ function buildLogOffsetsIndex() {
+ let stream = new ArrayDataStream(logData),
+ i,
+ logStart;
+
+ logBeginOffsets = [];
+
+ for (i = 0; ; i++) {
+ logStart = stream.nextOffsetOf(
+ FlightLogParser.prototype.FLIGHT_LOG_START_MARKER
+ );
+
+ if (logStart == -1) {
+ //No more logs found in the file
+ logBeginOffsets.push(stream.end);
+ break;
+ }
+
+ logBeginOffsets.push(logStart);
+
+ //Restart the search after this header
+ stream.pos =
+ logStart + FlightLogParser.prototype.FLIGHT_LOG_START_MARKER.length;
}
-
- function buildIntraframeDirectories() {
- var
- parser = new FlightLogParser(logData, that);
+ }
+
+ function buildIntraframeDirectories() {
+ let parser = new FlightLogParser(logData, that);
+
+ intraframeDirectories = [];
+
+ for (let i = 0; i < that.getLogCount(); i++) {
+ var intraIndex = {
+ times: [],
+ offsets: [],
+ avgThrottle: [],
+ maxRC: [],
+ maxMotorDiff: [],
+ initialIMU: [],
+ initialSlow: [],
+ initialGPSHome: [],
+ initialGPS: [],
+ hasEvent: [],
+ minTime: false,
+ maxTime: false,
+ unLoggedTime: 0,
+ },
+ imu = new IMU(),
+ gyroADC,
+ accSmooth,
+ magADC,
+ iframeCount = 0,
+ motorFields = [],
+ maxRCFields = [],
+ matches,
+ throttleTotal,
+ rcTotal,
+ maxMotor,
+ minMotor,
+ eventInThisChunk = null,
+ parsedHeader,
+ sawEndMarker = false;
+
+ try {
+ parser.parseHeader(logBeginOffsets[i], logBeginOffsets[i + 1]);
+ parsedHeader = true;
+ } catch (e) {
+ console.log(`Error parsing header of log #${i + 1}: ${e}`);
+ intraIndex.error = e;
+
+ parsedHeader = false;
+ }
+
+ // Only attempt to parse the log if the header wasn't corrupt
+ if (parsedHeader) {
+ var sysConfig = parser.sysConfig,
+ mainFrameDef = parser.frameDefs.I,
+ gyroADC = [
+ mainFrameDef.nameToIndex["gyroADC[0]"],
+ mainFrameDef.nameToIndex["gyroADC[1]"],
+ mainFrameDef.nameToIndex["gyroADC[2]"],
+ ],
+ accSmooth = [
+ mainFrameDef.nameToIndex["accSmooth[0]"],
+ mainFrameDef.nameToIndex["accSmooth[1]"],
+ mainFrameDef.nameToIndex["accSmooth[2]"],
+ ],
+ magADC = [
+ mainFrameDef.nameToIndex["magADC[0]"],
+ mainFrameDef.nameToIndex["magADC[1]"],
+ mainFrameDef.nameToIndex["magADC[2]"],
+ ],
+ lastSlow = [],
+ lastGPSHome = [],
+ lastGPS = [];
+
+ // Identify motor fields so they can be used to show the activity summary bar
+ for (let j = 0; j < 8; j++) {
+ if (mainFrameDef.nameToIndex[`motor[${j}]`] !== undefined) {
+ motorFields.push(mainFrameDef.nameToIndex[`motor[${j}]`]);
+ }
+ }
+
+ for (let j = 0; j < 3; j++) {
+ if (mainFrameDef.nameToIndex[`rcCommand[${j}]`] !== undefined) {
+ maxRCFields.push(mainFrameDef.nameToIndex[`rcCommand[${j}]`]);
+ } else {
+ console.log("RCField not found");
+ }
+ }
+
+ // Do we have mag fields? If not mark that data as absent
+ if (magADC[0] === undefined) {
+ magADC = false;
+ }
- intraframeDirectories = [];
-
- for (var i = 0; i < that.getLogCount(); i++) {
- var
- intraIndex = {
- times: [],
- offsets: [],
- avgThrottle: [],
- maxRC: [],
- maxMotorDiff: [],
- initialIMU: [],
- initialSlow: [],
- initialGPSHome: [],
- initialGPS: [],
- hasEvent: [],
- minTime: false,
- maxTime: false
- },
-
- imu = new IMU(),
- gyroADC, accSmooth, magADC,
-
- iframeCount = 0,
- motorFields = [],
- maxRCFields = [],
- matches,
- throttleTotal,
- rcTotal,
- maxMotor,
- minMotor,
- eventInThisChunk = null,
- parsedHeader,
- sawEndMarker = false;
-
- try {
- parser.parseHeader(logBeginOffsets[i], logBeginOffsets[i + 1]);
- parsedHeader = true;
- } catch (e) {
- console.log("Error parsing header of log #" + (i + 1) + ": " + e);
- intraIndex.error = e;
-
- parsedHeader = false;
- }
-
- // Only attempt to parse the log if the header wasn't corrupt
- if (parsedHeader) {
- var
- sysConfig = parser.sysConfig,
- mainFrameDef = parser.frameDefs.I,
-
- gyroADC = [mainFrameDef.nameToIndex["gyroADC[0]"], mainFrameDef.nameToIndex["gyroADC[1]"], mainFrameDef.nameToIndex["gyroADC[2]"]],
- accSmooth = [mainFrameDef.nameToIndex["accSmooth[0]"], mainFrameDef.nameToIndex["accSmooth[1]"], mainFrameDef.nameToIndex["accSmooth[2]"]],
- magADC = [mainFrameDef.nameToIndex["magADC[0]"], mainFrameDef.nameToIndex["magADC[1]"], mainFrameDef.nameToIndex["magADC[2]"]],
-
- lastSlow = [],
- lastGPSHome = [],
- lastGPS = [];
-
- // Identify motor fields so they can be used to show the activity summary bar
- for (let j = 0; j < 8; j++) {
- if (mainFrameDef.nameToIndex["motor[" + j + "]"] !== undefined) {
- motorFields.push(mainFrameDef.nameToIndex["motor[" + j + "]"]);
- }
- }
+ let frameTime;
+ parser.onFrameReady = function (
+ frameValid,
+ frame,
+ frameType,
+ frameOffset,
+ frameSize
+ ) {
+ if (!frameValid) {
+ return;
+ }
- for (let j = 0; j < 3; j++) {
- if (mainFrameDef.nameToIndex["rcCommand[" + j + "]"] !== undefined) {
- maxRCFields.push(mainFrameDef.nameToIndex["rcCommand[" + j + "]"]);
- } else {
- console.log("RCField not found");
- }
- }
-
- // Do we have mag fields? If not mark that data as absent
- if (magADC[0] === undefined) {
- magADC = false;
- }
-
- parser.onFrameReady = function(frameValid, frame, frameType, frameOffset, frameSize) {
- if (!frameValid) {
- return;
+ switch (frameType) {
+ case "P":
+ case "I":
+ frameTime = frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
+ if (intraIndex.minTime === false) {
+ intraIndex.minTime = frameTime;
+ }
+
+ if (
+ intraIndex.maxTime === false ||
+ frameTime > intraIndex.maxTime
+ ) {
+ intraIndex.maxTime = frameTime;
+ }
+
+ if (frameType == "I") {
+ // Start a new chunk on every 4th I-frame
+ if (iframeCount % 4 === 0) {
+ // Log the beginning of the new chunk
+ intraIndex.times.push(frameTime);
+ intraIndex.offsets.push(frameOffset);
+
+ if (motorFields.length) {
+ throttleTotal = 0;
+ maxMotor = 0;
+ minMotor = 2000;
+ for (let mofo of motorFields) {
+ maxMotor = Math.max(frame[mofo], maxMotor);
+ minMotor = Math.min(frame[mofo], minMotor);
+ throttleTotal += frame[mofo];
}
-
- switch (frameType) {
- case 'P':
- case 'I':
- var
- frameTime = frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
-
- if (intraIndex.minTime === false) {
- intraIndex.minTime = frameTime;
- }
-
- if (intraIndex.maxTime === false || frameTime > intraIndex.maxTime) {
- intraIndex.maxTime = frameTime;
- }
-
- if (frameType == 'I') {
- // Start a new chunk on every 4th I-frame
- if (iframeCount % 4 === 0) {
- // Log the beginning of the new chunk
- intraIndex.times.push(frameTime);
- intraIndex.offsets.push(frameOffset);
-
- if (motorFields.length) {
- throttleTotal = 0;
- maxMotor = 0;
- minMotor = 2000;
- for (let mofo of motorFields) {
- maxMotor = Math.max(frame[mofo], maxMotor);
- minMotor = Math.min(frame[mofo], minMotor);
- throttleTotal += frame[mofo];
- }
-
- intraIndex.maxMotorDiff.push(maxMotor - minMotor);
- intraIndex.avgThrottle.push(Math.round(throttleTotal / motorFields.length));
- }
- if (maxRCFields.length) {
- rcTotal = 0;
- for (let rcfo of maxRCFields) {
- rcTotal += Math.max(rcTotal,Math.abs(frame[rcfo]));
- }
-
- intraIndex.maxRC.push(rcTotal);
- }
-
- /* To enable seeking to an arbitrary point in the log without re-reading anything
- * that came before, we have to record the initial state of various items which aren't
- * logged anew every iteration.
- */
- intraIndex.initialIMU.push(new IMU(imu));
- intraIndex.initialSlow.push(lastSlow);
- intraIndex.initialGPSHome.push(lastGPSHome);
- intraIndex.initialGPS.push(lastGPS);
- }
-
- iframeCount++;
- }
-
- imu.updateEstimatedAttitude(
- [frame[gyroADC[0]], frame[gyroADC[1]], frame[gyroADC[2]]],
- [frame[accSmooth[0]], frame[accSmooth[1]], frame[accSmooth[2]]],
- frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME],
- sysConfig.acc_1G,
- sysConfig.gyroScale,
- magADC ? [frame[magADC[0]], frame[magADC[1]], frame[magADC[2]]] : false
- );
- break;
- case 'G':
- lastGPS = frame.slice(0);
- lastGPS.shift(); // Remove the time field
- break;
- case 'H':
- lastGPSHome = frame.slice(0);
- break;
- case 'E':
- // Mark that there was an event inside the current chunk
- if (intraIndex.times.length > 0) {
- intraIndex.hasEvent[intraIndex.times.length - 1] = true;
- }
-
- if (frame.event == FlightLogEvent.LOG_END) {
- sawEndMarker = true;
- }
- break;
- case 'S':
- lastSlow = frame.slice(0);
- break;
+
+ intraIndex.maxMotorDiff.push(maxMotor - minMotor);
+ intraIndex.avgThrottle.push(
+ Math.round(throttleTotal / motorFields.length)
+ );
+ }
+ if (maxRCFields.length) {
+ rcTotal = 0;
+ for (let rcfo of maxRCFields) {
+ rcTotal += Math.max(rcTotal, Math.abs(frame[rcfo]));
}
- };
-
- try {
- parser.parseLogData(false);
- } catch (e) {
- intraIndex.error = e;
- }
-
- // Don't bother including the initial (empty) states for S and H frames if we didn't have any in the source data
- if (!parser.frameDefs.S) {
- delete intraIndex.initialSlow;
- }
- if (!parser.frameDefs.H) {
- delete intraIndex.initialGPSHome;
+ intraIndex.maxRC.push(rcTotal);
+ }
+
+ /* To enable seeking to an arbitrary point in the log without re-reading anything
+ * that came before, we have to record the initial state of various items which aren't
+ * logged anew every iteration.
+ */
+ intraIndex.initialIMU.push(new IMU(imu));
+ intraIndex.initialSlow.push(lastSlow);
+ intraIndex.initialGPSHome.push(lastGPSHome);
+ intraIndex.initialGPS.push(lastGPS);
}
- intraIndex.stats = parser.stats;
- }
-
- // Did we not find any events in this log?
- if (intraIndex.minTime === false) {
- if (sawEndMarker) {
- intraIndex.error = "Logging paused, no data";
- } else {
- intraIndex.error = "Log truncated, no data";
+ iframeCount++;
+ }
+
+ imu.updateEstimatedAttitude(
+ [frame[gyroADC[0]], frame[gyroADC[1]], frame[gyroADC[2]]],
+ [frame[accSmooth[0]], frame[accSmooth[1]], frame[accSmooth[2]]],
+ frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME],
+ sysConfig.acc_1G,
+ sysConfig.gyroScale,
+ magADC
+ ? [frame[magADC[0]], frame[magADC[1]], frame[magADC[2]]]
+ : false
+ );
+ break;
+ case "G":
+ lastGPS = frame.slice(0);
+ lastGPS.shift(); // Remove the time field
+ break;
+ case "H":
+ lastGPSHome = frame.slice(0);
+ break;
+ case "E":
+ // Mark that there was an event inside the current chunk
+ if (intraIndex.times.length > 0) {
+ intraIndex.hasEvent[intraIndex.times.length - 1] = true;
+ }
+
+ if (frame.event == FlightLogEvent.LOG_END) {
+ sawEndMarker = true;
+ }
+
+ if (frame.event == FlightLogEvent.LOGGING_RESUME) {
+ if (frameTime) {
+ intraIndex.unLoggedTime += frame.data.currentTime - frameTime;
}
- }
-
- intraframeDirectories.push(intraIndex);
+ }
+
+ break;
+ case "S":
+ lastSlow = frame.slice(0);
+ break;
+ }
+ };
+
+ try {
+ parser.parseLogData(false);
+ } catch (e) {
+ intraIndex.error = e;
+ }
+
+ // Don't bother including the initial (empty) states for S and H frames if we didn't have any in the source data
+ if (!parser.frameDefs.S) {
+ delete intraIndex.initialSlow;
+ }
+
+ if (!parser.frameDefs.H) {
+ delete intraIndex.initialGPSHome;
+ }
+
+ intraIndex.stats = parser.stats;
+ }
+
+ // Did we not find any events in this log?
+ if (intraIndex.minTime === false) {
+ if (sawEndMarker) {
+ intraIndex.error = "Logging paused, no data";
+ } else {
+ intraIndex.error = "Log truncated, no data";
}
+ }
+
+ intraframeDirectories.push(intraIndex);
}
-
- //Public:
- this.loadFromJSON = function(json) {
-
- };
-
- this.saveToJSON = function() {
- var
- intraframeDirectories = this.getIntraframeDirectories(),
- i, j,
- resultIndexes = new Array(intraframeDirectories.length);
-
- for (i = 0; i < intraframeDirectories.length; i++) {
- var
- lastTime, lastLastTime,
- lastOffset, lastLastOffset,
- lastThrottle,
-
- sourceIndex = intraframeDirectories[i],
-
- resultIndex = {
- times: new Array(sourceIndex.times.length),
- offsets: new Array(sourceIndex.offsets.length),
- minTime: sourceIndex.minTime,
- maxTime: sourceIndex.maxTime,
- avgThrottle: new Array(sourceIndex.avgThrottle.length),
- maxRC: new Array(sourceIndex.maxRC.length),
- maxMotorDiff: new Array(sourceIndex.maxMotorDiff.length),
- };
-
- if (sourceIndex.times.length > 0) {
- resultIndex.times[0] = sourceIndex.times[0];
- resultIndex.offsets[0] = sourceIndex.offsets[0];
-
- lastLastTime = lastTime = sourceIndex.times[0];
- lastLastOffset = lastOffset = sourceIndex.offsets[0];
-
- for (j = 1; j < sourceIndex.times.length; j++) {
- resultIndex.times[j] = sourceIndex.times[j] - 2 * lastTime + lastLastTime;
- resultIndex.offsets[j] = sourceIndex.offsets[j] - 2 * lastOffset + lastLastOffset;
-
- lastLastTime = lastTime;
- lastTime = sourceIndex.times[j];
-
- lastLastOffset = lastOffset;
- lastOffset = sourceIndex.offsets[j];
- }
- }
-
- if (sourceIndex.avgThrottle.length > 0) {
- // Assuming that avgThrottle, maxRC and maxMotorDiff Arrays are the same length
- // since they are build in the same loop. Just to get rid of a codesmell on Sonarcloud
- for (let j = 0; j < sourceIndex.avgThrottle.length; j++) {
- resultIndex.avgThrottle[j] = sourceIndex.avgThrottle[j] - 1000;
- resultIndex.maxRC[j] = sourceIndex.maxRC[j] * 20 - 1000;
- resultIndex.maxMotorDiff[j] = sourceIndex.maxMotorDiff[j] * 20 - 1000;
- }
- }
- resultIndexes[i] = resultIndex;
+ }
+
+ //Public:
+ this.loadFromJSON = function (json) {};
+
+ this.saveToJSON = function () {
+ let intraframeDirectories = this.getIntraframeDirectories(),
+ i,
+ j,
+ resultIndexes = new Array(intraframeDirectories.length);
+
+ for (i = 0; i < intraframeDirectories.length; i++) {
+ var lastTime,
+ lastLastTime,
+ lastOffset,
+ lastLastOffset,
+ lastThrottle,
+ sourceIndex = intraframeDirectories[i],
+ resultIndex = {
+ times: new Array(sourceIndex.times.length),
+ offsets: new Array(sourceIndex.offsets.length),
+ minTime: sourceIndex.minTime,
+ maxTime: sourceIndex.maxTime,
+ avgThrottle: new Array(sourceIndex.avgThrottle.length),
+ maxRC: new Array(sourceIndex.maxRC.length),
+ maxMotorDiff: new Array(sourceIndex.maxMotorDiff.length),
+ };
+
+ if (sourceIndex.times.length > 0) {
+ resultIndex.times[0] = sourceIndex.times[0];
+ resultIndex.offsets[0] = sourceIndex.offsets[0];
+
+ lastLastTime = lastTime = sourceIndex.times[0];
+ lastLastOffset = lastOffset = sourceIndex.offsets[0];
+
+ for (j = 1; j < sourceIndex.times.length; j++) {
+ resultIndex.times[j] =
+ sourceIndex.times[j] - 2 * lastTime + lastLastTime;
+ resultIndex.offsets[j] =
+ sourceIndex.offsets[j] - 2 * lastOffset + lastLastOffset;
+
+ lastLastTime = lastTime;
+ lastTime = sourceIndex.times[j];
+
+ lastLastOffset = lastOffset;
+ lastOffset = sourceIndex.offsets[j];
}
-
- return JSON.stringify(resultIndexes);
- };
-
- this.getLogBeginOffset = function(index) {
- if (!logBeginOffsets)
- buildLogOffsetsIndex();
-
- return logBeginOffsets[index];
- };
-
- this.getLogCount = function() {
- if (!logBeginOffsets)
- buildLogOffsetsIndex();
-
- return logBeginOffsets.length - 1;
- };
-
- this.getIntraframeDirectories = function() {
- if (!intraframeDirectories)
- buildIntraframeDirectories();
-
- return intraframeDirectories;
- };
-
- this.getIntraframeDirectory = function(logIndex) {
- return this.getIntraframeDirectories()[logIndex];
- };
+ }
+
+ if (sourceIndex.avgThrottle.length > 0) {
+ // Assuming that avgThrottle, maxRC and maxMotorDiff Arrays are the same length
+ // since they are build in the same loop. Just to get rid of a codesmell on Sonarcloud
+ for (let j = 0; j < sourceIndex.avgThrottle.length; j++) {
+ resultIndex.avgThrottle[j] = sourceIndex.avgThrottle[j] - 1000;
+ resultIndex.maxRC[j] = sourceIndex.maxRC[j] * 20 - 1000;
+ resultIndex.maxMotorDiff[j] = sourceIndex.maxMotorDiff[j] * 20 - 1000;
+ }
+ }
+ resultIndexes[i] = resultIndex;
+ }
+
+ return JSON.stringify(resultIndexes);
+ };
+
+ this.getLogBeginOffset = function (index) {
+ if (!logBeginOffsets) buildLogOffsetsIndex();
+
+ return logBeginOffsets[index];
+ };
+
+ this.getLogCount = function () {
+ if (!logBeginOffsets) buildLogOffsetsIndex();
+
+ return logBeginOffsets.length - 1;
+ };
+
+ this.getIntraframeDirectories = function () {
+ if (!intraframeDirectories) buildIntraframeDirectories();
+
+ return intraframeDirectories;
+ };
+
+ this.getIntraframeDirectory = function (logIndex) {
+ return this.getIntraframeDirectories()[logIndex];
+ };
}
diff --git a/src/flightlog_parser.js b/src/flightlog_parser.js
index 9bc48998..e87c22f4 100644
--- a/src/flightlog_parser.js
+++ b/src/flightlog_parser.js
@@ -1,15 +1,15 @@
import { FlightLogFieldPresenter } from "./flightlog_fields_presenter";
import { adjustFieldDefsList, FlightLogEvent } from "./flightlog_fielddefs";
import { ArrayDataStream } from "./datastream";
-import './decoders';
+import "./decoders";
import {
- hexToFloat,
- uint32ToFloat,
- asciiArrayToString,
- asciiStringToByteArray,
- signExtend14Bit,
- stringHasComma,
- parseCommaSeparatedString,
+ hexToFloat,
+ uint32ToFloat,
+ asciiArrayToString,
+ asciiStringToByteArray,
+ signExtend14Bit,
+ stringHasComma,
+ parseCommaSeparatedString,
} from "./tools";
globalThis.FIRMWARE_TYPE_UNKNOWN = 0;
@@ -19,1862 +19,2044 @@ globalThis.FIRMWARE_TYPE_BETAFLIGHT = 3;
globalThis.FIRMWARE_TYPE_INAV = 4;
export function FlightLogParser(logData) {
- //Private constants:
- var
- FLIGHT_LOG_MAX_FIELDS = 128,
- FLIGHT_LOG_MAX_FRAME_LENGTH = 256,
-
- //Assume that even in the most woeful logging situation, we won't miss 10 seconds of frames
- MAXIMUM_TIME_JUMP_BETWEEN_FRAMES = (10 * 1000000),
-
- //Likewise for iteration count
- MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES = (500 * 10),
-
- // Flight log field predictors:
-
- //No prediction:
- FLIGHT_LOG_FIELD_PREDICTOR_0 = 0,
-
- //Predict that the field is the same as last frame:
- FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS = 1,
-
- //Predict that the slope between this field and the previous item is the same as that between the past two history items:
- FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE = 2,
-
- //Predict that this field is the same as the average of the last two history items:
- FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2 = 3,
-
- //Predict that this field is minthrottle
- FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE = 4,
-
- //Predict that this field is the same as motor 0
- FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0 = 5,
-
- //This field always increments
- FLIGHT_LOG_FIELD_PREDICTOR_INC = 6,
-
- //Predict this GPS co-ordinate is the GPS home co-ordinate (or no prediction if that coordinate is not set)
- FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD = 7,
-
- //Predict 1500
- FLIGHT_LOG_FIELD_PREDICTOR_1500 = 8,
-
- //Predict vbatref, the reference ADC level stored in the header
- FLIGHT_LOG_FIELD_PREDICTOR_VBATREF = 9,
-
- //Predict the last time value written in the main stream
- FLIGHT_LOG_FIELD_PREDICTOR_LAST_MAIN_FRAME_TIME = 10,
-
- //Predict that this field is minthrottle
- FLIGHT_LOG_FIELD_PREDICTOR_MINMOTOR = 11,
-
- //Home coord predictors appear in pairs (two copies of FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD). Rewrite the second
- //one we see to this to make parsing easier
- FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1 = 256,
-
- FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB = 0, // Signed variable-byte
- FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB = 1, // Unsigned variable-byte
- FLIGHT_LOG_FIELD_ENCODING_NEG_14BIT = 3, // Unsigned variable-byte but we negate the value before storing, value is 14 bits
- FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB = 6,
- FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32 = 7,
- FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16 = 8,
- FLIGHT_LOG_FIELD_ENCODING_NULL = 9, // Nothing is written to the file, take value to be zero
- FLIGHT_LOG_FIELD_ENCODING_TAG2_3SVARIABLE = 10,
-
- FLIGHT_LOG_EVENT_LOG_END = 255,
-
- EOF = ArrayDataStream.prototype.EOF,
- NEWLINE = '\n'.charCodeAt(0),
-
- INFLIGHT_ADJUSTMENT_FUNCTIONS = [
- {
- name: 'None'
- },
- {
- name: 'RC Rate',
- scale: 0.01
- },
- {
- name : 'RC Expo',
- scale: 0.01
- },
- {
- name: 'Throttle Expo',
- scale: 0.01
- },
- {
- name: 'Pitch & Roll Rate',
- scale: 0.01
- },
- {
- name: 'Yaw rate',
- scale: 0.01
- },
- {
- name: 'Pitch & Roll P',
- scale: 0.1,
- scalef: 1
- },
- {
- name: 'Pitch & Roll I',
- scale: 0.001,
- scalef: 0.1
- },
- {
- name: 'Pitch & Roll D',
- scalef: 1000
- },
- {
- name: 'Yaw P',
- scale: 0.1,
- scalef: 1
- },
- {
- name: 'Yaw I',
- scale: 0.001,
- scalef: 0.1
- },
- {
- name: 'Yaw D',
- scalef: 1000
- },
- {
- name: "Rate Profile"
- },
- {
- name: 'Pitch Rate',
- scale: 0.01
- },
- {
- name: 'Roll Rate',
- scale: 0.01
- },
- {
- name: 'Pitch P',
- scale: 0.1,
- scalef: 1
- },
- {
- name: 'Pitch I',
- scale: 0.001,
- scalef: 0.1
- },
- {
- name: 'Pitch D',
- scalef: 1000
- },
- {
- name: 'Roll P',
- scale: 0.1,
- scalef: 1
- },
- {
- name : 'Roll I',
- scale: 0.001,
- scalef: 0.1
- },
- {
- name: 'Roll D',
- scalef: 1000
- }
- ];
-
- //Private variables:
- var
- that = this,
-
- dataVersion,
-
- defaultSysConfig = {
- frameIntervalI: 32,
- frameIntervalPNum: 1,
- frameIntervalPDenom: 1,
- firmwareType: FIRMWARE_TYPE_UNKNOWN,
- rcRate: 90,
- vbatscale: 110,
- vbatref: 4095,
- vbatmincellvoltage: 33,
- vbatmaxcellvoltage:43,
- vbatwarningcellvoltage: 35,
- gyroScale: 0.0001, // Not even close to the default, but it's hardware specific so we can't do much better
- acc_1G: 2048, // Ditto ^
- minthrottle: 1150,
- maxthrottle: 1850,
- currentMeterOffset: 0,
- currentMeterScale: 400,
- deviceUID: null
- },
-
- // Blackbox log header parameter names.
- // each name should exist in the blackbox log of the current firmware, or
- // be an older name which is translated into a current name in the table below
-
- defaultSysConfigExtension = {
- abs_control_gain:null, // Absolute control gain
- anti_gravity_gain:null, // Anti gravity gain
- anti_gravity_p_gain:null, // Anti gravity P gain
- anti_gravity_mode:null, // Anti gravity mode
- anti_gravity_threshold:null, // Anti gravity threshold for step mode
- anti_gravity_cutoff_hz:null, // Anti gravity Cutoff
- blackbox_high_resolution:null, // Blackbox high resolution mode
- thrMid:null, // Throttle Mid Position
- thrExpo:null, // Throttle Expo
- tpa_mode:null, // TPA Mode
- tpa_breakpoint:null, // TPA Breakpoint
- airmode_activate_throttle:null, // airmode activation level
- serialrx_provider:null, // name of the serial rx provider
- superExpoFactor:null, // Super Expo Factor
- rates:[null, null, null], // Rates [ROLL, PITCH, YAW]
- rate_limits:[1998, 1998, 1998], // Limits [ROLL, PITCH, YAW] with defaults for backward compatibility
- rc_rates:[null, null, null], // RC Rates [ROLL, PITCH, YAW]
- rc_expo:[null, null, null], // RC Expo [ROLL, PITCH, YAW]
- looptime:null, // Looptime
- gyro_sync_denom:null, // Gyro Sync Denom
- pid_process_denom:null, // PID Process Denom
- pidController:null, // Active PID Controller
- rollPID:[null, null, null], // Roll [P, I, D]
- pitchPID:[null, null, null], // Pitch[P, I, D]
- yawPID:[null, null, null], // Yaw [P, I, D]
- altPID:[null, null, null], // Altitude Hold [P, I, D]
- posPID:[null, null, null], // Position Hold [P, I, D]
- posrPID:[null, null, null], // Position Rate [P, I, D]
- navrPID:[null, null, null], // Nav Rate [P, I, D]
- levelPID:[null, null, null], // Level Mode [P, I, D]
- magPID:null, // Magnetometer P
- velPID:[null, null, null], // Velocity [P, I, D]
- yaw_p_limit:null, // Yaw P Limit
- yaw_lpf_hz:null, // Yaw LowPass Filter Hz
- dterm_average_count:null, // DTerm Average Count
- rollPitchItermResetRate:null, // ITerm Reset rate for Roll and Pitch
- yawItermResetRate:null, // ITerm Reset Rate for Yaw
- dshot_bidir:null, // DShot bidir protocol enabled
- dterm_lpf_hz:null, // DTerm Lowpass Filter Hz
- dterm_lpf_dyn_hz:[null, null], // DTerm Lowpass Dynamic Filter Min and Max Hz
- dterm_lpf_dyn_expo:null, // DTerm Lowpass Dynamic Filter Expo
- dterm_lpf2_hz:null, // DTerm Lowpass Filter Hz 2
- dterm_differentiator:null, // DTerm Differentiator
- H_sensitivity:null, // Horizon Sensitivity
- iterm_reset_offset:null, // I-Term reset offset
- deadband:null, // Roll, Pitch Deadband
- yaw_deadband:null, // Yaw Deadband
- gyro_lpf:null, // Gyro lpf setting.
- gyro_32khz_hardware_lpf:null, // Gyro 32khz hardware lpf setting. (post BF3.4)
- gyro_lowpass_hz:null, // Gyro Soft Lowpass Filter Hz
- gyro_lowpass_dyn_hz:[null, null], // Gyro Soft Lowpass Dynamic Filter Min and Max Hz
- gyro_lowpass_dyn_expo:null, // Gyro Soft Lowpass Dynamic Filter Expo
- gyro_lowpass2_hz:null, // Gyro Soft Lowpass Filter Hz 2
- gyro_notch_hz:null, // Gyro Notch Frequency
- gyro_notch_cutoff:null, // Gyro Notch Cutoff
- gyro_rpm_notch_harmonics:null, // Number of Harmonics in the gyro rpm filter
- gyro_rpm_notch_q:null, // Value of Q in the gyro rpm filter
- gyro_rpm_notch_min:null, // Min Hz for the gyro rpm filter
- rpm_notch_lpf:null, // Cutoff for smoothing rpm filter data
- dterm_rpm_notch_harmonics:null, // Number of Harmonics in the dterm rpm filter
- dterm_rpm_notch_q:null, // Value of Q in the dterm rpm filter
- dterm_rpm_notch_min:null, // Min Hz for the dterm rpm filter
- dterm_notch_hz:null, // Dterm Notch Frequency
- dterm_notch_cutoff:null, // Dterm Notch Cutoff
- acc_lpf_hz:null, // Accelerometer Lowpass filter Hz
- acc_hardware:null, // Accelerometer Hardware type
- baro_hardware:null, // Barometer Hardware type
- mag_hardware:null, // Magnetometer Hardware type
- gyro_cal_on_first_arm:null, // Gyro Calibrate on first arm
- vbat_pid_compensation:null, // VBAT PID compensation
- rate_limits:[null, null, null], // RC Rate limits
- rc_smoothing:null, // RC Control Smoothing
- rc_interpolation:null, // RC Control Interpolation type
- rc_interpolation_channels:null, // RC Control Interpotlation channels
- rc_interpolation_interval:null, // RC Control Interpolation Interval
- rc_smoothing_active_cutoffs:[null,null],// RC Smoothing active cutoffs
- rc_smoothing_cutoffs:[null, null], // RC Smoothing input and derivative cutoff
- rc_smoothing_filter_type:[null,null], // RC Smoothing input and derivative type
- rc_smoothing_rx_average:null, // RC Smoothing rx average read in ms
- rc_smoothing_debug_axis:null, // Axis recorded in the debug mode of rc_smoothing
- dterm_filter_type:null, // D term filtering type (PT1, BIQUAD, PT2, PT3)
- dterm_filter2_type:null, // D term 2 filtering type (PT1, BIQUAD, PT2, PT3)
- pidAtMinThrottle:null, // Stabilisation at zero throttle
- itermThrottleGain:null, // Betaflight PID
- ptermSetpointWeight:null, // Betaflight PID
- dtermSetpointWeight:null, // Betaflight PID
- yawRateAccelLimit:null, // Betaflight PID
- rateAccelLimit:null, // Betaflight PID
- gyro_soft_type:null, // Gyro soft filter type (PT1, BIQUAD, PT2, PT3)
- gyro_soft2_type:null, // Gyro soft filter 2 type (PT1, BIQUAD, PT2, PT3)
- debug_mode:null, // Selected Debug Mode
- features:null, // Activated features (e.g. MOTORSTOP etc)
- Craft_name:null, // Craft Name
- motorOutput:[null,null], // Minimum and maximum outputs to motor's
- digitalIdleOffset:null, // min throttle for d-shot (as a percentage)
- pidSumLimit:null, // PID sum limit
- pidSumLimitYaw:null, // PID sum limit yaw
- use_integrated_yaw : null, // Use integrated yaw
- d_min : [null, null, null], // D_Min [P, I, D]
- d_min_gain : null, // D_Min gain - D_Max in 4.3
- d_min_advance : null, // D_Min advance - D_Max in 4.3
- iterm_relax: null, // ITerm Relax mode
- iterm_relax_type: null, // ITerm Relax type
- iterm_relax_cutoff: null, // ITerm Relax cutoff
- dyn_notch_range: null, // Dyn Notch Range (LOW, MED, HIGH or AUTO)
- dyn_notch_width_percent: null, // Dyn Notch width percent distance between the two notches
- dyn_notch_q: null, // Dyn Notch width of each dynamic filter
- dyn_notch_min_hz: null, // Dyn Notch min limit in Hz for the filter
- dyn_notch_max_hz: null, // Dyn Notch max limit in Hz for the filter
- rates_type: null,
- fields_disabled_mask: null,
- vbat_sag_compensation: null,
- gyro_to_use: null,
- dynamic_idle_min_rpm: null,
- motor_poles: 1,
- ff_transition: null,
- ff_averaging: null,
- ff_smooth_factor: null,
- ff_jitter_factor: null,
- ff_boost: null,
- ff_max_rate_limit: null,
- rc_smoothing_mode:null, // ** 4.3** RC on or off (0 or 1)
- rc_smoothing_feedforward_hz:null, // RC Smoothing manual cutoff for feedforward
- rc_smoothing_setpoint_hz:null, // RC Smoothing manual cutoff for setpoint
- rc_smoothing_auto_factor_setpoint:null, // RC Smoothing auto factor for roll, pitch and yaw setpoint
- rc_smoothing_throttle_hz:null, // RC Smoothing manual cutoff for throttle
- rc_smoothing_auto_factor_throttle:null, // RC Smoothing cutoff for throttle
- rc_smoothing_active_cutoffs_ff_sp_thr:[null,null,null],// RC Smoothing active cutoffs feedforward, setpoint, throttle
- dyn_notch_count: null, // Number of dynamic notches 4.3
- rpm_filter_fade_range_hz: null, // Fade range for RPM notch filters in Hz
- dyn_idle_p_gain: null,
- dyn_idle_i_gain: null,
- dyn_idle_d_gain: null,
- dyn_idle_max_increase: null,
- simplified_pids_mode: null, // Simplified / slider PIDS
- simplified_pi_gain: null,
- simplified_i_gain: null,
- simplified_d_gain: null,
- simplified_dmax_gain: null,
- simplified_feedforward_gain: null,
- simplified_pitch_d_gain: null,
- simplified_pitch_pi_gain: null,
- simplified_master_multiplier: null,
- simplified_dterm_filter: null,
- simplified_dterm_filter_multiplier: null,
- simplified_gyro_filter: null,
- simplified_gyro_filter_multiplier: null,
- motor_output_limit: null, // motor output limit
- throttle_limit_type: null, // throttle limit
- throttle_limit_percent: null,
- throttle_boost: null, // throttle boost
- throttle_boost_cutoff: null,
- thrust_linear: null,
- tpa_low_rate: null,
- tpa_low_breakpoint: null,
- tpa_low_always: null,
- mixer_type: null,
- unknownHeaders : [] // Unknown Extra Headers
- },
-
- // Translation of the field values name to the sysConfig var where it must be stored
- // on the left are field names from the latest versions of blackbox.c
- // on the right are older field names that must exist in the list above
-
- translationValues = {
- acc_limit_yaw : "yawRateAccelLimit",
- accel_limit : "rateAccelLimit",
- acc_limit : "rateAccelLimit",
- anti_gravity_thresh : "anti_gravity_threshold",
- currentSensor : "currentMeter",
- d_notch_cut : "dterm_notch_cutoff",
- d_setpoint_weight : "dtermSetpointWeight",
- dterm_lowpass_hz : "dterm_lpf_hz",
- dterm_lowpass_dyn_hz : "dterm_lpf_dyn_hz",
- dterm_lowpass2_hz : "dterm_lpf2_hz",
- dterm_lpf1_type : "dterm_filter_type",
- dterm_lpf1_static_hz : "dterm_lpf_hz",
- dterm_lpf1_dyn_hz : "dterm_lpf_dyn_hz",
- dterm_lpf1_dyn_expo : "dterm_lpf_dyn_expo",
- dterm_lpf2_type : "dterm_filter2_type",
- dterm_lpf2_static_hz : "dterm_lpf2_hz",
- dterm_setpoint_weight : "dtermSetpointWeight",
- digital_idle_value : "digitalIdleOffset",
- d_max_gain : "d_min_gain",
- d_max_advance : "d_min_advance",
- dshot_idle_value : "digitalIdleOffset",
- dyn_idle_min_rpm : "dynamic_idle_min_rpm",
- feedforward_transition : "ff_transition",
- feedforward_averaging : "ff_averaging",
- feedforward_smooth_factor : "ff_smooth_factor",
- feedforward_jitter_factor : "ff_jitter_factor",
- feedforward_boost : "ff_boost",
- feedforward_max_rate_limit : "ff_max_rate_limit",
- feedforward_weight : "dtermSetpointWeight",
- gyro_hardware_lpf : "gyro_lpf",
- gyro_lowpass : "gyro_lowpass_hz",
- gyro_lowpass_type : "gyro_soft_type",
- gyro_lowpass2_type : "gyro_soft2_type",
- gyro_lpf1_type : "gyro_soft_type",
- gyro_lpf1_static_hz : "gyro_lowpass_hz",
- gyro_lpf1_dyn_hz : "gyro_lowpass_dyn_hz",
- gyro_lpf1_dyn_expo : "gyro_lowpass_dyn_expo",
- gyro_lpf2_type : "gyro_soft2_type",
- gyro_lpf2_static_hz : "gyro_lowpass2_hz",
- "gyro.scale" : "gyro_scale",
- iterm_windup : "itermWindupPointPercent",
- motor_pwm_protocol : "fast_pwm_protocol",
- pid_at_min_throttle : "pidAtMinThrottle",
- pidsum_limit : "pidSumLimit",
- pidsum_limit_yaw : "pidSumLimitYaw",
- rc_expo_yaw : "rcYawExpo",
- rc_interp : "rc_interpolation",
- rc_interp_int : "rc_interpolation_interval",
- rc_rate : "rc_rates",
- rc_rate_yaw : "rcYawRate",
- rc_smoothing : "rc_smoothing_mode",
- rc_smoothing_auto_factor : "rc_smoothing_auto_factor_setpoint",
- rc_smoothing_feedforward_cutoff : "rc_smoothing_feedforward_hz",
- rc_smoothing_setpoint_cutoff : "rc_smoothing_setpoint_hz",
- rc_smoothing_throttle_cutoff : "rc_smoothing_throttle_hz",
- rc_smoothing_type : "rc_smoothing_mode",
- rc_yaw_expo : "rcYawExpo",
- rcExpo : "rc_expo",
- rcRate : "rc_rates",
- rpm_filter_harmonics : "gyro_rpm_notch_harmonics",
- rpm_filter_q : "gyro_rpm_notch_q",
- rpm_filter_min_hz : "gyro_rpm_notch_min",
- rpm_filter_lpf_hz : "rpm_notch_lpf",
- setpoint_relax_ratio : "setpointRelaxRatio",
- setpoint_relaxation_ratio : "setpointRelaxRatio",
- thr_expo : "thrExpo",
- thr_mid : "thrMid",
- dynThrPID : "tpa_rate",
- use_unsynced_pwm : "unsynced_fast_pwm",
- vbat_scale : "vbatscale",
- vbat_pid_gain : "vbat_pid_compensation",
- yaw_accel_limit : "yawRateAccelLimit",
- yaw_lowpass_hz : "yaw_lpf_hz",
- thrust_linear : "thrust_linear",
- tpa_low_rate : "tpa_low_rate",
- tpa_low_breakpoint : "tpa_low_breakpoint",
- tpa_low_always : "tpa_low_always",
- mixer_type : "mixer_type",
- },
-
- frameTypes,
-
- // Blackbox state:
- mainHistoryRing,
-
- /* Points into blackboxHistoryRing to give us a circular buffer.
- *
- * 0 - space to decode new frames into, 1 - previous frame, 2 - previous previous frame
- *
- * Previous frame pointers are null when no valid history exists of that age.
- */
- mainHistory = [null, null, null],
- mainStreamIsValid = false,
-
- gpsHomeHistory = new Array(2), // 0 - space to decode new frames into, 1 - previous frame
- gpsHomeIsValid = false,
-
- //Because these events don't depend on previous events, we don't keep copies of the old state, just the current one:
- lastEvent,
- lastGPS,
- lastSlow,
-
- // How many intentionally un-logged frames did we skip over before we decoded the current frame?
- lastSkippedFrames,
-
- // Details about the last main frame that was successfully parsed
- lastMainFrameIteration,
- lastMainFrameTime,
-
- //The actual log data stream we're reading:
- stream;
-
- //Public fields:
-
- /* Information about the frame types the log contains, along with details on their fields.
- * Each entry is an object with field details {encoding:[], predictor:[], name:[], count:0, signed:[]}
- */
- this.frameDefs = {};
-
- // Lets add the custom extensions
- var completeSysConfig = $.extend({}, defaultSysConfig, defaultSysConfigExtension);
- this.sysConfig = Object.create(completeSysConfig); // Object.create(defaultSysConfig);
-
- /*
- * Event handler of the signature (frameValid, frame, frameType, frameOffset, frameSize)
- * called when a frame has been decoded.
+ //Private constants:
+ let FLIGHT_LOG_MAX_FIELDS = 128,
+ FLIGHT_LOG_MAX_FRAME_LENGTH = 256,
+ //Assume that even in the most woeful logging situation, we won't miss 10 seconds of frames
+ MAXIMUM_TIME_JUMP_BETWEEN_FRAMES = 10 * 1000000,
+ //Likewise for iteration count
+ MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES = 500 * 10,
+ // Flight log field predictors:
+
+ //No prediction:
+ FLIGHT_LOG_FIELD_PREDICTOR_0 = 0,
+ //Predict that the field is the same as last frame:
+ FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS = 1,
+ //Predict that the slope between this field and the previous item is the same as that between the past two history items:
+ FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE = 2,
+ //Predict that this field is the same as the average of the last two history items:
+ FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2 = 3,
+ //Predict that this field is minthrottle
+ FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE = 4,
+ //Predict that this field is the same as motor 0
+ FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0 = 5,
+ //This field always increments
+ FLIGHT_LOG_FIELD_PREDICTOR_INC = 6,
+ //Predict this GPS co-ordinate is the GPS home co-ordinate (or no prediction if that coordinate is not set)
+ FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD = 7,
+ //Predict 1500
+ FLIGHT_LOG_FIELD_PREDICTOR_1500 = 8,
+ //Predict vbatref, the reference ADC level stored in the header
+ FLIGHT_LOG_FIELD_PREDICTOR_VBATREF = 9,
+ //Predict the last time value written in the main stream
+ FLIGHT_LOG_FIELD_PREDICTOR_LAST_MAIN_FRAME_TIME = 10,
+ //Predict that this field is minthrottle
+ FLIGHT_LOG_FIELD_PREDICTOR_MINMOTOR = 11,
+ //Home coord predictors appear in pairs (two copies of FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD). Rewrite the second
+ //one we see to this to make parsing easier
+ FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1 = 256,
+ FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB = 0, // Signed variable-byte
+ FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB = 1, // Unsigned variable-byte
+ FLIGHT_LOG_FIELD_ENCODING_NEG_14BIT = 3, // Unsigned variable-byte but we negate the value before storing, value is 14 bits
+ FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB = 6,
+ FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32 = 7,
+ FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16 = 8,
+ FLIGHT_LOG_FIELD_ENCODING_NULL = 9, // Nothing is written to the file, take value to be zero
+ FLIGHT_LOG_FIELD_ENCODING_TAG2_3SVARIABLE = 10,
+ FLIGHT_LOG_EVENT_LOG_END = 255,
+ EOF = ArrayDataStream.prototype.EOF,
+ NEWLINE = "\n".charCodeAt(0),
+ INFLIGHT_ADJUSTMENT_FUNCTIONS = [
+ {
+ name: "None",
+ },
+ {
+ name: "RC Rate",
+ scale: 0.01,
+ },
+ {
+ name: "RC Expo",
+ scale: 0.01,
+ },
+ {
+ name: "Throttle Expo",
+ scale: 0.01,
+ },
+ {
+ name: "Pitch & Roll Rate",
+ scale: 0.01,
+ },
+ {
+ name: "Yaw rate",
+ scale: 0.01,
+ },
+ {
+ name: "Pitch & Roll P",
+ scale: 0.1,
+ scalef: 1,
+ },
+ {
+ name: "Pitch & Roll I",
+ scale: 0.001,
+ scalef: 0.1,
+ },
+ {
+ name: "Pitch & Roll D",
+ scalef: 1000,
+ },
+ {
+ name: "Yaw P",
+ scale: 0.1,
+ scalef: 1,
+ },
+ {
+ name: "Yaw I",
+ scale: 0.001,
+ scalef: 0.1,
+ },
+ {
+ name: "Yaw D",
+ scalef: 1000,
+ },
+ {
+ name: "Rate Profile",
+ },
+ {
+ name: "Pitch Rate",
+ scale: 0.01,
+ },
+ {
+ name: "Roll Rate",
+ scale: 0.01,
+ },
+ {
+ name: "Pitch P",
+ scale: 0.1,
+ scalef: 1,
+ },
+ {
+ name: "Pitch I",
+ scale: 0.001,
+ scalef: 0.1,
+ },
+ {
+ name: "Pitch D",
+ scalef: 1000,
+ },
+ {
+ name: "Roll P",
+ scale: 0.1,
+ scalef: 1,
+ },
+ {
+ name: "Roll I",
+ scale: 0.001,
+ scalef: 0.1,
+ },
+ {
+ name: "Roll D",
+ scalef: 1000,
+ },
+ ];
+
+ //Private variables:
+ let that = this,
+ dataVersion,
+ defaultSysConfig = {
+ frameIntervalI: 32,
+ frameIntervalPNum: 1,
+ frameIntervalPDenom: 1,
+ firmwareType: FIRMWARE_TYPE_UNKNOWN,
+ rcRate: 90,
+ vbatscale: 110,
+ vbatref: 4095,
+ vbatmincellvoltage: 33,
+ vbatmaxcellvoltage: 43,
+ vbatwarningcellvoltage: 35,
+ gyroScale: 0.0001, // Not even close to the default, but it's hardware specific so we can't do much better
+ acc_1G: 2048, // Ditto ^
+ minthrottle: 1150,
+ maxthrottle: 1850,
+ currentMeterOffset: 0,
+ currentMeterScale: 400,
+ deviceUID: null,
+ },
+ // Blackbox log header parameter names.
+ // each name should exist in the blackbox log of the current firmware, or
+ // be an older name which is translated into a current name in the table below
+
+ defaultSysConfigExtension = {
+ abs_control_gain: null, // Absolute control gain
+ anti_gravity_gain: null, // Anti gravity gain
+ anti_gravity_p_gain: null, // Anti gravity P gain
+ anti_gravity_mode: null, // Anti gravity mode
+ anti_gravity_threshold: null, // Anti gravity threshold for step mode
+ anti_gravity_cutoff_hz: null, // Anti gravity Cutoff
+ blackbox_high_resolution: null, // Blackbox high resolution mode
+ thrMid: null, // Throttle Mid Position
+ thrExpo: null, // Throttle Expo
+ tpa_mode: null, // TPA Mode
+ tpa_breakpoint: null, // TPA Breakpoint
+ airmode_activate_throttle: null, // airmode activation level
+ serialrx_provider: null, // name of the serial rx provider
+ superExpoFactor: null, // Super Expo Factor
+ rates: [null, null, null], // Rates [ROLL, PITCH, YAW]
+ rate_limits: [1998, 1998, 1998], // Limits [ROLL, PITCH, YAW] with defaults for backward compatibility
+ rc_rates: [null, null, null], // RC Rates [ROLL, PITCH, YAW]
+ rc_expo: [null, null, null], // RC Expo [ROLL, PITCH, YAW]
+ looptime: null, // Looptime
+ gyro_sync_denom: null, // Gyro Sync Denom
+ pid_process_denom: null, // PID Process Denom
+ pidController: null, // Active PID Controller
+ rollPID: [null, null, null], // Roll [P, I, D]
+ pitchPID: [null, null, null], // Pitch[P, I, D]
+ yawPID: [null, null, null], // Yaw [P, I, D]
+ altPID: [null, null, null], // Altitude Hold [P, I, D]
+ posPID: [null, null, null], // Position Hold [P, I, D]
+ posrPID: [null, null, null], // Position Rate [P, I, D]
+ navrPID: [null, null, null], // Nav Rate [P, I, D]
+ levelPID: [null, null, null], // Level Mode [P, I, D]
+ magPID: null, // Magnetometer P
+ velPID: [null, null, null], // Velocity [P, I, D]
+ yaw_p_limit: null, // Yaw P Limit
+ yaw_lpf_hz: null, // Yaw LowPass Filter Hz
+ dterm_average_count: null, // DTerm Average Count
+ rollPitchItermResetRate: null, // ITerm Reset rate for Roll and Pitch
+ yawItermResetRate: null, // ITerm Reset Rate for Yaw
+ dshot_bidir: null, // DShot bidir protocol enabled
+ dterm_lpf_hz: null, // DTerm Lowpass Filter Hz
+ dterm_lpf_dyn_hz: [null, null], // DTerm Lowpass Dynamic Filter Min and Max Hz
+ dterm_lpf_dyn_expo: null, // DTerm Lowpass Dynamic Filter Expo
+ dterm_lpf2_hz: null, // DTerm Lowpass Filter Hz 2
+ dterm_differentiator: null, // DTerm Differentiator
+ H_sensitivity: null, // Horizon Sensitivity
+ iterm_reset_offset: null, // I-Term reset offset
+ deadband: null, // Roll, Pitch Deadband
+ yaw_deadband: null, // Yaw Deadband
+ gyro_lpf: null, // Gyro lpf setting.
+ gyro_32khz_hardware_lpf: null, // Gyro 32khz hardware lpf setting. (post BF3.4)
+ gyro_lowpass_hz: null, // Gyro Soft Lowpass Filter Hz
+ gyro_lowpass_dyn_hz: [null, null], // Gyro Soft Lowpass Dynamic Filter Min and Max Hz
+ gyro_lowpass_dyn_expo: null, // Gyro Soft Lowpass Dynamic Filter Expo
+ gyro_lowpass2_hz: null, // Gyro Soft Lowpass Filter Hz 2
+ gyro_notch_hz: null, // Gyro Notch Frequency
+ gyro_notch_cutoff: null, // Gyro Notch Cutoff
+ gyro_rpm_notch_harmonics: null, // Number of Harmonics in the gyro rpm filter
+ gyro_rpm_notch_q: null, // Value of Q in the gyro rpm filter
+ gyro_rpm_notch_min: null, // Min Hz for the gyro rpm filter
+ rpm_notch_lpf: null, // Cutoff for smoothing rpm filter data
+ dterm_rpm_notch_harmonics: null, // Number of Harmonics in the dterm rpm filter
+ dterm_rpm_notch_q: null, // Value of Q in the dterm rpm filter
+ dterm_rpm_notch_min: null, // Min Hz for the dterm rpm filter
+ dterm_notch_hz: null, // Dterm Notch Frequency
+ dterm_notch_cutoff: null, // Dterm Notch Cutoff
+ acc_lpf_hz: null, // Accelerometer Lowpass filter Hz
+ acc_hardware: null, // Accelerometer Hardware type
+ baro_hardware: null, // Barometer Hardware type
+ mag_hardware: null, // Magnetometer Hardware type
+ gyro_cal_on_first_arm: null, // Gyro Calibrate on first arm
+ vbat_pid_compensation: null, // VBAT PID compensation
+ rate_limits: [null, null, null], // RC Rate limits
+ rc_smoothing: null, // RC Control Smoothing
+ rc_interpolation: null, // RC Control Interpolation type
+ rc_interpolation_channels: null, // RC Control Interpotlation channels
+ rc_interpolation_interval: null, // RC Control Interpolation Interval
+ rc_smoothing_active_cutoffs: [null, null], // RC Smoothing active cutoffs
+ rc_smoothing_cutoffs: [null, null], // RC Smoothing input and derivative cutoff
+ rc_smoothing_filter_type: [null, null], // RC Smoothing input and derivative type
+ rc_smoothing_rx_average: null, // RC Smoothing rx average read in ms
+ rc_smoothing_debug_axis: null, // Axis recorded in the debug mode of rc_smoothing
+ dterm_filter_type: null, // D term filtering type (PT1, BIQUAD, PT2, PT3)
+ dterm_filter2_type: null, // D term 2 filtering type (PT1, BIQUAD, PT2, PT3)
+ pidAtMinThrottle: null, // Stabilisation at zero throttle
+ itermThrottleGain: null, // Betaflight PID
+ ptermSetpointWeight: null, // Betaflight PID
+ dtermSetpointWeight: null, // Betaflight PID
+ yawRateAccelLimit: null, // Betaflight PID
+ rateAccelLimit: null, // Betaflight PID
+ gyro_soft_type: null, // Gyro soft filter type (PT1, BIQUAD, PT2, PT3)
+ gyro_soft2_type: null, // Gyro soft filter 2 type (PT1, BIQUAD, PT2, PT3)
+ debug_mode: null, // Selected Debug Mode
+ features: null, // Activated features (e.g. MOTORSTOP etc)
+ Craft_name: null, // Craft Name
+ motorOutput: [null, null], // Minimum and maximum outputs to motor's
+ digitalIdleOffset: null, // min throttle for d-shot (as a percentage)
+ pidSumLimit: null, // PID sum limit
+ pidSumLimitYaw: null, // PID sum limit yaw
+ use_integrated_yaw: null, // Use integrated yaw
+ d_max: [null, null, null], // D_MAX [ROLL, PITCH, YAW]
+ d_max_gain: null, // D_MAX gain
+ d_max_advance: null, // D_MAX advance
+ iterm_relax: null, // ITerm Relax mode
+ iterm_relax_type: null, // ITerm Relax type
+ iterm_relax_cutoff: null, // ITerm Relax cutoff
+ dyn_notch_range: null, // Dyn Notch Range (LOW, MED, HIGH or AUTO)
+ dyn_notch_width_percent: null, // Dyn Notch width percent distance between the two notches
+ dyn_notch_q: null, // Dyn Notch width of each dynamic filter
+ dyn_notch_min_hz: null, // Dyn Notch min limit in Hz for the filter
+ dyn_notch_max_hz: null, // Dyn Notch max limit in Hz for the filter
+ rates_type: null,
+ fields_disabled_mask: null,
+ vbat_sag_compensation: null,
+ gyro_to_use: null,
+ dynamic_idle_min_rpm: null,
+ motor_poles: 1,
+ ff_transition: null,
+ ff_averaging: null,
+ ff_smooth_factor: null,
+ ff_jitter_factor: null,
+ ff_boost: null,
+ ff_max_rate_limit: null,
+ rc_smoothing_mode: null, // ** 4.3** RC on or off (0 or 1)
+ rc_smoothing_feedforward_hz: null, // RC Smoothing manual cutoff for feedforward
+ rc_smoothing_setpoint_hz: null, // RC Smoothing manual cutoff for setpoint
+ rc_smoothing_auto_factor_setpoint: null, // RC Smoothing auto factor for roll, pitch and yaw setpoint
+ rc_smoothing_throttle_hz: null, // RC Smoothing manual cutoff for throttle
+ rc_smoothing_auto_factor_throttle: null, // RC Smoothing cutoff for throttle
+ rc_smoothing_active_cutoffs_ff_sp_thr: [null, null, null], // RC Smoothing active cutoffs feedforward, setpoint, throttle
+ rc_smoothing_rx_smoothed: null,
+ dyn_notch_count: null, // Number of dynamic notches 4.3
+ rpm_filter_fade_range_hz: null, // Fade range for RPM notch filters in Hz
+ dyn_idle_p_gain: null,
+ dyn_idle_i_gain: null,
+ dyn_idle_d_gain: null,
+ dyn_idle_start_increase: null,
+ dyn_idle_max_increase: null,
+ simplified_pids_mode: null, // Simplified / slider PIDS
+ simplified_pi_gain: null,
+ simplified_i_gain: null,
+ simplified_d_gain: null,
+ simplified_d_max_gain: null,
+ simplified_feedforward_gain: null,
+ simplified_pitch_d_gain: null,
+ simplified_pitch_pi_gain: null,
+ simplified_master_multiplier: null,
+ simplified_dterm_filter: null,
+ simplified_dterm_filter_multiplier: null,
+ simplified_gyro_filter: null,
+ simplified_gyro_filter_multiplier: null,
+ motor_output_limit: null, // motor output limit
+ throttle_limit_type: null, // throttle limit
+ throttle_limit_percent: null,
+ throttle_boost: null, // throttle boost
+ throttle_boost_cutoff: null,
+ thrust_linear: null,
+ tpa_low_rate: null,
+ tpa_low_breakpoint: null,
+ tpa_low_always: null,
+ mixer_type: null,
+ chirp_lag_freq_hz: null,
+ chirp_lead_freq_hz: null,
+ chirp_amplitude_roll: null,
+ chirp_amplitude_pitch: null,
+ chirp_amplitude_yaw: null,
+ chirp_frequency_start_deci_hz: null,
+ chirp_frequency_end_deci_hz: null,
+ chirp_time_seconds: null,
+ unknownHeaders: [], // Unknown Extra Headers
+ },
+ // Translation of the field values name to the sysConfig var where it must be stored
+ // on the left are field names from the latest versions of blackbox.c
+ // on the right are older field names that must exist in the list above
+
+ translationValues = {
+ acc_limit_yaw: "yawRateAccelLimit",
+ accel_limit: "rateAccelLimit",
+ acc_limit: "rateAccelLimit",
+ anti_gravity_thresh: "anti_gravity_threshold",
+ currentSensor: "currentMeter",
+ d_notch_cut: "dterm_notch_cutoff",
+ d_setpoint_weight: "dtermSetpointWeight",
+ dterm_lowpass_hz: "dterm_lpf_hz",
+ dterm_lowpass_dyn_hz: "dterm_lpf_dyn_hz",
+ dterm_lowpass2_hz: "dterm_lpf2_hz",
+ dterm_lpf1_type: "dterm_filter_type",
+ dterm_lpf1_static_hz: "dterm_lpf_hz",
+ dterm_lpf1_dyn_hz: "dterm_lpf_dyn_hz",
+ dterm_lpf1_dyn_expo: "dterm_lpf_dyn_expo",
+ dterm_lpf2_type: "dterm_filter2_type",
+ dterm_lpf2_static_hz: "dterm_lpf2_hz",
+ dterm_setpoint_weight: "dtermSetpointWeight",
+ digital_idle_value: "digitalIdleOffset",
+ simplified_dmax_gain: "simplified_d_max_gain",
+ d_max: "d_min",
+ dshot_idle_value: "digitalIdleOffset",
+ dyn_idle_min_rpm: "dynamic_idle_min_rpm",
+ feedforward_transition: "ff_transition",
+ feedforward_averaging: "ff_averaging",
+ feedforward_smooth_factor: "ff_smooth_factor",
+ feedforward_jitter_factor: "ff_jitter_factor",
+ feedforward_boost: "ff_boost",
+ feedforward_max_rate_limit: "ff_max_rate_limit",
+ feedforward_weight: "dtermSetpointWeight",
+ gyro_hardware_lpf: "gyro_lpf",
+ gyro_lowpass: "gyro_lowpass_hz",
+ gyro_lowpass_type: "gyro_soft_type",
+ gyro_lowpass2_type: "gyro_soft2_type",
+ gyro_lpf1_type: "gyro_soft_type",
+ gyro_lpf1_static_hz: "gyro_lowpass_hz",
+ gyro_lpf1_dyn_hz: "gyro_lowpass_dyn_hz",
+ gyro_lpf1_dyn_expo: "gyro_lowpass_dyn_expo",
+ gyro_lpf2_type: "gyro_soft2_type",
+ gyro_lpf2_static_hz: "gyro_lowpass2_hz",
+ "gyro.scale": "gyro_scale",
+ iterm_windup: "itermWindupPointPercent",
+ motor_pwm_protocol: "fast_pwm_protocol",
+ pid_at_min_throttle: "pidAtMinThrottle",
+ pidsum_limit: "pidSumLimit",
+ pidsum_limit_yaw: "pidSumLimitYaw",
+ rc_expo_yaw: "rcYawExpo",
+ rc_interp: "rc_interpolation",
+ rc_interp_int: "rc_interpolation_interval",
+ rc_rate: "rc_rates",
+ rc_rate_yaw: "rcYawRate",
+ rc_smoothing: "rc_smoothing_mode",
+ rc_smoothing_auto_factor: "rc_smoothing_auto_factor_setpoint",
+ rc_smoothing_feedforward_cutoff: "rc_smoothing_feedforward_hz",
+ rc_smoothing_setpoint_cutoff: "rc_smoothing_setpoint_hz",
+ rc_smoothing_throttle_cutoff: "rc_smoothing_throttle_hz",
+ rc_smoothing_type: "rc_smoothing_mode",
+ rc_yaw_expo: "rcYawExpo",
+ rcExpo: "rc_expo",
+ rcRate: "rc_rates",
+ rpm_filter_harmonics: "gyro_rpm_notch_harmonics",
+ rpm_filter_q: "gyro_rpm_notch_q",
+ rpm_filter_min_hz: "gyro_rpm_notch_min",
+ rpm_filter_lpf_hz: "rpm_notch_lpf",
+ setpoint_relax_ratio: "setpointRelaxRatio",
+ setpoint_relaxation_ratio: "setpointRelaxRatio",
+ thr_expo: "thrExpo",
+ thr_mid: "thrMid",
+ dynThrPID: "tpa_rate",
+ use_unsynced_pwm: "unsynced_fast_pwm",
+ vbat_scale: "vbatscale",
+ vbat_pid_gain: "vbat_pid_compensation",
+ yaw_accel_limit: "yawRateAccelLimit",
+ yaw_lowpass_hz: "yaw_lpf_hz",
+ thrust_linear: "thrust_linear",
+ tpa_low_rate: "tpa_low_rate",
+ tpa_low_breakpoint: "tpa_low_breakpoint",
+ tpa_low_always: "tpa_low_always",
+ mixer_type: "mixer_type",
+ chirp_lag_freq_hz : "chirp_lag_freq_hz",
+ chirp_lead_freq_hz : "chirp_lead_freq_hz",
+ chirp_amplitude_roll : "chirp_amplitude_roll",
+ chirp_amplitude_pitch : "chirp_amplitude_pitch",
+ chirp_amplitude_yaw : "chirp_amplitude_yaw",
+ chirp_frequency_start_deci_hz : "chirp_frequency_start_deci_hz",
+ chirp_frequency_end_deci_hz : "chirp_frequency_end_deci_hz",
+ chirp_time_seconds : "chirp_time_seconds",
+ },
+ frameTypes,
+ // Blackbox state:
+ mainHistoryRing,
+ /* Points into blackboxHistoryRing to give us a circular buffer.
+ *
+ * 0 - space to decode new frames into, 1 - previous frame, 2 - previous previous frame
+ *
+ * Previous frame pointers are null when no valid history exists of that age.
*/
- this.onFrameReady = null;
+ mainHistory = [null, null, null],
+ mainStreamIsValid = false,
+ gpsHomeHistory = new Array(2), // 0 - space to decode new frames into, 1 - previous frame
+ gpsHomeIsValid = false,
+ //Because these events don't depend on previous events, we don't keep copies of the old state, just the current one:
+ lastEvent,
+ lastGPS,
+ lastSlow,
+ // How many intentionally un-logged frames did we skip over before we decoded the current frame?
+ lastSkippedFrames,
+ // Details about the last main frame that was successfully parsed
+ lastMainFrameIteration,
+ lastMainFrameTime,
+ //The actual log data stream we're reading:
+ stream;
+
+ //Public fields:
+
+ /* Information about the frame types the log contains, along with details on their fields.
+ * Each entry is an object with field details {encoding:[], predictor:[], name:[], count:0, signed:[]}
+ */
+ this.frameDefs = {};
+
+ // Lets add the custom extensions
+ let completeSysConfig = $.extend(
+ {},
+ defaultSysConfig,
+ defaultSysConfigExtension
+ );
+ this.sysConfig = Object.create(completeSysConfig); // Object.create(defaultSysConfig);
+
+ /*
+ * Event handler of the signature (frameValid, frame, frameType, frameOffset, frameSize)
+ * called when a frame has been decoded.
+ */
+ this.onFrameReady = null;
+
+ function mapFieldNamesToIndex(fieldNames) {
+ let result = {};
+
+ for (let i = 0; i < fieldNames.length; i++) {
+ result[fieldNames[i]] = i;
+ }
- function mapFieldNamesToIndex(fieldNames) {
- var
- result = {};
+ return result;
+ }
- for (var i = 0; i < fieldNames.length; i++) {
- result[fieldNames[i]] = i;
- }
+ /**
+ * Translates old field names in the given array to their modern equivalents and return the passed array.
+ */
+ function translateLegacyFieldNames(names) {
+ for (let i = 0; i < names.length; i++) {
+ var matches;
- return result;
+ if ((matches = names[i].match(/^gyroData(.+)$/))) {
+ names[i] = `gyroADC${matches[1]}`;
+ }
}
- /**
- * Translates old field names in the given array to their modern equivalents and return the passed array.
- */
- function translateLegacyFieldNames(names) {
- for (var i = 0; i < names.length; i++) {
- var
- matches;
-
- if ((matches = names[i].match(/^gyroData(.+)$/))) {
- names[i] = "gyroADC" + matches[1];
- }
- }
-
- return names;
+ return names;
+ }
+
+ /**
+ * Translates the name of a field to the parameter in sysConfig object equivalent
+ *
+ * fieldName Name of the field to translate
+ * returns The equivalent in the sysConfig object or the fieldName if not found
+ */
+ function translateFieldName(fieldName) {
+ let translation = translationValues[fieldName];
+ if (typeof translation !== "undefined") {
+ return translation;
+ } else {
+ return fieldName;
+ }
+ }
+
+ function parseHeaderLine() {
+ var COLON = ":".charCodeAt(0),
+ fieldName,
+ fieldValue,
+ lineStart,
+ lineEnd,
+ separatorPos = false,
+ matches,
+ i,
+ c;
+
+ if (stream.peekChar() != " ") return;
+
+ //Skip the leading space
+ stream.readChar();
+
+ lineStart = stream.pos;
+
+ for (
+ ;
+ stream.pos < lineStart + 1024 && stream.pos < stream.end;
+ stream.pos++
+ ) {
+ if (separatorPos === false && stream.data[stream.pos] == COLON)
+ separatorPos = stream.pos;
+
+ if (stream.data[stream.pos] == NEWLINE || stream.data[stream.pos] === 0)
+ break;
}
- /**
- * Translates the name of a field to the parameter in sysConfig object equivalent
- *
- * fieldName Name of the field to translate
- * returns The equivalent in the sysConfig object or the fieldName if not found
- */
- function translateFieldName(fieldName) {
- var translation = translationValues[fieldName];
- if (typeof translation !== 'undefined') {
- return translation;
+ if (stream.data[stream.pos] != NEWLINE || separatorPos === false) return;
+
+ lineEnd = stream.pos;
+
+ fieldName = asciiArrayToString(
+ stream.data.subarray(lineStart, separatorPos)
+ );
+ fieldValue = asciiArrayToString(
+ stream.data.subarray(separatorPos + 1, lineEnd)
+ );
+
+ // Translate the fieldName to the sysConfig parameter name. The fieldName has been changing between versions
+ // In this way is easier to maintain the code
+ fieldName = translateFieldName(fieldName);
+
+ switch (fieldName) {
+ case "I interval":
+ that.sysConfig.frameIntervalI = parseInt(fieldValue, 10);
+ if (that.sysConfig.frameIntervalI < 1)
+ that.sysConfig.frameIntervalI = 1;
+ break;
+ case "P interval":
+ matches = fieldValue.match(/(\d+)\/(\d+)/);
+
+ if (matches) {
+ that.sysConfig.frameIntervalPNum = parseInt(matches[1], 10);
+ that.sysConfig.frameIntervalPDenom = parseInt(matches[2], 10);
} else {
- return fieldName;
+ that.sysConfig.frameIntervalPNum = 1;
+ that.sysConfig.frameIntervalPDenom = parseInt(fieldValue, 10);
}
- }
-
- function parseHeaderLine() {
- var
- COLON = ":".charCodeAt(0),
-
- fieldName, fieldValue,
- lineStart, lineEnd, separatorPos = false,
- matches,
- i, c;
-
- if (stream.peekChar() != ' ')
- return;
-
- //Skip the leading space
- stream.readChar();
-
- lineStart = stream.pos;
-
- for (; stream.pos < lineStart + 1024 && stream.pos < stream.end; stream.pos++) {
- if (separatorPos === false && stream.data[stream.pos] == COLON)
- separatorPos = stream.pos;
-
- if (stream.data[stream.pos] == NEWLINE || stream.data[stream.pos] === 0)
- break;
+ break;
+ case "P denom":
+ case "P ratio":
+ // Don't do nothing with this, because is the same than frameIntervalI/frameIntervalPDenom so we don't need it
+ break;
+ case "Data version":
+ dataVersion = parseInt(fieldValue, 10);
+ break;
+ case "Firmware type":
+ switch (fieldValue) {
+ case "Cleanflight":
+ that.sysConfig.firmwareType = FIRMWARE_TYPE_CLEANFLIGHT;
+ $("html").removeClass("isBaseF");
+ $("html").addClass("isCF");
+ $("html").removeClass("isBF");
+ $("html").removeClass("isINAV");
+ break;
+ default:
+ that.sysConfig.firmwareType = FIRMWARE_TYPE_BASEFLIGHT;
+ $("html").addClass("isBaseF");
+ $("html").removeClass("isCF");
+ $("html").removeClass("isBF");
+ $("html").removeClass("isINAV");
}
+ break;
+
+ // Betaflight Log Header Parameters
+ case "minthrottle":
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ that.sysConfig.motorOutput[0] = that.sysConfig[fieldName]; // by default, set the minMotorOutput to match minThrottle
+ break;
+ case "maxthrottle":
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ that.sysConfig.motorOutput[1] = that.sysConfig[fieldName]; // by default, set the maxMotorOutput to match maxThrottle
+ break;
+ case "rcRate":
+ case "thrMid":
+ case "thrExpo":
+ case "tpa_rate":
+ case "tpa_mode":
+ case "tpa_breakpoint":
+ case "airmode_activate_throttle":
+ case "serialrx_provider":
+ case "looptime":
+ case "gyro_sync_denom":
+ case "pid_process_denom":
+ case "pidController":
+ case "yaw_p_limit":
+ case "dterm_average_count":
+ case "rollPitchItermResetRate":
+ case "yawItermResetRate":
+ case "rollPitchItermIgnoreRate":
+ case "yawItermIgnoreRate":
+ case "dterm_differentiator":
+ case "deltaMethod":
+ case "dynamic_dterm_threshold":
+ case "dynamic_pterm":
+ case "iterm_reset_offset":
+ case "deadband":
+ case "yaw_deadband":
+ case "gyro_lpf":
+ case "gyro_hardware_lpf":
+ case "gyro_32khz_hardware_lpf":
+ case "acc_lpf_hz":
+ case "acc_hardware":
+ case "baro_hardware":
+ case "mag_hardware":
+ case "gyro_cal_on_first_arm":
+ case "vbat_pid_compensation":
+ case "rc_smoothing":
+ case "rc_smoothing_type":
+ case "rc_smoothing_debug_axis":
+ case "rc_smoothing_rx_average":
+ case "rc_smoothing_rx_smoothed":
+ case "rc_smoothing_mode": // 4.3 rc smoothing stuff
+ case "rc_smoothing_auto_factor_setpoint":
+ case "rc_smoothing_auto_factor_throttle":
+ case "rc_smoothing_feedforward_hz":
+ case "rc_smoothing_setpoint_hz":
+ case "rc_smoothing_feedforward_hz":
+ case "rc_smoothing_throttle_hz":
+ case "superExpoYawMode":
+ case "features":
+ case "dynamic_pid":
+ case "rc_interpolation":
+ case "rc_interpolation_channels":
+ case "rc_interpolation_interval":
+ case "unsynced_fast_pwm":
+ case "fast_pwm_protocol":
+ case "motor_pwm_rate":
+ case "vbatscale":
+ case "vbatref":
+ case "acc_1G":
+ case "dterm_filter_type":
+ case "dterm_filter2_type":
+ case "pidAtMinThrottle":
+ case "pidSumLimit":
+ case "pidSumLimitYaw":
+ case "anti_gravity_threshold":
+ case "itermWindupPointPercent":
+ case "ptermSRateWeight":
+ case "setpointRelaxRatio":
+ case "ff_transition":
+ case "ff_averaging":
+ case "ff_smooth_factor":
+ case "ff_jitter_factor":
+ case "ff_boost":
+ case "ff_max_rate_limit":
+ case "dtermSetpointWeight":
+ case "gyro_soft_type":
+ case "gyro_soft2_type":
+ case "debug_mode":
+ case "anti_gravity_mode":
+ case "anti_gravity_gain":
+ case "anti_gravity_p_gain":
+ case "anti_gravity_cutoff_hz":
+ case "abs_control_gain":
+ case "use_integrated_yaw":
+ case "d_max_gain":
+ case "d_max_advance":
+ case "dshot_bidir":
+ case "gyro_rpm_notch_harmonics":
+ case "gyro_rpm_notch_q":
+ case "gyro_rpm_notch_min":
+ case "rpm_filter_fade_range_hz":
+ case "rpm_notch_lpf":
+ case "dterm_rpm_notch_harmonics":
+ case "dterm_rpm_notch_q":
+ case "dterm_rpm_notch_min":
+ case "iterm_relax":
+ case "iterm_relax_type":
+ case "iterm_relax_cutoff":
+ case "dyn_notch_range":
+ case "dyn_notch_width_percent":
+ case "dyn_notch_q":
+ case "dyn_notch_count":
+ case "dyn_notch_min_hz":
+ case "dyn_notch_max_hz":
+ case "rates_type":
+ case "vbat_sag_compensation":
+ case "fields_disabled_mask":
+ case "motor_pwm_protocol":
+ case "gyro_to_use":
+ case "dynamic_idle_min_rpm":
+ case "dyn_idle_p_gain":
+ case "dyn_idle_i_gain":
+ case "dyn_idle_d_gain":
+ case "dyn_idle_start_increase":
+ case "dyn_idle_max_increase":
+ case "simplified_pids_mode":
+ case "simplified_pi_gain":
+ case "simplified_i_gain":
+ case "simplified_d_gain":
+ case "simplified_dmax_gain":
+ case "simplified_d_max_gain":
+ case "simplified_feedforward_gain":
+ case "simplified_pitch_d_gain":
+ case "simplified_pitch_pi_gain":
+ case "simplified_master_multiplier":
+
+ case "simplified_dterm_filter":
+ case "simplified_dterm_filter_multiplier":
+ case "simplified_gyro_filter":
+ case "simplified_gyro_filter_multiplier":
+
+ case "motor_output_limit":
+ case "throttle_limit_type":
+ case "throttle_limit_percent":
+ case "throttle_boost":
+ case "throttle_boost_cutoff":
+
+ case "motor_poles":
+
+ case "blackbox_high_resolution":
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ break;
+ case "rc_expo":
+ case "rc_rates":
+ if (stringHasComma(fieldValue)) {
+ that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
+ } else {
+ that.sysConfig[fieldName][0] = parseInt(fieldValue, 10);
+ that.sysConfig[fieldName][1] = parseInt(fieldValue, 10);
+ }
+ break;
+ case "rcYawExpo":
+ that.sysConfig["rc_expo"][2] = parseInt(fieldValue, 10);
+ break;
+ case "rcYawRate":
+ that.sysConfig["rc_rates"][2] = parseInt(fieldValue, 10);
+ break;
+
+ case "yawRateAccelLimit":
+ case "rateAccelLimit":
+ if (
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "3.1.0")) ||
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "2.0.0"))
+ ) {
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 1000;
+ } else {
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ }
+ break;
+
+ case "yaw_lpf_hz":
+ case "gyro_lowpass_hz":
+ case "gyro_lowpass2_hz":
+ case "dterm_notch_hz":
+ case "dterm_notch_cutoff":
+ case "dterm_lpf_hz":
+ case "dterm_lpf2_hz":
+ if (
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "3.0.1")) ||
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "2.0.0"))
+ ) {
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ } else {
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
+ }
+ break;
+
+ case "gyro_notch_hz":
+ case "gyro_notch_cutoff":
+ if (
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "3.0.1")) ||
+ (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT &&
+ semver.gte(that.sysConfig.firmwareVersion, "2.0.0"))
+ ) {
+ that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
+ } else {
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
+ }
+ break;
+
+ case "motor_idle":
+ case "digitalIdleOffset":
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
+
+ /** Cleanflight Only log headers **/
+ case "dterm_cut_hz":
+ case "acc_cut_hz":
+ that.sysConfig[fieldName] = parseInt(fieldValue, 10);
+ break;
+ /** End of cleanflight only log headers **/
+
+ case "superExpoFactor":
+ if (stringHasComma(fieldValue)) {
+ let expoParams = parseCommaSeparatedString(fieldValue);
+ that.sysConfig.superExpoFactor = expoParams[0];
+ that.sysConfig.superExpoFactorYaw = expoParams[1];
+ } else {
+ that.sysConfig.superExpoFactor = parseInt(fieldValue, 10);
+ }
+ break;
+
+ /* CSV packed values */
+
+ case "rates":
+ case "rate_limits":
+ case "rollPID":
+ case "pitchPID":
+ case "yawPID":
+ case "altPID":
+ case "posPID":
+ case "posrPID":
+ case "navrPID":
+ case "levelPID":
+ case "velPID":
+ case "motorOutput":
+ case "rate_limits":
+ case "rc_smoothing_cutoffs":
+ case "rc_smoothing_active_cutoffs":
+ case "rc_smoothing_active_cutoffs_ff_sp_thr":
+ case "gyro_lowpass_dyn_hz":
+ case "gyro_lowpass_dyn_expo":
+ case "dterm_lpf_dyn_expo":
+ case "thrust_linear":
+ case "tpa_low_rate":
+ case "tpa_low_breakpoint":
+ case "tpa_low_always":
+ case "mixer_type":
+ case "chirp_lag_freq_hz":
+ case "chirp_lead_freq_hz":
+ case "chirp_amplitude_roll":
+ case "chirp_amplitude_pitch":
+ case "chirp_amplitude_yaw":
+ case "chirp_frequency_start_deci_hz":
+ case "chirp_frequency_end_deci_hz":
+ case "chirp_time_seconds":
+ case "dterm_lpf_dyn_hz":
+ that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
+ break;
+ case "magPID":
+ that.sysConfig.magPID = parseCommaSeparatedString(fieldValue, 3); //[parseInt(fieldValue, 10), null, null];
+ break;
+ case "d_min":
+ case "d_max":
+ // Add D MAX values as Derivative numbers to PID array
+ var dMaxValues = parseCommaSeparatedString(fieldValue);
+ that.sysConfig["rollPID"].push(dMaxValues[0]);
+ that.sysConfig["pitchPID"].push(dMaxValues[1]);
+ that.sysConfig["yawPID"].push(dMaxValues[2]);
+ break;
+ case "ff_weight":
+ // Add feedforward values to the PID array
+ var ffValues = parseCommaSeparatedString(fieldValue);
+ that.sysConfig["rollPID"].push(ffValues[0]);
+ that.sysConfig["pitchPID"].push(ffValues[1]);
+ that.sysConfig["yawPID"].push(ffValues[2]);
+ break;
+
+ /* End of CSV packed values */
+
+ case "vbatcellvoltage":
+ var vbatcellvoltageParams = parseCommaSeparatedString(fieldValue);
+ that.sysConfig.vbatmincellvoltage = vbatcellvoltageParams[0];
+ that.sysConfig.vbatwarningcellvoltage = vbatcellvoltageParams[1];
+ that.sysConfig.vbatmaxcellvoltage = vbatcellvoltageParams[2];
+ break;
+ case "currentMeter":
+ case "currentSensor":
+ var currentMeterParams = parseCommaSeparatedString(fieldValue);
+ that.sysConfig.currentMeterOffset = currentMeterParams[0];
+ that.sysConfig.currentMeterScale = currentMeterParams[1];
+ break;
+ case "gyro.scale":
+ case "gyro_scale":
+ that.sysConfig.gyroScale = hexToFloat(fieldValue);
+ /* Baseflight uses a gyroScale that'll give radians per microsecond as output, whereas Cleanflight produces degrees
+ * per second and leaves the conversion to radians per us to the IMU. Let's just convert Cleanflight's scale to
+ * match Baseflight so we can use Baseflight's IMU for both: */
+ if (
+ that.sysConfig.firmwareType == FIRMWARE_TYPE_INAV ||
+ that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT ||
+ that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT
+ ) {
+ that.sysConfig.gyroScale =
+ that.sysConfig.gyroScale * (Math.PI / 180.0) * 0.000001;
+ }
+ break;
+ case "Firmware revision":
+ //TODO Unify this somehow...
+
+ // Extract the firmware revision in case of Betaflight/Raceflight/Cleanfligh 2.x/Other
+ var matches = fieldValue.match(/(.*flight).* (\d+)\.(\d+)(\.(\d+))*/i);
+ if (matches != null) {
+ // Detecting Betaflight requires looking at the revision string
+ if (matches[1] === "Betaflight") {
+ that.sysConfig.firmwareType = FIRMWARE_TYPE_BETAFLIGHT;
+ $("html").removeClass("isBaseF");
+ $("html").removeClass("isCF");
+ $("html").addClass("isBF");
+ $("html").removeClass("isINAV");
+ }
+
+ that.sysConfig.firmware = parseFloat(
+ `${matches[2]}.${matches[3]}`
+ ).toFixed(1);
+ that.sysConfig.firmwarePatch =
+ matches[5] != null ? parseInt(matches[5]) : "0";
+ that.sysConfig.firmwareVersion = `${that.sysConfig.firmware}.${that.sysConfig.firmwarePatch}`;
+ } else {
+ /*
+ * Try to detect INAV
+ */
+ var matches = fieldValue.match(/(INAV).* (\d+)\.(\d+).(\d+)*/i);
+ if (matches != null) {
+ that.sysConfig.firmwareType = FIRMWARE_TYPE_INAV;
+ that.sysConfig.firmware = parseFloat(`${matches[2]}.${matches[3]}`);
+ that.sysConfig.firmwarePatch =
+ matches[5] != null ? parseInt(matches[5]) : "";
+ //added class definition as the isBF, isCF etc classes are only used for colors and
+ //a few images in the css.
+ $("html").removeClass("isBaseF");
+ $("html").removeClass("isCF");
+ $("html").removeClass("isBF");
+ $("html").addClass("isINAV");
+ } else {
+ // Cleanflight 1.x and others
+ that.sysConfig.firmwareVersion = "0.0.0";
+ that.sysConfig.firmware = 0.0;
+ that.sysConfig.firmwarePatch = 0;
+ }
+ }
+ that.sysConfig[fieldName] = fieldValue;
+
+ break;
+ case "Product":
+ case "Blackbox version":
+ case "Firmware date":
+ case "Board information":
+ case "Craft name":
+ case "Log start datetime":
+ // These fields are not presently used for anything, ignore them here so we don't warn about unsupported headers
+ // Just Add them anyway
+ that.sysConfig[fieldName] = fieldValue;
+ break;
+ case "DeviceUID":
+ that.sysConfig.deviceUID = fieldValue;
+ break;
+ default:
+ if ((matches = fieldName.match(/^Field (.) (.+)$/))) {
+ let frameName = matches[1],
+ frameInfo = matches[2],
+ frameDef;
+
+ if (!that.frameDefs[frameName]) {
+ that.frameDefs[frameName] = {
+ name: [],
+ nameToIndex: {},
+ count: 0,
+ signed: [],
+ predictor: [],
+ encoding: [],
+ };
+ }
+
+ frameDef = that.frameDefs[frameName];
+
+ switch (frameInfo) {
+ case "predictor":
+ frameDef.predictor = parseCommaSeparatedString(fieldValue);
+ break;
+ case "encoding":
+ frameDef.encoding = parseCommaSeparatedString(fieldValue);
+ break;
+ case "name":
+ frameDef.name = translateLegacyFieldNames(fieldValue.split(","));
+ frameDef.count = frameDef.name.length;
+
+ frameDef.nameToIndex = mapFieldNamesToIndex(frameDef.name);
+
+ /*
+ * We could survive with the `signed` header just being filled with zeros, so if it is absent
+ * then resize it to length.
+ */
+ frameDef.signed.length = frameDef.count;
+ break;
+ case "signed":
+ frameDef.signed = parseCommaSeparatedString(fieldValue);
+ break;
+ default:
+ console.log(`Saw unsupported field header "${fieldName}"`);
+ }
+ } else {
+ console.log(`Ignoring unsupported header ${fieldName} ${fieldValue}`);
+ if (that.sysConfig.unknownHeaders === null) {
+ that.sysConfig.unknownHeaders = new Array();
+ }
+ that.sysConfig.unknownHeaders.push({
+ name: fieldName,
+ value: fieldValue,
+ }); // Save the unknown headers
+ }
+ break;
+ }
+ }
- if (stream.data[stream.pos] != NEWLINE || separatorPos === false)
- return;
-
- lineEnd = stream.pos;
-
- fieldName = asciiArrayToString(stream.data.subarray(lineStart, separatorPos));
- fieldValue = asciiArrayToString(stream.data.subarray(separatorPos + 1, lineEnd));
-
- // Translate the fieldName to the sysConfig parameter name. The fieldName has been changing between versions
- // In this way is easier to maintain the code
- fieldName = translateFieldName(fieldName);
+ function invalidateMainStream() {
+ mainStreamIsValid = false;
- switch (fieldName) {
- case "I interval":
- that.sysConfig.frameIntervalI = parseInt(fieldValue, 10);
- if (that.sysConfig.frameIntervalI < 1)
- that.sysConfig.frameIntervalI = 1;
- break;
- case "P interval":
- matches = fieldValue.match(/(\d+)\/(\d+)/);
-
- if (matches) {
- that.sysConfig.frameIntervalPNum = parseInt(matches[1], 10);
- that.sysConfig.frameIntervalPDenom = parseInt(matches[2], 10);
- } else {
- that.sysConfig.frameIntervalPNum = 1;
- that.sysConfig.frameIntervalPDenom = parseInt(fieldValue, 10);
- }
- break;
- case "P denom":
- case "P ratio":
- // Don't do nothing with this, because is the same than frameIntervalI/frameIntervalPDenom so we don't need it
- break;
- case "Data version":
- dataVersion = parseInt(fieldValue, 10);
- break;
- case "Firmware type":
- switch (fieldValue) {
- case "Cleanflight":
- that.sysConfig.firmwareType = FIRMWARE_TYPE_CLEANFLIGHT;
- $('html').removeClass('isBaseF');
- $('html').addClass('isCF');
- $('html').removeClass('isBF');
- $('html').removeClass('isINAV');
- break;
- default:
- that.sysConfig.firmwareType = FIRMWARE_TYPE_BASEFLIGHT;
- $('html').addClass('isBaseF');
- $('html').removeClass('isCF');
- $('html').removeClass('isBF');
- $('html').removeClass('isINAV');
- }
- break;
+ mainHistory[0] = mainHistoryRing ? mainHistoryRing[0] : null;
+ mainHistory[1] = null;
+ mainHistory[2] = null;
+ }
- // Betaflight Log Header Parameters
- case "minthrottle":
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- that.sysConfig.motorOutput[0] = that.sysConfig[fieldName]; // by default, set the minMotorOutput to match minThrottle
- break;
- case "maxthrottle":
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- that.sysConfig.motorOutput[1] = that.sysConfig[fieldName]; // by default, set the maxMotorOutput to match maxThrottle
- break;
- case "rcRate":
- case "thrMid":
- case "thrExpo":
- case "tpa_rate":
- case "tpa_mode":
- case "tpa_breakpoint":
- case "airmode_activate_throttle":
- case "serialrx_provider":
- case "looptime":
- case "gyro_sync_denom":
- case "pid_process_denom":
- case "pidController":
- case "yaw_p_limit":
- case "dterm_average_count":
- case "rollPitchItermResetRate":
- case "yawItermResetRate":
- case "rollPitchItermIgnoreRate":
- case "yawItermIgnoreRate":
- case "dterm_differentiator":
- case "deltaMethod":
- case "dynamic_dterm_threshold":
- case "dynamic_pterm":
- case "iterm_reset_offset":
- case "deadband":
- case "yaw_deadband":
- case "gyro_lpf":
- case "gyro_hardware_lpf":
- case "gyro_32khz_hardware_lpf":
- case "acc_lpf_hz":
- case "acc_hardware":
- case "baro_hardware":
- case "mag_hardware":
- case "gyro_cal_on_first_arm":
- case "vbat_pid_compensation":
- case "rc_smoothing":
- case "rc_smoothing_type":
- case "rc_smoothing_debug_axis":
- case "rc_smoothing_rx_average":
- case "rc_smoothing_mode": // 4.3 rc smoothing stuff
- case "rc_smoothing_auto_factor_setpoint":
- case "rc_smoothing_auto_factor_throttle":
- case "rc_smoothing_feedforward_hz":
- case "rc_smoothing_setpoint_hz":
- case "rc_smoothing_feedforward_hz":
- case "rc_smoothing_throttle_hz":
- case "superExpoYawMode":
- case "features":
- case "dynamic_pid":
- case "rc_interpolation":
- case "rc_interpolation_channels":
- case "rc_interpolation_interval":
- case "unsynced_fast_pwm":
- case "fast_pwm_protocol":
- case "motor_pwm_rate":
- case "vbatscale":
- case "vbatref":
- case "acc_1G":
- case "dterm_filter_type":
- case "dterm_filter2_type":
- case "pidAtMinThrottle":
- case "pidSumLimit":
- case "pidSumLimitYaw":
- case "anti_gravity_threshold":
- case "itermWindupPointPercent":
- case "ptermSRateWeight":
- case "setpointRelaxRatio":
- case "ff_transition":
- case "ff_averaging":
- case "ff_smooth_factor":
- case "ff_jitter_factor":
- case "ff_boost":
- case "ff_max_rate_limit":
- case "dtermSetpointWeight":
- case "gyro_soft_type":
- case "gyro_soft2_type":
- case "debug_mode":
- case "anti_gravity_mode":
- case "anti_gravity_gain":
- case "anti_gravity_p_gain":
- case "anti_gravity_cutoff_hz":
- case "abs_control_gain":
- case "use_integrated_yaw":
- case "d_min_gain":
- case "d_min_advance":
- case "dshot_bidir":
- case "gyro_rpm_notch_harmonics":
- case "gyro_rpm_notch_q":
- case "gyro_rpm_notch_min":
- case "rpm_filter_fade_range_hz":
- case "rpm_notch_lpf":
- case "dterm_rpm_notch_harmonics":
- case "dterm_rpm_notch_q":
- case "dterm_rpm_notch_min":
- case "iterm_relax":
- case "iterm_relax_type":
- case "iterm_relax_cutoff":
- case "dyn_notch_range":
- case "dyn_notch_width_percent":
- case "dyn_notch_q":
- case "dyn_notch_count":
- case "dyn_notch_min_hz":
- case "dyn_notch_max_hz":
- case "rates_type":
- case "vbat_sag_compensation":
- case "fields_disabled_mask":
- case "motor_pwm_protocol":
- case "gyro_to_use":
- case "dynamic_idle_min_rpm":
- case "dyn_idle_p_gain":
- case "dyn_idle_i_gain":
- case "dyn_idle_d_gain":
- case "dyn_idle_max_increase":
- case "simplified_pids_mode":
- case "simplified_pi_gain":
- case "simplified_i_gain":
- case "simplified_d_gain":
- case "simplified_dmax_gain":
- case "simplified_feedforward_gain":
- case "simplified_pitch_d_gain":
- case "simplified_pitch_pi_gain":
- case "simplified_master_multiplier":
-
- case "simplified_dterm_filter":
- case "simplified_dterm_filter_multiplier":
- case "simplified_gyro_filter":
- case "simplified_gyro_filter_multiplier":
-
- case "motor_output_limit":
- case "throttle_limit_type":
- case "throttle_limit_percent":
- case "throttle_boost":
- case "throttle_boost_cutoff":
-
- case "motor_poles":
-
- case "blackbox_high_resolution":
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- break;
- case "rc_expo":
- case "rc_rates":
- if(stringHasComma(fieldValue)) {
- that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
- } else {
- that.sysConfig[fieldName][0] = parseInt(fieldValue, 10);
- that.sysConfig[fieldName][1] = parseInt(fieldValue, 10);
- }
- break;
- case "rcYawExpo":
- that.sysConfig["rc_expo"][2] = parseInt(fieldValue, 10);
- break;
- case "rcYawRate":
- that.sysConfig["rc_rates"][2] = parseInt(fieldValue, 10);
- break;
+ /**
+ * Use data from the given frame to update field statistics for the given frame type.
+ */
+ function updateFieldStatistics(frameType, frame) {
+ let i, fieldStats;
+ fieldStats = that.stats.frame[frameType].field;
- case "yawRateAccelLimit":
- case "rateAccelLimit":
- if((that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '3.1.0')) ||
- (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '2.0.0'))) {
- that.sysConfig[fieldName] = parseInt(fieldValue, 10)/1000;
- } else {
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- }
- break;
-
- case "yaw_lpf_hz":
- case "gyro_lowpass_hz":
- case "gyro_lowpass2_hz":
- case "dterm_notch_hz":
- case "dterm_notch_cutoff":
- case "dterm_lpf_hz":
- case "dterm_lpf2_hz":
- if((that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '3.0.1')) ||
- (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '2.0.0'))) {
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- } else {
- that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
- }
- break;
+ for (i = 0; i < frame.length; i++) {
+ if (!fieldStats[i]) {
+ fieldStats[i] = {
+ max: frame[i],
+ min: frame[i],
+ };
+ } else {
+ fieldStats[i].max =
+ frame[i] > fieldStats[i].max ? frame[i] : fieldStats[i].max;
+ fieldStats[i].min =
+ frame[i] < fieldStats[i].min ? frame[i] : fieldStats[i].min;
+ }
+ }
+ }
+
+ function completeIntraframe(frameType, frameStart, frameEnd, raw) {
+ let acceptFrame = true;
+
+ // Do we have a previous frame to use as a reference to validate field values against?
+ if (!raw && lastMainFrameIteration != -1) {
+ /*
+ * Check that iteration count and time didn't move backwards, and didn't move forward too much.
+ */
+ acceptFrame =
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ] >= lastMainFrameIteration &&
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ] <
+ lastMainFrameIteration + MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES &&
+ mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] >=
+ lastMainFrameTime &&
+ mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] <
+ lastMainFrameTime + MAXIMUM_TIME_JUMP_BETWEEN_FRAMES;
+ }
- case "gyro_notch_hz":
- case "gyro_notch_cutoff":
- if((that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '3.0.1')) ||
- (that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(that.sysConfig.firmwareVersion, '2.0.0'))) {
- that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
- } else {
- that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
- }
- break;
+ if (acceptFrame) {
+ that.stats.intentionallyAbsentIterations +=
+ countIntentionallySkippedFramesTo(
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ]
+ );
+
+ lastMainFrameIteration =
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ];
+ lastMainFrameTime =
+ mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- case "digitalIdleOffset":
- that.sysConfig[fieldName] = parseInt(fieldValue, 10) / 100.0;
+ mainStreamIsValid = true;
- /** Cleanflight Only log headers **/
- case "dterm_cut_hz":
- case "acc_cut_hz":
- that.sysConfig[fieldName] = parseInt(fieldValue, 10);
- break;
- /** End of cleanflight only log headers **/
-
- case "superExpoFactor":
- if(stringHasComma(fieldValue)) {
- var expoParams = parseCommaSeparatedString(fieldValue);
- that.sysConfig.superExpoFactor = expoParams[0];
- that.sysConfig.superExpoFactorYaw = expoParams[1];
- } else {
- that.sysConfig.superExpoFactor = parseInt(fieldValue, 10);
- }
- break;
+ updateFieldStatistics(frameType, mainHistory[0]);
+ } else {
+ invalidateMainStream();
+ }
- /* CSV packed values */
-
- case "rates":
- case "rate_limits":
- case "rollPID":
- case "pitchPID":
- case "yawPID":
- case "altPID":
- case "posPID":
- case "posrPID":
- case "navrPID":
- case "levelPID":
- case "velPID":
- case "motorOutput":
- case "rate_limits":
- case "rc_smoothing_cutoffs":
- case "rc_smoothing_active_cutoffs":
- case "rc_smoothing_active_cutoffs_ff_sp_thr":
- case "gyro_lowpass_dyn_hz":
- case "gyro_lowpass_dyn_expo":
- case "dterm_lpf_dyn_expo":
- case "thrust_linear":
- case "tpa_low_rate":
- case "tpa_low_breakpoint":
- case "tpa_low_always":
- case "mixer_type":
- case "dterm_lpf_dyn_hz":
- that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue);
- break;
- case "magPID":
- that.sysConfig.magPID = parseCommaSeparatedString(fieldValue,3); //[parseInt(fieldValue, 10), null, null];
+ if (that.onFrameReady)
+ that.onFrameReady(
+ mainStreamIsValid,
+ mainHistory[0],
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
+
+ // Rotate history buffers
+
+ // Both the previous and previous-previous states become the I-frame, because we can't look further into the past than the I-frame
+ mainHistory[1] = mainHistory[0];
+ mainHistory[2] = mainHistory[0];
+
+ // And advance the current frame into an empty space ready to be filled
+ if (mainHistory[0] == mainHistoryRing[0])
+ mainHistory[0] = mainHistoryRing[1];
+ else if (mainHistory[0] == mainHistoryRing[1])
+ mainHistory[0] = mainHistoryRing[2];
+ else mainHistory[0] = mainHistoryRing[0];
+ return mainStreamIsValid;
+ }
+
+ /**
+ * Should a frame with the given index exist in this log (based on the user's selection of sampling rates)?
+ */
+ function shouldHaveFrame(frameIndex) {
+ return (
+ ((frameIndex % that.sysConfig.frameIntervalI) +
+ that.sysConfig.frameIntervalPNum -
+ 1) %
+ that.sysConfig.frameIntervalPDenom <
+ that.sysConfig.frameIntervalPNum
+ );
+ }
+
+ /**
+ * Attempt to parse the frame of into the supplied `current` buffer using the encoding/predictor
+ * definitions from `frameDefs`. The previous frame values are used for predictions.
+ *
+ * frameDef - The definition for the frame type being parsed (from this.frameDefs)
+ * raw - Set to true to disable predictions (and so store raw values)
+ * skippedFrames - Set to the number of field iterations that were skipped over by rate settings since the last frame.
+ */
+ function parseFrame(
+ frameDef,
+ current,
+ previous,
+ previous2,
+ skippedFrames,
+ raw
+ ) {
+ let predictor = frameDef.predictor,
+ encoding = frameDef.encoding,
+ values = new Array(8),
+ i,
+ j,
+ groupCount;
+
+ i = 0;
+ while (i < frameDef.count) {
+ var value;
+
+ if (predictor[i] == FLIGHT_LOG_FIELD_PREDICTOR_INC) {
+ current[i] = skippedFrames + 1;
+
+ if (previous) current[i] += previous[i];
+
+ i++;
+ } else {
+ switch (encoding[i]) {
+ case FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB:
+ value = stream.readSignedVB();
break;
- case "d_min":
- // Add Dmin values as Derivative numbers to PID array
- var dMinValues = parseCommaSeparatedString(fieldValue);
- that.sysConfig["rollPID"].push(dMinValues[0]);
- that.sysConfig["pitchPID"].push(dMinValues[1]);
- that.sysConfig["yawPID"].push(dMinValues[2]);
- break;
- case "ff_weight":
- // Add feedforward values to the PID array
- var ffValues = parseCommaSeparatedString(fieldValue);
- that.sysConfig["rollPID"].push(ffValues[0]);
- that.sysConfig["pitchPID"].push(ffValues[1]);
- that.sysConfig["yawPID"].push(ffValues[2]);
- break;
-
- /* End of CSV packed values */
-
- case "vbatcellvoltage":
- var vbatcellvoltageParams = parseCommaSeparatedString(fieldValue);
- that.sysConfig.vbatmincellvoltage = vbatcellvoltageParams[0];
- that.sysConfig.vbatwarningcellvoltage = vbatcellvoltageParams[1];
- that.sysConfig.vbatmaxcellvoltage = vbatcellvoltageParams[2];
+ case FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB:
+ value = stream.readUnsignedVB();
break;
- case "currentMeter":
- case "currentSensor":
- var currentMeterParams = parseCommaSeparatedString(fieldValue);
- that.sysConfig.currentMeterOffset = currentMeterParams[0];
- that.sysConfig.currentMeterScale = currentMeterParams[1];
+ case FLIGHT_LOG_FIELD_ENCODING_NEG_14BIT:
+ value = -signExtend14Bit(stream.readUnsignedVB());
break;
- case "gyro.scale":
- case "gyro_scale":
- that.sysConfig.gyroScale = hexToFloat(fieldValue);
- /* Baseflight uses a gyroScale that'll give radians per microsecond as output, whereas Cleanflight produces degrees
- * per second and leaves the conversion to radians per us to the IMU. Let's just convert Cleanflight's scale to
- * match Baseflight so we can use Baseflight's IMU for both: */
- if (that.sysConfig.firmwareType == FIRMWARE_TYPE_INAV ||
- that.sysConfig.firmwareType == FIRMWARE_TYPE_CLEANFLIGHT ||
- that.sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT) {
- that.sysConfig.gyroScale = that.sysConfig.gyroScale * (Math.PI / 180.0) * 0.000001;
- }
+ case FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16:
+ if (dataVersion < 2) stream.readTag8_4S16_v1(values);
+ else stream.readTag8_4S16_v2(values);
+
+ //Apply the predictors for the fields:
+ for (j = 0; j < 4; j++, i++)
+ current[i] = applyPrediction(
+ i,
+ raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i],
+ values[j],
+ current,
+ previous,
+ previous2
+ );
+
+ continue;
break;
- case "Firmware revision":
-
- //TODO Unify this somehow...
-
- // Extract the firmware revision in case of Betaflight/Raceflight/Cleanfligh 2.x/Other
- var matches = fieldValue.match(/(.*flight).* (\d+)\.(\d+)(\.(\d+))*/i);
- if(matches!=null) {
-
- // Detecting Betaflight requires looking at the revision string
- if (matches[1] === "Betaflight") {
- that.sysConfig.firmwareType = FIRMWARE_TYPE_BETAFLIGHT;
- $('html').removeClass('isBaseF');
- $('html').removeClass('isCF');
- $('html').addClass('isBF');
- $('html').removeClass('isINAV');
- }
-
- that.sysConfig.firmware = parseFloat(matches[2] + '.' + matches[3]).toFixed(1);
- that.sysConfig.firmwarePatch = (matches[5] != null)?parseInt(matches[5]):'0';
- that.sysConfig.firmwareVersion = that.sysConfig.firmware + '.' + that.sysConfig.firmwarePatch;
-
- } else {
-
- /*
- * Try to detect INAV
- */
- var matches = fieldValue.match(/(INAV).* (\d+)\.(\d+).(\d+)*/i);
- if(matches!=null) {
- that.sysConfig.firmwareType = FIRMWARE_TYPE_INAV;
- that.sysConfig.firmware = parseFloat(matches[2] + '.' + matches[3]);
- that.sysConfig.firmwarePatch = (matches[5] != null)?parseInt(matches[5]):'';
- //added class definition as the isBF, isCF etc classes are only used for colors and
- //a few images in the css.
- $('html').removeClass('isBaseF');
- $('html').removeClass('isCF');
- $('html').removeClass('isBF');
- $('html').addClass('isINAV');
- } else {
-
- // Cleanflight 1.x and others
- that.sysConfig.firmwareVersion = '0.0.0';
- that.sysConfig.firmware = 0.0;
- that.sysConfig.firmwarePatch = 0;
- }
- }
- that.sysConfig[fieldName] = fieldValue;
-
+ case FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32:
+ stream.readTag2_3S32(values);
+
+ //Apply the predictors for the fields:
+ for (j = 0; j < 3; j++, i++)
+ current[i] = applyPrediction(
+ i,
+ raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i],
+ values[j],
+ current,
+ previous,
+ previous2
+ );
+
+ continue;
break;
- case "Product":
- case "Blackbox version":
- case "Firmware date":
- case "Board information":
- case "Craft name":
- case "Log start datetime":
- // These fields are not presently used for anything, ignore them here so we don't warn about unsupported headers
- // Just Add them anyway
- that.sysConfig[fieldName] = fieldValue;
+ case FLIGHT_LOG_FIELD_ENCODING_TAG2_3SVARIABLE:
+ stream.readTag2_3SVariable(values);
+
+ //Apply the predictors for the fields:
+ for (j = 0; j < 3; j++, i++)
+ current[i] = applyPrediction(
+ i,
+ raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i],
+ values[j],
+ current,
+ previous,
+ previous2
+ );
+
+ continue;
break;
- case "Device UID":
- that.sysConfig.deviceUID = fieldValue;
+ case FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB:
+ //How many fields are in this encoded group? Check the subsequent field encodings:
+ for (j = i + 1; j < i + 8 && j < frameDef.count; j++)
+ if (encoding[j] != FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB) break;
+
+ groupCount = j - i;
+
+ stream.readTag8_8SVB(values, groupCount);
+
+ for (j = 0; j < groupCount; j++, i++)
+ current[i] = applyPrediction(
+ i,
+ raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i],
+ values[j],
+ current,
+ previous,
+ previous2
+ );
+
+ continue;
break;
- default:
- if ((matches = fieldName.match(/^Field (.) (.+)$/))) {
- var
- frameName = matches[1],
- frameInfo = matches[2],
- frameDef;
-
- if (!that.frameDefs[frameName]) {
- that.frameDefs[frameName] = {
- name: [],
- nameToIndex: {},
- count: 0,
- signed: [],
- predictor: [],
- encoding: [],
- };
- }
-
- frameDef = that.frameDefs[frameName];
-
- switch (frameInfo) {
- case "predictor":
- frameDef.predictor = parseCommaSeparatedString(fieldValue);
- break;
- case "encoding":
- frameDef.encoding = parseCommaSeparatedString(fieldValue);
- break;
- case "name":
- frameDef.name = translateLegacyFieldNames(fieldValue.split(","));
- frameDef.count = frameDef.name.length;
-
- frameDef.nameToIndex = mapFieldNamesToIndex(frameDef.name);
-
- /*
- * We could survive with the `signed` header just being filled with zeros, so if it is absent
- * then resize it to length.
- */
- frameDef.signed.length = frameDef.count;
- break;
- case "signed":
- frameDef.signed = parseCommaSeparatedString(fieldValue);
- break;
- default:
- console.log("Saw unsupported field header \"" + fieldName + "\"");
- }
- } else {
- console.log(`Ignoring unsupported header ${fieldName} ${fieldValue}`);
- if (that.sysConfig.unknownHeaders === null) {
- that.sysConfig.unknownHeaders = new Array();
- }
- that.sysConfig.unknownHeaders.push({ name: fieldName, value: fieldValue }); // Save the unknown headers
- }
+ case FLIGHT_LOG_FIELD_ENCODING_NULL:
+ //Nothing to read
+ value = 0;
break;
+ default:
+ if (encoding[i] === undefined)
+ throw `Missing field encoding header for field #${i} '${frameDef.name[i]}'`;
+ else throw `Unsupported field encoding ${encoding[i]}`;
}
- }
- function invalidateMainStream() {
- mainStreamIsValid = false;
-
- mainHistory[0] = mainHistoryRing ? mainHistoryRing[0]: null;
- mainHistory[1] = null;
- mainHistory[2] = null;
+ current[i] = applyPrediction(
+ i,
+ raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i],
+ value,
+ current,
+ previous,
+ previous2
+ );
+ i++;
+ }
}
+ }
- /**
- * Use data from the given frame to update field statistics for the given frame type.
- */
- function updateFieldStatistics(frameType, frame) {
- var
- i, fieldStats;
-
- fieldStats = that.stats.frame[frameType].field;
-
- for (i = 0; i < frame.length; i++) {
- if (!fieldStats[i]) {
- fieldStats[i] = {
- max: frame[i],
- min: frame[i]
- };
- } else {
- fieldStats[i].max = frame[i] > fieldStats[i].max ? frame[i] : fieldStats[i].max;
- fieldStats[i].min = frame[i] < fieldStats[i].min ? frame[i] : fieldStats[i].min;
- }
- }
- }
-
- function completeIntraframe(frameType, frameStart, frameEnd, raw) {
- var acceptFrame = true;
-
- // Do we have a previous frame to use as a reference to validate field values against?
- if (!raw && lastMainFrameIteration != -1) {
- /*
- * Check that iteration count and time didn't move backwards, and didn't move forward too much.
- */
- acceptFrame =
- mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION] >= lastMainFrameIteration
- && mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION] < lastMainFrameIteration + MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES
- && mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] >= lastMainFrameTime
- && mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] < lastMainFrameTime + MAXIMUM_TIME_JUMP_BETWEEN_FRAMES;
- }
-
- if (acceptFrame) {
- that.stats.intentionallyAbsentIterations += countIntentionallySkippedFramesTo(mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION]);
-
- lastMainFrameIteration = mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION];
- lastMainFrameTime = mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
+ function parseIntraframe(raw) {
+ let current = mainHistory[0],
+ previous = mainHistory[1];
- mainStreamIsValid = true;
+ parseFrame(that.frameDefs.I, current, previous, null, 0, raw);
+ }
- updateFieldStatistics(frameType, mainHistory[0]);
- } else {
- invalidateMainStream();
- }
-
- if (that.onFrameReady)
- that.onFrameReady(mainStreamIsValid, mainHistory[0], frameType, frameStart, frameEnd - frameStart);
-
- // Rotate history buffers
+ function completeGPSHomeFrame(frameType, frameStart, frameEnd, raw) {
+ updateFieldStatistics(frameType, gpsHomeHistory[0]);
- // Both the previous and previous-previous states become the I-frame, because we can't look further into the past than the I-frame
- mainHistory[1] = mainHistory[0];
- mainHistory[2] = mainHistory[0];
+ that.setGPSHomeHistory(gpsHomeHistory[0]);
- // And advance the current frame into an empty space ready to be filled
- if (mainHistory[0] == mainHistoryRing[0])
- mainHistory[0] = mainHistoryRing[1];
- else if (mainHistory[0] == mainHistoryRing[1])
- mainHistory[0] = mainHistoryRing[2];
- else
- mainHistory[0] = mainHistoryRing[0];
+ if (that.onFrameReady) {
+ that.onFrameReady(
+ true,
+ gpsHomeHistory[0],
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
}
- /**
- * Should a frame with the given index exist in this log (based on the user's selection of sampling rates)?
- */
- function shouldHaveFrame(frameIndex)
- {
- return (frameIndex % that.sysConfig.frameIntervalI + that.sysConfig.frameIntervalPNum - 1)
- % that.sysConfig.frameIntervalPDenom < that.sysConfig.frameIntervalPNum;
- }
+ return true;
+ }
- /**
- * Attempt to parse the frame of into the supplied `current` buffer using the encoding/predictor
- * definitions from `frameDefs`. The previous frame values are used for predictions.
- *
- * frameDef - The definition for the frame type being parsed (from this.frameDefs)
- * raw - Set to true to disable predictions (and so store raw values)
- * skippedFrames - Set to the number of field iterations that were skipped over by rate settings since the last frame.
- */
- function parseFrame(frameDef, current, previous, previous2, skippedFrames, raw)
- {
- var
- predictor = frameDef.predictor,
- encoding = frameDef.encoding,
- values = new Array(8),
- i, j, groupCount;
-
- i = 0;
- while (i < frameDef.count) {
- var
- value;
-
- if (predictor[i] == FLIGHT_LOG_FIELD_PREDICTOR_INC) {
- current[i] = skippedFrames + 1;
-
- if (previous)
- current[i] += previous[i];
-
- i++;
- } else {
- switch (encoding[i]) {
- case FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB:
- value = stream.readSignedVB();
- break;
- case FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB:
- value = stream.readUnsignedVB();
- break;
- case FLIGHT_LOG_FIELD_ENCODING_NEG_14BIT:
- value = -signExtend14Bit(stream.readUnsignedVB());
- break;
- case FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16:
- if (dataVersion < 2)
- stream.readTag8_4S16_v1(values);
- else
- stream.readTag8_4S16_v2(values);
-
- //Apply the predictors for the fields:
- for (j = 0; j < 4; j++, i++)
- current[i] = applyPrediction(i, raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i], values[j], current, previous, previous2);
-
- continue;
- break;
- case FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32:
- stream.readTag2_3S32(values);
-
- //Apply the predictors for the fields:
- for (j = 0; j < 3; j++, i++)
- current[i] = applyPrediction(i, raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i], values[j], current, previous, previous2);
-
- continue;
- break;
- case FLIGHT_LOG_FIELD_ENCODING_TAG2_3SVARIABLE:
- stream.readTag2_3SVariable(values);
-
- //Apply the predictors for the fields:
- for (j = 0; j < 3; j++, i++)
- current[i] = applyPrediction(i, raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i], values[j], current, previous, previous2);
-
- continue;
- break;
- case FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB:
- //How many fields are in this encoded group? Check the subsequent field encodings:
- for (j = i + 1; j < i + 8 && j < frameDef.count; j++)
- if (encoding[j] != FLIGHT_LOG_FIELD_ENCODING_TAG8_8SVB)
- break;
-
- groupCount = j - i;
-
- stream.readTag8_8SVB(values, groupCount);
-
- for (j = 0; j < groupCount; j++, i++)
- current[i] = applyPrediction(i, raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i], values[j], current, previous, previous2);
-
- continue;
- break;
- case FLIGHT_LOG_FIELD_ENCODING_NULL:
- //Nothing to read
- value = 0;
- break;
- default:
- if (encoding[i] === undefined)
- throw "Missing field encoding header for field #" + i + " '" + frameDef.name[i] + "'";
- else
- throw "Unsupported field encoding " + encoding[i];
- }
-
- current[i] = applyPrediction(i, raw ? FLIGHT_LOG_FIELD_PREDICTOR_0 : predictor[i], value, current, previous, previous2);
- i++;
- }
- }
+ function completeGPSFrame(frameType, frameStart, frameEnd, raw) {
+ if (gpsHomeIsValid) {
+ updateFieldStatistics(frameType, lastGPS);
}
- function parseIntraframe(raw) {
- var
- current = mainHistory[0],
- previous = mainHistory[1];
-
- parseFrame(that.frameDefs.I, current, previous, null, 0, raw);
+ if (that.onFrameReady) {
+ that.onFrameReady(
+ gpsHomeIsValid,
+ lastGPS,
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
}
- function completeGPSHomeFrame(frameType, frameStart, frameEnd, raw) {
- updateFieldStatistics(frameType, gpsHomeHistory[0]);
-
- that.setGPSHomeHistory(gpsHomeHistory[0]);
+ return gpsHomeIsValid;
+ }
- if (that.onFrameReady) {
- that.onFrameReady(true, gpsHomeHistory[0], frameType, frameStart, frameEnd - frameStart);
- }
+ function completeSlowFrame(frameType, frameStart, frameEnd, raw) {
+ updateFieldStatistics(frameType, lastSlow);
- return true;
+ if (that.onFrameReady) {
+ that.onFrameReady(
+ true,
+ lastSlow,
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
+ }
+ return true;
+ }
+
+ function completeInterframe(frameType, frameStart, frameEnd, raw) {
+ // Reject this frame if the time or iteration count jumped too far
+ if (
+ mainStreamIsValid &&
+ !raw &&
+ (mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] >
+ lastMainFrameTime + MAXIMUM_TIME_JUMP_BETWEEN_FRAMES ||
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ] >
+ lastMainFrameIteration + MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES)
+ ) {
+ mainStreamIsValid = false;
}
- function completeGPSFrame(frameType, frameStart, frameEnd, raw) {
- if (gpsHomeIsValid) {
- updateFieldStatistics(frameType, lastGPS);
- }
+ if (mainStreamIsValid) {
+ lastMainFrameIteration =
+ mainHistory[0][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION
+ ];
+ lastMainFrameTime =
+ mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- if (that.onFrameReady) {
- that.onFrameReady(gpsHomeIsValid, lastGPS, frameType, frameStart, frameEnd - frameStart);
- }
+ that.stats.intentionallyAbsentIterations += lastSkippedFrames;
- return true;
+ updateFieldStatistics(frameType, mainHistory[0]);
}
- function completeSlowFrame(frameType, frameStart, frameEnd, raw) {
- updateFieldStatistics(frameType, lastSlow);
-
- if (that.onFrameReady) {
- that.onFrameReady(true, lastSlow, frameType, frameStart, frameEnd - frameStart);
- }
+ //Receiving a P frame can't resynchronise the stream so it doesn't set mainStreamIsValid to true
+
+ if (that.onFrameReady)
+ that.onFrameReady(
+ mainStreamIsValid,
+ mainHistory[0],
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
+
+ if (mainStreamIsValid) {
+ // Rotate history buffers
+
+ mainHistory[2] = mainHistory[1];
+ mainHistory[1] = mainHistory[0];
+
+ // And advance the current frame into an empty space ready to be filled
+ if (mainHistory[0] == mainHistoryRing[0])
+ mainHistory[0] = mainHistoryRing[1];
+ else if (mainHistory[0] == mainHistoryRing[1])
+ mainHistory[0] = mainHistoryRing[2];
+ else mainHistory[0] = mainHistoryRing[0];
}
-
- function completeInterframe(frameType, frameStart, frameEnd, raw) {
- // Reject this frame if the time or iteration count jumped too far
- if (mainStreamIsValid && !raw
- && (
- mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME] > lastMainFrameTime + MAXIMUM_TIME_JUMP_BETWEEN_FRAMES
- || mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION] > lastMainFrameIteration + MAXIMUM_ITERATION_JUMP_BETWEEN_FRAMES
- )) {
- mainStreamIsValid = false;
- }
-
- if (mainStreamIsValid) {
- lastMainFrameIteration = mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION];
- lastMainFrameTime = mainHistory[0][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
-
- that.stats.intentionallyAbsentIterations += lastSkippedFrames;
-
- updateFieldStatistics(frameType, mainHistory[0]);
+ return mainStreamIsValid;
+ }
+
+ /**
+ * Take the raw value for a a field, apply the prediction that is configured for it, and return it.
+ */
+ function applyPrediction(
+ fieldIndex,
+ predictor,
+ value,
+ current,
+ previous,
+ previous2
+ ) {
+ switch (predictor) {
+ case FLIGHT_LOG_FIELD_PREDICTOR_0:
+ // No correction to apply
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE:
+ /*
+ * Force the value to be a *signed* 32-bit integer. Encoded motor values can be negative when motors are
+ * below minthrottle, but despite this motor[0] is encoded in I-frames using *unsigned* encoding (to
+ * save space for positive values). So we need to convert those very large unsigned values into their
+ * corresponding 32-bit signed values.
+ */
+ value = (value | 0) + that.sysConfig.minthrottle;
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_MINMOTOR:
+ /*
+ * Force the value to be a *signed* 32-bit integer. Encoded motor values can be negative when motors are
+ * below minthrottle, but despite this motor[0] is encoded in I-frames using *unsigned* encoding (to
+ * save space for positive values). So we need to convert those very large unsigned values into their
+ * corresponding 32-bit signed values.
+ */
+ value = (value | 0) + (that.sysConfig.motorOutput[0] | 0); // motorOutput[0] is the min motor output
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_1500:
+ value += 1500;
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0:
+ if (that.frameDefs.I.nameToIndex["motor[0]"] < 0) {
+ throw "Attempted to base I-field prediction on motor0 before it was read";
}
-
- //Receiving a P frame can't resynchronise the stream so it doesn't set mainStreamIsValid to true
-
- if (that.onFrameReady)
- that.onFrameReady(mainStreamIsValid, mainHistory[0], frameType, frameStart, frameEnd - frameStart);
-
- if (mainStreamIsValid) {
- // Rotate history buffers
-
- mainHistory[2] = mainHistory[1];
- mainHistory[1] = mainHistory[0];
-
- // And advance the current frame into an empty space ready to be filled
- if (mainHistory[0] == mainHistoryRing[0])
- mainHistory[0] = mainHistoryRing[1];
- else if (mainHistory[0] == mainHistoryRing[1])
- mainHistory[0] = mainHistoryRing[2];
- else
- mainHistory[0] = mainHistoryRing[0];
+ value += current[that.frameDefs.I.nameToIndex["motor[0]"]];
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_VBATREF:
+ value += that.sysConfig.vbatref;
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS:
+ if (!previous) break;
+
+ value += previous[fieldIndex];
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE:
+ if (!previous) break;
+
+ value += 2 * previous[fieldIndex] - previous2[fieldIndex];
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2:
+ if (!previous) break;
+
+ //Round toward zero like C would do for integer division:
+ value += ~~((previous[fieldIndex] + previous2[fieldIndex]) / 2);
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD:
+ if (
+ !that.frameDefs.H ||
+ that.frameDefs.H.nameToIndex["GPS_home[0]"] === undefined
+ ) {
+ throw "Attempted to base prediction on GPS home position without GPS home frame definition";
}
- }
-
- /**
- * Take the raw value for a a field, apply the prediction that is configured for it, and return it.
- */
- function applyPrediction(fieldIndex, predictor, value, current, previous, previous2)
- {
- switch (predictor) {
- case FLIGHT_LOG_FIELD_PREDICTOR_0:
- // No correction to apply
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE:
- /*
- * Force the value to be a *signed* 32-bit integer. Encoded motor values can be negative when motors are
- * below minthrottle, but despite this motor[0] is encoded in I-frames using *unsigned* encoding (to
- * save space for positive values). So we need to convert those very large unsigned values into their
- * corresponding 32-bit signed values.
- */
- value = (value | 0) + that.sysConfig.minthrottle;
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_MINMOTOR:
- /*
- * Force the value to be a *signed* 32-bit integer. Encoded motor values can be negative when motors are
- * below minthrottle, but despite this motor[0] is encoded in I-frames using *unsigned* encoding (to
- * save space for positive values). So we need to convert those very large unsigned values into their
- * corresponding 32-bit signed values.
- */
- value = (value | 0) + (that.sysConfig.motorOutput[0] | 0); // motorOutput[0] is the min motor output
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_1500:
- value += 1500;
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0:
- if (that.frameDefs.I.nameToIndex["motor[0]"] < 0) {
- throw "Attempted to base I-field prediction on motor0 before it was read";
- }
- value += current[that.frameDefs.I.nameToIndex["motor[0]"]];
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_VBATREF:
- value += that.sysConfig.vbatref;
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS:
- if (!previous)
- break;
- value += previous[fieldIndex];
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE:
- if (!previous)
- break;
-
- value += 2 * previous[fieldIndex] - previous2[fieldIndex];
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2:
- if (!previous)
- break;
-
- //Round toward zero like C would do for integer division:
- value += ~~((previous[fieldIndex] + previous2[fieldIndex]) / 2);
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD:
- if (!that.frameDefs.H || that.frameDefs.H.nameToIndex["GPS_home[0]"] === undefined) {
- throw "Attempted to base prediction on GPS home position without GPS home frame definition";
- }
-
- value += gpsHomeHistory[1][that.frameDefs.H.nameToIndex["GPS_home[0]"]];
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1:
- if (!that.frameDefs.H || that.frameDefs.H.nameToIndex["GPS_home[1]"] === undefined) {
- throw "Attempted to base prediction on GPS home position without GPS home frame definition";
- }
-
- value += gpsHomeHistory[1][that.frameDefs.H.nameToIndex["GPS_home[1]"]];
- break;
- case FLIGHT_LOG_FIELD_PREDICTOR_LAST_MAIN_FRAME_TIME:
- if (mainHistory[1])
- value += mainHistory[1][FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME];
- break;
- default:
- throw "Unsupported field predictor " + predictor;
+ value += gpsHomeHistory[1][that.frameDefs.H.nameToIndex["GPS_home[0]"]];
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1:
+ if (
+ !that.frameDefs.H ||
+ that.frameDefs.H.nameToIndex["GPS_home[1]"] === undefined
+ ) {
+ throw "Attempted to base prediction on GPS home position without GPS home frame definition";
}
- return value;
+ value += gpsHomeHistory[1][that.frameDefs.H.nameToIndex["GPS_home[1]"]];
+ break;
+ case FLIGHT_LOG_FIELD_PREDICTOR_LAST_MAIN_FRAME_TIME:
+ if (mainHistory[1])
+ value +=
+ mainHistory[1][
+ FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME
+ ];
+ break;
+ default:
+ throw `Unsupported field predictor ${predictor}`;
}
- /*
- * Based on the log sampling rate, work out how many frames would have been skipped after the last frame that was
- * parsed until we get to the next logged iteration.
- */
- function countIntentionallySkippedFrames()
- {
- var
- count = 0, frameIndex;
-
- if (lastMainFrameIteration == -1) {
- // Haven't parsed a frame yet so there's no frames to skip
- return 0;
- } else {
- for (frameIndex = lastMainFrameIteration + 1; !shouldHaveFrame(frameIndex); frameIndex++) {
- count++;
- }
- }
-
- return count;
+ return value;
+ }
+
+ /*
+ * Based on the log sampling rate, work out how many frames would have been skipped after the last frame that was
+ * parsed until we get to the next logged iteration.
+ */
+ function countIntentionallySkippedFrames() {
+ let count = 0,
+ frameIndex;
+
+ if (lastMainFrameIteration == -1) {
+ // Haven't parsed a frame yet so there's no frames to skip
+ return 0;
+ } else {
+ for (
+ frameIndex = lastMainFrameIteration + 1;
+ !shouldHaveFrame(frameIndex);
+ frameIndex++
+ ) {
+ count++;
+ }
}
- /*
- * Based on the log sampling rate, work out how many frames would have been skipped after the last frame that was
- * parsed until we get to the iteration with the given index.
- */
- function countIntentionallySkippedFramesTo(targetIteration)
- {
- var
- count = 0, frameIndex;
-
- if (lastMainFrameIteration == -1) {
- // Haven't parsed a frame yet so there's no frames to skip
- return 0;
- } else {
- for (frameIndex = lastMainFrameIteration + 1; frameIndex < targetIteration; frameIndex++) {
- if (!shouldHaveFrame(frameIndex)) {
- count++;
- }
- }
+ return count;
+ }
+
+ /*
+ * Based on the log sampling rate, work out how many frames would have been skipped after the last frame that was
+ * parsed until we get to the iteration with the given index.
+ */
+ function countIntentionallySkippedFramesTo(targetIteration) {
+ let count = 0,
+ frameIndex;
+
+ if (lastMainFrameIteration == -1) {
+ // Haven't parsed a frame yet so there's no frames to skip
+ return 0;
+ } else {
+ for (
+ frameIndex = lastMainFrameIteration + 1;
+ frameIndex < targetIteration;
+ frameIndex++
+ ) {
+ if (!shouldHaveFrame(frameIndex)) {
+ count++;
}
-
- return count;
+ }
}
- function parseInterframe(raw) {
- var
- current = mainHistory[0],
- previous = mainHistory[1],
- previous2 = mainHistory[2];
-
- lastSkippedFrames = countIntentionallySkippedFrames();
-
- parseFrame(that.frameDefs.P, current, previous, previous2, lastSkippedFrames, raw);
+ return count;
+ }
+
+ function parseInterframe(raw) {
+ let current = mainHistory[0],
+ previous = mainHistory[1],
+ previous2 = mainHistory[2];
+
+ lastSkippedFrames = countIntentionallySkippedFrames();
+
+ parseFrame(
+ that.frameDefs.P,
+ current,
+ previous,
+ previous2,
+ lastSkippedFrames,
+ raw
+ );
+ }
+
+ function parseGPSFrame(raw) {
+ // Only parse a GPS frame if we have GPS header definitions
+ if (that.frameDefs.G) {
+ parseFrame(that.frameDefs.G, lastGPS, null, null, 0, raw);
}
+ }
- function parseGPSFrame(raw) {
- // Only parse a GPS frame if we have GPS header definitions
- if (that.frameDefs.G) {
- parseFrame(that.frameDefs.G, lastGPS, null, null, 0, raw);
- }
- }
-
- function parseGPSHomeFrame(raw) {
- if (that.frameDefs.H) {
- parseFrame(that.frameDefs.H, gpsHomeHistory[0], null, null, 0, raw);
- }
+ function parseGPSHomeFrame(raw) {
+ if (that.frameDefs.H) {
+ parseFrame(that.frameDefs.H, gpsHomeHistory[0], null, null, 0, raw);
}
+ }
- function parseSlowFrame(raw) {
- if (that.frameDefs.S) {
- parseFrame(that.frameDefs.S, lastSlow, null, null, 0, raw);
- }
+ function parseSlowFrame(raw) {
+ if (that.frameDefs.S) {
+ parseFrame(that.frameDefs.S, lastSlow, null, null, 0, raw);
}
-
- function completeEventFrame(frameType, frameStart, frameEnd, raw) {
- if (lastEvent) {
- switch (lastEvent.event) {
- case FlightLogEvent.LOGGING_RESUME:
- /*
- * Bring the "last time" and "last iteration" up to the new resume time so we accept the sudden jump into
- * the future.
- */
- lastMainFrameIteration = lastEvent.data.logIteration;
- lastMainFrameTime = lastEvent.data.currentTime;
- break;
- }
-
- if (that.onFrameReady) {
- that.onFrameReady(true, lastEvent, frameType, frameStart, frameEnd - frameStart);
- }
-
- return true;
- }
-
- return false;
+ }
+
+ function completeEventFrame(frameType, frameStart, frameEnd, raw) {
+ if (lastEvent) {
+ switch (lastEvent.event) {
+ case FlightLogEvent.LOGGING_RESUME:
+ /*
+ * Bring the "last time" and "last iteration" up to the new resume time so we accept the sudden jump into
+ * the future.
+ */
+ lastMainFrameIteration = lastEvent.data.logIteration;
+ lastMainFrameTime = lastEvent.data.currentTime;
+ break;
+ }
+
+ if (that.onFrameReady) {
+ that.onFrameReady(
+ true,
+ lastEvent,
+ frameType,
+ frameStart,
+ frameEnd - frameStart
+ );
+ }
+
+ return true;
}
- function parseEventFrame(raw) {
- var
- END_OF_LOG_MESSAGE = "End of log\0",
+ return false;
+ }
- eventType = stream.readByte();
-
- lastEvent = {
- event: eventType,
- data: {}
- };
-
- switch (eventType) {
- case FlightLogEvent.SYNC_BEEP:
- lastEvent.data.time = stream.readUnsignedVB();
- lastEvent.time = lastEvent.data.time;
- break;
- case FlightLogEvent.FLIGHT_MODE: // get the flag status change
- lastEvent.data.newFlags = stream.readUnsignedVB();
- lastEvent.data.lastFlags = stream.readUnsignedVB();
- break;
- case FlightLogEvent.DISARM:
- lastEvent.data.reason = stream.readUnsignedVB();
- break;
- case FlightLogEvent.AUTOTUNE_CYCLE_START:
- lastEvent.data.phase = stream.readByte();
-
- var cycleAndRising = stream.readByte();
-
- lastEvent.data.cycle = cycleAndRising & 0x7F;
- lastEvent.data.rising = (cycleAndRising >> 7) & 0x01;
-
- lastEvent.data.p = stream.readByte();
- lastEvent.data.i = stream.readByte();
- lastEvent.data.d = stream.readByte();
- break;
- case FlightLogEvent.AUTOTUNE_CYCLE_RESULT:
- lastEvent.data.overshot = stream.readByte();
- lastEvent.data.p = stream.readByte();
- lastEvent.data.i = stream.readByte();
- lastEvent.data.d = stream.readByte();
- break;
- case FlightLogEvent.AUTOTUNE_TARGETS:
- //Convert the angles from decidegrees back to plain old degrees for ease of use
- lastEvent.data.currentAngle = stream.readS16() / 10.0;
+ function parseEventFrame(raw) {
+ let END_OF_LOG_MESSAGE = "End of log\0",
+ eventType = stream.readByte();
- lastEvent.data.targetAngle = stream.readS8();
- lastEvent.data.targetAngleAtPeak = stream.readS8();
+ lastEvent = {
+ event: eventType,
+ data: {},
+ };
- lastEvent.data.firstPeakAngle = stream.readS16() / 10.0;
- lastEvent.data.secondPeakAngle = stream.readS16() / 10.0;
- break;
- case FlightLogEvent.GTUNE_CYCLE_RESULT:
- lastEvent.data.axis = stream.readU8();
- lastEvent.data.gyroAVG = stream.readSignedVB();
- lastEvent.data.newP = stream.readS16();
- break;
- case FlightLogEvent.INFLIGHT_ADJUSTMENT:
- var tmp = stream.readU8();
- lastEvent.data.name = 'Unknown';
- lastEvent.data.func = tmp & 127;
- lastEvent.data.value = tmp < 128 ? stream.readSignedVB() : uint32ToFloat(stream.readU32());
- if (INFLIGHT_ADJUSTMENT_FUNCTIONS[lastEvent.data.func] !== undefined) {
- var descr = INFLIGHT_ADJUSTMENT_FUNCTIONS[lastEvent.data.func];
- lastEvent.data.name = descr.name;
- var scale = 1;
- if (descr.scale !== undefined) {
- scale = descr.scale;
- }
- if (tmp >= 128 && descr.scalef !== undefined) {
- scale = descr.scalef;
- }
- lastEvent.data.value = Math.round((lastEvent.data.value * scale) * 10000) / 10000;
- }
+ switch (eventType) {
+ case FlightLogEvent.SYNC_BEEP:
+ lastEvent.data.time = stream.readUnsignedVB();
+ lastEvent.time = lastEvent.data.time;
+ break;
+ case FlightLogEvent.FLIGHT_MODE: // get the flag status change
+ lastEvent.data.newFlags = stream.readUnsignedVB();
+ lastEvent.data.lastFlags = stream.readUnsignedVB();
+ break;
+ case FlightLogEvent.DISARM:
+ lastEvent.data.reason = stream.readUnsignedVB();
+ break;
+ case FlightLogEvent.AUTOTUNE_CYCLE_START:
+ lastEvent.data.phase = stream.readByte();
+
+ var cycleAndRising = stream.readByte();
+
+ lastEvent.data.cycle = cycleAndRising & 0x7f;
+ lastEvent.data.rising = (cycleAndRising >> 7) & 0x01;
+
+ lastEvent.data.p = stream.readByte();
+ lastEvent.data.i = stream.readByte();
+ lastEvent.data.d = stream.readByte();
+ break;
+ case FlightLogEvent.AUTOTUNE_CYCLE_RESULT:
+ lastEvent.data.overshot = stream.readByte();
+ lastEvent.data.p = stream.readByte();
+ lastEvent.data.i = stream.readByte();
+ lastEvent.data.d = stream.readByte();
+ break;
+ case FlightLogEvent.AUTOTUNE_TARGETS:
+ //Convert the angles from decidegrees back to plain old degrees for ease of use
+ lastEvent.data.currentAngle = stream.readS16() / 10.0;
+
+ lastEvent.data.targetAngle = stream.readS8();
+ lastEvent.data.targetAngleAtPeak = stream.readS8();
+
+ lastEvent.data.firstPeakAngle = stream.readS16() / 10.0;
+ lastEvent.data.secondPeakAngle = stream.readS16() / 10.0;
+ break;
+ case FlightLogEvent.GTUNE_CYCLE_RESULT:
+ lastEvent.data.axis = stream.readU8();
+ lastEvent.data.gyroAVG = stream.readSignedVB();
+ lastEvent.data.newP = stream.readS16();
+ break;
+ case FlightLogEvent.INFLIGHT_ADJUSTMENT:
+ var tmp = stream.readU8();
+ lastEvent.data.name = "Unknown";
+ lastEvent.data.func = tmp & 127;
+ lastEvent.data.value =
+ tmp < 128 ? stream.readSignedVB() : uint32ToFloat(stream.readU32());
+ if (INFLIGHT_ADJUSTMENT_FUNCTIONS[lastEvent.data.func] !== undefined) {
+ let descr = INFLIGHT_ADJUSTMENT_FUNCTIONS[lastEvent.data.func];
+ lastEvent.data.name = descr.name;
+ let scale = 1;
+ if (descr.scale !== undefined) {
+ scale = descr.scale;
+ }
+ if (tmp >= 128 && descr.scalef !== undefined) {
+ scale = descr.scalef;
+ }
+ lastEvent.data.value =
+ Math.round(lastEvent.data.value * scale * 10000) / 10000;
+ }
+ break;
+ case FlightLogEvent.TWITCH_TEST:
+ //lastEvent.data.stage = stream.readU8();
+ var tmp = stream.readU8();
+ switch (tmp) {
+ case 1:
+ lastEvent.data.name = "Response Time->";
break;
- case FlightLogEvent.TWITCH_TEST:
- //lastEvent.data.stage = stream.readU8();
- var tmp = stream.readU8();
- switch (tmp) {
- case(1):
- lastEvent.data.name = "Response Time->";
- break;
- case(2):
- lastEvent.data.name = "Half Setpoint Time->";
- break;
- case(3):
- lastEvent.data.name = "Setpoint Time->";
- break;
- case(4):
- lastEvent.data.name = "Negative Setpoint->";
- break;
- case(5):
- lastEvent.data.name = "Initial Setpoint->";
- }
- lastEvent.data.value = uint32ToFloat(stream.readU32());
+ case 2:
+ lastEvent.data.name = "Half Setpoint Time->";
break;
- case FlightLogEvent.LOGGING_RESUME:
- lastEvent.data.logIteration = stream.readUnsignedVB();
- lastEvent.data.currentTime = stream.readUnsignedVB();
+ case 3:
+ lastEvent.data.name = "Setpoint Time->";
break;
- case FlightLogEvent.LOG_END:
- var endMessage = stream.readString(END_OF_LOG_MESSAGE.length);
-
- if (endMessage == END_OF_LOG_MESSAGE) {
- //Adjust the end of stream so we stop reading, this log is done
- stream.end = stream.pos;
- } else {
- /*
- * This isn't the real end of log message, it's probably just some bytes that happened to look like
- * an event header.
- */
- lastEvent = null;
- }
+ case 4:
+ lastEvent.data.name = "Negative Setpoint->";
break;
- default:
- lastEvent = null;
+ case 5:
+ lastEvent.data.name = "Initial Setpoint->";
+ }
+ lastEvent.data.value = uint32ToFloat(stream.readU32());
+ break;
+ case FlightLogEvent.LOGGING_RESUME:
+ lastEvent.data.logIteration = stream.readUnsignedVB();
+ lastEvent.data.currentTime = stream.readUnsignedVB();
+ break;
+ case FlightLogEvent.LOG_END:
+ var endMessage = stream.readString(END_OF_LOG_MESSAGE.length);
+
+ if (endMessage == END_OF_LOG_MESSAGE) {
+ //Adjust the end of stream so we stop reading, this log is done
+ stream.end = stream.pos;
+ } else {
+ /*
+ * This isn't the real end of log message, it's probably just some bytes that happened to look like
+ * an event header.
+ */
+ lastEvent = null;
}
+ break;
+ default:
+ lastEvent = null;
}
+ }
- function getFrameType(command) {
- return frameTypes[command];
- }
+ function getFrameType(command) {
+ return frameTypes[command];
+ }
- // Reset parsing state from the data section of the current log (don't reset header information). Useful for seeking.
- this.resetDataState = function() {
- lastSkippedFrames = 0;
+ // Reset parsing state from the data section of the current log (don't reset header information). Useful for seeking.
+ this.resetDataState = function () {
+ lastSkippedFrames = 0;
- lastMainFrameIteration = -1;
- lastMainFrameTime = -1;
+ lastMainFrameIteration = -1;
+ lastMainFrameTime = -1;
- invalidateMainStream();
- gpsHomeIsValid = false;
- lastEvent = null;
- };
+ invalidateMainStream();
+ gpsHomeIsValid = false;
+ lastEvent = null;
+ };
- // Reset any parsed information from previous parses (header & data)
- this.resetAllState = function() {
- this.resetStats();
+ // Reset any parsed information from previous parses (header & data)
+ this.resetAllState = function () {
+ this.resetStats();
- //Reset system configuration to MW's defaults
- // Lets add the custom extensions
- var completeSysConfig = $.extend({}, defaultSysConfig, defaultSysConfigExtension);
- this.sysConfig = Object.create(completeSysConfig); // Object.create(defaultSysConfig);
+ //Reset system configuration to MW's defaults
+ // Lets add the custom extensions
+ let completeSysConfig = $.extend(
+ {},
+ defaultSysConfig,
+ defaultSysConfigExtension
+ );
+ this.sysConfig = Object.create(completeSysConfig); // Object.create(defaultSysConfig);
- this.frameDefs = {};
+ this.frameDefs = {};
- this.resetDataState();
- };
+ this.resetDataState();
+ };
+
+ // Check that the given frame definition contains some fields and the right number of predictors & encodings to match
+ function isFrameDefComplete(frameDef) {
+ return (
+ frameDef &&
+ frameDef.count > 0 &&
+ frameDef.encoding.length == frameDef.count &&
+ frameDef.predictor.length == frameDef.count
+ );
+ }
+
+ this.parseHeader = function (startOffset, endOffset) {
+ this.resetAllState();
+
+ //Set parsing ranges up
+ stream.start = startOffset === undefined ? stream.pos : startOffset;
+ stream.pos = stream.start;
+ stream.end = endOffset === undefined ? stream.end : endOffset;
+ stream.eof = false;
+
+ mainloop: while (true) {
+ let command = stream.readChar();
+
+ switch (command) {
+ case "H":
+ parseHeaderLine();
+ break;
+ case EOF:
+ break mainloop;
+ default:
+ /*
+ * If we see something that looks like the beginning of a data frame, assume it
+ * is and terminate the header.
+ */
+ if (getFrameType(command)) {
+ stream.unreadChar(command);
+
+ break mainloop;
+ } // else skip garbage which apparently precedes the first data frame
+ break;
+ }
+ }
- // Check that the given frame definition contains some fields and the right number of predictors & encodings to match
- function isFrameDefComplete(frameDef) {
- return frameDef && frameDef.count > 0 && frameDef.encoding.length == frameDef.count && frameDef.predictor.length == frameDef.count;
+ adjustFieldDefsList(
+ that.sysConfig.firmwareType,
+ that.sysConfig.firmwareVersion
+ );
+ FlightLogFieldPresenter.adjustDebugDefsList(
+ that.sysConfig.firmwareType,
+ that.sysConfig.firmwareVersion
+ );
+
+ if (!isFrameDefComplete(this.frameDefs.I)) {
+ throw "Log is missing required definitions for I frames, header may be corrupt";
}
- this.parseHeader = function(startOffset, endOffset) {
- this.resetAllState();
-
- //Set parsing ranges up
- stream.start = startOffset === undefined ? stream.pos : startOffset;
- stream.pos = stream.start;
- stream.end = endOffset === undefined ? stream.end : endOffset;
- stream.eof = false;
-
- mainloop:
- while (true) {
- var command = stream.readChar();
-
- switch (command) {
- case "H":
- parseHeaderLine();
- break;
- case EOF:
- break mainloop;
- default:
- /*
- * If we see something that looks like the beginning of a data frame, assume it
- * is and terminate the header.
- */
- if (getFrameType(command)) {
- stream.unreadChar(command);
-
- break mainloop;
- } // else skip garbage which apparently precedes the first data frame
- break;
- }
- }
+ if (!this.frameDefs.P) {
+ throw "Log is missing required definitions for P frames, header may be corrupt";
+ }
- adjustFieldDefsList(that.sysConfig.firmwareType, that.sysConfig.firmwareVersion);
- FlightLogFieldPresenter.adjustDebugDefsList(that.sysConfig.firmwareType, that.sysConfig.firmwareVersion);
+ // P frames are derived from I frames so copy over frame definition information to those
+ this.frameDefs.P.count = this.frameDefs.I.count;
+ this.frameDefs.P.name = this.frameDefs.I.name;
+ this.frameDefs.P.nameToIndex = this.frameDefs.I.nameToIndex;
+ this.frameDefs.P.signed = this.frameDefs.I.signed;
- if (!isFrameDefComplete(this.frameDefs.I)) {
- throw "Log is missing required definitions for I frames, header may be corrupt";
- }
+ if (!isFrameDefComplete(this.frameDefs.P)) {
+ throw "Log is missing required definitions for P frames, header may be corrupt";
+ }
- if (!this.frameDefs.P) {
- throw "Log is missing required definitions for P frames, header may be corrupt";
+ // Now we know our field counts, we can allocate arrays to hold parsed data
+ mainHistoryRing = [
+ new Array(this.frameDefs.I.count),
+ new Array(this.frameDefs.I.count),
+ new Array(this.frameDefs.I.count),
+ ];
+
+ if (this.frameDefs.H && this.frameDefs.G) {
+ gpsHomeHistory = [
+ new Array(this.frameDefs.H.count),
+ new Array(this.frameDefs.H.count),
+ ];
+ lastGPS = new Array(this.frameDefs.G.count);
+
+ /* Home coord predictors appear in pairs (lat/lon), but the predictor ID is the same for both. It's easier to
+ * apply the right predictor during parsing if we rewrite the predictor ID for the second half of the pair here:
+ */
+ for (let i = 1; i < this.frameDefs.G.count; i++) {
+ if (
+ this.frameDefs.G.predictor[i - 1] ==
+ FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD &&
+ this.frameDefs.G.predictor[i] == FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD
+ ) {
+ this.frameDefs.G.predictor[i] =
+ FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1;
}
+ }
+ } else {
+ gpsHomeHistory = [];
+ lastGPS = [];
+ }
- // P frames are derived from I frames so copy over frame definition information to those
- this.frameDefs.P.count = this.frameDefs.I.count;
- this.frameDefs.P.name = this.frameDefs.I.name;
- this.frameDefs.P.nameToIndex = this.frameDefs.I.nameToIndex;
- this.frameDefs.P.signed = this.frameDefs.I.signed;
-
- if (!isFrameDefComplete(this.frameDefs.P)) {
- throw "Log is missing required definitions for P frames, header may be corrupt";
+ if (this.frameDefs.S) {
+ lastSlow = new Array(this.frameDefs.S.count);
+ } else {
+ lastSlow = [];
+ }
+ };
+
+ /**
+ * Set the current GPS home data to the given frame. Pass an empty array in in order to invalidate the GPS home
+ * frame data.
+ *
+ * (The data is stored in gpsHomeHistory[1])
+ */
+ this.setGPSHomeHistory = function (newGPSHome) {
+ if (newGPSHome.length == that.frameDefs.H.count) {
+ //Copy the decoded frame into the "last state" entry of gpsHomeHistory to publish it:
+ for (let i = 0; i < newGPSHome.length; i++) {
+ gpsHomeHistory[1][i] = newGPSHome[i];
+ }
+
+ gpsHomeIsValid = true;
+ } else {
+ gpsHomeIsValid = false;
+ }
+ };
+
+ /**
+ * Continue the current parse by scanning the given range of offsets for data. To begin an independent parse,
+ * call resetDataState() first.
+ */
+ this.parseLogData = function (raw, startOffset, endOffset) {
+ let looksLikeFrameCompleted = false,
+ prematureEof = false,
+ frameStart = 0,
+ frameType = null,
+ lastFrameType = null;
+
+ invalidateMainStream();
+
+ //Set parsing ranges up for the log the caller selected
+ stream.start = startOffset === undefined ? stream.pos : startOffset;
+ stream.pos = stream.start;
+ stream.end = endOffset === undefined ? stream.end : endOffset;
+ stream.eof = false;
+ while (true) {
+ let command = stream.readChar();
+
+ if (lastFrameType) {
+ var lastFrameSize = stream.pos - frameStart,
+ frameTypeStats;
+
+ // Is this the beginning of a new frame?
+ looksLikeFrameCompleted =
+ getFrameType(command) || (!prematureEof && command == EOF);
+
+ if (!this.stats.frame[lastFrameType.marker]) {
+ this.stats.frame[lastFrameType.marker] = {
+ bytes: 0,
+ sizeCount: new Int32Array(
+ 256
+ ) /* int32 arrays are zero-filled, handy! */,
+ validCount: 0,
+ corruptCount: 0,
+ desyncCount: 0,
+ field: [],
+ };
}
- // Now we know our field counts, we can allocate arrays to hold parsed data
- mainHistoryRing = [new Array(this.frameDefs.I.count), new Array(this.frameDefs.I.count), new Array(this.frameDefs.I.count)];
-
- if (this.frameDefs.H && this.frameDefs.G) {
- gpsHomeHistory = [new Array(this.frameDefs.H.count), new Array(this.frameDefs.H.count)];
- lastGPS = new Array(this.frameDefs.G.count);
-
- /* Home coord predictors appear in pairs (lat/lon), but the predictor ID is the same for both. It's easier to
- * apply the right predictor during parsing if we rewrite the predictor ID for the second half of the pair here:
- */
- for (var i = 1; i < this.frameDefs.G.count; i++) {
- if (this.frameDefs.G.predictor[i - 1] == FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD &&
- this.frameDefs.G.predictor[i] == FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD) {
- this.frameDefs.G.predictor[i] = FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD_1;
- }
- }
+ frameTypeStats = this.stats.frame[lastFrameType.marker];
+ // If we see what looks like the beginning of a new frame, assume that the previous frame was valid:
+ if (
+ lastFrameSize <= FLIGHT_LOG_MAX_FRAME_LENGTH &&
+ looksLikeFrameCompleted
+ ) {
+ let frameAccepted = true;
+
+ if (lastFrameType.complete)
+ frameAccepted = lastFrameType.complete(
+ lastFrameType.marker,
+ frameStart,
+ stream.pos,
+ raw
+ );
+
+ if (frameAccepted) {
+ //Update statistics for this frame type
+ frameTypeStats.bytes += lastFrameSize;
+ frameTypeStats.sizeCount[lastFrameSize]++;
+ frameTypeStats.validCount++;
+ } else {
+ frameTypeStats.desyncCount++;
+ }
} else {
- gpsHomeHistory = [];
- lastGPS = [];
+ //The previous frame was corrupt
+
+ //We need to resynchronise before we can deliver another main frame:
+ mainStreamIsValid = false;
+ frameTypeStats.corruptCount++;
+ this.stats.totalCorruptFrames++;
+
+ /*
+ * Start the search for a frame beginning after the first byte of the previous corrupt frame.
+ * This way we can find the start of the next frame after the corrupt frame if the corrupt frame
+ * was truncated.
+ */
+ stream.pos = frameStart + 1;
+ lastFrameType = null;
+ prematureEof = false;
+ stream.eof = false;
+ continue;
}
+ }
- if (this.frameDefs.S) {
- lastSlow = new Array(this.frameDefs.S.count);
- } else {
- lastSlow = [];
- }
- };
+ if (command == EOF) break;
- /**
- * Set the current GPS home data to the given frame. Pass an empty array in in order to invalidate the GPS home
- * frame data.
- *
- * (The data is stored in gpsHomeHistory[1])
- */
- this.setGPSHomeHistory = function(newGPSHome) {
- if (newGPSHome.length == that.frameDefs.H.count) {
- //Copy the decoded frame into the "last state" entry of gpsHomeHistory to publish it:
- for (var i = 0; i < newGPSHome.length; i++) {
- gpsHomeHistory[1][i] = newGPSHome[i];
- }
-
- gpsHomeIsValid = true;
- } else {
- gpsHomeIsValid = false;
- }
- };
+ frameStart = stream.pos - 1;
+ frameType = getFrameType(command);
+ // Reject the frame if it is one that we have no definitions for in the header
+ if (frameType && (command == "E" || that.frameDefs[command])) {
+ lastFrameType = frameType;
+ frameType.parse(raw);
- /**
- * Continue the current parse by scanning the given range of offsets for data. To begin an independent parse,
- * call resetDataState() first.
- */
- this.parseLogData = function(raw, startOffset, endOffset) {
- var
- looksLikeFrameCompleted = false,
- prematureEof = false,
- frameStart = 0,
- frameType = null,
- lastFrameType = null;
-
- invalidateMainStream();
-
- //Set parsing ranges up for the log the caller selected
- stream.start = startOffset === undefined ? stream.pos : startOffset;
- stream.pos = stream.start;
- stream.end = endOffset === undefined ? stream.end : endOffset;
- stream.eof = false;
-
- while (true) {
- var command = stream.readChar();
-
- if (lastFrameType) {
- var
- lastFrameSize = stream.pos - frameStart,
- frameTypeStats;
-
- // Is this the beginning of a new frame?
- looksLikeFrameCompleted = getFrameType(command) || (!prematureEof && command == EOF);
-
- if (!this.stats.frame[lastFrameType.marker]) {
- this.stats.frame[lastFrameType.marker] = {
- bytes: 0,
- sizeCount: new Int32Array(256), /* int32 arrays are zero-filled, handy! */
- validCount: 0,
- corruptCount: 0,
- field: []
- };
- }
-
- frameTypeStats = this.stats.frame[lastFrameType.marker];
-
- // If we see what looks like the beginning of a new frame, assume that the previous frame was valid:
- if (lastFrameSize <= FLIGHT_LOG_MAX_FRAME_LENGTH && looksLikeFrameCompleted) {
- var frameAccepted = true;
-
- if (lastFrameType.complete)
- frameAccepted = lastFrameType.complete(lastFrameType.marker, frameStart, stream.pos, raw);
-
- if (frameAccepted) {
- //Update statistics for this frame type
- frameTypeStats.bytes += lastFrameSize;
- frameTypeStats.sizeCount[lastFrameSize]++;
- frameTypeStats.validCount++;
- } else {
- frameTypeStats.desyncCount++;
- }
- } else {
- //The previous frame was corrupt
-
- //We need to resynchronise before we can deliver another main frame:
- mainStreamIsValid = false;
- frameTypeStats.corruptCount++;
- this.stats.totalCorruptFrames++;
-
- //Let the caller know there was a corrupt frame (don't give them a pointer to the frame data because it is totally worthless)
- if (this.onFrameReady)
- this.onFrameReady(false, null, lastFrameType.marker, frameStart, lastFrameSize);
-
- /*
- * Start the search for a frame beginning after the first byte of the previous corrupt frame.
- * This way we can find the start of the next frame after the corrupt frame if the corrupt frame
- * was truncated.
- */
- stream.pos = frameStart + 1;
- lastFrameType = null;
- prematureEof = false;
- stream.eof = false;
- continue;
- }
- }
-
- if (command == EOF)
- break;
-
- frameStart = stream.pos - 1;
- frameType = getFrameType(command);
-
- // Reject the frame if it is one that we have no definitions for in the header
- if (frameType && (command == 'E' || that.frameDefs[command])) {
- lastFrameType = frameType;
- frameType.parse(raw);
-
- //We shouldn't read an EOF during reading a frame (that'd imply the frame was truncated)
- if (stream.eof) {
- prematureEof = true;
- }
- } else {
- mainStreamIsValid = false;
- lastFrameType = null;
- }
+ //We shouldn't read an EOF during reading a frame (that'd imply the frame was truncated)
+ if (stream.eof) {
+ prematureEof = true;
}
+ } else {
+ mainStreamIsValid = false;
+ lastFrameType = null;
+ }
+ }
- this.stats.totalBytes += stream.end - stream.start;
+ this.stats.totalBytes += stream.end - stream.start;
- return true;
- };
+ return true;
+ };
- frameTypes = {
- "I": {marker: "I", parse: parseIntraframe, complete: completeIntraframe},
- "P": {marker: "P", parse: parseInterframe, complete: completeInterframe},
- "G": {marker: "G", parse: parseGPSFrame, complete: completeGPSFrame},
- "H": {marker: "H", parse: parseGPSHomeFrame, complete: completeGPSHomeFrame},
- "S": {marker: "S", parse: parseSlowFrame, complete: completeSlowFrame},
- "E": {marker: "E", parse: parseEventFrame, complete: completeEventFrame}
- };
+ frameTypes = {
+ I: { marker: "I", parse: parseIntraframe, complete: completeIntraframe },
+ P: { marker: "P", parse: parseInterframe, complete: completeInterframe },
+ G: { marker: "G", parse: parseGPSFrame, complete: completeGPSFrame },
+ H: {
+ marker: "H",
+ parse: parseGPSHomeFrame,
+ complete: completeGPSHomeFrame,
+ },
+ S: { marker: "S", parse: parseSlowFrame, complete: completeSlowFrame },
+ E: { marker: "E", parse: parseEventFrame, complete: completeEventFrame },
+ };
- stream = new ArrayDataStream(logData);
-};
+ stream = new ArrayDataStream(logData);
+}
-FlightLogParser.prototype.resetStats = function() {
- this.stats = {
- totalBytes: 0,
+FlightLogParser.prototype.resetStats = function () {
+ this.stats = {
+ totalBytes: 0,
- // Number of frames that failed to decode:
- totalCorruptFrames: 0,
+ // Number of frames that failed to decode:
+ totalCorruptFrames: 0,
- //If our sampling rate is less than 1, we won't log every loop iteration, and that is accounted for here:
- intentionallyAbsentIterations: 0,
+ //If our sampling rate is less than 1, we won't log every loop iteration, and that is accounted for here:
+ intentionallyAbsentIterations: 0,
- // Statistics for each frame type ("I", "P" etc)
- frame: {}
- };
+ // Statistics for each frame type ("I", "P" etc)
+ frame: {},
+ };
};
-FlightLogParser.prototype.FLIGHT_LOG_START_MARKER = asciiStringToByteArray("H Product:Blackbox flight data recorder by Nicholas Sherlock\n");
+FlightLogParser.prototype.FLIGHT_LOG_START_MARKER = asciiStringToByteArray(
+ "H Product:Blackbox flight data recorder by Nicholas Sherlock\n"
+);
FlightLogParser.prototype.FLIGHT_LOG_FIELD_UNSIGNED = 0;
-FlightLogParser.prototype.FLIGHT_LOG_FIELD_SIGNED = 1;
+FlightLogParser.prototype.FLIGHT_LOG_FIELD_SIGNED = 1;
FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_ITERATION = 0;
FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME = 1;
diff --git a/src/flightlog_video_renderer.js b/src/flightlog_video_renderer.js
index 7c5a9e17..0dd32506 100644
--- a/src/flightlog_video_renderer.js
+++ b/src/flightlog_video_renderer.js
@@ -2,9 +2,9 @@ import { FlightLogGrapher } from "./grapher";
/**
* Render a video of the given log using the given videoOptions (user video settings) and logParameters.
- *
+ *
* flightLog - FlightLog object to render
- *
+ *
* logParameters - Object with these fields:
* inTime - Blackbox time code the video should start at, or false to start from the beginning
* outTime - Blackbox time code the video should end at, or false to end at the end
@@ -22,318 +22,365 @@ import { FlightLogGrapher } from "./grapher";
* onComplete - On render completion, called with (success, frameCount)
* onProgress - Called periodically with (frameIndex, frameCount) to report progress
*/
-export function FlightLogVideoRenderer(flightLog, logParameters, videoOptions, events) {
- var
- WORK_CHUNK_SIZE_FOCUSED = 8,
- WORK_CHUNK_SIZE_UNFOCUSED = 32,
-
- videoWriter,
-
- canvas = document.createElement('canvas'),
- stickCanvas = document.createElement('canvas'),
- craftCanvas = document.createElement('canvas'),
- analyserCanvas = document.createElement('canvas'),
- stickCanvasLeft, stickCanvasTop, hasStick,
- craftCanvasLeft, craftCanvasTop, hasCraft,
- analyserCanvasLeft, analyserCanvasTop, hasAnalyser,
-
-
- canvasContext = canvas.getContext("2d"),
-
- frameCount, frameDuration /* Duration of a frame in Blackbox's microsecond time units */,
- frameTime, frameIndex,
- cancel = false,
-
- workChunkSize = WORK_CHUNK_SIZE_FOCUSED,
- hidden, visibilityChange,
-
- graph;
-
- // From https://developer.mozilla.org/en-US/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API
- if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
- hidden = "hidden";
- visibilityChange = "visibilitychange";
- } else if (typeof document.mozHidden !== "undefined") {
- hidden = "mozHidden";
- visibilityChange = "mozvisibilitychange";
- } else if (typeof document.msHidden !== "undefined") {
- hidden = "msHidden";
- visibilityChange = "msvisibilitychange";
- } else if (typeof document.webkitHidden !== "undefined") {
- hidden = "webkitHidden";
- visibilityChange = "webkitvisibilitychange";
+export function FlightLogVideoRenderer(
+ flightLog,
+ logParameters,
+ videoOptions,
+ events
+) {
+ let WORK_CHUNK_SIZE_FOCUSED = 8,
+ WORK_CHUNK_SIZE_UNFOCUSED = 32,
+ videoWriter,
+ canvas = document.createElement("canvas"),
+ stickCanvas = document.createElement("canvas"),
+ craftCanvas = document.createElement("canvas"),
+ analyserCanvas = document.createElement("canvas"),
+ stickCanvasLeft,
+ stickCanvasTop,
+ hasStick,
+ craftCanvasLeft,
+ craftCanvasTop,
+ hasCraft,
+ analyserCanvasLeft,
+ analyserCanvasTop,
+ hasAnalyser,
+ canvasContext = canvas.getContext("2d"),
+ frameCount,
+ frameDuration /* Duration of a frame in Blackbox's microsecond time units */,
+ frameTime,
+ frameIndex,
+ cancel = false,
+ workChunkSize = WORK_CHUNK_SIZE_FOCUSED,
+ hidden,
+ visibilityChange,
+ graph;
+
+ // From https://developer.mozilla.org/en-US/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API
+ if (typeof document.hidden !== "undefined") {
+ // Opera 12.10 and Firefox 18 and later support
+ hidden = "hidden";
+ visibilityChange = "visibilitychange";
+ } else if (typeof document.mozHidden !== "undefined") {
+ hidden = "mozHidden";
+ visibilityChange = "mozvisibilitychange";
+ } else if (typeof document.msHidden !== "undefined") {
+ hidden = "msHidden";
+ visibilityChange = "msvisibilitychange";
+ } else if (typeof document.webkitHidden !== "undefined") {
+ hidden = "webkitHidden";
+ visibilityChange = "webkitvisibilitychange";
+ }
+
+ /**
+ * Chrome slows down timers when the tab loses focus, so we want to fire fewer timer events (render more frames
+ * in a chunk) in order to compensate.
+ */
+ function handleVisibilityChange() {
+ if (document[hidden]) {
+ workChunkSize = WORK_CHUNK_SIZE_UNFOCUSED;
+ } else {
+ workChunkSize = WORK_CHUNK_SIZE_FOCUSED;
}
-
- /**
- * Chrome slows down timers when the tab loses focus, so we want to fire fewer timer events (render more frames
- * in a chunk) in order to compensate.
- */
- function handleVisibilityChange() {
- if (document[hidden]) {
- workChunkSize = WORK_CHUNK_SIZE_UNFOCUSED;
- } else {
- workChunkSize = WORK_CHUNK_SIZE_FOCUSED;
- }
+ }
+
+ function installVisibilityHandler() {
+ if (typeof document[hidden] !== "undefined") {
+ document.addEventListener(
+ visibilityChange,
+ handleVisibilityChange,
+ false
+ );
}
+ }
- function installVisibilityHandler() {
- if (typeof document[hidden] !== "undefined") {
- document.addEventListener(visibilityChange, handleVisibilityChange, false);
- }
+ function removeVisibilityHandler() {
+ if (typeof document[hidden] !== "undefined") {
+ document.removeEventListener(visibilityChange, handleVisibilityChange);
}
-
- function removeVisibilityHandler() {
- if (typeof document[hidden] !== "undefined") {
- document.removeEventListener(visibilityChange, handleVisibilityChange);
+ }
+
+ function supportsFileWriter() {
+ return !!(chrome && chrome.fileSystem);
+ }
+
+ /**
+ * Returns a Promise that resolves to a FileWriter for the file the user chose, or fails if the user cancels/
+ * something else bad happens.
+ */
+ function openFileForWrite(suggestedName, onComplete) {
+ return new Promise(function (resolve, reject) {
+ chrome.fileSystem.chooseEntry(
+ {
+ type: "saveFile",
+ suggestedName: suggestedName,
+ accepts: [{ extensions: ["webm"], description: "WebM video" }],
+ },
+ function (fileEntry) {
+ let error = chrome.runtime.lastError;
+
+ if (error) {
+ if (error.message == "User cancelled") {
+ reject(null);
+ } else {
+ reject(error.message);
+ }
+ } else {
+ fileEntry.createWriter(
+ function (fileWriter) {
+ fileWriter.onerror = function (e) {
+ console.error(e);
+ };
+
+ fileWriter.onwriteend = function () {
+ fileWriter.onwriteend = null;
+
+ resolve(fileWriter);
+ };
+
+ // If the file already existed then we need to truncate it to avoid doing a partial rewrite
+ fileWriter.truncate(0);
+ },
+ function (e) {
+ // File is not readable or does not exist!
+ reject(e);
+ }
+ );
+ }
}
+ );
+ });
+ }
+
+ function notifyCompletion(success, frameCount) {
+ removeVisibilityHandler();
+
+ if (events && events.onComplete) {
+ events.onComplete(success, frameCount);
}
-
- function supportsFileWriter() {
- return !!(chrome && chrome.fileSystem);
- }
-
- /**
- * Returns a Promise that resolves to a FileWriter for the file the user chose, or fails if the user cancels/
- * something else bad happens.
+ }
+
+ function finishRender() {
+ videoWriter.complete().then(function (webM) {
+ if (webM) {
+ window.saveAs(webM, "video.webm");
+ }
+
+ notifyCompletion(true, frameIndex);
+ });
+ }
+
+ function renderChunk() {
+ /*
+ * Allow the UI to have some time to run by breaking the work into chunks, yielding to the browser
+ * between chunks.
+ *
+ * I'd dearly like to run the rendering process in a Web Worker, but workers can't use Canvas because
+ * it happens to be a DOM element (and Workers aren't allowed access to the DOM). Stupid!
*/
- function openFileForWrite(suggestedName, onComplete) {
- return new Promise(function(resolve, reject) {
- chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: suggestedName,
- accepts: [{extensions: ['webm'], description: "WebM video"}]}, function(fileEntry) {
- var
- error = chrome.runtime.lastError;
-
- if (error) {
- if (error.message == "User cancelled") {
- reject(null);
- } else {
- reject(error.message);
- }
- } else {
- fileEntry.createWriter(function (fileWriter) {
- fileWriter.onerror = function (e) {
- console.error(e);
- };
-
- fileWriter.onwriteend = function() {
- fileWriter.onwriteend = null;
-
- resolve(fileWriter);
- };
-
- // If the file already existed then we need to truncate it to avoid doing a partial rewrite
- fileWriter.truncate(0);
- }, function (e) {
- // File is not readable or does not exist!
- reject(e);
- });
- }
- });
- });
- }
+ let framesToRender = Math.min(workChunkSize, frameCount - frameIndex);
- function notifyCompletion(success, frameCount) {
- removeVisibilityHandler();
-
- if (events && events.onComplete) {
- events.onComplete(success, frameCount);
- }
- }
-
- function finishRender() {
- videoWriter.complete().then(function(webM) {
- if (webM) {
- window.saveAs(webM, "video.webm");
- }
-
- notifyCompletion(true, frameIndex);
- });
+ if (cancel) {
+ notifyCompletion(false);
+ return;
}
-
- function renderChunk() {
- /*
- * Allow the UI to have some time to run by breaking the work into chunks, yielding to the browser
- * between chunks.
- *
- * I'd dearly like to run the rendering process in a Web Worker, but workers can't use Canvas because
- * it happens to be a DOM element (and Workers aren't allowed access to the DOM). Stupid!
- */
- var
- framesToRender = Math.min(workChunkSize, frameCount - frameIndex);
-
- if (cancel) {
- notifyCompletion(false);
- return;
+
+ let completeChunk = function () {
+ if (events && events.onProgress) {
+ events.onProgress(frameIndex, frameCount);
}
-
- var
- completeChunk = function() {
- if (events && events.onProgress) {
- events.onProgress(frameIndex, frameCount);
- }
-
- if (frameIndex >= frameCount) {
- finishRender();
- } else {
- setTimeout(renderChunk, 0);
- }
- },
-
- renderFrame = function() {
- graph.render(frameTime);
-
- if (logParameters.hasSticks && parseInt(userSettings.sticks.size) > 0) canvasContext.drawImage(stickCanvas, stickCanvasLeft, stickCanvasTop);
- if (logParameters.hasCraft && parseInt(userSettings.craft.size) > 0) canvasContext.drawImage(craftCanvas, craftCanvasLeft, craftCanvasTop);
- if (logParameters.hasAnalyser && parseInt(userSettings.analyser.size) > 0) canvasContext.drawImage(analyserCanvas, analyserCanvasLeft, analyserCanvasTop);
-
- videoWriter.addFrame(canvas);
-
- frameIndex++;
- frameTime += frameDuration;
- };
-
- if (logParameters.flightVideo) {
- var
- renderFrames = function(frameCount) {
- if (frameCount == 0) {
- completeChunk();
- return;
- }
-
- logParameters.flightVideo.onseeked = function() {
- canvasContext.drawImage(logParameters.flightVideo, 0, 0, videoOptions.width, videoOptions.height);
-
- if (videoOptions.videoDim > 0) {
- canvasContext.fillStyle = 'rgba(0,0,0,' + videoOptions.videoDim + ')';
-
- canvasContext.fillRect(0, 0, canvas.width, canvas.height);
- }
-
- // Render the normal graphs and add frame to video
- renderFrame();
-
- renderFrames(frameCount - 1);
- };
-
- logParameters.flightVideo.currentTime = (frameTime - flightLog.getMinTime()) / 1000000 + (logParameters.flightVideoOffset || 0);
- };
-
- renderFrames(framesToRender);
+
+ if (frameIndex >= frameCount) {
+ finishRender();
} else {
- for (var i = 0; i < framesToRender; i++) {
- renderFrame();
- }
-
- completeChunk();
+ setTimeout(renderChunk, 0);
}
- }
-
- /**
- * Attempt to cancel rendering sometime soon. An onComplete() event will be triggered with the 'success' parameter set
- * appropriately to report the outcome.
- */
- this.cancel = function() {
- cancel = true;
- };
-
- /**
- * Begin rendering the video and return immediately.
- */
- this.start = function() {
- cancel = false;
-
- frameTime = logParameters.inTime;
- frameIndex = 0;
-
- installVisibilityHandler();
-
- var
- webMOptions = {
- frameRate: videoOptions.frameRate,
- };
-
- if (supportsFileWriter()) {
- openFileForWrite("video.webm").then(function(fileWriter) {
- webMOptions.fileWriter = fileWriter;
-
- videoWriter = new WebMWriter(webMOptions);
- renderChunk();
- }, function(error) {
- console.error(error);
- notifyCompletion(false);
- });
- } else {
- videoWriter = new WebMWriter(webMOptions);
- renderChunk();
+ },
+ renderFrame = function () {
+ graph.render(frameTime);
+
+ if (logParameters.hasSticks && parseInt(userSettings.sticks.size) > 0)
+ canvasContext.drawImage(stickCanvas, stickCanvasLeft, stickCanvasTop);
+ if (logParameters.hasCraft && parseInt(userSettings.craft.size) > 0)
+ canvasContext.drawImage(craftCanvas, craftCanvasLeft, craftCanvasTop);
+ if (
+ logParameters.hasAnalyser &&
+ parseInt(userSettings.analyser.size) > 0
+ )
+ canvasContext.drawImage(
+ analyserCanvas,
+ analyserCanvasLeft,
+ analyserCanvasTop
+ );
+
+ videoWriter.addFrame(canvas);
+
+ frameIndex++;
+ frameTime += frameDuration;
+ };
+
+ if (logParameters.flightVideo) {
+ let renderFrames = function (frameCount) {
+ if (frameCount == 0) {
+ completeChunk();
+ return;
}
- };
-
- /**
- * Get the number of bytes flushed out to the device so far.
- */
- this.getWrittenSize = function() {
- return videoWriter ? videoWriter.getWrittenSize() : 0;
- };
-
- /**
- * Returns true if the video can be saved directly to disk (bypassing memory caching). If so, the user
- * will be prompted for a filename when the start() method is called.
- */
- this.willWriteDirectToDisk = function() {
- return supportsFileWriter();
- };
-
- canvas.width = videoOptions.width;
- canvas.height = videoOptions.height;
- // If we've asked to blank the flight video completely then just don't render that
- if (videoOptions.videoDim >= 1.0) {
- delete logParameters.flightVideo;
- }
+ logParameters.flightVideo.onseeked = function () {
+ canvasContext.drawImage(
+ logParameters.flightVideo,
+ 0,
+ 0,
+ videoOptions.width,
+ videoOptions.height
+ );
- var options = $.extend({}, userSettings || {}, {eraseBackground : !logParameters.flightVideo, drawEvents : false, fillBackground : !logParameters.flightVideo});
-
- graph = new FlightLogGrapher(flightLog, logParameters.graphConfig, canvas, stickCanvas, craftCanvas, analyserCanvas, options);
+ if (videoOptions.videoDim > 0) {
+ canvasContext.fillStyle = `rgba(0,0,0,${videoOptions.videoDim})`;
- stickCanvasLeft = parseInt($(stickCanvas).css('left'), 10);
- stickCanvasTop = parseInt($(stickCanvas).css('top'), 10);
+ canvasContext.fillRect(0, 0, canvas.width, canvas.height);
+ }
- craftCanvasLeft = parseInt($(craftCanvas).css('left'), 10);
- craftCanvasTop = parseInt($(craftCanvas).css('top'), 10);
-
- analyserCanvasLeft = parseInt($(analyserCanvas).css('left'), 10);
- analyserCanvasTop = parseInt($(analyserCanvas).css('top'), 10);
+ // Render the normal graphs and add frame to video
+ renderFrame();
- if (!("inTime" in logParameters) || logParameters.inTime === false) {
- logParameters.inTime = flightLog.getMinTime();
- }
-
- if (!("outTime" in logParameters) || logParameters.outTime === false) {
- logParameters.outTime = flightLog.getMaxTime();
+ renderFrames(frameCount - 1);
+ };
+
+ logParameters.flightVideo.currentTime =
+ (frameTime - flightLog.getMinTime()) / 1000000 +
+ (logParameters.flightVideoOffset || 0);
+ };
+
+ renderFrames(framesToRender);
+ } else {
+ for (let i = 0; i < framesToRender; i++) {
+ renderFrame();
+ }
+
+ completeChunk();
}
-
- frameDuration = 1000000 / videoOptions.frameRate;
-
- // If the in -> out time is not an exact number of frames, we'll round the end time of the video to make it so:
- frameCount = Math.round((logParameters.outTime - logParameters.inTime) / frameDuration);
-
- if (logParameters.flightVideo) {
- logParameters.flightVideo.muted = true;
+ }
+
+ /**
+ * Attempt to cancel rendering sometime soon. An onComplete() event will be triggered with the 'success' parameter set
+ * appropriately to report the outcome.
+ */
+ this.cancel = function () {
+ cancel = true;
+ };
+
+ /**
+ * Begin rendering the video and return immediately.
+ */
+ this.start = function () {
+ cancel = false;
+
+ frameTime = logParameters.inTime;
+ frameIndex = 0;
+
+ installVisibilityHandler();
+
+ let webMOptions = {
+ frameRate: videoOptions.frameRate,
+ };
+
+ if (supportsFileWriter()) {
+ openFileForWrite("video.webm").then(
+ function (fileWriter) {
+ webMOptions.fileWriter = fileWriter;
+
+ videoWriter = new WebMWriter(webMOptions);
+ renderChunk();
+ },
+ function (error) {
+ console.error(error);
+ notifyCompletion(false);
+ }
+ );
+ } else {
+ videoWriter = new WebMWriter(webMOptions);
+ renderChunk();
}
+ };
+
+ /**
+ * Get the number of bytes flushed out to the device so far.
+ */
+ this.getWrittenSize = function () {
+ return videoWriter ? videoWriter.getWrittenSize() : 0;
+ };
+
+ /**
+ * Returns true if the video can be saved directly to disk (bypassing memory caching). If so, the user
+ * will be prompted for a filename when the start() method is called.
+ */
+ this.willWriteDirectToDisk = function () {
+ return supportsFileWriter();
+ };
+
+ canvas.width = videoOptions.width;
+ canvas.height = videoOptions.height;
+
+ // If we've asked to blank the flight video completely then just don't render that
+ if (videoOptions.videoDim >= 1.0) {
+ delete logParameters.flightVideo;
+ }
+
+ let options = $.extend({}, userSettings || {}, {
+ eraseBackground: !logParameters.flightVideo,
+ drawEvents: false,
+ fillBackground: !logParameters.flightVideo,
+ });
+
+ graph = new FlightLogGrapher(
+ flightLog,
+ logParameters.graphConfig,
+ canvas,
+ stickCanvas,
+ craftCanvas,
+ analyserCanvas,
+ options
+ );
+
+ stickCanvasLeft = parseInt($(stickCanvas).css("left"), 10);
+ stickCanvasTop = parseInt($(stickCanvas).css("top"), 10);
+
+ craftCanvasLeft = parseInt($(craftCanvas).css("left"), 10);
+ craftCanvasTop = parseInt($(craftCanvas).css("top"), 10);
+
+ analyserCanvasLeft = parseInt($(analyserCanvas).css("left"), 10);
+ analyserCanvasTop = parseInt($(analyserCanvas).css("top"), 10);
+
+ if (!("inTime" in logParameters) || logParameters.inTime === false) {
+ logParameters.inTime = flightLog.getMinTime();
+ }
+
+ if (!("outTime" in logParameters) || logParameters.outTime === false) {
+ logParameters.outTime = flightLog.getMaxTime();
+ }
+
+ frameDuration = 1000000 / videoOptions.frameRate;
+
+ // If the in -> out time is not an exact number of frames, we'll round the end time of the video to make it so:
+ frameCount = Math.round(
+ (logParameters.outTime - logParameters.inTime) / frameDuration
+ );
+
+ if (logParameters.flightVideo) {
+ logParameters.flightVideo.muted = true;
+ }
}
/**
* Is video rendering supported on this web browser? We require the ability to encode canvases to WebP.
*/
-FlightLogVideoRenderer.isSupported = function() {
- var
- canvas = document.createElement('canvas');
-
- canvas.width = 16;
- canvas.height = 16;
-
- var
- encoded = canvas.toDataURL('image/webp', {quality: 0.9});
-
- return encoded && encoded.match(/^data:image\/webp;/);
+FlightLogVideoRenderer.isSupported = function () {
+ let canvas = document.createElement("canvas");
+
+ canvas.width = 16;
+ canvas.height = 16;
+
+ let encoded = canvas.toDataURL("image/webp", { quality: 0.9 });
+
+ return encoded && encoded.match(/^data:image\/webp;/);
};
diff --git a/src/gps_transform.js b/src/gps_transform.js
new file mode 100644
index 00000000..f9feaef3
--- /dev/null
+++ b/src/gps_transform.js
@@ -0,0 +1,67 @@
+export function GPS_transform(Lat0, Lon0, H0, Heading) {
+
+ function deg2rad(deg) {
+ return deg * Math.PI / 180.0;
+ }
+
+ Lat0 = deg2rad(Lat0);
+ Lon0 = deg2rad(Lon0);
+ const Semimajor = 6378137.0,
+ Flat = 1.0 / 298.257223563,
+ Ecc_2 = Flat * (2 - Flat),
+ SinB = Math.sin(Lat0),
+ CosB = Math.cos(Lat0),
+ SinL = Math.sin(Lon0),
+ CosL = Math.cos(Lon0),
+ N = Semimajor / Math.sqrt(1.0 - Ecc_2 * SinB * SinB),
+
+ a11 = -SinB * CosL,
+ a12 = -SinB * SinL,
+ a13 = CosB,
+ a21 = -SinL,
+ a22 = CosL,
+ a23 = 0,
+ a31 = CosL * CosB,
+ a32 = CosB * SinL,
+ a33 = SinB,
+
+ X0 = (N + H0) * CosB * CosL,
+ Y0 = (N + H0) * CosB * SinL,
+ Z0 = (N + H0 - Ecc_2 * N) * SinB,
+ c11 = Math.cos( deg2rad(Heading) ),
+ c12 = Math.sin( deg2rad(Heading) ),
+ c21 = -c12,
+ c22 = c11;
+
+ this.WGS_ECEF = function (Lat, Lon, H) {
+ Lat = deg2rad(Lat);
+ Lon = deg2rad(Lon);
+ const
+ SinB = Math.sin(Lat),
+ CosB = Math.cos(Lat),
+ SinL = Math.sin(Lon),
+ CosL = Math.cos(Lon),
+ N = Semimajor / Math.sqrt(1 - Ecc_2 * SinB * SinB);
+
+ return {
+ x: (N + H) * CosB * CosL,
+ y: (N + H) * CosB * SinL,
+ z: (N + H - Ecc_2 * N) * SinB,
+ };
+ };
+
+ this.ECEF_BS = function (pos) {
+ const PosX1= a11 * (pos.x - X0) + a12 * (pos.y - Y0) + a13 * (pos.z - Z0);
+ const PosZ1= a21 * (pos.x - X0) + a22 * (pos.y - Y0) + a23 * (pos.z - Z0);
+
+ return {
+ x: c11 * PosX1 + c12 * PosZ1,
+ y: a31 * (pos.x - X0) + a32 * (pos.y - Y0) + a33 * (pos.z - Z0),
+ z: c21 * PosX1 + c22 * PosZ1,
+ };
+ };
+
+ this.WGS_BS = function (Lat, Lon, H) {
+ return this.ECEF_BS(this.WGS_ECEF(Lat, Lon, H));
+ };
+}
diff --git a/src/gpx-exporter.js b/src/gpx-exporter.js
index eaafe8cf..14a1025a 100644
--- a/src/gpx-exporter.js
+++ b/src/gpx-exporter.js
@@ -1,30 +1,35 @@
/**
* @constructor
- * @param {FlightLog} flightLog
+ * @param {FlightLog} flightLog
*/
export function GpxExporter(flightLog) {
+ /**
+ * @param {function} success is a callback triggered when export is done
+ */
+ function dump(success) {
+ let frames = _(
+ flightLog.getChunksInTimeRange(
+ flightLog.getMinTime(),
+ flightLog.getMaxTime()
+ )
+ )
+ .map((chunk) => chunk.frames)
+ .value(),
+ worker = new Worker("/js/webworkers/gpx-export-worker.js");
- /**
- * @param {function} success is a callback triggered when export is done
- */
- function dump(success) {
- let frames = _(flightLog.getChunksInTimeRange(flightLog.getMinTime(), flightLog.getMaxTime()))
- .map(chunk => chunk.frames).value(),
- worker = new Worker("/js/webworkers/gpx-export-worker.js");
-
- worker.onmessage = event => {
- success(event.data);
- worker.terminate();
- };
- worker.postMessage({
- sysConfig: flightLog.getSysConfig(),
- fieldNames: flightLog.getMainFieldNames(),
- frames: frames,
- });
- }
-
- // exposed functions
- return {
- dump: dump,
+ worker.onmessage = (event) => {
+ success(event.data);
+ worker.terminate();
};
-};
+ worker.postMessage({
+ sysConfig: flightLog.getSysConfig(),
+ fieldNames: flightLog.getMainFieldNames(),
+ frames: frames,
+ });
+ }
+
+ // exposed functions
+ return {
+ dump: dump,
+ };
+}
diff --git a/src/graph_config.js b/src/graph_config.js
index cdf92759..2b9b71a9 100644
--- a/src/graph_config.js
+++ b/src/graph_config.js
@@ -1,1271 +1,1607 @@
import { FlightLogFieldPresenter } from "./flightlog_fields_presenter";
-import { DSHOT_MIN_VALUE, DSHOT_RANGE, RATES_TYPE, DEBUG_MODE } from "./flightlog_fielddefs";
+import {
+ DSHOT_MIN_VALUE,
+ DSHOT_RANGE,
+ RATES_TYPE,
+ DEBUG_MODE,
+} from "./flightlog_fielddefs";
import { escapeRegExp } from "./tools";
export function GraphConfig(graphConfig) {
- var
- graphs = graphConfig ? graphConfig : [],
- listeners = [],
- that = this;
+ const listeners = [];
+ const that = this;
+ let graphs = graphConfig ?? [];
- function notifyListeners() {
- for (var i = 0; i < listeners.length; i++) {
- listeners[i](that);
- }
+ function notifyListeners() {
+ for (const listener of listeners) {
+ listener(that);
}
+ }
- this.selectedFieldName = null;
- this.selectedGraphIndex = 0;
- this.selectedFieldIndex = 0;
-
- this.highlightGraphIndex = null;
- this.highlightFieldIndex = null;
+ this.selectedFieldName = null;
+ this.selectedGraphIndex = 0;
+ this.selectedFieldIndex = 0;
- const hiddenGraphFields = new Set();
+ this.highlightGraphIndex = null;
+ this.highlightFieldIndex = null;
- this.getGraphs = function() {
- return graphs;
- };
+ const hiddenGraphFields = new Set();
- /**
- * newGraphs is an array of objects like {label: "graph label", height:, fields:[{name: curve:{offset:, power:, inputRange:, outputRange:, steps:}, color:, }, ...]}
- */
- this.setGraphs = function(newGraphs) {
- graphs = newGraphs;
+ this.getGraphs = function () {
+ return graphs;
+ };
- hiddenGraphFields.clear();
+ let redrawChart = true;
+ this.setRedrawChart = function (isRedraw) {
+ redrawChart = isRedraw;
+ };
- notifyListeners();
- };
+ /**
+ * newGraphs is an array of objects like {label: "graph label", height:, fields:[{name: curve:{power:, MinMax:, steps:}, color:, }, ...]}
+ */
+ this.setGraphs = function (newGraphs) {
+ graphs = newGraphs;
- /**
- * Convert the given graph configs to make them appropriate for the given flight log.
- */
- this.adaptGraphs = function(flightLog, graphs) {
- var
- logFieldNames = flightLog.getMainFieldNames(),
-
- // Make copies of graphs into here so we can modify them without wrecking caller's copy
- newGraphs = [];
+ hiddenGraphFields.clear();
+ if (redrawChart) {
+ notifyListeners();
+ }
+ };
- for (var i = 0; i < graphs.length; i++) {
- var
- graph = graphs[i],
- newGraph = $.extend(
- // Default values for missing properties:
- {
- height: 1,
- },
- // The old graph
- graph,
- // New fields to replace the old ones:
- {
- fields:[],
- },
+ this.extendFields = function (flightLog, field) {
+ const matches = field.name.match(/^(.+)\[all\]$/);
+ const logFieldNames = flightLog.getMainFieldNames();
+ const fields = [];
+ const setupColor = field?.color == -1;
+ if (matches) {
+ const nameRoot = matches[1],
+ nameRegex = new RegExp(`^${escapeRegExp(nameRoot)}\[[0-9]+\]$`);
+ let colorIndex = 0;
+ for (const fieldName of logFieldNames) {
+ if (fieldName.match(nameRegex)) {
+ // forceNewCurve must be true for min max computing extended curves.
+ const forceNewCurve = true;
+ const color =
+ GraphConfig.PALETTE[colorIndex++ % GraphConfig.PALETTE.length]
+ .color;
+ field.color = setupColor ? color : undefined;
+ fields.push(
+ adaptField(
+ flightLog,
+ $.extend({}, field, {
+ curve: $.extend({}, field.curve),
+ name: fieldName,
+ friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(
+ fieldName,
+ flightLog.getSysConfig().debug_mode
),
- colorIndex = 0;
-
- for (var j = 0; j < graph.fields.length; j++) {
- var
- field = graph.fields[j],
- matches;
-
- var adaptField = function(field, colorIndexOffset, forceNewCurve) {
- const defaultCurve = GraphConfig.getDefaultCurveForField(flightLog, field.name);
-
- if (field.curve === undefined || forceNewCurve) {
- field.curve = defaultCurve;
- } else {
- /* The curve may have been originally created for a craft with different endpoints, so use the
- * recommended offset and input range instead of the provided one.
- */
- field.curve.offset = defaultCurve.offset;
- field.curve.inputRange = defaultCurve.inputRange;
- }
+ }),
+ forceNewCurve
+ )
+ );
+ }
+ }
+ } else {
+ // Don't add fields if they don't exist in this log
+ if (flightLog.getMainFieldIndexByName(field.name) !== undefined) {
+ fields.push(
+ adaptField(
+ flightLog,
+ $.extend({}, field, {
+ curve: $.extend({}, field.curve),
+ friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(
+ field.name,
+ flightLog.getSysConfig().debug_mode
+ ),
+ })
+ )
+ );
+ }
+ }
+ return fields;
+ };
- if(colorIndexOffset!=null && field.color != undefined) { // auto offset the actual color (to expand [all] selections)
- var index;
- for(index=0; index < GraphConfig.PALETTE.length; index++)
- {
- if(GraphConfig.PALETTE[index].color == field.color) break;
- }
- field.color = GraphConfig.PALETTE[(index + colorIndexOffset) % GraphConfig.PALETTE.length].color
- }
+ let adaptField = function (flightLog, field, forceNewCurve) {
+ const defaultCurve = GraphConfig.getDefaultCurveForField(
+ flightLog,
+ field.name
+ );
+ if (field.curve === undefined || forceNewCurve) {
+ field.curve = defaultCurve;
+ } else {
+ if (field.curve.MinMax == undefined)
+ field.curve.MinMax = defaultCurve.MinMax;
+ }
- if (field.color === undefined) {
- field.color = GraphConfig.PALETTE[colorIndex % GraphConfig.PALETTE.length].color;
- colorIndex++;
- }
+ if (field.smoothing === undefined) {
+ field.smoothing = GraphConfig.getDefaultSmoothingForField(
+ flightLog,
+ field.name
+ );
+ }
- if (field.smoothing === undefined) {
- field.smoothing = GraphConfig.getDefaultSmoothingForField(flightLog, field.name);
- }
+ return field;
+ };
- return field;
- };
+ /**
+ * Convert the given graph configs to make them appropriate for the given flight log.
+ */
+ this.adaptGraphs = function (flightLog, graphs) {
+ const // Make copies of graphs into here so we can modify them without wrecking caller's copy
+ newGraphs = [];
- if ((matches = field.name.match(/^(.+)\[all\]$/))) {
- var
- nameRoot = matches[1],
- nameRegex = new RegExp("^" + escapeRegExp(nameRoot) + "\[[0-9]+\]$"),
- colorIndexOffset = 0;
+ for (const graph of graphs) {
+ const newGraph = $.extend(
+ // Default values for missing properties:
+ {
+ height: 1,
+ },
+ // The old graph
+ graph,
+ // New fields to replace the old ones:
+ {
+ fields: [],
+ }
+ );
- for (var k = 0; k < logFieldNames.length; k++) {
- if (logFieldNames[k].match(nameRegex)) {
- // add special condition for rcCommands and debug as each of the fields requires a different scaling.
- let forceNewCurve = (nameRoot=='rcCommand') || (nameRoot=='rcCommands') || (nameRoot=='debug');
- newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), name: logFieldNames[k], friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(logFieldNames[k], flightLog.getSysConfig().debug_mode)}), colorIndexOffset, forceNewCurve));
- colorIndexOffset++;
- }
- }
- } else {
- // Don't add fields if they don't exist in this log
- if (flightLog.getMainFieldIndexByName(field.name) !== undefined) {
- newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(field.name, flightLog.getSysConfig().debug_mode)})));
- }
- }
- }
+ for (const field of graph.fields) {
+ const fields = this.extendFields(flightLog, field);
+ newGraph.fields = newGraph.fields.concat(fields);
+ }
- newGraphs.push(newGraph);
- }
+ newGraphs.push(newGraph);
+ }
- this.setGraphs(newGraphs);
- };
+ this.setGraphs(newGraphs);
+ };
- this.addListener = function(listener) {
- listeners.push(listener);
- };
+ this.addListener = function (listener) {
+ listeners.push(listener);
+ };
- this.toggleGraphField = (graphIndex, fieldIndex) => {
- const item = graphIndex + ":" + fieldIndex;
- if (hiddenGraphFields.has(item)) {
- hiddenGraphFields.delete(item);
- } else {
- hiddenGraphFields.add(item);
- }
- };
+ this.toggleGraphField = (graphIndex, fieldIndex) => {
+ const item = `${graphIndex}:${fieldIndex}`;
+ if (hiddenGraphFields.has(item)) {
+ hiddenGraphFields.delete(item);
+ } else {
+ hiddenGraphFields.add(item);
+ }
+ };
- this.isGraphFieldHidden = (graphIndex, fieldIndex) => {
- return hiddenGraphFields.has(graphIndex + ":" + fieldIndex);
- };
+ this.isGraphFieldHidden = (graphIndex, fieldIndex) => {
+ return hiddenGraphFields.has(`${graphIndex}:${fieldIndex}`);
+ };
}
GraphConfig.PALETTE = [
- {color: "#fb8072", name: "Red" },
- {color: "#8dd3c7", name: "Cyan" },
- {color: "#ffffb3", name: "Yellow" },
- {color: "#bebada", name: "Purple" },
- {color: "#80b1d3", name: "Blue" },
- {color: "#fdb462", name: "Orange" },
- {color: "#b3de69", name: "Green" },
- {color: "#fccde5", name: "Pink" },
- {color: "#d9d9d9", name: "Grey" },
- {color: "#bc80bd", name: "Dark Purple" },
- {color: "#ccebc5", name: "Light Green" },
- {color: "#ffed6f", name: "Dark Yellow" }
+ { color: "#fb8072", name: "Red" },
+ { color: "#8dd3c7", name: "Cyan" },
+ { color: "#ffffb3", name: "Yellow" },
+ { color: "#bebada", name: "Purple" },
+ { color: "#80b1d3", name: "Blue" },
+ { color: "#fdb462", name: "Orange" },
+ { color: "#b3de69", name: "Green" },
+ { color: "#fccde5", name: "Pink" },
+ { color: "#d9d9d9", name: "Grey" },
+ { color: "#bc80bd", name: "Dark Purple" },
+ { color: "#ccebc5", name: "Light Green" },
+ { color: "#ffed6f", name: "Dark Yellow" },
];
-
-GraphConfig.load = function(config) {
- // Upgrade legacy configs to suit the newer standard by translating field names
- if (config) {
- for (var i = 0; i < config.length; i++) {
- var graph = config[i];
-
- for (var j = 0; j < graph.fields.length; j++) {
- var
- field = graph.fields[j],
- matches;
-
- if ((matches = field.name.match(/^gyroData(.+)$/))) {
- field.name = "gyroADC" + matches[1];
- }
- }
+GraphConfig.load = function (config) {
+ // Upgrade legacy configs to suit the newer standard by translating field names
+ if (config) {
+ for (const graph of config) {
+ for (const field of graph.fields) {
+ const matches = field.name.match(/^gyroData(.+)$/);
+ if (matches) {
+ field.name = `gyroADC${matches[1]}`;
}
- } else {
- config = false;
+ }
}
+ } else {
+ config = false;
+ }
- return config;
+ return config;
};
-GraphConfig.getDefaultSmoothingForField = function(flightLog, fieldName) {
- try{
- if (fieldName.match(/^motor(Raw)?\[/)) {
- return 5000;
- } else if (fieldName.match(/^servo\[/)) {
- return 5000;
- } else if (fieldName.match(/^gyroADC.*\[/)) {
- return 3000;
- } else if (fieldName.match(/^gyroUnfilt.*\[/)) {
- return 3000;
- } else if (fieldName.match(/^accSmooth\[/)) {
- return 3000;
- } else if (fieldName.match(/^axis.+\[/)) {
- return 3000;
- } else {
- return 0;
- }
- } catch (e) { return 0;}
+GraphConfig.getDefaultSmoothingForField = function (flightLog, fieldName) {
+ try {
+ if (fieldName.match(/^motor(Raw)?\[/)) {
+ return 5000;
+ } else if (fieldName.match(/^servo\[/)) {
+ return 5000;
+ } else if (fieldName.match(/^gyroADC.*\[/)) {
+ return 3000;
+ } else if (fieldName.match(/^gyroUnfilt.*\[/)) {
+ return 3000;
+ } else if (fieldName.match(/^accSmooth\[/)) {
+ return 3000;
+ } else if (fieldName.match(/^axis.+\[/)) {
+ return 3000;
+ } else {
+ return 0;
+ }
+ } catch (e) {
+ return 0;
+ }
};
-GraphConfig.getDefaultCurveForField = function(flightLog, fieldName) {
- var
- sysConfig = flightLog.getSysConfig();
+GraphConfig.getDefaultCurveForField = function (flightLog, fieldName) {
+ const sysConfig = flightLog.getSysConfig();
- var maxDegreesSecond = function(scale) {
- switch(sysConfig["rates_type"]){
- case RATES_TYPE.indexOf('ACTUAL'):
- case RATES_TYPE.indexOf('QUICK'):
- return Math.max(sysConfig["rates"][0] * 10.0 * scale,
- sysConfig["rates"][1] * 10.0 * scale,
- sysConfig["rates"][2] * 10.0 * scale);
- default:
- return Math.max(flightLog.rcCommandRawToDegreesPerSecond(500,0) * scale,
- flightLog.rcCommandRawToDegreesPerSecond(500,1) * scale,
- flightLog.rcCommandRawToDegreesPerSecond(500,2) * scale);
- }
+ let maxDegreesSecond = function (scale) {
+ switch (sysConfig["rates_type"]) {
+ case RATES_TYPE.indexOf("ACTUAL"):
+ case RATES_TYPE.indexOf("QUICK"):
+ return Math.max(
+ sysConfig["rates"][0] * 10.0 * scale,
+ sysConfig["rates"][1] * 10.0 * scale,
+ sysConfig["rates"][2] * 10.0 * scale
+ );
+ default:
+ return Math.max(
+ flightLog.rcCommandRawToDegreesPerSecond(500, 0) * scale,
+ flightLog.rcCommandRawToDegreesPerSecond(500, 1) * scale,
+ flightLog.rcCommandRawToDegreesPerSecond(500, 2) * scale
+ );
}
+ };
- var getMinMaxForFields = function(/* fieldName1, fieldName2, ... */) {
- // helper to make a curve scale based on the combined min/max of one or more fields
- var
- stats = flightLog.getStats(),
- min = Number.MAX_VALUE,
- max = Number.MIN_VALUE;
-
- for(var i in arguments) {
- var
- fieldIndex = flightLog.getMainFieldIndexByName(arguments[i]),
- fieldStat = fieldIndex !== undefined ? stats.field[fieldIndex] : false;
-
- if (fieldStat) {
- min = Math.min(min, fieldStat.min);
- max = Math.max(max, fieldStat.max);
- }
- }
-
- if (min != Number.MAX_VALUE && max != Number.MIN_VALUE) {
- return {min:min, max:max};
- }
+ let getMinMaxForFields = function (/* fieldName1, fieldName2, ... */) {
+ // helper to make a curve scale based on the combined min/max of one or more fields
+ let min = Number.MAX_VALUE,
+ max = -Number.MAX_VALUE;
- return {min:-500, max:500};
+ for (const argument of arguments) {
+ const mm = flightLog.getMinMaxForFieldDuringAllTime(argument);
+ min = Math.min(mm.min, min);
+ max = Math.max(mm.max, max);
}
- var getCurveForMinMaxFields = function(/* fieldName1, fieldName2, ... */) {
- var mm = getMinMaxForFields.apply(null, arguments);
-
- return {
- offset: -(mm.max + mm.min) / 2,
- power: 1.0,
- inputRange: Math.max((mm.max - mm.min) / 2, 1.0),
- outputRange: 1.0
- };
+ if (min != Number.MAX_VALUE && max != -Number.MAX_VALUE) {
+ return { min: min, max: max };
}
- var getCurveForMinMaxFieldsZeroOffset = function(/* fieldName1, fieldName2, ... */) {
- var mm = getMinMaxForFields.apply(null, arguments);
+ return { min: -500, max: 500 };
+ };
- return {
- offset: 0,
- power: 1.0,
- inputRange: Math.max(Math.max(Math.abs(mm.max), Math.abs(mm.min)), 1.0),
- outputRange: 1.0
- };
- }
+ let getCurveForMinMaxFields = function (/* fieldName1, fieldName2, ... */) {
+ const mm = getMinMaxForFields.apply(null, arguments);
+ // added convertation min max values from log file units to friendly chart
+ const mmChartUnits = {
+ min: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ ),
+ max: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ ),
+ };
+ return {
+ power: 1.0,
+ MinMax: mmChartUnits,
+ };
+ };
- const gyroScaleMargin = 1.20; // Give a 20% margin for gyro graphs
- const highResolutionScale = sysConfig.blackbox_high_resolution > 0 ? 10 : 1;
+ let getCurveForMinMaxFieldsZeroOffset =
+ function (/* fieldName1, fieldName2, ... */) {
+ const mm = getMinMaxForFields.apply(null, arguments);
+ // added convertation min max values from log file units to friendly chart
+ const mmChartUnits = {
+ min: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ ),
+ max: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ ),
+ };
+ mmChartUnits.max = Math.max(
+ Math.max(Math.abs(mmChartUnits.max), Math.abs(mmChartUnits.min)),
+ 1.0
+ );
+ mmChartUnits.min = -mmChartUnits.max;
+ return {
+ power: 1.0,
+ MinMax: mmChartUnits,
+ };
+ };
- try {
- if (fieldName.match(/^motor\[/)) {
- return {
- offset: flightLog.isDigitalProtocol() ?
- -(DSHOT_MIN_VALUE + DSHOT_RANGE / 2) : -(sysConfig.minthrottle + (sysConfig.maxthrottle - sysConfig.minthrottle) / 2),
+ const gyroScaleMargin = 1.2; // Give a 20% margin for gyro graphs
+ const highResolutionScale = sysConfig.blackbox_high_resolution > 0 ? 10 : 1;
+ const mm = getMinMaxForFields(fieldName);
+ // added convertation min max values from log file units to friendly chart
+ const mmChartUnits = {
+ min: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ ),
+ max: FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ ),
+ };
+ try {
+ if (fieldName.match(/^motor\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ } else if (fieldName.match(/^eRPM\[/)) {
+ return getCurveForMinMaxFields(
+ "eRPM[0]",
+ "eRPM[1]",
+ "eRPM[2]",
+ "eRPM[3]",
+ "eRPM[4]",
+ "eRPM[5]",
+ "eRPM[6]",
+ "eRPM[7]"
+ );
+ } else if (fieldName.match(/^servo\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 1000,
+ max: 2000,
+ },
+ };
+ } else if (fieldName.match(/^accSmooth\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -16,
+ max: 16,
+ },
+ };
+ } else if (fieldName == "rcCommands[3]") {
+ // Throttle scaled
+ return {
+ power: 1.0 /* Make this 1.0 to scale linearly */,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ } else if (
+ fieldName.match(/^axisError\[/) || // Gyro, Gyro Scaled, RC Command Scaled and axisError
+ fieldName.match(/^rcCommands\[/) || // These use the same scaling as they are in the
+ fieldName.match(/^gyroADC\[/) || // same range.
+ fieldName.match(/^gyroUnfilt\[/)
+ ) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ } else if (fieldName.match(/^axis.+\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ } else if (fieldName == "rcCommand[3]") {
+ // Throttle
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 1000,
+ max: 2000,
+ },
+ };
+ } else if (fieldName.match(/^rcCommand\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 1000,
+ max: 2000,
+ },
+ };
+ } else if (fieldName == "heading[2]") {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 360,
+ },
+ };
+ } else if (fieldName.match(/^heading\[/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -180,
+ max: 180,
+ },
+ };
+ } else if (fieldName.match(/^sonar.*/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 400,
+ },
+ };
+ } else if (fieldName.match(/^rssi.*/)) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ } else if (fieldName == "GPS_ground_course") {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 360,
+ },
+ };
+ } else if (fieldName == "GPS_numSat") {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 40,
+ },
+ };
+ } else if (fieldName == "GPS_speed") {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ } else if (fieldName == "gpsHomeAzimuth") {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 360,
+ },
+ };
+ } else if (fieldName.match(/^debug.*/) && sysConfig.debug_mode != null) {
+ const debugModeName = DEBUG_MODE[sysConfig.debug_mode];
+ switch (debugModeName) {
+ case "CYCLETIME":
+ switch (fieldName) {
+ case "debug[1]": //CPU Load
+ return {
+ power: 1,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ default:
+ return {
power: 1.0,
- inputRange: flightLog.isDigitalProtocol() ?
- DSHOT_RANGE / 2 : (sysConfig.maxthrottle - sysConfig.minthrottle) / 2,
- outputRange: 1.0,
- };
- } else if (fieldName.match(/^eRPM\[/)) {
- return getCurveForMinMaxFields('eRPM[0]', 'eRPM[1]', 'eRPM[2]', 'eRPM[3]', 'eRPM[4]', 'eRPM[5]', 'eRPM[6]', 'eRPM[7]');
- } else if (fieldName.match(/^servo\[/)) {
- return {
- offset: -1500,
+ MinMax: {
+ min: 0,
+ max: 2000,
+ },
+ };
+ }
+ case "PIDLOOP":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 500,
+ },
+ };
+ case "GYRO":
+ case "GYRO_FILTERED":
+ case "GYRO_SCALED":
+ case "DUAL_GYRO":
+ case "DUAL_GYRO_COMBINED":
+ case "DUAL_GYRO_DIFF":
+ case "DUAL_GYRO_RAW":
+ case "DUAL_GYRO_SCALED":
+ case "NOTCH":
+ case "AC_CORRECTION":
+ case "AC_ERROR":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ case "ACCELEROMETER":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -16,
+ max: 16,
+ },
+ };
+ case "MIXER":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ case "BATTERY":
+ switch (fieldName) {
+ case "debug[0]": //Raw Value (0-4095)
+ return {
power: 1.0,
- inputRange: 500,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^accSmooth\[/)) {
- return {
- offset: 0,
- power: 0.5,
- inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */
- outputRange: 1.0
- };
- } else if (fieldName == "rcCommands[3]") { // Throttle scaled
- return {
- offset: -50,
- power: 1.0, /* Make this 1.0 to scale linearly */
- inputRange: 50,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^axisError\[/) || // Gyro, Gyro Scaled, RC Command Scaled and axisError
- fieldName.match(/^rcCommands\[/) || // These use the same scaling as they are in the
- fieldName.match(/^gyroADC\[/) || // same range.
- fieldName.match(/^gyroUnfilt\[/)) {
- return {
- offset: 0,
- power: 0.25, /* Make this 1.0 to scale linearly */
- inputRange: maxDegreesSecond(gyroScaleMargin * highResolutionScale), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- } else if (fieldName.match(/^axis.+\[/)) {
- return {
- offset: 0,
- power: 0.3,
- inputRange: 1000, // Was 400 ?
- outputRange: 1.0
- };
- } else if (fieldName == "rcCommand[3]") { // Throttle
- return {
- offset: -1500 * highResolutionScale,
+ MinMax: {
+ min: 0,
+ max: 4096,
+ },
+ };
+ default:
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 26,
+ },
+ };
+ }
+ case "RC_INTERPOLATION":
+ switch (fieldName) {
+ case "debug[0]": // Roll RC Command
+ case "debug[3]": // refresh period
+ return getCurveForMinMaxFieldsZeroOffset(fieldName);
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RC_SMOOTHING":
+ switch (fieldName) {
+ case "debug[0]": // raw RC command
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 1000,
+ max: 2000 * gyroScaleMargin,
+ },
+ };
+ case "debug[1]": // raw RC command derivative
+ case "debug[2]": // smoothed RC command derivative
+ return getCurveForMinMaxFieldsZeroOffset("debug[1]", "debug[2]");
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RC_SMOOTHING_RATE":
+ switch (fieldName) {
+ case "debug[0]": // current frame rate [us]
+ case "debug[2]": // average frame rate [us]
+ return getCurveForMinMaxFields("debug[0]", "debug[2]");
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "ANGLERATE":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ case "ALTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // GPS Trust
+ return {
power: 1.0,
- inputRange: 500 * highResolutionScale,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^rcCommand\[/)) {
- return {
- offset: 0,
- power: 0.25,
- inputRange: 500 * highResolutionScale * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands
- outputRange: 1.0
- };
- } else if (fieldName == "heading[2]") {
- return {
- offset: -Math.PI,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ case "debug[1]": // Baro Alt
+ case "debug[2]": // GPS Alt
+ return {
power: 1.0,
- inputRange: Math.PI,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^heading\[/)) {
- return {
- offset: 0,
+ MinMax: {
+ min: -50,
+ max: 50,
+ },
+ };
+ case "debug[3]": // vario
+ return {
power: 1.0,
- inputRange: Math.PI,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^sonar.*/)) {
- return {
- offset: -200,
+ MinMax: {
+ min: -5,
+ max: 5,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FFT":
+ switch (fieldName) {
+ case "debug[0]": // pre-dyn notch gyro [for gyro debug axis]
+ case "debug[1]": // post-dyn notch gyro [for gyro debug axis]
+ case "debug[2]": // pre-dyn notch gyro downsampled for FFT [for gyro debug axis]
+ return {
power: 1.0,
- inputRange: 200,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^rssi.*/)) {
- return {
- offset: -512,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FFT_FREQ":
+ switch (fieldName) {
+ case "debug[0]": // notch 1 center freq [for gyro debug axis]
+ case "debug[1]": // notch 2 center freq [for gyro debug axis]
+ case "debug[2]": // notch 3 center freq [for gyro debug axis]
+ return getCurveForMinMaxFields(
+ "debug[0]",
+ "debug[1]",
+ "debug[2]"
+ );
+ case "debug[3]": // pre-dyn notch gyro [for gyro debug axis]
+ return {
power: 1.0,
- inputRange: 512,
- outputRange: 1.0
- };
- } else if (fieldName == 'GPS_ground_course') {
- return {
- offset: -1800,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "DYN_LPF":
+ switch (fieldName) {
+ case "debug[1]": // Notch center
+ case "debug[2]": // Lowpass Cutoff
+ return getCurveForMinMaxFields("debug[1]", "debug[2]");
+ case "debug[0]": // gyro scaled [for selected axis]
+ case "debug[3]": // pre-dyn notch gyro [for selected axis]
+ return {
power: 1.0,
- inputRange: 1800,
- outputRange: 1.0
- };
- } else if (fieldName == 'GPS_numSat') {
- return {
- offset: -20,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FFT_TIME":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ case "ESC_SENSOR_RPM":
+ case "DSHOT_RPM_TELEMETRY":
+ case "RPM_FILTER":
+ return getCurveForMinMaxFields(
+ "debug[0]",
+ "debug[1]",
+ "debug[2]",
+ "debug[3]"
+ );
+ case "D_MAX":
+ switch (fieldName) {
+ case "debug[0]": // roll gyro factor
+ case "debug[1]": // roll setpoint Factor
+ return getCurveForMinMaxFields("debug[0]", "debug[1]");
+ case "debug[2]": // roll actual D
+ case "debug[3]": // pitch actual D
+ return getCurveForMinMaxFields("debug[2]", "debug[3]");
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "ITERM_RELAX":
+ switch (fieldName) {
+ case "debug[2]": // roll I relaxed error
+ case "debug[3]": // roll absolute control axis error
+ return getCurveForMinMaxFieldsZeroOffset(fieldName);
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FF_INTERPOLATED":
+ switch (fieldName) {
+ case "debug[0]": // setpoint Delta
+ case "debug[1]": // AccelerationModified
+ case "debug[2]": // Acceleration
+ return {
power: 1.0,
- inputRange: 20,
- outputRange: 1.0
- };
- } else if (fieldName == 'GPS_speed') {
- return {
- offset: 0,
+ MinMax: {
+ min: -1000,
+ max: 1000,
+ },
+ };
+ case "debug[3]": // Clip or Count
+ return {
power: 1.0,
- inputRange: 1000,
- outputRange: 1.0
- };
- } else if (fieldName.match(/^debug.*/) && sysConfig.debug_mode!=null) {
-
- var debugModeName = DEBUG_MODE[sysConfig.debug_mode];
- switch (debugModeName) {
- case 'CYCLETIME':
- switch (fieldName) {
- case 'debug[1]': //CPU Load
- return {
- offset: -50,
- power: 1,
- inputRange: 50,
- outputRange: 1.0
- };
- default:
- return {
- offset: -1000, // zero offset
- power: 1.0,
- inputRange: 1000, // 0-2000uS
- outputRange: 1.0
- };
- }
- case 'PIDLOOP':
- return {
- offset: -250, // zero offset
- power: 1.0,
- inputRange: 250, // 0-500uS
- outputRange: 1.0
- };
- case 'GYRO':
- case 'GYRO_FILTERED':
- case 'GYRO_SCALED':
- case 'DUAL_GYRO':
- case 'DUAL_GYRO_COMBINED':
- case 'DUAL_GYRO_DIFF':
- case 'DUAL_GYRO_RAW':
- case 'DUAL_GYRO_SCALED':
- case 'NOTCH':
- case 'AC_CORRECTION':
- case 'AC_ERROR':
- return {
- offset: 0,
- power: 0.25,
- inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- case 'ACCELEROMETER':
- return {
- offset: 0,
- power: 0.5,
- inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */
- outputRange: 1.0
- };
- case 'MIXER':
- return {
- offset: -(sysConfig.motorOutput[1] + sysConfig.motorOutput[0]) / 2,
- power: 1.0,
- inputRange: (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]) / 2,
- outputRange: 1.0
- };
- case 'BATTERY':
- switch (fieldName) {
- case 'debug[0]': //Raw Value (0-4095)
- return {
- offset: -2048,
- power: 1,
- inputRange: 2048,
- outputRange: 1.0
- };
- default:
- return {
- offset: -130,
- power: 1.0,
- inputRange: 130, // 0-26.0v
- outputRange: 1.0
- };
- }
- case 'RC_INTERPOLATION':
- switch (fieldName) {
- case 'debug[0]': // Roll RC Command
- case 'debug[3]': // refresh period
- return getCurveForMinMaxFieldsZeroOffset(fieldName);
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RC_SMOOTHING':
- switch (fieldName) {
- case 'debug[0]': // raw RC command
- return {
- offset: 0,
- power: 0.25,
- inputRange: 500 * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands
- outputRange: 1.0
- };
- case 'debug[1]': // raw RC command derivative
- case 'debug[2]': // smoothed RC command derivative
- return getCurveForMinMaxFieldsZeroOffset('debug[1]', 'debug[2]');
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RC_SMOOTHING_RATE':
- switch (fieldName) {
- case 'debug[0]': // current frame rate [us]
- case 'debug[2]': // average frame rate [us]
- return getCurveForMinMaxFields('debug[0]', 'debug[2]');
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'ANGLERATE':
- return {
- offset: 0,
- power: 0.25, /* Make this 1.0 to scale linearly */
- inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- case 'ALTITUDE':
- switch (fieldName) {
- case 'debug[0]': // GPS Trust
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- case 'debug[1]': // Baro Alt
- case 'debug[2]': // GPS Alt
- return {
- offset: 0,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- case 'debug[3]': // Vario
- return {
- offset: 0,
- power: 1.0,
- inputRange: 500,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FFT':
- switch (fieldName) {
- case 'debug[0]': // pre-dyn notch gyro [for gyro debug axis]
- case 'debug[1]': // post-dyn notch gyro [for gyro debug axis]
- case 'debug[2]': // pre-dyn notch gyro downsampled for FFT [for gyro debug axis]
- return {
- offset: 0,
- power: 1.0,
- inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FFT_FREQ':
- switch (fieldName) {
- case 'debug[0]': // notch 1 center freq [for gyro debug axis]
- case 'debug[1]': // notch 2 center freq [for gyro debug axis]
- case 'debug[2]': // notch 3 center freq [for gyro debug axis]
- return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]');
- case 'debug[3]': // pre-dyn notch gyro [for gyro debug axis]
- return {
- offset: 0,
- power: 1.0,
- inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'DYN_LPF':
- switch (fieldName) {
- case 'debug[1]': // Notch center
- case 'debug[2]': // Lowpass Cutoff
- return getCurveForMinMaxFields('debug[1]', 'debug[2]');
- case 'debug[0]': // gyro scaled [for selected axis]
- case 'debug[3]': // pre-dyn notch gyro [for selected axis]
- return {
- offset: 0,
- power: 0.25,
- inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FFT_TIME':
- return {
- offset: 0,
- power: 1.0,
- inputRange: 100,
- outputRange: 1.0
- };
- case 'ESC_SENSOR_RPM':
- case 'DSHOT_RPM_TELEMETRY':
- case 'RPM_FILTER':
- return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]', 'debug[3]');
- case 'D_MIN':
- switch (fieldName) {
- case 'debug[0]': // roll gyro factor
- case 'debug[1]': // roll setpoint Factor
- return getCurveForMinMaxFields('debug[0]', 'debug[1]');
- case 'debug[2]': // roll actual D
- case 'debug[3]': // pitch actual D
- return getCurveForMinMaxFields('debug[2]', 'debug[3]');
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'ITERM_RELAX':
- switch (fieldName) {
- case 'debug[2]': // roll I relaxed error
- case 'debug[3]': // roll absolute control axis error
- return getCurveForMinMaxFieldsZeroOffset(fieldName);
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FF_INTERPOLATED':
- switch (fieldName) {
- case 'debug[0]': // setpoint Delta
- case 'debug[1]': // AccelerationModified
- case 'debug[2]': // Acceleration
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[3]': // Clip or Count
- return {
- offset: -10,
- power: 1.0,
- inputRange: 10,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FEEDFORWARD': // replaces FF_INTERPOLATED in 4.3
- switch (fieldName) {
- case 'debug[0]': // in 4.3 is interpolated setpoint
- return {
- offset: 0,
- power: 1.0,
- inputRange: maxDegreesSecond(gyroScaleMargin),
- outputRange: 1.0,
- };
- case 'debug[1]': // feedforward delta element
- case 'debug[2]': // feedforward boost element
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[3]': // rcCommand delta
- return {
- offset: 0,
- power: 1.0,
- inputRange: 10000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FF_LIMIT':
- case 'FEEDFORWARD_LIMIT':
- return {
- offset: 0,
- power: 1.0,
- inputRange: 300,
- outputRange: 1.0,
- };
- case 'BARO':
- switch (fieldName) {
- case 'debug[0]': // Baro state 0-10
- return {
- offset: 0,
- power: 1.0,
- inputRange: 20,
- outputRange: 1.0,
- };
- case 'debug[1]': // Baro Temp
- case 'debug[2]': // Baro Raw
- case 'debug[3]': // Baro smoothed
- return {
- offset: 0,
- power: 1.0,
- inputRange: 2000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_RESCUE_THROTTLE_PID':
- switch (fieldName) {
- case 'debug[0]': // Throttle P uS added
- case 'debug[1]': // Throttle D uS added
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- case 'debug[2]': // Altitude
- case 'debug[3]': // Target Altitude
- return {
- offset: 0,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'DYN_IDLE':
- switch (fieldName) {
- case 'debug[0]': // in 4.3 is dyn idle P
- case 'debug[1]': // in 4.3 is dyn idle I
- case 'debug[2]': // in 4.3 is dyn idle D
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[3]': // in 4.3 and 4.2 is minRPS
- return {
- offset: -1000,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GYRO_SAMPLE':
- switch (fieldName) {
- case 'debug[0]': // Before downsampling
- case 'debug[1]': // After downsampling
- case 'debug[2]': // After RPM
- case 'debug[3]': // After all but Dyn Notch
- return {
- offset: 0,
- power: 0.25, /* Make this 1.0 to scale linearly */
- inputRange: maxDegreesSecond(gyroScaleMargin * highResolutionScale), // Maximum grad/s + 20%
- outputRange: 1.0
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RX_TIMING':
- switch (fieldName) {
- case 'debug[0]': // CRC 0 to max int16_t
- return { // start at bottom, scale up to 20ms
- offset: -1000,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- // debug 1 is Count of Unknown Frames
- // debug 2 and 3 not used
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GHST':
- switch (fieldName) {
- case 'debug[0]': // CRC 0 to max int16_t
- case 'debug[1]': // Count of Unknown Frames
- return getCurveForMinMaxFieldsZeroOffset(fieldName);
- case 'debug[2]': // RSSI
- return {
- offset: 128,
- power: 1.0,
- inputRange: 128,
- outputRange: 1.0,
- };
- case 'debug[3]': // LQ percent
- return {
- offset: -50,
- power: 1.0,
- inputRange: 50,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'SCHEDULER_DETERMINISM':
- switch (fieldName) {
- case 'debug[0]': // Gyro task cycle us * 10 so 1250 = 125us
- return {
- offset: -5000,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- case 'debug[1]': // ID of late task
- case 'debug[2]': // task delay time 100us in middle
- return {
- offset: -1000,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[3]': // gyro skew 100 = 10us
- return {
- offset: 0,
- power: 1.0,
- inputRange: 500,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'TIMING_ACCURACY':
- switch (fieldName) {
- case 'debug[0]': // % CPU Busy
- case 'debug[1]': // late tasks per second
- return {
- offset: -50,
- power: 1.0,
- inputRange: 50,
- outputRange: 1.0,
- };
- case 'debug[2]': // total delay in last second
- return {
- offset: -500,
- power: 1.0,
- inputRange: 500,
- outputRange: 1.0,
- };
- case 'debug[3]': // total tasks per second
- return {
- offset: -5000,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RX_EXPRESSLRS_SPI':
- switch (fieldName) {
- case 'debug[2]': // Uplink LQ
- return {
- offset: -50,
- power: 1.0,
- inputRange: 50,
- outputRange: 1.0,
- };
- // debug 0 = Lost connection count
- // debug 1 = RSSI
- // debug 3 = SNR
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RX_EXPRESSLRS_PHASELOCK':
- switch (fieldName) {
- case 'debug[2]': // Frequency offset in ticks
- return getCurveForMinMaxFieldsZeroOffset(fieldName);
- // debug 0 = Phase offset us
- // debug 1 = Filtered phase offset us
- // debug 3 = Phase shift in us
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_RESCUE_VELOCITY':
- switch (fieldName) {
- case 'debug[0]': // Pitch P deg * 100
- case 'debug[1]': // Pitch D deg * 100
- return {
- offset: 0,
- power: 1.0,
- inputRange: 2000,
- outputRange: 1.0,
- };
- case 'debug[2]': // Velocity in cm/s
- case 'debug[3]': // Velocity to home in cm/s
- return {
- offset: 0,
- power: 1.0,
- inputRange: 500,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_RESCUE_HEADING':
- switch (fieldName) {
- case 'debug[0]': // Groundspeed cm/s
- return {
- offset: 0,
- power: 1.0,
- inputRange: 10000,
- outputRange: 1.0,
- };
- case 'debug[1]': // GPS GroundCourse
- case 'debug[2]': // Yaw attitude * 10
- case 'debug[3]': // Angle to home * 10
- case 'debug[4]': // magYaw * 10
- return {
- offset: -1800,
- power: 1.0,
- inputRange: 1800,
- outputRange: 1.0,
- };
- case 'debug[5]': // magYaw * 10
- return {
- offset: -10,
- power: 1.0,
- inputRange: 10,
- outputRange: 1.0,
- };
- case 'debug[6]': // roll angle *100
- return {
- offset: -900,
- power: 1.0,
- inputRange: 900,
- outputRange: 1.0,
- };
- case 'debug[7]': // yaw rate deg/s
- return {
- offset: -100,
- power: 1.0,
- inputRange: 100,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'RTH':
- switch (fieldName) {
- case 'debug[0]': // Pitch angle, deg * 100
- return {
- offset: 0,
- power: 1.0,
- inputRange: 4000,
- outputRange: 1.0,
- };
- case 'debug[1]': // Rescue Phase
- case 'debug[2]': // Failure code
- return {
- offset: -10,
- power: 1.0,
- inputRange: 10,
- outputRange: 1.0,
- };
- case 'debug[3]': // Failure counters coded
- return {
- offset: -2000,
- power: 1.0,
- inputRange: 2000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_RESCUE_TRACKING':
- switch (fieldName) {
- case 'debug[0]': // velocity to home cm/s
- case 'debug[1]': // target velocity cm/s
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[2]': // altitude m
- case 'debug[3]': // Target altitude m
- return {
- offset: 0,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_CONNECTION':
- switch (fieldName) {
- case 'debug[0]': // GPS flight model
- case 'debug[1]': // Nav Data interval
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- case 'debug[2]': // task interval
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- case 'debug[3]': // Baud rate / resolved packet interval
- case 'debug[4]': // State*100 + SubState
- return getCurveForMinMaxFields(fieldName);
- case 'debug[5]': // ExecuteTimeUs
- return {
- offset: 0,
- power: 1.0,
- inputRange: 100,
- outputRange: 1.0,
- };
- case 'debug[6]': // ackState
- return {
- offset: 0,
- power: 1.0,
- inputRange: 10,
- outputRange: 1.0,
- };
- case 'debug[7]': // Incoming buffer
- return {
- offset: 0,
- power: 1.0,
- inputRange: 100,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'GPS_DOP':
- switch (fieldName) {
- case 'debug[0]': // Number of Satellites (now this is in normal GPS data, maybe gpsTrust?)
- case 'debug[1]': // pDOP
- case 'debug[2]': // hDOP
- case 'debug[3]': // vDOP
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'FAILSAFE':
- switch (fieldName) {
- case 'debug[0]':
- case 'debug[1]':
- case 'debug[2]':
- case 'debug[3]':
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'ANGLE_MODE':
- switch (fieldName) {
- case 'debug[0]': // angle target
- case 'debug[3]': // angle achieved
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[1]': // angle error correction
- case 'debug[2]': // angle feedforward
- return {
- offset: 0,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'DSHOT_TELEMETRY_COUNTS':
- switch (fieldName) {
- case 'debug[0]':
- case 'debug[1]':
- case 'debug[2]':
- case 'debug[3]':
- return {
- offset: 0,
- power: 1.0,
- inputRange: 200,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'MAG_CALIB':
- switch (fieldName) {
- case 'debug[0]': // X
- case 'debug[1]': // Y
- case 'debug[2]': // Z
- case 'debug[3]': // Field
- return {
- offset: 0,
- power: 1.0,
- inputRange: 2000,
- outputRange: 1.0,
- };
- case 'debug[4]': // X Cal
- case 'debug[5]': // Y Cal
- case 'debug[6]': // Z Cal
- return {
- offset: 0,
- power: 1.0,
- inputRange: 500,
- outputRange: 1.0,
- };
- case 'debug[7]': // Lambda
- return {
- offset: -2000,
- power: 1.0,
- inputRange: 2000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'MAG_TASK_RATE':
- switch (fieldName) {
- case 'debug[0]': // Task Rate
- case 'debug[1]': // Data Rate
- return {
- offset: 0,
- power: 1.0,
- inputRange: 1000,
- outputRange: 1.0,
- };
- case 'debug[2]': // Data Interval
- return {
- offset: 0,
- power: 1.0,
- inputRange: 10000,
- outputRange: 1.0,
- };
- case 'debug[3]': // Execute Time
- return {
- offset: 0,
- power: 1.0,
- inputRange: 20,
- outputRange: 1.0,
- };
- case 'debug[4]': // Bus Busy Check
- case 'debug[5]': // Read State Check
- return {
- offset: 0,
- power: 1.0,
- inputRange: 2,
- outputRange: 1.0,
- };
- case 'debug[6]': // Time since previous task uS
- return {
- offset: 0,
- power: 1.0,
- inputRange: 10000,
- outputRange: 1.0,
- };
- default:
- return getCurveForMinMaxFields(fieldName);
- }
- case 'EZLANDING':
- return {
- offset: -5000,
- power: 1.0,
- inputRange: 5000,
- outputRange: 1.0,
- };
- }
- }
- // if not found above then
- // Scale and center the field based on the whole-log observed ranges for that field
- return getCurveForMinMaxFields(fieldName);
- } catch(e) {
- return {
- offset: 0,
+ MinMax: {
+ min: 0,
+ max: 20,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FEEDFORWARD": // replaces FF_INTERPOLATED in 4.3
+ switch (fieldName) {
+ case "debug[0]": // in 4.3 is interpolated setpoint
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin),
+ max: maxDegreesSecond(gyroScaleMargin),
+ },
+ };
+ case "debug[1]": // feedforward delta element
+ case "debug[2]": // feedforward boost element
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -1000,
+ max: 1000,
+ },
+ };
+ case "debug[3]": // rcCommand delta
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -10000,
+ max: 10000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FF_LIMIT":
+ case "FEEDFORWARD_LIMIT":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -300,
+ max: 300,
+ },
+ };
+ case "BARO":
+ switch (fieldName) {
+ case "debug[0]": // Baro state 0-10
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -20,
+ max: 20,
+ },
+ };
+ case "debug[1]": // Baro Temp
+ case "debug[2]": // Baro Raw
+ case "debug[3]": // Baro smoothed
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_RESCUE_THROTTLE_PID":
+ switch (fieldName) {
+ case "debug[0]": // Throttle P uS added
+ case "debug[1]": // Throttle D uS added
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ case "debug[2]": // Altitude
+ case "debug[3]": // Target Altitude
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -50,
+ max: 50,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "DYN_IDLE":
+ switch (fieldName) {
+ case "debug[0]": // in 4.3 is dyn idle P
+ case "debug[1]": // in 4.3 is dyn idle I
+ case "debug[2]": // in 4.3 is dyn idle D
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -1000,
+ max: 1000,
+ },
+ };
+ case "debug[3]": // in 4.3 and 4.2 is minRPS
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 12000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GYRO_SAMPLE":
+ switch (fieldName) {
+ case "debug[0]": // Before downsampling
+ case "debug[1]": // After downsampling
+ case "debug[2]": // After RPM
+ case "debug[3]": // After all but Dyn Notch
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -maxDegreesSecond(gyroScaleMargin * highResolutionScale),
+ max: maxDegreesSecond(gyroScaleMargin * highResolutionScale),
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RX_TIMING":
+ switch (fieldName) {
+ case "debug[0]": // CRC 0 to max int16_t
+ return {
+ // start at bottom, scale up to 20ms
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 20,
+ },
+ };
+ // debug 1 is Count of Unknown Frames
+ // debug 2 and 3 not used
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GHST":
+ switch (fieldName) {
+ case "debug[0]": // CRC 0 to max int16_t
+ case "debug[1]": // Count of Unknown Frames
+ return getCurveForMinMaxFieldsZeroOffset(fieldName);
+ case "debug[2]": // RSSI
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -256,
+ max: 0,
+ },
+ };
+ case "debug[3]": // LQ percent
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "SCHEDULER_DETERMINISM":
+ switch (fieldName) {
+ case "debug[0]": // Gyro task cycle us * 10 so 1250 = 125us
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 1000,
+ },
+ };
+ case "debug[1]": // ID of late task
+ case "debug[2]": // task delay time 100us in middle
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 200,
+ },
+ };
+ case "debug[3]": // gyro skew 100 = 10us
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -50,
+ max: 50,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "TIMING_ACCURACY":
+ switch (fieldName) {
+ case "debug[0]": // % CPU Busy
+ case "debug[1]": // late tasks per second
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ case "debug[2]": // total delay in last second
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ case "debug[3]": // total tasks per second
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 10000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RX_EXPRESSLRS_SPI":
+ switch (fieldName) {
+ case "debug[2]": // Uplink LQ
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 100,
+ },
+ };
+ // debug 0 = Lost connection count
+ // debug 1 = RSSI
+ // debug 3 = SNR
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RX_EXPRESSLRS_PHASELOCK":
+ switch (fieldName) {
+ case "debug[2]": // Frequency offset in ticks
+ return getCurveForMinMaxFieldsZeroOffset(fieldName);
+ // debug 0 = Phase offset us
+ // debug 1 = Filtered phase offset us
+ // debug 3 = Phase shift in us
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_RESCUE_VELOCITY":
+ switch (fieldName) {
+ case "debug[0]": // Pitch P deg * 100
+ case "debug[1]": // Pitch D deg * 100
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -20,
+ max: 20,
+ },
+ };
+ case "debug[2]": // Velocity in cm/s
+ case "debug[3]": // Velocity to home in cm/s
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -5,
+ max: 5,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_RESCUE_HEADING":
+ switch (fieldName) {
+ case "debug[0]": // Groundspeed cm/s
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ case "debug[1]": // GPS GroundCourse
+ case "debug[2]": // Yaw attitude * 10
+ case "debug[3]": // Angle to home * 10
+ case "debug[4]": // magYaw * 10
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 360,
+ },
+ };
+ case "debug[5]": // magYaw * 10
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 20,
+ },
+ };
+ case "debug[6]": // roll angle *100
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 180,
+ },
+ };
+ case "debug[7]": // yaw rate deg/s
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 200,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "RTH":
+ switch (fieldName) {
+ case "debug[0]": // Pitch angle, deg * 100
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -4000,
+ max: 4000,
+ },
+ };
+ case "debug[1]": // Rescue Phase
+ case "debug[2]": // Failure code
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 20,
+ },
+ };
+ case "debug[3]": // Failure counters coded
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 4000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_RESCUE_TRACKING":
+ switch (fieldName) {
+ case "debug[0]": // velocity to home cm/s
+ case "debug[1]": // target velocity cm/s
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -10,
+ max: 10,
+ },
+ };
+ case "debug[2]": // altitude m
+ case "debug[3]": // Target altitude m
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -50,
+ max: 50,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_CONNECTION":
+ switch (fieldName) {
+ case "debug[0]": // GPS flight model
+ case "debug[1]": // Nav Data interval
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ case "debug[2]": // task interval
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ case "debug[3]": // Baud rate / resolved packet interval
+ case "debug[4]": // State*100 + SubState
+ return getCurveForMinMaxFields(fieldName);
+ case "debug[5]": // ExecuteTimeUs
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ case "debug[6]": // ackState
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -10,
+ max: 10,
+ },
+ };
+ case "debug[7]": // Incoming buffer
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "GPS_DOP":
+ switch (fieldName) {
+ case "debug[0]": // Number of Satellites (now this is in normal GPS data, maybe gpsTrust?)
+ case "debug[1]": // pDOP
+ case "debug[2]": // hDOP
+ case "debug[3]": // vDOP
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "FAILSAFE":
+ switch (fieldName) {
+ case "debug[0]":
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "ANGLE_MODE":
+ switch (fieldName) {
+ case "debug[0]": // angle target
+ case "debug[3]": // angle achieved
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -100,
+ max: 100,
+ },
+ };
+ case "debug[1]": // angle error correction
+ case "debug[2]": // angle feedforward
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -500,
+ max: 500,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "DSHOT_TELEMETRY_COUNTS":
+ switch (fieldName) {
+ case "debug[0]":
+ case "debug[1]":
+ case "debug[2]":
+ case "debug[3]":
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -200,
+ max: 200,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "MAG_CALIB":
+ switch (fieldName) {
+ case "debug[0]": // X
+ case "debug[1]": // Y
+ case "debug[2]": // Z
+ case "debug[3]": // Field
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -2000,
+ max: 2000,
+ },
+ };
+ case "debug[4]": // X Cal
+ case "debug[5]": // Y Cal
+ case "debug[6]": // Z Cal
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -500,
+ max: 500,
+ },
+ };
+ case "debug[7]": // Lambda
+ return {
+ power: 1.0,
+ MinMax: {
+ min: 0,
+ max: 4000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "MAG_TASK_RATE":
+ switch (fieldName) {
+ case "debug[0]": // Task Rate
+ case "debug[1]": // Data Rate
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -1000,
+ max: 1000,
+ },
+ };
+ case "debug[2]": // Data Interval
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -10000,
+ max: 10000,
+ },
+ };
+ case "debug[3]": // Execute Time
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -20,
+ max: 20,
+ },
+ };
+ case "debug[4]": // Bus Busy Check
+ case "debug[5]": // Read State Check
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -2,
+ max: 2,
+ },
+ };
+ case "debug[6]": // Time since previous task uS
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -10000,
+ max: 10000,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ case "EZLANDING":
+ return {
+ offset: -5000,
power: 1.0,
- inputRange: 500,
- outputRange: 1.0
- };
+ inputRange: 5000,
+ outputRange: 1.0,
+ };
+ case "ATTITUDE":
+ switch (fieldName) {
+ case "debug[0]": // Roll angle
+ case "debug[1]": // Pitch angle
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -180,
+ max: 180,
+ },
+ };
+ default:
+ return getCurveForMinMaxFields(fieldName);
+ }
+ }
}
+ // if not found above then
+ // Scale and center the field based on the whole-log observed ranges for that field
+ return getCurveForMinMaxFields(fieldName);
+ } catch (e) {
+ return {
+ power: 1.0,
+ MinMax: {
+ min: -500,
+ max: 500,
+ },
+ };
+ }
};
/**
- * Get an array of suggested graph configurations will be usable for the fields available in the given flightlog.
+ * Compute min-max values for field during current windows time interval.
*
- * Supply an array of strings `graphNames` to only fetch the graph with the given names.
+ * @param flightLog The reference to the FlightLog object
+ * @param logGrapher The reference to the FlightLogGrapher object
+ * @param fieldName Name of the field
*/
-GraphConfig.getExampleGraphConfigs = function(flightLog, graphNames) {
- var
- result = [],
- i, j;
+GraphConfig.getMinMaxForFieldDuringWindowTimeInterval = function (
+ flightLog,
+ logGrapher,
+ fieldName
+) {
+ const WindowCenterTime = logGrapher.getWindowCenterTime();
+ const WindowWidthTime = logGrapher.getWindowWidthTime();
+ const minTime = WindowCenterTime - WindowWidthTime / 2;
+ const maxTime = WindowCenterTime + WindowWidthTime / 2;
- const EXAMPLE_GRAPHS = [];
+ const mm = flightLog.getMinMaxForFieldDuringTimeInterval(
+ fieldName,
+ minTime,
+ maxTime
+ );
+ if (mm == undefined)
+ return {
+ min: -500,
+ max: 500,
+ };
- if (!flightLog.isFieldDisabled().MOTORS) {
- EXAMPLE_GRAPHS.push({label: "Motors",fields: ["motor[all]", "servo[5]"]});
- EXAMPLE_GRAPHS.push({label: "Motors (Legacy)",fields: ["motorLegacy[all]", "servo[5]"]});
- }
- if (!flightLog.isFieldDisabled().RPM) {
- EXAMPLE_GRAPHS.push({label: "RPM",fields: ["eRPM[all]"]});
- }
- if (!flightLog.isFieldDisabled().GYRO) {
- EXAMPLE_GRAPHS.push({label: "Gyros",fields: ["gyroADC[all]"]});
- }
- if (!flightLog.isFieldDisabled().GYROUNFILT) {
- EXAMPLE_GRAPHS.push({label: "Unfiltered Gyros",fields: ["gyroUnfilt[all]"]});
- }
- if (!flightLog.isFieldDisabled().SETPOINT) {
- EXAMPLE_GRAPHS.push({label: "Setpoint",fields: ["rcCommands[all]"]});
- }
- if (!flightLog.isFieldDisabled().RC_COMMANDS) {
- EXAMPLE_GRAPHS.push({label: "RC Command",fields: ["rcCommand[all]"]});
- }
- if (!flightLog.isFieldDisabled().PID) {
- EXAMPLE_GRAPHS.push({label: "PIDs",fields: ["axisSum[all]"]});
- }
- if (!(flightLog.isFieldDisabled().GYRO || flightLog.isFieldDisabled().PID)) {
- EXAMPLE_GRAPHS.push({label: "PID Error",fields: ["axisError[all]"]},
- {label: "Gyro + PID roll",fields: ["axisP[0]", "axisI[0]", "axisD[0]", "axisF[0]", "gyroADC[0]"]},
- {label: "Gyro + PID pitch",fields: ["axisP[1]", "axisI[1]", "axisD[1]", "axisF[1]", "gyroADC[1]"]},
- {label: "Gyro + PID yaw",fields: ["axisP[2]", "axisI[2]", "axisD[2]", "axisF[2]", "gyroADC[2]"]});
- }
- if (!flightLog.isFieldDisabled().ACC) {
- EXAMPLE_GRAPHS.push({label: "Accelerometers",fields: ["accSmooth[all]"]});
- }
- if (!flightLog.isFieldDisabled().HEADING) {
- EXAMPLE_GRAPHS.push({label: "Heading",fields: ["heading[all]"]});
- }
- if (!flightLog.isFieldDisabled().MAGNETOMETER) {
- EXAMPLE_GRAPHS.push({label: "Compass",fields: ["magADC[all]"]});
- }
- if (!flightLog.isFieldDisabled().DEBUG) {
- EXAMPLE_GRAPHS.push({label: "Debug",fields: ["debug[all]"]});
- }
+ mm.min = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ );
+ mm.max = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ );
+ return mm;
+};
- if (!flightLog.isFieldDisabled().GPS) {
- EXAMPLE_GRAPHS.push({label: "GPS",fields: ["GPS_numSat", "GPS_altitude", "GPS_speed", "GPS_ground_course", "GPS_coord[all]"]});
- }
+/**
+ * Compute min-max values for field during marked in-out time interval.
+ *
+ * @param flightLog The reference to the FlightLog object
+ * @param logGrapher The reference to the FlightLogGrapher object
+ * @param fieldName Name of the field
+ */
+GraphConfig.getMinMaxForFieldDuringMarkedInterval = function (
+ flightLog,
+ logGrapher,
+ fieldName
+) {
+ let minTime = logGrapher.getMarkedInTime();
+ let maxTime = logGrapher.getMarkedOutTime();
+ if (minTime == false) minTime = flightLog.getMinTime();
+ if (maxTime == false) maxTime = flightLog.getMaxTime();
- for (i = 0; i < EXAMPLE_GRAPHS.length; i++) {
- var
- srcGraph = EXAMPLE_GRAPHS[i],
- destGraph = {
- label: srcGraph.label,
- fields: [],
- height: srcGraph.height || 1
- },
- found;
+ const mm = flightLog.getMinMaxForFieldDuringTimeInterval(
+ fieldName,
+ minTime,
+ maxTime
+ );
+ if (mm == undefined)
+ return {
+ min: -500,
+ max: 500,
+ };
- if (graphNames !== undefined) {
- found = false;
- for (j = 0; j < graphNames.length; j++) {
- if (srcGraph.label == graphNames[j]) {
- found = true;
- break;
- }
- }
+ mm.min = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ );
+ mm.max = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ );
+ return mm;
+};
- if (!found) {
- continue;
- }
- }
+/**
+ * Compute min-max values for field during all time.
+ * @param fieldName Name of the field
+ */
+GraphConfig.getMinMaxForFieldDuringAllTime = function (flightLog, fieldName) {
+ const mm = flightLog.getMinMaxForFieldDuringAllTime(fieldName);
+ if (mm.min == Number.MAX_VALUE || mm.max == -Number.MAX_VALUE) {
+ return {
+ min: -500,
+ max: 500,
+ };
+ }
+
+ mm.min = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.min
+ );
+ mm.max = FlightLogFieldPresenter.ConvertFieldValue(
+ flightLog,
+ fieldName,
+ true,
+ mm.max
+ );
+ return mm;
+};
+
+/**
+ * Get an array of suggested graph configurations will be usable for the fields available in the given flightlog.
+ *
+ * Supply an array of strings `graphNames` to only fetch the graph with the given names.
+ */
+GraphConfig.getExampleGraphConfigs = function (flightLog, graphNames) {
+ const result = [];
+ const EXAMPLE_GRAPHS = [];
+
+ if (!flightLog.isFieldDisabled().MOTORS) {
+ EXAMPLE_GRAPHS.push({
+ label: "Motors",
+ fields: ["motor[all]", "servo[5]"],
+ });
+ EXAMPLE_GRAPHS.push({
+ label: "Motors (Legacy)",
+ fields: ["motorLegacy[all]", "servo[5]"],
+ });
+ }
+ if (!flightLog.isFieldDisabled().RPM) {
+ EXAMPLE_GRAPHS.push({ label: "RPM", fields: ["eRPM[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().GYRO) {
+ EXAMPLE_GRAPHS.push({ label: "Gyros", fields: ["gyroADC[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().GYROUNFILT) {
+ EXAMPLE_GRAPHS.push({
+ label: "Unfiltered Gyros",
+ fields: ["gyroUnfilt[all]"],
+ });
+ }
+ if (!flightLog.isFieldDisabled().SETPOINT) {
+ EXAMPLE_GRAPHS.push({ label: "Setpoint", fields: ["rcCommands[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().RC_COMMANDS) {
+ EXAMPLE_GRAPHS.push({ label: "RC Command", fields: ["rcCommand[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().PID) {
+ EXAMPLE_GRAPHS.push({ label: "PIDs", fields: ["axisSum[all]"] });
+ }
+ if (!(flightLog.isFieldDisabled().GYRO || flightLog.isFieldDisabled().PID)) {
+ EXAMPLE_GRAPHS.push(
+ { label: "PID Error", fields: ["axisError[all]"] },
+ {
+ label: "Gyro + PID roll",
+ fields: ["axisP[0]", "axisI[0]", "axisD[0]", "axisF[0]", "gyroADC[0]"],
+ },
+ {
+ label: "Gyro + PID pitch",
+ fields: ["axisP[1]", "axisI[1]", "axisD[1]", "axisF[1]", "gyroADC[1]"],
+ },
+ {
+ label: "Gyro + PID yaw",
+ fields: ["axisP[2]", "axisI[2]", "axisD[2]", "axisF[2]", "gyroADC[2]"],
+ }
+ );
+ }
+ if (!flightLog.isFieldDisabled().ACC) {
+ EXAMPLE_GRAPHS.push({
+ label: "Accelerometers",
+ fields: ["accSmooth[all]"],
+ });
+ }
+ if (!flightLog.isFieldDisabled().HEADING) {
+ EXAMPLE_GRAPHS.push({ label: "Heading", fields: ["heading[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().MAGNETOMETER) {
+ EXAMPLE_GRAPHS.push({ label: "Compass", fields: ["magADC[all]"] });
+ }
+ if (!flightLog.isFieldDisabled().DEBUG) {
+ EXAMPLE_GRAPHS.push({ label: "Debug", fields: ["debug[all]"] });
+ }
+
+ if (!flightLog.isFieldDisabled().GPS) {
+ EXAMPLE_GRAPHS.push({
+ label: "GPS",
+ fields: [
+ "GPS_numSat",
+ "GPS_altitude",
+ "GPS_speed",
+ "GPS_ground_course",
+ "GPS_coord[all]",
+ ],
+ });
+ EXAMPLE_GRAPHS.push({
+ label: "GPS Cartesian coords",
+ fields: [
+ "gpsCartesianCoords[all]",
+ "gpsDistance",
+ "gpsHomeAzimuth",
+ ],
+ });
+ }
- for (j = 0; j < srcGraph.fields.length; j++) {
- var
- srcFieldName = srcGraph.fields[j],
- destField = {
- name: srcFieldName
- };
-
- destGraph.fields.push(destField);
+ for (const srcGraph of EXAMPLE_GRAPHS) {
+ const destGraph = {
+ label: srcGraph.label,
+ fields: [],
+ height: srcGraph.height || 1,
+ };
+ let found;
+
+ if (graphNames !== undefined) {
+ found = false;
+ for (const name of graphNames) {
+ if (srcGraph.label == name) {
+ found = true;
+ break;
}
+ }
+
+ if (!found) {
+ continue;
+ }
+ }
+
+ for (const srcFieldName of srcGraph.fields) {
+ const destField = {
+ name: srcFieldName,
+ color: -1,
+ };
- result.push(destGraph);
+ destGraph.fields.push(destField);
}
- return result;
+ result.push(destGraph);
+ }
+
+ return result;
};
diff --git a/src/graph_config_dialog.js b/src/graph_config_dialog.js
index 839690e9..b62c9e9f 100644
--- a/src/graph_config_dialog.js
+++ b/src/graph_config_dialog.js
@@ -1,447 +1,631 @@
import { GraphConfig } from "./graph_config";
import { FlightLogFieldPresenter } from "./flightlog_fields_presenter";
+import { showMinMaxSetupContextMenu } from "./graph_minmax_setting_menu";
+import { closeMinMaxContextMenu } from "./graph_minmax_setting_menu";
+import { isMinMaxContextMenuActive } from "./graph_minmax_setting_menu";
export function GraphConfigurationDialog(dialog, onSave) {
- var
- // Some fields it doesn't make sense to graph
- BLACKLISTED_FIELDS = {time:true, loopIteration:true, 'setpoint[0]':true, 'setpoint[1]':true, 'setpoint[2]':true, 'setpoint[3]':true},
- offeredFieldNames = [],
- exampleGraphs = [],
- activeFlightLog;
-
-
- function chooseColor(currentSelection) {
- var selectColor = $(' ');
- for(var i=0; i')
- .text(GraphConfig.PALETTE[i].name)
- .attr('value', GraphConfig.PALETTE[i].color)
- .css('color', GraphConfig.PALETTE[i].color);
- if(currentSelection == GraphConfig.PALETTE[i].color) {
- option.attr('selected', 'selected');
- selectColor.css('background', GraphConfig.PALETTE[i].color)
- .css('color', GraphConfig.PALETTE[i].color);
- }
- selectColor.append(option);
- }
-
- return selectColor;
+ let // Some fields it doesn't make sense to graph
+ BLACKLISTED_FIELDS = {
+ time: true,
+ loopIteration: true,
+ "setpoint[0]": true,
+ "setpoint[1]": true,
+ "setpoint[2]": true,
+ "setpoint[3]": true,
+ },
+ offeredFieldNames = [],
+ exampleGraphs = [],
+ activeFlightLog,
+ logGrapher = null,
+ prevCfg = null,
+ activeGraphConfig = null,
+ cfgMustBeRestored = false;
+
+ function chooseColor(currentSelection) {
+ const selectColor = $(' ');
+ for (let i = 0; i < GraphConfig.PALETTE.length; i++) {
+ let option = $(" ")
+ .text(GraphConfig.PALETTE[i].name)
+ .attr("value", GraphConfig.PALETTE[i].color)
+ .css("color", GraphConfig.PALETTE[i].color);
+ if (currentSelection == GraphConfig.PALETTE[i].color) {
+ option.attr("selected", "selected");
+ selectColor
+ .css("background", GraphConfig.PALETTE[i].color)
+ .css("color", GraphConfig.PALETTE[i].color);
+ }
+ selectColor.append(option);
}
- function chooseHeight(currentSelection) {
- var MAX_HEIGHT = 5;
-
- var selectHeight = $(' ');
- for(var i=1; i<=MAX_HEIGHT; i++) {
- var option = $(' ')
- .text(i)
- .attr('value', i);
- if(currentSelection == i || (currentSelection==null && i==1)) {
- option.attr('selected', 'selected');
- }
- selectHeight.append(option);
- }
-
- return selectHeight;
+ return selectColor;
+ }
+
+ function chooseHeight(currentSelection) {
+ const MAX_HEIGHT = 5;
+
+ const selectHeight = $(
+ ' '
+ );
+ for (let i = 1; i <= MAX_HEIGHT; i++) {
+ const option = $(" ").text(i).attr("value", i);
+ if (currentSelection == i || (currentSelection == null && i == 1)) {
+ option.attr("selected", "selected");
+ }
+ selectHeight.append(option);
}
- // Show/Hide remove all button
- function updateRemoveAllButton() {
- var graphCount = $('.config-graph').length;
-
- if (graphCount > 0) {
- $('.config-graphs-remove-all-graphs').show();
- } else {
- $('.config-graphs-remove-all-graphs').hide();
- }
- renumberGraphIndexes();
- }
-
- // Renumber the "Graph X" blocks after additions/deletions
- function renumberGraphIndexes() {
- var graphIndexes = $('.graph-index-number');
- var graphCount = graphIndexes.length;
- for (var i = 0; i < graphCount; i++) {
- var currentGraphNumber = i+1;
- $(graphIndexes[i]).html(currentGraphNumber);
- }
- }
-
- function renderFieldOption(fieldName, selectedName) {
- var
- option = $(" ")
- .text(FlightLogFieldPresenter.fieldNameToFriendly(fieldName, activeFlightLog.getSysConfig().debug_mode))
- .attr("value", fieldName);
-
- if (fieldName == selectedName) {
- option.attr("selected", "selected");
- }
+ return selectHeight;
+ }
+
+ // Show/Hide remove all button
+ function updateRemoveAllButton() {
+ const graphCount = $(".config-graph").length;
- return option;
+ if (graphCount > 0) {
+ $(".config-graphs-remove-all-graphs").show();
+ } else {
+ $(".config-graphs-remove-all-graphs").hide();
}
+ renumberGraphIndexes();
+ }
+
+ // Renumber the "Graph X" blocks after additions/deletions
+ function renumberGraphIndexes() {
+ const graphIndexes = $(".graph-index-number");
+ const graphCount = graphIndexes.length;
+ for (let i = 0; i < graphCount; i++) {
+ let currentGraphNumber = i + 1;
+ $(graphIndexes[i]).html(currentGraphNumber);
+ }
+ }
+
+ function renderFieldOption(fieldName, selectedName) {
+ const option = $(" ")
+ .text(
+ FlightLogFieldPresenter.fieldNameToFriendly(
+ fieldName,
+ activeFlightLog.getSysConfig().debug_mode
+ )
+ )
+ .attr("value", fieldName);
- // Set the current smoothing options for a field
- function renderSmoothingOptions(elem, flightLog, field) {
- if(elem) {
- // the smoothing is in uS rather than %, scale the value somewhere between 0 and 10000uS
- $('input[name=smoothing]',elem).val((field.smoothing!=null)?(field.smoothing/100).toFixed(0)+'%':(GraphConfig.getDefaultSmoothingForField(flightLog, field.name)/100)+'%');
- if(field.curve!=null) {
- $('input[name=power]',elem).val((field.curve.power!=null)?(field.curve.power*100).toFixed(0)+'%':(GraphConfig.getDefaultCurveForField(flightLog, field.name).power*100)+'%');
- $('input[name=scale]',elem).val((field.curve.outputRange!=null)?(field.curve.outputRange*100).toFixed(0)+'%':(GraphConfig.getDefaultCurveForField(flightLog, field.name).outputRange*100)+'%');
- } else
- {
- $('input[name=power]',elem).val((GraphConfig.getDefaultCurveForField(flightLog, field.name).power*100).toFixed(0)+'%');
- $('input[name=scale]',elem).val((GraphConfig.getDefaultCurveForField(flightLog, field.name).outputRange*100).toFixed(0)+'%');
- }
- }
+ if (fieldName == selectedName) {
+ option.attr("selected", "selected");
}
- /**
- * Render the element for the "pick a field" dropdown box. Provide "field" from the config in order to set up the
- * initial selection.
- */
- function renderField(flightLog, field, color) {
- var
- elem = $(
- ''
- + '(choose a field) '
- + ' '
- + ' '
- + ' '
- + ' '
- + ' '
- + ' '
- + ' '
- + ' '
- ),
- select = $('select.form-control', elem),
- selectedFieldName = field ?field.name : false,
- i;
-
- for (i = 0; i < offeredFieldNames.length; i++) {
- select.append(renderFieldOption(offeredFieldNames[i], selectedFieldName));
+ return option;
+ }
+
+ // Set the current smoothing options for a field
+ function renderSmoothingOptions(elem, flightLog, field) {
+ if (elem) {
+ // the smoothing is in uS rather than %, scale the value somewhere between 0 and 10000uS
+ $("input[name=smoothing]", elem).val(
+ field.smoothing != null
+ ? `${(field.smoothing / 100).toFixed(0)}%`
+ : `${
+ GraphConfig.getDefaultSmoothingForField(flightLog, field.name) /
+ 100
+ }%`
+ );
+ if (field.curve != null) {
+ $("input[name=power]", elem).val(
+ field.curve.power != null
+ ? `${(field.curve.power * 100).toFixed(0)}%`
+ : `${
+ GraphConfig.getDefaultCurveForField(flightLog, field.name)
+ .power * 100
+ }%`
+ );
+ if (field.curve.MinMax != null) {
+ $("input[name=MinValue]", elem).val(
+ field.curve.MinMax.min.toFixed(1)
+ );
+ $("input[name=MaxValue]", elem).val(
+ field.curve.MinMax.max.toFixed(1)
+ );
+ } else {
+ $("input[name=MinValue]", elem).val(
+ GraphConfig.getDefaultCurveForField(
+ flightLog,
+ field.name
+ ).MinMax.min.toFixed(1)
+ );
+ $("input[name=MaxValue]", elem).val(
+ GraphConfig.getDefaultCurveForField(
+ flightLog,
+ field.name
+ ).MinMax.max.toFixed(1)
+ );
}
-
- // Set the smoothing values
- renderSmoothingOptions(elem, flightLog, field);
-
- // Set the line width values
- $('input[name=linewidth]',elem).val((field.lineWidth)?field.lineWidth:1);
-
- // Set the grid state
- $('input[name=grid]',elem).attr("checked", (field.grid)?field.grid:false);
-
- //Populate the Color Picker
- $('select.color-picker', elem).replaceWith(chooseColor(color));
-
-
- // Add event when selection changed to retrieve the current smoothing settings.
- $('select.form-control', elem).change( function() {
- var selectedField = {
- name: $('select.form-control option:selected', elem).val()
- };
- renderSmoothingOptions(elem, activeFlightLog, selectedField);
- });
-
- // Add event when color picker is changed to change the dropdown coloe
- $('select.color-picker', elem).change( function() {
- $(this).css('background', $('select.color-picker option:selected', elem).val())
- .css('color', $('select.color-picker option:selected', elem).val());
- });
-
-
- return elem;
+ } else {
+ $("input[name=power]", elem).val(
+ `${(
+ GraphConfig.getDefaultCurveForField(flightLog, field.name).power *
+ 100
+ ).toFixed(0)}%`
+ );
+ $("input[name=MinValue]", elem).val(
+ GraphConfig.getDefaultCurveForField(
+ flightLog,
+ field.name
+ ).MinMax.min.toFixed(1)
+ );
+ $("input[name=MaxValue]", elem).val(
+ GraphConfig.getDefaultCurveForField(
+ flightLog,
+ field.name
+ ).MinMax.max.toFixed(1)
+ );
+ }
+ }
+ }
+
+ /**
+ * Render the element for the "pick a field" dropdown box. Provide "field" from the config in order to set up the
+ * initial selection.
+ */
+ function renderField(flightLog, field, color) {
+ const elem = $(
+ '' +
+ '(choose a field) ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ " "
+ ),
+ select = $("select.form-control", elem),
+ selectedFieldName = field ? field.name : false;
+
+ for (const field of offeredFieldNames) {
+ select.append(renderFieldOption(field, selectedFieldName));
}
- function renderGraph(flightLog, index, graph) {
- var
- graphElem = $(
- ''
- + ''
- + ''
- + ' Graph ' + '' + (index + 1) + ' ' + ' '
- + ' Remove graph ' + ' '
- + ' '
- + ''
- + ''
- + ' '
- + ' '
- + ' '
- ),
- fieldList = $(".config-graph-field-list", graphElem);
-
- $("input", graphElem).val(graph.label);
-
- var fieldCount = graph.fields.length;
-
- // "Add field" button
- $(".add-field-button", graphElem).click(function(e) {
- fieldList.append(renderField(flightLog, {}, GraphConfig.PALETTE[fieldCount++].color));
- e.preventDefault();
- });
-
- // "Remove Graph" button
- $(".remove-single-graph-button", graphElem).click(function(e) {
- var parentGraph = $(this).parents('.config-graph');
- parentGraph.remove();
- updateRemoveAllButton();
- e.preventDefault();
- });
-
- //Populate the Height seletor
- $('select.graph-height', graphElem).replaceWith(chooseHeight(graph.height?(graph.height):1));
-
- // Add Field List
- for (var i = 0; i < graph.fields.length; i++) {
- var
- field = graph.fields[i],
- fieldElem = renderField(flightLog, field, field.color?(field.color):(GraphConfig.PALETTE[i].color));
-
- fieldList.append(fieldElem);
+ // Set the smoothing values
+ renderSmoothingOptions(elem, flightLog, field);
+
+ // Set the line width values
+ $("input[name=linewidth]", elem).val(field.lineWidth ? field.lineWidth : 1);
+
+ //Populate the Color Picker
+ $("select.color-picker", elem).replaceWith(chooseColor(color));
+
+ // Add event when selection changed to retrieve the current smoothing settings.
+ $("select.form-control", elem).change(function () {
+ const selectedField = {
+ name: $("select.form-control option:selected", elem).val(),
+ };
+ const fields = activeGraphConfig.extendFields(
+ activeFlightLog,
+ selectedField
+ );
+ if (fields.length === 1) {
+ renderSmoothingOptions(elem, activeFlightLog, fields[0]);
+ } else {
+ let colorIndex = $("select.color-picker", elem).prop("selectedIndex");
+ for (let i = 0; i < fields.length - 1; i++) {
+ const color =
+ GraphConfig.PALETTE[colorIndex++ % GraphConfig.PALETTE.length]
+ .color;
+ const row = renderField(flightLog, fields[i], color);
+ elem.before(row);
}
- fieldList.on('click', 'button', function(e) {
- var
- parentGraph = $(this).parents('.config-graph');
-
- $(this).parents('.config-graph-field').remove();
+ const index = $("select.form-control", elem).prop("selectedIndex");
+ $("select.form-control", elem).prop(
+ "selectedIndex",
+ index + fields.length
+ );
+ $("select.form-control", elem).trigger("change");
+
+ const colorPicker = $("select.color-picker", elem);
+ colorPicker.prop(
+ "selectedIndex",
+ colorIndex % GraphConfig.PALETTE.length
+ );
+ colorPicker.trigger("change");
+ }
+ RefreshCharts();
+ });
- // Remove the graph upon removal of the last field
- if ($(".config-graph-field", parentGraph).length === 0) {
- parentGraph.remove();
- }
- updateRemoveAllButton();
+ // Add event when color picker is changed to change the dropdown coloe
+ $("select.color-picker", elem).change(function () {
+ $(this)
+ .css("background", $("select.color-picker option:selected", elem).val())
+ .css("color", $("select.color-picker option:selected", elem).val());
+ RefreshCharts();
+ });
- e.preventDefault();
- });
+ // Add event when mouse double click at the Minimum/Maxcimum input field to restore default MinMax values.
+ $(".minmax-control", elem).dblclick(function (e) {
+ const name = $("select.form-control option:selected", elem).val();
+ const MinMax = GraphConfig.getDefaultCurveForField(
+ flightLog,
+ name
+ ).MinMax;
+ const value = e.target.name == "MinValue" ? MinMax.min : MinMax.max;
+ $(this).val(value.toFixed(1));
+ RefreshCharts();
+ });
- updateRemoveAllButton();
+ $(".minmax-control", elem).change(function (e) {
+ RefreshCharts();
+ });
+ $("input[name=smoothing]", elem).change(function (e) {
+ RefreshCharts();
+ });
+ $("input[name=power]", elem).change(function (e) {
+ RefreshCharts();
+ });
+ $("input[name=linewidth]", elem).change(function (e) {
+ RefreshCharts();
+ });
- return graphElem;
- }
+ $(".minmax-control", elem).contextmenu(function (e) {
+ const name = $("select.form-control option:selected", elem).val();
+ e.preventDefault();
+ showMinMaxSetupContextMenu(
+ e.clientX,
+ e.clientY,
+ name,
+ elem,
+ $(".config-graph-field", $(this).parents(".config-graph")),
+ flightLog,
+ logGrapher,
+ RefreshCharts
+ );
+ return false;
+ });
- function renderGraphs(flightLog, graphs) {
- var
- graphList = $(".config-graphs-list", dialog);
+ return elem;
+ }
+
+ function renderGraph(flightLog, index, graph) {
+ const graphElem = $(
+ `` +
+ `` +
+ `` +
+ ` Graph ` +
+ `${index + 1} ` +
+ ` ` +
+ ` Remove graph ` +
+ ` ` +
+ ` ` +
+ `` +
+ `` +
+ ` ` +
+ ` ` +
+ ` `
+ ),
+ fieldList = $(".config-graph-field-list", graphElem);
+
+ $("input", graphElem).val(graph.label);
+
+ // "Add field" button
+ $(".add-field-button", graphElem).click(function (e) {
+ const colorIndex = $("tbody", graphElem)[0].childElementCount;
+ const color =
+ GraphConfig.PALETTE[colorIndex % GraphConfig.PALETTE.length].color;
+ fieldList.append(renderField(flightLog, {}, color));
+ e.preventDefault();
+ });
- graphList.empty();
+ // "Remove Graph" button
+ $(".remove-single-graph-button", graphElem).click(function (e) {
+ const parentGraph = $(this).parents(".config-graph");
+ parentGraph.remove();
+ updateRemoveAllButton();
+ RefreshCharts();
+ e.preventDefault();
+ });
- for (var i = 0; i < graphs.length; i++) {
- graphList.append(renderGraph(flightLog, i, graphs[i]));
+ //Populate the Height seletor
+ $("select.graph-height", graphElem).replaceWith(
+ chooseHeight(graph.height ? graph.height : 1)
+ );
+
+ // Add Field List
+ let colorIndex = 0;
+ for (const field of graph.fields) {
+ const extendedFields = activeGraphConfig.extendFields(
+ activeFlightLog,
+ field
+ );
+ for (const extField of extendedFields) {
+ if (!extField.color || extField.color == -1) {
+ extField.color =
+ GraphConfig.PALETTE[
+ colorIndex++ % GraphConfig.PALETTE.length
+ ].color;
}
+ const fieldElem = renderField(flightLog, extField, extField.color);
+ fieldList.append(fieldElem);
+ }
}
- function populateExampleGraphs(flightLog, menu) {
- var
- i;
+ fieldList.on("click", "button", function (e) {
+ const parentGraph = $(this).parents(".config-graph");
- menu.empty();
+ $(this).parents(".config-graph-field").remove();
- exampleGraphs = GraphConfig.getExampleGraphConfigs(flightLog);
+ // Remove the graph upon removal of the last field
+ if ($(".config-graph-field", parentGraph).length === 0) {
+ parentGraph.remove();
+ }
+ updateRemoveAllButton();
+ RefreshCharts();
+ e.preventDefault();
+ });
- exampleGraphs.unshift({
- label: "Custom graph",
- fields: [{name:""}],
- dividerAfter: true
- });
+ updateRemoveAllButton();
- for (i = 0; i < exampleGraphs.length; i++) {
- var
- graph = exampleGraphs[i],
- li = $(' ');
+ return graphElem;
+ }
- $('a', li)
- .text(graph.label)
- .data('graphIndex', i);
+ function renderGraphs(flightLog, graphs) {
+ const graphList = $(".config-graphs-list", dialog);
- menu.append(li);
+ graphList.empty();
- if (graph.dividerAfter) {
- menu.append(' ');
- }
- }
+ for (let i = 0; i < graphs.length; i++) {
+ graphList.append(renderGraph(flightLog, i, graphs[i]));
}
+ }
- function convertUIToGraphConfig() {
- var
- graphs = [],
- graph,
- field;
-
- $(".config-graph", dialog).each(function() {
- graph = {
- fields: [],
- height: 1
- };
-
- graph.label = $("input[type='text']", this).val();
- graph.height = parseInt($('select.graph-height option:selected', this).val());
-
- $(".config-graph-field", this).each(function() {
- field = {
- name: $("select", this).val(),
- smoothing: parseInt($("input[name=smoothing]", this).val())*100, // Value 0-100% = 0-10000uS (higher values are more smooth, 30% is typical)
- curve: {
- power: parseInt($("input[name=power]", this).val())/100.0, // Value 0-100% = 0-1.0 (lower values exaggerate center values - expo)
- outputRange: parseInt($("input[name=scale]", this).val())/100.0 // Value 0-100% = 0-1.0 (higher values > 100% zoom in graph vertically)
- },
- default: { // These are used to restore configuration if using mousewheel adjustments
- smoothing: parseInt($("input[name=smoothing]", this).val())*100,
- power: parseInt($("input[name=power]", this).val())/100.0,
- outputRange: parseInt($("input[name=scale]", this).val())/100.0
- },
- color: $('select.color-picker option:selected', this).val(),
- lineWidth: parseInt($("input[name=linewidth]", this).val()),
- grid: $('input[name=grid]', this).is(':checked'),
- };
-
- if (field.name.length > 0) {
- graph.fields.push(field);
- }
- });
-
- graphs.push(graph);
- });
-
- return graphs;
- }
+ function populateExampleGraphs(flightLog, menu) {
+ menu.empty();
- // Decide which fields we should offer to the user
- function buildOfferedFieldNamesList(flightLog, config) {
- var
- i, j,
- lastRoot = null,
- fieldNames = flightLog.getMainFieldNames(),
- fieldsSeen = {};
-
- offeredFieldNames = [];
-
- for (i = 0; i < fieldNames.length; i++) {
- // For fields with multiple bracketed x[0], x[1] versions, add an "[all]" option
- var
- fieldName = fieldNames[i],
- matches = fieldName.match(/^(.+)\[[0-9]+\]$/);
-
- if (BLACKLISTED_FIELDS[fieldName])
- continue;
-
- if (matches) {
- if (matches[1] != lastRoot) {
- lastRoot = matches[1];
-
- offeredFieldNames.push(lastRoot + "[all]");
- fieldsSeen[lastRoot + "[all]"] = true;
- }
- } else {
- lastRoot = null;
- }
-
- offeredFieldNames.push(fieldName);
- fieldsSeen[fieldName] = true;
- }
+ exampleGraphs = GraphConfig.getExampleGraphConfigs(flightLog);
- /*
- * If the graph config has any fields in it that we don't have available in our flight log, add them to
- * the GUI anyway. (This way we can build a config when using a tricopter (which includes a tail servo) and
- * keep that tail servo in the config when we're viewing a quadcopter).
- */
- for (i = 0; i < config.length; i++) {
- var
- graph = config[i];
-
- for (j = 0; j < graph.fields.length; j++) {
- var
- field = graph.fields[j];
-
- if (!fieldsSeen[field.name]) {
- offeredFieldNames.push(field.name);
- }
- }
- }
- }
+ exampleGraphs.unshift({
+ label: "Custom graph",
+ fields: [{ name: "" }],
+ dividerAfter: true,
+ });
- this.show = function(flightLog, config) {
- dialog.modal('show');
+ for (let i = 0; i < exampleGraphs.length; i++) {
+ const li = $(' ');
- activeFlightLog = flightLog;
+ $("a", li).text(exampleGraphs[i].label).data("graphIndex", i);
- buildOfferedFieldNamesList(flightLog, config);
+ menu.append(li);
- populateExampleGraphs(flightLog, exampleGraphsMenu);
- renderGraphs(flightLog, config);
- };
+ if (exampleGraphs[i].dividerAfter) {
+ menu.append(' ');
+ }
+ }
+ }
+
+ function convertUIToGraphConfig() {
+ const graphs = [];
+ $(".config-graph", dialog).each(function () {
+ const graph = {
+ fields: [],
+ height: 1,
+ };
+
+ graph.label = $("input[type='text']", this).val();
+ graph.height = parseInt(
+ $("select.graph-height option:selected", this).val()
+ );
+
+ $(".config-graph-field", this).each(function () {
+ const fieldName = $("select", this).val();
+ const minimum = $("input[name=MinValue]", this).val();
+ const maximum = $("input[name=MaxValue]", this).val();
+ const field = {
+ name: fieldName,
+ smoothing: parseInt($("input[name=smoothing]", this).val()) * 100, // Value 0-100% = 0-10000uS (higher values are more smooth, 30% is typical)
+ curve: {
+ power: parseInt($("input[name=power]", this).val()) / 100.0, // Value 0-100% = 0-1.0 (lower values exaggerate center values - expo)
+ MinMax: {
+ min: parseFloat(minimum),
+ max: parseFloat(maximum),
+ },
+ },
+ default: {
+ // These are used to restore configuration if using mousewheel adjustments
+ smoothing: parseInt($("input[name=smoothing]", this).val()) * 100,
+ power: parseInt($("input[name=power]", this).val()) / 100.0,
+ MinMax: {
+ min: parseFloat(minimum),
+ max: parseFloat(maximum),
+ },
+ },
+ color: $("select.color-picker option:selected", this).val(),
+ lineWidth: parseInt($("input[name=linewidth]", this).val()),
+ };
+
+ if (field.name.length > 0) {
+ graph.fields.push(field);
+ }
+ });
- $(".graph-configuration-dialog-save").click(function() {
- onSave(convertUIToGraphConfig());
+ graphs.push(graph);
});
+ return graphs;
+ }
- var
- exampleGraphsButton = $(".config-graphs-add"),
- exampleGraphsMenu = $(".config-graphs-add ~ .dropdown-menu"),
- configGraphsList = $('.config-graphs-list');
+ // Decide which fields we should offer to the user
+ function buildOfferedFieldNamesList(flightLog, config) {
+ let lastRoot = null;
+ const fieldNames = flightLog.getMainFieldNames(),
+ fieldsSeen = {};
- // Make the graph order drag-able
- configGraphsList
- .sortable(
- {
- cursor: "move"
- }
- )
- .disableSelection();
+ offeredFieldNames = [];
- exampleGraphsButton.dropdown();
- exampleGraphsMenu.on("click", "a", function(e) {
- var
- graph = exampleGraphs[$(this).data("graphIndex")],
- graphElem = renderGraph(activeFlightLog, $(".config-graph", dialog).length, graph);
+ for (const fieldName of fieldNames) {
+ // For fields with multiple bracketed x[0], x[1] versions, add an "[all]" option
+ const matches = fieldName.match(/^(.+)\[[0-9]+\]$/);
- $(configGraphsList, dialog).append(graphElem);
- updateRemoveAllButton();
+ if (BLACKLISTED_FIELDS[fieldName]) continue;
- // Dismiss the dropdown button
- exampleGraphsButton.dropdown("toggle");
+ if (matches) {
+ if (matches[1] != lastRoot) {
+ lastRoot = matches[1];
- e.preventDefault();
- });
+ offeredFieldNames.push(`${lastRoot}[all]`);
+ fieldsSeen[`${lastRoot}[all]`] = true;
+ }
+ } else {
+ lastRoot = null;
+ }
- // Remove all Graphs button
- var removeAllGraphsButton = $(".config-graphs-remove-all-graphs");
- removeAllGraphsButton.on("click", function() {
- $('.config-graph').remove();
- updateRemoveAllButton();
- });
-}
\ No newline at end of file
+ offeredFieldNames.push(fieldName);
+ fieldsSeen[fieldName] = true;
+ }
+
+ /*
+ * If the graph config has any fields in it that we don't have available in our flight log, add them to
+ * the GUI anyway. (This way we can build a config when using a tricopter (which includes a tail servo) and
+ * keep that tail servo in the config when we're viewing a quadcopter).
+ */
+ for (const graph of config) {
+ for (const field of graph.fields) {
+ if (!fieldsSeen[field.name]) {
+ offeredFieldNames.push(field.name);
+ }
+ }
+ }
+ }
+
+ this.show = function (flightLog, graphConfig, grapher) {
+ dialog.modal("show");
+ activeFlightLog = flightLog;
+ logGrapher = grapher;
+ activeGraphConfig = graphConfig;
+ const config = activeGraphConfig.getGraphs();
+
+ buildOfferedFieldNamesList(flightLog, config);
+
+ populateExampleGraphs(flightLog, exampleGraphsMenu);
+ renderGraphs(flightLog, config);
+ prevCfg = convertUIToGraphConfig();
+ cfgMustBeRestored = false;
+ };
+
+ // Set focus to 'Cancel' button to do possible a closing dialog box by Esc or Enter keys
+ $("#dlgGraphConfiguration").on("shown.bs.modal", function (e) {
+ $(".graph-configuration-dialog-cancel").focus();
+ });
+
+ $("#dlgGraphConfiguration").on("hide.bs.modal", function (e) {
+ // Lock close window if MinMax menu is openned
+ if (isMinMaxContextMenuActive()) {
+ e.preventDefault();
+ return;
+ }
+
+ if (cfgMustBeRestored) {
+ const noRedraw = false;
+ onSave(prevCfg, noRedraw);
+ }
+ });
+
+ $(".graph-configuration-dialog-save").click(function () {
+ if (isMinMaxContextMenuActive()) closeMinMaxContextMenu();
+
+ cfgMustBeRestored = false;
+ const noRedraw = true;
+ onSave(convertUIToGraphConfig(), noRedraw);
+ });
+
+ $(".graph-configuration-dialog-cancel").click(function () {
+ if (isMinMaxContextMenuActive()) closeMinMaxContextMenu();
+
+ const noRedraw = !cfgMustBeRestored;
+ onSave(prevCfg, noRedraw);
+ cfgMustBeRestored = false;
+ });
+
+ function RefreshCharts() {
+ cfgMustBeRestored = true;
+ const noRedraw = false;
+ onSave(convertUIToGraphConfig(), noRedraw);
+ }
+
+ let exampleGraphsButton = $(".config-graphs-add"),
+ exampleGraphsMenu = $(".config-graphs-add ~ .dropdown-menu"),
+ configGraphsList = $(".config-graphs-list");
+
+ // Make the graph order drag-able
+ configGraphsList
+ .sortable({
+ cursor: "move",
+ })
+ .disableSelection();
+
+ exampleGraphsButton.dropdown();
+ exampleGraphsMenu.on("click", "a", function (e) {
+ const graph = exampleGraphs[$(this).data("graphIndex")],
+ graphElem = renderGraph(
+ activeFlightLog,
+ $(".config-graph", dialog).length,
+ graph
+ );
+
+ $(configGraphsList, dialog).append(graphElem);
+ updateRemoveAllButton();
+
+ // Dismiss the dropdown button
+ exampleGraphsButton.dropdown("toggle");
+ if (graph.label != "Custom graph") {
+ RefreshCharts();
+ }
+ e.preventDefault();
+ });
+
+ // Remove all Graphs button
+ const removeAllGraphsButton = $(".config-graphs-remove-all-graphs");
+ removeAllGraphsButton.on("click", function () {
+ $(".config-graph").remove();
+ updateRemoveAllButton();
+ RefreshCharts();
+ });
+}
diff --git a/src/graph_legend.js b/src/graph_legend.js
index 7f09b0bf..b1f95aea 100644
--- a/src/graph_legend.js
+++ b/src/graph_legend.js
@@ -1,228 +1,252 @@
import { FlightLogFieldPresenter } from "./flightlog_fields_presenter";
-export function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChange, onHighlightChange, onZoomGraph, onExpandGraph, onNewGraphConfig) {
- var
- that = this;
-
- function buildLegend() {
- var
- graphs = config.getGraphs(),
- i, j;
-
- targetElem.empty();
-
- for (i = 0; i < graphs.length; i++) {
- var
- graph = graphs[i],
- graphDiv = $(''),
- graphTitle = $("h3", graphDiv),
- fieldList = $("ul", graphDiv);
-
- graphTitle.text(graph.label);
- graphTitle.prepend(' ');
-
- for (j = 0; j < graph.fields.length; j++) {
- var
- field = graph.fields[j],
- li = $(' '),
- nameElem = $(' '),
- valueElem = $(' '),
- settingsElem = $('
'),
- visibilityIcon = config.isGraphFieldHidden(i, j) ? "glyphicon-eye-close" : "glyphicon-eye-open",
- visibilityElem = $(' ');
- li.append(nameElem);
- li.append(visibilityElem);
- li.append(valueElem);
- li.append(settingsElem);
-
- nameElem.text(field.friendlyName);
- settingsElem.text(" ");
- settingsElem.css('background', field.color);
- fieldList.append(li);
- }
-
- targetElem.append(graphDiv);
- }
-
- // Add a trigger on legend; highlight the hovered field in plot
- $('.graph-legend-field').on('mouseenter', function(e){
- $(this).addClass("highlight")
- config.highlightGraphIndex = $(this).attr('graph');
- config.highlightFieldIndex = $(this).attr('field');
- if (onHighlightChange) {
- onHighlightChange();
- }
- });
-
- $('.graph-legend-field').on('mouseleave', function(e){
- $(this).removeClass("highlight")
- config.highlightGraphIndex = null;
- config.highlightFieldIndex = null;
- if (onHighlightChange) {
- onHighlightChange();
- }
- });
-
- // Add a trigger on legend; select the analyser graph/field to plot
- $('.graph-legend-field-name, .graph-legend-field-settings, .graph-legend-field-value').on('click', function (e) {
-
- if(e.which!=1) return; // only accept left mouse clicks
-
- var
- selectedGraphIndex = $(this).attr('graph'),
- selectedFieldIndex = $(this).attr('field');
-
- if(!e.altKey) {
- config.selectedFieldName = config.getGraphs()[selectedGraphIndex].fields[selectedFieldIndex].friendlyName;
- config.selectedGraphIndex = selectedGraphIndex;
- config.selectedFieldIndex = selectedFieldIndex;
- if (onNewSelectionChange) {
- onNewSelectionChange();
- }
- } else { // toggle the grid setting
- var graphs = config.getGraphs();
- for(var i=0; i `
+ ),
+ graphTitle = $("h3", graphDiv),
+ fieldList = $("ul", graphDiv);
+
+ graphTitle.text(graph.label);
+ graphTitle.prepend('