diff --git a/js/flightlog.js b/js/flightlog.js index 19544c28..b8e72f6b 100644 --- a/js/flightlog.js +++ b/js/flightlog.js @@ -209,7 +209,7 @@ function FlightLog(logData) { } else return false; }; - + function buildFieldNames() { // Make an independent copy fieldNames = parser.frameDefs.I.name.slice(0); @@ -239,7 +239,7 @@ function FlightLog(logData) { 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)) { + if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) { fieldNames.push("axisError[0]", "axisError[1]", "axisError[2]"); // Custom calculated error field } @@ -385,8 +385,8 @@ function FlightLog(logData) { 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 + // 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) { @@ -419,7 +419,7 @@ function FlightLog(logData) { 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]; @@ -617,7 +617,7 @@ function FlightLog(logData) { destFrame = destChunk.frames[i], fieldIndex = destFrame.length - ADDITIONAL_COMPUTED_FIELD_COUNT; - if (gyroADC) { //don't calculate attitude if no gyro data + 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]]], @@ -632,7 +632,7 @@ function FlightLog(logData) { } // Add the Feedforward PID sum (P+I+D+F) - if (axisPID) { + 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) + @@ -647,7 +647,7 @@ function FlightLog(logData) { } // Assign value - destFrame[fieldIndex++] = pidSum; + destFrame[fieldIndex++] = pidSum; } } @@ -657,29 +657,31 @@ function FlightLog(logData) { // Calculate the Scaled rcCommand (setpoint) (in deg/s, % for throttle) var fieldIndexRcCommands = fieldIndex; - // 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 (var axis = 0; axis <= AXIS.YAW; axis++) { - destFrame[fieldIndex++] = srcFrame[setpoint[axis]]; - } - // Throttle - destFrame[fieldIndex++] = srcFrame[setpoint[AXIS.YAW + 1]]/10; + if (!that.isFieldDisabled().SETPOINT) { - // Versions earlier to 4.0 we must calculate the expected setpoint - } else { - // Roll, pitch and yaw - for (var axis = 0; axis <= AXIS.YAW; axis++) { + // 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] !== 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); + (rcCommand[AXIS.YAW + 1] !== undefined ? that.rcCommandRawToThrottle(srcFrame[rcCommand[AXIS.YAW + 1]]) : 0); + } } // Calculate the PID Error - if (axisPID && gyroADC) { + 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; @@ -1131,7 +1133,7 @@ FlightLog.prototype.rcCommandRawToDegreesPerSecond = function(value, axis, curre } var rcRate = sysConfig["rc_rates"][axis] / 100.0; - if (rcRate > 2.0) { + if (rcRate > 2.0) { rcRate += RC_RATE_INCREMENTAL * (rcRate - 2.0); } diff --git a/js/flightlog_fields_presenter.js b/js/flightlog_fields_presenter.js index a8980de9..b40212ac 100644 --- a/js/flightlog_fields_presenter.js +++ b/js/flightlog_fields_presenter.js @@ -911,7 +911,7 @@ function FlightLogFieldPresenter() { '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]':'Not Used', + 'debug[7]':'Gyro cycle deviation in 100th of a us', }, 'TIMING_ACCURACY' : { 'debug[all]':'Timing Accuracy', @@ -1897,6 +1897,8 @@ function FlightLogFieldPresenter() { return value.toFixed(0); case 'DSHOT_TELEMETRY_COUNTS': return value.toFixed(0); + case 'EZLANDING': + return `${(value / 100.0).toFixed(2)} %`; } return value.toFixed(0); } diff --git a/js/graph_config.js b/js/graph_config.js index 67d1b4b7..a8c938d4 100644 --- a/js/graph_config.js +++ b/js/graph_config.js @@ -1274,6 +1274,13 @@ TODO - The stats data have small issues of min-max data !!!! default: return getCurveForMinMaxFields(fieldName); } + case 'EZLANDING': + return { + offset: -5000, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; } } // if not found above then diff --git a/js/graph_config_dialog.js b/js/graph_config_dialog.js index d8b8d100..1d8b9a4a 100644 --- a/js/graph_config_dialog.js +++ b/js/graph_config_dialog.js @@ -11,7 +11,6 @@ function GraphConfigurationDialog(dialog, onSave) { prevCfg = null, cfgMustBeRestored = false; - function chooseColor(currentSelection) { const selectColor = $(''); for(let i=0; i") .text(FlightLogFieldPresenter.fieldNameToFriendly(fieldName, activeFlightLog.getSysConfig().debug_mode)) .attr("value", fieldName); @@ -108,12 +107,12 @@ function GraphConfigurationDialog(dialog, onSave) { } } - /** + /* * 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 + var elem = $( '' + '' @@ -146,7 +145,7 @@ function GraphConfigurationDialog(dialog, onSave) { //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() { @@ -764,7 +763,7 @@ function GraphConfigurationDialog(dialog, onSave) { } function renderGraph(flightLog, index, graph) { - var + var graphElem = $( '
  • ' + '
    ' @@ -844,11 +843,11 @@ function GraphConfigurationDialog(dialog, onSave) { }); //Populate the Height seletor - $('select.graph-height', graphElem).replaceWith(chooseHeight(graph.height?(graph.height):1)); + $('select.graph-height', graphElem).replaceWith(chooseHeight(graph.height?(graph.height):1)); // Add Field List for (var i = 0; i < graph.fields.length; i++) { - var + var field = graph.fields[i], fieldElem = renderField(flightLog, field, field.color?(field.color):(GraphConfig.PALETTE[i].color)); @@ -858,9 +857,9 @@ function GraphConfigurationDialog(dialog, onSave) { fieldList.on('click', 'button', function(e) { var parentGraph = $(this).parents('.config-graph'); - + $(this).parents('.config-graph-field').remove(); - + // Remove the graph upon removal of the last field if ($(".config-graph-field", parentGraph).length === 0) { parentGraph.remove(); @@ -874,7 +873,7 @@ function GraphConfigurationDialog(dialog, onSave) { return graphElem; } - + function renderGraphs(flightLog, graphs) { var graphList = $(".config-graphs-list", dialog); @@ -889,7 +888,7 @@ function GraphConfigurationDialog(dialog, onSave) { function populateExampleGraphs(flightLog, menu) { var i; - + menu.empty(); exampleGraphs = GraphConfig.getExampleGraphConfigs(flightLog); @@ -899,18 +898,18 @@ function GraphConfigurationDialog(dialog, onSave) { fields: [{name:""}], dividerAfter: true }); - + for (i = 0; i < exampleGraphs.length; i++) { - var + var graph = exampleGraphs[i], li = $('
  • '); - + $('a', li) .text(graph.label) .data('graphIndex', i); menu.append(li); - + if (graph.dividerAfter) { menu.append('
  • '); } @@ -918,7 +917,7 @@ function GraphConfigurationDialog(dialog, onSave) { } function convertUIToGraphConfig() { - var + var graphs = [], graph, field; @@ -983,7 +982,7 @@ function GraphConfigurationDialog(dialog, onSave) { for (i = 0; i < fieldNames.length; i++) { // For fields with multiple bracketed x[0], x[1] versions, add an "[all]" option - var + var fieldName = fieldNames[i], matches = fieldName.match(/^(.+)\[[0-9]+\]$/); @@ -993,7 +992,7 @@ function GraphConfigurationDialog(dialog, onSave) { if (matches) { if (matches[1] != lastRoot) { lastRoot = matches[1]; - + offeredFieldNames.push(lastRoot + "[all]"); fieldsSeen[lastRoot + "[all]"] = true; } @@ -1011,13 +1010,13 @@ function GraphConfigurationDialog(dialog, onSave) { * keep that tail servo in the config when we're viewing a quadcopter). */ for (i = 0; i < config.length; i++) { - var + var graph = config[i]; - + for (j = 0; j < graph.fields.length; j++) { - var + var field = graph.fields[j]; - + if (!fieldsSeen[field.name]) { offeredFieldNames.push(field.name); } @@ -1043,7 +1042,7 @@ function GraphConfigurationDialog(dialog, onSave) { if (cfgMustBeRestored) onSave(prevCfg); }); - + $(".graph-configuration-dialog-save").click(function() { cfgMustBeRestored = false; onSave(convertUIToGraphConfig()); @@ -1075,16 +1074,16 @@ function GraphConfigurationDialog(dialog, onSave) { exampleGraphsButton.dropdown(); exampleGraphsMenu.on("click", "a", function(e) { - var + var 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"); - + e.preventDefault(); }); diff --git a/js/graph_spectrum.js b/js/graph_spectrum.js index dc80996e..59a88a92 100644 --- a/js/graph_spectrum.js +++ b/js/graph_spectrum.js @@ -8,7 +8,7 @@ const ANALYSER_LARGE_HEIGHT_MARGIN = 20, ANALYSER_LARGE_WIDTH_MARGIN = 20; -var +var that = this, analyserZoomX = 1.0, /* 100% */ @@ -31,9 +31,9 @@ var var isFullscreen = false; var sysConfig = flightLog.getSysConfig(); - GraphSpectrumCalc.initialize(flightLog, sysConfig); + const logRateInfo = GraphSpectrumCalc.initialize(flightLog, sysConfig); GraphSpectrumPlot.initialize(analyserCanvas, sysConfig); - + GraphSpectrumPlot.setLogRateWarningInfo(logRateInfo); var analyserZoomXElem = $("#analyserZoomX"); var analyserZoomYElem = $("#analyserZoomY"); @@ -75,7 +75,7 @@ var } }; - this.resize = function() { + this.resize = function() { var newSize = getSize(); @@ -125,13 +125,12 @@ var fftData = GraphSpectrumCalc.dataLoadFrequency(); break; } - }; /* This function is called from the canvas drawing routines within grapher.js - It is only used to record the current curve positions, collect the data and draw the + It is only used to record the current curve positions, collect the data and draw the analyser on screen*/ - this.plotSpectrum = function (fieldIndex, curve, fieldName) { + this.plotSpectrum = function (fieldIndex, curve, fieldName) { // Store the data pointers dataBuffer = { fieldIndex: fieldIndex, @@ -142,7 +141,7 @@ var // Detect change of selected field.... reload and redraw required. if ((fftData == null) || (fieldIndex != fftData.fieldIndex) || dataReload) { dataReload = false; - dataLoad(); + dataLoad(); GraphSpectrumPlot.setData(fftData, userSettings.spectrumType); } @@ -231,7 +230,7 @@ var // track frequency under mouse var lastMouseX = 0, lastMouseY = 0; - + function trackFrequency(e, analyser) { if(e.shiftKey) { diff --git a/js/graph_spectrum_calc.js b/js/graph_spectrum_calc.js index 65adbcf9..c772319e 100644 --- a/js/graph_spectrum_calc.js +++ b/js/graph_spectrum_calc.js @@ -6,10 +6,12 @@ const FREQ_VS_THR_CHUNK_TIME_MS = 300, FREQ_VS_THR_WINDOW_DIVISOR = 6, MAX_ANALYSER_LENGTH = 300 * 1000 * 1000, // 5min - NUM_VS_BINS = 100; + NUM_VS_BINS = 100, + WARNING_RATE_DIFFERENCE = 0.05, + MAX_RPM_VALUE = 10000; var GraphSpectrumCalc = GraphSpectrumCalc || { - _analyserTimeRange : { + _analyserTimeRange : { in: 0, out: MAX_ANALYSER_LENGTH }, @@ -25,7 +27,7 @@ var GraphSpectrumCalc = GraphSpectrumCalc || { GraphSpectrumCalc.initialize = function(flightLog, sysConfig) { - this._flightLog = flightLog; + this._flightLog = flightLog; this._sysConfig = sysConfig; var gyroRate = (1000000 / this._sysConfig['looptime']).toFixed(0); @@ -33,6 +35,29 @@ GraphSpectrumCalc.initialize = function(flightLog, sysConfig) { if (this._sysConfig.pid_process_denom != null) { this._blackBoxRate = this._blackBoxRate / this._sysConfig.pid_process_denom; } + this._BetaflightRate = this._blackBoxRate; + + let minTime = this._flightLog.getMinTime(), + maxTime = this._flightLog.getMaxTime(); + let timeRange = maxTime - minTime; + if (timeRange > MAX_ANALYSER_LENGTH) { + maxTime = minTime + MAX_ANALYSER_LENGTH; + timeRange = MAX_ANALYSER_LENGTH; + } + const allChunks = this._flightLog.getChunksInTimeRange(minTime, maxTime); + const length = allChunks.reduce((acc, chunk) => acc + chunk.frames.length, 0); + this._actualeRate = 1e6 * length / timeRange; + + if (Math.abs(this._BetaflightRate - this._actualeRate) / this._actualeRate > WARNING_RATE_DIFFERENCE) + this._blackBoxRate = Math.round(this._actualeRate); + + if (this._BetaflightRate !== this._blackBoxRate) { + return { + actualRate: this._actualeRate, + betaflightRate: this._BetaflightRate, + }; + } + return undefined; }; GraphSpectrumCalc.setInTime = function(time) { @@ -51,6 +76,7 @@ GraphSpectrumCalc.setOutTime = function(time) { GraphSpectrumCalc.setDataBuffer = function(dataBuffer) { this._dataBuffer = dataBuffer; + return undefined; }; GraphSpectrumCalc.dataLoadFrequency = function() { @@ -75,8 +101,8 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi let flightSamples = this._getFlightSamplesFreqVsX(vsFieldNames, minValue, maxValue); - // We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle - // for each chunk. We use a moving window to get more chunks available. + // We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle + // for each chunk. We use a moving window to get more chunks available. var fftChunkLength = this._blackBoxRate * FREQ_VS_THR_CHUNK_TIME_MS / 1000; var fftChunkWindow = Math.round(fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR); @@ -88,10 +114,10 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi var fft = new FFT.complex(fftChunkLength, false); for (var fftChunkIndex = 0; fftChunkIndex + fftChunkLength < flightSamples.samples.length; fftChunkIndex += fftChunkWindow) { - + let fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength); let fftOutput = new Float64Array(fftChunkLength * 2); - + // Hanning window applied to input data if(userSettings.analyserHanning) { this._hanningWindow(fftInput, fftChunkLength); @@ -137,8 +163,8 @@ GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infi } } - // The output data needs to be smoothed, the sampling is not perfect - // but after some tests we let the data as is, an we prefer to apply a + // The output data needs to be smoothed, the sampling is not perfect + // but after some tests we let the data as is, an we prefer to apply a // blur algorithm to the heat map image var fftData = { @@ -231,13 +257,13 @@ GraphSpectrumCalc._getFlightChunks = function() { logStart = this._analyserTimeRange.in; } else { logStart = this._flightLog.getMinTime(); - } + } - var logEnd = 0; + var logEnd = 0; if(this._analyserTimeRange.out) { logEnd = this._analyserTimeRange.out; } else { - logEnd = this._flightLog.getMaxTime(); + logEnd = this._flightLog.getMaxTime(); } // Limit size @@ -289,6 +315,7 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I let vsValues = new Array(vsIndexes.length).fill(null).map(() => new Float64Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate)); var samplesCount = 0; + let lastRPM = 0; for (var chunkIndex = 0; chunkIndex < allChunks.length; chunkIndex++) { var chunk = allChunks[chunkIndex]; for (var frameIndex = 0; frameIndex < chunk.frames.length; frameIndex++) { @@ -297,6 +324,11 @@ GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = I for (let i = 0; i < vsIndexes.length; i++) { let vsFieldIx = vsIndexes[i]; let value = chunk.frames[frameIndex][vsFieldIx]; + if (vsFieldNames == FIELD_RPM_NAMES) { + if (value > MAX_RPM_VALUE || value < 0) + value = lastRPM; + lastRPM = value; + } maxValue = Math.max(maxValue, value); minValue = Math.min(minValue, value); vsValues[i][samplesCount] = value; @@ -405,7 +437,7 @@ GraphSpectrumCalc._normalizeFft = function(fftOutput, fftLength) { var noiseLowEndIdx = 100 / maxFrequency * fftLength; var maxNoiseIdx = 0; var maxNoise = 0; - + for (var i = 0; i < fftLength; i++) { fftOutput[i] = Math.abs(fftOutput[i]); if (i > noiseLowEndIdx && fftOutput[i] > maxNoise) { diff --git a/js/graph_spectrum_plot.js b/js/graph_spectrum_plot.js index 2bea4faa..da411f86 100644 --- a/js/graph_spectrum_plot.js +++ b/js/graph_spectrum_plot.js @@ -53,6 +53,7 @@ GraphSpectrumPlot.initialize = function(canvas, sysConfig) { this._sysConfig = sysConfig; this._invalidateCache(); this._invalidateDataCache(); + this._logRateWarning = undefined; }; GraphSpectrumPlot.setZoom = function(zoomX, zoomY) { @@ -462,6 +463,8 @@ GraphSpectrumPlot._drawFiltersAndMarkers = function(canvasCtx) { offset++; } + this._drawRateWarning(canvasCtx); + }; GraphSpectrumPlot._drawNotCachedElements = function() { @@ -855,3 +858,29 @@ GraphSpectrumPlot._invalidateCache = function() { GraphSpectrumPlot._invalidateDataCache = function() { this._cachedDataCanvas = null; }; + +GraphSpectrumPlot.setLogRateWarningInfo = function(logRateInfo) { + this._logRateWarning = logRateInfo; +}; + +GraphSpectrumPlot._drawRateWarning = function(canvasCtx) { + if (this._logRateWarning != undefined) { + canvasCtx.save(); + + canvasCtx.font = `${((this._isFullScreen)? this._drawingParams.fontSizeFrameLabelFullscreen : this._drawingParams.fontSizeFrameLabel)}pt ${DEFAULT_FONT_FACE}`; + canvasCtx.fillStyle = 'orange'; + canvasCtx.textAlign = 'center'; + canvasCtx.shadowColor = 'black'; + canvasCtx.strokeStyle = 'black'; + + const actualRate = this._logRateWarning.actualRate.toFixed(0), + betaflightRate = this._logRateWarning.betaflightRate.toFixed(0); + const WarningText = "THE ACTUAL AND CONFIG LOG DATA RATE DIFFERENCE: " + actualRate + " : " + betaflightRate; + const X = canvasCtx.canvas.width/2, + Y = canvasCtx.canvas.height/12; + canvasCtx.strokeText(WarningText, X, Y); + canvasCtx.fillText(WarningText, X, Y); + + canvasCtx.restore(); + } +}; diff --git a/js/header_dialog.js b/js/header_dialog.js index 3a9e73ef..88e96eeb 100644 --- a/js/header_dialog.js +++ b/js/header_dialog.js @@ -465,7 +465,7 @@ function HeaderDialog(dialog, onSave) { {name: 'Unfiltered Gyroscope', description: 'Unfiltered gyro data'}, ]; - const fieldsList_e = $('.fields_list'); + const fieldsList_e = $('.fields_list').empty(); for (let i = 0; i < fields.length; i++) { const row_e = $(`