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

#include "PlanMasterController.h"
#include "QGCApplication.h"
#include "MultiVehicleManager.h"
13
#include "SettingsManager.h"
14 15
#include "AppSettings.h"
#include "JsonHelper.h"
16
#include "MissionManager.h"
17 18

#include <QJsonDocument>
Don Gagne's avatar
Don Gagne committed
19
#include <QFileInfo>
20

DonLakeFlyer's avatar
DonLakeFlyer committed
21 22
QGC_LOGGING_CATEGORY(PlanMasterControllerLog, "PlanMasterControllerLog")

23 24 25 26 27 28 29 30 31
const int   PlanMasterController::_planFileVersion =            1;
const char* PlanMasterController::_planFileType =               "Plan";
const char* PlanMasterController::_jsonMissionObjectKey =       "mission";
const char* PlanMasterController::_jsonGeoFenceObjectKey =      "geoFence";
const char* PlanMasterController::_jsonRallyPointsObjectKey =   "rallyPoints";

PlanMasterController::PlanMasterController(QObject* parent)
    : QObject(parent)
    , _multiVehicleMgr(qgcApp()->toolbox()->multiVehicleManager())
32 33
    , _controllerVehicle(new Vehicle((MAV_AUTOPILOT)qgcApp()->toolbox()->settingsManager()->appSettings()->offlineEditingFirmwareType()->rawValue().toInt(), (MAV_TYPE)qgcApp()->toolbox()->settingsManager()->appSettings()->offlineEditingVehicleType()->rawValue().toInt(), qgcApp()->toolbox()->firmwarePluginManager()))
    , _managerVehicle(_controllerVehicle)
34
    , _editMode(false)
35 36 37 38
    , _offline(true)
    , _missionController(this)
    , _geoFenceController(this)
    , _rallyPointController(this)
39 40 41 42
    , _loadGeoFence(false)
    , _loadRallyPoints(false)
    , _sendGeoFence(false)
    , _sendRallyPoints(false)
DonLakeFlyer's avatar
DonLakeFlyer committed
43
    , _syncInProgress(false)
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
{
    connect(&_missionController,    &MissionController::dirtyChanged,       this, &PlanMasterController::dirtyChanged);
    connect(&_geoFenceController,   &GeoFenceController::dirtyChanged,      this, &PlanMasterController::dirtyChanged);
    connect(&_rallyPointController, &RallyPointController::dirtyChanged,    this, &PlanMasterController::dirtyChanged);

    connect(&_missionController,    &MissionController::containsItemsChanged,       this, &PlanMasterController::containsItemsChanged);
    connect(&_geoFenceController,   &GeoFenceController::containsItemsChanged,      this, &PlanMasterController::containsItemsChanged);
    connect(&_rallyPointController, &RallyPointController::containsItemsChanged,    this, &PlanMasterController::containsItemsChanged);

    connect(&_missionController,    &MissionController::syncInProgressChanged,      this, &PlanMasterController::syncInProgressChanged);
    connect(&_geoFenceController,   &GeoFenceController::syncInProgressChanged,     this, &PlanMasterController::syncInProgressChanged);
    connect(&_rallyPointController, &RallyPointController::syncInProgressChanged,   this, &PlanMasterController::syncInProgressChanged);
}

PlanMasterController::~PlanMasterController()
{

}

void PlanMasterController::start(bool editMode)
{
    _editMode = editMode;
    _missionController.start(editMode);
    _geoFenceController.start(editMode);
    _rallyPointController.start(editMode);

    connect(_multiVehicleMgr, &MultiVehicleManager::activeVehicleChanged, this, &PlanMasterController::_activeVehicleChanged);
    _activeVehicleChanged(_multiVehicleMgr->activeVehicle());
}

void PlanMasterController::startStaticActiveVehicle(Vehicle* vehicle)
{
    _editMode = false;
77 78 79
    _missionController.start(false);
    _geoFenceController.start(false);
    _rallyPointController.start(false);
80 81 82 83 84
    _activeVehicleChanged(vehicle);
}

void PlanMasterController::_activeVehicleChanged(Vehicle* activeVehicle)
{
85 86 87
    if (_managerVehicle == activeVehicle) {
        // We are already setup for this vehicle
        return;
88 89
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
90 91
    qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged" << activeVehicle;

92 93 94 95 96 97 98 99 100 101
    if (_managerVehicle) {
        // Disconnect old vehicle
        disconnect(_managerVehicle->missionManager(),       &MissionManager::newMissionItemsAvailable,  this, &PlanMasterController::_loadMissionComplete);
        disconnect(_managerVehicle->geoFenceManager(),      &GeoFenceManager::loadComplete,             this, &PlanMasterController::_loadGeoFenceComplete);
        disconnect(_managerVehicle->rallyPointManager(),    &RallyPointManager::loadComplete,           this, &PlanMasterController::_loadRallyPointsComplete);
        disconnect(_managerVehicle->missionManager(),       &MissionManager::sendComplete,              this, &PlanMasterController::_sendMissionComplete);
        disconnect(_managerVehicle->geoFenceManager(),      &GeoFenceManager::sendComplete,             this, &PlanMasterController::_sendGeoFenceComplete);
        disconnect(_managerVehicle->rallyPointManager(),    &RallyPointManager::sendComplete,           this, &PlanMasterController::_sendRallyPointsComplete);
    }

102 103 104 105 106
    bool newOffline = false;
    if (activeVehicle == NULL) {
        // Since there is no longer an active vehicle we use the offline controller vehicle as the manager vehicle
        _managerVehicle = _controllerVehicle;
        newOffline = true;
107
    } else {
108
        newOffline = false;
109 110 111 112 113 114 115 116
        _managerVehicle = activeVehicle;

        // Update controllerVehicle to the currently connected vehicle
        AppSettings* appSettings = qgcApp()->toolbox()->settingsManager()->appSettings();
        appSettings->offlineEditingFirmwareType()->setRawValue(AppSettings::offlineEditingFirmwareTypeFromFirmwareType(_managerVehicle->firmwareType()));
        appSettings->offlineEditingVehicleType()->setRawValue(AppSettings::offlineEditingVehicleTypeFromVehicleType(_managerVehicle->vehicleType()));

        // We use these signals to sequence upload and download to the multiple controller/managers
DonLakeFlyer's avatar
DonLakeFlyer committed
117 118 119 120 121 122
        connect(_managerVehicle->missionManager(),      &MissionManager::newMissionItemsAvailable,  this, &PlanMasterController::_loadMissionComplete);
        connect(_managerVehicle->geoFenceManager(),     &GeoFenceManager::loadComplete,             this, &PlanMasterController::_loadGeoFenceComplete);
        connect(_managerVehicle->rallyPointManager(),   &RallyPointManager::loadComplete,           this, &PlanMasterController::_loadRallyPointsComplete);
        connect(_managerVehicle->missionManager(),      &MissionManager::sendComplete,              this, &PlanMasterController::_sendMissionComplete);
        connect(_managerVehicle->geoFenceManager(),     &GeoFenceManager::sendComplete,             this, &PlanMasterController::_sendGeoFenceComplete);
        connect(_managerVehicle->rallyPointManager(),   &RallyPointManager::sendComplete,           this, &PlanMasterController::_sendRallyPointsComplete);
123 124 125 126
    }
    if (newOffline != _offline) {
        _offline = newOffline;
        emit offlineEditingChanged(newOffline);
127 128
    }

129 130 131
    _missionController.managerVehicleChanged(_managerVehicle);
    _geoFenceController.managerVehicleChanged(_managerVehicle);
    _rallyPointController.managerVehicleChanged(_managerVehicle);
132

DonLakeFlyer's avatar
DonLakeFlyer committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
    if (_editMode) {
        if (!offline()) {
            // We are in Plan view and we have a newly connected vehicle:
            //  - If there is no plan available in Plan view show the one from the vehicle
            //  - Otherwise leave the current plan alone
            if (!containsItems()) {
                qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Plan view is empty so loading from manager";
                _showPlanFromManagerVehicle();
            }
        }
    } else {
        if (offline()) {
            // No more active vehicle, clear mission
            qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Fly view is offline clearing plan";
            removeAll();
        } else {
            // Fly view has changed to a new active vehicle, update to show correct mission
            qCDebug(PlanMasterControllerLog) << "_activeVehicleChanged: Fly view is online so loading from manager";
            _showPlanFromManagerVehicle();
        }
153
    }
154 155 156 157
}

void PlanMasterController::loadFromVehicle(void)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
158 159 160 161 162 163 164
    if (offline()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while offline";
    } else if (!_editMode) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called from Fly view";
    } else if (syncInProgress()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle called while syncInProgress";
    } else {
165
        _loadGeoFence = true;
DonLakeFlyer's avatar
DonLakeFlyer committed
166
        _syncInProgress = true;
167
        emit syncInProgressChanged(true);
DonLakeFlyer's avatar
DonLakeFlyer committed
168
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::loadFromVehicle _missionController.loadFromVehicle";
169 170 171
        _missionController.loadFromVehicle();
        setDirty(false);
    }
172 173
}

174

DonLakeFlyer's avatar
DonLakeFlyer committed
175
void PlanMasterController::_loadMissionComplete(void)
176
{
DonLakeFlyer's avatar
DonLakeFlyer committed
177
    if (_editMode && _loadGeoFence) {
178 179
        _loadGeoFence = false;
        _loadRallyPoints = true;
180 181 182 183 184 185 186 187
        if (_geoFenceController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete _geoFenceController.loadFromVehicle";
            _geoFenceController.loadFromVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete GeoFence not supported skipping";
            _geoFenceController.removeAll();
            _loadGeoFenceComplete();
        }
188
        setDirty(false);
DonLakeFlyer's avatar
DonLakeFlyer committed
189 190 191 192 193 194 195
    }
}

void PlanMasterController::_loadGeoFenceComplete(void)
{
    if (_editMode && _loadRallyPoints) {
        _loadRallyPoints = false;
196 197 198 199 200 201 202 203
        if (_rallyPointController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadGeoFenceComplete _rallyPointController.loadFromVehicle";
            _rallyPointController.loadFromVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::_loadMissionComplete Rally Points not supported skipping";
            _rallyPointController.removeAll();
            _loadRallyPointsComplete();
        }
DonLakeFlyer's avatar
DonLakeFlyer committed
204 205 206 207 208 209 210 211 212 213 214 215 216 217
        setDirty(false);
    }
}

void PlanMasterController::_loadRallyPointsComplete(void)
{
    if (_editMode) {
        _syncInProgress = false;
        emit syncInProgressChanged(false);
    }
}

void PlanMasterController::_sendMissionComplete(void)
{
218
    if (_sendGeoFence) {
219 220
        _sendGeoFence = false;
        _sendRallyPoints = true;
221 222 223 224 225 226 227
        if (_geoFenceController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
            _geoFenceController.sendToVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle GeoFence not supported skipping";
            _sendGeoFenceComplete();
        }
228 229 230 231
        setDirty(false);
    }
}

DonLakeFlyer's avatar
DonLakeFlyer committed
232
void PlanMasterController::_sendGeoFenceComplete(void)
233
{
234
    if (_sendRallyPoints) {
235
        _sendRallyPoints = false;
236 237 238 239 240 241 242
        if (_rallyPointController.supported()) {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start rally sendToVehicle";
            _rallyPointController.sendToVehicle();
        } else {
            qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Points not support skipping";
            _sendRallyPointsComplete();
        }
243 244 245
    }
}

DonLakeFlyer's avatar
DonLakeFlyer committed
246 247
void PlanMasterController::_sendRallyPointsComplete(void)
{
248
    if (_syncInProgress) {
249
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle Rally Point send complete";
DonLakeFlyer's avatar
DonLakeFlyer committed
250 251 252 253 254
        _syncInProgress = false;
        emit syncInProgressChanged(false);
    }
}

255 256
void PlanMasterController::sendToVehicle(void)
{
DonLakeFlyer's avatar
DonLakeFlyer committed
257 258 259 260 261 262
    if (offline()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle called while offline";
    } else if (syncInProgress()) {
        qCWarning(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle called while syncInProgress";
    } else {
        qCDebug(PlanMasterControllerLog) << "PlanMasterController::sendToVehicle start mission sendToVehicle";
263
        _sendGeoFence = true;
264 265
        _syncInProgress = true;
        emit syncInProgressChanged(true);
266 267 268
        _missionController.sendToVehicle();
        setDirty(false);
    }
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
}

void PlanMasterController::loadFromFile(const QString& filename)
{
    QString errorString;
    QString errorMessage = tr("Error reading Plan file (%1). %2").arg(filename).arg("%1");

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

    QFile file(filename);

    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        errorString = file.errorString() + QStringLiteral(" ") + filename;
284
        qgcApp()->showMessage(errorMessage.arg(errorString));
285 286 287
        return;
    }

288 289 290 291 292 293 294 295 296 297 298 299 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
    QString fileExtension(".%1");
    if (filename.endsWith(fileExtension.arg(AppSettings::planFileExtension))) {
        QJsonDocument   jsonDoc;
        QByteArray      bytes = file.readAll();

        if (!JsonHelper::isJsonFile(bytes, jsonDoc, errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
            return;
        }

        int version;
        QJsonObject json = jsonDoc.object();
        if (!JsonHelper::validateQGCJsonFile(json, _planFileType, _planFileVersion, _planFileVersion, version, errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
            return;
        }

        QList<JsonHelper::KeyValidateInfo> rgKeyInfo = {
            { _jsonMissionObjectKey,        QJsonValue::Object, true },
            { _jsonGeoFenceObjectKey,       QJsonValue::Object, true },
            { _jsonRallyPointsObjectKey,    QJsonValue::Object, true },
        };
        if (!JsonHelper::validateKeys(json, rgKeyInfo, errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
            return;
        }

        if (!_missionController.load(json[_jsonMissionObjectKey].toObject(), errorString) ||
                !_geoFenceController.load(json[_jsonGeoFenceObjectKey].toObject(), errorString) ||
                !_rallyPointController.load(json[_jsonRallyPointsObjectKey].toObject(), errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
        }
    } else if (filename.endsWith(fileExtension.arg(AppSettings::missionFileExtension))) {
        if (!_missionController.loadJsonFile(file, errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
        }
    } else if (filename.endsWith(fileExtension.arg(AppSettings::waypointsFileExtension)) ||
               filename.endsWith(fileExtension.arg(QStringLiteral("txt")))) {
        if (!_missionController.loadTextFile(file, errorString)) {
            qgcApp()->showMessage(errorMessage.arg(errorString));
        }
329 330
    }

331
    if (!offline()) {
332
        setDirty(true);
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
    }
}

void PlanMasterController::saveToFile(const QString& filename)
{
    if (filename.isEmpty()) {
        return;
    }

    QString planFilename = filename;
    if (!QFileInfo(filename).fileName().contains(".")) {
        planFilename += QString(".%1").arg(fileExtension());
    }

    QFile file(planFilename);

    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qgcApp()->showMessage(tr("Plan save error %1 : %2").arg(filename).arg(file.errorString()));
    } else {
        QJsonObject planJson;
        QJsonObject missionJson;
        QJsonObject fenceJson;
        QJsonObject rallyJson;

        JsonHelper::saveQGCJsonFileHeader(planJson, _planFileType, _planFileVersion);
        _missionController.save(missionJson);
        _geoFenceController.save(fenceJson);
        _rallyPointController.save(rallyJson);
        planJson[_jsonMissionObjectKey] = missionJson;
        planJson[_jsonGeoFenceObjectKey] = fenceJson;
        planJson[_jsonRallyPointsObjectKey] = rallyJson;

        QJsonDocument saveDoc(planJson);
        file.write(saveDoc.toJson());
    }

369 370
    // Only clear dirty bit if we are offline
    if (offline()) {
371 372 373 374 375 376
        setDirty(false);
    }
}

void PlanMasterController::removeAll(void)
{
377 378 379
    _missionController.removeAll();
    _geoFenceController.removeAll();
    _rallyPointController.removeAll();
380 381 382 383
}

void PlanMasterController::removeAllFromVehicle(void)
{
384 385
    if (!offline()) {
        _missionController.removeAllFromVehicle();
386 387 388 389 390 391
        if (_geoFenceController.supported()) {
            _geoFenceController.removeAllFromVehicle();
        }
        if (_rallyPointController.supported()) {
            _rallyPointController.removeAllFromVehicle();
        }
392
        setDirty(false);
393 394 395
    } else {
        qWarning() << "PlanMasterController::removeAllFromVehicle called while offline";
    }
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
}

bool PlanMasterController::containsItems(void) const
{
    return _missionController.containsItems() || _geoFenceController.containsItems() || _rallyPointController.containsItems();
}

bool PlanMasterController::dirty(void) const
{
    return _missionController.dirty() || _geoFenceController.dirty() || _rallyPointController.dirty();
}

void PlanMasterController::setDirty(bool dirty)
{
    _missionController.setDirty(dirty);
    _geoFenceController.setDirty(dirty);
    _rallyPointController.setDirty(dirty);
}

QString PlanMasterController::fileExtension(void) const
{
    return AppSettings::planFileExtension;
}

QStringList PlanMasterController::loadNameFilters(void) const
{
    QStringList filters;

Don Gagne's avatar
Don Gagne committed
424
    filters << tr("Supported types (*.%1 *.%2 *.%3 *.%4)").arg(AppSettings::planFileExtension).arg(AppSettings::missionFileExtension).arg(AppSettings::waypointsFileExtension).arg("txt") <<
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
               tr("All Files (*.*)");
    return filters;
}


QStringList PlanMasterController::saveNameFilters(void) const
{
    QStringList filters;

    filters << tr("Plan Files (*.%1)").arg(fileExtension()) << tr("All Files (*.*)");
    return filters;
}

void PlanMasterController::sendPlanToVehicle(Vehicle* vehicle, const QString& filename)
{
    // Use a transient PlanMasterController to accomplish this
    PlanMasterController* controller = new PlanMasterController();
    controller->startStaticActiveVehicle(vehicle);
    controller->loadFromFile(filename);
444
    controller->sendToVehicle();
445 446
    delete controller;
}
DonLakeFlyer's avatar
DonLakeFlyer committed
447 448 449

void PlanMasterController::_showPlanFromManagerVehicle(void)
{
450 451 452 453 454 455 456 457
    if (!_managerVehicle->initialPlanRequestComplete() &&
            !_missionController.syncInProgress() &&
            !_geoFenceController.syncInProgress() &&
            !_rallyPointController.syncInProgress()) {
        // Something went wrong with initial load. All controllers are idle, so just force it off
        _managerVehicle->forceInitialPlanRequestComplete();
    }

DonLakeFlyer's avatar
DonLakeFlyer committed
458 459 460 461 462 463 464
    // The crazy if structure is to handle the load propogating by itself through the system
    if (!_missionController.showPlanFromManagerVehicle()) {
        if (!_geoFenceController.showPlanFromManagerVehicle()) {
            _rallyPointController.showPlanFromManagerVehicle();
        }
    }
}