Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c77a1a5
feat: add start/stop_tlog_recording() API to Mavsdk
bansiesta Apr 24, 2026
3f3f4eb
fix: address review feedback on tlog recording API
bansiesta Apr 24, 2026
5f208cf
fix: regenerate docs and add MAVProxy to tlog docstring
bansiesta Apr 24, 2026
cec451e
fix: move <fstream> include to cpp and use unique_ptr<ofstream> in he…
bansiesta Apr 25, 2026
531f87f
fix: move implementation-only includes from hpp to cpp
bansiesta Apr 25, 2026
9fd3b99
fix: address review feedback on tlog recording PR
bansiesta Apr 25, 2026
d852cff
docs: sync API reference to remove QGroundControl from tlog docstring
bansiesta Apr 25, 2026
417091a
review: address feedback on start/stop_tlog_recording API
bansiesta Apr 26, 2026
d6bbdee
docs: sync API reference with updated tlog destructor docstrings
bansiesta Apr 26, 2026
b42f111
fix: remove redundant <memory> and <string> includes from mavsdk_impl…
bansiesta Apr 26, 2026
5d88a26
fix: add TlogFile RAII destructor to guarantee flush on destruction
bansiesta Apr 26, 2026
c515650
refactor: move TlogFile definition into mavsdk_impl.hpp
bansiesta Apr 26, 2026
1ec35c7
refactor: address review feedback on tlog recording API
bansiesta Apr 26, 2026
a1b870f
refactor: move TlogFile definition back into mavsdk_impl.hpp
bansiesta Apr 27, 2026
5cf89b2
refactor: move TlogFile definition to mavsdk_impl.cpp
bansiesta Apr 27, 2026
51efc86
fix: tlog recording was empty when using libmav connection path
bansiesta Apr 28, 2026
8680290
refactor: address review feedback on tlog API (nodiscard, bounds-chec…
bansiesta Apr 28, 2026
8645653
fix: log tlog write errors and start recording before system discovery
bansiesta Apr 28, 2026
c453a22
docs: add raw_bytes field to MavlinkMessage API reference
bansiesta Apr 28, 2026
d03e57b
refactor: remove verbose AI-generated comments per review feedback
bansiesta Apr 28, 2026
bfcb352
fix: flush tlog stream after each write to prevent zero-byte files
bansiesta Apr 28, 2026
7f78a1d
fix: remove verbose AI-generated comments from libmav_receiver and ma…
bansiesta Apr 28, 2026
0584aec
fix: revert unrelated curl_test.cpp changes
bansiesta Apr 29, 2026
f52f045
fix: restore curl_test.cpp to match upstream (curl_wrapper.hpp include)
bansiesta Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion cpp/docs/en/cpp/api_reference/classmavsdk_1_1_mavsdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ void | [unsubscribe_incoming_messages_json](#classmavsdk_1_1_mavsdk_1a4be244c389
[InterceptJsonHandle](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1a3b40ae4fd8af4c4419b61f0ad955812f) | [subscribe_outgoing_messages_json](#classmavsdk_1_1_mavsdk_1a58f85b2f74a32404a8e975feefed8f47) (const [InterceptJsonCallback](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1a17923db3b1504e911487729114b68f48) & callback) | Intercept outgoing messages as JSON.
void | [unsubscribe_outgoing_messages_json](#classmavsdk_1_1_mavsdk_1aa3a490358db87cfed617cdad902bb753) ([InterceptJsonHandle](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1a3b40ae4fd8af4c4419b61f0ad955812f) handle) | Unsubscribe from outgoing messages as JSON.
void | [intercept_incoming_messages_async](#classmavsdk_1_1_mavsdk_1ac80c8909958533131cbdbc61d061794f) (std::function< bool(mavlink_message_t &)> callback) | Intercept incoming messages.
bool | [start_tlog_recording](#classmavsdk_1_1_mavsdk_1af8ed201951f4b264a864217c5e5db5c1) (const std::string & path) | Start recording all incoming MAVLink traffic to a .tlog file.
void | [stop_tlog_recording](#classmavsdk_1_1_mavsdk_1a507d9f58439233b5ddd3d5d1ba30bc0c) () | Stop recording and close the .tlog file.
void | [intercept_outgoing_messages_async](#classmavsdk_1_1_mavsdk_1a040ee5c1d41e71c0d63cf8f76d2db275) (std::function< bool(mavlink_message_t &)> callback) | Intercept outgoing messages.
void | [pass_received_raw_bytes](#classmavsdk_1_1_mavsdk_1a65329315ac07bae110839d9e054fbc05) (const char * bytes, size_t length) | Pass received raw MAVLink bytes.
[RawBytesHandle](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1ac766258f137aa3e8b0dabb5a66435ea1) | [subscribe_raw_bytes_to_be_sent](#classmavsdk_1_1_mavsdk_1a116e9bab0efdf7ec90866107ef517b20) ([RawBytesCallback](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1acb5be9a1be97d251387ffe87ae8b9eb0) callback) | Subscribe to raw bytes to be sent.
Expand Down Expand Up @@ -110,7 +112,7 @@ mavsdk::Mavsdk::~Mavsdk()

Destructor.

Disconnects all connected vehicles and releases all resources.
Disconnects all connected vehicles and releases all resources. Any active .tlog recording is automatically stopped and flushed.

## Member Typdef Documentation

Expand Down Expand Up @@ -647,6 +649,40 @@ This functionality is provided primarily for testing in order to simulate packet

* std::function< bool(mavlink_message_t &)> **callback** - Callback to be called for each incoming message. To drop a message, return 'false' from the callback.

### start_tlog_recording() {#classmavsdk_1_1_mavsdk_1af8ed201951f4b264a864217c5e5db5c1}
```cpp
bool mavsdk::Mavsdk::start_tlog_recording(const std::string &path)
```


Start recording all incoming MAVLink traffic to a .tlog file.

A .tlog (telemetry log) is a binary file where each record consists of an 8-byte big-endian microsecond Unix timestamp followed by the raw MAVLink wire packet. The format is compatible with [Mission](classmavsdk_1_1_mission.md) Planner, MAVProxy, and pymavlink.


Recording captures traffic across the entire [Mavsdk](classmavsdk_1_1_mavsdk.md) instance (all connected systems and connections), not per-system. If recording is already active it is stopped and restarted with the new file.


The recording is automatically stopped and flushed when the [Mavsdk](classmavsdk_1_1_mavsdk.md) instance is destroyed, so explicit [stop_tlog_recording()](classmavsdk_1_1_mavsdk.md#classmavsdk_1_1_mavsdk_1a507d9f58439233b5ddd3d5d1ba30bc0c) is optional.

**Parameters**

* const std::string& **path** - Output file path (e.g. "flight.tlog").

**Returns**

&emsp;bool - true if the file was opened successfully, false otherwise.

### stop_tlog_recording() {#classmavsdk_1_1_mavsdk_1a507d9f58439233b5ddd3d5d1ba30bc0c}
```cpp
void mavsdk::Mavsdk::stop_tlog_recording()
```


Stop recording and close the .tlog file.

Does nothing if recording is not active. Automatically called on [Mavsdk](classmavsdk_1_1_mavsdk.md) destruction.

### intercept_outgoing_messages_async() {#classmavsdk_1_1_mavsdk_1a040ee5c1d41e71c0d63cf8f76d2db275}
```cpp
void mavsdk::Mavsdk::intercept_outgoing_messages_async(std::function< bool(mavlink_message_t &)> callback)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ uint32_t [target_component_id](#structmavsdk_1_1_mavsdk_1_1_mavlink_message_1ad1

std::string [fields_json](#structmavsdk_1_1_mavsdk_1_1_mavlink_message_1a73165621a97083c143ba7c36461c9754) {} - All message fields as single JSON object.

std::vector< uint8_t > [raw_bytes](#structmavsdk_1_1_mavsdk_1_1_mavlink_message_1aa4fc863b9e0103a894fdd99d5219847b) {} - Raw MAVLink wire bytes (populated for incoming messages; empty when sending)


## Field Documentation

Expand Down Expand Up @@ -85,3 +87,13 @@ std::string mavsdk::Mavsdk::MavlinkMessage::fields_json {}

All message fields as single JSON object.


### raw_bytes {#structmavsdk_1_1_mavsdk_1_1_mavlink_message_1aa4fc863b9e0103a894fdd99d5219847b}

```cpp
std::vector<uint8_t> mavsdk::Mavsdk::MavlinkMessage::raw_bytes {}
```


Raw MAVLink wire bytes (populated for incoming messages; empty when sending)

1 change: 1 addition & 0 deletions cpp/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ add_subdirectory(tune)
add_subdirectory(vtol_transition)
add_subdirectory(winch)
add_subdirectory(wind)
add_subdirectory(write_tlog)
# Disabled, requires mavsdk_server library.
#add_subdirectory(start_stop_server)

Expand Down
22 changes: 22 additions & 0 deletions cpp/examples/write_tlog/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.10.2)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(write_tlog)

add_executable(write_tlog
write_tlog.cpp
)

find_package(MAVSDK REQUIRED)

target_link_libraries(write_tlog
MAVSDK::mavsdk
)

if(NOT MSVC)
add_compile_options(write_tlog PRIVATE -Wall -Wextra)
else()
add_compile_options(write_tlog PRIVATE -W2)
endif()
103 changes: 103 additions & 0 deletions cpp/examples/write_tlog/write_tlog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// Example: record all incoming MAVLink traffic to a TLOG file.
//
// Usage:
// write_tlog <connection_url> <output.tlog>
//
// Example (SITL):
// write_tlog udpin://0.0.0.0:14540 flight.tlog
//
// Press Ctrl+C to stop recording. The file can be opened in QGroundControl.
//

#include <mavsdk/mavsdk.hpp>

#include <atomic>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <future>
#include <iostream>
#include <thread>

using namespace mavsdk;

static std::atomic<bool> should_exit{false};

static void signal_handler(int /*signum*/)
{
should_exit.store(true, std::memory_order_relaxed);
}

static void usage(const char* bin)
{
std::cerr << "Usage: " << bin << " <connection_url> <output.tlog>\n"
<< "Connection URL examples:\n"
<< " UDP server : udpin://0.0.0.0:14540\n"
<< " UDP client : udpout://127.0.0.1:14540\n"
<< " TCP client : tcpout://192.168.1.1:5760\n"
<< " Serial : serial:///dev/ttyUSB0:57600\n";
}

int main(int argc, char** argv)
{
if (argc != 3) {
usage(argv[0]);
return 1;
}

const std::string connection_url = argv[1];
const std::string tlog_path = argv[2];

// Register signal handler so Ctrl+C flushes and closes the file cleanly.
std::signal(SIGINT, signal_handler);
std::signal(SIGTERM, signal_handler);

Mavsdk mavsdk{Mavsdk::Configuration{ComponentType::CompanionComputer}};

const ConnectionResult connection_result = mavsdk.add_any_connection(connection_url);
if (connection_result != ConnectionResult::Success) {
std::cerr << "Connection failed: " << connection_result << '\n';
return 1;
}

// Start recording immediately so the initial handshake messages are captured too.
if (!mavsdk.start_tlog_recording(tlog_path)) {
std::cerr << "Failed to open '" << tlog_path << "' for writing.\n";
return 1;
}

std::cout << "Recording to '" << tlog_path << "'. Waiting to discover system...\n";

auto prom = std::promise<std::shared_ptr<System>>{};
auto fut = prom.get_future();
bool promise_set = false;

Mavsdk::NewSystemHandle handle = mavsdk.subscribe_on_new_system([&]() {
auto system = mavsdk.systems().back();
if (!promise_set) {
promise_set = true;
prom.set_value(system);
}
});

if (fut.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) {
std::cerr << "No system found within 5 s. Is the vehicle connected?\n";
mavsdk.stop_tlog_recording();
return 1;
}

mavsdk.unsubscribe_on_new_system(handle);
auto system = fut.get();
std::cout << "Discovered system (sysid " << static_cast<int>(system->get_system_id())
<< "). Press Ctrl+C to stop...\n";

while (!should_exit.load(std::memory_order_relaxed)) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}

std::cout << "\nStopping recording.\n";
mavsdk.stop_tlog_recording();

return 0;
}
31 changes: 31 additions & 0 deletions cpp/src/mavsdk/core/include/mavsdk/mavsdk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ class MAVSDK_PUBLIC Mavsdk {
* @brief Destructor.
*
* Disconnects all connected vehicles and releases all resources.
* Any active .tlog recording is automatically stopped and flushed.
*/
~Mavsdk();

Expand Down Expand Up @@ -472,6 +473,8 @@ class MAVSDK_PUBLIC Mavsdk {
uint32_t
target_component_id{}; /**< @brief Target component ID (for sending, 0 for broadcast) */
std::string fields_json{}; /**< @brief All message fields as single JSON object */
std::vector<uint8_t> raw_bytes{}; /**< @brief Raw MAVLink wire bytes (populated for
incoming messages; empty when sending) */
};

/**
Expand Down Expand Up @@ -530,6 +533,34 @@ class MAVSDK_PUBLIC Mavsdk {
*/
void intercept_incoming_messages_async(std::function<bool(mavlink_message_t&)> callback);

/**
* @brief Start recording all incoming MAVLink traffic to a .tlog file.
*
* A .tlog (telemetry log) is a binary file where each record consists of
* an 8-byte big-endian microsecond Unix timestamp followed by the raw
* MAVLink wire packet. The format is compatible with
* Mission Planner, MAVProxy, and pymavlink.
*
* Recording captures traffic across the entire Mavsdk instance (all
* connected systems and connections), not per-system. If recording is
* already active it is stopped and restarted with the new file.
*
* The recording is automatically stopped and flushed when the Mavsdk
* instance is destroyed, so explicit stop_tlog_recording() is optional.
*
* @param path Output file path (e.g. "flight.tlog").
* @return true if the file was opened successfully, false otherwise.
*/
[[nodiscard]] bool start_tlog_recording(const std::string& path);

/**
* @brief Stop recording and close the .tlog file.
*
* Does nothing if recording is not active. Automatically called on
* Mavsdk destruction.
*/
void stop_tlog_recording();

/**
* @brief Intercept outgoing messages.
*
Expand Down
12 changes: 12 additions & 0 deletions cpp/src/mavsdk/core/libmav_receiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ bool LibmavReceiver::parse_libmav_message_from_buffer()
_last_message.system_id = header.systemId();
_last_message.component_id = header.componentId();

if (_last_libmav_message) {
const uint32_t wire_len = _last_libmav_message->finalizedSize();
if (wire_len > 0) {
const uint8_t* wire_ptr = _last_libmav_message->data();
_last_message.raw_bytes.assign(wire_ptr, wire_ptr + wire_len);
} else {
_last_message.raw_bytes.clear();
}
} else {
_last_message.raw_bytes.clear();
}

// Extract target_system and target_component if present in message fields
uint8_t target_system_id = 0;
uint8_t target_component_id = 0;
Expand Down
10 changes: 10 additions & 0 deletions cpp/src/mavsdk/core/mavsdk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,16 @@ void Mavsdk::intercept_outgoing_messages_async(std::function<bool(mavlink_messag
_impl->intercept_outgoing_messages_async(callback);
}

bool Mavsdk::start_tlog_recording(const std::string& path)
{
return _impl->start_tlog_recording(path);
}

void Mavsdk::stop_tlog_recording()
{
_impl->stop_tlog_recording();
}

void Mavsdk::pass_received_raw_bytes(const char* bytes, size_t length)
{
_impl->pass_received_raw_bytes(bytes, length);
Expand Down
Loading
Loading