Skip to content
MissionController.cc 105 KiB
Newer Older
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
 * (c) 2009-2020 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 "MissionCommandUIInfo.h"
#include "MissionController.h"
#include "MultiVehicleManager.h"
#include "MissionManager.h"
#include "CoordinateVector.h"
#include "FirmwarePlugin.h"
#include "QGCApplication.h"
#include "SimpleMissionItem.h"
#include "SurveyComplexItem.h"
#include "FixedWingLandingComplexItem.h"
#include "StructureScanComplexItem.h"
#include "CorridorScanComplexItem.h"
#include "JsonHelper.h"
Don Gagne's avatar
Don Gagne committed
#include "ParameterManager.h"
#include "QGroundControlQmlGlobal.h"
#include "SettingsManager.h"
#include "MissionSettingsItem.h"
#include "QGCQGeoCoordinate.h"
#include "PlanMasterController.h"
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
#include "KMLPlanDomDocument.h"
Don Gagne's avatar
 
Don Gagne committed
#include "QGCCorePlugin.h"
DonLakeFlyer's avatar
 
DonLakeFlyer committed
#include "TakeoffMissionItem.h"
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
#include "PlanViewSettings.h"
Gus Grubba's avatar
Gus Grubba committed
#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes

QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")

Don Gagne's avatar
Don Gagne committed
const char* MissionController::_settingsGroup =                 "MissionController";
Don Gagne's avatar
Don Gagne committed
const char* MissionController::_jsonFileTypeValue =             "Mission";
const char* MissionController::_jsonItemsKey =                  "items";
Don Gagne's avatar
Don Gagne committed
const char* MissionController::_jsonPlannedHomePositionKey =    "plannedHomePosition";
Don Gagne's avatar
Don Gagne committed
const char* MissionController::_jsonFirmwareTypeKey =           "firmwareType";
const char* MissionController::_jsonVehicleTypeKey =            "vehicleType";
const char* MissionController::_jsonCruiseSpeedKey =            "cruiseSpeed";
const char* MissionController::_jsonHoverSpeedKey =             "hoverSpeed";
Don Gagne's avatar
Don Gagne committed
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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
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"));
Don Gagne's avatar
 
Don Gagne committed

MissionController::MissionController(PlanMasterController* masterController, QObject *parent)
Don Gagne's avatar
 
Don Gagne committed
    : PlanElementController     (masterController, parent)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    , _controllerVehicle        (masterController->controllerVehicle())
    , _managerVehicle           (masterController->managerVehicle())
    , _missionManager           (masterController->managerVehicle()->missionManager())
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
    , _planViewSettings         (qgcApp()->toolbox()->settingsManager()->planViewSettings())
Don Gagne's avatar
 
Don Gagne committed
    , _appSettings              (qgcApp()->toolbox()->settingsManager()->appSettings())
    _resetMissionFlightStatus();
DonLakeFlyer's avatar
 
DonLakeFlyer committed

Gus Grubba's avatar
Gus Grubba committed
    _updateTimer.setSingleShot(true);
    connect(&_updateTimer, &QTimer::timeout, this, &MissionController::_updateTimeout);
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed

    connect(_planViewSettings->takeoffItemNotRequired(), &Fact::rawValueChanged, this, &MissionController::_takeoffItemNotRequiredChanged);
}

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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _managerVehicleChanged(_managerVehicle);
    connect(_masterController, &PlanMasterController::managerVehicleChanged, this, &MissionController::_managerVehicleChanged);

    PlanElementController::start(flyView);
    _init();
}

void MissionController::_init(void)
{
Don Gagne's avatar
Don Gagne committed
    // We start with an empty mission
    _visualItems = new QmlObjectListModel(this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _addMissionSettings(_visualItems);
    _initAllVisualItems();
Don Gagne's avatar
Don Gagne committed
// Called when new mission items have completed downloading from Vehicle
void MissionController::_newMissionItemsAvailableFromVehicle(bool removeAllRequested)
    qCDebug(MissionControllerLog) << "_newMissionItemsAvailableFromVehicle flyView:count" << _flyView << _missionManager->missionItems().count();
DonLakeFlyer's avatar
DonLakeFlyer committed
    // Fly view always reloads on _loadComplete
Don Gagne's avatar
 
Don Gagne committed
    // Plan view only reloads if:
    //  - Load was specifically requested
    //  - There is no current Plan
    if (_flyView || removeAllRequested || _itemsRequested || isEmpty()) {
        // Fly Mode (accept if):
Don Gagne's avatar
Don Gagne committed
        //      - 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
Don Gagne's avatar
Don Gagne committed
        //      - The initial automatic load from a vehicle completed and the current editor is empty
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _deinitAllVisualItems();
        _visualItems->deleteLater();
        _visualItems  = nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _settingsItem = nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _updateContainsItems(); // This will clear containsItems which will be set again below. This will re-pop Start Mission confirmation.

        QmlObjectListModel* newControllerMissionItems = new QmlObjectListModel(this);
        const QList<MissionItem*>& newMissionItems = _missionManager->missionItems();
        qCDebug(MissionControllerLog) << "loading from vehicle: count"<< newMissionItems.count();

Don Gagne's avatar
 
Don Gagne committed
        _missionItemCount = newMissionItems.count();
        emit missionItemCountChanged(_missionItemCount);

DonLakeFlyer's avatar
 
DonLakeFlyer committed
        MissionSettingsItem* settingsItem = _addMissionSettings(newControllerMissionItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed

        int i=0;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        if (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() && newMissionItems.count() != 0) {
            // First item is fake home position
            MissionItem* fakeHomeItem = newMissionItems[0];
            if (fakeHomeItem->coordinate().latitude() != 0 || fakeHomeItem->coordinate().longitude() != 0) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                settingsItem->setInitialHomePosition(fakeHomeItem->coordinate());
        for (; i < newMissionItems.count(); i++) {
            const MissionItem* missionItem = newMissionItems[i];
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, *missionItem, this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(simpleItem->command()))) {
                // This needs to be a TakeoffMissionItem
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(*missionItem, _masterController, _flyView, settingsItem, this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                simpleItem->deleteLater();
                simpleItem = takeoffItem;
            }
            newControllerMissionItems->append(simpleItem);
        }

        _visualItems = newControllerMissionItems;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _settingsItem = settingsItem;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        MissionController::_scanForAdditionalSettings(_visualItems, _masterController);
        _initAllVisualItems();
        _updateContainsItems();
        emit newItemsFromVehicle();
DonLakeFlyer's avatar
DonLakeFlyer committed
    _itemsRequested = false;
void MissionController::loadFromVehicle(void)
DonLakeFlyer's avatar
DonLakeFlyer committed
    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::sendToVehicle(void)
DonLakeFlyer's avatar
DonLakeFlyer committed
    if (_masterController->offline()) {
        qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(MissionControllerLog) << "MissionControllerLog::sendToVehicle called while syncInProgress";
    } else {
DonLakeFlyer's avatar
DonLakeFlyer committed
        qCDebug(MissionControllerLog) << "MissionControllerLog::sendToVehicle";
DonLakeFlyer's avatar
DonLakeFlyer committed
        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)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
    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;
}

DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
void MissionController::addMissionToKML(KMLPlanDomDocument& planKML)
    QObject*            deleteParent = new QObject();
    QList<MissionItem*> rgMissionItems;
    _convertToMissionItems(_visualItems, rgMissionItems, deleteParent);
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
    planKML.addMissionItems(_controllerVehicle, rgMissionItems);
    deleteParent->deleteLater();
Don Gagne's avatar
Don Gagne committed
void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems)
{
    if (vehicle) {
        QList<MissionItem*> rgMissionItems;
        _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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::_insertSimpleMissionItemWorker(QGeoCoordinate coordinate, MAV_CMD command, int visualItemIndex, bool makeCurrentItem)
    int sequenceNumber = _nextSequenceNumber();
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
    SimpleMissionItem * newItem = new SimpleMissionItem(_masterController, _flyView, false /* forLoad */, this);
    newItem->setSequenceNumber(sequenceNumber);
    newItem->setCoordinate(coordinate);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    newItem->setCommand(command);
    _initVisualItem(newItem);
DonLakeFlyer's avatar
 
DonLakeFlyer committed

    if (newItem->specifiesAltitude()) {
        const MissionCommandUIInfo* uiInfo = qgcApp()->toolbox()->missionCommandTree()->getUIInfo(_controllerVehicle, command);
        if (!uiInfo->isLandCommand()) {
            double  prevAltitude;
            int     prevAltitudeMode;

            if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) {
                newItem->altitude()->setRawValue(prevAltitude);
                newItem->setAltitudeMode(static_cast<QGroundControlQmlGlobal::AltitudeMode>(prevAltitudeMode));
            }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    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
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _recalcAllWithCoordinate(coordinate);
DonLakeFlyer's avatar
 
DonLakeFlyer committed

    if (makeCurrentItem) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    }

    return newItem;
}

DonLakeFlyer's avatar
 
DonLakeFlyer committed

VisualMissionItem* MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
{
    return _insertSimpleMissionItemWorker(coordinate, MAV_CMD_NAV_WAYPOINT, visualItemIndex, makeCurrentItem);
}

DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::insertTakeoffItem(QGeoCoordinate /*coordinate*/, int visualItemIndex, bool makeCurrentItem)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
{
    int sequenceNumber = _nextSequenceNumber();
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    TakeoffMissionItem * newItem = new TakeoffMissionItem(_controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_TAKEOFF : MAV_CMD_NAV_TAKEOFF, _masterController, _flyView, _settingsItem, this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    newItem->setSequenceNumber(sequenceNumber);
    _initVisualItem(newItem);

    if (newItem->specifiesAltitude()) {
        double  prevAltitude;
        int     prevAltitudeMode;
Don Gagne's avatar
 
Don Gagne committed
        if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) {
            newItem->altitude()->setRawValue(prevAltitude);
Don Gagne's avatar
 
Don Gagne committed
            newItem->setAltitudeMode(static_cast<QGroundControlQmlGlobal::AltitudeMode>(prevAltitudeMode));
    newItem->setMissionFlightStatus(_missionFlightStatus);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (visualItemIndex == -1) {
        _visualItems->append(newItem);
    } else {
        _visualItems->insert(visualItemIndex, newItem);
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _recalcAll();
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (makeCurrentItem) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    }

    return newItem;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::insertLandItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (_managerVehicle->fixedWing()) {
        FixedWingLandingComplexItem* fwLanding = qobject_cast<FixedWingLandingComplexItem*>(insertComplexMissionItem(MissionController::patternFWLandingName, coordinate, visualItemIndex, makeCurrentItem));
        fwLanding->setLoiterDragAngleOnly(true);
        return fwLanding;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    } else {
        return _insertSimpleMissionItemWorker(coordinate, _managerVehicle->vtol() ? MAV_CMD_NAV_VTOL_LAND : MAV_CMD_NAV_RETURN_TO_LAUNCH, visualItemIndex, makeCurrentItem);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
}
DonLakeFlyer's avatar
 
DonLakeFlyer committed

DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
{
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(_insertSimpleMissionItemWorker(coordinate, MAV_CMD_DO_SET_ROI_LOCATION, visualItemIndex, makeCurrentItem));

    if (!_controllerVehicle->firmwarePlugin()->supportedMissionCommands().contains(MAV_CMD_DO_SET_ROI_LOCATION)) {
        simpleItem->setCommand(MAV_CMD_DO_SET_ROI)  ;
        simpleItem->missionItem().setParam1(MAV_ROI_LOCATION);
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _recalcROISpecialVisuals();
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    return simpleItem;
}

VisualMissionItem* MissionController::insertCancelROIMissionItem(int visualItemIndex, bool makeCurrentItem)
{
    SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(_insertSimpleMissionItemWorker(QGeoCoordinate(), MAV_CMD_DO_SET_ROI_NONE, visualItemIndex, makeCurrentItem));

    if (!_controllerVehicle->firmwarePlugin()->supportedMissionCommands().contains(MAV_CMD_DO_SET_ROI_NONE)) {
        simpleItem->setCommand(MAV_CMD_DO_SET_ROI)  ;
        simpleItem->missionItem().setParam1(MAV_ROI_NONE);
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _recalcROISpecialVisuals();
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    return simpleItem;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int visualItemIndex, bool makeCurrentItem)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    ComplexMissionItem* newItem = nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (itemName == patternSurveyName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
        newItem->setCoordinate(mapCenterCoordinate);
Don Gagne's avatar
 
Don Gagne committed
    } else if (itemName == patternFWLandingName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new FixedWingLandingComplexItem(_masterController, _flyView, _visualItems /* parent */);
Don Gagne's avatar
 
Don Gagne committed
    } else if (itemName == patternStructureScanName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new StructureScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
Don Gagne's avatar
 
Don Gagne committed
    } else if (itemName == patternCorridorScanName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new CorridorScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
    } else {
        qWarning() << "Internal error: Unknown complex item:" << itemName;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        return nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _insertComplexMissionItemWorker(mapCenterCoordinate, newItem, visualItemIndex, makeCurrentItem);
DonLakeFlyer's avatar
 
DonLakeFlyer committed

    return newItem;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
VisualMissionItem* MissionController::insertComplexMissionItemFromKMLOrSHP(QString itemName, QString file, int visualItemIndex, bool makeCurrentItem)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    ComplexMissionItem* newItem = nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (itemName == patternSurveyName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new SurveyComplexItem(_masterController, _flyView, file, _visualItems);
Don Gagne's avatar
 
Don Gagne committed
    } else if (itemName == patternStructureScanName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new StructureScanComplexItem(_masterController, _flyView, file, _visualItems);
Don Gagne's avatar
 
Don Gagne committed
    } else if (itemName == patternCorridorScanName) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        newItem = new CorridorScanComplexItem(_masterController, _flyView, file, _visualItems);
    } else {
        qWarning() << "Internal error: Unknown complex item:" << itemName;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        return nullptr;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _insertComplexMissionItemWorker(QGeoCoordinate(), newItem, visualItemIndex, makeCurrentItem);
DonLakeFlyer's avatar
 
DonLakeFlyer committed

    return newItem;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
void MissionController::_insertComplexMissionItemWorker(const QGeoCoordinate& mapCenterCoordinate, ComplexMissionItem* complexItem, int visualItemIndex, bool makeCurrentItem)
{
    int sequenceNumber = _nextSequenceNumber();
Don Gagne's avatar
 
Don Gagne committed
    bool surveyStyleItem = qobject_cast<SurveyComplexItem*>(complexItem) ||
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            qobject_cast<CorridorScanComplexItem*>(complexItem) ||
            qobject_cast<StructureScanComplexItem*>(complexItem);
    if (surveyStyleItem) {
Gus Grubba's avatar
Gus Grubba committed
        bool rollSupported  = false;
        bool pitchSupported = false;
Gus Grubba's avatar
Gus Grubba committed
        bool yawSupported   = 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);
        // Point gimbal straight down
        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);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    complexItem->setWizardMode(true);
    _initVisualItem(complexItem);
Don Gagne's avatar
 
Don Gagne committed
    if (visualItemIndex == -1) {
        _visualItems->append(complexItem);
    } else {
Don Gagne's avatar
 
Don Gagne committed
        _visualItems->insert(visualItemIndex, complexItem);
Gus Grubba's avatar
Gus Grubba committed
    //-- Keep track of bounding box changes in complex items
    if(!complexItem->isSimpleItem()) {
        connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, &MissionController::_complexBoundingBoxChanged);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    _recalcAllWithCoordinate(mapCenterCoordinate);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (makeCurrentItem) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        setCurrentPlanViewSeqNum(complexItem->sequenceNumber(), true);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
void MissionController::removeMissionItem(int viIndex)
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (viIndex <= 0 || viIndex >= _visualItems->count()) {
        qWarning() << "MissionController::removeMissionItem called with bad index - count:index" << _visualItems->count() << viIndex;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    bool removeSurveyStyle = _visualItems->value<SurveyComplexItem*>(viIndex) || _visualItems->value<CorridorScanComplexItem*>(viIndex);
    VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->removeAt(viIndex));
    _deinitVisualItem(item);
Don Gagne's avatar
Don Gagne committed
    item->deleteLater();
    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)) {
        // 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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            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);
            }
    _recalcAll();
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed

    // Adjust current item
    int newVIIndex;
    if (viIndex >= _visualItems->count()) {
        newVIIndex = _visualItems->count() - 1;
    } else {
        newVIIndex = viIndex;
    }
    setCurrentPlanViewSeqNum(_visualItems->value<VisualMissionItem*>(newVIIndex)->sequenceNumber(), true);

    setDirty(true);
void MissionController::removeAll(void)
    if (_visualItems) {
        _deinitAllVisualItems();
DonLakeFlyer's avatar
DonLakeFlyer committed
        _visualItems->clearAndDeleteContents();
Don Gagne's avatar
Don Gagne committed
        _visualItems->deleteLater();
        _settingsItem = nullptr;
        _visualItems = new QmlObjectListModel(this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _addMissionSettings(_visualItems);
        _initAllVisualItems();
        setDirty(true);
        _resetMissionFlightStatus();
bool MissionController::_loadJsonMissionFileV1(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
Don Gagne's avatar
Don Gagne committed
{
    // 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)) {
Don Gagne's avatar
Don Gagne committed
        return false;
    }

    // Read complex items
    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;
        }

DonLakeFlyer's avatar
 
DonLakeFlyer committed
        SurveyComplexItem* item = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems /* parent */);
Don Gagne's avatar
Don Gagne committed
        const QJsonObject itemObject = itemValue.toObject();
        if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
            surveyItems.append(item);
        } else {
            return false;
    // 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
Don Gagne's avatar
Don Gagne committed
    QJsonArray itemArray(json[_jsonItemsKey].toArray());
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    MissionSettingsItem* settingsItem = _addMissionSettings(visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    if (json.contains(_jsonPlannedHomePositionKey)) {
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
        SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        if (item->load(json[_jsonPlannedHomePositionKey].toObject(), 0, errorString)) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            settingsItem->setInitialHomePositionFromUser(item->coordinate());
DonLakeFlyer's avatar
 
DonLakeFlyer committed
            item->deleteLater();
        } else {
            return false;
        }
    }

    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++];

Don Gagne's avatar
Don Gagne committed
            if (!itemValue.isObject()) {
                errorString = QStringLiteral("Mission item is not an object");
                return false;
            }

Don Gagne's avatar
Don Gagne committed
            const QJsonObject itemObject = itemValue.toObject();
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
            SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
Don Gagne's avatar
Don Gagne committed
            if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                if (TakeoffMissionItem::isTakeoffCommand(item->mavCommand())) {
                    // This needs to be a TakeoffMissionItem
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
                    TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                    takeoffItem->load(itemObject, itemObject["id"].toInt(), errorString);
                    item->deleteLater();
                    item = takeoffItem;
                }
                qCDebug(MissionControllerLog) << "Json load: adding simple item expectedSequence:actualSequence" << nextSequenceNumber << item->sequenceNumber();
                nextSequenceNumber = item->lastSequenceNumber() + 1;
                visualItems->append(item);
Don Gagne's avatar
Don Gagne committed
            } else {
                return false;
            }
        }
    } while (nextSimpleItemIndex < itemArray.count() || nextComplexItemIndex < surveyItems.count());
bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
Don Gagne's avatar
Don Gagne committed
{
    // 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 },
Don Gagne's avatar
Don Gagne committed
    };
    if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) {
        return false;
    }

    qCDebug(MissionControllerLog) << "MissionController::_loadJsonMissionFileV2 itemCount:" << json[_jsonItemsKey].toArray().count();

    // Mission Settings
    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;
    }
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    MissionSettingsItem* settingsItem = new MissionSettingsItem(_masterController, _flyView, visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    settingsItem->setCoordinate(homeCoordinate);
    visualItems->insert(0, settingsItem);
Don Gagne's avatar
Don Gagne committed
    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) {
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
            SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
            if (simpleItem->load(itemObject, nextSequenceNumber, errorString)) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(simpleItem->command()))) {
                    // This needs to be a TakeoffMissionItem
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
                    TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, this);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                    takeoffItem->load(itemObject, nextSequenceNumber, errorString);
                    simpleItem->deleteLater();
                    simpleItem = takeoffItem;
                }
                qCDebug(MissionControllerLog) << "Loading simple item: nextSequenceNumber:command" << nextSequenceNumber << simpleItem->command();
                nextSequenceNumber = simpleItem->lastSequenceNumber() + 1;
Don Gagne's avatar
Don Gagne committed
                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) {
Don Gagne's avatar
Don Gagne committed
                qCDebug(MissionControllerLog) << "Loading Survey: nextSequenceNumber" << nextSequenceNumber;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                SurveyComplexItem* surveyItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems);
Don Gagne's avatar
Don Gagne committed
                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) {
DonLakeFlyer's avatar
DonLakeFlyer committed
                qCDebug(MissionControllerLog) << "Loading Fixed Wing Landing Pattern: nextSequenceNumber" << nextSequenceNumber;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                FixedWingLandingComplexItem* landingItem = new FixedWingLandingComplexItem(_masterController, _flyView, visualItems);
DonLakeFlyer's avatar
DonLakeFlyer committed
                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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                StructureScanComplexItem* structureItem = new StructureScanComplexItem(_masterController, _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;
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_masterController, _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);
Don Gagne's avatar
Don Gagne committed
            } 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) {
Don Gagne's avatar
Don Gagne committed
                bool found = false;
                int findDoJumpId = static_cast<int>(doJumpItem->missionItem().param1());
Don Gagne's avatar
Don Gagne committed
                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;
Don Gagne's avatar
Don Gagne committed

    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;
Don Gagne's avatar
Don Gagne committed
        } else if (version[2] == "120") {
            // Old QGC file, no planned home position
            versionOk = true;
            plannedHomePositionInFile = false;
Don Gagne's avatar
Don Gagne committed
        }
    }

    if (versionOk) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        MissionSettingsItem* settingsItem = _addMissionSettings(visualItems);
Don Gagne's avatar
Don Gagne committed
        while (!stream.atEnd()) {
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
            SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
Don Gagne's avatar
Don Gagne committed
            if (item->load(stream)) {
                if (firstItem && plannedHomePositionInFile) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                    settingsItem->setInitialHomePositionFromUser(item->coordinate());
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                    if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(item->command()))) {
                        // This needs to be a TakeoffMissionItem
DoinLakeFlyer's avatar
 
DoinLakeFlyer committed
                        TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
                        takeoffItem->load(stream);
                        item->deleteLater();
                        item = takeoffItem;
                    }
                    visualItems->append(item);
                }
                firstItem = false;
Don Gagne's avatar
Don Gagne committed
            } else {
                errorString = tr("The mission file is corrupted.");
Don Gagne's avatar
Don Gagne committed
                return false;
            }
        }
    } else {
        errorString = tr("The mission file is not compatible with this version of %1.").arg(qgcApp()->applicationName());
Don Gagne's avatar
Don Gagne committed
        return false;
    }

    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));
DonLakeFlyer's avatar
DonLakeFlyer committed
            if (item && item->command() == MAV_CMD_DO_JUMP) {
                item->missionItem().setParam1(static_cast<int>(item->missionItem().param1()) + 1);
Don Gagne's avatar
Don Gagne committed
    }

    return true;
void MissionController::_initLoadedVisualItems(QmlObjectListModel* loadedVisualItems)
Don Gagne's avatar
Don Gagne committed
    if (_visualItems) {
        _deinitAllVisualItems();
        _visualItems->deleteLater();
        _settingsItem = nullptr;
    _visualItems = loadedVisualItems;
Don Gagne's avatar
Don Gagne committed

    if (_visualItems->count() == 0) {
DonLakeFlyer's avatar
 
DonLakeFlyer committed
        _addMissionSettings(_visualItems);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    } else {
        _settingsItem = _visualItems->value<MissionSettingsItem*>(0);
DonLakeFlyer's avatar
 
DonLakeFlyer committed
    MissionController::_scanForAdditionalSettings(_visualItems, _masterController);
Don Gagne's avatar
Don Gagne committed
    _initAllVisualItems();
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);