MissionController.cc 41.4 KB
Newer Older
1 2 3 4 5 6 7 8
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
9 10 11 12 13 14


#include "MissionController.h"
#include "MultiVehicleManager.h"
#include "MissionManager.h"
#include "CoordinateVector.h"
15
#include "FirmwarePlugin.h"
16
#include "QGCApplication.h"
17 18
#include "SimpleMissionItem.h"
#include "ComplexMissionItem.h"
19
#include "JsonHelper.h"
20

21 22 23 24
#ifndef __mobile__
#include "QGCFileDialog.h"
#endif

25 26
QGC_LOGGING_CATEGORY(MissionControllerLog, "MissionControllerLog")

27 28
const char* MissionController::jsonSimpleItemsKey = "items";

29 30 31 32
const char* MissionController::_settingsGroup =                 "MissionController";
const char* MissionController::_jsonVersionKey =                "version";
const char* MissionController::_jsonGroundStationKey =          "groundStation";
const char* MissionController::_jsonMavAutopilotKey =           "MAV_AUTOPILOT";
33
const char* MissionController::_jsonComplexItemsKey =           "complexItems";
34
const char* MissionController::_jsonPlannedHomePositionKey =    "plannedHomePosition";
35 36 37

MissionController::MissionController(QObject *parent)
    : QObject(parent)
38
    , _editMode(false)
39 40
    , _visualItems(NULL)
    , _complexItems(NULL)
41
    , _activeVehicle(NULL)
42
    , _autoSync(false)
43
    , _firstItemsFromVehicle(false)
44 45
    , _missionItemsRequested(false)
    , _queuedSend(false)
46
{
47 48 49 50 51

}

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

53 54 55 56
}

void MissionController::start(bool editMode)
{
57 58
    qCDebug(MissionControllerLog) << "start editMode" << editMode;

59 60
    _editMode = editMode;

61
    MultiVehicleManager* multiVehicleMgr = qgcApp()->toolbox()->multiVehicleManager();
62

63
    connect(multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &MissionController::_activeVehicleChanged);
64
    _activeVehicleChanged(multiVehicleMgr->activeVehicle());
65

66
    // We start with an empty mission
67 68 69
    _visualItems = new QmlObjectListModel(this);
    _addPlannedHomePosition(_visualItems, false /* addToCenter */);
    _initAllVisualItems();
70 71
}

72
// Called when new mission items have completed downloading from Vehicle
73
void MissionController::_newMissionItemsAvailableFromVehicle(void)
74
{
75 76
    qCDebug(MissionControllerLog) << "_newMissionItemsAvailableFromVehicle";

77
    if (!_editMode || _missionItemsRequested || _visualItems->count() == 1) {
78 79 80 81 82
        // Fly Mode:
        //      - Always accepts new items fromthe vehicle so Fly view is kept up to date
        // Edit Mode:
        //      - Either a load from vehicle was manually requested or
        //      - The initial automatic load from a vehicle completed and the current editor it empty
83

84 85
        QmlObjectListModel* newControllerMissionItems = new QmlObjectListModel(this);
        const QList<MissionItem*>& newMissionItems = _activeVehicle->missionManager()->missionItems();
86

87 88 89 90 91 92 93 94 95 96 97 98
        qCDebug(MissionControllerLog) << "loading from vehicle: count"<< _visualItems->count();
        foreach(const MissionItem* missionItem, newMissionItems) {
            newControllerMissionItems->append(new SimpleMissionItem(_activeVehicle, *missionItem, this));
        }

        _deinitAllVisualItems();

        _visualItems->deleteListAndContents();
        _visualItems = newControllerMissionItems;

        if (!_activeVehicle->firmwarePlugin()->sendHomePositionToVehicle() || _visualItems->count() == 0) {
            _addPlannedHomePosition(_visualItems,true /* addToCenter */);
99 100
        }

101
        _missionItemsRequested = false;
102

103
        _initAllVisualItems();
104
        emit newItemsFromVehicle();
105 106 107
    }
}

108
void MissionController::getMissionItems(void)
109
{
110
    Vehicle* activeVehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle();
111

112 113 114 115 116 117 118 119
    if (activeVehicle) {
        _missionItemsRequested = true;
        activeVehicle->missionManager()->requestMissionItems();
    }
}

void MissionController::sendMissionItems(void)
{
120 121 122 123 124 125 126
    if (_activeVehicle) {
        // Convert to MissionItems so we can send to vehicle
        QList<MissionItem*> missionItems;

        for (int i=0; i<_visualItems->count(); i++) {
            VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
            if (visualItem->isSimpleItem()) {
127
                missionItems.append(new MissionItem(qobject_cast<SimpleMissionItem*>(visualItem)->missionItem()));
128
            } else {
129
                ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(visualItem);
130 131 132
                QmlObjectListModel* complexMissionItems = complexItem->getMissionItems();
                for (int j=0; j<complexMissionItems->count(); j++) {
                    missionItems.append(new MissionItem(*qobject_cast<MissionItem*>(complexMissionItems->get(j))));
133
                }
134
                delete complexMissionItems;
135 136 137 138 139
            }
        }

        _activeVehicle->missionManager()->writeMissionItems(missionItems);
        _visualItems->setDirty(false);
140 141 142 143

        for (int i=0; i<missionItems.count(); i++) {
            delete missionItems[i];
        }
144 145
    }
}
146

147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
int MissionController::_nextSequenceNumber(void)
{
    if (_visualItems->count() == 0) {
        qWarning() << "Internal error: Empty visual item list";
        return 0;
    } else {
        VisualMissionItem* lastItem = qobject_cast<VisualMissionItem*>(_visualItems->get(_visualItems->count() - 1));

        if (lastItem->isSimpleItem()) {
            return lastItem->sequenceNumber() + 1;
        } else {
            return qobject_cast<ComplexMissionItem*>(lastItem)->lastSequenceNumber() + 1;
        }
    }
}

163
int MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int i)
164
{
165
    int sequenceNumber = _nextSequenceNumber();
166
    SimpleMissionItem * newItem = new SimpleMissionItem(_activeVehicle, this);
167
    newItem->setSequenceNumber(sequenceNumber);
168
    newItem->setCoordinate(coordinate);
169 170 171
    newItem->setCommand(MavlinkQmlSingleton::MAV_CMD_NAV_WAYPOINT);
    _initVisualItem(newItem);
    if (_visualItems->count() == 1) {
172 173
        newItem->setCommand(MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF);
    }
174
    newItem->setDefaultsForCommand();
Don Gagne's avatar
Don Gagne committed
175
    if ((MAV_CMD)newItem->command() == MAV_CMD_NAV_WAYPOINT) {
176 177 178
        double lastValue;

        if (_findLastAcceptanceRadius(&lastValue)) {
179
            newItem->missionItem().setParam2(lastValue);
180 181
        }
        if (_findLastAltitude(&lastValue)) {
182
            newItem->missionItem().setParam7(lastValue);
183
        }
184
    }
185
    _visualItems->insert(i, newItem);
186 187 188

    _recalcAll();

189
    return newItem->sequenceNumber();
190 191
}

192 193
int MissionController::insertComplexMissionItem(QGeoCoordinate coordinate, int i)
{
194
    int sequenceNumber = _nextSequenceNumber();
195
    ComplexMissionItem* newItem = new ComplexMissionItem(_activeVehicle, this);
196
    newItem->setSequenceNumber(sequenceNumber);
197
    newItem->setCoordinate(coordinate);
198
    _initVisualItem(newItem);
199

200 201
    _visualItems->insert(i, newItem);
    _complexItems->append(newItem);
202 203 204

    _recalcAll();

205
    return newItem->sequenceNumber();
206 207
}

208 209
void MissionController::removeMissionItem(int index)
{
210
    VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->removeAt(index));
211

212 213 214 215 216 217 218 219 220
    _deinitVisualItem(item);
    if (!item->isSimpleItem()) {
        ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(_complexItems->removeOne(item));
        if (complexItem) {
            complexItem->deleteLater();
        } else {
            qWarning() << "Complex item missing";
        }
    }
221
    item->deleteLater();
222 223 224 225 226

    _recalcAll();

    // Set the new current item

227
    if (index >= _visualItems->count()) {
228 229
        index--;
    }
230
    for (int i=0; i<_visualItems->count(); i++) {
Don Gagne's avatar
Don Gagne committed
231
        VisualMissionItem* item =  qobject_cast<VisualMissionItem*>(_visualItems->get(i));
232 233
        item->setIsCurrentItem(i == index);
    }
234
    _visualItems->setDirty(true);
235 236
}

237 238
void MissionController::removeAllMissionItems(void)
{
239 240 241 242 243 244
    if (_visualItems) {
        _deinitAllVisualItems();
        _visualItems->deleteListAndContents();
        _visualItems = new QmlObjectListModel(this);
        _addPlannedHomePosition(_visualItems, false /* addToCenter */);
        _initAllVisualItems();
Don Gagne's avatar
Don Gagne committed
245
        _visualItems->setDirty(true);
246
    }
247
}
248

249
bool MissionController::_loadJsonMissionFile(const QByteArray& bytes, QmlObjectListModel* visualItems, QmlObjectListModel* complexItems, QString& errorString)
250 251 252 253 254 255 256 257 258 259 260
{
    QJsonParseError jsonParseError;
    QJsonDocument   jsonDoc(QJsonDocument::fromJson(bytes, &jsonParseError));

    if (jsonParseError.error != QJsonParseError::NoError) {
        errorString = jsonParseError.errorString();
        return false;
    }

    QJsonObject json = jsonDoc.object();

261 262 263 264 265 266 267 268 269 270 271 272 273
    // Check for required keys
    QStringList requiredKeys;
    requiredKeys << _jsonVersionKey << _jsonPlannedHomePositionKey;
    if (!JsonHelper::validateRequiredKeys(json, requiredKeys, errorString)) {
        return false;
    }

    // Validate base key types
    QStringList             keyList;
    QList<QJsonValue::Type> typeList;
    keyList << jsonSimpleItemsKey << _jsonVersionKey << _jsonGroundStationKey << _jsonMavAutopilotKey << _jsonComplexItemsKey << _jsonPlannedHomePositionKey;
    typeList << QJsonValue::Array << QJsonValue::String << QJsonValue::String << QJsonValue::Double << QJsonValue::Array << QJsonValue::Object;
    if (!JsonHelper::validateKeyTypes(json, keyList, typeList, errorString)) {
274 275
        return false;
    }
276 277

    // Version check
278 279 280 281 282
    if (json[_jsonVersionKey].toString() != "1.0") {
        errorString = QStringLiteral("QGroundControl does not support this file version");
        return false;
    }

283 284 285 286 287
    // Read complex items
    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];
288

289 290 291 292 293 294 295 296 297 298 299
        if (!itemValue.isObject()) {
            errorString = QStringLiteral("Mission item is not an object");
            return false;
        }

        ComplexMissionItem* item = new ComplexMissionItem(_activeVehicle, this);
        if (item->load(itemValue.toObject(), errorString)) {
            qCDebug(MissionControllerLog) << "Json load: complex item start:stop" << item->sequenceNumber() << item->lastSequenceNumber();
            complexItems->append(item);
        } else {
            return false;
300
        }
301
    }
302

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
    // Read simple items, interspersing complex items into the full list

    int nextSimpleItemIndex= 0;
    int nextComplexItemIndex= 0;
    int nextSequenceNumber = 1; // Start with 1 since home is in 0
    QJsonArray itemArray(json[jsonSimpleItemsKey].toArray());

    qCDebug(MissionControllerLog) << "Json load: simple item loop start simpleItemCount:ComplexItemCount" << itemArray.count() << complexItems->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 < complexItems->count()) {
            ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(complexItems->get(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++];

331 332 333 334 335
            if (!itemValue.isObject()) {
                errorString = QStringLiteral("Mission item is not an object");
                return false;
            }

336
            SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this);
337
            if (item->load(itemValue.toObject(), errorString)) {
338 339
                qCDebug(MissionControllerLog) << "Json load: adding simple item expectedSequence:actualSequence" << nextSequenceNumber << item->sequenceNumber();
                visualItems->append(item);
340 341 342
            } else {
                return false;
            }
343 344

            nextSequenceNumber++;
345
        }
Don Gagne's avatar
Don Gagne committed
346
    } while (nextSimpleItemIndex < itemArray.count() || nextComplexItemIndex < complexItems->count());
347 348

    if (json.contains(_jsonPlannedHomePositionKey)) {
349
        SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this);
350 351

        if (item->load(json[_jsonPlannedHomePositionKey].toObject(), errorString)) {
352
            visualItems->insert(0, item);
353 354 355 356
        } else {
            return false;
        }
    } else {
357
        _addPlannedHomePosition(visualItems, true /* addToCenter */);
358 359 360 361 362
    }

    return true;
}

363
bool MissionController::_loadTextMissionFile(QTextStream& stream, QmlObjectListModel* visualItems, QString& errorString)
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
{
    bool addPlannedHomePosition = false;

    QString firstLine = stream.readLine();
    const QStringList& version = firstLine.split(" ");

    bool versionOk = false;
    if (version.size() == 3 && version[0] == "QGC" && version[1] == "WPL") {
        if (version[2] == "110") {
            // ArduPilot file, planned home position is already in position 0
            versionOk = true;
        } else if (version[2] == "120") {
            // Old QGC file, no planned home position
            versionOk = true;
            addPlannedHomePosition = true;
        }
    }

    if (versionOk) {
        while (!stream.atEnd()) {
384
            SimpleMissionItem* item = new SimpleMissionItem(_activeVehicle, this);
385 386

            if (item->load(stream)) {
387
                visualItems->append(item);
388 389 390 391 392 393 394 395 396 397
            } else {
                errorString = QStringLiteral("The mission file is corrupted.");
                return false;
            }
        }
    } else {
        errorString = QStringLiteral("The mission file is not compatible with this version of QGroundControl.");
        return false;
    }

398 399
    if (addPlannedHomePosition || visualItems->count() == 0) {
        _addPlannedHomePosition(visualItems, true /* addToCenter */);
Don Gagne's avatar
Don Gagne committed
400

401 402 403 404 405
        // Update sequence numbers in DO_JUMP commands to take into account added home position in index 0
        for (int i=1; i<visualItems->count(); i++) {
            SimpleMissionItem* item = qobject_cast<SimpleMissionItem*>(visualItems->get(i));
            if (item && item->command() == MavlinkQmlSingleton::MAV_CMD_DO_JUMP) {
                item->missionItem().setParam1((int)item->missionItem().param1() + 1);
Don Gagne's avatar
Don Gagne committed
406 407
            }
        }
408 409 410
    }

    return true;
411 412
}

Don Gagne's avatar
Don Gagne committed
413
void MissionController::loadMissionFromFile(const QString& filename)
414 415 416 417 418 419 420
{
    QString errorString;

    if (filename.isEmpty()) {
        return;
    }

421 422
    QmlObjectListModel* newVisualItems = new QmlObjectListModel(this);
    QmlObjectListModel* newComplexItems = new QmlObjectListModel(this);
423 424 425 426 427 428

    QFile file(filename);

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        errorString = file.errorString();
    } else {
429 430
        QByteArray  bytes = file.readAll();
        QTextStream stream(&bytes);
431

432 433 434
        QString firstLine = stream.readLine();
        if (firstLine.contains(QRegExp("QGC.*WPL"))) {
            stream.seek(0);
435
            _loadTextMissionFile(stream, newVisualItems, errorString);
436
        } else {
437
            _loadJsonMissionFile(bytes, newVisualItems, newComplexItems, errorString);
438 439
        }
    }
440

441
    if (!errorString.isEmpty()) {
442 443 444 445 446 447 448 449 450
        for (int i=0; i<newVisualItems->count(); i++) {
            newVisualItems->get(i)->deleteLater();
        }
        for (int i=0; i<newComplexItems->count(); i++) {
            newComplexItems->get(i)->deleteLater();
        }
        delete newVisualItems;
        delete newComplexItems;

451
        qgcApp()->showMessage(errorString);
452
        return;
453 454
    }

455 456 457
    if (_visualItems) {
        _deinitAllVisualItems();
        _visualItems->deleteListAndContents();
458
    }
459 460 461 462 463 464 465
    if (_complexItems) {
        _complexItems->deleteLater();
    }

    _visualItems = newVisualItems;
    _complexItems = newComplexItems;

466 467
    if (_visualItems->count() == 0) {
        _addPlannedHomePosition(_visualItems, true /* addToCenter */);
468 469
    }

470
    _initAllVisualItems();
471 472
}

Don Gagne's avatar
Don Gagne committed
473
void MissionController::loadMissionFromFilePicker(void)
474
{
475
#ifndef __mobile__
476 477 478 479 480
    QString filename = QGCFileDialog::getOpenFileName(NULL, "Select Mission File to load", QString(), "Mission file (*.mission);;All Files (*.*)");

    if (filename.isEmpty()) {
        return;
    }
Don Gagne's avatar
Don Gagne committed
481
    loadMissionFromFile(filename);
482 483 484
#endif
}

Don Gagne's avatar
Don Gagne committed
485
void MissionController::saveMissionToFile(const QString& filename)
486 487
{
    qDebug() << filename;
488 489 490 491

    if (filename.isEmpty()) {
        return;
    }
492

493
    QString missionFilename = filename;
494
    if (!QFileInfo(filename).fileName().contains(".")) {
Don Gagne's avatar
Don Gagne committed
495
        missionFilename += QString(".%1").arg(QGCApplication::missionFileExtension);
496 497
    }

498
    QFile file(missionFilename);
499

500
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
501
        qgcApp()->showMessage(file.errorString());
502
    } else {
503 504 505
        QJsonObject missionFileObject;      // top level json object
        QJsonArray  simpleItemsObject;
        QJsonArray  complexItemsObject;
506

507 508
        missionFileObject[_jsonVersionKey] =        "1.0";
        missionFileObject[_jsonGroundStationKey] =  "QGroundControl";
509

510 511 512 513 514 515 516 517 518 519
        MAV_AUTOPILOT firmwareType = MAV_AUTOPILOT_GENERIC;
        if (_activeVehicle) {
            firmwareType = _activeVehicle->firmwareType();
        } else {
            // FIXME: Hack duplicated code from QGroundControlQmlGlobal. Had to do this for now since
            // QGroundControlQmlGlobal is not available from C++ side.

            QSettings settings;
            firmwareType = (MAV_AUTOPILOT)settings.value("OfflineEditingFirmwareType", MAV_AUTOPILOT_ARDUPILOTMEGA).toInt();
        }
520
        missionFileObject[_jsonMavAutopilotKey] = firmwareType;
521

522
        // Save planned home position
523
        QJsonObject homePositionObject;
524 525 526 527 528 529 530
        SimpleMissionItem* homeItem = qobject_cast<SimpleMissionItem*>(_visualItems->get(0));
        if (homeItem) {
            homeItem->missionItem().save(homePositionObject);
        } else {
            qgcApp()->showMessage(QStringLiteral("Internal error: VisualMissionItem at index 0 not SimpleMissionItem"));
            return;
        }
531
        missionFileObject[_jsonPlannedHomePositionKey] = homePositionObject;
532

533
        // Save the visual items
534
        for (int i=1; i<_visualItems->count(); i++) {
535 536
            QJsonObject itemObject;

537
            VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
538 539 540 541 542 543
            visualItem->save(itemObject);

            if (visualItem->isSimpleItem()) {
                simpleItemsObject.append(itemObject);
            } else {
                complexItemsObject.append(itemObject);
544
            }
545
        }
546

547 548 549 550
        missionFileObject[jsonSimpleItemsKey] = simpleItemsObject;
        missionFileObject[_jsonComplexItemsKey] = complexItemsObject;

        QJsonDocument saveDoc(missionFileObject);
551
        file.write(saveDoc.toJson());
552 553
    }

554
    _visualItems->setDirty(false);
555 556
}

Don Gagne's avatar
Don Gagne committed
557
void MissionController::saveMissionToFilePicker(void)
558 559 560 561 562 563 564
{
#ifndef __mobile__
    QString filename = QGCFileDialog::getSaveFileName(NULL, "Select file to save mission to", QString(), "Mission file (*.mission);;All Files (*.*)");

    if (filename.isEmpty()) {
        return;
    }
Don Gagne's avatar
Don Gagne committed
565
    saveMissionToFile(filename);
566
#endif
567 568
}

569
void MissionController::_calcPrevWaypointValues(double homeAlt, VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference)
570
{
Don Gagne's avatar
Don Gagne committed
571
    QGeoCoordinate  currentCoord =  currentItem->coordinate();
572
    QGeoCoordinate  prevCoord =     prevItem->exitCoordinate();
573 574 575 576
    bool            distanceOk =    false;

    // Convert to fixed altitudes

577
    qCDebug(MissionControllerLog) << homeAlt
578 579
                                  << currentItem->coordinateHasRelativeAltitude() << currentItem->coordinate().altitude()
                                  << prevItem->exitCoordinateHasRelativeAltitude() << prevItem->exitCoordinate().altitude();
580

581
    distanceOk = true;
582
    if (currentItem->coordinateHasRelativeAltitude()) {
583 584
        currentCoord.setAltitude(homeAlt + currentCoord.altitude());
    }
585
    if (prevItem->exitCoordinateHasRelativeAltitude()) {
586
        prevCoord.setAltitude(homeAlt + prevCoord.altitude());
587 588 589 590 591
    }

    qCDebug(MissionControllerLog) << "distanceOk" << distanceOk;

    if (distanceOk) {
Don Gagne's avatar
Don Gagne committed
592 593 594
        *altDifference = currentCoord.altitude() - prevCoord.altitude();
        *distance = prevCoord.distanceTo(currentCoord);
        *azimuth = prevCoord.azimuthTo(currentCoord);
595
    } else {
Don Gagne's avatar
Don Gagne committed
596
        *altDifference = 0.0;
597
        *azimuth = 0.0;
Don Gagne's avatar
Don Gagne committed
598
        *distance = -1.0;   // Signals no values
599 600 601
    }
}

602 603
void MissionController::_recalcWaypointLines(void)
{
604 605 606 607 608 609 610
    bool                firstCoordinateItem =   true;
    VisualMissionItem*  lastCoordinateItem =    qobject_cast<VisualMissionItem*>(_visualItems->get(0));

    SimpleMissionItem*  homeItem = qobject_cast<SimpleMissionItem*>(lastCoordinateItem);

    if (!homeItem) {
        qWarning() << "Home item is not SimpleMissionItem";
611 612
    } else {
        connect(homeItem, &VisualMissionItem::coordinateChanged, this, &MissionController::_recalcAltitudeRangeBearing);
613 614 615
    }

    bool    showHomePosition =  homeItem->showHomePosition();
616 617 618

    qCDebug(MissionControllerLog) << "_recalcWaypointLines";

Nate Weibley's avatar
Nate Weibley committed
619 620
    CoordVectHashTable old_table = _linesTable;
    _linesTable.clear();
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
    _waypointLines.clear();

    bool linkBackToHome = false;
    for (int i=1; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));


        // If we still haven't found the first coordinate item and we hit a a takeoff command link back to home
        if (firstCoordinateItem &&
                item->isSimpleItem() &&
                qobject_cast<SimpleMissionItem*>(item)->command() == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF) {
            linkBackToHome = true;
        }

        if (item->specifiesCoordinate()) {
            if (!item->isStandaloneCoordinate()) {
                firstCoordinateItem = false;
                VisualItemPair pair(lastCoordinateItem, item);
                if (lastCoordinateItem != homeItem || (showHomePosition && linkBackToHome)) {
                    if (old_table.contains(pair)) {
                        // Do nothing, this segment already exists and is wired up
Nate Weibley's avatar
Nate Weibley committed
642
                        _linesTable[pair] = old_table.take(pair);
643 644 645 646 647 648 649 650 651 652 653 654
                    } else {
                        // Create a new segment and wire update notifiers
                        auto linevect       = new CoordinateVector(lastCoordinateItem->isSimpleItem() ? lastCoordinateItem->coordinate() : lastCoordinateItem->exitCoordinate(), item->coordinate(), this);
                        auto originNotifier = lastCoordinateItem->isSimpleItem() ? &VisualMissionItem::coordinateChanged : &VisualMissionItem::exitCoordinateChanged,
                             endNotifier    = &VisualMissionItem::coordinateChanged;
                        // Use signals/slots to update the coordinate endpoints
                        connect(lastCoordinateItem, originNotifier, linevect, &CoordinateVector::setCoordinate1);
                        connect(item,               endNotifier,    linevect, &CoordinateVector::setCoordinate2);

                        // FIXME: We should ideally have signals for 2D position change, alt change, and 3D position change
                        // Not optimal, but still pretty fast, do a full update of range/bearing/altitudes
                        connect(item, &VisualMissionItem::coordinateChanged, this, &MissionController::_recalcAltitudeRangeBearing);
Nate Weibley's avatar
Nate Weibley committed
655
                        _linesTable[pair] = linevect;
656 657 658 659 660 661 662 663 664 665 666
                    }
                }
                lastCoordinateItem = item;
            }
        }
    }


    {
        // Create a temporary QObjectList and replace the model data
        QObjectList objs;
Nate Weibley's avatar
Nate Weibley committed
667 668
        objs.reserve(_linesTable.count());
        foreach(CoordinateVector *vect, _linesTable.values()) {
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
            objs.append(vect);
        }

        // We don't delete here because many links may still be valid
        _waypointLines.swapObjectList(objs);
    }


    // Anything left in the old table is an obsolete line object that can go
    qDeleteAll(old_table);

    _recalcAltitudeRangeBearing();

    emit waypointLinesChanged();
}

void MissionController::_recalcAltitudeRangeBearing()
{
    if (!_visualItems->count())
        return;

    bool                firstCoordinateItem =   true;
    VisualMissionItem*  lastCoordinateItem =    qobject_cast<VisualMissionItem*>(_visualItems->get(0));

    SimpleMissionItem*  homeItem = qobject_cast<SimpleMissionItem*>(lastCoordinateItem);

    if (!homeItem) {
        qWarning() << "Home item is not SimpleMissionItem";
    }

    bool    showHomePosition =  homeItem->showHomePosition();

    qCDebug(MissionControllerLog) << "_recalcAltitudeRangeBearing";

703 704 705
    // If home position is valid we can calculate distances between all waypoints.
    // If home position is not valid we can only calculate distances between waypoints which are
    // both relative altitude.
706

707
    // No values for first item
708
    lastCoordinateItem->setAltDifference(0.0);
709
    lastCoordinateItem->setAzimuth(0.0);
710 711
    lastCoordinateItem->setDistance(-1.0);

712 713
    double minAltSeen = 0.0;
    double maxAltSeen = 0.0;
714
    const double homePositionAltitude = homeItem->coordinate().altitude();
715 716
    minAltSeen = maxAltSeen = homeItem->coordinate().altitude();

Don Gagne's avatar
Don Gagne committed
717
    bool linkBackToHome = false;
718 719
    for (int i=1; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
720

721 722 723
        // Assume the worst
        item->setAzimuth(0.0);
        item->setDistance(-1.0);
724

725 726 727 728
        // If we still haven't found the first coordinate item and we hit a a takeoff command link back to home
        if (firstCoordinateItem &&
                item->isSimpleItem() &&
                qobject_cast<SimpleMissionItem*>(item)->command() == MavlinkQmlSingleton::MAV_CMD_NAV_TAKEOFF) {
Don Gagne's avatar
Don Gagne committed
729 730 731
            linkBackToHome = true;
        }

732
        if (item->specifiesCoordinate()) {
733 734
            // Keep track of the min/max altitude for all waypoints so we can show altitudes as a percentage

735
            double absoluteAltitude = item->coordinate().altitude();
736
            if (item->coordinateHasRelativeAltitude()) {
737 738 739 740 741
                absoluteAltitude += homePositionAltitude;
            }
            minAltSeen = std::min(minAltSeen, absoluteAltitude);
            maxAltSeen = std::max(maxAltSeen, absoluteAltitude);

742 743 744 745 746 747 748 749 750 751
            if (!item->exitCoordinateSameAsEntry()) {
                absoluteAltitude = item->exitCoordinate().altitude();
                if (item->exitCoordinateHasRelativeAltitude()) {
                    absoluteAltitude += homePositionAltitude;
                }
                minAltSeen = std::min(minAltSeen, absoluteAltitude);
                maxAltSeen = std::max(maxAltSeen, absoluteAltitude);
            }

            if (!item->isStandaloneCoordinate()) {
Don Gagne's avatar
Don Gagne committed
752
                firstCoordinateItem = false;
753
                if (lastCoordinateItem != homeItem || (showHomePosition && linkBackToHome)) {
754 755 756 757
                    double azimuth, distance, altDifference;

                    // Subsequent coordinate items link to last coordinate item. If the last coordinate item
                    // is an invalid home position we skip the line
758
                    _calcPrevWaypointValues(homePositionAltitude, item, lastCoordinateItem, &azimuth, &distance, &altDifference);
759 760 761
                    item->setAltDifference(altDifference);
                    item->setAzimuth(azimuth);
                    item->setDistance(distance);
762
                }
763 764 765 766 767 768 769
                lastCoordinateItem = item;
            }
        }
    }

    // Walk the list again calculating altitude percentages
    double altRange = maxAltSeen - minAltSeen;
770 771
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
772 773 774

        if (item->specifiesCoordinate()) {
            double absoluteAltitude = item->coordinate().altitude();
775
            if (item->coordinateHasRelativeAltitude()) {
776 777 778 779 780 781
                absoluteAltitude += homePositionAltitude;
            }
            if (altRange == 0.0) {
                item->setAltPercent(0.0);
            } else {
                item->setAltPercent((absoluteAltitude - minAltSeen) / altRange);
782
            }
783 784 785 786
        }
    }
}

787 788 789
// This will update the sequence numbers to be sequential starting from 0
void MissionController::_recalcSequence(void)
{
790 791 792 793 794 795 796 797 798
    // Setup ascending sequence numbers for all visual items

    int sequenceNumber = 0;
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));

        item->setSequenceNumber(sequenceNumber++);
        if (!item->isSimpleItem()) {
            ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(item);
799

800
            if (complexItem) {
801
                 sequenceNumber = complexItem->lastSequenceNumber() + 1;
802 803 804 805
            } else {
                qWarning() << "isSimpleItem == false, yet not ComplexMissionItem";
            }
        }
806 807 808
    }
}

809 810 811
// This will update the child item hierarchy
void MissionController::_recalcChildItems(void)
{
812
    VisualMissionItem* currentParentItem = qobject_cast<VisualMissionItem*>(_visualItems->get(0));
813 814 815

    currentParentItem->childItems()->clear();

816 817
    for (int i=1; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
818 819 820 821 822

        // Set up non-coordinate item child hierarchy
        if (item->specifiesCoordinate()) {
            item->childItems()->clear();
            currentParentItem = item;
823
        } else if (item->isSimpleItem()) {
824 825 826 827 828
            currentParentItem->childItems()->append(item);
        }
    }
}

829 830
void MissionController::_recalcAll(void)
{
831
    _recalcSequence();
832 833 834 835
    _recalcChildItems();
    _recalcWaypointLines();
}

836
/// Initializes a new set of mission items
837
void MissionController::_initAllVisualItems(void)
838
{
839 840 841 842 843 844 845 846 847
    SimpleMissionItem* homeItem = NULL;

    // Setup home position at index 0

    homeItem = qobject_cast<SimpleMissionItem*>(_visualItems->get(0));
    if (!homeItem) {
        qWarning() << "homeItem not SimpleMissionItem";
        return;
    }
848

Don Gagne's avatar
Don Gagne committed
849
    homeItem->setHomePositionSpecialCase(true);
850
    homeItem->setShowHomePosition(_editMode);
851 852
    homeItem->missionItem().setCommand(MAV_CMD_NAV_WAYPOINT);
    homeItem->missionItem().setFrame(MAV_FRAME_GLOBAL);
853 854 855 856 857
    homeItem->setIsCurrentItem(true);

    if (!_editMode && _activeVehicle && _activeVehicle->homePositionAvailable()) {
        homeItem->setCoordinate(_activeVehicle->homePosition());
        homeItem->setShowHomePosition(true);
858
    }
859

860
    QmlObjectListModel* newComplexItems = new QmlObjectListModel(this);
861 862 863
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
        _initVisualItem(item);
864

865 866 867
        // Set up complex item list
        if (!item->isSimpleItem()) {
            ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(item);
868

869 870 871 872 873
            if (complexItem) {
                newComplexItems->append(item);
            } else {
                qWarning() << "isSimpleItem == false, but not ComplexMissionItem";
            }
874
        }
875 876
    }

877 878 879 880
    if (_complexItems) {
        _complexItems->deleteLater();
    }
    _complexItems = newComplexItems;
881

882
    _recalcAll();
883

884 885
    emit visualItemsChanged();
    emit complexVisualItemsChanged();
886

887
    _visualItems->setDirty(false);
888

889
    connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::_dirtyChanged);
890 891
}

892
void MissionController::_deinitAllVisualItems(void)
893
{
894 895
    for (int i=0; i<_visualItems->count(); i++) {
        _deinitVisualItem(qobject_cast<VisualMissionItem*>(_visualItems->get(i)));
896 897
    }

898
    connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::_dirtyChanged);
899 900
}

901
void MissionController::_initVisualItem(VisualMissionItem* visualItem)
902
{
903 904
    _visualItems->setDirty(false);

905
    connect(visualItem, &VisualMissionItem::specifiesCoordinateChanged,                 this, &MissionController::_recalcWaypointLines);
906 907
    connect(visualItem, &VisualMissionItem::coordinateHasRelativeAltitudeChanged,       this, &MissionController::_recalcWaypointLines);
    connect(visualItem, &VisualMissionItem::exitCoordinateHasRelativeAltitudeChanged,   this, &MissionController::_recalcWaypointLines);
908

909 910 911 912 913 914 915 916
    if (visualItem->isSimpleItem()) {
        // We need to track commandChanged on simple item since recalc has special handling for takeoff command
        SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(visualItem);
        if (simpleItem) {
            connect(&simpleItem->missionItem()._commandFact, &Fact::valueChanged, this, &MissionController::_itemCommandChanged);
        } else {
            qWarning() << "isSimpleItem == true, yet not SimpleMissionItem";
        }
917 918 919 920
    } else {
        // We need to track changes of lastSequenceNumber so we can recalc sequence numbers for subsequence items
        ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(visualItem);
        connect(complexItem, &ComplexMissionItem::lastSequenceNumberChanged, this, &MissionController::_recalcSequence);
921
    }
922 923
}

924
void MissionController::_deinitVisualItem(VisualMissionItem* visualItem)
925
{
926 927
    // Disconnect all signals
    disconnect(visualItem, 0, 0, 0);
928 929
}

930
void MissionController::_itemCommandChanged(void)
931
{
932 933
    _recalcChildItems();
    _recalcWaypointLines();
934 935 936 937
}

void MissionController::_activeVehicleChanged(Vehicle* activeVehicle)
{
938 939
    qCDebug(MissionControllerLog) << "_activeVehicleChanged activeVehicle" << activeVehicle;

940 941
    if (_activeVehicle) {
        MissionManager* missionManager = _activeVehicle->missionManager();
942

943
        disconnect(missionManager, &MissionManager::newMissionItemsAvailable,   this, &MissionController::_newMissionItemsAvailableFromVehicle);
944
        disconnect(missionManager, &MissionManager::inProgressChanged,          this, &MissionController::_inProgressChanged);
945
        disconnect(missionManager, &MissionManager::currentItemChanged,         this, &MissionController::_currentMissionItemChanged);
946 947
        disconnect(_activeVehicle, &Vehicle::homePositionAvailableChanged,      this, &MissionController::_activeVehicleHomePositionAvailableChanged);
        disconnect(_activeVehicle, &Vehicle::homePositionChanged,               this, &MissionController::_activeVehicleHomePositionChanged);
948
        _activeVehicle = NULL;
Don Gagne's avatar
Don Gagne committed
949 950
    }

951
    _activeVehicle = activeVehicle;
952

953 954
    if (_activeVehicle) {
        MissionManager* missionManager = activeVehicle->missionManager();
955

956
        connect(missionManager, &MissionManager::newMissionItemsAvailable,  this, &MissionController::_newMissionItemsAvailableFromVehicle);
957
        connect(missionManager, &MissionManager::inProgressChanged,         this, &MissionController::_inProgressChanged);
958
        connect(missionManager, &MissionManager::currentItemChanged,        this, &MissionController::_currentMissionItemChanged);
959 960 961
        connect(_activeVehicle, &Vehicle::homePositionAvailableChanged,     this, &MissionController::_activeVehicleHomePositionAvailableChanged);
        connect(_activeVehicle, &Vehicle::homePositionChanged,              this, &MissionController::_activeVehicleHomePositionChanged);

962 963 964
        if (!_editMode) {
            removeAllMissionItems();
        }
965 966 967 968

        _activeVehicleHomePositionChanged(_activeVehicle->homePosition());
        _activeVehicleHomePositionAvailableChanged(_activeVehicle->homePositionAvailable());
    }
969 970 971

    // Whenever vehicle changes we need to update syncInProgress
    emit syncInProgressChanged(syncInProgress());
972 973 974 975
}

void MissionController::_activeVehicleHomePositionAvailableChanged(bool homePositionAvailable)
{
976 977 978 979 980 981 982 983 984
    if (!_editMode && _visualItems) {
        SimpleMissionItem* homeItem = qobject_cast<SimpleMissionItem*>(_visualItems->get(0));

        if (homeItem) {
            homeItem->setShowHomePosition(homePositionAvailable);
            _recalcWaypointLines();
        } else {
            qWarning() << "Unabled to cast home item to SimpleMissionItem";
        }
985
    }
986 987 988 989
}

void MissionController::_activeVehicleHomePositionChanged(const QGeoCoordinate& homePosition)
{
990 991
    if (!_editMode && _visualItems) {
        qobject_cast<VisualMissionItem*>(_visualItems->get(0))->setCoordinate(homePosition);
992 993
        _recalcWaypointLines();
    }
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
}

void MissionController::setAutoSync(bool autoSync)
{
    // FIXME: AutoSync temporarily turned off
#if 0
    _autoSync = autoSync;
    emit autoSyncChanged(_autoSync);

    if (_autoSync) {
        _dirtyChanged(true);
    }
#else
    Q_UNUSED(autoSync)
#endif
}

void MissionController::_dirtyChanged(bool dirty)
{
    if (dirty && _autoSync) {
1014
        Vehicle* activeVehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle();
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028

        if (activeVehicle && !activeVehicle->armed()) {
            if (_activeVehicle->missionManager()->inProgress()) {
                _queuedSend = true;
            } else {
                _autoSyncSend();
            }
        }
    }
}

void MissionController::_autoSyncSend(void)
{
    _queuedSend = false;
1029
    if (_visualItems) {
1030
        sendMissionItems();
1031
        _visualItems->setDirty(false);
1032 1033
    }
}
1034

1035
void MissionController::_inProgressChanged(bool inProgress)
1036
{
1037
    emit syncInProgressChanged(inProgress);
1038 1039 1040 1041
    if (!inProgress && _queuedSend) {
        _autoSyncSend();
    }
}
1042

1043
bool MissionController::_findLastAltitude(double* lastAltitude)
1044
{
1045 1046
    bool found = false;
    double foundAltitude;
1047

1048
    // Don't use home position
1049
    for (int i=1; i<_visualItems->count(); i++) {
1050
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1051

1052 1053
        if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) {
            foundAltitude = visualItem->exitCoordinate().altitude();
1054
            found = true;
1055 1056 1057 1058 1059 1060 1061

            if (visualItem->isSimpleItem()) {
                SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(visualItem);
                if ((MAV_CMD)simpleItem->command() == MAV_CMD_NAV_TAKEOFF) {
                    found = false;
                }
            }
1062 1063 1064
        }
    }

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076
    if (found) {
        *lastAltitude = foundAltitude;
    }

    return found;
}

bool MissionController::_findLastAcceptanceRadius(double* lastAcceptanceRadius)
{
    bool found = false;
    double foundAcceptanceRadius;

1077 1078
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1079

1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
        if (visualItem->isSimpleItem()) {
            SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(visualItem);

            if (simpleItem) {
                if ((MAV_CMD)simpleItem->command() == MAV_CMD_NAV_WAYPOINT) {
                    foundAcceptanceRadius = simpleItem->missionItem().param2();
                    found = true;
                }
            } else {
                qWarning() << "isSimpleItem == true, yet not SimpleMissionItem";
            }
1091 1092 1093 1094 1095 1096 1097 1098
        }
    }

    if (found) {
        *lastAcceptanceRadius = foundAcceptanceRadius;
    }

    return found;
1099
}
1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113

double MissionController::_normalizeLat(double lat)
{
    // Normalize latitude to range: 0 to 180, S to N
    return lat + 90.0;
}

double MissionController::_normalizeLon(double lon)
{
    // Normalize longitude to range: 0 to 360, W to E
    return lon  + 180.0;
}

/// Add the home position item to the front of the list
1114
void MissionController::_addPlannedHomePosition(QmlObjectListModel* visualItems, bool addToCenter)
1115
{
1116 1117
    SimpleMissionItem* homeItem = new SimpleMissionItem(_activeVehicle, this);
    visualItems->insert(0, homeItem);
1118

1119 1120
    if (visualItems->count() > 1  && addToCenter) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(visualItems->get(1));
1121 1122 1123 1124 1125 1126

        double north = _normalizeLat(item->coordinate().latitude());
        double south = north;
        double east = _normalizeLon(item->coordinate().longitude());
        double west = east;

1127 1128
        for (int i=2; i<visualItems->count(); i++) {
            item = qobject_cast<VisualMissionItem*>(visualItems->get(i));
1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143

            double lat = _normalizeLat(item->coordinate().latitude());
            double lon = _normalizeLon(item->coordinate().longitude());

            north = fmax(north, lat);
            south = fmin(south, lat);
            east = fmax(east, lon);
            west = fmin(west, lon);
        }

        homeItem->setCoordinate(QGeoCoordinate((south + ((north - south) / 2)) - 90.0, (west + ((east - west) / 2)) - 180.0, 0.0));
    } else {
        homeItem->setCoordinate(qgcApp()->lastKnownHomePosition());
    }
}
1144 1145 1146 1147 1148 1149 1150 1151

void MissionController::_currentMissionItemChanged(int sequenceNumber)
{
    if (!_editMode) {
        if (!_activeVehicle->firmwarePlugin()->sendHomePositionToVehicle()) {
            sequenceNumber++;
        }

1152 1153
        for (int i=0; i<_visualItems->count(); i++) {
            VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1154 1155 1156 1157
            item->setIsCurrentItem(item->sequenceNumber() == sequenceNumber);
        }
    }
}
1158

1159 1160
bool MissionController::syncInProgress(void)
{
1161 1162 1163 1164 1165
    if (_activeVehicle) {
        return _activeVehicle->missionManager()->inProgress();
    } else {
        return false;
    }
1166
}