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 <julian@oes.ch>
This commit is contained in:
Julian Oes
2026-06-02 15:51:47 +12:00
committed by GitHub
parent bb914860ea
commit cc6473bdb9
2 changed files with 19 additions and 4 deletions

View File

@@ -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) {

View File

@@ -96,6 +96,7 @@ public:
kCmdRename, ///< Rename <path1> to <path2>
kCmdCalcFileCRC32, ///< Calculate CRC32 for file at <path>
kCmdBurstReadFile, ///< Burst download session file
kCmdListDirectoryWithTime, ///< List files in <path> from <offset>, 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);