18#ifdef QGC_GST_STREAMING
22#include <QtCore/QFile>
23#include <QtCore/QJsonArray>
24#include <QtCore/QJsonDocument>
25#include <QtCore/QJsonObject>
26#include <QtCore/QLatin1StringView>
27#include <QtCore/QMutexLocker>
29#include <QtCore/QRandomGenerator>
30#include <QtCore/QTemporaryFile>
31#include <QtCore/QThread>
32#include <QtCore/QTimer>
40std::atomic<
int>
MockLink::_nextVehicleSystemId{128};
42QList<MockLink::FlightMode_t> MockLink::_availableFlightModes = {
66QList<MockLink::FlightMode_t> MockLink::_apmCopterAvailableFlightModes = {
68 {
"Stabilize", 0, 0,
true,
true },
69 {
"Acro", 0, 1,
true,
true },
70 {
"Altitude Hold", 0, 2,
true,
true },
71 {
"Auto", 0, 3,
true,
true },
72 {
"Guided", 0, 4,
true,
true },
73 {
"Loiter", 0, 5,
true,
true },
74 {
"RTL", 0, 6,
true,
true },
75 {
"Circle", 0, 7,
true,
true },
76 {
"Land", 0, 9,
true,
true },
77 {
"Drift", 0, 11,
true,
true },
78 {
"Sport", 0, 13,
true,
true },
79 {
"Flip", 0, 14,
true,
true },
80 {
"Autotune", 0, 15,
true,
true },
81 {
"Position Hold", 0, 16,
true,
true },
82 {
"Brake", 0, 17,
true,
true },
83 {
"Throw", 0, 18,
true,
true },
84 {
"Avoid ADSB", 0, 19,
true,
true },
85 {
"Guided No GPS", 0, 20,
true,
true },
86 {
"Smart RTL", 0, 21,
true,
true },
87 {
"Flow Hold", 0, 22,
true,
true },
88 {
"Follow", 0, 23,
true,
true },
89 {
"ZigZag", 0, 24,
true,
true },
90 {
"SystemID", 0, 25,
true,
true },
91 {
"AutoRotate", 0, 26,
true,
true },
92 {
"AutoRTL", 0, 27,
true,
true },
93 {
"Turtle", 0, 28,
true,
true },
96QList<MockLink::FlightMode_t> MockLink::_apmPlaneAvailableFlightModes = {
98 {
"Manual", 0, 0,
true,
true },
99 {
"Circle", 0, 1,
true,
true },
100 {
"Stabilize", 0, 2,
true,
true },
101 {
"Training", 0, 3,
true,
true },
102 {
"Acro", 0, 4,
true,
true },
103 {
"FBW A", 0, 5,
true,
true },
104 {
"FBW B", 0, 6,
true,
true },
105 {
"Cruise", 0, 7,
true,
true },
106 {
"Autotune", 0, 8,
true,
true },
107 {
"Auto", 0, 10,
true,
true },
108 {
"RTL", 0, 11,
true,
true },
109 {
"Loiter", 0, 12,
true,
true },
110 {
"Takeoff", 0, 13,
true,
true },
111 {
"Avoid ADSB", 0, 14,
true,
true },
112 {
"Guided", 0, 15,
true,
true },
113 {
"Initializing", 0, 16,
false,
true },
114 {
"QuadPlane Stabilize", 0, 17,
true,
true },
115 {
"QuadPlane Hover", 0, 18,
true,
true },
116 {
"QuadPlane Loiter", 0, 19,
true,
true },
117 {
"QuadPlane Land", 0, 20,
true,
true },
118 {
"QuadPlane RTL", 0, 21,
true,
true },
119 {
"QuadPlane AutoTune", 0, 22,
true,
true },
120 {
"QuadPlane Acro", 0, 23,
true,
true },
121 {
"Thermal", 0, 24,
true,
true },
122 {
"Loiter to QLand", 0, 25,
true,
true },
123 {
"Autoland", 0, 26,
true,
true },
126QList<MockLink::FlightMode_t> MockLink::_apmRoverAvailableFlightModes = {
128 {
"Manual", 0, 0,
true,
true },
129 {
"Acro", 0, 1,
true,
true },
130 {
"Learning", 0, 2,
false,
true },
131 {
"Steering", 0, 3,
true,
true },
132 {
"Hold", 0, 4,
true,
true },
133 {
"Loiter", 0, 5,
true,
true },
134 {
"Follow", 0, 6,
true,
true },
135 {
"Simple", 0, 7,
true,
true },
136 {
"Dock", 0, 8,
true,
true },
137 {
"Circle", 0, 9,
true,
true },
138 {
"Auto", 0, 10,
true,
true },
139 {
"RTL", 0, 11,
true,
true },
140 {
"Smart RTL", 0, 12,
true,
true },
141 {
"Guided", 0, 15,
true,
true },
142 {
"Initializing", 0, 16,
false,
true },
145QList<MockLink::FlightMode_t> MockLink::_apmSubAvailableFlightModes = {
147 {
"Manual", 0, 19,
true,
true },
148 {
"Stabilize", 0, 0,
true,
true },
149 {
"Acro", 0, 1,
true,
true },
150 {
"Depth Hold", 0, 2,
true,
true },
151 {
"Auto", 0, 3,
true,
true },
152 {
"Guided", 0, 4,
true,
true },
153 {
"Circle", 0, 7,
true,
true },
154 {
"Surface", 0, 9,
true,
true },
155 {
"Position Hold", 0, 16,
true,
true },
156 {
"Motor Detection", 0, 20,
false,
true },
157 {
"Surftrak", 0, 21,
true,
true },
163 , _firmwareType(_mockConfig->firmwareType())
164 , _vehicleType(_mockConfig->vehicleType())
165 , _sendStatusText(_mockConfig->sendStatusText())
166 , _apmStartFreshParams(_mockConfig->apmStartFreshParams())
167 , _enableCamera(_mockConfig->enableCamera())
168 , _enableGimbal(_mockConfig->enableGimbal())
169 , _enableProximity(_mockConfig->enableProximity())
170 , _failureMode(_mockConfig->failureMode())
171 , _stayMavlinkV1(_mockConfig->stayMavlinkV1())
172 , _ftpCapability(_mockConfig->ftpCapability())
173 , _sendRadioStatusEnabled(_mockConfig->sendRadioStatus())
174 , _vehicleSystemId(_mockConfig->incrementVehicleId() ? _nextVehicleSystemId++ : static_cast<int>(_nextVehicleSystemId))
175 , _vehicleLatitude(_defaultVehicleLatitude + ((_vehicleSystemId - 128) * 0.0001))
176 , _vehicleLongitude(_defaultVehicleLongitude + ((_vehicleSystemId - 128) * 0.0001))
177 , _boardVendorId(_mockConfig->boardVendorId())
178 , _boardProductId(_mockConfig->boardProductId())
181 _mockConfig->cameraCaptureVideo(),
182 _mockConfig->cameraCaptureImage(),
183 _mockConfig->cameraHasModes(),
184 _mockConfig->cameraHasVideoStream(),
185 _mockConfig->cameraCanCaptureImageInVideoMode(),
186 _mockConfig->cameraCanCaptureVideoInImageMode(),
187 _mockConfig->cameraHasBasicZoom(),
188 _mockConfig->cameraHasTrackingPoint(),
189 _mockConfig->cameraHasTrackingRectangle())
192 _mockConfig->gimbalHasRollAxis(),
193 _mockConfig->gimbalHasPitchAxis(),
194 _mockConfig->gimbalHasYawAxis(),
195 _mockConfig->gimbalHasYawFollow(),
196 _mockConfig->gimbalHasYawLock(),
197 _mockConfig->gimbalHasRetract(),
198 _mockConfig->gimbalHasNeutral(),
199 static_cast<uint8_t>(_mockConfig->gimbalDeviceId()))
202 , _mockLinkFTP(new
MockLinkFTP(_vehicleSystemId, _vehicleComponentId, this))
203 , _requestedVideoStreamType(_mockConfig->videoStreamTypeEnum())
205 qCDebug(MockLinkLog) <<
this;
216 _adsbVehicles.reserve(_numberOfVehicles);
217 for (
int i = 0; i < _numberOfVehicles; ++i) {
218 ADSBVehicle vehicle{};
219 vehicle.angle = i * 72.0;
222 const double latOffset = 0.001 * i;
223 const double lonOffset = 0.001 * (i % 2 == 0 ? i : -i);
224 vehicle.coordinate = QGeoCoordinate(_defaultVehicleLatitude + latOffset, _defaultVehicleLongitude + lonOffset);
227 vehicle.altitude = _defaultVehicleHomeAltitude + (i * 5);
229 _adsbVehicles.append(vehicle);
235 _runningTime.start();
237 _workerThread =
new QThread(
this);
238 _workerThread->setObjectName(QStringLiteral(
"Mock_%1").arg(_mockConfig->
name()));
240 _worker->moveToThread(_workerThread);
242 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
243 _workerThread->start();
250 delete _mockLinkCamera;
251 delete _mockLinkGimbal;
252 delete _mockLinkPX4Calibration;
254 if (!_logDownloadFilename.isEmpty()) {
255 QFile::remove(_logDownloadFilename);
259 _workerThread->quit();
260 _workerThread->wait();
263 qCDebug(MockLinkLog) <<
this;
266bool MockLink::_connect()
270 _disconnectedEmitted =
false;
277 if (_mavlinkV2Upgraded) {
278 outgoingStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
280 outgoingStatus->flags |= MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
283 incomingStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
285 _startVideoStreamServer();
301 if (_workerThread && _workerThread->isRunning()) {
302 _workerThread->quit();
303 _workerThread->wait();
307 if (_outgoingMavlinkChannelIsSet()) {
309 outgoingStatus->signing =
nullptr;
310 outgoingStatus->signing_streams =
nullptr;
311 mavlink_reset_channel_status(_outgoingMavlinkChannel);
316 _stopVideoStreamServer();
320 if (!_disconnectedEmitted.exchange(
true)) {
326void MockLink::_startVideoStreamServer()
328#ifdef QGC_GST_STREAMING
337 switch (_requestedVideoStreamType) {
363 if (!server->start(serverType, QStringLiteral(
"127.0.0.1"), port)) {
364 qCWarning(MockLinkLog) <<
"Failed to start mock video stream server for type" << _requestedVideoStreamType;
369 _videoStreamServer = server;
371 QMutexLocker locker(&_videoStreamMutex);
372 _servedVideoStreamType = _requestedVideoStreamType;
373 _videoStreamUri = server->servedUri();
375 qCDebug(MockLinkLog) <<
"Mock video stream server serving" << server->servedUri();
379void MockLink::_stopVideoStreamServer()
381#ifdef QGC_GST_STREAMING
385 QMutexLocker locker(&_videoStreamMutex);
387 _videoStreamUri.clear();
389 if (_videoStreamServer) {
390 delete _videoStreamServer;
391 _videoStreamServer =
nullptr;
398 QMutexLocker locker(&_videoStreamMutex);
399 type = _servedVideoStreamType;
400 uri = _videoStreamUri;
415 _sendBatteryStatus();
416 _sendNamedValueFloats();
421 if (_vehicleType != MAV_TYPE_SUBMARINE) {
422 _sendRemoteIDArmStatus();
424 _sendAvailableModesMonitor();
430 if (_enableProximity) {
431 _sendDistanceSensors();
436 if (_sendRadioStatusEnabled) {
451 _sendHomePositionDelayCount--;
465 const bool gpsDelayExpired = (_sendGPSPositionDelayCount == 0);
466 if (_sendGPSPositionDelayCount > 0) {
468 _sendGPSPositionDelayCount--;
471 if (_vehicleType != MAV_TYPE_SUBMARINE) {
473 _sendGlobalPositionInt();
475 _sendExtendedSysState();
478 _sendAttitudeQuaternion();
479 _sendAttitudeTarget();
480 _sendLocalPositionNed();
481 _sendPositionTargetLocalNed();
495 _availableModesWorker();
504 for (
int i = 0; i < paramSends; ++i) {
505 _paramRequestListWorker();
507 _logDownloadWorker();
508 _apmCompassCalWorker();
509 _apmAccelCalWorker();
515 _sendStatusTextMessages();
518bool MockLink::_allocateMavlinkChannel()
521 if (_incomingMavlinkChannelIsSet() || _outgoingMavlinkChannelIsSet() ||
mavlinkChannelIsSet()) {
522 qCWarning(MockLinkLog) <<
"Mock MAVLink channels are already allocated";
527 qCWarning(MockLinkLog) <<
"LinkInterface::_allocateMavlinkChannel failed";
532 if (!_incomingMavlinkChannelIsSet()) {
533 qCWarning(MockLinkLog) <<
"_allocateMavlinkChannel aux failed";
539 if (!_outgoingMavlinkChannelIsSet()) {
540 qCWarning(MockLinkLog) <<
"_allocateMavlinkChannel vehicle failed";
547 qCDebug(MockLinkLog) <<
"_allocateMavlinkChannel aux:" << _incomingMavlinkChannel <<
"vehicle:" << _outgoingMavlinkChannel;
551void MockLink::_freeMavlinkChannel()
553 qCDebug(MockLinkLog) <<
"_freeMavlinkChannel aux:" << _incomingMavlinkChannel <<
"vehicle:" << _outgoingMavlinkChannel;
554 if (!_incomingMavlinkChannelIsSet()) {
556 qCWarning(MockLinkLog) <<
"Releasing a partially allocated mock MAVLink channel set";
560 if (_outgoingMavlinkChannelIsSet()) {
562 mavlink_reset_channel_status(_outgoingMavlinkChannel);
566 if (_incomingMavlinkChannelIsSet()) {
567 mavlink_reset_channel_status(_incomingMavlinkChannel);
576bool MockLink::_incomingMavlinkChannelIsSet()
const
581bool MockLink::_outgoingMavlinkChannelIsSet()
const
586void MockLink::_loadParams()
589 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
590 if (_vehicleType == MAV_TYPE_FIXED_WING) {
591 paramFile.setFileName(
":/FirmwarePlugin/APM/Plane.OfflineEditing.params");
592 }
else if (_vehicleType == MAV_TYPE_SUBMARINE ) {
593 paramFile.setFileName(
":/FirmwarePlugin/APM/Sub.OfflineEditing.params");
594 }
else if (_vehicleType == MAV_TYPE_GROUND_ROVER ) {
595 paramFile.setFileName(
":/FirmwarePlugin/APM/Rover.OfflineEditing.params");
597 paramFile.setFileName(
":/FirmwarePlugin/APM/Copter.OfflineEditing.params");
599 }
else if (_firmwareType == MAV_AUTOPILOT_GENERIC) {
600 paramFile.setFileName(
":/MockLink/GenericMockLink.params");
602 paramFile.setFileName(
":/MockLink/PX4MockLink.params");
605 if (!paramFile.open(QFile::ReadOnly)) {
606 qCWarning(MockLinkLog) <<
"Cannot open mock parameters:" << paramFile.fileName() << paramFile.errorString();
610 QTextStream paramStream(¶mFile);
611 while (!paramStream.atEnd()) {
612 const QString
line = paramStream.readLine();
614 if (
line.startsWith(
"#")) {
618 const QStringList paramData =
line.split(
"\t");
619 if (paramData.size() != 5) {
620 qCWarning(MockLinkLog) <<
"Invalid mock parameter row in" << paramFile.fileName();
621 _mapParamName2Value.clear();
622 _mapParamName2MavParamType.clear();
626 const int compId = paramData.at(1).toInt();
627 const QString paramName = paramData.at(2);
628 const QString valStr = paramData.at(3);
629 const uint paramType = paramData.at(4).toUInt();
633 case MAV_PARAM_TYPE_REAL32:
636 case MAV_PARAM_TYPE_UINT32:
639 case MAV_PARAM_TYPE_INT32:
642 case MAV_PARAM_TYPE_UINT16:
643 paramValue = QVariant((quint16)valStr.toUInt());
645 case MAV_PARAM_TYPE_INT16:
646 paramValue = QVariant((qint16)valStr.toInt());
648 case MAV_PARAM_TYPE_UINT8:
649 paramValue = QVariant((quint8)valStr.toUInt());
651 case MAV_PARAM_TYPE_INT8:
652 paramValue = QVariant((qint8)valStr.toUInt());
655 qCCritical(MockLinkVerboseLog) <<
"Unknown type" << paramType;
660 qCDebug(MockLinkVerboseLog) <<
"Loading param" << paramName <<
paramValue;
662 _mapParamName2Value[compId][paramName] =
paramValue;
663 _mapParamName2MavParamType[compId][paramName] =
static_cast<MAV_PARAM_TYPE
>(paramType);
666 if (_hasNonDefaultParamComponent()) {
667 auto &values = _mapParamName2Value[_nonDefaultParamComponentId];
668 auto &types = _mapParamName2MavParamType[_nonDefaultParamComponentId];
669 values[QStringLiteral(
"CAN_TERMINATE")] = QVariant(
static_cast<qint32
>(0));
670 types[QStringLiteral(
"CAN_TERMINATE")] = MAV_PARAM_TYPE_INT32;
671 values[QLatin1StringView(_nonDefaultFailParam)] = QVariant(
static_cast<qint32
>(8));
672 types[QLatin1StringView(_nonDefaultFailParam)] = MAV_PARAM_TYPE_INT32;
673 values[QStringLiteral(
"BATT_AMP_OFFSET")] = QVariant(0.0f);
674 types[QStringLiteral(
"BATT_AMP_OFFSET")] = MAV_PARAM_TYPE_REAL32;
675 values[QStringLiteral(
"OPTIONS")] = QVariant(
static_cast<qint32
>(0));
676 types[QStringLiteral(
"OPTIONS")] = MAV_PARAM_TYPE_INT32;
678 for (
const char *name: {
"TEMP1_ADDR",
"TEMP1_TYPE",
"TEMP2_ADDR",
"TEMP2_TYPE",
"TEMP3_ADDR",
"TEMP3_TYPE",
"UAVCAN_NODE_ID",
"UAVCAN_RATE",
"VOLT_MULT",
"VOLT_PIN"}) {
679 values[QLatin1StringView(name)] = QVariant(
static_cast<qint32
>(0));
680 types[QLatin1StringView(name)] = MAV_PARAM_TYPE_INT32;
684 if ((_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) && _apmStartFreshParams) {
685 _applyAPMFreshFlashState();
699void MockLink::_resetParamsToDefaults()
701 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
705 for (
auto compIt = _mapParamName2Value.begin(); compIt != _mapParamName2Value.end(); ++compIt) {
706 for (
auto paramIt = compIt.value().begin(); paramIt != compIt.value().end(); ++paramIt) {
707 if (kAPMCalOffsetParams.contains(paramIt.key())) {
708 paramIt.value() = QVariant(0.0f);
715 if (_firmwareType != MAV_AUTOPILOT_PX4) {
716 qCWarning(MockLinkLog) <<
"Param reset to defaults not supported for firmware type" << _firmwareType;
720 QFile metaDataFile(QStringLiteral(
":/MockLink/Parameter.MetaData.json"));
721 if (!metaDataFile.open(QFile::ReadOnly)) {
722 qCWarning(MockLinkLog) <<
"Unable to open parameter metadata for reset" << metaDataFile.fileName();
726 QJsonParseError parseError{};
727 const QJsonDocument doc = QJsonDocument::fromJson(metaDataFile.readAll(), &parseError);
728 if (parseError.error != QJsonParseError::NoError) {
729 qCWarning(MockLinkLog) <<
"Unable to parse parameter metadata for reset:" << parseError.errorString();
733 QHash<QString, QVariant> defaults;
734 const QJsonArray parameters = doc.object().value(QStringLiteral(
"parameters")).toArray();
735 for (
const QJsonValue ¶meter : parameters) {
736 const QJsonObject paramObject = parameter.toObject();
737 if (paramObject.contains(QStringLiteral(
"default"))) {
738 defaults[paramObject.value(QStringLiteral(
"name")).toString()] = paramObject.value(QStringLiteral(
"default")).toVariant();
742 for (
auto compIt = _mapParamName2Value.begin(); compIt != _mapParamName2Value.end(); ++compIt) {
743 const int compId = compIt.key();
744 for (
auto paramIt = compIt.value().begin(); paramIt != compIt.value().end(); ++paramIt) {
745 const QString ¶mName = paramIt.key();
746 if (!_resetSysAutostartOnParamReset && (paramName == QLatin1String(
"SYS_AUTOSTART"))) {
749 const auto defaultIt = defaults.constFind(paramName);
750 if (defaultIt == defaults.constEnd()) {
753 switch (_mapParamName2MavParamType[compId][paramName]) {
754 case MAV_PARAM_TYPE_REAL32:
755 paramIt.value() = QVariant(defaultIt->toFloat());
757 case MAV_PARAM_TYPE_UINT32:
758 paramIt.value() = QVariant(defaultIt->toUInt());
760 case MAV_PARAM_TYPE_INT32:
761 paramIt.value() = QVariant(defaultIt->toInt());
763 case MAV_PARAM_TYPE_UINT16:
764 paramIt.value() = QVariant(
static_cast<quint16
>(defaultIt->toUInt()));
766 case MAV_PARAM_TYPE_INT16:
767 paramIt.value() = QVariant(
static_cast<qint16
>(defaultIt->toInt()));
769 case MAV_PARAM_TYPE_UINT8:
770 paramIt.value() = QVariant(
static_cast<quint8
>(defaultIt->toUInt()));
772 case MAV_PARAM_TYPE_INT8:
773 paramIt.value() = QVariant(
static_cast<qint8
>(defaultIt->toInt()));
776 qCWarning(MockLinkLog) <<
"Param reset skipped unhandled type" << _mapParamName2MavParamType[compId][paramName] << paramName;
786void MockLink::_applyAPMFreshFlashState()
789 _resetParamsToDefaults();
791 auto ¶mMap = _mapParamName2Value[MAV_COMP_ID_AUTOPILOT1];
792 const auto ¶mTypeMap = _mapParamName2MavParamType[MAV_COMP_ID_AUTOPILOT1];
795 auto setIntParam = [¶mMap, ¶mTypeMap](
const QString ¶mName,
int value) {
796 if (!paramMap.contains(paramName)) {
799 switch (paramTypeMap.value(paramName)) {
800 case MAV_PARAM_TYPE_UINT32: paramMap[paramName] = QVariant(
static_cast<quint32
>(value));
break;
801 case MAV_PARAM_TYPE_INT32: paramMap[paramName] = QVariant(value);
break;
802 case MAV_PARAM_TYPE_UINT16: paramMap[paramName] = QVariant(
static_cast<quint16
>(value));
break;
803 case MAV_PARAM_TYPE_INT16: paramMap[paramName] = QVariant(
static_cast<qint16
>(value));
break;
804 case MAV_PARAM_TYPE_UINT8: paramMap[paramName] = QVariant(
static_cast<quint8
>(value));
break;
805 case MAV_PARAM_TYPE_INT8: paramMap[paramName] = QVariant(
static_cast<qint8
>(value));
break;
806 default: paramMap[paramName] = QVariant(value);
break;
811 setIntParam(QStringLiteral(
"FRAME_CLASS"), 0);
814 const QStringList rcMapParams = {
815 QStringLiteral(
"RCMAP_ROLL"),
816 QStringLiteral(
"RCMAP_PITCH"),
817 QStringLiteral(
"RCMAP_YAW"),
818 QStringLiteral(
"RCMAP_THROTTLE"),
820 for (
const QString &mapParam : rcMapParams) {
821 if (!paramMap.contains(mapParam)) {
824 const int channel = paramMap[mapParam].toInt();
825 setIntParam(QStringLiteral(
"RC%1_MIN").arg(channel), 1100);
826 setIntParam(QStringLiteral(
"RC%1_MAX").arg(channel), 1900);
827 setIntParam(QStringLiteral(
"RC%1_TRIM").arg(channel), 1500);
831void MockLink::_sendHeartBeat()
835 const uint8_t mavState = (_vehicleAltitudeAMSL > _defaultVehicleHomeAltitude) ? MAV_STATE_ACTIVE : MAV_STATE_STANDBY;
838 (void) mavlink_msg_heartbeat_pack_chan(
841 _outgoingMavlinkChannel,
852void MockLink::_sendHighLatency2()
854 qCDebug(MockLinkLog) <<
"Sending" << _mavCustomMode;
857 px4_cm.
data = _mavCustomMode;
860 (void) mavlink_msg_high_latency2_pack_chan(
863 _outgoingMavlinkChannel,
868 px4_cm.custom_mode_hl,
869 static_cast<int32_t
>(_vehicleLatitude * 1E7),
870 static_cast<int32_t
>(_vehicleLongitude * 1E7),
871 static_cast<int16_t
>(_vehicleAltitudeAMSL),
872 static_cast<int16_t
>(_vehicleAltitudeAMSL),
894void MockLink::_sendSysStatus()
897 (void) mavlink_msg_sys_status_pack_chan(
900 _outgoingMavlinkChannel,
902 MAV_SYS_STATUS_SENSOR_GPS,
908 _battery1PctRemaining,
914void MockLink::_sendBatteryStatus()
916 if (_battery1PctRemaining > 1) {
917 _battery1PctRemaining =
static_cast<int8_t
>(100 - (_runningTime.elapsed() / 1000));
918 _battery1TimeRemaining =
static_cast<double>(_batteryMaxTimeRemaining) * (
static_cast<double>(_battery1PctRemaining) / 100.0);
919 if (_battery1PctRemaining > 50) {
920 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_OK;
921 }
else if (_battery1PctRemaining > 30) {
922 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_LOW;
923 }
else if (_battery1PctRemaining > 20) {
924 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_CRITICAL;
926 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_EMERGENCY;
930 if (_battery2PctRemaining > 1) {
931 _battery2PctRemaining =
static_cast<int8_t
>(100 - ((_runningTime.elapsed() / 1000) / 2));
932 _battery2TimeRemaining =
static_cast<double>(_batteryMaxTimeRemaining) * (
static_cast<double>(_battery2PctRemaining) / 100.0);
933 if (_battery2PctRemaining > 50) {
934 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_OK;
935 }
else if (_battery2PctRemaining > 30) {
936 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_LOW;
937 }
else if (_battery2PctRemaining > 20) {
938 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_CRITICAL;
940 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_EMERGENCY;
945 uint16_t rgVoltages[10]{};
946 uint16_t rgVoltagesNone[10]{};
947 uint16_t rgVoltagesExtNone[4]{};
949 for (
size_t i = 0; i < std::size(rgVoltages); i++) {
950 rgVoltages[i] = UINT16_MAX;
951 rgVoltagesNone[i] = UINT16_MAX;
953 rgVoltages[0] = rgVoltages[1] = rgVoltages[2] = 4200;
955 (void) mavlink_msg_battery_status_pack_chan(
958 _outgoingMavlinkChannel,
961 MAV_BATTERY_FUNCTION_ALL,
962 MAV_BATTERY_TYPE_LIPO,
968 _battery1PctRemaining,
969 _battery1TimeRemaining,
970 _battery1ChargeState,
977 (void) mavlink_msg_battery_status_pack_chan(
980 _outgoingMavlinkChannel,
983 MAV_BATTERY_FUNCTION_ALL,
984 MAV_BATTERY_TYPE_LIPO,
990 _battery2PctRemaining,
991 _battery2TimeRemaining,
992 _battery2ChargeState,
1000void MockLink::_sendNamedValueFloats()
1002 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
1005 const float sinVal =
static_cast<float>(std::sin(
static_cast<double>(timeBootMs) / 1000.0));
1006 const float cosVal =
static_cast<float>(std::cos(
static_cast<double>(timeBootMs) / 1000.0));
1009 static constexpr char kSinName[10] =
"sin_wave";
1010 static constexpr char kCosName[10] =
"cos_wave";
1013 (void) mavlink_msg_named_value_float_pack_chan(
1015 _vehicleComponentId,
1016 _outgoingMavlinkChannel,
1024 (void) mavlink_msg_named_value_float_pack_chan(
1026 _vehicleComponentId,
1027 _outgoingMavlinkChannel,
1036void MockLink::_sendVibration()
1039 (void) mavlink_msg_vibration_pack_chan(
1041 _vehicleComponentId,
1042 _outgoingMavlinkChannel,
1055void MockLink::_sendDistanceSensors()
1059 static constexpr MAV_SENSOR_ORIENTATION rgOrientations[] = {
1060 MAV_SENSOR_ROTATION_NONE,
1061 MAV_SENSOR_ROTATION_YAW_45,
1062 MAV_SENSOR_ROTATION_YAW_90,
1063 MAV_SENSOR_ROTATION_YAW_180,
1064 MAV_SENSOR_ROTATION_YAW_270,
1065 MAV_SENSOR_ROTATION_YAW_315,
1068 static constexpr uint16_t minDistanceCm = 20;
1069 static constexpr uint16_t maxDistanceCm = 4000;
1071 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
1072 const float quaternion[4]{};
1074 for (
size_t i = 0; i < std::size(rgOrientations); i++) {
1076 const double sweep = std::sin((timeBootMs / 5000.0) + (i * M_PI / 4));
1077 const uint16_t currentDistanceCm =
static_cast<uint16_t
>(2000 + (1500 * sweep));
1080 (void) mavlink_msg_distance_sensor_pack_chan(
1082 _vehicleComponentId,
1083 _outgoingMavlinkChannel,
1089 MAV_DISTANCE_SENSOR_LASER,
1090 static_cast<uint8_t
>(i),
1105 uint8_t buffer[MAVLINK_MAX_PACKET_LEN]{};
1106 const int cBuffer = mavlink_msg_to_send_buffer(buffer, &msg);
1107 const QByteArray bytes(
reinterpret_cast<char*
>(buffer), cBuffer);
1109 if (_duplicateResponses) {
1115void MockLink::_writeBytes(
const QByteArray &bytes)
1121void MockLink::_writeBytesQueued(
const QByteArray &bytes)
1124 qCDebug(MockLinkLog) <<
"Dropping queued bytes on disconnected/uninitialized mock link";
1129 _handleIncomingNSHBytes(bytes.constData(), bytes.length());
1133 if (bytes.startsWith(QByteArrayLiteral(
"\r\r\r"))) {
1135 _handleIncomingNSHBytes(&bytes.constData()[3], bytes.length() - 3);
1138 _handleIncomingMavlinkBytes(
reinterpret_cast<const uint8_t*
>(bytes.constData()), bytes.length());
1141void MockLink::_handleIncomingNSHBytes(
const char *bytes,
int cBytes)
1146 if ((cBytes == 4) && (bytes[0] ==
'\r') && (bytes[1] ==
'\r') && (bytes[2] ==
'\r')) {
1152 qCDebug(MockLinkLog) <<
"NSH:" << bytes;
1155 if (strncmp(bytes,
"sh /etc/init.d/rc.usb\n", cBytes) == 0) {
1157 _mavlinkStarted =
true;
1163void MockLink::_handleIncomingMavlinkBytes(
const uint8_t *bytes,
int cBytes)
1166 mavlink_status_t comm{};
1168 QMutexLocker lock(&_incomingMavlinkMutex);
1169 for (qint64 i = 0; i < cBytes; i++) {
1170 const int parsed = mavlink_parse_char(_incomingMavlinkChannel, bytes[i], &msg, &comm);
1174 if (!_mavlinkV2Upgraded && !_stayMavlinkV1 && (msg.magic == MAVLINK_STX)) {
1176 _mavlinkV2Upgraded =
true;
1178 outgoingStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
1179 qCDebug(MockLinkLog) <<
"Received MAVLink v2 message from GCS, upgrading outgoing traffic to v2";
1182 _handleIncomingMavlinkMsg(msg);
1189 _receivedMavlinkMessageCountMap[msg.msgid]++;
1190 _lastReceivedMavlinkMessageMap[msg.msgid] = msg;
1193 if (msg.msgid == MAVLINK_MSG_ID_COMMAND_LONG) {
1195 mavlink_msg_command_long_decode(&msg, &request);
1197 _receivedMavCommandCountMap[
static_cast<MAV_CMD
>(request.command)]++;
1198 _receivedMavCommandByCompCountMap[
static_cast<MAV_CMD
>(request.command)][request.target_component]++;
1200 if (request.command == MAV_CMD_REQUEST_MESSAGE) {
1201 _receivedRequestMessageCountMap[
static_cast<uint32_t
>(request.param1)]++;
1202 _receivedRequestMessageByCompAndMsgCountMap[request.target_component][
static_cast<int>(request.param1)]++;
1204 }
else if (msg.msgid == MAVLINK_MSG_ID_COMMAND_INT) {
1205 mavlink_command_int_t request{};
1206 mavlink_msg_command_int_decode(&msg, &request);
1208 _receivedMavCommandCountMap[
static_cast<MAV_CMD
>(request.command)]++;
1209 _receivedMavCommandByCompCountMap[
static_cast<MAV_CMD
>(request.command)][request.target_component]++;
1215 _updateIncomingMessageCounts(msg);
1229 switch (msg.msgid) {
1230 case MAVLINK_MSG_ID_HEARTBEAT:
1231 _handleHeartBeat(msg);
1233 case MAVLINK_MSG_ID_PARAM_REQUEST_LIST:
1234 _handleParamRequestList(msg);
1236 case MAVLINK_MSG_ID_SET_MODE:
1237 _handleSetMode(msg);
1239 case MAVLINK_MSG_ID_PARAM_SET:
1240 _handleParamSet(msg);
1242 case MAVLINK_MSG_ID_PARAM_REQUEST_READ:
1243 _handleParamRequestRead(msg);
1245 case MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL:
1248 case MAVLINK_MSG_ID_COMMAND_LONG:
1249 _handleCommandLong(msg);
1251 case MAVLINK_MSG_ID_COMMAND_INT:
1252 _handleCommandInt(msg);
1254 case MAVLINK_MSG_ID_MANUAL_CONTROL:
1255 _handleManualControl(msg);
1257 case MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE:
1258 _handleRCChannelsOverride(msg);
1260 case MAVLINK_MSG_ID_LOG_REQUEST_LIST:
1261 _handleLogRequestList(msg);
1263 case MAVLINK_MSG_ID_LOG_REQUEST_DATA:
1264 _handleLogRequestData(msg);
1266 case MAVLINK_MSG_ID_LOG_ERASE:
1267 _handleLogErase(msg);
1269 case MAVLINK_MSG_ID_PARAM_MAP_RC:
1270 _handleParamMapRC(msg);
1272 case MAVLINK_MSG_ID_SETUP_SIGNING:
1273 _handleSetupSigning(msg);
1275 case MAVLINK_MSG_ID_COMMAND_ACK:
1278 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1280 mavlink_msg_command_ack_decode(&msg, &ack);
1281 if (ack.command == 0) {
1282 QMutexLocker locker(&_apmAccelCalMutex);
1283 if (_apmAccelCalPosIndex >= 0 && _apmAccelCalPosIndex < 6) {
1284 _apmAccelCalGotAck =
true;
1297 qCDebug(MockLinkVerboseLog) <<
"Heartbeat";
1302 mavlink_param_map_rc_t paramMapRC{};
1303 mavlink_msg_param_map_rc_decode(&msg, ¶mMapRC);
1305 const QString paramName(QString::fromLocal8Bit(paramMapRC.param_id,
static_cast<int>(strnlen(paramMapRC.param_id, MAVLINK_MSG_PARAM_MAP_RC_FIELD_PARAM_ID_LEN))));
1307 if (paramMapRC.param_index == -1) {
1308 qCDebug(MockLinkLog) << QStringLiteral(
"MockLink - PARAM_MAP_RC: param(%1) tuningID(%2) centerValue(%3) scale(%4) min(%5) max(%6)").arg(paramName).arg(paramMapRC.parameter_rc_channel_index).arg(paramMapRC.param_value0).arg(paramMapRC.scale).arg(paramMapRC.param_value_min).arg(paramMapRC.param_value_max);
1309 }
else if (paramMapRC.param_index == -2) {
1310 qCDebug(MockLinkLog) <<
"MockLink - PARAM_MAP_RC: Clear tuningID" << paramMapRC.parameter_rc_channel_index;
1312 qCWarning(MockLinkLog) <<
"MockLink - PARAM_MAP_RC: Unsupported param_index" << paramMapRC.param_index;
1319 mavlink_msg_setup_signing_decode(&msg, &setupSigning);
1321 if (setupSigning.target_system != _vehicleSystemId) {
1326 bool allZeroKey =
true;
1327 for (
const uint8_t
byte : setupSigning.secret_key) {
1334 _signingEnabled = !allZeroKey;
1338 if (_signingEnabled) {
1339 memcpy(_mockSigning.secret_key, setupSigning.secret_key,
sizeof(_mockSigning.secret_key));
1340 _mockSigning.link_id = _outgoingMavlinkChannel;
1341 _mockSigning.flags = MAVLINK_SIGNING_FLAG_SIGN_OUTGOING;
1344 status->signing = &_mockSigning;
1345 status->signing_streams = &_mockSigningStreams;
1347 QGC::secureZero(_mockSigning.secret_key,
sizeof(_mockSigning.secret_key));
1348 _mockSigning.accept_unsigned_callback =
nullptr;
1349 status->signing =
nullptr;
1350 status->signing_streams =
nullptr;
1353 qCDebug(MockLinkLog) <<
"Signing" << (_signingEnabled ?
"enabled" :
"disabled");
1358 mavlink_set_mode_t request{};
1359 mavlink_msg_set_mode_decode(&msg, &request);
1361 if (request.target_system != _vehicleSystemId) {
1362 qCDebug(MockLinkLog) <<
"Ignoring SET_MODE for system" << request.target_system;
1366 _mavBaseMode = request.base_mode;
1367 _mavCustomMode = request.custom_mode;
1372 mavlink_manual_control_t manualControl{};
1373 mavlink_msg_manual_control_decode(&msg, &manualControl);
1376 const auto axisStr = [](int16_t v) -> QString {
1377 return (v == INT16_MAX) ? QStringLiteral(
"invalid") : QString::
number(v);
1380 const auto extStr = [](int16_t v,
bool enabled) -> QString {
1381 return enabled ? QString::number(v) : QStringLiteral(
"disabled");
1384 const uint8_t ext = manualControl.enabled_extensions;
1386 qCDebug(MockLinkVerboseLog).noquote()
1388 <<
"target:" << manualControl.target
1389 <<
"x:" << axisStr(manualControl.x)
1390 <<
"y:" << axisStr(manualControl.y)
1391 <<
"z:" << axisStr(manualControl.z)
1392 <<
"r:" << axisStr(manualControl.r)
1393 <<
"buttons:" << QStringLiteral(
"0x%1").arg(manualControl.buttons, 4, 16, QLatin1Char(
'0'))
1394 <<
"buttons2:" << QStringLiteral(
"0x%1").arg(manualControl.buttons2, 4, 16, QLatin1Char(
'0'))
1395 <<
"enabled_extensions:" << QStringLiteral(
"0x%1").arg(ext, 2, 16, QLatin1Char(
'0'))
1396 <<
"s(pitch):" << extStr(manualControl.s, ext & (1 << 0))
1397 <<
"t(roll):" << extStr(manualControl.t, ext & (1 << 1))
1398 <<
"aux1:" << extStr(manualControl.aux1, ext & (1 << 2))
1399 <<
"aux2:" << extStr(manualControl.aux2, ext & (1 << 3))
1400 <<
"aux3:" << extStr(manualControl.aux3, ext & (1 << 4))
1401 <<
"aux4:" << extStr(manualControl.aux4, ext & (1 << 5))
1402 <<
"aux5:" << extStr(manualControl.aux5, ext & (1 << 6))
1403 <<
"aux6:" << extStr(manualControl.aux6, ext & (1 << 7));
1408 mavlink_rc_channels_override_t
override{};
1409 mavlink_msg_rc_channels_override_decode(&msg, &
override);
1414 const uint16_t rawValues[18] = {
1415 override.chan1_raw,
override.chan2_raw,
override.chan3_raw,
override.chan4_raw,
1416 override.chan5_raw,
override.chan6_raw,
override.chan7_raw,
override.chan8_raw,
1417 override.chan9_raw,
override.chan10_raw,
override.chan11_raw,
override.chan12_raw,
1418 override.chan13_raw,
override.chan14_raw,
override.chan15_raw,
override.chan16_raw,
1419 override.chan17_raw,
override.chan18_raw,
1422 bool anyChange =
false;
1423 for (
int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
1424 const uint16_t raw = rawValues[i];
1425 const bool isExtended = (i >= 8);
1427 RCChannelOverride::State newState;
1429 if (raw == 0 || raw == UINT16_MAX) {
1431 }
else if (raw ==
static_cast<uint16_t
>(UINT16_MAX - 1)) {
1432 newState = RCChannelOverride::State::Released;
1434 newState = RCChannelOverride::State::Overridden;
1437 if (raw == UINT16_MAX) {
1439 }
else if (raw == 0) {
1440 newState = RCChannelOverride::State::Released;
1442 newState = RCChannelOverride::State::Overridden;
1446 RCChannelOverride &ch = _rcChannelOverrides[i];
1447 if (ch.state == newState) {
1453 const auto stateLabel = [](RCChannelOverride::State s) ->
const char * {
1455 case RCChannelOverride::State::Ignore:
return "ignore";
1456 case RCChannelOverride::State::Released:
return "released";
1457 case RCChannelOverride::State::Overridden:
return "overridden";
1461 qCDebug(MockLinkLog).noquote() << QStringLiteral(
"RC_CHANNELS_OVERRIDE ch%1: %2 -> %3").arg(i + 1).arg(stateLabel(ch.state)).arg(stateLabel(newState));
1463 ch.state = newState;
1464 ch.value = (newState == RCChannelOverride::State::Overridden) ? raw : 0;
1469 for (
int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
1470 if (_rcChannelOverrides[i].state == RCChannelOverride::State::Overridden) {
1471 active << QStringLiteral(
"ch%1").arg(i + 1);
1474 if (active.isEmpty()) {
1475 qCDebug(MockLinkLog) <<
"RC_CHANNELS_OVERRIDE: no channels currently overridden";
1477 qCDebug(MockLinkLog).noquote() <<
"RC_CHANNELS_OVERRIDE active overrides:" << active.join(QStringLiteral(
", "));
1481 for (
int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
1482 if (_rcChannelOverrides[i].state == RCChannelOverride::State::Overridden) {
1483 qCDebug(MockLinkVerboseLog).noquote() << QStringLiteral(
"RC_CHANNELS_OVERRIDE ch%1 value: %2").arg(i + 1).arg(_rcChannelOverrides[i].value);
1488void MockLink::_setParamFloatUnionIntoMap(
int componentId,
const QString ¶mName,
float paramFloat)
1490 if (!_mapParamName2Value.contains(componentId) || !_mapParamName2Value[componentId].contains(paramName) ||
1491 !_mapParamName2MavParamType.contains(componentId) ||
1492 !_mapParamName2MavParamType[componentId].contains(paramName)) {
1493 qCWarning(MockLinkLog) <<
"Cannot update unknown mock parameter" << componentId << paramName;
1497 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
1498 QVariant paramVariant;
1500 valueUnion.param_float = paramFloat;
1501 switch (paramType) {
1502 case MAV_PARAM_TYPE_REAL32:
1503 paramVariant = QVariant::fromValue(valueUnion.param_float);
1505 case MAV_PARAM_TYPE_UINT32:
1506 paramVariant = QVariant::fromValue(valueUnion.param_uint32);
1508 case MAV_PARAM_TYPE_INT32:
1509 paramVariant = QVariant::fromValue(valueUnion.param_int32);
1511 case MAV_PARAM_TYPE_UINT16:
1512 paramVariant = QVariant::fromValue(valueUnion.param_uint16);
1514 case MAV_PARAM_TYPE_INT16:
1515 paramVariant = QVariant::fromValue(valueUnion.param_int16);
1517 case MAV_PARAM_TYPE_UINT8:
1518 paramVariant = QVariant::fromValue(valueUnion.param_uint8);
1520 case MAV_PARAM_TYPE_INT8:
1521 paramVariant = QVariant::fromValue(valueUnion.param_int8);
1524 qCCritical(MockLinkLog) <<
"Invalid parameter type" << paramType;
1525 paramVariant = QVariant::fromValue(valueUnion.param_int32);
1529 qCDebug(MockLinkLog) <<
"_setParamFloatUnionIntoMap" << paramName << paramVariant;
1530 _mapParamName2Value[componentId][paramName] = paramVariant;
1536 valueUnion.param_float = value;
1537 _setParamFloatUnionIntoMap(componentId, paramName, valueUnion.param_float);
1540float MockLink::_floatUnionForParam(
int componentId,
const QString ¶mName)
1542 if (!_mapParamName2Value.contains(componentId) || !_mapParamName2Value[componentId].contains(paramName) ||
1543 !_mapParamName2MavParamType.contains(componentId) ||
1544 !_mapParamName2MavParamType[componentId].contains(paramName)) {
1545 qCWarning(MockLinkLog) <<
"Cannot read unknown mock parameter" << componentId << paramName;
1549 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
1550 const QVariant paramVar = _mapParamName2Value[componentId][paramName];
1553 switch (paramType) {
1554 case MAV_PARAM_TYPE_REAL32:
1555 valueUnion.param_float = paramVar.toFloat();
1557 case MAV_PARAM_TYPE_UINT32:
1558 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1559 valueUnion.param_float = paramVar.toUInt();
1561 valueUnion.param_uint32 = paramVar.toUInt();
1564 case MAV_PARAM_TYPE_INT32:
1565 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1566 valueUnion.param_float = paramVar.toInt();
1568 valueUnion.param_int32 = paramVar.toInt();
1571 case MAV_PARAM_TYPE_UINT16:
1572 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1573 valueUnion.param_float = paramVar.toUInt();
1575 valueUnion.param_uint16 = paramVar.toUInt();
1578 case MAV_PARAM_TYPE_INT16:
1579 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1580 valueUnion.param_float = paramVar.toInt();
1582 valueUnion.param_int16 = paramVar.toInt();
1585 case MAV_PARAM_TYPE_UINT8:
1586 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1587 valueUnion.param_float = paramVar.toUInt();
1589 valueUnion.param_uint8 = paramVar.toUInt();
1592 case MAV_PARAM_TYPE_INT8:
1593 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1594 valueUnion.param_float = (
unsigned char)paramVar.toChar().toLatin1();
1596 valueUnion.param_int8 = (
unsigned char)paramVar.toChar().toLatin1();
1600 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1601 valueUnion.param_float = paramVar.toInt();
1603 valueUnion.param_int32 = paramVar.toInt();
1605 qCCritical(MockLinkLog) <<
"Invalid parameter type" << paramType;
1608 return valueUnion.param_float;
1611uint32_t MockLink::_computeParamHash(
int componentId)
const
1616 static const QSet<QString> volatileParams = []() {
1617 QSet<QString> volatiles;
1618 QFile metaDataFile(QStringLiteral(
":/MockLink/Parameter.MetaData.json"));
1619 if (metaDataFile.open(QFile::ReadOnly)) {
1620 QJsonParseError parseError{};
1621 const QJsonDocument doc = QJsonDocument::fromJson(metaDataFile.readAll(), &parseError);
1622 if (parseError.error != QJsonParseError::NoError) {
1623 qCWarning(MockLinkLog) <<
"Unable to parse parameter metadata for volatile param list:" << parseError.errorString();
1625 const QJsonArray parameters = doc.object().value(QStringLiteral(
"parameters")).toArray();
1626 for (
const QJsonValue ¶meter : parameters) {
1627 const QJsonObject paramObject = parameter.toObject();
1628 if (paramObject.value(QStringLiteral(
"volatile")).toBool()) {
1629 volatiles.insert(paramObject.value(QStringLiteral(
"name")).toString());
1633 qCWarning(MockLinkLog) <<
"Unable to open parameter metadata for volatile param list" << metaDataFile.fileName();
1639 const auto ¶ms = _mapParamName2Value[componentId];
1640 for (
auto it = params.constBegin(); it != params.constEnd(); ++it) {
1641 const QString &name = it.key();
1642 if (volatileParams.contains(name)) {
1645 const QVariant &value = it.value();
1646 const MAV_PARAM_TYPE mavType = _mapParamName2MavParamType[componentId][name];
1649 crc =
QGC::crc32(
reinterpret_cast<const uint8_t *
>(qPrintable(name)), name.length(), crc);
1655bool MockLink::_hasNonDefaultParamComponent()
const
1657 switch (_failureMode) {
1668bool MockLink::_shouldSkipParamSend(
int componentId,
const QString ¶mName,
int paramIndex)
const
1670 switch (_failureMode) {
1673 return paramName == _failParam;
1675 return (componentId == _nonDefaultParamComponentId) && (paramName == _nonDefaultFailParam);
1677 return paramIndex == _sharedFailParamIndex;
1680 return (componentId == _nonDefaultParamComponentId) && (paramIndex >= _nonDefaultStreamedParamCount);
1686bool MockLink::_shouldSkipParamRead(
int componentId,
const QString ¶mName,
int paramIndex)
1688 switch (_failureMode) {
1690 return paramName == _failParam;
1692 return (componentId == _nonDefaultParamComponentId) && (paramName == _nonDefaultFailParam);
1694 return (componentId == MAV_COMP_ID_AUTOPILOT1) && (paramIndex == _sharedFailParamIndex);
1696 return componentId == _nonDefaultParamComponentId;
1698 if (componentId != _nonDefaultParamComponentId) {
1701 if (_nonDefaultReadAttempted.contains(qMakePair(componentId, paramIndex))) {
1704 _nonDefaultReadAttempted.insert(qMakePair(componentId, paramIndex));
1717 mavlink_param_request_list_t request{};
1718 mavlink_msg_param_request_list_decode(&msg, &request);
1720 if (request.target_system != _vehicleSystemId) {
1721 qCDebug(MockLinkLog) <<
"Ignoring PARAM_REQUEST_LIST for system" << request.target_system;
1727 QMutexLocker locker(&_paramRequestListMutex);
1728 if (request.target_component == MAV_COMP_ID_ALL) {
1729 _paramRequestListComponentIds = _mapParamName2Value.keys();
1731 _paramRequestListComponentIds.clear();
1732 if (_mapParamName2Value.contains(request.target_component)) {
1733 _paramRequestListComponentIds.append(request.target_component);
1738 if (_paramRequestListComponentIds.removeOne(MAV_COMP_ID_AUTOPILOT1)) {
1739 _paramRequestListComponentIds.append(MAV_COMP_ID_AUTOPILOT1);
1741 if (!_paramRequestListComponentIds.isEmpty()) {
1742 _paramRequestListParamNames = _mapParamName2Value[_paramRequestListComponentIds.first()].keys();
1746 _currentParamRequestListComponentIndex = 0;
1747 _currentParamRequestListParamIndex = 0;
1748 _paramRequestListHashCheckSent =
false;
1751void MockLink::_paramRequestListWorker()
1753 if (_currentParamRequestListComponentIndex == -1) {
1759 QMutexLocker locker(&_paramRequestListMutex);
1762 if (_currentParamRequestListComponentIndex >= _paramRequestListComponentIds.count()) {
1763 _currentParamRequestListComponentIndex = -1;
1767 const int componentId = _paramRequestListComponentIds.at(_currentParamRequestListComponentIndex);
1768 const int cParameters = _paramRequestListParamNames.count();
1770 if (_currentParamRequestListParamIndex >= cParameters) {
1774 if (_firmwareType == MAV_AUTOPILOT_PX4 && !_paramRequestListHashCheckSent) {
1775 _paramRequestListHashCheckSent =
true;
1778 valueUnion.type = MAV_PARAM_TYPE_UINT32;
1779 valueUnion.param_uint32 = _computeParamHash(componentId);
1781 char paramId[MAVLINK_MSG_ID_PARAM_VALUE_LEN]{};
1782 (void) strncpy(paramId,
"_HASH_CHECK", MAVLINK_MSG_ID_PARAM_VALUE_LEN);
1784 qCDebug(MockLinkLog) <<
"Sending _HASH_CHECK in PARAM_REQUEST_LIST stream" << componentId <<
"hash:" << valueUnion.param_uint32;
1787 (void) mavlink_msg_param_value_pack_chan(
1790 _outgoingMavlinkChannel,
1793 valueUnion.param_float,
1794 MAV_PARAM_TYPE_UINT32,
1803 if (++_currentParamRequestListComponentIndex >= _paramRequestListComponentIds.count()) {
1804 _currentParamRequestListComponentIndex = -1;
1805 _paramRequestListComponentIds.clear();
1806 _paramRequestListParamNames.clear();
1809 _paramRequestListParamNames = _mapParamName2Value[_paramRequestListComponentIds.at(_currentParamRequestListComponentIndex)].keys();
1810 _currentParamRequestListParamIndex = 0;
1811 _paramRequestListHashCheckSent =
false;
1816 const QString ¶mName = _paramRequestListParamNames.at(_currentParamRequestListParamIndex);
1818 if (_shouldSkipParamSend(componentId, paramName, _currentParamRequestListParamIndex)) {
1819 qCDebug(MockLinkLog) <<
"Skipping param send:" << paramName;
1821 char paramId[MAVLINK_MSG_ID_PARAM_VALUE_LEN]{};
1824 if (!_mapParamName2Value[componentId].contains(paramName) ||
1825 !_mapParamName2MavParamType[componentId].contains(paramName) ||
1826 paramName.length() > MAVLINK_MSG_ID_PARAM_VALUE_LEN) {
1827 qCWarning(MockLinkLog) <<
"Cannot stream invalid mock parameter" << componentId << paramName;
1828 _currentParamRequestListComponentIndex = -1;
1829 _paramRequestListComponentIds.clear();
1830 _paramRequestListParamNames.clear();
1834 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
1836 (void) strncpy(paramId, paramName.toLocal8Bit().constData(), MAVLINK_MSG_ID_PARAM_VALUE_LEN);
1838 qCDebug(MockLinkLog) <<
"Sending msg_param_value" << componentId << paramId << paramType << _mapParamName2Value[componentId][paramId];
1840 (void) mavlink_msg_param_value_pack_chan(
1843 _outgoingMavlinkChannel,
1846 _floatUnionForParam(componentId, paramName),
1849 _currentParamRequestListParamIndex
1855 ++_currentParamRequestListParamIndex;
1860 mavlink_param_set_t request{};
1861 mavlink_msg_param_set_decode(&msg, &request);
1863 if (request.target_system != _vehicleSystemId) {
1864 qCDebug(MockLinkLog) <<
"Ignoring PARAM_SET for system" << request.target_system;
1867 const int componentId = request.target_component;
1870 char paramId[MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN + 1]{};
1871 paramId[MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN] = 0;
1872 (void) strncpy(paramId, request.param_id, MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN);
1874 qCDebug(MockLinkLog) <<
"_handleParamSet" << componentId << paramId << request.param_type;
1879 if ((_firmwareType == MAV_AUTOPILOT_PX4) && (strncmp(paramId,
"_HASH_CHECK", MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN) == 0)) {
1880 QMutexLocker locker(&_paramRequestListMutex);
1881 _currentParamRequestListComponentIndex = -1;
1882 _paramRequestListComponentIds.clear();
1883 _paramRequestListParamNames.clear();
1884 qCDebug(MockLinkLog) <<
"Received _HASH_CHECK PARAM_SET, stopping parameter stream";
1891 if (!_mapParamName2Value.contains(componentId) || !_mapParamName2MavParamType.contains(componentId)) {
1892 qCDebug(MockLinkLog) <<
"_handleParamSet unknown component, rejecting with PARAM_ERROR - componentId:" << componentId <<
"param:" << paramId;
1893 _sendParamError(componentId, paramId, -1, MAV_PARAM_ERROR_COMPONENT_NOT_FOUND);
1896 if (!_mapParamName2Value[componentId].contains(paramId)) {
1897 qCDebug(MockLinkLog) <<
"_handleParamSet unknown param, rejecting with PARAM_ERROR - componentId:" << componentId <<
"param:" << paramId;
1898 _sendParamError(componentId, paramId, -1, MAV_PARAM_ERROR_DOES_NOT_EXIST);
1901 if (request.param_type != _mapParamName2MavParamType[componentId][paramId]) {
1902 qCDebug(MockLinkLog) <<
"_handleParamSet type mismatch, rejecting with PARAM_ERROR - param:" << paramId
1903 <<
"requested type:" << request.param_type <<
"actual type:" << _mapParamName2MavParamType[componentId][paramId];
1904 _sendParamError(componentId, paramId, -1, MAV_PARAM_ERROR_TYPE_MISMATCH);
1910 qCDebug(MockLinkLog) <<
"Param set failure: first attempt no ack" << paramId;
1911 _paramSetFailureFirstAttemptPending =
false;
1916 qCDebug(MockLinkLog) <<
"Param set failure: no ack" << paramId;
1921 qCDebug(MockLinkLog) <<
"Param set failure: PARAM_ERROR" << paramId;
1922 _sendParamError(componentId, paramId,
1923 _mapParamName2Value[componentId].
keys().indexOf(paramId),
1924 MAV_PARAM_ERROR_VALUE_OUT_OF_RANGE);
1929 _setParamFloatUnionIntoMap(componentId, paramId, request.param_value);
1932 mavlink_msg_param_value_pack_chan(
1935 _outgoingMavlinkChannel,
1938 request.param_value,
1940 _mapParamName2Value[componentId].count(),
1941 _mapParamName2Value[componentId].keys().indexOf(paramId)
1949 mavlink_param_request_read_t request{};
1950 mavlink_msg_param_request_read_decode(&msg, &request);
1952 const QString paramName(QString::fromLocal8Bit(request.param_id,
static_cast<int>(strnlen(request.param_id, MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN))));
1953 const int componentId = request.target_component;
1956 if ((_firmwareType == MAV_AUTOPILOT_PX4) && (paramName ==
"_HASH_CHECK")) {
1957 _hashCheckRequestCount++;
1958 if (_hashCheckNoResponse) {
1962 const int hashComponentId = _mapParamName2Value.contains(MAV_COMP_ID_AUTOPILOT1)
1963 ? MAV_COMP_ID_AUTOPILOT1
1964 : _mapParamName2Value.keys().first();
1967 valueUnion.type = MAV_PARAM_TYPE_UINT32;
1968 valueUnion.param_uint32 = _computeParamHash(hashComponentId);
1969 (void) mavlink_msg_param_value_pack_chan(
1972 _outgoingMavlinkChannel,
1975 valueUnion.param_float,
1976 MAV_PARAM_TYPE_UINT32,
1984 if (!_mapParamName2Value.contains(componentId)) {
1985 qCDebug(MockLinkLog) <<
"_handleParamRequestRead unknown component, rejecting with PARAM_ERROR - componentId:" << componentId <<
"param:" << paramName;
1986 const QByteArray paramIdBytes = paramName.toLocal8Bit();
1987 _sendParamError(componentId, paramIdBytes.constData(), request.param_index, MAV_PARAM_ERROR_COMPONENT_NOT_FOUND);
1991 char paramId[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN + 1]{};
1994 if (request.target_system != _vehicleSystemId) {
1995 qCDebug(MockLinkLog) <<
"Ignoring PARAM_REQUEST_READ for system" << request.target_system;
1999 if (request.param_index == -1) {
2001 (void) strncpy(paramId, request.param_id, MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN);
2004 if (request.param_index < 0 || request.param_index >= _mapParamName2Value[componentId].size()) {
2005 qCWarning(MockLinkLog) <<
"Invalid mock parameter index" << componentId << request.param_index;
2006 _sendParamError(componentId,
"", request.param_index, MAV_PARAM_ERROR_DOES_NOT_EXIST);
2010 const QString key = _mapParamName2Value[componentId].keys().at(request.param_index);
2011 if (key.length() > MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN) {
2012 qCWarning(MockLinkLog) <<
"Mock parameter name exceeds the wire limit" << componentId << key;
2015 strcpy(paramId, key.toLocal8Bit().constData());
2016 _paramRequestReadIndexLog.append(qMakePair(componentId,
static_cast<int>(request.param_index)));
2019 if (!_mapParamName2Value[componentId].contains(paramId) || !_mapParamName2MavParamType[componentId].contains(paramId)) {
2022 qCDebug(MockLinkLog) <<
"_handleParamRequestRead unknown param, rejecting with PARAM_ERROR - componentId:" << componentId <<
"param:" << paramId;
2023 _sendParamError(componentId, paramId, request.param_index, MAV_PARAM_ERROR_DOES_NOT_EXIST);
2027 const int paramIndex = (request.param_index == -1) ?
static_cast<int>(_mapParamName2Value[componentId].
keys().indexOf(paramId)) : request.param_index;
2028 if (_shouldSkipParamRead(componentId, paramId, paramIndex)) {
2029 qCDebug(MockLinkLog) <<
"Ignoring request read for" << paramId;
2034 qCDebug(MockLinkLog) <<
"Param request read failure: first attempt no response" << paramId;
2035 _paramRequestReadFailureFirstAttemptPending =
false;
2040 qCDebug(MockLinkLog) <<
"Param request read failure: no response" << paramId;
2045 qCDebug(MockLinkLog) <<
"Param request read failure: PARAM_ERROR" << paramId;
2046 _sendParamError(componentId, paramId, request.param_index, MAV_PARAM_ERROR_DOES_NOT_EXIST);
2050 (void) mavlink_msg_param_value_pack_chan(
2053 _outgoingMavlinkChannel,
2056 _floatUnionForParam(componentId, paramId),
2057 _mapParamName2MavParamType[componentId][paramId],
2058 _mapParamName2Value[componentId].count(),
2059 _mapParamName2Value[componentId].keys().indexOf(paramId)
2064void MockLink::_sendParamError(
int componentId,
const char *paramId, int16_t paramIndex, uint8_t errorCode)
2067 char paramIdBuf[MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN + 1] = {};
2068 (void) strncpy(paramIdBuf, paramId, MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN);
2070 (void) mavlink_msg_param_error_pack_chan(
2072 static_cast<uint8_t
>(componentId),
2073 _outgoingMavlinkChannel,
2091 uint8_t commandResult = MAV_RESULT_UNSUPPORTED;
2093 switch (request.command) {
2096 commandResult = MAV_RESULT_ACCEPTED;
2100 commandResult = MAV_RESULT_FAILED;
2108 (void) mavlink_msg_command_ack_pack_chan(
2110 _vehicleComponentId,
2111 _outgoingMavlinkChannel,
2114 MAV_RESULT_IN_PROGRESS,
2123 (void) mavlink_msg_command_ack_pack_chan(
2125 _vehicleComponentId,
2126 _outgoingMavlinkChannel,
2139void MockLink::_handleCommandLongSetMessageInterval(
const mavlink_command_long_t &request,
bool &accepted)
2143 static const QSet<int> kPidTuningMessageIds = {
2144 MAVLINK_MSG_ID_ATTITUDE_QUATERNION,
2145 MAVLINK_MSG_ID_ATTITUDE_TARGET,
2146 MAVLINK_MSG_ID_LOCAL_POSITION_NED,
2147 MAVLINK_MSG_ID_POSITION_TARGET_LOCAL_NED,
2148 MAVLINK_MSG_ID_NAV_CONTROLLER_OUTPUT,
2149 MAVLINK_MSG_ID_VFR_HUD,
2151 accepted = kPidTuningMessageIds.contains(
static_cast<int>(request.param1));
2156 static bool firstCmdUser3 =
true;
2157 static bool firstCmdUser4 =
true;
2160 mavlink_msg_command_long_decode(&msg, &request);
2162 uint8_t commandResult = MAV_RESULT_UNSUPPORTED;
2164 switch (request.command) {
2165 case MAV_CMD_COMPONENT_ARM_DISARM:
2166 if (request.param1 == 0.0f) {
2167 _mavBaseMode &= ~MAV_MODE_FLAG_SAFETY_ARMED;
2169 _mavBaseMode |= MAV_MODE_FLAG_SAFETY_ARMED;
2171 commandResult = MAV_RESULT_ACCEPTED;
2173 case MAV_CMD_DO_SET_MODE:
2175 _mavBaseMode = (_mavBaseMode & MAV_MODE_FLAG_SAFETY_ARMED) |
static_cast<uint8_t
>(request.param1);
2176 _mavCustomMode =
static_cast<uint32_t
>(request.param2);
2177 commandResult = MAV_RESULT_ACCEPTED;
2179 case MAV_CMD_MISSION_START:
2180 commandResult = MAV_RESULT_ACCEPTED;
2182 case MAV_CMD_PREFLIGHT_CALIBRATION:
2183 _handlePreFlightCalibration(request);
2184 commandResult = MAV_RESULT_ACCEPTED;
2186 case MAV_CMD_DO_MOTOR_TEST:
2187 commandResult = MAV_RESULT_ACCEPTED;
2189 case MAV_CMD_DO_START_MAG_CAL:
2193 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
2194 if (_apmMagCalStartFailureMode) {
2195 commandResult = MAV_RESULT_FAILED;
2197 QMutexLocker locker(&_apmCompassCalMutex);
2198 _apmCompassCalProgress = 0;
2199 _apmCompassCalTickCount = 0;
2200 commandResult = MAV_RESULT_ACCEPTED;
2204 case MAV_CMD_DO_CANCEL_MAG_CAL:
2206 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
2207 QMutexLocker locker(&_apmCompassCalMutex);
2208 _apmStaleFailedMagCalReportStreaming =
false;
2209 _apmCompassCalProgress = -1;
2210 commandResult = MAV_RESULT_ACCEPTED;
2213 case MAV_CMD_CONTROL_HIGH_LATENCY:
2215 _highLatencyTransmissionEnabled =
static_cast<int>(request.param1) != 0;
2217 commandResult = MAV_RESULT_ACCEPTED;
2219 commandResult = MAV_RESULT_FAILED;
2222 case MAV_CMD_PREFLIGHT_STORAGE:
2223 if (
static_cast<int>(request.param1) == 2) {
2225 _resetParamsToDefaults();
2227 commandResult = MAV_RESULT_ACCEPTED;
2229 case MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN:
2233 commandResult = MAV_RESULT_ACCEPTED;
2235 case MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES:
2236 commandResult = MAV_RESULT_ACCEPTED;
2237 _respondWithAutopilotVersion();
2239 case MAV_CMD_REQUEST_MESSAGE:
2241 bool accepted =
false;
2243 _handleRequestMessage(request, accepted, noAck);
2249 commandResult = MAV_RESULT_ACCEPTED;
2253 case MAV_CMD_NAV_TAKEOFF:
2254 _handleTakeoff(request);
2255 commandResult = MAV_RESULT_ACCEPTED;
2259 commandResult = MAV_RESULT_ACCEPTED;
2263 commandResult = MAV_RESULT_FAILED;
2267 if (firstCmdUser3) {
2268 firstCmdUser3 =
false;
2271 firstCmdUser3 =
true;
2272 commandResult = MAV_RESULT_ACCEPTED;
2277 if (firstCmdUser4) {
2278 firstCmdUser4 =
false;
2281 firstCmdUser4 =
true;
2282 commandResult = MAV_RESULT_FAILED;
2292 _handleInProgressCommandLong(request);
2294 case MAV_CMD_SET_MESSAGE_INTERVAL:
2296 bool accepted =
false;
2298 _handleCommandLongSetMessageInterval(request, accepted);
2300 commandResult = MAV_RESULT_ACCEPTED;
2307 (void) mavlink_msg_command_ack_pack_chan(
2309 _vehicleComponentId,
2310 _outgoingMavlinkChannel,
2324 mavlink_command_int_t request{};
2325 mavlink_msg_command_int_decode(&msg, &request);
2330 uint8_t commandResult = MAV_RESULT_UNSUPPORTED;
2332 switch (request.command) {
2333 case MAV_CMD_DO_SET_ROI_LOCATION:
2335 commandResult = MAV_RESULT_ACCEPTED;
2337 case MAV_CMD_DO_SET_MODE:
2339 _mavBaseMode = (_mavBaseMode & MAV_MODE_FLAG_SAFETY_ARMED) |
static_cast<uint8_t
>(request.param1);
2340 _mavCustomMode =
static_cast<uint32_t
>(request.param2);
2341 commandResult = MAV_RESULT_ACCEPTED;
2343 case MAV_CMD_MISSION_START:
2344 commandResult = MAV_RESULT_ACCEPTED;
2351 (void) mavlink_msg_command_ack_pack_chan(
2353 _vehicleComponentId,
2354 _outgoingMavlinkChannel,
2369 (void) mavlink_msg_command_ack_pack_chan(
2371 _vehicleComponentId,
2372 _outgoingMavlinkChannel,
2384void MockLink::_respondWithAutopilotVersion()
2386 union FlightVersion {
2396 FlightVersion(uint32_t version = 0) : raw(version) {}
2398 FlightVersion flightVersion;
2400 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
2401 flightVersion.parts.major = 4;
2402 flightVersion.parts.minor = 7;
2403 flightVersion.parts.patch = 0;
2404 flightVersion.parts.type = FIRMWARE_VERSION_TYPE_OFFICIAL;
2405 }
else if (_firmwareType == MAV_AUTOPILOT_PX4) {
2406 flightVersion.parts.major = 1;
2407 flightVersion.parts.minor = 17;
2408 flightVersion.parts.patch = 0;
2409 flightVersion.parts.type = FIRMWARE_VERSION_TYPE_OFFICIAL;
2412 const uint8_t customVersion[8]{};
2413 const uint64_t capabilities = MAV_PROTOCOL_CAPABILITY_MAVLINK2 | MAV_PROTOCOL_CAPABILITY_MISSION_FENCE | MAV_PROTOCOL_CAPABILITY_MISSION_RALLY | MAV_PROTOCOL_CAPABILITY_MISSION_INT
2414 | MAV_PROTOCOL_CAPABILITY_COMMAND_INT
2415 | ((_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) ? MAV_PROTOCOL_CAPABILITY_TERRAIN : 0)
2416 | (_ftpCapability ? MAV_PROTOCOL_CAPABILITY_FTP : 0);
2419 (void) mavlink_msg_autopilot_version_pack_chan(
2421 _vehicleComponentId,
2422 _outgoingMavlinkChannel,
2429 reinterpret_cast<const uint8_t*
>(&customVersion),
2430 reinterpret_cast<const uint8_t*
>(&customVersion),
2431 reinterpret_cast<const uint8_t*
>(&customVersion),
2440void MockLink::_sendHomePosition()
2442 const float bogus[4]{};
2445 (void) mavlink_msg_home_position_pack_chan(
2447 _vehicleComponentId,
2448 _outgoingMavlinkChannel,
2450 static_cast<int32_t
>(_vehicleLatitude * 1E7),
2451 static_cast<int32_t
>(_vehicleLongitude * 1E7),
2452 static_cast<int32_t
>(_defaultVehicleHomeAltitude * 1000),
2461void MockLink::_sendGpsRawInt()
2463 static uint64_t timeTick = 0;
2466 (void) mavlink_msg_gps_raw_int_pack_chan(
2468 _vehicleComponentId,
2469 _outgoingMavlinkChannel,
2472 GPS_FIX_TYPE_3D_FIX,
2473 static_cast<int32_t
>(_vehicleLatitude * 1E7),
2474 static_cast<int32_t
>(_vehicleLongitude * 1E7),
2475 static_cast<int32_t
>(_vehicleAltitudeAMSL * 1000),
2492void MockLink::_sendGlobalPositionInt()
2494 static uint64_t timeTick = 0;
2497 (void) mavlink_msg_global_position_int_pack_chan(
2499 _vehicleComponentId,
2500 _outgoingMavlinkChannel,
2503 static_cast<int32_t
>(_vehicleLatitude * 1E7),
2504 static_cast<int32_t
>(_vehicleLongitude * 1E7),
2505 static_cast<int32_t
>(_vehicleAltitudeAMSL * 1000),
2506 static_cast<int32_t
>((_vehicleAltitudeAMSL - _defaultVehicleHomeAltitude) * 1000),
2513void MockLink::_sendAttitudeQuaternion()
2515 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
2516 const float t = timeBootMs / 1000.0f;
2518 AttitudeOverride
override;
2520 QMutexLocker locker(&_attitudeOverrideMutex);
2521 override = _attitudeOverride;
2523 const bool overridden =
override.enabled;
2526 const float roll = overridden ?
override.rollRad : 0.20f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.50f * t);
2527 const float pitch = overridden ?
override.pitchRad : 0.10f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.40f * t);
2528 const float yaw = overridden ?
override.yawRad : 0.30f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2531 const float cr = std::cos(roll / 2.0f), sr = std::sin(roll / 2.0f);
2532 const float cp = std::cos(pitch / 2.0f), sp = std::sin(pitch / 2.0f);
2533 const float cy = std::cos(yaw / 2.0f), sy = std::sin(yaw / 2.0f);
2534 const float q1 = cr * cp * cy + sr * sp * sy;
2535 const float q2 = sr * cp * cy - cr * sp * sy;
2536 const float q3 = cr * sp * cy + sr * cp * sy;
2537 const float q4 = cr * cp * sy - sr * sp * cy;
2540 const float rollspeed = overridden ? 0.0f : 0.20f * (2.0f *
static_cast<float>(M_PI) * 0.50f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.50f * t);
2541 const float pitchspeed = overridden ? 0.0f : 0.10f * (2.0f *
static_cast<float>(M_PI) * 0.40f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.40f * t);
2542 const float yawspeed = overridden ? 0.0f : 0.30f * (2.0f *
static_cast<float>(M_PI) * 0.10f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2544 const float reprOffsetQ[4] = {1.0f, 0.0f, 0.0f, 0.0f};
2547 (void) mavlink_msg_attitude_quaternion_pack_chan(
2549 _vehicleComponentId,
2550 _outgoingMavlinkChannel,
2554 rollspeed, pitchspeed, yawspeed,
2560void MockLink::_sendAttitudeTarget()
2563 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
2564 const float t = timeBootMs / 1000.0f;
2565 static constexpr float kPhase = 0.3f;
2567 const float roll = 0.20f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.50f * t + kPhase);
2568 const float pitch = 0.10f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.40f * t + kPhase);
2569 const float yaw = 0.30f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.10f * t + kPhase);
2571 const float cr = std::cos(roll / 2.0f), sr = std::sin(roll / 2.0f);
2572 const float cp = std::cos(pitch / 2.0f), sp = std::sin(pitch / 2.0f);
2573 const float cy = std::cos(yaw / 2.0f), sy = std::sin(yaw / 2.0f);
2574 const float qSp[4] = {
2575 cr * cp * cy + sr * sp * sy,
2576 sr * cp * cy - cr * sp * sy,
2577 cr * sp * cy + sr * cp * sy,
2578 cr * cp * sy - sr * sp * cy,
2581 const float bodyRollRate = 0.20f * (2.0f *
static_cast<float>(M_PI) * 0.50f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.50f * t + kPhase);
2582 const float bodyPitchRate = 0.10f * (2.0f *
static_cast<float>(M_PI) * 0.40f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.40f * t + kPhase);
2583 const float bodyYawRate = 0.30f * (2.0f *
static_cast<float>(M_PI) * 0.10f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.10f * t + kPhase);
2586 (void) mavlink_msg_attitude_target_pack_chan(
2588 _vehicleComponentId,
2589 _outgoingMavlinkChannel,
2594 bodyRollRate, bodyPitchRate, bodyYawRate,
2600void MockLink::_sendLocalPositionNed()
2602 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
2603 const float t = timeBootMs / 1000.0f;
2605 const float x = 5.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.08f * t);
2606 const float y = 5.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2607 const float z = -10.0f + 1.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.15f * t);
2608 const float vx = 5.0f * (2.0f *
static_cast<float>(M_PI) * 0.08f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.08f * t);
2609 const float vy = 5.0f * (2.0f *
static_cast<float>(M_PI) * 0.10f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2610 const float vz = 1.0f * (2.0f *
static_cast<float>(M_PI) * 0.15f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.15f * t);
2613 (void) mavlink_msg_local_position_ned_pack_chan(
2615 _vehicleComponentId,
2616 _outgoingMavlinkChannel,
2625void MockLink::_sendPositionTargetLocalNed()
2628 const uint32_t timeBootMs =
static_cast<uint32_t
>(_runningTime.elapsed());
2629 const float t = timeBootMs / 1000.0f + 0.5f;
2631 const float x = 5.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.08f * t);
2632 const float y = 5.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2633 const float z = -10.0f + 1.0f * std::sin(2.0f *
static_cast<float>(M_PI) * 0.15f * t);
2634 const float vx = 5.0f * (2.0f *
static_cast<float>(M_PI) * 0.08f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.08f * t);
2635 const float vy = 5.0f * (2.0f *
static_cast<float>(M_PI) * 0.10f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.10f * t);
2636 const float vz = 1.0f * (2.0f *
static_cast<float>(M_PI) * 0.15f) * std::cos(2.0f *
static_cast<float>(M_PI) * 0.15f * t);
2639 (void) mavlink_msg_position_target_local_ned_pack_chan(
2641 _vehicleComponentId,
2642 _outgoingMavlinkChannel,
2645 MAV_FRAME_LOCAL_NED,
2655void MockLink::_sendExtendedSysState()
2658 (void) mavlink_msg_extended_sys_state_pack_chan(
2660 _vehicleComponentId,
2661 _outgoingMavlinkChannel,
2663 MAV_VTOL_STATE_UNDEFINED,
2664 (_vehicleAltitudeAMSL > _defaultVehicleHomeAltitude) ? MAV_LANDED_STATE_IN_AIR : MAV_LANDED_STATE_ON_GROUND
2669void MockLink::_sendChunkedStatusText(uint16_t chunkId,
bool missingChunks)
2671 constexpr int cChunks = 4;
2674 for (
int i = 0; i < cChunks; i++) {
2675 if (missingChunks && (i & 1)) {
2679 int cBuf = MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN;
2680 char msgBuf[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN]{};
2682 if (i == cChunks - 1) {
2687 for (
int j = 0; j < cBuf - 1; j++) {
2688 msgBuf[j] =
'0' + num++;
2693 msgBuf[cBuf-1] =
'A' + i;
2696 (void) mavlink_msg_statustext_pack_chan(
2698 _vehicleComponentId,
2699 _outgoingMavlinkChannel,
2710void MockLink::_sendStatusTextMessages()
2712 struct StatusMessage {
2713 MAV_SEVERITY severity;
2717 static constexpr struct StatusMessage rgMessages[] = {
2718 { MAV_SEVERITY_INFO,
"#Testing audio output" },
2719 { MAV_SEVERITY_EMERGENCY,
"Status text emergency" },
2720 { MAV_SEVERITY_ALERT,
"Status text alert" },
2721 { MAV_SEVERITY_CRITICAL,
"Status text critical" },
2722 { MAV_SEVERITY_ERROR,
"Status text error" },
2723 { MAV_SEVERITY_WARNING,
"Status text warning" },
2724 { MAV_SEVERITY_NOTICE,
"Status text notice" },
2725 { MAV_SEVERITY_INFO,
"Status text info" },
2726 { MAV_SEVERITY_DEBUG,
"Status text debug" },
2730 for (
size_t i = 0; i < std::size(rgMessages); i++) {
2731 const struct StatusMessage *status = &rgMessages[i];
2732 char statusText[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN] = {};
2733 (void) std::strncpy(statusText, status->msg,
sizeof(statusText) - 1);
2735 (void) mavlink_msg_statustext_pack_chan(
2737 _vehicleComponentId,
2738 _outgoingMavlinkChannel,
2748 _sendChunkedStatusText(1,
false );
2749 _sendChunkedStatusText(2,
true );
2750 _sendChunkedStatusText(3,
false );
2751 _sendChunkedStatusText(4,
true );
2760 return qobject_cast<MockLink*>(
config->link());
2784 return _startMockLink(mockConfig);
2789 return _startMockLinkWorker(QStringLiteral(
"PX4 MultiRotor MockLink"), MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, options, failureMode, videoStreamType);
2799 return _startMockLinkWorker(QStringLiteral(
"Generic MockLink"), MAV_AUTOPILOT_GENERIC, MAV_TYPE_QUADROTOR, options, failureMode, videoStreamType);
2804 return _startMockLinkWorker(QStringLiteral(
"No Initial Connect MockLink"), MAV_AUTOPILOT_PX4, MAV_TYPE_GENERIC, options, failureMode);
2809 return _startMockLinkWorker(QStringLiteral(
"ArduCopter MockLink"),MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_QUADROTOR, options, failureMode, videoStreamType);
2814 return _startMockLinkWorker(QStringLiteral(
"ArduPlane MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_FIXED_WING, options, failureMode, videoStreamType);
2819 return _startMockLinkWorker(QStringLiteral(
"ArduSub MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_SUBMARINE, options, failureMode, videoStreamType);
2824 return _startMockLinkWorker(QStringLiteral(
"ArduRover MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_GROUND_ROVER, options, failureMode, videoStreamType);
2827void MockLink::_sendRCChannels()
2830 (void) mavlink_msg_rc_channels_pack_chan(
2832 _vehicleComponentId,
2833 _outgoingMavlinkChannel,
2837 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500,
2838 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500,
2839 UINT16_MAX, UINT16_MAX,
2847 if ((request.param1 == 0) && (request.param2 == 0) && (request.param3 == 0) &&
2848 (request.param4 == 0) && (request.param5 == 0) && (request.param6 == 0) &&
2849 (request.param7 == 0)) {
2851 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
2853 QMutexLocker locker(&_apmAccelCalMutex);
2854 if (_apmAccelCalPosIndex >= 0) {
2857 cmd.target_system = 255;
2858 cmd.target_component = MAV_COMP_ID_MISSIONPLANNER;
2859 cmd.command = MAV_CMD_ACCELCAL_VEHICLE_POS;
2860 cmd.param1 =
static_cast<float>(ACCELCAL_VEHICLE_POS_FAILED);
2861 (void) mavlink_msg_command_long_encode_chan(
2862 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &cmd);
2864 _apmAccelCalPosIndex = -1;
2868 (void) _mockLinkPX4Calibration->
cancel();
2873 if (request.param2 == 1) {
2879 if (request.param1 == 1) {
2884 if (request.param5 == 1) {
2885 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
2887 QMutexLocker locker(&_apmAccelCalMutex);
2888 _apmAccelCalPosIndex = 0;
2889 _apmAccelCalGotAck =
false;
2890 _apmAccelCalTickCount = 0;
2900 char statusText[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN] = {};
2901 (void) std::strncpy(statusText, text.toUtf8().constData(),
sizeof(statusText) - 1);
2904 (void) mavlink_msg_statustext_pack_chan(
2906 _vehicleComponentId,
2907 _outgoingMavlinkChannel,
2919 _vehicleAltitudeAMSL = request.param7 + _defaultVehicleHomeAltitude;
2920 _mavBaseMode |= MAV_MODE_FLAG_SAFETY_ARMED;
2925 mavlink_log_request_list_t request{};
2926 mavlink_msg_log_request_list_decode(&msg, &request);
2928 if ((request.start != 0) && (request.end != 0xffff)) {
2929 qCWarning(MockLinkLog) <<
"_handleLogRequestList cannot handle partial requests";
2935 const QList<MockLinkFTP::LogFile> logFiles = _mockLinkFTP->
logFiles();
2936 if (!_logsErased && !logFiles.isEmpty()) {
2937 const uint16_t numLogs =
static_cast<uint16_t
>(logFiles.count());
2938 for (uint16_t
id = 0;
id < numLogs;
id++) {
2940 (void) mavlink_msg_log_entry_pack_chan(
2942 _vehicleComponentId,
2943 _outgoingMavlinkChannel,
2949 static_cast<uint32_t
>(logFiles[
id].size)
2956 const uint16_t numLogs = _logsErased ? 0 : 1;
2957 const uint16_t logId = _logsErased ? 0 : _logDownloadLogId;
2958 const uint32_t logSize = _logsErased ? 0 : _logDownloadFileSize;
2960 mavlink_msg_log_entry_pack_chan(
2962 _vehicleComponentId,
2963 _outgoingMavlinkChannel,
2976 mavlink_log_erase_t request{};
2977 mavlink_msg_log_erase_decode(&msg, &request);
2979 if ((request.target_system != _vehicleSystemId) || (request.target_component != _vehicleComponentId)) {
2987QString MockLink::_createRandomFile(uint32_t byteCount)
2989 QTemporaryFile tempFile;
2990 tempFile.setAutoRemove(
false);
2991 if (!tempFile.open()) {
2992 qCWarning(MockLinkLog) <<
"MockLink::createRandomFile open failed" << tempFile.errorString();
2996 for (uint32_t bytesWritten = 0; bytesWritten < byteCount; bytesWritten++) {
2997 const unsigned char byte = (QRandomGenerator::global()->generate() * 0xFF) / RAND_MAX;
2998 (void) tempFile.write(
reinterpret_cast<const char*
>(&
byte), 1);
3002 return tempFile.fileName();
3005QString MockLink::_createLogContentsFile(
const QString &logName)
3007 QTemporaryFile tempFile;
3008 tempFile.setAutoRemove(
false);
3009 if (!tempFile.open()) {
3010 qCWarning(MockLinkLog) <<
"_createLogContentsFile open failed" << tempFile.errorString();
3015 return tempFile.fileName();
3020 mavlink_log_request_data_t request{};
3021 mavlink_msg_log_request_data_decode(&msg, &request);
3024 QMutexLocker locker(&_logDownloadMutex);
3026 const QList<MockLinkFTP::LogFile> logFiles = _logsErased ? QList<MockLinkFTP::LogFile>() : _mockLinkFTP->logFiles();
3027 if (!logFiles.isEmpty()) {
3029 if (request.id >= logFiles.count()) {
3030 qCWarning(MockLinkLog) <<
"_handleLogRequestData id out of range:" << request.id;
3033 if (_logDownloadFilename.isEmpty() || (_logDownloadId != request.id)) {
3034 _logDownloadFilename = _createLogContentsFile(logFiles[request.id].name);
3035 _logDownloadId = request.id;
3036 _logDownloadSize =
static_cast<uint32_t
>(logFiles[request.id].size);
3039#ifdef QGC_UNITTEST_BUILD
3040 if (_logDownloadFilename.isEmpty()) {
3041 _logDownloadFilename = _createRandomFile(_logDownloadFileSize);
3044 if (request.id != _logDownloadLogId) {
3045 qCWarning(MockLinkLog) <<
"_handleLogRequestData id must be" << _logDownloadLogId;
3048 _logDownloadId = _logDownloadLogId;
3049 _logDownloadSize = _logDownloadFileSize;
3052 if (request.ofs > (_logDownloadSize - 1)) {
3053 qCWarning(MockLinkLog) <<
"_handleLogRequestData offset past end of file request.ofs:size" << request.ofs << _logDownloadSize;
3058 _logDownloadCurrentOffset = request.ofs;
3059 if (request.ofs + request.count > _logDownloadSize) {
3060 request.count = _logDownloadSize - request.ofs;
3062 _logDownloadBytesRemaining = request.count;
3065void MockLink::_logDownloadWorker()
3069 QMutexLocker locker(&_logDownloadMutex);
3070 if (_logDownloadBytesRemaining == 0) {
3074 QFile file(_logDownloadFilename);
3075 if (!file.open(QIODevice::ReadOnly)) {
3076 qCWarning(MockLinkLog) <<
"_logDownloadWorker open failed" << file.errorString();
3077 _logDownloadBytesRemaining = 0;
3081 uint8_t buffer[MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN]{};
3083 const qint64 bytesToRead = qMin(_logDownloadBytesRemaining, (uint32_t)MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
3084 if (!file.seek(_logDownloadCurrentOffset)) {
3085 qCWarning(MockLinkLog) <<
"_logDownloadWorker seek failed - offset:" << _logDownloadCurrentOffset << file.errorString();
3086 _logDownloadBytesRemaining = 0;
3089 if (file.read(
reinterpret_cast<char*
>(buffer), bytesToRead) != bytesToRead) {
3090 qCWarning(MockLinkLog) <<
"_logDownloadWorker read failed - bytesToRead:" << bytesToRead << file.errorString();
3091 _logDownloadBytesRemaining = 0;
3095 qCDebug(MockLinkLog) <<
"_logDownloadWorker" << _logDownloadCurrentOffset << _logDownloadBytesRemaining;
3098 (void) mavlink_msg_log_data_pack_chan(
3100 _vehicleComponentId,
3101 _outgoingMavlinkChannel,
3104 _logDownloadCurrentOffset,
3110 _logDownloadCurrentOffset += bytesToRead;
3111 _logDownloadBytesRemaining -= bytesToRead;
3116void MockLink::_sendADSBVehicles()
3118 for (
int i = 0; i < _adsbVehicles.size(); ++i) {
3120 _adsbVehicles[i].angle += (i + 1);
3123 _adsbVehicles[i].coordinate = _adsbVehicles[i].coordinate.atDistanceAndAzimuth(5, _adsbVehicles[i].angle);
3126 _adsbVehicles[i].altitude += (i % 2 == 0 ? 0.5 : -0.5);
3128 QByteArray callsign = QString(
"N12345%1").arg(i, 2, 10, QChar(
'0')).toLatin1();
3129 callsign.resize(MAVLINK_MSG_ADSB_VEHICLE_FIELD_CALLSIGN_LEN);
3133 (void) mavlink_msg_adsb_vehicle_pack_chan(
3135 _vehicleComponentId,
3136 _outgoingMavlinkChannel,
3139 _adsbVehicles[i].
coordinate.latitude() * 1e7,
3140 _adsbVehicles[i].coordinate.longitude() * 1e7,
3141 ADSB_ALTITUDE_TYPE_GEOMETRIC,
3142 _adsbVehicles[i].altitude * 1000,
3144 static_cast<uint16_t
>(_adsbVehicles[i].angle * 100),
3146 callsign.constData(),
3147 ADSB_EMITTER_TYPE_ROTOCRAFT,
3149 ADSB_FLAGS_VALID_COORDS | ADSB_FLAGS_VALID_ALTITUDE | ADSB_FLAGS_VALID_HEADING | ADSB_FLAGS_VALID_CALLSIGN | ADSB_FLAGS_SIMULATED,
3156void MockLink::_moveADSBVehicle(
int vehicleIndex)
3158 _adsbAngles[vehicleIndex] += 10;
3159 QGeoCoordinate &coord = _adsbVehicleCoordinates[vehicleIndex];
3162 coord = QGeoCoordinate(coord.latitude(), coord.longitude()).atDistanceAndAzimuth(500, _adsbAngles[vehicleIndex]);
3163 coord.setAltitude(100);
3170 switch (_failureMode) {
3183 _respondWithAutopilotVersion();
3191 switch (_requestMessageFailureMode) {
3206 (void) mavlink_msg_debug_pack_chan(
3208 _vehicleComponentId,
3209 _outgoingMavlinkChannel,
3220 if (request.param2 == 0) {
3222 QMutexLocker locker(&_availableModesWorkerMutex);
3223 if (_availableModesWorkerNextModeIndex != 0) {
3224 qCWarning(MockLinkLog) <<
"MAVLINK_MSG_ID_AVAILABLE_MODES: _availableModesWorker already running - _availableModesWorkerNextModeIndex:" << _availableModesWorkerNextModeIndex;
3228 qCDebug(MockLinkLog) <<
"MAVLINK_MSG_ID_AVAILABLE_MODES: starting available modes sequence worker";
3229 _availableModesWorkerNextModeIndex = 1;
3232 if (request.param2 < 1 || request.param2 > _flightModeList().count()) {
3233 qCWarning(MockLinkLog) <<
"MAVLINK_MSG_ID_AVAILABLE_MODES: requested mode index out of range" << request.param2 << _flightModeList().count();
3237 qCDebug(MockLinkLog) <<
"MAVLINK_MSG_ID_AVAILABLE_MODES: received specific mode request for index" << request.param2;
3239 _sendAvailableMode(
static_cast<uint8_t
>(request.param2));
3248 const uint32_t requestedMessageId =
static_cast<uint32_t
>(request.param1);
3252 QMutexLocker locker(&_requestMessageNoResponseMutex);
3253 if (_requestMessageNoResponseIds.contains(requestedMessageId)) {
3259 switch (
static_cast<int>(request.param1)) {
3260 case MAVLINK_MSG_ID_AUTOPILOT_VERSION:
3261 _handleRequestMessageAutopilotVersion(request, accepted);
3263 case MAVLINK_MSG_ID_COMPONENT_METADATA:
3264 if (_firmwareType == MAV_AUTOPILOT_PX4) {
3265 _sendGeneralMetaData();
3269 case MAVLINK_MSG_ID_DEBUG:
3270 _handleRequestMessageDebug(request, accepted, noAck);
3272 case MAVLINK_MSG_ID_AVAILABLE_MODES:
3273 _handleRequestMessageAvailableModes(request, accepted);
3278void MockLink::_sendGeneralMetaData()
3280 static constexpr const char metaDataURI[MAVLINK_MSG_COMPONENT_METADATA_FIELD_URI_LEN] =
"mftp://[;comp=1]general.json";
3283 (void) mavlink_msg_component_metadata_pack_chan(
3285 _vehicleComponentId,
3286 _outgoingMavlinkChannel,
3297 QMutexLocker locker(&_remoteIDArmStatusMutex);
3298 _remoteIDArmStatus = status;
3299 _remoteIDArmStatusError =
error;
3302void MockLink::_sendRemoteIDArmStatus()
3305 QByteArray errorUtf8;
3307 QMutexLocker locker(&_remoteIDArmStatusMutex);
3308 armStatus = _remoteIDArmStatus;
3309 errorUtf8 = _remoteIDArmStatusError.toUtf8();
3312 char armStatusError[MAVLINK_MSG_OPEN_DRONE_ID_ARM_STATUS_FIELD_ERROR_LEN] = {};
3313 std::strncpy(armStatusError, errorUtf8.constData(),
sizeof(armStatusError) - 1);
3316 (void) mavlink_msg_open_drone_id_arm_status_pack_chan(
3318 MAV_COMP_ID_ODID_TXRX_1,
3319 static_cast<uint8_t
>(_outgoingMavlinkChannel),
3327void MockLink::_sendEscInfo()
3331 static const uint16_t failureFlags[4] = {0, 0, 0, 0};
3332 static const uint32_t errorCount[4] = {0, 0, 0, 0};
3333 static const int16_t temperature[4] = {3000, 3000, 3000, 3000};
3336 (void) mavlink_msg_esc_info_pack_chan(
3338 _vehicleComponentId,
3339 _outgoingMavlinkChannel,
3342 static_cast<uint64_t
>(_runningTime.elapsed()) * 1000,
3345 ESC_CONNECTION_TYPE_DSHOT,
3354void MockLink::_sendEscStatus()
3356 static const int32_t rpm[4] = {5000, 5000, 5000, 5000};
3357 static const float voltage[4] = {16.0f, 16.0f, 16.0f, 16.0f};
3358 static const float current[4] = {5.0f, 5.0f, 5.0f, 5.0f};
3361 (void) mavlink_msg_esc_status_pack_chan(
3363 _vehicleComponentId,
3364 _outgoingMavlinkChannel,
3367 static_cast<uint64_t
>(_runningTime.elapsed()) * 1000,
3375void MockLink::_sendRadioStatus()
3380 (void) mavlink_msg_radio_status_pack_chan(
3382 _vehicleComponentId,
3383 _outgoingMavlinkChannel,
3404 return _mockLinkFTP;
3409 QMutexLocker locker(&_attitudeOverrideMutex);
3410 _attitudeOverride.enabled =
true;
3411 _attitudeOverride.rollRad = qDegreesToRadians(rollDeg);
3412 _attitudeOverride.pitchRad = qDegreesToRadians(pitchDeg);
3413 _attitudeOverride.yawRad = qDegreesToRadians(yawDeg);
3418 QMutexLocker locker(&_attitudeOverrideMutex);
3419 _attitudeOverride.enabled =
false;
3422void MockLink::_sendAvailableMode(uint8_t modeIndexOneBased)
3424 if (modeIndexOneBased < 1 || modeIndexOneBased > _availableModesCount()) {
3425 qCWarning(MockLinkLog) <<
"modeIndexOneBased out of range" << modeIndexOneBased << _availableModesCount();
3429 qCDebug(MockLinkLog) <<
"_sendAvailableMode modeIndexOneBased:" << modeIndexOneBased;
3431 const FlightMode_t &availableMode = _flightModeList()[modeIndexOneBased - 1];
3432 char modeName[MAVLINK_MSG_AVAILABLE_MODES_FIELD_MODE_NAME_LEN] = {};
3433 std::strncpy(modeName, availableMode.name,
sizeof(modeName) - 1);
3437 (void) mavlink_msg_available_modes_pack_chan(
3439 _vehicleComponentId,
3440 _outgoingMavlinkChannel,
3442 _availableModesCount(),
3444 availableMode.standard_mode,
3445 availableMode.custom_mode,
3446 availableMode.canBeSet ? 0 : MAV_MODE_PROPERTY_NOT_USER_SELECTABLE,
3451void MockLink::_availableModesWorker()
3454 QMutexLocker locker(&_availableModesWorkerMutex);
3455 if (_availableModesWorkerNextModeIndex == 0) {
3460 _sendAvailableMode(_availableModesWorkerNextModeIndex);
3461 if (++_availableModesWorkerNextModeIndex > _availableModesCount()) {
3463 _availableModesWorkerNextModeIndex = 0;
3464 qCDebug(MockLinkLog) <<
"_availableModesWorker: all modes sent, stopping worker";
3468void MockLink::_sendAvailableModesMonitor()
3472 (void) mavlink_msg_available_modes_monitor_pack_chan(
3474 _vehicleComponentId,
3475 _outgoingMavlinkChannel,
3477 _availableModesMonitorSeqNumber);
3481int MockLink::_availableModesCount()
const
3483 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
3484 return _flightModeList().count();
3486 return _flightModeList().count() - (_availableModesMonitorSeqNumber == 0 ? 1 : 0);
3489const QList<MockLink::FlightMode_t> &MockLink::_flightModeList()
const
3491 if (_firmwareType != MAV_AUTOPILOT_ARDUPILOTMEGA) {
3492 return _availableFlightModes;
3495 switch (_vehicleType) {
3496 case MAV_TYPE_FIXED_WING:
3497 return _apmPlaneAvailableFlightModes;
3498 case MAV_TYPE_GROUND_ROVER:
3499 return _apmRoverAvailableFlightModes;
3500 case MAV_TYPE_SUBMARINE:
3501 return _apmSubAvailableFlightModes;
3503 return _apmCopterAvailableFlightModes;
3517 QMutexLocker locker(&_apmCompassCalMutex);
3518 _apmStaleFailedMagCalReportStreaming =
true;
3519 _apmCompassCalTickCount = 0;
3524 QMutexLocker locker(&_apmCompassCalMutex);
3525 return _apmStaleFailedMagCalReportStreaming;
3528void MockLink::_apmCompassCalWorker()
3530 if (_firmwareType != MAV_AUTOPILOT_ARDUPILOTMEGA) {
3534 QMutexLocker locker(&_apmCompassCalMutex);
3535 if ((_apmCompassCalProgress < 0) && !_apmStaleFailedMagCalReportStreaming) {
3540 if (++_apmCompassCalTickCount < 50) {
3543 _apmCompassCalTickCount = 0;
3545 if (_apmStaleFailedMagCalReportStreaming) {
3548 mavlink_mag_cal_report_t report{};
3549 report.compass_id = 0;
3550 report.cal_mask = 0x01;
3551 report.cal_status = MAG_CAL_FAILED;
3552 report.fitness = 999.0f;
3553 (void) mavlink_msg_mag_cal_report_encode_chan(
3554 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &report);
3559 const int pct = _apmCompassCalProgress;
3563 for (uint8_t
id = 0;
id < 3; ++id) {
3564 mavlink_mag_cal_progress_t progress{};
3565 progress.compass_id = id;
3566 progress.cal_mask = 0x07;
3567 progress.cal_status = MAG_CAL_RUNNING_STEP_ONE;
3568 progress.completion_pct =
static_cast<uint8_t
>(qMin(pct, 100));
3569 (void) mavlink_msg_mag_cal_progress_encode_chan(
3570 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &progress);
3576 for (uint8_t
id = 0;
id < 3; ++id) {
3577 mavlink_mag_cal_report_t report{};
3578 report.compass_id = id;
3579 report.cal_mask = 0x07;
3580 report.cal_status = MAG_CAL_SUCCESS;
3581 report.fitness = 0.5f;
3582 (void) mavlink_msg_mag_cal_report_encode_chan(
3583 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &report);
3590 (void) QMetaObject::invokeMethod(
this, [
this] {
3591 constexpr int compId = MAV_COMP_ID_AUTOPILOT1;
3592 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS_X")] = QVariant(10.0f);
3593 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS_Y")] = QVariant(10.0f);
3594 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS_Z")] = QVariant(10.0f);
3595 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS2_X")] = QVariant(10.0f);
3596 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS2_Y")] = QVariant(10.0f);
3597 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS2_Z")] = QVariant(10.0f);
3598 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS3_X")] = QVariant(10.0f);
3599 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS3_Y")] = QVariant(10.0f);
3600 _mapParamName2Value[compId][QStringLiteral(
"COMPASS_OFS3_Z")] = QVariant(10.0f);
3601 }, Qt::QueuedConnection);
3603 _apmCompassCalProgress = -1;
3605 _apmCompassCalProgress = qMin(pct + 5, 100);
3616void MockLink::_apmAccelCalWorker()
3618 if (_firmwareType != MAV_AUTOPILOT_ARDUPILOTMEGA) {
3622 QMutexLocker locker(&_apmAccelCalMutex);
3623 if (_apmAccelCalPosIndex < 0) {
3627 if (_apmAccelCalPosIndex == 6) {
3631 cmd.target_system = 255;
3632 cmd.target_component = MAV_COMP_ID_MISSIONPLANNER;
3633 cmd.command = MAV_CMD_ACCELCAL_VEHICLE_POS;
3634 cmd.param1 =
static_cast<float>(ACCELCAL_VEHICLE_POS_SUCCESS);
3635 (void) mavlink_msg_command_long_encode_chan(
3636 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &cmd);
3642 (void) QMetaObject::invokeMethod(
this, [
this] {
3643 constexpr int compId = MAV_COMP_ID_AUTOPILOT1;
3644 _mapParamName2Value[compId][QStringLiteral(
"INS_ACCOFFS_X")] = QVariant(0.1f);
3645 _mapParamName2Value[compId][QStringLiteral(
"INS_ACCOFFS_Y")] = QVariant(0.1f);
3646 _mapParamName2Value[compId][QStringLiteral(
"INS_ACCOFFS_Z")] = QVariant(0.1f);
3647 }, Qt::QueuedConnection);
3649 _apmAccelCalPosIndex = -1;
3654 if (!_apmAccelCalGotAck) {
3656 if (++_apmAccelCalTickCount < 50) {
3659 _apmAccelCalTickCount = 0;
3663 cmd.target_system = 255;
3664 cmd.target_component = MAV_COMP_ID_MISSIONPLANNER;
3665 cmd.command = MAV_CMD_ACCELCAL_VEHICLE_POS;
3666 cmd.param1 =
static_cast<float>(kAPMAccelCalPosSequence[_apmAccelCalPosIndex]);
3667 (void) mavlink_msg_command_long_encode_chan(
3668 _vehicleSystemId, _vehicleComponentId, _outgoingMavlinkChannel, &msg, &cmd);
3672 _apmAccelCalGotAck =
false;
3673 _apmAccelCalPosIndex++;
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
struct __mavlink_setup_signing_t mavlink_setup_signing_t
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct __mavlink_command_ack_t mavlink_command_ack_t
struct param_union mavlink_param_union_t
struct __mavlink_command_long_t mavlink_command_long_t
std::array< MsgOutKey, 4 > keys
void setDynamic(bool dynamic=true)
Set if this is this a dynamic configuration. (decided at runtime)
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
virtual void _freeMavlinkChannel()
void _connectionRemoved()
bool mavlinkChannelIsSet() const
virtual bool _allocateMavlinkChannel()
SharedLinkConfigurationPtr linkConfiguration()
SharedLinkConfigurationPtr addConfiguration(LinkConfiguration *config)
uint8_t allocateMavlinkChannel()
static LinkManager * instance()
void freeMavlinkChannel(uint8_t channel)
static constexpr uint8_t invalidMavlinkChannel()
static int getComponentId()
static MAVLinkProtocol * instance()
bool preloadMission() const
@ VideoStreamRtpUdpH265
RTP/UDP H.265 -> udp265://.
@ VideoStreamNone
No stream served.
@ VideoStreamRtpUdpH264
RTP/UDP H.264 -> udp://.
@ VideoStreamRtspH264
RTSP H.264 -> rtsp://.
@ VideoStreamMpegTsTcp
MPEG-TS over TCP -> tcp://.
@ VideoStreamMpegTsUdp
MPEG-TS over UDP -> mpegts://.
void setVehicleType(MAV_TYPE vehicleType)
@ FailMissingParamOnAllRequestsNonDefaultComponent
Adds a second (non-autopilot) param component which never sends one of its params.
@ FailInitialConnectRequestMessageAutopilotVersionLost
REQUEST_MESSAGE:AUTOPILOT_VERSION success, AUTOPILOT_VERSION never sent.
@ FailMissingParamOnInitialRequest
Not all params are sent on initial request, should still succeed since QGC will re-query missing para...
@ FailMissingParamOnAllRequests
Not all params are sent on initial request, QGC retries will fail as well.
@ FailNonDefaultComponentLossy
Second component streams only its first two params and drops the first read of every param.
@ FailMissingParamSharedIndexAcrossComponents
Autopilot never sends its param at index 1; second component skips its index 1 on initial request onl...
@ FailInitialConnectRequestMessageAutopilotVersionFailure
REQUEST_MESSAGE:AUTOPILOT_VERSION returns failure.
@ FailNonDefaultComponentDead
Second component streams only its first two params and never answers a read.
@ FailParamNoResponseToRequestList
Do not respond to PARAM_REQUEST_LIST.
void setFailureMode(FailureMode_t failureMode)
void setEnableProximity(bool enableProximity)
void setApmStartFreshParams(bool apmStartFreshParams)
void setEnableCamera(bool enableCamera)
void setEnableGimbal(bool enableGimbal)
void setSendStatusText(bool sendStatusText)
bool cameraHasVideoStream() const
void setFtpCapability(bool ftpCapability)
void setFirmwareType(MAV_AUTOPILOT firmwareType)
void setSendRadioStatus(bool sendRadioStatus)
@ OptionAPMStartFreshParams
void setVideoStreamType(int value)
void setPreloadMission(bool preloadMission)
void setStayMavlinkV1(bool stayV1)
Simulates MAVLink Camera Protocol v2 components for MockLink.
void sendCameraHeartbeats()
Send heartbeats for all simulated camera components (call from 1Hz tasks)
bool handleMavlinkMessage(const mavlink_message_t &msg)
void run10HzTasks()
Update camera states (call from 10Hz tasks)
Mock implementation of Mavlink FTP server.
QList< LogFile > logFiles() const
Returns the log files served from the @MAV_LOG virtual directory.
void setLogFiles(const QList< LogFile > &logFiles)
void mavlinkMessageReceived(const mavlink_message_t &message)
Called to handle an FTP message.
QByteArray logFileContents(const QString &name) const
Simulates MAVLink Gimbal Manager Protocol for MockLink.
void run1HzTasks()
Send periodic gimbal status messages (call from 1Hz tasks)
bool handleMavlinkMessage(const mavlink_message_t &msg)
bool handleMavlinkMessage(const mavlink_message_t &msg)
void loadSimpleMultirotorMission()
Simulates the PX4 commander magnetometer and accelerometer calibration protocols for MockLink.
void startMagCalibration()
void run10HzTasks()
Called by MockLink::run10HzTasks on the worker thread.
void startAccelCalibration()
Worker class that runs periodic tasks for MockLink simulation.
void servedVideoStream(MockConfiguration::VideoStreamType &type, QString &uri) const
void setVehicleAttitudeOverrideDeg(float rollDeg, float pitchDeg, float yawDeg)
@ FailRequestMessageCommandAcceptedMsgNotSent
@ FailRequestMessageCommandNoResponse
@ FailRequestMessageCommandUnsupported
void writeBytesQueuedSignal(const QByteArray &bytes)
void setMockParamValue(int componentId, const QString ¶mName, float value)
Change a float parameter value directly on MockLink (for testing cache invalidation)
void sendStatusTextMessage(uint8_t severity, const QString &text)
void clearVehicleAttitudeOverride()
static constexpr MAV_CMD MAV_CMD_MOCKLINK_ALWAYS_RESULT_ACCEPTED
static MockLink * startPX4MockLinkWithMission(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone)
@ FailParamSetParamError
Respond with PARAM_ERROR (VALUE_OUT_OF_RANGE) instead of PARAM_VALUE.
@ FailParamSetFirstAttemptNoAck
Skip ack on first attempt, respond to retry.
@ FailParamSetNoAck
Do not send PARAM_VALUE ack.
static constexpr MAV_CMD MAV_CMD_MOCKLINK_SECOND_ATTEMPT_RESULT_ACCEPTED
static MockLink * startGenericMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
static MockLink * startAPMArduCopterMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
MockLinkFTP * mockLinkFTP() const
static MockLink * startPX4MockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
static constexpr MAV_CMD MAV_CMD_MOCKLINK_ALWAYS_RESULT_FAILED
static MockLink * startNoInitialConnectMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone)
QVariant paramValue(int componentId, const QString ¶mName) const
static constexpr MAV_CMD MAV_CMD_MOCKLINK_SECOND_ATTEMPT_RESULT_FAILED
static MockLink * startAPMArduSubMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
static MockLink * startAPMArduPlaneMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
void respondWithMavlinkMessage(const mavlink_message_t &msg)
Sends the specified mavlink message to QGC.
static MockLink * startAPMArduRoverMockLink(MockConfiguration::Options options=MockConfiguration::OptionNone, MockConfiguration::FailureMode_t failureMode=MockConfiguration::FailNone, MockConfiguration::VideoStreamType videoStreamType=MockConfiguration::VideoStreamNone)
bool apmStaleFailedMagCalReportStreamingActive() const
Returns true while the stale failed MAG_CAL_REPORT stream is active.
static constexpr MAV_CMD MAV_CMD_MOCKLINK_NO_RESPONSE
void setRemoteIDArmStatus(uint8_t status, const QString &error)
MockLink(SharedLinkConfigurationPtr &config, QObject *parent=nullptr)
void setArmed(bool armed)
Set the armed state of the simulated vehicle.
static constexpr MAV_CMD MAV_CMD_MOCKLINK_RESULT_IN_PROGRESS_ACCEPTED
void highLatencyTransmissionEnabledChanged(bool highLatencyTransmissionEnabled)
static constexpr MAV_CMD MAV_CMD_MOCKLINK_RESULT_IN_PROGRESS_FAILED
void sendUnexpectedCommandAck(MAV_CMD command, MAV_RESULT ackResult)
void sendStatusTextMessages()
@ FailParamRequestReadFirstAttemptNoResponse
Skip response on first attempt, respond to retry.
@ FailParamRequestReadParamError
Respond with PARAM_ERROR (DOES_NOT_EXIST) instead of PARAM_VALUE.
@ FailParamRequestReadNoResponse
Do not respond to PARAM_REQUEST_READ.
static constexpr MAV_CMD MAV_CMD_MOCKLINK_RESULT_IN_PROGRESS_NO_ACK
static constexpr MAV_CMD MAV_CMD_MOCKLINK_NO_RESPONSE_NO_RETRY
void startAPMStaleFailedMagCalReportStreaming()
Q_INVOKABLE void simulateConnectionRemoved()
Serves a synthetic (videotestsrc) live video stream for MockLink so the GStreamer receive pipeline ca...
@ MpegTsTcp
MPEG-TS over TCP -> tcp://.
@ RtpUdpH265
RTP/H.265 over UDP -> udp265://.
@ RtpUdpH264
RTP/H.264 over UDP -> udp://.
@ RtspH264
RTSP (H.264) -> rtsp://.
@ MpegTsUdp
MPEG-TS over UDP -> mpegts://.
static FactMetaData::ValueType_t mavTypeToFactType(MAV_PARAM_TYPE mavType)
QByteArray line(std::string_view command)
command as one CR/LF-terminated line.
uint64_t currentSigningTimestampTicks()
Current signing timestamp in 10µs ticks since 2015-01-01.
bool insecureConnectionAcceptUnsignedCallback(const mavlink_status_t *status, uint32_t message_id)
std::optional< T > number(std::string_view field, int base=DECIMAL_BASE)
std::optional< double > coordinate(std::string_view field, std::string_view hemisphere, bool latitude)
quint32 crc32(const quint8 *src, unsigned len, unsigned state)
void secureZero(void *data, size_t size)
GPSCommandSequence port(QByteArrayView port, unsigned baud)
Clears the SBF output of port, keeps a serial port at the link's baud, and selects SBF and the WGS84 ...
@ PX4_CUSTOM_MAIN_MODE_AUTO
@ PX4_CUSTOM_SUB_MODE_AUTO_RESERVED_DO_NOT_USE