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