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

mavlink / MAVSDK / 23911409490

02 Apr 2026 04:40PM UTC coverage: 50.475% (+1.4%) from 49.029%
23911409490

push

github

web-flow
Merge pull request #2833 from bansiesta/v3-cherry-picks

Backport recent main fixes and features to v3

632 of 815 new or added lines in 29 files covered. (77.55%)

34 existing lines in 11 files now uncovered.

19249 of 38136 relevant lines covered (50.47%)

671.0 hits per line

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

54.65
/src/mavsdk/plugins/camera/camera_impl.cpp
1
#include "camera_impl.h"
2
#include "camera_definition.h"
3
#include "system.h"
4
#include "unused.h"
5
#include "callback_list.tpp"
6
#include "fs_utils.h"
7
#include "inflate_lzma.h"
8
#include "string_utils.h"
9
#include "math_utils.h"
10

11
#if BUILD_WITHOUT_CURL != 1
12
#include "http_loader.h"
13
#endif
14

15
#include <algorithm>
16
#include <cassert>
17
#include <cmath>
18
#include <chrono>
19
#include <iterator>
20
#include <filesystem>
21
#include <functional>
22
#include <string>
23
#include <thread>
24

25
namespace mavsdk {
26

27
template class CallbackList<Camera::Mode>;
28
template class CallbackList<std::vector<Camera::Setting>>;
29
template class CallbackList<std::vector<Camera::SettingOptions>>;
30
template class CallbackList<Camera::CaptureInfo>;
31
template class CallbackList<Camera::VideoStreamInfo>;
32
template class CallbackList<Camera::Storage>;
33

34
CameraImpl::CameraImpl(System& system) : PluginImplBase(system)
×
35
{
36
    _system_impl->register_plugin(this);
×
37
}
×
38

39
CameraImpl::CameraImpl(std::shared_ptr<System> system) : PluginImplBase(std::move(system))
11✔
40
{
41
    _system_impl->register_plugin(this);
11✔
42
}
11✔
43

44
CameraImpl::~CameraImpl()
22✔
45
{
46
    _system_impl->unregister_plugin(this);
11✔
47
}
22✔
48

49
void CameraImpl::init()
11✔
50
{
51
    if (const char* env_p = std::getenv("MAVSDK_CAMERA_DEBUGGING")) {
11✔
52
        if (std::string(env_p) == "1") {
×
53
            LogDebug() << "Camera debugging is on.";
×
54
            _debugging = true;
×
55
        }
56
    }
57

58
    const auto cache_dir_option = get_cache_directory();
11✔
59
    if (cache_dir_option) {
11✔
60
        _file_cache.emplace(cache_dir_option.value() / "camera", 50, true);
11✔
61
    } else {
62
        LogErr() << "Failed to get cache directory";
×
63
    }
64

65
    const auto tmp_option = create_tmp_directory("mavsdk-component-metadata");
22✔
66
    if (tmp_option) {
11✔
67
        _tmp_download_path = tmp_option.value();
11✔
68
    } else {
69
        _tmp_download_path = "./mavsdk-component-metadata";
×
70
        std::error_code err;
×
71
        std::filesystem::create_directory(_tmp_download_path, err);
×
72
    }
73

74
    _system_impl->register_mavlink_message_handler(
11✔
75
        MAVLINK_MSG_ID_HEARTBEAT,
76
        [this](const mavlink_message_t& message) { process_heartbeat(message); },
28✔
77
        this);
78

79
    _system_impl->register_mavlink_message_handler(
11✔
80
        MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS,
81
        [this](const mavlink_message_t& message) { process_camera_capture_status(message); },
15✔
82
        this);
83

84
    _system_impl->register_mavlink_message_handler(
11✔
85
        MAVLINK_MSG_ID_STORAGE_INFORMATION,
86
        [this](const mavlink_message_t& message) { process_storage_information(message); },
2✔
87
        this);
88

89
    _system_impl->register_mavlink_message_handler(
11✔
90
        MAVLINK_MSG_ID_CAMERA_IMAGE_CAPTURED,
91
        [this](const mavlink_message_t& message) { process_camera_image_captured(message); },
4✔
92
        this);
93

94
    _system_impl->register_mavlink_message_handler(
11✔
95
        MAVLINK_MSG_ID_CAMERA_SETTINGS,
96
        [this](const mavlink_message_t& message) { process_camera_settings(message); },
11✔
97
        this);
98

99
    _system_impl->register_mavlink_message_handler(
11✔
100
        MAVLINK_MSG_ID_CAMERA_INFORMATION,
101
        [this](const mavlink_message_t& message) { process_camera_information(message); },
11✔
102
        this);
103

104
    _system_impl->register_mavlink_message_handler(
11✔
105
        MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION,
106
        [this](const mavlink_message_t& message) { process_video_information(message); },
1✔
107
        this);
108

109
    _system_impl->register_mavlink_message_handler(
11✔
110
        MAVLINK_MSG_ID_VIDEO_STREAM_STATUS,
111
        [this](const mavlink_message_t& message) { process_video_stream_status(message); },
1✔
112
        this);
113

114
    _request_missing_capture_info_cookie =
11✔
115
        _system_impl->add_call_every([this]() { request_missing_capture_info(); }, 0.5);
57✔
116

117
    // TODO: use SET_INTERVAL commands instead and fall back to request commands when needed.
118
    _request_slower_call_every_cookie =
11✔
119
        _system_impl->add_call_every([this]() { request_slower(); }, 20.0);
22✔
120
    _request_faster_call_every_cookie =
11✔
121
        _system_impl->add_call_every([this]() { request_faster(); }, 5.0);
23✔
122
}
11✔
123

124
void CameraImpl::deinit()
11✔
125
{
126
    _system_impl->unregister_all_mavlink_message_handlers_blocking(this);
11✔
127

128
    _system_impl->cancel_all_param(this);
11✔
129

130
    _system_impl->remove_call_every(_request_missing_capture_info_cookie);
11✔
131
    _system_impl->remove_call_every(_request_slower_call_every_cookie);
11✔
132
    _system_impl->remove_call_every(_request_faster_call_every_cookie);
11✔
133

134
    // Cancel any pending MAVLink FTP operations so their callbacks don't fire
135
    // after we are gone.  This is synchronous: once it returns no further FTP
136
    // callbacks will be dispatched from the io_context thread.
137
    _system_impl->mavlink_ftp_client().cancel_all_operations();
11✔
138

139
    // Wait briefly for call_every lambdas that may already be mid-flight
140
    // (remove_call_every only prevents future invocations).
141
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
11✔
142

143
    std::lock_guard lock(_mutex);
11✔
144
    // Signal any user-callback-queued lambdas that slipped through before
145
    // cancel_all_operations() returned that they must not touch our members.
146
    *_alive = false;
11✔
147
    _storage_subscription_callbacks.clear();
11✔
148
    _mode_subscription_callbacks.clear();
11✔
149
    _capture_info_callbacks.clear();
11✔
150
    _video_stream_info_subscription_callbacks.clear();
11✔
151
    _camera_list_subscription_callbacks.clear();
11✔
152
    _subscribe_current_settings_callbacks.clear();
11✔
153
    _subscribe_possible_setting_options_callbacks.clear();
11✔
154
}
11✔
155

156
void CameraImpl::enable() {}
11✔
157
void CameraImpl::disable() {}
11✔
158

159
MavlinkCommandSender::CommandLong
160
CameraImpl::make_command_take_photo(int32_t component_id, float interval_s, float no_of_photos)
2✔
161
{
162
    MavlinkCommandSender::CommandLong cmd{};
2✔
163

164
    cmd.command = MAV_CMD_IMAGE_START_CAPTURE;
2✔
165
    cmd.params.maybe_param1 = 0.0f; // Reserved, set to 0
2✔
166
    cmd.params.maybe_param2 = interval_s;
2✔
167
    cmd.params.maybe_param3 = no_of_photos;
2✔
168
    cmd.params.maybe_param4 = get_and_increment_capture_sequence(component_id);
2✔
169
    cmd.target_component_id = fixup_component_target(component_id);
2✔
170

171
    return cmd;
2✔
172
}
173

174
MavlinkCommandSender::CommandLong CameraImpl::make_command_zoom_out(int32_t component_id)
×
175
{
176
    MavlinkCommandSender::CommandLong cmd{};
×
177
    cmd.command = MAV_CMD_SET_CAMERA_ZOOM;
×
178
    cmd.params.maybe_param1 = static_cast<float>(ZOOM_TYPE_CONTINUOUS);
×
179
    cmd.params.maybe_param2 = -1.f;
×
180
    cmd.target_component_id = fixup_component_target(component_id);
×
181

182
    return cmd;
×
183
}
184

185
MavlinkCommandSender::CommandLong CameraImpl::make_command_zoom_in(int32_t component_id)
×
186
{
187
    MavlinkCommandSender::CommandLong cmd{};
×
188
    cmd.command = MAV_CMD_SET_CAMERA_ZOOM;
×
189
    cmd.params.maybe_param1 = static_cast<float>(ZOOM_TYPE_CONTINUOUS);
×
190
    cmd.params.maybe_param2 = 1.f;
×
191
    cmd.target_component_id = fixup_component_target(component_id);
×
192

193
    return cmd;
×
194
}
195

196
MavlinkCommandSender::CommandLong CameraImpl::make_command_zoom_stop(int32_t component_id)
×
197
{
198
    MavlinkCommandSender::CommandLong cmd{};
×
199
    cmd.command = MAV_CMD_SET_CAMERA_ZOOM;
×
200
    cmd.params.maybe_param1 = static_cast<float>(ZOOM_TYPE_CONTINUOUS);
×
201
    cmd.params.maybe_param2 = 0.f;
×
202
    cmd.target_component_id = fixup_component_target(component_id);
×
203

204
    return cmd;
×
205
}
206

207
MavlinkCommandSender::CommandLong
208
CameraImpl::make_command_zoom_range(int32_t component_id, float range)
×
209
{
210
    // Clip to safe range.
211
    range = std::max(0.f, std::min(range, 100.f));
×
212

213
    MavlinkCommandSender::CommandLong cmd{};
×
214
    cmd.command = MAV_CMD_SET_CAMERA_ZOOM;
×
215
    cmd.params.maybe_param1 = static_cast<float>(ZOOM_TYPE_RANGE);
×
216
    cmd.params.maybe_param2 = range;
×
217
    cmd.target_component_id = fixup_component_target(component_id);
×
218

219
    return cmd;
×
220
}
221

222
MavlinkCommandSender::CommandLong CameraImpl::make_command_track_point(
×
223
    int32_t component_id, float point_x, float point_y, float radius)
224
{
225
    MavlinkCommandSender::CommandLong cmd{};
×
226
    cmd.command = MAV_CMD_CAMERA_TRACK_POINT;
×
227
    cmd.params.maybe_param1 = point_x;
×
228
    cmd.params.maybe_param2 = point_y;
×
229
    cmd.params.maybe_param3 = radius;
×
230
    cmd.target_component_id = fixup_component_target(component_id);
×
231

232
    return cmd;
×
233
}
234

235
MavlinkCommandSender::CommandLong CameraImpl::make_command_track_rectangle(
×
236
    int32_t component_id,
237
    float top_left_x,
238
    float top_left_y,
239
    float bottom_right_x,
240
    float bottom_right_y)
241
{
242
    MavlinkCommandSender::CommandLong cmd{};
×
243
    cmd.command = MAV_CMD_CAMERA_TRACK_RECTANGLE;
×
244
    cmd.params.maybe_param1 = top_left_x;
×
245
    cmd.params.maybe_param2 = top_left_y;
×
246
    cmd.params.maybe_param3 = bottom_right_x;
×
247
    cmd.params.maybe_param4 = bottom_right_y;
×
248
    cmd.target_component_id = fixup_component_target(component_id);
×
249

250
    return cmd;
×
251
}
252

253
MavlinkCommandSender::CommandLong CameraImpl::make_command_track_stop(int32_t component_id)
×
254
{
255
    MavlinkCommandSender::CommandLong cmd{};
×
256
    cmd.command = MAV_CMD_CAMERA_STOP_TRACKING;
×
257
    cmd.target_component_id = fixup_component_target(component_id);
×
258

259
    return cmd;
×
260
}
261

262
MavlinkCommandSender::CommandLong CameraImpl::make_command_focus_in(int32_t component_id)
×
263
{
264
    MavlinkCommandSender::CommandLong cmd{};
×
265
    cmd.command = MAV_CMD_SET_CAMERA_FOCUS;
×
266
    cmd.params.maybe_param1 = static_cast<float>(FOCUS_TYPE_CONTINUOUS);
×
267
    cmd.params.maybe_param2 = -1.f;
×
268
    cmd.target_component_id = fixup_component_target(component_id);
×
269

270
    return cmd;
×
271
}
272

273
MavlinkCommandSender::CommandLong CameraImpl::make_command_focus_out(int32_t component_id)
×
274
{
275
    MavlinkCommandSender::CommandLong cmd{};
×
276
    cmd.command = MAV_CMD_SET_CAMERA_FOCUS;
×
277
    cmd.params.maybe_param1 = static_cast<float>(FOCUS_TYPE_CONTINUOUS);
×
278
    cmd.params.maybe_param2 = 1.f;
×
279
    cmd.target_component_id = fixup_component_target(component_id);
×
280

281
    return cmd;
×
282
}
283

284
MavlinkCommandSender::CommandLong CameraImpl::make_command_focus_stop(int32_t component_id)
×
285
{
286
    MavlinkCommandSender::CommandLong cmd{};
×
287
    cmd.command = MAV_CMD_SET_CAMERA_FOCUS;
×
288
    cmd.params.maybe_param1 = static_cast<float>(FOCUS_TYPE_CONTINUOUS);
×
289
    cmd.params.maybe_param2 = 0.f;
×
290
    cmd.target_component_id = fixup_component_target(component_id);
×
291

292
    return cmd;
×
293
}
294

295
MavlinkCommandSender::CommandLong
296
CameraImpl::make_command_focus_range(int32_t component_id, float range)
×
297
{
298
    // Clip to safe range.
299
    range = std::max(0.f, std::min(range, 100.f));
×
300

301
    MavlinkCommandSender::CommandLong cmd{};
×
302
    cmd.command = MAV_CMD_SET_CAMERA_FOCUS;
×
303
    cmd.params.maybe_param1 = static_cast<float>(FOCUS_TYPE_RANGE);
×
304
    cmd.params.maybe_param2 = range;
×
305
    cmd.target_component_id = fixup_component_target(component_id);
×
306

307
    return cmd;
×
308
}
309

310
MavlinkCommandSender::CommandLong CameraImpl::make_command_stop_photo(int32_t component_id)
1✔
311
{
312
    MavlinkCommandSender::CommandLong cmd_stop_photo{};
1✔
313

314
    cmd_stop_photo.command = MAV_CMD_IMAGE_STOP_CAPTURE;
1✔
315
    cmd_stop_photo.target_component_id = fixup_component_target(component_id);
1✔
316

317
    return cmd_stop_photo;
1✔
318
}
319

320
MavlinkCommandSender::CommandLong
321
CameraImpl::make_command_start_video(int32_t component_id, float capture_status_rate_hz)
×
322
{
323
    MavlinkCommandSender::CommandLong cmd_start_video{};
×
324

325
    cmd_start_video.command = MAV_CMD_VIDEO_START_CAPTURE;
×
326
    cmd_start_video.params.maybe_param1 = 0.f; // Reserved, set to 0
×
327
    cmd_start_video.params.maybe_param2 = capture_status_rate_hz;
×
328
    cmd_start_video.target_component_id = fixup_component_target(component_id);
×
329

330
    return cmd_start_video;
×
331
}
332

333
MavlinkCommandSender::CommandLong CameraImpl::make_command_stop_video(int32_t component_id)
×
334
{
335
    MavlinkCommandSender::CommandLong cmd_stop_video{};
×
336

337
    cmd_stop_video.command = MAV_CMD_VIDEO_STOP_CAPTURE;
×
338
    cmd_stop_video.params.maybe_param1 = 0.f; // Reserved, set to 0
×
339
    cmd_stop_video.target_component_id = fixup_component_target(component_id);
×
340

341
    return cmd_stop_video;
×
342
}
343

344
MavlinkCommandSender::CommandLong
345
CameraImpl::make_command_set_camera_mode(int32_t component_id, float mavlink_mode)
2✔
346
{
347
    MavlinkCommandSender::CommandLong cmd_set_camera_mode{};
2✔
348

349
    cmd_set_camera_mode.command = MAV_CMD_SET_CAMERA_MODE;
2✔
350
    cmd_set_camera_mode.params.maybe_param1 = 0.0f; // Reserved, set to 0
2✔
351
    cmd_set_camera_mode.params.maybe_param2 = mavlink_mode;
2✔
352
    cmd_set_camera_mode.target_component_id = fixup_component_target(component_id);
2✔
353

354
    return cmd_set_camera_mode;
2✔
355
}
356

357
MavlinkCommandSender::CommandLong
358
CameraImpl::make_command_start_video_streaming(int32_t component_id, int32_t stream_id)
×
359
{
360
    MavlinkCommandSender::CommandLong cmd_start_video_streaming{};
×
361

362
    cmd_start_video_streaming.command = MAV_CMD_VIDEO_START_STREAMING;
×
363
    cmd_start_video_streaming.params.maybe_param1 = static_cast<float>(stream_id);
×
364
    cmd_start_video_streaming.target_component_id = fixup_component_target(component_id);
×
365

366
    return cmd_start_video_streaming;
×
367
}
368

369
MavlinkCommandSender::CommandLong
370
CameraImpl::make_command_stop_video_streaming(int32_t component_id, int32_t stream_id)
×
371
{
372
    MavlinkCommandSender::CommandLong cmd_stop_video_streaming{};
×
373

374
    cmd_stop_video_streaming.command = MAV_CMD_VIDEO_STOP_STREAMING;
×
375
    cmd_stop_video_streaming.params.maybe_param1 = static_cast<float>(stream_id);
×
376
    cmd_stop_video_streaming.target_component_id = fixup_component_target(component_id);
×
377

378
    return cmd_stop_video_streaming;
×
379
}
380

381
Camera::Result CameraImpl::take_photo(int32_t component_id)
1✔
382
{
383
    // Take 1 photo only with no interval
384
    auto cmd_take_photo = make_command_take_photo(component_id, 0.f, 1.0f);
1✔
385

386
    return camera_result_from_command_result(_system_impl->send_command(cmd_take_photo));
1✔
387
}
388

389
Camera::Result CameraImpl::zoom_out_start(int32_t component_id)
×
390
{
391
    auto cmd = make_command_zoom_out(component_id);
×
392

393
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
394
}
395

396
Camera::Result CameraImpl::zoom_in_start(int32_t component_id)
×
397
{
398
    auto cmd = make_command_zoom_in(component_id);
×
399

400
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
401
}
402

403
Camera::Result CameraImpl::zoom_stop(int32_t component_id)
×
404
{
405
    auto cmd = make_command_zoom_stop(component_id);
×
406

407
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
408
}
409

410
Camera::Result CameraImpl::zoom_range(int32_t component_id, float range)
×
411
{
412
    auto cmd = make_command_zoom_range(component_id, range);
×
413

414
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
415
}
416

417
Camera::Result
418
CameraImpl::track_point(int32_t component_id, float point_x, float point_y, float radius)
×
419
{
420
    auto cmd = make_command_track_point(component_id, point_x, point_y, radius);
×
421

422
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
423
}
424

425
Camera::Result CameraImpl::track_rectangle(
×
426
    int32_t component_id,
427
    float top_left_x,
428
    float top_left_y,
429
    float bottom_right_x,
430
    float bottom_right_y)
431
{
432
    auto cmd = make_command_track_rectangle(
×
433
        component_id, top_left_x, top_left_y, bottom_right_x, bottom_right_y);
434

435
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
436
}
437

438
Camera::Result CameraImpl::track_stop(int32_t component_id)
×
439
{
440
    auto cmd = make_command_track_stop(component_id);
×
441

442
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
443
}
444

445
Camera::Result CameraImpl::focus_in_start(int32_t component_id)
×
446
{
447
    auto cmd = make_command_focus_in(component_id);
×
448

449
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
450
}
451

452
Camera::Result CameraImpl::focus_out_start(int32_t component_id)
×
453
{
454
    auto cmd = make_command_focus_out(component_id);
×
455

456
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
457
}
458

459
Camera::Result CameraImpl::focus_stop(int32_t component_id)
×
460
{
461
    auto cmd = make_command_focus_stop(component_id);
×
462

463
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
464
}
465

466
Camera::Result CameraImpl::focus_range(int32_t component_id, float range)
×
467
{
468
    auto cmd = make_command_focus_range(component_id, range);
×
469

470
    return camera_result_from_command_result(_system_impl->send_command(cmd));
×
471
}
472

473
Camera::Result CameraImpl::start_photo_interval(int32_t component_id, float interval_s)
1✔
474
{
475
    auto cmd_take_photo_time_lapse = make_command_take_photo(component_id, interval_s, 0.f);
1✔
476

477
    return camera_result_from_command_result(_system_impl->send_command(cmd_take_photo_time_lapse));
1✔
478
}
479

480
Camera::Result CameraImpl::stop_photo_interval(int32_t component_id)
1✔
481
{
482
    auto cmd_stop_photo_interval = make_command_stop_photo(component_id);
1✔
483

484
    return camera_result_from_command_result(_system_impl->send_command(cmd_stop_photo_interval));
1✔
485
}
486

487
Camera::Result CameraImpl::start_video(int32_t component_id)
×
488
{
489
    // Capture status rate is not set
490
    auto cmd_start_video = make_command_start_video(component_id, 0.f);
×
491

492
    return camera_result_from_command_result(_system_impl->send_command(cmd_start_video));
×
493
}
494

495
Camera::Result CameraImpl::stop_video(int32_t component_id)
×
496
{
497
    auto cmd_stop_video = make_command_stop_video(component_id);
×
498

499
    return camera_result_from_command_result(_system_impl->send_command(cmd_stop_video));
×
500
}
501

502
void CameraImpl::zoom_in_start_async(int32_t component_id, const Camera::ResultCallback& callback)
×
503
{
504
    auto cmd = make_command_zoom_in(component_id);
×
505

506
    _system_impl->send_command_async(
×
507
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
508
            receive_command_result(result, callback);
×
509
        });
×
510
}
×
511

512
void CameraImpl::zoom_out_start_async(int32_t component_id, const Camera::ResultCallback& callback)
×
513
{
514
    auto cmd = make_command_zoom_out(component_id);
×
515

516
    _system_impl->send_command_async(
×
517
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
518
            receive_command_result(result, callback);
×
519
        });
×
520
}
×
521

522
void CameraImpl::zoom_stop_async(int32_t component_id, const Camera::ResultCallback& callback)
×
523
{
524
    auto cmd = make_command_zoom_stop(component_id);
×
525

526
    _system_impl->send_command_async(
×
527
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
528
            receive_command_result(result, callback);
×
529
        });
×
530
}
×
531

532
void CameraImpl::zoom_range_async(
×
533
    int32_t component_id, float range, const Camera::ResultCallback& callback)
534
{
535
    auto cmd = make_command_zoom_range(component_id, range);
×
536

537
    _system_impl->send_command_async(
×
538
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
539
            receive_command_result(result, callback);
×
540
        });
×
541
}
×
542

543
void CameraImpl::track_point_async(
×
544
    int32_t component_id,
545
    float point_x,
546
    float point_y,
547
    float radius,
548
    const Camera::ResultCallback& callback)
549
{
550
    auto cmd = make_command_track_point(component_id, point_x, point_y, radius);
×
551

552
    _system_impl->send_command_async(
×
553
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
554
            receive_command_result(result, callback);
×
555
        });
×
556
}
×
557

558
void CameraImpl::track_rectangle_async(
×
559
    int32_t component_id,
560
    float top_left_x,
561
    float top_left_y,
562
    float bottom_right_x,
563
    float bottom_right_y,
564
    const Camera::ResultCallback& callback)
565
{
566
    auto cmd = make_command_track_rectangle(
×
567
        component_id, top_left_x, top_left_y, bottom_right_x, bottom_right_y);
568

569
    _system_impl->send_command_async(
×
570
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
571
            receive_command_result(result, callback);
×
572
        });
×
573
}
×
574

575
void CameraImpl::track_stop_async(int32_t component_id, const Camera::ResultCallback& callback)
×
576
{
577
    auto cmd = make_command_track_stop(component_id);
×
578

579
    _system_impl->send_command_async(
×
580
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
581
            receive_command_result(result, callback);
×
582
        });
×
583
}
×
584

585
void CameraImpl::focus_in_start_async(int32_t component_id, const Camera::ResultCallback& callback)
×
586
{
587
    auto cmd = make_command_focus_in(component_id);
×
588

589
    _system_impl->send_command_async(
×
590
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
591
            receive_command_result(result, callback);
×
592
        });
×
593
}
×
594

595
void CameraImpl::focus_out_start_async(int32_t component_id, const Camera::ResultCallback& callback)
×
596
{
597
    auto cmd = make_command_focus_out(component_id);
×
598

599
    _system_impl->send_command_async(
×
600
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
601
            receive_command_result(result, callback);
×
602
        });
×
603
}
×
604

605
void CameraImpl::focus_stop_async(int32_t component_id, const Camera::ResultCallback& callback)
×
606
{
607
    auto cmd = make_command_focus_stop(component_id);
×
608

609
    _system_impl->send_command_async(
×
610
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
611
            receive_command_result(result, callback);
×
612
        });
×
613
}
×
614

615
void CameraImpl::focus_range_async(
×
616
    int32_t component_id, float range, const Camera::ResultCallback& callback)
617
{
618
    auto cmd = make_command_focus_range(component_id, range);
×
619

620
    _system_impl->send_command_async(
×
621
        cmd, [this, callback](MavlinkCommandSender::Result result, float) {
×
622
            receive_command_result(result, callback);
×
623
        });
×
624
}
×
625

626
void CameraImpl::take_photo_async(int32_t component_id, const Camera::ResultCallback& callback)
×
627
{
628
    // Take 1 photo only with no interval
629
    auto cmd_take_photo = make_command_take_photo(component_id, 0.f, 1.0f);
×
630

631
    _system_impl->send_command_async(
×
632
        cmd_take_photo, [this, callback](MavlinkCommandSender::Result result, float) {
×
633
            receive_command_result(result, callback);
×
634
        });
×
635
}
×
636

637
void CameraImpl::start_photo_interval_async(
×
638
    int32_t component_id, float interval_s, const Camera::ResultCallback& callback)
639
{
640
    auto cmd_take_photo_time_lapse = make_command_take_photo(component_id, interval_s, 0.f);
×
641

642
    _system_impl->send_command_async(
×
643
        cmd_take_photo_time_lapse, [this, callback](MavlinkCommandSender::Result result, float) {
×
644
            receive_command_result(result, callback);
×
645
        });
×
646
}
×
647

648
void CameraImpl::stop_photo_interval_async(
×
649
    int32_t component_id, const Camera::ResultCallback& callback)
650
{
651
    auto cmd_stop_photo_interval = make_command_stop_photo(component_id);
×
652

653
    _system_impl->send_command_async(
×
654
        cmd_stop_photo_interval, [this, callback](MavlinkCommandSender::Result result, float) {
×
655
            receive_command_result(result, callback);
×
656
        });
×
657
}
×
658

659
void CameraImpl::start_video_async(int32_t component_id, const Camera::ResultCallback& callback)
×
660
{
661
    // Capture status rate is not set
662
    auto cmd_start_video = make_command_start_video(component_id, 0.f);
×
663

664
    _system_impl->send_command_async(
×
665
        cmd_start_video, [this, callback](MavlinkCommandSender::Result result, float) {
×
666
            receive_command_result(result, callback);
×
667
        });
×
668
}
×
669

670
void CameraImpl::stop_video_async(int32_t component_id, const Camera::ResultCallback& callback)
×
671
{
672
    auto cmd_stop_video = make_command_stop_video(component_id);
×
673

674
    _system_impl->send_command_async(
×
675
        cmd_stop_video, [this, callback](MavlinkCommandSender::Result result, float) {
×
676
            receive_command_result(result, callback);
×
677
        });
×
678
}
×
679

680
Camera::CameraList CameraImpl::camera_list()
109✔
681
{
682
    std::lock_guard lock(_mutex);
109✔
683

684
    return camera_list_with_lock();
109✔
685
}
109✔
686

687
Camera::CameraList CameraImpl::camera_list_with_lock()
121✔
688
{
689
    Camera::CameraList result{};
121✔
690

691
    for (auto& potential_camera : potential_cameras_with_lock()) {
226✔
692
        if (!potential_camera->maybe_information) {
105✔
693
            continue;
×
694
        }
695
        result.cameras.push_back(potential_camera->maybe_information.value());
105✔
696
    }
121✔
697

698
    return result;
121✔
699
}
700

701
Camera::CameraListHandle
702
CameraImpl::subscribe_camera_list(const Camera::CameraListCallback& callback)
1✔
703
{
704
    std::lock_guard lock(_mutex);
1✔
705
    auto handle = _camera_list_subscription_callbacks.subscribe(callback);
1✔
706

707
    notify_camera_list_with_lock();
1✔
708

709
    return handle;
2✔
710
}
1✔
711

712
void CameraImpl::unsubscribe_camera_list(Camera::CameraListHandle handle)
1✔
713
{
714
    std::lock_guard lock(_mutex);
1✔
715
    _camera_list_subscription_callbacks.unsubscribe(handle);
1✔
716
}
1✔
717

718
void CameraImpl::notify_camera_list_with_lock()
12✔
719
{
720
    // Build the camera-list snapshot synchronously while the caller holds _mutex,
721
    // then post each subscriber's callback individually to the user callback thread.
722
    //
723
    // The previous implementation used call_user_callback([&](){...}), which posted
724
    // the entire work item to the user callback thread.  That lambda ran *after* the
725
    // inner FTP/HTTP download callback returned.  The main thread could observe the
726
    // loaded definition, destroy the Camera plugin, and free CameraImpl before the
727
    // [&] lambda had a chance to run.  The lambda then accessed freed CameraImpl
728
    // members (_camera_list_subscription_callbacks, _potential_cameras), causing a
729
    // heap use-after-free that manifested as a __stack_chk_fail canary failure.
730
    //
731
    // With this fix the snapshot is built and each subscriber's call_user_callback
732
    // is posted while the caller still holds _mutex (i.e. CameraImpl is alive).
733
    // The per-subscriber func lambdas capture only the snapshot value and the
734
    // system_impl pointer, neither of which is a CameraImpl member, so they are
735
    // safe to run even after CameraImpl is destroyed.
736
    auto snapshot = camera_list_with_lock();
12✔
737
    _camera_list_subscription_callbacks.queue(
12✔
738
        snapshot, [this](const auto& func) { _system_impl->call_user_callback(func); });
2✔
739
}
12✔
740

741
Camera::Result CameraImpl::start_video_streaming(int32_t component_id, int32_t stream_id)
×
742
{
743
    auto command = make_command_start_video_streaming(component_id, stream_id);
×
744

745
    return camera_result_from_command_result(_system_impl->send_command(command));
×
746
}
747

748
Camera::Result CameraImpl::stop_video_streaming(int32_t component_id, int32_t stream_id)
×
749
{
750
    auto command = make_command_stop_video_streaming(component_id, stream_id);
×
751

752
    return camera_result_from_command_result(_system_impl->send_command(command));
×
753
}
754

755
void CameraImpl::request_slower()
11✔
756
{
757
    std::lock_guard lock(_mutex);
11✔
758

759
    for (auto& camera : _potential_cameras) {
21✔
760
        _system_impl->mavlink_request_message().request(
20✔
761
            MAVLINK_MSG_ID_VIDEO_STREAM_INFORMATION,
762
            fixup_component_target(camera.component_id),
10✔
763
            nullptr);
764
    }
765
}
11✔
766

767
void CameraImpl::request_faster()
12✔
768
{
769
    std::lock_guard lock(_mutex);
12✔
770

771
    for (auto& camera : _potential_cameras) {
23✔
772
        _system_impl->mavlink_request_message().request(
22✔
773
            MAVLINK_MSG_ID_VIDEO_STREAM_STATUS,
774
            fixup_component_target(camera.component_id),
11✔
775
            nullptr);
776

777
        _system_impl->mavlink_request_message().request(
22✔
778
            MAVLINK_MSG_ID_CAMERA_CAPTURE_STATUS,
779
            fixup_component_target(camera.component_id),
11✔
780
            nullptr);
781

782
        _system_impl->mavlink_request_message().request(
22✔
783
            MAVLINK_MSG_ID_STORAGE_INFORMATION,
784
            fixup_component_target(camera.component_id),
11✔
785
            nullptr);
786

787
        _system_impl->mavlink_request_message().request(
22✔
788
            MAVLINK_MSG_ID_CAMERA_SETTINGS, fixup_component_target(camera.component_id), nullptr);
11✔
789
    }
790
}
12✔
791

792
Camera::VideoStreamInfoHandle
793
CameraImpl::subscribe_video_stream_info(const Camera::VideoStreamInfoCallback& callback)
1✔
794
{
795
    std::lock_guard lock(_mutex);
1✔
796

797
    auto handle = _video_stream_info_subscription_callbacks.subscribe(callback);
1✔
798

799
    notify_video_stream_info_for_all_with_lock();
1✔
800

801
    return handle;
2✔
802
}
1✔
803

804
void CameraImpl::unsubscribe_video_stream_info(Camera::VideoStreamInfoHandle handle)
×
805
{
806
    std::lock_guard lock(_mutex);
×
807
    _video_stream_info_subscription_callbacks.unsubscribe(handle);
×
808
}
×
809

810
Camera::Result
811
CameraImpl::camera_result_from_command_result(const MavlinkCommandSender::Result command_result)
7✔
812
{
813
    switch (command_result) {
7✔
814
        case MavlinkCommandSender::Result::Success:
7✔
815
            return Camera::Result::Success;
7✔
816
        case MavlinkCommandSender::Result::NoSystem:
×
817
            // FALLTHROUGH
818
        case MavlinkCommandSender::Result::ConnectionError:
819
            // FALLTHROUGH
820
        case MavlinkCommandSender::Result::Busy:
821
            // FALLTHROUGH
822
        case MavlinkCommandSender::Result::Failed:
823
            return Camera::Result::Error;
×
824
        case MavlinkCommandSender::Result::Denied:
×
825
            // FALLTHROUGH
826
        case MavlinkCommandSender::Result::TemporarilyRejected:
827
            return Camera::Result::Denied;
×
828
        case MavlinkCommandSender::Result::Timeout:
×
829
            return Camera::Result::Timeout;
×
830
        case MavlinkCommandSender::Result::Unsupported:
×
831
            return Camera::Result::ActionUnsupported;
×
NEW
832
        case MavlinkCommandSender::Result::InProgress:
×
NEW
833
            return Camera::Result::InProgress;
×
UNCOV
834
        case MavlinkCommandSender::Result::Cancelled:
×
835
        default:
836
            return Camera::Result::Unknown;
×
837
    }
838
}
839

840
Camera::Result CameraImpl::camera_result_from_parameter_result(
×
841
    const MavlinkParameterClient::Result parameter_result)
842
{
843
    switch (parameter_result) {
×
844
        case MavlinkParameterClient::Result::Success:
×
845
            return Camera::Result::Success;
×
846
        case MavlinkParameterClient::Result::Timeout:
×
847
            return Camera::Result::Timeout;
×
848
        case MavlinkParameterClient::Result::WrongType:
×
849
            // FALLTHROUGH
850
        case MavlinkParameterClient::Result::ParamNameTooLong:
851
            // FALLTHROUGH
852
        case MavlinkParameterClient::Result::NotFound:
853
            // FALLTHROUGH
854
        case MavlinkParameterClient::Result::ValueUnsupported:
855
            return Camera::Result::WrongArgument;
×
856
        case MavlinkParameterClient::Result::ConnectionError:
×
857
            // FALLTHROUGH
858
        case MavlinkParameterClient::Result::Failed:
859
            // FALLTHROUGH
860
        case MavlinkParameterClient::Result::UnknownError:
861
            return Camera::Result::Error;
×
862
        default:
×
863
            return Camera::Result::Unknown;
×
864
    }
865
}
866

867
Camera::Result CameraImpl::set_mode(int32_t component_id, const Camera::Mode mode)
2✔
868
{
869
    const float mavlink_mode = to_mavlink_camera_mode(mode);
2✔
870
    auto cmd_set_camera_mode = make_command_set_camera_mode(component_id, mavlink_mode);
2✔
871
    const auto command_result = _system_impl->send_command(cmd_set_camera_mode);
2✔
872
    const auto camera_result = camera_result_from_command_result(command_result);
2✔
873

874
    if (camera_result == Camera::Result::Success) {
2✔
875
        std::lock_guard lock(_mutex);
2✔
876
        auto maybe_potential_camera =
877
            maybe_potential_camera_for_component_id_with_lock(component_id, 0);
2✔
878
        if (maybe_potential_camera != nullptr) {
2✔
879
            save_camera_mode_with_lock(*maybe_potential_camera, mode);
2✔
880
        }
881
    }
2✔
882

883
    return camera_result;
2✔
884
}
885

886
void CameraImpl::save_camera_mode_with_lock(PotentialCamera& potential_camera, Camera::Mode mode)
13✔
887
{
888
    potential_camera.mode = mode;
13✔
889

890
    // If there is a camera definition which is the case if we are in this
891
    // function, and if CAM_MODE is defined there, then we reuse that type.
892
    // Otherwise, we hardcode it to `uint32_t`.
893

894
    // Note that it could be that the camera definition defines options
895
    // different from {PHOTO, VIDEO}, in which case the mode received from
896
    // CAMERA_SETTINGS will be wrong.
897

898
    if (!potential_camera.camera_definition) {
13✔
899
        return;
10✔
900
    }
901

902
    ParamValue value;
3✔
903
    if (potential_camera.camera_definition->get_setting("CAM_MODE", value)) {
3✔
904
        if (value.is<uint8_t>()) {
×
905
            value.set<uint8_t>(static_cast<uint8_t>(mode));
×
906
        } else if (value.is<int8_t>()) {
×
907
            value.set<int8_t>(static_cast<int8_t>(mode));
×
908
        } else if (value.is<uint16_t>()) {
×
909
            value.set<uint16_t>(static_cast<uint16_t>(mode));
×
910
        } else if (value.is<int16_t>()) {
×
911
            value.set<int16_t>(static_cast<int16_t>(mode));
×
912
        } else if (value.is<uint32_t>()) {
×
913
            value.set<uint32_t>(static_cast<uint32_t>(mode));
×
914
        } else if (value.is<int32_t>()) {
×
915
            value.set<int32_t>(static_cast<int32_t>(mode));
×
916
        } else if (value.is<uint64_t>()) {
×
917
            value.set<uint64_t>(static_cast<uint64_t>(mode));
×
918
        } else if (value.is<int64_t>()) {
×
919
            value.set<int64_t>(static_cast<int64_t>(mode));
×
920
        } else if (value.is<float>()) {
×
921
            value.set<float>(static_cast<float>(mode));
×
922
        } else if (value.is<double>()) {
×
923
            value.set<double>(static_cast<double>(mode));
×
924
        }
925
    } else {
926
        value.set<uint32_t>(static_cast<uint32_t>(mode));
3✔
927
    }
928

929
    potential_camera.camera_definition->set_setting("CAM_MODE", value);
3✔
930
    refresh_params_with_lock(potential_camera, false);
3✔
931
    notify_mode_with_lock(potential_camera);
3✔
932
}
3✔
933

934
float CameraImpl::to_mavlink_camera_mode(const Camera::Mode mode)
2✔
935
{
936
    switch (mode) {
2✔
937
        case Camera::Mode::Photo:
1✔
938
            return CAMERA_MODE_IMAGE;
1✔
939
        case Camera::Mode::Video:
1✔
940
            return CAMERA_MODE_VIDEO;
1✔
941
        default:
×
942
        case Camera::Mode::Unknown:
943
            return NAN;
×
944
    }
945
}
946

947
void CameraImpl::set_mode_async(
×
948
    int32_t component_id, const Camera::Mode mode, const Camera::ResultCallback& callback)
949
{
950
    const auto mavlink_mode = to_mavlink_camera_mode(mode);
×
951
    auto cmd_set_camera_mode = make_command_set_camera_mode(component_id, mavlink_mode);
×
952

953
    _system_impl->send_command_async(
×
954
        cmd_set_camera_mode,
955
        [this, callback, mode, component_id](MavlinkCommandSender::Result result, float progress) {
×
956
            UNUSED(progress);
×
957
            receive_set_mode_command_result(result, callback, mode, component_id);
×
958
        });
×
959
}
×
960

961
Camera::ModeHandle CameraImpl::subscribe_mode(const Camera::ModeCallback& callback)
×
962
{
963
    std::lock_guard lock(_mutex);
×
964
    auto handle = _mode_subscription_callbacks.subscribe(callback);
×
965

966
    notify_mode_for_all_with_lock();
×
967

968
    return handle;
×
969
}
×
970

971
void CameraImpl::unsubscribe_mode(Camera::ModeHandle handle)
×
972
{
973
    std::lock_guard lock(_mutex);
×
974
    _mode_subscription_callbacks.unsubscribe(handle);
×
975
}
×
976

977
Camera::StorageHandle CameraImpl::subscribe_storage(const Camera::StorageCallback& callback)
1✔
978
{
979
    std::lock_guard lock(_mutex);
1✔
980
    auto handle = _storage_subscription_callbacks.subscribe(callback);
1✔
981

982
    notify_storage_for_all_with_lock();
1✔
983

984
    return handle;
2✔
985
}
1✔
986

987
void CameraImpl::unsubscribe_storage(Camera::StorageHandle handle)
×
988
{
989
    std::lock_guard lock(_mutex);
×
990
    _storage_subscription_callbacks.unsubscribe(handle);
×
991
}
×
992

993
Camera::CaptureInfoHandle
994
CameraImpl::subscribe_capture_info(const Camera::CaptureInfoCallback& callback)
2✔
995
{
996
    std::lock_guard lock(_mutex);
2✔
997
    return _capture_info_callbacks.subscribe(callback);
2✔
998
}
2✔
999

1000
void CameraImpl::unsubscribe_capture_info(Camera::CaptureInfoHandle handle)
2✔
1001
{
1002
    std::lock_guard lock(_mutex);
2✔
1003
    _capture_info_callbacks.unsubscribe(handle);
2✔
1004
}
2✔
1005

1006
void CameraImpl::process_heartbeat(const mavlink_message_t& message)
28✔
1007
{
1008
    // Check for potential camera
1009
    std::lock_guard lock(_mutex);
28✔
1010
    auto found =
1011
        std::any_of(_potential_cameras.begin(), _potential_cameras.end(), [&](const auto& item) {
28✔
1012
            return item.component_id == message.compid;
17✔
1013
        });
1014

1015
    if (!found) {
28✔
1016
        _potential_cameras.emplace_back(message.compid);
11✔
1017
        check_potential_cameras_with_lock();
11✔
1018
    }
1019
}
28✔
1020

1021
void CameraImpl::check_potential_cameras_with_lock()
11✔
1022
{
1023
    for (auto& potential_camera : _potential_cameras) {
22✔
1024
        // First step, get information if we don't already have it.
1025
        if (!potential_camera.maybe_information) {
11✔
1026
            request_camera_information(potential_camera.component_id);
11✔
1027
            potential_camera.information_requested = true;
11✔
1028
        }
1029
    }
1030
}
11✔
1031

1032
void CameraImpl::process_camera_capture_status(const mavlink_message_t& message)
15✔
1033
{
1034
    mavlink_camera_capture_status_t camera_capture_status;
1035
    mavlink_msg_camera_capture_status_decode(&message, &camera_capture_status);
15✔
1036

1037
    std::lock_guard lock(_mutex);
15✔
1038
    auto maybe_potential_camera = maybe_potential_camera_for_component_id_with_lock(
15✔
1039
        message.compid, camera_capture_status.camera_device_id);
15✔
1040

1041
    if (maybe_potential_camera == nullptr) {
15✔
1042
        return;
×
1043
    }
1044

1045
    auto& camera = *maybe_potential_camera;
15✔
1046

1047
    // If image_count got smaller, consider that the storage was formatted.
1048
    if (camera_capture_status.image_count < camera.capture_status.image_count) {
15✔
1049
        LogInfo() << "Seems like storage was formatted, setting state accordingly";
×
1050
        reset_following_format_storage_with_lock(camera);
×
1051
    }
1052

1053
    camera.storage.video_on = (camera_capture_status.video_status == 1);
15✔
1054
    camera.storage.photo_interval_on =
15✔
1055
        (camera_capture_status.image_status == 2 || camera_capture_status.image_status == 3);
15✔
1056
    camera.capture_status.received_camera_capture_status = true;
15✔
1057
    camera.storage.recording_time_s =
15✔
1058
        static_cast<float>(camera_capture_status.recording_time_ms) / 1e3f;
15✔
1059

1060
    camera.capture_status.image_count = camera_capture_status.image_count;
15✔
1061

1062
    if (camera.capture_status.image_count_at_connection == -1) {
15✔
1063
        camera.capture_status.image_count_at_connection = camera_capture_status.image_count;
10✔
1064
    }
1065
}
15✔
1066

1067
void CameraImpl::process_storage_information(const mavlink_message_t& message)
2✔
1068
{
1069
    mavlink_storage_information_t storage_information;
1070
    mavlink_msg_storage_information_decode(&message, &storage_information);
2✔
1071

1072
    std::lock_guard lock(_mutex);
2✔
1073
    // TODO: should STORAGE_INFORMATION have a camera_device_id?
1074
    auto maybe_potential_camera =
1075
        maybe_potential_camera_for_component_id_with_lock(message.compid, 0);
2✔
1076

1077
    if (maybe_potential_camera == nullptr) {
2✔
1078
        return;
×
1079
    }
1080

1081
    auto& camera = *maybe_potential_camera;
2✔
1082

1083
    camera.storage.storage_status = storage_status_from_mavlink(storage_information.status);
2✔
1084
    camera.storage.available_storage_mib = storage_information.available_capacity;
2✔
1085
    camera.storage.used_storage_mib = storage_information.used_capacity;
2✔
1086
    camera.storage.total_storage_mib = storage_information.total_capacity;
2✔
1087
    camera.storage.storage_id = storage_information.storage_id;
2✔
1088
    camera.storage.storage_type = storage_type_from_mavlink(storage_information.type);
2✔
1089
    camera.received_storage = true;
2✔
1090

1091
    notify_storage_with_lock(camera);
2✔
1092
}
2✔
1093

1094
Camera::Storage::StorageStatus CameraImpl::storage_status_from_mavlink(const int storage_status)
2✔
1095
{
1096
    switch (storage_status) {
2✔
1097
        case STORAGE_STATUS_EMPTY:
×
1098
            return Camera::Storage::StorageStatus::NotAvailable;
×
1099
        case STORAGE_STATUS_UNFORMATTED:
×
1100
            return Camera::Storage::StorageStatus::Unformatted;
×
1101
        case STORAGE_STATUS_READY:
2✔
1102
            return Camera::Storage::StorageStatus::Formatted;
2✔
1103
            break;
1104
        case STORAGE_STATUS_NOT_SUPPORTED:
×
1105
            return Camera::Storage::StorageStatus::NotSupported;
×
1106
        default:
×
1107
            LogErr() << "Unknown storage status received.";
×
1108
            return Camera::Storage::StorageStatus::NotSupported;
×
1109
    }
1110
}
1111

1112
Camera::Storage::StorageType CameraImpl::storage_type_from_mavlink(const int storage_type)
2✔
1113
{
1114
    switch (storage_type) {
2✔
1115
        default:
×
1116
            LogErr() << "Unknown storage_type enum value: " << storage_type;
×
1117
        // FALLTHROUGH
1118
        case STORAGE_TYPE_UNKNOWN:
2✔
1119
            return mavsdk::Camera::Storage::StorageType::Unknown;
2✔
1120
        case STORAGE_TYPE_USB_STICK:
×
1121
            return mavsdk::Camera::Storage::StorageType::UsbStick;
×
1122
        case STORAGE_TYPE_SD:
×
1123
            return mavsdk::Camera::Storage::StorageType::Sd;
×
1124
        case STORAGE_TYPE_MICROSD:
×
1125
            return mavsdk::Camera::Storage::StorageType::Microsd;
×
1126
        case STORAGE_TYPE_HD:
×
1127
            return mavsdk::Camera::Storage::StorageType::Hd;
×
1128
        case STORAGE_TYPE_OTHER:
×
1129
            return mavsdk::Camera::Storage::StorageType::Other;
×
1130
    }
1131
}
1132

1133
void CameraImpl::process_camera_image_captured(const mavlink_message_t& message)
4✔
1134
{
1135
    mavlink_camera_image_captured_t image_captured;
1136
    mavlink_msg_camera_image_captured_decode(&message, &image_captured);
4✔
1137

1138
    std::lock_guard lock(_mutex);
4✔
1139
    auto maybe_potential_camera =
1140
        maybe_potential_camera_for_component_id_with_lock(message.compid, image_captured.camera_id);
4✔
1141

1142
    if (maybe_potential_camera == nullptr) {
4✔
1143
        return;
×
1144
    }
1145

1146
    auto& camera = *maybe_potential_camera;
4✔
1147

1148
    mavsdk::Quaternion quaternion{};
4✔
1149
    quaternion.w = image_captured.q[0];
4✔
1150
    quaternion.x = image_captured.q[1];
4✔
1151
    quaternion.y = image_captured.q[2];
4✔
1152
    quaternion.z = image_captured.q[3];
4✔
1153
    auto euler = to_euler_angle_from_quaternion(quaternion);
4✔
1154

1155
    Camera::CaptureInfo capture_info = {};
4✔
1156
    capture_info.component_id = camera.component_id;
4✔
1157
    capture_info.position.latitude_deg = image_captured.lat / 1e7;
4✔
1158
    capture_info.position.longitude_deg = image_captured.lon / 1e7;
4✔
1159
    capture_info.position.absolute_altitude_m = static_cast<float>(image_captured.alt) / 1e3f;
4✔
1160
    capture_info.position.relative_altitude_m =
4✔
1161
        static_cast<float>(image_captured.relative_alt) / 1e3f;
4✔
1162
    capture_info.time_utc_us = image_captured.time_utc;
4✔
1163
    capture_info.attitude_quaternion.w = image_captured.q[0];
4✔
1164
    capture_info.attitude_quaternion.x = image_captured.q[1];
4✔
1165
    capture_info.attitude_quaternion.y = image_captured.q[2];
4✔
1166
    capture_info.attitude_quaternion.z = image_captured.q[3];
4✔
1167
    capture_info.attitude_euler_angle.roll_deg = euler.roll_deg;
4✔
1168
    capture_info.attitude_euler_angle.pitch_deg = euler.pitch_deg;
4✔
1169
    capture_info.attitude_euler_angle.yaw_deg = euler.yaw_deg;
4✔
1170
    capture_info.file_url = std::string(image_captured.file_url);
4✔
1171
    capture_info.is_success = (image_captured.capture_result == 1);
4✔
1172
    capture_info.index = image_captured.image_index;
4✔
1173

1174
    camera.capture_status.photo_list.insert(
4✔
1175
        std::make_pair(image_captured.image_index, capture_info));
8✔
1176

1177
    // Notify user if a new image has been captured.
1178
    if (camera.capture_info.last_advertised_image_index < capture_info.index) {
4✔
1179
        _capture_info_callbacks.queue(
4✔
1180
            capture_info, [this](const auto& func) { _system_impl->call_user_callback(func); });
4✔
1181

1182
        if (camera.capture_info.last_advertised_image_index != -1) {
4✔
1183
            // Save captured indices that have been dropped to request later, however, don't
1184
            // do it from the very beginning as there might be many photos from a previous
1185
            // time that we don't want to request.
1186
            for (int i = camera.capture_info.last_advertised_image_index + 1;
2✔
1187
                 i < capture_info.index;
2✔
1188
                 ++i) {
1189
                if (camera.capture_info.missing_image_retries.find(i) ==
×
1190
                    camera.capture_info.missing_image_retries.end()) {
×
1191
                    camera.capture_info.missing_image_retries[i] = 0;
×
1192
                }
1193
            }
1194
        }
1195

1196
        camera.capture_info.last_advertised_image_index = capture_info.index;
4✔
1197
    }
1198

1199
    else if (auto it = camera.capture_info.missing_image_retries.find(capture_info.index);
×
1200
             it != camera.capture_info.missing_image_retries.end()) {
×
1201
        _capture_info_callbacks.queue(
×
1202
            capture_info, [this](const auto& func) { _system_impl->call_user_callback(func); });
×
1203
        camera.capture_info.missing_image_retries.erase(it);
×
1204
    }
1205
}
4✔
1206

1207
void CameraImpl::request_missing_capture_info()
46✔
1208
{
1209
    std::lock_guard lock(_mutex);
46✔
1210
    for (auto& potential_camera : _potential_cameras) {
89✔
1211
        // Clean out entries once we have tried 3 times.
1212
        for (auto it = potential_camera.capture_info.missing_image_retries.begin();
43✔
1213
             it != potential_camera.capture_info.missing_image_retries.end();
43✔
1214
             /* ++it */) {
1215
            if (it->second > 3) {
×
1216
                it = potential_camera.capture_info.missing_image_retries.erase(it);
×
1217
            } else {
1218
                ++it;
×
1219
            }
1220
        }
1221

1222
        // Request a few entries, 3 each time.
1223
        for (unsigned i = 0; i < 3; ++i) {
172✔
1224
            if (!potential_camera.capture_info.missing_image_retries.empty()) {
129✔
1225
                auto it_lowest_retries = std::min_element(
×
1226
                    potential_camera.capture_info.missing_image_retries.begin(),
1227
                    potential_camera.capture_info.missing_image_retries.end());
1228
                _system_impl->mavlink_request_message().request(
×
1229
                    MAVLINK_MSG_ID_CAMERA_IMAGE_CAPTURED,
1230
                    potential_camera.component_id,
×
1231
                    nullptr,
1232
                    it_lowest_retries->first);
×
1233
                it_lowest_retries->second += 1;
×
1234
            }
1235
        }
1236
    }
1237
}
46✔
1238

1239
void CameraImpl::process_camera_settings(const mavlink_message_t& message)
11✔
1240
{
1241
    mavlink_camera_settings_t camera_settings;
1242
    mavlink_msg_camera_settings_decode(&message, &camera_settings);
11✔
1243

1244
    std::lock_guard lock(_mutex);
11✔
1245
    auto maybe_potential_camera = maybe_potential_camera_for_component_id_with_lock(
11✔
1246
        message.compid, camera_settings.camera_device_id);
11✔
1247

1248
    if (maybe_potential_camera == nullptr) {
11✔
1249
        return;
×
1250
    }
1251

1252
    save_camera_mode_with_lock(*maybe_potential_camera, to_camera_mode(camera_settings.mode_id));
11✔
1253
}
11✔
1254

1255
Camera::Mode CameraImpl::to_camera_mode(const uint8_t mavlink_camera_mode)
11✔
1256
{
1257
    switch (mavlink_camera_mode) {
11✔
1258
        case CAMERA_MODE_IMAGE:
11✔
1259
            return Camera::Mode::Photo;
11✔
1260
        case CAMERA_MODE_VIDEO:
×
1261
            return Camera::Mode::Video;
×
1262
        default:
×
1263
            return Camera::Mode::Unknown;
×
1264
    }
1265
}
1266

1267
void CameraImpl::process_camera_information(const mavlink_message_t& message)
11✔
1268
{
1269
    mavlink_camera_information_t camera_information;
1270
    mavlink_msg_camera_information_decode(&message, &camera_information);
11✔
1271

1272
    // Make sure all strings are zero terminated, so we don't overrun anywhere.
1273
    camera_information.vendor_name[sizeof(camera_information.vendor_name) - 1] = '\0';
11✔
1274
    camera_information.model_name[sizeof(camera_information.model_name) - 1] = '\0';
11✔
1275
    camera_information.cam_definition_uri[sizeof(camera_information.cam_definition_uri) - 1] = '\0';
11✔
1276

1277
    Camera::Information new_information{};
11✔
1278
    // TODO: Check the case for 1-6.
1279
    new_information.component_id = message.compid;
11✔
1280
    new_information.vendor_name = reinterpret_cast<char*>(camera_information.vendor_name);
11✔
1281
    new_information.model_name = reinterpret_cast<char*>(camera_information.model_name);
11✔
1282
    new_information.focal_length_mm = camera_information.focal_length;
11✔
1283
    new_information.horizontal_sensor_size_mm = camera_information.sensor_size_h;
11✔
1284
    new_information.vertical_sensor_size_mm = camera_information.sensor_size_v;
11✔
1285
    new_information.horizontal_resolution_px = camera_information.resolution_h;
11✔
1286
    new_information.vertical_resolution_px = camera_information.resolution_v;
11✔
1287

1288
    std::lock_guard lock(_mutex);
11✔
1289

1290
    auto potential_camera =
1291
        std::find_if(_potential_cameras.begin(), _potential_cameras.end(), [&](auto& item) {
11✔
1292
            return item.component_id == message.compid;
11✔
1293
        });
1294

1295
    if (potential_camera == _potential_cameras.end()) {
11✔
1296
        _potential_cameras.emplace_back(message.compid);
×
1297
        potential_camera = std::prev(_potential_cameras.end());
×
1298
    }
1299

1300
    // We need a copy of the component ID inside the information.
1301
    potential_camera->component_id = new_information.component_id;
11✔
1302
    potential_camera->maybe_information = new_information;
11✔
1303
    potential_camera->camera_definition_url = camera_information.cam_definition_uri;
11✔
1304
    potential_camera->camera_definition_version = camera_information.cam_definition_version;
11✔
1305
    check_camera_definition_with_lock(*potential_camera);
11✔
1306
}
11✔
1307

1308
void CameraImpl::check_camera_definition_with_lock(PotentialCamera& potential_camera)
11✔
1309
{
1310
    const std::string url = potential_camera.camera_definition_url;
11✔
1311

1312
    if (potential_camera.camera_definition_url.empty()) {
11✔
1313
        potential_camera.camera_definition_result = Camera::Result::Unavailable;
7✔
1314
        notify_camera_list_with_lock();
7✔
1315
        return; // No URL means no definition to fetch.
7✔
1316
    }
1317

1318
    const auto& info = potential_camera.maybe_information.value();
4✔
1319
    auto file_cache_tag = replace_non_ascii_and_whitespace(
1320
        std::string("camera_definition-") + info.model_name + "_" + info.vendor_name + "-" +
8✔
1321
        std::to_string(potential_camera.camera_definition_version) + ".xml");
12✔
1322

1323
    std::optional<std::filesystem::path> cached_file_option{};
4✔
1324
    if (_file_cache) {
4✔
1325
        cached_file_option = _file_cache->access(file_cache_tag);
4✔
1326
    }
1327

1328
    if (cached_file_option) {
4✔
1329
        LogInfo() << "Using cached file " << cached_file_option.value();
2✔
1330
        load_camera_definition_with_lock(potential_camera, cached_file_option.value());
2✔
1331
        potential_camera.is_fetching_camera_definition = false;
2✔
1332
        potential_camera.camera_definition_result = Camera::Result::Success;
2✔
1333
        notify_camera_list_with_lock();
2✔
1334

1335
    } else {
1336
        potential_camera.is_fetching_camera_definition = true;
2✔
1337

1338
        if (url.empty()) {
2✔
UNCOV
1339
            LogInfo() << "No camera definition URL available";
×
UNCOV
1340
            potential_camera.camera_definition_result = Camera::Result::ProtocolUnsupported;
×
UNCOV
1341
            notify_camera_list_with_lock();
×
1342

1343
        } else if (starts_with(url, "http://") || starts_with(url, "https://")) {
2✔
1344
#if BUILD_WITHOUT_CURL == 1
1345
            potential_camera.camera_definition_result = Camera::Result::ProtocolUnsupported;
1346
            notify_camera_list_with_lock();
1347
#else
1348
            if (_http_loader == nullptr) {
×
1349
                _http_loader = std::make_unique<HttpLoader>();
×
1350
            }
1351
            LogInfo() << "Downloading camera definition from: " << url;
×
1352

1353
            auto component_id = potential_camera.component_id;
×
1354

1355
            auto download_path = _tmp_download_path / file_cache_tag;
×
1356

1357
            _http_loader->download_async(
×
1358
                url,
1359
                download_path.string(),
×
NEW
1360
                [download_path,
×
1361
                 file_cache_tag,
1362
                 component_id,
NEW
1363
                 mutex_ptr = _mutex_keep_alive,
×
NEW
1364
                 alive = _alive,
×
NEW
1365
                 this](int progress, HttpStatus status, CURLcode curl_code) mutable {
×
1366
                    LogDebug() << "Download progress: " << progress
×
1367
                               << ", status: " << static_cast<int>(status)
×
1368
                               << ", curl_code: " << std::to_string(curl_code);
×
1369

NEW
1370
                    std::lock_guard lock(*mutex_ptr);
×
1371
                    // Bail out if CameraImpl was already destroyed
NEW
1372
                    if (!alive->load()) {
×
NEW
1373
                        return;
×
1374
                    }
1375
                    auto maybe_potential_camera =
1376
                        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
×
1377
                    if (maybe_potential_camera == nullptr) {
×
1378
                        LogErr() << "Failed to find camera.";
×
NEW
1379
                        return;
×
1380
                    }
1381

1382
                    if (status == HttpStatus::Error) {
×
1383
                        LogErr() << "File download failed with result "
×
1384
                                 << std::to_string(curl_code);
×
1385
                        maybe_potential_camera->is_fetching_camera_definition = false;
×
1386
                        maybe_potential_camera->camera_definition_result = Camera::Result::Error;
×
1387
                        notify_camera_list_with_lock();
×
1388

1389
                    } else if (status == HttpStatus::Finished) {
×
1390
                        LogDebug() << "File download finished " << download_path;
×
NEW
1391
                        if (download_path.extension() == ".xz") {
×
NEW
1392
                            auto decompressed = download_path;
×
NEW
1393
                            decompressed.replace_extension(".extracted");
×
NEW
1394
                            if (InflateLZMA::inflateLZMAFile(download_path, decompressed)) {
×
NEW
1395
                                std::filesystem::remove(download_path);
×
NEW
1396
                                download_path = decompressed;
×
1397
                            } else {
NEW
1398
                                LogErr()
×
NEW
1399
                                    << "Failed to decompress camera definition: " << download_path;
×
NEW
1400
                                maybe_potential_camera->is_fetching_camera_definition = false;
×
NEW
1401
                                maybe_potential_camera->camera_definition_result =
×
1402
                                    Camera::Result::Error;
NEW
1403
                                notify_camera_list_with_lock();
×
NEW
1404
                                return;
×
1405
                            }
NEW
1406
                        }
×
UNCOV
1407
                        if (_file_cache) {
×
1408
                            // Cache the file (this will move/remove the temp file as well)
1409
                            download_path = _file_cache->insert(file_cache_tag, download_path)
×
1410
                                                .value_or(download_path);
×
1411
                            LogDebug() << "Cached path: " << download_path;
×
1412
                        }
1413
                        load_camera_definition_with_lock(*maybe_potential_camera, download_path);
×
1414
                        maybe_potential_camera->is_fetching_camera_definition = false;
×
1415
                        maybe_potential_camera->camera_definition_result = Camera::Result::Success;
×
1416
                        notify_camera_list_with_lock();
×
1417
                    }
1418
                });
×
1419
#endif
1420
        } else if (starts_with(url, "mftp://") || starts_with(url, "mavlinkftp://")) {
2✔
1421
            LogInfo() << "Download file: " << url << " using MAVLink FTP...";
2✔
1422

1423
            auto component_id = potential_camera.component_id;
2✔
1424

1425
            auto downloaded_filename = strip_prefix(strip_prefix(url, "mavlinkftp://"), "mftp://");
4✔
1426

1427
            _system_impl->mavlink_ftp_client().download_async(
6✔
1428
                downloaded_filename,
1429
                _tmp_download_path.string(),
4✔
1430
                false,
1431
                [file_cache_tag, downloaded_filename, component_id, this](
22✔
1432
                    MavlinkFtpClient::ClientResult client_result,
1433
                    MavlinkFtpClient::ProgressData progress_data) mutable {
8✔
1434
                    // TODO: check if we still exist
1435
                    if (client_result == MavlinkFtpClient::ClientResult::Next) {
22✔
1436
                        LogDebug()
20✔
1437
                            << "Mavlink FTP download progress: "
20✔
1438
                            << 100 * progress_data.bytes_transferred / progress_data.total_bytes
40✔
1439
                            << " %";
20✔
1440
                        return;
20✔
1441
                    }
1442

1443
                    // Use call_user_callback to defer callback execution and avoid deadlock
1444
                    _system_impl->call_user_callback([file_cache_tag,
7✔
1445
                                                      downloaded_filename,
1446
                                                      component_id,
1447
                                                      client_result,
1448
                                                      mutex_ptr = _mutex_keep_alive,
1449
                                                      alive = _alive,
1450
                                                      this]() {
1451
                        std::lock_guard lock(*mutex_ptr);
1452
                        // Bail out if CameraImpl was already destroyed
1453
                        if (!alive->load()) {
1454
                            return;
1455
                        }
1456
                        auto maybe_potential_camera =
1457
                            maybe_potential_camera_for_component_id_with_lock(component_id, 0);
1458
                        if (maybe_potential_camera == nullptr) {
1459
                            LogErr() << "Failed to find camera with ID " << component_id;
1460
                            return;
1461
                        }
1462

1463
                        if (client_result != MavlinkFtpClient::ClientResult::Success) {
1464
                            LogErr() << "File download failed with result " << client_result;
1465
                            maybe_potential_camera->is_fetching_camera_definition = false;
1466
                            maybe_potential_camera->camera_definition_result =
1467
                                Camera::Result::Error;
1468
                            notify_camera_list_with_lock();
1469
                            return;
1470
                        }
1471

1472
                        auto downloaded_filepath = _tmp_download_path / downloaded_filename;
1473

1474
                        LogDebug() << "File download finished to " << downloaded_filepath;
1475
                        if (downloaded_filepath.extension() == ".xz") {
1476
                            auto decompressed = downloaded_filepath;
1477
                            decompressed.replace_extension(".extracted");
1478
                            if (InflateLZMA::inflateLZMAFile(downloaded_filepath, decompressed)) {
1479
                                std::filesystem::remove(downloaded_filepath);
1480
                                downloaded_filepath = decompressed;
1481
                            } else {
1482
                                LogErr() << "Failed to decompress camera definition: "
1483
                                         << downloaded_filepath;
1484
                                maybe_potential_camera->is_fetching_camera_definition = false;
1485
                                maybe_potential_camera->camera_definition_result =
1486
                                    Camera::Result::Error;
1487
                                notify_camera_list_with_lock();
1488
                                return;
1489
                            }
1490
                        }
1491
                        if (_file_cache) {
1492
                            // Cache the file (this will move/remove the temp file as well)
1493
                            downloaded_filepath =
1494
                                _file_cache->insert(file_cache_tag, downloaded_filepath)
1495
                                    .value_or(downloaded_filepath);
1496
                            LogDebug() << "Cached path: " << downloaded_filepath;
1497
                        }
1498
                        load_camera_definition_with_lock(
1499
                            *maybe_potential_camera, downloaded_filepath);
1500
                        maybe_potential_camera->is_fetching_camera_definition = false;
1501
                        maybe_potential_camera->camera_definition_result = Camera::Result::Success;
1502
                        notify_camera_list_with_lock();
1503
                    });
1504
                });
1505
        } else {
2✔
1506
            LogErr() << "Unknown protocol for URL: " << url;
×
1507
            potential_camera.camera_definition_result = Camera::Result::ProtocolUnsupported;
×
1508
            notify_camera_list_with_lock();
×
1509
        }
1510
    }
1511
}
11✔
1512

1513
void CameraImpl::load_camera_definition_with_lock(
4✔
1514
    PotentialCamera& potential_camera, const std::filesystem::path& path)
1515
{
1516
    if (potential_camera.camera_definition == nullptr) {
4✔
1517
        potential_camera.camera_definition = std::make_unique<CameraDefinition>();
4✔
1518
    }
1519

1520
    if (!potential_camera.camera_definition->load_file(path.string())) {
4✔
1521
        LogErr() << "Failed to load camera definition: " << path;
×
1522
        // We can't keep something around that's not loaded correctly.
1523
        potential_camera.camera_definition = nullptr;
×
1524
        return;
×
1525
    }
1526

1527
    // We assume default settings initially and then load the params one by one.
1528
    // This way we can start using it straightaway.
1529
    potential_camera.camera_definition->reset_to_default_settings(true);
4✔
1530

1531
    refresh_params_with_lock(potential_camera, true);
4✔
1532
}
1533

1534
void CameraImpl::process_video_information(const mavlink_message_t& message)
1✔
1535
{
1536
    mavlink_video_stream_information_t received_video_info;
1537
    mavlink_msg_video_stream_information_decode(&message, &received_video_info);
1✔
1538

1539
    std::lock_guard lock(_mutex);
1✔
1540
    auto maybe_potential_camera = maybe_potential_camera_for_component_id_with_lock(
1✔
1541
        message.compid, received_video_info.camera_device_id);
1✔
1542

1543
    if (maybe_potential_camera == nullptr) {
1✔
1544
        return;
×
1545
    }
1546

1547
    auto& camera = *maybe_potential_camera;
1✔
1548

1549
    // TODO: use stream_id and count
1550
    camera.video_stream_info.status =
1✔
1551
        (received_video_info.flags & VIDEO_STREAM_STATUS_FLAGS_RUNNING ?
1✔
1552
             Camera::VideoStreamInfo::VideoStreamStatus::InProgress :
1553
             Camera::VideoStreamInfo::VideoStreamStatus::NotRunning);
1554
    camera.video_stream_info.spectrum =
1✔
1555
        (received_video_info.flags & VIDEO_STREAM_STATUS_FLAGS_THERMAL ?
1✔
1556
             Camera::VideoStreamInfo::VideoStreamSpectrum::Infrared :
1557
             Camera::VideoStreamInfo::VideoStreamSpectrum::VisibleLight);
1558

1559
    auto& video_stream_info = camera.video_stream_info.settings;
1✔
1560
    video_stream_info.frame_rate_hz = received_video_info.framerate;
1✔
1561
    video_stream_info.horizontal_resolution_pix = received_video_info.resolution_h;
1✔
1562
    video_stream_info.vertical_resolution_pix = received_video_info.resolution_v;
1✔
1563
    video_stream_info.bit_rate_b_s = received_video_info.bitrate;
1✔
1564
    video_stream_info.rotation_deg = received_video_info.rotation;
1✔
1565
    video_stream_info.horizontal_fov_deg = static_cast<float>(received_video_info.hfov);
1✔
1566
    video_stream_info.uri = received_video_info.uri;
1✔
1567
    camera.video_stream_info.stream_id = received_video_info.stream_id;
1✔
1568
    camera.received_video_stream_info = true;
1✔
1569

1570
    notify_video_stream_info_with_lock(camera);
1✔
1571
}
1✔
1572

1573
void CameraImpl::process_video_stream_status(const mavlink_message_t& message)
1✔
1574
{
1575
    mavlink_video_stream_status_t received_video_stream_status;
1576
    mavlink_msg_video_stream_status_decode(&message, &received_video_stream_status);
1✔
1577

1578
    std::lock_guard lock(_mutex);
1✔
1579
    auto maybe_potential_camera = maybe_potential_camera_for_component_id_with_lock(
1✔
1580
        message.compid, received_video_stream_status.camera_device_id);
1✔
1581

1582
    if (maybe_potential_camera == nullptr) {
1✔
1583
        return;
×
1584
    }
1585

1586
    auto& camera = *maybe_potential_camera;
1✔
1587

1588
    camera.video_stream_info.status =
1✔
1589
        (received_video_stream_status.flags & VIDEO_STREAM_STATUS_FLAGS_RUNNING ?
1✔
1590
             Camera::VideoStreamInfo::VideoStreamStatus::InProgress :
1591
             Camera::VideoStreamInfo::VideoStreamStatus::NotRunning);
1592
    camera.video_stream_info.spectrum =
1✔
1593
        (received_video_stream_status.flags & VIDEO_STREAM_STATUS_FLAGS_THERMAL ?
1✔
1594
             Camera::VideoStreamInfo::VideoStreamSpectrum::Infrared :
1595
             Camera::VideoStreamInfo::VideoStreamSpectrum::VisibleLight);
1596

1597
    auto& video_stream_info = camera.video_stream_info.settings;
1✔
1598
    video_stream_info.frame_rate_hz = received_video_stream_status.framerate;
1✔
1599
    video_stream_info.horizontal_resolution_pix = received_video_stream_status.resolution_h;
1✔
1600
    video_stream_info.vertical_resolution_pix = received_video_stream_status.resolution_v;
1✔
1601
    video_stream_info.bit_rate_b_s = received_video_stream_status.bitrate;
1✔
1602
    video_stream_info.rotation_deg = received_video_stream_status.rotation;
1✔
1603
    video_stream_info.horizontal_fov_deg = static_cast<float>(received_video_stream_status.hfov);
1✔
1604

1605
    // We only set this when we get the static information rather than just the status.
1606
    // camera.received_video_stream_info = true;
1607

1608
    notify_video_stream_info_with_lock(camera);
1✔
1609
}
1✔
1610

1611
void CameraImpl::notify_video_stream_info_for_all_with_lock()
1✔
1612
{
1613
    for (auto& camera : _potential_cameras) {
2✔
1614
        notify_video_stream_info_with_lock(camera);
1✔
1615
    }
1616
}
1✔
1617

1618
void CameraImpl::notify_video_stream_info_with_lock(PotentialCamera& camera)
3✔
1619
{
1620
    if (!camera.received_video_stream_info) {
3✔
1621
        return;
×
1622
    }
1623

1624
    _video_stream_info_subscription_callbacks.queue(
9✔
1625
        Camera::VideoStreamUpdate{camera.component_id, camera.video_stream_info},
6✔
1626
        [this](const auto& func) { _system_impl->call_user_callback(func); });
1✔
1627
}
1628

1629
void CameraImpl::notify_storage_for_all_with_lock()
1✔
1630
{
1631
    for (auto& potential_camera : potential_cameras_with_lock()) {
2✔
1632
        notify_storage_with_lock(*potential_camera);
1✔
1633
    }
1✔
1634
}
1✔
1635

1636
void CameraImpl::notify_storage_with_lock(PotentialCamera& camera)
3✔
1637
{
1638
    if (!camera.received_storage) {
3✔
UNCOV
1639
        return;
×
1640
    }
1641

1642
    _storage_subscription_callbacks.queue(
9✔
1643
        Camera::StorageUpdate{camera.component_id, camera.storage},
6✔
1644
        [this](const auto& func) { _system_impl->call_user_callback(func); });
2✔
1645
}
1646

1647
void CameraImpl::receive_command_result(
2✔
1648
    MavlinkCommandSender::Result command_result, const Camera::ResultCallback& callback) const
1649
{
1650
    Camera::Result camera_result = camera_result_from_command_result(command_result);
2✔
1651

1652
    if (callback) {
2✔
1653
        _system_impl->call_user_callback([callback, camera_result]() { callback(camera_result); });
4✔
1654
    }
1655
}
2✔
1656

1657
void CameraImpl::receive_set_mode_command_result(
×
1658
    const MavlinkCommandSender::Result command_result,
1659
    const Camera::ResultCallback& callback,
1660
    const Camera::Mode mode,
1661
    int32_t component_id)
1662
{
1663
    Camera::Result camera_result = camera_result_from_command_result(command_result);
×
1664

1665
    if (callback) {
×
1666
        _system_impl->call_user_callback([callback, camera_result]() { callback(camera_result); });
×
1667
    }
1668

1669
    if (camera_result == Camera::Result::Success) {
×
1670
        std::lock_guard lock(_mutex);
×
1671
        auto maybe_potential_camera =
1672
            maybe_potential_camera_for_component_id_with_lock(component_id, 0);
×
1673
        if (maybe_potential_camera != nullptr) {
×
1674
            save_camera_mode_with_lock(*maybe_potential_camera, mode);
×
1675
        }
1676
    }
×
1677
}
×
1678

1679
void CameraImpl::notify_mode_for_all_with_lock()
×
1680
{
1681
    for (auto& camera : potential_cameras_with_lock()) {
×
1682
        notify_mode_with_lock(*camera);
×
1683
    }
×
1684
}
×
1685

1686
void CameraImpl::notify_mode_with_lock(PotentialCamera& camera)
3✔
1687
{
1688
    _mode_subscription_callbacks.queue(
6✔
1689
        Camera::ModeUpdate{camera.component_id, camera.mode},
3✔
1690
        [this](const auto& func) { _system_impl->call_user_callback(func); });
×
1691
}
3✔
1692

1693
bool CameraImpl::get_possible_options_with_lock(
106✔
1694
    PotentialCamera& camera, const std::string& setting_id, std::vector<Camera::Option>& options)
1695
{
1696
    options.clear();
106✔
1697

1698
    if (!camera.camera_definition) {
106✔
1699
        LogWarn() << "Error: no camera definition available yet";
×
1700
        return false;
×
1701
    }
1702

1703
    std::vector<ParamValue> values;
106✔
1704
    if (!camera.camera_definition->get_possible_options(setting_id, values)) {
106✔
1705
        return false;
×
1706
    }
1707

1708
    for (const auto& value : values) {
384✔
1709
        Camera::Option option{};
278✔
1710
        option.option_id = value.get_string();
278✔
1711
        if (!camera.camera_definition->is_setting_range(setting_id)) {
278✔
1712
            get_option_str_with_lock(
110✔
1713
                camera, setting_id, option.option_id, option.option_description);
1714
        }
1715
        options.push_back(option);
278✔
1716
    }
278✔
1717

1718
    return !options.empty();
106✔
1719
}
106✔
1720

1721
Camera::Result CameraImpl::set_setting(int32_t component_id, const Camera::Setting& setting)
3✔
1722

1723
{
1724
    auto prom = std::make_shared<std::promise<Camera::Result>>();
3✔
1725
    auto ret = prom->get_future();
3✔
1726

1727
    set_setting_async(
3✔
1728
        component_id, setting, [&prom](Camera::Result result) { prom->set_value(result); });
3✔
1729

1730
    return ret.get();
3✔
1731
}
3✔
1732

1733
void CameraImpl::set_setting_async(
3✔
1734
    int32_t component_id, const Camera::Setting& setting, const Camera::ResultCallback& callback)
1735
{
1736
    set_option_async(component_id, setting.setting_id, setting.option, callback);
3✔
1737
}
3✔
1738

1739
void CameraImpl::set_option_async(
3✔
1740
    int32_t component_id,
1741
    const std::string& setting_id,
1742
    const Camera::Option& option,
1743
    const Camera::ResultCallback& callback)
1744
{
1745
    std::lock_guard lock(_mutex);
3✔
1746
    auto maybe_potential_camera =
1747
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
3✔
1748
    if (maybe_potential_camera == nullptr) {
3✔
1749
        if (callback != nullptr) {
×
1750
            _system_impl->call_user_callback(
×
1751
                [callback]() { callback(Camera::Result::CameraIdInvalid); });
1752
        }
1753
        return;
×
1754
    }
1755

1756
    auto& camera = *maybe_potential_camera;
3✔
1757

1758
    if (camera.camera_definition == nullptr) {
3✔
1759
        if (callback != nullptr) {
×
1760
            _system_impl->call_user_callback(
×
1761
                [callback]() { callback(Camera::Result::Unavailable); });
1762
        }
1763
        return;
×
1764
    }
1765

1766
    // We get it first so that we have the type of the param value.
1767
    ParamValue value;
3✔
1768

1769
    if (camera.camera_definition->is_setting_range(setting_id)) {
3✔
1770
        // TODO: Get type from minimum.
1771
        std::vector<ParamValue> all_values;
×
1772
        if (!camera.camera_definition->get_all_options(setting_id, all_values)) {
×
1773
            if (callback) {
×
1774
                LogErr() << "Could not get all options to get type for range param.";
×
1775
                _system_impl->call_user_callback([callback]() { callback(Camera::Result::Error); });
×
1776
            }
1777
            return;
×
1778
        }
1779

1780
        if (all_values.empty()) {
×
1781
            if (callback) {
×
1782
                LogErr() << "Could not get any options to get type for range param.";
×
1783
                _system_impl->call_user_callback([callback]() { callback(Camera::Result::Error); });
×
1784
            }
1785
            return;
×
1786
        }
1787
        value = all_values[0];
×
1788

1789
        // Now re-use that type. This is quite ugly, but I don't know how we could do better than
1790
        // that.
1791
        if (!value.set_as_same_type(option.option_id)) {
×
1792
            if (callback) {
×
1793
                LogErr() << "Could not set option value to given type.";
×
1794
                _system_impl->call_user_callback([callback]() { callback(Camera::Result::Error); });
×
1795
            }
1796
            return;
×
1797
        }
1798

1799
    } else {
×
1800
        if (!camera.camera_definition->get_option_value(setting_id, option.option_id, value)) {
3✔
1801
            if (callback) {
×
1802
                LogErr() << "Could not get option value.";
×
1803
                _system_impl->call_user_callback([callback]() { callback(Camera::Result::Error); });
×
1804
            }
1805
            return;
×
1806
        }
1807

1808
        std::vector<ParamValue> possible_values;
3✔
1809
        camera.camera_definition->get_possible_options(setting_id, possible_values);
3✔
1810
        bool allowed = false;
3✔
1811
        for (const auto& possible_value : possible_values) {
9✔
1812
            if (value == possible_value) {
6✔
1813
                allowed = true;
3✔
1814
            }
1815
        }
1816
        if (!allowed) {
3✔
1817
            LogErr() << "Setting " << setting_id << "(" << option.option_id << ") not allowed";
×
1818
            if (callback) {
×
1819
                _system_impl->call_user_callback([callback]() { callback(Camera::Result::Error); });
×
1820
            }
1821
            return;
×
1822
        }
1823
    }
3✔
1824

1825
    _system_impl->set_param_async(
6✔
1826
        setting_id,
1827
        value,
1828
        [this, component_id, callback, setting_id, value](MavlinkParameterClient::Result result) {
12✔
1829
            std::lock_guard lock_later(_mutex);
3✔
1830
            auto maybe_potential_camera_later =
1831
                maybe_potential_camera_for_component_id_with_lock(component_id, 0);
3✔
1832
            // We already checked these fields earlier, so we don't check again.
1833
            assert(maybe_potential_camera_later != nullptr);
3✔
1834
            assert(maybe_potential_camera_later->camera_definition != nullptr);
3✔
1835

1836
            auto& camera_later = *maybe_potential_camera_later;
3✔
1837

1838
            if (result != MavlinkParameterClient::Result::Success) {
3✔
1839
                if (callback) {
×
1840
                    _system_impl->call_user_callback([callback, result]() {
×
1841
                        callback(camera_result_from_parameter_result(result));
1842
                    });
1843
                }
1844
                return;
×
1845
            }
1846

1847
            if (!camera_later.camera_definition->set_setting(setting_id, value)) {
3✔
1848
                if (callback) {
×
1849
                    _system_impl->call_user_callback(
×
1850
                        [callback]() { callback(Camera::Result::Error); });
1851
                }
1852
                return;
×
1853
            }
1854

1855
            if (callback) {
3✔
1856
                _system_impl->call_user_callback(
6✔
1857
                    [callback]() { callback(Camera::Result::Success); });
1858
            }
1859
            refresh_params_with_lock(camera_later, false);
3✔
1860
        },
3✔
1861
        this,
1862
        camera.component_id,
3✔
1863
        true);
1864
}
3✔
1865

1866
void CameraImpl::get_setting_async(
2✔
1867
    int32_t component_id,
1868
    const Camera::Setting& setting,
1869
    const Camera::GetSettingCallback& callback)
1870
{
1871
    {
1872
        std::lock_guard lock(_mutex);
2✔
1873
        auto maybe_potential_camera =
1874
            maybe_potential_camera_for_component_id_with_lock(component_id, 0);
2✔
1875
        if (maybe_potential_camera == nullptr) {
2✔
1876
            if (callback != nullptr) {
×
1877
                _system_impl->call_user_callback(
×
1878
                    [callback]() { callback(Camera::Result::CameraIdInvalid, {}); });
1879
            }
1880
            return;
×
1881
        }
1882

1883
        auto& camera = *maybe_potential_camera;
2✔
1884

1885
        if (camera.camera_definition == nullptr) {
2✔
1886
            if (callback != nullptr) {
×
1887
                _system_impl->call_user_callback(
×
1888
                    [callback]() { callback(Camera::Result::Unavailable, {}); });
1889
            }
1890
            return;
×
1891
        }
1892
    }
2✔
1893

1894
    get_option_async(
4✔
1895
        component_id,
1896
        setting.setting_id,
2✔
1897
        [this, callback](Camera::Result result, const Camera::Option& option) {
4✔
1898
            Camera::Setting new_setting{};
2✔
1899
            new_setting.option = option;
2✔
1900
            if (callback) {
2✔
1901
                _system_impl->call_user_callback(
4✔
1902
                    [callback, result, new_setting]() { callback(result, new_setting); });
1903
            }
1904
        });
2✔
1905
}
1906

1907
std::pair<Camera::Result, Camera::Setting>
1908
CameraImpl::get_setting(int32_t component_id, const Camera::Setting& setting)
2✔
1909
{
1910
    auto prom = std::make_shared<std::promise<std::pair<Camera::Result, Camera::Setting>>>();
2✔
1911
    auto ret = prom->get_future();
2✔
1912

1913
    get_setting_async(
2✔
1914
        component_id, setting, [&prom](Camera::Result result, const Camera::Setting& new_setting) {
2✔
1915
            prom->set_value(std::make_pair<>(result, new_setting));
2✔
1916
        });
2✔
1917

1918
    return ret.get();
2✔
1919
}
2✔
1920

1921
Camera::Result
1922
CameraImpl::get_option(int32_t component_id, const std::string& setting_id, Camera::Option& option)
×
1923
{
1924
    auto prom = std::make_shared<std::promise<Camera::Result>>();
×
1925
    auto ret = prom->get_future();
×
1926

1927
    get_option_async(
×
1928
        component_id,
1929
        setting_id,
1930
        [prom, &option](Camera::Result result, const Camera::Option& option_gotten) {
×
1931
            prom->set_value(result);
×
1932
            if (result == Camera::Result::Success) {
×
1933
                option = option_gotten;
×
1934
            }
1935
        });
×
1936

1937
    auto status = ret.wait_for(std::chrono::seconds(1));
×
1938

1939
    if (status == std::future_status::ready) {
×
1940
        return Camera::Result::Success;
×
1941
    } else {
1942
        return Camera::Result::Timeout;
×
1943
    }
1944
}
×
1945

1946
void CameraImpl::get_option_async(
2✔
1947
    int32_t component_id,
1948
    const std::string& setting_id,
1949
    const std::function<void(Camera::Result, const Camera::Option&)>& callback)
1950
{
1951
    std::lock_guard lock(_mutex);
2✔
1952
    auto maybe_potential_camera =
1953
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
2✔
1954
    if (maybe_potential_camera == nullptr) {
2✔
1955
        if (callback != nullptr) {
×
1956
            _system_impl->call_user_callback(
×
1957
                [callback]() { callback(Camera::Result::CameraIdInvalid, {}); });
1958
        }
1959
        return;
×
1960
    }
1961

1962
    auto& camera = *maybe_potential_camera;
2✔
1963

1964
    if (camera.camera_definition == nullptr) {
2✔
1965
        if (callback != nullptr) {
×
1966
            _system_impl->call_user_callback(
×
1967
                [callback]() { callback(Camera::Result::Unavailable, {}); });
1968
        }
1969
        return;
×
1970
    }
1971

1972
    ParamValue value;
2✔
1973
    // We should have this cached and don't need to get the param.
1974
    if (camera.camera_definition->get_setting(setting_id, value)) {
2✔
1975
        if (callback) {
2✔
1976
            Camera::Option new_option{};
2✔
1977
            new_option.option_id = value.get_string();
2✔
1978
            if (!camera.camera_definition->is_setting_range(setting_id)) {
2✔
1979
                get_option_str_with_lock(
1✔
1980
                    camera, setting_id, new_option.option_id, new_option.option_description);
1981
            }
1982
            const auto temp_callback = callback;
2✔
1983
            _system_impl->call_user_callback([temp_callback, new_option]() {
4✔
1984
                temp_callback(Camera::Result::Success, new_option);
1985
            });
1986
        }
2✔
1987
    } else {
1988
        // If this still happens, we request the param, but also complain.
1989
        LogWarn() << "Setting '" << setting_id << "' not found.";
×
1990
        if (callback) {
×
1991
            const auto temp_callback = callback;
×
1992
            _system_impl->call_user_callback(
×
1993
                [temp_callback]() { temp_callback(Camera::Result::Error, {}); });
1994
        }
×
1995
    }
1996
}
2✔
1997

1998
Camera::CurrentSettingsHandle
1999
CameraImpl::subscribe_current_settings(const Camera::CurrentSettingsCallback& callback)
1✔
2000
{
2001
    std::lock_guard lock(_mutex);
1✔
2002
    auto handle = _subscribe_current_settings_callbacks.subscribe(callback);
1✔
2003

2004
    notify_current_settings_for_all_with_lock();
1✔
2005
    return handle;
2✔
2006
}
1✔
2007

2008
void CameraImpl::unsubscribe_current_settings(Camera::CurrentSettingsHandle handle)
1✔
2009
{
2010
    std::lock_guard lock(_mutex);
1✔
2011
    _subscribe_current_settings_callbacks.unsubscribe(handle);
1✔
2012
}
1✔
2013

2014
Camera::PossibleSettingOptionsHandle CameraImpl::subscribe_possible_setting_options(
1✔
2015
    const Camera::PossibleSettingOptionsCallback& callback)
2016
{
2017
    std::lock_guard lock(_mutex);
1✔
2018
    auto handle = _subscribe_possible_setting_options_callbacks.subscribe(callback);
1✔
2019

2020
    notify_possible_setting_options_for_all_with_lock();
1✔
2021
    return handle;
2✔
2022
}
1✔
2023

2024
void CameraImpl::unsubscribe_possible_setting_options(Camera::PossibleSettingOptionsHandle handle)
×
2025
{
2026
    _subscribe_possible_setting_options_callbacks.unsubscribe(handle);
×
2027
}
×
2028

2029
void CameraImpl::notify_current_settings_for_all_with_lock()
1✔
2030
{
2031
    for (auto& potential_camera : potential_cameras_with_lock()) {
2✔
2032
        if (potential_camera->camera_definition != nullptr) {
1✔
2033
            notify_current_settings_with_lock(*potential_camera);
1✔
2034
        }
2035
    }
1✔
2036
}
1✔
2037

2038
void CameraImpl::notify_possible_setting_options_for_all_with_lock()
1✔
2039
{
2040
    for (auto& potential_camera : potential_cameras_with_lock()) {
2✔
2041
        if (potential_camera->camera_definition != nullptr) {
1✔
2042
            notify_possible_setting_options_with_lock(*potential_camera);
1✔
2043
        }
2044
    }
1✔
2045
}
1✔
2046

2047
void CameraImpl::notify_current_settings_with_lock(PotentialCamera& potential_camera)
4✔
2048
{
2049
    assert(potential_camera.camera_definition != nullptr);
4✔
2050

2051
    if (_subscribe_current_settings_callbacks.empty()) {
4✔
2052
        return;
2✔
2053
    }
2054

2055
    auto possible_setting_options = get_possible_setting_options_with_lock(potential_camera);
2✔
2056
    if (possible_setting_options.first != Camera::Result::Success) {
2✔
2057
        LogErr() << "Could not get possible settings in current options subscription.";
×
2058
        return;
×
2059
    }
2060

2061
    auto& camera = potential_camera;
2✔
2062

2063
    Camera::CurrentSettingsUpdate update{};
2✔
2064
    update.component_id = potential_camera.component_id;
2✔
2065

2066
    for (auto& possible_setting : possible_setting_options.second) {
23✔
2067
        // use the cache for this, presumably we updated it right before.
2068
        ParamValue value;
21✔
2069
        if (camera.camera_definition->get_setting(possible_setting.setting_id, value)) {
21✔
2070
            Camera::Setting setting{};
18✔
2071
            setting.setting_id = possible_setting.setting_id;
18✔
2072
            setting.is_range =
18✔
2073
                camera.camera_definition->is_setting_range(possible_setting.setting_id);
18✔
2074
            get_setting_str_with_lock(camera, setting.setting_id, setting.setting_description);
18✔
2075
            setting.option.option_id = value.get_string();
18✔
2076
            if (!camera.camera_definition->is_setting_range(possible_setting.setting_id)) {
18✔
2077
                get_option_str_with_lock(
9✔
2078
                    camera,
2079
                    setting.setting_id,
2080
                    setting.option.option_id,
2081
                    setting.option.option_description);
2082
            }
2083
            update.current_settings.push_back(setting);
18✔
2084
        }
18✔
2085
    }
21✔
2086

2087
    _subscribe_current_settings_callbacks.queue(
2✔
2088
        update, [this](const auto& func) { _system_impl->call_user_callback(func); });
2✔
2089
}
2✔
2090

2091
void CameraImpl::notify_possible_setting_options_with_lock(PotentialCamera& potential_camera)
4✔
2092
{
2093
    assert(potential_camera.camera_definition != nullptr);
4✔
2094

2095
    if (_subscribe_possible_setting_options_callbacks.empty()) {
4✔
2096
        return;
2✔
2097
    }
2098

2099
    Camera::PossibleSettingOptionsUpdate update{};
2✔
2100
    update.component_id = potential_camera.component_id;
2✔
2101

2102
    auto setting_options = get_possible_setting_options_with_lock(potential_camera);
2✔
2103
    if (setting_options.second.empty()) {
2✔
2104
        return;
×
2105
    }
2106

2107
    update.setting_options = setting_options.second;
2✔
2108

2109
    _subscribe_possible_setting_options_callbacks.queue(
2✔
2110
        update, [this](const auto& func) { _system_impl->call_user_callback(func); });
2✔
2111
}
2✔
2112

2113
std::pair<Camera::Result, std::vector<Camera::SettingOptions>>
2114
CameraImpl::get_possible_setting_options(int32_t component_id)
4✔
2115
{
2116
    std::pair<Camera::Result, std::vector<Camera::SettingOptions>> result;
4✔
2117

2118
    std::lock_guard lock(_mutex);
4✔
2119
    auto maybe_potential_camera =
2120
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
4✔
2121
    if (maybe_potential_camera == nullptr) {
4✔
2122
        result.first = Camera::Result::CameraIdInvalid;
×
2123
        return result;
×
2124
    }
2125

2126
    auto& camera = *maybe_potential_camera;
4✔
2127
    if (camera.is_fetching_camera_definition || camera.camera_definition == nullptr) {
4✔
2128
        result.first = Camera::Result::Unavailable;
×
2129
        return result;
×
2130
    }
2131

2132
    return get_possible_setting_options_with_lock(*maybe_potential_camera);
4✔
2133
}
4✔
2134

2135
std::pair<Camera::Result, std::vector<Camera::SettingOptions>>
2136
CameraImpl::get_possible_setting_options_with_lock(PotentialCamera& potential_camera)
10✔
2137
{
2138
    std::pair<Camera::Result, std::vector<Camera::SettingOptions>> result;
10✔
2139

2140
    std::unordered_map<std::string, ParamValue> possible_settings;
10✔
2141
    assert(potential_camera.camera_definition != nullptr);
10✔
2142

2143
    auto& camera = potential_camera;
10✔
2144

2145
    camera.camera_definition->get_possible_settings(possible_settings);
10✔
2146

2147
    for (auto& possible_setting : possible_settings) {
116✔
2148
        Camera::SettingOptions setting_options{};
106✔
2149
        setting_options.setting_id = possible_setting.first;
106✔
2150
        setting_options.is_range =
106✔
2151
            camera.camera_definition->is_setting_range(possible_setting.first);
106✔
2152
        get_setting_str_with_lock(
106✔
2153
            camera, setting_options.setting_id, setting_options.setting_description);
2154
        get_possible_options_with_lock(camera, possible_setting.first, setting_options.options);
106✔
2155
        result.second.push_back(setting_options);
106✔
2156
    }
106✔
2157

2158
    result.first = Camera::Result::Success;
10✔
2159
    return result;
10✔
2160
}
10✔
2161

2162
void CameraImpl::refresh_params_with_lock(PotentialCamera& potential_camera, bool initial_load)
10✔
2163
{
2164
    assert(potential_camera.camera_definition != nullptr);
10✔
2165

2166
    std::vector<std::pair<std::string, ParamValue>> params;
10✔
2167
    potential_camera.camera_definition->get_unknown_params(params);
10✔
2168

2169
    if (params.empty()) {
10✔
2170
        // We're assuming that we changed one option and this did not cause
2171
        // any other possible settings to change. However, we still would
2172
        // like to notify the current settings with this one change.
2173
        notify_current_settings_with_lock(potential_camera);
3✔
2174
        notify_possible_setting_options_with_lock(potential_camera);
3✔
2175
        return;
3✔
2176
    }
2177

2178
    auto component_id = potential_camera.component_id;
7✔
2179

2180
    unsigned count = 0;
7✔
2181
    for (const auto& param : params) {
89✔
2182
        const std::string& param_name = param.first;
82✔
2183
        const ParamValue& param_value_type = param.second;
82✔
2184
        const bool is_last = (count == params.size() - 1);
82✔
2185
        if (_debugging) {
82✔
2186
            LogDebug() << "Trying to get param: " << param_name;
×
2187
        }
2188
        _system_impl->param_sender(potential_camera.component_id, true)
164✔
2189
            ->get_param_async(
82✔
2190
                param_name,
2191
                param_value_type,
2192
                [component_id, param_name, is_last, this](
32✔
2193
                    MavlinkParameterClient::Result result, const ParamValue& value) {
96✔
2194
                    if (result != MavlinkParameterClient::Result::Success) {
32✔
2195
                        return;
32✔
2196
                    }
2197

2198
                    std::lock_guard lock_later(_mutex);
32✔
2199
                    auto maybe_potential_camera_later =
2200
                        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
32✔
2201
                    // We already checked these fields earlier, so we don't check again.
2202
                    assert(maybe_potential_camera_later != nullptr);
32✔
2203
                    assert(maybe_potential_camera_later->camera_definition != nullptr);
32✔
2204

2205
                    auto& camera_later = *maybe_potential_camera_later;
32✔
2206

2207
                    if (camera_later.camera_definition->set_setting(param_name, value)) {
32✔
2208
                        if (_debugging) {
32✔
2209
                            LogDebug() << "Got setting for " << param_name << ": " << value;
×
2210
                        }
2211
                        return;
32✔
2212
                    }
2213

2214
                    if (is_last) {
×
2215
                        notify_current_settings_with_lock(camera_later);
×
2216
                        notify_possible_setting_options_with_lock(camera_later);
×
2217
                    }
2218
                },
32✔
2219
                this);
2220

2221
        if (initial_load) {
82✔
2222
            subscribe_to_param_changes_with_lock(potential_camera, param_name, param_value_type);
52✔
2223
        }
2224
        ++count;
82✔
2225
    }
2226
}
10✔
2227

2228
void CameraImpl::subscribe_to_param_changes_with_lock(
52✔
2229
    PotentialCamera& camera, std::string param_name, const ParamValue& param_value_type)
2230
{
2231
    auto component_id = camera.component_id;
52✔
2232
    auto changed = [this, component_id, param_name](auto new_param) {
39✔
2233
        if (_debugging) {
13✔
2234
            LogDebug() << "Got changing param: " << param_name << " -> " << new_param;
×
2235
        }
2236

2237
        std::lock_guard lock_later(_mutex);
13✔
2238

2239
        auto maybe_potential_camera_later =
2240
            maybe_potential_camera_for_component_id_with_lock(component_id, 0);
13✔
2241
        // We already checked these fields earlier, so we don't check again.
2242
        assert(maybe_potential_camera_later != nullptr);
13✔
2243
        assert(maybe_potential_camera_later->camera_definition != nullptr);
13✔
2244
        auto& camera_later = *maybe_potential_camera_later;
13✔
2245

2246
        ParamValue param_value;
13✔
2247
        param_value.set<decltype(new_param)>(new_param);
13✔
2248
        camera_later.camera_definition->set_setting(param_name, param_value);
13✔
2249
    };
65✔
2250

2251
    if (param_value_type.is<uint8_t>()) {
52✔
2252
        _system_impl->param_sender(camera.component_id, true)
×
2253
            ->subscribe_param_changed<uint8_t>(param_name, changed, this);
×
2254
    } else if (param_value_type.is<uint8_t>()) {
52✔
2255
        _system_impl->param_sender(camera.component_id, true)
×
2256
            ->subscribe_param_changed<uint16_t>(param_name, changed, this);
×
2257
    } else if (param_value_type.is<uint32_t>()) {
52✔
2258
        _system_impl->param_sender(camera.component_id, true)
×
2259
            ->subscribe_param_changed<uint32_t>(param_name, changed, this);
×
2260
    } else if (param_value_type.is<uint64_t>()) {
52✔
2261
        _system_impl->param_sender(camera.component_id, true)
×
2262
            ->subscribe_param_changed<uint64_t>(param_name, changed, this);
×
2263
    } else if (param_value_type.is<int8_t>()) {
52✔
2264
        _system_impl->param_sender(camera.component_id, true)
×
2265
            ->subscribe_param_changed<int8_t>(param_name, changed, this);
×
2266
    } else if (param_value_type.is<int16_t>()) {
52✔
2267
        _system_impl->param_sender(camera.component_id, true)
×
2268
            ->subscribe_param_changed<int16_t>(param_name, changed, this);
×
2269
    } else if (param_value_type.is<int32_t>()) {
52✔
2270
        _system_impl->param_sender(camera.component_id, true)
52✔
2271
            ->subscribe_param_changed<int32_t>(param_name, changed, this);
52✔
2272
    } else if (param_value_type.is<int64_t>()) {
×
2273
        _system_impl->param_sender(camera.component_id, true)
×
2274
            ->subscribe_param_changed<int64_t>(param_name, changed, this);
×
2275
    } else if (param_value_type.is<float>()) {
×
2276
        _system_impl->param_sender(camera.component_id, true)
×
2277
            ->subscribe_param_changed<float>(param_name, changed, this);
×
2278
    } else if (param_value_type.is<double>()) {
×
2279
        _system_impl->param_sender(camera.component_id, true)
×
2280
            ->subscribe_param_changed<double>(param_name, changed, this);
×
2281
    } else if (param_value_type.is<std::string>()) {
×
2282
        _system_impl->param_sender(camera.component_id, true)
×
2283
            ->subscribe_param_changed<std::string>(param_name, changed, this);
×
2284
    } else {
2285
        LogErr() << "Unknown type for param " << param_name;
×
2286
    }
2287
}
52✔
2288

2289
bool CameraImpl::get_setting_str_with_lock(
145✔
2290
    PotentialCamera& potential_camera, const std::string& setting_id, std::string& description)
2291
{
2292
    if (potential_camera.camera_definition == nullptr) {
145✔
2293
        return false;
×
2294
    }
2295

2296
    return potential_camera.camera_definition->get_setting_str(setting_id, description);
145✔
2297
}
2298

2299
bool CameraImpl::get_option_str_with_lock(
130✔
2300
    PotentialCamera& potential_camera,
2301
    const std::string& setting_id,
2302
    const std::string& option_id,
2303
    std::string& description)
2304
{
2305
    if (potential_camera.camera_definition == nullptr) {
130✔
2306
        return false;
×
2307
    }
2308

2309
    return potential_camera.camera_definition->get_option_str(setting_id, option_id, description);
130✔
2310
}
2311

2312
void CameraImpl::request_camera_information(uint8_t component_id)
11✔
2313
{
2314
    _system_impl->mavlink_request_message().request(
22✔
2315
        MAVLINK_MSG_ID_CAMERA_INFORMATION, fixup_component_target(component_id), nullptr);
11✔
2316
}
11✔
2317

2318
Camera::Result CameraImpl::format_storage(int32_t component_id, int32_t storage_id)
1✔
2319
{
2320
    auto prom = std::make_shared<std::promise<Camera::Result>>();
1✔
2321
    auto ret = prom->get_future();
1✔
2322

2323
    format_storage_async(
1✔
2324
        component_id, storage_id, [prom](Camera::Result result) { prom->set_value(result); });
1✔
2325

2326
    return ret.get();
1✔
2327
}
1✔
2328

2329
void CameraImpl::format_storage_async(
1✔
2330
    int32_t component_id, int32_t storage_id, const Camera::ResultCallback& callback)
2331
{
2332
    MavlinkCommandSender::CommandLong cmd_format{};
1✔
2333

2334
    cmd_format.command = MAV_CMD_STORAGE_FORMAT;
1✔
2335
    cmd_format.params.maybe_param1 = static_cast<float>(storage_id); // storage ID
1✔
2336
    cmd_format.params.maybe_param2 = 1.0f; // format
1✔
2337
    cmd_format.params.maybe_param3 = 1.0f; // clear
1✔
2338
    cmd_format.target_component_id = component_id;
1✔
2339

2340
    _system_impl->send_command_async(
1✔
2341
        cmd_format, [this, callback](MavlinkCommandSender::Result result, float progress) {
2✔
2342
            UNUSED(progress);
1✔
2343

2344
            receive_command_result(result, [this, callback](Camera::Result camera_result) {
1✔
2345
                callback(camera_result);
1✔
2346
            });
1✔
2347
        });
1✔
2348
}
1✔
2349

2350
Camera::Result CameraImpl::reset_settings(int32_t component_id)
1✔
2351
{
2352
    auto prom = std::make_shared<std::promise<Camera::Result>>();
1✔
2353
    auto ret = prom->get_future();
1✔
2354

2355
    reset_settings_async(component_id, [prom](Camera::Result result) { prom->set_value(result); });
2✔
2356

2357
    return ret.get();
1✔
2358
}
1✔
2359

2360
void CameraImpl::reset_settings_async(int32_t component_id, const Camera::ResultCallback& callback)
1✔
2361
{
2362
    MavlinkCommandSender::CommandLong cmd_format{};
1✔
2363

2364
    cmd_format.command = MAV_CMD_RESET_CAMERA_SETTINGS;
1✔
2365
    cmd_format.params.maybe_param1 = 1.0f; // reset
1✔
2366
    cmd_format.target_component_id = component_id;
1✔
2367

2368
    _system_impl->send_command_async(
1✔
2369
        cmd_format, [this, callback](MavlinkCommandSender::Result result, float progress) {
2✔
2370
            UNUSED(progress);
1✔
2371

2372
            receive_command_result(result, [this, callback](Camera::Result camera_result) {
1✔
2373
                callback(camera_result);
1✔
2374
            });
1✔
2375
        });
1✔
2376
}
1✔
2377

2378
void CameraImpl::reset_following_format_storage_with_lock(PotentialCamera& camera)
×
2379
{
2380
    camera.capture_status.photo_list.clear();
×
2381
    camera.capture_status.image_count = 0;
×
2382
    camera.capture_status.image_count_at_connection = 0;
×
2383
    camera.capture_info.last_advertised_image_index = -1;
×
2384
    camera.capture_info.missing_image_retries.clear();
×
2385
}
×
2386

2387
std::pair<Camera::Result, std::vector<Camera::CaptureInfo>>
2388
CameraImpl::list_photos(int32_t component_id, Camera::PhotosRange photos_range)
×
2389
{
2390
    std::promise<std::pair<Camera::Result, std::vector<Camera::CaptureInfo>>> prom;
×
2391
    auto ret = prom.get_future();
×
2392

2393
    list_photos_async(
×
2394
        component_id,
2395
        photos_range,
2396
        [&prom](Camera::Result result, const std::vector<Camera::CaptureInfo>& photo_list) {
×
2397
            prom.set_value(std::make_pair(result, photo_list));
×
2398
        });
×
2399

2400
    return ret.get();
×
2401
}
×
2402

2403
void CameraImpl::list_photos_async(
×
2404
    int32_t component_id,
2405
    Camera::PhotosRange photos_range,
2406
    const Camera::ListPhotosCallback& callback)
2407
{
2408
    if (!callback) {
×
2409
        LogWarn() << "Trying to get a photo list with a null callback, ignoring...";
×
2410
        return;
×
2411
    }
2412

2413
    std::lock_guard lock(_mutex);
×
2414

2415
    auto maybe_potential_camera =
2416
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
×
2417
    if (maybe_potential_camera == nullptr) {
×
2418
        LogWarn() << "Invalid camera ID: " << component_id;
×
2419
        return;
×
2420
    }
2421
    auto& camera = *maybe_potential_camera;
×
2422

2423
    if (camera.capture_status.image_count == -1) {
×
2424
        LogErr() << "Cannot list photos: camera status has not been received yet!";
×
2425
        _system_impl->call_user_callback(
×
2426
            [callback]() { callback(Camera::Result::Error, std::vector<Camera::CaptureInfo>{}); });
2427
        return;
×
2428
    }
2429

2430
    // By default we just try to keep track of photos captured while we were connected.
2431
    // If the API user asks for all, we will start requesting all previous ones.
2432

2433
    if (camera.capture_status.photos_range == Camera::PhotosRange::SinceConnection &&
×
2434
        photos_range == Camera::PhotosRange::All) {
×
2435
        camera.capture_status.photos_range = photos_range;
×
2436

2437
        auto oldest_photo_it = std::min_element(
×
2438
            camera.capture_status.photo_list.begin(),
2439
            camera.capture_status.photo_list.end(),
2440
            [](auto& a, auto& b) { return a.first < b.first; });
×
2441

2442
        if (oldest_photo_it != camera.capture_status.photo_list.end()) {
×
2443
            for (int i = 0; i < oldest_photo_it->first; ++i) {
×
2444
                if (camera.capture_info.missing_image_retries.find(i) ==
×
2445
                    camera.capture_info.missing_image_retries.end()) {
×
2446
                    camera.capture_info.missing_image_retries[i] = 0;
×
2447
                }
2448
            }
2449
        }
2450
    }
2451

2452
    const int start_index = [&, this]() {
×
2453
        switch (photos_range) {
×
2454
            case Camera::PhotosRange::SinceConnection:
×
2455
                return camera.capture_status.image_count_at_connection;
×
2456
            case Camera::PhotosRange::All:
×
2457
            // FALLTHROUGH
2458
            default:
2459
                return 0;
×
2460
        }
2461
    }();
×
2462

2463
    std::vector<Camera::CaptureInfo> photo_list;
×
2464

2465
    for (const auto& capture_info : camera.capture_status.photo_list) {
×
2466
        if (capture_info.first >= start_index) {
×
2467
            photo_list.push_back(capture_info.second);
×
2468
        }
2469
    }
2470

2471
    _system_impl->call_user_callback(
×
2472
        [callback, photo_list]() { callback(Camera::Result::Success, photo_list); });
2473
}
×
2474

2475
std::pair<Camera::Result, Camera::Mode> CameraImpl::get_mode(int32_t component_id)
1✔
2476
{
2477
    std::pair<Camera::Result, Camera::Mode> result{};
1✔
2478

2479
    std::lock_guard lock(_mutex);
1✔
2480

2481
    auto maybe_potential_camera =
2482
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
1✔
2483
    if (maybe_potential_camera == nullptr) {
1✔
2484
        result.first = Camera::Result::CameraIdInvalid;
×
2485
    } else {
2486
        result.first = Camera::Result::Success;
1✔
2487
        result.second = maybe_potential_camera->mode;
1✔
2488
    }
2489

2490
    return result;
2✔
2491
}
1✔
2492

2493
std::pair<Camera::Result, Camera::Storage> CameraImpl::get_storage(int32_t component_id)
60✔
2494
{
2495
    std::pair<Camera::Result, Camera::Storage> result{};
60✔
2496

2497
    std::lock_guard lock(_mutex);
60✔
2498

2499
    auto maybe_potential_camera =
2500
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
60✔
2501
    if (maybe_potential_camera == nullptr) {
60✔
2502
        result.first = Camera::Result::CameraIdInvalid;
×
2503
    } else {
2504
        if (maybe_potential_camera->received_storage) {
60✔
2505
            result.first = Camera::Result::Success;
60✔
2506
            result.second = maybe_potential_camera->storage;
60✔
2507
        } else {
UNCOV
2508
            result.first = Camera::Result::Unavailable;
×
2509
        }
2510
    }
2511

2512
    return result;
60✔
2513
}
60✔
2514

2515
std::pair<Camera::Result, Camera::VideoStreamInfo>
2516
CameraImpl::get_video_stream_info(int32_t component_id)
×
2517
{
2518
    std::pair<Camera::Result, Camera::VideoStreamInfo> result{};
×
2519

2520
    std::lock_guard lock(_mutex);
×
2521

2522
    auto maybe_potential_camera =
2523
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
×
2524
    if (maybe_potential_camera == nullptr) {
×
2525
        result.first = Camera::Result::CameraIdInvalid;
×
2526
    } else {
2527
        result.first = Camera::Result::Success;
×
2528
        result.second = maybe_potential_camera->video_stream_info;
×
2529
    }
2530

2531
    return result;
×
2532
}
×
2533

2534
std::pair<Camera::Result, std::vector<Camera::Setting>>
2535
CameraImpl::get_current_settings(int32_t component_id)
2✔
2536
{
2537
    std::pair<Camera::Result, std::vector<Camera::Setting>> result;
2✔
2538

2539
    std::lock_guard lock(_mutex);
2✔
2540
    auto maybe_potential_camera =
2541
        maybe_potential_camera_for_component_id_with_lock(component_id, 0);
2✔
2542
    if (maybe_potential_camera == nullptr) {
2✔
2543
        result.first = Camera::Result::CameraIdInvalid;
×
2544
        return result;
×
2545
    }
2546

2547
    auto& camera = *maybe_potential_camera;
2✔
2548
    if (camera.is_fetching_camera_definition || camera.camera_definition == nullptr) {
2✔
2549
        result.first = Camera::Result::Unavailable;
×
2550
        return result;
×
2551
    }
2552

2553
    auto possible_setting_options = get_possible_setting_options_with_lock(camera);
2✔
2554
    if (possible_setting_options.first != Camera::Result::Success) {
2✔
2555
        result.first = possible_setting_options.first;
×
2556
        return result;
×
2557
    }
2558

2559
    for (auto& possible_setting : possible_setting_options.second) {
23✔
2560
        // use the cache for this, presumably we updated it right before.
2561
        ParamValue value;
21✔
2562
        if (camera.camera_definition->get_setting(possible_setting.setting_id, value)) {
21✔
2563
            Camera::Setting setting{};
21✔
2564
            setting.setting_id = possible_setting.setting_id;
21✔
2565
            setting.is_range =
21✔
2566
                camera.camera_definition->is_setting_range(possible_setting.setting_id);
21✔
2567
            get_setting_str_with_lock(camera, setting.setting_id, setting.setting_description);
21✔
2568
            setting.option.option_id = value.get_string();
21✔
2569
            if (!camera.camera_definition->is_setting_range(possible_setting.setting_id)) {
21✔
2570
                get_option_str_with_lock(
10✔
2571
                    camera,
2572
                    setting.setting_id,
2573
                    setting.option.option_id,
2574
                    setting.option.option_description);
2575
            }
2576
            result.second.push_back(setting);
21✔
2577
        }
21✔
2578
    }
21✔
2579

2580
    result.first = Camera::Result::Success;
2✔
2581
    return result;
2✔
2582
}
2✔
2583

2584
uint16_t CameraImpl::get_and_increment_capture_sequence(int32_t component_id)
2✔
2585
{
2586
    if (component_id == 0) {
2✔
2587
        // All cameras
2588
        return 0;
×
2589
    }
2590

2591
    std::lock_guard lock(_mutex);
2✔
2592

2593
    for (auto& potential_camera : _potential_cameras) {
2✔
2594
        if (potential_camera.component_id == component_id) {
2✔
2595
            return potential_camera.capture_sequence++;
2✔
2596
        }
2597
    }
2598

2599
    return 0;
×
2600
}
2✔
2601

2602
std::vector<CameraImpl::PotentialCamera*> CameraImpl::potential_cameras_with_lock()
124✔
2603
{
2604
    std::vector<CameraImpl::PotentialCamera*> result;
124✔
2605

2606
    for (auto& potential_camera : _potential_cameras) {
235✔
2607
        if (!potential_camera.maybe_information) {
111✔
2608
            continue;
3✔
2609
        }
2610
        result.push_back(&potential_camera);
108✔
2611
    }
2612

2613
    return result;
124✔
2614
}
2615

2616
CameraImpl::PotentialCamera* CameraImpl::maybe_potential_camera_for_component_id_with_lock(
160✔
2617
    uint8_t component_id, uint8_t camera_device_id)
2618
{
2619
    // Component Ids 1-6 means the camera is connected to the autopilot and the
2620
    // ID is set by the camera_device_id instead.
2621
    if (component_id == 1 && camera_device_id != 0) {
160✔
2622
        component_id = camera_device_id;
×
2623
    }
2624

2625
    const auto it = std::find_if(
160✔
2626
        _potential_cameras.begin(), _potential_cameras.end(), [&](auto& potential_camera) {
160✔
2627
            return potential_camera.component_id == component_id;
160✔
2628
        });
2629

2630
    if (it == _potential_cameras.end()) {
160✔
2631
        return nullptr;
×
2632
    }
2633

2634
    // How to get pointer from iterator?
2635
    return &(*it);
160✔
2636
}
2637

2638
uint8_t CameraImpl::fixup_component_target(uint8_t component_id)
70✔
2639
{
2640
    // Component Ids 1-6 means the camera is connected to the autopilot.
2641
    if (component_id >= 1 && component_id <= 6) {
70✔
2642
        return 1;
×
2643
    }
2644

2645
    return component_id;
70✔
2646
}
2647

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