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

mavlink / MAVSDK / 20604212380

30 Dec 2025 07:24PM UTC coverage: 48.017% (-0.06%) from 48.078%
20604212380

Pull #2746

github

web-flow
Merge 5269e5f1e into 5d2947b34
Pull Request #2746: Configuration and component cleanup

47 of 158 new or added lines in 19 files covered. (29.75%)

21 existing lines in 9 files now uncovered.

17769 of 37006 relevant lines covered (48.02%)

483.39 hits per line

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

70.39
/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
namespace mavsdk {
26

27
template class CallbackList<>;
28

29
MavsdkImpl::MavsdkImpl(const Mavsdk::Configuration& configuration) :
138✔
30
    timeout_handler(time),
138✔
31
    call_every_handler(time)
276✔
32
{
33
    LogInfo() << "MAVSDK version: " << mavsdk_version;
138✔
34

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

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

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

57
    set_configuration(configuration);
138✔
58

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

67
    // Initialize BufferParser for thread-safe parsing
68
    _buffer_parser = std::make_unique<mav::BufferParser>(*_message_set);
138✔
69

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

73
    _process_user_callbacks_thread =
276✔
74
        new std::thread(&MavsdkImpl::process_user_callbacks_thread, this);
138✔
75

76
    _work_thread = new std::thread(&MavsdkImpl::work_thread, this);
138✔
77
}
138✔
78

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

86
    _should_exit = true;
138✔
87

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

91
    if (_work_thread != nullptr) {
138✔
92
        _work_thread->join();
138✔
93
        delete _work_thread;
138✔
94
        _work_thread = nullptr;
138✔
95
    }
96

97
    if (_process_user_callbacks_thread != nullptr) {
138✔
98
        _user_callback_queue.stop();
138✔
99
        _process_user_callbacks_thread->join();
138✔
100
        delete _process_user_callbacks_thread;
138✔
101
        _process_user_callbacks_thread = nullptr;
138✔
102
    }
103

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

106
    _systems.clear();
138✔
107
    _connections.clear();
138✔
108
}
138✔
109

110
std::string MavsdkImpl::version()
1✔
111
{
112
    static unsigned version_counter = 0;
113

114
    ++version_counter;
1✔
115

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

138
std::vector<std::shared_ptr<System>> MavsdkImpl::systems() const
177✔
139
{
140
    std::vector<std::shared_ptr<System>> systems_result{};
177✔
141

142
    std::lock_guard lock(_mutex);
177✔
143
    for (auto& system : _systems) {
280✔
144
        // We ignore the 0 entry because it's just a null system.
145
        // It's only created because the older, deprecated API needs a
146
        // reference.
147
        if (system.first == 0) {
103✔
148
            continue;
×
149
        }
150
        systems_result.push_back(system.second);
103✔
151
    }
152

153
    return systems_result;
177✔
154
}
177✔
155

156
std::optional<std::shared_ptr<System>> MavsdkImpl::first_autopilot(double timeout_s)
57✔
157
{
158
    {
159
        std::lock_guard lock(_mutex);
57✔
160
        for (auto system : _systems) {
58✔
161
            if (system.second->is_connected() && system.second->has_autopilot()) {
4✔
162
                return system.second;
3✔
163
            }
164
        }
4✔
165
    }
57✔
166

167
    if (timeout_s == 0.0) {
54✔
168
        // Don't wait at all.
169
        return {};
×
170
    }
171

172
    auto prom = std::make_shared<std::promise<std::shared_ptr<System>>>();
54✔
173
    auto fut = prom->get_future();
54✔
174

175
    auto flag = std::make_shared<std::once_flag>();
54✔
176
    auto handle = subscribe_on_new_system([this, prom, flag]() {
54✔
177
        // Check all systems, not just the first one
178
        auto all_systems = systems();
55✔
179
        for (auto& system : all_systems) {
57✔
180
            if (system->is_connected() && system->has_autopilot()) {
56✔
181
                std::call_once(*flag, [prom, system]() { prom->set_value(system); });
108✔
182
                break;
54✔
183
            }
184
        }
185
    });
55✔
186

187
    if (timeout_s > 0.0) {
54✔
188
        if (fut.wait_for(std::chrono::milliseconds(int64_t(timeout_s * 1e3))) ==
54✔
189
            std::future_status::ready) {
190
            unsubscribe_on_new_system(handle);
54✔
191
            return fut.get();
54✔
192

193
        } else {
194
            unsubscribe_on_new_system(handle);
×
195
            return std::nullopt;
×
196
        }
197
    } else {
198
        fut.wait();
×
199
        unsubscribe_on_new_system(handle);
×
200
        return std::optional(fut.get());
×
201
    }
202
}
54✔
203

204
std::shared_ptr<ServerComponent> MavsdkImpl::server_component(unsigned instance)
54✔
205
{
206
    std::lock_guard lock(_mutex);
54✔
207

208
    auto component_type = _configuration.get_component_type();
54✔
209
    switch (component_type) {
54✔
210
        case ComponentType::Autopilot:
54✔
211
        case ComponentType::GroundStation:
212
        case ComponentType::CompanionComputer:
213
        case ComponentType::Camera:
214
        case ComponentType::Gimbal:
215
        case ComponentType::RemoteId:
216
        case ComponentType::Custom:
217
            return server_component_by_type(component_type, instance);
54✔
218
        default:
×
219
            LogErr() << "Unknown component type";
×
220
            return {};
×
221
    }
222
}
54✔
223

224
std::shared_ptr<ServerComponent>
225
MavsdkImpl::server_component_by_type(ComponentType server_component_type, unsigned instance)
54✔
226
{
227
    const auto mav_type = Mavsdk::Configuration::mav_type_for_component_type(server_component_type);
54✔
228

229
    switch (server_component_type) {
54✔
230
        case ComponentType::Autopilot:
40✔
231
            if (instance == 0) {
40✔
232
                return server_component_by_id(MAV_COMP_ID_AUTOPILOT1, mav_type);
40✔
233
            } else {
234
                LogErr() << "Only autopilot instance 0 is valid";
×
235
                return {};
×
236
            }
237

238
        case ComponentType::GroundStation:
×
239
            if (instance == 0) {
×
NEW
240
                return server_component_by_id(MAV_COMP_ID_MISSIONPLANNER, mav_type);
×
241
            } else {
242
                LogErr() << "Only one ground station supported at this time";
×
243
                return {};
×
244
            }
245

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

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

NEW
278
        case ComponentType::Gimbal:
×
NEW
279
            if (instance == 0) {
×
NEW
280
                return server_component_by_id(MAV_COMP_ID_GIMBAL, mav_type);
×
281
            } else {
NEW
282
                LogErr() << "Only gimbal instance 0 is valid";
×
NEW
283
                return {};
×
284
            }
285

NEW
286
        case ComponentType::RemoteId:
×
NEW
287
            if (instance == 0) {
×
NEW
288
                return server_component_by_id(MAV_COMP_ID_ODID_TXRX_1, mav_type);
×
289
            } else {
NEW
290
                LogErr() << "Only remote ID instance 0 is valid";
×
NEW
291
                return {};
×
292
            }
293

NEW
294
        case ComponentType::Custom:
×
NEW
295
            LogErr() << "Custom component type requires explicit component ID";
×
NEW
296
            return {};
×
297

298
        default:
×
299
            LogErr() << "Unknown server component type";
×
300
            return {};
×
301
    }
302
}
303

304
std::shared_ptr<ServerComponent>
305
MavsdkImpl::server_component_by_id(uint8_t component_id, uint8_t mav_type)
54✔
306
{
307
    if (component_id == 0) {
54✔
308
        LogErr() << "Server component with component ID 0 not allowed";
×
309
        return nullptr;
×
310
    }
311

312
    std::lock_guard lock(_server_components_mutex);
54✔
313

314
    return server_component_by_id_with_lock(component_id, mav_type);
54✔
315
}
54✔
316

317
std::shared_ptr<ServerComponent>
318
MavsdkImpl::server_component_by_id_with_lock(uint8_t component_id, uint8_t mav_type)
261✔
319
{
320
    for (auto& it : _server_components) {
262✔
321
        if (it.first == component_id) {
123✔
322
            if (it.second != nullptr) {
122✔
323
                return it.second;
122✔
324
            } else {
NEW
325
                it.second = std::make_shared<ServerComponent>(*this, component_id, mav_type);
×
326
            }
327
        }
328
    }
329

330
    _server_components.emplace_back(std::pair<uint8_t, std::shared_ptr<ServerComponent>>(
139✔
331
        component_id, std::make_shared<ServerComponent>(*this, component_id, mav_type)));
278✔
332

333
    return _server_components.back().second;
139✔
334
}
335

336
void MavsdkImpl::forward_message(mavlink_message_t& message, Connection* connection)
51✔
337
{
338
    // Forward_message Function implementing Mavlink routing rules.
339
    // See https://mavlink.io/en/guide/routing.html
340

341
    bool forward_heartbeats_enabled = true;
51✔
342
    const uint8_t target_system_id = get_target_system_id(message);
51✔
343
    const uint8_t target_component_id = get_target_component_id(message);
51✔
344

345
    // If it's a message only for us, we keep it, otherwise, we forward it.
346
    const bool targeted_only_at_us =
347
        (target_system_id == get_own_system_id() && target_component_id == get_own_component_id());
51✔
348

349
    // We don't forward heartbeats unless it's specifically enabled.
350
    const bool heartbeat_check_ok =
51✔
351
        (message.msgid != MAVLINK_MSG_ID_HEARTBEAT || forward_heartbeats_enabled);
51✔
352

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

379
void MavsdkImpl::receive_message(
2,587✔
380
    MavlinkReceiver::ParseResult result, mavlink_message_t& message, Connection* connection)
381
{
382
    if (result == MavlinkReceiver::ParseResult::MessageParsed) {
2,587✔
383
        // Valid message: queue for full processing (which includes forwarding)
384
        {
385
            std::lock_guard lock(_received_messages_mutex);
2,586✔
386
            _received_messages.emplace(ReceivedMessage{std::move(message), connection});
2,588✔
387
        }
2,586✔
388
        _received_messages_cv.notify_one();
2,585✔
389

390
    } else if (result == MavlinkReceiver::ParseResult::BadCrc) {
1✔
391
        // Unknown message: forward only, don't process locally
392
        forward_message(message, connection);
4✔
393
    }
394
}
2,591✔
395

396
void MavsdkImpl::receive_libmav_message(
2,590✔
397
    const Mavsdk::MavlinkMessage& message, Connection* connection)
398
{
399
    {
400
        std::lock_guard lock(_received_libmav_messages_mutex);
2,590✔
401
        _received_libmav_messages.emplace(ReceivedLibmavMessage{message, connection});
2,591✔
402
    }
2,593✔
403
    _received_libmav_messages_cv.notify_one();
2,591✔
404
}
2,593✔
405

406
void MavsdkImpl::process_messages()
36,124✔
407
{
408
    std::lock_guard lock(_received_messages_mutex);
36,124✔
409
    while (!_received_messages.empty()) {
38,374✔
410
        auto message_copied = _received_messages.front();
2,574✔
411
        process_message(message_copied.message, message_copied.connection_ptr);
2,574✔
412
        _received_messages.pop();
2,575✔
413
    }
414
}
35,132✔
415

416
void MavsdkImpl::process_libmav_messages()
33,736✔
417
{
418
    std::lock_guard lock(_received_libmav_messages_mutex);
33,736✔
419
    while (!_received_libmav_messages.empty()) {
37,435✔
420
        auto message_copied = _received_libmav_messages.front();
2,575✔
421
        process_libmav_message(message_copied.message, message_copied.connection_ptr);
2,573✔
422
        _received_libmav_messages.pop();
2,573✔
423
    }
2,575✔
424
}
34,959✔
425

426
void MavsdkImpl::process_message(mavlink_message_t& message, Connection* connection)
2,575✔
427
{
428
    // Assumes _received_messages_mutex
429

430
    if (_message_logging_on) {
2,575✔
431
        LogDebug() << "Processing message " << message.msgid << " from "
×
432
                   << static_cast<int>(message.sysid) << "/" << static_cast<int>(message.compid);
×
433
    }
434

435
    if (_should_exit) {
2,575✔
436
        // If we're meant to clean up, let's not try to acquire any more locks but bail.
437
        return;
×
438
    }
439

440
    {
441
        std::lock_guard lock(_mutex);
2,573✔
442

443
        /** @note: Forward message FIRST (before intercept) if option is enabled and multiple
444
         * interfaces are connected. This ensures that forwarded messages are not affected by
445
         * intercept modifications. Performs message forwarding checks for every messages if message
446
         * forwarding is enabled on at least one connection, and in case of a single forwarding
447
         * connection, we check that it is not the one which received the current message.
448
         *
449
         * Conditions:
450
         * 1. At least 2 connections.
451
         * 2. At least 1 forwarding connection.
452
         * 3. At least 2 forwarding connections or current connection is not forwarding.
453
         */
454

455
        if (_connections.size() > 1 && mavsdk::Connection::forwarding_connections_count() > 0 &&
2,623✔
456
            (mavsdk::Connection::forwarding_connections_count() > 1 ||
47✔
UNCOV
457
             !connection->should_forward_messages())) {
×
458
            if (_message_logging_on) {
47✔
459
                LogDebug() << "Forwarding message " << message.msgid << " from "
×
460
                           << static_cast<int>(message.sysid) << "/"
×
461
                           << static_cast<int>(message.compid);
×
462
            }
463
            forward_message(message, connection);
47✔
464
        }
465

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

471
        // This is a low level interface where incoming messages can be tampered
472
        // with or even dropped FOR LOCAL PROCESSING ONLY (after forwarding).
473
        {
474
            bool keep = true;
2,572✔
475
            {
476
                std::lock_guard<std::mutex> intercept_lock(_intercept_callbacks_mutex);
2,572✔
477
                if (_intercept_incoming_messages_callback != nullptr) {
2,574✔
478
                    keep = _intercept_incoming_messages_callback(message);
247✔
479
                }
480
            }
2,573✔
481

482
            if (!keep) {
2,574✔
483
                LogDebug() << "Dropped incoming message: " << int(message.msgid);
36✔
484
                return;
35✔
485
            }
486
        }
487

488
        // Don't ever create a system with sysid 0.
489
        if (message.sysid == 0) {
2,538✔
490
            if (_message_logging_on) {
×
491
                LogDebug() << "Ignoring message with sysid == 0";
×
492
            }
493
            return;
×
494
        }
495

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

505
        if (_configuration.get_component_type() == ComponentType::GroundStation &&
2,538✔
506
            message.sysid == 255 && message.compid == MAV_COMP_ID_MISSIONPLANNER) {
2,538✔
507
            if (_message_logging_on) {
×
508
                LogDebug() << "Ignoring messages from QGC as we are also a ground station";
×
509
            }
510
            return;
×
511
        }
512

513
        bool found_system = false;
2,538✔
514
        for (auto& system : _systems) {
2,570✔
515
            if (system.first == message.sysid) {
2,434✔
516
                system.second->system_impl()->add_new_component(message.compid);
2,402✔
517
                found_system = true;
2,403✔
518
                break;
2,403✔
519
            }
520
        }
521

522
        if (!found_system) {
2,539✔
523
            if (_system_debugging) {
136✔
524
                LogWarn() << "Create new system/component " << (int)message.sysid << "/"
×
525
                          << (int)message.compid;
×
526
                LogWarn() << "From message " << (int)message.msgid << " with len "
×
527
                          << (int)message.len;
×
528
                std::string bytes = "";
×
529
                for (unsigned i = 0; i < 12 + message.len; ++i) {
×
530
                    bytes += std::to_string(reinterpret_cast<uint8_t*>(&message)[i]) + ' ';
×
531
                }
532
                LogWarn() << "Bytes: " << bytes;
×
533
            }
×
534
            make_system_with_component(message.sysid, message.compid);
136✔
535

536
            // We now better talk back.
537
            start_sending_heartbeats();
136✔
538
        }
539

540
        if (_should_exit) {
2,539✔
541
            // Don't try to call at() if systems have already been destroyed
542
            // in destructor.
543
            return;
×
544
        }
545
    }
2,570✔
546

547
    mavlink_message_handler.process_message(message);
2,537✔
548

549
    for (auto& system : _systems) {
2,574✔
550
        if (system.first == message.sysid) {
2,574✔
551
            system.second->system_impl()->process_mavlink_message(message);
2,538✔
552
            break;
2,540✔
553
        }
554
    }
555
}
556

557
void MavsdkImpl::process_libmav_message(
2,575✔
558
    const Mavsdk::MavlinkMessage& message, Connection* /* connection */)
559
{
560
    // Assumes _received_libmav_messages_mutex
561

562
    if (_message_logging_on) {
2,575✔
563
        LogDebug() << "MavsdkImpl::process_libmav_message: " << message.message_name << " from "
×
564
                   << static_cast<int>(message.system_id) << "/"
×
565
                   << static_cast<int>(message.component_id);
×
566
    }
567

568
    // JSON message interception for incoming messages
569
    if (!call_json_interception_callbacks(message, _incoming_json_message_subscriptions)) {
2,575✔
570
        // Message was dropped by interception callback
571
        if (_message_logging_on) {
×
572
            LogDebug() << "Incoming JSON message " << message.message_name
×
573
                       << " dropped by interception";
×
574
        }
575
        return;
×
576
    }
577

578
    if (_message_logging_on) {
2,574✔
579
        LogDebug() << "Processing libmav message " << message.message_name << " from "
×
580
                   << static_cast<int>(message.system_id) << "/"
×
581
                   << static_cast<int>(message.component_id);
×
582
    }
583

584
    if (_should_exit) {
2,574✔
585
        // If we're meant to clean up, let's not try to acquire any more locks but bail.
586
        return;
×
587
    }
588

589
    {
590
        std::lock_guard lock(_mutex);
2,573✔
591

592
        // Don't ever create a system with sysid 0.
593
        if (message.system_id == 0) {
2,576✔
594
            if (_message_logging_on) {
×
595
                LogDebug() << "Ignoring libmav message with sysid == 0";
×
596
            }
597
            return;
×
598
        }
599

600
        // Filter out QGroundControl messages similar to regular mavlink processing
601
        if (_configuration.get_component_type() == ComponentType::GroundStation &&
2,576✔
602
            message.system_id == 255 && message.component_id == MAV_COMP_ID_MISSIONPLANNER) {
2,571✔
603
            if (_message_logging_on) {
×
604
                LogDebug() << "Ignoring libmav messages from QGC as we are also a ground station";
×
605
            }
606
            return;
×
607
        }
608

609
        bool found_system = false;
2,571✔
610
        for (auto& system : _systems) {
2,605✔
611
            if (system.first == message.system_id) {
2,604✔
612
                system.second->system_impl()->add_new_component(message.component_id);
2,570✔
613
                found_system = true;
2,571✔
614
                break;
2,571✔
615
            }
616
        }
617

618
        if (!found_system) {
2,577✔
619
            if (_system_debugging) {
6✔
620
                LogWarn() << "Create new system/component from libmav " << (int)message.system_id
×
621
                          << "/" << (int)message.component_id;
×
622
            }
623
            make_system_with_component(message.system_id, message.component_id);
6✔
624

625
            // We now better talk back.
626
            start_sending_heartbeats();
6✔
627
        }
628

629
        if (_should_exit) {
2,577✔
630
            // Don't try to call at() if systems have already been destroyed
631
            // in destructor.
632
            return;
×
633
        }
634
    }
2,575✔
635

636
    // Distribute libmav message to systems for libmav-specific handling
637
    bool found_system = false;
2,576✔
638
    for (auto& system : _systems) {
5,219✔
639
        if (system.first == message.system_id) {
2,644✔
640
            if (_message_logging_on) {
2,575✔
641
                LogDebug() << "Distributing libmav message " << message.message_name
×
642
                           << " to SystemImpl for system " << system.first;
×
643
            }
644
            system.second->system_impl()->process_libmav_message(message);
2,575✔
645
            found_system = true;
2,574✔
646
            // Don't break - distribute to all matching system instances
647
        }
648
    }
649

650
    if (!found_system) {
2,570✔
651
        LogWarn() << "No system found for libmav message " << message.message_name
×
652
                  << " from system " << message.system_id;
×
653
    }
654
}
655

656
bool MavsdkImpl::send_message(mavlink_message_t& message)
2,721✔
657
{
658
    // Create a copy of the message to avoid reference issues
659
    mavlink_message_t message_copy = message;
2,721✔
660

661
    {
662
        std::lock_guard lock(_messages_to_send_mutex);
2,721✔
663
        _messages_to_send.push(std::move(message_copy));
2,719✔
664
    }
2,718✔
665

666
    // For heartbeat messages, we want to process them immediately to speed up system discovery
667
    if (message.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
2,713✔
668
        // Trigger message processing in the work thread
669
        // This is a hint to process messages sooner, but doesn't block
670
        std::this_thread::yield();
490✔
671
    }
672

673
    return true;
2,718✔
674
}
675

676
void MavsdkImpl::deliver_messages()
38,647✔
677
{
678
    // Process messages one at a time to avoid holding the mutex while delivering
679
    while (true) {
680
        mavlink_message_t message;
681
        {
682
            std::lock_guard lock(_messages_to_send_mutex);
38,647✔
683
            if (_messages_to_send.empty()) {
37,907✔
684
                break;
35,277✔
685
            }
686
            message = _messages_to_send.front();
2,720✔
687
            _messages_to_send.pop();
2,717✔
688
        }
37,996✔
689
        deliver_message(message);
2,718✔
690
    }
2,723✔
691
}
35,069✔
692

693
void MavsdkImpl::deliver_message(mavlink_message_t& message)
2,719✔
694
{
695
    if (_message_logging_on) {
2,719✔
696
        LogDebug() << "Sending message " << message.msgid << " from "
×
697
                   << static_cast<int>(message.sysid) << "/" << static_cast<int>(message.compid)
×
698
                   << " to " << static_cast<int>(get_target_system_id(message)) << "/"
×
699
                   << static_cast<int>(get_target_component_id(message));
×
700
    }
701

702
    // This is a low level interface where outgoing messages can be tampered
703
    // with or even dropped.
704
    bool keep = true;
2,719✔
705
    {
706
        std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
2,719✔
707
        if (_intercept_outgoing_messages_callback != nullptr) {
2,715✔
708
            keep = _intercept_outgoing_messages_callback(message);
228✔
709
        }
710
    }
2,716✔
711

712
    if (!keep) {
2,719✔
713
        // We fake that everything was sent as instructed because
714
        // a potential loss would happen later, and we would not be informed
715
        // about it.
716
        LogDebug() << "Dropped outgoing message: " << int(message.msgid);
86✔
717
        return;
169✔
718
    }
719

720
    // JSON message interception for outgoing messages
721
    // Convert mavlink_message_t to Mavsdk::MavlinkMessage for JSON interception
722
    uint8_t buffer[MAVLINK_MAX_PACKET_LEN];
723
    uint16_t len = mavlink_msg_to_send_buffer(buffer, &message);
2,633✔
724

725
    size_t bytes_consumed = 0;
2,632✔
726
    auto libmav_msg_opt = parse_message_safe(buffer, len, bytes_consumed);
2,632✔
727

728
    if (libmav_msg_opt) {
2,630✔
729
        // Create Mavsdk::MavlinkMessage directly for JSON interception
730
        Mavsdk::MavlinkMessage json_message;
2,633✔
731
        json_message.message_name = libmav_msg_opt.value().name();
2,631✔
732
        json_message.system_id = message.sysid;
2,632✔
733
        json_message.component_id = message.compid;
2,632✔
734

735
        // Extract target_system and target_component if present
736
        uint8_t target_system_id = 0;
2,632✔
737
        uint8_t target_component_id = 0;
2,632✔
738
        if (libmav_msg_opt.value().get("target_system", target_system_id) ==
2,632✔
739
            mav::MessageResult::Success) {
740
            json_message.target_system_id = target_system_id;
1,885✔
741
        } else {
742
            json_message.target_system_id = 0;
747✔
743
        }
744
        if (libmav_msg_opt.value().get("target_component", target_component_id) ==
2,632✔
745
            mav::MessageResult::Success) {
746
            json_message.target_component_id = target_component_id;
1,884✔
747
        } else {
748
            json_message.target_component_id = 0;
746✔
749
        }
750

751
        // Generate JSON using LibmavReceiver's public method
752
        auto connections = get_connections();
2,630✔
753
        if (!connections.empty() && connections[0]->get_libmav_receiver()) {
2,625✔
754
            json_message.fields_json =
755
                connections[0]->get_libmav_receiver()->libmav_message_to_json(
7,646✔
756
                    libmav_msg_opt.value());
5,102✔
757
        } else {
758
            // Fallback: create minimal JSON if no receiver available
759
            json_message.fields_json =
760
                "{\"message_id\":" + std::to_string(libmav_msg_opt.value().id()) +
166✔
761
                ",\"message_name\":\"" + libmav_msg_opt.value().name() + "\"}";
167✔
762
        }
763

764
        if (!call_json_interception_callbacks(json_message, _outgoing_json_message_subscriptions)) {
2,633✔
765
            // Message was dropped by JSON interception callback
766
            if (_message_logging_on) {
4✔
767
                LogDebug() << "Outgoing JSON message " << json_message.message_name
×
768
                           << " dropped by interception";
×
769
            }
770
            return;
×
771
        }
772
    }
2,631✔
773

774
    std::lock_guard lock(_mutex);
2,632✔
775

776
    if (_connections.empty()) {
2,627✔
777
        // We obviously can't send any messages without a connection added, so
778
        // we silently ignore this.
779
        return;
83✔
780
    }
781

782
    uint8_t successful_emissions = 0;
2,541✔
783
    for (auto& _connection : _connections) {
5,113✔
784
        const uint8_t target_system_id = get_target_system_id(message);
2,570✔
785

786
        if (target_system_id != 0 && !(*_connection.connection).has_system_id(target_system_id)) {
2,565✔
787
            continue;
4✔
788
        }
789
        const auto result = (*_connection.connection).send_message(message);
2,557✔
790
        if (result.first) {
2,569✔
791
            successful_emissions++;
2,563✔
792
        } else {
793
            _connections_errors_subscriptions.queue(
12✔
794
                Mavsdk::ConnectionError{result.second, _connection.handle},
6✔
795
                [this](const auto& func) { call_user_callback(func); });
×
796
        }
797
    }
2,569✔
798

799
    if (successful_emissions == 0) {
2,551✔
800
        LogErr() << "Sending message failed";
3✔
801
    }
802
}
2,634✔
803

804
std::pair<ConnectionResult, Mavsdk::ConnectionHandle> MavsdkImpl::add_any_connection(
144✔
805
    const std::string& connection_url, ForwardingOption forwarding_option)
806
{
807
    CliArg cli_arg;
144✔
808
    if (!cli_arg.parse(connection_url)) {
144✔
809
        return {ConnectionResult::ConnectionUrlInvalid, Mavsdk::ConnectionHandle{}};
×
810
    }
811

812
    return std::visit(
144✔
813
        overloaded{
288✔
814
            [](std::monostate) {
×
815
                // Should not happen anyway.
816
                return std::pair<ConnectionResult, Mavsdk::ConnectionHandle>{
×
817
                    ConnectionResult::ConnectionUrlInvalid, Mavsdk::ConnectionHandle()};
×
818
            },
819
            [this, forwarding_option](const CliArg::Udp& udp) {
134✔
820
                return add_udp_connection(udp, forwarding_option);
134✔
821
            },
822
            [this, forwarding_option](const CliArg::Tcp& tcp) {
8✔
823
                return add_tcp_connection(tcp, forwarding_option);
8✔
824
            },
825
            [this, forwarding_option](const CliArg::Serial& serial) {
×
826
                return add_serial_connection(
×
827
                    serial.path, serial.baudrate, serial.flow_control_enabled, forwarding_option);
×
828
            },
829
            [this, forwarding_option](const CliArg::Raw&) {
2✔
830
                return add_raw_connection(forwarding_option);
2✔
831
            }},
832
        cli_arg.protocol);
144✔
833
}
144✔
834

835
std::pair<ConnectionResult, Mavsdk::ConnectionHandle>
836
MavsdkImpl::add_udp_connection(const CliArg::Udp& udp, ForwardingOption forwarding_option)
134✔
837
{
838
    auto new_conn = std::make_unique<UdpConnection>(
839
        [this](
2,520✔
840
            MavlinkReceiver::ParseResult result,
841
            mavlink_message_t& message,
842
            Connection* connection) { receive_message(result, message, connection); },
2,520✔
843
        [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
2,524✔
844
            receive_libmav_message(message, connection);
2,524✔
845
        },
2,522✔
846
        *this, // Pass MavsdkImpl reference for thread-safe MessageSet access
847
        udp.mode == CliArg::Udp::Mode::In ? udp.host : "0.0.0.0",
268✔
848
        udp.mode == CliArg::Udp::Mode::In ? udp.port : 0,
134✔
849
        forwarding_option);
134✔
850

851
    if (!new_conn) {
134✔
852
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
853
    }
854

855
    ConnectionResult ret = new_conn->start();
134✔
856

857
    if (ret != ConnectionResult::Success) {
134✔
858
        return {ret, Mavsdk::ConnectionHandle{}};
×
859
    }
860

861
    if (udp.mode == CliArg::Udp::Mode::Out) {
134✔
862
        // We need to add the IP rather than a hostname, otherwise we end up with two remotes:
863
        // one for the IP, and one for a hostname.
864
        auto remote_ip = resolve_hostname_to_ip(udp.host);
67✔
865

866
        if (!remote_ip) {
67✔
867
            return {ConnectionResult::DestinationIpUnknown, Mavsdk::ConnectionHandle{}};
×
868
        }
869

870
        new_conn->add_remote_to_keep(remote_ip.value(), udp.port);
67✔
871
        std::lock_guard lock(_mutex);
67✔
872

873
        // With a UDP remote, we need to initiate the connection by sending heartbeats.
874
        auto new_configuration = get_configuration();
67✔
875
        new_configuration.set_always_send_heartbeats(true);
67✔
876
        set_configuration(new_configuration);
67✔
877
    }
67✔
878

879
    auto handle = add_connection(std::move(new_conn));
134✔
880

881
    return {ConnectionResult::Success, handle};
134✔
882
}
134✔
883

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

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

960
        auto new_configuration = get_configuration();
×
961

962
        // PX4 starting with v1.13 does not send heartbeats by default, so we need
963
        // to initiate the MAVLink connection by sending heartbeats.
964
        // Therefore, we override the default here and enable sending heartbeats.
965
        new_configuration.set_always_send_heartbeats(true);
×
966
        set_configuration(new_configuration);
×
967

968
        return {ret, handle};
×
969

970
    } else {
971
        return {ret, Mavsdk::ConnectionHandle{}};
×
972
    }
973
}
×
974

975
std::pair<ConnectionResult, Mavsdk::ConnectionHandle>
976
MavsdkImpl::add_raw_connection(ForwardingOption forwarding_option)
2✔
977
{
978
    // Check if a raw connection already exists
979
    if (find_raw_connection() != nullptr) {
2✔
980
        LogErr() << "Raw connection already exists. Only one raw connection is allowed.";
×
981
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
982
    }
983

984
    auto new_conn = std::make_unique<RawConnection>(
985
        [this](
1✔
986
            MavlinkReceiver::ParseResult result,
987
            mavlink_message_t& message,
988
            Connection* connection) { receive_message(result, message, connection); },
1✔
989
        [this](const Mavsdk::MavlinkMessage& message, Connection* connection) {
1✔
990
            receive_libmav_message(message, connection);
1✔
991
        },
1✔
992
        *this,
993
        forwarding_option);
2✔
994

995
    if (!new_conn) {
2✔
996
        return {ConnectionResult::ConnectionError, Mavsdk::ConnectionHandle{}};
×
997
    }
998

999
    ConnectionResult ret = new_conn->start();
2✔
1000
    if (ret != ConnectionResult::Success) {
2✔
1001
        return {ret, Mavsdk::ConnectionHandle{}};
×
1002
    }
1003

1004
    auto handle = add_connection(std::move(new_conn));
2✔
1005

1006
    // Enable heartbeats for raw connection
1007
    auto new_configuration = get_configuration();
2✔
1008
    new_configuration.set_always_send_heartbeats(true);
2✔
1009
    set_configuration(new_configuration);
2✔
1010

1011
    return {ConnectionResult::Success, handle};
2✔
1012
}
2✔
1013

1014
Mavsdk::ConnectionHandle MavsdkImpl::add_connection(std::unique_ptr<Connection>&& new_connection)
144✔
1015
{
1016
    std::lock_guard lock(_mutex);
144✔
1017
    auto handle = _connections_handle_factory.create();
144✔
1018
    _connections.emplace_back(ConnectionEntry{std::move(new_connection), handle});
144✔
1019

1020
    return handle;
288✔
1021
}
144✔
1022

1023
void MavsdkImpl::remove_connection(Mavsdk::ConnectionHandle handle)
12✔
1024
{
1025
    std::lock_guard lock(_mutex);
12✔
1026

1027
    _connections.erase(std::remove_if(_connections.begin(), _connections.end(), [&](auto&& entry) {
12✔
1028
        return (entry.handle == handle);
12✔
1029
    }));
1030
}
12✔
1031

1032
Mavsdk::Configuration MavsdkImpl::get_configuration() const
69✔
1033
{
1034
    std::lock_guard configuration_lock(_mutex);
69✔
1035
    return _configuration;
138✔
1036
}
69✔
1037

1038
void MavsdkImpl::set_configuration(Mavsdk::Configuration new_configuration)
207✔
1039
{
1040
    std::lock_guard server_components_lock(_server_components_mutex);
207✔
1041
    // We just point the default to the newly created component. This means
1042
    // that the previous default component will be deleted if it is not
1043
    // used/referenced anywhere.
1044
    _default_server_component = server_component_by_id_with_lock(
414✔
1045
        new_configuration.get_component_id(), new_configuration.get_mav_type());
414✔
1046

1047
    if (new_configuration.get_always_send_heartbeats() &&
345✔
1048
        !_configuration.get_always_send_heartbeats()) {
138✔
1049
        start_sending_heartbeats();
73✔
1050
    } else if (
134✔
1051
        !new_configuration.get_always_send_heartbeats() &&
203✔
1052
        _configuration.get_always_send_heartbeats() && !is_any_system_connected()) {
203✔
1053
        stop_sending_heartbeats();
×
1054
    }
1055

1056
    _configuration = new_configuration;
207✔
1057
    // We cache these values as atomic to avoid having to lock any mutex for them.
1058
    _our_system_id = new_configuration.get_system_id();
207✔
1059
    _our_component_id = new_configuration.get_component_id();
207✔
1060
}
207✔
1061

1062
uint8_t MavsdkImpl::get_own_system_id() const
6,067✔
1063
{
1064
    return _our_system_id;
6,067✔
1065
}
1066

1067
uint8_t MavsdkImpl::get_own_component_id() const
1,467✔
1068
{
1069
    return _our_component_id;
1,467✔
1070
}
1071

NEW
1072
uint8_t MavsdkImpl::get_mav_type() const
×
1073
{
NEW
1074
    return _configuration.get_mav_type();
×
1075
}
1076

NEW
1077
Autopilot MavsdkImpl::get_autopilot() const
×
1078
{
NEW
1079
    return _configuration.get_autopilot();
×
1080
}
1081

1082
uint8_t MavsdkImpl::get_mav_autopilot() const
238✔
1083
{
1084
    switch (_configuration.get_autopilot()) {
238✔
NEW
1085
        case Autopilot::Px4:
×
NEW
1086
            return MAV_AUTOPILOT_PX4;
×
NEW
1087
        case Autopilot::ArduPilot:
×
NEW
1088
            return MAV_AUTOPILOT_ARDUPILOTMEGA;
×
1089
        case Autopilot::Unknown:
238✔
1090
        default:
1091
            return MAV_AUTOPILOT_GENERIC;
238✔
1092
    }
1093
}
1094

1095
CompatibilityMode MavsdkImpl::get_compatibility_mode() const
39✔
1096
{
1097
    return _configuration.get_compatibility_mode();
39✔
1098
}
1099

1100
Autopilot MavsdkImpl::effective_autopilot(Autopilot detected) const
1,474✔
1101
{
1102
    switch (_configuration.get_compatibility_mode()) {
1,474✔
1103
        case CompatibilityMode::Auto:
1,474✔
1104
            return detected;
1,474✔
NEW
1105
        case CompatibilityMode::Pure:
×
NEW
1106
            return Autopilot::Unknown; // Unknown = no quirks
×
NEW
1107
        case CompatibilityMode::Px4:
×
NEW
1108
            return Autopilot::Px4;
×
NEW
1109
        case CompatibilityMode::ArduPilot:
×
NEW
1110
            return Autopilot::ArduPilot;
×
NEW
1111
        default:
×
NEW
1112
            return detected;
×
1113
    }
1114
}
1115

1116
void MavsdkImpl::make_system_with_component(uint8_t system_id, uint8_t comp_id)
142✔
1117
{
1118
    // Needs _systems_lock
1119

1120
    if (_should_exit) {
142✔
1121
        // When the system got destroyed in the destructor, we have to give up.
1122
        return;
×
1123
    }
1124

1125
    if (static_cast<int>(system_id) == 0 && static_cast<int>(comp_id) == 0) {
142✔
1126
        LogDebug() << "Initializing connection to remote system...";
×
1127
    } else {
1128
        LogDebug() << "New system ID: " << static_cast<int>(system_id)
284✔
1129
                   << " Comp ID: " << static_cast<int>(comp_id);
142✔
1130
    }
1131

1132
    // Make a system with its first component
1133
    auto new_system = std::make_shared<System>(*this);
142✔
1134
    new_system->init(system_id, comp_id);
142✔
1135

1136
    _systems.emplace_back(system_id, new_system);
142✔
1137
}
142✔
1138

1139
void MavsdkImpl::notify_on_discover()
146✔
1140
{
1141
    // Queue the callbacks without holding the mutex to avoid deadlocks
1142
    _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
211✔
1143
}
146✔
1144

1145
void MavsdkImpl::notify_on_timeout()
5✔
1146
{
1147
    // Queue the callbacks without holding the mutex to avoid deadlocks
1148
    _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
5✔
1149
}
5✔
1150

1151
Mavsdk::NewSystemHandle
1152
MavsdkImpl::subscribe_on_new_system(const Mavsdk::NewSystemCallback& callback)
64✔
1153
{
1154
    std::lock_guard lock(_mutex);
64✔
1155

1156
    const auto handle = _new_system_callbacks.subscribe(callback);
64✔
1157

1158
    if (is_any_system_connected()) {
64✔
1159
        _new_system_callbacks.queue([this](const auto& func) { call_user_callback(func); });
×
1160
    }
1161

1162
    return handle;
128✔
1163
}
64✔
1164

1165
void MavsdkImpl::unsubscribe_on_new_system(Mavsdk::NewSystemHandle handle)
63✔
1166
{
1167
    _new_system_callbacks.unsubscribe(handle);
63✔
1168
}
63✔
1169

1170
bool MavsdkImpl::is_any_system_connected() const
64✔
1171
{
1172
    std::vector<std::shared_ptr<System>> connected_systems = systems();
64✔
1173
    return std::any_of(connected_systems.cbegin(), connected_systems.cend(), [](auto& system) {
64✔
1174
        return system->is_connected();
×
1175
    });
64✔
1176
}
64✔
1177

1178
void MavsdkImpl::work_thread()
138✔
1179
{
1180
    while (!_should_exit) {
35,870✔
1181
        // Process incoming messages
1182
        process_messages();
34,834✔
1183

1184
        // Process incoming libmav messages
1185
        process_libmav_messages();
35,433✔
1186

1187
        // Run timers
1188
        timeout_handler.run_once();
34,922✔
1189
        call_every_handler.run_once();
35,501✔
1190

1191
        // Do component work
1192
        {
1193
            std::lock_guard lock(_server_components_mutex);
35,842✔
1194
            for (auto& it : _server_components) {
71,568✔
1195
                if (it.second != nullptr) {
36,129✔
1196
                    it.second->_impl->do_work();
35,955✔
1197
                }
1198
            }
1199
        }
34,795✔
1200

1201
        // Deliver outgoing messages
1202
        deliver_messages();
35,110✔
1203

1204
        // If no messages to send, check if there are messages to receive
1205
        std::unique_lock lock_received(_received_messages_mutex);
35,122✔
1206
        if (_received_messages.empty()) {
35,275✔
1207
            // No messages to process, wait for a signal or timeout
1208
            _received_messages_cv.wait_for(lock_received, std::chrono::milliseconds(10), [this]() {
34,866✔
1209
                return !_received_messages.empty() || _should_exit;
71,088✔
1210
            });
1211
        }
1212
    }
34,431✔
1213
}
281✔
1214

1215
void MavsdkImpl::call_user_callback_located(
1,227✔
1216
    const std::string& filename, const int linenumber, const std::function<void()>& func)
1217
{
1218
    // Don't enqueue callbacks if we're shutting down
1219
    if (_should_exit) {
1,227✔
1220
        return;
×
1221
    }
1222

1223
    auto callback_size = _user_callback_queue.size();
1,227✔
1224

1225
    if (_callback_tracker) {
1,227✔
1226
        _callback_tracker->record_queued(filename, linenumber);
×
1227
        _callback_tracker->maybe_print_stats(callback_size);
×
1228
    }
1229

1230
    if (callback_size >= 100) {
1,227✔
1231
        return;
×
1232

1233
    } else if (callback_size == 99) {
1,227✔
1234
        LogErr()
×
1235
            << "User callback queue overflown\n"
1236
               "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1237
        return;
×
1238

1239
    } else if (callback_size >= 10) {
1,227✔
1240
        LogWarn()
×
1241
            << "User callback queue slow (queue size: " << callback_size
×
1242
            << ").\n"
1243
               "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1244
    }
1245

1246
    // We only need to keep track of filename and linenumber if we're actually debugging this.
1247
    UserCallback user_callback =
1248
        _callback_debugging ? UserCallback{func, filename, linenumber} : UserCallback{func};
2,454✔
1249

1250
    _user_callback_queue.push_back(std::make_shared<UserCallback>(user_callback));
1,227✔
1251
}
1,227✔
1252

1253
void MavsdkImpl::process_user_callbacks_thread()
138✔
1254
{
1255
    while (!_should_exit) {
1,503✔
1256
        UserCallback callback;
1,365✔
1257
        {
1258
            LockedQueue<UserCallback>::Guard guard(_user_callback_queue);
1,365✔
1259
            auto ptr = guard.wait_and_pop_front();
1,365✔
1260
            if (!ptr) {
1,365✔
1261
                continue;
138✔
1262
            }
1263
            // We need to get a copy instead of just a shared_ptr because the queue might
1264
            // be invalidated when the lock is released.
1265
            callback = *ptr;
1,227✔
1266
        }
1,503✔
1267

1268
        // Check if we're in the process of shutting down before executing the callback
1269
        if (_should_exit) {
1,227✔
1270
            continue;
×
1271
        }
1272

1273
        const double timeout_s = 1.0;
1,227✔
1274
        auto cookie = timeout_handler.add(
1,227✔
1275
            [&]() {
×
1276
                if (_callback_debugging) {
×
1277
                    LogWarn() << "Callback called from " << callback.filename << ":"
×
1278
                              << callback.linenumber << " took more than " << timeout_s
×
1279
                              << " second to run.";
×
1280
                    fflush(stdout);
×
1281
                    fflush(stderr);
×
1282
                    abort();
×
1283
                } else {
1284
                    LogWarn()
×
1285
                        << "Callback took more than " << timeout_s << " second to run.\n"
×
1286
                        << "See: https://mavsdk.mavlink.io/main/en/cpp/troubleshooting.html#user_callbacks";
×
1287
                }
1288
            },
×
1289
            timeout_s);
1290
        auto callback_start = std::chrono::steady_clock::now();
1,227✔
1291
        callback.func();
1,227✔
1292
        auto callback_end = std::chrono::steady_clock::now();
1,227✔
1293
        timeout_handler.remove(cookie);
1,227✔
1294

1295
        if (_callback_tracker) {
1,227✔
1296
            auto callback_duration_us =
1297
                std::chrono::duration_cast<std::chrono::microseconds>(callback_end - callback_start)
×
1298
                    .count();
×
1299
            _callback_tracker->record_executed(
×
1300
                callback.filename, callback.linenumber, callback_duration_us);
1301
        }
1302
    }
1,365✔
1303
}
138✔
1304

1305
void MavsdkImpl::start_sending_heartbeats()
361✔
1306
{
1307
    // Check if we're in the process of shutting down
1308
    if (_should_exit) {
361✔
1309
        return;
×
1310
    }
1311

1312
    // Before sending out first heartbeats we need to make sure we have a
1313
    // default server component.
1314
    default_server_component_impl();
361✔
1315

1316
    {
1317
        std::lock_guard<std::mutex> lock(_heartbeat_mutex);
361✔
1318
        call_every_handler.remove(_heartbeat_send_cookie);
361✔
1319
        _heartbeat_send_cookie =
361✔
1320
            call_every_handler.add([this]() { send_heartbeats(); }, HEARTBEAT_SEND_INTERVAL_S);
851✔
1321
    }
361✔
1322
}
1323

1324
void MavsdkImpl::stop_sending_heartbeats()
5✔
1325
{
1326
    if (!_configuration.get_always_send_heartbeats()) {
5✔
1327
        std::lock_guard<std::mutex> lock(_heartbeat_mutex);
1✔
1328
        call_every_handler.remove(_heartbeat_send_cookie);
1✔
1329
    }
1✔
1330
}
5✔
1331

1332
ServerComponentImpl& MavsdkImpl::default_server_component_impl()
1,406✔
1333
{
1334
    std::lock_guard lock(_server_components_mutex);
1,406✔
1335
    return default_server_component_with_lock();
1,406✔
1336
}
1,406✔
1337

1338
ServerComponentImpl& MavsdkImpl::default_server_component_with_lock()
1,407✔
1339
{
1340
    if (_default_server_component == nullptr) {
1,407✔
1341
        _default_server_component =
NEW
1342
            server_component_by_id_with_lock(_our_component_id, get_mav_type());
×
1343
    }
1344
    return *_default_server_component->_impl;
1,404✔
1345
}
1346

1347
void MavsdkImpl::send_heartbeats()
487✔
1348
{
1349
    std::lock_guard lock(_server_components_mutex);
487✔
1350

1351
    for (auto& it : _server_components) {
979✔
1352
        if (it.second != nullptr) {
492✔
1353
            it.second->_impl->send_heartbeat();
489✔
1354
        }
1355
    }
1356
}
485✔
1357

1358
void MavsdkImpl::intercept_incoming_messages_async(std::function<bool(mavlink_message_t&)> callback)
24✔
1359
{
1360
    std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
24✔
1361
    _intercept_incoming_messages_callback = callback;
24✔
1362
}
24✔
1363

1364
void MavsdkImpl::intercept_outgoing_messages_async(std::function<bool(mavlink_message_t&)> callback)
14✔
1365
{
1366
    std::lock_guard<std::mutex> lock(_intercept_callbacks_mutex);
14✔
1367
    _intercept_outgoing_messages_callback = callback;
14✔
1368
}
14✔
1369

1370
bool MavsdkImpl::call_json_interception_callbacks(
5,208✔
1371
    const Mavsdk::MavlinkMessage& json_message,
1372
    std::vector<std::pair<Mavsdk::InterceptJsonHandle, Mavsdk::InterceptJsonCallback>>&
1373
        callback_list)
1374
{
1375
    bool keep_message = true;
5,208✔
1376

1377
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
5,208✔
1378
    for (const auto& subscription : callback_list) {
5,206✔
1379
        if (!subscription.second(json_message)) {
3✔
1380
            keep_message = false;
×
1381
        }
1382
    }
1383

1384
    return keep_message;
10,401✔
1385
}
5,195✔
1386

1387
Mavsdk::InterceptJsonHandle
1388
MavsdkImpl::subscribe_incoming_messages_json(const Mavsdk::InterceptJsonCallback& callback)
1✔
1389
{
1390
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1391
    auto handle = _json_handle_factory.create();
1✔
1392
    _incoming_json_message_subscriptions.push_back(std::make_pair(handle, callback));
1✔
1393
    return handle;
2✔
1394
}
1✔
1395

1396
void MavsdkImpl::unsubscribe_incoming_messages_json(Mavsdk::InterceptJsonHandle handle)
1✔
1397
{
1398
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1399
    auto it = std::find_if(
1✔
1400
        _incoming_json_message_subscriptions.begin(),
1401
        _incoming_json_message_subscriptions.end(),
1402
        [handle](const auto& subscription) { return subscription.first == handle; });
1✔
1403
    if (it != _incoming_json_message_subscriptions.end()) {
1✔
1404
        _incoming_json_message_subscriptions.erase(it);
1✔
1405
    }
1406
}
1✔
1407

1408
Mavsdk::InterceptJsonHandle
1409
MavsdkImpl::subscribe_outgoing_messages_json(const Mavsdk::InterceptJsonCallback& callback)
1✔
1410
{
1411
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1412
    auto handle = _json_handle_factory.create();
1✔
1413
    _outgoing_json_message_subscriptions.push_back(std::make_pair(handle, callback));
1✔
1414
    return handle;
2✔
1415
}
1✔
1416

1417
void MavsdkImpl::unsubscribe_outgoing_messages_json(Mavsdk::InterceptJsonHandle handle)
1✔
1418
{
1419
    std::lock_guard<std::mutex> lock(_json_subscriptions_mutex);
1✔
1420
    auto it = std::find_if(
1✔
1421
        _outgoing_json_message_subscriptions.begin(),
1422
        _outgoing_json_message_subscriptions.end(),
1423
        [handle](const auto& subscription) { return subscription.first == handle; });
1✔
1424
    if (it != _outgoing_json_message_subscriptions.end()) {
1✔
1425
        _outgoing_json_message_subscriptions.erase(it);
1✔
1426
    }
1427
}
1✔
1428

1429
RawConnection* MavsdkImpl::find_raw_connection()
4✔
1430
{
1431
    std::lock_guard lock(_mutex);
4✔
1432

1433
    for (auto& entry : _connections) {
4✔
1434
        auto* raw_conn = dynamic_cast<RawConnection*>(entry.connection.get());
2✔
1435
        if (raw_conn != nullptr) {
2✔
1436
            return raw_conn;
2✔
1437
        }
1438
    }
1439
    return nullptr;
2✔
1440
}
4✔
1441

1442
void MavsdkImpl::pass_received_raw_bytes(const char* bytes, size_t length)
1✔
1443
{
1444
    auto* raw_conn = find_raw_connection();
1✔
1445
    if (raw_conn == nullptr) {
1✔
1446
        LogErr()
×
1447
            << "No raw connection available. Please add one using add_any_connection(\"raw://\")";
×
1448
        return;
×
1449
    }
1450

1451
    raw_conn->receive(bytes, length);
1✔
1452
}
1453

1454
Mavsdk::RawBytesHandle
1455
MavsdkImpl::subscribe_raw_bytes_to_be_sent(const Mavsdk::RawBytesCallback& callback)
1✔
1456
{
1457
    if (find_raw_connection() == nullptr) {
1✔
1458
        LogWarn() << "No raw connection available. Subscription will only receive bytes after you "
×
1459
                     "add a connection using add_any_connection(\"raw://\")";
×
1460
    }
1461
    return _raw_bytes_subscriptions.subscribe(callback);
1✔
1462
}
1463

1464
void MavsdkImpl::unsubscribe_raw_bytes_to_be_sent(Mavsdk::RawBytesHandle handle)
1✔
1465
{
1466
    _raw_bytes_subscriptions.unsubscribe(handle);
1✔
1467
}
1✔
1468

1469
bool MavsdkImpl::notify_raw_bytes_sent(const char* bytes, size_t length)
3✔
1470
{
1471
    if (_raw_bytes_subscriptions.empty()) {
3✔
1472
        return false;
2✔
1473
    }
1474

1475
    _raw_bytes_subscriptions(bytes, length);
1✔
1476

1477
    return true;
1✔
1478
}
1479

1480
Mavsdk::ConnectionErrorHandle
1481
MavsdkImpl::subscribe_connection_errors(Mavsdk::ConnectionErrorCallback callback)
×
1482
{
1483
    std::lock_guard lock(_mutex);
×
1484

1485
    const auto handle = _connections_errors_subscriptions.subscribe(callback);
×
1486

1487
    return handle;
×
1488
}
×
1489

1490
void MavsdkImpl::unsubscribe_connection_errors(Mavsdk::ConnectionErrorHandle handle)
×
1491
{
1492
    std::lock_guard lock(_mutex);
×
1493
    _connections_errors_subscriptions.unsubscribe(handle);
×
1494
}
×
1495

1496
uint8_t MavsdkImpl::get_target_system_id(const mavlink_message_t& message)
2,623✔
1497
{
1498
    // Checks whether connection knows target system ID by extracting target system if set.
1499
    const mavlink_msg_entry_t* meta = mavlink_get_msg_entry(message.msgid);
2,623✔
1500

1501
    if (meta == nullptr || !(meta->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_SYSTEM)) {
2,620✔
1502
        return 0;
714✔
1503
    }
1504

1505
    // Don't look at the target system offset if it is outside the payload length.
1506
    // This can happen if the fields are trimmed.
1507
    if (meta->target_system_ofs >= message.len) {
1,906✔
1508
        return 0;
32✔
1509
    }
1510

1511
    return (_MAV_PAYLOAD(&message))[meta->target_system_ofs];
1,874✔
1512
}
1513

1514
uint8_t MavsdkImpl::get_target_component_id(const mavlink_message_t& message)
51✔
1515
{
1516
    // Checks whether connection knows target system ID by extracting target system if set.
1517
    const mavlink_msg_entry_t* meta = mavlink_get_msg_entry(message.msgid);
51✔
1518

1519
    if (meta == nullptr || !(meta->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_COMPONENT)) {
51✔
1520
        return 0;
34✔
1521
    }
1522

1523
    // Don't look at the target component offset if it is outside the payload length.
1524
    // This can happen if the fields are trimmed.
1525
    if (meta->target_component_ofs >= message.len) {
17✔
UNCOV
1526
        return 0;
×
1527
    }
1528

1529
    return (_MAV_PAYLOAD(&message))[meta->target_component_ofs];
17✔
1530
}
1531

1532
Sender& MavsdkImpl::sender()
×
1533
{
1534
    std::lock_guard lock(_server_components_mutex);
×
1535
    return default_server_component_with_lock().sender();
×
1536
}
×
1537

1538
std::vector<Connection*> MavsdkImpl::get_connections() const
2,629✔
1539
{
1540
    std::lock_guard lock(_mutex);
2,629✔
1541
    std::vector<Connection*> connections;
2,630✔
1542
    for (const auto& connection_entry : _connections) {
5,204✔
1543
        connections.push_back(connection_entry.connection.get());
2,570✔
1544
    }
1545
    return connections;
2,627✔
1546
}
2,627✔
1547

1548
mav::MessageSet& MavsdkImpl::get_message_set() const
23✔
1549
{
1550
    // Note: This returns a reference to MessageSet without locking.
1551
    // Thread safety for MessageSet operations must be ensured by:
1552
    // 1. Using load_custom_xml_to_message_set() for write operations (XML loading)
1553
    // 2. libmav MessageSet should be internally thread-safe for read operations
1554
    // 3. If race conditions persist, consider implementing a thread-safe MessageSet wrapper
1555
    return *_message_set;
23✔
1556
}
1557

1558
bool MavsdkImpl::load_custom_xml_to_message_set(const std::string& xml_content)
6✔
1559
{
1560
    std::lock_guard<std::mutex> lock(_message_set_mutex);
6✔
1561
    auto result = _message_set->addFromXMLString(xml_content, false /* recursive_open_includes */);
6✔
1562
    return result == ::mav::MessageSetResult::Success;
12✔
1563
}
6✔
1564

1565
// Thread-safe MessageSet read operations
1566
std::optional<std::string> MavsdkImpl::message_id_to_name_safe(uint32_t id) const
×
1567
{
1568
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1569
    auto message_def = _message_set->getMessageDefinition(static_cast<int>(id));
×
1570
    if (message_def) {
×
1571
        return message_def.get().name();
×
1572
    }
1573
    return std::nullopt;
×
1574
}
×
1575

1576
std::optional<int> MavsdkImpl::message_name_to_id_safe(const std::string& name) const
×
1577
{
1578
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1579
    return _message_set->idForMessage(name);
×
1580
}
×
1581

1582
std::optional<mav::Message> MavsdkImpl::create_message_safe(const std::string& message_name) const
×
1583
{
1584
    std::lock_guard<std::mutex> lock(_message_set_mutex);
×
1585
    return _message_set->create(message_name);
×
1586
}
×
1587

1588
// Thread-safe parsing for LibmavReceiver
1589
std::optional<mav::Message> MavsdkImpl::parse_message_safe(
5,211✔
1590
    const uint8_t* buffer, size_t buffer_len, size_t& bytes_consumed) const
1591
{
1592
    std::lock_guard<std::mutex> lock(_message_set_mutex);
5,211✔
1593
    return _buffer_parser->parseMessage(buffer, buffer_len, bytes_consumed);
5,219✔
1594
}
5,221✔
1595

1596
mav::OptionalReference<const mav::MessageDefinition>
1597
MavsdkImpl::get_message_definition_safe(int message_id) const
5,140✔
1598
{
1599
    std::lock_guard<std::mutex> lock(_message_set_mutex);
5,140✔
1600
    return _message_set->getMessageDefinition(message_id);
5,141✔
1601
}
5,145✔
1602

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

© 2026 Coveralls, Inc