Newer
Older
/****************************************************************************
*
* (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "MissionController.h"
#include "MultiVehicleManager.h"
#include "MissionManager.h"
#include "CoordinateVector.h"
#include "QGCApplication.h"
#include "FixedWingLandingComplexItem.h"
#include "AppSettings.h"
#include "MissionSettingsItem.h"
#include "PlanMasterController.h"
#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes
QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")
const char* MissionController::_settingsGroup = "MissionController";
const char* MissionController::_jsonFileTypeValue = "Mission";
const char* MissionController::_jsonItemsKey = "items";
const char* MissionController::_jsonPlannedHomePositionKey = "plannedHomePosition";
const char* MissionController::_jsonFirmwareTypeKey = "firmwareType";
const char* MissionController::_jsonVehicleTypeKey = "vehicleType";
const char* MissionController::_jsonCruiseSpeedKey = "cruiseSpeed";
const char* MissionController::_jsonHoverSpeedKey = "hoverSpeed";
const char* MissionController::_jsonParamsKey = "params";
// Deprecated V1 format keys
const char* MissionController::_jsonComplexItemsKey = "complexItems";
const char* MissionController::_jsonMavAutopilotKey = "MAV_AUTOPILOT";
const int MissionController::_missionFileVersion = 2;
const QString MissionController::patternSurveyName (QT_TRANSLATE_NOOP("MissionController", "Survey"));
const QString MissionController::patternFWLandingName (QT_TRANSLATE_NOOP("MissionController", "Fixed Wing Landing"));
const QString MissionController::patternStructureScanName (QT_TRANSLATE_NOOP("MissionController", "Structure Scan"));
const QString MissionController::patternCorridorScanName (QT_TRANSLATE_NOOP("MissionController", "Corridor Scan"));
MissionController::MissionController(PlanMasterController* masterController, QObject *parent)
: PlanElementController (masterController, parent)
, _missionManager (_managerVehicle->missionManager())
, _missionItemCount (0)
, _visualItems (nullptr)
, _settingsItem (nullptr)
, _firstItemsFromVehicle (false)
, _itemsRequested (false)
, _inRecalcSequence (false)
, _appSettings (qgcApp()->toolbox()->settingsManager()->appSettings())
, _progressPct (0)
, _currentPlanViewIndex (-1)
, _currentPlanViewItem (nullptr)
managerVehicleChanged(_managerVehicle);
_updateTimer.setSingleShot(true);
connect(&_updateTimer, &QTimer::timeout, this, &MissionController::_updateTimeout);
}
MissionController::~MissionController()
{
void MissionController::_resetMissionFlightStatus(void)
{
_missionFlightStatus.totalDistance = 0.0;
_missionFlightStatus.maxTelemetryDistance = 0.0;
_missionFlightStatus.totalTime = 0.0;
_missionFlightStatus.hoverTime = 0.0;
_missionFlightStatus.cruiseTime = 0.0;
_missionFlightStatus.hoverDistance = 0.0;
_missionFlightStatus.cruiseDistance = 0.0;
_missionFlightStatus.cruiseSpeed = _controllerVehicle->defaultCruiseSpeed();
_missionFlightStatus.hoverSpeed = _controllerVehicle->defaultHoverSpeed();
_missionFlightStatus.vehicleSpeed = _controllerVehicle->multiRotor() || _managerVehicle->vtol() ? _missionFlightStatus.hoverSpeed : _missionFlightStatus.cruiseSpeed;
_missionFlightStatus.vehicleYaw = 0.0;
_missionFlightStatus.gimbalYaw = std::numeric_limits<double>::quiet_NaN();
_missionFlightStatus.gimbalPitch = std::numeric_limits<double>::quiet_NaN();
// Battery information
_missionFlightStatus.mAhBattery = 0;
_missionFlightStatus.hoverAmps = 0;
_missionFlightStatus.cruiseAmps = 0;
_missionFlightStatus.ampMinutesAvailable = 0;
_missionFlightStatus.hoverAmpsTotal = 0;
_missionFlightStatus.cruiseAmpsTotal = 0;
_missionFlightStatus.batteryChangePoint = -1;
_missionFlightStatus.batteriesRequired = -1;
_controllerVehicle->firmwarePlugin()->batteryConsumptionData(_controllerVehicle, _missionFlightStatus.mAhBattery, _missionFlightStatus.hoverAmps, _missionFlightStatus.cruiseAmps);
if (_missionFlightStatus.mAhBattery != 0) {
double batteryPercentRemainingAnnounce = qgcApp()->toolbox()->settingsManager()->appSettings()->batteryPercentRemainingAnnounce()->rawValue().toDouble();
_missionFlightStatus.ampMinutesAvailable = static_cast<double>(_missionFlightStatus.mAhBattery) / 1000.0 * 60.0 * ((100.0 - batteryPercentRemainingAnnounce) / 100.0);
emit missionDistanceChanged(_missionFlightStatus.totalDistance);
emit missionTimeChanged();
emit missionHoverDistanceChanged(_missionFlightStatus.hoverDistance);
emit missionCruiseDistanceChanged(_missionFlightStatus.cruiseDistance);
emit missionHoverTimeChanged();
emit missionCruiseTimeChanged();
emit missionMaxTelemetryChanged(_missionFlightStatus.maxTelemetryDistance);
emit batteryChangePointChanged(_missionFlightStatus.batteryChangePoint);
emit batteriesRequiredChanged(_missionFlightStatus.batteriesRequired);
void MissionController::start(bool flyView)
qCDebug(MissionControllerLog) << "start flyView" << flyView;
_init();
}
void MissionController::_init(void)
{
_visualItems = new QmlObjectListModel(this);
_addMissionSettings(_visualItems, false /* addToCenter */);
// Called when new mission items have completed downloading from Vehicle
void MissionController::_newMissionItemsAvailableFromVehicle(bool removeAllRequested)
qCDebug(MissionControllerLog) << "_newMissionItemsAvailableFromVehicle flyView:count" << _flyView << _missionManager->missionItems().count();
// Plan view only reloads if:
// - Load was specifically requested
// - There is no current Plan
if (_flyView || removeAllRequested || _itemsRequested || isEmpty()) {
// Fly Mode (accept if):
// - Always accepts new items from the vehicle so Fly view is kept up to date
// Edit Mode (accept if):
// - Remove all was requested from Fly view (clear mission on flight end)
// - A load from vehicle was manually requested
// - The initial automatic load from a vehicle completed and the current editor is empty
QmlObjectListModel* newControllerMissionItems = new QmlObjectListModel(this);
const QList<MissionItem*>& newMissionItems = _missionManager->missionItems();
qCDebug(MissionControllerLog) << "loading from vehicle: count"<< newMissionItems.count();
_missionItemCount = newMissionItems.count();
emit missionItemCountChanged(_missionItemCount);
if (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() && newMissionItems.count() != 0) {
// First item is fake home position
_addMissionSettings(newControllerMissionItems, false /* addToCenter */);
MissionSettingsItem* settingsItem = newControllerMissionItems->value<MissionSettingsItem*>(0);
if (!settingsItem) {
qWarning() << "First item is not settings item";
return;
}
MissionItem* fakeHomeItem = newMissionItems[0];
if (fakeHomeItem->coordinate().latitude() != 0 || fakeHomeItem->coordinate().longitude() != 0) {
settingsItem->setCoordinate(fakeHomeItem->coordinate());
}
for (; i < newMissionItems.count(); i++) {
const MissionItem* missionItem = newMissionItems[i];
newControllerMissionItems->append(new SimpleMissionItem(_controllerVehicle, _flyView, *missionItem, this));
_settingsItem = nullptr;
_visualItems = nullptr;
_updateContainsItems(); // This will clear containsItems which will be set again below. This will re-pop Start Mission confirmation.
_visualItems = newControllerMissionItems;
if (!_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() || _visualItems->count() == 0) {
_addMissionSettings(_visualItems, !_flyView && _visualItems->count() > 0 /* addToCenter */);
MissionController::_scanForAdditionalSettings(_visualItems, _controllerVehicle);
_updateContainsItems();
void MissionController::loadFromVehicle(void)
if (_masterController->offline()) {
qCWarning(MissionControllerLog) << "MissionControllerLog::loadFromVehicle called while offline";
} else if (syncInProgress()) {
qCWarning(MissionControllerLog) << "MissionControllerLog::loadFromVehicle called while syncInProgress";
} else {
_itemsRequested = true;
_managerVehicle->missionManager()->loadFromVehicle();
}
void MissionController::_warnIfTerrainFrameUsed(void)
{
for (int i=1; i<_visualItems->count(); i++) {
SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(_visualItems->get(i));
if (simpleItem && simpleItem->altitudeMode() == QGroundControlQmlGlobal::AltitudeModeTerrainFrame) {
qgcApp()->showMessage(tr("Warning: You are using MAV_FRAME_GLOBAL_TERRAIN_ALT in a mission. %1 does not support sending terrain tiles to vehicle.").arg(qgcApp()->applicationName()));
break;
}
}
}
if (_masterController->offline()) {
qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while offline";
} else if (syncInProgress()) {
qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while syncInProgress";
} else {
qCDebug(MissionControllerLog) << "MissionControllerLog::sendToVehicle";
if (_visualItems->count() == 1) {
// This prevents us from sending a possibly bogus home position to the vehicle
QmlObjectListModel emptyModel;
sendItemsToVehicle(_managerVehicle, &emptyModel);
} else {
sendItemsToVehicle(_managerVehicle, _visualItems);
}
setDirty(false);
}
/// Converts from visual items to MissionItems
/// @param missionItemParent QObject parent for newly allocated MissionItems
/// @return true: Mission end action was added to end of list
bool MissionController::_convertToMissionItems(QmlObjectListModel* visualMissionItems, QList<MissionItem*>& rgMissionItems, QObject* missionItemParent)
{
if (visualMissionItems->count() == 0) {
return false;
}
bool endActionSet = false;
int lastSeqNum = 0;
for (int i=0; i<visualMissionItems->count(); i++) {
VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(visualMissionItems->get(i));
lastSeqNum = visualItem->lastSequenceNumber();
visualItem->appendMissionItems(rgMissionItems, missionItemParent);
qCDebug(MissionControllerLog) << "_convertToMissionItems seqNum:lastSeqNum:command"
<< visualItem->sequenceNumber()
<< lastSeqNum
<< visualItem->commandName();
}
// Mission settings has a special case for end mission action
MissionSettingsItem* settingsItem = visualMissionItems->value<MissionSettingsItem*>(0);
if (settingsItem) {
endActionSet = settingsItem->addMissionEndAction(rgMissionItems, lastSeqNum + 1, missionItemParent);
}
return endActionSet;
}
void MissionController::convertToKMLDocument(QDomDocument& document)
{
QObject* deleteParent = new QObject();
QList<MissionItem*> rgMissionItems;
_convertToMissionItems(_visualItems, rgMissionItems, deleteParent);
if (rgMissionItems.count() == 0) {
const double homePositionAltitude = _settingsItem->coordinate().altitude();
QString coord;
QStringList coords;
// Drop home position
bool dropPoint = true;
for(const auto& item : rgMissionItems) {
if(dropPoint) {
dropPoint = false;
continue;
}
const MissionCommandUIInfo* uiInfo = \
qgcApp()->toolbox()->missionCommandTree()->getUIInfo(_controllerVehicle, item->command());
if (uiInfo && uiInfo->specifiesCoordinate() && !uiInfo->isStandaloneCoordinate()) {
double amslAltitude = item->param7() + (item->frame() == MAV_FRAME_GLOBAL ? 0 : homePositionAltitude);
+ "," \
+ QString::number(item->param5(),'f',7) \
+ "," \
+ QString::number(amslAltitude,'f',2);
deleteParent->deleteLater();
Kml kml;
kml.points(coords);
kml.save(document);
}
void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems)
{
if (vehicle) {
_convertToMissionItems(visualMissionItems, rgMissionItems, vehicle);
// PlanManager takes control of MissionItems so no need to delete
vehicle->missionManager()->writeMissionItems(rgMissionItems);
int MissionController::_nextSequenceNumber(void)
{
if (_visualItems->count() == 0) {
qWarning() << "Internal error: Empty visual item list";
return 0;
} else {
VisualMissionItem* lastItem = _visualItems->value<VisualMissionItem*>(_visualItems->count() - 1);
return lastItem->lastSequenceNumber() + 1;
VisualMissionItem* MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
SimpleMissionItem * newItem = new SimpleMissionItem(_controllerVehicle, _flyView, this);
if (_visualItems->count() == 1 && (_controllerVehicle->fixedWing() || _controllerVehicle->vtol() || _controllerVehicle->multiRotor())) {
MAV_CMD takeoffCmd = _controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_TAKEOFF : MAV_CMD_NAV_TAKEOFF;
if (_controllerVehicle->firmwarePlugin()->supportedMissionCommands().contains(takeoffCmd)) {
newItem->setCommand(takeoffCmd);
}
if (newItem->specifiesAltitude()) {
double prevAltitude;
int prevAltitudeMode;
if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) {
newItem->altitude()->setRawValue(prevAltitude);
newItem->setAltitudeMode(static_cast<QGroundControlQmlGlobal::AltitudeMode>(prevAltitudeMode));
newItem->setMissionFlightStatus(_missionFlightStatus);
if (visualItemIndex == -1) {
_visualItems->append(newItem);
} else {
_visualItems->insert(visualItemIndex, newItem);
}
// We send the click coordinate through here to be able to set the planned home position from the user click location if needed
_recalcAllWithClickCoordinate(coordinate);
if (makeCurrentItem) {
setCurrentPlanViewIndex(newItem->sequenceNumber(), true);
}
return newItem;
VisualMissionItem* MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
{
int sequenceNumber = _nextSequenceNumber();
SimpleMissionItem * newItem = new SimpleMissionItem(_controllerVehicle, _flyView, this);
newItem->setSequenceNumber(sequenceNumber);
newItem->setCommand((_controllerVehicle->firmwarePlugin()->supportedMissionCommands().contains(MAV_CMD_DO_SET_ROI_LOCATION) ?
MAV_CMD_DO_SET_ROI_LOCATION :
MAV_CMD_DO_SET_ROI));
double prevAltitude;
int prevAltitudeMode;
if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) {
newItem->altitude()->setRawValue(prevAltitude);
newItem->setAltitudeMode(static_cast<QGroundControlQmlGlobal::AltitudeMode>(prevAltitudeMode));
if (visualItemIndex == -1) {
_visualItems->append(newItem);
} else {
_visualItems->insert(visualItemIndex, newItem);
}
if (makeCurrentItem) {
setCurrentPlanViewIndex(newItem->sequenceNumber(), true);
}
return newItem;
VisualMissionItem* MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int visualItemIndex, bool makeCurrentItem)
// If the ComplexMissionItem is inserted first, add a TakeOff SimpleMissionItem
if (_visualItems->count() == 1 && (_controllerVehicle->fixedWing() || _controllerVehicle->vtol() || _controllerVehicle->multiRotor())) {
insertSimpleMissionItem(mapCenterCoordinate, visualItemIndex);
visualItemIndex++;
newItem = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
newItem->setCoordinate(mapCenterCoordinate);
newItem = new FixedWingLandingComplexItem(_controllerVehicle, _flyView, _visualItems /* parent */);
newItem = new StructureScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
newItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
} else {
qWarning() << "Internal error: Unknown complex item:" << itemName;
_insertComplexMissionItemWorker(newItem, visualItemIndex, makeCurrentItem);
return newItem;
VisualMissionItem* MissionController::insertComplexMissionItemFromKMLOrSHP(QString itemName, QString file, int visualItemIndex, bool makeCurrentItem)
newItem = new SurveyComplexItem(_controllerVehicle, _flyView, file, _visualItems);
newItem = new StructureScanComplexItem(_controllerVehicle, _flyView, file, _visualItems);
newItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, file, _visualItems);
} else {
qWarning() << "Internal error: Unknown complex item:" << itemName;
_insertComplexMissionItemWorker(newItem, visualItemIndex, makeCurrentItem);
return newItem;
void MissionController::_insertComplexMissionItemWorker(ComplexMissionItem* complexItem, int visualItemIndex, bool makeCurrentItem)
{
int sequenceNumber = _nextSequenceNumber();
bool surveyStyleItem = qobject_cast<SurveyComplexItem*>(complexItem) ||
qobject_cast<CorridorScanComplexItem*>(complexItem) ||
qobject_cast<StructureScanComplexItem*>(complexItem);
if (surveyStyleItem) {
bool pitchSupported = false;
// If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set
MissionSettingsItem* settingsItem = _visualItems->value<MissionSettingsItem*>(0);
CameraSection* cameraSection = settingsItem->cameraSection();
// Set camera to photo mode (leave alone if user already specified)
if (cameraSection->cameraModeSupported() && !cameraSection->specifyCameraMode()) {
cameraSection->setSpecifyCameraMode(true);
cameraSection->cameraMode()->setRawValue(CAMERA_MODE_IMAGE_SURVEY);
if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) {
// If the user already specified a gimbal angle leave it alone
if (!cameraSection->specifyGimbal()) {
cameraSection->setSpecifyGimbal(true);
cameraSection->gimbalPitch()->setRawValue(-90.0);
complexItem->setSequenceNumber(sequenceNumber);
_initVisualItem(complexItem);
_visualItems->append(complexItem);
} else {
//-- Keep track of bounding box changes in complex items
if(!complexItem->isSimpleItem()) {
connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, &MissionController::_complexBoundingBoxChanged);
if (makeCurrentItem) {
setCurrentPlanViewIndex(complexItem->sequenceNumber(), true);
}
void MissionController::removeMissionItem(int index)
{
if (index <= 0 || index >= _visualItems->count()) {
qWarning() << "MissionController::removeMissionItem called with bad index - count:index" << _visualItems->count() << index;
return;
}
bool removeSurveyStyle = _visualItems->value<SurveyComplexItem*>(index) || _visualItems->value<CorridorScanComplexItem*>(index);
VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->removeAt(index));
if (removeSurveyStyle) {
// Determine if the mission still has another survey style item in it
bool foundSurvey = false;
for (int i=1; i<_visualItems->count(); i++) {
if (_visualItems->value<SurveyComplexItem*>(i) || _visualItems->value<CorridorScanComplexItem*>(i)) {
foundSurvey = true;
break;
}
}
// If there is no longer a survey item in the mission remove added commands
if (!foundSurvey) {
bool rollSupported = false;
bool pitchSupported = false;
bool yawSupported = false;
MissionSettingsItem* settingsItem = _visualItems->value<MissionSettingsItem*>(0);
CameraSection* cameraSection = settingsItem->cameraSection();
if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) {
if (cameraSection->specifyGimbal() && cameraSection->gimbalPitch()->rawValue().toDouble() == -90.0 && cameraSection->gimbalYaw()->rawValue().toDouble() == 0.0) {
cameraSection->setSpecifyGimbal(false);
}
}
if (cameraSection->cameraModeSupported() && cameraSection->specifyCameraMode() && cameraSection->cameraMode()->rawValue().toInt() == 0) {
cameraSection->setSpecifyCameraMode(false);
}
if (_visualItems) {
_deinitAllVisualItems();
_visualItems = new QmlObjectListModel(this);
_addMissionSettings(_visualItems, false /* addToCenter */);
_resetMissionFlightStatus();
bool MissionController::_loadJsonMissionFileV1(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
{
// Validate root object keys
QList<JsonHelper::KeyValidateInfo> rootKeyInfoList = {
{ _jsonPlannedHomePositionKey, QJsonValue::Object, true },
{ _jsonItemsKey, QJsonValue::Array, true },
{ _jsonMavAutopilotKey, QJsonValue::Double, true },
{ _jsonComplexItemsKey, QJsonValue::Array, true },
};
if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) {
QList<SurveyComplexItem*> surveyItems;
QJsonArray complexArray(json[_jsonComplexItemsKey].toArray());
qCDebug(MissionControllerLog) << "Json load: complex item count" << complexArray.count();
for (int i=0; i<complexArray.count(); i++) {
const QJsonValue& itemValue = complexArray[i];
if (!itemValue.isObject()) {
errorString = QStringLiteral("Mission item is not an object");
return false;
}
SurveyComplexItem* item = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems /* parent */);
const QJsonObject itemObject = itemValue.toObject();
if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
// Read simple items, interspersing complex items into the full list
int nextSimpleItemIndex= 0;
int nextComplexItemIndex= 0;
int nextSequenceNumber = 1; // Start with 1 since home is in 0
QJsonArray itemArray(json[_jsonItemsKey].toArray());
qCDebug(MissionControllerLog) << "Json load: simple item loop start simpleItemCount:ComplexItemCount" << itemArray.count() << surveyItems.count();
do {
qCDebug(MissionControllerLog) << "Json load: simple item loop nextSimpleItemIndex:nextComplexItemIndex:nextSequenceNumber" << nextSimpleItemIndex << nextComplexItemIndex << nextSequenceNumber;
// If there is a complex item that should be next in sequence add it in
if (nextComplexItemIndex < surveyItems.count()) {
SurveyComplexItem* complexItem = surveyItems[nextComplexItemIndex];
if (complexItem->sequenceNumber() == nextSequenceNumber) {
qCDebug(MissionControllerLog) << "Json load: injecting complex item expectedSequence:actualSequence:" << nextSequenceNumber << complexItem->sequenceNumber();
visualItems->append(complexItem);
nextSequenceNumber = complexItem->lastSequenceNumber() + 1;
nextComplexItemIndex++;
continue;
}
}
// Add the next available simple item
if (nextSimpleItemIndex < itemArray.count()) {
const QJsonValue& itemValue = itemArray[nextSimpleItemIndex++];
if (!itemValue.isObject()) {
errorString = QStringLiteral("Mission item is not an object");
return false;
}
const QJsonObject itemObject = itemValue.toObject();
SimpleMissionItem* item = new SimpleMissionItem(_controllerVehicle, _flyView, visualItems);
if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
qCDebug(MissionControllerLog) << "Json load: adding simple item expectedSequence:actualSequence" << nextSequenceNumber << item->sequenceNumber();
nextSequenceNumber = item->lastSequenceNumber() + 1;
} while (nextSimpleItemIndex < itemArray.count() || nextComplexItemIndex < surveyItems.count());
if (json.contains(_jsonPlannedHomePositionKey)) {
SimpleMissionItem* item = new SimpleMissionItem(_controllerVehicle, _flyView, visualItems);
if (item->load(json[_jsonPlannedHomePositionKey].toObject(), 0, errorString)) {
MissionSettingsItem* settingsItem = new MissionSettingsItem(_controllerVehicle, _flyView, visualItems);
settingsItem->setCoordinate(item->coordinate());
visualItems->insert(0, settingsItem);
item->deleteLater();
_addMissionSettings(visualItems, true /* addToCenter */);
bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
{
// Validate root object keys
QList<JsonHelper::KeyValidateInfo> rootKeyInfoList = {
{ _jsonPlannedHomePositionKey, QJsonValue::Array, true },
{ _jsonItemsKey, QJsonValue::Array, true },
{ _jsonFirmwareTypeKey, QJsonValue::Double, true },
{ _jsonVehicleTypeKey, QJsonValue::Double, false },
{ _jsonCruiseSpeedKey, QJsonValue::Double, false },
{ _jsonHoverSpeedKey, QJsonValue::Double, false },
};
if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) {
return false;
}
qCDebug(MissionControllerLog) << "MissionController::_loadJsonMissionFileV2 itemCount:" << json[_jsonItemsKey].toArray().count();
AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings();
if (_masterController->offline()) {
// We only update if offline since if we are online we use the online vehicle settings
appSettings->offlineEditingFirmwareType()->setRawValue(AppSettings::offlineEditingFirmwareTypeFromFirmwareType(static_cast<MAV_AUTOPILOT>(json[_jsonFirmwareTypeKey].toInt())));
if (json.contains(_jsonVehicleTypeKey)) {
appSettings->offlineEditingVehicleType()->setRawValue(AppSettings::offlineEditingVehicleTypeFromVehicleType(static_cast<MAV_TYPE>(json[_jsonVehicleTypeKey].toInt())));
if (json.contains(_jsonCruiseSpeedKey)) {
appSettings->offlineEditingCruiseSpeed()->setRawValue(json[_jsonCruiseSpeedKey].toDouble());
}
if (json.contains(_jsonHoverSpeedKey)) {
appSettings->offlineEditingHoverSpeed()->setRawValue(json[_jsonHoverSpeedKey].toDouble());
QGeoCoordinate homeCoordinate;
if (!JsonHelper::loadGeoCoordinate(json[_jsonPlannedHomePositionKey], true /* altitudeRequired */, homeCoordinate, errorString)) {
return false;
}
MissionSettingsItem* settingsItem = new MissionSettingsItem(_controllerVehicle, _flyView, visualItems);
settingsItem->setCoordinate(homeCoordinate);
visualItems->insert(0, settingsItem);
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
qCDebug(MissionControllerLog) << "plannedHomePosition" << homeCoordinate;
// Read mission items
int nextSequenceNumber = 1; // Start with 1 since home is in 0
const QJsonArray rgMissionItems(json[_jsonItemsKey].toArray());
for (int i=0; i<rgMissionItems.count(); i++) {
// Convert to QJsonObject
const QJsonValue& itemValue = rgMissionItems[i];
if (!itemValue.isObject()) {
errorString = tr("Mission item %1 is not an object").arg(i);
return false;
}
const QJsonObject itemObject = itemValue.toObject();
// Load item based on type
QList<JsonHelper::KeyValidateInfo> itemKeyInfoList = {
{ VisualMissionItem::jsonTypeKey, QJsonValue::String, true },
};
if (!JsonHelper::validateKeys(itemObject, itemKeyInfoList, errorString)) {
return false;
}
QString itemType = itemObject[VisualMissionItem::jsonTypeKey].toString();
if (itemType == VisualMissionItem::jsonTypeSimpleItemValue) {
SimpleMissionItem* simpleItem = new SimpleMissionItem(_controllerVehicle, _flyView, visualItems);
if (simpleItem->load(itemObject, nextSequenceNumber, errorString)) {
qCDebug(MissionControllerLog) << "Loading simple item: nextSequenceNumber:command" << nextSequenceNumber << simpleItem->command();
nextSequenceNumber = simpleItem->lastSequenceNumber() + 1;
visualItems->append(simpleItem);
} else {
return false;
}
} else if (itemType == VisualMissionItem::jsonTypeComplexItemValue) {
QList<JsonHelper::KeyValidateInfo> complexItemKeyInfoList = {
{ ComplexMissionItem::jsonComplexItemTypeKey, QJsonValue::String, true },
};
if (!JsonHelper::validateKeys(itemObject, complexItemKeyInfoList, errorString)) {
return false;
}
QString complexItemType = itemObject[ComplexMissionItem::jsonComplexItemTypeKey].toString();
if (complexItemType == SurveyComplexItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Survey: nextSequenceNumber" << nextSequenceNumber;
SurveyComplexItem* surveyItem = new SurveyComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems);
if (!surveyItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = surveyItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Survey load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(surveyItem);
} else if (complexItemType == FixedWingLandingComplexItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Fixed Wing Landing Pattern: nextSequenceNumber" << nextSequenceNumber;
FixedWingLandingComplexItem* landingItem = new FixedWingLandingComplexItem(_controllerVehicle, _flyView, visualItems);
if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = landingItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "FW Landing Pattern load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(landingItem);
} else if (complexItemType == StructureScanComplexItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Structure Scan: nextSequenceNumber" << nextSequenceNumber;
StructureScanComplexItem* structureItem = new StructureScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems);
if (!structureItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = structureItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Structure Scan load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(structureItem);
} else if (complexItemType == CorridorScanComplexItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Corridor Scan: nextSequenceNumber" << nextSequenceNumber;
CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_controllerVehicle, _flyView, QString() /* kmlFile */, visualItems);
if (!corridorItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = corridorItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Corridor Scan load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(corridorItem);
} else if (complexItemType == MissionSettingsItem::jsonComplexItemTypeValue) {
qCDebug(MissionControllerLog) << "Loading Mission Settings: nextSequenceNumber" << nextSequenceNumber;
MissionSettingsItem* settingsItem = new MissionSettingsItem(_controllerVehicle, _flyView, visualItems);
if (!settingsItem->load(itemObject, nextSequenceNumber++, errorString)) {
return false;
}
nextSequenceNumber = settingsItem->lastSequenceNumber() + 1;
qCDebug(MissionControllerLog) << "Mission Settings load complete: nextSequenceNumber" << nextSequenceNumber;
visualItems->append(settingsItem);
} else {
errorString = tr("Unsupported complex item type: %1").arg(complexItemType);
}
} else {
errorString = tr("Unknown item type: %1").arg(itemType);
return false;
}
}
// Fix up the DO_JUMP commands jump sequence number by finding the item with the matching doJumpId
for (int i=0; i<visualItems->count(); i++) {
if (visualItems->value<VisualMissionItem*>(i)->isSimpleItem()) {
SimpleMissionItem* doJumpItem = visualItems->value<SimpleMissionItem*>(i);
if (doJumpItem->command() == MAV_CMD_DO_JUMP) {
int findDoJumpId = static_cast<int>(doJumpItem->missionItem().param1());
for (int j=0; j<visualItems->count(); j++) {
if (visualItems->value<VisualMissionItem*>(j)->isSimpleItem()) {
SimpleMissionItem* targetItem = visualItems->value<SimpleMissionItem*>(j);
if (targetItem->missionItem().doJumpId() == findDoJumpId) {
doJumpItem->missionItem().setParam1(targetItem->sequenceNumber());
found = true;
break;
}
}
}
if (!found) {
errorString = tr("Could not find doJumpId: %1").arg(findDoJumpId);
return false;
}
}
}
}
return true;
}
bool MissionController::_loadItemsFromJson(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
{
// V1 file format has no file type key and version key is string. Convert to new format.
if (!json.contains(JsonHelper::jsonFileTypeKey)) {
json[JsonHelper::jsonFileTypeKey] = _jsonFileTypeValue;
}
int fileVersion;
JsonHelper::validateQGCJsonFile(json,
_jsonFileTypeValue, // expected file type
1, // minimum supported version
2, // maximum supported version
fileVersion,
errorString);
if (fileVersion == 1) {
return _loadJsonMissionFileV1(json, visualItems, errorString);
return _loadJsonMissionFileV2(json, visualItems, errorString);
bool MissionController::_loadTextMissionFile(QTextStream& stream, QmlObjectListModel* visualItems, QString& errorString)
bool firstItem = true;
bool plannedHomePositionInFile = false;
QString firstLine = stream.readLine();
const QStringList& version = firstLine.split(" ");
bool versionOk = false;
if (version.size() == 3 && version[0] == "QGC" && version[1] == "WPL") {
if (version[2] == "110") {
// ArduPilot file, planned home position is already in position 0
versionOk = true;
plannedHomePositionInFile = true;
} else if (version[2] == "120") {
// Old QGC file, no planned home position
versionOk = true;
plannedHomePositionInFile = false;
// Start with planned home in center
_addMissionSettings(visualItems, true /* addToCenter */);
MissionSettingsItem* settingsItem = visualItems->value<MissionSettingsItem*>(0);
SimpleMissionItem* item = new SimpleMissionItem(_controllerVehicle, _flyView, visualItems);
if (firstItem && plannedHomePositionInFile) {
settingsItem->setCoordinate(item->coordinate());
} else {
visualItems->append(item);
}
firstItem = false;
errorString = tr("The mission file is corrupted.");
errorString = tr("The mission file is not compatible with this version of %1.").arg(qgcApp()->applicationName());
if (!plannedHomePositionInFile) {
// Update sequence numbers in DO_JUMP commands to take into account added home position in index 0
for (int i=1; i<visualItems->count(); i++) {
SimpleMissionItem* item = qobject_cast<SimpleMissionItem*>(visualItems->get(i));
if (item && item->command() == MAV_CMD_DO_JUMP) {
item->missionItem().setParam1(static_cast<int>(item->missionItem().param1()) + 1);
void MissionController::_initLoadedVisualItems(QmlObjectListModel* loadedVisualItems)
if (_visualItems) {
_deinitAllVisualItems();
_visualItems->deleteLater();
_visualItems = loadedVisualItems;
_addMissionSettings(_visualItems, true /* addToCenter */);
MissionController::_scanForAdditionalSettings(_visualItems, _controllerVehicle);
bool MissionController::load(const QJsonObject& json, QString& errorString)
{
QString errorStr;
QString errorMessage = tr("Mission: %1");
QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this);
if (!_loadJsonMissionFileV2(json, loadedVisualItems, errorStr)) {
errorString = errorMessage.arg(errorStr);
return false;
}
_initLoadedVisualItems(loadedVisualItems);
return true;
}
bool MissionController::loadJsonFile(QFile& file, QString& errorString)
{
QString errorStr;
QString errorMessage = tr("Mission: %1");
QJsonDocument jsonDoc;
QByteArray bytes = file.readAll();
if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorStr)) {
errorString = errorMessage.arg(errorStr);
return false;
}
QJsonObject json = jsonDoc.object();
QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this);
if (!_loadItemsFromJson(json, loadedVisualItems, errorStr)) {
errorString = errorMessage.arg(errorStr);
return false;
_initLoadedVisualItems(loadedVisualItems);
return true;
}
bool MissionController::loadTextFile(QFile& file, QString& errorString)
{
QString errorStr;
QString errorMessage = tr("Mission: %1");
QByteArray bytes = file.readAll();
QTextStream stream(bytes);
QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this);
if (!_loadTextMissionFile(stream, loadedVisualItems, errorStr)) {
errorString = errorMessage.arg(errorStr);
return false;
}