From cc6473bdb9004a20da9fb1ee73ffe50a8ed0711b Mon Sep 17 00:00:00 2001 From: Julian Oes Date: Tue, 2 Jun 2026 15:51:47 +1200 Subject: [PATCH] feat(mavlink): support FTP ListDirectoryWithTime (#27542) Add support for the MAVLink FTP ListDirectoryWithTime command (opcode 16), which returns directory listings with each file's last-modification time appended as decimal seconds since the UNIX epoch (0 if unknown). This lets a GCS retrieve file timestamps without downloading the files, which is useful for log download. The plain ListDirectory output is unchanged, and clients fall back to it when the server NAKs with UnknownCommand. Signed-off-by: Julian Oes --- src/modules/mavlink/mavlink_ftp.cpp | 20 +++++++++++++++++--- src/modules/mavlink/mavlink_ftp.h | 3 ++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/modules/mavlink/mavlink_ftp.cpp b/src/modules/mavlink/mavlink_ftp.cpp index b97cc199ef..3809847f90 100644 --- a/src/modules/mavlink/mavlink_ftp.cpp +++ b/src/modules/mavlink/mavlink_ftp.cpp @@ -171,6 +171,10 @@ MavlinkFTP::_process_request( errorCode = _workList(payload); break; + case kCmdListDirectoryWithTime: + errorCode = _workList(payload, true); + break; + case kCmdOpenFileRO: errorCode = _workOpen(payload, O_RDONLY); break; @@ -322,7 +326,7 @@ void MavlinkFTP::_constructPath(char *dst, int dst_len, const char *path) const /// @brief Responds to a List command MavlinkFTP::ErrorCode -MavlinkFTP::_workList(PayloadHeader *payload) +MavlinkFTP::_workList(PayloadHeader *payload, bool include_time) { _constructPath(_work_buffer1, _work_buffer1_len, _data_as_cstring(payload)); @@ -380,6 +384,7 @@ MavlinkFTP::_workList(PayloadHeader *payload) } uint32_t fileSize = 0; + uint32_t fileTime = 0; // seconds since the UNIX epoch, 0 if unknown char direntType; // Determine the directory entry type @@ -401,6 +406,7 @@ MavlinkFTP::_workList(PayloadHeader *payload) if (stat(_work_buffer2, &st) == 0) { fileSize = st.st_size; + fileTime = st.st_mtime; } } @@ -433,8 +439,16 @@ MavlinkFTP::_workList(PayloadHeader *payload) _work_buffer2[0] = '\0'; } else if (direntType == kDirentFile) { - // Files send filename and file length - int ret = snprintf(_work_buffer2, _work_buffer2_len, "%s\t%" PRIu32, result->d_name, fileSize); + // Files send filename and file length, optionally followed by the modification time + int ret; + + if (include_time) { + ret = snprintf(_work_buffer2, _work_buffer2_len, "%s\t%" PRIu32 "\t%" PRIu32, result->d_name, fileSize, fileTime); + + } else { + ret = snprintf(_work_buffer2, _work_buffer2_len, "%s\t%" PRIu32, result->d_name, fileSize); + } + bool buf_is_ok = ((ret > 0) && (ret < _work_buffer2_len)); if (!buf_is_ok) { diff --git a/src/modules/mavlink/mavlink_ftp.h b/src/modules/mavlink/mavlink_ftp.h index 344a61997f..eb0c0b8823 100644 --- a/src/modules/mavlink/mavlink_ftp.h +++ b/src/modules/mavlink/mavlink_ftp.h @@ -96,6 +96,7 @@ public: kCmdRename, ///< Rename to kCmdCalcFileCRC32, ///< Calculate CRC32 for file at kCmdBurstReadFile, ///< Burst download session file + kCmdListDirectoryWithTime, ///< List files in from , including last-modification time kRspAck = 128, ///< Ack response kRspNak ///< Nak response @@ -125,7 +126,7 @@ private: void _reply(mavlink_file_transfer_protocol_t *ftp_req); int _copy_file(const char *src_path, const char *dst_path, size_t length); - ErrorCode _workList(PayloadHeader *payload); + ErrorCode _workList(PayloadHeader *payload, bool include_time = false); ErrorCode _workOpen(PayloadHeader *payload, int oflag); ErrorCode _workRead(PayloadHeader *payload); ErrorCode _workBurst(PayloadHeader *payload, uint8_t target_system_id, uint8_t target_component_id);