inav-toolkit 2.18.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.18.0 → inav_toolkit-2.19.0}/PKG-INFO +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/__init__.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/blackbox_analyzer.py +132 -7
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/flight_db.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/msp.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/param_analyzer.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/vtol_configurator.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/wizard.py +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/PKG-INFO +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/pyproject.toml +1 -1
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/LICENSE +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/README.md +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/autotune.py +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/i18n.py +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/en.json +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/es.json +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit/locales/pt_BR.json +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/SOURCES.txt +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/dependency_links.txt +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/entry_points.txt +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/requires.txt +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/inav_toolkit.egg-info/top_level.txt +0 -0
- {inav_toolkit-2.18.0 → inav_toolkit-2.19.0}/setup.cfg +0 -0
- {inav_toolkit-2.18.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:
|
|
@@ -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",
|
|
@@ -6188,6 +6279,7 @@ def main():
|
|
|
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:
|
|
@@ -6279,13 +6371,16 @@ def main():
|
|
|
6279
6371
|
print()
|
|
6280
6372
|
filepath = fc.download_blackbox(
|
|
6281
6373
|
output_dir=args.blackbox_dir,
|
|
6282
|
-
erase_after=
|
|
6374
|
+
erase_after=False, # erase happens after successful analysis
|
|
6283
6375
|
)
|
|
6284
6376
|
|
|
6285
6377
|
if not filepath:
|
|
6286
6378
|
print(" ERROR: Download failed.")
|
|
6287
6379
|
sys.exit(1)
|
|
6288
6380
|
|
|
6381
|
+
# Store port for post-analysis erase
|
|
6382
|
+
device_port = fc.port_path if hasattr(fc, 'port_path') else args.device
|
|
6383
|
+
|
|
6289
6384
|
if args.download_only:
|
|
6290
6385
|
print(f"\n To analyze:\n python3 {sys.argv[0]} {filepath}")
|
|
6291
6386
|
sys.exit(0)
|
|
@@ -6417,17 +6512,45 @@ def main():
|
|
|
6417
6512
|
# Flash may contain flights from multiple sessions (config changes between
|
|
6418
6513
|
# arm cycles). Only the LATEST session's best flight deserves full analysis.
|
|
6419
6514
|
# Earlier flights are stored in DB for progression but shown as one-liners.
|
|
6515
|
+
analyzed_file = None
|
|
6420
6516
|
if getattr(args, 'nav', False):
|
|
6421
6517
|
# Nav mode: skip multi-flight tuning scan, analyze last flight directly
|
|
6422
6518
|
target = log_files[-1]
|
|
6423
6519
|
if len(log_files) > 1:
|
|
6424
6520
|
print(f"\n Nav mode: analyzing last flight ({len(log_files)} in flash)")
|
|
6425
6521
|
_analyze_single_log(target, args, config_raw)
|
|
6522
|
+
analyzed_file = target
|
|
6426
6523
|
elif len(log_files) > 1:
|
|
6427
|
-
_process_multi_log(log_files, args, config_raw)
|
|
6524
|
+
analyzed_file = _process_multi_log(log_files, args, config_raw)
|
|
6428
6525
|
else:
|
|
6429
6526
|
# Single flight - full analysis
|
|
6430
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}")
|
|
6431
6554
|
|
|
6432
6555
|
|
|
6433
6556
|
def _process_multi_log(log_files, args, config_raw):
|
|
@@ -6613,6 +6736,8 @@ def _process_multi_log(log_files, args, config_raw):
|
|
|
6613
6736
|
except Exception:
|
|
6614
6737
|
pass
|
|
6615
6738
|
|
|
6739
|
+
return log_files[best_idx]
|
|
6740
|
+
|
|
6616
6741
|
|
|
6617
6742
|
def _verdict_short(verdict):
|
|
6618
6743
|
"""Short display string for verdict codes."""
|
|
@@ -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
|