Skip to content

Commit eab71ab

Browse files
nerdCopterclaude
andcommitted
merge: master into 20260402_Quickflash-ESO
Resolve OVERVIEW.md conflict: drop stale --eso-axis and --report/ Statistical Report section; take master's cleaned ESO-only text. src/constants.rs merged cleanly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents f19b2fb + f506c57 commit eab71ab

3 files changed

Lines changed: 44 additions & 28 deletions

File tree

OVERVIEW.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ All analysis parameters, thresholds, plot dimensions, and algorithmic constants
2828
* Additional options include `--help` and `--version` for user assistance.
2929
* The `--output-dir` parameter now requires a directory path when specified. If omitted, plots are saved in the source folder (input file's directory).
3030
* Handles multiple input files and determines if a directory prefix should be added to output filenames to avoid collisions when processing files from different directories.
31-
* **ESO flags:** `--eso` enables 2nd-order LESO bandwidth optimization; `--eso-axis <roll,pitch,yaw>` selects axes (default: all); `--eso-b0 <value>` sets control effectiveness (default: 1.0).
32-
* **Report flag:** `--report` writes a markdown statistical report (`<stem>_report.md`) alongside plot outputs.
31+
* **ESO flags:** `--eso` enables 2nd-order LESO bandwidth optimization; `--eso-b0 <value>` sets control effectiveness (default: 1.0).
3332

3433
2. **File Processing (`src/main.rs:process_file`):**
3534
* For each input CSV:
@@ -169,7 +168,7 @@ All analysis parameters, thresholds, plot dimensions, and algorithmic constants
169168
### ESO Gain Optimization (Optional)
170169

171170
* **Purpose:** Offline system identification of 2nd-order LESO (Linear Extended State Observer) bandwidth (omega_0) from recorded flight data. Finds observer gains that minimise tracking error against measured gyro rate.
172-
* **Activation:** Disabled by default; enable with `--eso`. Optionally restrict axes with `--eso-axis roll,pitch,yaw` and set control effectiveness with `--eso-b0 <value>`.
171+
* **Activation:** Disabled by default; enable with `--eso`. Set control effectiveness with `--eso-b0 <value>`.
173172
* **Algorithm (`src/eso.rs`):**
174173
* Extracts filtered gyro (omega) and PID sum (P+I+D+F) per axis as measured output and control input respectively.
175174
* Simulates a discrete Euler-forward 2nd-order LESO at each candidate omega_0:
@@ -179,17 +178,10 @@ All analysis parameters, thresholds, plot dimensions, and algorithmic constants
179178
* Bandwidth parameterisation (Gao 2003): `beta1 = 2*omega_0`, `beta2 = omega_0^2`.
180179
* Minimises MSE(omega_hat, omega_meas) via golden-section search over `[ESO_OMEGA0_MIN, min(sample_rate/3, ESO_OMEGA0_MAX)]`.
181180
* **Stability constraint:** omega_0 < sample_rate / 3 (enforced automatically).
182-
* **Output:** Prints optimal omega_0, beta1, beta2, and MSE per axis to console. Results are also included in the markdown report if `--report` is also given.
181+
* **Output:** Prints optimal omega_0, beta1, beta2, and MSE per axis to console.
183182
* **Limitations:** `b0=1.0` (default) is dimensionless. For absolute accuracy co-tune b0 using known frame inertia. The cost function is MSE on the closed-loop observer output; unimodality is assumed over the search range.
184183

185-
### Statistical Report Output (Optional)
186184

187-
* **Purpose:** Produces a markdown file summarising per-axis signal statistics and optional ESO results alongside plot outputs.
188-
* **Activation:** Enable with `--report`. File is written as `<stem>_report.md` in the output directory.
189-
* **Content (`src/report.rs`):**
190-
* **Metadata:** Sample rate, total row count, flight duration, selected firmware/configuration keys.
191-
* **Per-axis tables:** Mean, std dev, min, max, RMS, sample count for gyro (filtered), setpoint, PID sum, P-term, I-term, and D-term.
192-
* **ESO table (when `--eso` is also active):** omega_0, beta1, beta2, b0, MSE, and sample count per axis.
193185

194186

195187

@@ -212,8 +204,6 @@ When `--step` flag is not used, all plots below are generated:
212204

213205
#### Generated Reports
214206

215-
- **`*_report.md`** — Markdown statistical report (requires `--report`). Per-axis signal statistics table and, when combined with `--eso`, the optimised LESO gains.
216-
217207
#### P:D Ratio Recommendations
218208

219209
The system provides intelligent P:D tuning recommendations based on step-response peak analysis:

src/constants.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,3 +393,8 @@ pub const TORQUE_PROFILER_P_SCALE: f64 = 100.0;
393393
/// Empirically calibrated on a 5" 6S freestyle build (HELIO H7); may need
394394
/// adjustment for significantly heavier or lighter aircraft classes.
395395
pub const TORQUE_PROFILER_ACHIEVABILITY_FACTOR: f64 = 2.50;
396+
397+
/// Betaflight/EmuFlight debug_mode value for GYRO_SCALED.
398+
/// Only this mode populates debug[0-2] with raw unfiltered gyro data,
399+
/// making it a valid fallback source for gyroUnfilt.
400+
pub const DEBUG_MODE_GYRO_SCALED: u32 = 6;

src/data_input/log_parser.rs

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::fs::File;
66
use std::io::{BufRead, BufReader, Seek, SeekFrom};
77
use std::path::Path;
88

9+
use crate::constants::DEBUG_MODE_GYRO_SCALED;
910
use crate::data_input::log_data::LogRowData;
1011
use crate::types::LogParseResult;
1112

@@ -206,6 +207,7 @@ pub fn parse_log_file(input_file_path: &Path, debug_mode: bool) -> LogParseResul
206207

207208
let header_indices: Vec<Option<usize>>;
208209
let mut motor_indices: Vec<usize> = Vec::new();
210+
let using_debug_fallback: bool;
209211

210212
// Read CSV header and map target headers to indices.
211213
{
@@ -442,22 +444,39 @@ pub fn parse_log_file(input_file_path: &Path, debug_mode: bool) -> LogParseResul
442444
.into());
443445
}
444446

445-
// Report debug fallback usage if applicable
446-
let using_debug_fallback = !gyro_unfilt_header_found.iter().any(|&found| found)
447-
&& debug_header_found.iter().take(3).any(|&found| found);
448-
449-
if using_debug_fallback {
450-
println!(" ⚠️ Using debug[0-2] as fallback for gyroUnfilt[0-2]");
451-
452-
// Try to report which debug mode is being used
453-
if let Some((_, debug_mode_value)) =
454-
header_metadata.iter().find(|(k, _)| k == "debug_mode")
455-
{
456-
if let Ok(debug_int) = debug_mode_value.parse::<u32>() {
457-
println!(" Debug mode value: {}", debug_int);
447+
// Apply debug[0-2] fallback for gyroUnfilt only when debug_mode=GYRO_SCALED (6).
448+
// Other debug modes do not populate debug[0-2] with raw gyro data.
449+
let have_gyro_unfilt = gyro_unfilt_header_found.iter().any(|&found| found);
450+
let have_debug_axes = debug_header_found.iter().take(3).any(|&found| found);
451+
using_debug_fallback = if !have_gyro_unfilt && have_debug_axes {
452+
let debug_mode_val = header_metadata
453+
.iter()
454+
.find(|(k, _)| k == "debug_mode")
455+
.and_then(|(_, v)| v.parse::<u32>().ok());
456+
match debug_mode_val {
457+
Some(DEBUG_MODE_GYRO_SCALED) => {
458+
// GYRO_SCALED: debug[0-2] contains raw unfiltered gyro
459+
println!(
460+
" ⚠️ Using debug[0-2] as gyroUnfilt fallback (debug_mode=GYRO_SCALED)"
461+
);
462+
true
463+
}
464+
Some(mode) => {
465+
println!(
466+
" ⚠️ Skipping debug[0-2] gyroUnfilt fallback: debug_mode={} is not GYRO_SCALED ({}) — unfiltered gyro analysis unavailable",
467+
mode, DEBUG_MODE_GYRO_SCALED
468+
);
469+
false
470+
}
471+
None => {
472+
// debug_mode absent from headers — allow but warn
473+
println!(" ⚠️ Using debug[0-2] as gyroUnfilt fallback (debug_mode unknown, assuming GYRO_SCALED)");
474+
true
458475
}
459476
}
460-
}
477+
} else {
478+
false
479+
};
461480
}
462481

463482
// --- Data Reading and Storage ---
@@ -547,10 +566,12 @@ pub fn parse_log_file(input_file_path: &Path, debug_mode: bool) -> LogParseResul
547566
}
548567

549568
// Apply Fallback Logic for gyro_unfilt (debug[0-2] --> gyroUnfilt[0-2])
569+
// Fallback is only valid when debug_mode=GYRO_SCALED (6); see using_debug_fallback.
550570
for axis in 0..crate::axis_names::AXIS_NAMES.len() {
551571
current_row_data.gyro_unfilt[axis] = match parsed_gyro_unfilt[axis] {
552572
Some(val) => Some(val),
553-
None => parsed_debug[axis],
573+
None if using_debug_fallback => parsed_debug[axis],
574+
None => None,
554575
};
555576
}
556577

0 commit comments

Comments
 (0)