Skip to content

Commit 931607e

Browse files
authored
Add a RPM vs frequency spectrum plot (#668)
* Add frequency vs rpm spectrum * Make vs spectrum scaling more flexible * Render vs values individually in vs spectrum, instead of averaging * refactor graph_spectrum_calc.js
1 parent 9771888 commit 931607e

File tree

4 files changed

+139
-63
lines changed

4 files changed

+139
-63
lines changed

index.html

+1
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ <h4>Workspace</h4>
468468
<select id="spectrumTypeSelect">
469469
<option value="0">Frequency</option>
470470
<option value="1">Freq. vs Throttle</option>
471+
<option value="3">Freq. vs RPM</option>
471472
<option value="2">Error vs Setpoint</option>
472473
</select>
473474
</div>

js/graph_spectrum.js

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ var
112112
fftData = GraphSpectrumCalc.dataLoadFrequencyVsThrottle();
113113
break;
114114

115+
case SPECTRUM_TYPE.FREQ_VS_RPM:
116+
fftData = GraphSpectrumCalc.dataLoadFrequencyVsRpm();
117+
break;
118+
115119
case SPECTRUM_TYPE.PIDERROR_VS_SETPOINT:
116120
fftData = GraphSpectrumCalc.dataLoadPidErrorVsSetpoint();
117121
break;

js/graph_spectrum_calc.js

+91-44
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"use strict";
22

33
const
4-
FIELD_THROTTLE_NAME = 'rcCommands[3]',
4+
FIELD_THROTTLE_NAME = ['rcCommands[3]'],
5+
FIELD_RPM_NAMES = ['eRPM[0]', 'eRPM[1]', 'eRPM[2]', 'eRPM[3]', 'eRPM[4]', 'eRPM[5]', 'eRPM[6]', 'eRPM[7]'],
56
FREQ_VS_THR_CHUNK_TIME_MS = 300,
67
FREQ_VS_THR_WINDOW_DIVISOR = 6,
78
MAX_ANALYSER_LENGTH = 300 * 1000 * 1000, // 5min
8-
THROTTLE_VALUES = 100;
9+
NUM_VS_BINS = 100;
910

1011
var GraphSpectrumCalc = GraphSpectrumCalc || {
1112
_analyserTimeRange : {
@@ -69,24 +70,27 @@ GraphSpectrumCalc.dataLoadFrequency = function() {
6970
return fftData;
7071
};
7172

72-
GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() {
7373

74-
var flightSamples = this._getFlightSamplesFreqVsThrottle();
74+
GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) {
75+
76+
let flightSamples = this._getFlightSamplesFreqVsX(vsFieldNames, minValue, maxValue);
7577

7678
// We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle
7779
// for each chunk. We use a moving window to get more chunks available.
7880
var fftChunkLength = this._blackBoxRate * FREQ_VS_THR_CHUNK_TIME_MS / 1000;
7981
var fftChunkWindow = Math.round(fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR);
8082

81-
var maxNoiseThrottle = 0; // Stores the max noise produced
82-
var matrixFftOutput = new Array(THROTTLE_VALUES); // One for each throttle value, without decimal part
83-
var numberSamplesThrottle = new Uint32Array(THROTTLE_VALUES); // Number of samples in each throttle value, used to average them later.
83+
let maxNoise = 0; // Stores the maximum amplitude of the fft over all chunks
84+
// Matrix where each row represents a bin of vs values, and the columns are amplitudes at frequencies
85+
let matrixFftOutput = new Array(NUM_VS_BINS).fill(null).map(() => new Float64Array(fftChunkLength * 2));
86+
87+
let numberSamples = new Uint32Array(NUM_VS_BINS); // Number of samples in each vs value, used to average them later.
8488

8589
var fft = new FFT.complex(fftChunkLength, false);
8690
for (var fftChunkIndex = 0; fftChunkIndex + fftChunkLength < flightSamples.samples.length; fftChunkIndex += fftChunkWindow) {
8791

88-
var fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
89-
var fftOutput = new Float64Array(fftChunkLength * 2);
92+
let fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength);
93+
let fftOutput = new Float64Array(fftChunkLength * 2);
9094

9195
// Hanning window applied to input data
9296
if(userSettings.analyserHanning) {
@@ -98,39 +102,36 @@ GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() {
98102
fftOutput = fftOutput.slice(0, fftChunkLength);
99103

100104
// Use only abs values
101-
for (var i = 0; i < fftChunkLength; i++) {
105+
for (let i = 0; i < fftChunkLength; i++) {
102106
fftOutput[i] = Math.abs(fftOutput[i]);
103-
if (fftOutput[i] > maxNoiseThrottle) {
104-
maxNoiseThrottle = fftOutput[i];
105-
}
107+
maxNoise = Math.max(fftOutput[i], maxNoise);
106108
}
107109

108-
// Calculate average throttle
109-
var avgThrottle = 0;
110-
for (var indexThrottle = fftChunkIndex; indexThrottle < fftChunkIndex + fftChunkLength; indexThrottle++) {
111-
avgThrottle += flightSamples.throttle[indexThrottle];
112-
}
113-
// Average throttle, removing the decimal part
114-
avgThrottle = Math.round(avgThrottle / 10 / fftChunkLength);
115-
116-
numberSamplesThrottle[avgThrottle]++;
117-
if (!matrixFftOutput[avgThrottle]) {
118-
matrixFftOutput[avgThrottle] = fftOutput;
119-
} else {
120-
matrixFftOutput[avgThrottle] = matrixFftOutput[avgThrottle].map(function (num, idx) {
121-
return num + fftOutput[idx];
122-
});
110+
// calculate a bin index and put the fft value in that bin for each field (e.g. eRPM[0], eRPM[1]..) sepparately
111+
for (const vsValueArray of flightSamples.vsValues) {
112+
// Calculate average of the VS values in the chunk
113+
let sumVsValues = 0;
114+
for (let indexVs = fftChunkIndex; indexVs < fftChunkIndex + fftChunkLength; indexVs++) {
115+
sumVsValues += vsValueArray[indexVs];
116+
}
117+
// Translate the average vs value to a bin index
118+
const avgVsValue = sumVsValues / fftChunkLength;
119+
const vsBinIndex = Math.floor(NUM_VS_BINS * (avgVsValue - flightSamples.minValue) / (flightSamples.maxValue - flightSamples.minValue));
120+
numberSamples[vsBinIndex]++;
121+
122+
// add the output from the fft to the row given by the vs value bin index
123+
for (let i = 0; i < fftOutput.length; i++) {
124+
matrixFftOutput[vsBinIndex][i] += fftOutput[i];
125+
}
123126
}
124127
}
125128

126-
// Divide by the number of samples
127-
for (var i = 0; i < THROTTLE_VALUES; i++) {
128-
if (numberSamplesThrottle[i] > 1) {
129+
// Divide the values from the fft in each row (vs value bin) by the number of samples in the bin
130+
for (let i = 0; i < NUM_VS_BINS; i++) {
131+
if (numberSamples[i] > 1) {
129132
for (var j = 0; j < matrixFftOutput[i].length; j++) {
130-
matrixFftOutput[i][j] /= numberSamplesThrottle[i];
133+
matrixFftOutput[i][j] /= numberSamples[i];
131134
}
132-
} else if (numberSamplesThrottle[i] == 0) {
133-
matrixFftOutput[i] = new Float64Array(fftChunkLength * 2);
134135
}
135136
}
136137

@@ -143,14 +144,27 @@ GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() {
143144
fieldName : this._dataBuffer.fieldName,
144145
fftLength : fftChunkLength,
145146
fftOutput : matrixFftOutput,
146-
maxNoise : maxNoiseThrottle,
147+
maxNoise : maxNoise,
147148
blackBoxRate : this._blackBoxRate,
149+
vsRange : { min: flightSamples.minValue, max: flightSamples.maxValue},
148150
};
149151

150152
return fftData;
151153

152154
};
153155

156+
GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() {
157+
return this._dataLoadFrequencyVsX(FIELD_THROTTLE_NAME, 0, 100);
158+
};
159+
160+
GraphSpectrumCalc.dataLoadFrequencyVsRpm = function() {
161+
let fftData = this._dataLoadFrequencyVsX(FIELD_RPM_NAMES, 0);
162+
const motorPoles = this._flightLog.getSysConfig()['motor_poles'];
163+
fftData.vsRange.max *= 3.333 / motorPoles;
164+
fftData.vsRange.min *= 3.333 / motorPoles;
165+
return fftData;
166+
};
167+
154168
GraphSpectrumCalc.dataLoadPidErrorVsSetpoint = function() {
155169

156170
// Detect the axis
@@ -254,30 +268,63 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() {
254268
};
255269
};
256270

257-
GraphSpectrumCalc._getFlightSamplesFreqVsThrottle = function() {
271+
GraphSpectrumCalc._getVsIndexes = function(vsFieldNames) {
272+
let fieldIndexes = [];
273+
for (const fieldName of vsFieldNames) {
274+
if (Object.hasOwn(this._flightLog.getMainFieldIndexes(), fieldName)) {
275+
fieldIndexes.push(this._flightLog.getMainFieldIndexByName(fieldName));
276+
}
277+
}
278+
return fieldIndexes;
279+
};
280+
281+
GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) {
258282

259283
var allChunks = this._getFlightChunks();
284+
let vsIndexes = this._getVsIndexes(vsFieldNames);
260285

261286
var samples = new Float64Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate);
262-
var throttle = new Uint16Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate);
287+
let vsValues = new Array(vsIndexes.length).fill(null).map(() => new Float64Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate));
263288

264-
const FIELD_THROTTLE_INDEX = this._flightLog.getMainFieldIndexByName(FIELD_THROTTLE_NAME);
265-
266-
// Loop through all the samples in the chunks and assign them to a sample array ready to pass to the FFT.
267289
var samplesCount = 0;
268290
for (var chunkIndex = 0; chunkIndex < allChunks.length; chunkIndex++) {
269291
var chunk = allChunks[chunkIndex];
270292
for (var frameIndex = 0; frameIndex < chunk.frames.length; frameIndex++) {
271293
samples[samplesCount] = (this._dataBuffer.curve.lookupRaw(chunk.frames[frameIndex][this._dataBuffer.fieldIndex]));
272-
throttle[samplesCount] = chunk.frames[frameIndex][FIELD_THROTTLE_INDEX]*10;
294+
295+
for (let i = 0; i < vsIndexes.length; i++) {
296+
let vsFieldIx = vsIndexes[i];
297+
let value = chunk.frames[frameIndex][vsFieldIx];
298+
maxValue = Math.max(maxValue, value);
299+
minValue = Math.min(minValue, value);
300+
vsValues[i][samplesCount] = value;
301+
}
273302
samplesCount++;
274303
}
275304
}
276305

306+
if (minValue > maxValue) {
307+
if (minValue == Infinity) { // this should never happen
308+
minValue = 0;
309+
maxValue = 100;
310+
console.log("Invalid minimum value");
311+
} else {
312+
console.log("Maximum value %f smaller than minimum value %d", maxValue, minValue);
313+
minValue = 0;
314+
maxValue = 100;
315+
}
316+
}
317+
318+
let slicedVsValues = [];
319+
for (const vsValueArray of vsValues) {
320+
slicedVsValues.push(vsValueArray.slice(0, samplesCount));
321+
}
277322
return {
278-
samples : samples,
279-
throttle : throttle,
280-
count : samplesCount
323+
samples : samples.slice(0, samplesCount),
324+
vsValues : slicedVsValues,
325+
count : samplesCount,
326+
minValue : minValue,
327+
maxValue : maxValue,
281328
};
282329
};
283330

0 commit comments

Comments
 (0)