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

mavlink / MAVSDK / 11588874603

30 Oct 2024 07:37AM UTC coverage: 38.621% (+0.7%) from 37.918%
11588874603

Pull #2394

github

web-flow
Merge 991248b3a into cebb708a4
Pull Request #2394: Consolidate CI

12034 of 31159 relevant lines covered (38.62%)

243.25 hits per line

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

0.0
/src/mavsdk/plugins/gimbal/gimbal_impl.cpp
1
#include "gimbal_impl.h"
2
#include "callback_list.tpp"
3
#include "mavsdk_math.h"
4
#include "math_conversions.h"
5
#include <algorithm>
6
#include <cassert>
7
#include <chrono>
8
#include <cmath>
9
#include <functional>
10

11
namespace mavsdk {
12

13
template class CallbackList<Gimbal::ControlStatus>;
14

15
GimbalImpl::GimbalImpl(System& system) : PluginImplBase(system)
×
16
{
17
    _system_impl->register_plugin(this);
×
18
}
×
19

20
GimbalImpl::GimbalImpl(std::shared_ptr<System> system) : PluginImplBase(std::move(system))
×
21
{
22
    _system_impl->register_plugin(this);
×
23
}
×
24

25
GimbalImpl::~GimbalImpl()
×
26
{
27
    _system_impl->unregister_plugin(this);
×
28
}
×
29

30
void GimbalImpl::init()
×
31
{
32
    if (const char* env_p = std::getenv("MAVSDK_GIMBAL_DEBUGGING")) {
×
33
        if (std::string(env_p) == "1") {
×
34
            LogDebug() << "Gimbal debugging is on.";
×
35
            _debugging = true;
×
36
        }
37
    }
38

39
    _system_impl->register_mavlink_message_handler(
×
40
        MAVLINK_MSG_ID_HEARTBEAT,
41
        [this](const mavlink_message_t& message) { process_heartbeat(message); },
×
42
        this);
43

44
    _system_impl->register_mavlink_message_handler(
×
45
        MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION,
46
        [this](const mavlink_message_t& message) { process_gimbal_manager_information(message); },
×
47
        this);
48

49
    _system_impl->register_mavlink_message_handler(
×
50
        MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS,
51
        [this](const mavlink_message_t& message) { process_gimbal_manager_status(message); },
×
52
        this);
53

54
    _system_impl->register_mavlink_message_handler(
×
55
        MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION,
56
        [this](const mavlink_message_t& message) { process_gimbal_device_information(message); },
×
57
        this);
58

59
    _system_impl->register_mavlink_message_handler(
×
60
        MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS,
61
        [this](const mavlink_message_t& message) {
×
62
            process_gimbal_device_attitude_status(message);
×
63
        },
×
64
        this);
65

66
    _system_impl->register_mavlink_message_handler(
×
67
        MAVLINK_MSG_ID_ATTITUDE,
68
        [this](const mavlink_message_t& message) { process_attitude(message); },
×
69
        this);
70
}
×
71

72
void GimbalImpl::deinit()
×
73
{
74
    _system_impl->unregister_all_mavlink_message_handlers(this);
×
75
}
×
76

77
void GimbalImpl::enable() {}
×
78

79
void GimbalImpl::disable() {}
×
80

81
void GimbalImpl::request_gimbal_manager_information(uint8_t target_component_id) const
×
82
{
83
    if (_debugging) {
×
84
        LogDebug() << "Requesting GIMBAL_MANAGER_INFORMATION from: "
×
85
                   << std::to_string(_system_impl->get_system_id()) << "/"
×
86
                   << std::to_string(target_component_id);
×
87
    }
88

89
    _system_impl->mavlink_request_message().request(
×
90
        MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION, target_component_id, nullptr);
91
}
×
92

93
void GimbalImpl::request_gimbal_device_information(uint8_t target_component_id) const
×
94
{
95
    if (_debugging) {
×
96
        LogDebug() << "Requesting GIMBAL_DEVICE_INFORMATION from: "
×
97
                   << std::to_string(_system_impl->get_system_id()) << "/"
×
98
                   << std::to_string(target_component_id);
×
99
    }
100

101
    _system_impl->mavlink_request_message().request(
×
102
        MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION, target_component_id, nullptr);
103
}
×
104

105
void GimbalImpl::process_heartbeat(const mavlink_message_t& message)
×
106
{
107
    std::lock_guard<std::mutex> lock(_mutex);
×
108

109
    auto maybe_gimbal = std::find_if(_gimbals.begin(), _gimbals.end(), [&](const GimbalItem& item) {
×
110
        return item.gimbal_manager_compid == message.compid;
×
111
    });
112

113
    // Every component can potentially be a gimbal manager. Therefore, on any
114
    // heartbeat arriving, we create an entry in the potential gimbal manager
115
    // list and subsequently try to figure out whether it sends gimbal manager
116
    // messages.
117
    auto* gimbal = [&]() {
×
118
        if (maybe_gimbal != _gimbals.end()) {
×
119
            // Going from iterator to pointer is not exactly pretty.
120
            return &(*maybe_gimbal);
×
121
        } else {
122
            GimbalItem new_item{};
×
123
            new_item.gimbal_manager_compid = message.compid;
×
124
            _gimbals.emplace_back(std::move(new_item));
×
125

126
            return &_gimbals.back();
×
127
        }
×
128
    }();
×
129

130
    check_is_gimbal_valid(gimbal);
×
131
}
×
132

133
void GimbalImpl::process_gimbal_manager_information(const mavlink_message_t& message)
×
134
{
135
    mavlink_gimbal_manager_information_t gimbal_manager_information;
×
136
    mavlink_msg_gimbal_manager_information_decode(&message, &gimbal_manager_information);
×
137

138
    if (_debugging) {
×
139
        LogDebug() << "Got GIMBAL_MANAGER_INFORMATION from: " << std::to_string(message.sysid)
×
140
                   << "/" << std::to_string(message.compid) << " with gimbal_device_id: "
×
141
                   << std::to_string(gimbal_manager_information.gimbal_device_id);
×
142
    }
143

144
    std::lock_guard<std::mutex> lock(_mutex);
×
145

146
    auto maybe_gimbal = std::find_if(_gimbals.begin(), _gimbals.end(), [&](const GimbalItem& item) {
×
147
        return item.gimbal_manager_compid == message.compid;
×
148
    });
149

150
    auto* gimbal = [&]() {
×
151
        if (maybe_gimbal != _gimbals.end()) {
×
152
            // Going from iterator to pointer is not exactly pretty.
153
            return &(*maybe_gimbal);
×
154
        } else {
155
            GimbalItem new_item{};
×
156
            new_item.gimbal_manager_compid = message.compid;
×
157
            new_item.gimbal_device_id = gimbal_manager_information.gimbal_device_id;
×
158
            _gimbals.emplace_back(new_item);
×
159
            return &_gimbals.back();
×
160
        }
×
161
    }();
×
162

163
    if (gimbal->gimbal_manager_information_received &&
×
164
        gimbal->gimbal_device_id != gimbal_manager_information.gimbal_device_id) {
×
165
        LogWarn() << "gimbal_manager_information.gimbal_device_id changed from "
×
166
                  << gimbal->gimbal_device_id << " to "
×
167
                  << gimbal_manager_information.gimbal_device_id;
×
168
    }
169
    gimbal->gimbal_device_id = gimbal_manager_information.gimbal_device_id;
×
170
    gimbal->gimbal_manager_information_received = true;
×
171

172
    check_is_gimbal_valid(gimbal);
×
173
}
×
174

175
void GimbalImpl::process_gimbal_manager_status(const mavlink_message_t& message)
×
176
{
177
    mavlink_gimbal_manager_status_t status;
×
178
    mavlink_msg_gimbal_manager_status_decode(&message, &status);
×
179

180
    std::lock_guard<std::mutex> lock(_mutex);
×
181

182
    auto maybe_gimbal = std::find_if(_gimbals.begin(), _gimbals.end(), [&](const GimbalItem& item) {
×
183
        return item.gimbal_manager_compid == message.compid &&
×
184
               item.gimbal_device_id == status.gimbal_device_id;
×
185
    });
186

187
    if (maybe_gimbal == _gimbals.end()) {
×
188
        // No potential entry exists yet, we just give up for now.
189
        return;
×
190
    }
191

192
    auto& gimbal = *maybe_gimbal;
×
193

194
    // We need to populate the MAVSDK gimbal ID, so the user knows which is which.
195
    // +1 because 0 means all, so it's one-based.
196
    gimbal.control_status.gimbal_id =
×
197
        static_cast<int32_t>(std::distance(_gimbals.begin(), maybe_gimbal)) + 1;
×
198

199
    if (status.primary_control_sysid == static_cast<int>(_system_impl->get_own_system_id()) &&
×
200
        status.primary_control_compid == static_cast<int>(_system_impl->get_own_component_id())) {
×
201
        gimbal.control_status.control_mode = Gimbal::ControlMode::Primary;
×
202
    } else if (
×
203
        status.secondary_control_sysid == static_cast<int>(_system_impl->get_own_system_id()) &&
×
204
        status.secondary_control_compid == static_cast<int>(_system_impl->get_own_component_id())) {
×
205
        gimbal.control_status.control_mode = Gimbal::ControlMode::Secondary;
×
206
    } else {
207
        gimbal.control_status.control_mode = Gimbal::ControlMode::None;
×
208
    }
209

210
    gimbal.control_status.sysid_primary_control = status.primary_control_sysid;
×
211
    gimbal.control_status.compid_primary_control = status.primary_control_compid;
×
212
    gimbal.control_status.sysid_secondary_control = status.secondary_control_sysid;
×
213
    gimbal.control_status.compid_secondary_control = status.secondary_control_compid;
×
214

215
    _control_status_subscriptions.queue(gimbal.control_status, [this](const auto& func) {
×
216
        _system_impl->call_user_callback(func);
×
217
    });
×
218
}
×
219

220
void GimbalImpl::process_gimbal_device_information(const mavlink_message_t& message)
×
221
{
222
    mavlink_gimbal_device_information_t gimbal_device_information;
×
223
    mavlink_msg_gimbal_device_information_decode(&message, &gimbal_device_information);
×
224

225
    if (_debugging) {
×
226
        LogDebug() << "Got GIMBAL_DEVICE_INFORMATION from: " << std::to_string(message.sysid) << "/"
×
227
                   << std::to_string(message.compid) << " with gimbal_device_id: "
×
228
                   << std::to_string(gimbal_device_information.gimbal_device_id);
×
229
    }
230

231
    auto maybe_gimbal = std::find_if(_gimbals.begin(), _gimbals.end(), [&](const GimbalItem& item) {
×
232
        if (gimbal_device_information.gimbal_device_id == 0) {
×
233
            return item.gimbal_device_id == message.compid;
×
234
        } else {
235
            return item.gimbal_manager_compid == message.compid;
×
236
        }
237
    });
238

239
    if (maybe_gimbal == _gimbals.end()) {
×
240
        if (_debugging) {
×
241
            LogDebug() << "Didn't find gimbal for gimbal device";
×
242
        }
243
        return;
×
244
    }
245
    auto gimbal = &(*maybe_gimbal);
×
246

247
    gimbal->gimbal_device_information_received = true;
×
248
    gimbal->vendor_name = gimbal_device_information.vendor_name;
×
249
    gimbal->model_name = gimbal_device_information.model_name;
×
250
    gimbal->custom_name = gimbal_device_information.custom_name;
×
251

252
    check_is_gimbal_valid(gimbal);
×
253
}
254

255
void GimbalImpl::process_gimbal_device_attitude_status(const mavlink_message_t& message)
×
256
{
257
    mavlink_gimbal_device_attitude_status_t attitude_status;
×
258
    mavlink_msg_gimbal_device_attitude_status_decode(&message, &attitude_status);
×
259

260
    // By default, we assume it's in vehicle/forward frame.
261
    bool is_in_forward_frame = true;
×
262

263
    if (attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME ||
×
264
        attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME) {
×
265
        // Flags are set correctly according to newer spec, so we can use it.
266
        if (attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME) {
×
267
            is_in_forward_frame = false;
×
268
        }
269
    } else {
270
        // Neither of the flags indicating the frame are set, we fall back to previous way
271
        // which depends on lock flag.
272
        if (attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) {
×
273
            is_in_forward_frame = false;
×
274
        }
275
    }
276

277
    if (attitude_status.gimbal_device_id > 6) {
×
278
        LogWarn() << "Ignoring gimbal device attitude status with invalid gimbal_device_id "
×
279
                  << attitude_status.gimbal_device_id << " from (" << message.sysid << "/" << ")";
×
280
        return;
×
281
    }
282

283
    std::lock_guard<std::mutex> lock(_mutex);
×
284

285
    auto maybe_gimbal = std::find_if(_gimbals.begin(), _gimbals.end(), [&](const GimbalItem& item) {
×
286
        if (attitude_status.gimbal_device_id == 0) {
×
287
            return item.gimbal_device_id == message.compid;
×
288
        } else {
289
            return item.gimbal_manager_compid == message.compid &&
×
290
                   item.gimbal_device_id == attitude_status.gimbal_device_id;
×
291
        }
292
    });
293

294
    if (maybe_gimbal == _gimbals.end()) {
×
295
        // Only warn if we have found any gimbals.
296
        if (std::any_of(_gimbals.begin(), _gimbals.end(), [](const GimbalItem& item) {
×
297
                return item.is_valid;
×
298
            })) {
299
            LogWarn() << "Received gimbal manager status for unknown gimbal.";
×
300
        }
301
        // Otherwise, ignore it silently
302
        return;
×
303
    }
304

305
    auto& gimbal = *maybe_gimbal;
×
306

307
    // Reset to defaults (e.g. NaN) first.
308
    gimbal.attitude = {};
×
309
    // We need to populate the MAVSDK gimbal ID, so the user knows which is which.
310
    // +1 because 0 means all, so it's one-based.
311
    gimbal.attitude.gimbal_id =
×
312
        static_cast<int32_t>(std::distance(_gimbals.begin(), maybe_gimbal)) + 1;
×
313

314
    if (is_in_forward_frame) {
×
315
        gimbal.attitude.quaternion_forward.w = attitude_status.q[0];
×
316
        gimbal.attitude.quaternion_forward.x = attitude_status.q[1];
×
317
        gimbal.attitude.quaternion_forward.y = attitude_status.q[2];
×
318
        gimbal.attitude.quaternion_forward.z = attitude_status.q[3];
×
319

320
        auto quaternion_forward = Quaternion{};
×
321
        quaternion_forward.w = attitude_status.q[0];
×
322
        quaternion_forward.x = attitude_status.q[1];
×
323
        quaternion_forward.y = attitude_status.q[2];
×
324
        quaternion_forward.z = attitude_status.q[3];
×
325
        const auto euler_angle_forward = to_euler_angle_from_quaternion(quaternion_forward);
×
326

327
        gimbal.attitude.euler_angle_forward.roll_deg = euler_angle_forward.roll_deg;
×
328
        gimbal.attitude.euler_angle_forward.pitch_deg = euler_angle_forward.pitch_deg;
×
329
        gimbal.attitude.euler_angle_forward.yaw_deg = euler_angle_forward.yaw_deg;
×
330

331
        gimbal.attitude.timestamp_us = attitude_status.time_boot_ms * 1000;
×
332

333
        // Calculate angle relative to North as well
334
        if (!std::isnan(_vehicle_yaw_rad)) {
×
335
            auto rotation =
×
336
                to_quaternion_from_euler_angle(EulerAngle{0, 0, to_deg_from_rad(_vehicle_yaw_rad)});
×
337
            auto quaternion_north = rotation * quaternion_forward;
×
338

339
            gimbal.attitude.quaternion_north.w = quaternion_north.w;
×
340
            gimbal.attitude.quaternion_north.x = quaternion_north.x;
×
341
            gimbal.attitude.quaternion_north.y = quaternion_north.y;
×
342
            gimbal.attitude.quaternion_north.z = quaternion_north.z;
×
343

344
            const auto euler_angle_north = to_euler_angle_from_quaternion(quaternion_north);
×
345
            gimbal.attitude.euler_angle_north.roll_deg = euler_angle_north.roll_deg;
×
346
            gimbal.attitude.euler_angle_north.pitch_deg = euler_angle_north.pitch_deg;
×
347
            gimbal.attitude.euler_angle_north.yaw_deg = euler_angle_north.yaw_deg;
×
348
        }
349

350
    } else {
351
        gimbal.attitude.quaternion_north.w = attitude_status.q[0];
×
352
        gimbal.attitude.quaternion_north.x = attitude_status.q[1];
×
353
        gimbal.attitude.quaternion_north.y = attitude_status.q[2];
×
354
        gimbal.attitude.quaternion_north.z = attitude_status.q[3];
×
355

356
        auto quaternion_north = Quaternion{};
×
357
        quaternion_north.w = attitude_status.q[0];
×
358
        quaternion_north.x = attitude_status.q[1];
×
359
        quaternion_north.y = attitude_status.q[2];
×
360
        quaternion_north.z = attitude_status.q[3];
×
361
        const auto euler_angle_north = to_euler_angle_from_quaternion(quaternion_north);
×
362

363
        gimbal.attitude.euler_angle_north.roll_deg = euler_angle_north.roll_deg;
×
364
        gimbal.attitude.euler_angle_north.pitch_deg = euler_angle_north.pitch_deg;
×
365
        gimbal.attitude.euler_angle_north.yaw_deg = euler_angle_north.yaw_deg;
×
366

367
        // Calculate angle relative to forward as well
368
        if (!std::isnan(_vehicle_yaw_rad)) {
×
369
            auto rotation = to_quaternion_from_euler_angle(
×
370
                EulerAngle{0, 0, -to_deg_from_rad(_vehicle_yaw_rad)});
×
371
            auto quaternion_forward = rotation * quaternion_north;
×
372

373
            gimbal.attitude.quaternion_forward.w = quaternion_forward.w;
×
374
            gimbal.attitude.quaternion_forward.x = quaternion_forward.x;
×
375
            gimbal.attitude.quaternion_forward.y = quaternion_forward.y;
×
376
            gimbal.attitude.quaternion_forward.z = quaternion_forward.z;
×
377

378
            const auto euler_angle_forward = to_euler_angle_from_quaternion(quaternion_forward);
×
379
            gimbal.attitude.euler_angle_forward.roll_deg = euler_angle_forward.roll_deg;
×
380
            gimbal.attitude.euler_angle_forward.pitch_deg = euler_angle_forward.pitch_deg;
×
381
            gimbal.attitude.euler_angle_forward.yaw_deg = euler_angle_forward.yaw_deg;
×
382
        }
383
    }
384

385
    gimbal.attitude.angular_velocity.roll_rad_s = attitude_status.angular_velocity_x;
×
386
    gimbal.attitude.angular_velocity.pitch_rad_s = attitude_status.angular_velocity_y;
×
387
    gimbal.attitude.angular_velocity.yaw_rad_s = attitude_status.angular_velocity_z;
×
388

389
    _attitude_subscriptions.queue(
×
390
        gimbal.attitude, [this](const auto& func) { _system_impl->call_user_callback(func); });
×
391
}
×
392

393
void GimbalImpl::process_attitude(const mavlink_message_t& message)
×
394
{
395
    mavlink_attitude_t attitude;
×
396
    mavlink_msg_attitude_decode(&message, &attitude);
×
397

398
    std::lock_guard<std::mutex> lock(_mutex);
×
399

400
    _vehicle_yaw_rad = attitude.yaw;
×
401
}
×
402

403
void GimbalImpl::check_is_gimbal_valid(GimbalItem* gimbal)
×
404
{
405
    assert(gimbal != nullptr);
×
406

407
    // Assumes lock
408

409
    if (gimbal->is_valid) {
×
410
        // We're already done.
411
        return;
×
412
    }
413

414
    // Check if we should request GIMBAL_MANAGER_INFORMATION again.
415
    if (!gimbal->gimbal_manager_information_received &&
×
416
        gimbal->gimbal_manager_information_requests_left-- > 0) {
×
417
        request_gimbal_manager_information(gimbal->gimbal_manager_compid);
×
418
    }
419

420
    if (!gimbal->gimbal_manager_information_received) {
×
421
        // Leave it at this for now, we need the gimbal_device_id for further steps
422
        return;
×
423
    }
424

425
    // Check if we should request GIMBAL_DEVICE_INFORMATION again.
426
    if (!gimbal->gimbal_device_information_received &&
×
427
        gimbal->gimbal_device_information_requests_left-- > 0) {
×
428
        auto component_id = (gimbal->gimbal_device_id > 0 && gimbal->gimbal_device_id <= 6) ?
×
429
                                gimbal->gimbal_manager_compid :
430
                                gimbal->gimbal_device_id;
431
        if (component_id != 0) {
×
432
            request_gimbal_device_information(component_id);
×
433
        }
434
        return;
×
435
    }
436

437
    // If we have gimbal_manager_information but no GIMBAL_DEVICE_INFORMATION despite
438
    // having tried multiple times, we might as well continue without.
439
    if (!gimbal->gimbal_device_information_received) {
×
440
        LogWarn() << "Continuing despite GIMBAL_DEVICE_INFORMATION missing";
×
441
    }
442

443
    gimbal->is_valid = true;
×
444
    _gimbal_list_subscriptions.queue(gimbal_list_with_lock(), [this](const auto& func) {
×
445
        _system_impl->call_user_callback(func);
×
446
    });
×
447
}
448

449
Gimbal::Result GimbalImpl::set_angles(
×
450
    int32_t gimbal_id,
451
    float roll_deg,
452
    float pitch_deg,
453
    float yaw_deg,
454
    Gimbal::GimbalMode gimbal_mode,
455
    Gimbal::SendMode send_mode)
456
{
457
    auto prom = std::promise<Gimbal::Result>();
×
458
    auto fut = prom.get_future();
×
459

460
    set_angles_async_internal(
×
461
        gimbal_id,
462
        roll_deg,
463
        pitch_deg,
464
        yaw_deg,
465
        gimbal_mode,
466
        send_mode,
467
        [&prom](Gimbal::Result result) { prom.set_value(result); });
×
468

469
    return fut.get();
×
470
}
×
471

472
void GimbalImpl::set_angles_async(
×
473
    int32_t gimbal_id,
474
    float roll_deg,
475
    float pitch_deg,
476
    float yaw_deg,
477
    Gimbal::GimbalMode gimbal_mode,
478
    Gimbal::SendMode send_mode,
479
    const Gimbal::ResultCallback& callback)
480
{
481
    set_angles_async_internal(
×
482
        gimbal_id,
483
        roll_deg,
484
        pitch_deg,
485
        yaw_deg,
486
        gimbal_mode,
487
        send_mode,
488
        [this, callback](auto result) {
×
489
            if (callback) {
×
490
                _system_impl->call_user_callback([callback, result]() { callback(result); });
×
491
            }
492
        });
×
493
}
×
494

495
void GimbalImpl::set_angles_async_internal(
×
496
    int32_t gimbal_id,
497
    float roll_deg,
498
    float pitch_deg,
499
    float yaw_deg,
500
    Gimbal::GimbalMode gimbal_mode,
501
    Gimbal::SendMode send_mode,
502
    const Gimbal::ResultCallback& callback)
503
{
504
    std::lock_guard<std::mutex> lock(_mutex);
×
505

506
    auto maybe_address = maybe_address_for_gimbal_id(gimbal_id);
×
507

508
    if (!maybe_address) {
×
509
        if (callback) {
×
510
            callback(Gimbal::Result::InvalidArgument);
×
511
        }
512
        return;
×
513
    }
514

515
    auto address = maybe_address.value();
×
516

517
    const float roll_rad = to_rad_from_deg(roll_deg);
×
518
    const float pitch_rad = to_rad_from_deg(pitch_deg);
×
519
    const float yaw_rad = to_rad_from_deg(yaw_deg);
×
520

521
    float quaternion[4];
×
522
    mavlink_euler_to_quaternion(roll_rad, pitch_rad, yaw_rad, quaternion);
×
523

524
    const uint32_t flags =
×
525
        GIMBAL_MANAGER_FLAGS_ROLL_LOCK | GIMBAL_MANAGER_FLAGS_PITCH_LOCK |
×
526
        ((gimbal_mode == Gimbal::GimbalMode::YawLock) ? GIMBAL_MANAGER_FLAGS_YAW_LOCK : 0);
527

528
    switch (send_mode) {
×
529
        case Gimbal::SendMode::Stream: {
×
530
            if (_debugging) {
×
531
                LogDebug() << "Sending GIMBAL_MANAGER_SET_ATTITUDE message with angles";
×
532
            }
533
            auto result =
534
                _system_impl->queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
×
535
                    mavlink_message_t message;
536
                    mavlink_msg_gimbal_manager_set_attitude_pack_chan(
×
537
                        mavlink_address.system_id,
×
538
                        mavlink_address.component_id,
×
539
                        channel,
540
                        &message,
541
                        _system_impl->get_system_id(),
×
542
                        address.gimbal_manager_compid,
×
543
                        flags,
×
544
                        address.gimbal_device_id,
×
545
                        quaternion,
×
546
                        NAN,
547
                        NAN,
548
                        NAN);
549
                    return message;
×
550
                }) ?
×
551
                    Gimbal::Result::Success :
552
                    Gimbal::Result::Error;
×
553
            if (callback) {
×
554
                callback(result);
×
555
            }
556
        } break;
×
557
        case Gimbal::SendMode::Once:
×
558
            if (roll_deg != 0.0f) {
×
559
                LogWarn() << "Roll needs to be 0 for SendMode::Once.";
×
560
                if (callback) {
×
561
                    callback(Gimbal::Result::InvalidArgument);
×
562
                }
563

564
            } else {
565
                if (_debugging) {
×
566
                    LogDebug() << "Sending DO_GIMBAL_MANAGER_PITCHYAW command with angles";
×
567
                }
568

569
                MavlinkCommandSender::CommandLong command{};
×
570
                command.command = MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW;
×
571
                command.params.maybe_param1 = pitch_deg;
×
572
                command.params.maybe_param2 = yaw_deg;
×
573
                command.params.maybe_param3 = NAN;
×
574
                command.params.maybe_param4 = NAN;
×
575
                command.params.maybe_param5 = static_cast<float>(flags);
×
576
                command.params.maybe_param7 = address.gimbal_device_id;
×
577
                command.target_system_id = _system_impl->get_system_id();
×
578
                command.target_component_id = address.gimbal_manager_compid;
×
579

580
                _system_impl->send_command_async(
×
581
                    command, [callback](MavlinkCommandSender::Result result, float) {
×
582
                        receive_command_result(result, callback);
×
583
                    });
×
584
            }
585
            break;
×
586
        default:
×
587
            LogErr() << "Invalid send mode";
×
588
            if (callback) {
×
589
                callback(Gimbal::Result::InvalidArgument);
×
590
            }
591
            break;
×
592
    }
593
}
×
594

595
Gimbal::Result GimbalImpl::set_angular_rates(
×
596
    int32_t gimbal_id,
597
    float roll_rate_deg_s,
598
    float pitch_rate_deg_s,
599
    float yaw_rate_deg_s,
600
    Gimbal::GimbalMode gimbal_mode,
601
    Gimbal::SendMode send_mode)
602
{
603
    auto prom = std::promise<Gimbal::Result>();
×
604
    auto fut = prom.get_future();
×
605

606
    set_angular_rates_async_internal(
×
607
        gimbal_id,
608
        roll_rate_deg_s,
609
        pitch_rate_deg_s,
610
        yaw_rate_deg_s,
611
        gimbal_mode,
612
        send_mode,
613
        [&prom](Gimbal::Result result) { prom.set_value(result); });
×
614

615
    return fut.get();
×
616
}
×
617

618
void GimbalImpl::set_angular_rates_async(
×
619
    int32_t gimbal_id,
620
    float roll_rate_deg_s,
621
    float pitch_rate_deg_s,
622
    float yaw_rate_deg_s,
623
    Gimbal::GimbalMode gimbal_mode,
624
    Gimbal::SendMode send_mode,
625
    const Gimbal::ResultCallback& callback)
626
{
627
    set_angular_rates_async_internal(
×
628
        gimbal_id,
629
        roll_rate_deg_s,
630
        pitch_rate_deg_s,
631
        yaw_rate_deg_s,
632
        gimbal_mode,
633
        send_mode,
634
        [this, callback](auto result) {
×
635
            if (callback) {
×
636
                _system_impl->call_user_callback([callback, result]() { callback(result); });
×
637
            }
638
        });
×
639
}
×
640

641
void GimbalImpl::set_angular_rates_async_internal(
×
642
    int32_t gimbal_id,
643
    float roll_rate_deg_s,
644
    float pitch_rate_deg_s,
645
    float yaw_rate_deg_s,
646
    Gimbal::GimbalMode gimbal_mode,
647
    Gimbal::SendMode send_mode,
648
    const Gimbal::ResultCallback& callback)
649
{
650
    std::lock_guard<std::mutex> lock(_mutex);
×
651

652
    auto maybe_address = maybe_address_for_gimbal_id(gimbal_id);
×
653

654
    if (!maybe_address) {
×
655
        if (callback) {
×
656
            callback(Gimbal::Result::InvalidArgument);
×
657
        }
658
        return;
×
659
    }
660

661
    auto address = maybe_address.value();
×
662

663
    const uint32_t flags =
×
664
        GIMBAL_MANAGER_FLAGS_ROLL_LOCK | GIMBAL_MANAGER_FLAGS_PITCH_LOCK |
×
665
        ((gimbal_mode == Gimbal::GimbalMode::YawLock) ? GIMBAL_MANAGER_FLAGS_YAW_LOCK : 0);
666

667
    switch (send_mode) {
×
668
        case Gimbal::SendMode::Stream: {
×
669
            constexpr float quaternion[4] = {NAN, NAN, NAN, NAN};
×
670

671
            if (_debugging) {
×
672
                LogDebug() << "Sending GIMBAL_MANAGER_SET_ATTITUDE message with angular rates";
×
673
            }
674
            auto result =
675
                _system_impl->queue_message([&](MavlinkAddress mavlink_address, uint8_t channel) {
×
676
                    mavlink_message_t message;
677
                    mavlink_msg_gimbal_manager_set_attitude_pack_chan(
×
678
                        mavlink_address.system_id,
×
679
                        mavlink_address.component_id,
×
680
                        channel,
681
                        &message,
682
                        _system_impl->get_system_id(),
×
683
                        address.gimbal_manager_compid,
×
684
                        flags,
×
685
                        address.gimbal_device_id,
×
686
                        quaternion,
×
687
                        to_rad_from_deg(roll_rate_deg_s),
×
688
                        to_rad_from_deg(pitch_rate_deg_s),
×
689
                        to_rad_from_deg(yaw_rate_deg_s));
×
690
                    return message;
×
691
                }) ?
×
692
                    Gimbal::Result::Success :
693
                    Gimbal::Result::Error;
×
694
            if (callback) {
×
695
                callback(result);
×
696
            }
697
            break;
×
698
        }
699
        case Gimbal::SendMode::Once:
×
700
            if (roll_rate_deg_s != 0.0f) {
×
701
                LogWarn() << "Roll rate needs to be 0 for SendMode::Once.";
×
702
                if (callback) {
×
703
                    callback(Gimbal::Result::InvalidArgument);
×
704
                }
705

706
            } else {
707
                if (_debugging) {
×
708
                    LogDebug() << "Sending DO_GIMBAL_MANAGER_PITCHYAW command with angular rates";
×
709
                }
710

711
                MavlinkCommandSender::CommandInt command{};
×
712
                command.command = MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW;
×
713
                command.params.maybe_param1 = NAN;
×
714
                command.params.maybe_param2 = NAN;
×
715
                command.params.maybe_param3 = pitch_rate_deg_s;
×
716
                command.params.maybe_param4 = yaw_rate_deg_s;
×
717
                command.params.x = static_cast<int32_t>(flags);
×
718
                command.params.maybe_z = address.gimbal_device_id;
×
719
                command.target_system_id = _system_impl->get_system_id();
×
720
                command.target_component_id = address.gimbal_manager_compid;
×
721

722
                _system_impl->send_command_async(
×
723
                    command, [callback](MavlinkCommandSender::Result result, float) {
×
724
                        receive_command_result(result, callback);
×
725
                    });
×
726
            }
727
            break;
×
728
        default:
×
729
            LogErr() << "Invalid send mode";
×
730
            callback(Gimbal::Result::InvalidArgument);
×
731
            break;
×
732
    }
733
}
×
734

735
Gimbal::Result GimbalImpl::set_roi_location(
×
736
    int32_t gimbal_id, double latitude_deg, double longitude_deg, float altitude_m)
737
{
738
    auto prom = std::promise<Gimbal::Result>();
×
739
    auto fut = prom.get_future();
×
740

741
    set_roi_location_async(
×
742
        gimbal_id, latitude_deg, longitude_deg, altitude_m, [&prom](Gimbal::Result result) {
×
743
            prom.set_value(result);
×
744
        });
×
745

746
    return fut.get();
×
747
}
×
748

749
void GimbalImpl::set_roi_location_async(
×
750
    int32_t gimbal_id,
751
    double latitude_deg,
752
    double longitude_deg,
753
    float altitude_m,
754
    const Gimbal::ResultCallback& callback)
755
{
756
    std::lock_guard<std::mutex> lock(_mutex);
×
757

758
    auto maybe_address = maybe_address_for_gimbal_id(gimbal_id);
×
759

760
    if (!maybe_address) {
×
761
        if (callback) {
×
762
            _system_impl->call_user_callback(
×
763
                [callback]() { callback(Gimbal::Result::InvalidArgument); });
764
        }
765
        return;
×
766
    }
767

768
    auto address = maybe_address.value();
×
769

770
    MavlinkCommandSender::CommandInt command{};
×
771

772
    command.command = MAV_CMD_DO_SET_ROI_LOCATION;
×
773
    command.params.maybe_param1 = address.gimbal_device_id;
×
774
    command.params.x = static_cast<int32_t>(std::round(latitude_deg * 1e7));
×
775
    command.params.y = static_cast<int32_t>(std::round(longitude_deg * 1e7));
×
776
    command.params.maybe_z = altitude_m;
×
777
    command.target_system_id = _system_impl->get_system_id();
×
778
    command.target_component_id = address.gimbal_manager_compid;
×
779

780
    _system_impl->send_command_async(
×
781
        command, [callback](MavlinkCommandSender::Result result, float) {
×
782
            receive_command_result(result, callback);
×
783
        });
×
784
}
×
785

786
Gimbal::Result GimbalImpl::take_control(int32_t gimbal_id, Gimbal::ControlMode control_mode)
×
787
{
788
    auto prom = std::promise<Gimbal::Result>();
×
789
    auto fut = prom.get_future();
×
790

791
    take_control_async(
×
792
        gimbal_id, control_mode, [&prom](Gimbal::Result result) { prom.set_value(result); });
×
793

794
    return fut.get();
×
795
}
×
796

797
void GimbalImpl::take_control_async(
×
798
    int32_t gimbal_id, Gimbal::ControlMode control_mode, const Gimbal::ResultCallback& callback)
799
{
800
    std::lock_guard<std::mutex> lock(_mutex);
×
801

802
    auto maybe_address = maybe_address_for_gimbal_id(gimbal_id);
×
803

804
    if (!maybe_address) {
×
805
        if (callback) {
×
806
            callback(Gimbal::Result::InvalidArgument);
×
807
        }
808
        return;
×
809
    }
810

811
    auto address = maybe_address.value();
×
812

813
    if (control_mode == Gimbal::ControlMode::None) {
×
814
        release_control_async(gimbal_id, callback);
×
815
        return;
×
816
    } else if (control_mode == Gimbal::ControlMode::Secondary) {
×
817
        if (callback) {
×
818
            _system_impl->call_user_callback(
×
819
                [callback]() { callback(Gimbal::Result::Unsupported); });
820
        }
821
        LogErr() << "Gimbal secondary control is not implemented yet!";
×
822
        return;
×
823
    }
824

825
    float own_sysid = _system_impl->get_own_system_id();
×
826
    float own_compid = _system_impl->get_own_component_id();
×
827

828
    MavlinkCommandSender::CommandLong command{};
×
829

830
    command.command = MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE;
×
831
    command.params.maybe_param1 =
×
832
        control_mode == Gimbal::ControlMode::Primary ? own_sysid : -3.0f; // sysid primary control
×
833
    command.params.maybe_param2 =
×
834
        control_mode == Gimbal::ControlMode::Primary ? own_compid : -3.0f; // compid primary control
×
835
    command.params.maybe_param3 =
×
836
        control_mode == Gimbal::ControlMode::Primary ? own_sysid : -3.0f; // sysid secondary control
×
837
    command.params.maybe_param4 = control_mode == Gimbal::ControlMode::Primary ?
×
838
                                      own_compid :
839
                                      -3.0f; // compid secondary control
840

841
    command.params.maybe_param7 = address.gimbal_device_id;
×
842
    command.target_system_id = _system_impl->get_system_id();
×
843
    command.target_component_id = address.gimbal_manager_compid;
×
844

845
    _system_impl->send_command_async(
×
846
        command, [callback](MavlinkCommandSender::Result result, float) {
×
847
            GimbalImpl::receive_command_result(result, callback);
×
848
        });
×
849
}
×
850

851
Gimbal::Result GimbalImpl::release_control(int32_t gimbal_id)
×
852
{
853
    auto prom = std::promise<Gimbal::Result>();
×
854
    auto fut = prom.get_future();
×
855

856
    release_control_async(gimbal_id, [&prom](Gimbal::Result result) { prom.set_value(result); });
×
857

858
    return fut.get();
×
859
}
×
860

861
void GimbalImpl::release_control_async(int32_t gimbal_id, const Gimbal::ResultCallback& callback)
×
862
{
863
    std::lock_guard<std::mutex> lock(_mutex);
×
864

865
    MavlinkCommandSender::CommandLong command{};
×
866

867
    command.command = MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE;
×
868
    command.params.maybe_param1 = -3.0f; // sysid primary control
×
869
    command.params.maybe_param2 = -3.0f; // compid primary control
×
870
    command.params.maybe_param3 = -3.0f; // sysid secondary control
×
871
    command.params.maybe_param4 = -3.0f; // compid secondary control
×
872

873
    auto maybe_address = maybe_address_for_gimbal_id(gimbal_id);
×
874

875
    if (!maybe_address) {
×
876
        if (callback) {
×
877
            _system_impl->call_user_callback(
×
878
                [callback]() { callback(Gimbal::Result::InvalidArgument); });
879
        }
880
        return;
×
881
    }
882

883
    auto address = maybe_address.value();
×
884

885
    command.target_component_id = address.gimbal_manager_compid;
×
886
    command.target_system_id = _system_impl->get_system_id();
×
887
    command.params.maybe_param7 = address.gimbal_device_id;
×
888

889
    _system_impl->send_command_async(
×
890
        command, [callback](MavlinkCommandSender::Result result, float) {
×
891
            receive_command_result(result, callback);
×
892
        });
×
893
}
×
894

895
std::pair<Gimbal::Result, Gimbal::ControlStatus> GimbalImpl::get_control_status(int32_t gimbal_id)
×
896
{
897
    std::lock_guard<std::mutex> lock(_mutex);
×
898

899
    auto* maybe_gimbal = maybe_gimbal_item_for_gimbal_id(gimbal_id);
×
900

901
    if (!maybe_gimbal) {
×
902
        return {Gimbal::Result::InvalidArgument, {}};
×
903
    }
904

905
    auto& gimbal = *maybe_gimbal;
×
906
    return {Gimbal::Result::Success, gimbal.control_status};
×
907
}
×
908

909
Gimbal::GimbalListHandle
910
GimbalImpl::subscribe_gimbal_list(const Gimbal::GimbalListCallback& callback)
×
911
{
912
    std::lock_guard<std::mutex> lock(_mutex);
×
913
    auto handle = _gimbal_list_subscriptions.subscribe(callback);
×
914

915
    if (std::any_of(_gimbals.begin(), _gimbals.end(), [](const GimbalItem& item) {
×
916
            return item.is_valid;
×
917
        })) {
918
        // We already have some gimbals detected that we need to tell the subscriber.
919
        _gimbal_list_subscriptions.queue(gimbal_list_with_lock(), [this](const auto& func) {
×
920
            _system_impl->call_user_callback(func);
×
921
        });
×
922
    }
923

924
    return handle;
×
925
}
×
926

927
void GimbalImpl::unsubscribe_gimbal_list(Gimbal::GimbalListHandle handle)
×
928
{
929
    std::lock_guard<std::mutex> lock(_mutex);
×
930
    _gimbal_list_subscriptions.unsubscribe(handle);
×
931
}
×
932

933
Gimbal::GimbalList GimbalImpl::gimbal_list()
×
934
{
935
    std::lock_guard<std::mutex> lock(_mutex);
×
936

937
    return gimbal_list_with_lock();
×
938
}
×
939

940
Gimbal::GimbalList GimbalImpl::gimbal_list_with_lock()
×
941
{
942
    // Obviously assume lock
943

944
    int32_t gimbal_id = 1;
×
945
    Gimbal::GimbalList new_list;
×
946
    for (auto& gimbal : _gimbals) {
×
947
        if (!gimbal.is_valid) {
×
948
            continue;
×
949
        }
950
        Gimbal::GimbalItem new_item{};
×
951
        new_item.gimbal_id = gimbal_id++;
×
952
        new_item.gimbal_device_id = gimbal.gimbal_device_id;
×
953
        new_item.gimbal_manager_component_id = gimbal.gimbal_manager_compid;
×
954
        new_item.vendor_name = gimbal.vendor_name;
×
955
        new_item.model_name = gimbal.model_name;
×
956
        new_item.custom_name = gimbal.custom_name;
×
957
        new_list.gimbals.emplace_back(std::move(new_item));
×
958
    }
×
959
    return new_list;
×
960
}
961

962
Gimbal::ControlStatusHandle
963
GimbalImpl::subscribe_control_status(const Gimbal::ControlStatusCallback& callback)
×
964
{
965
    std::lock_guard<std::mutex> lock(_mutex);
×
966
    return _control_status_subscriptions.subscribe(callback);
×
967
}
×
968

969
void GimbalImpl::unsubscribe_control_status(Gimbal::ControlStatusHandle handle)
×
970
{
971
    std::lock_guard<std::mutex> lock(_mutex);
×
972
    _control_status_subscriptions.unsubscribe(handle);
×
973
}
×
974

975
Gimbal::AttitudeHandle GimbalImpl::subscribe_attitude(const Gimbal::AttitudeCallback& callback)
×
976
{
977
    std::lock_guard<std::mutex> lock(_mutex);
×
978
    return _attitude_subscriptions.subscribe(callback);
×
979
}
×
980

981
void GimbalImpl::unsubscribe_attitude(Gimbal::AttitudeHandle handle)
×
982
{
983
    std::lock_guard<std::mutex> lock(_mutex);
×
984
    _attitude_subscriptions.unsubscribe(handle);
×
985
}
×
986

987
std::pair<Gimbal::Result, Gimbal::Attitude> GimbalImpl::get_attitude(int32_t gimbal_id)
×
988
{
989
    std::lock_guard<std::mutex> lock(_mutex);
×
990

991
    auto* maybe_gimbal = maybe_gimbal_item_for_gimbal_id(gimbal_id);
×
992

993
    if (!maybe_gimbal) {
×
994
        return {Gimbal::Result::InvalidArgument, {}};
×
995
    }
996

997
    auto& gimbal = *maybe_gimbal;
×
998

999
    return {Gimbal::Result::Success, gimbal.attitude};
×
1000
}
×
1001

1002
void GimbalImpl::receive_command_result(
×
1003
    MavlinkCommandSender::Result command_result, const Gimbal::ResultCallback& callback)
1004
{
1005
    Gimbal::Result gimbal_result = gimbal_result_from_command_result(command_result);
×
1006

1007
    if (callback) {
×
1008
        callback(gimbal_result);
×
1009
    }
1010
}
×
1011

1012
Gimbal::Result
1013
GimbalImpl::gimbal_result_from_command_result(MavlinkCommandSender::Result command_result)
×
1014
{
1015
    switch (command_result) {
×
1016
        case MavlinkCommandSender::Result::Success:
×
1017
            return Gimbal::Result::Success;
×
1018
        case MavlinkCommandSender::Result::Timeout:
×
1019
            return Gimbal::Result::Timeout;
×
1020
        case MavlinkCommandSender::Result::NoSystem:
×
1021
        case MavlinkCommandSender::Result::ConnectionError:
1022
        case MavlinkCommandSender::Result::Busy:
1023
        case MavlinkCommandSender::Result::Denied:
1024
        case MavlinkCommandSender::Result::TemporarilyRejected:
1025
        default:
1026
            return Gimbal::Result::Error;
×
1027
    }
1028
}
1029

1030
std::optional<GimbalImpl::GimbalAddress>
1031
GimbalImpl::maybe_address_for_gimbal_id(int32_t gimbal_id) const
×
1032
{
1033
    // Assumes lock
1034

1035
    if (gimbal_id < 0) {
×
1036
        LogWarn() << "Invalid negative gimbal ID: " << gimbal_id;
×
1037
        return {};
×
1038
    }
1039

1040
    if (gimbal_id > _gimbals.size()) {
×
1041
        LogWarn() << "Invalid positive gimbal ID: " << gimbal_id;
×
1042
        return {};
×
1043
    }
1044

1045
    if (gimbal_id == 0) {
×
1046
        return GimbalAddress{0, 0};
×
1047
    }
1048

1049
    return GimbalAddress{
×
1050
        _gimbals[gimbal_id - 1].gimbal_manager_compid, _gimbals[gimbal_id - 1].gimbal_device_id};
×
1051
}
1052

1053
GimbalImpl::GimbalItem* GimbalImpl::maybe_gimbal_item_for_gimbal_id(int32_t gimbal_id)
×
1054
{
1055
    // Assumes lock
1056

1057
    if (gimbal_id == 0) {
×
1058
        LogWarn() << "Invalid gimbal ID: " << gimbal_id;
×
1059
        return nullptr;
×
1060
    }
1061

1062
    if (gimbal_id < 0) {
×
1063
        LogWarn() << "Invalid negative gimbal ID: " << gimbal_id;
×
1064
        return nullptr;
×
1065
    }
1066

1067
    if (gimbal_id > _gimbals.size()) {
×
1068
        LogWarn() << "Invalid positive gimbal ID: " << gimbal_id;
×
1069
        return nullptr;
×
1070
    }
1071

1072
    return &_gimbals[gimbal_id - 1];
×
1073
}
1074

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

© 2025 Coveralls, Inc