QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
APMFirmwarePlugin.cc
Go to the documentation of this file.
1#include "APMFirmwarePlugin.h"
2
3#include <QtCore/QRegularExpression>
4#include <QtCore/QRegularExpressionMatch>
5#include <QtNetwork/QTcpSocket>
6
10#include "AppMessages.h"
15#include "LinkManager.h"
16#include "MAVLinkLib.h"
17#include "MAVLinkProtocol.h"
18#include "MavlinkSettings.h"
19#include "MissionManager.h"
20#include "ParameterManager.h"
21#include "PlanViewSettings.h"
22#include "QGCApplication.h"
23#include "QGCLoggingCategory.h"
24#include "QGCMAVLink.h"
25#include "QGCSensors.h"
26#include "SettingsManager.h"
27#include "StatusTextHandler.h"
28#include "Vehicle.h"
29#include "VehicleLinkManager.h"
30#include "VideoSettings.h"
31
32QGC_LOGGING_CATEGORY(APMFirmwarePluginLog, "FirmwarePlugin.APMFirmwarePlugin")
33
35 : FirmwarePlugin(parent)
36{
37 _setModeEnumToModeStringMapping({
38 { APMCustomMode::GUIDED, _guidedFlightMode },
39 { APMCustomMode::RTL, _rtlFlightMode },
40 { APMCustomMode::SMART_RTL, _smartRtlFlightMode },
41 { APMCustomMode::AUTO, _autoFlightMode },
42 });
43
44 static FlightModeList modeList {
45 // Mode Name , Custom Mode CanBeSet adv
46 { _guidedFlightMode , APMCustomMode::GUIDED, true , true },
47 { _rtlFlightMode , APMCustomMode::RTL, true , true },
48 { _smartRtlFlightMode , APMCustomMode::SMART_RTL, true , true },
49 { _autoFlightMode , APMCustomMode::AUTO, true , true },
50 };
51
52 updateAvailableFlightModes(modeList);
53}
54
59
61{
62 return new APMAutoPilotPlugin(vehicle, vehicle);
63}
64
65bool APMFirmwarePlugin::isCapable(const Vehicle* vehicle, FirmwareCapabilities capabilities) const
66{
68 if (vehicle->fixedWing()) {
69 available |= TakeoffVehicleCapability;
70 } else if (vehicle->multiRotor()) {
71 available |= TakeoffVehicleCapability;
72 available |= GuidedTakeoffCapability;
73 available |= ChangeHeadingCapability;
74 } else if (vehicle->vtol()) {
75 available |= TakeoffVehicleCapability;
76 available |= GuidedTakeoffCapability;
77 } else if (vehicle->sub()) {
78 available |= ChangeHeadingCapability;
79 }
80
81 return (capabilities & available) == capabilities;
82}
83
84QStringList APMFirmwarePlugin::flightModes(Vehicle *vehicle) const
85{
86 Q_UNUSED(vehicle)
87 QStringList flightModesList;
88 for (const FirmwareFlightMode &mode : _flightModeList) {
89 if (mode.canBeSet){
90 flightModesList += mode.mode_name;
91 }
92 }
93 return flightModesList;
94}
95
96QString APMFirmwarePlugin::flightMode(uint8_t base_mode, uint32_t custom_mode) const
97{
98 QString flightMode = "Unknown";
99
100 if (base_mode & MAV_MODE_FLAG_CUSTOM_MODE_ENABLED) {
101 return _modeEnumToString.value(custom_mode, flightMode);
102 }
103 return flightMode;
104}
105
106bool APMFirmwarePlugin::setFlightMode(const QString &flightMode, uint8_t *base_mode, uint32_t *custom_mode) const
107{
108 *base_mode = 0;
109 *custom_mode = 0;
110
111 bool found = false;
112
113 for (const FirmwareFlightMode &mode: _flightModeList){
114 if (flightMode.compare(mode.mode_name, Qt::CaseInsensitive) == 0){
115 *base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
116 *custom_mode = mode.custom_mode;
117 found = true;
118 break;
119 }
120 }
121
122 if (!found) {
123 qCWarning(APMFirmwarePluginLog) << "Unknown flight Mode" << flightMode;
124 }
125
126 return found;
127}
128
129void APMFirmwarePlugin::_handleIncomingParamValue(Vehicle *vehicle, mavlink_message_t *message)
130{
131 if (!qgcApp() || qgcApp()->thread() != QThread::currentThread()) {
132 qCCritical(APMFirmwarePluginLog) << "Parameter re-encoding requires the application thread";
133 return;
134 }
135 Q_UNUSED(vehicle);
136
137 mavlink_param_value_t paramValue;
138 mavlink_param_union_t paramUnion;
139
140 (void) memset(&paramValue, 0, sizeof(paramValue));
141
142 // APM stack passes all parameter values in mavlink_param_union_t.param_float no matter what
143 // type they are. Fix that up to correct usage.
144
145 mavlink_msg_param_value_decode(message, &paramValue);
146
147 switch (paramValue.param_type) {
148 case MAV_PARAM_TYPE_UINT8:
149 paramUnion.param_uint8 = static_cast<uint8_t>(paramValue.param_value);
150 break;
151 case MAV_PARAM_TYPE_INT8:
152 paramUnion.param_int8 = static_cast<int8_t>(paramValue.param_value);
153 break;
154 case MAV_PARAM_TYPE_UINT16:
155 paramUnion.param_uint16 = static_cast<uint16_t>(paramValue.param_value);
156 break;
157 case MAV_PARAM_TYPE_INT16:
158 paramUnion.param_int16 = static_cast<int16_t>(paramValue.param_value);
159 break;
160 case MAV_PARAM_TYPE_UINT32:
161 paramUnion.param_uint32 = static_cast<uint32_t>(paramValue.param_value);
162 break;
163 case MAV_PARAM_TYPE_INT32:
164 paramUnion.param_int32 = static_cast<int32_t>(paramValue.param_value);
165 break;
166 case MAV_PARAM_TYPE_REAL32:
167 paramUnion.param_float = paramValue.param_value;
168 break;
169 default:
170 qCCritical(APMFirmwarePluginLog) << "Invalid/Unsupported data type used in parameter:" << paramValue.param_type;
171 }
172
173 paramValue.param_value = paramUnion.param_float;
174
175 // Re-Encoding is always done using mavlink 1.0
176 const uint8_t channel = _reencodeMavlinkChannel();
177 QMutexLocker reencode_lock{&_reencodeMavlinkChannelMutex()};
178
179 mavlink_status_t *mavlinkStatusReEncode = mavlink_get_channel_status(channel);
180 mavlinkStatusReEncode->flags |= MAVLINK_STATUS_FLAG_IN_MAVLINK1;
181
182 (void) mavlink_msg_param_value_encode_chan(
183 message->sysid,
184 message->compid,
185 channel,
186 message,
187 &paramValue
188 );
189}
190
191void APMFirmwarePlugin::_handleOutgoingParamSetThreadSafe(Vehicle* /*vehicle*/, LinkInterface *outgoingLink, mavlink_message_t *message)
192{
193 mavlink_param_set_t paramSet;
194 mavlink_param_union_t paramUnion;
195
196 (void) memset(&paramSet, 0, sizeof(paramSet));
197
198 // APM stack passes all parameter values in mavlink_param_union_t.param_float no matter what
199 // type they are. Fix it back to the wrong way on the way out.
200
201 mavlink_msg_param_set_decode(message, &paramSet);
202
203 if (!_ardupilotComponentMap[paramSet.target_system][paramSet.target_component]) {
204 // Message is targetted to non-ArduPilot firmware component, assume it uses current mavlink spec
205 return;
206 }
207
208 paramUnion.param_float = paramSet.param_value;
209
210 switch (paramSet.param_type) {
211 case MAV_PARAM_TYPE_UINT8:
212 paramSet.param_value = paramUnion.param_uint8;
213 break;
214 case MAV_PARAM_TYPE_INT8:
215 paramSet.param_value = paramUnion.param_int8;
216 break;
217 case MAV_PARAM_TYPE_UINT16:
218 paramSet.param_value = paramUnion.param_uint16;
219 break;
220 case MAV_PARAM_TYPE_INT16:
221 paramSet.param_value = paramUnion.param_int16;
222 break;
223 case MAV_PARAM_TYPE_UINT32:
224 paramSet.param_value = paramUnion.param_uint32;
225 break;
226 case MAV_PARAM_TYPE_INT32:
227 paramSet.param_value = paramUnion.param_int32;
228 break;
229 case MAV_PARAM_TYPE_REAL32:
230 // Already in param_float
231 break;
232 default:
233 qCCritical(APMFirmwarePluginLog) << "Invalid/Unsupported data type used in parameter:" << paramSet.param_type;
234 }
235
236 _adjustOutgoingMavlinkMutex.lock();
237 mavlink_msg_param_set_encode_chan(
238 message->sysid,
239 message->compid,
240 outgoingLink->mavlinkChannel(),
241 message,
242 &paramSet
243 );
244 _adjustOutgoingMavlinkMutex.unlock();
245}
246
247bool APMFirmwarePlugin::_handleIncomingStatusText(Vehicle* /*vehicle*/, mavlink_message_t *message)
248{
249 // APM user facing calibration messages come through as high severity, we need to parse them out
250 // and lower the severity on them so that they don't pop in the users face.
251
252 const QString messageText = StatusTextHandler::getMessageText(*message);
253 if (messageText.contains("Place vehicle") || messageText.contains("Calibration successful")) {
254 _adjustCalibrationMessageSeverity(message);
255 return true;
256 }
257
258 static const QRegularExpression APM_FRAME_REXP("^Frame: (\\S*)");
259 const QRegularExpressionMatch match = APM_FRAME_REXP.match(messageText);
260 if (match.hasMatch()) {
261 static const QSet<QString> coaxialFrames = {"Y6", "OCTA_QUAD", "COAX", "QUAD/PLUS"};
262 const QString frameType = match.captured(1);
263 _coaxialMotors = coaxialFrames.contains(frameType);
264 }
265
266 return true;
267}
268
269void APMFirmwarePlugin::_handleIncomingHeartbeat(Vehicle *vehicle, mavlink_message_t *message)
270{
271 mavlink_heartbeat_t heartbeat{};
272 mavlink_msg_heartbeat_decode(message, &heartbeat);
273
274 if (message->compid == MAV_COMP_ID_AUTOPILOT1) {
275 bool underway = false;
276
277 // We pull Vehicle::underway state from HEARTBEAT on ArduPilot. This is a firmware specific test.
278 if (vehicle->armed() && ((heartbeat.system_status == MAV_STATE_ACTIVE) || (heartbeat.system_status == MAV_STATE_CRITICAL) || (heartbeat.system_status == MAV_STATE_EMERGENCY))) {
279 underway = true;
280 }
281 vehicle->_setUnderway(underway);
282 }
283
284 // We need to know whether this component is part of the ArduPilot stack code or not so we can adjust mavlink quirks appropriately.
285 // If the component sends a heartbeat we can know that. If it's doesn't there is pretty much no way to know.
286 _ardupilotComponentMap[message->sysid][message->compid] = heartbeat.autopilot == MAV_AUTOPILOT_ARDUPILOTMEGA;
287
288 // Force the ESP8266 to be non-ArduPilot code (it doesn't send heartbeats)
289 _ardupilotComponentMap[message->sysid][MAV_COMP_ID_UDP_BRIDGE] = false;
290}
291
293{
294 // We use loss of BATTERY_STATUS/HOME_POSITION as a trigger to reinitialize stream rates
295 auto instanceData = qobject_cast<APMFirmwarePluginInstanceData*>(vehicle->firmwarePluginInstanceData());
296
297 if (message->msgid == MAVLINK_MSG_ID_HEARTBEAT) {
298 // We need to look at all heartbeats that go by from any component
299 _handleIncomingHeartbeat(vehicle, message);
300 } else if (message->msgid == MAVLINK_MSG_ID_BATTERY_STATUS && instanceData) {
301 instanceData->lastBatteryStatusTime = QTime::currentTime();
302 } else if (message->msgid == MAVLINK_MSG_ID_HOME_POSITION && instanceData) {
303 instanceData->lastHomePositionTime = QTime::currentTime();
304 } else {
305 // Only translate messages which come from ArduPilot code. All other components are expected to follow current mavlink spec.
306 if (_ardupilotComponentMap[vehicle->id()][message->compid]) {
307 switch (message->msgid) {
308 case MAVLINK_MSG_ID_PARAM_VALUE:
309 _handleIncomingParamValue(vehicle, message);
310 break;
311 case MAVLINK_MSG_ID_STATUSTEXT:
312 return _handleIncomingStatusText(vehicle, message);
313 case MAVLINK_MSG_ID_RC_CHANNELS:
314 _handleRCChannels(vehicle, message);
315 break;
316 case MAVLINK_MSG_ID_RC_CHANNELS_RAW:
317 _handleRCChannelsRaw(vehicle, message);
318 break;
319 }
320 }
321 }
322
323 // If we lose BATTERY_STATUS/HOME_POSITION for reinitStreamsTimeoutSecs seconds we re-initialize stream rates
324 const int reinitStreamsTimeoutSecs = 10;
325 if (instanceData && ((instanceData->lastBatteryStatusTime.secsTo(QTime::currentTime()) > reinitStreamsTimeoutSecs) || (instanceData->lastHomePositionTime.secsTo(QTime::currentTime()) > reinitStreamsTimeoutSecs))) {
326 initializeStreamRates(vehicle);
327 }
328
329 return true;
330}
331
333{
334 switch (message->msgid) {
335 case MAVLINK_MSG_ID_PARAM_SET:
336 _handleOutgoingParamSetThreadSafe(vehicle, outgoingLink, message);
337 break;
338 default:
339 break;
340 }
341}
342
343void APMFirmwarePlugin::_setInfoSeverity(mavlink_message_t *message) const
344{
345 if (!qgcApp() || qgcApp()->thread() != QThread::currentThread()) {
346 qCCritical(APMFirmwarePluginLog) << "Status text re-encoding requires the application thread";
347 return;
348 }
349 // Re-Encoding is always done using mavlink 1.0
350 const uint8_t channel = _reencodeMavlinkChannel();
351 QMutexLocker reencode_lock{&_reencodeMavlinkChannelMutex()};
352 mavlink_status_t *mavlinkStatusReEncode = mavlink_get_channel_status(channel);
353 mavlinkStatusReEncode->flags |= MAVLINK_STATUS_FLAG_IN_MAVLINK1;
354
355 mavlink_statustext_t statusText{};
356 mavlink_msg_statustext_decode(message, &statusText);
357
358 statusText.severity = MAV_SEVERITY_INFO;
359
360 (void) mavlink_msg_statustext_encode_chan(
361 message->sysid,
362 message->compid,
363 channel,
364 message,
365 &statusText
366 );
367}
368
369void APMFirmwarePlugin::_adjustCalibrationMessageSeverity(mavlink_message_t *message) const
370{
371 if (!qgcApp() || qgcApp()->thread() != QThread::currentThread()) {
372 qCCritical(APMFirmwarePluginLog) << "Status text re-encoding requires the application thread";
373 return;
374 }
375 mavlink_statustext_t statusText{};
376 mavlink_msg_statustext_decode(message, &statusText);
377
378 // Re-Encoding is always done using mavlink 1.0
379 const uint8_t channel = _reencodeMavlinkChannel();
380 QMutexLocker reencode_lock{&_reencodeMavlinkChannelMutex()};
381
382 mavlink_status_t *mavlinkStatusReEncode = mavlink_get_channel_status(channel);
383 mavlinkStatusReEncode->flags |= MAVLINK_STATUS_FLAG_IN_MAVLINK1;
384 statusText.severity = MAV_SEVERITY_INFO;
385
386 (void) mavlink_msg_statustext_encode_chan(
387 message->sysid,
388 message->compid,
389 channel,
390 message,
391 &statusText
392 );
393}
394
396{
397 // We use loss of BATTERY_STATUS/HOME_POSITION as a trigger to reinitialize stream rates
398 auto instanceData = qobject_cast<APMFirmwarePluginInstanceData*>(vehicle->firmwarePluginInstanceData());
399 if (!instanceData) {
400 instanceData = new APMFirmwarePluginInstanceData(vehicle);
401 instanceData->lastBatteryStatusTime = instanceData->lastHomePositionTime = QTime::currentTime();
402 vehicle->setFirmwarePluginInstanceData(instanceData);
403 }
404
405 if (SettingsManager::instance()->mavlinkSettings()->apmStartMavlinkStreams()->rawValue().toBool()) {
406
408
409 struct StreamInfo_s {
410 MAV_DATA_STREAM mavStream;
411 int streamRate;
412 };
413
414 const StreamInfo_s rgStreamInfo[] = {
415 { MAV_DATA_STREAM_RAW_SENSORS, streamRates->streamRateRawSensors()->rawValue().toInt() },
416 { MAV_DATA_STREAM_EXTENDED_STATUS, streamRates->streamRateExtendedStatus()->rawValue().toInt() },
417 { MAV_DATA_STREAM_RC_CHANNELS, streamRates->streamRateRCChannels()->rawValue().toInt() },
418 { MAV_DATA_STREAM_POSITION, streamRates->streamRatePosition()->rawValue().toInt() },
419 { MAV_DATA_STREAM_EXTRA1, streamRates->streamRateExtra1()->rawValue().toInt() },
420 { MAV_DATA_STREAM_EXTRA2, streamRates->streamRateExtra2()->rawValue().toInt() },
421 { MAV_DATA_STREAM_EXTRA3, streamRates->streamRateExtra3()->rawValue().toInt() },
422 };
423
424 for (size_t i = 0; i < std::size(rgStreamInfo); i++) {
425 const StreamInfo_s &streamInfo = rgStreamInfo[i];
426
427 if (streamInfo.streamRate >= 0) {
428 vehicle->requestDataStream(streamInfo.mavStream, static_cast<uint16_t>(streamInfo.streamRate));
429 }
430 }
431 }
432
433 // The MAV_CMD_SET_MESSAGE_INTERVAL command is only supported on newer firmwares. So we set showError=false.
434 // Which also means than on older firmwares you may be left with some missing features.
435
436 // ArduPilot only sends home position on first boot and then when it arms. It does not stream the position.
437 // This means that depending on when QGC connects to the vehicle it may not have home position.
438 // This can cause various features to not be available. So we request home position streaming ourselves.
439 vehicle->sendMavCommand(MAV_COMP_ID_AUTOPILOT1, MAV_CMD_SET_MESSAGE_INTERVAL, false /* showError */, MAVLINK_MSG_ID_HOME_POSITION, 1000000 /* 1 second interval in usec */);
440
441 // ArduPilot doesn't send MAVLINK_MSG_ID_EXTENDED_SYS_STATE messages unless requested, so we request it to
442 // make the LandAbort action available.
443 vehicle->sendMavCommand(MAV_COMP_ID_AUTOPILOT1, MAV_CMD_SET_MESSAGE_INTERVAL, false /* showError */, MAVLINK_MSG_ID_EXTENDED_SYS_STATE, 1000000 /* 1 second interval in usec */);
444
445 instanceData->lastBatteryStatusTime = instanceData->lastHomePositionTime = QTime::currentTime();
446}
447
448APMFirmwarePlugin::FirmwareParameterHeader APMFirmwarePlugin::_parseParamsHeader(const QString &filePath)
449{
450 APMFirmwarePlugin::FirmwareParameterHeader data{};
451
452 QFile file(filePath);
453 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
454 return data;
455 }
456
457 QTextStream stream(&file);
458 while (!stream.atEnd()) {
459 const QString line = stream.readLine();
460
461 // Stop once non-comment parameter rows begin and we already saw some header
462 if (!line.startsWith('#')) {
463 break;
464 }
465
466 using namespace Qt::StringLiterals;
467
468 static const QRegularExpression reStack(uR"(^#\s*Stack:\s*(.+)\s*$)"_s);
469 auto match = reStack.match(line);
470 if (match.hasMatch()) {
471 const QString firmwareTypeStr = match.captured(1).trimmed();
472 const MAV_AUTOPILOT firmwareType = QGCMAVLink::firmwareTypeFromString(firmwareTypeStr);
473 data.firmwareType = firmwareType;
474 continue;
475 }
476
477 static const QRegularExpression reVehicle(uR"(^#\s*Vehicle:\s*(.+)\s*$)"_s);
478 match = reVehicle.match(line);
479 if (match.hasMatch()) {
480 const QString vehicleTypeStr = match.captured(1).trimmed();
481 const MAV_TYPE vehicleType = QGCMAVLink::vehicleTypeFromString(vehicleTypeStr);
482 data.vehicleType = vehicleType;
483 continue;
484 }
485
486 static const QRegularExpression reVersion(uR"(^#\s*Version:\s*([0-9]+(?:\.[0-9]+){0,2})(?:\s+([A-Za-z0-9]+))?\s*$)"_s);
487 match = reVersion.match(line);
488 if (match.hasMatch()) {
489 const QString versionNumber = match.captured(1).trimmed();
490 data.versionNumber = QVersionNumber::fromString(versionNumber);
491
492 const QString versionType = match.captured(2).trimmed();
493 if (versionType.isEmpty()) {
494 data.versionType = FIRMWARE_VERSION_TYPE_OFFICIAL;
495 } else {
496 data.versionType = QGCMAVLink::firmwareVersionTypeFromString(versionType);
497 }
498 continue;
499 }
500
501 static const QRegularExpression reGit(uR"(^#\s*Git Revision:\s*([0-9a-fA-F]+)\s*$)"_s);
502 match = reGit.match(line);
503 if (match.hasMatch()) {
504 data.gitRevision = match.captured(1).trimmed();
505 continue;
506 }
507 }
508
509 return data;
510}
511
513{
514 if (vehicle->isOfflineEditingVehicle()) {
515 const QString offlineParameterFile = offlineEditingParamFile(vehicle);
516 const APMFirmwarePlugin::FirmwareParameterHeader offlineParameterHeader = _parseParamsHeader(offlineParameterFile);
517 if (offlineParameterHeader.vehicleType != MAV_TYPE_GENERIC) {
518 vehicle->setFirmwareVersion(offlineParameterHeader.versionNumber.majorVersion(), offlineParameterHeader.versionNumber.minorVersion(), offlineParameterHeader.versionNumber.microVersion());
519 }
520 } else {
521 initializeStreamRates(vehicle);
522 }
523
524 if (SettingsManager::instance()->videoSettings()->videoSource()->rawValue() == VideoSettings::videoSource3DRSolo) {
525 _soloVideoHandshake();
526 }
527}
528
530{
531 QList<MAV_CMD> supportedCommands = {
532 MAV_CMD_NAV_WAYPOINT,
533 MAV_CMD_NAV_LOITER_UNLIM, MAV_CMD_NAV_LOITER_TURNS, MAV_CMD_NAV_LOITER_TIME,
534 MAV_CMD_NAV_RETURN_TO_LAUNCH,
535 MAV_CMD_NAV_CONTINUE_AND_CHANGE_ALT,
536 MAV_CMD_NAV_LOITER_TO_ALT,
537 MAV_CMD_NAV_SPLINE_WAYPOINT,
538 MAV_CMD_NAV_GUIDED_ENABLE,
539 MAV_CMD_NAV_DELAY,
540 MAV_CMD_CONDITION_DELAY, MAV_CMD_CONDITION_DISTANCE, MAV_CMD_CONDITION_YAW,
541 MAV_CMD_DO_SET_MODE,
542 MAV_CMD_DO_JUMP,
543 MAV_CMD_DO_CHANGE_SPEED,
544 MAV_CMD_DO_SET_HOME,
545 MAV_CMD_DO_SET_RELAY, MAV_CMD_DO_REPEAT_RELAY,
546 MAV_CMD_DO_SET_SERVO, MAV_CMD_DO_REPEAT_SERVO,
547 MAV_CMD_DO_LAND_START,
548 MAV_CMD_DO_SET_ROI,
549 MAV_CMD_DO_DIGICAM_CONFIGURE, MAV_CMD_DO_DIGICAM_CONTROL,
550 MAV_CMD_DO_MOUNT_CONTROL,
551 MAV_CMD_DO_GIMBAL_MANAGER_PITCHYAW,
552 MAV_CMD_DO_SET_CAM_TRIGG_DIST,
553 MAV_CMD_IMAGE_START_CAPTURE, MAV_CMD_IMAGE_STOP_CAPTURE, MAV_CMD_VIDEO_START_CAPTURE, MAV_CMD_VIDEO_STOP_CAPTURE,
554 MAV_CMD_DO_FENCE_ENABLE,
555 MAV_CMD_DO_PARACHUTE,
556 MAV_CMD_DO_INVERTED_FLIGHT,
557 MAV_CMD_DO_GRIPPER,
558 MAV_CMD_DO_GUIDED_LIMITS,
559 MAV_CMD_DO_AUTOTUNE_ENABLE,
560 };
561
562 QList<MAV_CMD> vtolCommands = {
563 MAV_CMD_NAV_VTOL_TAKEOFF, MAV_CMD_NAV_VTOL_LAND, MAV_CMD_DO_VTOL_TRANSITION,
564 };
565
566 QList<MAV_CMD> flightCommands = {
567 MAV_CMD_NAV_LAND, MAV_CMD_NAV_TAKEOFF,
568 };
569
570 if (vehicleClass == QGCMAVLink::VehicleClassGeneric) {
571 supportedCommands += vtolCommands;
572 supportedCommands += flightCommands;
573 }
574 if (vehicleClass == QGCMAVLink::VehicleClassVTOL) {
575 supportedCommands += vtolCommands;
576 supportedCommands += flightCommands;
577 } else if (vehicleClass == QGCMAVLink::VehicleClassFixedWing || vehicleClass == QGCMAVLink::VehicleClassMultiRotor) {
578 supportedCommands += flightCommands;
579 }
580
581 if (SettingsManager::instance()->planViewSettings()->useConditionGate()->rawValue().toBool()) {
582 supportedCommands.append(MAV_CMD_CONDITION_GATE);
583 }
584
585 return supportedCommands;
586}
587
589{
590 switch (vehicleClass) {
592 return QStringLiteral(":/json/APM-MavCmdInfoCommon.json");
594 return QStringLiteral(":/json/APM-MavCmdInfoFixedWing.json");
596 return QStringLiteral(":/json/APM-MavCmdInfoMultiRotor.json");
598 return QStringLiteral(":/json/APM-MavCmdInfoVTOL.json");
600 return QStringLiteral(":/json/APM-MavCmdInfoSub.json");
602 return QStringLiteral(":/json/APM-MavCmdInfoRover.json");
603 default:
604 qCWarning(APMFirmwarePluginLog) << "APMFirmwarePlugin::missionCommandOverrides called with bad VehicleClass_t:" << vehicleClass;
605 return QString();
606 }
607}
608
613
615{
616 uint64_t hobbsTimeSeconds = 0;
617
619 Fact *const factFltTime = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, "STAT_FLTTIME");
620 hobbsTimeSeconds = static_cast<uint64_t>(factFltTime->rawValue().toUInt());
621 qCDebug(APMFirmwarePluginLog) << "Hobbs Meter raw Ardupilot(s):" << "(" << hobbsTimeSeconds << ")";
622 }
623
624 const int hours = hobbsTimeSeconds / 3600;
625 const int minutes = (hobbsTimeSeconds % 3600) / 60;
626 const int seconds = hobbsTimeSeconds % 60;
627 const QString timeStr = QString::asprintf("%04d:%02d:%02d", hours, minutes, seconds);
628 qCDebug(APMFirmwarePluginLog) << "Hobbs Meter string:" << timeStr;
629 return timeStr;
630}
631
632bool APMFirmwarePlugin::hasGripper(const Vehicle *vehicle) const
633{
635 const bool _hasGripper = ((vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("GRIP_ENABLE"))->rawValue().toInt()) == 1);
636 return _hasGripper;
637 }
638 return false;
639}
640
641const QVariantList &APMFirmwarePlugin::toolIndicators(const Vehicle *vehicle)
642{
643 if (_toolIndicatorList.isEmpty()) {
644 // First call the base class to get the standard QGC list
645 _toolIndicatorList = FirmwarePlugin::toolIndicators(vehicle);
646
647 // Add the forwarding support indicator
648 _toolIndicatorList.append(QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/FirmwarePlugin/APM/APMSupportForwardingIndicator.qml")));
649 }
650
651 return _toolIndicatorList;
652}
653
654bool APMFirmwarePlugin::isGuidedMode(const Vehicle *vehicle) const
655{
656 return (vehicle->flightMode() == guidedFlightMode());
657}
658
663
668
673
678
679void APMFirmwarePlugin::_soloVideoHandshake()
680{
681 QTcpSocket *const socket = new QTcpSocket(this);
682
683 (void) QObject::connect(socket, &QAbstractSocket::errorOccurred, this, &APMFirmwarePlugin::_artooSocketError);
684 socket->connectToHost(_artooIP, _artooVideoHandshakePort);
685}
686
687void APMFirmwarePlugin::_artooSocketError(QAbstractSocket::SocketError socketError)
688{
689 QGC::showAppMessage(tr("Error during Solo video link setup: %1").arg(socketError));
690}
691
692QString APMFirmwarePlugin::_vehicleClassToString(QGCMAVLink::VehicleClass_t vehicleClass) const
693{
694 switch (vehicleClass) {
696 return QStringLiteral("Copter");
699 return QStringLiteral("Plane");
701 return QStringLiteral("Rover");
703 return QStringLiteral("Sub");
704 default:
705 qCWarning(APMFirmwarePluginLog) << Q_FUNC_INFO << "called with bad VehicleClass_t:" << vehicleClass;
706 return QString();
707 }
708}
709
711{
712 const QGCMAVLink::VehicleClass_t vehicleClass = QGCMAVLink::vehicleClass(vehicle->vehicleType());
713
714 const QString vehicleName = _vehicleClassToString(vehicleClass);
715 if (vehicleName.isEmpty()) {
716 qCWarning(APMFirmwarePluginLog) << Q_FUNC_INFO << "called with bad VehicleClass_t:" << vehicleClass;
717 return QString();
718 }
719
720 int currMajor = vehicle->firmwareMajorVersion();
721 int currMinor = vehicle->firmwareMinorVersion();
722
723 // Find next newest version available
724 while ((currMajor >= 4) && (currMinor > 0)) {
725 const QString file = QStringLiteral(":/FirmwarePlugin/APM/APMParameterFactMetaData.%1.%2.%3.json").arg(vehicleName).arg(currMajor).arg(currMinor);
726 if (QFileInfo::exists(file)) {
727 return file;
728 }
729 currMinor--;
730 if (currMinor == 0) {
731 currMinor = 10;
732 currMajor--;
733 }
734 }
735
736 // Fallback: use oldest version available
737 for (int i = 0; i < 10; i++) {
738 const QString file = QStringLiteral(":/FirmwarePlugin/APM/APMParameterFactMetaData.%1.%2.%3.json").arg(vehicleName).arg(4).arg(i);
739 if (QFileInfo::exists(file)) {
740 return file;
741 }
742 }
743
744 qCWarning(APMFirmwarePluginLog) << Q_FUNC_INFO << "Meta Data File Not Found";
745 return QString();
746}
747
748void APMFirmwarePlugin::setGuidedMode(Vehicle *vehicle, bool guidedMode) const
749{
750 if (guidedMode) {
752 } else {
753 pauseVehicle(vehicle);
754 }
755}
756
761
765
766static void _MAV_CMD_DO_REPOSITION_ResultHandler(void *resultHandlerData, int /*compId*/, const mavlink_command_ack_t &ack, Vehicle::MavCmdResultFailureCode_t /*failureCode*/)
767{
768 auto *data = static_cast<MAV_CMD_DO_REPOSITION_HandlerData*>(resultHandlerData);
769 auto *vehicle = data->vehicle;
770 auto *instanceData = qobject_cast<APMFirmwarePluginInstanceData*>(vehicle->firmwarePluginInstanceData());
771
772 if (instanceData->MAV_CMD_DO_REPOSITION_supported ||
773 instanceData->MAV_CMD_DO_REPOSITION_unsupported) {
774 // we never change out minds once set
775 goto out;
776 }
777
778 instanceData->MAV_CMD_DO_REPOSITION_supported = (ack.result == MAV_RESULT_ACCEPTED);
779 instanceData->MAV_CMD_DO_REPOSITION_unsupported = (ack.result == MAV_RESULT_UNSUPPORTED);
780
781out:
782 delete data;
783}
784
785bool APMFirmwarePlugin::guidedModeGotoLocation(Vehicle *vehicle, const QGeoCoordinate &gotoCoord, double forwardFlightLoiterRadius) const
786{
787 if (qIsNaN(vehicle->altitudeRelative()->rawValue().toDouble())) {
788 QGC::showAppMessage(tr("Unable to go to location, vehicle position not known."));
789 return false;
790 }
791
792 // attempt to use MAV_CMD_DO_REPOSITION to move vehicle. If that
793 // comes back as unsupported, try using the old system of sending
794 // through mission items with custom "current" field values.
795 auto *instanceData = qobject_cast<APMFirmwarePluginInstanceData*>(vehicle->firmwarePluginInstanceData());
796
797 // if we know it is supported or we don't know for sure it is
798 // unsupported then send the command:
799 if (instanceData) {
800 if (instanceData->MAV_CMD_DO_REPOSITION_supported || !instanceData->MAV_CMD_DO_REPOSITION_unsupported) {
801 auto *result_handler_data = new MAV_CMD_DO_REPOSITION_HandlerData {
802 vehicle
803 };
804
805 Vehicle::MavCmdAckHandlerInfo_t handlerInfo = {};
807 handlerInfo.resultHandlerData = result_handler_data;
808
809 // For copters, this parameter specifies a yaw heading (heading
810 // reference defined in Bitmask field). NaN to use the current
811 // system yaw heading mode (e.g. yaw towards next waypoint, yaw to
812 // home, etc.).
813 // For planes it indicates loiter direction (0: clockwise, 1:
814 // counter clockwise)
815 float yawParam = NAN;
816 if (forwardFlightLoiterRadius > 0) {
817 yawParam = 0.0f;
818 } else if (forwardFlightLoiterRadius < 0) {
819 yawParam = 1.0f;
820 }
821
823 &handlerInfo,
824 vehicle->defaultComponentId(),
825 MAV_CMD_DO_REPOSITION,
826 MAV_FRAME_GLOBAL,
827 -1.0f,
828 MAV_DO_REPOSITION_FLAGS_CHANGE_MODE,
829 static_cast<float>(abs(forwardFlightLoiterRadius)),
830 yawParam,
831 gotoCoord.latitude(),
832 gotoCoord.longitude(),
833 vehicle->altitudeAMSL()->rawValue().toFloat()
834 );
835 }
836 if (instanceData->MAV_CMD_DO_REPOSITION_supported) {
837 // no need to fall back
838 return true;
839 }
840 }
841
842 setGuidedMode(vehicle, true);
843
844 QGeoCoordinate coordWithAltitude = gotoCoord;
845 coordWithAltitude.setAltitude(vehicle->altitudeRelative()->rawValue().toDouble());
846 vehicle->missionManager()->writeArduPilotGuidedMissionItem(coordWithAltitude, false /* altChangeOnly */);
847
848 return true;
849}
850
851void APMFirmwarePlugin::guidedModeRTL(Vehicle *vehicle, bool smartRTL) const
852{
854}
855
856void APMFirmwarePlugin::guidedModeChangeAltitude(Vehicle *vehicle, double altitudeChange, bool pauseVehicle)
857{
858 if (qIsNaN(vehicle->altitudeRelative()->rawValue().toDouble())) {
859 QGC::showAppMessage(tr("Unable to change altitude, vehicle altitude not known."));
860 return;
861 }
862
864 QGC::showAppMessage(tr("Unable to pause vehicle."));
865 return;
866 }
867
868 if (abs(altitudeChange) < 0.01) {
869 // This prevents unecessary changes to Guided mode when the users selects pause and doesn't really touch the altitude slider
870 return;
871 }
872
873 setGuidedMode(vehicle, true);
874
875 SharedLinkInterfacePtr sharedLink = vehicle->vehicleLinkManager()->primaryLink().lock();
876 if (sharedLink) {
877 mavlink_message_t msg{};
878 mavlink_set_position_target_local_ned_t cmd{};
879
880 (void) memset(&cmd, 0, sizeof(cmd));
881
882 cmd.target_system = static_cast<uint8_t>(vehicle->id());
883 cmd.target_component = static_cast<uint8_t>(vehicle->defaultComponentId());
884 cmd.coordinate_frame = MAV_FRAME_LOCAL_OFFSET_NED;
885 cmd.type_mask = 0xFFF8; // Only x/y/z valid
886 cmd.x = 0.0f;
887 cmd.y = 0.0f;
888 cmd.z = static_cast<float>(-(altitudeChange));
889
890 mavlink_msg_set_position_target_local_ned_encode_chan(
891 static_cast<uint8_t>(MAVLinkProtocol::instance()->getSystemId()),
892 static_cast<uint8_t>(MAVLinkProtocol::getComponentId()),
893 sharedLink->mavlinkChannel(),
894 &msg,
895 &cmd
896 );
897
898 (void) vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
899 }
900}
901
903{
904 // Use noremap. to bypass remap and check for specific parameter names directly,
905 // since the old and new parameters have different units.
906 return vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.WP_SPD"))
907 || vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.WPNAV_SPEED"));
908}
909
911{
912 // Use noremap. to bypass remap and check for specific parameter names directly,
913 // since the old and new parameters have different units.
914
915 // 4.7+: WP_SPD is in m/s
916 if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.WP_SPD"))) {
917 return vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.WP_SPD"))->rawValue().toDouble();
918 }
919
920 // pre-4.7: WPNAV_SPEED is in cm/s
921 if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.WPNAV_SPEED"))) {
922 return vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.WPNAV_SPEED"))->rawValue().toDouble() * 0.01;
923 }
924
926}
927
929{
930 vehicle->sendMavCommand(
931 vehicle->defaultComponentId(),
932 MAV_CMD_DO_CHANGE_SPEED,
933 true, // show error is fails
934 1, // 0: airspeed, 1: groundspeed
935 static_cast<float>(groundspeed), // groundspeed setpoint
936 -1, // throttle
937 0, // 0: absolute speed, 1: relative to current
938 NAN, NAN, NAN // param 5-7 unused
939 );
940}
941
942void APMFirmwarePlugin::guidedModeTakeoff(Vehicle *vehicle, double altitudeRel) const
943{
944 _guidedModeTakeoff(vehicle, altitudeRel);
945}
946
947void APMFirmwarePlugin::guidedModeChangeHeading(Vehicle *vehicle, const QGeoCoordinate &headingCoord) const
948{
950 QGC::showAppMessage(tr("Vehicle does not support guided rotate"));
951 return;
952 }
953
954 const float degrees = vehicle->coordinate().azimuthTo(headingCoord);
955 const float currentHeading = vehicle->heading()->rawValue().toFloat();
956
957 float diff = degrees - currentHeading;
958 if (diff < -180) {
959 diff += 360;
960 } else if (diff > 180) {
961 diff -= 360;
962 }
963
964 const int8_t direction = (diff > 0) ? 1 : -1;
965 diff = qAbs(diff);
966
967 float maxYawRate = 0.f;
968 static const QString maxYawRateParam = QStringLiteral("ATC_RATE_Y_MAX");
970 const Fact* maxYawRateFact =
972 if (maxYawRateFact) {
973 maxYawRate = maxYawRateFact->rawValue().toFloat();
974 }
975 }
976
977 vehicle->sendMavCommand(
978 vehicle->defaultComponentId(),
979 MAV_CMD_CONDITION_YAW,
980 true,
981 diff,
982 maxYawRate,
983 direction,
984 true
985 );
986}
987
989{
990 double minTakeoffAlt = 0;
991
992 // Use noremap. to bypass remap and check for specific parameter names directly,
993 // since the old and new parameters have different units.
994
995 if (vehicle->vtol()) {
996 // 4.7+: Q_PILOT_TKO_ALT_M (meters)
997 if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.Q_PILOT_TKO_ALT_M"))) {
998 minTakeoffAlt = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.Q_PILOT_TKO_ALT_M"))->rawValue().toDouble();
999 } else if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.Q_PILOT_TKOFF_ALT"))) {
1000 // pre-4.7: Q_PILOT_TKOFF_ALT (centimeters)
1001 minTakeoffAlt = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.Q_PILOT_TKOFF_ALT"))->rawValue().toDouble() / 100.0;
1002 } else if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("Q_RTL_ALT"))) {
1003 minTakeoffAlt = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("Q_RTL_ALT"))->rawValue().toDouble();
1004 }
1005 } else {
1006 // 4.7+: PILOT_TKO_ALT_M (meters)
1007 if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.PILOT_TKO_ALT_M"))) {
1008 minTakeoffAlt = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.PILOT_TKO_ALT_M"))->rawValue().toDouble();
1009 } else if (vehicle->parameterManager()->parameterExists(ParameterManager::defaultComponentId, QStringLiteral("noremap.PILOT_TKOFF_ALT"))) {
1010 // pre-4.7: PILOT_TKOFF_ALT (centimeters)
1011 minTakeoffAlt = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, QStringLiteral("noremap.PILOT_TKOFF_ALT"))->rawValue().toDouble() / 100.0;
1012 }
1013 }
1014
1015 if (minTakeoffAlt == 0) {
1016 minTakeoffAlt = FirmwarePlugin::minimumTakeoffAltitudeMeters(vehicle);
1017 }
1018
1019 return minTakeoffAlt;
1020}
1021
1022bool APMFirmwarePlugin::_guidedModeTakeoff(Vehicle *vehicle, double altitudeRel) const
1023{
1024 if (!vehicle->multiRotor() && !vehicle->vtol()) {
1025 QGC::showAppMessage(tr("Vehicle does not support guided takeoff"));
1026 return false;
1027 }
1028
1029 const double vehicleAltitudeAMSL = vehicle->altitudeAMSL()->rawValue().toDouble();
1030 if (qIsNaN(vehicleAltitudeAMSL)) {
1031 QGC::showAppMessage(tr("Unable to takeoff, vehicle position not known."));
1032 return false;
1033 }
1034
1035 double takeoffAltRel = minimumTakeoffAltitudeMeters(vehicle);
1036 if (!qIsNaN(altitudeRel) && altitudeRel > takeoffAltRel) {
1037 takeoffAltRel = altitudeRel;
1038 }
1039
1041 QGC::showAppMessage(tr("Unable to takeoff: Vehicle failed to change to Guided mode."));
1042 return false;
1043 }
1044
1045 if (!_armVehicleAndValidate(vehicle)) {
1046 QGC::showAppMessage(tr("Unable to takeoff: Vehicle failed to arm."));
1047 return false;
1048 }
1049
1050 vehicle->sendMavCommand(
1051 vehicle->defaultComponentId(),
1052 MAV_CMD_NAV_TAKEOFF,
1053 true, // show error
1054 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
1055 static_cast<float>(takeoffAltRel) // Relative altitude
1056 );
1057
1058 return true;
1059}
1060
1062{
1063 if (vehicle->airborne()) {
1064 QGC::showAppMessage(tr("Unable to start takeoff: Vehicle is already in the air."));
1065 return;
1066 }
1067
1068 // The vehicle is on the ground, so it's safe to switch to Takeoff mode regardless of arming state
1070 QGC::showAppMessage(tr("Unable to start takeoff: Vehicle failed to change to Takeoff mode."));
1071 return;
1072 }
1073
1074 if (!_armVehicleAndValidate(vehicle)) {
1075 QGC::showAppMessage(tr("Unable to start takeoff: Vehicle failed to arm."));
1076 return;
1077 }
1078}
1079
1081{
1082 if (vehicle->underway()) {
1083 // Vehicle already underway, we just need to switch to auto
1085 QGC::showAppMessage(tr("Unable to start mission: Vehicle failed to change to Auto mode."));
1086 }
1087 return;
1088 }
1089
1090 // If we get here vehicle is assumed to be on the ground (it may or may not be already armed)
1091
1092 if (vehicle->fixedWing() || vehicle->vtol()) {
1093 // Plane/VTOL starts the mission from Auto mode. Set the mode before arming, since arming in Guided
1094 // with tilt rotors would arm them in forward flight position, being dangerous.
1096 QGC::showAppMessage(tr("Unable to start mission: Vehicle failed to change to Auto mode."));
1097 return;
1098 }
1099
1100 if (!vehicle->armed() && !_armVehicleAndValidate(vehicle)) {
1101 QGC::showAppMessage(tr("Unable to start mission: Vehicle failed to arm."));
1102 return;
1103 }
1104 } else {
1105 // All other vehicle types arm in Guided and start the mission with MAV_CMD_MISSION_START
1106 if (!vehicle->armed()) {
1108 QGC::showAppMessage(tr("Unable to start mission: Vehicle failed to change to Guided mode."));
1109 return;
1110 }
1111
1112 if (!_armVehicleAndValidate(vehicle)) {
1113 QGC::showAppMessage(tr("Unable to start mission: Vehicle failed to arm."));
1114 return;
1115 }
1116 }
1117
1118 vehicle->sendMavCommand(vehicle->defaultComponentId(), MAV_CMD_MISSION_START, true /*show error */);
1119 }
1120}
1121
1122QString APMFirmwarePlugin::_getLatestVersionFileUrl(Vehicle *vehicle) const
1123{
1124 const static QString baseUrl("http://firmware.ardupilot.org/%1/stable/Pixhawk1/git-version.txt");
1125
1126 if (qobject_cast<ArduPlaneFirmwarePlugin*>(vehicle->firmwarePlugin())) {
1127 return baseUrl.arg("Plane");
1128 } else if (qobject_cast<ArduRoverFirmwarePlugin*>(vehicle->firmwarePlugin())) {
1129 return baseUrl.arg("Rover");
1130 } else if (qobject_cast<ArduSubFirmwarePlugin*>(vehicle->firmwarePlugin())) {
1131 return baseUrl.arg("Sub");
1132 } else if (qobject_cast<ArduCopterFirmwarePlugin*>(vehicle->firmwarePlugin())) {
1133 return baseUrl.arg("Copter");
1134 } else {
1135 qCWarning(APMFirmwarePluginLog) << "APMFirmwarePlugin::_getLatestVersionFileUrl Unknown vehicle firmware type" << vehicle->vehicleType();
1136 return QString();
1137 }
1138}
1139
1140void APMFirmwarePlugin::_handleRCChannels(Vehicle *vehicle, mavlink_message_t *message)
1141{
1142 SharedLinkInterfacePtr sharedLink = vehicle->vehicleLinkManager()->primaryLink().lock();
1143 if (sharedLink) {
1144 mavlink_rc_channels_t channels{};
1145
1146 mavlink_msg_rc_channels_decode(message, &channels);
1147 //-- Ardupilot uses 0-254 to indicate 0-100% where QGC expects 0-100
1148 // As per mavlink specs, 255 means invalid, we must leave it like that for indicators to hide if no rssi data
1149 if (channels.rssi && (channels.rssi != 255)) {
1150 channels.rssi = static_cast<uint8_t>((static_cast<double>(channels.rssi) / 254.0) * 100.0);
1151 }
1152 mavlink_msg_rc_channels_encode_chan(
1153 static_cast<uint8_t>(MAVLinkProtocol::instance()->getSystemId()),
1154 static_cast<uint8_t>(MAVLinkProtocol::getComponentId()),
1155 sharedLink->mavlinkChannel(),
1156 message,
1157 &channels
1158 );
1159 }
1160}
1161
1162void APMFirmwarePlugin::_handleRCChannelsRaw(Vehicle *vehicle, mavlink_message_t *message)
1163{
1164 SharedLinkInterfacePtr sharedLink = vehicle->vehicleLinkManager()->primaryLink().lock();
1165 if (sharedLink) {
1166 mavlink_rc_channels_raw_t channels{};
1167
1168 mavlink_msg_rc_channels_raw_decode(message, &channels);
1169 //-- Ardupilot uses 0-255 to indicate 0-100% where QGC expects 0-100
1170 if (channels.rssi) {
1171 channels.rssi = static_cast<uint8_t>((static_cast<double>(channels.rssi) / 255.0) * 100.0);
1172 }
1173 mavlink_msg_rc_channels_raw_encode_chan(
1174 static_cast<uint8_t>(MAVLinkProtocol::instance()->getSystemId()),
1175 static_cast<uint8_t>(MAVLinkProtocol::getComponentId()),
1176 sharedLink->mavlinkChannel(),
1177 message,
1178 &channels
1179 );
1180 }
1181}
1182
1183void APMFirmwarePlugin::sendGCSMotionReport(Vehicle *vehicle, const FollowMe::GCSMotionReport &motionReport, uint8_t estimationCapabilities) const
1184{
1185 if (!vehicle->homePosition().isValid()) {
1186 static bool sentOnce = false;
1187 if (!sentOnce) {
1188 sentOnce = true;
1189 QGC::showAppMessage(tr("Follow failed: Home position not set."));
1190 }
1191 return;
1192 }
1193
1194 if (!(estimationCapabilities & (FollowMe::POS | FollowMe::VEL | FollowMe::HEADING))) {
1195 static bool sentOnce = false;
1196 if (!sentOnce) {
1197 sentOnce = true;
1198 qCWarning(APMFirmwarePluginLog) << "estimateCapabilities" << estimationCapabilities;
1199 QGC::showAppMessage(tr("Follow failed: Ground station cannot provide required position information."));
1200 }
1201 return;
1202 }
1203
1204 SharedLinkInterfacePtr sharedLink = vehicle->vehicleLinkManager()->primaryLink().lock();
1205 if (!sharedLink) {
1206 return;
1207 }
1208
1209 const mavlink_global_position_int_t globalPositionInt = {
1210 static_cast<uint32_t>(qgcApp()->msecsSinceBoot()), /*< [ms] Timestamp (time since system boot).*/
1211 motionReport.lat_int, /*< [degE7] Latitude, expressed*/
1212 motionReport.lon_int, /*< [degE7] Longitude, expressed*/
1213 static_cast<int32_t>(vehicle->homePosition().altitude() * 1000), /*< [mm] Altitude (MSL).*/
1214 static_cast<int32_t>(0), /*< [mm] Altitude above home*/
1215 static_cast<int16_t>(motionReport.vxMetersPerSec * 100), /*< [cm/s] Ground X Speed (Latitude, positive north)*/
1216 static_cast<int16_t>(motionReport.vyMetersPerSec * 100), /*< [cm/s] Ground Y Speed (Longitude, positive east)*/
1217 static_cast<int16_t>(motionReport.vzMetersPerSec * 100), /*< [cm/s] Ground Z Speed (Altitude, positive down)*/
1218 static_cast<uint16_t>(motionReport.headingDegrees * 100.0) /*< [cdeg] Vehicle heading (yaw angle)*/
1219 };
1220
1221 mavlink_message_t message{};
1222 (void) mavlink_msg_global_position_int_encode_chan(
1223 static_cast<uint8_t>(MAVLinkProtocol::instance()->getSystemId()),
1224 static_cast<uint8_t>(MAVLinkProtocol::getComponentId()),
1225 sharedLink->mavlinkChannel(),
1226 &message,
1227 &globalPositionInt
1228 );
1229 (void) vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), message);
1230}
1231
1232uint8_t APMFirmwarePlugin::_reencodeMavlinkChannel()
1233{
1234 // This mutex is only to guard against a race on allocating the channel id
1235 // if two firmware plugins are created simultaneously from different threads
1236 //
1237 // Use of the allocated channel should be guarded by the mutex returned from
1238 // _reencodeMavlinkChannelMutex()
1239 //
1240 static QMutex _channelMutex{};
1241 _channelMutex.lock();
1242 static uint8_t channel{LinkManager::invalidMavlinkChannel()};
1243 if (LinkManager::invalidMavlinkChannel() == channel) {
1245 }
1246 _channelMutex.unlock();
1247 return channel;
1248}
1249
1250QMutex &APMFirmwarePlugin::_reencodeMavlinkChannelMutex()
1251{
1252 static QMutex _mutex{};
1253 return _mutex;
1254}
1255
1257{
1258 const QString airspeedMax("AIRSPEED_MAX");
1259
1261 const Fact* airspeedFact =
1263 if (airspeedFact) {
1264 return airspeedFact->rawValue().toDouble();
1265 }
1266 }
1267
1269}
1270
1272{
1273 const QString airspeedMin("AIRSPEED_MIN");
1274
1276 const Fact* airspeedFact =
1278 if (airspeedFact) {
1279 return airspeedFact->rawValue().toDouble();
1280 }
1281 }
1282
1284}
1285
1291
1293{
1294 vehicle->sendMavCommand(
1295 vehicle->defaultComponentId(),
1296 MAV_CMD_DO_CHANGE_SPEED,
1297 true, // show error is fails
1298 0, // 0: airspeed, 1: groundspeed
1299 static_cast<float>(airspeed_equiv), // speed setpoint
1300 -1, // throttle, no change
1301 0 // 0: absolute speed, 1: relative to current
1302 ); // param 5-7 unused
1303}
1304
1305void APMFirmwarePlugin::_setBaroGndTemp(Vehicle* vehicle, qreal temp)
1306{
1307 if (!vehicle) {
1308 return;
1309 }
1310
1311 const QString bareGndTemp("BARO_GND_TEMP");
1312
1314 Fact* const param = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, bareGndTemp);
1315 param->setRawValue(temp);
1316 }
1317}
1318
1319void APMFirmwarePlugin::_setBaroAltOffset(Vehicle* vehicle, qreal offset)
1320{
1321 if (!vehicle) {
1322 return;
1323 }
1324
1325 const QString baroAltOffset("BARO_ALT_OFFSET");
1326
1328 Fact* const param = vehicle->parameterManager()->getParameter(ParameterManager::defaultComponentId, baroAltOffset);
1329 param->setRawValue(offset);
1330 }
1331}
1332
1333#define ALT_METERS (145366.45 * 0.3048)
1334#define ALT_EXP (1. / 5.225)
1335#define SEA_PRESSURE 101325.
1336#define CELSIUS_TO_KELVIN(celsius) (celsius + 273.15)
1337#define ALT_OFFSET_P(pressure) (1. - pow((pressure / SEA_PRESSURE), ALT_EXP))
1338#define ALT_OFFSET_PT(pressure,temperature) ((ALT_OFFSET_P(pressure) * CELSIUS_TO_KELVIN(temperature)) / 0.0065)
1339
1340qreal APMFirmwarePlugin::calcAltOffsetPT(uint32_t atmospheric1, qreal temperature1, uint32_t atmospheric2, qreal temperature2)
1341{
1342 const qreal alt1 = ALT_OFFSET_PT(atmospheric1, temperature1);
1343 const qreal alt2 = ALT_OFFSET_PT(atmospheric2, temperature2);
1344 const qreal offset = alt1 - alt2;
1345 return offset;
1346}
1347
1348qreal APMFirmwarePlugin::calcAltOffsetP(uint32_t atmospheric1, uint32_t atmospheric2)
1349{
1350 const qreal alt1 = ALT_OFFSET_P(atmospheric1) * ALT_METERS;
1351 const qreal alt2 = ALT_OFFSET_P(atmospheric2) * ALT_METERS;
1352 const qreal offset = alt1 - alt2;
1353 return offset;
1354}
1355
1356QPair<QMetaObject::Connection,QMetaObject::Connection> APMFirmwarePlugin::startCompensatingBaro(Vehicle *vehicle)
1357{
1358 // TODO: Running Average?
1359 const QMetaObject::Connection baroPressureUpdater = QObject::connect(
1361 [vehicle](qreal pressure, qreal temperature) {
1362 if (!vehicle || !vehicle->airborne()) {
1363 return;
1364 }
1365
1366 if (qFuzzyIsNull(pressure)) {
1367 return;
1368 }
1369
1370 const qreal initialPressure = vehicle->getInitialGCSPressure();
1371 if (qFuzzyIsNull(initialPressure)) {
1372 return;
1373 }
1374
1375 const qreal initialTemperature = vehicle->getInitialGCSTemperature();
1376
1377 qreal offset = 0.;
1378 if (!qFuzzyIsNull(temperature) && !qFuzzyIsNull(initialTemperature)) {
1379 offset = APMFirmwarePlugin::calcAltOffsetPT(initialPressure, initialTemperature, pressure, temperature);
1380 } else {
1381 offset = APMFirmwarePlugin::calcAltOffsetP(initialPressure, pressure);
1382 }
1383
1384 APMFirmwarePlugin::_setBaroAltOffset(vehicle, offset);
1385 });
1386
1387 const QMetaObject::Connection baroTempUpdater =
1389 vehicle, [vehicle](qreal temperature) {
1390 if (!vehicle || !vehicle->airborne()) {
1391 return;
1392 }
1393
1394 if (qFuzzyIsNull(temperature)) {
1395 return;
1396 }
1397
1398 APMFirmwarePlugin::_setBaroGndTemp(vehicle, temperature);
1399 });
1400
1401 return QPair<QMetaObject::Connection,QMetaObject::Connection>(baroPressureUpdater, baroTempUpdater);
1402}
1403
1404bool APMFirmwarePlugin::stopCompensatingBaro(const Vehicle *vehicle, QPair<QMetaObject::Connection,QMetaObject::Connection> updaters)
1405{
1406 Q_UNUSED(vehicle);
1407 /*if (!vehicle) {
1408 return false;
1409 }*/
1410
1411 bool result = false;
1412
1413 if (updaters.first) {
1414 result |= QObject::disconnect(updaters.first);
1415 }
1416
1417 if (updaters.second) {
1418 result |= QObject::disconnect(updaters.second);
1419 }
1420
1421 return result;
1422}
1423
1424QVariant APMFirmwarePlugin::expandedToolbarIndicatorSource(const Vehicle* vehicle, const QString& indicatorName) const
1425{
1426 if (indicatorName == "Battery") {
1427 return QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/FirmwarePlugin/APM/APMBatteryIndicator.qml"));
1428 } else if (indicatorName == "FlightMode" && vehicle->multiRotor()) {
1429 return QVariant::fromValue(QUrl::fromUserInput("qrc:/qml/QGroundControl/FirmwarePlugin/APM/APMFlightModeIndicator.qml"));
1430 }
1431
1432 return QVariant();
1433}
static void _MAV_CMD_DO_REPOSITION_ResultHandler(void *resultHandlerData, int, const mavlink_command_ack_t &ack, Vehicle::MavCmdResultFailureCode_t)
QList< FirmwareFlightMode > FlightModeList
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
#define qgcApp()
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
This is the AutoPilotPlugin implementation for the MAV_AUTOPILOT_ARDUPILOT type.
This is the base class for all stack specific APM firmware plugins.
bool isGuidedMode(const Vehicle *vehicle) const override
Returns whether the vehicle is in guided mode or not.
QVariant expandedToolbarIndicatorSource(const Vehicle *vehicle, const QString &indicatorName) const override
void setGuidedMode(Vehicle *vehicle, bool guidedMode) const override
Set guided flight mode.
QString flightMode(uint8_t base_mode, uint32_t custom_mode) const override
QString rtlFlightMode() const override
Returns the flight mode for RTL.
bool setFlightMode(const QString &flightMode, uint8_t *base_mode, uint32_t *custom_mode) const override
static qreal calcAltOffsetPT(uint32_t atmospheric1, qreal temperature1, uint32_t atmospheric2, qreal temperature2)
QString getHobbsMeter(Vehicle *vehicle) const override
gets hobbs meter from autopilot. This should be reimplmeented for each firmware
void sendGCSMotionReport(Vehicle *vehicle, const FollowMe::GCSMotionReport &motionReport, uint8_t estimatationCapabilities) const override
Sends the appropriate mavlink message for follow me support.
bool fixedWingAirSpeedLimitsAvailable(Vehicle *vehicle) const override
bool guidedModeGotoLocation(Vehicle *vehicle, const QGeoCoordinate &gotoCoord, double forwardFlightLoiterRadius) const override
static QPair< QMetaObject::Connection, QMetaObject::Connection > startCompensatingBaro(Vehicle *vehicle)
void startMission(Vehicle *vehicle) const override
Command the vehicle to start the mission.
virtual void initializeStreamRates(Vehicle *vehicle)
const QString _guidedFlightMode
QString missionCommandOverrides(QGCMAVLink::VehicleClass_t vehicleClass) const override
void guidedModeChangeGroundSpeedMetersSecond(Vehicle *vehicle, double speed) const override
void guidedModeChangeHeading(Vehicle *vehicle, const QGeoCoordinate &headingCoord) const override
Command vehicle to rotate towards specified location.
bool hasGripper(const Vehicle *vehicle) const override
double minimumTakeoffAltitudeMeters(Vehicle *vehicle) const override
QStringList flightModes(Vehicle *vehicle) const override
void guidedModeRTL(Vehicle *vehicle, bool smartRTL) const override
Command vehicle to return to launch.
bool adjustIncomingMavlinkMessage(Vehicle *vehicle, mavlink_message_t *message) override
static bool stopCompensatingBaro(const Vehicle *vehicle, QPair< QMetaObject::Connection, QMetaObject::Connection > updaters)
void guidedModeTakeoff(Vehicle *vehicle, double altitudeRel) const override
Command vehicle to takeoff from current location to the specified height.
double minimumEquivalentAirspeed(Vehicle *vehicle) const override
void startTakeoff(Vehicle *vehicle) const override
Command the vehicle to start a takeoff.
void adjustOutgoingMavlinkMessageThreadSafe(Vehicle *vehicle, LinkInterface *outgoingLink, mavlink_message_t *message) override
ParameterMetaData * _createParameterMetaData() override
QString smartRTLFlightMode() const override
Returns the flight mode for Smart RTL.
const QString _autoFlightMode
double maximumEquivalentAirspeed(Vehicle *vehicle) const override
QString missionFlightMode() const override
Returns the flight mode for running missions.
void guidedModeChangeAltitude(Vehicle *vehicle, double altitudeChange, bool pauseVehicle) override
bool mulirotorSpeedLimitsAvailable(Vehicle *vehicle) const override
QList< MAV_CMD > supportedMissionCommands(QGCMAVLink::VehicleClass_t vehicleClass) const override
List of supported mission commands. Empty list for all commands supported.
const QString _smartRtlFlightMode
double maximumHorizontalSpeedMultirotorMetersSecond(Vehicle *vehicle) const override
bool isCapable(const Vehicle *vehicle, FirmwareCapabilities capabilities) const override
void guidedModeChangeEquivalentAirspeedMetersSecond(Vehicle *vehicle, double airspeed_equiv) const override
static qreal calcAltOffsetP(uint32_t atmospheric1, uint32_t atmospheric2)
AutoPilotPlugin * autopilotPlugin(Vehicle *vehicle) const override
virtual QString guidedFlightMode() const
const QVariantList & toolIndicators(const Vehicle *vehicle) override
QString _internalParameterMetaDataFile(const Vehicle *vehicle) const override
void pauseVehicle(Vehicle *vehicle) const override
void initializeVehicle(Vehicle *vehicle) override
Called when Vehicle is first created to perform any firmware specific setup.
const QString _rtlFlightMode
The AutoPilotPlugin class is an abstract base class which represents the methods and objects which ar...
A Fact is used to hold a single value within the system.
Definition Fact.h:15
void setRawValue(const QVariant &value)
Definition Fact.cc:135
QVariant rawValue() const
Definition Fact.h:87
The FirmwarePlugin class represents the methods and objects which are specific to a certain Firmware ...
virtual QString pauseFlightMode() const
Returns The flight mode which indicates the vehicle is paused.
FlightModeList _flightModeList
virtual double minimumEquivalentAirspeed(Vehicle *) const
virtual const QVariantList & toolIndicators(const Vehicle *vehicle)
FirmwareCapabilities
Set of optional capabilites which firmware may support.
@ ChangeHeadingCapability
Vehicle supports changing heading at current location.
@ GuidedTakeoffCapability
Vehicle supports guided takeoff.
@ TakeoffVehicleCapability
Vehicle supports taking off.
@ GuidedModeCapability
Vehicle supports guided mode commands.
@ SetFlightModeCapability
FirmwarePlugin::setFlightMode method is supported.
@ ROIModeCapability
Vehicle supports ROI (both in Fly guided mode and from Plan creation)
@ PauseVehicleCapability
Vehicle supports pausing at current location.
virtual double maximumEquivalentAirspeed(Vehicle *) const
bool _setFlightModeAndValidate(Vehicle *vehicle, const QString &flightMode) const
virtual uint32_t _convertToCustomFlightModeEnum(uint32_t val) const
bool _armVehicleAndValidate(Vehicle *vehicle) const
virtual QString takeOffFlightMode() const
Returns the flight mode for TakeOff.
FlightModeCustomModeMap _modeEnumToString
virtual QString offlineEditingParamFile(Vehicle *) const
Return the resource file which contains the set of params loaded for offline editing.
virtual double maximumHorizontalSpeedMultirotorMetersSecond(Vehicle *) const
virtual double minimumTakeoffAltitudeMeters(Vehicle *) const
@ HEADING
Definition FollowMe.h:38
The link interface defines the interface for all links used to communicate with the ground station ap...
uint8_t mavlinkChannel() const
uint8_t allocateMavlinkChannel()
static LinkManager * instance()
static constexpr uint8_t invalidMavlinkChannel()
static int getComponentId()
static MAVLinkProtocol * instance()
int getSystemId() const
void writeArduPilotGuidedMissionItem(const QGeoCoordinate &gotoCoord, bool altChangeOnly)
bool parameterExists(int componentId, const QString &paramName) const
Fact * getParameter(int componentId, const QString &paramName)
static constexpr int defaultComponentId
static QGCAmbientTemperature * instance()
Definition QGCSensors.cc:15
void temperatureUpdated(qreal temperature)
static QGCPressure * instance()
void pressureUpdated(qreal pressure, qreal temperature)
static SettingsManager * instance()
APMMavlinkStreamRateSettings * apmMavlinkStreamRateSettings() const
static QString getMessageText(const mavlink_message_t &message)
WeakLinkInterfacePtr primaryLink() const
bool sub() const
Definition Vehicle.cc:1765
void sendMavCommand(int compId, MAV_CMD command, bool showError, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, float param5=0.0f, float param6=0.0f, float param7=0.0f)
Definition Vehicle.cc:2173
qreal getInitialGCSPressure() const
Definition Vehicle.h:430
QString flightMode() const
Definition Vehicle.cc:1485
bool vtol() const
Definition Vehicle.cc:1780
void requestDataStream(MAV_DATA_STREAM stream, uint16_t rate, bool sendMultiple=true)
Definition Vehicle.cc:1546
QGeoCoordinate homePosition()
Definition Vehicle.cc:1451
bool multiRotor() const
Definition Vehicle.cc:1775
void setFirmwarePluginInstanceData(FirmwarePluginInstanceData *firmwarePluginInstanceData)
Definition Vehicle.cc:2594
bool airborne() const
Definition Vehicle.h:521
MAV_TYPE vehicleType() const
Definition Vehicle.h:446
VehicleLinkManager * vehicleLinkManager()
Definition Vehicle.h:597
FirmwarePlugin * firmwarePlugin()
Provides access to the Firmware Plugin for this Vehicle.
Definition Vehicle.h:462
qreal getInitialGCSTemperature() const
Definition Vehicle.h:431
int firmwareMinorVersion() const
Definition Vehicle.h:680
bool isOfflineEditingVehicle() const
Definition Vehicle.h:532
int id() const
Definition Vehicle.h:443
bool sendMessageOnLinkThreadSafe(LinkInterface *link, mavlink_message_t message)
Definition Vehicle.cc:1401
void _setUnderway(bool underway)
Definition Vehicle.cc:1824
int defaultComponentId() const
Definition Vehicle.h:700
ParameterManager * parameterManager()
Definition Vehicle.h:595
bool underway() const
Definition Vehicle.h:519
void setFirmwareVersion(int majorVersion, int minorVersion, int patchVersion, FIRMWARE_VERSION_TYPE versionType=FIRMWARE_VERSION_TYPE_OFFICIAL)
Definition Vehicle.cc:2302
bool fixedWing() const
Definition Vehicle.cc:1755
QGeoCoordinate coordinate()
Definition Vehicle.h:427
void sendMavCommandIntWithHandler(const MavCmdAckHandlerInfo_t *ackHandlerInfo, int compId, MAV_CMD command, MAV_FRAME frame, float param1=0.0f, float param2=0.0f, float param3=0.0f, float param4=0.0f, double param5=0.0f, double param6=0.0f, float param7=0.0f)
Definition Vehicle.cc:2207
bool armed() const
Definition Vehicle.h:467
class FirmwarePluginInstanceData * firmwarePluginInstanceData()
Definition Vehicle.h:715
int firmwareMajorVersion() const
Definition Vehicle.h:679
MissionManager * missionManager()
Definition Vehicle.h:592
static constexpr const char * videoSource3DRSolo
QByteArray line(std::string_view command)
command as one CR/LF-terminated line.
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.
Definition AppMessages.cc:9
static constexpr VehicleClass_t VehicleClassGeneric
Callback info bundle for sendMavCommandWithHandler.
MavCmdResultHandler resultHandler
nullptr for no handler