From b6d3eaad820cebf1e96ee7871a34f0eab9ce2744 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 22 Jul 2026 15:13:17 -0700 Subject: [PATCH] Implement bfebbx: Betaflight blackbox extractor Cross-platform (macOS + Linux) C++17 CLI that extracts a Betaflight FC's blackbox log in one command: auto-detect the serial port, read craft_name over the Betaflight CLI, send `msc` to reboot into USB mass storage, wait for the volume to mount, copy btfl_all.bbl to __.bbl, and eject. Native throughout (termios serial, getmntinfo/proc-mounts mount detection, DiskArbitration/umount eject); the only subprocess is the Linux udisksctl eject fallback. Includes CMake/ctest build, unit tests for the pure logic (naming, craft_name parsing, mount diff, serial via pty, CLI parsing), README with a manual hardware integration procedure, and design/plan docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitignore | 5 + CMakeLists.txt | 61 +++++++++++++ README.md | 78 ++++++++++++++++ src/betaflight.cpp | 50 ++++++++++ src/betaflight.h | 17 ++++ src/cli.cpp | 71 +++++++++++++++ src/cli.h | 20 ++++ src/eject.cpp | 107 ++++++++++++++++++++++ src/eject.h | 8 ++ src/main.cpp | 157 ++++++++++++++++++++++++++++++++ src/mounts.cpp | 66 ++++++++++++++ src/mounts.h | 16 ++++ src/naming.cpp | 25 +++++ src/naming.h | 10 ++ src/serial.cpp | 131 ++++++++++++++++++++++++++ src/serial.h | 29 ++++++ tests/test_betaflight_parse.cpp | 16 ++++ tests/test_betaflight_proto.cpp | 38 ++++++++ tests/test_cli.cpp | 73 +++++++++++++++ tests/test_mounts.cpp | 29 ++++++ tests/test_naming.cpp | 28 ++++++ tests/test_serial.cpp | 33 +++++++ 22 files changed, 1068 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 src/betaflight.cpp create mode 100644 src/betaflight.h create mode 100644 src/cli.cpp create mode 100644 src/cli.h create mode 100644 src/eject.cpp create mode 100644 src/eject.h create mode 100644 src/main.cpp create mode 100644 src/mounts.cpp create mode 100644 src/mounts.h create mode 100644 src/naming.cpp create mode 100644 src/naming.h create mode 100644 src/serial.cpp create mode 100644 src/serial.h create mode 100644 tests/test_betaflight_parse.cpp create mode 100644 tests/test_betaflight_proto.cpp create mode 100644 tests/test_cli.cpp create mode 100644 tests/test_mounts.cpp create mode 100644 tests/test_naming.cpp create mode 100644 tests/test_serial.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e2213f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Build output +/build/ + +# SDD scratch (ledger, briefs, reports, review packages) +/.superpowers/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..32a81ad --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.16) +project(bfebbx CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_compile_options(-Wall -Wextra) + +enable_testing() + +# --- library sources (grows as tasks are added) --- +add_library(bfebbx_lib + src/naming.cpp + src/betaflight.cpp + src/mounts.cpp + src/serial.cpp + src/eject.cpp + src/cli.cpp +) +target_include_directories(bfebbx_lib PUBLIC src) + +# --- platform-specific linking --- +if(APPLE) + target_link_libraries(bfebbx_lib PUBLIC + "-framework DiskArbitration" + "-framework CoreFoundation") +endif() + +# --- tests --- +add_executable(test_naming tests/test_naming.cpp) +target_link_libraries(test_naming PRIVATE bfebbx_lib) +add_test(NAME naming COMMAND test_naming) + +add_executable(test_betaflight_parse tests/test_betaflight_parse.cpp) +target_link_libraries(test_betaflight_parse PRIVATE bfebbx_lib) +add_test(NAME betaflight_parse COMMAND test_betaflight_parse) + +add_executable(test_mounts tests/test_mounts.cpp) +target_link_libraries(test_mounts PRIVATE bfebbx_lib) +add_test(NAME mounts COMMAND test_mounts) + +add_executable(test_serial tests/test_serial.cpp) +target_link_libraries(test_serial PRIVATE bfebbx_lib) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries(test_serial PRIVATE util) # openpty +endif() +add_test(NAME serial COMMAND test_serial) + +add_executable(test_betaflight_proto tests/test_betaflight_proto.cpp) +target_link_libraries(test_betaflight_proto PRIVATE bfebbx_lib) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries(test_betaflight_proto PRIVATE util) +endif() +add_test(NAME betaflight_proto COMMAND test_betaflight_proto) + +# --- main executable --- +add_executable(bfebbx src/main.cpp) +target_link_libraries(bfebbx PRIVATE bfebbx_lib) + +add_executable(test_cli tests/test_cli.cpp) +target_link_libraries(test_cli PRIVATE bfebbx_lib) +add_test(NAME cli COMMAND test_cli) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3d7a4b --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# bfebbx — Betaflight Extract Blackbox + +Extract a Betaflight flight controller's blackbox log to a timestamped, +craft-named file in a single command. macOS and Linux. + +## What it does + +1. Auto-detects the FC's serial port. +2. Reads `craft_name` over the Betaflight CLI. +3. Sends `msc` to reboot the FC into USB mass-storage mode. +4. Waits for the storage volume to mount, then copies `btfl_all.bbl` to + `__.bbl`. +5. Ejects the volume. + +## Build + +```bash +cmake -S . -B build +cmake --build build +ctest --test-dir build # run unit tests +``` + +The binary is `build/bfebbx`. + +Requirements: a C++17 compiler and CMake ≥ 3.16. No third-party libraries. +macOS links the system DiskArbitration/CoreFoundation frameworks. + +## Usage + +``` +bfebbx [--port PORT] [--output DIR] [--craft-name NAME] [--timeout SEC] [-v] +``` + +| Flag | Default | Meaning | +|------|---------|---------| +| `--port` | auto-detect | Serial device (e.g. `/dev/tty.usbmodem1101`). | +| `--output` | current dir | Directory for the extracted `.bbl`. | +| `--craft-name` | read from FC | Override / fallback craft name. | +| `--timeout` | 30 | Seconds to wait for the storage volume. | +| `-v` | off | Verbose progress on stderr. | + +### Exit codes + +| Code | Meaning | +|------|---------| +| 0 | Success | +| 2 | No candidate serial port | +| 3 | Multiple candidate ports (pass `--port`) | +| 4 | Serial open / handshake failure | +| 5 | Storage volume did not appear before timeout | +| 6 | `btfl_all.bbl` not found on the volume | +| 7 | Copy failed | +| 8 | Destination file already exists | +| 64 | Usage error | + +## Manual integration test (requires a real FC) + +The pure logic is unit-tested. The serial handshake and eject need hardware: + +1. Flash/confirm a Betaflight FC with blackbox logs on onboard flash. +2. Connect it via USB. Confirm the port appears: `ls /dev/tty.usbmodem*` + (macOS) or `ls /dev/ttyACM*` (Linux). +3. Run `./build/bfebbx -v` in an empty directory. +4. Verify: + - The craft name is read and printed. + - The FC re-enumerates as a USB drive. + - A file `__