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

mavlink / MAVSDK / 11549442665

28 Oct 2024 07:40AM UTC coverage: 42.635% (+4.7%) from 37.918%
11549442665

Pull #2386

github

web-flow
Merge db5fdd683 into d46bb7a96
Pull Request #2386: camera: support multiple cameras within one instance

1216 of 1893 new or added lines in 46 files covered. (64.24%)

54 existing lines in 9 files now uncovered.

13095 of 30714 relevant lines covered (42.64%)

289.07 hits per line

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

53.25
/src/mavsdk/core/mavlink_command_sender.cpp
1
#include "mavlink_command_sender.h"
2
#include "mavlink_address.h"
3
#include "system_impl.h"
4
#include "unused.h"
5
#include <cmath>
6
#include <future>
7
#include <memory>
8

9
namespace mavsdk {
10

11
MavlinkCommandSender::MavlinkCommandSender(SystemImpl& system_impl) : _system_impl(system_impl)
86✔
12
{
13
    if (const char* env_p = std::getenv("MAVSDK_COMMAND_DEBUGGING")) {
86✔
14
        if (std::string(env_p) == "1") {
×
15
            LogDebug() << "Command debugging is on.";
×
16
            _command_debugging = true;
×
17
        }
18
    }
19

20
    _system_impl.register_mavlink_message_handler(
86✔
21
        MAVLINK_MSG_ID_COMMAND_ACK,
22
        [this](const mavlink_message_t& message) { receive_command_ack(message); },
100✔
23
        this);
24
}
86✔
25

26
MavlinkCommandSender::~MavlinkCommandSender()
86✔
27
{
28
    if (_command_debugging) {
86✔
29
        LogDebug() << "CommandSender destroyed";
×
30
    }
31
    _system_impl.unregister_all_mavlink_message_handlers(this);
86✔
32

33
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
172✔
34
    for (const auto& work : _work_queue) {
88✔
35
        _system_impl.unregister_timeout_handler(work->timeout_cookie);
2✔
36
    }
37
}
86✔
38

NEW
39
MavlinkCommandSender::Result MavlinkCommandSender::send_command(
×
40
    const MavlinkCommandSender::CommandInt& command, unsigned retries)
41
{
42
    // We wrap the async call with a promise and future.
43
    auto prom = std::make_shared<std::promise<Result>>();
×
44
    auto res = prom->get_future();
×
45

NEW
46
    queue_command_async(
×
47
        command,
NEW
48
        [prom](Result result, float progress) {
×
NEW
49
            UNUSED(progress);
×
50
            // We can only fulfill the promise once in C++11.
51
            // Therefore, we have to ignore the IN_PROGRESS state and wait
52
            // for the final result.
NEW
53
            if (result != Result::InProgress) {
×
NEW
54
                prom->set_value(result);
×
55
            }
NEW
56
        },
×
57
        retries);
58

59
    // Block now to wait for result.
60
    return res.get();
×
61
}
62

63
MavlinkCommandSender::Result MavlinkCommandSender::send_command(
5✔
64
    const MavlinkCommandSender::CommandLong& command, unsigned retries)
65
{
66
    // We wrap the async call with a promise and future.
67
    auto prom = std::make_shared<std::promise<Result>>();
10✔
68
    auto res = prom->get_future();
10✔
69

70
    queue_command_async(
5✔
71
        command,
72
        [prom](Result result, float progress) {
6✔
73
            UNUSED(progress);
6✔
74
            // We can only fulfill the promise once in C++11.
75
            // Therefore, we have to ignore the IN_PROGRESS state and wait
76
            // for the final result.
77
            if (result != Result::InProgress) {
6✔
78
                prom->set_value(result);
5✔
79
            }
80
        },
6✔
81
        retries);
82

83
    return res.get();
5✔
84
}
85

86
void MavlinkCommandSender::queue_command_async(
×
87
    const CommandInt& command, const CommandResultCallback& callback, unsigned retries)
88
{
89
    if (_command_debugging) {
×
90
        LogDebug() << "COMMAND_INT " << static_cast<int>(command.command) << " to send to "
×
91
                   << static_cast<int>(command.target_system_id) << ", "
×
92
                   << static_cast<int>(command.target_component_id);
×
93
    }
94

95
    CommandIdentification identification = identification_from_command(command);
×
96

97
    for (const auto& work : _work_queue) {
×
98
        if (work->identification == identification && callback == nullptr) {
×
99
            if (_command_debugging) {
×
100
                LogDebug() << "Dropping command " << static_cast<int>(identification.command)
×
101
                           << " that is already being sent";
×
102
            }
103
            return;
×
104
        }
105
    }
106

107
    auto new_work = std::make_shared<Work>();
×
108
    new_work->timeout_s = _system_impl.timeout_s();
×
109
    new_work->command = command;
×
110
    new_work->identification = identification;
×
111
    new_work->callback = callback;
×
NEW
112
    new_work->retries_to_do = retries;
×
UNCOV
113
    _work_queue.push_back(new_work);
×
114
}
115

116
void MavlinkCommandSender::queue_command_async(
98✔
117
    const CommandLong& command, const CommandResultCallback& callback, unsigned retries)
118
{
119
    if (_command_debugging) {
98✔
120
        LogDebug() << "COMMAND_LONG " << static_cast<int>(command.command) << " to send to "
×
121
                   << static_cast<int>(command.target_system_id) << ", "
×
122
                   << static_cast<int>(command.target_component_id);
×
123
    }
124

125
    CommandIdentification identification = identification_from_command(command);
98✔
126

127
    for (const auto& work : _work_queue) {
190✔
128
        if (work->identification == identification && callback == nullptr) {
92✔
129
            if (_command_debugging) {
×
130
                LogDebug() << "Dropping command " << static_cast<int>(identification.command)
×
131
                           << " that is already being sent";
×
132
            }
133
            return;
×
134
        }
135
    }
136

137
    auto new_work = std::make_shared<Work>();
196✔
138
    new_work->timeout_s = _system_impl.timeout_s();
98✔
139
    new_work->command = command;
98✔
140
    new_work->identification = identification;
98✔
141
    new_work->callback = callback;
98✔
142
    new_work->time_started = _system_impl.get_time().steady_time();
98✔
143
    new_work->retries_to_do = retries;
98✔
144
    _work_queue.push_back(new_work);
98✔
145
}
146

147
void MavlinkCommandSender::receive_command_ack(const mavlink_message_t& message)
100✔
148
{
149
    mavlink_command_ack_t command_ack;
100✔
150
    mavlink_msg_command_ack_decode(&message, &command_ack);
100✔
151

152
    if ((command_ack.target_system &&
97✔
153
         command_ack.target_system != _system_impl.get_own_system_id()) ||
200✔
154
        (command_ack.target_component &&
197✔
155
         command_ack.target_component != _system_impl.get_own_component_id())) {
97✔
156
        if (_command_debugging) {
×
157
            LogDebug() << "Ignoring command ack for command "
×
158
                       << static_cast<int>(command_ack.command) << " from "
×
159
                       << static_cast<int>(message.sysid) << '/' << static_cast<int>(message.compid)
×
160
                       << " to " << static_cast<int>(command_ack.target_system) << '/'
×
161
                       << static_cast<int>(command_ack.target_component);
×
162
        }
163
        return;
×
164
    }
165

166
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
100✔
167

168
    for (auto it = _work_queue.begin(); it != _work_queue.end(); ++it) {
121✔
169
        const auto& work = *it;
118✔
170

171
        if (!work) {
118✔
172
            LogErr() << "No work available! (should not happen #1)";
×
173
            return;
×
174
        }
175

176
        if (work->identification.command != command_ack.command ||
118✔
177
            (work->identification.target_system_id != 0 &&
97✔
178
             work->identification.target_system_id != message.sysid) ||
312✔
179
            (work->identification.target_component_id != 0 &&
97✔
180
             work->identification.target_component_id != message.compid)) {
97✔
181
            if (_command_debugging) {
21✔
182
                LogDebug() << "Command ack for " << command_ack.command
×
183
                           << " (from: " << std::to_string(message.sysid) << "/"
×
184
                           << std::to_string(message.compid) << ")" << " does not match command "
×
185
                           << work->identification.command
×
186
                           << " (to: " << std::to_string(work->identification.target_system_id)
×
187
                           << "/" << std::to_string(work->identification.target_component_id) << ")"
×
188
                           << " after "
×
189
                           << _system_impl.get_time().elapsed_since_s(work->time_started) << " s";
×
190
            }
191
            continue;
21✔
192
        }
193

194
        if (_command_debugging) {
97✔
195
            LogDebug() << "Received command ack for " << command_ack.command << " with result "
×
196
                       << static_cast<int>(command_ack.result) << " after "
×
197
                       << _system_impl.get_time().elapsed_since_s(work->time_started) << " s";
×
198
        }
199

200
        CommandResultCallback temp_callback = work->callback;
194✔
201
        std::pair<Result, float> temp_result{Result::UnknownError, NAN};
97✔
202

203
        switch (command_ack.result) {
97✔
204
            case MAV_RESULT_ACCEPTED:
63✔
205
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
63✔
206
                temp_result = {Result::Success, 1.0f};
63✔
207
                _work_queue.erase(it);
63✔
208
                break;
63✔
209

210
            case MAV_RESULT_DENIED:
21✔
211
                if (_command_debugging) {
21✔
212
                    LogDebug() << "command denied (" << work->identification.command << ").";
×
213
                    if (work->identification.command == 512) {
×
214
                        LogDebug() << "(message " << work->identification.maybe_param1 << ")";
×
215
                    }
216
                }
217
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
21✔
218
                temp_result = {Result::Denied, NAN};
21✔
219
                _work_queue.erase(it);
21✔
220
                break;
21✔
221

222
            case MAV_RESULT_UNSUPPORTED:
10✔
223
                if (_command_debugging) {
10✔
224
                    LogDebug() << "command unsupported (" << work->identification.command << ").";
×
225
                }
226
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
10✔
227
                temp_result = {Result::Unsupported, NAN};
10✔
228
                _work_queue.erase(it);
10✔
229
                break;
10✔
230

231
            case MAV_RESULT_TEMPORARILY_REJECTED:
2✔
232
                if (_command_debugging) {
2✔
233
                    LogDebug() << "command temporarily rejected (" << work->identification.command
×
234
                               << ").";
×
235
                }
236
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
2✔
237
                temp_result = {Result::TemporarilyRejected, NAN};
2✔
238
                _work_queue.erase(it);
2✔
239
                break;
2✔
240

241
            case MAV_RESULT_FAILED:
×
242
                if (_command_debugging) {
×
243
                    LogDebug() << "command failed (" << work->identification.command << ").";
×
244
                }
245
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
×
246
                temp_result = {Result::Failed, NAN};
×
247
                _work_queue.erase(it);
×
248
                break;
×
249

250
            case MAV_RESULT_IN_PROGRESS:
1✔
251
                if (_command_debugging) {
1✔
252
                    if (static_cast<int>(command_ack.progress) != 255) {
×
253
                        LogDebug() << "progress: " << static_cast<int>(command_ack.progress)
×
254
                                   << " % (" << work->identification.command << ").";
×
255
                    }
256
                }
257
                // If we get a progress update, we can raise the timeout
258
                // to something higher because we know the initial command
259
                // has arrived.
260
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
1✔
261
                work->timeout_cookie = _system_impl.register_timeout_handler(
2✔
262
                    [this, identification = work->identification] {
1✔
263
                        receive_timeout(identification);
×
264
                    },
×
265
                    3.0);
266

267
                temp_result = {
1✔
268
                    Result::InProgress, static_cast<float>(command_ack.progress) / 100.0f};
1✔
269
                break;
1✔
270

271
            case MAV_RESULT_CANCELLED:
×
272
                if (_command_debugging) {
×
273
                    LogDebug() << "command cancelled (" << work->identification.command << ").";
×
274
                }
275
                _system_impl.unregister_timeout_handler(work->timeout_cookie);
×
276
                temp_result = {Result::Cancelled, NAN};
×
277
                _work_queue.erase(it);
×
278
                break;
×
279

280
            default:
×
281
                LogWarn() << "Received unknown ack.";
×
282
                break;
×
283
        }
284

285
        if (temp_callback != nullptr) {
97✔
286
            call_callback(temp_callback, temp_result.first, temp_result.second);
97✔
287
        }
288

289
        return;
97✔
290
    }
291

292
    if (_command_debugging) {
3✔
293
        LogDebug() << "Received ack from " << static_cast<int>(message.sysid) << '/'
×
294
                   << static_cast<int>(message.compid)
×
295
                   << " for not-existing command: " << static_cast<int>(command_ack.command)
×
296
                   << "! Ignoring...";
×
297
    } else {
298
        LogWarn() << "Received ack for not-existing command: "
6✔
299
                  << static_cast<int>(command_ack.command) << "! Ignoring...";
6✔
300
    }
301
}
302

303
void MavlinkCommandSender::receive_timeout(const CommandIdentification& identification)
1✔
304
{
305
    if (_command_debugging) {
1✔
306
        LogDebug() << "Got timeout!";
×
307
    }
308
    bool found_command = false;
1✔
309
    CommandResultCallback temp_callback = nullptr;
1✔
310
    std::pair<Result, float> temp_result{Result::UnknownError, NAN};
1✔
311

312
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
1✔
313

314
    for (auto it = _work_queue.begin(); it != _work_queue.end(); ++it) {
2✔
315
        const auto& work = *it;
1✔
316

317
        if (!work) {
1✔
318
            LogErr() << "No work available! (should not happen #2)";
×
319
            return;
×
320
        }
321

322
        if (work->identification != identification) {
1✔
323
            continue;
×
324
        }
325

326
        found_command = true;
1✔
327

328
        if (work->retries_to_do > 0) {
1✔
329
            // We're not sure the command arrived, let's retransmit.
330
            LogWarn() << "sending again after "
2✔
331
                      << _system_impl.get_time().elapsed_since_s(work->time_started)
2✔
332
                      << " s, retries to do: " << work->retries_to_do << "  ("
1✔
333
                      << work->identification.command << ").";
3✔
334

335
            if (work->identification.command == MAV_CMD_REQUEST_MESSAGE) {
1✔
336
                LogWarn() << "Request was for msg ID: " << work->identification.maybe_param1;
1✔
337
            }
338

339
            if (!send_mavlink_message(work->command)) {
1✔
340
                LogErr() << "connection send error in retransmit (" << work->identification.command
×
341
                         << ").";
×
342
                temp_callback = work->callback;
×
343
                temp_result = {Result::ConnectionError, NAN};
×
344
                _work_queue.erase(it);
×
345
                break;
×
346
            } else {
347
                --work->retries_to_do;
1✔
348
                work->timeout_cookie = _system_impl.register_timeout_handler(
2✔
349
                    [this, identification = work->identification] {
1✔
350
                        receive_timeout(identification);
×
351
                    },
×
352
                    work->timeout_s);
1✔
353
            }
354
        } else {
355
            // We have tried retransmitting, giving up now.
356
            if (work->identification.command == 512) {
×
357
                uint8_t target_sysid;
358
                uint8_t target_compid;
359
                if (auto command_int = std::get_if<CommandInt>(&work->command)) {
×
360
                    target_sysid = command_int->target_system_id;
×
361
                    target_compid = command_int->target_component_id;
×
362
                } else if (auto command_long = std::get_if<CommandLong>(&work->command)) {
×
363
                    target_sysid = command_long->target_system_id;
×
364
                    target_compid = command_long->target_component_id;
×
365
                } else {
366
                    LogErr() << "No command, that's awkward";
×
367
                    continue;
×
368
                }
NEW
369
                LogWarn() << "Retrying failed for REQUEST_MESSAGE command for message: "
×
NEW
370
                          << work->identification.maybe_param1 << ", to ("
×
NEW
371
                          << std::to_string(target_sysid) << "/" << std::to_string(target_compid)
×
NEW
372
                          << ")";
×
373
            } else {
NEW
374
                LogWarn() << "Retrying failed for command: " << work->identification.command << ")";
×
375
            }
376

377
            temp_callback = work->callback;
×
378
            temp_result = {Result::Timeout, NAN};
×
379
            _work_queue.erase(it);
×
380
            break;
×
381
        }
382
    }
383

384
    if (temp_callback != nullptr) {
1✔
385
        call_callback(temp_callback, temp_result.first, temp_result.second);
×
386
    }
387

388
    if (!found_command) {
1✔
389
        LogWarn() << "Timeout for not-existing command: "
×
390
                  << static_cast<int>(identification.command) << "! Ignoring...";
×
391
    }
392
}
393

394
void MavlinkCommandSender::do_work()
5,644✔
395
{
396
    LockedQueue<Work>::Guard work_queue_guard(_work_queue);
11,288✔
397

398
    for (const auto& work : _work_queue) {
5,818✔
399
        if (work->already_sent) {
174✔
400
            continue;
26✔
401
        }
402

403
        bool already_being_sent = false;
148✔
404
        for (const auto& other_work : _work_queue) {
354✔
405
            // Ignore itself:
406
            if (other_work == work) {
256✔
407
                continue;
98✔
408
            }
409

410
            // Check if command with same command ID is already being sent.
411
            if (other_work->already_sent &&
228✔
412
                other_work->identification.command == work->identification.command) {
70✔
413
                if (_command_debugging) {
50✔
414
                    LogDebug() << "Command " << static_cast<int>(work->identification.command)
×
415
                               << " is already being sent, waiting...";
×
416
                }
417
                already_being_sent = true;
50✔
418
                break;
50✔
419
            }
420
        }
421

422
        if (already_being_sent) {
148✔
423
            continue;
50✔
424
        }
425

426
        // LogDebug() << "sending it the first time (" << work->mavlink_command << ")";
427
        work->time_started = _system_impl.get_time().steady_time();
98✔
428

429
        {
430
            if (!send_mavlink_message(work->command)) {
98✔
431
                LogErr() << "connection send error (" << work->identification.command << ")";
×
432
                // In this case we try again after the timeout. Chances are slim it will work next
433
                // time though.
434
            } else {
435
                if (_command_debugging) {
98✔
436
                    LogDebug() << "Sent command " << static_cast<int>(work->identification.command);
×
437
                }
438
            }
439
        }
440

441
        work->already_sent = true;
98✔
442

443
        work->timeout_cookie = _system_impl.register_timeout_handler(
196✔
444
            [this, identification = work->identification] { receive_timeout(identification); },
99✔
445
            work->timeout_s);
98✔
446
    }
447
}
5,644✔
448

449
void MavlinkCommandSender::call_callback(
97✔
450
    const CommandResultCallback& callback, Result result, float progress) const
451
{
452
    if (!callback) {
97✔
453
        return;
×
454
    }
455

456
    // It seems that we need to queue the callback on the thread pool otherwise
457
    // we lock ourselves out when we send a command in the callback receiving a command result.
458
    auto temp_callback = callback;
194✔
459
    _system_impl.call_user_callback(
194✔
460
        [temp_callback, result, progress]() { temp_callback(result, progress); });
461
}
462

463
bool MavlinkCommandSender::send_mavlink_message(const Command& command) const
99✔
464
{
465
    if (auto command_int = std::get_if<CommandInt>(&command)) {
99✔
466
        return _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
×
467
            mavlink_message_t message;
468
            mavlink_msg_command_int_pack_chan(
×
469
                mavlink_address.system_id,
×
470
                mavlink_address.component_id,
×
471
                channel,
472
                &message,
473
                command_int->target_system_id,
×
474
                command_int->target_component_id,
×
475
                command_int->frame,
×
476
                command_int->command,
×
477
                command_int->current,
×
478
                command_int->autocontinue,
×
479
                maybe_reserved(command_int->params.maybe_param1),
×
480
                maybe_reserved(command_int->params.maybe_param2),
×
481
                maybe_reserved(command_int->params.maybe_param3),
×
482
                maybe_reserved(command_int->params.maybe_param4),
×
483
                command_int->params.x,
×
484
                command_int->params.y,
×
485
                maybe_reserved(command_int->params.maybe_z));
×
486
            return message;
×
487
        });
×
488

489
    } else if (auto command_long = std::get_if<CommandLong>(&command)) {
99✔
490
        return _system_impl.queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
198✔
491
            mavlink_message_t message;
492
            mavlink_msg_command_long_pack_chan(
792✔
493
                mavlink_address.system_id,
99✔
494
                mavlink_address.component_id,
99✔
495
                channel,
496
                &message,
497
                command_long->target_system_id,
792✔
498
                command_long->target_component_id,
99✔
499
                command_long->command,
99✔
500
                command_long->confirmation,
99✔
501
                maybe_reserved(command_long->params.maybe_param1),
99✔
502
                maybe_reserved(command_long->params.maybe_param2),
99✔
503
                maybe_reserved(command_long->params.maybe_param3),
99✔
504
                maybe_reserved(command_long->params.maybe_param4),
99✔
505
                maybe_reserved(command_long->params.maybe_param5),
99✔
506
                maybe_reserved(command_long->params.maybe_param6),
99✔
507
                maybe_reserved(command_long->params.maybe_param7));
99✔
508
            return message;
99✔
509
        });
99✔
510
    } else {
511
        return false;
×
512
    }
513
}
514

515
float MavlinkCommandSender::maybe_reserved(const std::optional<float>& maybe_param) const
693✔
516
{
517
    if (maybe_param) {
693✔
518
        return maybe_param.value();
114✔
519

520
    } else {
521
        if (_system_impl.autopilot() == Autopilot::ArduPilot) {
579✔
522
            return 0.0f;
×
523
        } else {
524
            return NAN;
579✔
525
        }
526
    }
527
}
528

529
} // 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