MissionController.cc 38.3 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 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 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 611 612 613 614
    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();
    double  homeAlt =           homeItem->coordinate().altitude();
615 616 617 618 619 620

    qCDebug(MissionControllerLog) << "_recalcWaypointLines";

    // 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.
621

622
    // No values for first item
623
    lastCoordinateItem->setAltDifference(0.0);
624
    lastCoordinateItem->setAzimuth(0.0);
625 626
    lastCoordinateItem->setDistance(-1.0);

627 628 629 630 631
    double minAltSeen = 0.0;
    double maxAltSeen = 0.0;
    double homePositionAltitude = homeItem->coordinate().altitude();
    minAltSeen = maxAltSeen = homeItem->coordinate().altitude();

632
    _waypointLines.clearAndDeleteContents();
633

Don Gagne's avatar
Don Gagne committed
634
    bool linkBackToHome = false;
635 636
    for (int i=1; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
637

638 639 640
        // Assume the worst
        item->setAzimuth(0.0);
        item->setDistance(-1.0);
641

642 643 644 645
        // 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
646 647 648
            linkBackToHome = true;
        }

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

652
            double absoluteAltitude = item->coordinate().altitude();
653
            if (item->coordinateHasRelativeAltitude()) {
654 655 656 657 658
                absoluteAltitude += homePositionAltitude;
            }
            minAltSeen = std::min(minAltSeen, absoluteAltitude);
            maxAltSeen = std::max(maxAltSeen, absoluteAltitude);

659 660 661 662 663 664 665 666 667 668
            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
669
                firstCoordinateItem = false;
670
                if (lastCoordinateItem != homeItem || (showHomePosition && linkBackToHome)) {
671 672 673 674
                    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
675
                    _calcPrevWaypointValues(homeAlt, item, lastCoordinateItem, &azimuth, &distance, &altDifference);
676 677 678
                    item->setAltDifference(altDifference);
                    item->setAzimuth(azimuth);
                    item->setDistance(distance);
679
                    _waypointLines.append(new CoordinateVector(lastCoordinateItem->isSimpleItem() ? lastCoordinateItem->coordinate() : lastCoordinateItem->exitCoordinate(), item->coordinate()));
680
                }
681 682 683 684 685 686 687
                lastCoordinateItem = item;
            }
        }
    }

    // Walk the list again calculating altitude percentages
    double altRange = maxAltSeen - minAltSeen;
688 689
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
690 691 692

        if (item->specifiesCoordinate()) {
            double absoluteAltitude = item->coordinate().altitude();
693
            if (item->coordinateHasRelativeAltitude()) {
694 695 696 697 698 699
                absoluteAltitude += homePositionAltitude;
            }
            if (altRange == 0.0) {
                item->setAltPercent(0.0);
            } else {
                item->setAltPercent((absoluteAltitude - minAltSeen) / altRange);
700
            }
701 702
        }
    }
703 704

    emit waypointLinesChanged();
705 706
}

707 708 709
// This will update the sequence numbers to be sequential starting from 0
void MissionController::_recalcSequence(void)
{
710 711 712 713 714 715 716 717 718
    // 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);
719

720
            if (complexItem) {
721
                 sequenceNumber = complexItem->lastSequenceNumber() + 1;
722 723 724 725
            } else {
                qWarning() << "isSimpleItem == false, yet not ComplexMissionItem";
            }
        }
726 727 728
    }
}

729 730 731
// This will update the child item hierarchy
void MissionController::_recalcChildItems(void)
{
732
    VisualMissionItem* currentParentItem = qobject_cast<VisualMissionItem*>(_visualItems->get(0));
733 734 735

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

736 737
    for (int i=1; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
738 739 740 741 742

        // Set up non-coordinate item child hierarchy
        if (item->specifiesCoordinate()) {
            item->childItems()->clear();
            currentParentItem = item;
743
        } else if (item->isSimpleItem()) {
744 745 746 747 748
            currentParentItem->childItems()->append(item);
        }
    }
}

749 750
void MissionController::_recalcAll(void)
{
751
    _recalcSequence();
752 753 754 755
    _recalcChildItems();
    _recalcWaypointLines();
}

756
/// Initializes a new set of mission items
757
void MissionController::_initAllVisualItems(void)
758
{
759 760 761 762 763 764 765 766 767
    SimpleMissionItem* homeItem = NULL;

    // Setup home position at index 0

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

Don Gagne's avatar
Don Gagne committed
769
    homeItem->setHomePositionSpecialCase(true);
770
    homeItem->setShowHomePosition(_editMode);
771 772
    homeItem->missionItem().setCommand(MAV_CMD_NAV_WAYPOINT);
    homeItem->missionItem().setFrame(MAV_FRAME_GLOBAL);
773 774 775 776 777
    homeItem->setIsCurrentItem(true);

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

780
    QmlObjectListModel* newComplexItems = new QmlObjectListModel(this);
781 782 783
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
        _initVisualItem(item);
784

785 786 787
        // Set up complex item list
        if (!item->isSimpleItem()) {
            ComplexMissionItem* complexItem = qobject_cast<ComplexMissionItem*>(item);
788

789 790 791 792 793
            if (complexItem) {
                newComplexItems->append(item);
            } else {
                qWarning() << "isSimpleItem == false, but not ComplexMissionItem";
            }
794
        }
795 796
    }

797 798 799 800
    if (_complexItems) {
        _complexItems->deleteLater();
    }
    _complexItems = newComplexItems;
801

802
    _recalcAll();
803

804 805
    emit visualItemsChanged();
    emit complexVisualItemsChanged();
806

807
    _visualItems->setDirty(false);
808

809
    connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::_dirtyChanged);
810 811
}

812
void MissionController::_deinitAllVisualItems(void)
813
{
814 815
    for (int i=0; i<_visualItems->count(); i++) {
        _deinitVisualItem(qobject_cast<VisualMissionItem*>(_visualItems->get(i)));
816 817
    }

818
    connect(_visualItems, &QmlObjectListModel::dirtyChanged, this, &MissionController::_dirtyChanged);
819 820
}

821
void MissionController::_initVisualItem(VisualMissionItem* visualItem)
822
{
823 824 825
    _visualItems->setDirty(false);

    connect(visualItem, &VisualMissionItem::coordinateChanged,                          this, &MissionController::_itemCoordinateChanged);
826
    connect(visualItem, &VisualMissionItem::specifiesCoordinateChanged,                 this, &MissionController::_recalcWaypointLines);
827 828
    connect(visualItem, &VisualMissionItem::coordinateHasRelativeAltitudeChanged,       this, &MissionController::_recalcWaypointLines);
    connect(visualItem, &VisualMissionItem::exitCoordinateHasRelativeAltitudeChanged,   this, &MissionController::_recalcWaypointLines);
829

830 831 832 833 834 835 836 837
    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";
        }
838 839 840 841
    } 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);
842
    }
843 844
}

845
void MissionController::_deinitVisualItem(VisualMissionItem* visualItem)
846
{
847 848
    // Disconnect all signals
    disconnect(visualItem, 0, 0, 0);
849 850 851 852 853 854 855 856
}

void MissionController::_itemCoordinateChanged(const QGeoCoordinate& coordinate)
{
    Q_UNUSED(coordinate);
    _recalcWaypointLines();
}

857
void MissionController::_itemCommandChanged(void)
858
{
859 860
    _recalcChildItems();
    _recalcWaypointLines();
861 862 863 864
}

void MissionController::_activeVehicleChanged(Vehicle* activeVehicle)
{
865 866
    qCDebug(MissionControllerLog) << "_activeVehicleChanged activeVehicle" << activeVehicle;

867 868
    if (_activeVehicle) {
        MissionManager* missionManager = _activeVehicle->missionManager();
869

870
        disconnect(missionManager, &MissionManager::newMissionItemsAvailable,   this, &MissionController::_newMissionItemsAvailableFromVehicle);
871
        disconnect(missionManager, &MissionManager::inProgressChanged,          this, &MissionController::_inProgressChanged);
872
        disconnect(missionManager, &MissionManager::currentItemChanged,         this, &MissionController::_currentMissionItemChanged);
873 874
        disconnect(_activeVehicle, &Vehicle::homePositionAvailableChanged,      this, &MissionController::_activeVehicleHomePositionAvailableChanged);
        disconnect(_activeVehicle, &Vehicle::homePositionChanged,               this, &MissionController::_activeVehicleHomePositionChanged);
875
        _activeVehicle = NULL;
Don Gagne's avatar
Don Gagne committed
876 877
    }

878
    _activeVehicle = activeVehicle;
879

880 881
    if (_activeVehicle) {
        MissionManager* missionManager = activeVehicle->missionManager();
882

883
        connect(missionManager, &MissionManager::newMissionItemsAvailable,  this, &MissionController::_newMissionItemsAvailableFromVehicle);
884
        connect(missionManager, &MissionManager::inProgressChanged,         this, &MissionController::_inProgressChanged);
885
        connect(missionManager, &MissionManager::currentItemChanged,        this, &MissionController::_currentMissionItemChanged);
886 887 888
        connect(_activeVehicle, &Vehicle::homePositionAvailableChanged,     this, &MissionController::_activeVehicleHomePositionAvailableChanged);
        connect(_activeVehicle, &Vehicle::homePositionChanged,              this, &MissionController::_activeVehicleHomePositionChanged);

889 890 891
        if (!_editMode) {
            removeAllMissionItems();
        }
892 893 894 895

        _activeVehicleHomePositionChanged(_activeVehicle->homePosition());
        _activeVehicleHomePositionAvailableChanged(_activeVehicle->homePositionAvailable());
    }
896 897 898

    // Whenever vehicle changes we need to update syncInProgress
    emit syncInProgressChanged(syncInProgress());
899 900 901 902
}

void MissionController::_activeVehicleHomePositionAvailableChanged(bool homePositionAvailable)
{
903 904 905 906 907 908 909 910 911
    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";
        }
912
    }
913 914 915 916
}

void MissionController::_activeVehicleHomePositionChanged(const QGeoCoordinate& homePosition)
{
917 918
    if (!_editMode && _visualItems) {
        qobject_cast<VisualMissionItem*>(_visualItems->get(0))->setCoordinate(homePosition);
919 920
        _recalcWaypointLines();
    }
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
}

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) {
941
        Vehicle* activeVehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle();
942 943 944 945 946 947 948 949 950 951 952 953 954 955

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

void MissionController::_autoSyncSend(void)
{
    _queuedSend = false;
956
    if (_visualItems) {
957
        sendMissionItems();
958
        _visualItems->setDirty(false);
959 960
    }
}
961

962
void MissionController::_inProgressChanged(bool inProgress)
963
{
964
    emit syncInProgressChanged(inProgress);
965 966 967 968
    if (!inProgress && _queuedSend) {
        _autoSyncSend();
    }
}
969

970
bool MissionController::_findLastAltitude(double* lastAltitude)
971
{
972 973
    bool found = false;
    double foundAltitude;
974

975
    // Don't use home position
976
    for (int i=1; i<_visualItems->count(); i++) {
977
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
978

979 980
        if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) {
            foundAltitude = visualItem->exitCoordinate().altitude();
981
            found = true;
982 983 984 985 986 987 988

            if (visualItem->isSimpleItem()) {
                SimpleMissionItem* simpleItem = qobject_cast<SimpleMissionItem*>(visualItem);
                if ((MAV_CMD)simpleItem->command() == MAV_CMD_NAV_TAKEOFF) {
                    found = false;
                }
            }
989 990 991
        }
    }

992 993 994 995 996 997 998 999 1000 1001 1002 1003
    if (found) {
        *lastAltitude = foundAltitude;
    }

    return found;
}

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

1004 1005
    for (int i=0; i<_visualItems->count(); i++) {
        VisualMissionItem* visualItem = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1006

1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017
        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";
            }
1018 1019 1020 1021 1022 1023 1024 1025
        }
    }

    if (found) {
        *lastAcceptanceRadius = foundAcceptanceRadius;
    }

    return found;
1026
}
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040

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
1041
void MissionController::_addPlannedHomePosition(QmlObjectListModel* visualItems, bool addToCenter)
1042
{
1043 1044
    SimpleMissionItem* homeItem = new SimpleMissionItem(_activeVehicle, this);
    visualItems->insert(0, homeItem);
1045

1046 1047
    if (visualItems->count() > 1  && addToCenter) {
        VisualMissionItem* item = qobject_cast<VisualMissionItem*>(visualItems->get(1));
1048 1049 1050 1051 1052 1053

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

1054 1055
        for (int i=2; i<visualItems->count(); i++) {
            item = qobject_cast<VisualMissionItem*>(visualItems->get(i));
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070

            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());
    }
}
1071 1072 1073 1074 1075 1076 1077 1078

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

1079 1080
        for (int i=0; i<_visualItems->count(); i++) {
            VisualMissionItem* item = qobject_cast<VisualMissionItem*>(_visualItems->get(i));
1081 1082 1083 1084
            item->setIsCurrentItem(item->sequenceNumber() == sequenceNumber);
        }
    }
}
1085

1086 1087
bool MissionController::syncInProgress(void)
{
1088 1089 1090 1091 1092
    if (_activeVehicle) {
        return _activeVehicle->missionManager()->inProgress();
    } else {
        return false;
    }
1093
}