QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MockLink.cc
Go to the documentation of this file.
1#include "MockLink.h"
2#include "MAVLinkLib.h"
3#include "LinkManager.h"
4#include "MAVLinkProtocol.h"
5#include "MAVLinkSigning.h"
6#include "SecureMemory.h"
7#include "MockLinkCamera.h"
8#include "MockLinkFTP.h"
9#include "MockLinkGimbal.h"
10#include "MockLinkWorker.h"
11#include "QGCLoggingCategory.h"
12#include "FirmwarePlugin.h"
13#include "FactMetaData.h"
14#include "ParameterManager.h"
15#include "AppMessages.h"
16#include "QGCMath.h"
17
18#include <QtCore/QFile>
19#include <QtCore/QMutexLocker>
20#include <QtCore/QRandomGenerator>
21#include <QtCore/QTemporaryFile>
22#include <QtCore/QThread>
23#include <QtCore/QTimer>
24
25#include <cmath>
26#include <cstring>
27
28QGC_LOGGING_CATEGORY(MockLinkLog, "Comms.MockLink.MockLink")
29QGC_LOGGING_CATEGORY(MockLinkVerboseLog, "Comms.MockLink.MockLink:verbose")
30
31std::atomic<int> MockLink::_nextVehicleSystemId{128};
32
33QList<MockLink::FlightMode_t> MockLink::_availableFlightModes = {
34 // Mode Name Standard Mode Custom Mode CanBeSet adv
35 { "Manual", 0, PX4CustomMode::MANUAL, true, true },
36 { "Stabilized", 0, PX4CustomMode::STABILIZED, true, true },
37 { "Acro", 0, PX4CustomMode::ACRO, true, true },
38 { "Altitude", 0, PX4CustomMode::ALTCTL, true, false},
39 { "Offboard", 0, PX4CustomMode::OFFBOARD, true, true },
40 { "Position", 0, PX4CustomMode::POSCTL_POSCTL, true, false},
41 { "Orbit", 0, PX4CustomMode::POSCTL_ORBIT, false, true },
42 { "Hold", 0, PX4CustomMode::AUTO_LOITER, true, true },
43 { "Mission", 0, PX4CustomMode::AUTO_MISSION, true, true },
44 { "Return", 0, PX4CustomMode::AUTO_RTL, true, true },
45 { "Land", MAV_STANDARD_MODE_LAND, PX4CustomMode::AUTO_LAND, false, true },
46 { "Precision Landing", 0, PX4CustomMode::AUTO_PRECLAND, true, true },
47 { "Takeoff", MAV_STANDARD_MODE_TAKEOFF, PX4CustomMode::AUTO_TAKEOFF, false, false},
48 { "MockLink Mode", 0, PX4CustomMode::RATTITUDE, true, false},
49 { "(Mode not available)", 0, PX4CustomMode::AUTO_RTGS, false, false},
50 { "MockLink Mode (delayed)",0, PX4CustomMode::AUTO_FOLLOW_TARGET, true, false},
51};
52
54 : LinkInterface(config, parent)
55 , _mockConfig(qobject_cast<const MockConfiguration*>(_config.get()))
56 , _firmwareType(_mockConfig->firmwareType())
57 , _vehicleType(_mockConfig->vehicleType())
58 , _sendStatusText(_mockConfig->sendStatusText())
59 , _enableCamera(_mockConfig->enableCamera())
60 , _enableGimbal(_mockConfig->enableGimbal())
61 , _failureMode(_mockConfig->failureMode())
62 , _vehicleSystemId(_mockConfig->incrementVehicleId() ? _nextVehicleSystemId++ : static_cast<int>(_nextVehicleSystemId))
63 , _vehicleLatitude(_defaultVehicleLatitude + ((_vehicleSystemId - 128) * 0.0001))
64 , _vehicleLongitude(_defaultVehicleLongitude + ((_vehicleSystemId - 128) * 0.0001))
65 , _boardVendorId(_mockConfig->boardVendorId())
66 , _boardProductId(_mockConfig->boardProductId())
67 , _missionItemHandler(new MockLinkMissionItemHandler(this))
68 , _mockLinkCamera(_enableCamera ? new MockLinkCamera(this,
69 _mockConfig->cameraCaptureVideo(),
70 _mockConfig->cameraCaptureImage(),
71 _mockConfig->cameraHasModes(),
72 _mockConfig->cameraHasVideoStream(),
73 _mockConfig->cameraCanCaptureImageInVideoMode(),
74 _mockConfig->cameraCanCaptureVideoInImageMode(),
75 _mockConfig->cameraHasBasicZoom(),
76 _mockConfig->cameraHasTrackingPoint(),
77 _mockConfig->cameraHasTrackingRectangle())
78 : nullptr)
79 , _mockLinkGimbal(_enableGimbal ? new MockLinkGimbal(this,
80 _mockConfig->gimbalHasRollAxis(),
81 _mockConfig->gimbalHasPitchAxis(),
82 _mockConfig->gimbalHasYawAxis(),
83 _mockConfig->gimbalHasYawFollow(),
84 _mockConfig->gimbalHasYawLock(),
85 _mockConfig->gimbalHasRetract(),
86 _mockConfig->gimbalHasNeutral())
87 : nullptr)
88 , _mockLinkFTP(new MockLinkFTP(_vehicleSystemId, _vehicleComponentId, this))
89{
90 qCDebug(MockLinkLog) << this;
91
92 if (_mockConfig->startArmed()) {
93 setArmed(true);
94 }
95
96 // Initialize ADS-B vehicles with different starting conditions
97 _adsbVehicles.reserve(_numberOfVehicles);
98 for (int i = 0; i < _numberOfVehicles; ++i) {
99 ADSBVehicle vehicle{};
100 vehicle.angle = i * 72.0; // Different starting directions (angles 0, 72, 144, 216, 288)
101
102 // Set initial coordinates slightly offset from the default coordinates
103 const double latOffset = 0.001 * i;
104 const double lonOffset = 0.001 * (i % 2 == 0 ? i : -i);
105 vehicle.coordinate = QGeoCoordinate(_defaultVehicleLatitude + latOffset, _defaultVehicleLongitude + lonOffset);
106
107 // Set a unique starting altitude for each vehicle near the home altitude
108 vehicle.altitude = _defaultVehicleHomeAltitude + (i * 5);
109
110 _adsbVehicles.append(vehicle);
111 }
112
113 (void) QObject::connect(this, &MockLink::writeBytesQueuedSignal, this, &MockLink::_writeBytesQueued, Qt::QueuedConnection);
114
115 _loadParams();
116 _runningTime.start();
117
118 _workerThread = new QThread(this);
119 _workerThread->setObjectName(QStringLiteral("Mock_%1").arg(_mockConfig->name()));
120 _worker = new MockLinkWorker(this);
121 _worker->moveToThread(_workerThread);
122 (void) connect(_workerThread, &QThread::started, _worker, &MockLinkWorker::startWork);
123 (void) connect(_workerThread, &QThread::finished, _worker, &QObject::deleteLater);
124 _workerThread->start();
125}
126
128{
130
131 delete _mockLinkCamera;
132 delete _mockLinkGimbal;
133
134 if (!_logDownloadFilename.isEmpty()) {
135 QFile::remove(_logDownloadFilename);
136 }
137
138 if (_workerThread) {
139 _workerThread->quit();
140 _workerThread->wait();
141 }
142
143 qCDebug(MockLinkLog) << this;
144}
145
146bool MockLink::_connect()
147{
148 if (!_connected) {
149 _connected = true;
150 _disconnectedEmitted = false;
151 mavlink_status_t *const vehicleStatus = mavlink_get_channel_status(_getMavlinkVehicleChannel());
152 vehicleStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
153 mavlink_status_t *const auxStatus = mavlink_get_channel_status(_getMavlinkAuxChannel());
154 auxStatus->flags &= ~MAVLINK_STATUS_FLAG_OUT_MAVLINK1;
155 emit connected();
156 }
157
158 return true;
159}
160
162{
163 _missionItemHandler->shutdown();
164
165 // Stop worker thread first to prevent any more messages from being sent.
166 // This must happen before setting _connected = false to avoid race conditions
167 // where the worker checks _connected (true), then we set it false, then the
168 // worker continues sending messages to a disconnecting/destroyed vehicle.
169 if (_workerThread && _workerThread->isRunning()) {
170 _workerThread->quit();
171 _workerThread->wait();
172 }
173
174 // signing/streams pointers alias MockLink memory — clear before emit in case the disconnected signal frees us.
175 if (_mavlinkVehicleChannelIsSet()) {
176 mavlink_status_t* const vehicleStatus = mavlink_get_channel_status(_mavlinkVehicleChannel);
177 vehicleStatus->signing = nullptr;
178 vehicleStatus->signing_streams = nullptr;
179 mavlink_reset_channel_status(_mavlinkVehicleChannel);
180 }
181
182 if (_connected) {
183 _connected = false;
184 if (!_disconnectedEmitted.exchange(true)) {
185 emit disconnected();
186 }
187 }
188}
189
191{
192 if (!_mavlinkStarted || !_connected || !mavlinkChannelIsSet()) {
193 return;
194 }
195
196 if (linkConfiguration()->isHighLatency() && _highLatencyTransmissionEnabled) {
197 _sendHighLatency2();
198 return;
199 }
200
201 _sendVibration();
202 _sendBatteryStatus();
203 _sendNamedValueFloats();
204 _sendSysStatus();
205 _sendADSBVehicles();
206 if (_vehicleType != MAV_TYPE_SUBMARINE) {
207 _sendRemoteIDArmStatus();
208 }
209 _sendAvailableModesMonitor();
210
211 if (_enableGimbal) {
212 _mockLinkGimbal->run1HzTasks();
213 }
214
215 if (_enableCamera) {
216 _mockLinkCamera->sendCameraHeartbeats();
217 }
218
219 if (!QGC::runningUnitTests()) {
220 // Sending RC Channels during unit test breaks RC tests which does it's own RC simulation
221 _sendRCChannels();
222 }
223
224 if (_sendHomePositionDelayCount > 0) {
225 // We delay home position for better testing
226 _sendHomePositionDelayCount--;
227 } else {
228 _sendHomePosition();
229 // We piggy back on this delay to signal we have new standard modes available
230 if (_availableModesMonitorSeqNumber == 0) {
231 qCDebug(MockLinkLog) << "Bumping sequence number for available modes monitor to trigger requery of modes";
232 _availableModesMonitorSeqNumber = 1;
233 }
234 }
235}
236
238{
239 if (linkConfiguration()->isHighLatency()) {
240 return;
241 }
242
243 if (_mavlinkStarted && _connected && mavlinkChannelIsSet()) {
244 _sendHeartBeat();
245 if (_sendGPSPositionDelayCount > 0) {
246 // We delay gps position for better testing
247 _sendGPSPositionDelayCount--;
248 } else {
249 if (_vehicleType != MAV_TYPE_SUBMARINE) {
250 _sendGpsRawInt();
251 _sendGlobalPositionInt();
252 }
253 _sendExtendedSysState();
254 }
255
256 if (_enableCamera) {
257 _mockLinkCamera->run10HzTasks();
258 }
259 }
260}
261
263{
264 if (linkConfiguration()->isHighLatency()) {
265 return;
266 }
267
268 if (_mavlinkStarted && _connected && mavlinkChannelIsSet()) {
269 _paramRequestListWorker();
270 _logDownloadWorker();
271 _availableModesWorker();
272 }
273}
274
276{
277 _sendStatusTextMessages();
278}
279
280bool MockLink::_allocateMavlinkChannel()
281{
282 // should only be called by the LinkManager during setup
283 Q_ASSERT(!_mavlinkAuxChannelIsSet());
284 Q_ASSERT(!_mavlinkVehicleChannelIsSet());
285 Q_ASSERT(!mavlinkChannelIsSet());
286
288 qCWarning(MockLinkLog) << "LinkInterface::_allocateMavlinkChannel failed";
289 return false;
290 }
291
292 _mavlinkAuxChannel = LinkManager::instance()->allocateMavlinkChannel();
293 if (!_mavlinkAuxChannelIsSet()) {
294 qCWarning(MockLinkLog) << "_allocateMavlinkChannel aux failed";
296 return false;
297 }
298
299 _mavlinkVehicleChannel = LinkManager::instance()->allocateMavlinkChannel();
300 if (!_mavlinkVehicleChannelIsSet()) {
301 qCWarning(MockLinkLog) << "_allocateMavlinkChannel vehicle failed";
302 LinkManager::instance()->freeMavlinkChannel(_mavlinkAuxChannel);
304 return false;
305 }
306
307 qCDebug(MockLinkLog) << "_allocateMavlinkChannel aux:" << _mavlinkAuxChannel << "vehicle:" << _mavlinkVehicleChannel;
308 return true;
309}
310
311void MockLink::_freeMavlinkChannel()
312{
313 qCDebug(MockLinkLog) << "_freeMavlinkChannel aux:" << _mavlinkAuxChannel << "vehicle:" << _mavlinkVehicleChannel;
314 if (!_mavlinkAuxChannelIsSet()) {
315 Q_ASSERT(!_mavlinkVehicleChannelIsSet());
316 Q_ASSERT(!mavlinkChannelIsSet());
317 return;
318 }
319
320 if (_mavlinkVehicleChannelIsSet()) {
321 // Detach our _mockSigning before the channel is freed; the bytes back this struct in MockLink memory.
322 mavlink_reset_channel_status(_mavlinkVehicleChannel);
323 LinkManager::instance()->freeMavlinkChannel(_mavlinkVehicleChannel);
324 _mavlinkVehicleChannel = LinkManager::invalidMavlinkChannel();
325 }
326 mavlink_reset_channel_status(_mavlinkAuxChannel);
327 LinkManager::instance()->freeMavlinkChannel(_mavlinkAuxChannel);
328 _mavlinkAuxChannel = LinkManager::invalidMavlinkChannel();
330}
331
332bool MockLink::_mavlinkAuxChannelIsSet() const
333{
334 return (LinkManager::invalidMavlinkChannel() != _mavlinkAuxChannel);
335}
336
337bool MockLink::_mavlinkVehicleChannelIsSet() const
338{
339 return (LinkManager::invalidMavlinkChannel() != _mavlinkVehicleChannel);
340}
341
342void MockLink::_loadParams()
343{
344 QFile paramFile;
345 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
346 if (_vehicleType == MAV_TYPE_FIXED_WING) {
347 paramFile.setFileName(":/FirmwarePlugin/APM/Plane.OfflineEditing.params");
348 } else if (_vehicleType == MAV_TYPE_SUBMARINE ) {
349 paramFile.setFileName(":/FirmwarePlugin/APM/Sub.OfflineEditing.params");
350 } else if (_vehicleType == MAV_TYPE_GROUND_ROVER ) {
351 paramFile.setFileName(":/FirmwarePlugin/APM/Rover.OfflineEditing.params");
352 } else {
353 paramFile.setFileName(":/FirmwarePlugin/APM/Copter.OfflineEditing.params");
354 }
355 } else {
356 paramFile.setFileName(":/MockLink/PX4MockLink.params");
357 }
358
359 const bool success = paramFile.open(QFile::ReadOnly);
360 Q_UNUSED(success);
361 Q_ASSERT(success);
362
363 QTextStream paramStream(&paramFile);
364 while (!paramStream.atEnd()) {
365 const QString line = paramStream.readLine();
366
367 if (line.startsWith("#")) {
368 continue;
369 }
370
371 const QStringList paramData = line.split("\t");
372 Q_ASSERT(paramData.count() == 5);
373
374 const int compId = paramData.at(1).toInt();
375 const QString paramName = paramData.at(2);
376 const QString valStr = paramData.at(3);
377 const uint paramType = paramData.at(4).toUInt();
378
379 QVariant paramValue;
380 switch (paramType) {
381 case MAV_PARAM_TYPE_REAL32:
382 paramValue = QVariant(valStr.toFloat());
383 break;
384 case MAV_PARAM_TYPE_UINT32:
385 paramValue = QVariant(valStr.toUInt());
386 break;
387 case MAV_PARAM_TYPE_INT32:
388 paramValue = QVariant(valStr.toInt());
389 break;
390 case MAV_PARAM_TYPE_UINT16:
391 paramValue = QVariant((quint16)valStr.toUInt());
392 break;
393 case MAV_PARAM_TYPE_INT16:
394 paramValue = QVariant((qint16)valStr.toInt());
395 break;
396 case MAV_PARAM_TYPE_UINT8:
397 paramValue = QVariant((quint8)valStr.toUInt());
398 break;
399 case MAV_PARAM_TYPE_INT8:
400 paramValue = QVariant((qint8)valStr.toUInt());
401 break;
402 default:
403 qCCritical(MockLinkVerboseLog) << "Unknown type" << paramType;
404 paramValue = QVariant(valStr.toInt());
405 break;
406 }
407
408 qCDebug(MockLinkVerboseLog) << "Loading param" << paramName << paramValue;
409
410 _mapParamName2Value[compId][paramName] = paramValue;
411 _mapParamName2MavParamType[compId][paramName] = static_cast<MAV_PARAM_TYPE>(paramType);
412 }
413}
414
415void MockLink::_sendHeartBeat()
416{
417 mavlink_message_t msg{};
418 (void) mavlink_msg_heartbeat_pack_chan(
419 _vehicleSystemId,
420 _vehicleComponentId,
421 _getMavlinkVehicleChannel(),
422 &msg,
423 _vehicleType, // MAV_TYPE
424 _firmwareType, // MAV_AUTOPILOT
425 _mavBaseMode, // MAV_MODE
426 _mavCustomMode, // custom mode
427 _mavState // MAV_STATE
428 );
430}
431
432void MockLink::_sendHighLatency2()
433{
434 qCDebug(MockLinkLog) << "Sending" << _mavCustomMode;
435
436 union px4_custom_mode px4_cm{};
437 px4_cm.data = _mavCustomMode;
438
439 mavlink_message_t msg{};
440 (void) mavlink_msg_high_latency2_pack_chan(
441 _vehicleSystemId,
442 _vehicleComponentId,
443 _getMavlinkVehicleChannel(),
444 &msg,
445 0, // timestamp
446 _vehicleType, // MAV_TYPE
447 _firmwareType, // MAV_AUTOPILOT
448 px4_cm.custom_mode_hl, // custom_mode
449 static_cast<int32_t>(_vehicleLatitude * 1E7),
450 static_cast<int32_t>(_vehicleLongitude * 1E7),
451 static_cast<int16_t>(_vehicleAltitudeAMSL),
452 static_cast<int16_t>(_vehicleAltitudeAMSL), // target_altitude,
453 0, // heading
454 0, // target_heading
455 0, // target_distance
456 0, // throttle
457 0, // airspeed
458 0, // airspeed_sp
459 0, // groundspeed
460 0, // windspeed,
461 0, // wind_heading
462 UINT8_MAX, // eph not known
463 UINT8_MAX, // epv not known
464 0, // temperature_air
465 0, // climb_rate
466 -1, // battery, do not use?
467 0, // wp_num
468 0, // failure_flags
469 0, 0, 0 // custom0, custom1, custom2
470 );
472}
473
474void MockLink::_sendSysStatus()
475{
476 mavlink_message_t msg{};
477 (void) mavlink_msg_sys_status_pack_chan(
478 _vehicleSystemId,
479 _vehicleComponentId,
480 _getMavlinkVehicleChannel(),
481 &msg,
482 MAV_SYS_STATUS_SENSOR_GPS, // onboard_control_sensors_present
483 0, // onboard_control_sensors_enabled
484 0, // onboard_control_sensors_health
485 250, // load
486 4200 * 4, // voltage_battery
487 8000, // current_battery
488 _battery1PctRemaining, // battery_remaining
489 0,0,0,0,0,0,0,0,0
490 );
492}
493
494void MockLink::_sendBatteryStatus()
495{
496 if (_battery1PctRemaining > 1) {
497 _battery1PctRemaining = static_cast<int8_t>(100 - (_runningTime.elapsed() / 1000));
498 _battery1TimeRemaining = static_cast<double>(_batteryMaxTimeRemaining) * (static_cast<double>(_battery1PctRemaining) / 100.0);
499 if (_battery1PctRemaining > 50) {
500 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_OK;
501 } else if (_battery1PctRemaining > 30) {
502 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_LOW;
503 } else if (_battery1PctRemaining > 20) {
504 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_CRITICAL;
505 } else {
506 _battery1ChargeState = MAV_BATTERY_CHARGE_STATE_EMERGENCY;
507 }
508 }
509
510 if (_battery2PctRemaining > 1) {
511 _battery2PctRemaining = static_cast<int8_t>(100 - ((_runningTime.elapsed() / 1000) / 2));
512 _battery2TimeRemaining = static_cast<double>(_batteryMaxTimeRemaining) * (static_cast<double>(_battery2PctRemaining) / 100.0);
513 if (_battery2PctRemaining > 50) {
514 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_OK;
515 } else if (_battery2PctRemaining > 30) {
516 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_LOW;
517 } else if (_battery2PctRemaining > 20) {
518 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_CRITICAL;
519 } else {
520 _battery2ChargeState = MAV_BATTERY_CHARGE_STATE_EMERGENCY;
521 }
522 }
523
524 mavlink_message_t msg{};
525 uint16_t rgVoltages[10]{};
526 uint16_t rgVoltagesNone[10]{};
527 uint16_t rgVoltagesExtNone[4]{};
528
529 for (size_t i = 0; i < std::size(rgVoltages); i++) {
530 rgVoltages[i] = UINT16_MAX;
531 rgVoltagesNone[i] = UINT16_MAX;
532 }
533 rgVoltages[0] = rgVoltages[1] = rgVoltages[2] = 4200;
534
535 (void) mavlink_msg_battery_status_pack_chan(
536 _vehicleSystemId,
537 _vehicleComponentId,
538 _getMavlinkVehicleChannel(),
539 &msg,
540 1, // battery id
541 MAV_BATTERY_FUNCTION_ALL,
542 MAV_BATTERY_TYPE_LIPO,
543 2100, // temp cdegC
544 rgVoltages,
545 600, // battery cA
546 100, // current consumed mAh
547 -1, // energy consumed not supported
548 _battery1PctRemaining,
549 _battery1TimeRemaining,
550 _battery1ChargeState,
551 rgVoltagesExtNone,
552 0, // MAV_BATTERY_MODE
553 0 // MAV_BATTERY_FAULT
554 );
556
557 (void) mavlink_msg_battery_status_pack_chan(
558 _vehicleSystemId,
559 _vehicleComponentId,
560 _getMavlinkVehicleChannel(),
561 &msg,
562 2, // battery id
563 MAV_BATTERY_FUNCTION_ALL,
564 MAV_BATTERY_TYPE_LIPO,
565 INT16_MAX, // temp cdegC
566 rgVoltagesNone,
567 600, // battery cA
568 100, // current consumed mAh
569 -1, // energy consumed not supported
570 _battery2PctRemaining,
571 _battery2TimeRemaining,
572 _battery2ChargeState,
573 rgVoltagesExtNone,
574 0, // MAV_BATTERY_MODE
575 0 // MAV_BATTERY_FAULT
576 );
578}
579
580void MockLink::_sendNamedValueFloats()
581{
582 const uint32_t timeBootMs = static_cast<uint32_t>(_runningTime.elapsed());
583
584 // Send two named float values with varying data to exercise Inspector instance separation
585 const float sinVal = static_cast<float>(std::sin(static_cast<double>(timeBootMs) / 1000.0));
586 const float cosVal = static_cast<float>(std::cos(static_cast<double>(timeBootMs) / 1000.0));
587
588 mavlink_message_t msg{};
589 (void) mavlink_msg_named_value_float_pack_chan(
590 _vehicleSystemId,
591 _vehicleComponentId,
592 _getMavlinkVehicleChannel(),
593 &msg,
594 timeBootMs,
595 "sin_wave",
596 sinVal
597 );
599
600 (void) mavlink_msg_named_value_float_pack_chan(
601 _vehicleSystemId,
602 _vehicleComponentId,
603 _getMavlinkVehicleChannel(),
604 &msg,
605 timeBootMs,
606 "cos_wave",
607 cosVal
608 );
610}
611
612void MockLink::_sendVibration()
613{
614 mavlink_message_t msg{};
615 (void) mavlink_msg_vibration_pack_chan(
616 _vehicleSystemId,
617 _vehicleComponentId,
618 _getMavlinkVehicleChannel(),
619 &msg,
620 0, // time_usec
621 50.5, // vibration_x,
622 10.5, // vibration_y,
623 60.0, // vibration_z,
624 1, // clipping_0
625 2, // clipping_0
626 3 // clipping_0
627 );
629}
630
632{
633 if (!_commLost) {
634 uint8_t buffer[MAVLINK_MAX_PACKET_LEN]{};
635 const int cBuffer = mavlink_msg_to_send_buffer(buffer, &msg);
636 const QByteArray bytes(reinterpret_cast<char*>(buffer), cBuffer);
637 emit bytesReceived(this, bytes);
638 }
639}
640
641void MockLink::_writeBytes(const QByteArray &bytes)
642{
643 // This prevents the responses to mavlink messages from being sent until the _writeBytes returns.
644 emit writeBytesQueuedSignal(bytes);
645}
646
647void MockLink::_writeBytesQueued(const QByteArray &bytes)
648{
649 if (!_connected || !mavlinkChannelIsSet()) {
650 qCDebug(MockLinkLog) << "Dropping queued bytes on disconnected/uninitialized mock link";
651 return;
652 }
653
654 if (_inNSH) {
655 _handleIncomingNSHBytes(bytes.constData(), bytes.length());
656 return;
657 }
658
659 if (bytes.startsWith(QByteArrayLiteral("\r\r\r"))) {
660 _inNSH = true;
661 _handleIncomingNSHBytes(&bytes.constData()[3], bytes.length() - 3);
662 }
663
664 _handleIncomingMavlinkBytes(reinterpret_cast<const uint8_t*>(bytes.constData()), bytes.length());
665}
666
667void MockLink::_handleIncomingNSHBytes(const char *bytes, int cBytes)
668{
669 Q_UNUSED(cBytes);
670
671 // Drop back out of NSH
672 if ((cBytes == 4) && (bytes[0] == '\r') && (bytes[1] == '\r') && (bytes[2] == '\r')) {
673 _inNSH = false;
674 return;
675 }
676
677 if (cBytes > 0) {
678 qCDebug(MockLinkLog) << "NSH:" << bytes;
679#if 0
680 // MockLink not quite ready to handle this correctly yet
681 if (strncmp(bytes, "sh /etc/init.d/rc.usb\n", cBytes) == 0) {
682 // This is the mavlink start command
683 _mavlinkStarted = true;
684 }
685#endif
686 }
687}
688
689void MockLink::_handleIncomingMavlinkBytes(const uint8_t *bytes, int cBytes)
690{
691 mavlink_message_t msg{};
692 mavlink_status_t comm{};
693
694 QMutexLocker lock(&_mavlinkAuxMutex);
695 for (qint64 i = 0; i < cBytes; i++) {
696 const int parsed = mavlink_parse_char(_getMavlinkAuxChannel(), bytes[i], &msg, &comm);
697 if (!parsed) {
698 continue;
699 }
700 lock.unlock();
701 _handleIncomingMavlinkMsg(msg);
702 lock.relock();
703 }
704}
705
706void MockLink::_updateIncomingMessageCounts(const mavlink_message_t &msg)
707{
708 _receivedMavlinkMessageCountMap[msg.msgid]++;
709
710 // Update command-specific counts if this is a COMMAND_LONG message
711 if (msg.msgid == MAVLINK_MSG_ID_COMMAND_LONG) {
712 mavlink_command_long_t request{};
713 mavlink_msg_command_long_decode(&msg, &request);
714
715 _receivedMavCommandCountMap[static_cast<MAV_CMD>(request.command)]++;
716 _receivedMavCommandByCompCountMap[static_cast<MAV_CMD>(request.command)][request.target_component]++;
717
718 if (request.command == MAV_CMD_REQUEST_MESSAGE) {
719 _receivedRequestMessageCountMap[static_cast<uint32_t>(request.param1)]++;
720 _receivedRequestMessageByCompAndMsgCountMap[request.target_component][static_cast<int>(request.param1)]++;
721 }
722 }
723}
724
725void MockLink::_handleIncomingMavlinkMsg(const mavlink_message_t &msg)
726{
727 _updateIncomingMessageCounts(msg);
728
729 if (_missionItemHandler->handleMavlinkMessage(msg)) {
730 return;
731 }
732
733 if (_enableCamera && _mockLinkCamera->handleMavlinkMessage(msg)) {
734 return;
735 }
736
737 if (_enableGimbal && _mockLinkGimbal->handleMavlinkMessage(msg)) {
738 return;
739 }
740
741 switch (msg.msgid) {
742 case MAVLINK_MSG_ID_HEARTBEAT:
743 _handleHeartBeat(msg);
744 break;
745 case MAVLINK_MSG_ID_PARAM_REQUEST_LIST:
746 _handleParamRequestList(msg);
747 break;
748 case MAVLINK_MSG_ID_SET_MODE:
749 _handleSetMode(msg);
750 break;
751 case MAVLINK_MSG_ID_PARAM_SET:
752 _handleParamSet(msg);
753 break;
754 case MAVLINK_MSG_ID_PARAM_REQUEST_READ:
755 _handleParamRequestRead(msg);
756 break;
757 case MAVLINK_MSG_ID_FILE_TRANSFER_PROTOCOL:
758 _handleFTP(msg);
759 break;
760 case MAVLINK_MSG_ID_COMMAND_LONG:
761 _handleCommandLong(msg);
762 break;
763 case MAVLINK_MSG_ID_MANUAL_CONTROL:
764 _handleManualControl(msg);
765 break;
766 case MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE:
767 _handleRCChannelsOverride(msg);
768 break;
769 case MAVLINK_MSG_ID_LOG_REQUEST_LIST:
770 _handleLogRequestList(msg);
771 break;
772 case MAVLINK_MSG_ID_LOG_REQUEST_DATA:
773 _handleLogRequestData(msg);
774 break;
775 case MAVLINK_MSG_ID_PARAM_MAP_RC:
776 _handleParamMapRC(msg);
777 break;
778 case MAVLINK_MSG_ID_SETUP_SIGNING:
779 _handleSetupSigning(msg);
780 break;
781 default:
782 break;
783 }
784}
785
786void MockLink::_handleHeartBeat(const mavlink_message_t &msg)
787{
788 Q_UNUSED(msg);
789 qCDebug(MockLinkVerboseLog) << "Heartbeat";
790}
791
792void MockLink::_handleParamMapRC(const mavlink_message_t &msg)
793{
794 mavlink_param_map_rc_t paramMapRC{};
795 mavlink_msg_param_map_rc_decode(&msg, &paramMapRC);
796
797 const QString paramName(QString::fromLocal8Bit(paramMapRC.param_id, static_cast<int>(strnlen(paramMapRC.param_id, MAVLINK_MSG_PARAM_MAP_RC_FIELD_PARAM_ID_LEN))));
798
799 if (paramMapRC.param_index == -1) {
800 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);
801 } else if (paramMapRC.param_index == -2) {
802 qCDebug(MockLinkLog) << "MockLink - PARAM_MAP_RC: Clear tuningID" << paramMapRC.parameter_rc_channel_index;
803 } else {
804 qCWarning(MockLinkLog) << "MockLink - PARAM_MAP_RC: Unsupported param_index" << paramMapRC.param_index;
805 }
806}
807
808void MockLink::_handleSetupSigning(const mavlink_message_t &msg)
809{
810 mavlink_setup_signing_t setupSigning{};
811 mavlink_msg_setup_signing_decode(&msg, &setupSigning);
812
813 if (setupSigning.target_system != _vehicleSystemId) {
814 return;
815 }
816
817 // All-zero key = disable signing
818 bool allZeroKey = true;
819 for (const uint8_t byte : setupSigning.secret_key) {
820 if (byte != 0) {
821 allZeroKey = false;
822 break;
823 }
824 }
825
826 _signingEnabled = !allZeroKey;
827
828 // Write C-state directly; routing through SigningController would clobber its _keyHint mid-confirmation.
829 mavlink_status_t* const status = mavlink_get_channel_status(_getMavlinkVehicleChannel());
830 if (_signingEnabled) {
831 memcpy(_mockSigning.secret_key, setupSigning.secret_key, sizeof(_mockSigning.secret_key));
832 _mockSigning.link_id = _getMavlinkVehicleChannel();
833 _mockSigning.flags = MAVLINK_SIGNING_FLAG_SIGN_OUTGOING;
834 _mockSigning.timestamp = MAVLinkSigning::currentSigningTimestampTicks();
835 _mockSigning.accept_unsigned_callback = MAVLinkSigning::insecureConnectionAcceptUnsignedCallback;
836 status->signing = &_mockSigning;
837 status->signing_streams = &_mockSigningStreams;
838 } else {
839 QGC::secureZero(_mockSigning.secret_key, sizeof(_mockSigning.secret_key));
840 _mockSigning.accept_unsigned_callback = nullptr;
841 status->signing = nullptr;
842 status->signing_streams = nullptr;
843 }
844
845 qCDebug(MockLinkLog) << "Signing" << (_signingEnabled ? "enabled" : "disabled");
846}
847
848void MockLink::_handleSetMode(const mavlink_message_t &msg)
849{
850 mavlink_set_mode_t request{};
851 mavlink_msg_set_mode_decode(&msg, &request);
852
853 Q_ASSERT(request.target_system == _vehicleSystemId);
854
855 _mavBaseMode = request.base_mode;
856 _mavCustomMode = request.custom_mode;
857}
858
859void MockLink::_handleManualControl(const mavlink_message_t &msg)
860{
861 mavlink_manual_control_t manualControl{};
862 mavlink_msg_manual_control_decode(&msg, &manualControl);
863
864 // INT16_MAX means "axis invalid/not provided" per MAVLink spec
865 const auto axisStr = [](int16_t v) -> QString {
866 return (v == INT16_MAX) ? QStringLiteral("invalid") : QString::number(v);
867 };
868 // Extension fields are only valid when the corresponding enabled_extensions bit is set
869 const auto extStr = [](int16_t v, bool enabled) -> QString {
870 return enabled ? QString::number(v) : QStringLiteral("disabled");
871 };
872
873 const uint8_t ext = manualControl.enabled_extensions;
874
875 qCDebug(MockLinkVerboseLog).noquote()
876 << "MANUAL_CONTROL"
877 << "target:" << manualControl.target
878 << "x:" << axisStr(manualControl.x)
879 << "y:" << axisStr(manualControl.y)
880 << "z:" << axisStr(manualControl.z)
881 << "r:" << axisStr(manualControl.r)
882 << "buttons:" << QStringLiteral("0x%1").arg(manualControl.buttons, 4, 16, QLatin1Char('0'))
883 << "buttons2:" << QStringLiteral("0x%1").arg(manualControl.buttons2, 4, 16, QLatin1Char('0'))
884 << "enabled_extensions:" << QStringLiteral("0x%1").arg(ext, 2, 16, QLatin1Char('0'))
885 << "s(pitch):" << extStr(manualControl.s, ext & (1 << 0))
886 << "t(roll):" << extStr(manualControl.t, ext & (1 << 1))
887 << "aux1:" << extStr(manualControl.aux1, ext & (1 << 2))
888 << "aux2:" << extStr(manualControl.aux2, ext & (1 << 3))
889 << "aux3:" << extStr(manualControl.aux3, ext & (1 << 4))
890 << "aux4:" << extStr(manualControl.aux4, ext & (1 << 5))
891 << "aux5:" << extStr(manualControl.aux5, ext & (1 << 6))
892 << "aux6:" << extStr(manualControl.aux6, ext & (1 << 7));
893}
894
895void MockLink::_handleRCChannelsOverride(const mavlink_message_t &msg)
896{
897 mavlink_rc_channels_override_t override{};
898 mavlink_msg_rc_channels_override_decode(&msg, &override);
899
900 // Per the MAVLink spec:
901 // Channels 1-8: UINT16_MAX = ignore (no state change), 0 = release back to RC radio
902 // Channels 9-18: UINT16_MAX or 0 = ignore, UINT16_MAX-1 = release back to RC radio
903 const uint16_t rawValues[18] = {
904 override.chan1_raw, override.chan2_raw, override.chan3_raw, override.chan4_raw,
905 override.chan5_raw, override.chan6_raw, override.chan7_raw, override.chan8_raw,
906 override.chan9_raw, override.chan10_raw, override.chan11_raw, override.chan12_raw,
907 override.chan13_raw, override.chan14_raw, override.chan15_raw, override.chan16_raw,
908 override.chan17_raw, override.chan18_raw,
909 };
910
911 bool anyChange = false;
912 for (int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
913 const uint16_t raw = rawValues[i];
914 const bool isExtended = (i >= 8);
915
916 RCChannelOverride::State newState;
917 if (isExtended) {
918 if (raw == 0 || raw == UINT16_MAX) {
919 continue; // ignore — no change to this channel's state
920 } else if (raw == static_cast<uint16_t>(UINT16_MAX - 1)) {
921 newState = RCChannelOverride::State::Released;
922 } else {
923 newState = RCChannelOverride::State::Overridden;
924 }
925 } else {
926 if (raw == UINT16_MAX) {
927 continue; // ignore — no change to this channel's state
928 } else if (raw == 0) {
929 newState = RCChannelOverride::State::Released;
930 } else {
931 newState = RCChannelOverride::State::Overridden;
932 }
933 }
934
935 RCChannelOverride &ch = _rcChannelOverrides[i];
936 if (ch.state == newState) {
937 continue;
938 }
939
940 anyChange = true;
941
942 const auto stateLabel = [](RCChannelOverride::State s) -> const char * {
943 switch (s) {
944 case RCChannelOverride::State::Ignore: return "ignore";
945 case RCChannelOverride::State::Released: return "released";
946 case RCChannelOverride::State::Overridden: return "overridden";
947 }
948 return "unknown";
949 };
950 qCDebug(MockLinkLog).noquote() << QStringLiteral("RC_CHANNELS_OVERRIDE ch%1: %2 -> %3").arg(i + 1).arg(stateLabel(ch.state)).arg(stateLabel(newState));
951
952 ch.state = newState;
953 ch.value = (newState == RCChannelOverride::State::Overridden) ? raw : 0;
954 }
955
956 if (anyChange) {
957 QStringList active;
958 for (int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
959 if (_rcChannelOverrides[i].state == RCChannelOverride::State::Overridden) {
960 active << QStringLiteral("ch%1").arg(i + 1);
961 }
962 }
963 if (active.isEmpty()) {
964 qCDebug(MockLinkLog) << "RC_CHANNELS_OVERRIDE: no channels currently overridden";
965 } else {
966 qCDebug(MockLinkLog).noquote() << "RC_CHANNELS_OVERRIDE active overrides:" << active.join(QStringLiteral(", "));
967 }
968 }
969
970 for (int i = 0; i < kRcChannelOverrideChannelCount; ++i) {
971 if (_rcChannelOverrides[i].state == RCChannelOverride::State::Overridden) {
972 qCDebug(MockLinkVerboseLog).noquote() << QStringLiteral("RC_CHANNELS_OVERRIDE ch%1 value: %2").arg(i + 1).arg(_rcChannelOverrides[i].value);
973 }
974 }
975}
976
977void MockLink::_setParamFloatUnionIntoMap(int componentId, const QString &paramName, float paramFloat)
978{
979 Q_ASSERT(_mapParamName2Value.contains(componentId));
980 Q_ASSERT(_mapParamName2Value[componentId].contains(paramName));
981 Q_ASSERT(_mapParamName2MavParamType[componentId].contains(paramName));
982
983 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
984 QVariant paramVariant;
985 mavlink_param_union_t valueUnion{};
986 valueUnion.param_float = paramFloat;
987 switch (paramType) {
988 case MAV_PARAM_TYPE_REAL32:
989 paramVariant = QVariant::fromValue(valueUnion.param_float);
990 break;
991 case MAV_PARAM_TYPE_UINT32:
992 paramVariant = QVariant::fromValue(valueUnion.param_uint32);
993 break;
994 case MAV_PARAM_TYPE_INT32:
995 paramVariant = QVariant::fromValue(valueUnion.param_int32);
996 break;
997 case MAV_PARAM_TYPE_UINT16:
998 paramVariant = QVariant::fromValue(valueUnion.param_uint16);
999 break;
1000 case MAV_PARAM_TYPE_INT16:
1001 paramVariant = QVariant::fromValue(valueUnion.param_int16);
1002 break;
1003 case MAV_PARAM_TYPE_UINT8:
1004 paramVariant = QVariant::fromValue(valueUnion.param_uint8);
1005 break;
1006 case MAV_PARAM_TYPE_INT8:
1007 paramVariant = QVariant::fromValue(valueUnion.param_int8);
1008 break;
1009 default:
1010 qCCritical(MockLinkLog) << "Invalid parameter type" << paramType;
1011 paramVariant = QVariant::fromValue(valueUnion.param_int32);
1012 break;
1013 }
1014
1015 qCDebug(MockLinkLog) << "_setParamFloatUnionIntoMap" << paramName << paramVariant;
1016 _mapParamName2Value[componentId][paramName] = paramVariant;
1017}
1018
1019void MockLink::setMockParamValue(int componentId, const QString &paramName, float value)
1020{
1021 mavlink_param_union_t valueUnion{};
1022 valueUnion.param_float = value;
1023 _setParamFloatUnionIntoMap(componentId, paramName, valueUnion.param_float);
1024}
1025
1026float MockLink::_floatUnionForParam(int componentId, const QString &paramName)
1027{
1028 Q_ASSERT(_mapParamName2Value.contains(componentId));
1029 Q_ASSERT(_mapParamName2Value[componentId].contains(paramName));
1030 Q_ASSERT(_mapParamName2MavParamType[componentId].contains(paramName));
1031
1032 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
1033 const QVariant paramVar = _mapParamName2Value[componentId][paramName];
1034
1035 mavlink_param_union_t valueUnion{};
1036 switch (paramType) {
1037 case MAV_PARAM_TYPE_REAL32:
1038 valueUnion.param_float = paramVar.toFloat();
1039 break;
1040 case MAV_PARAM_TYPE_UINT32:
1041 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1042 valueUnion.param_float = paramVar.toUInt();
1043 } else {
1044 valueUnion.param_uint32 = paramVar.toUInt();
1045 }
1046 break;
1047 case MAV_PARAM_TYPE_INT32:
1048 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1049 valueUnion.param_float = paramVar.toInt();
1050 } else {
1051 valueUnion.param_int32 = paramVar.toInt();
1052 }
1053 break;
1054 case MAV_PARAM_TYPE_UINT16:
1055 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1056 valueUnion.param_float = paramVar.toUInt();
1057 } else {
1058 valueUnion.param_uint16 = paramVar.toUInt();
1059 }
1060 break;
1061 case MAV_PARAM_TYPE_INT16:
1062 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1063 valueUnion.param_float = paramVar.toInt();
1064 } else {
1065 valueUnion.param_int16 = paramVar.toInt();
1066 }
1067 break;
1068 case MAV_PARAM_TYPE_UINT8:
1069 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1070 valueUnion.param_float = paramVar.toUInt();
1071 } else {
1072 valueUnion.param_uint8 = paramVar.toUInt();
1073 }
1074 break;
1075 case MAV_PARAM_TYPE_INT8:
1076 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1077 valueUnion.param_float = (unsigned char)paramVar.toChar().toLatin1();
1078 } else {
1079 valueUnion.param_int8 = (unsigned char)paramVar.toChar().toLatin1();
1080 }
1081 break;
1082 default:
1083 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1084 valueUnion.param_float = paramVar.toInt();
1085 } else {
1086 valueUnion.param_int32 = paramVar.toInt();
1087 }
1088 qCCritical(MockLinkLog) << "Invalid parameter type" << paramType;
1089 }
1090
1091 return valueUnion.param_float;
1092}
1093
1094uint32_t MockLink::_computeParamHash(int componentId) const
1095{
1096 // Volatile parameters are excluded from the hash, matching PX4 firmware and ParameterManager::_tryCacheHashLoad
1097 static const QStringList volatileParams = {
1098 QStringLiteral("COM_FLIGHT_UUID"),
1099 QStringLiteral("EKF2_MAGBIAS_X"),
1100 QStringLiteral("EKF2_MAGBIAS_Y"),
1101 QStringLiteral("EKF2_MAGBIAS_Z"),
1102 QStringLiteral("EKF2_MAG_DECL"),
1103 QStringLiteral("LND_FLIGHT_T_HI"),
1104 QStringLiteral("LND_FLIGHT_T_LO"),
1105 QStringLiteral("SYS_RESTART_TYPE"),
1106 };
1107
1108 uint32_t crc = 0;
1109 const auto &params = _mapParamName2Value[componentId];
1110 for (auto it = params.constBegin(); it != params.constEnd(); ++it) {
1111 const QString &name = it.key();
1112 if (volatileParams.contains(name)) {
1113 continue;
1114 }
1115 const QVariant &value = it.value();
1116 const MAV_PARAM_TYPE mavType = _mapParamName2MavParamType[componentId][name];
1118
1119 crc = QGC::crc32(reinterpret_cast<const uint8_t *>(qPrintable(name)), name.length(), crc);
1120 crc = QGC::crc32(static_cast<const uint8_t *>(value.constData()), FactMetaData::typeToSize(factType), crc);
1121 }
1122 return crc;
1123}
1124
1125void MockLink::_handleParamRequestList(const mavlink_message_t &msg)
1126{
1128 return;
1129 }
1130
1131 mavlink_param_request_list_t request{};
1132 mavlink_msg_param_request_list_decode(&msg, &request);
1133
1134 Q_ASSERT(request.target_system == _vehicleSystemId);
1135 Q_ASSERT(request.target_component == MAV_COMP_ID_ALL);
1136
1137 // Cache component IDs and first component's param names to avoid repeated keys() calls in worker
1138 // Thread safety: Lock mutex before modifying shared state accessed by worker thread
1139 QMutexLocker locker(&_paramRequestListMutex);
1140 _paramRequestListComponentIds = _mapParamName2Value.keys();
1141 if (!_paramRequestListComponentIds.isEmpty()) {
1142 _paramRequestListParamNames = _mapParamName2Value[_paramRequestListComponentIds.first()].keys();
1143 }
1144
1145 // Start the worker routine
1146 _currentParamRequestListComponentIndex = 0;
1147 _currentParamRequestListParamIndex = 0;
1148 _paramRequestListHashCheckSent = false;
1149}
1150
1151void MockLink::_paramRequestListWorker()
1152{
1153 if (_currentParamRequestListComponentIndex == -1) {
1154 // Initial request complete
1155 return;
1156 }
1157
1158 // Thread safety: Lock mutex before accessing shared state modified by main thread
1159 QMutexLocker locker(&_paramRequestListMutex);
1160
1161 // Use cached lists instead of calling keys() on every iteration (500Hz)
1162 if (_currentParamRequestListComponentIndex >= _paramRequestListComponentIds.count()) {
1163 _currentParamRequestListComponentIndex = -1;
1164 return;
1165 }
1166
1167 const int componentId = _paramRequestListComponentIds.at(_currentParamRequestListComponentIndex);
1168 const int cParameters = _paramRequestListParamNames.count();
1169
1170 if (_currentParamRequestListParamIndex >= cParameters) {
1171 // All regular params sent — for PX4, append _HASH_CHECK as the last entry in the stream.
1172 // Uses param_count=0, param_index=-1 (same as standalone response) so ParameterManager
1173 // handles it via the _HASH_CHECK early-return path without affecting param count tracking.
1174 if (_firmwareType == MAV_AUTOPILOT_PX4 && !_paramRequestListHashCheckSent) {
1175 _paramRequestListHashCheckSent = true;
1176
1177 mavlink_param_union_t valueUnion{};
1178 valueUnion.type = MAV_PARAM_TYPE_UINT32;
1179 valueUnion.param_uint32 = _computeParamHash(componentId);
1180
1181 char paramId[MAVLINK_MSG_ID_PARAM_VALUE_LEN]{};
1182 (void) strncpy(paramId, "_HASH_CHECK", MAVLINK_MSG_ID_PARAM_VALUE_LEN);
1183
1184 qCDebug(MockLinkLog) << "Sending _HASH_CHECK in PARAM_REQUEST_LIST stream" << componentId << "hash:" << valueUnion.param_uint32;
1185
1186 mavlink_message_t responseMsg{};
1187 (void) mavlink_msg_param_value_pack_chan(
1188 _vehicleSystemId,
1189 componentId,
1190 _getMavlinkVehicleChannel(),
1191 &responseMsg,
1192 paramId,
1193 valueUnion.param_float,
1194 MAV_PARAM_TYPE_UINT32,
1195 0, // param_count: 0 to avoid affecting ParameterManager's count tracking
1196 -1 // param_index: -1 signals this is a virtual/out-of-band parameter
1197 );
1198 respondWithMavlinkMessage(responseMsg);
1199 return;
1200 }
1201
1202 // Move to next component
1203 if (++_currentParamRequestListComponentIndex >= _paramRequestListComponentIds.count()) {
1204 _currentParamRequestListComponentIndex = -1;
1205 _paramRequestListComponentIds.clear();
1206 _paramRequestListParamNames.clear();
1207 } else {
1208 // Cache param names for the new component
1209 _paramRequestListParamNames = _mapParamName2Value[_paramRequestListComponentIds.at(_currentParamRequestListComponentIndex)].keys();
1210 _currentParamRequestListParamIndex = 0;
1211 _paramRequestListHashCheckSent = false;
1212 }
1213 return;
1214 }
1215
1216 const QString &paramName = _paramRequestListParamNames.at(_currentParamRequestListParamIndex);
1217
1218 if (((_failureMode == MockConfiguration::FailMissingParamOnInitialRequest) || (_failureMode == MockConfiguration::FailMissingParamOnAllRequests)) && (paramName == _failParam)) {
1219 qCDebug(MockLinkLog) << "Skipping param send:" << paramName;
1220 } else {
1221 char paramId[MAVLINK_MSG_ID_PARAM_VALUE_LEN]{};
1222 mavlink_message_t responseMsg{};
1223
1224 Q_ASSERT(_mapParamName2Value[componentId].contains(paramName));
1225 Q_ASSERT(_mapParamName2MavParamType[componentId].contains(paramName));
1226
1227 const MAV_PARAM_TYPE paramType = _mapParamName2MavParamType[componentId][paramName];
1228
1229 Q_ASSERT(paramName.length() <= MAVLINK_MSG_ID_PARAM_VALUE_LEN);
1230 (void) strncpy(paramId, paramName.toLocal8Bit().constData(), MAVLINK_MSG_ID_PARAM_VALUE_LEN);
1231
1232 qCDebug(MockLinkLog) << "Sending msg_param_value" << componentId << paramId << paramType << _mapParamName2Value[componentId][paramId];
1233
1234 (void) mavlink_msg_param_value_pack_chan(
1235 _vehicleSystemId,
1236 componentId, // component id
1237 _getMavlinkVehicleChannel(),
1238 &responseMsg, // Outgoing message
1239 paramId, // Parameter name
1240 _floatUnionForParam(componentId, paramName), // Parameter value
1241 paramType, // MAV_PARAM_TYPE
1242 cParameters, // Total number of parameters
1243 _currentParamRequestListParamIndex // Index of this parameter
1244 );
1245 respondWithMavlinkMessage(responseMsg);
1246 }
1247
1248 // Move to next param index
1249 ++_currentParamRequestListParamIndex;
1250}
1251
1252void MockLink::_handleParamSet(const mavlink_message_t &msg)
1253{
1254 mavlink_param_set_t request{};
1255 mavlink_msg_param_set_decode(&msg, &request);
1256
1257 Q_ASSERT(request.target_system == _vehicleSystemId);
1258 const int componentId = request.target_component;
1259
1260 // Param may not be null terminated if exactly fits
1261 char paramId[MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN + 1]{};
1262 paramId[MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN] = 0;
1263 (void) strncpy(paramId, request.param_id, MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN);
1264
1265 qCDebug(MockLinkLog) << "_handleParamSet" << componentId << paramId << request.param_type;
1266
1267 // PX4 special case: _HASH_CHECK is a virtual parameter used by ParameterManager
1268 // to signal cache-hit and stop parameter streaming. It is intentionally not part
1269 // of the normal parameter maps.
1270 if ((_firmwareType == MAV_AUTOPILOT_PX4) && (strncmp(paramId, "_HASH_CHECK", MAVLINK_MSG_PARAM_SET_FIELD_PARAM_ID_LEN) == 0)) {
1271 QMutexLocker locker(&_paramRequestListMutex);
1272 _currentParamRequestListComponentIndex = -1;
1273 _paramRequestListComponentIds.clear();
1274 _paramRequestListParamNames.clear();
1275 qCDebug(MockLinkLog) << "Received _HASH_CHECK PARAM_SET, stopping parameter stream";
1276 return;
1277 }
1278
1279 Q_ASSERT(_mapParamName2Value.contains(componentId));
1280 Q_ASSERT(_mapParamName2MavParamType.contains(componentId));
1281 Q_ASSERT(_mapParamName2Value[componentId].contains(paramId));
1282 Q_ASSERT(request.param_type == _mapParamName2MavParamType[componentId][paramId]);
1283
1284 // Apply failure behaviors before committing change.
1285 if (_paramSetFailureMode == FailParamSetFirstAttemptNoAck && _paramSetFailureFirstAttemptPending) {
1286 qCDebug(MockLinkLog) << "Param set failure: first attempt no ack" << paramId;
1287 _paramSetFailureFirstAttemptPending = false;
1288 return;
1289 }
1290
1291 if (_paramSetFailureMode == FailParamSetNoAck) {
1292 qCDebug(MockLinkLog) << "Param set failure: no ack" << paramId;
1293 return;
1294 }
1295
1296 if (_paramSetFailureMode == FailParamSetParamError) {
1297 qCDebug(MockLinkLog) << "Param set failure: PARAM_ERROR" << paramId;
1298 _sendParamError(componentId, paramId,
1299 _mapParamName2Value[componentId].keys().indexOf(paramId),
1300 MAV_PARAM_ERROR_VALUE_OUT_OF_RANGE);
1301 return;
1302 }
1303
1304 // Normal success path
1305 _setParamFloatUnionIntoMap(componentId, paramId, request.param_value);
1306
1307 mavlink_message_t responseMsg;
1308 mavlink_msg_param_value_pack_chan(
1309 _vehicleSystemId,
1310 componentId, // component id
1311 _getMavlinkVehicleChannel(),
1312 &responseMsg, // Outgoing message
1313 paramId, // Parameter name
1314 request.param_value, // Send same value back
1315 request.param_type, // Send same type back
1316 _mapParamName2Value[componentId].count(), // Total number of parameters
1317 _mapParamName2Value[componentId].keys().indexOf(paramId) // Index of this parameter
1318 );
1319 respondWithMavlinkMessage(responseMsg);
1320}
1321
1322void MockLink::_handleParamRequestRead(const mavlink_message_t &msg)
1323{
1324 mavlink_message_t responseMsg{};
1325 mavlink_param_request_read_t request{};
1326 mavlink_msg_param_request_read_decode(&msg, &request);
1327
1328 const QString paramName(QString::fromLocal8Bit(request.param_id, static_cast<int>(strnlen(request.param_id, MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN))));
1329 const int componentId = request.target_component;
1330
1331 // special case for magic _HASH_CHECK value (PX4 only)
1332 if ((_firmwareType == MAV_AUTOPILOT_PX4) && (paramName == "_HASH_CHECK")) {
1333 _hashCheckRequestCount++;
1334 if (_hashCheckNoResponse) {
1335 return;
1336 }
1337
1338 const int hashComponentId = _mapParamName2Value.contains(MAV_COMP_ID_AUTOPILOT1)
1339 ? MAV_COMP_ID_AUTOPILOT1
1340 : _mapParamName2Value.keys().first();
1341
1342 mavlink_param_union_t valueUnion{};
1343 valueUnion.type = MAV_PARAM_TYPE_UINT32;
1344 valueUnion.param_uint32 = _computeParamHash(hashComponentId);
1345 (void) mavlink_msg_param_value_pack_chan(
1346 _vehicleSystemId,
1347 hashComponentId,
1348 _getMavlinkVehicleChannel(),
1349 &responseMsg,
1350 request.param_id,
1351 valueUnion.param_float,
1352 MAV_PARAM_TYPE_UINT32,
1353 0,
1354 -1
1355 );
1356 respondWithMavlinkMessage(responseMsg);
1357 return;
1358 }
1359
1360 Q_ASSERT(_mapParamName2Value.contains(componentId));
1361
1362 char paramId[MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN + 1]{};
1363 paramId[0] = 0;
1364
1365 Q_ASSERT(request.target_system == _vehicleSystemId);
1366
1367 if (request.param_index == -1) {
1368 // Request is by param name. Param may not be null terminated if exactly fits
1369 (void) strncpy(paramId, request.param_id, MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN);
1370 } else {
1371 // Request is by index
1372 Q_ASSERT(request.param_index >= 0 && request.param_index < _mapParamName2Value[componentId].count());
1373
1374 const QString key = _mapParamName2Value[componentId].keys().at(request.param_index);
1375 Q_ASSERT(key.length() <= MAVLINK_MSG_PARAM_REQUEST_READ_FIELD_PARAM_ID_LEN);
1376 strcpy(paramId, key.toLocal8Bit().constData());
1377 }
1378
1379 Q_ASSERT(_mapParamName2Value[componentId].contains(paramId));
1380 Q_ASSERT(_mapParamName2MavParamType[componentId].contains(paramId));
1381
1382 if ((_failureMode == MockConfiguration::FailMissingParamOnAllRequests) && (strcmp(paramId, _failParam) == 0)) {
1383 qCDebug(MockLinkLog) << "Ignoring request read for " << _failParam;
1384 // Fail to send this param no matter what
1385 return;
1386 }
1387
1388 if (_paramRequestReadFailureMode == FailParamRequestReadFirstAttemptNoResponse && _paramRequestReadFailureFirstAttemptPending) {
1389 qCDebug(MockLinkLog) << "Param request read failure: first attempt no response" << paramId;
1390 _paramRequestReadFailureFirstAttemptPending = false;
1391 return;
1392 }
1393
1394 if (_paramRequestReadFailureMode == FailParamRequestReadNoResponse) {
1395 qCDebug(MockLinkLog) << "Param request read failure: no response" << paramId;
1396 return;
1397 }
1398
1399 if (_paramRequestReadFailureMode == FailParamRequestReadParamError) {
1400 qCDebug(MockLinkLog) << "Param request read failure: PARAM_ERROR" << paramId;
1401 _sendParamError(componentId, paramId, request.param_index, MAV_PARAM_ERROR_DOES_NOT_EXIST);
1402 return;
1403 }
1404
1405 (void) mavlink_msg_param_value_pack_chan(
1406 _vehicleSystemId,
1407 componentId, // component id
1408 _getMavlinkVehicleChannel(),
1409 &responseMsg, // Outgoing message
1410 paramId, // Parameter name
1411 _floatUnionForParam(componentId, paramId), // Parameter value
1412 _mapParamName2MavParamType[componentId][paramId], // Parameter type
1413 _mapParamName2Value[componentId].count(), // Total number of parameters
1414 _mapParamName2Value[componentId].keys().indexOf(paramId) // Index of this parameter
1415 );
1416 respondWithMavlinkMessage(responseMsg);
1417}
1418
1419void MockLink::_sendParamError(int componentId, const char *paramId, int16_t paramIndex, uint8_t errorCode)
1420{
1421 mavlink_message_t responseMsg{};
1422 char paramIdBuf[MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN + 1] = {};
1423 (void) strncpy(paramIdBuf, paramId, MAVLINK_MSG_PARAM_ERROR_FIELD_PARAM_ID_LEN);
1424
1425 (void) mavlink_msg_param_error_pack_chan(
1426 _vehicleSystemId,
1427 static_cast<uint8_t>(componentId),
1429 &responseMsg,
1432 paramIdBuf,
1433 paramIndex,
1434 errorCode
1435 );
1436 respondWithMavlinkMessage(responseMsg);
1437}
1438
1439void MockLink::_handleFTP(const mavlink_message_t &msg)
1440{
1441 _mockLinkFTP->mavlinkMessageReceived(msg);
1442}
1443
1444void MockLink::_handleInProgressCommandLong(const mavlink_command_long_t &request)
1445{
1446 uint8_t commandResult = MAV_RESULT_UNSUPPORTED;
1447
1448 switch (request.command) {
1450 // Test command which sends in progress messages and then acceptance ack
1451 commandResult = MAV_RESULT_ACCEPTED;
1452 break;
1454 // Test command which sends in progress messages and then failure ack
1455 commandResult = MAV_RESULT_FAILED;
1456 break;
1458 // Test command which sends in progress messages and then never sends final result ack
1459 break;
1460 }
1461
1462 mavlink_message_t commandAck{};
1463 (void) mavlink_msg_command_ack_pack_chan(
1464 _vehicleSystemId,
1465 _vehicleComponentId,
1467 &commandAck,
1468 request.command,
1469 MAV_RESULT_IN_PROGRESS,
1470 1, // progress
1471 0, // result_param2
1472 0, // target_system
1473 0 // target_component
1474 );
1475 respondWithMavlinkMessage(commandAck);
1476
1478 (void) mavlink_msg_command_ack_pack_chan(
1479 _vehicleSystemId,
1480 _vehicleComponentId,
1482 &commandAck,
1483 request.command,
1484 commandResult,
1485 0, // progress
1486 0, // result_param2
1487 0, // target_system
1488 0 // target_component
1489 );
1490 respondWithMavlinkMessage(commandAck);
1491 }
1492}
1493
1494void MockLink::_handleCommandLongSetMessageInterval(const mavlink_command_long_t &request, bool &accepted)
1495{
1496 Q_UNUSED(request);
1497 accepted = false;
1498}
1499
1500void MockLink::_handleCommandLong(const mavlink_message_t &msg)
1501{
1502 static bool firstCmdUser3 = true;
1503 static bool firstCmdUser4 = true;
1504
1505 mavlink_command_long_t request{};
1506 mavlink_msg_command_long_decode(&msg, &request);
1507
1508 uint8_t commandResult = MAV_RESULT_UNSUPPORTED;
1509
1510 switch (request.command) {
1511 case MAV_CMD_COMPONENT_ARM_DISARM:
1512 if (request.param1 == 0.0f) {
1513 _mavBaseMode &= ~MAV_MODE_FLAG_SAFETY_ARMED;
1514 } else {
1515 _mavBaseMode |= MAV_MODE_FLAG_SAFETY_ARMED;
1516 }
1517 commandResult = MAV_RESULT_ACCEPTED;
1518 break;
1519 case MAV_CMD_PREFLIGHT_CALIBRATION:
1520 _handlePreFlightCalibration(request);
1521 commandResult = MAV_RESULT_ACCEPTED;
1522 break;
1523 case MAV_CMD_DO_MOTOR_TEST:
1524 commandResult = MAV_RESULT_ACCEPTED;
1525 break;
1526 case MAV_CMD_CONTROL_HIGH_LATENCY:
1527 if (linkConfiguration()->isHighLatency()) {
1528 _highLatencyTransmissionEnabled = static_cast<int>(request.param1) != 0;
1529 emit highLatencyTransmissionEnabledChanged(_highLatencyTransmissionEnabled);
1530 commandResult = MAV_RESULT_ACCEPTED;
1531 } else {
1532 commandResult = MAV_RESULT_FAILED;
1533 }
1534 break;
1535 case MAV_CMD_PREFLIGHT_STORAGE:
1536 commandResult = MAV_RESULT_ACCEPTED;
1537 break;
1538 case MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES:
1539 commandResult = MAV_RESULT_ACCEPTED;
1540 _respondWithAutopilotVersion();
1541 break;
1542 case MAV_CMD_REQUEST_MESSAGE:
1543 {
1544 bool accepted = false;
1545 bool noAck = false;
1546 _handleRequestMessage(request, accepted, noAck);
1547 if (noAck) {
1548 // FailRequestMessageCommandNoResponse: don't send any ack, let vehicle timeout
1549 return;
1550 }
1551 if (accepted) {
1552 commandResult = MAV_RESULT_ACCEPTED;
1553 }
1554 break;
1555 }
1556 case MAV_CMD_NAV_TAKEOFF:
1557 _handleTakeoff(request);
1558 commandResult = MAV_RESULT_ACCEPTED;
1559 break;
1561 // Test command which always returns MAV_RESULT_ACCEPTED
1562 commandResult = MAV_RESULT_ACCEPTED;
1563 break;
1565 // Test command which always returns MAV_RESULT_FAILED
1566 commandResult = MAV_RESULT_FAILED;
1567 break;
1569 // Test command which does not respond to first request and returns MAV_RESULT_ACCEPTED on second attempt
1570 if (firstCmdUser3) {
1571 firstCmdUser3 = false;
1572 return;
1573 } else {
1574 firstCmdUser3 = true;
1575 commandResult = MAV_RESULT_ACCEPTED;
1576 }
1577 break;
1579 // Test command which does not respond to first request and returns MAV_RESULT_FAILED on second attempt
1580 if (firstCmdUser4) {
1581 firstCmdUser4 = false;
1582 return;
1583 } else {
1584 firstCmdUser4 = true;
1585 commandResult = MAV_RESULT_FAILED;
1586 }
1587 break;
1590 // Test command which never responds
1591 return;
1595 _handleInProgressCommandLong(request);
1596 return;
1597 case MAV_CMD_SET_MESSAGE_INTERVAL:
1598 {
1599 bool accepted = false;
1600
1601 _handleCommandLongSetMessageInterval(request, accepted);
1602 if (accepted) {
1603 commandResult = MAV_RESULT_ACCEPTED;
1604 }
1605 break;
1606 }
1607 }
1608
1609 mavlink_message_t commandAck{};
1610 (void) mavlink_msg_command_ack_pack_chan(
1611 _vehicleSystemId,
1612 _vehicleComponentId,
1614 &commandAck,
1615 request.command,
1616 commandResult,
1617 0, // progress
1618 0, // result_param2
1619 0, // target_system
1620 0 // target_component
1621 );
1622 respondWithMavlinkMessage(commandAck);
1623}
1624
1625void MockLink::sendUnexpectedCommandAck(MAV_CMD command, MAV_RESULT ackResult)
1626{
1627 mavlink_message_t commandAck{};
1628 (void) mavlink_msg_command_ack_pack_chan(
1629 _vehicleSystemId,
1630 _vehicleComponentId,
1632 &commandAck,
1633 command,
1634 ackResult,
1635 0, // progress
1636 0, // result_param2
1637 0, // target_system
1638 0 // target_component
1639 );
1640 respondWithMavlinkMessage(commandAck);
1641}
1642
1643void MockLink::_respondWithAutopilotVersion()
1644{
1645 union FlightVersion {
1646 uint32_t raw;
1647
1648 struct {
1649 uint8_t type; // bits 0–7
1650 uint8_t patch; // bits 8–15
1651 uint8_t minor; // bits 16–23
1652 uint8_t major; // bits 24–31
1653 } parts;
1654
1655 FlightVersion(uint32_t version = 0) : raw(version) {}
1656 };
1657 FlightVersion flightVersion;
1658
1659#ifndef QGC_NO_ARDUPILOT_DIALECT
1660 if (_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) {
1661 flightVersion.parts.major = 4;
1662 flightVersion.parts.minor = 7;
1663 flightVersion.parts.patch = 0;
1664 flightVersion.parts.type = FIRMWARE_VERSION_TYPE_OFFICIAL;
1665 } else if (_firmwareType == MAV_AUTOPILOT_PX4) {
1666#endif
1667 flightVersion.parts.major = 1;
1668 flightVersion.parts.minor = 4;
1669 flightVersion.parts.patch = 1;
1670 flightVersion.parts.type = FIRMWARE_VERSION_TYPE_DEV;
1671#ifndef QGC_NO_ARDUPILOT_DIALECT
1672 }
1673#endif
1674
1675 const uint8_t customVersion[8]{};
1676 const uint64_t capabilities = MAV_PROTOCOL_CAPABILITY_MAVLINK2 | MAV_PROTOCOL_CAPABILITY_MISSION_FENCE | MAV_PROTOCOL_CAPABILITY_MISSION_RALLY | MAV_PROTOCOL_CAPABILITY_MISSION_INT | ((_firmwareType == MAV_AUTOPILOT_ARDUPILOTMEGA) ? MAV_PROTOCOL_CAPABILITY_TERRAIN : 0);
1677
1678 mavlink_message_t msg{};
1679 (void) mavlink_msg_autopilot_version_pack_chan(
1680 _vehicleSystemId,
1681 _vehicleComponentId,
1683 &msg,
1684 capabilities,
1685 flightVersion.raw, // flight_sw_version,
1686 0, // middleware_sw_version,
1687 0, // os_sw_version,
1688 0, // board_version,
1689 reinterpret_cast<const uint8_t*>(&customVersion), // flight_custom_version,
1690 reinterpret_cast<const uint8_t*>(&customVersion), // middleware_custom_version,
1691 reinterpret_cast<const uint8_t*>(&customVersion), // os_custom_version,
1692 _boardVendorId,
1693 _boardProductId,
1694 0, // uid
1695 0 // uid2
1696 );
1698}
1699
1700void MockLink::_sendHomePosition()
1701{
1702 const float bogus[4]{};
1703
1704 mavlink_message_t msg{};
1705 (void) mavlink_msg_home_position_pack_chan(
1706 _vehicleSystemId,
1707 _vehicleComponentId,
1709 &msg,
1710 static_cast<int32_t>(_vehicleLatitude * 1E7),
1711 static_cast<int32_t>(_vehicleLongitude * 1E7),
1712 static_cast<int32_t>(_defaultVehicleHomeAltitude * 1000),
1713 0.0f, 0.0f, 0.0f,
1714 &bogus[0],
1715 0.0f, 0.0f, 0.0f,
1716 0
1717 );
1719}
1720
1721void MockLink::_sendGpsRawInt()
1722{
1723 static uint64_t timeTick = 0;
1724
1725 mavlink_message_t msg{};
1726 (void) mavlink_msg_gps_raw_int_pack_chan(
1727 _vehicleSystemId,
1728 _vehicleComponentId,
1730 &msg,
1731 timeTick++, // time since boot
1732 GPS_FIX_TYPE_3D_FIX,
1733 static_cast<int32_t>(_vehicleLatitude * 1E7),
1734 static_cast<int32_t>(_vehicleLongitude * 1E7),
1735 static_cast<int32_t>(_vehicleAltitudeAMSL * 1000),
1736 3 * 100, // hdop
1737 3 * 100, // vdop
1738 UINT16_MAX, // velocity not known
1739 UINT16_MAX, // course over ground not known
1740 8, // satellites visible
1741 //-- Extension
1742 0, // Altitude (above WGS84, EGM96 ellipsoid), in meters * 1000 (positive for up).
1743 0, // Position uncertainty in meters * 1000 (positive for up).
1744 0, // Altitude uncertainty in meters * 1000 (positive for up).
1745 0, // Speed uncertainty in meters * 1000 (positive for up).
1746 0, // Heading / track uncertainty in degrees * 1e5.
1747 65535 // Yaw not provided
1748 );
1750}
1751
1752void MockLink::_sendGlobalPositionInt()
1753{
1754 static uint64_t timeTick = 0;
1755
1756 mavlink_message_t msg{};
1757 (void) mavlink_msg_global_position_int_pack_chan(
1758 _vehicleSystemId,
1759 _vehicleComponentId,
1761 &msg,
1762 timeTick++, // time since boot
1763 static_cast<int32_t>(_vehicleLatitude * 1E7),
1764 static_cast<int32_t>(_vehicleLongitude * 1E7),
1765 static_cast<int32_t>(_vehicleAltitudeAMSL * 1000),
1766 static_cast<int32_t>((_vehicleAltitudeAMSL - _defaultVehicleHomeAltitude) * 1000),
1767 0, 0, 0, // no speed sent
1768 UINT16_MAX // no heading sent
1769 );
1771}
1772
1773void MockLink::_sendExtendedSysState()
1774{
1775 mavlink_message_t msg{};
1776 (void) mavlink_msg_extended_sys_state_pack_chan(
1777 _vehicleSystemId,
1778 _vehicleComponentId,
1780 &msg,
1781 MAV_VTOL_STATE_UNDEFINED,
1782 (_vehicleAltitudeAMSL > _defaultVehicleHomeAltitude) ? MAV_LANDED_STATE_IN_AIR : MAV_LANDED_STATE_ON_GROUND
1783 );
1785}
1786
1787void MockLink::_sendChunkedStatusText(uint16_t chunkId, bool missingChunks)
1788{
1789 constexpr int cChunks = 4;
1790
1791 int num = 0;
1792 for (int i = 0; i < cChunks; i++) {
1793 if (missingChunks && (i & 1)) {
1794 continue;
1795 }
1796
1797 int cBuf = MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN;
1798 char msgBuf[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN]{};
1799
1800 if (i == cChunks - 1) {
1801 // Last chunk is partial
1802 cBuf /= 2;
1803 }
1804
1805 for (int j = 0; j < cBuf - 1; j++) {
1806 msgBuf[j] = '0' + num++;
1807 if (num > 9) {
1808 num = 0;
1809 }
1810 }
1811 msgBuf[cBuf-1] = 'A' + i;
1812
1813 mavlink_message_t msg{};
1814 (void) mavlink_msg_statustext_pack_chan(
1815 _vehicleSystemId,
1816 _vehicleComponentId,
1818 &msg,
1819 MAV_SEVERITY_INFO,
1820 msgBuf,
1821 chunkId,
1822 i // chunk sequence number
1823 );
1825 }
1826}
1827
1828void MockLink::_sendStatusTextMessages()
1829{
1830 struct StatusMessage {
1831 MAV_SEVERITY severity;
1832 const char *msg;
1833 };
1834
1835 static constexpr struct StatusMessage rgMessages[] = {
1836 { MAV_SEVERITY_INFO, "#Testing audio output" },
1837 { MAV_SEVERITY_EMERGENCY, "Status text emergency" },
1838 { MAV_SEVERITY_ALERT, "Status text alert" },
1839 { MAV_SEVERITY_CRITICAL, "Status text critical" },
1840 { MAV_SEVERITY_ERROR, "Status text error" },
1841 { MAV_SEVERITY_WARNING, "Status text warning" },
1842 { MAV_SEVERITY_NOTICE, "Status text notice" },
1843 { MAV_SEVERITY_INFO, "Status text info" },
1844 { MAV_SEVERITY_DEBUG, "Status text debug" },
1845 };
1846
1847 mavlink_message_t msg{};
1848 for (size_t i = 0; i < std::size(rgMessages); i++) {
1849 const struct StatusMessage *status = &rgMessages[i];
1850 char statusText[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN] = {};
1851 (void) std::strncpy(statusText, status->msg, sizeof(statusText) - 1);
1852
1853 (void) mavlink_msg_statustext_pack_chan(
1854 _vehicleSystemId,
1855 _vehicleComponentId,
1857 &msg,
1858 status->severity,
1859 statusText,
1860 0, // Not a chunked sequence
1861 0 // Not a chunked sequence
1862 );
1864 }
1865
1866 _sendChunkedStatusText(1, false /* missingChunks */);
1867 _sendChunkedStatusText(2, true /* missingChunks */);
1868 _sendChunkedStatusText(3, false /* missingChunks */); // This should cause the previous incomplete chunk to spit out
1869 _sendChunkedStatusText(4, true /* missingChunks */); // This should cause the timeout to fire
1870}
1871
1872MockLink *MockLink::_startMockLink(MockConfiguration *mockConfig)
1873{
1874 mockConfig->setDynamic(true);
1876
1877 if (LinkManager::instance()->createConnectedLink(config)) {
1878 return qobject_cast<MockLink*>(config->link());
1879 }
1880
1881 return nullptr;
1882}
1883
1884MockLink *MockLink::_startMockLinkWorker(const QString &configName, MAV_AUTOPILOT firmwareType, MAV_TYPE vehicleType, bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1885{
1886 MockConfiguration *const mockConfig = new MockConfiguration(configName);
1887
1888 mockConfig->setFirmwareType(firmwareType);
1889 mockConfig->setVehicleType(vehicleType);
1890 mockConfig->setSendStatusText(sendStatusText);
1891 mockConfig->setEnableCamera(enableCamera);
1892 mockConfig->setEnableGimbal(enableGimbal);
1893 mockConfig->setFailureMode(failureMode);
1894
1895 return _startMockLink(mockConfig);
1896}
1897
1898MockLink *MockLink::startPX4MockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1899{
1900 return _startMockLinkWorker(QStringLiteral("PX4 MultiRotor MockLink"), MAV_AUTOPILOT_PX4, MAV_TYPE_QUADROTOR, sendStatusText, enableCamera, enableGimbal, failureMode);
1901}
1902
1903MockLink *MockLink::startGenericMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1904{
1905 return _startMockLinkWorker(QStringLiteral("Generic MockLink"), MAV_AUTOPILOT_GENERIC, MAV_TYPE_QUADROTOR, sendStatusText, enableCamera, enableGimbal, failureMode);
1906}
1907
1908MockLink *MockLink::startNoInitialConnectMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1909{
1910 return _startMockLinkWorker(QStringLiteral("No Initial Connect MockLink"), MAV_AUTOPILOT_PX4, MAV_TYPE_GENERIC, sendStatusText, enableCamera, enableGimbal, failureMode);
1911}
1912
1913MockLink *MockLink::startAPMArduCopterMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1914{
1915 return _startMockLinkWorker(QStringLiteral("ArduCopter MockLink"),MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_QUADROTOR, sendStatusText, enableCamera, enableGimbal, failureMode);
1916}
1917
1918MockLink *MockLink::startAPMArduPlaneMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1919{
1920 return _startMockLinkWorker(QStringLiteral("ArduPlane MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_FIXED_WING, sendStatusText, enableCamera, enableGimbal, failureMode);
1921}
1922
1923MockLink *MockLink::startAPMArduSubMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1924{
1925 return _startMockLinkWorker(QStringLiteral("ArduSub MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_SUBMARINE, sendStatusText, enableCamera, enableGimbal, failureMode);
1926}
1927
1928MockLink *MockLink::startAPMArduRoverMockLink(bool sendStatusText, bool enableCamera, bool enableGimbal, MockConfiguration::FailureMode_t failureMode)
1929{
1930 return _startMockLinkWorker(QStringLiteral("ArduRover MockLink"), MAV_AUTOPILOT_ARDUPILOTMEGA, MAV_TYPE_GROUND_ROVER, sendStatusText, enableCamera, enableGimbal, failureMode);
1931}
1932
1933void MockLink::_sendRCChannels()
1934{
1935 mavlink_message_t msg{};
1936 (void) mavlink_msg_rc_channels_pack_chan(
1937 _vehicleSystemId,
1938 _vehicleComponentId,
1940 &msg,
1941 0, // time_boot_ms
1942 16, // chancount
1943 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, // channel 1-8
1944 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, // channel 9-16
1945 UINT16_MAX, UINT16_MAX, // channel 17/18 unused
1946 0 // rssi
1947 );
1949}
1950
1951void MockLink::_handlePreFlightCalibration(const mavlink_command_long_t& request)
1952{
1953 static constexpr const char *gyroCalResponse = "[cal] calibration started: 2 gyro";
1954 static constexpr const char *magCalResponse = "[cal] calibration started: 2 mag";
1955 static constexpr const char *accelCalResponse = "[cal] calibration started: 2 accel";
1956 const char *pCalMessage;
1957
1958 if (request.param1 == 1) {
1959 pCalMessage = gyroCalResponse;
1960 } else if (request.param2 == 1) {
1961 pCalMessage = magCalResponse;
1962 } else if (request.param5 == 1) {
1963 pCalMessage = accelCalResponse;
1964 } else {
1965 return;
1966 }
1967
1968 char statusText[MAVLINK_MSG_STATUSTEXT_FIELD_TEXT_LEN] = {};
1969 (void) std::strncpy(statusText, pCalMessage, sizeof(statusText) - 1);
1970
1971 mavlink_message_t msg{};
1972 (void) mavlink_msg_statustext_pack_chan(
1973 _vehicleSystemId,
1974 _vehicleComponentId,
1976 &msg,
1977 MAV_SEVERITY_INFO,
1978 statusText,
1979 0,
1980 0 // Not chunked
1981 );
1983}
1984
1985void MockLink::_handleTakeoff(const mavlink_command_long_t &request)
1986{
1987 _vehicleAltitudeAMSL = request.param7 + _defaultVehicleHomeAltitude;
1988 _mavBaseMode |= MAV_MODE_FLAG_SAFETY_ARMED;
1989}
1990
1991void MockLink::_handleLogRequestList(const mavlink_message_t &msg)
1992{
1993 mavlink_log_request_list_t request{};
1994 mavlink_msg_log_request_list_decode(&msg, &request);
1995
1996 if ((request.start != 0) && (request.end != 0xffff)) {
1997 qCWarning(MockLinkLog) << "_handleLogRequestList cannot handle partial requests";
1998 return;
1999 }
2000
2001 mavlink_message_t responseMsg{};
2002 mavlink_msg_log_entry_pack_chan(
2003 _vehicleSystemId,
2004 _vehicleComponentId,
2006 &responseMsg,
2007 _logDownloadLogId, // log id
2008 1, // num_logs
2009 1, // last_log_num
2010 0, // time_utc
2011 _logDownloadFileSize // size
2012 );
2013 respondWithMavlinkMessage(responseMsg);
2014}
2015
2016QString MockLink::_createRandomFile(uint32_t byteCount)
2017{
2018 QTemporaryFile tempFile;
2019 tempFile.setAutoRemove(false);
2020 if (!tempFile.open()) {
2021 qCWarning(MockLinkLog) << "MockLink::createRandomFile open failed" << tempFile.errorString();
2022 return QString();
2023 }
2024
2025 for (uint32_t bytesWritten = 0; bytesWritten < byteCount; bytesWritten++) {
2026 const unsigned char byte = (QRandomGenerator::global()->generate() * 0xFF) / RAND_MAX;
2027 (void) tempFile.write(reinterpret_cast<const char*>(&byte), 1);
2028 }
2029
2030 tempFile.close();
2031 return tempFile.fileName();
2032}
2033
2034void MockLink::_handleLogRequestData(const mavlink_message_t &msg)
2035{
2036 mavlink_log_request_data_t request{};
2037 mavlink_msg_log_request_data_decode(&msg, &request);
2038
2039#ifdef QGC_UNITTEST_BUILD
2040 if (_logDownloadFilename.isEmpty()) {
2041 _logDownloadFilename = _createRandomFile(_logDownloadFileSize);
2042 }
2043#endif
2044
2045 if (request.id != 0) {
2046 qCWarning(MockLinkLog) << "_handleLogRequestData id must be 0";
2047 return;
2048 }
2049
2050 if (request.ofs > (_logDownloadFileSize - 1)) {
2051 qCWarning(MockLinkLog) << "_handleLogRequestData offset past end of file request.ofs:size" << request.ofs << _logDownloadFileSize;
2052 return;
2053 }
2054
2055 // This will trigger _logDownloadWorker to send data
2056 // Thread-safe access: Main thread writes, worker thread reads every 2ms. Serialize to avoid
2057 // worker reading inconsistent offset/count or using stale values while downloading.
2058 QMutexLocker locker(&_logDownloadMutex);
2059 _logDownloadCurrentOffset = request.ofs;
2060 if (request.ofs + request.count > _logDownloadFileSize) {
2061 request.count = _logDownloadFileSize - request.ofs;
2062 }
2063 _logDownloadBytesRemaining = request.count;
2064}
2065
2066void MockLink::_logDownloadWorker()
2067{
2068 // Runs every 2ms (500Hz on worker thread). Must protect shared state modified by main thread.
2069 // Without lock: main could write new offset/count while we're reading, causing corrupted downloads.
2070 QMutexLocker locker(&_logDownloadMutex);
2071 if (_logDownloadBytesRemaining == 0) {
2072 return;
2073 }
2074
2075 QFile file(_logDownloadFilename);
2076 if (!file.open(QIODevice::ReadOnly)) {
2077 qCWarning(MockLinkLog) << "_logDownloadWorker open failed" << file.errorString();
2078 return;
2079 }
2080
2081 uint8_t buffer[MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN]{};
2082
2083 const qint64 bytesToRead = qMin(_logDownloadBytesRemaining, (uint32_t)MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
2084 Q_ASSERT(file.seek(_logDownloadCurrentOffset));
2085 Q_ASSERT(file.read(reinterpret_cast<char*>(buffer), bytesToRead) == bytesToRead);
2086
2087 qCDebug(MockLinkLog) << "_logDownloadWorker" << _logDownloadCurrentOffset << _logDownloadBytesRemaining;
2088
2089 mavlink_message_t responseMsg{};
2090 (void) mavlink_msg_log_data_pack_chan(
2091 _vehicleSystemId,
2092 _vehicleComponentId,
2094 &responseMsg,
2095 _logDownloadLogId,
2096 _logDownloadCurrentOffset,
2097 bytesToRead,
2098 &buffer[0]
2099 );
2100 respondWithMavlinkMessage(responseMsg);
2101
2102 _logDownloadCurrentOffset += bytesToRead;
2103 _logDownloadBytesRemaining -= bytesToRead;
2104
2105 file.close();
2106}
2107
2108void MockLink::_sendADSBVehicles()
2109{
2110 for (int i = 0; i < _adsbVehicles.size(); ++i) {
2111 // Slightly change the direction to simulate different paths
2112 _adsbVehicles[i].angle += (i + 1); // Vary the change to make each path unique
2113
2114 // Move each vehicle by a smaller distance to simulate slower speed
2115 _adsbVehicles[i].coordinate = _adsbVehicles[i].coordinate.atDistanceAndAzimuth(5, _adsbVehicles[i].angle); // 50 meters per update for slower speed
2116
2117 // Simulate slight variations in altitude
2118 _adsbVehicles[i].altitude += (i % 2 == 0 ? 0.5 : -0.5); // Increase or decrease altitude
2119
2120 QByteArray callsign = QString("N12345%1").arg(i, 2, 10, QChar('0')).toLatin1();
2121 callsign.resize(MAVLINK_MSG_ADSB_VEHICLE_FIELD_CALLSIGN_LEN);
2122
2123 // Prepare and send MAVLink message for each vehicle
2124 mavlink_message_t responseMsg{};
2125 (void) mavlink_msg_adsb_vehicle_pack_chan(
2126 _vehicleSystemId,
2127 _vehicleComponentId,
2129 &responseMsg,
2130 12345 + i, // Unique ICAO address for each vehicle
2131 _adsbVehicles[i].coordinate.latitude() * 1e7,
2132 _adsbVehicles[i].coordinate.longitude() * 1e7,
2133 ADSB_ALTITUDE_TYPE_GEOMETRIC,
2134 _adsbVehicles[i].altitude * 1000, // Altitude in millimeters
2135 // Use the current angle as heading
2136 static_cast<uint16_t>(_adsbVehicles[i].angle * 100), // Heading in centidegrees
2137 0, 0, // Horizontal/Vertical velocity
2138 callsign.constData(), // Unique callsign
2139 ADSB_EMITTER_TYPE_ROTOCRAFT,
2140 1, // Seconds since last communication
2141 ADSB_FLAGS_VALID_COORDS | ADSB_FLAGS_VALID_ALTITUDE | ADSB_FLAGS_VALID_HEADING | ADSB_FLAGS_VALID_CALLSIGN | ADSB_FLAGS_SIMULATED,
2142 0 // Squawk code
2143 );
2144 respondWithMavlinkMessage(responseMsg);
2145 }
2146}
2147
2148void MockLink::_moveADSBVehicle(int vehicleIndex)
2149{
2150 _adsbAngles[vehicleIndex] += 10; // Increment angle for smoother movement
2151 QGeoCoordinate &coord = _adsbVehicleCoordinates[vehicleIndex];
2152
2153 // Update the position based on the new angle
2154 coord = QGeoCoordinate(coord.latitude(), coord.longitude()).atDistanceAndAzimuth(500, _adsbAngles[vehicleIndex]);
2155 coord.setAltitude(100); // Keeping altitude constant for simplicity
2156}
2157
2158void MockLink::_handleRequestMessageAutopilotVersion(const mavlink_command_long_t &/*request*/, bool &accepted)
2159{
2160 accepted = true;
2161
2162 switch (_failureMode) {
2164 break;
2166 accepted = false;
2167 return;
2169 accepted = true;
2170 return;
2171 default:
2172 break;
2173 }
2174
2175 _respondWithAutopilotVersion();
2176}
2177
2178void MockLink::_handleRequestMessageDebug(const mavlink_command_long_t &/*request*/, bool &accepted, bool &noAck)
2179{
2180 accepted = true;
2181 noAck = false;
2182
2183 switch (_requestMessageFailureMode) {
2185 break;
2187 return;
2189 accepted = false;
2190 return;
2192 accepted = false;
2193 noAck = true;
2194 return;
2195 }
2196
2197 mavlink_message_t responseMsg{};
2198 (void) mavlink_msg_debug_pack_chan(
2199 _vehicleSystemId,
2200 _vehicleComponentId,
2202 &responseMsg,
2203 0, 0, 0
2204 );
2205 respondWithMavlinkMessage(responseMsg);
2206}
2207
2208void MockLink::_handleRequestMessageAvailableModes(const mavlink_command_long_t &request, bool &accepted)
2209{
2210 accepted = true;
2211
2212 // Thread-safe access: Check-then-set pattern must be atomic. Worker increments index every 2ms,
2213 // so check for "already running" and start/stop operations must serialize to prevent race where
2214 // main reads false, worker increments, main overwrites with different value -> lost update.
2215 QMutexLocker locker(&_availableModesWorkerMutex);
2216 if (request.param2 == 0) {
2217 // Request for available modes to be streamed out
2218 if (_availableModesWorkerNextModeIndex != 0) {
2219 qCWarning(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: _availableModesWorker already running - _availableModesWorkerNextModeIndex:" << _availableModesWorkerNextModeIndex;
2220 accepted = false;
2221 return;
2222 }
2223 qCDebug(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: starting available modes sequence worker";
2224 _availableModesWorkerNextModeIndex = 1; // Start with the first mode in sequence (1-based index)
2225 } else {
2226 // Request for specific mode
2227 if (request.param2 > _availableFlightModes.count()) {
2228 qCWarning(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: requested mode index out of range" << request.param2 << _availableFlightModes.count();
2229 accepted = false;
2230 return;
2231 }
2232 qCDebug(MockLinkLog) << "MAVLINK_MSG_ID_AVAILABLE_MODES: received specific mode request for index" << request.param2;
2233 _availableModesWorkerNextModeIndex = -request.param2; // Negative index indicates a specific single mode request
2234 }
2235}
2236
2237void MockLink::_handleRequestMessage(const mavlink_command_long_t &request, bool &accepted, bool &noAck)
2238{
2239 accepted = false;
2240 noAck = false;
2241
2242 const uint32_t requestedMessageId = static_cast<uint32_t>(request.param1);
2243
2244 // Per-message-ID no-response injection: silently drop the request (no ACK, no message)
2245 {
2246 QMutexLocker locker(&_requestMessageNoResponseMutex);
2247 if (_requestMessageNoResponseIds.contains(requestedMessageId)) {
2248 noAck = true;
2249 return;
2250 }
2251 }
2252
2253 switch (static_cast<int>(request.param1)) {
2254 case MAVLINK_MSG_ID_AUTOPILOT_VERSION:
2255 _handleRequestMessageAutopilotVersion(request, accepted);
2256 break;
2257 case MAVLINK_MSG_ID_COMPONENT_METADATA:
2258 if (_firmwareType == MAV_AUTOPILOT_PX4) {
2259 _sendGeneralMetaData();
2260 accepted = true;
2261 }
2262 break;
2263 case MAVLINK_MSG_ID_DEBUG:
2264 _handleRequestMessageDebug(request, accepted, noAck);
2265 break;
2266 case MAVLINK_MSG_ID_AVAILABLE_MODES:
2267 _handleRequestMessageAvailableModes(request, accepted);
2268 break;
2269 }
2270}
2271
2272void MockLink::_sendGeneralMetaData()
2273{
2274 static constexpr const char metaDataURI[MAVLINK_MSG_COMPONENT_METADATA_FIELD_URI_LEN] = "mftp://[;comp=1]general.json";
2275
2276 mavlink_message_t responseMsg{};
2277 (void) mavlink_msg_component_metadata_pack_chan(
2278 _vehicleSystemId,
2279 _vehicleComponentId,
2281 &responseMsg,
2282 0, // time_boot_ms
2283 100, // general_metadata_file_crc
2284 metaDataURI
2285 );
2286 respondWithMavlinkMessage(responseMsg);
2287}
2288
2289void MockLink::_sendRemoteIDArmStatus()
2290{
2291 char armStatusError[MAVLINK_MSG_OPEN_DRONE_ID_ARM_STATUS_FIELD_ERROR_LEN] = {};
2292 std::strncpy(armStatusError, "No Error", sizeof(armStatusError) - 1);
2293
2294 mavlink_message_t msg{};
2295 (void) mavlink_msg_open_drone_id_arm_status_pack(
2296 _vehicleSystemId,
2297 MAV_COMP_ID_ODID_TXRX_1,
2298 &msg,
2299 MAV_ODID_ARM_STATUS_GOOD_TO_ARM,
2300 armStatusError
2301 );
2303}
2304
2306{
2307 _commLost = true;
2309}
2310
2312{
2313 return _mockLinkFTP;
2314}
2315
2316void MockLink::_sendAvailableMode(uint8_t modeIndexOneBased)
2317{
2318 if (modeIndexOneBased > _availableModesCount()) {
2319 qCWarning(MockLinkLog) << "modeIndexOneBased out of range" << modeIndexOneBased << _availableModesCount();
2320 return;
2321 }
2322
2323 qCDebug(MockLinkLog) << "_sendAvailableMode modeIndexOneBased:" << modeIndexOneBased;
2324
2325 const FlightMode_t &availableMode = _availableFlightModes[modeIndexOneBased - 1];
2326 char modeName[MAVLINK_MSG_AVAILABLE_MODES_FIELD_MODE_NAME_LEN] = {};
2327 std::strncpy(modeName, availableMode.name, sizeof(modeName) - 1);
2328
2329 mavlink_message_t msg{};
2330
2331 (void) mavlink_msg_available_modes_pack_chan(
2332 _vehicleSystemId,
2333 _vehicleComponentId,
2335 &msg,
2336 _availableModesCount(),
2337 modeIndexOneBased,
2338 availableMode.standard_mode,
2339 availableMode.custom_mode,
2340 availableMode.canBeSet ? 0 : MAV_MODE_PROPERTY_NOT_USER_SELECTABLE,
2341 modeName);
2343}
2344
2345void MockLink::_availableModesWorker()
2346{
2347 // Runs every 2ms (500Hz on worker thread). Reads and increments shared index modified by main.
2348 // Read-modify-write must be atomic to prevent lost updates or incorrect state transitions.
2349 QMutexLocker locker(&_availableModesWorkerMutex);
2350 if (_availableModesWorkerNextModeIndex == 0) {
2351 // Not active
2352 return;
2353 }
2354
2355 _sendAvailableMode(qAbs(_availableModesWorkerNextModeIndex));
2356
2357 if (_availableModesWorkerNextModeIndex < 0) {
2358 // Single mode request, stop worker
2359 _availableModesWorkerNextModeIndex = 0;
2360 } else if (++_availableModesWorkerNextModeIndex > _availableModesCount()) {
2361 // All modes sent, stop worker
2362 _availableModesWorkerNextModeIndex = 0;
2363 qCDebug(MockLinkLog) << "_availableModesWorker: all modes sent, stopping worker";
2364 }
2365}
2366
2367void MockLink::_sendAvailableModesMonitor()
2368{
2369 mavlink_message_t msg{};
2370
2371 (void) mavlink_msg_available_modes_monitor_pack_chan(
2372 _vehicleSystemId,
2373 _vehicleComponentId,
2375 &msg,
2376 _availableModesMonitorSeqNumber);
2378}
2379
2380int MockLink::_availableModesCount() const
2381{
2382 return _availableFlightModes.count() - (_availableModesMonitorSeqNumber == 0 ? 1 : 0); // Exclude the delayed mode
2383}
std::shared_ptr< LinkConfiguration > SharedLinkConfigurationPtr
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
struct __mavlink_setup_signing_t mavlink_setup_signing_t
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
struct param_union mavlink_param_union_t
struct __mavlink_command_long_t mavlink_command_long_t
static size_t typeToSize(ValueType_t type)
void setDynamic(bool dynamic=true)
Set if this is this a dynamic configuration. (decided at runtime)
QString name() const
The link interface defines the interface for all links used to communicate with the ground station ap...
void bytesReceived(LinkInterface *link, const QByteArray &data)
void disconnected()
uint8_t mavlinkChannel() const
virtual void _freeMavlinkChannel()
void _connectionRemoved()
bool mavlinkChannelIsSet() const
virtual bool _allocateMavlinkChannel()
void connected()
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()
int getSystemId() const
void setVehicleType(MAV_TYPE vehicleType)
@ 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.
@ FailInitialConnectRequestMessageAutopilotVersionFailure
REQUEST_MESSAGE:AUTOPILOT_VERSION returns failure.
@ FailParamNoResponseToRequestList
Do not respond to PARAM_REQUEST_LIST.
void setFailureMode(FailureMode_t failureMode)
void setEnableCamera(bool enableCamera)
void setEnableGimbal(bool enableGimbal)
void setSendStatusText(bool sendStatusText)
void setFirmwareType(MAV_AUTOPILOT firmwareType)
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.
Definition MockLinkFTP.h:16
void mavlinkMessageReceived(const mavlink_message_t &message)
Called to handle an FTP message.
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)
Worker class that runs periodic tasks for MockLink simulation.
static FactMetaData::ValueType_t mavTypeToFactType(MAV_PARAM_TYPE mavType)
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)
quint32 crc32(const quint8 *src, unsigned len, unsigned state)
Definition QGCMath.cc:100
bool runningUnitTests()
void secureZero(void *data, size_t size)