inav-toolkit 2.17.0__tar.gz → 2.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/PKG-INFO +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/__init__.py +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/blackbox_analyzer.py +214 -80
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/flight_db.py +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/msp.py +12 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/param_analyzer.py +119 -22
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/vtol_configurator.py +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/wizard.py +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/PKG-INFO +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/pyproject.toml +1 -1
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/LICENSE +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/README.md +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/autotune.py +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/i18n.py +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/en.json +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/es.json +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/pt_BR.json +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/SOURCES.txt +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/dependency_links.txt +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/entry_points.txt +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/requires.txt +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/top_level.txt +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/setup.cfg +0 -0
- {inav_toolkit-2.17.0 → inav_toolkit-2.19.0}/tests/test_smoke.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
INAV Blackbox Analyzer - Multirotor Tuning Tool v2.
|
|
3
|
+
INAV Blackbox Analyzer - Multirotor Tuning Tool v2.19.0
|
|
4
4
|
=====================================================
|
|
5
5
|
Analyzes INAV blackbox logs and tells you EXACTLY what to change.
|
|
6
6
|
|
|
@@ -104,7 +104,7 @@ def _disable_colors():
|
|
|
104
104
|
AXIS_NAMES = ["Roll", "Pitch", "Yaw"]
|
|
105
105
|
AXIS_COLORS = ["#FF6B6B", "#4ECDC4", "#FFD93D"]
|
|
106
106
|
MOTOR_COLORS = ["#FF6B6B", "#4ECDC4", "#FFD93D", "#A78BFA"]
|
|
107
|
-
REPORT_VERSION = "2.
|
|
107
|
+
REPORT_VERSION = "2.19.0"
|
|
108
108
|
|
|
109
109
|
# ─── Frame and Prop Profiles ─────────────────────────────────────────────────
|
|
110
110
|
# Two separate concerns:
|
|
@@ -882,7 +882,7 @@ CLI_BOOL_KEYS = {
|
|
|
882
882
|
}
|
|
883
883
|
|
|
884
884
|
|
|
885
|
-
def merge_diff_into_config(config,
|
|
885
|
+
def merge_diff_into_config(config, config_raw):
|
|
886
886
|
"""Merge INAV CLI 'diff all' output into the analysis config dict.
|
|
887
887
|
|
|
888
888
|
Strategy:
|
|
@@ -895,19 +895,19 @@ def merge_diff_into_config(config, diff_raw):
|
|
|
895
895
|
|
|
896
896
|
Args:
|
|
897
897
|
config: Existing config dict from extract_fc_config()
|
|
898
|
-
|
|
898
|
+
config_raw: Raw 'diff all' output string
|
|
899
899
|
|
|
900
900
|
Returns:
|
|
901
901
|
Number of settings merged
|
|
902
902
|
"""
|
|
903
|
-
if not
|
|
903
|
+
if not config_raw:
|
|
904
904
|
return 0
|
|
905
905
|
|
|
906
906
|
try:
|
|
907
907
|
from inav_toolkit.flight_db import parse_diff_output
|
|
908
908
|
except ImportError:
|
|
909
909
|
from inav_flight_db import parse_diff_output
|
|
910
|
-
diff_settings = parse_diff_output(
|
|
910
|
+
diff_settings = parse_diff_output(config_raw)
|
|
911
911
|
|
|
912
912
|
merged = 0
|
|
913
913
|
mismatches = []
|
|
@@ -6098,10 +6098,95 @@ def count_blackbox_logs(filepath):
|
|
|
6098
6098
|
return max(1, count)
|
|
6099
6099
|
|
|
6100
6100
|
|
|
6101
|
+
# ─── Post-Analysis Cleanup ────────────────────────────────────────────────────
|
|
6102
|
+
|
|
6103
|
+
def _post_analysis_cleanup(blackbox_dir, raw_download, split_files, analyzed_file,
|
|
6104
|
+
archive=False, keep_logs=False):
|
|
6105
|
+
"""Clean up blackbox directory after successful analysis.
|
|
6106
|
+
|
|
6107
|
+
Default: delete raw download and all split files.
|
|
6108
|
+
--archive: compress analyzed flight to archive/ first.
|
|
6109
|
+
--keep-logs: skip everything.
|
|
6110
|
+
|
|
6111
|
+
Also cleans stale .bbl files from previous sessions.
|
|
6112
|
+
"""
|
|
6113
|
+
if keep_logs:
|
|
6114
|
+
return
|
|
6115
|
+
|
|
6116
|
+
R, B, C, G, Y, RED, DIM = _colors()
|
|
6117
|
+
deleted = 0
|
|
6118
|
+
archived = 0
|
|
6119
|
+
|
|
6120
|
+
# Archive the analyzed flight if requested
|
|
6121
|
+
if archive and analyzed_file and os.path.isfile(analyzed_file):
|
|
6122
|
+
import gzip
|
|
6123
|
+
archive_dir = os.path.join(blackbox_dir, "archive")
|
|
6124
|
+
os.makedirs(archive_dir, exist_ok=True)
|
|
6125
|
+
gz_name = os.path.basename(analyzed_file) + ".gz"
|
|
6126
|
+
gz_path = os.path.join(archive_dir, gz_name)
|
|
6127
|
+
try:
|
|
6128
|
+
with open(analyzed_file, "rb") as f_in:
|
|
6129
|
+
with gzip.open(gz_path, "wb") as f_out:
|
|
6130
|
+
f_out.write(f_in.read())
|
|
6131
|
+
archived_size = os.path.getsize(gz_path)
|
|
6132
|
+
original_size = os.path.getsize(analyzed_file)
|
|
6133
|
+
ratio = archived_size / original_size * 100 if original_size > 0 else 0
|
|
6134
|
+
print(f" Archived: {gz_name} ({archived_size // 1024}KB, {ratio:.0f}% of original)")
|
|
6135
|
+
archived = 1
|
|
6136
|
+
except Exception as e:
|
|
6137
|
+
print(f" Warning: archive failed: {e}")
|
|
6138
|
+
|
|
6139
|
+
# Delete the raw download file
|
|
6140
|
+
if raw_download and os.path.isfile(raw_download):
|
|
6141
|
+
try:
|
|
6142
|
+
sz = os.path.getsize(raw_download) // 1024
|
|
6143
|
+
os.remove(raw_download)
|
|
6144
|
+
deleted += 1
|
|
6145
|
+
except Exception:
|
|
6146
|
+
pass
|
|
6147
|
+
|
|
6148
|
+
# Delete all split files
|
|
6149
|
+
if split_files:
|
|
6150
|
+
for sf in split_files:
|
|
6151
|
+
if sf and os.path.isfile(sf):
|
|
6152
|
+
try:
|
|
6153
|
+
os.remove(sf)
|
|
6154
|
+
deleted += 1
|
|
6155
|
+
except Exception:
|
|
6156
|
+
pass
|
|
6157
|
+
|
|
6158
|
+
# Clean stale .bbl files from previous sessions
|
|
6159
|
+
# (anything not from the current download)
|
|
6160
|
+
current_base = os.path.basename(raw_download) if raw_download else ""
|
|
6161
|
+
if os.path.isdir(blackbox_dir):
|
|
6162
|
+
for fname in os.listdir(blackbox_dir):
|
|
6163
|
+
if not fname.endswith(".bbl"):
|
|
6164
|
+
continue
|
|
6165
|
+
fpath = os.path.join(blackbox_dir, fname)
|
|
6166
|
+
if fpath == raw_download:
|
|
6167
|
+
continue # already handled
|
|
6168
|
+
if any(fpath == sf for sf in (split_files or [])):
|
|
6169
|
+
continue # already handled
|
|
6170
|
+
# This is a stale .bbl from a previous session
|
|
6171
|
+
try:
|
|
6172
|
+
os.remove(fpath)
|
|
6173
|
+
deleted += 1
|
|
6174
|
+
except Exception:
|
|
6175
|
+
pass
|
|
6176
|
+
|
|
6177
|
+
if deleted > 0 or archived > 0:
|
|
6178
|
+
parts = []
|
|
6179
|
+
if deleted > 0:
|
|
6180
|
+
parts.append(f"{deleted} log files removed")
|
|
6181
|
+
if archived > 0:
|
|
6182
|
+
parts.append(f"1 archived")
|
|
6183
|
+
print(f" Cleanup: {', '.join(parts)}")
|
|
6184
|
+
|
|
6185
|
+
|
|
6101
6186
|
# ─── Main ─────────────────────────────────────────────────────────────────────
|
|
6102
6187
|
|
|
6103
6188
|
def main():
|
|
6104
|
-
parser = argparse.ArgumentParser(description="INAV Blackbox Analyzer v2.
|
|
6189
|
+
parser = argparse.ArgumentParser(description="INAV Blackbox Analyzer v2.19.0 - Prescriptive Tuning",
|
|
6105
6190
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
6106
6191
|
parser.add_argument("--version", action="version", version=f"inav-analyze {REPORT_VERSION}")
|
|
6107
6192
|
parser.add_argument("logfile", nargs="?", default=None,
|
|
@@ -6115,8 +6200,14 @@ def main():
|
|
|
6115
6200
|
parser.add_argument("--device", metavar="PORT",
|
|
6116
6201
|
help="Download blackbox from FC via serial. Use 'auto' to scan "
|
|
6117
6202
|
"or specify port (e.g., /dev/ttyACM0, COM3).")
|
|
6118
|
-
parser.add_argument("--erase", action="store_true",
|
|
6119
|
-
help="
|
|
6203
|
+
parser.add_argument("--no-erase", action="store_true",
|
|
6204
|
+
help="Don't erase FC flash after successful download and analysis. "
|
|
6205
|
+
"Default: flash is erased automatically after pipeline completes.")
|
|
6206
|
+
parser.add_argument("--archive", action="store_true",
|
|
6207
|
+
help="Compress the analyzed flight log to blackbox/archive/ instead of deleting. "
|
|
6208
|
+
"Builds a compressed history for re-analysis with future versions.")
|
|
6209
|
+
parser.add_argument("--keep-logs", action="store_true",
|
|
6210
|
+
help="Skip all cleanup - keep raw downloads, splits, and don't erase flash.")
|
|
6120
6211
|
parser.add_argument("--download-only", action="store_true",
|
|
6121
6212
|
help="Download blackbox from device but don't analyze.")
|
|
6122
6213
|
parser.add_argument("--blackbox-dir", default="./blackbox",
|
|
@@ -6141,11 +6232,11 @@ def main():
|
|
|
6141
6232
|
help="Omit the plain-English description of the quad's behavior.")
|
|
6142
6233
|
parser.add_argument("--no-color", action="store_true",
|
|
6143
6234
|
help="Disable colored terminal output.")
|
|
6144
|
-
parser.add_argument("--
|
|
6145
|
-
help="Path to a CLI 'diff all' file for enriched analysis. "
|
|
6235
|
+
parser.add_argument("--config", metavar="FILE", default=None,
|
|
6236
|
+
help="Path to a CLI 'dump all' or 'diff all' file for enriched analysis. "
|
|
6146
6237
|
"Adds config cross-referencing to nav and tuning results. "
|
|
6147
6238
|
"Not needed with --device (config is pulled automatically). "
|
|
6148
|
-
"Also auto-discovered if a *_diff.txt file sits next to the BBL.")
|
|
6239
|
+
"Also auto-discovered if a *_dump.txt or *_diff.txt file sits next to the BBL.")
|
|
6149
6240
|
parser.add_argument("--nav", action="store_true",
|
|
6150
6241
|
help="Enable navigation health analysis (compass, GPS, baro, "
|
|
6151
6242
|
"estimator). Works on any flight with nav fields in the log.")
|
|
@@ -6187,7 +6278,8 @@ def main():
|
|
|
6187
6278
|
|
|
6188
6279
|
# ── Device mode: download blackbox from FC ──
|
|
6189
6280
|
logfile = args.logfile
|
|
6190
|
-
|
|
6281
|
+
config_raw = None
|
|
6282
|
+
device_port = None
|
|
6191
6283
|
if args.device:
|
|
6192
6284
|
try:
|
|
6193
6285
|
try:
|
|
@@ -6257,19 +6349,18 @@ def main():
|
|
|
6257
6349
|
pct = summary['used_size'] * 100 // summary['total_size'] if summary['total_size'] > 0 else 0
|
|
6258
6350
|
print(f" Dataflash: {used_kb:.0f}KB / {total_kb:.0f}KB ({pct}% used)")
|
|
6259
6351
|
|
|
6260
|
-
# Pull
|
|
6261
|
-
|
|
6262
|
-
print(" Pulling configuration (
|
|
6263
|
-
|
|
6264
|
-
if
|
|
6265
|
-
n_settings = len([l for l in
|
|
6266
|
-
print(f" {n_settings}
|
|
6267
|
-
|
|
6268
|
-
diff_path = os.path.join(args.blackbox_dir, f"{info['craft_name'] or 'fc'}_diff.txt")
|
|
6352
|
+
# Pull full config dump (all parameters — used for analysis, fingerprinting, and backup)
|
|
6353
|
+
config_raw = None
|
|
6354
|
+
print(" Pulling configuration (dump all)...", end="", flush=True)
|
|
6355
|
+
config_raw = fc.get_dump_all(timeout=30.0)
|
|
6356
|
+
if config_raw:
|
|
6357
|
+
n_settings = len([l for l in config_raw.splitlines() if l.strip().startswith("set ")])
|
|
6358
|
+
print(f" {n_settings} parameters")
|
|
6359
|
+
config_path = os.path.join(args.blackbox_dir, f"{info['craft_name'] or 'fc'}_dump.txt")
|
|
6269
6360
|
os.makedirs(args.blackbox_dir, exist_ok=True)
|
|
6270
|
-
with open(
|
|
6271
|
-
f.write(
|
|
6272
|
-
print(f" Saved: {
|
|
6361
|
+
with open(config_path, "w") as f:
|
|
6362
|
+
f.write(config_raw)
|
|
6363
|
+
print(f" Saved: {config_path}")
|
|
6273
6364
|
else:
|
|
6274
6365
|
print(" no response (FC may not support CLI over MSP)")
|
|
6275
6366
|
|
|
@@ -6280,13 +6371,16 @@ def main():
|
|
|
6280
6371
|
print()
|
|
6281
6372
|
filepath = fc.download_blackbox(
|
|
6282
6373
|
output_dir=args.blackbox_dir,
|
|
6283
|
-
erase_after=
|
|
6374
|
+
erase_after=False, # erase happens after successful analysis
|
|
6284
6375
|
)
|
|
6285
6376
|
|
|
6286
6377
|
if not filepath:
|
|
6287
6378
|
print(" ERROR: Download failed.")
|
|
6288
6379
|
sys.exit(1)
|
|
6289
6380
|
|
|
6381
|
+
# Store port for post-analysis erase
|
|
6382
|
+
device_port = fc.port_path if hasattr(fc, 'port_path') else args.device
|
|
6383
|
+
|
|
6290
6384
|
if args.download_only:
|
|
6291
6385
|
print(f"\n To analyze:\n python3 {sys.argv[0]} {filepath}")
|
|
6292
6386
|
sys.exit(0)
|
|
@@ -6311,39 +6405,42 @@ def main():
|
|
|
6311
6405
|
print(f"ERROR: File not found: {logfile}"); sys.exit(1)
|
|
6312
6406
|
|
|
6313
6407
|
# ── Load diff from file (when not using --device) ──
|
|
6314
|
-
if
|
|
6315
|
-
|
|
6316
|
-
if args.
|
|
6317
|
-
# Explicit file path: --
|
|
6318
|
-
|
|
6408
|
+
if config_raw is None:
|
|
6409
|
+
config_file = None
|
|
6410
|
+
if args.config:
|
|
6411
|
+
# Explicit file path: --config my_dump.txt
|
|
6412
|
+
config_file = args.config
|
|
6319
6413
|
else:
|
|
6320
|
-
# Auto-discover
|
|
6414
|
+
# Auto-discover config files in same directory as BBL
|
|
6415
|
+
# Prefer dump (complete) over diff (only changed params)
|
|
6321
6416
|
log_dir = os.path.dirname(os.path.abspath(logfile))
|
|
6322
6417
|
candidates = []
|
|
6323
6418
|
for fname in os.listdir(log_dir):
|
|
6324
|
-
if fname.endswith("
|
|
6419
|
+
if (fname.endswith("_dump.txt") or fname.endswith("_diff.txt")
|
|
6420
|
+
or fname in ("dump.txt", "dump_all.txt", "diff.txt", "diff_all.txt")):
|
|
6325
6421
|
candidates.append(os.path.join(log_dir, fname))
|
|
6326
|
-
if
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6422
|
+
if candidates:
|
|
6423
|
+
# Prefer dump over diff, then most recent
|
|
6424
|
+
def _score(path):
|
|
6425
|
+
is_dump = "_dump" in path or "dump" in os.path.basename(path)
|
|
6426
|
+
return (1 if is_dump else 0, os.path.getmtime(path))
|
|
6427
|
+
config_file = max(candidates, key=_score)
|
|
6428
|
+
|
|
6429
|
+
if config_file:
|
|
6430
|
+
if os.path.isfile(config_file):
|
|
6334
6431
|
try:
|
|
6335
|
-
with open(
|
|
6336
|
-
|
|
6337
|
-
n_settings = len([l for l in
|
|
6432
|
+
with open(config_file, "r", errors="ignore") as f:
|
|
6433
|
+
config_raw = f.read()
|
|
6434
|
+
n_settings = len([l for l in config_raw.splitlines()
|
|
6338
6435
|
if l.strip().startswith("set ")])
|
|
6339
6436
|
if n_settings > 0:
|
|
6340
|
-
print(f" Config: {os.path.basename(
|
|
6437
|
+
print(f" Config: {os.path.basename(config_file)} ({n_settings} settings)")
|
|
6341
6438
|
else:
|
|
6342
|
-
|
|
6439
|
+
config_raw = None # not a valid config file
|
|
6343
6440
|
except Exception:
|
|
6344
|
-
|
|
6441
|
+
config_raw = None
|
|
6345
6442
|
else:
|
|
6346
|
-
print(f" Warning:
|
|
6443
|
+
print(f" Warning: Config file not found: {config_file}")
|
|
6347
6444
|
|
|
6348
6445
|
# ── History mode: show progression and exit ──
|
|
6349
6446
|
if args.history or args.trend:
|
|
@@ -6374,14 +6471,14 @@ def main():
|
|
|
6374
6471
|
if not os.path.isfile(args.compare):
|
|
6375
6472
|
print(f"ERROR: Comparison file not found: {args.compare}")
|
|
6376
6473
|
sys.exit(1)
|
|
6377
|
-
_run_comparison(logfile, args.compare, args,
|
|
6474
|
+
_run_comparison(logfile, args.compare, args, config_raw)
|
|
6378
6475
|
return
|
|
6379
6476
|
|
|
6380
6477
|
# ── Replay mode: interactive HTML time-series ──
|
|
6381
6478
|
if args.replay:
|
|
6382
6479
|
if not logfile:
|
|
6383
6480
|
parser.error("logfile required for --replay")
|
|
6384
|
-
_run_replay(logfile, args,
|
|
6481
|
+
_run_replay(logfile, args, config_raw)
|
|
6385
6482
|
return
|
|
6386
6483
|
|
|
6387
6484
|
# ── Log quality check mode ──
|
|
@@ -6415,20 +6512,48 @@ def main():
|
|
|
6415
6512
|
# Flash may contain flights from multiple sessions (config changes between
|
|
6416
6513
|
# arm cycles). Only the LATEST session's best flight deserves full analysis.
|
|
6417
6514
|
# Earlier flights are stored in DB for progression but shown as one-liners.
|
|
6515
|
+
analyzed_file = None
|
|
6418
6516
|
if getattr(args, 'nav', False):
|
|
6419
6517
|
# Nav mode: skip multi-flight tuning scan, analyze last flight directly
|
|
6420
6518
|
target = log_files[-1]
|
|
6421
6519
|
if len(log_files) > 1:
|
|
6422
6520
|
print(f"\n Nav mode: analyzing last flight ({len(log_files)} in flash)")
|
|
6423
|
-
_analyze_single_log(target, args,
|
|
6521
|
+
_analyze_single_log(target, args, config_raw)
|
|
6522
|
+
analyzed_file = target
|
|
6424
6523
|
elif len(log_files) > 1:
|
|
6425
|
-
_process_multi_log(log_files, args,
|
|
6524
|
+
analyzed_file = _process_multi_log(log_files, args, config_raw)
|
|
6426
6525
|
else:
|
|
6427
6526
|
# Single flight - full analysis
|
|
6428
|
-
_analyze_single_log(log_files[0], args,
|
|
6527
|
+
_analyze_single_log(log_files[0], args, config_raw)
|
|
6528
|
+
analyzed_file = log_files[0]
|
|
6529
|
+
|
|
6530
|
+
# ── Post-analysis cleanup (only in device mode) ──
|
|
6531
|
+
if device_port and not args.keep_logs:
|
|
6532
|
+
# raw_download is the original downloaded file (pre-split)
|
|
6533
|
+
# For single-flight, it's the same as the analyzed file
|
|
6534
|
+
raw_download = logfile
|
|
6535
|
+
split_files = log_files if n_logs > 1 else []
|
|
6536
|
+
|
|
6537
|
+
_post_analysis_cleanup(
|
|
6538
|
+
args.blackbox_dir, raw_download, split_files, analyzed_file,
|
|
6539
|
+
archive=args.archive, keep_logs=args.keep_logs)
|
|
6540
|
+
|
|
6541
|
+
# Erase FC flash
|
|
6542
|
+
if not args.no_erase:
|
|
6543
|
+
try:
|
|
6544
|
+
try:
|
|
6545
|
+
from inav_toolkit.msp import INAVDevice
|
|
6546
|
+
except ImportError:
|
|
6547
|
+
from inav_msp import INAVDevice
|
|
6548
|
+
fc2 = INAVDevice(device_port)
|
|
6549
|
+
fc2.open()
|
|
6550
|
+
fc2.erase_dataflash()
|
|
6551
|
+
fc2.close()
|
|
6552
|
+
except Exception as e:
|
|
6553
|
+
print(f" Erase failed: {e}")
|
|
6429
6554
|
|
|
6430
6555
|
|
|
6431
|
-
def _process_multi_log(log_files, args,
|
|
6556
|
+
def _process_multi_log(log_files, args, config_raw):
|
|
6432
6557
|
"""Handle multiple flights from a single flash download.
|
|
6433
6558
|
|
|
6434
6559
|
Strategy:
|
|
@@ -6444,7 +6569,7 @@ def _process_multi_log(log_files, args, diff_raw):
|
|
|
6444
6569
|
print(f" Scanning {len(log_files)} flights...")
|
|
6445
6570
|
summaries = []
|
|
6446
6571
|
for lf in log_files:
|
|
6447
|
-
s = _analyze_single_log(lf, args,
|
|
6572
|
+
s = _analyze_single_log(lf, args, config_raw, summary_only=True)
|
|
6448
6573
|
if s:
|
|
6449
6574
|
summaries.append(s)
|
|
6450
6575
|
|
|
@@ -6457,7 +6582,7 @@ def _process_multi_log(log_files, args, diff_raw):
|
|
|
6457
6582
|
# header config matches the diff are "current session" (post-change),
|
|
6458
6583
|
# flights that don't match are "old session" (pre-change).
|
|
6459
6584
|
# Without a diff, fall back to comparing consecutive flights.
|
|
6460
|
-
current_fp = _fingerprint_from_diff(
|
|
6585
|
+
current_fp = _fingerprint_from_diff(config_raw)
|
|
6461
6586
|
is_current = [] # True/False per flight
|
|
6462
6587
|
|
|
6463
6588
|
if current_fp:
|
|
@@ -6581,7 +6706,7 @@ def _process_multi_log(log_files, args, diff_raw):
|
|
|
6581
6706
|
|
|
6582
6707
|
# ── Phase 5: Full analysis on the selected flight ──
|
|
6583
6708
|
print(f"{'═' * 70}")
|
|
6584
|
-
_analyze_single_log(log_files[best_idx], args,
|
|
6709
|
+
_analyze_single_log(log_files[best_idx], args, config_raw)
|
|
6585
6710
|
|
|
6586
6711
|
# ── Phase 6: Show cross-session progression ──
|
|
6587
6712
|
if not args.no_db:
|
|
@@ -6611,6 +6736,8 @@ def _process_multi_log(log_files, args, diff_raw):
|
|
|
6611
6736
|
except Exception:
|
|
6612
6737
|
pass
|
|
6613
6738
|
|
|
6739
|
+
return log_files[best_idx]
|
|
6740
|
+
|
|
6614
6741
|
|
|
6615
6742
|
def _verdict_short(verdict):
|
|
6616
6743
|
"""Short display string for verdict codes."""
|
|
@@ -6635,7 +6762,7 @@ def _config_fingerprint(config):
|
|
|
6635
6762
|
return "|".join(parts) if parts else ""
|
|
6636
6763
|
|
|
6637
6764
|
|
|
6638
|
-
def _fingerprint_from_diff(
|
|
6765
|
+
def _fingerprint_from_diff(config_raw):
|
|
6639
6766
|
"""Build a config fingerprint from CLI 'diff all' output.
|
|
6640
6767
|
|
|
6641
6768
|
This represents the FC's CURRENT config - the ground truth.
|
|
@@ -6643,14 +6770,14 @@ def _fingerprint_from_diff(diff_raw):
|
|
|
6643
6770
|
session; flights that don't match are from before the user applied
|
|
6644
6771
|
changes.
|
|
6645
6772
|
"""
|
|
6646
|
-
if not
|
|
6773
|
+
if not config_raw:
|
|
6647
6774
|
return ""
|
|
6648
6775
|
|
|
6649
6776
|
try:
|
|
6650
6777
|
from inav_toolkit.flight_db import parse_diff_output
|
|
6651
6778
|
except ImportError:
|
|
6652
6779
|
from inav_flight_db import parse_diff_output
|
|
6653
|
-
diff_settings = parse_diff_output(
|
|
6780
|
+
diff_settings = parse_diff_output(config_raw)
|
|
6654
6781
|
|
|
6655
6782
|
# Map CLI names → config keys (same mapping as merge_diff_into_config)
|
|
6656
6783
|
config = {}
|
|
@@ -6666,8 +6793,12 @@ def _fingerprint_from_diff(diff_raw):
|
|
|
6666
6793
|
return _config_fingerprint(config)
|
|
6667
6794
|
|
|
6668
6795
|
|
|
6669
|
-
def _print_config_review(
|
|
6670
|
-
"""Run parameter analyzer on FC
|
|
6796
|
+
def _print_config_review(config_raw, config, frame_inches, plan):
|
|
6797
|
+
"""Run parameter analyzer on FC config and show findings not covered by flight analysis.
|
|
6798
|
+
|
|
6799
|
+
Accepts either 'dump all' or 'diff all' output. Dump is preferred since it
|
|
6800
|
+
includes every parameter including unchanged defaults, which matters for
|
|
6801
|
+
nav PID checks on large frames.
|
|
6671
6802
|
|
|
6672
6803
|
Only shows CRITICAL and WARNING findings from categories that the blackbox
|
|
6673
6804
|
analyzer doesn't cover (safety, nav, motor protocol, GPS, battery, RX).
|
|
@@ -6676,6 +6807,9 @@ def _print_config_review(diff_raw, config, frame_inches, plan):
|
|
|
6676
6807
|
"""
|
|
6677
6808
|
R, B, C, G, Y, RED, DIM = _colors()
|
|
6678
6809
|
|
|
6810
|
+
if not config_raw:
|
|
6811
|
+
return
|
|
6812
|
+
|
|
6679
6813
|
try:
|
|
6680
6814
|
try:
|
|
6681
6815
|
from inav_toolkit.param_analyzer import parse_diff_all, run_all_checks, CRITICAL, WARNING
|
|
@@ -6685,7 +6819,7 @@ def _print_config_review(diff_raw, config, frame_inches, plan):
|
|
|
6685
6819
|
return # param analyzer not available
|
|
6686
6820
|
|
|
6687
6821
|
try:
|
|
6688
|
-
parsed = parse_diff_all(
|
|
6822
|
+
parsed = parse_diff_all(config_raw)
|
|
6689
6823
|
except Exception:
|
|
6690
6824
|
return
|
|
6691
6825
|
|
|
@@ -6956,15 +7090,15 @@ footer {{ text-align:center; color:#555; margin-top:30px; padding:10px; border-t
|
|
|
6956
7090
|
|
|
6957
7091
|
# ─── Comparison Mode ─────────────────────────────────────────────────────────
|
|
6958
7092
|
|
|
6959
|
-
def _analyze_for_compare(logfile, args,
|
|
7093
|
+
def _analyze_for_compare(logfile, args, config_raw=None):
|
|
6960
7094
|
"""Run analysis pipeline on a single file and return structured results.
|
|
6961
7095
|
Returns dict with: config, data, noise_results, pid_results, motor_analysis,
|
|
6962
7096
|
dterm_results, plan, noise_fp, hover_osc, profile
|
|
6963
7097
|
"""
|
|
6964
7098
|
raw_params = parse_headers_from_bbl(logfile)
|
|
6965
7099
|
config = extract_fc_config(raw_params)
|
|
6966
|
-
if
|
|
6967
|
-
merge_diff_into_config(config,
|
|
7100
|
+
if config_raw:
|
|
7101
|
+
merge_diff_into_config(config, config_raw)
|
|
6968
7102
|
|
|
6969
7103
|
# Auto-detect frame
|
|
6970
7104
|
craft = config.get("craft_name", "")
|
|
@@ -7249,7 +7383,7 @@ footer{{text-align:center;color:var(--dm);font-size:.7rem;padding:24px 0;border-
|
|
|
7249
7383
|
</div><footer>INAV Blackbox Analyzer v{REPORT_VERSION} - Comparison Report</footer></body></html>"""
|
|
7250
7384
|
|
|
7251
7385
|
|
|
7252
|
-
def _run_comparison(file_a, file_b, args,
|
|
7386
|
+
def _run_comparison(file_a, file_b, args, config_raw):
|
|
7253
7387
|
"""Run comparative analysis on two flight logs."""
|
|
7254
7388
|
R, B, C, G, Y, RED, DIM = _colors()
|
|
7255
7389
|
|
|
@@ -7259,12 +7393,12 @@ def _run_comparison(file_a, file_b, args, diff_raw):
|
|
|
7259
7393
|
print()
|
|
7260
7394
|
|
|
7261
7395
|
print(f" Analyzing flight A...", end=" ", flush=True)
|
|
7262
|
-
res_a = _analyze_for_compare(file_a, args,
|
|
7396
|
+
res_a = _analyze_for_compare(file_a, args, config_raw)
|
|
7263
7397
|
sa = res_a["plan"]["scores"]
|
|
7264
7398
|
print(f"score {sa['overall']:.0f}/100")
|
|
7265
7399
|
|
|
7266
7400
|
print(f" Analyzing flight B...", end=" ", flush=True)
|
|
7267
|
-
res_b = _analyze_for_compare(file_b, args,
|
|
7401
|
+
res_b = _analyze_for_compare(file_b, args, config_raw)
|
|
7268
7402
|
sb = res_b["plan"]["scores"]
|
|
7269
7403
|
print(f"score {sb['overall']:.0f}/100")
|
|
7270
7404
|
|
|
@@ -7774,7 +7908,7 @@ allPlotIds.forEach(srcId => {{
|
|
|
7774
7908
|
</script></body></html>"""
|
|
7775
7909
|
|
|
7776
7910
|
|
|
7777
|
-
def _run_replay(logfile, args,
|
|
7911
|
+
def _run_replay(logfile, args, config_raw):
|
|
7778
7912
|
"""Generate interactive replay HTML for a single flight."""
|
|
7779
7913
|
R, B, C, G, Y, RED, DIM = _colors()
|
|
7780
7914
|
|
|
@@ -7783,8 +7917,8 @@ def _run_replay(logfile, args, diff_raw):
|
|
|
7783
7917
|
|
|
7784
7918
|
raw_params = parse_headers_from_bbl(logfile)
|
|
7785
7919
|
config = extract_fc_config(raw_params)
|
|
7786
|
-
if
|
|
7787
|
-
merge_diff_into_config(config,
|
|
7920
|
+
if config_raw:
|
|
7921
|
+
merge_diff_into_config(config, config_raw)
|
|
7788
7922
|
|
|
7789
7923
|
ext = os.path.splitext(logfile)[1].lower()
|
|
7790
7924
|
is_blackbox = ext in (".bbl", ".bfl", ".bbs")
|
|
@@ -7833,13 +7967,13 @@ def _run_replay(logfile, args, diff_raw):
|
|
|
7833
7967
|
print()
|
|
7834
7968
|
|
|
7835
7969
|
|
|
7836
|
-
def _analyze_single_log(logfile, args,
|
|
7970
|
+
def _analyze_single_log(logfile, args, config_raw=None, summary_only=False):
|
|
7837
7971
|
"""Analyze a single blackbox log file.
|
|
7838
7972
|
|
|
7839
7973
|
Args:
|
|
7840
7974
|
logfile: Path to log file
|
|
7841
7975
|
args: Command line arguments
|
|
7842
|
-
|
|
7976
|
+
config_raw: Optional CLI diff text
|
|
7843
7977
|
summary_only: If True, skip verbose output/reports but still analyze
|
|
7844
7978
|
and store in DB. Returns a summary dict.
|
|
7845
7979
|
|
|
@@ -7878,8 +8012,8 @@ def _analyze_single_log(logfile, args, diff_raw=None, summary_only=False):
|
|
|
7878
8012
|
config = extract_fc_config(raw_params)
|
|
7879
8013
|
|
|
7880
8014
|
# ── Merge CLI diff if available ──
|
|
7881
|
-
if
|
|
7882
|
-
n_merged = merge_diff_into_config(config,
|
|
8015
|
+
if config_raw:
|
|
8016
|
+
n_merged = merge_diff_into_config(config, config_raw)
|
|
7883
8017
|
mismatches = config.get("_diff_mismatches", [])
|
|
7884
8018
|
if n_merged > 0 or mismatches:
|
|
7885
8019
|
parts = [f"{n_merged} new settings from CLI diff"]
|
|
@@ -8226,7 +8360,7 @@ def _analyze_single_log(logfile, args, diff_raw=None, summary_only=False):
|
|
|
8226
8360
|
db = FlightDB(args.db_path)
|
|
8227
8361
|
flight_id, is_new = db.store_flight(
|
|
8228
8362
|
plan, config, data, hover_osc, motor_analysis,
|
|
8229
|
-
pid_results, noise_results, log_file=logfile,
|
|
8363
|
+
pid_results, noise_results, log_file=logfile, config_raw=config_raw)
|
|
8230
8364
|
db.close()
|
|
8231
8365
|
summary["flight_id"] = flight_id
|
|
8232
8366
|
summary["is_new"] = is_new
|
|
@@ -8241,8 +8375,8 @@ def _analyze_single_log(logfile, args, diff_raw=None, summary_only=False):
|
|
|
8241
8375
|
# ── Config review from diff (if available) ──
|
|
8242
8376
|
# Runs parameter analyzer checks on the FC's current config.
|
|
8243
8377
|
# Catches safety, nav, motor protocol issues that flight data alone can't detect.
|
|
8244
|
-
if
|
|
8245
|
-
_print_config_review(
|
|
8378
|
+
if config_raw and not args.no_terminal and plan["verdict"] != "GROUND_ONLY":
|
|
8379
|
+
_print_config_review(config_raw, config, frame_inches, plan)
|
|
8246
8380
|
|
|
8247
8381
|
nav_results = None # nav analysis only runs in --nav mode
|
|
8248
8382
|
if not args.no_html and plan["verdict"] != "GROUND_ONLY":
|
|
@@ -8284,7 +8418,7 @@ def _analyze_single_log(logfile, args, diff_raw=None, summary_only=False):
|
|
|
8284
8418
|
db = FlightDB(args.db_path)
|
|
8285
8419
|
flight_id, is_new = db.store_flight(
|
|
8286
8420
|
plan, config, data, hover_osc, motor_analysis,
|
|
8287
|
-
pid_results, noise_results, log_file=logfile,
|
|
8421
|
+
pid_results, noise_results, log_file=logfile, config_raw=config_raw)
|
|
8288
8422
|
craft = config.get("craft_name", "unknown")
|
|
8289
8423
|
n_flights = db.get_flight_count(craft)
|
|
8290
8424
|
if is_new:
|
|
@@ -33,7 +33,7 @@ try:
|
|
|
33
33
|
except ImportError:
|
|
34
34
|
serial = None # Checked in open()
|
|
35
35
|
|
|
36
|
-
VERSION = "2.
|
|
36
|
+
VERSION = "2.19.0"
|
|
37
37
|
|
|
38
38
|
# ─── MSP Command IDs ─────────────────────────────────────────────────────────
|
|
39
39
|
|
|
@@ -1005,6 +1005,17 @@ class INAVDevice:
|
|
|
1005
1005
|
"""
|
|
1006
1006
|
return self.cli_command("diff all", timeout=timeout)
|
|
1007
1007
|
|
|
1008
|
+
def get_dump_all(self, timeout=30.0):
|
|
1009
|
+
"""Pull the full 'dump all' configuration from the FC.
|
|
1010
|
+
|
|
1011
|
+
This is a complete backup of every parameter — much larger output
|
|
1012
|
+
than 'diff all' which only shows non-defaults.
|
|
1013
|
+
|
|
1014
|
+
Returns:
|
|
1015
|
+
Raw dump output string, or None on error
|
|
1016
|
+
"""
|
|
1017
|
+
return self.cli_command("dump all", timeout=timeout)
|
|
1018
|
+
|
|
1008
1019
|
def cli_batch(self, commands, timeout=5.0, save=True):
|
|
1009
1020
|
"""Send multiple CLI commands in a single CLI session.
|
|
1010
1021
|
|
|
@@ -21,7 +21,7 @@ import sys
|
|
|
21
21
|
import textwrap
|
|
22
22
|
from datetime import datetime
|
|
23
23
|
|
|
24
|
-
VERSION = "2.
|
|
24
|
+
VERSION = "2.19.0"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def _enable_ansi_colors():
|
|
@@ -811,7 +811,7 @@ def run_all_checks(parsed, frame_inches=None, blackbox_state=None):
|
|
|
811
811
|
findings.extend(check_motors_protocol(parsed))
|
|
812
812
|
findings.extend(check_filters(parsed, frame_inches))
|
|
813
813
|
findings.extend(check_pid_config(parsed, frame_inches))
|
|
814
|
-
findings.extend(check_navigation(parsed))
|
|
814
|
+
findings.extend(check_navigation(parsed, frame_inches))
|
|
815
815
|
findings.extend(check_gps(parsed))
|
|
816
816
|
findings.extend(check_blackbox(parsed))
|
|
817
817
|
findings.extend(check_battery(parsed))
|
|
@@ -1280,7 +1280,7 @@ def check_pid_config(parsed, frame_inches=None):
|
|
|
1280
1280
|
|
|
1281
1281
|
# ─── Navigation Checks ───────────────────────────────────────────────────────
|
|
1282
1282
|
|
|
1283
|
-
def check_navigation(parsed):
|
|
1283
|
+
def check_navigation(parsed, frame_inches=None):
|
|
1284
1284
|
findings = []
|
|
1285
1285
|
|
|
1286
1286
|
rth_alt = get_setting(parsed, "nav_rth_altitude", 5000)
|
|
@@ -1346,26 +1346,123 @@ def check_navigation(parsed):
|
|
|
1346
1346
|
setting="nav_mc_hover_thr",
|
|
1347
1347
|
current=str(hover)))
|
|
1348
1348
|
|
|
1349
|
-
#
|
|
1350
|
-
|
|
1351
|
-
|
|
1349
|
+
# ── Frame-aware nav PID checks ──
|
|
1350
|
+
# INAV defaults are tuned for 5" quads. Larger frames have more inertia
|
|
1351
|
+
# and need softer nav PIDs and longer deceleration time. The default
|
|
1352
|
+
# values cause oscillation on deceleration, overshoot on RTH arrival,
|
|
1353
|
+
# and bouncy position hold on 10"+ frames.
|
|
1354
|
+
#
|
|
1355
|
+
# Recommended ranges by frame size:
|
|
1356
|
+
# 5": pos_p=50-65 vel_p=35-50 vel_d=80-120 decel=100-150
|
|
1357
|
+
# 7": pos_p=40-55 vel_p=30-40 vel_d=80-120 decel=120-180
|
|
1358
|
+
# 10": pos_p=30-45 vel_p=20-30 vel_d=80-120 decel=180-280
|
|
1359
|
+
# 12": pos_p=25-40 vel_p=15-25 vel_d=80-120 decel=220-350
|
|
1360
|
+
# 15": pos_p=20-35 vel_p=10-20 vel_d=80-120 decel=280-400
|
|
1361
|
+
|
|
1362
|
+
nav_rec = None
|
|
1363
|
+
if frame_inches and frame_inches >= 7:
|
|
1364
|
+
if frame_inches >= 15:
|
|
1365
|
+
nav_rec = {"pos_p_max": 35, "vel_p_max": 20, "decel_min": 280,
|
|
1366
|
+
"pos_p_rec": 25, "vel_p_rec": 15, "vel_i_rec": 8, "decel_rec": 350}
|
|
1367
|
+
elif frame_inches >= 12:
|
|
1368
|
+
nav_rec = {"pos_p_max": 40, "vel_p_max": 25, "decel_min": 220,
|
|
1369
|
+
"pos_p_rec": 30, "vel_p_rec": 20, "vel_i_rec": 10, "decel_rec": 280}
|
|
1370
|
+
elif frame_inches >= 10:
|
|
1371
|
+
nav_rec = {"pos_p_max": 45, "vel_p_max": 30, "decel_min": 180,
|
|
1372
|
+
"pos_p_rec": 40, "vel_p_rec": 25, "vel_i_rec": 10, "decel_rec": 250}
|
|
1373
|
+
elif frame_inches >= 7:
|
|
1374
|
+
nav_rec = {"pos_p_max": 55, "vel_p_max": 40, "decel_min": 120,
|
|
1375
|
+
"pos_p_rec": 45, "vel_p_rec": 35, "vel_i_rec": 15, "decel_rec": 150}
|
|
1376
|
+
|
|
1377
|
+
# INAV defaults for nav PIDs (these won't appear in diff all if unchanged)
|
|
1378
|
+
INAV_NAV_DEFAULTS = {
|
|
1379
|
+
"nav_mc_pos_xy_p": 65,
|
|
1380
|
+
"nav_mc_vel_xy_p": 40,
|
|
1381
|
+
"nav_mc_vel_xy_i": 15,
|
|
1382
|
+
"nav_mc_pos_deceleration_time": 120,
|
|
1383
|
+
"nav_mc_heading_p": 60,
|
|
1384
|
+
}
|
|
1352
1385
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1386
|
+
pos_p = profile.get("nav_mc_pos_xy_p",
|
|
1387
|
+
get_setting(parsed, "nav_mc_pos_xy_p", INAV_NAV_DEFAULTS["nav_mc_pos_xy_p"]))
|
|
1388
|
+
vel_p = profile.get("nav_mc_vel_xy_p",
|
|
1389
|
+
get_setting(parsed, "nav_mc_vel_xy_p", INAV_NAV_DEFAULTS["nav_mc_vel_xy_p"]))
|
|
1390
|
+
vel_i = profile.get("nav_mc_vel_xy_i",
|
|
1391
|
+
get_setting(parsed, "nav_mc_vel_xy_i", INAV_NAV_DEFAULTS["nav_mc_vel_xy_i"]))
|
|
1392
|
+
decel = get_setting(parsed, "nav_mc_pos_deceleration_time",
|
|
1393
|
+
INAV_NAV_DEFAULTS["nav_mc_pos_deceleration_time"])
|
|
1394
|
+
heading_p = profile.get("nav_mc_heading_p",
|
|
1395
|
+
get_setting(parsed, "nav_mc_heading_p", INAV_NAV_DEFAULTS["nav_mc_heading_p"]))
|
|
1396
|
+
|
|
1397
|
+
if nav_rec:
|
|
1398
|
+
is_default_pos = (pos_p == INAV_NAV_DEFAULTS["nav_mc_pos_xy_p"])
|
|
1399
|
+
is_default_vel = (vel_p == INAV_NAV_DEFAULTS["nav_mc_vel_xy_p"])
|
|
1400
|
+
is_default_decel = (decel == INAV_NAV_DEFAULTS["nav_mc_pos_deceleration_time"])
|
|
1401
|
+
default_note = " (INAV default - tuned for 5-inch)"
|
|
1402
|
+
|
|
1403
|
+
# Frame-aware checks
|
|
1404
|
+
if pos_p is not None and isinstance(pos_p, (int, float)):
|
|
1405
|
+
if pos_p > nav_rec["pos_p_max"]:
|
|
1406
|
+
val_note = default_note if is_default_pos else ""
|
|
1407
|
+
findings.append(Finding(
|
|
1408
|
+
WARNING, "Navigation",
|
|
1409
|
+
f"Position P = {pos_p}{val_note} - too aggressive for {frame_inches}-inch",
|
|
1410
|
+
f"On {frame_inches}-inch with more "
|
|
1411
|
+
f"inertia, high position P causes overshoot and oscillation on RTH arrival "
|
|
1412
|
+
f"and position hold. The quad overshoots the target position, corrects back, "
|
|
1413
|
+
f"overshoots again.",
|
|
1414
|
+
setting="nav_mc_pos_xy_p",
|
|
1415
|
+
current=str(pos_p),
|
|
1416
|
+
recommended=str(nav_rec["pos_p_rec"]),
|
|
1417
|
+
cli_fix=f"set nav_mc_pos_xy_p = {nav_rec['pos_p_rec']}"))
|
|
1418
|
+
|
|
1419
|
+
if vel_p is not None and isinstance(vel_p, (int, float)):
|
|
1420
|
+
if vel_p > nav_rec["vel_p_max"]:
|
|
1421
|
+
val_note = default_note if is_default_vel else ""
|
|
1422
|
+
findings.append(Finding(
|
|
1423
|
+
WARNING, "Navigation",
|
|
1424
|
+
f"Velocity XY P = {vel_p}{val_note} - too aggressive for {frame_inches}-inch",
|
|
1425
|
+
f"Controls how hard the quad brakes when decelerating. "
|
|
1426
|
+
f"On {frame_inches}-inch, the quad can't stop as fast due to momentum, "
|
|
1427
|
+
f"so high velocity P causes oscillation in the direction of travel when "
|
|
1428
|
+
f"stopping or changing direction.",
|
|
1429
|
+
setting="nav_mc_vel_xy_p",
|
|
1430
|
+
current=str(vel_p),
|
|
1431
|
+
recommended=str(nav_rec["vel_p_rec"]),
|
|
1432
|
+
cli_fix=f"set nav_mc_vel_xy_p = {nav_rec['vel_p_rec']}"))
|
|
1433
|
+
|
|
1434
|
+
if decel is not None and isinstance(decel, (int, float)):
|
|
1435
|
+
if decel < nav_rec["decel_min"]:
|
|
1436
|
+
val_note = default_note if is_default_decel else ""
|
|
1437
|
+
findings.append(Finding(
|
|
1438
|
+
WARNING, "Navigation",
|
|
1439
|
+
f"Deceleration time = {decel} ({decel/100:.1f}s){val_note} - too short for {frame_inches}-inch",
|
|
1440
|
+
f"This controls how quickly the quad tries to stop from cruise speed. "
|
|
1441
|
+
f"A {frame_inches}-inch has much more "
|
|
1442
|
+
f"momentum and needs more distance/time to decelerate smoothly. "
|
|
1443
|
+
f"Too short causes overshoot and oscillation on RTH and position hold transitions.",
|
|
1444
|
+
setting="nav_mc_pos_deceleration_time",
|
|
1445
|
+
current=f"{decel} ({decel/100:.1f}s)",
|
|
1446
|
+
recommended=f"{nav_rec['decel_rec']} ({nav_rec['decel_rec']/100:.1f}s)",
|
|
1447
|
+
cli_fix=f"set nav_mc_pos_deceleration_time = {nav_rec['decel_rec']}"))
|
|
1448
|
+
else:
|
|
1449
|
+
# Generic checks (no frame size or small frame)
|
|
1450
|
+
if pos_p is not None and isinstance(pos_p, (int, float)):
|
|
1451
|
+
if pos_p > 50:
|
|
1452
|
+
findings.append(Finding(
|
|
1453
|
+
WARNING, "Navigation", f"Position hold P = {pos_p} - aggressive",
|
|
1454
|
+
"High position P gain can cause oscillation in position hold and RTH. "
|
|
1455
|
+
"The quad overcorrects, overshoots, and oscillates around the target position.",
|
|
1456
|
+
setting="nav_mc_pos_xy_p",
|
|
1457
|
+
current=str(pos_p),
|
|
1458
|
+
recommended="20-35",
|
|
1459
|
+
cli_fix=f"set nav_mc_pos_xy_p = 30"))
|
|
1460
|
+
elif pos_p < 15:
|
|
1461
|
+
findings.append(Finding(
|
|
1462
|
+
INFO, "Navigation", f"Position hold P = {pos_p} - conservative",
|
|
1463
|
+
"Low position P may result in slow corrections and drifting in wind.",
|
|
1464
|
+
setting="nav_mc_pos_xy_p",
|
|
1465
|
+
current=str(pos_p)))
|
|
1369
1466
|
|
|
1370
1467
|
if heading_p is not None and isinstance(heading_p, (int, float)):
|
|
1371
1468
|
if heading_p > 60:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inav-toolkit"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.19.0"
|
|
8
8
|
description = "Blackbox analyzer, parameter checker, and tuning wizard for INAV flight controllers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|