QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
GimbalController.cc
Go to the documentation of this file.
1#include "GimbalController.h"
2
3#include <cmath>
4#include <cstring>
5
6#include "Gimbal.h"
8#include "MAVLinkLib.h"
9#include "MAVLinkProtocol.h"
10#include "ParameterManager.h"
11#include "QGCCameraManager.h"
12#include "QGCLoggingCategory.h"
13#include "QmlObjectListModel.h"
14#include "SettingsManager.h"
15#include "Vehicle.h"
16#include "VehicleLinkManager.h"
17
18QGC_LOGGING_CATEGORY(GimbalControllerLog, "Gimbal.GimbalController")
19
21 : QObject(vehicle)
22 , _vehicle(vehicle)
23 , _gimbals(new QmlObjectListModel(this))
24{
25 qCDebug(GimbalControllerLog) << this;
26
27 (void) connect(_vehicle, &Vehicle::initialConnectComplete, this, &GimbalController::_initialConnectCompleted, Qt::UniqueConnection);
28 (void) connect(_vehicle, &Vehicle::mavlinkMessageReceived, this, &GimbalController::_mavlinkMessageReceived);
29
30 _rateSenderTimer.setInterval(500);
31 (void) connect(&_rateSenderTimer, &QTimer::timeout, this, &GimbalController::_rateSenderTimeout);
32}
33
35{
36 qCDebug(GimbalControllerLog) << this;
37}
38
39void GimbalController::_initialConnectCompleted()
40{
41 _initialConnectComplete = true;
42}
43
45{
46 if (!gimbal) {
47 qCCritical(GimbalControllerLog) << "Set active gimbal: attempted to set a nullptr, returning";
48 return;
49 }
50
51 if (gimbal != _activeGimbal) {
52 qCDebug(GimbalControllerLog) << "Set active gimbal:" << gimbal;
53 _activeGimbal = gimbal;
55 }
56}
57
58void GimbalController::_mavlinkMessageReceived(const mavlink_message_t &message)
59{
60 if (!_initialConnectComplete) {
61 return;
62 }
63
64 switch (message.msgid) {
65 case MAVLINK_MSG_ID_HEARTBEAT:
66 _handleHeartbeat(message);
67 break;
68 case MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION:
69 _handleGimbalManagerInformation(message);
70 break;
71 case MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS:
72 _handleGimbalManagerStatus(message);
73 break;
74 case MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS:
75 _handleGimbalDeviceAttitudeStatus(message);
76 break;
77 case MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION:
78 _handleGimbalDeviceInformation(message);
79 break;
80 default:
81 break;
82 }
83}
84
85void GimbalController::_handleHeartbeat(const mavlink_message_t &message)
86{
87 if (!_potentialGimbalManagers.contains(message.compid)) {
88 qCDebug(GimbalControllerLog) << "new potential gimbal manager component:" << message.compid;
89 }
90
91 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
92
93 // Note that we are working over potential gimbal managers here, instead of potential gimbals.
94 // This is because we address the gimbal manager by compid, but a gimbal device might have an
95 // id different than the message compid it comes from. For more information see https://mavlink.io/en/services/gimbal_v2.html
96 if (!gimbalManager.receivedGimbalManagerInformation && (gimbalManager.requestGimbalManagerInformationRetries > 0)) {
97 if (_requestGimbalInformation(message.compid)) {
98 --gimbalManager.requestGimbalManagerInformationRetries;
99 }
100 }
101}
102
103void GimbalController::_handleGimbalManagerInformation(const mavlink_message_t &message)
104{
105 mavlink_gimbal_manager_information_t information{};
106 mavlink_msg_gimbal_manager_information_decode(&message, &information);
107
108 if (information.gimbal_device_id == 0) {
109 qCWarning(GimbalControllerLog) << "_handleGimbalManagerInformation for invalid gimbal device:"
110 << information.gimbal_device_id << ", from component id:" << message.compid;
111 return;
112 }
113
114 qCDebug(GimbalControllerLog) << "_handleGimbalManagerInformation for gimbal device:" << information.gimbal_device_id << ", component id:" << message.compid;
115
116 const GimbalPairId pairId{message.compid, information.gimbal_device_id};
117
118 auto gimbalIt = _potentialGimbals.find(pairId);
119 if (gimbalIt == _potentialGimbals.constEnd()) {
120 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
121 }
122
123 Gimbal *const gimbal = gimbalIt.value();
124 gimbal->setManagerCompid(message.compid);
125 gimbal->setDeviceId(information.gimbal_device_id);
126 gimbal->setCapabilityFlags(information.cap_flags);
127
128 if (!gimbal->_receivedGimbalManagerInformation) {
129 qCDebug(GimbalControllerLog) << "gimbal manager with compId:" << message.compid
130 << " is responsible for gimbal device:" << information.gimbal_device_id;
131 }
132
133 gimbal->_receivedGimbalManagerInformation = true;
134 // It is important to flag our potential gimbal manager as well, so we stop requesting gimbal_manger_information message
135 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
136 gimbalManager.receivedGimbalManagerInformation = true;
137
138 _checkComplete(*gimbal, pairId);
139}
140
141void GimbalController::_handleGimbalManagerStatus(const mavlink_message_t &message)
142{
143 mavlink_gimbal_manager_status_t status{};
144 mavlink_msg_gimbal_manager_status_decode(&message, &status);
145
146 // qCDebug(GimbalControllerLog) << "_handleGimbalManagerStatus for gimbal device:" << status.gimbal_device_id << ", component id:" << message.compid;
147
148 if (status.gimbal_device_id == 0) {
149 qCDebug(GimbalControllerLog) << "gimbal manager with compId:" << message.compid
150 << "reported status of gimbal device id:" << status.gimbal_device_id << "which is not a valid gimbal device id";
151 return;
152 }
153
154 const GimbalPairId pairId{message.compid, status.gimbal_device_id};
155
156 auto gimbalIt = _potentialGimbals.find(pairId);
157 if (gimbalIt == _potentialGimbals.constEnd()) {
158 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
159 }
160
161 Gimbal *const gimbal = gimbalIt.value();
162 if (gimbal->deviceId()->rawValue().toUInt() == 0) {
163 gimbal->setDeviceId(status.gimbal_device_id);
164 } else if (gimbal->deviceId()->rawValue().toUInt() != status.gimbal_device_id) {
165 qCWarning(GimbalControllerLog) << "conflicting GIMBAL_MANAGER_STATUS.gimbal_device_id:" << status.gimbal_device_id;
166 }
167
168 if (gimbal->managerCompid()->rawValue().toUInt() == 0) {
169 gimbal->setManagerCompid(message.compid);
170 } else if (gimbal->managerCompid()->rawValue().toUInt() != message.compid) {
171 qCWarning(GimbalControllerLog) << "conflicting GIMBAL_MANAGER_STATUS compid:" << message.compid;
172 }
173
174 // Only log this message once
175 if (!gimbal->_receivedGimbalManagerStatus) {
176 qCDebug(GimbalControllerLog) << "_handleGimbalManagerStatus: gimbal manager with compId" << message.compid
177 << "is responsible for gimbal device" << status.gimbal_device_id;
178 }
179
180 gimbal->_receivedGimbalManagerStatus = true;
181
182 const bool haveControl =
183 (status.primary_control_sysid == MAVLinkProtocol::instance()->getSystemId()) &&
184 (status.primary_control_compid == MAVLinkProtocol::getComponentId());
185
186 const bool othersHaveControl = !haveControl &&
187 (status.primary_control_sysid != 0 && status.primary_control_compid != 0);
188
189 if (gimbal->gimbalHaveControl() != haveControl) {
190 gimbal->setGimbalHaveControl(haveControl);
191 }
192
193 if (gimbal->gimbalOthersHaveControl() != othersHaveControl) {
194 gimbal->setGimbalOthersHaveControl(othersHaveControl);
195 }
196
197 _checkComplete(*gimbal, pairId);
198}
199
200void GimbalController::_handleGimbalDeviceAttitudeStatus(const mavlink_message_t &message)
201{
202 mavlink_gimbal_device_attitude_status_t attitude_status{};
203 mavlink_msg_gimbal_device_attitude_status_decode(&message, &attitude_status);
204
205 GimbalPairId pairId{};
206
207 if (attitude_status.gimbal_device_id == 0) {
208 // If gimbal_device_id is 0, we must take the compid of the message
209 pairId.deviceId = message.compid;
210
211 // We do a reverse lookup here
212 const auto foundGimbal = std::find_if(_potentialGimbals.begin(), _potentialGimbals.end(),
213 [pairId](Gimbal *gimbal) { return (gimbal->deviceId()->rawValue().toUInt() == pairId.deviceId); });
214
215 if (foundGimbal == _potentialGimbals.constEnd()) {
216 qCDebug(GimbalControllerLog) << "_handleGimbalDeviceAttitudeStatus for unknown device id:"
217 << pairId.deviceId << "from component id:" << message.compid;
218 return;
219 }
220
221 pairId.managerCompid = foundGimbal.key().managerCompid;
222 } else if (attitude_status.gimbal_device_id <= 6) {
223 // If the gimbal_device_id field is set to 1-6, we must use this device id instead
224 pairId.deviceId = attitude_status.gimbal_device_id;
225 pairId.managerCompid = message.compid;
226 } else {
227 // Otherwise, this is invalid and we don't know how to deal with it.
228 qCDebug(GimbalControllerLog) << "_handleGimbalDeviceAttitudeStatus for invalid device id: "
229 << attitude_status.gimbal_device_id << " from component id: " << message.compid;
230 return;
231 }
232
233 auto gimbalIt = _potentialGimbals.find(pairId);
234 if (gimbalIt == _potentialGimbals.end()) {
235 gimbalIt = _potentialGimbals.insert(pairId, new Gimbal(this));
236 }
237
238 Gimbal *const gimbal = gimbalIt.value();
239
240 gimbal->setRetracted((attitude_status.flags & GIMBAL_DEVICE_FLAGS_RETRACT) > 0);
241 gimbal->setYawLock((attitude_status.flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) > 0);
242 gimbal->_neutral = (attitude_status.flags & GIMBAL_DEVICE_FLAGS_NEUTRAL) > 0;
243
244 float roll, pitch, yaw;
245 mavlink_quaternion_to_euler(attitude_status.q, &roll, &pitch, &yaw);
246
247 gimbal->setAbsoluteRoll(qRadiansToDegrees(roll));
248 gimbal->setAbsolutePitch(qRadiansToDegrees(pitch));
249
250 // The two yaw frame flags are mutually exclusive. Both set is malformed: ignore both and fall back to the
251 // legacy YAW_LOCK frame semantics with the vehicle's heading.
252 constexpr uint16_t kYawFrameFlags = GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME | GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME;
253 uint16_t flags = attitude_status.flags;
254 const bool yawFrameFlagsInvalid = (flags & kYawFrameFlags) == kYawFrameFlags;
255 if (yawFrameFlagsInvalid) {
256 flags &= ~kYawFrameFlags;
257 }
258 if (yawFrameFlagsInvalid != gimbal->_yawFrameFlagsInvalid) {
259 gimbal->_yawFrameFlagsInvalid = yawFrameFlagsInvalid;
260 if (yawFrameFlagsInvalid) {
261 qCWarning(GimbalControllerLog) << "GIMBAL_DEVICE_ATTITUDE_STATUS has both yaw frame flags set, ignoring them for device:"
262 << gimbal->deviceId()->rawValue().toUInt();
263 }
264 }
265
266 // Spec: delta_yaw (gimbal's own estimate of vehicle heading) is only meaningful when a yaw frame flag is
267 // set; NaN means unknown. Without it fall back to the vehicle's heading.
268 const bool frameFlagged = (flags & kYawFrameFlags) != 0;
269 const bool deltaYawValid = frameFlagged && !std::isnan(attitude_status.delta_yaw);
270 const float deltaYawDeg = deltaYawValid ? std::remainder(qRadiansToDegrees(attitude_status.delta_yaw), 360.0f) : qQNaN();
271 if (deltaYawValid != gimbal->_deltaYawValid) {
272 gimbal->_deltaYawValid = deltaYawValid;
273 qCDebug(GimbalControllerLog) << "delta_yaw heading source" << (deltaYawValid ? "available" : "unavailable")
274 << "for device:" << gimbal->deviceId()->rawValue().toUInt();
275 }
276 gimbal->setDeltaYaw(deltaYawDeg);
277
278 const float headingDeg = deltaYawValid ? deltaYawDeg : _vehicle->heading()->rawValue().toFloat();
279
280 const bool yaw_in_vehicle_frame = _yawInVehicleFrame(flags);
281 if (yaw_in_vehicle_frame) {
282 const float bodyYaw = qRadiansToDegrees(yaw);
283 const float absoluteYaw = std::remainder(bodyYaw + headingDeg, 360.0f);
284
285 gimbal->setBodyYaw(bodyYaw);
286 gimbal->setAbsoluteYaw(absoluteYaw);
287
288 } else {
289 const float absoluteYaw = qRadiansToDegrees(yaw);
290 const float bodyYaw = std::remainder(absoluteYaw - headingDeg, 360.0f);
291
292 gimbal->setBodyYaw(bodyYaw);
293 gimbal->setAbsoluteYaw(absoluteYaw);
294 }
295
296 gimbal->_receivedGimbalDeviceAttitudeStatus = true;
297
298 _checkComplete(*gimbal, pairId);
299}
300
301void GimbalController::_handleGimbalDeviceInformation(const mavlink_message_t& message)
302{
303 mavlink_gimbal_device_information_t information{};
304 mavlink_msg_gimbal_device_information_decode(&message, &information);
305
306 // Build a human-readable name: prefer the user-set custom name, otherwise
307 // combine vendor and model. The char arrays are not guaranteed null-terminated.
308 const auto toString = [](const char* field, size_t size) {
309 return QString::fromLatin1(field, static_cast<int>(strnlen(field, size))).trimmed();
310 };
311 QString name = toString(information.custom_name, sizeof(information.custom_name));
312 if (name.isEmpty()) {
313 const QString vendor = toString(information.vendor_name, sizeof(information.vendor_name));
314 const QString model = toString(information.model_name, sizeof(information.model_name));
315 name = QStringLiteral("%1 %2").arg(vendor, model).trimmed();
316 }
317
318 if (name.isEmpty()) {
319 return;
320 }
321
322 // Match to the gimbal. If gimbal_device_id is 1-6 the manager and device
323 // share this component id, so the pair is {compid, gimbal_device_id}.
324 // Otherwise the device is its own component and its compid is the device id.
325 Gimbal* gimbal = nullptr;
326 if (information.gimbal_device_id != 0) {
327 const auto it = _potentialGimbals.find(GimbalPairId{message.compid, information.gimbal_device_id});
328 if (it != _potentialGimbals.constEnd()) {
329 gimbal = it.value();
330 }
331 } else {
332 for (Gimbal* const candidate : _potentialGimbals) {
333 if (candidate->deviceId()->rawValue().toUInt() == message.compid) {
334 gimbal = candidate;
335 break;
336 }
337 }
338 }
339
340 if (gimbal) {
341 gimbal->setDeviceName(name);
342 gimbal->_receivedGimbalDeviceInformation = true;
343 qCDebug(GimbalControllerLog) << "gimbal device name:" << name << "for compid:" << message.compid;
344 }
345}
346
347bool GimbalController::_requestGimbalInformation(uint8_t compid)
348{
349 qCDebug(GimbalControllerLog) << "_requestGimbalInformation(" << compid << ")";
350
351 if (!_vehicle) {
352 return false;
353 }
354 if (_pendingInformationRequestCompId != -1) {
355 qCDebug(GimbalControllerLog) << "_requestGimbalInformation: request already in flight for compid" << _pendingInformationRequestCompId;
356 return false;
357 }
358
359 // Must go through requestMessage rather than sending MAV_CMD_REQUEST_MESSAGE directly so
360 // that it serializes with other request-message users targeting the same component
361 // (a raw send collides with theirs in the command queue's duplicate-command check).
362 _pendingInformationRequestCompId = compid;
363 _vehicle->requestMessage(_requestMessageResultHandler,
364 this,
365 compid,
366 MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION);
367 return true;
368}
369
370void GimbalController::_requestMessageResultHandler(void* resultHandlerData, MAV_RESULT result, VehicleTypes::RequestMessageResultHandlerFailureCode_t failureCode, const mavlink_message_t& message)
371{
372 Q_UNUSED(message);
373
374 auto* controller = static_cast<GimbalController*>(resultHandlerData);
375 controller->_pendingInformationRequestCompId = -1;
376
377 // Success is handled by the normal GIMBAL_MANAGER_INFORMATION message dispatch and
378 // failures are retried from _checkComplete, so just log here.
379 if (result != MAV_RESULT_ACCEPTED) {
380 qCDebug(GimbalControllerLog) << "GIMBAL_MANAGER_INFORMATION request failed - result:" << result << "failureCode:" << failureCode;
381 }
382}
383
384void GimbalController::_requestDeviceInformationResultHandler(
385 void* resultHandlerData, MAV_RESULT result, VehicleTypes::RequestMessageResultHandlerFailureCode_t failureCode,
386 const mavlink_message_t& message)
387{
388 Q_UNUSED(resultHandlerData);
389 Q_UNUSED(message);
390
391 // Success is handled by the normal GIMBAL_DEVICE_INFORMATION message dispatch. The name is
392 // optional, so on failure we keep showing the IDs.
393 if (result != MAV_RESULT_ACCEPTED) {
394 qCDebug(GimbalControllerLog) << "GIMBAL_DEVICE_INFORMATION request failed - result:" << result
395 << "failureCode:" << failureCode;
396 }
397}
398
399void GimbalController::_checkComplete(Gimbal &gimbal, GimbalPairId pairId)
400{
401 if (gimbal._isComplete) {
402 // Already complete, nothing to do.
403 return;
404 }
405
406 if (!gimbal._receivedGimbalManagerInformation && gimbal._requestInformationRetries > 0) {
407 if (_requestGimbalInformation(pairId.managerCompid)) {
408 --gimbal._requestInformationRetries;
409 }
410 }
411 // Limit to 1 second between set message interval requests, per gimbal
412 const qint64 now = QDateTime::currentMSecsSinceEpoch();
413 if (!gimbal._receivedGimbalManagerStatus && (gimbal._requestStatusRetries > 0) && (now - gimbal._lastStatusRequestMs > 1000)) {
414 gimbal._lastStatusRequestMs = now;
415 _vehicle->sendMavCommand(pairId.managerCompid,
416 MAV_CMD_SET_MESSAGE_INTERVAL,
417 false /* no error */,
418 MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS,
419 (gimbal._requestStatusRetries > 2) ? 0 : 5000000); // request default rate, if we don't succeed, last attempt is fixed 0.2 Hz instead
420 --gimbal._requestStatusRetries;
421 qCDebug(GimbalControllerLog) << "attempt to set GIMBAL_MANAGER_STATUS message at"
422 << (gimbal._requestStatusRetries > 2 ? "default rate" : "0.2 Hz") << "interval for device:"
423 << gimbal.deviceId()->rawValue().toUInt() << "manager compID:" << pairId.managerCompid
424 << ", retries remaining:" << gimbal._requestStatusRetries;
425 }
426
427 if (!gimbal._receivedGimbalDeviceAttitudeStatus && (gimbal._requestAttitudeRetries > 0) &&
428 gimbal._receivedGimbalManagerInformation && (pairId.deviceId != 0)) {
429 // We request the attitude directly from the gimbal device component.
430 // We can only do that once we have received the gimbal manager information
431 // telling us which gimbal device it is responsible for.
432 uint8_t gimbalDeviceCompid = pairId.deviceId;
433 // If the device ID is 1-6, we need to request the message from the manager itself.
434 if (gimbalDeviceCompid <= 6) {
435 gimbalDeviceCompid = pairId.managerCompid;
436 }
437 _vehicle->sendMavCommand(gimbalDeviceCompid,
438 MAV_CMD_SET_MESSAGE_INTERVAL,
439 false /* no error */,
440 MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS,
441 0 /* request default rate */);
442
443 --gimbal._requestAttitudeRetries;
444 }
445
446 if (!gimbal._receivedGimbalManagerInformation || !gimbal._receivedGimbalManagerStatus || !gimbal._receivedGimbalDeviceAttitudeStatus) {
447 // Not complete yet.
448 return;
449 }
450
451 gimbal._isComplete = true;
452
453 // Now that discovery is done, make a best-effort request for the gimbal
454 // device information, which carries the vendor/model/custom name.
455 // If it's never answered (e.g. the SITL gimbal sends empty names),
456 // we simply fall back to showing the IDs.
457 if (pairId.deviceId != 0) {
458 uint8_t gimbalDeviceCompid = pairId.deviceId;
459 // If the device ID is 1-6, the device shares the manager's component id.
460 if (gimbalDeviceCompid <= 6) {
461 gimbalDeviceCompid = pairId.managerCompid;
462 }
463 // Use requestMessage so it serializes with other request-message users targeting this component.
464 _vehicle->requestMessage(_requestDeviceInformationResultHandler, nullptr, gimbalDeviceCompid,
465 MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION);
466 }
467
468 // If there is no current active gimbal, set this one as active
469 if (!_activeGimbal) {
470 setActiveGimbal(&gimbal);
471 }
472
473 _gimbals->append(&gimbal);
474 // This is needed for new Gimbals telemetry to be available for the user to show in flyview telemetry panel
475 _vehicle->_addFactGroup(&gimbal, QStringLiteral("%1%2%3").arg(_gimbalFactGroupNamePrefix).arg(pairId.managerCompid).arg(pairId.deviceId));
476}
477
478bool GimbalController::_tryGetGimbalControl()
479{
480 if (!_activeGimbal) {
481 qCCritical(GimbalControllerLog) << "_tryGetGimbalControl: active gimbal is nullptr, returning";
482 return false;
483 }
484
485 if (_activeGimbal->gimbalOthersHaveControl()) {
486 qCDebug(GimbalControllerLog) << "Others in control, showing popup for user to confirm control..";
488 return false;
489 } else if (!_activeGimbal->gimbalHaveControl()) {
490 qCDebug(GimbalControllerLog) << "Nobody in control, acquiring control ourselves..";
492 }
493
494 return true;
495}
496
497bool GimbalController::_yawInVehicleFrame(uint32_t flags)
498{
499 if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME) > 0) {
500 return true;
501 } else if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME) > 0) {
502 return false;
503 } else {
504 // For backwards compatibility: if both new flags are 0, yaw lock defines the frame.
505 return ((flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) == 0);
506 }
507}
508
510{
511 if (!_activeGimbal) {
512 qCCritical(GimbalControllerLog) << "gimbalPitchStart: active gimbal is nullptr, returning";
513 return;
514 }
515
516 const float speed = SettingsManager::instance()->gimbalControllerSettings()->joystickButtonsSpeed()->rawValue().toInt();
517 activeGimbal()->setPitchRate(direction * speed);
518
519 sendRate();
520}
521
523{
524 if (!_activeGimbal) {
525 qCCritical(GimbalControllerLog) << "gimbalYawStart: active gimbal is nullptr, returning";
526 return;
527 }
528
529 const float speed = SettingsManager::instance()->gimbalControllerSettings()->joystickButtonsSpeed()->rawValue().toInt();
530 activeGimbal()->setYawRate(direction * speed);
531 sendRate();
532}
533
535{
536 if (!_activeGimbal) {
537 qCCritical(GimbalControllerLog) << "gimbalPitchStop: active gimbal is nullptr, returning";
538 return;
539 }
540
541 activeGimbal()->setPitchRate(0.0f);
542 sendRate();
543}
544
546{
547 if (!_activeGimbal) {
548 qCCritical(GimbalControllerLog) << "gimbalYawStop: active gimbal is nullptr, returning";
549 return;
550 }
551
552 activeGimbal()->setYawRate(0.0f);
553 sendRate();
554}
555
557{
558 if (!_activeGimbal) {
559 qCCritical(GimbalControllerLog) << "gimbalYawStep: active gimbal is nullptr, returning";
560 return;
561 }
562 sendPitchBodyYaw(0.0, 0.0, true);
563}
564
565void GimbalController::gimbalOnScreenControl(float panPct, float tiltPct, bool clickAndPoint, bool clickAndDrag, bool /*rateControl*/, bool /*retract*/, bool /*neutral*/, bool /*yawlock*/)
566{
567 // Pan and tilt comes as +-(0-1)
568
569 if (!_activeGimbal) {
570 qCCritical(GimbalControllerLog) << "gimbalOnScreenControl: active gimbal is nullptr, returning";
571 return;
572 }
573
574 if (clickAndPoint) { // based on FOV
575 const float hFov = SettingsManager::instance()->gimbalControllerSettings()->cameraHFov()->rawValue().toFloat();
576 const float vFov = SettingsManager::instance()->gimbalControllerSettings()->cameraVFov()->rawValue().toFloat();
577
578 const float panIncDesired = panPct * hFov * 0.5f;
579 const float tiltIncDesired = tiltPct * vFov * 0.5f;
580
581 const float panDesired = panIncDesired + _activeGimbal->bodyYaw()->rawValue().toFloat();
582 const float tiltDesired = tiltIncDesired + _activeGimbal->absolutePitch()->rawValue().toFloat();
583
584 if (_activeGimbal->yawLock()) {
585 sendPitchAbsoluteYaw(tiltDesired, panIncDesired + _activeGimbal->absoluteYaw()->rawValue().toFloat(), false);
586 } else {
587 sendPitchBodyYaw(tiltDesired, panDesired, false);
588 }
589 } else if (clickAndDrag) { // based on maximum speed
590 // Should send rate commands, but it seems for some reason it is not working on AP side.
591 // Pitch works ok but yaw doesn't stop, it keeps like inertia, like if it was buffering the messages.
592 // So we do a workaround with angle targets
593 const float maxSpeed = SettingsManager::instance()->gimbalControllerSettings()->cameraSlideSpeed()->rawValue().toFloat();
594
595 const float panIncDesired = panPct * maxSpeed * 0.1f;
596 const float tiltIncDesired = tiltPct * maxSpeed * 0.1f;
597
598 const float panDesired = panIncDesired + _activeGimbal->bodyYaw()->rawValue().toFloat();
599 const float tiltDesired = tiltIncDesired + _activeGimbal->absolutePitch()->rawValue().toFloat();
600
601 if (_activeGimbal->yawLock()) {
602 sendPitchAbsoluteYaw(tiltDesired, panIncDesired + _activeGimbal->absoluteYaw()->rawValue().toFloat(), false);
603 } else {
604 sendPitchBodyYaw(tiltDesired, panDesired, false);
605 }
606 }
607}
608
609void GimbalController::sendPitchBodyYaw(float pitch, float yaw, bool showError)
610{
611 if (!_tryGetGimbalControl()) {
612 return;
613 }
614
615 _rateSenderTimer.stop();
616 _activeGimbal->setAbsolutePitch(0.0f);
617 _activeGimbal->setYawRate(0.0f);
618
619 // qCDebug(GimbalControllerLog) << "sendPitch: " << pitch << " BodyYaw: " << yaw;
620
621 const unsigned flags = GIMBAL_MANAGER_FLAGS_ROLL_LOCK
622 | GIMBAL_MANAGER_FLAGS_PITCH_LOCK
623 | GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME;
624
625 _vehicle->sendMavCommand(
626 _activeGimbal->managerCompid()->rawValue().toUInt(),
627 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
628 showError,
629 pitch,
630 yaw,
631 NAN,
632 NAN,
633 flags,
634 0,
635 _activeGimbal->deviceId()->rawValue().toUInt());
636}
637
638void GimbalController::sendPitchAbsoluteYaw(float pitch, float yaw, bool showError)
639{
640 if (!_tryGetGimbalControl()) {
641 return;
642 }
643
644 _rateSenderTimer.stop();
645 _activeGimbal->setAbsolutePitch(0.0f);
646 _activeGimbal->setYawRate(0.0f);
647
648 if (yaw > 180.0f) {
649 yaw -= 360.0f;
650 }
651
652 if (yaw < -180.0f) {
653 yaw += 360.0f;
654 }
655
656 // qCDebug() << "sendPitch: " << pitch << " absoluteYaw: " << yaw;
657
658 const unsigned flags = GIMBAL_MANAGER_FLAGS_ROLL_LOCK
659 | GIMBAL_MANAGER_FLAGS_PITCH_LOCK
660 | GIMBAL_MANAGER_FLAGS_YAW_LOCK
661 | GIMBAL_MANAGER_FLAGS_YAW_IN_EARTH_FRAME;
662
663 _vehicle->sendMavCommand(
664 _activeGimbal->managerCompid()->rawValue().toUInt(),
665 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
666 showError,
667 pitch,
668 yaw,
669 NAN,
670 NAN,
671 flags,
672 0,
673 _activeGimbal->deviceId()->rawValue().toUInt());
674}
675
677{
678 if (!_tryGetGimbalControl()) {
679 return;
680 }
681
682 uint32_t flags = 0;
683 if (set) {
684 flags |= GIMBAL_DEVICE_FLAGS_RETRACT;
685 } else {
686 flags &= ~GIMBAL_DEVICE_FLAGS_RETRACT;
687 }
688
689 sendPitchYawFlags(flags);
690}
691
693{
694 if (!_tryGetGimbalControl()) {
695 return;
696 }
697
698 // We send raw mavlink instead of using MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW because
699 // when both pitch and yaw stop simultaneously, Vehicle's duplicate command detection
700 // drops the second sendMavCommand call, leaving one axis spinning indefinitely.
701 _sendGimbalAttitudeRates(_activeGimbal->pitchRate(), _activeGimbal->yawRate());
702
703 // Stop timeout if both unset.
704 if ((_activeGimbal->pitchRate() == 0.f) && (_activeGimbal->yawRate() == 0.f)) {
705 _rateSenderTimer.stop();
706 } else {
707 _rateSenderTimer.start();
708 }
709}
710
711void GimbalController::sendGimbalRate(float pitch_rate_deg_s, float yaw_rate_deg_s)
712{
713 if (!_tryGetGimbalControl()) {
714 return;
715 }
716
717 _sendGimbalAttitudeRates(pitch_rate_deg_s, yaw_rate_deg_s);
718
719 if (pitch_rate_deg_s == 0.f && yaw_rate_deg_s == 0.f) {
720 _rateSenderTimer.stop();
721 } else {
722 _rateSenderTimer.start();
723 }
724}
725
726void GimbalController::_sendGimbalAttitudeRates(float pitch_rate_deg_s,
727 float yaw_rate_deg_s)
728{
729
730 auto sharedLink = _vehicle->vehicleLinkManager()->primaryLink().lock();
731 if (!sharedLink) {
732 qCDebug(GimbalControllerLog) << "_sendGimbalAttitudeRates: primary link gone!";
733 return;
734 }
735
736 uint32_t flags =
737 GIMBAL_MANAGER_FLAGS_ROLL_LOCK |
738 GIMBAL_MANAGER_FLAGS_PITCH_LOCK |
739 GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME; // use vehicle/body frame
740
741 // Preserve current yaw-lock state instead of changing it:
742 if (_activeGimbal->yawLock()) {
743 flags |= GIMBAL_MANAGER_FLAGS_YAW_LOCK;
744 }
745
746 const float qnan[4] = {NAN, NAN, NAN, NAN};
748
749 mavlink_msg_gimbal_manager_set_attitude_pack_chan(
750 MAVLinkProtocol::instance()->getSystemId(),
752 sharedLink->mavlinkChannel(),
753 &msg,
754 _vehicle->id(),
755 static_cast<uint8_t>(_activeGimbal->managerCompid()->rawValue().toUInt()),
756 flags,
757 static_cast<uint8_t>(_activeGimbal->deviceId()->rawValue().toUInt()),
758 qnan,
759 NAN,
760 qDegreesToRadians(pitch_rate_deg_s),
761 qDegreesToRadians(yaw_rate_deg_s)
762 );
763
764 _vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
765}
766
767void GimbalController::_rateSenderTimeout()
768{
769 // Send rate again to avoid timeout on autopilot side.
770 sendRate();
771}
772
774{
775 if (!_tryGetGimbalControl()) {
776 return;
777 }
778
779 // Roll and pitch are usually "locked", so with horizon and not with aircraft.
780 uint32_t flags = GIMBAL_DEVICE_FLAGS_ROLL_LOCK | GIMBAL_DEVICE_FLAGS_PITCH_LOCK;
781 if (set) {
782 flags |= GIMBAL_DEVICE_FLAGS_YAW_LOCK;
783 }
784
785 sendPitchYawFlags(flags);
786}
787
789{
790 const bool yaw_in_vehicle_frame = _yawInVehicleFrame(flags);
791
792 _vehicle->sendMavCommand(
793 _activeGimbal->managerCompid()->rawValue().toUInt(),
794 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
795 true,
796 _activeGimbal->absolutePitch()->rawValue().toFloat(),
797 yaw_in_vehicle_frame ? _activeGimbal->bodyYaw()->rawValue().toFloat() : _activeGimbal->absoluteYaw()->rawValue().toFloat(),
798 NAN,
799 NAN,
800 flags,
801 0,
802 _activeGimbal->deviceId()->rawValue().toUInt());
803}
804
806{
807 if (!_activeGimbal) {
808 qCCritical(GimbalControllerLog) << "acquireGimbalControl: active gimbal is nullptr, returning";
809 return;
810 }
811
812 _vehicle->sendMavCommand(
813 _activeGimbal->managerCompid()->rawValue().toUInt(),
814 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
815 true,
816 MAVLinkProtocol::instance()->getSystemId(), // Set us in primary control.
817 MAVLinkProtocol::getComponentId(), // Set us in primary control
818 -1.f, // Leave secondary unchanged
819 -1.f, // Leave secondary unchanged
820 NAN, // Reserved
821 NAN, // Reserved
822 _activeGimbal->deviceId()->rawValue().toUInt());
823}
824
826{
827 if (!_activeGimbal) {
828 qCCritical(GimbalControllerLog) << "releaseGimbalControl: active gimbal is nullptr, returning";
829 return;
830 }
831
832 _vehicle->sendMavCommand(
833 _activeGimbal->managerCompid()->rawValue().toUInt(),
834 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
835 true,
836 -3.f, // Release primary control if we have control
837 -3.f, // Release primary control if we have control
838 -1.f, // Leave secondary control unchanged
839 -1.f, // Leave secondary control unchanged
840 NAN, // Reserved
841 NAN, // Reserved
842 _activeGimbal->deviceId()->rawValue().toUInt());
843}
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
Definition Fact.h:87
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:54
float pitchRate() const
Definition Gimbal.h:46
void setYawLock(bool yawLock)
Definition Gimbal.h:71
void setPitchRate(float pitchRate)
Definition Gimbal.h:69
Fact * absolutePitch()
Definition Gimbal.h:37
bool yawLock() const
Definition Gimbal.h:48
void setManagerCompid(uint id)
Definition Gimbal.h:59
Fact * managerCompid()
Definition Gimbal.h:42
Fact * deviceId()
Definition Gimbal.h:41
void setAbsoluteYaw(float absYaw)
Definition Gimbal.h:56
void setYawRate(float yawRate)
Definition Gimbal.h:70
bool gimbalOthersHaveControl() const
Definition Gimbal.h:51
void setGimbalHaveControl(bool set)
Definition Gimbal.h:73
void setBodyYaw(float yaw)
Definition Gimbal.h:55
void setDeviceId(uint id)
Definition Gimbal.h:58
void setRetracted(bool retracted)
Definition Gimbal.h:72
void setDeviceName(const QString &name)
Definition Gimbal.h:61
Fact * absoluteYaw()
Definition Gimbal.h:39
bool gimbalHaveControl() const
Definition Gimbal.h:50
void setAbsoluteRoll(float absRoll)
Definition Gimbal.h:53
void setGimbalOthersHaveControl(bool set)
Definition Gimbal.h:74
void setDeltaYaw(float delta)
Definition Gimbal.h:57
float yawRate() const
Definition Gimbal.h:47
void setCapabilityFlags(uint32_t flags)
Definition Gimbal.cc:76
Fact * bodyYaw()
Definition Gimbal.h:38
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:2173
void initialConnectComplete()
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:597
int id() const
Definition Vehicle.h:443
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1401
void mavlinkMessageReceived(const mavlink_message_t &message)
void requestMessage(RequestMessageResultHandler resultHandler, void *resultHandlerData, int compId, int messageId, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f)
Definition Vehicle.cc:2282
RequestMessageResultHandlerFailureCode_t