MissionController.cc 109 KB
Newer Older
1 2
/****************************************************************************
 *
Gus Grubba's avatar
Gus Grubba committed
3
 * (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
4 5 6 7 8
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
9

10
#include "MissionCommandUIInfo.h"
11 12 13
#include "MissionController.h"
#include "MultiVehicleManager.h"
#include "MissionManager.h"
14
#include "FlightPathSegment.h"
15
#include "FirmwarePlugin.h"
16
#include "QGCApplication.h"
17
#include "SimpleMissionItem.h"
18
#include "SurveyComplexItem.h"
19
#include "FixedWingLandingComplexItem.h"
20
#include "VTOLLandingComplexItem.h"
21
#include "StructureScanComplexItem.h"
22
#include "CorridorScanComplexItem.h"
23
#include "JsonHelper.h"
24
#include "ParameterManager.h"
25
#include "QGroundControlQmlGlobal.h"
26
#include "SettingsManager.h"
27
#include "AppSettings.h"
28
#include "MissionSettingsItem.h"
29
#include "QGCQGeoCoordinate.h"
30
#include "PlanMasterController.h"
31
#include "KMLPlanDomDocument.h"
32
#include "QGCCorePlugin.h"
33
#include "TakeoffMissionItem.h"
34
#include "PlanViewSettings.h"
35

36 37
#define UPDATE_TIMEOUT 5000 ///< How often we check for bounding box changes

38 39
QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")

40
const char* MissionController::_settingsGroup =                 "MissionController";
Don Gagne's avatar
Don Gagne committed
41 42
const char* MissionController::_jsonFileTypeValue =             "Mission";
const char* MissionController::_jsonItemsKey =                  "items";
43
const char* MissionController::_jsonPlannedHomePositionKey =    "plannedHomePosition";
Don Gagne's avatar
Don Gagne committed
44
const char* MissionController::_jsonFirmwareTypeKey =           "firmwareType";
45 46 47
const char* MissionController::_jsonVehicleTypeKey =            "vehicleType";
const char* MissionController::_jsonCruiseSpeedKey =            "cruiseSpeed";
const char* MissionController::_jsonHoverSpeedKey =             "hoverSpeed";
Don Gagne's avatar
Don Gagne committed
48 49 50 51 52 53 54
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;
55

56
MissionController::MissionController(PlanMasterController* masterController, QObject *parent)
57 58 59 60 61 62 63
    : PlanElementController (masterController, parent)
    , _controllerVehicle    (masterController->controllerVehicle())
    , _managerVehicle       (masterController->managerVehicle())
    , _missionManager       (masterController->managerVehicle()->missionManager())
    , _visualItems          (new QmlObjectListModel(this))
    , _planViewSettings     (qgcApp()->toolbox()->settingsManager()->planViewSettings())
    , _appSettings          (qgcApp()->toolbox()->settingsManager()->appSettings())
64
{
65
    _resetMissionFlightStatus();
66

67 68
    _updateTimer.setSingleShot(true);
    connect(&_updateTimer, &QTimer::timeout, this, &MissionController::_updateTimeout);
69 70

    connect(_planViewSettings->takeoffItemNotRequired(), &Fact::rawValueChanged, this, &MissionController::_takeoffItemNotRequiredChanged);
71 72 73 74 75 76 77 78 79

    connect(this, &MissionController::missionDistanceChanged,  this, &MissionController::recalcTerrainProfile);

    // The follow is used to compress multiple recalc calls in a row to into a single call.
    connect(this, &MissionController::_recalcMissionFlightStatusSignal, this, &MissionController::_recalcMissionFlightStatus,   Qt::QueuedConnection);
    connect(this, &MissionController::_recalcFlightPathSegmentsSignal,  this, &MissionController::_recalcFlightPathSegments,    Qt::QueuedConnection);
    qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::_recalcMissionFlightStatusSignal));
    qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::_recalcFlightPathSegmentsSignal));
    qgcApp()->addCompressedSignal(QMetaMethod::fromSignal(&MissionController::recalcTerrainProfile));
80 81 82 83
}

MissionController::~MissionController()
{
Don Gagne's avatar
Don Gagne committed
84

85 86
}

87 88 89 90 91 92 93 94 95
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;
96 97 98
    _missionFlightStatus.cruiseSpeed =          _controllerVehicle->defaultCruiseSpeed();
    _missionFlightStatus.hoverSpeed =           _controllerVehicle->defaultHoverSpeed();
    _missionFlightStatus.vehicleSpeed =         _controllerVehicle->multiRotor() || _managerVehicle->vtol() ? _missionFlightStatus.hoverSpeed : _missionFlightStatus.cruiseSpeed;
99
    _missionFlightStatus.vehicleYaw =           0.0;
100
    _missionFlightStatus.gimbalYaw =            std::numeric_limits<double>::quiet_NaN();
101
    _missionFlightStatus.gimbalPitch =          std::numeric_limits<double>::quiet_NaN();
102 103 104 105 106 107 108 109 110 111 112 113

    // 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;

114 115 116
    _controllerVehicle->firmwarePlugin()->batteryConsumptionData(_controllerVehicle, _missionFlightStatus.mAhBattery, _missionFlightStatus.hoverAmps, _missionFlightStatus.cruiseAmps);
    if (_missionFlightStatus.mAhBattery != 0) {
        double batteryPercentRemainingAnnounce = qgcApp()->toolbox()->settingsManager()->appSettings()->batteryPercentRemainingAnnounce()->rawValue().toDouble();
117
        _missionFlightStatus.ampMinutesAvailable = static_cast<double>(_missionFlightStatus.mAhBattery) / 1000.0 * 60.0 * ((100.0 - batteryPercentRemainingAnnounce) / 100.0);
118
    }
119 120 121 122 123 124 125 126 127 128 129

    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);

130 131
}

132
void MissionController::start(bool flyView)
133
{
134
    qCDebug(MissionControllerLog) << "start flyView" << flyView;
135

136 137 138
    _managerVehicleChanged(_managerVehicle);
    connect(_masterController, &PlanMasterController::managerVehicleChanged, this, &MissionController::_managerVehicleChanged);

139
    PlanElementController::start(flyView);
140 141 142 143 144
    _init();
}

void MissionController::_init(void)
{
145
    // We start with an empty mission
146
    _addMissionSettings(_visualItems);
147
    _initAllVisualItems();
148 149
}

150
// Called when new mission items have completed downloading from Vehicle
151
void MissionController::_newMissionItemsAvailableFromVehicle(bool removeAllRequested)
152
{
153
    qCDebug(MissionControllerLog) << "_newMissionItemsAvailableFromVehicle flyView:count" << _flyView << _missionManager->missionItems().count();
154

DonLakeFlyer's avatar
DonLakeFlyer committed
155
    // Fly view always reloads on _loadComplete
156 157 158 159
    // Plan view only reloads if:
    //  - Load was specifically requested
    //  - There is no current Plan
    if (_flyView || removeAllRequested || _itemsRequested || isEmpty()) {
160
        // Fly Mode (accept if):
Don Gagne's avatar
Don Gagne committed
161
        //      - Always accepts new items from the vehicle so Fly view is kept up to date
162
        // Edit Mode (accept if):
163 164
        //      - 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
165
        //      - The initial automatic load from a vehicle completed and the current editor is empty
166

167 168 169
        _deinitAllVisualItems();
        _visualItems->deleteLater();
        _visualItems  = nullptr;
170
        _settingsItem = nullptr;
171 172
        _updateContainsItems(); // This will clear containsItems which will be set again below. This will re-pop Start Mission confirmation.

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

177 178 179
        _missionItemCount = newMissionItems.count();
        emit missionItemCountChanged(_missionItemCount);

180
        MissionSettingsItem* settingsItem = _addMissionSettings(newControllerMissionItems);
181 182

        int i=0;
183
        if (_controllerVehicle->firmwarePlugin()->sendHomePositionToVehicle() && newMissionItems.count() != 0) {
184
            // First item is fake home position
185 186
            MissionItem* fakeHomeItem = newMissionItems[0];
            if (fakeHomeItem->coordinate().latitude() != 0 || fakeHomeItem->coordinate().longitude() != 0) {
187
                settingsItem->setInitialHomePosition(fakeHomeItem->coordinate());
188
            }
189 190
            i = 1;
        }
191

192
        for (; i < newMissionItems.count(); i++) {
193
            const MissionItem* missionItem = newMissionItems[i];
194
            SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, *missionItem, this);
195 196
            if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(simpleItem->command()))) {
                // This needs to be a TakeoffMissionItem
197
                TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(*missionItem, _masterController, _flyView, settingsItem, this);
198 199 200 201
                simpleItem->deleteLater();
                simpleItem = takeoffItem;
            }
            newControllerMissionItems->append(simpleItem);
202 203 204
        }

        _visualItems = newControllerMissionItems;
205
        _settingsItem = settingsItem;
206

207
        MissionController::_scanForAdditionalSettings(_visualItems, _masterController);
208

209
        _initAllVisualItems();
210
        _updateContainsItems();
211
        emit newItemsFromVehicle();
212
    }
DonLakeFlyer's avatar
DonLakeFlyer committed
213
    _itemsRequested = false;
214 215
}

216
void MissionController::loadFromVehicle(void)
217
{
DonLakeFlyer's avatar
DonLakeFlyer committed
218 219 220 221 222 223 224 225
    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();
    }
226 227
}

228
void MissionController::sendToVehicle(void)
229
{
DonLakeFlyer's avatar
DonLakeFlyer committed
230 231 232 233 234
    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
235
        qCDebug(MissionControllerLog) << "MissionControllerLog::sendToVehicle";
DonLakeFlyer's avatar
DonLakeFlyer committed
236 237 238 239 240 241 242 243 244
        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);
    }
Don Gagne's avatar
Don Gagne committed
245 246
}

247 248 249 250 251
/// 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
252 253 254 255
    if (visualMissionItems->count() == 0) {
        return false;
    }

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    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
272
    MissionSettingsItem* settingsItem = visualMissionItems->value<MissionSettingsItem*>(0);
273 274 275 276 277 278 279
    if (settingsItem) {
        endActionSet = settingsItem->addMissionEndAction(rgMissionItems, lastSeqNum + 1, missionItemParent);
    }

    return endActionSet;
}

280
void MissionController::addMissionToKML(KMLPlanDomDocument& planKML)
281
{
282 283
    QObject*            deleteParent = new QObject();
    QList<MissionItem*> rgMissionItems;
284

285
    _convertToMissionItems(_visualItems, rgMissionItems, deleteParent);
286
    planKML.addMission(_controllerVehicle, _visualItems, rgMissionItems);
287
    deleteParent->deleteLater();
288 289
}

Don Gagne's avatar
Don Gagne committed
290 291 292
void MissionController::sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems)
{
    if (vehicle) {
293
        QList<MissionItem*> rgMissionItems;
294

295
        _convertToMissionItems(visualMissionItems, rgMissionItems, vehicle);
296

297
        // PlanManager takes control of MissionItems so no need to delete
298
        vehicle->missionManager()->writeMissionItems(rgMissionItems);
299 300
    }
}
301

302 303 304 305 306 307
int MissionController::_nextSequenceNumber(void)
{
    if (_visualItems->count() == 0) {
        qWarning() << "Internal error: Empty visual item list";
        return 0;
    } else {
308 309
        VisualMissionItem* lastItem = _visualItems->value<VisualMissionItem*>(_visualItems->count() - 1);
        return lastItem->lastSequenceNumber() + 1;
310 311 312
    }
}

313
VisualMissionItem* MissionController::_insertSimpleMissionItemWorker(QGeoCoordinate coordinate, MAV_CMD command, int visualItemIndex, bool makeCurrentItem)
314
{
315
    int sequenceNumber = _nextSequenceNumber();
316
    SimpleMissionItem * newItem = new SimpleMissionItem(_masterController, _flyView, false /* forLoad */, this);
317
    newItem->setSequenceNumber(sequenceNumber);
318
    newItem->setCoordinate(coordinate);
319
    newItem->setCommand(command);
320
    _initVisualItem(newItem);
321 322

    if (newItem->specifiesAltitude()) {
323 324 325 326 327 328 329 330 331
        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));
            }
332
        }
333
    }
334 335 336 337 338 339 340 341
    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
342
    _recalcAllWithCoordinate(coordinate);
343 344

    if (makeCurrentItem) {
345
        setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true);
346 347 348 349 350
    }

    return newItem;
}

351 352 353 354 355 356

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

357
VisualMissionItem* MissionController::insertTakeoffItem(QGeoCoordinate /*coordinate*/, int visualItemIndex, bool makeCurrentItem)
358 359
{
    int sequenceNumber = _nextSequenceNumber();
360
    TakeoffMissionItem * newItem = new TakeoffMissionItem(_controllerVehicle->vtol() ? MAV_CMD_NAV_VTOL_TAKEOFF : MAV_CMD_NAV_TAKEOFF, _masterController, _flyView, _settingsItem, this);
361 362 363
    newItem->setSequenceNumber(sequenceNumber);
    _initVisualItem(newItem);

364 365 366
    if (newItem->specifiesAltitude()) {
        double  prevAltitude;
        int     prevAltitudeMode;
367

Don Gagne's avatar
Don Gagne committed
368
        if (_findPreviousAltitude(visualItemIndex, &prevAltitude, &prevAltitudeMode)) {
369
            newItem->altitude()->setRawValue(prevAltitude);
370
            newItem->setAltitudeMode(static_cast<QGroundControlQmlGlobal::AltitudeMode>(prevAltitudeMode));
371
        }
372
    }
373
    newItem->setMissionFlightStatus(_missionFlightStatus);
374 375 376 377 378
    if (visualItemIndex == -1) {
        _visualItems->append(newItem);
    } else {
        _visualItems->insert(visualItemIndex, newItem);
    }
379

380
    _recalcAll();
381

382
    if (makeCurrentItem) {
383
        setCurrentPlanViewSeqNum(newItem->sequenceNumber(), true);
384 385 386
    }

    return newItem;
387 388
}

389
VisualMissionItem* MissionController::insertLandItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
390
{
391
    if (_managerVehicle->fixedWing()) {
392
        FixedWingLandingComplexItem* fwLanding = qobject_cast<FixedWingLandingComplexItem*>(insertComplexMissionItem(FixedWingLandingComplexItem::name, coordinate, visualItemIndex, makeCurrentItem));
393
        return fwLanding;
394
    } else if (_managerVehicle->vtol()) {
395
        VTOLLandingComplexItem* vtolLanding = qobject_cast<VTOLLandingComplexItem*>(insertComplexMissionItem(VTOLLandingComplexItem::name, coordinate, visualItemIndex, makeCurrentItem));
396
        return vtolLanding;
397
    } else {
398
        return _insertSimpleMissionItemWorker(coordinate, _managerVehicle->vtol() ? MAV_CMD_NAV_VTOL_LAND : MAV_CMD_NAV_RETURN_TO_LAUNCH, visualItemIndex, makeCurrentItem);
399
    }
400
}
401

402 403
VisualMissionItem* MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int visualItemIndex, bool makeCurrentItem)
{
404 405 406 407 408 409
    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);
    }
410
    _recalcROISpecialVisuals();
411 412 413 414 415 416 417 418 419 420 421
    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);
    }
422
    _recalcROISpecialVisuals();
423
    return simpleItem;
424 425
}

426
VisualMissionItem* MissionController::insertComplexMissionItem(QString itemName, QGeoCoordinate mapCenterCoordinate, int visualItemIndex, bool makeCurrentItem)
427
{
428
    ComplexMissionItem* newItem = nullptr;
429

430
    if (itemName == SurveyComplexItem::name) {
431
        newItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
432
        newItem->setCoordinate(mapCenterCoordinate);
433
    } else if (itemName == FixedWingLandingComplexItem::name) {
434
        newItem = new FixedWingLandingComplexItem(_masterController, _flyView, _visualItems /* parent */);
435
    } else if (itemName == VTOLLandingComplexItem::name) {
436
        newItem = new VTOLLandingComplexItem(_masterController, _flyView, _visualItems /* parent */);
437
    } else if (itemName == StructureScanComplexItem::name) {
438
        newItem = new StructureScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
439
    } else if (itemName == CorridorScanComplexItem::name) {
440
        newItem = new CorridorScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, _visualItems /* parent */);
441 442
    } else {
        qWarning() << "Internal error: Unknown complex item:" << itemName;
443
        return nullptr;
444 445
    }

446
    _insertComplexMissionItemWorker(mapCenterCoordinate, newItem, visualItemIndex, makeCurrentItem);
447 448

    return newItem;
449 450
}

451
VisualMissionItem* MissionController::insertComplexMissionItemFromKMLOrSHP(QString itemName, QString file, int visualItemIndex, bool makeCurrentItem)
452
{
453
    ComplexMissionItem* newItem = nullptr;
454

455
    if (itemName == SurveyComplexItem::name) {
456
        newItem = new SurveyComplexItem(_masterController, _flyView, file, _visualItems);
457
    } else if (itemName == StructureScanComplexItem::name) {
458
        newItem = new StructureScanComplexItem(_masterController, _flyView, file, _visualItems);
459
    } else if (itemName == CorridorScanComplexItem::name) {
460
        newItem = new CorridorScanComplexItem(_masterController, _flyView, file, _visualItems);
461 462
    } else {
        qWarning() << "Internal error: Unknown complex item:" << itemName;
463
        return nullptr;
464 465
    }

466
    _insertComplexMissionItemWorker(QGeoCoordinate(), newItem, visualItemIndex, makeCurrentItem);
467 468

    return newItem;
469 470
}

471
void MissionController::_insertComplexMissionItemWorker(const QGeoCoordinate& mapCenterCoordinate, ComplexMissionItem* complexItem, int visualItemIndex, bool makeCurrentItem)
472 473
{
    int sequenceNumber = _nextSequenceNumber();
474
    bool surveyStyleItem = qobject_cast<SurveyComplexItem*>(complexItem) ||
475 476
            qobject_cast<CorridorScanComplexItem*>(complexItem) ||
            qobject_cast<StructureScanComplexItem*>(complexItem);
477

478
    if (surveyStyleItem) {
479
        bool rollSupported  = false;
480
        bool pitchSupported = false;
481
        bool yawSupported   = false;
482 483 484

        // If the vehicle is known to have a gimbal then we automatically point the gimbal straight down if not already set

485 486
        MissionSettingsItem* settingsItem = _visualItems->value<MissionSettingsItem*>(0);
        CameraSection* cameraSection = settingsItem->cameraSection();
487

488
        // Set camera to photo mode (leave alone if user already specified)
489
        if (cameraSection->cameraModeSupported() && !cameraSection->specifyCameraMode()) {
490
            cameraSection->setSpecifyCameraMode(true);
491
            cameraSection->cameraMode()->setRawValue(CAMERA_MODE_IMAGE_SURVEY);
492
        }
493

494
        // Point gimbal straight down
495
        if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) {
496 497 498
            // If the user already specified a gimbal angle leave it alone
            if (!cameraSection->specifyGimbal()) {
                cameraSection->setSpecifyGimbal(true);
499
                cameraSection->gimbalPitch()->setRawValue(-90.0);
500 501
            }
        }
502
    }
503

504
    complexItem->setSequenceNumber(sequenceNumber);
505
    complexItem->setWizardMode(true);
506
    _initVisualItem(complexItem);
507

Don Gagne's avatar
Don Gagne committed
508
    if (visualItemIndex == -1) {
509 510
        _visualItems->append(complexItem);
    } else {
Don Gagne's avatar
Don Gagne committed
511
        _visualItems->insert(visualItemIndex, complexItem);
512
    }
513

514
    //-- Keep track of bounding box changes in complex items
515 516
    if(!complexItem->isSimpleItem()) {
        connect(complexItem, &ComplexMissionItem::boundingCubeChanged, this, &MissionController::_complexBoundingBoxChanged);
517
    }
518
    _recalcAllWithCoordinate(mapCenterCoordinate);
519

520
    if (makeCurrentItem) {
521
        setCurrentPlanViewSeqNum(complexItem->sequenceNumber(), true);
522
    }
523 524
}

525
void MissionController::removeVisualItem(int viIndex)
526
{
527
    if (viIndex <= 0 || viIndex >= _visualItems->count()) {
528
        qWarning() << "MissionController::removeVisualItem called with bad index - count:index" << _visualItems->count() << viIndex;
529 530 531
        return;
    }

532 533
    bool removeSurveyStyle = _visualItems->value<SurveyComplexItem*>(viIndex) || _visualItems->value<CorridorScanComplexItem*>(viIndex);
    VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->removeAt(viIndex));
534

535
    _deinitVisualItem(item);
536
    item->deleteLater();
537

538 539
    if (removeSurveyStyle) {
        // Determine if the mission still has another survey style item in it
540 541
        bool foundSurvey = false;
        for (int i=1; i<_visualItems->count(); i++) {
542
            if (_visualItems->value<SurveyComplexItem*>(i) || _visualItems->value<CorridorScanComplexItem*>(i)) {
543 544 545 546 547
                foundSurvey = true;
                break;
            }
        }

548
        // If there is no longer a survey item in the mission remove added commands
549 550 551 552
        if (!foundSurvey) {
            bool rollSupported = false;
            bool pitchSupported = false;
            bool yawSupported = false;
553
            CameraSection* cameraSection = _settingsItem->cameraSection();
554
            if (_controllerVehicle->firmwarePlugin()->hasGimbal(_controllerVehicle, rollSupported, pitchSupported, yawSupported) && pitchSupported) {
555
                if (cameraSection->specifyGimbal() && cameraSection->gimbalPitch()->rawValue().toDouble() == -90.0 && cameraSection->gimbalYaw()->rawValue().toDouble() == 0.0) {
556 557 558
                    cameraSection->setSpecifyGimbal(false);
                }
            }
559
            if (cameraSection->cameraModeSupported() && cameraSection->specifyCameraMode() && cameraSection->cameraMode()->rawValue().toInt() == 0) {
560 561
                cameraSection->setSpecifyCameraMode(false);
            }
562 563 564
        }
    }

565
    _recalcAll();
566 567 568 569 570 571 572 573 574 575

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

576
    setDirty(true);
577 578
}

579
void MissionController::removeAll(void)
580
{
581 582
    if (_visualItems) {
        _deinitAllVisualItems();
DonLakeFlyer's avatar
DonLakeFlyer committed
583
        _visualItems->clearAndDeleteContents();
Don Gagne's avatar
Don Gagne committed
584
        _visualItems->deleteLater();
585
        _settingsItem = nullptr;
586
        _visualItems = new QmlObjectListModel(this);
587
        _addMissionSettings(_visualItems);
588
        _initAllVisualItems();
589
        setDirty(true);
590
        _resetMissionFlightStatus();
Don Gagne's avatar
Don Gagne committed
591 592 593
    }
}

594
bool MissionController::_loadJsonMissionFileV1(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
Don Gagne's avatar
Don Gagne committed
595 596 597 598 599 600 601 602 603
{
    // 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)) {
604 605 606
        return false;
    }

607
    // Read complex items
608
    QList<SurveyComplexItem*> surveyItems;
609 610 611 612
    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];
613

614 615 616 617 618
        if (!itemValue.isObject()) {
            errorString = QStringLiteral("Mission item is not an object");
            return false;
        }

619
        SurveyComplexItem* item = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems /* parent */);
Don Gagne's avatar
Don Gagne committed
620 621
        const QJsonObject itemObject = itemValue.toObject();
        if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
622
            surveyItems.append(item);
623 624
        } else {
            return false;
625
        }
626
    }
627

628 629 630 631 632
    // 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
633
    QJsonArray itemArray(json[_jsonItemsKey].toArray());
634

635
    MissionSettingsItem* settingsItem = _addMissionSettings(visualItems);
636
    if (json.contains(_jsonPlannedHomePositionKey)) {
637
        SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
638
        if (item->load(json[_jsonPlannedHomePositionKey].toObject(), 0, errorString)) {
639
            settingsItem->setInitialHomePositionFromUser(item->coordinate());
640 641 642 643 644 645
            item->deleteLater();
        } else {
            return false;
        }
    }

646
    qCDebug(MissionControllerLog) << "Json load: simple item loop start simpleItemCount:ComplexItemCount" << itemArray.count() << surveyItems.count();
647 648 649 650
    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
651
        if (nextComplexItemIndex < surveyItems.count()) {
652
            SurveyComplexItem* complexItem = surveyItems[nextComplexItemIndex];
653 654 655 656 657 658 659 660 661 662 663 664 665 666

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

667 668 669 670 671
            if (!itemValue.isObject()) {
                errorString = QStringLiteral("Mission item is not an object");
                return false;
            }

Don Gagne's avatar
Don Gagne committed
672
            const QJsonObject itemObject = itemValue.toObject();
673
            SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
Don Gagne's avatar
Don Gagne committed
674
            if (item->load(itemObject, itemObject["id"].toInt(), errorString)) {
675 676
                if (TakeoffMissionItem::isTakeoffCommand(item->mavCommand())) {
                    // This needs to be a TakeoffMissionItem
677
                    TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems);
678 679 680 681
                    takeoffItem->load(itemObject, itemObject["id"].toInt(), errorString);
                    item->deleteLater();
                    item = takeoffItem;
                }
682
                qCDebug(MissionControllerLog) << "Json load: adding simple item expectedSequence:actualSequence" << nextSequenceNumber << item->sequenceNumber();
683
                nextSequenceNumber = item->lastSequenceNumber() + 1;
684
                visualItems->append(item);
685 686 687 688
            } else {
                return false;
            }
        }
689
    } while (nextSimpleItemIndex < itemArray.count() || nextComplexItemIndex < surveyItems.count());
690 691 692 693

    return true;
}

694
bool MissionController::_loadJsonMissionFileV2(const QJsonObject& json, QmlObjectListModel* visualItems, QString& errorString)
Don Gagne's avatar
Don Gagne committed
695 696 697 698 699 700
{
    // Validate root object keys
    QList<JsonHelper::KeyValidateInfo> rootKeyInfoList = {
        { _jsonPlannedHomePositionKey,      QJsonValue::Array,  true },
        { _jsonItemsKey,                    QJsonValue::Array,  true },
        { _jsonFirmwareTypeKey,             QJsonValue::Double, true },
701
        { _jsonVehicleTypeKey,              QJsonValue::Double, false },
702 703
        { _jsonCruiseSpeedKey,              QJsonValue::Double, false },
        { _jsonHoverSpeedKey,               QJsonValue::Double, false },
Don Gagne's avatar
Don Gagne committed
704 705 706 707 708 709 710
    };
    if (!JsonHelper::validateKeys(json, rootKeyInfoList, errorString)) {
        return false;
    }

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

711
    // Mission Settings
712
    AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings();
713

714 715
    if (_masterController->offline()) {
        // We only update if offline since if we are online we use the online vehicle settings
716
        appSettings->offlineEditingFirmwareType()->setRawValue(AppSettings::offlineEditingFirmwareTypeFromFirmwareType(static_cast<MAV_AUTOPILOT>(json[_jsonFirmwareTypeKey].toInt())));
717
        if (json.contains(_jsonVehicleTypeKey)) {
718
            appSettings->offlineEditingVehicleType()->setRawValue(AppSettings::offlineEditingVehicleTypeFromVehicleType(static_cast<MAV_TYPE>(json[_jsonVehicleTypeKey].toInt())));
719
        }
720
    }
721
    if (json.contains(_jsonCruiseSpeedKey)) {
722
        appSettings->offlineEditingCruiseSpeed()->setRawValue(json[_jsonCruiseSpeedKey].toDouble());
723 724
    }
    if (json.contains(_jsonHoverSpeedKey)) {
725
        appSettings->offlineEditingHoverSpeed()->setRawValue(json[_jsonHoverSpeedKey].toDouble());
726 727
    }

728 729 730 731
    QGeoCoordinate homeCoordinate;
    if (!JsonHelper::loadGeoCoordinate(json[_jsonPlannedHomePositionKey], true /* altitudeRequired */, homeCoordinate, errorString)) {
        return false;
    }
732
    MissionSettingsItem* settingsItem = new MissionSettingsItem(_masterController, _flyView, visualItems);
733 734
    settingsItem->setCoordinate(homeCoordinate);
    visualItems->insert(0, settingsItem);
Don Gagne's avatar
Don Gagne committed
735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
    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) {
761
            SimpleMissionItem* simpleItem = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
762
            if (simpleItem->load(itemObject, nextSequenceNumber, errorString)) {
763 764
                if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(simpleItem->command()))) {
                    // This needs to be a TakeoffMissionItem
765
                    TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, this);
766 767 768 769
                    takeoffItem->load(itemObject, nextSequenceNumber, errorString);
                    simpleItem->deleteLater();
                    simpleItem = takeoffItem;
                }
770
                qCDebug(MissionControllerLog) << "Loading simple item: nextSequenceNumber:command" << nextSequenceNumber << simpleItem->command();
771
                nextSequenceNumber = simpleItem->lastSequenceNumber() + 1;
Don Gagne's avatar
Don Gagne committed
772 773 774 775 776 777 778 779 780 781 782 783 784
                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();

785
            if (complexItemType == SurveyComplexItem::jsonComplexItemTypeValue) {
Don Gagne's avatar
Don Gagne committed
786
                qCDebug(MissionControllerLog) << "Loading Survey: nextSequenceNumber" << nextSequenceNumber;
787
                SurveyComplexItem* surveyItem = new SurveyComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems);
Don Gagne's avatar
Don Gagne committed
788 789 790 791 792 793
                if (!surveyItem->load(itemObject, nextSequenceNumber++, errorString)) {
                    return false;
                }
                nextSequenceNumber = surveyItem->lastSequenceNumber() + 1;
                qCDebug(MissionControllerLog) << "Survey load complete: nextSequenceNumber" << nextSequenceNumber;
                visualItems->append(surveyItem);
794
            } else if (complexItemType == FixedWingLandingComplexItem::jsonComplexItemTypeValue) {
DonLakeFlyer's avatar
DonLakeFlyer committed
795
                qCDebug(MissionControllerLog) << "Loading Fixed Wing Landing Pattern: nextSequenceNumber" << nextSequenceNumber;
796
                FixedWingLandingComplexItem* landingItem = new FixedWingLandingComplexItem(_masterController, _flyView, visualItems);
DonLakeFlyer's avatar
DonLakeFlyer committed
797 798 799 800 801 802
                if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) {
                    return false;
                }
                nextSequenceNumber = landingItem->lastSequenceNumber() + 1;
                qCDebug(MissionControllerLog) << "FW Landing Pattern load complete: nextSequenceNumber" << nextSequenceNumber;
                visualItems->append(landingItem);
803 804 805 806 807 808 809 810 811
            } else if (complexItemType == VTOLLandingComplexItem::jsonComplexItemTypeValue) {
                qCDebug(MissionControllerLog) << "Loading VTOL Landing Pattern: nextSequenceNumber" << nextSequenceNumber;
                VTOLLandingComplexItem* landingItem = new VTOLLandingComplexItem(_masterController, _flyView, visualItems);
                if (!landingItem->load(itemObject, nextSequenceNumber++, errorString)) {
                    return false;
                }
                nextSequenceNumber = landingItem->lastSequenceNumber() + 1;
                qCDebug(MissionControllerLog) << "VTOL Landing Pattern load complete: nextSequenceNumber" << nextSequenceNumber;
                visualItems->append(landingItem);
812 813
            } else if (complexItemType == StructureScanComplexItem::jsonComplexItemTypeValue) {
                qCDebug(MissionControllerLog) << "Loading Structure Scan: nextSequenceNumber" << nextSequenceNumber;
814
                StructureScanComplexItem* structureItem = new StructureScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems);
815 816 817 818 819 820
                if (!structureItem->load(itemObject, nextSequenceNumber++, errorString)) {
                    return false;
                }
                nextSequenceNumber = structureItem->lastSequenceNumber() + 1;
                qCDebug(MissionControllerLog) << "Structure Scan load complete: nextSequenceNumber" << nextSequenceNumber;
                visualItems->append(structureItem);
821 822
            } else if (complexItemType == CorridorScanComplexItem::jsonComplexItemTypeValue) {
                qCDebug(MissionControllerLog) << "Loading Corridor Scan: nextSequenceNumber" << nextSequenceNumber;
823
                CorridorScanComplexItem* corridorItem = new CorridorScanComplexItem(_masterController, _flyView, QString() /* kmlFile */, visualItems);
824 825 826 827 828 829
                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
830 831 832 833 834 835 836 837 838 839 840 841 842
            } 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);
843
            if (doJumpItem->command() == MAV_CMD_DO_JUMP) {
Don Gagne's avatar
Don Gagne committed
844
                bool found = false;
845
                int findDoJumpId = static_cast<int>(doJumpItem->missionItem().param1());
Don Gagne's avatar
Don Gagne committed
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
                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;
}

867 868 869 870 871 872 873 874
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;
875
    JsonHelper::validateExternalQGCJsonFile(json,
876 877 878 879 880
                                    _jsonFileTypeValue,    // expected file type
                                    1,                     // minimum supported version
                                    2,                     // maximum supported version
                                    fileVersion,
                                    errorString);
881 882

    if (fileVersion == 1) {
883
        return _loadJsonMissionFileV1(json, visualItems, errorString);
884
    } else {
885
        return _loadJsonMissionFileV2(json, visualItems, errorString);
886 887 888
    }
}

889
bool MissionController::_loadTextMissionFile(QTextStream& stream, QmlObjectListModel* visualItems, QString& errorString)
890
{
891 892
    bool firstItem = true;
    bool plannedHomePositionInFile = false;
893 894 895 896 897 898 899 900 901

    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;
902
            plannedHomePositionInFile = true;
903 904 905
        } else if (version[2] == "120") {
            // Old QGC file, no planned home position
            versionOk = true;
906
            plannedHomePositionInFile = false;
907 908 909 910
        }
    }

    if (versionOk) {
911
        MissionSettingsItem* settingsItem = _addMissionSettings(visualItems);
912

913
        while (!stream.atEnd()) {
914
            SimpleMissionItem* item = new SimpleMissionItem(_masterController, _flyView, true /* forLoad */, visualItems);
915
            if (item->load(stream)) {
916
                if (firstItem && plannedHomePositionInFile) {
917
                    settingsItem->setInitialHomePositionFromUser(item->coordinate());
918
                } else {
919 920
                    if (TakeoffMissionItem::isTakeoffCommand(static_cast<MAV_CMD>(item->command()))) {
                        // This needs to be a TakeoffMissionItem
921
                        TakeoffMissionItem* takeoffItem = new TakeoffMissionItem(_masterController, _flyView, settingsItem, true /* forLoad */, visualItems);
922 923 924 925
                        takeoffItem->load(stream);
                        item->deleteLater();
                        item = takeoffItem;
                    }
926 927 928
                    visualItems->append(item);
                }
                firstItem = false;
929
            } else {
930
                errorString = tr("The mission file is corrupted.");
931 932 933 934
                return false;
            }
        }
    } else {
935
        errorString = tr("The mission file is not compatible with this version of %1.").arg(qgcApp()->applicationName());
936 937 938
        return false;
    }

939
    if (!plannedHomePositionInFile) {
940 941 942
        // 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));
943
            if (item && item->command() == MAV_CMD_DO_JUMP) {
944
                item->missionItem().setParam1(static_cast<int>(item->missionItem().param1()) + 1);
Don Gagne's avatar
Don Gagne committed
945 946
            }
        }
947 948 949
    }

    return true;
950 951
}

952
void MissionController::_initLoadedVisualItems(QmlObjectListModel* loadedVisualItems)
953
{
Don Gagne's avatar
Don Gagne committed
954 955 956
    if (_visualItems) {
        _deinitAllVisualItems();
        _visualItems->deleteLater();
957
        _settingsItem = nullptr;
Don Gagne's avatar
Don Gagne committed
958 959
    }

960
    _visualItems = loadedVisualItems;
Don Gagne's avatar
Don Gagne committed
961 962

    if (_visualItems->count() == 0) {
963
        _addMissionSettings(_visualItems);
964 965
    } else {
        _settingsItem = _visualItems->value<MissionSettingsItem*>(0);
Don Gagne's avatar
Don Gagne committed
966 967
    }

968
    MissionController::_scanForAdditionalSettings(_visualItems, _masterController);
969

Don Gagne's avatar
Don Gagne committed
970
    _initAllVisualItems();
971
}
972

973 974 975 976 977 978
bool MissionController::load(const QJsonObject& json, QString& errorString)
{
    QString errorStr;
    QString errorMessage = tr("Mission: %1");
    QmlObjectListModel* loadedVisualItems = new QmlObjectListModel(this);

979
    if (!_loadJsonMissionFileV2(json, loadedVisualItems, errorStr)) {
980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004
        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;
1005
    }
1006

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
    _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);
1020
    if (!_loadTextMissionFile(stream, loadedVisualItems, errorStr)) {
1021 1022 1023 1024 1025 1026
        errorString = errorMessage.arg(errorStr);
        return false;
    }

    _initLoadedVisualItems(loadedVisualItems);

Don Gagne's avatar
Don Gagne committed
1027
    return true;
1028 1029
}

1030
int MissionController::readyForSaveState(void) const
1031 1032 1033
{
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
DonLakeFlyer's avatar
DonLakeFlyer committed
1034
        if (visualItem->readyForSaveState() != VisualMissionItem::ReadyForSave) {
1035
            return visualItem->readyForSaveState();
1036 1037 1038
        }
    }

1039
    return VisualMissionItem::ReadyForSave;
1040 1041
}

1042
void MissionController::save(QJsonObject& json)
1043
{
1044
    json[JsonHelper::jsonVersionKey] = _missionFileVersion;
1045

1046
    // Mission settings
1047

1048 1049 1050 1051 1052 1053 1054
    MissionSettingsItem* settingsItem = _visualItems->value<MissionSettingsItem*>(0);
    if (!settingsItem) {
        qWarning() << "First item is not MissionSettingsItem";
        return;
    }
    QJsonValue coordinateValue;
    JsonHelper::saveGeoCoordinate(settingsItem->coordinate(), true /* writeAltitude */, coordinateValue);
1055 1056 1057 1058 1059
    json[_jsonPlannedHomePositionKey]   = coordinateValue;
    json[_jsonFirmwareTypeKey]          = _controllerVehicle->firmwareType();
    json[_jsonVehicleTypeKey]           = _controllerVehicle->vehicleType();
    json[_jsonCruiseSpeedKey]           = _controllerVehicle->defaultCruiseSpeed();
    json[_jsonHoverSpeedKey]            = _controllerVehicle->defaultHoverSpeed();
1060

1061
    // Save the visual items
1062

1063 1064 1065
    QJsonArray rgJsonMissionItems;
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1066

1067 1068
        visualItem->save(rgJsonMissionItems);
    }
1069

1070 1071 1072
    // Mission settings has a special case for end mission action
    if (settingsItem) {
        QList<MissionItem*> rgMissionItems;
1073

1074 1075 1076 1077 1078
        if (_convertToMissionItems(_visualItems, rgMissionItems, this /* missionItemParent */)) {
            QJsonObject saveObject;
            MissionItem* missionItem = rgMissionItems[rgMissionItems.count() - 1];
            missionItem->save(saveObject);
            rgJsonMissionItems.append(saveObject);
1079
        }
1080 1081
        for (int i=0; i<rgMissionItems.count(); i++) {
            rgMissionItems[i]->deleteLater();
1082 1083 1084
        }
    }

1085
    json[_jsonItemsKey] = rgJsonMissionItems;
1086 1087
}

1088
void MissionController::_calcPrevWaypointValues(VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference)
1089
{
Don Gagne's avatar
Don Gagne committed
1090
    QGeoCoordinate  currentCoord =  currentItem->coordinate();
1091
    QGeoCoordinate  prevCoord =     prevItem->exitCoordinate();
1092 1093 1094

    // Convert to fixed altitudes

1095 1096 1097
    *altDifference = currentItem->amslEntryAlt() - prevItem->amslExitAlt();
    *distance = prevCoord.distanceTo(currentCoord);