25 qCDebug(GimbalControllerLog) <<
this;
30 _rateSenderTimer.setInterval(500);
31 (void) connect(&_rateSenderTimer, &QTimer::timeout,
this, &GimbalController::_rateSenderTimeout);
36 qCDebug(GimbalControllerLog) <<
this;
39void GimbalController::_initialConnectCompleted()
41 _initialConnectComplete =
true;
47 qCCritical(GimbalControllerLog) <<
"Set active gimbal: attempted to set a nullptr, returning";
51 if (gimbal != _activeGimbal) {
52 qCDebug(GimbalControllerLog) <<
"Set active gimbal:" << gimbal;
53 _activeGimbal = gimbal;
60 if (!_initialConnectComplete) {
64 switch (message.msgid) {
65 case MAVLINK_MSG_ID_HEARTBEAT:
66 _handleHeartbeat(message);
68 case MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION:
69 _handleGimbalManagerInformation(message);
71 case MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS:
72 _handleGimbalManagerStatus(message);
74 case MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS:
75 _handleGimbalDeviceAttitudeStatus(message);
77 case MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION:
78 _handleGimbalDeviceInformation(message);
87 if (!_potentialGimbalManagers.contains(message.compid)) {
88 qCDebug(GimbalControllerLog) <<
"new potential gimbal manager component:" << message.compid;
91 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
96 if (!gimbalManager.receivedGimbalManagerInformation && (gimbalManager.requestGimbalManagerInformationRetries > 0)) {
97 if (_requestGimbalInformation(message.compid)) {
98 --gimbalManager.requestGimbalManagerInformationRetries;
103void GimbalController::_handleGimbalManagerInformation(
const mavlink_message_t &message)
105 mavlink_gimbal_manager_information_t information{};
106 mavlink_msg_gimbal_manager_information_decode(&message, &information);
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;
114 qCDebug(GimbalControllerLog) <<
"_handleGimbalManagerInformation for gimbal device:" << information.gimbal_device_id <<
", component id:" << message.compid;
116 const GimbalPairId pairId{message.compid, information.gimbal_device_id};
118 auto gimbalIt = _potentialGimbals.find(pairId);
119 if (gimbalIt == _potentialGimbals.constEnd()) {
120 gimbalIt = _potentialGimbals.insert(pairId,
new Gimbal(
this));
123 Gimbal *
const gimbal = gimbalIt.value();
128 if (!gimbal->_receivedGimbalManagerInformation) {
129 qCDebug(GimbalControllerLog) <<
"gimbal manager with compId:" << message.compid
130 <<
" is responsible for gimbal device:" << information.gimbal_device_id;
133 gimbal->_receivedGimbalManagerInformation =
true;
135 PotentialGimbalManager &gimbalManager = _potentialGimbalManagers[message.compid];
136 gimbalManager.receivedGimbalManagerInformation =
true;
138 _checkComplete(*gimbal, pairId);
141void GimbalController::_handleGimbalManagerStatus(
const mavlink_message_t &message)
143 mavlink_gimbal_manager_status_t status{};
144 mavlink_msg_gimbal_manager_status_decode(&message, &status);
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";
154 const GimbalPairId pairId{message.compid, status.gimbal_device_id};
156 auto gimbalIt = _potentialGimbals.find(pairId);
157 if (gimbalIt == _potentialGimbals.constEnd()) {
158 gimbalIt = _potentialGimbals.insert(pairId,
new Gimbal(
this));
161 Gimbal *
const gimbal = gimbalIt.value();
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;
171 qCWarning(GimbalControllerLog) <<
"conflicting GIMBAL_MANAGER_STATUS compid:" << message.compid;
175 if (!gimbal->_receivedGimbalManagerStatus) {
176 qCDebug(GimbalControllerLog) <<
"_handleGimbalManagerStatus: gimbal manager with compId" << message.compid
177 <<
"is responsible for gimbal device" << status.gimbal_device_id;
180 gimbal->_receivedGimbalManagerStatus =
true;
182 const bool haveControl =
186 const bool othersHaveControl = !haveControl &&
187 (status.primary_control_sysid != 0 && status.primary_control_compid != 0);
197 _checkComplete(*gimbal, pairId);
200void GimbalController::_handleGimbalDeviceAttitudeStatus(
const mavlink_message_t &message)
202 mavlink_gimbal_device_attitude_status_t attitude_status{};
203 mavlink_msg_gimbal_device_attitude_status_decode(&message, &attitude_status);
205 GimbalPairId pairId{};
207 if (attitude_status.gimbal_device_id == 0) {
209 pairId.deviceId = message.compid;
212 const auto foundGimbal = std::find_if(_potentialGimbals.begin(), _potentialGimbals.end(),
213 [pairId](
Gimbal *gimbal) { return (gimbal->deviceId()->rawValue().toUInt() == pairId.deviceId); });
215 if (foundGimbal == _potentialGimbals.constEnd()) {
216 qCDebug(GimbalControllerLog) <<
"_handleGimbalDeviceAttitudeStatus for unknown device id:"
217 << pairId.deviceId <<
"from component id:" << message.compid;
221 pairId.managerCompid = foundGimbal.key().managerCompid;
222 }
else if (attitude_status.gimbal_device_id <= 6) {
224 pairId.deviceId = attitude_status.gimbal_device_id;
225 pairId.managerCompid = message.compid;
228 qCDebug(GimbalControllerLog) <<
"_handleGimbalDeviceAttitudeStatus for invalid device id: "
229 << attitude_status.gimbal_device_id <<
" from component id: " << message.compid;
233 auto gimbalIt = _potentialGimbals.find(pairId);
234 if (gimbalIt == _potentialGimbals.end()) {
235 gimbalIt = _potentialGimbals.insert(pairId,
new Gimbal(
this));
238 Gimbal *
const gimbal = gimbalIt.value();
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;
244 float roll, pitch, yaw;
245 mavlink_quaternion_to_euler(attitude_status.q, &roll, &pitch, &yaw);
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;
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:"
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")
278 const float headingDeg = deltaYawValid ? deltaYawDeg : _vehicle->
heading()->
rawValue().toFloat();
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);
289 const float absoluteYaw = qRadiansToDegrees(yaw);
290 const float bodyYaw = std::remainder(absoluteYaw - headingDeg, 360.0f);
296 gimbal->_receivedGimbalDeviceAttitudeStatus =
true;
298 _checkComplete(*gimbal, pairId);
301void GimbalController::_handleGimbalDeviceInformation(
const mavlink_message_t& message)
303 mavlink_gimbal_device_information_t information{};
304 mavlink_msg_gimbal_device_information_decode(&message, &information);
308 const auto toString = [](
const char* field,
size_t size) {
309 return QString::fromLatin1(field,
static_cast<int>(strnlen(field, size))).trimmed();
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();
318 if (name.isEmpty()) {
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()) {
332 for (
Gimbal*
const candidate : _potentialGimbals) {
333 if (candidate->deviceId()->rawValue().toUInt() == message.compid) {
342 gimbal->_receivedGimbalDeviceInformation =
true;
343 qCDebug(GimbalControllerLog) <<
"gimbal device name:" << name <<
"for compid:" << message.compid;
347bool GimbalController::_requestGimbalInformation(uint8_t compid)
349 qCDebug(GimbalControllerLog) <<
"_requestGimbalInformation(" << compid <<
")";
354 if (_pendingInformationRequestCompId != -1) {
355 qCDebug(GimbalControllerLog) <<
"_requestGimbalInformation: request already in flight for compid" << _pendingInformationRequestCompId;
362 _pendingInformationRequestCompId = compid;
366 MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION);
375 controller->_pendingInformationRequestCompId = -1;
379 if (result != MAV_RESULT_ACCEPTED) {
380 qCDebug(GimbalControllerLog) <<
"GIMBAL_MANAGER_INFORMATION request failed - result:" << result <<
"failureCode:" << failureCode;
384void GimbalController::_requestDeviceInformationResultHandler(
388 Q_UNUSED(resultHandlerData);
393 if (result != MAV_RESULT_ACCEPTED) {
394 qCDebug(GimbalControllerLog) <<
"GIMBAL_DEVICE_INFORMATION request failed - result:" << result
395 <<
"failureCode:" << failureCode;
399void GimbalController::_checkComplete(
Gimbal &gimbal, GimbalPairId pairId)
401 if (gimbal._isComplete) {
406 if (!gimbal._receivedGimbalManagerInformation && gimbal._requestInformationRetries > 0) {
407 if (_requestGimbalInformation(pairId.managerCompid)) {
408 --gimbal._requestInformationRetries;
412 const qint64 now = QDateTime::currentMSecsSinceEpoch();
413 if (!gimbal._receivedGimbalManagerStatus && (gimbal._requestStatusRetries > 0) && (now - gimbal._lastStatusRequestMs > 1000)) {
414 gimbal._lastStatusRequestMs = now;
416 MAV_CMD_SET_MESSAGE_INTERVAL,
418 MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS,
419 (gimbal._requestStatusRetries > 2) ? 0 : 5000000);
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;
427 if (!gimbal._receivedGimbalDeviceAttitudeStatus && (gimbal._requestAttitudeRetries > 0) &&
428 gimbal._receivedGimbalManagerInformation && (pairId.deviceId != 0)) {
432 uint8_t gimbalDeviceCompid = pairId.deviceId;
434 if (gimbalDeviceCompid <= 6) {
435 gimbalDeviceCompid = pairId.managerCompid;
438 MAV_CMD_SET_MESSAGE_INTERVAL,
440 MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS,
443 --gimbal._requestAttitudeRetries;
446 if (!gimbal._receivedGimbalManagerInformation || !gimbal._receivedGimbalManagerStatus || !gimbal._receivedGimbalDeviceAttitudeStatus) {
451 gimbal._isComplete =
true;
457 if (pairId.deviceId != 0) {
458 uint8_t gimbalDeviceCompid = pairId.deviceId;
460 if (gimbalDeviceCompid <= 6) {
461 gimbalDeviceCompid = pairId.managerCompid;
464 _vehicle->
requestMessage(_requestDeviceInformationResultHandler,
nullptr, gimbalDeviceCompid,
465 MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION);
469 if (!_activeGimbal) {
473 _gimbals->
append(&gimbal);
475 _vehicle->
_addFactGroup(&gimbal, QStringLiteral(
"%1%2%3").arg(_gimbalFactGroupNamePrefix).arg(pairId.managerCompid).arg(pairId.deviceId));
478bool GimbalController::_tryGetGimbalControl()
480 if (!_activeGimbal) {
481 qCCritical(GimbalControllerLog) <<
"_tryGetGimbalControl: active gimbal is nullptr, returning";
486 qCDebug(GimbalControllerLog) <<
"Others in control, showing popup for user to confirm control..";
490 qCDebug(GimbalControllerLog) <<
"Nobody in control, acquiring control ourselves..";
497bool GimbalController::_yawInVehicleFrame(uint32_t flags)
499 if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME) > 0) {
501 }
else if ((flags & GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME) > 0) {
505 return ((flags & GIMBAL_DEVICE_FLAGS_YAW_LOCK) == 0);
511 if (!_activeGimbal) {
512 qCCritical(GimbalControllerLog) <<
"gimbalPitchStart: active gimbal is nullptr, returning";
524 if (!_activeGimbal) {
525 qCCritical(GimbalControllerLog) <<
"gimbalYawStart: active gimbal is nullptr, returning";
536 if (!_activeGimbal) {
537 qCCritical(GimbalControllerLog) <<
"gimbalPitchStop: active gimbal is nullptr, returning";
547 if (!_activeGimbal) {
548 qCCritical(GimbalControllerLog) <<
"gimbalYawStop: active gimbal is nullptr, returning";
558 if (!_activeGimbal) {
559 qCCritical(GimbalControllerLog) <<
"gimbalYawStep: active gimbal is nullptr, returning";
569 if (!_activeGimbal) {
570 qCCritical(GimbalControllerLog) <<
"gimbalOnScreenControl: active gimbal is nullptr, returning";
578 const float panIncDesired = panPct * hFov * 0.5f;
579 const float tiltIncDesired = tiltPct * vFov * 0.5f;
581 const float panDesired = panIncDesired + _activeGimbal->
bodyYaw()->
rawValue().toFloat();
584 if (_activeGimbal->
yawLock()) {
589 }
else if (clickAndDrag) {
595 const float panIncDesired = panPct * maxSpeed * 0.1f;
596 const float tiltIncDesired = tiltPct * maxSpeed * 0.1f;
598 const float panDesired = panIncDesired + _activeGimbal->
bodyYaw()->
rawValue().toFloat();
601 if (_activeGimbal->
yawLock()) {
611 if (!_tryGetGimbalControl()) {
615 _rateSenderTimer.stop();
621 const unsigned flags = GIMBAL_MANAGER_FLAGS_ROLL_LOCK
622 | GIMBAL_MANAGER_FLAGS_PITCH_LOCK
623 | GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME;
627 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
640 if (!_tryGetGimbalControl()) {
644 _rateSenderTimer.stop();
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;
665 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
678 if (!_tryGetGimbalControl()) {
684 flags |= GIMBAL_DEVICE_FLAGS_RETRACT;
686 flags &= ~GIMBAL_DEVICE_FLAGS_RETRACT;
694 if (!_tryGetGimbalControl()) {
701 _sendGimbalAttitudeRates(_activeGimbal->
pitchRate(), _activeGimbal->
yawRate());
704 if ((_activeGimbal->
pitchRate() == 0.f) && (_activeGimbal->
yawRate() == 0.f)) {
705 _rateSenderTimer.stop();
707 _rateSenderTimer.start();
713 if (!_tryGetGimbalControl()) {
717 _sendGimbalAttitudeRates(pitch_rate_deg_s, yaw_rate_deg_s);
719 if (pitch_rate_deg_s == 0.f && yaw_rate_deg_s == 0.f) {
720 _rateSenderTimer.stop();
722 _rateSenderTimer.start();
726void GimbalController::_sendGimbalAttitudeRates(
float pitch_rate_deg_s,
727 float yaw_rate_deg_s)
732 qCDebug(GimbalControllerLog) <<
"_sendGimbalAttitudeRates: primary link gone!";
737 GIMBAL_MANAGER_FLAGS_ROLL_LOCK |
738 GIMBAL_MANAGER_FLAGS_PITCH_LOCK |
739 GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME;
742 if (_activeGimbal->
yawLock()) {
743 flags |= GIMBAL_MANAGER_FLAGS_YAW_LOCK;
746 const float qnan[4] = {NAN, NAN, NAN, NAN};
749 mavlink_msg_gimbal_manager_set_attitude_pack_chan(
752 sharedLink->mavlinkChannel(),
760 qDegreesToRadians(pitch_rate_deg_s),
761 qDegreesToRadians(yaw_rate_deg_s)
767void GimbalController::_rateSenderTimeout()
775 if (!_tryGetGimbalControl()) {
780 uint32_t flags = GIMBAL_DEVICE_FLAGS_ROLL_LOCK | GIMBAL_DEVICE_FLAGS_PITCH_LOCK;
782 flags |= GIMBAL_DEVICE_FLAGS_YAW_LOCK;
790 const bool yaw_in_vehicle_frame = _yawInVehicleFrame(flags);
794 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
807 if (!_activeGimbal) {
808 qCCritical(GimbalControllerLog) <<
"acquireGimbalControl: active gimbal is nullptr, returning";
814 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
827 if (!_activeGimbal) {
828 qCCritical(GimbalControllerLog) <<
"releaseGimbalControl: active gimbal is nullptr, returning";
834 MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE,
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
void _addFactGroup(FactGroup *factGroup, const QString &name)
QVariant rawValue() const
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)
void setAbsolutePitch(float absPitch)
void setYawLock(bool yawLock)
void setPitchRate(float pitchRate)
void setManagerCompid(uint id)
void setAbsoluteYaw(float absYaw)
void setYawRate(float yawRate)
bool gimbalOthersHaveControl() const
void setGimbalHaveControl(bool set)
void setBodyYaw(float yaw)
void setDeviceId(uint id)
void setRetracted(bool retracted)
void setDeviceName(const QString &name)
bool gimbalHaveControl() const
void setAbsoluteRoll(float absRoll)
void setGimbalOthersHaveControl(bool set)
void setDeltaYaw(float delta)
void setCapabilityFlags(uint32_t flags)
static int getComponentId()
static MAVLinkProtocol * instance()
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)
void initialConnectComplete()
VehicleLinkManager * vehicleLinkManager()
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
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)
RequestMessageResultHandlerFailureCode_t