7#include <QtCore/QDateTime>
23 uint8_t gimbalDeviceId)
25 , _gimbalDeviceId(gimbalDeviceId)
26 , _hasRollAxis(hasRollAxis)
27 , _hasPitchAxis(hasPitchAxis)
28 , _hasYawAxis(hasYawAxis)
29 , _hasYawFollow(hasYawFollow)
30 , _hasYawLock(hasYawLock)
31 , _hasRetract(hasRetract)
32 , _hasNeutral(hasNeutral)
42 QMutexLocker locker(&_stateMutex);
43 const qint64 nowMs = QDateTime::currentMSecsSinceEpoch();
46 if (_managerStatusIntervalUs > 0) {
47 const qint64 intervalMs = _managerStatusIntervalUs / 1000;
48 if ((nowMs - _managerStatusLastSentMs) >= intervalMs) {
49 _sendGimbalManagerStatus();
50 _managerStatusLastSentMs = nowMs;
55 if (_deviceAttitudeStatusIntervalUs > 0) {
56 const qint64 intervalMs = _deviceAttitudeStatusIntervalUs / 1000;
57 if ((nowMs - _deviceAttitudeStatusLastSentMs) >= intervalMs) {
59 if (!_manualControl) {
61 const qint64 secondsSinceEpoch = nowMs / 1000;
62 _pitch = 10.0f * qSin(secondsSinceEpoch * 0.1);
63 _yaw = 15.0f * qCos(secondsSinceEpoch * 0.15);
66 _sendGimbalDeviceAttitudeStatus();
67 _deviceAttitudeStatusLastSentMs = nowMs;
74 if (msg.msgid == MAVLINK_MSG_ID_GIMBAL_MANAGER_SET_ATTITUDE) {
75 _handleGimbalManagerSetAttitude(msg);
79 if (msg.msgid != MAVLINK_MSG_ID_COMMAND_LONG) {
84 mavlink_msg_command_long_decode(&msg, &request);
87 switch (request.command) {
88 case MAV_CMD_SET_MESSAGE_INTERVAL:
89 if (_handleSetMessageInterval(request)) {
90 _sendCommandAck(request.command, MAV_RESULT_ACCEPTED, request.target_component);
95 case MAV_CMD_REQUEST_MESSAGE: {
96 if (
static_cast<int>(request.param1) == MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION) {
97 _sendCommandAck(request.command, MAV_RESULT_ACCEPTED, request.target_component);
98 _handleRequestMessage(request);
101 if (
static_cast<int>(request.param1) != MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION) {
106 QMutexLocker locker(&_stateMutex);
107 response = _informationResponse;
111 _sendCommandAck(request.command, MAV_RESULT_ACCEPTED, request.target_component);
112 _handleRequestMessage(request);
115 _sendCommandAck(request.command, MAV_RESULT_UNSUPPORTED, request.target_component);
123 case MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW:
124 if (_handleGimbalManagerPitchYaw(request)) {
125 _sendCommandAck(request.command, MAV_RESULT_ACCEPTED, request.target_component);
128 _sendCommandAck(request.command, MAV_RESULT_DENIED, request.target_component);
131 case MAV_CMD_DO_GIMBAL_MANAGER_CONFIGURE:
132 if (_handleGimbalManagerConfigure(request, msg.sysid, msg.compid)) {
133 _sendCommandAck(request.command, MAV_RESULT_ACCEPTED, request.target_component);
136 _sendCommandAck(request.command, MAV_RESULT_DENIED, request.target_component);
144void MockLinkGimbal::_sendGimbalDeviceInformation()
146 qCDebug(MockLinkGimbalLog) <<
"Sending GIMBAL_DEVICE_INFORMATION - vendor:" << kVendorName <<
"model:" << kModelName
147 <<
"deviceId:" << _attitudeDeviceIdField();
150 char vendorName[MAVLINK_MSG_GIMBAL_DEVICE_INFORMATION_FIELD_VENDOR_NAME_LEN + 1] = {};
151 char modelName[MAVLINK_MSG_GIMBAL_DEVICE_INFORMATION_FIELD_MODEL_NAME_LEN + 1] = {};
152 char customName[MAVLINK_MSG_GIMBAL_DEVICE_INFORMATION_FIELD_CUSTOM_NAME_LEN + 1] = {};
153 (void) strncpy(vendorName, kVendorName, MAVLINK_MSG_GIMBAL_DEVICE_INFORMATION_FIELD_VENDOR_NAME_LEN);
154 (void) strncpy(modelName, kModelName, MAVLINK_MSG_GIMBAL_DEVICE_INFORMATION_FIELD_MODEL_NAME_LEN);
157 (void) mavlink_msg_gimbal_device_information_pack_chan(_mockLink->
vehicleId(), _attitudeSourceCompid(),
160 vendorName, modelName, customName,
166 -45, 45, -45, 45, -180, 180, _attitudeDeviceIdField(),
171void MockLinkGimbal::_sendCommandAck(uint16_t command, uint8_t result, uint8_t sourceCompId)
174 qCDebug(MockLinkGimbalLog) <<
"Sending command ACK -" << QString(
"%1(%2)").arg(commandName).arg(command) <<
"result:" << (result == MAV_RESULT_ACCEPTED ?
"ACCEPTED" : result == MAV_RESULT_DENIED ?
"DENIED" : QString::number(result));
177 (void) mavlink_msg_command_ack_pack_chan(
194 const int msgId =
static_cast<int>(request.param1);
195 const int intervalUs =
static_cast<int>(request.param2);
196 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(
static_cast<uint32_t
>(msgId));
197 QString msgName = info ? info->name : QString::number(msgId);
199 qCDebug(MockLinkGimbalLog) <<
"SET_MESSAGE_INTERVAL -" << QString(
"%1(%2)").arg(msgName).arg(msgId) <<
"intervalUs:" << intervalUs;
205 int effectiveInterval = intervalUs;
206 if (intervalUs == 0) {
207 effectiveInterval = kDefaultIntervalUs;
208 }
else if (intervalUs < -1) {
215 QMutexLocker locker(&_stateMutex);
216 if (msgId == MAVLINK_MSG_ID_GIMBAL_MANAGER_STATUS) {
217 _statusIntervalRequests.append(intervalUs);
218 if (_ignoreStatusIntervalRequests > 0) {
219 --_ignoreStatusIntervalRequests;
220 qCDebug(MockLinkGimbalLog) << msgName <<
"interval request ignored, remaining to ignore:" << _ignoreStatusIntervalRequests;
223 _managerStatusIntervalUs = effectiveInterval;
224 qCDebug(MockLinkGimbalLog) << msgName <<
"interval set to" << effectiveInterval <<
"us";
226 }
else if (msgId == MAVLINK_MSG_ID_GIMBAL_DEVICE_ATTITUDE_STATUS) {
227 _deviceAttitudeStatusIntervalUs = effectiveInterval;
228 qCDebug(MockLinkGimbalLog) << msgName <<
"interval set to" << effectiveInterval <<
"us";
236 const int msgId =
static_cast<int>(request.param1);
237 const mavlink_message_info_t* info = mavlink_get_message_info_by_id(
static_cast<uint32_t
>(msgId));
238 QString msgName = info ? info->name : QString::number(msgId);
240 if (msgId == MAVLINK_MSG_ID_GIMBAL_MANAGER_INFORMATION) {
241 qCDebug(MockLinkGimbalLog) <<
"REQUEST_MESSAGE -" << QString(
"%1(%2)").arg(msgName).arg(msgId);
242 _sendGimbalManagerInformation();
246 if (msgId == MAVLINK_MSG_ID_GIMBAL_DEVICE_INFORMATION) {
247 qCDebug(MockLinkGimbalLog) <<
"REQUEST_MESSAGE -" << QString(
"%1(%2)").arg(msgName).arg(msgId);
248 _sendGimbalDeviceInformation();
264 const float requestedPitch = request.param1;
265 const float requestedYaw = request.param2;
266 const uint32_t flags =
static_cast<uint32_t
>(request.param5);
268 qCDebug(MockLinkGimbalLog) <<
"DO_GIMBAL_MANAGER_PITCHYAW - pitch:" << requestedPitch <<
"yaw:" << requestedYaw <<
"flags:" << flags;
270 QMutexLocker locker(&_stateMutex);
271 _lastPitchYawCommand.
pitchDeg = requestedPitch;
272 _lastPitchYawCommand.
yawDeg = requestedYaw;
275 _lastPitchYawCommand.
flags = flags;
276 _lastPitchYawCommand.
deviceId =
static_cast<uint8_t
>(request.param7);
277 ++_lastPitchYawCommand.
count;
280 _yawLock = (flags & GIMBAL_MANAGER_FLAGS_YAW_LOCK) != 0;
283 const bool updatePitch = !qIsNaN(requestedPitch);
284 const bool updateYaw = !qIsNaN(requestedYaw);
285 const bool updateRates = !qIsNaN(request.param3) || !qIsNaN(request.param4);
287 if (!updatePitch && !updateYaw && !updateRates) {
288 qCDebug(MockLinkGimbalLog) <<
"DO_GIMBAL_MANAGER_PITCHYAW - no valid pitch/yaw requested (both NaN)";
293 if (updatePitch && _hasPitchAxis) {
294 _pitch = qBound(-45.0f, requestedPitch, 45.0f);
295 _manualControl =
true;
298 if (updateYaw && _hasYawAxis) {
300 bool earthFrame =
false;
301 if (flags & GIMBAL_MANAGER_FLAGS_YAW_IN_EARTH_FRAME) {
303 }
else if (!(flags & GIMBAL_MANAGER_FLAGS_YAW_IN_VEHICLE_FRAME)) {
304 earthFrame = _yawLock;
306 _yawInEarthFrame = earthFrame;
309 float bodyYaw = qBound(-180.0f, requestedYaw, 180.0f);
311 bodyYaw = std::remainder(requestedYaw - _deltaYawDeg, 360.0f);
312 }
else if (earthFrame) {
313 qCDebug(MockLinkGimbalLog) <<
"earth-frame yaw command stored as body yaw: mock has no heading in Legacy mode";
316 _manualControl =
true;
319 qCDebug(MockLinkGimbalLog) <<
"Gimbal commanded to pitch:" << _pitch <<
"yaw:" << _yaw <<
"- switched to manual control";
323bool MockLinkGimbal::_handleGimbalManagerConfigure(
const mavlink_command_long_t &request, uint8_t senderSysid, uint8_t senderCompid)
329 QMutexLocker locker(&_stateMutex);
330 _applyControlPair(request.param1, request.param2, senderSysid, senderCompid,
331 _gimbalManagerSysidPrimary, _gimbalManagerCompidPrimary);
332 _applyControlPair(request.param3, request.param4, senderSysid, senderCompid,
333 _gimbalManagerSysidSecondary, _gimbalManagerCompidSecondary);
335 qCDebug(MockLinkGimbalLog) <<
"Gimbal manager configured - Primary:" << _gimbalManagerSysidPrimary
336 <<
"/" << _gimbalManagerCompidPrimary
337 <<
"Secondary:" << _gimbalManagerSysidSecondary
338 <<
"/" << _gimbalManagerCompidSecondary;
342void MockLinkGimbal::_applyControlPair(
float sysidValue,
float compidValue, uint8_t senderSysid, uint8_t senderCompid,
343 uint8_t &targetSysid, uint8_t &targetCompid)
346 if (sysidValue == -3.0f || compidValue == -3.0f) {
347 if (targetSysid == senderSysid && targetCompid == senderCompid) {
353 _applyControlField(sysidValue, senderSysid, targetSysid);
354 _applyControlField(compidValue, senderCompid, targetCompid);
357void MockLinkGimbal::_applyControlField(
float value, uint8_t senderValue, uint8_t &target)
359 if (qIsNaN(value) || value == -1.0f) {
362 if (value == -2.0f) {
363 target = senderValue;
364 }
else if (value >= 0.0f) {
365 target =
static_cast<uint8_t
>(value);
369void MockLinkGimbal::_handleGimbalManagerSetAttitude(
const mavlink_message_t &msg)
371 mavlink_gimbal_manager_set_attitude_t setAttitude{};
372 mavlink_msg_gimbal_manager_set_attitude_decode(&msg, &setAttitude);
374 qCDebug(MockLinkGimbalLog) <<
"GIMBAL_MANAGER_SET_ATTITUDE - pitchRate:" << setAttitude.angular_velocity_y
375 <<
"yawRate:" << setAttitude.angular_velocity_z
376 <<
"flags:" << setAttitude.flags;
378 QMutexLocker locker(&_stateMutex);
379 _lastSetAttitudeCommand.
pitchRateDegS = qRadiansToDegrees(setAttitude.angular_velocity_y);
380 _lastSetAttitudeCommand.
yawRateDegS = qRadiansToDegrees(setAttitude.angular_velocity_z);
381 _lastSetAttitudeCommand.
flags = setAttitude.flags;
382 ++_lastSetAttitudeCommand.
count;
387 QMutexLocker locker(&_stateMutex);
391 _manualControl =
true;
396 QMutexLocker locker(&_stateMutex);
397 _deltaYawMode = mode;
402 QMutexLocker locker(&_stateMutex);
403 _deltaYawDeg = deltaYawDeg;
408 QMutexLocker locker(&_stateMutex);
409 _yawInEarthFrame = earthFrame;
414 QMutexLocker locker(&_stateMutex);
415 _gimbalManagerSysidPrimary = sysid;
416 _gimbalManagerCompidPrimary = compid;
421 QMutexLocker locker(&_stateMutex);
422 _sendGimbalDeviceAttitudeStatus();
427 QMutexLocker locker(&_stateMutex);
428 _sendGimbalManagerStatus();
433 _sendGimbalManagerInformation(deviceIdField);
438 QMutexLocker locker(&_stateMutex);
439 _sendGimbalManagerStatus(deviceIdField);
444 QMutexLocker locker(&_stateMutex);
445 _sendGimbalDeviceAttitudeStatus(sourceCompid, deviceIdField);
450 QMutexLocker locker(&_stateMutex);
451 _informationResponse = response;
456 QMutexLocker locker(&_stateMutex);
457 _ignoreStatusIntervalRequests = count;
462 QMutexLocker locker(&_stateMutex);
463 return _statusIntervalRequests;
468 QMutexLocker locker(&_stateMutex);
469 return _lastPitchYawCommand;
474 QMutexLocker locker(&_stateMutex);
475 return _lastSetAttitudeCommand;
480 QMutexLocker locker(&_stateMutex);
484void MockLinkGimbal::_sendGimbalManagerStatus()
486 _sendGimbalManagerStatus(_gimbalDeviceId);
489void MockLinkGimbal::_sendGimbalManagerStatus(uint8_t deviceIdField)
491 qCDebug(MockLinkGimbalLog) <<
"Sending GIMBAL_MANAGER_STATUS - deviceId:" << deviceIdField;
494 (void) mavlink_msg_gimbal_manager_status_pack_chan(
496 MAV_COMP_ID_AUTOPILOT1,
502 _gimbalManagerSysidPrimary,
503 _gimbalManagerCompidPrimary,
504 _gimbalManagerSysidSecondary,
505 _gimbalManagerCompidSecondary);
509void MockLinkGimbal::_sendGimbalDeviceAttitudeStatus()
511 _sendGimbalDeviceAttitudeStatus(_attitudeSourceCompid(), _attitudeDeviceIdField());
514void MockLinkGimbal::_sendGimbalDeviceAttitudeStatus(uint8_t sourceCompid, uint8_t deviceIdField)
516 qCDebug(MockLinkGimbalLog) <<
"Sending GIMBAL_DEVICE_ATTITUDE_STATUS - pitch:" << _pitch <<
"yaw:" << _yaw
517 <<
"manual:" << _manualControl <<
"deltaYawMode:" <<
static_cast<int>(_deltaYawMode)
518 <<
"deltaYaw:" << _deltaYawDeg <<
"earthFrame:" << _yawInEarthFrame
519 <<
"from:" << sourceCompid <<
"deviceId:" << deviceIdField;
521 uint16_t flags = GIMBAL_DEVICE_FLAGS_NEUTRAL | GIMBAL_DEVICE_FLAGS_ROLL_LOCK | GIMBAL_DEVICE_FLAGS_PITCH_LOCK;
523 flags |= GIMBAL_DEVICE_FLAGS_YAW_LOCK;
526 float deltaYawRad = 0.0f;
527 switch (_deltaYawMode) {
531 flags |= _yawInEarthFrame ? GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME : GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME;
535 flags |= _yawInEarthFrame ? GIMBAL_DEVICE_FLAGS_YAW_IN_EARTH_FRAME : GIMBAL_DEVICE_FLAGS_YAW_IN_VEHICLE_FRAME;
536 deltaYawRad = qDegreesToRadians(_deltaYawDeg);
542 const float reportedYawDeg = (_yawInEarthFrame && _deltaYawMode !=
DeltaYawMode::Legacy) ? _yaw + _deltaYawDeg : _yaw;
545 const float rollRad = qDegreesToRadians(_roll);
546 const float pitchRad = qDegreesToRadians(_pitch);
547 const float yawRad = qDegreesToRadians(reportedYawDeg);
549 const float cy = qCos(yawRad * 0.5f);
550 const float sy = qSin(yawRad * 0.5f);
551 const float cp = qCos(pitchRad * 0.5f);
552 const float sp = qSin(pitchRad * 0.5f);
553 const float cr = qCos(rollRad * 0.5f);
554 const float sr = qSin(rollRad * 0.5f);
557 q[0] = cr * cp * cy + sr * sp * sy;
558 q[1] = sr * cp * cy - cr * sp * sy;
559 q[2] = cr * sp * cy + sr * cp * sy;
560 q[3] = cr * cp * sy - sr * sp * cy;
563 (void) mavlink_msg_gimbal_device_attitude_status_pack_chan(
580void MockLinkGimbal::_sendGimbalManagerInformation()
582 _sendGimbalManagerInformation(_gimbalDeviceId);
585void MockLinkGimbal::_sendGimbalManagerInformation(uint8_t deviceIdField)
588 uint32_t capFlags = 0;
589 if (_hasRollAxis) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_ROLL_AXIS;
590 if (_hasPitchAxis) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_PITCH_AXIS;
591 if (_hasYawAxis) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_YAW_AXIS;
592 if (_hasYawFollow) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_YAW_FOLLOW;
593 if (_hasYawLock) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_YAW_LOCK;
594 if (_hasRetract) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_RETRACT;
595 if (_hasNeutral) capFlags |= GIMBAL_MANAGER_CAP_FLAGS_HAS_NEUTRAL;
597 qCDebug(MockLinkGimbalLog) <<
"Sending GIMBAL_MANAGER_INFORMATION - capFlags:" << QString::number(capFlags, 16) <<
"deviceId:" << deviceIdField;
600 (void) mavlink_msg_gimbal_manager_information_pack_chan(
602 MAV_COMP_ID_AUTOPILOT1,
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct __mavlink_command_long_t mavlink_command_long_t
static MissionCommandTree * instance()
QString rawName(MAV_CMD command) const
Returns the raw name for the specified command.
Simulates MAVLink Gimbal Manager Protocol for MockLink.
DeltaYawMode
Controls how GIMBAL_DEVICE_ATTITUDE_STATUS.delta_yaw and the yaw frame flags are reported.
@ Legacy
No YAW_IN_*_FRAME flag, delta_yaw = 0 (pre-extension firmware wire image)
@ Unknown
Frame flag set, delta_yaw = NaN.
@ Explicit
Frame flag set, delta_yaw = value from setDeltaYawDeg()
void sendGimbalDeviceAttitudeStatusNow()
Sends GIMBAL_DEVICE_ATTITUDE_STATUS immediately instead of waiting for the 1Hz worker.
SetAttitudeCommand lastSetAttitudeCommand() const
QList< int > statusIntervalRequests() const
Every GIMBAL_MANAGER_STATUS interval value (us) received, in order.
void setDeltaYawDeg(float deltaYawDeg)
void setInformationResponse(InformationResponse response)
void sendGimbalManagerStatusWithDeviceId(uint8_t deviceIdField)
void run1HzTasks()
Send periodic gimbal status messages (call from 1Hz tasks)
void setPrimaryControl(uint8_t sysid, uint8_t compid)
Overrides who holds primary control as reported in GIMBAL_MANAGER_STATUS.
bool handleMavlinkMessage(const mavlink_message_t &msg)
void sendGimbalManagerInformationWithDeviceId(uint8_t deviceIdField)
Raw sends with protocol-violating field overrides, for exercising QGC's rejection paths.
void setDeltaYawMode(DeltaYawMode mode)
void setAttitudeDeg(float rollDeg, float pitchDeg, float yawDeg)
Pins gimbal attitude (degrees) and disables the automatic oscillation.
void setYawInEarthFrame(bool earthFrame)
void sendGimbalManagerStatusNow()
void setIgnoreStatusIntervalRequests(int count)
The first count MAV_CMD_SET_MESSAGE_INTERVAL requests for GIMBAL_MANAGER_STATUS are ACKed but not hon...
PitchYawCommand lastPitchYawCommand() const
InformationResponse
How the manager answers MAV_CMD_REQUEST_MESSAGE(GIMBAL_MANAGER_INFORMATION)
@ Silent
Swallow the request, no ACK, no message.
@ Nack
ACK with MAV_RESULT_UNSUPPORTED, no message.
@ Accept
ACK and send the message.
void sendGimbalDeviceAttitudeStatusFrom(uint8_t sourceCompid, uint8_t deviceIdField)
uint8_t outgoingMavlinkChannel() const
Vehicle-side channel used to encode outgoing (mock vehicle -> QGC) messages. Signing state lives here...
void respondWithMavlinkMessage(const mavlink_message_t &msg)
Sends the specified mavlink message to QGC.
GPSCommandSequence::Command command(QByteArrayView body, GPSTextMatcher reply, std::chrono::milliseconds timeout)
body labelled by its text, framed as a sentence, and completed by reply within timeout.
Last MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW received (angles in degrees, rates in deg/s)
Last GIMBAL_MANAGER_SET_ATTITUDE received (rates in deg/s)