From 4d98eefe36b44f1e2763e2c4d070f3ca4871cd2e Mon Sep 17 00:00:00 2001 From: Vladimir Demidov Date: Tue, 5 Mar 2024 23:57:43 +0300 Subject: [PATCH] Actual log rate computing and bad rpm values filtration for spectrum chart. (#711) * added actual log rate computing * added: the warning is showed if actual and config log rate has big difference * code style improvement * Update js/graph_spectrum.js Co-authored-by: nerdCopter <56646290+nerdCopter@users.noreply.github.com> * Update js/graph_spectrum_plot.js * added checking of RPM values in the spectrum charts * Update js/graph_spectrum.js The code style imprivement Co-authored-by: Mark Haslinghuis * Update js/graph_spectrum_calc.js Code style improvement Co-authored-by: Mark Haslinghuis * Update js/graph_spectrum_plot.js * Update js/graph_spectrum_calc.js --------- Co-authored-by: Mark Haslinghuis Co-authored-by: nerdCopter <56646290+nerdCopter@users.noreply.github.com> --- js/graph_config_dialog.js | 178 +++++++++++++++++++------------------- js/graph_spectrum.js | 17 ++-- js/graph_spectrum_calc.js | 58 ++++++++++--- js/graph_spectrum_plot.js | 29 +++++++ 4 files changed, 171 insertions(+), 111 deletions(-) diff --git a/js/graph_config_dialog.js b/js/graph_config_dialog.js index e76c14e6..1b135f72 100644 --- a/js/graph_config_dialog.js +++ b/js/graph_config_dialog.js @@ -7,41 +7,41 @@ function GraphConfigurationDialog(dialog, onSave) { 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; + 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; } - + 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; + 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; } // Show/Hide remove all button @@ -67,18 +67,18 @@ function GraphConfigurationDialog(dialog, onSave) { } function renderFieldOption(fieldName, selectedName) { - var + var option = $("") .text(FlightLogFieldPresenter.fieldNameToFriendly(fieldName, activeFlightLog.getSysConfig().debug_mode)) .attr("value", fieldName); - + if (fieldName == selectedName) { option.attr("selected", "selected"); } - + return option; } - + // Set the current smoothing options for a field function renderSmoothingOptions(elem, flightLog, field) { if(elem) { @@ -100,7 +100,7 @@ function GraphConfigurationDialog(dialog, onSave) { * initial selection. */ function renderField(flightLog, field, color) { - var + var elem = $( '' + '' @@ -116,11 +116,11 @@ function GraphConfigurationDialog(dialog, onSave) { select = $('select.form-control', elem), selectedFieldName = field ?field.name : false, i; - + for (i = 0; i < offeredFieldNames.length; i++) { select.append(renderFieldOption(offeredFieldNames[i], selectedFieldName)); } - + // Set the smoothing values renderSmoothingOptions(elem, flightLog, field); @@ -132,7 +132,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() { @@ -147,13 +147,13 @@ function GraphConfigurationDialog(dialog, onSave) { $(this).css('background', $('select.color-picker option:selected', elem).val()) .css('color', $('select.color-picker option:selected', elem).val()); }); - + return elem; } - + function renderGraph(flightLog, index, graph) { - var + var graphElem = $( '
  • ' + '
    ' @@ -201,9 +201,9 @@ function GraphConfigurationDialog(dialog, onSave) { + '
  • ' ), fieldList = $(".config-graph-field-list", graphElem); - + $("input", graphElem).val(graph.label); - + var fieldCount = graph.fields.length; // "Add field" button @@ -221,23 +221,23 @@ 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)); - + fieldList.append(fieldElem); } - + 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(); @@ -248,67 +248,67 @@ function GraphConfigurationDialog(dialog, onSave) { }); updateRemoveAllButton(); - + return graphElem; } - + function renderGraphs(flightLog, graphs) { var graphList = $(".config-graphs-list", dialog); - + graphList.empty(); - + for (var i = 0; i < graphs.length; i++) { graphList.append(renderGraph(flightLog, i, graphs[i])); } } - + function populateExampleGraphs(flightLog, menu) { var i; - + menu.empty(); - + exampleGraphs = GraphConfig.getExampleGraphConfigs(flightLog); - + exampleGraphs.unshift({ label: "Custom graph", 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('
  • '); } } } - + function convertUIToGraphConfig() { - var + 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(), @@ -326,7 +326,7 @@ function GraphConfigurationDialog(dialog, onSave) { lineWidth: parseInt($("input[name=linewidth]", this).val()), grid: $('input[name=grid]', this).is(':checked'), }; - + if (field.name.length > 0) { graph.fields.push(field); } @@ -334,7 +334,7 @@ function GraphConfigurationDialog(dialog, onSave) { graphs.push(graph); }); - + return graphs; } @@ -345,64 +345,64 @@ function GraphConfigurationDialog(dialog, onSave) { 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 + 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; } - - /* + + /* * 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 + 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); } } } } - + this.show = function(flightLog, config) { dialog.modal('show'); - + activeFlightLog = flightLog; - + buildOfferedFieldNamesList(flightLog, config); populateExampleGraphs(flightLog, exampleGraphsMenu); renderGraphs(flightLog, config); }; - + $(".graph-configuration-dialog-save").click(function() { onSave(convertUIToGraphConfig()); }); @@ -424,16 +424,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(); + } +};