#include #include #include #include #include #include "betaflight.h" #include "cli.h" #include "eject.h" #include "mounts.h" #include "naming.h" #include "serial.h" namespace fs = std::filesystem; using namespace bfebbx; namespace { const char* kUsage = "usage: bfebbx [--port PORT] [--output DIR] [--craft-name NAME] " "[--timeout SEC] [-v]\n"; // Exit codes (documented in README). enum Exit { OK = 0, NO_PORT = 2, AMBIGUOUS_PORT = 3, SERIAL_FAIL = 4, VOLUME_TIMEOUT = 5, FILE_MISSING = 6, COPY_FAIL = 7, DEST_EXISTS = 8, USAGE = 64, }; std::string resolvePort(const Options& opt) { if (!opt.port.empty()) return opt.port; auto ports = scanPorts("/dev", defaultPortPrefix()); if (ports.empty()) { std::cerr << "error: no Betaflight serial port found (looked for /dev/" << defaultPortPrefix() << "*). Use --port.\n"; std::exit(NO_PORT); } if (ports.size() > 1) { std::cerr << "error: multiple candidate ports; pass --port:\n"; for (const auto& p : ports) std::cerr << " " << p << "\n"; std::exit(AMBIGUOUS_PORT); } return ports.front(); } // Poll for a newly-mounted volume that contains btfl_all.bbl. MountEntry waitForVolume(const std::vector& before, int timeoutSec, bool verbose) { using clock = std::chrono::steady_clock; auto deadline = clock::now() + std::chrono::seconds(timeoutSec); while (clock::now() < deadline) { auto added = diffMounts(before, listMounts()); for (const auto& m : added) { fs::path candidate = fs::path(m.mountpoint) / "btfl_all.bbl"; std::error_code ec; if (fs::exists(candidate, ec)) { if (verbose) std::cerr << "found volume: " << m.mountpoint << "\n"; return m; } } std::this_thread::sleep_for(std::chrono::milliseconds(500)); } std::cerr << "error: mass-storage volume did not appear within " << timeoutSec << "s (try --timeout).\n"; std::exit(VOLUME_TIMEOUT); } } // namespace int main(int argc, char** argv) { Options opt; try { opt = parseArgs(argc, argv); } catch (const std::exception& e) { std::cerr << "error: " << e.what() << "\n" << kUsage; return USAGE; } if (opt.help) { std::cout << kUsage; return OK; } const std::string port = resolvePort(opt); if (opt.verbose) std::cerr << "port: " << port << "\n"; // 1. Read craft_name over the CLI (before msc reboots the board). std::string craft = opt.craftName; try { Serial s = Serial::openPort(port, 115200); enterCli(s); auto name = getCraftName(s, std::chrono::milliseconds(2000)); if (craft.empty() && name.has_value()) craft = name.value(); // 2. Snapshot mounts, then trigger mass-storage mode. auto before = listMounts(); if (opt.verbose) std::cerr << "entering mass-storage mode...\n"; enterMsc(s); // Ensure the msc reboot trigger is actually transmitted before we // release the fd, since a non-blocking fd offers no such guarantee. s.drain(); // Serial port goes away as the FC reboots; drop it before polling. s = Serial::fromFd(-1); // 3. Wait for the volume. MountEntry vol = waitForVolume(before, opt.timeoutSec, opt.verbose); // 4. Build destination name and copy. std::string sanitized = sanitizeCraftName(craft); if (sanitized.empty()) { sanitized = "betaflight"; if (craft.empty()) { std::cerr << "warning: could not read craft_name; using '" << sanitized << "'\n"; } } std::string fname = buildFilename(sanitized, std::time(nullptr)); fs::path dest = fs::path(opt.output) / fname; std::error_code ec; if (fs::exists(dest, ec)) { std::cerr << "error: destination already exists: " << dest << "\n"; return DEST_EXISTS; } fs::path src = fs::path(vol.mountpoint) / "btfl_all.bbl"; if (!fs::exists(src, ec)) { std::cerr << "error: btfl_all.bbl not found on " << vol.mountpoint << " (leaving volume mounted).\n"; return FILE_MISSING; } fs::copy_file(src, dest, ec); if (ec) { std::cerr << "error: copy failed: " << ec.message() << " (leaving volume mounted).\n"; return COPY_FAIL; } std::cout << "wrote " << dest << "\n"; // 5. Eject on success. try { ejectVolume(vol); if (opt.verbose) std::cerr << "ejected " << vol.mountpoint << "\n"; } catch (const std::exception& e) { std::cerr << "warning: eject failed: " << e.what() << " (file already saved; unmount manually).\n"; } } catch (const std::exception& e) { std::cerr << "error: " << e.what() << "\n"; return SERIAL_FAIL; } return OK; }