• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mavlink / MAVSDK / 29564081841

17 Jul 2026 07:09AM UTC coverage: 50.512% (+0.01%) from 50.498%
29564081841

push

github

web-flow
Merge pull request #2936 from mavlink/v3-backport

Backport various bugfixes to v3

162 of 257 new or added lines in 24 files covered. (63.04%)

56 existing lines in 15 files now uncovered.

19343 of 38294 relevant lines covered (50.51%)

669.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.47
/src/mavsdk/core/mavsdk_impl.cpp
1
#include "mavsdk_impl.h"
2

3
#include <algorithm>
4
#include <mutex>
5
#include <tcp_server_connection.h>
6

7
#include "connection.h"
8
#include "log.h"
9
#include "tcp_client_connection.h"
10
#include "tcp_server_connection.h"
11
#include "udp_connection.h"
12
#include "raw_connection.h"
13
#include "system.h"
14
#include "system_impl.h"
15
#include "serial_connection.h"
16
#include "version.h"
17
#include "server_component_impl.h"
18
#include "overloaded.h"
19
#include "mavlink_channels.h"
20
#include "callback_list.tpp"
21
#include "hostname_to_ip.h"
22
#include "embedded_mavlink_xml.h"
23
#include <mav/MessageSet.h>
24

25
#include "mavsdk_export.h"
26

27
namespace mavsdk {
28

29
template class MAVSDK_TEMPL_INST CallbackList<>;
30

31
MavsdkImpl::MavsdkImpl(const Mavsdk::Configuration& configuration) :
168✔
32
    timeout_handler(time),
168✔
33
    call_every_handler(time)
336✔
34
{
35
    LogInfo() << "MAVSDK version: " << mavsdk_version;
168✔
36

37
    if (const char* env_p = std::getenv("MAVSDK_CALLBACK_DEBUGGING")) {
168✔
38
        if (std::string(env_p) == "1") {
×
39
            LogDebug() << "Callback debugging is on.";
×
40
            _callback_debugging = true;
×
41
            _callback_tracker = std::make_unique<CallbackTracker>();
×
42
        }
43
    }
44

45
    if (const char* env_p = std::getenv("MAVSDK_MESSAGE_DEBUGGING")) {
168✔
46
        if (std::string(env_p) == "1") {
×
47
            LogDebug() << "Message debugging is on.";
×
48
            _message_logging_on = true;
×
49
        }
50
    }
51

52
    if (const char* env_p = std::getenv("MAVSDK_SYSTEM_DEBUGGING")) {
168✔
53
        if (std::string(env_p) == "1") {
×
54
            LogDebug() << "System debugging is on.";
×
55
            _system_debugging = true;
×
56
        }
57
    }
58

59
    set_configuration(configuration);
168✔
60

61
    // Initialize MessageSet with embedded XML content in dependency order
62
    // This happens at startup before any connections are created, so no synchronization needed
63
    _message_set = std::make_unique<mav::MessageSet>();
168✔
64
    _message_set->addFromXMLString(mav_embedded::MINIMAL_XML);
168✔
65
    _message_set->addFromXMLString(mav_embedded::STANDARD_XML);
168✔
66
    _message_set->addFromXMLString(mav_embedded::COMMON_XML);
168✔
67
    _message_set->addFromXMLString(mav_embedded::ARDUPILOTMEGA_XML);
168✔
68

69
    // Initialize BufferParser for thread-safe parsing
70
    _buffer_parser = std::make_unique<mav::BufferParser>(*_message_set);
168✔
71

72
    // Start the user callback thread first, so it is ready for anything generated by
73
    // the work thread.
74

75
    _process_user_callbacks_thread =
76
        std::make_unique<std::thread>(&MavsdkImpl::process_user_callbacks_thread, this);
168✔
77

78
    _work_thread = std::make_unique<std::thread>(&MavsdkImpl::work_thread, this);
168✔
79
}
168✔
80

81
MavsdkImpl::~MavsdkImpl()
168✔
82
{
83
    {
84
        std::lock_guard<std::mutex> lock(_heartbeat_mutex);
168✔
85
        call_every_handler.remove(_heartbeat_send_cookie);
168✔
86
    }
168✔
87

88
    _should_exit = true;
168✔
89

90
    // Stop work first because we don't want to trigger anything that would
91
    // potentially want to call into user code.
92

93
    if (_work_thread) {
168✔
94
        _work_thread->join();
168✔
95
        _work_thread.reset();
168✔
96
    }
97

98
    if (_process_user_callbacks_thread) {
168✔
99
        _user_callback_queue.stop();
168✔
100
        _process_user_callbacks_thread->join();
168✔
101
        _process_user_callbacks_thread.reset();
168✔
102
    }
103

104
    std::lock_guard lock(_mutex);
168✔
105

106
    // Tell all systems to stop their threads BEFORE clearing
107
    for (auto& pair : _systems) {
340✔
108
        pair.second->_system_impl->signal_exit();
172✔
109
    }
110

111
    _systems.clear();
168✔
112
    _connections.clear();
168✔
113
}
168✔
114

115
std::string MavsdkImpl::version()
1✔
116
{
117
    static unsigned version_counter = 0;
118

119
    ++version_counter;
1✔
120

121
    switch (version_counter) {
1✔
122
        case 10:
×
123
            return "You were wondering about the name of this library?";
×
124
        case 11:
×
125
            return "Let's look at the history:";
×
126
        case 12:
×
127
            return "DroneLink";
×
128
        case 13:
×
129
            return "DroneCore";
×
130
        case 14:
×
131
            return "DronecodeSDK";
×
132
        case 15:
×
133
            return "MAVSDK";
×
134
        case 16:
×
135
            return "And that's it...";
×
136
        case 17:
×
137
            return "At least for now ¯\\_(ツ)_/¯.";
×
138
        default:
1✔
139
            return mavsdk_version;
1✔
140
    }
141
}
142

143
std::vector<std::shared_ptr<System>> MavsdkImpl::systems() const
223✔
144
{
145
    std::vector<std::shared_ptr<System>> systems_result{};
223✔
146

147
    std::lock_guard lock(_mutex);
223✔
148
    for (auto& system : _systems) {
353✔
149
        // We ignore the 0 entry because it's just a null system.
150
        // It's only created because the older, deprecated API needs a
151
        // reference.
152
        if (system.first == 0) {
130✔
153
            continue;
×
154
        }
155
        systems_result.push_back(system.second);
130✔
156
    }
157

158
    return systems_result;
223✔
159
}
223✔
160

161
std::optional<std::shared_ptr<System>> MavsdkImpl::first_autopilot(double timeout_s)
70✔
162
{
163
    {
164
        std::lock_guard lock(_mutex);
70✔
165
        for (auto system : _systems) {
71✔
166
            if (system.second->is_connected() && system.second->has_autopilot()) {
4✔
167
                return system.second;
3✔
168
            }
169
        }
4✔
170
    }
70✔
171

172
    if (timeout_s == 0.0) {
67✔
173
        // Don't wait at all.
174
        return {};
×
175
    }
176

177
    auto prom = std::make_shared<std::promise<std::shared_ptr<System>>>();
67✔
178
    auto fut = prom->get_future();
67✔
179

180
    auto flag = std::make_shared<std::once_flag>();
67✔
181
    auto handle = subscribe_on_new_system([this, prom, flag]() {
67✔
182
        // Check all systems, not just the first one
183
        auto all_systems = systems();
68✔
184
        for (auto& system : all_systems) {
70✔
185
            if (system->is_connected() && system->has_autopilot()) {
69✔
186
                std::call_once(*flag, [prom, system]() { prom->set_value(system); });
134✔
187
                break;
67✔
188
            }
189
        }
190
    });
68✔
191

192
    if (timeout_s > 0.0) {
67✔
193
        if (fut.wait_for(std::chrono::milliseconds(int64_t(timeout_s * 1e3))) ==
67✔
194
            std::future_status::ready) {
195
            unsubscribe_on_new_system(handle);
67✔
196
            return fut.get();
67✔
197

198
        } else {
199
            unsubscribe_on_new_system(handle);
×
200
            return std::nullopt;
×
201
        }
202
    } else {
203
        fut.wait();
×
204
        unsubscribe_on_new_system(handle);
×
205
        return std::optional(fut.get());
×
206
    }
207
}
67✔
208

209
std::shared_ptr<ServerComponent> MavsdkImpl::server_component(unsigned instance)
64✔
210
{
211
    std::lock_guard lock(_mutex);
64✔
212

213
    auto component_type = get_component_type();
64✔
214
    switch (component_type) {
64✔
215
        case ComponentType::Autopilot:
64✔
216
        case ComponentType::GroundStation:
217
        case ComponentType::CompanionComputer:
218
        case ComponentType::Camera:
219
        case ComponentType::Gimbal:
220
        case ComponentType::RemoteId:
221
        case ComponentType::Custom:
222
            return server_component_by_type(component_type, instance);
64✔
223
        default:
×
224
            LogErr() << "Unknown component type";
×
225
            return {};
×
226
    }
227
}
64✔
228

229
std::shared_ptr<ServerComponent>
230
MavsdkImpl::server_component_by_type(ComponentType server_component_type, unsigned instance)
64✔
231
{
232
    const auto mav_type = mav_type_for_component_type(server_component_type);
64✔
233

234
    switch (server_component_type) {
64✔
235
        case ComponentType::Autopilot:
46✔
236
            if (instance == 0) {
46✔
237
                return server_component_by_id(MAV_COMP_ID_AUTOPILOT1, mav_type);
46✔
238
            } else {
239
                LogErr() << "Only autopilot instance 0 is valid";
×
240
                return {};
×
241
            }
242

243
        case ComponentType::GroundStation:
×
244
            if (instance == 0) {
×
245
                return server_component_by_id(MAV_COMP_ID_MISSIONPLANNER, mav_type);
×
246
            } else {
247
                LogErr() << "Only one ground station supported at this time";
×
248
                return {};
×
249
            }
250

251
        case ComponentType::CompanionComputer:
1✔
252
            if (instance == 0) {
1✔
253
                return server_component_by_id(MAV_COMP_ID_ONBOARD_COMPUTER, mav_type);
1✔
254
            } else if (instance == 1) {
×
255
                return server_component_by_id(MAV_COMP_ID_ONBOARD_COMPUTER2, mav_type);
×
256
            } else if (instance == 2) {
×
257
                return server_component_by_id(MAV_COMP_ID_ONBOARD_COMPUTER3, mav_type);
×
258
            } else if (instance == 3) {
×
259
                return server_component_by_id(MAV_COMP_ID_ONBOARD_COMPUTER4, mav_type);
×
260
            } else {
261
                LogErr() << "Only companion computer 0..3 are supported";
×
262
                return {};
×
263
            }
264

265
        case ComponentType::Camera:
17✔
266
            if (instance == 0) {
17✔
267
                return server_component_by_id(MAV_COMP_ID_CAMERA, mav_type);
17✔
268
            } else if (instance == 1) {
×
269
                return server_component_by_id(MAV_COMP_ID_CAMERA2, mav_type);
×
270
            } else if (instance == 2) {
×
271
                return server_component_by_id(MAV_COMP_ID_CAMERA3, mav_type);
×
272
            } else if (instance == 3) {
×
273
                return server_component_by_id(MAV_COMP_ID_CAMERA4, mav_type);
×
274
            } else if (instance == 4) {
×
275
                return server_component_by_id(MAV_COMP_ID_CAMERA5, mav_type);
×
276
            } else if (instance == 5) {
×
277
                return server_component_by_id(MAV_COMP_ID_CAMERA6, mav_type);
×
278
            } else {
279
                LogErr() << "Only camera 0..5 are supported";
×
280
                return {};
×
281
            }
282

283
        case ComponentType::Gimbal:
×
284
            if (instance == 0) {
×
285
                return server_component_by_id(MAV_COMP_ID_GIMBAL, mav_type);
×
286
            } else {
287
                LogErr() << "Only gimbal instance 0 is valid";
×
288
                return {};
×
289
            }
290

291
        case ComponentType::RemoteId:
×
292
            if (instance == 0) {
×
293
                return server_component_by_id(MAV_COMP_ID_ODID_TXRX_1, mav_type);
×
294
            } else {
295
                LogErr() << "Only remote ID instance 0 is valid";
×
296
                return {};
×
297
            }
298

299
        case ComponentType::Custom:
×
300
            LogErr() << "Custom component type requires explicit component ID";
×
301
            return {};
×
302

303
        default:
×
304
            LogErr() << "Unknown server component type";
×
305
            return {};
×
306
    }
307
}
308

309
std::shared_ptr<ServerComponent>
310
MavsdkImpl::server_component_by_id(uint8_t component_id, uint8_t mav_type)
64✔
311
{
312
    if (component_id == 0) {
64✔
313
        LogErr() << "Server component with component ID 0 not allowed";
×
314
        return nullptr;
×
315
    }
316

317
    std::lock_guard lock(_server_components_mutex);
64✔
318

319
    return server_component_by_id_with_lock(component_id, mav_type);
64✔
320
}
64✔
321

322
std::shared_ptr<ServerComponent>
323
MavsdkImpl::server_component_by_id_with_lock(uint8_t component_id, uint8_t mav_type)
315✔
324
{
325
    for (auto& it : _server_components) {
316✔
326
        if (it.first == component_id) {
147✔
327
            if (it.second != nullptr) {
146✔
328
                return it.second;
146✔
329
            } else {
330
                it.second = std::make_shared<ServerComponent>(*this, component_id, mav_type);
×
331
            }
332
        }
333
    }
334

335
    _server_components.emplace_back(std::pair<uint8_t, std::shared_ptr<ServerComponent>>(
169✔
336
        component_id, std::make_shared<ServerComponent>(*this, component_id, mav_type)));
338✔
337

338
    return _server_components.back().second;
169✔
339
}
340

341
void MavsdkImpl::forward_message(mavlink_message_t& message, Connection* connection)
49✔
342
{
343
    // Forward_message Function implementing Mavlink routing rules.
344
    // See https://mavlink.io/en/guide/routing.html
345

346
    bool forward_heartbeats_enabled = true;
49✔
347
    const uint8_t target_system_id = get_target_system_id(message);
49✔
348
    const uint8_t target_component_id = get_target_component_id(message);
49✔
349

350
    // If it's a message only for us, we keep it, otherwise, we forward it.
351
    const bool targeted_only_at_us =
352
        (target_system_id == get_own_system_id() && target_component_id == get_own_component_id());
49✔
353

354
    // We don't forward heartbeats unless it's specifically enabled.
355
    const bool heartbeat_check_ok =
49✔
356
        (message.msgid != MAVLINK_MSG_ID_HEARTBEAT || forward_heartbeats_enabled);
49✔
357

358
    if (!targeted_only_at_us && heartbeat_check_ok) {
49✔
359
        unsigned successful_emissions = 0;
42✔
360
        for (auto& entry : _connections) {
123✔
361
            // Check whether the connection is not the one from which we received the message.
362
            // And also check if the connection was set to forward messages.
363
            if (entry.connection.get() == connection ||
120✔
364
                !entry.connection->should_forward_messages()) {
39✔
365
                continue;
42✔
366
            }
367
            auto result = (*entry.connection).send_message(message);
39✔
368
            if (result.first) {
39✔
369
                successful_emissions++;
38✔
370
            } else {
371
                _connections_errors_subscriptions.queue(
2✔
372
                    Mavsdk::ConnectionError{result.second, entry.handle},
1✔
373
                    [this](const auto& func) { call_user_callback(func); });
×
374
            }
375
        }
39✔
376
        if (successful_emissions == 0) {
42✔
377
            if (_system_debugging) {
4✔
378
                LogErr() << "Message forwarding failed";
×
379
            }
380
        }
381
    }
382
}
49✔
383

384
void MavsdkImpl::receive_message(
9,058✔
385
    MavlinkReceiver::ParseResult result, mavlink_message_t& message, Connection* connection)
386
{
387
    if (result == MavlinkReceiver::ParseResult::MessageParsed) {
9,058✔
388
        // Valid message: queue for full processing (which includes forwarding)
389
        {
390
            std::lock_guard lock(_received_messages_mutex);
9,060✔
391
            _received_messages.emplace(ReceivedMessage{std::move(message), connection});
9,068✔
392
        }
9,064✔
393
        _received_messages_cv.notify_one();
9,068✔
394

395
    } else if (result == MavlinkReceiver::ParseResult::BadCrc) {
396
        // Unknown message: forward only, don't process locally
397
        forward_message(message, connection);
4✔
398
    }
399
}
9,065✔
400

401
void MavsdkImpl::receive_libmav_message(
9,068✔
402
    const Mavsdk::MavlinkMessage& message, Connection* connection)
403
{
404
    {
405
        std::lock_guard lock(_received_libmav_messages_mutex);
9,068✔
406
        _received_libmav_messages.emplace(ReceivedLibmavMessage{message, connection});
9,071✔
407
    }
9,069✔
408
    _received_libmav_messages_cv.notify_one();
9,071✔
409
}
9,069✔
410

411
void MavsdkImpl::process_messages()
44,499✔
412
{
413
    std::lock_guard lock(_received_messages_mutex);
44,499✔
414
    while (!_received_messages.empty()) {
53,715✔
415
        auto message_copied = _received_messages.front();
9,040✔
416
        process_message(message_copied.message, message_copied.connection_ptr);
9,032✔
417
        _received_messages.pop();
9,038✔
418
    }
419
}
44,540✔
420

421
void MavsdkImpl::process_libmav_messages()
44,445✔
422
{
423
    std::lock_guard lock(_received_libmav_messages_mutex);
44,445✔
424
    while (!_received_libmav_messages.empty()) {
53,713✔
425
        auto message_copied = _received_libmav_messages.front();
9,039✔
426
        process_libmav_message(message_copied.message, message_copied.connection_ptr);
9,036✔
427
        _received_libmav_messages.pop();
9,036✔
428
    }
9,039✔
429
}
44,508✔
430

431
void MavsdkImpl::process_message(mavlink_message_t& message, Connection* connection)
9,034✔
432
{
433
    // Assumes _received_messages_mutex
434

435
    if (_message_logging_on) {
9,034✔
436
        LogDebug() << "Processing message " << message.msgid << " from "
×
437
                   << static_cast<int>(message.sysid) << "/" << static_cast<int>(message.compid);
×
438
    }
439

440
    if (_should_exit) {
9,034✔
441
        // If we're meant to clean up, let's not try to acquire any more locks but bail.
442
        return;
×
443
    }
444

445
    {
446
        std::lock_guard lock(_mutex);
9,039✔
447

448
        // This is a low level interface where incoming messages can be tampered
449
        // with or even dropped. This happens BEFORE forwarding, so modifications
450
        // and drops affect both local processing AND forwarded messages.
451
        {
452
            bool keep = true;
9,040✔
453
            {
454
                std::lock_guard<std::mutex> intercept_lock(_intercept_callbacks_mutex);
9,040✔
455
                if (_intercept_incoming_messages_callback != nullptr) {
9,040✔
456
                    keep = _intercept_incoming_messages_callback(message);
356✔
457
                }
458
            }
9,040✔
459

460
            if (!keep) {
9,039✔
461
                LogDebug() << "Dropped incoming message: " << int(message.msgid);
40✔
462
                return;
40✔
463
            }
464
        }
465

466
        if (_should_exit) {
8,999✔
467
            // If we're meant to clean up, let's not try to acquire any more locks but bail.
468
            return;
×
469
        }
470

471
        /* Forward message (after intercept) if option is enabled and multiple interfaces
472
         * are connected.
473
         * Performs message forwarding checks for every message if message forwarding is
474
         * enabled on at least one connection, and in case of a single forwarding connection,
475
         * we check that it is not the one which received the current message.
476
         *
477
         * Conditions:
478
         * 1. At least 2 connections.
479
         * 2. At least 1 forwarding connection.
480
         * 3. At least 2 forwarding connections or current connection is not forwarding.
481
         */
482

483
        if (_connections.size() > 1 && mavsdk::Connection::forwarding_connections_count() > 0 &&
9,043✔
484
            (mavsdk::Connection::forwarding_connections_count() > 1 ||
45✔
UNCOV
485
             !connection->should_forward_messages())) {
×
486
            if (_message_logging_on) {
45✔
487
                LogDebug() << "Forwarding message " << message.msgid << " from "
×
488
                           << static_cast<int>(message.sysid) << "/"
×
489
                           << static_cast<int>(message.compid);
×
490
            }
491
            forward_message(message, connection);
45✔
492
        }
493

494
        // Don't ever create a system with sysid 0.
495
        if (message.sysid == 0) {
8,992✔
496
            if (_message_logging_on) {
×
497
                LogDebug() << "Ignoring message with sysid == 0";
×
498
            }
499
            return;
×
500
        }
501

502
        // Filter out messages by QGroundControl, however, only do that if MAVSDK
503
        // is also implementing a ground station and not if it is used in another
504
        // configuration, e.g. on a companion.
505
        //
506
        // This is a workaround because PX4 started forwarding messages between
507
        // mavlink instances which leads to existing implementations (including
508
        // examples and integration tests) to connect to QGroundControl by accident
509
        // instead of PX4 because the check `has_autopilot()` is not used.
510

511
        if (get_component_type() == ComponentType::GroundStation && message.sysid == 255 &&
8,992✔
NEW
512
            message.compid == MAV_COMP_ID_MISSIONPLANNER) {
×
513
            if (_message_logging_on) {
×
514
                LogDebug() << "Ignoring messages from QGC as we are also a ground station";
×
515
            }
516
            return;
×
517
        }
518

519
        bool found_system = false;
9,000✔
520
        for (auto& system : _systems) {
9,035✔
521
            if (system.first == message.sysid) {
8,864✔
522
                system.second->system_impl()->add_new_component(message.compid);
8,829✔
523
                found_system = true;
8,834✔
524
                break;
8,834✔
525
            }
526
        }
527

528
        if (!found_system) {
9,000✔
529
            if (_system_debugging) {
166✔
530
                LogWarn() << "Create new system/component " << (int)message.sysid << "/"
×
531
                          << (int)message.compid;
×
532
                LogWarn() << "From message " << (int)message.msgid << " with len "
×
533
                          << (int)message.len;
×
534
                std::string bytes = "";
×
535
                for (unsigned i = 0; i < 12 + message.len; ++i) {
×
536
                    bytes += std::to_string(reinterpret_cast<uint8_t*>(&message)[i]) + ' ';
×
537
                }
538
                LogWarn() << "Bytes: " << bytes;
×
539
            }
×
540
            make_system_with_component(message.sysid, message.compid);
166✔
541

542
            // We now better talk back.
543
            start_sending_heartbeats();
166✔
544
        }
545

546
        if (_should_exit) {
9,000✔
547
            // Don't try to call at() if systems have already been destroyed
548
            // in destructor.
549
            return;
×
550
        }
551
    }
9,033✔
552

553
    mavlink_message_handler.process_message(message);
8,999✔
554

555
    for (auto& system : _systems) {
9,035✔
556
        if (system.first == message.sysid) {
9,035✔
557
            system.second->system_impl()->process_mavlink_message(message);
8,999✔
558
            break;
9,000✔
559
        }
560
    }
561
}
562

563
void MavsdkImpl::process_libmav_message(
9,036✔
564
    const Mavsdk::MavlinkMessage& message, Connection* /* connection */)
565
{
566
    // Assumes _received_libmav_messages_mutex
567

568
    if (_message_logging_on) {
9,036✔
569
        LogDebug() << "MavsdkImpl::process_libmav_message: " << message.message_name << " from "
×
570
                   << static_cast<int>(message.system_id) << "/"
×
571
                   << static_cast<int>(message.component_id);
×
572
    }
573

574
    // JSON message interception for incoming messages
575
    if (!call_json_interception_callbacks(message, _incoming_json_message_subscriptions)) {
9,036✔
576
        // Message was dropped by interception callback
577
        if (_message_logging_on) {
×
578
            LogDebug() << "Incoming JSON message " << message.message_name
×
579
                       << " dropped by interception";
×
580
        }
581
        return;
×
582
    }
583

584
    if (_message_logging_on) {
9,040✔
585
        LogDebug() << "Processing libmav message " << message.message_name << " from "
×
586
                   << static_cast<int>(message.system_id) << "/"
×
587
                   << static_cast<int>(message.component_id);
×
588
    }
589

590
    if (_should_exit) {
9,040✔
591
        // If we're meant to clean up, let's not try to acquire any more locks but bail.
592
        return;
×
593
    }
594

595
    {
596
        std::lock_guard lock(_mutex);
9,037✔
597

598
        // Don't ever create a system with sysid 0.
599
        if (message.system_id == 0) {
9,037✔
600
            if (_message_logging_on) {
×
601
                LogDebug() << "Ignoring libmav message with sysid == 0";
×
602
            }
603
            return;
×
604
        }
605

606
        // Filter out QGroundControl messages similar to regular mavlink processing
607
        if (get_component_type() == ComponentType::GroundStation && message.system_id == 255 &&
9,037✔
NEW
608
            message.component_id == MAV_COMP_ID_MISSIONPLANNER) {
×
609
            if (_message_logging_on) {
×
610
                LogDebug() << "Ignoring libmav messages from QGC as we are also a ground station";
×
611
            }
612
            return;
×
613
        }
614

615
        bool found_system = false;
9,041✔
616
        for (auto& system : _systems) {
9,077✔
617
            if (system.first == message.system_id) {
9,067✔
618
                system.second->system_impl()->add_new_component(message.component_id);
9,031✔
619
                found_system = true;
9,035✔
620
                break;
9,035✔
621
            }
622
        }
623

624
        if (!found_system) {
9,041✔
625
            if (_system_debugging) {
6✔
626
                LogWarn() << "Create new system/component from libmav " << (int)message.system_id
×
627
                          << "/" << (int)message.component_id;
×
628
            }
629
            make_system_with_component(message.system_id, message.component_id);
6✔
630

631
            // We now better talk back.
632
            start_sending_heartbeats();
6✔
633
        }
634

635
        if (_should_exit) {
9,041✔
636
            // Don't try to call at() if systems have already been destroyed
637
            // in destructor.
638
            return;
×
639
        }
640
    }
9,039✔
641

642
    // Distribute libmav message to systems for libmav-specific handling
643
    bool found_system = false;
9,039✔
644
    for (auto& system : _systems) {
18,145✔
645
        if (system.first == message.system_id) {
9,106✔
646
            if (_message_logging_on) {
9,042✔
647
                LogDebug() << "Distributing libmav message " << message.message_name
×
648
                           << " to SystemImpl for system " << system.first;
×
649
            }
650
            system.second->system_impl()->process_libmav_message(message);
9,042✔
651
            found_system = true;
9,042✔
652
            // Don't break - distribute to all matching system instances
653
        }
654
    }
655

656
    if (!found_system) {
9,039✔
657
        LogWarn() << "No system found for libmav message " << message.message_name
×
658
                  << " from system " << message.system_id;
×
659
    }
660
}
661

662
bool MavsdkImpl::send_message(mavlink_message_t& message)
9,213✔
663
{
664
    // Create a copy of the message to avoid reference issues
665
    mavlink_message_t message_copy = message;
9,213✔
666

667
    {
668
        std::lock_guard lock(_messages_to_send_mutex);
9,213✔
669
        _messages_to_send.push(std::move(message_copy));
9,227✔
670
    }
9,218✔
671

672
    // For heartbeat messages, we want to process them immediately to speed up system discovery
673
    if (message.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
9,228✔
674
        // Trigger message processing in the work thread
675
        // This is a hint to process messages sooner, but doesn't block
676
        std::this_thread::yield();
578✔
677
    }
678

679
    return true;
9,228✔
680
}
681

682
void MavsdkImpl::deliver_messages()
53,808✔
683
{
684
    // Process messages one at a time to avoid holding the mutex while delivering
685
    while (true) {
686
        mavlink_message_t message;
687
        {
688
            std::lock_guard lock(_messages_to_send_mutex);
53,808✔
689
            if (_messages_to_send.empty()) {
53,891✔
690
                break;
44,567✔
691
            }
692
            message = _messages_to_send.front();
9,228✔
693
            _messages_to_send.pop();
9,225✔
694
        }
53,791✔
695
        deliver_message(message);
9,227✔
696
    }
9,229✔
697
}
44,613✔
698

699
void MavsdkImpl::deliver_message(mavlink_message_t& message)
9,223✔
700
{
701
    if (_message_logging_on) {
9,223✔
702
        LogDebug() << "Sending message " << message.msgid << " from "
×
703
                   << static_cast<int>(message.sysid) << "/" << static_cast<int>(message.compid)
×
704
                   << " to " << static_cast<int>(get_target_system_id(message)) << "/"
×
705
                   << static_cast<int>(get_target_component_id(message));
×
706
    }
707

708
    // This is a low level interface where outgoing messages can be tampered
709
    // with or even dropped.
710
    bool keep = true;
9,223✔
711
    {
712
        std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
9,223✔
713
        if (_intercept_outgoing_messages_callback != nullptr) {
9,226✔
714
            keep = _intercept_outgoing_messages_callback(message);
321✔
715
        }
716
    }
9,224✔
717

718
    if (!keep) {
9,228✔
719
        // We fake that everything was sent as instructed because
720
        // a potential loss would happen later, and we would not be informed
721
        // about it.
722
        LogDebug() << "Dropped outgoing message: " << int(message.msgid);
88✔
723
        return;
192✔
724
    }
725

726
    // JSON message interception for outgoing messages
727
    // Convert mavlink_message_t to Mavsdk::MavlinkMessage for JSON interception
728
    uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
729
    uint16_t len = mavlink_msg_to_send_buffer(buffer, &message);
9,140✔
730

731
    size_t bytes_consumed = 0;
9,134✔
732
    auto libmav_msg_opt = parse_message_safe(buffer, len, bytes_consumed);
9,134✔
733

734
    if (libmav_msg_opt) {
9,138✔
735
        // Create Mavsdk::MavlinkMessage directly for JSON interception
736
        Mavsdk::MavlinkMessage json_message;
9,141✔
737
        json_message.message_name = libmav_msg_opt.value().name();
9,136✔
738
        json_message.system_id = message.sysid;
9,133✔
739
        json_message.component_id = message.compid;
9,133✔
740

741
        // Extract target_system and target_component if present
742
        uint8_t target_system_id = 0;
9,133✔
743
        uint8_t target_component_id = 0;
9,133✔
744
        if (libmav_msg_opt.value().get("target_system", target_system_id) ==
9,133✔
745
            mav::MessageResult::Success) {
746
            json_message.target_system_id = target_system_id;
8,274✔
747
        } else {
748
            json_message.target_system_id = 0;
863✔
749
        }
750
        if (libmav_msg_opt.value().get("target_component", target_component_id) ==
9,137✔
751
            mav::MessageResult::Success) {
752
            json_message.target_component_id = target_component_id;
8,275✔
753
        } else {
754
            json_message.target_component_id = 0;
865✔
755
        }
756

757
        // Generate JSON using LibmavReceiver's public method
758
        auto connections = get_connections();
9,140✔
759
        if (!connections.empty() && connections[0]->get_libmav_receiver()) {
9,140✔
760
            json_message.fields_json =
761
                connections[0]->get_libmav_receiver()->libmav_message_to_json(
27,102✔
762
                    libmav_msg_opt.value());
18,061✔
763
        } else {
764
            // Fallback: create minimal JSON if no receiver available
765
            json_message.fields_json =
766
                "{\"message_id\":" + std::to_string(libmav_msg_opt.value().id()) +
208✔
767
                ",\"message_name\":\"" + libmav_msg_opt.value().name() + "\"}";
208✔
768
        }
769

770
        if (!call_json_interception_callbacks(json_message, _outgoing_json_message_subscriptions)) {
9,138✔
771
            // Message was dropped by JSON interception callback
772
            if (_message_logging_on) {
773
                LogDebug() << "Outgoing JSON message " << json_message.message_name
×
774
                           << " dropped by interception";
×
775
            }
776
            return;
×
777
        }
778
    }
9,137✔
779

780
    std::lock_guard lock(_mutex);
9,135✔
781

782
    if (_connections.empty()) {
9,140✔
783
        // We obviously can't send any messages without a connection added, so
784
        // we silently ignore this.
785
        return;
104✔
786
    }
787

788
    uint8_t successful_emissions = 0;
9,033✔
789
    for (auto& _connection : _connections) {
18,088✔
790
        const uint8_t target_system_id = get_target_system_id(message);
9,048✔
791

792
        if (target_system_id != 0 && !(*_connection.connection).has_system_id(target_system_id)) {
9,049✔
793
            continue;
5✔
794
        }
795
        const auto result = (*_connection.connection).send_message(message);
9,044✔
796
        if (result.first) {
9,049✔
797
            successful_emissions++;
9,039✔
798
        } else {
799
            _connections_errors_subscriptions.queue(
20✔
800
                Mavsdk::ConnectionError{result.second, _connection.handle},
10✔
801
                [this](const auto& func) { call_user_callback(func); });
×
802
        }
803
    }
9,049✔
804

805
    if (successful_emissions == 0) {
9,031✔
806
        LogErr() << "Sending message failed";
8✔
807
    }
808
}
9,140✔
809

810
std::pair<ConnectionResult, Mavsdk::ConnectionHandle> MavsdkImpl::add_any_connection(
174✔
811
    const std::string& connection_url, ForwardingOption forwarding_option)
812
{
813
    CliArg cli_arg;
174✔
814
    if (!cli_arg.parse(connection_url)) {
174✔
815
        return {ConnectionResult::ConnectionUrlInvalid, Mavsdk::ConnectionHandle{}};
×
816
    }
817

818
    return std::visit(
174✔
819
        overloaded{
348✔
820
            [](std::monostate) {
×
821
                // Should not happen anyway.
822
                return std::pair<ConnectionResult, Mavsdk::ConnectionHandle>{
×
823
                    ConnectionResult::ConnectionUrlInvalid, Mavsdk::ConnectionHandle()};
×
824
            },
825
            [this, forwarding_option](const CliArg::Udp& udp) {
162✔
826
                return add_udp_connection(udp, forwarding_option);
162✔
827
            },
828
            [this, forwarding_option](const CliArg::Tcp& tcp) {
10✔
829
                return add_tcp_connection(tcp, forwarding_option);
10✔
830
            },
831
            [this, forwarding_option](const CliArg::Serial& serial) {
×
832
                return add_serial_connection(
×
833
                    serial.path, serial.baudrate, serial.flow_control_enabled, forwarding_option);
×
834
            },
835
            [this, forwarding_option](const CliArg::Raw&) {
2✔
836
                return add_raw_connection(forwarding_option);
2✔
837
            }},
838
        cli_arg.protocol);
174✔
839
}
174✔
840

841
std::pair<ConnectionResult, Mavsdk::ConnectionHandle>
842
MavsdkImpl::add_udp_connection(const CliArg::Udp& udp, ForwardingOption forwarding_option)
162✔
843
{
844
    auto new_conn = std::make_unique<UdpConnection>(
845
        [this](
8,979✔
846
            MavlinkReceiver::ParseResult result,
847
            mavlink_message_t& message,
848
            Connection* connection) { receive_message(result, message, connection); },
8,979✔
849
        [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
8,988✔
850
            receive_libmav_message(message, connection);
8,988✔
851
        },
8,988✔
852
        *this, // Pass MavsdkImpl reference for thread-safe MessageSet access
853
        udp.mode == CliArg::Udp::Mode::In ? udp.host : "0.0.0.0",
324✔
854
        udp.mode == CliArg::Udp::Mode::In ? udp.port : 0,
162✔
855
        forwarding_option);
162✔
856

857
    if (!new_conn) {
162✔
858
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
859
    }
860

861
    ConnectionResult ret = new_conn->start();
162✔
862

863
    if (ret != ConnectionResult::Success) {
162✔
864
        return {ret, Mavsdk::ConnectionHandle{}};
×
865
    }
866

867
    if (udp.mode == CliArg::Udp::Mode::Out) {
162✔
868
        // We need to add the IP rather than a hostname, otherwise we end up with two remotes:
869
        // one for the IP, and one for a hostname.
870
        auto remote_ip = resolve_hostname_to_ip(udp.host);
81✔
871

872
        if (!remote_ip) {
81✔
873
            return {ConnectionResult::DestinationIpUnknown, Mavsdk::ConnectionHandle{}};
×
874
        }
875

876
        new_conn->add_remote_to_keep(remote_ip.value(), udp.port);
81✔
877
        std::lock_guard lock(_mutex);
81✔
878

879
        // With a UDP remote, we need to initiate the connection by sending heartbeats.
880
        auto new_configuration = get_configuration();
81✔
881
        new_configuration.set_always_send_heartbeats(true);
81✔
882
        set_configuration(new_configuration);
81✔
883
    }
81✔
884

885
    auto handle = add_connection(std::move(new_conn));
162✔
886

887
    return {ConnectionResult::Success, handle};
162✔
888
}
162✔
889

890
std::pair<ConnectionResult, Mavsdk::ConnectionHandle>
891
MavsdkImpl::add_tcp_connection(const CliArg::Tcp& tcp, ForwardingOption forwarding_option)
10✔
892
{
893
    if (tcp.mode == CliArg::Tcp::Mode::Out) {
10✔
894
        auto new_conn = std::make_unique<TcpClientConnection>(
895
            [this](
42✔
896
                MavlinkReceiver::ParseResult result,
897
                mavlink_message_t& message,
898
                Connection* connection) { receive_message(result, message, connection); },
42✔
899
            [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
42✔
900
                receive_libmav_message(message, connection);
42✔
901
            },
42✔
902
            *this, // Pass MavsdkImpl reference for thread-safe MessageSet access
903
            tcp.host,
5✔
904
            tcp.port,
5✔
905
            forwarding_option);
5✔
906
        if (!new_conn) {
5✔
907
            return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
908
        }
909
        ConnectionResult ret = new_conn->start();
5✔
910
        if (ret == ConnectionResult::Success) {
5✔
911
            return {ret, add_connection(std::move(new_conn))};
5✔
912
        } else {
913
            return {ret, Mavsdk::ConnectionHandle{}};
×
914
        }
915
    } else {
5✔
916
        auto new_conn = std::make_unique<TcpServerConnection>(
917
            [this](
39✔
918
                MavlinkReceiver::ParseResult result,
919
                mavlink_message_t& message,
920
                Connection* connection) { receive_message(result, message, connection); },
39✔
921
            [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
39✔
922
                receive_libmav_message(message, connection);
39✔
923
            },
39✔
924
            *this, // Pass MavsdkImpl reference for thread-safe MessageSet access
925
            tcp.host,
5✔
926
            tcp.port,
5✔
927
            forwarding_option);
5✔
928
        if (!new_conn) {
5✔
929
            return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
930
        }
931
        ConnectionResult ret = new_conn->start();
5✔
932
        if (ret == ConnectionResult::Success) {
5✔
933
            return {ret, add_connection(std::move(new_conn))};
5✔
934
        } else {
935
            return {ret, Mavsdk::ConnectionHandle{}};
×
936
        }
937
    }
5✔
938
}
939

940
std::pair<ConnectionResult, Mavsdk::ConnectionHandle> MavsdkImpl::add_serial_connection(
×
941
    const std::string& dev_path,
942
    int baudrate,
943
    bool flow_control,
944
    ForwardingOption forwarding_option)
945
{
946
    auto new_conn = std::make_unique<SerialConnection>(
947
        [this](
×
948
            MavlinkReceiver::ParseResult result,
949
            mavlink_message_t& message,
950
            Connection* connection) { receive_message(result, message, connection); },
×
951
        [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
×
952
            receive_libmav_message(message, connection);
×
953
        },
×
954
        *this, // Pass MavsdkImpl reference for thread-safe MessageSet access
955
        dev_path,
956
        baudrate,
957
        flow_control,
958
        forwarding_option);
×
959
    if (!new_conn) {
×
960
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
961
    }
962
    ConnectionResult ret = new_conn->start();
×
963
    if (ret == ConnectionResult::Success) {
×
964
        auto handle = add_connection(std::move(new_conn));
×
965

966
        auto new_configuration = get_configuration();
×
967

968
        // PX4 starting with v1.13 does not send heartbeats by default, so we need
969
        // to initiate the MAVLink connection by sending heartbeats.
970
        // Therefore, we override the default here and enable sending heartbeats.
971
        new_configuration.set_always_send_heartbeats(true);
×
972
        set_configuration(new_configuration);
×
973

974
        return {ret, handle};
×
975

976
    } else {
977
        return {ret, Mavsdk::ConnectionHandle{}};
×
978
    }
979
}
×
980

981
std::pair<ConnectionResult, Mavsdk::ConnectionHandle>
982
MavsdkImpl::add_raw_connection(ForwardingOption forwarding_option)
2✔
983
{
984
    // Check if a raw connection already exists
985
    if (find_raw_connection() != nullptr) {
2✔
986
        LogErr() << "Raw connection already exists. Only one raw connection is allowed.";
×
987
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
988
    }
989

990
    auto new_conn = std::make_unique<RawConnection>(
991
        [this](
1✔
992
            MavlinkReceiver::ParseResult result,
993
            mavlink_message_t& message,
994
            Connection* connection) { receive_message(result, message, connection); },
1✔
995
        [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
1✔
996
            receive_libmav_message(message, connection);
1✔
997
        },
1✔
998
        *this,
999
        forwarding_option);
2✔
1000

1001
    if (!new_conn) {
2✔
1002
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
1003
    }
1004

1005
    ConnectionResult ret = new_conn->start();
2✔
1006
    if (ret != ConnectionResult::Success) {
2✔
1007
        return {ret, Mavsdk::ConnectionHandle{}};
×
1008
    }
1009

1010
    auto handle = add_connection(std::move(new_conn));
2✔
1011

1012
    // Enable heartbeats for raw connection
1013
    auto new_configuration = get_configuration();
2✔
1014
    new_configuration.set_always_send_heartbeats(true);
2✔
1015
    set_configuration(new_configuration);
2✔
1016

1017
    return {ConnectionResult::Success, handle};
2✔
1018
}
2✔
1019

1020
Mavsdk::ConnectionHandle MavsdkImpl::add_connection(std::unique_ptr<Connection>&& new_connection)
174✔
1021
{
1022
    std::lock_guard lock(_mutex);
174✔
1023
    auto handle = _connections_handle_factory.create();
174✔
1024
    _connections.emplace_back(ConnectionEntry{std::move(new_connection), handle});
174✔
1025

1026
    return handle;
348✔
1027
}
174✔
1028

1029
void MavsdkImpl::remove_connection(Mavsdk::ConnectionHandle handle)
12✔
1030
{
1031
    std::lock_guard lock(_mutex);
12✔
1032

1033
    _connections.erase(std::remove_if(_connections.begin(), _connections.end(), [&](auto&& entry) {
12✔
1034
        return (entry.handle == handle);
12✔
1035
    }));
1036
}
12✔
1037

1038
Mavsdk::Configuration MavsdkImpl::get_configuration() const
83✔
1039
{
1040
    std::lock_guard configuration_lock(_configuration_mutex);
83✔
1041
    return _configuration;
166✔
1042
}
83✔
1043

1044
ComponentType MavsdkImpl::get_component_type() const
18,091✔
1045
{
1046
    std::lock_guard configuration_lock(_configuration_mutex);
18,091✔
1047
    return _configuration.get_component_type();
18,105✔
1048
}
18,105✔
1049

1050
void MavsdkImpl::set_configuration(Mavsdk::Configuration new_configuration)
251✔
1051
{
1052
    std::lock_guard server_components_lock(_server_components_mutex);
251✔
1053
    // We just point the default to the newly created component. This means
1054
    // that the previous default component will be deleted if it is not
1055
    // used/referenced anywhere.
1056
    _default_server_component = server_component_by_id_with_lock(
502✔
1057
        new_configuration.get_component_id(), new_configuration.get_mav_type());
502✔
1058

1059
    const bool was_always_sending_heartbeats = [this] {
502✔
1060
        std::lock_guard configuration_lock(_configuration_mutex);
251✔
1061
        return _configuration.get_always_send_heartbeats();
251✔
1062
    }();
502✔
1063

1064
    if (new_configuration.get_always_send_heartbeats() && !was_always_sending_heartbeats) {
251✔
1065
        start_sending_heartbeats();
88✔
1066
    } else if (
163✔
1067
        !new_configuration.get_always_send_heartbeats() && was_always_sending_heartbeats &&
163✔
NEW
1068
        !is_any_system_connected()) {
×
UNCOV
1069
        stop_sending_heartbeats();
×
1070
    }
1071

1072
    {
1073
        std::lock_guard configuration_lock(_configuration_mutex);
251✔
1074
        _configuration = new_configuration;
251✔
1075
    }
251✔
1076
    // We cache these values as atomic to avoid having to lock any mutex for them.
1077
    _our_system_id = new_configuration.get_system_id();
251✔
1078
    _our_component_id = new_configuration.get_component_id();
251✔
1079
}
251✔
1080

1081
uint8_t MavsdkImpl::get_own_system_id() const
12,710✔
1082
{
1083
    return _our_system_id;
12,710✔
1084
}
1085

1086
uint8_t MavsdkImpl::get_own_component_id() const
1,503✔
1087
{
1088
    return _our_component_id;
1,503✔
1089
}
1090

1091
uint8_t MavsdkImpl::get_mav_type() const
×
1092
{
NEW
1093
    std::lock_guard configuration_lock(_configuration_mutex);
×
UNCOV
1094
    return _configuration.get_mav_type();
×
UNCOV
1095
}
×
1096

1097
Autopilot MavsdkImpl::get_autopilot() const
×
1098
{
NEW
1099
    std::lock_guard configuration_lock(_configuration_mutex);
×
UNCOV
1100
    return _configuration.get_autopilot();
×
UNCOV
1101
}
×
1102

1103
uint8_t MavsdkImpl::get_mav_autopilot() const
312✔
1104
{
1105
    std::lock_guard configuration_lock(_configuration_mutex);
312✔
1106
    switch (_configuration.get_autopilot()) {
312✔
1107
        case Autopilot::Px4:
×
1108
            return MAV_AUTOPILOT_PX4;
×
1109
        case Autopilot::ArduPilot:
×
1110
            return MAV_AUTOPILOT_ARDUPILOTMEGA;
×
1111
        case Autopilot::Unknown:
312✔
1112
        default:
1113
            return MAV_AUTOPILOT_GENERIC;
312✔
1114
    }
1115
}
312✔
1116

1117
CompatibilityMode MavsdkImpl::get_compatibility_mode() const
43✔
1118
{
1119
    std::lock_guard configuration_lock(_configuration_mutex);
43✔
1120
    return _configuration.get_compatibility_mode();
43✔
1121
}
43✔
1122

1123
Autopilot MavsdkImpl::effective_autopilot(Autopilot detected) const
1,750✔
1124
{
1125
    CompatibilityMode compatibility_mode;
1126
    {
1127
        std::lock_guard configuration_lock(_configuration_mutex);
1,750✔
1128
        compatibility_mode = _configuration.get_compatibility_mode();
1,750✔
1129
    }
1,750✔
1130
    switch (compatibility_mode) {
1,750✔
1131
        case CompatibilityMode::Auto:
1,750✔
1132
            return detected;
1,750✔
1133
        case CompatibilityMode::Pure:
×
1134
            return Autopilot::Unknown; // Unknown = no quirks
×
1135
        case CompatibilityMode::Px4:
×
1136
            return Autopilot::Px4;
×
1137
        case CompatibilityMode::ArduPilot:
×
1138
            return Autopilot::ArduPilot;
×
1139
        default:
×
1140
            return detected;
×
1141
    }
1142
}
1143

1144
uint8_t MavsdkImpl::mav_type_for_component_type(ComponentType component_type)
401✔
1145
{
1146
    switch (component_type) {
401✔
1147
        case ComponentType::Autopilot:
117✔
1148
            return MAV_TYPE_GENERIC;
117✔
1149
        case ComponentType::GroundStation:
252✔
1150
            return MAV_TYPE_GCS;
252✔
1151
        case ComponentType::CompanionComputer:
4✔
1152
            return MAV_TYPE_ONBOARD_CONTROLLER;
4✔
1153
        case ComponentType::Camera:
28✔
1154
            return MAV_TYPE_CAMERA;
28✔
1155
        case ComponentType::Gimbal:
×
1156
            return MAV_TYPE_GIMBAL;
×
1157
        case ComponentType::RemoteId:
×
1158
            return MAV_TYPE_ODID;
×
1159
        case ComponentType::Custom:
×
1160
            return MAV_TYPE_GENERIC;
×
1161
        default:
×
1162
            return MAV_TYPE_GENERIC;
×
1163
    }
1164
}
1165

1166
void MavsdkImpl::make_system_with_component(uint8_t system_id, uint8_t comp_id)
172✔
1167
{
1168
    // Needs _systems_lock
1169

1170
    if (_should_exit) {
172✔
1171
        // When the system got destroyed in the destructor, we have to give up.
1172
        return;
×
1173
    }
1174

1175
    if (static_cast<int>(system_id) == 0 && static_cast<int>(comp_id) == 0) {
172✔
1176
        LogDebug() << "Initializing connection to remote system...";
×
1177
    }
1178

1179
    // Make a system with its first component
1180
    auto new_system = std::make_shared<System>(*this);
172✔
1181
    new_system->init(system_id, comp_id);
172✔
1182

1183
    _systems.emplace_back(system_id, new_system);
172✔
1184
}
172✔
1185

1186
void MavsdkImpl::notify_on_discover()
176✔
1187
{
1188
    // Queue the callbacks without holding the mutex to avoid deadlocks
1189
    _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
256✔
1190
}
176✔
1191

1192
void MavsdkImpl::notify_on_timeout()
5✔
1193
{
1194
    // Queue the callbacks without holding the mutex to avoid deadlocks
1195
    _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
5✔
1196
}
5✔
1197

1198
Mavsdk::NewSystemHandle
1199
MavsdkImpl::subscribe_on_new_system(const Mavsdk::NewSystemCallback& callback)
79✔
1200
{
1201
    std::lock_guard lock(_mutex);
79✔
1202

1203
    const auto handle = _new_system_callbacks.subscribe(callback);
79✔
1204

1205
    if (is_any_system_connected()) {
79✔
1206
        _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
×
1207
    }
1208

1209
    return handle;
158✔
1210
}
79✔
1211

1212
void MavsdkImpl::unsubscribe_on_new_system(Mavsdk::NewSystemHandle handle)
78✔
1213
{
1214
    _new_system_callbacks.unsubscribe(handle);
78✔
1215
}
78✔
1216

1217
bool MavsdkImpl::is_any_system_connected() const
79✔
1218
{
1219
    std::vector<std::shared_ptr<System>> connected_systems = systems();
79✔
1220
    return std::any_of(connected_systems.cbegin(), connected_systems.cend(), [](auto& system) {
79✔
1221
        return system->is_connected();
×
1222
    });
79✔
1223
}
79✔
1224

1225
void MavsdkImpl::work_thread()
168✔
1226
{
1227
    while (!_should_exit) {
44,609✔
1228
        // Process incoming messages
1229
        process_messages();
44,584✔
1230

1231
        // Process incoming libmav messages
1232
        process_libmav_messages();
44,452✔
1233

1234
        // Run timers
1235
        timeout_handler.run_once();
44,616✔
1236
        call_every_handler.run_once();
44,091✔
1237

1238
        // Do component work
1239
        {
1240
            std::lock_guard lock(_server_components_mutex);
44,356✔
1241
            for (auto& it : _server_components) {
89,176✔
1242
                if (it.second != nullptr) {
44,447✔
1243
                    it.second->_impl->do_work();
44,364✔
1244
                }
1245
            }
1246
        }
44,341✔
1247

1248
        // Deliver outgoing messages
1249
        deliver_messages();
44,666✔
1250

1251
        // If no messages to send, check if there are messages to receive
1252
        std::unique_lock lock_received(_received_messages_mutex);
44,571✔
1253
        if (_received_messages.empty()) {
44,666✔
1254
            // No messages to process, wait for a signal or timeout
1255
            _received_messages_cv.wait_for(lock_received, std::chrono::milliseconds(10), [this]() {
44,395✔
1256
                return !_received_messages.empty() || _should_exit;
88,088✔
1257
            });
1258
        }
1259
    }
43,503✔
1260
}
143✔
1261

1262
void MavsdkImpl::set_callback_executor(std::function<void(std::function<void()>)> executor)
×
1263
{
1264
    bool has_executor;
1265
    {
1266
        std::lock_guard<std::mutex> lock(_callback_executor_mutex);
×
1267
        _callback_executor = std::move(executor);
×
1268
        has_executor = !!_callback_executor;
×
1269
    }
×
1270

1271
    if (has_executor) {
×
1272
        // Stop the internal callback thread since user will handle callbacks
1273
        if (_process_user_callbacks_thread) {
×
1274
            _user_callback_queue.stop();
×
1275
            _process_user_callbacks_thread->join();
×
1276
            _process_user_callbacks_thread.reset();
×
1277
        }
1278

1279
        // Drain any remaining callbacks through the executor
1280
        {
1281
            std::lock_guard<std::mutex> lock(_callback_executor_mutex);
×
1282
            if (_callback_executor) {
×
1283
                LockedQueue<UserCallback>::Guard guard(_user_callback_queue);
×
1284
                while (auto ptr = guard.get_front()) {
×
1285
                    _callback_executor(ptr->func);
×
1286
                    guard.pop_front();
×
1287
                }
×
1288
            }
×
1289
        }
×
1290
    } else {
1291
        // Revert to default internal callback thread
1292
        if (!_process_user_callbacks_thread) {
×
1293
            _user_callback_queue.restart();
×
1294
            _process_user_callbacks_thread =
1295
                std::make_unique<std::thread>(&MavsdkImpl::process_user_callbacks_thread, this);
×
1296
        }
1297
    }
1298
}
×
1299

1300
void MavsdkImpl::call_user_callback_located(
1,337✔
1301
    const std::string& filename, const int linenumber, const std::function<void()>& func)
1302
{
1303
    // Don't enqueue callbacks if we're shutting down
1304
    if (_should_exit) {
1,337✔
1305
        return;
×
1306
    }
1307

1308
    {
1309
        std::lock_guard<std::mutex> lock(_callback_executor_mutex);
1,337✔
1310
        if (_callback_executor) {
1,337✔
1311
            _callback_executor(func);
×
1312
            return;
×
1313
        }
1314
    }
1,337✔
1315

1316
    auto callback_size = _user_callback_queue.size();
1,337✔
1317

1318
    if (_callback_tracker) {
1,337✔
1319
        _callback_tracker->record_queued(filename, linenumber);
×
1320
        _callback_tracker->maybe_print_stats(callback_size);
×
1321
    }
1322

1323
    if (callback_size >= 100) {
1,337✔
1324
        return;
×
1325

1326
    } else if (callback_size == 99) {
1,337✔
1327
        LogErr()
×
1328
            << "User callback queue overflown\n"
1329
               "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1330
        return;
×
1331

1332
    } else if (callback_size >= 10) {
1,337✔
1333
        LogWarn()
×
1334
            << "User callback queue slow (queue size: " << callback_size
×
1335
            << ").\n"
1336
               "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1337
    }
1338

1339
    // We only need to keep track of filename and linenumber if we're actually debugging this.
1340
    UserCallback user_callback =
1341
        _callback_debugging ? UserCallback{func, filename, linenumber} : UserCallback{func};
2,674✔
1342

1343
    _user_callback_queue.push_back(std::make_shared<UserCallback>(user_callback));
1,337✔
1344
}
1,337✔
1345

1346
void MavsdkImpl::process_user_callbacks_thread()
168✔
1347
{
1348
    while (!_should_exit) {
1,505✔
1349
        UserCallback callback;
1,505✔
1350
        {
1351
            LockedQueue<UserCallback>::Guard guard(_user_callback_queue);
1,505✔
1352
            auto ptr = guard.wait_and_pop_front();
1,505✔
1353
            if (!ptr) {
1,505✔
1354
                break;
168✔
1355
            }
1356
            // We need to get a copy instead of just a shared_ptr because the queue might
1357
            // be invalidated when the lock is released.
1358
            callback = *ptr;
1,337✔
1359
        }
1,673✔
1360

1361
        // Check if we're in the process of shutting down before executing the callback
1362
        if (_should_exit) {
1,337✔
1363
            continue;
×
1364
        }
1365

1366
        const double timeout_s = 1.0;
1,337✔
1367
        // Capture the fields we need by value: this watchdog runs on the io thread and
1368
        // can fire while (or just after) callback.func() runs here, so referencing the
1369
        // loop-local 'callback' would race with its destruction at the next iteration.
1370
        auto cookie = timeout_handler.add(
2,674✔
1371
            [this, timeout_s, filename = callback.filename, linenumber = callback.linenumber]() {
1,337✔
1372
                if (_callback_debugging) {
×
NEW
1373
                    LogWarn() << "Callback called from " << filename << ":" << linenumber
×
NEW
1374
                              << " took more than " << timeout_s << " second to run.";
×
1375
                    fflush(stdout);
×
1376
                    fflush(stderr);
×
1377
                    abort();
×
1378
                } else {
1379
                    LogWarn()
×
1380
                        << "Callback took more than " << timeout_s << " second to run.\n"
×
1381
                        << "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1382
                }
1383
            },
×
1384
            timeout_s);
1385
        auto callback_start = std::chrono::steady_clock::now();
1,337✔
1386
        callback.func();
1,337✔
1387
        auto callback_end = std::chrono::steady_clock::now();
1,337✔
1388
        timeout_handler.remove(cookie);
1,337✔
1389

1390
        if (_callback_tracker) {
1,337✔
1391
            auto callback_duration_us =
1392
                std::chrono::duration_cast<std::chrono::microseconds>(callback_end - callback_start)
×
1393
                    .count();
×
1394
            _callback_tracker->record_executed(
×
1395
                callback.filename, callback.linenumber, callback_duration_us);
1396
        }
1397
    }
1,505✔
1398
}
168✔
1399

1400
void MavsdkImpl::start_sending_heartbeats()
436✔
1401
{
1402
    // Check if we're in the process of shutting down
1403
    if (_should_exit) {
436✔
1404
        return;
×
1405
    }
1406

1407
    // Before sending out first heartbeats we need to make sure we have a
1408
    // default server component.
1409
    default_server_component_impl();
436✔
1410

1411
    {
1412
        std::lock_guard<std::mutex> lock(_heartbeat_mutex);
436✔
1413
        call_every_handler.remove(_heartbeat_send_cookie);
436✔
1414
        _heartbeat_send_cookie =
436✔
1415
            call_every_handler.add([this]() { send_heartbeats(); }, HEARTBEAT_SEND_INTERVAL_S);
1,004✔
1416
    }
436✔
1417
}
1418

1419
void MavsdkImpl::stop_sending_heartbeats()
5✔
1420
{
1421
    const bool always_send_heartbeats = [this] {
10✔
1422
        std::lock_guard configuration_lock(_configuration_mutex);
5✔
1423
        return _configuration.get_always_send_heartbeats();
5✔
1424
    }();
10✔
1425
    if (!always_send_heartbeats) {
5✔
1426
        std::lock_guard<std::mutex> lock(_heartbeat_mutex);
2✔
1427
        call_every_handler.remove(_heartbeat_send_cookie);
2✔
1428
    }
2✔
1429
}
5✔
1430

1431
ServerComponentImpl& MavsdkImpl::default_server_component_impl()
1,590✔
1432
{
1433
    std::lock_guard lock(_server_components_mutex);
1,590✔
1434
    return default_server_component_with_lock();
1,590✔
1435
}
1,590✔
1436

1437
ServerComponentImpl& MavsdkImpl::default_server_component_with_lock()
1,590✔
1438
{
1439
    if (_default_server_component == nullptr) {
1,590✔
1440
        _default_server_component =
1441
            server_component_by_id_with_lock(_our_component_id, get_mav_type());
×
1442
    }
1443
    return *_default_server_component->_impl;
1,590✔
1444
}
1445

1446
void MavsdkImpl::send_heartbeats()
569✔
1447
{
1448
    std::lock_guard lock(_server_components_mutex);
569✔
1449

1450
    for (auto& it : _server_components) {
1,139✔
1451
        if (it.second != nullptr) {
572✔
1452
            it.second->_impl->send_heartbeat();
573✔
1453
        }
1454
    }
1455
}
571✔
1456

1457
void MavsdkImpl::intercept_incoming_messages_async(std::function<bool(mavlink_message_t&)> callback)
28✔
1458
{
1459
    std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
28✔
1460
    _intercept_incoming_messages_callback = callback;
28✔
1461
}
28✔
1462

1463
void MavsdkImpl::intercept_outgoing_messages_async(std::function<bool(mavlink_message_t&)> callback)
16✔
1464
{
1465
    std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
16✔
1466
    _intercept_outgoing_messages_callback = callback;
16✔
1467
}
16✔
1468

1469
bool MavsdkImpl::call_json_interception_callbacks(
18,174✔
1470
    const Mavsdk::MavlinkMessage& json_message,
1471
    std::vector<std::pair<Mavsdk::InterceptJsonHandle, Mavsdk::InterceptJsonCallback>>&
1472
        callback_list)
1473
{
1474
    bool keep_message = true;
18,174✔
1475

1476
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
18,174✔
1477
    for (const auto& subscription : callback_list) {
18,186✔
1478
        if (!subscription.second(json_message)) {
6✔
1479
            keep_message = false;
×
1480
        }
1481
    }
1482

1483
    return keep_message;
36,353✔
1484
}
18,179✔
1485

1486
Mavsdk::InterceptJsonHandle
1487
MavsdkImpl::subscribe_incoming_messages_json(const Mavsdk::InterceptJsonCallback& callback)
1✔
1488
{
1489
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1490
    auto handle = _json_handle_factory.create();
1✔
1491
    _incoming_json_message_subscriptions.push_back(std::make_pair(handle, callback));
1✔
1492
    return handle;
2✔
1493
}
1✔
1494

1495
void MavsdkImpl::unsubscribe_incoming_messages_json(Mavsdk::InterceptJsonHandle handle)
1✔
1496
{
1497
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1498
    auto it = std::find_if(
1✔
1499
        _incoming_json_message_subscriptions.begin(),
1500
        _incoming_json_message_subscriptions.end(),
1501
        [handle](const auto& subscription) { return subscription.first == handle; });
1✔
1502
    if (it != _incoming_json_message_subscriptions.end()) {
1✔
1503
        _incoming_json_message_subscriptions.erase(it);
1✔
1504
    }
1505
}
1✔
1506

1507
Mavsdk::InterceptJsonHandle
1508
MavsdkImpl::subscribe_outgoing_messages_json(const Mavsdk::InterceptJsonCallback& callback)
1✔
1509
{
1510
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1511
    auto handle = _json_handle_factory.create();
1✔
1512
    _outgoing_json_message_subscriptions.push_back(std::make_pair(handle, callback));
1✔
1513
    return handle;
2✔
1514
}
1✔
1515

1516
void MavsdkImpl::unsubscribe_outgoing_messages_json(Mavsdk::InterceptJsonHandle handle)
1✔
1517
{
1518
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1519
    auto it = std::find_if(
1✔
1520
        _outgoing_json_message_subscriptions.begin(),
1521
        _outgoing_json_message_subscriptions.end(),
1522
        [handle](const auto& subscription) { return subscription.first == handle; });
1✔
1523
    if (it != _outgoing_json_message_subscriptions.end()) {
1✔
1524
        _outgoing_json_message_subscriptions.erase(it);
1✔
1525
    }
1526
}
1✔
1527

1528
RawConnection* MavsdkImpl::find_raw_connection()
4✔
1529
{
1530
    std::lock_guard lock(_mutex);
4✔
1531

1532
    for (auto& entry : _connections) {
4✔
1533
        auto* raw_conn = dynamic_cast<RawConnection*>(entry.connection.get());
2✔
1534
        if (raw_conn != nullptr) {
2✔
1535
            return raw_conn;
2✔
1536
        }
1537
    }
1538
    return nullptr;
2✔
1539
}
4✔
1540

1541
void MavsdkImpl::pass_received_raw_bytes(const char* bytes, size_t length)
1✔
1542
{
1543
    auto* raw_conn = find_raw_connection();
1✔
1544
    if (raw_conn == nullptr) {
1✔
1545
        LogErr()
×
1546
            << "No raw connection available. Please add one using add_any_connection(\"raw://\")";
×
1547
        return;
×
1548
    }
1549

1550
    raw_conn->receive(bytes, length);
1✔
1551
}
1552

1553
Mavsdk::RawBytesHandle
1554
MavsdkImpl::subscribe_raw_bytes_to_be_sent(const Mavsdk::RawBytesCallback& callback)
1✔
1555
{
1556
    if (find_raw_connection() == nullptr) {
1✔
1557
        LogWarn() << "No raw connection available. Subscription will only receive bytes after you "
×
1558
                     "add a connection using add_any_connection(\"raw://\")";
×
1559
    }
1560
    return _raw_bytes_subscriptions.subscribe(callback);
1✔
1561
}
1562

1563
void MavsdkImpl::unsubscribe_raw_bytes_to_be_sent(Mavsdk::RawBytesHandle handle)
1✔
1564
{
1565
    _raw_bytes_subscriptions.unsubscribe(handle);
1✔
1566
}
1✔
1567

1568
bool MavsdkImpl::notify_raw_bytes_sent(const char* bytes, size_t length)
4✔
1569
{
1570
    if (_raw_bytes_subscriptions.empty()) {
4✔
1571
        return false;
3✔
1572
    }
1573

1574
    _raw_bytes_subscriptions(bytes, length);
1✔
1575

1576
    return true;
1✔
1577
}
1578

1579
Mavsdk::ConnectionErrorHandle
1580
MavsdkImpl::subscribe_connection_errors(Mavsdk::ConnectionErrorCallback callback)
×
1581
{
1582
    std::lock_guard lock(_mutex);
×
1583

1584
    const auto handle = _connections_errors_subscriptions.subscribe(callback);
×
1585

1586
    return handle;
×
1587
}
×
1588

1589
void MavsdkImpl::unsubscribe_connection_errors(Mavsdk::ConnectionErrorHandle handle)
×
1590
{
1591
    std::lock_guard lock(_mutex);
×
1592
    _connections_errors_subscriptions.unsubscribe(handle);
×
1593
}
×
1594

1595
uint8_t MavsdkImpl::get_target_system_id(const mavlink_message_t& message)
9,096✔
1596
{
1597
    // Checks whether connection knows target system ID by extracting target system if set.
1598
    const mavlink_msg_entry_t* meta = mavlink_get_msg_entry(message.msgid);
9,096✔
1599

1600
    if (meta == nullptr || !(meta->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_SYSTEM)) {
9,098✔
1601
        return 0;
803✔
1602
    }
1603

1604
    // Don't look at the target system offset if it is outside the payload length.
1605
    // This can happen if the fields are trimmed.
1606
    if (meta->target_system_ofs >= message.len) {
8,295✔
1607
        return 0;
40✔
1608
    }
1609

1610
    return (_MAV_PAYLOAD(&message))[meta->target_system_ofs];
8,255✔
1611
}
1612

1613
uint8_t MavsdkImpl::get_target_component_id(const mavlink_message_t& message)
49✔
1614
{
1615
    // Checks whether connection knows target system ID by extracting target system if set.
1616
    const mavlink_msg_entry_t* meta = mavlink_get_msg_entry(message.msgid);
49✔
1617

1618
    if (meta == nullptr || !(meta->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_COMPONENT)) {
49✔
1619
        return 0;
34✔
1620
    }
1621

1622
    // Don't look at the target component offset if it is outside the payload length.
1623
    // This can happen if the fields are trimmed.
1624
    if (meta->target_component_ofs >= message.len) {
15✔
1625
        return 0;
1✔
1626
    }
1627

1628
    return (_MAV_PAYLOAD(&message))[meta->target_component_ofs];
14✔
1629
}
1630

1631
Sender& MavsdkImpl::sender()
×
1632
{
1633
    std::lock_guard lock(_server_components_mutex);
×
1634
    return default_server_component_with_lock().sender();
×
1635
}
×
1636

1637
std::vector<Connection*> MavsdkImpl::get_connections() const
9,136✔
1638
{
1639
    std::lock_guard lock(_mutex);
9,136✔
1640
    std::vector<Connection*> connections;
9,140✔
1641
    for (const auto& connection_entry : _connections) {
18,179✔
1642
        connections.push_back(connection_entry.connection.get());
9,047✔
1643
    }
1644
    return connections;
9,134✔
1645
}
9,134✔
1646

1647
mav::MessageSet& MavsdkImpl::get_message_set() const
31✔
1648
{
1649
    // Note: This returns a reference to MessageSet without locking.
1650
    // Thread safety for MessageSet operations must be ensured by:
1651
    // 1. Using load_custom_xml_to_message_set() for write operations (XML loading)
1652
    // 2. libmav MessageSet should be internally thread-safe for read operations
1653
    // 3. If race conditions persist, consider implementing a thread-safe MessageSet wrapper
1654
    return *_message_set;
31✔
1655
}
1656

1657
bool MavsdkImpl::load_custom_xml_to_message_set(const std::string& xml_content)
6✔
1658
{
1659
    std::lock_guard<std::mutex> lock(_message_set_mutex);
6✔
1660
    auto result = _message_set->addFromXMLString(xml_content, false /* recursive_open_includes */);
6✔
1661
    return result == ::mav::MessageSetResult::Success;
12✔
1662
}
6✔
1663

1664
// Thread-safe MessageSet read operations
1665
std::optional<std::string> MavsdkImpl::message_id_to_name_safe(uint32_t id) const
×
1666
{
1667
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1668
    auto message_def = _message_set->getMessageDefinition(static_cast<int>(id));
×
1669
    if (message_def) {
×
1670
        return message_def.get().name();
×
1671
    }
1672
    return std::nullopt;
×
1673
}
×
1674

1675
std::optional<int> MavsdkImpl::message_name_to_id_safe(const std::string& name) const
×
1676
{
1677
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1678
    return _message_set->idForMessage(name);
×
1679
}
×
1680

1681
std::optional<mav::Message> MavsdkImpl::create_message_safe(const std::string& message_name) const
×
1682
{
1683
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1684
    return _message_set->create(message_name);
×
1685
}
×
1686

1687
// Thread-safe parsing for LibmavReceiver
1688
std::optional<mav::Message> MavsdkImpl::parse_message_safe(
18,196✔
1689
    const uint8_t* buffer, size_t buffer_len, size_t& bytes_consumed) const
1690
{
1691
    std::lock_guard<std::mutex> lock(_message_set_mutex);
18,196✔
1692
    return _buffer_parser->parseMessage(buffer, buffer_len, bytes_consumed);
18,211✔
1693
}
18,190✔
1694

1695
mav::OptionalReference<const mav::MessageDefinition>
1696
MavsdkImpl::get_message_definition_safe(int message_id) const
18,101✔
1697
{
1698
    std::lock_guard<std::mutex> lock(_message_set_mutex);
18,101✔
1699
    return _message_set->getMessageDefinition(message_id);
18,108✔
1700
}
18,103✔
1701

1702
} // namespace mavsdk
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc