From 3a6b153d5536d032b07f5a58a4e3bcd3222c2d80 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Fri, 17 Jun 2022 21:49:31 +1000 Subject: [PATCH] Tools: added filter tool to web-firmware makes it easier others to PR changes to improve the tool --- .../Tools/FilterTool/FileSaver.js | 171 ++++++ .../web-firmware/Tools/FilterTool/filters.js | 571 ++++++++++++++++++ .../web-firmware/Tools/FilterTool/index.html | 180 ++++++ Tools/autotest/web-firmware/images/filter.png | Bin 0 -> 29215 bytes Tools/autotest/web-firmware/index.html | 2 + 5 files changed, 924 insertions(+) create mode 100644 Tools/autotest/web-firmware/Tools/FilterTool/FileSaver.js create mode 100644 Tools/autotest/web-firmware/Tools/FilterTool/filters.js create mode 100644 Tools/autotest/web-firmware/Tools/FilterTool/index.html create mode 100644 Tools/autotest/web-firmware/images/filter.png diff --git a/Tools/autotest/web-firmware/Tools/FilterTool/FileSaver.js b/Tools/autotest/web-firmware/Tools/FilterTool/FileSaver.js new file mode 100644 index 0000000000..5d204aee15 --- /dev/null +++ b/Tools/autotest/web-firmware/Tools/FilterTool/FileSaver.js @@ -0,0 +1,171 @@ +/* +* FileSaver.js +* A saveAs() FileSaver implementation. +* +* By Eli Grey, http://eligrey.com +* +* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT) +* source : http://purl.eligrey.com/github/FileSaver.js +*/ + +// The one and only way of getting global scope in all environments +// https://stackoverflow.com/q/3277182/1008999 +var _global = typeof window === 'object' && window.window === window + ? window : typeof self === 'object' && self.self === self + ? self : typeof global === 'object' && global.global === global + ? global + : this + +function bom (blob, opts) { + if (typeof opts === 'undefined') opts = { autoBom: false } + else if (typeof opts !== 'object') { + console.warn('Deprecated: Expected third argument to be a object') + opts = { autoBom: !opts } + } + + // prepend BOM for UTF-8 XML and text/* types (including HTML) + // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF + if (opts.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) { + return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type }) + } + return blob +} + +function download (url, name, opts) { + var xhr = new XMLHttpRequest() + xhr.open('GET', url) + xhr.responseType = 'blob' + xhr.onload = function () { + saveAs(xhr.response, name, opts) + } + xhr.onerror = function () { + console.error('could not download file') + } + xhr.send() +} + +function corsEnabled (url) { + var xhr = new XMLHttpRequest() + // use sync to avoid popup blocker + xhr.open('HEAD', url, false) + try { + xhr.send() + } catch (e) {} + return xhr.status >= 200 && xhr.status <= 299 +} + +// `a.click()` doesn't work for all browsers (#465) +function click (node) { + try { + node.dispatchEvent(new MouseEvent('click')) + } catch (e) { + var evt = document.createEvent('MouseEvents') + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80, + 20, false, false, false, false, 0, null) + node.dispatchEvent(evt) + } +} + +// Detect WebView inside a native macOS app by ruling out all browsers +// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too +// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos +var isMacOSWebView = _global.navigator && /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent) + +var saveAs = _global.saveAs || ( + // probably in some web worker + (typeof window !== 'object' || window !== _global) + ? function saveAs () { /* noop */ } + + // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView + : ('download' in HTMLAnchorElement.prototype && !isMacOSWebView) + ? function saveAs (blob, name, opts) { + var URL = _global.URL || _global.webkitURL + var a = document.createElement('a') + name = name || blob.name || 'download' + + a.download = name + a.rel = 'noopener' // tabnabbing + + // TODO: detect chrome extensions & packaged apps + // a.target = '_blank' + + if (typeof blob === 'string') { + // Support regular links + a.href = blob + if (a.origin !== location.origin) { + corsEnabled(a.href) + ? download(blob, name, opts) + : click(a, a.target = '_blank') + } else { + click(a) + } + } else { + // Support blobs + a.href = URL.createObjectURL(blob) + setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s + setTimeout(function () { click(a) }, 0) + } + } + + // Use msSaveOrOpenBlob as a second approach + : 'msSaveOrOpenBlob' in navigator + ? function saveAs (blob, name, opts) { + name = name || blob.name || 'download' + + if (typeof blob === 'string') { + if (corsEnabled(blob)) { + download(blob, name, opts) + } else { + var a = document.createElement('a') + a.href = blob + a.target = '_blank' + setTimeout(function () { click(a) }) + } + } else { + navigator.msSaveOrOpenBlob(bom(blob, opts), name) + } + } + + // Fallback to using FileReader and a popup + : function saveAs (blob, name, opts, popup) { + // Open a popup immediately do go around popup blocker + // Mostly only available on user interaction and the fileReader is async so... + popup = popup || open('', '_blank') + if (popup) { + popup.document.title = + popup.document.body.innerText = 'downloading...' + } + + if (typeof blob === 'string') return download(blob, name, opts) + + var force = blob.type === 'application/octet-stream' + var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari + var isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent) + + if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') { + // Safari doesn't allow downloading of blob URLs + var reader = new FileReader() + reader.onloadend = function () { + var url = reader.result + url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;') + if (popup) popup.location.href = url + else location = url + popup = null // reverse-tabnabbing #460 + } + reader.readAsDataURL(blob) + } else { + var URL = _global.URL || _global.webkitURL + var url = URL.createObjectURL(blob) + if (popup) popup.location = url + else location.href = url + popup = null // reverse-tabnabbing #460 + setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s + } + } +) + +_global.saveAs = saveAs.saveAs = saveAs + +if (typeof module !== 'undefined') { + module.exports = saveAs; +} diff --git a/Tools/autotest/web-firmware/Tools/FilterTool/filters.js b/Tools/autotest/web-firmware/Tools/FilterTool/filters.js new file mode 100644 index 0000000000..94104b7ab6 --- /dev/null +++ b/Tools/autotest/web-firmware/Tools/FilterTool/filters.js @@ -0,0 +1,571 @@ +function calc_lowpass_alpha_dt(dt, cutoff_freq) +{ + if (dt <= 0.0 || cutoff_freq <= 0.0) { + return 1.0; + } + var rc = 1.0/(Math.PI*2*cutoff_freq); + return dt/(dt+rc); +} + +function LPF_1P(sample_rate,cutoff) { + this.reset = function(sample) { + this.value = sample; + } + if (cutoff <= 0) { + this.apply = function(sample) { + return sample; + } + return this; + } + this.alpha = calc_lowpass_alpha_dt(1.0/sample_rate,cutoff) + this.value = 0.0; + this.apply = function(sample) { + this.value += this.alpha * (sample - this.value); + return this.value; + } + return this; +} + +function DigitalBiquadFilter(sample_freq, cutoff_freq) { + this.delay_element_1 = 0; + this.delay_element_2 = 0; + this.cutoff_freq = cutoff_freq; + + if (cutoff_freq <= 0) { + // zero cutoff means pass-thru + this.reset = function(sample) { + } + this.apply = function(sample) { + return sample; + } + return this; + } + + var fr = sample_freq/cutoff_freq; + var ohm = Math.tan(Math.PI/fr); + var c = 1.0+2.0*Math.cos(Math.PI/4.0)*ohm + ohm*ohm; + + this.b0 = ohm*ohm/c; + this.b1 = 2.0*this.b0; + this.b2 = this.b0; + this.a1 = 2.0*(ohm*ohm-1.0)/c; + this.a2 = (1.0-2.0*Math.cos(Math.PI/4.0)*ohm+ohm*ohm)/c; + this.initialised = false; + + this.apply = function(sample) { + if (!this.initialised) { + this.reset(sample); + this.initialised = true; + } + var delay_element_0 = sample - this.delay_element_1 * this.a1 - this.delay_element_2 * this.a2; + var output = delay_element_0 * this.b0 + this.delay_element_1 * this.b1 + this.delay_element_2 * this.b2; + + this.delay_element_2 = this.delay_element_1; + this.delay_element_1 = delay_element_0; + return output; + } + + this.reset = function(sample) { + this.delay_element_1 = this.delay_element_2 = sample * (1.0 / (1 + this.a1 + this.a2)); + } + + return this; +} + +function sq(v) { + return v*v; +} + +function constrain_float(v,vmin,vmax) { + if (v < vmin) { + return vmin; + } + if (v > vmax) { + return vmax; + } + return v; +} + +function NotchFilter(sample_freq,center_freq_hz,bandwidth_hz,attenuation_dB) { + this.sample_freq = sample_freq; + this.center_freq_hz = center_freq_hz; + this.bandwidth_hz = bandwidth_hz; + this.attenuation_dB = attenuation_dB; + this.need_reset = true; + this.initialised = false; + + this.calculate_A_and_Q = function() { + this.A = Math.pow(10.0, -this.attenuation_dB / 40.0); + if (this.center_freq_hz > 0.5 * this.bandwidth_hz) { + var octaves = Math.log2(this.center_freq_hz / (this.center_freq_hz - this.bandwidth_hz / 2.0)) * 2.0; + this.Q = Math.sqrt(Math.pow(2.0, octaves)) / (Math.pow(2.0, octaves) - 1.0); + } else { + this.Q = 0.0; + } + } + + this.init_with_A_and_Q = function() { + if ((this.center_freq_hz > 0.0) && (this.center_freq_hz < 0.5 * this.sample_freq) && (this.Q > 0.0)) { + var omega = 2.0 * Math.PI * this.center_freq_hz / this.sample_freq; + var alpha = Math.sin(omega) / (2 * this.Q); + this.b0 = 1.0 + alpha*sq(this.A); + this.b1 = -2.0 * Math.cos(omega); + this.b2 = 1.0 - alpha*sq(this.A); + this.a0_inv = 1.0/(1.0 + alpha); + this.a1 = this.b1; + this.a2 = 1.0 - alpha; + this.initialised = true; + } else { + this.initialised = false; + } + } + + // check center frequency is in the allowable range + if ((center_freq_hz > 0.5 * bandwidth_hz) && (center_freq_hz < 0.5 * sample_freq)) { + this.calculate_A_and_Q(); + this.init_with_A_and_Q(); + } else { + this.initialised = false; + } + + this.apply = function(sample) { + if (!this.initialised || this.need_reset) { + // if we have not been initialised when return the input + // sample as output and update delayed samples + this.signal1 = sample; + this.signal2 = sample; + this.ntchsig = sample; + this.ntchsig1 = sample; + this.ntchsig2 = sample; + this.need_reset = false; + return sample; + } + this.ntchsig2 = this.ntchsig1; + this.ntchsig1 = this.ntchsig; + this.ntchsig = sample; + var output = (this.ntchsig*this.b0 + this.ntchsig1*this.b1 + this.ntchsig2*this.b2 - this.signal1*this.a1 - this.signal2*this.a2) * this.a0_inv; + this.signal2 = this.signal1; + this.signal1 = output; + return output; + } + + this.reset = function(sample) { + this.need_reset = true; + this.apply(sample); + } + + return this; +} + +function HarmonicNotchFilter(sample_freq,enable,mode,freq,bw,att,ref,fm_rat,hmncs,opts) { + this.notches = [] + var chained = 1; + var dbl = false; + if (opts & 1) { + dbl = true; + } + + this.reset = function(sample) { + for (n in this.notches) { + this.notches[n].reset(sample); + } + } + + if (enable <= 0) { + this.apply = function(sample) { + return sample; + } + return this; + } + + if (mode == 0) { + // fixed notch + } + if (mode == 1) { + var motors_throttle = Math.max(0,get_form("Throttle")); + var throttle_freq = freq * Math.max(fm_rat,Math.sqrt(motors_throttle / ref)); + freq = throttle_freq; + } + if (mode == 2) { + var rpm = get_form("RPM1"); + freq = Math.max(rpm/60.0,freq) * ref; + } + if (mode == 5) { + var rpm = get_form("RPM2"); + freq = Math.max(rpm/60.0,freq) * ref; + } + if (mode == 3) { + if (opts & 2) { + chained = get_form("NUM_MOTORS"); + } + var rpm = get_form("ESC_RPM"); + freq = Math.max(rpm/60.0,freq) * ref; + } + for (var n=0;n<8;n++) { + var fmul = n+1; + if (hmncs & (1< samples/10) { + integral_in += Math.abs(sample); + } + if (sample >= 0 && last_in < 0) { + crossing_in_i = i; + } + last_in = sample; + for (var j=0;j samples/10) { + integral_out += Math.abs(sample); + } + if (sample >= 0 && last_out < 0) { + crossing_sum += (i-crossing_in_i); + crossing_count ++; + } + last_out = sample; + } + var ratio = integral_out/integral_in; + var avg_lag = crossing_sum / crossing_count; + var lag = (360.0 * avg_lag * freq) / sample_rate; + return [ratio,lag]; +} + +var chart; + +function calculate_filter() { + var sample_rate = get_form("GyroSampleRate"); + var filters = [] + var freq_max = get_form("MaxFreq"); + var samples = 100000; + var freq_step = 1; + filters.push(new HarmonicNotchFilter(sample_rate, + get_form("INS_HNTCH_ENABLE"), + get_form("INS_HNTCH_MODE"), + get_form("INS_HNTCH_FREQ"), + get_form("INS_HNTCH_BW"), + get_form("INS_HNTCH_ATT"), + get_form("INS_HNTCH_REF"), + get_form("INS_HNTCH_FM_RAT"), + get_form("INS_HNTCH_HMNCS"), + get_form("INS_HNTCH_OPTS"))); + filters.push(new HarmonicNotchFilter(sample_rate, + get_form("INS_HNTC2_ENABLE"), + get_form("INS_HNTC2_MODE"), + get_form("INS_HNTC2_FREQ"), + get_form("INS_HNTC2_BW"), + get_form("INS_HNTC2_ATT"), + get_form("INS_HNTC2_REF"), + get_form("INS_HNTC2_FM_RAT"), + get_form("INS_HNTC2_HMNCS"), + get_form("INS_HNTC2_OPTS"))); + filters.push(new DigitalBiquadFilter(sample_rate,get_form("INS_GYRO_FILTER"))); + filters.push(new LPF_1P(sample_rate,get_form("FLTD"))); + + var num_notches = 0; + for (const f in filters) { + if ('notches' in filters[f]) { + num_notches += filters[f].notches.length; + } + } + samples /= (num_notches+1); + samples /= Math.max(1, freq_max/150.0); + // console.log("samples: " + samples) + + var attenuation = [] + var phase_lag = [] + var max_phase_lag = 0.0; + var phase_wrap = 0.0; + for (freq=1; freq<=freq_max; freq++) { + var v = run_filters(filters, freq, sample_rate, samples); + attenuation.push({x:freq, y:v[0]}); + var phase = v[1] + phase_wrap; + if (phase < max_phase_lag-100) { + // we have wrapped + phase_wrap += 360.0; + phase += 360.0; + } + phase_lag.push({x:freq, y:-phase}); + if (phase > max_phase_lag) { + max_phase_lag = phase; + } + } + max_phase_lag = Math.ceil((max_phase_lag+10)/10)*10; + max_phase_lag = Math.min(get_form("MaxPhaseLag"), max_phase_lag); + if (chart) { + chart.data.datasets[0].data = attenuation; + chart.data.datasets[1].data = phase_lag; + chart.options.scales.xAxes[0].ticks.max = freq_max; + chart.options.scales.yAxes[1].ticks.min = -max_phase_lag; + chart.options.scales.yAxes[1].ticks.max = 0; + chart.update(); + } else { + chart = new Chart("Attenuation", { + type : "scatter", + data: { + datasets: [ + { + label: 'Attenuation', + yAxisID: 'Attenuation', + pointRadius: 4, + borderColor: "rgba(0,0,255,1.0)", + pointBackgroundColor: "rgba(0,0,255,1.0)", + data: attenuation, + showLine: true, + fill: false + }, + { + label: 'PhaseLag', + yAxisID: 'PhaseLag', + pointRadius: 4, + borderColor: "rgba(255,0,0,1.0)", + pointBackgroundColor: "rgba(255,0,0,1.0)", + data: phase_lag, + showLine: true, + fill: false + } + ] + }, + options: { + legend: {display: true}, + scales: { + yAxes: [ + { + scaleLabel: { display: true, labelString: "Attenuation" }, + id: 'Attenuation', + position: 'left', + ticks: {min:0, max:1.0, stepSize:0.1} + }, + { + scaleLabel: { display: true, labelString: "Phase Lag(deg)" }, + id: 'PhaseLag', + position: 'right', + ticks: {min:-max_phase_lag, max:0, stepSize:10} + } + ], + xAxes: [ + { + scaleLabel: { display: true, labelString: "Frequency(Hz)" }, + ticks: {min:0, max:freq_max, stepSize:10} + } + ], + } + } + }); + } + //console.log("At 15Hz: " + attenuation[14].y + " " + phase_lag[14].y); + //console.log("At 30Hz: " + attenuation[29].y + " " + phase_lag[29].y); +} + +function setCookie(c_name, value) { + var exdate = new Date(); + var exdays = 365; + exdate.setDate(exdate.getDate() + exdays); + var c_value = escape(value) + ";expires=" + exdate.toUTCString(); + document.cookie = c_name + "=" + c_value + ";path=/"; +} + +function getCookie(c_name, def_value) { + let name = c_name + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for(let i = 0; i -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } +} + +function save_parameters() { + var params = ""; + var inputs = document.forms["params"].getElementsByTagName("input"); + for (const v in inputs) { + var name = "" + inputs[v].name; + if (name.startsWith("INS_")) { + var value = inputs[v].value; + params += name + "=" + value + "\n"; + } + } + var blob = new Blob([params], { type: "text/plain;charset=utf-8" }); + saveAs(blob, "filter.param"); +} + +async function load_parameters(file) { + var text = await file.text(); + var lines = text.split('\n'); + for (i in lines) { + var line = lines[i]; + if (line.startsWith("INS_")) { + v = line.split(/[\s,=\t]+/); + if (v.length >= 2) { + var vname = v[0]; + var value = v[1]; + var fvar = document.getElementById(vname); + if (fvar) { + fvar.value = value; + console.log("set " + vname + "=" + value); + } + } + } + } + fill_docs(); + calculate_filter(); +} + +function fill_docs() +{ + var inputs = document.forms["params"].getElementsByTagName("input"); + for (const v in inputs) { + var name = inputs[v].name; + var doc = document.getElementById(name + ".doc"); + if (!doc) { + continue; + } + inputs[v].onchange = fill_docs; + var value = parseFloat(inputs[v].value); + if (name.endsWith("_ENABLE")) { + if (value >= 1) { + doc.innerHTML = "Enabled"; + } else { + doc.innerHTML = "Disabled"; + } + } else if (name.endsWith("_MODE")) { + switch (Math.floor(value)) { + case 0: + doc.innerHTML = "Fixed notch"; + break; + case 1: + doc.innerHTML = "Throttle"; + break; + case 2: + doc.innerHTML = "RPM Sensor 1"; + break; + case 3: + doc.innerHTML = "ESC Telemetry"; + break; + case 4: + doc.innerHTML = "Dynamic FFT"; + break; + case 5: + doc.innerHTML = "RPM Sensor 2"; + break; + default: + doc.innerHTML = "INVALID"; + break; + } + } else if (name.endsWith("_OPTS")) { + var ival = Math.floor(value); + var bits = []; + if (ival & 1) { + bits.push("Double Notch"); + } + if (ival & 2) { + bits.push("Dynamic Harmonic"); + } + if (ival & 4) { + bits.push("Loop Rate"); + } + if (ival & 8) { + bits.push("All IMUs Rate"); + } + doc.innerHTML = bits.join(", "); + } else if (name.endsWith("_HMNCS")) { + var ival = Math.floor(value); + var bits = []; + if (ival & 1) { + bits.push("Fundamental"); + } + if (ival & 2) { + bits.push("1st Harmonic"); + } + if (ival & 4) { + bits.push("2nd Harmonic"); + } + if (ival & 8) { + bits.push("3rd Harmonic"); + } + if (ival & 16) { + bits.push("4th Harmonic"); + } + if (ival & 32) { + bits.push("5th Harmonic"); + } + if (ival & 64) { + bits.push("6th Harmonic"); + } + doc.innerHTML = bits.join(", "); + } + + } +} diff --git a/Tools/autotest/web-firmware/Tools/FilterTool/index.html b/Tools/autotest/web-firmware/Tools/FilterTool/index.html new file mode 100644 index 0000000000..0f80369157 --- /dev/null +++ b/Tools/autotest/web-firmware/Tools/FilterTool/index.html @@ -0,0 +1,180 @@ + + + + +ArduPilot Filter Analysis + + + + + +

ArduPilot Filter Analysis

+ +The following form will display the attenuation and phase lag for an +ArduPilot 4.2 filter setup. + + +

+ + + + + +

+
+ INS Settings +

+ + +

+

+ + +

+

+ + +

+

+ + +

+
+
+ PID Settings +

+ + +

+
+
+ Throttle Based +

+ + +

+
+
+ ESC Telemetry +

+ + +

+

+ + +

+
+
+ RPM/EFI Based +

+ + +

+

+ + +

+
+
+ First Notch Filter +

+ + + +

+

+ + + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + + +

+

+ + + +

+
+
+ Second Notch Filter +

+ + + +

+

+ + + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + + +

+

+ + + +

+
+
+
+ + + + + diff --git a/Tools/autotest/web-firmware/images/filter.png b/Tools/autotest/web-firmware/images/filter.png new file mode 100644 index 0000000000000000000000000000000000000000..bc541d07d59c23b5f629ecfd456b9a72feef8015 GIT binary patch literal 29215 zcmeFZRd8HQvMwrSCd;-POiOU%j z(uD6Ps!Qi3iA>+n{6Tgu4o#3m(%<+$9#cOCa+0&y8I#hrrjz;Wh7P&u%e~+4UN%?V zdbasHdiWfBx@{l4ym#EGub6p0s-BkK-@0F%u3nEjrS$NZ)WmIGp9Ldp-cN1uyKFF6 zh-V`2f0uS~!M#8Ac5xloeA`!@c*Gd?@#(JiP4@Q{+a7+|yt8;+|1dJRSKa07d%@SP z^+Nu|>X+-g6*L_=>G~xC=cXalZ5uYPQ>;JW)jW&O7l(uk$KutfK(EqaIj8F02k6tH zdRIgKt3`A=C&$-oyYjXIrMbZIWDkreLkAS~5Vbtm11ksW>``^V-fImb)Ywp$05`ybPpue&{OOdscv zN0b18aXR2wGMd0(2_u7%g&~OxXxM&M zr(A>GBlN)PN;PO2)D}5}VMrRkC2a{hZmmxG>AF}>Azh*H z;=*|8Z%vK!mZf#Y>6T?p&1)I9ptCZxE%O$ZRjwDSCvA?-C4!d&F$}vBCi{wF_||vf zFOwV#lMRzx>t_=+wY@P(Y8uW>OD*fI^FO%HJ143;&VB5f&?mzwvb@I2O0wST(mjLE zFZDixmo_FW-ZiBVCq^tfwZ=Q(DL5qS)4DVU4X%mNcV>0j8kUBRft;(8Z1T#aGk^rE z=1iIp$UPT6{Kw|3>ljgB+egx-OnA@FsgDoM=~1VTqJwvf7Jd!uZ-`Ttbrf(-rEDnP zCCU!W6LGIL+3YX6%qbqv`O#xIvW-TQy*G=TOuV6qp2+8RPg22dwg(KdT(?gk%F(u* zQQ57H1`sI`W2vj5x{nqeMPrECDHMKMtIXR*r^-wjCDXs9*Nw8#pHH&g?FvxaOf}aV zgUWv3@>Q`IUh4?7m^>QQf_#-;3LbN*Wfd9DdRz_d3u3X7=9^050@0i_H@9C;+W1k_ zpxj_}N&Itr;46qmBLV>hWpY`L)JQfevt1{^q-6%A>5-}TOa_rqONRO^O`P5+CtbRw zU3B2mXd-e*!_p~+(^YD!`g|0kbq)_RR??&`AZDFYqq8}Zp1QFE4i+2<(D3VPcpVJu z3RTCEJsMpO8mwg!*y0Io!uM+%(&E7nYiu%6yF5g&x<@xRRH>VEr{A8L9N5Fv74py1 zHe|z6ozYg!!1Rr(Dxr_6OI{(mYD5}+>1ZTRbZ!V?Yr75}WOcZT<0MFabV7T+-SJ`k zovP@uUapYUvZ~;4jr+C8j36}$%cd=~VOlq_Y{!eZ1S56zQql`0c}C|b63;9?!%d?` zywh?U+YETVQ}&8Z$#`y)c2{6X)0kwk*bi7t7=HqacaFd%XztKUn zMPDJ}sF~?Z;~TMT4hn2rkgz^(GVx)jE2F`hWP~~X47rZeP_;D?nHo;D=O7i-@LK&h zO9eqQ4o6-1Gze^j5{M z(KMojhf9)^{FZY9U}8d69J*KF>8Pzesj zdD<-0O;yfelFZzOW>V z=kioJgc32)L}ih|pJF>qkGuzfGR+cPStZxOD}xA{xJmKM4PCPah~QAjB6+zC@_kq4 zB4&T40SB=|XE<4^)!kvxAkOR`9mYyEwu|HwNpmgZLoSMDo+k+B_$q+V*%rSiZWvPA zqdV#M1+Vb9x6juEI~U1L#1@%xnYwfp27-eCh3S+p*K1?*l4S_ikoZ}nRaoC>hp({8 zkm9S2Ay!bRGYwB%3sxGe6R8M$0f15hL!?{OVi9ii-U|)$v0FD8QfI*84G#{_h_5@>d0>l{X=;R^bdcT(}P%cT_!Z5isUq<(9Dis3^ z3Mx3;Rsd2Km>`jCUD!~pa-#8A1opJxbgmL^1(-oXWynHML8n;bitx=T;ZXoomK04b zfCRj$kSPuf1(%n}l>dHg15>@m4@dRNMOoM6jDt~tgmpLBaEC)F5C-%YRQbX<#(ehj z1X#lgDUbB;UVDIPtp9Z;Gp-}F^L3(9b*P@Hr?&NlnK}Whd-sRi2fd)+GJ1}nrFXm4 zq)Q4tG@IU(&W1*!v09!>O0b2FdqTGWd38(^`jo5p>v2l=RB)M4<`hapk;tE=4cxF0 z(Y1`Mml#i%WpeP!=td9^p*?+LQs#K@25r_5&_4rOJMavdKTrm#_G+kOrT-j3GHCj@ zi`m&FKVQr*`S%%Q@gl(23q$ODC4MOV?dIU?{SjVf-o5sdw-j~v|_H2x#6hbIOazQ zipD#vf#iiAD$Nyh21`U~oL!1@42$@#kA;bZ`SR_ZSp;p12-`Bqn(}dPItj41*=Si; z0Q;iIKR;V3;D?ZXA%w}5nbdnBEe%bg6W%HAcEL5k*7ry=d@NRKWN!|KvPv8<=Z)%M zr3}vpgBe9|N5M*TACT>Fs(x3(^gpb*?spl>`4q z3d^Y|J&%@oCXwl1PW@G=mUhJiJp`1JQW~1=i@uM{RR+esS6w`CpEcMMq)d{D$SA-$ z7}-E|vbzvY=o}pV-6ddaOdB#|PBcMf}X38d0z9ms)b0q0J$g zTJ`H4x-ob0wZW=z@ zt|0l@ZV-?e4uC1!3rmn+aa^hlf+*bO#-q~clsp!W%eYEdg=nd`^9IVPIu8B_IZhgK z1uIyth^~?wM1D%pDDXKtE&!aQ!M>pm0c`|l4`ngJq5XoTDA~K7x+mK~4JeYZt9uZM zSZi<(bHp|hE|v_y+j0~E>gwHa4UkQF;uPcW%K(%d&PBC>o0H=O7~%;Wah)0HGE8h1 zaP;&6+V%*nvDoKdyxpYXYm=B?@dl@ql7g3^Mijz!VCs0ozo43pzb%zibK5^>fX6*Jp74v1xuuuLPh$l&}+Qb%V7>CgdADaf*G<{Zip z{9+|0G+!vbBsh%+$RoqRVj&aQGX!2MvHXO+ZxUa%@mECjBQgL3C8A%lp1a#I)q$+s zHtzm*5Vao}Z3o#*M-aS(;HTb3Z__{D`>WnAc?YsY8Cu3wx^j( z5(t!;>X3{NMwgmaBfn?kk}P!xDTl^_s0LS>iQP^dJFFE>p;5I{P{isSbb3NHf?VF_ zi%M~8&x%tq7V>}f#~^Poj>~`a_5)XDJo|DOjl>~V<`;{)h%q|3cUdRh{lne9wo{wW zITZYj;*nMF9akAZm_Kz=AuWbg3xGJ1Cwd5HxGh0BonuNBEA$byO`R>iLiilA>K=VD zO81A=s65C^eiiaO>FSxd!P0Eto`H76F4{PW3VxDk?pv9$p(tZpd%hm%3eNJfcRYd> zy?d}jUy7PHN0*vkE-U>&4WqJozwCfZ%ABF${m#-SWf`jV1vHQ!SFlAR=Z75}>eGo3 z>9~ntvw>o&v-85PTe&n+J>a{Cu;o=-GX+=^Kr=kClpPDkgTS$Ds zgt0G4FPPeUPlpo$fp)B%oum@Pihe&$otDd_fT+T1f_`!e89~Q>_o)!XPGIO;Sd1nH zZGOmi&C6XnLH=a@k45#CMr1KMx{!iim9gxP7HDV1);tZ=-}8O~gphTFAp8MEtpYo- zye%`t&*j0zisWYv(52cYtCB~R2pSN-Y0TZdi1El4`;8n-dZ-gQgh2GMiwb%6hTvGj zs1VF>=L1Hr>(&|QWArQY*-`n%97rM5dVYdSle;p*^am`yJ-C7tzz|3{&fH&}z1i}# zMrTd87Cqf8L~v}B>$}nzy*D9~m_xv#CO@MaUgNtjq3k^w?7>V^TTQzvb_wE3{GbR! z6jhTpQ`m~x@3XAZO|fX4qj(qd5iT#2a6ixqHP%MR#peu~yf^zn`Hjj{ye6LE>!f)k z510@X(Td9aOIsN0jFjk_KDO_PitES1)Ra88TM8EF$J4CH>_LPe4_*uCa=}eW$klhD zwrUHgqlMosN7(^30-ik+sl(=l zer&jWMYIctl?af`&ovn>wZ|8$!GuB#N|B#4 z46UoO416_>tNwifF0CRDX2=LYo?4*&T1DF#PlLs-Jt@qb-DJWc>3qi%EpP;q+f|yI zy*{!*o4014#%ht6TZ>}3yN?x~qpLY2@3Q6!oO0vFHh;V1J(y4wh`Fh5?B-&X#{|=A z_q9vpe+nNR-n)8VZ!pjt7kBt*OdnTt{n{jYrlM=%53{-&_c+%nVMm$x8wf$Zd-NyoLk0lP`5cbF~g?YEHy?vMp@R}U4Si?r?np%*a<;qM? zr1^UR0VY`-J&3lO4ir_@or~|R)7x59--IuW8#hp3yC9M_(F*mADw)&EtqBs8>0EGGl_aCXyfMeoG=toV zAQAAX3b}$+(4aN|8w?*sQ*h9N!8~9DDir*1g-JGi242z z=dfGX9P}tys47izCDbxYDxVsGtLqaXv0gfVt-a6+X*uus@%fBC;aKvYyM-8% zy+BlX4U5R*o4Fak_Uh2V^)|y)`>NodBq&DFYUo0>;K?FQX5uMr{{0Zy!LE|xCiK{b zs~2pze(Q0pvqtK98+rILyLJL49m<&eDq6vwen&bP0JA8nn@ndVN!srXV8LcS8q-Yuf4ZCy=cM$1p$;|cl*tm=k{jy851^Y#` zfKc+@a|Qi1hp>DINFfC}T*TQGM1FxP6ONb>+BnIOu@*$HQ8JzolO|>2onqrArHa&V zUI8+R)P(3y92E$uKNC6vC5-fyF{g_t~3JNcEzz zyLPulhQAwhfEPYV!CH=SQVAS{P{vDe%YDDpWcQHzmP}sux%M?>SD}c1BC1ZR0H!3X z^%!&lTy4q+GLcmc>|ZJPtQivCj2<&uZ| zF0Tj>ZPzEs+&ruwY9kkIO;S_`OKS&qr?UIto!{P=p7lJ~E~~dUy^-}sHh0)%SsBQ5 zRFIG~&OihTWl$GAslf2m4^7(KkSRoujeCMwO5b=G!MG(yq(s1)8&`2k@d7x$n1630 z|0!g92RyFi5GbKs4?s35p0m%+j;&0%Zr1h|TE+;Xc#M^aNJ1dAxpyGa-+vx;21`Qv z%5!`ji$QircC#_u0Es81JpHWk$DiWLCcO~Hp3yA`75#c`L;G3-+6!&f9;V*oai!1T zypyRXXUP&amgq)3AKp(|ocERla?7OtZ9JHt=15fjz{e1ug@@Ru5;RXsXPxGuQ=BMW zgyT;}GZTT6AByqZ<0ND3n3D~IU!xrX;W^TemD|Ot@6^ZqnXCJlSylqVwD{i^V56a6 zh`puMg^LQ4(E%_NWHCjcU!sIl^KM0%*MmIs$R_o744maJVI-1Rj+UR&E+pJKjFB6g z&%&w|aLeLe;pqJm`y#gGCb;72=OXBn?;BD(RbFYf1)ta%zqSsi1!)<>!^H97Vt}}; zOOAl-fpr&Bf>W}AYal;d5aqSS#=zx;YZ8E98m+Hq~kk1nkVT$dIAnxZY^-xab$>DqgPwtL^#F~&u5iWz7W8QN@jLN|Q~?~7$*65bfy@dx!;qcc2zlxo4X*Z8h#tUeUBA{adksv< z@lAlRcJ4LEhqf;k=!VP^+8P%;7=jR~Gu+7nce=8OG)&1ej8*P>(WdUtbVcO4zP0*k zDN!Er3k1*m@+{2Gia(}Gf*lcLrB~6l0-)bso&U%$PPu?XSy4X}L_|wSSU=b>u2A1*_F&R9{?O!~71Y5>w)Q)DLnjO;R-w9O&-XlMbk;f}IVSGU)hU-RlEVj;3Yxg>ODdL5v~pnyckHPAs){njI(c ztyeA~dMr$%WnEgQf)Vd0XpNmm_JDa7Mwrcl1>TmXi@fsXg%|mfiwe|&Ma1lzsfD6* z8|<=kS+k-V#tI*~`iHh|kF6Yypc=7#E?A)c0$ar-QB7Cc5d>P+H>&paxQ<6HB!k;t7*ZEki zkGs%`g*~al2A!KKb7~%y>ao*tQSD{?ke4Ku%4_ugR!>5{%Mt}-vd2G;p@KhNH1Mrz z-q9!PlwAkvCod3*Bq0x&PPJxW%RNF(a$hs5mW_~9y8;AOYmY|OPDS8p)cFM9r;Ck7E&5}ux!E4kl3-#S z$G6_Pj<%dsTAPR}QmS*MuJOJKos`S$o+J0^q4!kL_cZYBy9Y+mqr6w?VOtmOvRPyf zn>?jKu|2&XLEe{>tm6P{84L@EDKPj=sfM+}AU*$PL?#aZwxW8h>vItFxu&`Jj-*Z%hURz6Gla^QPzCfas7CwqYM6113 z92Zu4HcvU*)Z#j6(AeCoW?v8us(zB z?@y?9mmjl*9@0QCZ#FTB^j$B+0%cG$*aJZw(HQ+v$cvf4dT0X?JH|)!g4o|FG1pEl z45tk$&<0aJB3eI6xGI&;574PT>K7YYxi(SKVBtrqIAiJ-Jx%0{kGV8oB37Rp8BLjq z=`kvh^h2MdTBS4$$=KWO7d(~=l|sP}4@qkzr_P*M(~vMQ0V0f~?=n8xo=%hl-X>KW zJ%tre4z&10_9NyiSgPj=OmIM6qef?*vv{HkAp0HTA1&V7U}3?Qyjw}rRtDW!n;HHW zm2}>(yhFECklV-aEI=le)OzRM-L}cKnwi@XvV5N2*v73YefAl)AZ%yZoIPy~G8j(F zdT$P5)VAA{b8N>)qlg3sF%vU2G3SwRW2H`4^Gi=d`|O*u$qoFf8>(2knm`6F2h+R2 zKkZ~~bPm9oPQU(_ADHc)18h&kpb8gkcZzS&h5pvZJo(eTQKXw6UUx6F+1$NyHY=9h zmVq8FB5&Iy04_O_WWCO|oP#4eu9gBvujgv$kL>4jd1RZ|AGVO1Mt_`P4j&n7al8Tf zxka?UsA&>zl(3mud~+mdrrSI>oUhRja^?;!vDFv61kpHZ z8G2>_P3icUCON*;xEd1f! zm=j=~B_|Qm^!Qc~gW~)LoEt@j3VS;+l)+BqdXI!lBqsT*Zl`-q*-?%m)K90;=|{&e z89_Dp^VPh#qx@HcEA|F8%uas2u{n*f_jUq z7w6#Y(bKqxs2%G5ydmmd$7=if$8ajHdYMh(o#pPnxbikLKx551QU#j*!Mm)rjQ!5e z>E4B&MSgdsY{|`L(_)SP?rlV{a~+cDsyk!XQp&;?XWf%&B1Jq^4H~wCwfB!X=u4|K zef>>)11j|&mMO0H$UVNt_b72RA}^*B%X+}Nnrol&Be7bJR%lK!c5UK=CIVgnON1bX zj;?y@73X2FH^N>92#@<8Jv%{eUFw5CYAzQY)ZA;=yvC zxl_+8zMgm^o!H{+BN+vLr+^UewP#G%+2Yz#69ji^rLc^LO9F==-;r6sFibu)d*k)_ zeI;7Z57d0VrX?^F~5@ET^xvgJozZ;QsL zM`GakCh-)P9NPS{$x=GyDxw}hW&2rFn-;ZT0vCH4l{;Kkp7%r&lQ$831e+a{zbc(< zuGRZ#N}J7p05Te$X*N3!mdLWPUN6qjjf)2^9H+H?Yl}V>F9ZXjJ8zaD&FYDLpCotvvFd6 z`CAbEW_~XoBZs~XC&ek~N8Xut{(}ERX*)wCq6Xzx5$@IIN*uk(1owD~&}^iWk%AAH z2*~l{Ue96o!0at;t~|&YGvS#eY{Q67x!UYJN87Mn4*jnP=^IiFrMF(ucrAT1K28kG zGsC@90{bd-q2FCmzRwug{Oq3(bn;k6*R&!dB%&|lL?Z$Fe0bq9bp$}~UE-pWol)iX z_?^H&5ovY4qmdOgqg!&5w=uTj{m32WA=cz{rGR;k^rYfGjV`5%T*B($TRM8|Gr4Yb z<(VI+UBE@xp8f}4=RS8jqC&3x2oE4-DHVjkE8VD$V5$_1Qe^@#o0e9s01Ku69qUP1 zcs&ewsxS?s*=cs-i)!uw4PO(8vTbn>F*J;^Rdc#D)8ev)q+Ru+tY$B2+x|76JRo+v zeI7#J!ctTF1l{7Ko6+@*2UpCT)Dod`-x^5Jh|6_h|M-~xz~GxrHkUwp2`zc0N)wFK zcyS>^WM$aZBea(vBNCOZ!8Q+Oh*188?`Y=q2;k{4-O!m0)<*jZj#;wo>KBHvND zO^D(b_vl~oVz%wMpW`!isy^+>G)Zh>^mg%`NBrh8v7bcv=vlriZGF>HIj1{26#K#i z>RSL_q^h$LCGuvyex*yIQ@i90R#iu=rqv&O={zZnbiBC}1xZ-LUXgo!iGY=1c;=;0 zd(yglHz8Hf4%Q0kIG<7Hq|ML_P@kml9zUt2)DPP7Oc};ysM4UrMqs$4VZO}KhXNLO zMe}vK5jkKLkrG2y|BatwZO}D%ijM?DYZ|>;_!%&O%H51~x zps5vyN_fZ?Rpz27_RSqL9t%sh> zU}H$np4SKZwh_V08~Tq9qr#e73eXG5(C*AqWl+?d(VF_w!+f^|T#Qbk6Y(y<81+IS zpBR*`ov^?-ii6VAe7S=Rah?6P95>c5VdJyciTPmKkT7XR9kadmutZkredW+JO^mzxPpyg~2_YMnKQv|i}m+(m|(yGVJA*VI6E9~&q#%r)f;l)ZgNBQ=KN z3locso$Hrrj<6OcKvZkecIs*UlJ@l_;{&>hY`9);q)!b51T@%OSXe<)SopsVP<Z zH>9BkttN=_3S#f%@ZBj6?IQ(4bI{FhBgXZS!1WTuw`)?l!3a-*bwYw{SBSiW3PNu# z81#-fBy3Q|TMi@P3QzCx>iV8;xS@0Ajq^D%a||R$fM>#KfOrd#z18S6{W55vnuxLM zxXTzwC-FM!yn4Vs4H^2z!)#TkbUSEZm^VA@fZLFthbE6Ij;T`M z-h2shSQ}?rF-Ict7o`yl0{N4ZnW34Fj!p>)W;1ZeuZ7l9jpf$cb0V2i*^`nr}QVF|K8db`vX2y|$#nScEX@7Ct# z)_RX=`r8|bU7|yA-a%C2=Sepl^Uo7)>asFiMz+>;2FA9AKsq;TyU!DEARs*aZgvJn zmOv+fA<)#^hL`xVqlXw^Zp=%p#wyDoYbOjeGnepi04jONDI0lM8gUvE^Yg*+xN&^~ zSOc950B+V+HjZ3wyu^Rwa(({(tC*e`@VAJQB`>kMtO7vT)&U4$p<|(ApcQp9cVQyt zg9Gq57@Kf?7ZLj>#OD<+v6+*T9Tz>ltE(%WD>I#~gDE{DCnqO80~0+H6YZx2t)sh* zlYtwpjU&ll5dXjs0XiBvnA+E0z6m)SaySpDseu@ODc3TXW)>iF3zBD}*fsK{bklDbH)zIi)pd@V^oeXS@fPX=Kg43CQ;&2)n zFd8$Mu+y5DFf!A!Ff+5!au|Pt8E~2~0y$Vrm^e9D{sltb!Td8S4Xpmvs=uI&KcUz- zKFu%$GSaehn6P|8F#u@|IZZfeIgD7Dm<&vejafMVVU4j7mzb@CwZUh1np+!~0_p8+ zO#g1CD~ z-~u%MyP;3Le^eQn8Q7QtKgah!1M0uZ&Hpc*^~oSRn;|DFEs)Wek(P!1v)c?AS=ebg zjf_}~SXd1i4H=mK9o^B^#L3mb0Vrtt>FK98p8@o@Hvr1Nsr>fu(ynH}zj$K!9NP?R zw2bV^OpIJCY+TG7)Qk*V3=G8d|8$uCuTlM99`n%ue>ma!Ti{>Pz^C3n%0AP}XSSmM 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 F{{xFyQtSW# literal 0 HcmV?d00001 diff --git a/Tools/autotest/web-firmware/index.html b/Tools/autotest/web-firmware/index.html index 2e15d88e3c..4ceef07a40 100644 --- a/Tools/autotest/web-firmware/index.html +++ b/Tools/autotest/web-firmware/index.html @@ -98,6 +98,8 @@ The software and hardware we provide is only for use in unmanned alt="Companion">Companion - Companion Computer example code and Images

AP_PeriphAP_Periph - UAVCAN Peripheral Firmware

+FilterToolFilterTool - Filter Analysis Tool

Types of firmware available