diff --git a/Libraries/LogHelpers.js b/Libraries/LogHelpers.js index e55af0f6..c4d5847f 100644 --- a/Libraries/LogHelpers.js +++ b/Libraries/LogHelpers.js @@ -63,18 +63,22 @@ function get_version_and_board(log) { // Check we have bracketed the messages we need continue } + let types = [] + for (const type of Object.keys(build_types)) { + types.push("(?:" + type + ")") + } + const regex = new RegExp("(" + types.join("|") + ").+\\((.+)\\)", 'g') + const found = regex.exec(MSG.Message[i]) + if (found == null) { + continue + } if (fw_string == null) { - let types = [] - for (const type of Object.keys(build_types)) { - types.push("(?:" + type + ")") - } - const regex = new RegExp("(" + types.join("|") + ").+\\((.+)\\)", 'g') - const found = regex.exec(MSG.Message[i]) - if (found == null) { - continue - } fw_string = found[0] + } + if (build_type == null) { build_type = build_types[found[1]] + } + if (fw_hash == null) { fw_hash = found[2] } os_string = MSG.Message[i+1] diff --git a/PIDReview/ArduCopter_angle_rate_control_loop.drawio.png b/PIDReview/ArduCopter_angle_rate_control_loop.drawio.png new file mode 100644 index 00000000..0d95a194 Binary files /dev/null and b/PIDReview/ArduCopter_angle_rate_control_loop.drawio.png differ diff --git a/PIDReview/PIDReview.js b/PIDReview/PIDReview.js index 175a1ab2..4da4498f 100644 --- a/PIDReview/PIDReview.js +++ b/PIDReview/PIDReview.js @@ -4,7 +4,7 @@ var DataflashParser const import_done = import('../modules/JsDataflashParser/parser.js').then((mod) => { DataflashParser = mod.default }); // Keys in data object to run FFT of -const fft_keys = ["Tar", "Act", "Err", "P", "I", "D", "FF", "Out"] +const fft_keys = ["Tar", "Act", "Err", "P", "I", "D", "FF", "DFF", "Out"] function run_batch_fft(data_set) { @@ -64,7 +64,8 @@ function run_batch_fft(data_set) { // Log section is too short, skip continue } - var ret = run_fft(data_set[j][i], fft_keys, window_size, window_spacing, windowing_function, fft) + const valid_keys = fft_keys.filter(key => data_set[j][i][key] != null) + var ret = run_fft(data_set[j][i], valid_keys, window_size, window_spacing, windowing_function, fft) // Initialize arrays if (!have_data) { @@ -76,7 +77,7 @@ function run_batch_fft(data_set) { } data_set[j].FFT.time.push(...array_offset(array_scale(ret.center, sample_time), data_set[j][i].time[0])) - for (const key of fft_keys) { + for (const key of valid_keys) { data_set[j].FFT[key].push(...ret[key]) } } @@ -272,7 +273,7 @@ function setup_plots() { Plotly.newPlot(plot, TimeInputs.data, TimeInputs.layout, {displaylogo: false}) - const pid_outputs = ["P","I","D","FF","Output"] + const pid_outputs = ["P","I","D","FF","D FF","Output"] TimeOutputs.data = [] for (const item of pid_outputs) { TimeOutputs.data.push({ mode: "lines", @@ -388,7 +389,7 @@ function link_plots() { } // Add data sets to FFT plot -const plot_types = ["Target", "Actual", "Error", "P", "I", "D", "FF", "Output"] +const plot_types = ["Target", "Actual", "Error", "P", "I", "D", "FF", "D FF", "Output"] function get_FFT_data_index(set_num, plot_type) { return set_num*plot_types.length + plot_type } @@ -618,13 +619,13 @@ function add_param_sets() { set_cell_style(item) const names = get_PID_param_names(PID.params.prefix) - for (const [name, param_string] of Object.entries(names)) { + for (const [name, param] of Object.entries(names)) { let item = document.createElement("th") header.appendChild(item) set_cell_style(item) - item.appendChild(document.createTextNode(name.replace("_", " "))) - item.setAttribute('title', param_string) + item.appendChild(document.createTextNode(param.title)) + item.setAttribute('title', param.name) } // Add line for each param set @@ -667,21 +668,21 @@ function add_param_sets() { checkbox.disabled = (valid_sets == 1) || !valid item.appendChild(checkbox) - for (const name of Object.keys(names)) { + for (const [key, param] of Object.entries(names)) { let item = document.createElement("td") row.appendChild(item) set_cell_style(item, color) - const value = set[name] + const value = set[key] if (value == null) { continue } - const text = document.createTextNode(value.toFixed(4)) + const text = document.createTextNode(value.toFixed(param.decimalPlaces)) let changed = false if (i > 0) { - const last_value = PID.params.sets[i-1][name] + const last_value = PID.params.sets[i-1][key] if (value != last_value) { changed = true } @@ -724,6 +725,11 @@ function add_param_sets() { document.getElementById("Spec_D").disabled = !have_all document.getElementById("Spec_FF").disabled = !have_all + // DFF is only available in newer firmware logs + const have_DFF = have_all && PID.sets.some(set => set != null && set.some(batch => batch.DFF != null)) + document.getElementById("PIDX_DFF").disabled = !have_DFF + document.getElementById("Spec_DFF").disabled = !have_DFF + // Uncheck any that are disabled if (!have_all) { document.getElementById("PIDX_Err").checked = false @@ -731,17 +737,20 @@ function add_param_sets() { document.getElementById("PIDX_I").checked = false document.getElementById("PIDX_D").checked = false document.getElementById("PIDX_FF").checked = false + } + if (!have_DFF) { + document.getElementById("PIDX_DFF").checked = false + } - // Change to Out on spectrogram if disabled option is set - const disabled_checked = document.getElementById("Spec_Err").checked || - document.getElementById("Spec_P").checked || - document.getElementById("Spec_I").checked || - document.getElementById("Spec_D").checked || - document.getElementById("Spec_FF").checked - if (disabled_checked) { - document.getElementById("Spec_Out").checked = true - } - + // Change to Out on spectrogram if disabled option is set + const disabled_checked = document.getElementById("Spec_Err").checked || + document.getElementById("Spec_P").checked || + document.getElementById("Spec_I").checked || + document.getElementById("Spec_D").checked || + document.getElementById("Spec_FF").checked || + document.getElementById("Spec_DFF").checked + if ((!have_all || !have_DFF) && disabled_checked) { + document.getElementById("Spec_Out").checked = true } @@ -806,7 +815,10 @@ function redraw() { if ("FF" in set[i]) { TimeOutputs.data[3].y = TimeOutputs.data[3].y.concat(set[i].FF) } - TimeOutputs.data[4].y = TimeOutputs.data[4].y.concat(set[i].Out) + if (set[i].DFF != null) { + TimeOutputs.data[4].y = TimeOutputs.data[4].y.concat(set[i].DFF) + } + TimeOutputs.data[5].y = TimeOutputs.data[5].y.concat(set[i].Out) } } @@ -1204,7 +1216,7 @@ function update_hidden(source) { var index for (let j = 0; j < fft_keys.length; j++) { const key = fft_keys[j] - if (id.endsWith(key)) { + if (id.endsWith("_" + key)) { index = j break } @@ -1296,15 +1308,68 @@ function time_range_changed() { } function get_PID_param_names(prefix) { - return { KP: prefix + "P", - KI: prefix + "I", - KD: prefix + "D", - FF: prefix + "FF", - I_max: prefix + "IMAX", - Target_filter: prefix + "FLTT", - Error_filter: prefix + "FLTE", - D_filter: prefix + "FLTD", - Slew_max: prefix + "SMAX"} + return { + KP: { + title: "KP", + name: prefix + "P", + decimalPlaces: 4, + }, + KI: { + title: "KI", + name: prefix + "I", + decimalPlaces: 4, + }, + KD: { + title: "KD", + name: prefix + "D", + decimalPlaces: 4, + }, + FF: { + title: "KFF", + name: prefix + "FF", + decimalPlaces: 4, + }, + D_FF: { + title: "KDFF", + name: prefix + "D_FF", + decimalPlaces: 4, + }, + I_max: { + title: "I Max", + name: prefix + "IMAX", + decimalPlaces: 4, + }, + Target_filter: { + title: "Target Filter (Hz)", + name: prefix + "FLTT", + decimalPlaces: 4, + }, + Notch_target: { + title: "Target Notch Index", + name: prefix + "NTF", + decimalPlaces: 0, + }, + Error_filter: { + title: "Error Filter (Hz)", + name: prefix + "FLTE", + decimalPlaces: 4, + }, + Notch_error: { + title: "Error Notch Index", + name: prefix + "NEF", + decimalPlaces: 0, + }, + D_filter: { + title: "D Filter (Hz)", + name: prefix + "FLTD", + decimalPlaces: 4, + }, + Slew_max: { + title: "Slew Max", + name: prefix + "SMAX", + decimalPlaces: 4, + } + } } // Split use the given time array to return split points in log data @@ -1459,14 +1524,14 @@ async function load(log_file) { let last_set_end for (let j = 0; j < PARM.Name.length; j++) { const param_name = PARM.Name[j] - for (const [name, param_string] of Object.entries(names)) { - if (param_name !== param_string) { + for (const [key, param] of Object.entries(names)) { + if (param_name !== param.name) { continue } const time = PARM.TimeUS[j] * US2S const value = PARM.Value[j] found_param = true - if (param_values[name] != null && (param_values[name] != value)) { + if (param_values[key] != null && (param_values[key] != value)) { if ((last_set_end == null) || (time - last_set_end > 1.0)) { // First param change for a second last_set_end = time @@ -1479,12 +1544,12 @@ async function load(log_file) { } else { // Very recent param change, combine with latest set, this leaves gap between sets - param_values[name] = value + param_values[key] = value param_values.start_time = time } } - param_values[name] = value + param_values[key] = value break } } @@ -1545,7 +1610,9 @@ async function load(log_file) { P: Array.from(log_msg.P.slice(batch.batch_start, batch.batch_end)), I: Array.from(log_msg.I.slice(batch.batch_start, batch.batch_end)), D: Array.from(log_msg.D.slice(batch.batch_start, batch.batch_end)), - FF: Array.from(log_msg.FF.slice(batch.batch_start, batch.batch_end))}) + FF: Array.from(log_msg.FF.slice(batch.batch_start, batch.batch_end)), + DFF: ("DFF" in log_msg) ? Array.from(log_msg.DFF.slice(batch.batch_start, batch.batch_end)) : null, + }) } } @@ -1610,7 +1677,7 @@ async function load(log_file) { const len = batch.P.length batch.Out = new Array(len) for (let i = 0; i