QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GimbalController.cc
Go to the documentation of this file.
1#include "GimbalController.h"
3#include "MAVLinkLib.h"
4#include "MAVLinkProtocol.h"
5#include "ParameterManager.h"
8#include "SettingsManager.h"
9#include "Vehicle.h"
10#include "VehicleLinkManager.h"
11#include <cmath>
12#include "Gimbal.h"
13#include "QGCCameraManager.h"
14
15QGC_LOGGING_CATEGORY(GimbalControllerLog, "Gimbal.GimbalController")
16
18 : QObject(vehicle)
19 , _vehicle(vehicle)
20 , _gimbals(new QmlObjectListModel(this))
21{
22 qCDebug(GimbalControllerLog) << this;
23
24 (void) connect(_vehicle, &Vehicle::initialConnectComplete, this, &GimbalController::_initialConnectCompleted, Qt::UniqueConnection);
25 (void) connect(_vehicle, &Vehicle::mavlinkMessageReceived, this, &GimbalController::_mavlinkMessageReceived);
26
27 _rateSenderTimer.setInterval(500);
28 (void) connect(&_rateSenderTimer, &QTimer::timeout, this, &GimbalController::_rateSenderTimeout);
29}
30
32{
33 qCDebug(GimbalControllerLog) << this;
34}
35
36void GimbalController::_initialConnectCompleted()
37{
38 _initialConnectComplete = true;
39}
40
42{
43 if (!gimbal) {
44 qCCritical(GimbalControllerLog) << "Set active gimbal: attempted to set a nullptr, returning";
45 return;
46 }
47
48 if (gimbal != _activeGimbal) {
49 qCDebug(GimbalControllerLog) << "Set active gimbal:" << gimbal;
50 _activeGimbal = gimbal;
52 }
53}
54
55void GimbalController::_mavlinkMessageReceived(const mavlink_message_t &message)
56{
57 if (!_initialConnectComplete) {
58 return;
59 }
60
61 switch (message.msgid) {
62 case MAVLINK_MSG_ID_HEARTBEAT:
63 _handleHeartbeat(message);
64 break;
65 case MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION:
66 _handleGimbalManagerInformation(message);
67 break;
68 case MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS:
69 _handleGimbalManagerStatus(message);
70 break;
71 case MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS:
72 _handleGimbalDeviceAttitudeStatus(message);
73 break;
74 default:
75 break;
76 }
77}
78
79void GimbalController::_handleHeartbeat(const mavlink_message_t &message)
80{
81 if (!_potentialGimbalManagers.contains(message.compid)) {
82 qCDebug(GimbalControllerLog) << "new potential gimbal manager component:" << message.compid;
83 }
84
85 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
86
87 // Note that we are working over potential gimbal managers here, instead of potential gimbals.
88 // This is because we address the gimbal manager by compid, but a gimbal device might have an
89 // id different than the message compid it comes from. For more information see https://mavlink.io/en/services/gimbal_v2.html
90 if (!gimbalManager.receivedGimbalManagerInformation && (gimbalManager.requestGimbalManagerInformationRetries > 0)) {
91 _requestGimbalInformation(message.compid);
92 --gimbalManager.requestGimbalManagerInformationRetries;
93 }
94}
95
96void GimbalController::_handleGimbalManagerInformation(const mavlink_message_t &message)
97{
98 mavlink_gimbal_manager_information_t information{};
99 mavlink_msg_gimbal_manager_information_decode(&message, &information);
100
101 if (information.gimbal_device_id == 0) {
102 qCWarning(GimbalControllerLog) << "_handleGimbalManagerInformation for invalid gimbal device:"
103 << information.gimbal_device_id << ", from component id:" << message.compid;
104 return;
105 }
106
107 qCDebug(GimbalControllerLog) << "_handleGimbalManagerInformation for gimbal device:" << information.gimbal_device_id << ", component id:" << message.compid;
108
109 const GimbalPairId pairId{message.compid, information.gimbal_device_id};
110
111 auto gimbalIt = _potentialGimbals.find(pairId);
112 if (gimbalIt == _potentialGimbals.constEnd()) {
113 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
114 }
115
116 Gimbal *const gimbal = gimbalIt.value();
117 gimbal->setManagerCompid(message.compid);
118 gimbal->setDeviceId(information.gimbal_device_id);
119 gimbal->setCapabilityFlags(information.cap_flags);
120
121 if (!gimbal->_receivedGimbalManagerInformation) {
122 qCDebug(GimbalControllerLog) << "gimbal manager with compId:" << message.compid
123 << " is responsible for gimbal device:" << information.gimbal_device_id;
124 }
125
126 gimbal->_receivedGimbalManagerInformation = true;
127 // It is important to flag our potential gimbal manager as well, so we stop requesting gimbal_manger_information message
128 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
129 gimbalManager.receivedGimbalManagerInformation = true;
130
131 _checkComplete(*gimbal, pairId);
132}
133
134void GimbalController::_handleGimbalManagerStatus(const mavlink_message_t &message)
135{
136 mavlink_gimbal_manager_status_t status{};
137 mavlink_msg_gimbal_manager_status_decode(&message, &status);
138
139 // qCDebug(GimbalControllerLog) << "_handleGimbalManagerStatus for gimbal device:" << status.gimbal_device_id << ", component id:" << message.compid;
140
141 if (status.gimbal_device_id == 0) {
142 qCDebug(GimbalControllerLog) << "gimbal manager with compId:" << message.compid
143 << "reported status of gimbal device id:" << status.gimbal_device_id << "which is not a valid gimbal device id";
144 return;
145 }
146
147 const GimbalPairId pairId{message.compid, status.gimbal_device_id};
148
149 auto gimbalIt = _potentialGimbals.find(pairId);
150 if (gimbalIt == _potentialGimbals.constEnd()) {
151 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
152 }
153
154 Gimbal *const gimbal = gimbalIt.value();
155 if (gimbal->deviceId()->rawValue().toUInt() == 0) {
156 gimbal->setDeviceId(status.gimbal_device_id);
157 } else if (gimbal->deviceId()->rawValue().toUInt() != status.gimbal_device_id) {
158 qCWarning(GimbalControllerLog) << "conflicting GIMBAL_MANAGER_STATUS.gimbal_device_id:" << status.gimbal_device_id;
159 }
160
161 if (gimbal->managerCompid()->rawValue().toUInt() == 0) {
162 gimbal->setManagerCompid(message.compid);
163 } else if (gimbal->managerCompid()->rawValue().toUInt() != message.compid) {
164 qCWarning(GimbalControllerLog) << "conflicting GIMBAL_MANAGER_STATUS compid:" << message.compid;
165 }
166
167 // Only log this message once
168 if (!gimbal->_receivedGimbalManagerStatus) {
169 qCDebug(GimbalControllerLog) << "_handleGimbalManagerStatus: gimbal manager with compId" << message.compid
170 << "is responsible for gimbal device" << status.gimbal_device_id;
171 }
172
173 gimbal->_receivedGimbalManagerStatus = true;
174
175 const bool haveControl =
176 (status.primary_control_sysid == MAVLinkProtocol::instance()->getSystemId()) &&
177 (status.primary_control_compid == MAVLinkProtocol::getComponentId());
178
179 const bool othersHaveControl = !haveControl &&
180 (status.primary_control_sysid != 0 && status.primary_control_compid != 0);
181
182 if (gimbal->gimbalHaveControl() != haveControl) {
183 gimbal->setGimbalHaveControl(haveControl);
184 }
185
186 if (gimbal->gimbalOthersHaveControl() != othersHaveControl) {
187 gimbal->setGimbalOthersHaveControl(othersHaveControl);
188 }
189
190 _checkComplete(*gimbal, pairId);
191}
192
193void GimbalController::_handleGimbalDeviceAttitudeStatus(const mavlink_message_t &message)
194{
195 mavlink_gimbal_device_attitude_status_t attitude_status{};
196 mavlink_msg_gimbal_device_attitude_status_decode(&message, &attitude_status);
197
198 GimbalPairId pairId{};
199
200 if (attitude_status.gimbal_device_id == 0) {
201 // If gimbal_device_id is 0, we must take the compid of the message
202 pairId.deviceId = message.compid;
203
204 // We do a reverse lookup here
205 const auto foundGimbal = std::find_if(_potentialGimbals.begin(), _potentialGimbals.end(),
206 [pairId](Gimbal *gimbal) { return (gimbal->deviceId()->rawValue().toUInt() == pairId.deviceId); });
207
208 if (foundGimbal == _potentialGimbals.constEnd()) {
209 qCDebug(GimbalControllerLog) << "_handleGimbalDeviceAttitudeStatus for unknown device id:"
210 << pairId.deviceId << "from component id:" << message.compid;
211 return;
212 }
213
214 pairId.managerCompid = foundGimbal.key().managerCompid;
215 } else if (attitude_status.gimbal_device_id <= 6) {
216 // If the gimbal_device_id field is set to 1-6, we must use this device id instead
217 pairId.deviceId = attitude_status.gimbal_device_id;
218 pairId.managerCompid = message.compid;
219 } else {
220 // Otherwise, this is invalid and we don't know how to deal with it.
221 qCDebug(GimbalControllerLog) << "_handleGimbalDeviceAttitudeStatus for invalid device id: "
222 << attitude_status.gimbal_device_id << " from component id: " << message.compid;
223 return;
224 }
225
226 auto gimbalIt = _potentialGimbals.find(pairId);
227 if (gimbalIt == _potentialGimbals.end()) {
228 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
229 }
230
231 Gimbal *const gimbal = gimbalIt.value();
232
233 gimbal->setRetracted((attitude_status.flags & GIMBAL_DEVICE_FLAGS_RETRACT) > 0);
234 gimbal->setYawLock((attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) > 0);
235 gimbal->_neutral = (attitude_status.flags & GIMBAL_DEVICE_FLAGS_NEUTRAL) > 0;
236
237 float roll, pitch, yaw;
238 mavlink_quaternion_to_euler(attitude_status.q, &roll, &pitch, &yaw);
239
240 gimbal->setAbsoluteRoll(qRadiansToDegrees(roll));
241 gimbal->setAbsolutePitch(qRadiansToDegrees(pitch));
242
243 const bool yaw_in_vehicle_frame = _yawInVehicleFrame(attitude_status.flags);
244 if (yaw_in_vehicle_frame) {
245 const float bodyYaw = qRadiansToDegrees(yaw);
246 float absoluteYaw = bodyYaw + _vehicle->heading()->rawValue().toFloat();
247 if (absoluteYaw > 180.0f) {
248 absoluteYaw -= 360.0f;
249 }
250
251 gimbal->setBodyYaw(bodyYaw);
252 gimbal->setAbsoluteYaw(absoluteYaw);
253
254 } else {
255 const float absoluteYaw = qRadiansToDegrees(yaw);
256 float bodyYaw = absoluteYaw - _vehicle->heading()->rawValue().toFloat();
257 if (bodyYaw < -180.0f) {
258 bodyYaw += 360.0f;
259 }
260
261 gimbal->setBodyYaw(bodyYaw);
262 gimbal->setAbsoluteYaw(absoluteYaw);
263 }
264
265 gimbal->_receivedGimbalDeviceAttitudeStatus = true;
266
267 _checkComplete(*gimbal, pairId);
268}
269
270void GimbalController::_requestGimbalInformation(uint8_t compid)
271{
272 qCDebug(GimbalControllerLog) << "_requestGimbalInformation(" << compid << ")";
273
274 if (_vehicle) {
275 _vehicle->sendMavCommand(compid,
276 MAV_CMD_REQUEST_MESSAGE,
277 false /* no error */,
278 MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION);
279 }
280}
281
282void GimbalController::_checkComplete(Gimbal &gimbal, GimbalPairId pairId)
283{
284 if (gimbal._isComplete) {
285 // Already complete, nothing to do.
286 return;
287 }
288
289 if (!gimbal._receivedGimbalManagerInformation && gimbal._requestInformationRetries > 0) {
290 _requestGimbalInformation(pairId.managerCompid);
291 --gimbal._requestInformationRetries;
292 }
293 // Limit to 1 second between set message interface requests
294 static qint64 lastRequestStatusMessage = 0;
295 qint64 now = QDateTime::currentMSecsSinceEpoch();
296 if (!gimbal._receivedGimbalManagerStatus && (gimbal._requestStatusRetries > 0) && (now - lastRequestStatusMessage > 1000)) {
297 lastRequestStatusMessage = now;
298 _vehicle->sendMavCommand(pairId.managerCompid,
299 MAV_CMD_SET_MESSAGE_INTERVAL,
300 false /* no error */,
301 MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS,
302 (gimbal._requestStatusRetries > 2) ? 0 : 5000000); // request default rate, if we don't succeed, last attempt is fixed 0.2 Hz instead
303 --gimbal._requestStatusRetries;
304 qCDebug(GimbalControllerLog) << "attempt to set GIMBAL_MANAGER_STATUS message at"
305 << (gimbal._requestStatusRetries > 2 ? "default rate" : "0.2 Hz") << "interval for device:"
306 << gimbal.deviceId()->rawValue().toUInt() << "manager compID:" << pairId.managerCompid
307 << ", retries remaining:" << gimbal._requestStatusRetries;
308 }
309
310 if (!gimbal._receivedGimbalDeviceAttitudeStatus && (gimbal._requestAttitudeRetries > 0) &&
311 gimbal._receivedGimbalManagerInformation && (pairId.deviceId != 0)) {
312 // We request the attitude directly from the gimbal device component.
313 // We can only do that once we have received the gimbal manager information
314 // telling us which gimbal device it is responsible for.
315 uint8_t gimbalDeviceCompid = pairId.deviceId;
316 // If the device ID is 1-6, we need to request the message from the manager itself.
317 if (gimbalDeviceCompid <= 6) {
318 gimbalDeviceCompid = pairId.managerCompid;
319 }
320 _vehicle->sendMavCommand(gimbalDeviceCompid,
321 MAV_CMD_SET_MESSAGE_INTERVAL,
322 false /* no error */,
323 MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS,
324 0 /* request default rate */);
325
326 --gimbal._requestAttitudeRetries;
327 }
328
329 if (!gimbal._receivedGimbalManagerInformation || !gimbal._receivedGimbalManagerStatus || !gimbal._receivedGimbalDeviceAttitudeStatus) {
330 // Not complete yet.
331 return;
332 }
333
334 gimbal._isComplete = true;
335
336 // If there is no current active gimbal, set this one as active
337 if (!_activeGimbal) {
338 setActiveGimbal(&gimbal);
339 }
340
341 _gimbals->append(&gimbal);
342 // This is needed for new Gimbals telemetry to be available for the user to show in flyview telemetry panel
343 _vehicle->_addFactGroup(&gimbal, QStringLiteral("%1%2%3").arg(_gimbalFactGroupNamePrefix).arg(pairId.managerCompid).arg(pairId.deviceId));
344}
345
346bool GimbalController::_tryGetGimbalControl()
347{
348 if (!_activeGimbal) {
349 qCCritical(GimbalControllerLog) << "_tryGetGimbalControl: active gimbal is nullptr, returning";
350 return false;
351 }
352
353 if (_activeGimbal->gimbalOthersHaveControl()) {
354 qCDebug(GimbalControllerLog) << "Others in control, showing popup for user to confirm control..";
356 return false;
357 } else if (!_activeGimbal->gimbalHaveControl()) {
358 qCDebug(GimbalControllerLog) << "Nobody in control, acquiring control ourselves..";
360 }
361
362 return true;
363}
364
365bool GimbalController::_yawInVehicleFrame(uint32_t flags)
366{
367 if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME) > 0) {
368 return true;
369 } else if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME) > 0) {
370 return false;
371 } else {
372 // For backwards compatibility: if both new flags are 0, yaw lock defines the frame.
373 return ((flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) == 0);
374 }
375}
376
378{
379 if (!_activeGimbal) {
380 qCCritical(GimbalControllerLog) << "gimbalPitchStart: active gimbal is nullptr, returning";
381 return;
382 }
383
384 const float speed = SettingsManager::instance()->gimbalControllerSettings()->joystickButtonsSpeed()->rawValue().toInt();
385 activeGimbal()->setPitchRate(direction * speed);
386
387 sendRate();
388}
389
391{
392 if (!_activeGimbal) {
393 qCCritical(GimbalControllerLog) << "gimbalYawStart: active gimbal is nullptr, returning";
394 return;
395 }
396
397 const float speed = SettingsManager::instance()->gimbalControllerSettings()->joystickButtonsSpeed()->rawValue().toInt();
398 activeGimbal()->setYawRate(direction * speed);
399 sendRate();
400}
401
403{
404 if (!_activeGimbal) {
405 qCCritical(GimbalControllerLog) << "gimbalPitchStop: active gimbal is nullptr, returning";
406 return;
407 }
408
409 activeGimbal()->setPitchRate(0.0f);
410 sendRate();
411}
412
414{
415 if (!_activeGimbal) {
416 qCCritical(GimbalControllerLog) << "gimbalYawStop: active gimbal is nullptr, returning";
417 return;
418 }
419
420 activeGimbal()->setYawRate(0.0f);
421 sendRate();
422}
423
425{
426 if (!_activeGimbal) {
427 qCCritical(GimbalControllerLog) << "gimbalYawStep: active gimbal is nullptr, returning";
428 return;
429 }
430 sendPitchBodyYaw(0.0, 0.0, true);
431}
432
433void GimbalController::gimbalOnScreenControl(float panPct, float tiltPct, bool clickAndPoint, bool clickAndDrag, bool /*rateControl*/, bool /*retract*/, bool /*neutral*/, bool /*yawlock*/)
434{
435 // Pan and tilt comes as +-(0-1)
436
437 if (!_activeGimbal) {
438 qCCritical(GimbalControllerLog) << "gimbalOnScreenControl: active gimbal is nullptr, returning";
439 return;
440 }
441
442 if (clickAndPoint) { // based on FOV
443 const float hFov = SettingsManager::instance()->gimbalControllerSettings()->cameraHFov()->rawValue().toFloat();
444 const float vFov = SettingsManager::instance()->gimbalControllerSettings()->cameraVFov()->rawValue().toFloat();
445
446 const float panIncDesired = panPct * hFov * 0.5f;
447 const float tiltIncDesired = tiltPct * vFov * 0.5f;
448
449 const float panDesired = panIncDesired + _activeGimbal->bodyYaw()->rawValue().toFloat();
450 const float tiltDesired = tiltIncDesired + _activeGimbal->absolutePitch()->rawValue().toFloat();
451
452 if (_activeGimbal->yawLock()) {
453 sendPitchAbsoluteYaw(tiltDesired, panDesired + _vehicle->heading()->rawValue().toFloat(), false);
454 } else {
455 sendPitchBodyYaw(tiltDesired, panDesired, false);
456 }
457 } else if (clickAndDrag) { // based on maximum speed
458 // Should send rate commands, but it seems for some reason it is not working on AP side.
459 // Pitch works ok but yaw doesn't stop, it keeps like inertia, like if it was buffering the messages.
460 // So we do a workaround with angle targets
461 const float maxSpeed = SettingsManager::instance()->gimbalControllerSettings()->cameraSlideSpeed()->rawValue().toFloat();
462
463 const float panIncDesired = panPct * maxSpeed * 0.1f;
464 const float tiltIncDesired = tiltPct * maxSpeed * 0.1f;
465
466 const float panDesired = panIncDesired + _activeGimbal->bodyYaw()->rawValue().toFloat();
467 const float tiltDesired = tiltIncDesired + _activeGimbal->absolutePitch()->rawValue().toFloat();
468
469 if (_activeGimbal->yawLock()) {
470 sendPitchAbsoluteYaw(tiltDesired, panDesired + _vehicle->heading()->rawValue().toFloat(), false);
471 } else {
472 sendPitchBodyYaw(tiltDesired, panDesired, false);
473 }
474 }
475}
476
477void GimbalController::sendPitchBodyYaw(float pitch, float yaw, bool showError)
478{
479 if (!_tryGetGimbalControl()) {
480 return;
481 }
482
483 _rateSenderTimer.stop();
484 _activeGimbal->setAbsolutePitch(0.0f);
485 _activeGimbal->setYawRate(0.0f);
486
487 // qCDebug(GimbalControllerLog) << "sendPitch: " << pitch << " BodyYaw: " << yaw;
488
489 const unsigned flags = GIMBAL_MANAGER_FLAGS_ROLL_LOCK
490 | GIMBAL_MANAGER_FLAGS_PITCH_LOCK
491 | GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME;
492
493 _vehicle->sendMavCommand(
494 _activeGimbal->managerCompid()->rawValue().toUInt(),
495 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
496 showError,
497 pitch,
498 yaw,
499 NAN,
500 NAN,
501 flags,
502 0,
503 _activeGimbal->deviceId()->rawValue().toUInt());
504}
505
506void GimbalController::sendPitchAbsoluteYaw(float pitch, float yaw, bool showError)
507{
508 if (!_tryGetGimbalControl()) {
509 return;
510 }
511
512 _rateSenderTimer.stop();
513 _activeGimbal->setAbsolutePitch(0.0f);
514 _activeGimbal->setYawRate(0.0f);
515
516 if (yaw > 180.0f) {
517 yaw -= 360.0f;
518 }
519
520 if (yaw < -180.0f) {
521 yaw += 360.0f;
522 }
523
524 // qCDebug() << "sendPitch: " << pitch << " absoluteYaw: " << yaw;
525
526 const unsigned flags = GIMBAL_MANAGER_FLAGS_ROLL_LOCK
527 | GIMBAL_MANAGER_FLAGS_PITCH_LOCK
528 | GIMBAL_MANAGER_FLAGS_YAW_LOCK
529 | GIMBAL_MANAGER_FLAGS_YAW_IN_EARTH_FRAME;
530
531 _vehicle->sendMavCommand(
532 _activeGimbal->managerCompid()->rawValue().toUInt(),
533 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
534 showError,
535 pitch,
536 yaw,
537 NAN,
538 NAN,
539 flags,
540 0,
541 _activeGimbal->deviceId()->rawValue().toUInt());
542}
543
545{
546 if (!_tryGetGimbalControl()) {
547 return;
548 }
549
550 uint32_t flags = 0;
551 if (set) {
552 flags |= GIMBAL_DEVICE_FLAGS_RETRACT;
553 } else {
554 flags &= ~GIMBAL_DEVICE_FLAGS_RETRACT;
555 }
556
557 sendPitchYawFlags(flags);
558}
559
561{
562 if (!_tryGetGimbalControl()) {
563 return;
564 }
565
566 // We send raw mavlink instead of using MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW because
567 // when both pitch and yaw stop simultaneously, Vehicle's duplicate command detection
568 // drops the second sendMavCommand call, leaving one axis spinning indefinitely.
569 _sendGimbalAttitudeRates(_activeGimbal->pitchRate(), _activeGimbal->yawRate());
570
571 // Stop timeout if both unset.
572 if ((_activeGimbal->pitchRate() == 0.f) && (_activeGimbal->yawRate() == 0.f)) {
573 _rateSenderTimer.stop();
574 } else {
575 _rateSenderTimer.start();
576 }
577}
578
579void GimbalController::sendGimbalRate(float pitch_rate_deg_s, float yaw_rate_deg_s)
580{
581 if (!_tryGetGimbalControl()) {
582 return;
583 }
584
585 _sendGimbalAttitudeRates(pitch_rate_deg_s, yaw_rate_deg_s);
586
587 if (pitch_rate_deg_s == 0.f && yaw_rate_deg_s == 0.f) {
588 _rateSenderTimer.stop();
589 } else {
590 _rateSenderTimer.start();
591 }
592}
593
594void GimbalController::_sendGimbalAttitudeRates(float pitch_rate_deg_s,
595 float yaw_rate_deg_s)
596{
597
598 auto sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
599 if (!sharedLink) {
600 qCDebug(GimbalControllerLog) << "_sendGimbalAttitudeRates: primary link gone!";
601 return;
602 }
603
604 uint32_t flags =
605 GIMBAL_MANAGER_FLAGS_ROLL_LOCK |
606 GIMBAL_MANAGER_FLAGS_PITCH_LOCK |
607 GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME; // use vehicle/body frame
608
609 // Preserve current yaw-lock state instead of changing it:
610 if (_activeGimbal->yawLock()) {
611 flags |= GIMBAL_MANAGER_FLAGS_YAW_LOCK;
612 }
613
614 const float qnan[4] = {NAN, NAN, NAN, NAN};
616
617 mavlink_msg_gimbal_manager_set_attitude_pack_chan(
618 MAVLinkProtocol::instance()->getSystemId(),
620 sharedLink->mavlinkChannel(),
621 &msg,
622 _vehicle->id(),
623 static_cast<uint8_t>(_activeGimbal->managerCompid()->rawValue().toUInt()),
624 flags,
625 static_cast<uint8_t>(_activeGimbal->deviceId()->rawValue().toUInt()),
626 qnan,
627 NAN,
628 qDegreesToRadians(pitch_rate_deg_s),
629 qDegreesToRadians(yaw_rate_deg_s)
630 );
631
632 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
633}
634
635void GimbalController::_rateSenderTimeout()
636{
637 // Send rate again to avoid timeout on autopilot side.
638 sendRate();
639}
640
642{
643 if (!_tryGetGimbalControl()) {
644 return;
645 }
646
647 // Roll and pitch are usually "locked", so with horizon and not with aircraft.
648 uint32_t flags = GIMBAL_DEVICE_FLAGS_ROLL_LOCK | GIMBAL_DEVICE_FLAGS_PITCH_LOCK;
649 if (set) {
650 flags |= GIMBAL_DEVICE_FLAGS_YAW_LOCK;
651 }
652
653 sendPitchYawFlags(flags);
654}
655
657{
658 const bool yaw_in_vehicle_frame = _yawInVehicleFrame(flags);
659
660 _vehicle->sendMavCommand(
661 _activeGimbal->managerCompid()->rawValue().toUInt(),
662 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
663 true,
664 _activeGimbal->absolutePitch()->rawValue().toFloat(),
665 yaw_in_vehicle_frame ? _activeGimbal->bodyYaw()->rawValue().toFloat() : _activeGimbal->absoluteYaw()->rawValue().toFloat(),
666 NAN,
667 NAN,
668 flags,
669 0,
670 _activeGimbal->deviceId()->rawValue().toUInt());
671}
672
674{
675 if (!_activeGimbal) {
676 qCCritical(GimbalControllerLog) << "acquireGimbalControl: active gimbal is nullptr, returning";
677 return;
678 }
679
680 _vehicle->sendMavCommand(
681 _activeGimbal->managerCompid()->rawValue().toUInt(),
682 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
683 true,
684 MAVLinkProtocol::instance()->getSystemId(), // Set us in primary control.
685 MAVLinkProtocol::getComponentId(), // Set us in primary control
686 -1.f, // Leave secondary unchanged
687 -1.f, // Leave secondary unchanged
688 NAN, // Reserved
689 NAN, // Reserved
690 _activeGimbal->deviceId()->rawValue().toUInt());
691}
692
694{
695 if (!_activeGimbal) {
696 qCCritical(GimbalControllerLog) << "releaseGimbalControl: active gimbal is nullptr, returning";
697 return;
698 }
699
700 _vehicle->sendMavCommand(
701 _activeGimbal->managerCompid()->rawValue().toUInt(),
702 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
703 true,
704 -3.f, // Release primary control if we have control
705 -3.f, // Release primary control if we have control
706 -1.f, // Leave secondary control unchanged
707 -1.f, // Leave secondary control unchanged
708 NAN, // Reserved
709 NAN, // Reserved
710 _activeGimbal->deviceId()->rawValue().toUInt());
711}
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void _addFactGroup(FactGroup *factGroup, const QString &name)
Definition FactGroup.cc:133
QVariant rawValue() const
Value after translation.
Definition Fact.h:85
void sendPitchYawFlags(uint32_t flags)
Q_INVOKABLE void sendGimbalRate(float pitch_rate_deg_s, float yaw_rate_deg_s)
Q_INVOKABLE void setGimbalYawLock(bool set)
Q_INVOKABLE void centerGimbal()
Gimbal * activeGimbal() const
void activeGimbalChanged()
Q_INVOKABLE void sendRate()
Q_INVOKABLE void sendPitchAbsoluteYaw(float pitch, float yaw, bool showError=true)
Q_INVOKABLE void gimbalOnScreenControl(float panpct, float tiltpct, bool clickAndPoint, bool clickAndDrag, bool rateControl, bool retract=false, bool neutral=false, bool yawlock=false)
Q_INVOKABLE void acquireGimbalControl()
void setActiveGimbal(Gimbal *gimbal)
void gimbalYawStart(int direction)
Q_INVOKABLE void setGimbalRetract(bool set)
void showAcquireGimbalControlPopup()
void gimbalPitchStart(int direction)
Q_INVOKABLE void releaseGimbalControl()
Q_INVOKABLE void sendPitchBodyYaw(float pitch, float yaw, bool showError=true)
Definition Gimbal.h:9
void setAbsolutePitch(float absPitch)
Definition Gimbal.h:49
float pitchRate() const
Definition Gimbal.h:41
void setYawLock(bool yawLock)
Definition Gimbal.h:57
void setPitchRate(float pitchRate)
Definition Gimbal.h:55
Fact * absolutePitch()
Definition Gimbal.h:35
bool yawLock() const
Definition Gimbal.h:43
void setManagerCompid(uint id)
Definition Gimbal.h:53
Fact * managerCompid()
Definition Gimbal.h:39
Fact * deviceId()
Definition Gimbal.h:38
void setAbsoluteYaw(float absYaw)
Definition Gimbal.h:51
void setYawRate(float yawRate)
Definition Gimbal.h:56
bool gimbalOthersHaveControl() const
Definition Gimbal.h:46
void setGimbalHaveControl(bool set)
Definition Gimbal.h:59
void setBodyYaw(float yaw)
Definition Gimbal.h:50
void setDeviceId(uint id)
Definition Gimbal.h:52
void setRetracted(bool retracted)
Definition Gimbal.h:58
Fact * absoluteYaw()
Definition Gimbal.h:37
bool gimbalHaveControl() const
Definition Gimbal.h:45
void setAbsoluteRoll(float absRoll)
Definition Gimbal.h:48
void setGimbalOthersHaveControl(bool set)
Definition Gimbal.h:60
float yawRate() const
Definition Gimbal.h:42
void setCapabilityFlags(uint32_t flags)
Definition Gimbal.cc:69
Fact * bodyYaw()
Definition Gimbal.h:36
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
static SettingsManager * instance()
GimbalControllerSettings * gimbalControllerSettings() const
WeakLinkInterfacePtr primaryLink() const
void sendMavCommand(int compId, MAV_CMD command, bool showError, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f, float param6=0.0f, float param7=0.0f)
Definition Vehicle.cc:2146
void initialConnectComplete()
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:575
int id() const
Definition Vehicle.h:425
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1389
void mavlinkMessageReceived(const mavlink_message_t &message)