From b4d4d90ecc78b7f3abcd527b6d8825120c96b347 Mon Sep 17 00:00:00 2001 From: Jacob Dahl Date: Mon, 27 Jul 2026 21:08:10 -0600 Subject: [PATCH 1/8] feat(logloader): download logs over MAVLink FTP only LOG_DATA (msg 120) carries no target_system/target_component, so a router between logloader and the autopilot has no addressing information and copies every chunk to every endpoint it serves -- the telemetry radio included. On a node running mavlink-router a log download saturates links that have no interest in it. Move both halves of the job to MAVLink FTP: the listing that finds the logs and the transfer that downloads them. FILE_TRANSFER_PROTOCOL (msg 110) is addressed and both PX4 and ArduPilot reply to the requesting sysid/compid, so everything is unicast to logloader. The classic log protocol is gone entirely -- no LOG_REQUEST_LIST, no LOG_ENTRY, no LOG_DATA, and no MAVSDK LogFiles plugin. Dropping LOG_ENTRY means dropping the only thing that identified a log, so identity moves to the listing: the path below the log root plus the size. That is also what makes ArduPilot work without special cases. Its list entry numbering, its LOG_MAX_FILES wrap and its .BIN extension were all consequences of the old protocol; a directory listing just reports files. The log root is probed at "@MAV_LOG" -- the virtual directory the MAVLink FTP specification defines for this -- then at /fs/microsd/log and /APM/LOGS for firmware that predates it. Timestamps come from the listing when the vehicle implements ListDirectoryWithTime, otherwise from the start time PX4 encodes in the path; ArduPilot logs simply have no timestamp and nothing needs one. MAVSDK's Ftp plugin drops the size and modification time from list replies, so FtpListClient runs ListDirectory / ListDirectoryWithTime over MavlinkPassthrough and keeps the whole entry. Bulk transfers still go through the plugin. Databases written by earlier versions keyed logs on the LOG_ENTRY timestamp, which FTP cannot reproduce. Their rows are set aside as logs_legacy on first start and matched to the listing by size, so a fleet upgrading to this does not re-download and re-upload its history. Also: - A log is only queued once two consecutive listings agree on its size, so the log being written right now is not downloaded at a size it will not keep. - Downloads are staged and size-checked before being moved next to the finished logs, and a log that cannot be fetched sorts to the back of the queue instead of blocking the ones behind it. - Open FTP sessions are reset at connect, which is what left PX4 refusing transfers until reboot after logloader was killed mid-download. - The databases are created before they are opened, which failed when application_directory did not exist yet. --- CMakeLists.txt | 2 + README.md | 21 ++- config.toml | 9 + src/FtpListClient.cpp | 251 +++++++++++++++++++++++++ src/FtpListClient.hpp | 114 ++++++++++++ src/FtpLogFetcher.cpp | 324 ++++++++++++++++++++++++++++++++ src/FtpLogFetcher.hpp | 96 ++++++++++ src/LogLoader.cpp | 303 ++++++++++++++++++------------ src/LogLoader.hpp | 25 ++- src/ServerInterface.cpp | 400 +++++++++++++++++++++++++++++----------- src/ServerInterface.hpp | 42 ++++- src/main.cpp | 4 +- 12 files changed, 1342 insertions(+), 249 deletions(-) create mode 100644 src/FtpListClient.cpp create mode 100644 src/FtpListClient.hpp create mode 100644 src/FtpLogFetcher.cpp create mode 100644 src/FtpLogFetcher.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6529639..d2a99f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,8 @@ include_directories(${SQLite3_INCLUDE_DIRS}) add_executable(${PROJECT_NAME} src/main.cpp src/ServerInterface.cpp + src/FtpListClient.cpp + src/FtpLogFetcher.cpp src/LogLoader.cpp) target_link_libraries(${PROJECT_NAME} diff --git a/README.md b/README.md index 8993abb..8145739 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,29 @@ ![image](logloader_logo.png) -Downloads PX4 log files (.ulg) and uploads them to a local server and optionally a remote server. +Downloads flight logs from the vehicle and uploads them to a local server and optionally a remote server. Works with PX4 (`.ulg`) and ArduPilot (`.BIN`). The **config.toml** file is used to configure the program settings. ### Behavior Downloading and uploading will only occur while the vehicle is not armed. Downloading and uploading operations are performed in separate threads. An sqlite database per server is used to track log file download/upload status. +### Log transport +Everything runs over **MAVLink FTP** (`FILE_TRANSFER_PROTOCOL`, msg 110): the directory listing that finds the logs and the bulk transfer that downloads them. The classic log protocol (`LOG_REQUEST_LIST` / `LOG_ENTRY` / `LOG_DATA`, msgs 117-120) is not used at all. + +`FILE_TRANSFER_PROTOCOL` carries `target_system` and `target_component`, and both PX4 and ArduPilot address their replies back to the requesting sysid/compid, so a download is unicast between the vehicle and logloader. `LOG_DATA` has no target fields, which leaves a router in between — mavlink-router, for instance — no choice but to copy every chunk to every endpoint it serves, telemetry radio included. A log download over the old protocol saturates links that have no interest in it. + +The autopilot's MAVLink instance must have FTP enabled (`mavlink start -x` on PX4). If it does not, logloader says so and idles. + +### Log discovery +At startup logloader lists the vehicle's log directory, trying `@MAV_LOG` first — the virtual log directory the MAVLink FTP specification defines, supported by PX4 v1.17 and newer — then the physical `/fs/microsd/log` (PX4) and `/APM/LOGS` (ArduPilot). Set `remote_log_directory` to skip the probe. The listing is what identifies a log: its path below the log root plus its size. PX4's nested `/