18#include <QtCore/QFileInfo>
19#include <QtCore/QJsonDocument>
27 , _managerVehicle (_controllerVehicle)
28 , _missionController (this)
29 , _geoFenceController (this)
30 , _rallyPointController (this)
35#ifdef QGC_UNITTEST_BUILD
39 , _controllerVehicle (new
Vehicle(firmwareType, vehicleType))
40 , _managerVehicle (_controllerVehicle)
41 , _missionController (this)
42 , _geoFenceController (this)
43 , _rallyPointController (this)
49void PlanMasterController::_commonInit(
void)
77 _missionController.
start (_flyView);
78 _geoFenceController.
start (_flyView);
79 _rallyPointController.
start (_flyView);
84 _updatePlanCreatorsList();
90 _deleteWhenSendCompleted = deleteWhenSendCompleted;
91 _missionController.
start(_flyView);
92 _geoFenceController.
start(_flyView);
93 _rallyPointController.
start(_flyView);
94 _activeVehicleChanged(vehicle);
97void PlanMasterController::_activeVehicleChanged(
Vehicle* activeVehicle)
99 if (_managerVehicle == activeVehicle) {
104 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged" << activeVehicle;
106 if (_managerVehicle) {
109 disconnect(_managerVehicle->
missionManager(),
nullptr,
this,
nullptr);
110 disconnect(_managerVehicle->
geoFenceManager(),
nullptr,
this,
nullptr);
114 _loadSequence = SyncSequence::Idle;
115 _sendSequence = SyncSequence::Idle;
118 bool newOffline =
false;
119 if (activeVehicle ==
nullptr) {
121 _managerVehicle = _controllerVehicle;
125 _managerVehicle = activeVehicle;
142 _offline = newOffline;
150 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Fly View - No active vehicle, clearing stale plan";
154 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Fly View - New active vehicle, loading new plan from manager vehicle";
155 _showPlanFromManagerVehicle();
161 _setDirtyForUpload(
true);
166 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous dirty plan exists, no new active vehicle, sending promptForPlanUsageOnVehicleChange signal";
172 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous clean plan exists, no new active vehicle, clear stale plan";
176 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous clean plan exists, new active vehicle, loading from new manager vehicle";
177 _showPlanFromManagerVehicle();
182 _setDirtyStates(
false,
false);
185 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - No previous plan, no longer connected to vehicle, nothing to do";
188 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - No previous plan, new active vehicle, loading from new manager vehicle";
189 _showPlanFromManagerVehicle();
200 _updatePlanCreatorsList();
207 if (sharedLink->linkConfiguration()->isHighLatency()) {
217 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called while offline";
218 }
else if (_flyView) {
219 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called from Fly view";
221 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called while syncInProgress";
223 _loadSequence = SyncSequence::Mission;
224 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle calling _missionController.loadFromVehicle";
230void PlanMasterController::_loadMissionComplete(
void)
232 if (_loadSequence != SyncSequence::Mission) {
235 _loadSequence = SyncSequence::GeoFence;
237 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadMissionComplete calling _geoFenceController.loadFromVehicle";
240 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadMissionComplete GeoFence not supported skipping";
242 _loadGeoFenceComplete();
246void PlanMasterController::_loadGeoFenceComplete(
void)
248 if (_loadSequence != SyncSequence::GeoFence) {
251 _loadSequence = SyncSequence::RallyPoints;
253 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadGeoFenceComplete calling _rallyPointController.loadFromVehicle";
256 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadGeoFenceComplete Rally Points not supported skipping";
258 _loadRallyPointsComplete();
262void PlanMasterController::_loadRallyPointsComplete(
void)
264 if (_loadSequence != SyncSequence::RallyPoints) {
267 _loadSequence = SyncSequence::Idle;
268 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadRallyPointsComplete";
272 _clearCurrentPlanFile();
273 _setDirtyStates(
false ,
false );
276void PlanMasterController::_sendMissionComplete(
void)
278 if (_sendSequence != SyncSequence::Mission) {
281 _sendSequence = SyncSequence::GeoFence;
283 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
286 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle GeoFence not supported skipping";
287 _sendGeoFenceComplete();
291void PlanMasterController::_sendGeoFenceComplete(
void)
293 if (_sendSequence != SyncSequence::GeoFence) {
296 _sendSequence = SyncSequence::RallyPoints;
298 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start rally sendToVehicle";
301 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Points not support skipping";
302 _sendRallyPointsComplete();
306void PlanMasterController::_sendRallyPointsComplete(
void)
308 if (_sendSequence != SyncSequence::RallyPoints) {
311 _sendSequence = SyncSequence::Idle;
312 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Point send complete";
313 _setDirtyForUpload(
false);
314 if (_deleteWhenSendCompleted) {
323 if (sharedLink->linkConfiguration()->isHighLatency()) {
333 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while offline";
335 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while syncInProgress";
337 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start mission sendToVehicle";
338 _sendSequence = SyncSequence::Mission;
346 QString errorMessage = tr(
"Error loading Plan file (%1). %2").arg(filename).arg(
"%1");
348 if (filename.isEmpty()) {
352 QFileInfo fileInfo(filename);
353 QFile file(filename);
355 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
356 errorString = file.errorString() + QStringLiteral(
" ") + filename;
361 bool success =
false;
365 success = _loadPlanJson(file.readAll(),
errorString);
372 _currentPlanFile = QString::asprintf(
"%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(),
AppSettings::planFileExtension);
374 _setDirtyStates(
false ,
true );
376 _clearCurrentPlanFile();
380bool PlanMasterController::_loadPlanJson(
const QByteArray& bytes, QString&
errorString)
382 QJsonDocument jsonDoc;
387 QJsonObject json = jsonDoc.object();
396 const QList<JsonParsing::KeyValidateInfo> rgKeyInfo = {
418 QJsonObject planJson;
420 QJsonObject missionJson;
421 QJsonObject fenceJson;
422 QJsonObject rallyJson;
426 _missionController.
save(missionJson);
429 _geoFenceController.
save(fenceJson);
430 _rallyPointController.
save(rallyJson);
435 return QJsonDocument(planJson);
441 if (!_currentPlanFile.isEmpty()) {
442 const bool saveSuccess =
saveToFile(_currentPlanFile);
451 if (filename.isEmpty()) {
455 QString planFilename = filename;
456 if (!QFileInfo(filename).fileName().contains(
".")) {
460 QFile file(planFilename);
462 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
466 const QByteArray saveBytes =
saveToJson().toJson();
467 const qint64 bytesWritten = file.write(saveBytes);
468 if (bytesWritten != saveBytes.size()) {
472 if(_currentPlanFile != planFilename) {
473 _currentPlanFile = planFilename;
476 _setDirtyForSave(
false);
484 if (filename.isEmpty()) {
488 QString kmlFilename = filename;
489 if (!QFileInfo(filename).fileName().contains(
".")) {
493 QFile file(kmlFilename);
495 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
500 QTextStream stream(&file);
501 stream << planKML.toString();
508 _suppressOverallDirtyUpdate =
true;
512 _suppressOverallDirtyUpdate =
false;
514 _setDirtyStates(
false,
false);
516 _clearCurrentPlanFile();
531 _setDirtyForUpload(
false);
532 _clearCurrentPlanFile();
534 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::removeAllFromVehicle called while offline";
544void PlanMasterController::_updateShowCreateFromTemplate(
void)
549 _userSelectedManualCreation =
false;
553 if (show != _showCreateFromTemplate) {
554 _showCreateFromTemplate = show;
566 return _currentPlanFile.isEmpty() ? QString() : QFileInfo(_currentPlanFile).completeBaseName();
569void PlanMasterController::_clearCurrentPlanFile()
571 if (!_currentPlanFile.isEmpty()) {
572 _currentPlanFile.clear();
596 filters << tr(
"Plan Files (*.%1)").arg(
fileExtension()) << tr(
"All Files (*)");
609void PlanMasterController::_showPlanFromManagerVehicle(
void)
613 _loadSequence = SyncSequence::InitialPlanLoad;
626 _clearCurrentPlanFile();
627 _setDirtyStates(
false,
false);
630void PlanMasterController::_initialPlanRequestCompleteChanged(
bool initialPlanRequestComplete)
632 if (!initialPlanRequestComplete || _loadSequence != SyncSequence::InitialPlanLoad) {
635 _loadSequence = SyncSequence::Idle;
636 qCDebug(PlanMasterControllerLog) <<
"_initialPlanRequestCompleteChanged: initial download complete";
639 _showPlanFromManagerVehicle();
651 return _missionController.
isEmpty() &&
652 _geoFenceController.
isEmpty() &&
653 _rallyPointController.
isEmpty();
656void PlanMasterController::_updateOverallDirty(
void)
662 const bool saveDirty = _missionController.
dirty() || _geoFenceController.
dirty() || _rallyPointController.
dirty();
664 _setDirtyForSave(
true);
668void PlanMasterController::_setDirtyForSave(
bool dirtyForSave)
671 _clearElementDirtyStates();
678 _setDirtyForUpload(
true);
683void PlanMasterController::_setDirtyForUpload(
bool dirtyForUpload)
691void PlanMasterController::_setDirtyStates(
bool dirtyForSave,
bool dirtyForUpload)
694 _clearElementDirtyStates();
697 const bool saveChanged = (_dirtyForSave !=
dirtyForSave);
712void PlanMasterController::_clearElementDirtyStates()
714 _suppressOverallDirtyUpdate =
true;
716 _geoFenceController.
setDirty(
false);
717 _rallyPointController.
setDirty(
false);
718 _suppressOverallDirtyUpdate =
false;
721void PlanMasterController::_updatePlanCreatorsList(
void)
727 const auto vehicleClass = _managerVehicle->
vehicleClass();
730 if (_planCreators && _planCreatorsVehicleClass == vehicleClass) {
734 if (!_planCreators) {
740 _planCreatorsVehicleClass = vehicleClass;
747 if (creator->supportsVehicleClass(vehicleClass)) {
748 _planCreators->
append(creator);
761 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - No new vehicle, clear any previous plan";
765 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - New vehicle available, show plan from new manager vehicle";
766 _showPlanFromManagerVehicle();
778 if (show != _showCreateFromTemplate) {
779 _showCreateFromTemplate = show;
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * planFileExtension
static constexpr const char * kmlFileExtension
static constexpr const char * waypointsFileExtension
bool showPlanFromManagerVehicle(void) final
void removeAllFromVehicle(void) final
void setDirty(bool dirty) final
bool containsItems(void) const final
void sendToVehicle(void) final
bool syncInProgress(void) const final
void removeAll(void) final
Removes all from controller only.
bool load(const QJsonObject &json, QString &errorString) final
bool supported(void) const final
true: controller is waiting for the current load to complete
void loadFromVehicle(void) final
bool dirty(void) const final
void start(bool flyView) final
Should be called immediately upon Component.onCompleted.
void save(QJsonObject &json) final
void sendComplete(bool error)
Used to convert a Plan to a KML document.
bool showPlanFromManagerVehicle(void) final
bool dirty(void) const final
bool loadTextFile(QFile &file, QString &errorString)
void removeAllFromVehicle(void) final
bool load(const QJsonObject &json, QString &errorString) final
void addMissionToKML(KMLPlanDomDocument &planKML)
bool syncInProgress(void) const final
void sendToVehicle(void) final
void save(QJsonObject &json) final
bool containsItems(void) const final
void removeAll(void) final
Removes all from controller only.
void setDirty(bool dirty) final
void start(bool flyView) final
Should be called immediately upon Component.onCompleted.
void loadFromVehicle(void) final
Vehicle * activeVehicle() const
void activeVehicleChanged(Vehicle *activeVehicle)
Base class for PlanCreator objects which are used to create a full plan in a single step.
void syncInProgressChanged(bool syncInProgress)
void containsItemsChanged()
void dirtyChanged(bool dirty)
void sendComplete(bool error)
void newMissionItemsAvailable(bool removeAllRequested)
Master controller for mission, fence, rally.
Q_INVOKABLE void removeAll(void)
Removes all from controller only, sync required to remove from vehicle.
PlanMasterController(QObject *parent=nullptr)
Q_INVOKABLE bool saveToCurrent()
void containsItemsChanged()
QStringList saveNameFilters(void) const
void promptForPlanUsageOnVehicleChange(void)
void showCreateFromTemplateChanged()
Q_INVOKABLE void saveToKml(const QString &filename)
void setUserSelectedManualCreation(bool userSelectedManualCreation)
Q_INVOKABLE void showPlanFromManagerVehicle(void)
Replaces any current plan with the plan from the manager vehicle even if offline.
static constexpr int kPlanFileVersion
Q_INVOKABLE void start(void)
Should be called immediately upon Component.onCompleted.
QStringList loadNameFilters(void) const
void planCreatorsChanged(QmlObjectListModel *planCreators)
Q_INVOKABLE void loadFromVehicle(void)
void managerVehicleChanged(Vehicle *managerVehicle)
QString kmlFileExtension(void) const
void syncInProgressChanged(void)
Q_INVOKABLE void loadFromFile(const QString &filename)
Q_INVOKABLE void startStaticActiveVehicle(Vehicle *vehicle, bool deleteWhenSendCompleted=false)
bool dirtyForUpload(void) const
static constexpr const char * kJsonGeoFenceObjectKey
void dirtyForSaveChanged(bool dirtyForSave)
QString currentPlanFileName(void) const
void userSelectedManualCreationChanged()
bool syncInProgress(void) const
void currentPlanFileChanged(void)
bool showCreateFromTemplate(void) const
bool dirtyForSave(void) const
QString fileExtension(void) const
~PlanMasterController()
Either active vehicle or _controllerVehicle if no active vehicle.
Q_INVOKABLE bool saveToFile(const QString &filename)
void dirtyForUploadChanged(bool dirtyForUpload)
static constexpr const char * kPlanFileType
static constexpr const char * kJsonMissionObjectKey
Q_INVOKABLE void removeAllFromVehicle(void)
Removes all from vehicle and controller.
static void sendPlanToVehicle(Vehicle *vehicle, const QString &filename)
QJsonDocument saveToJson()
void offlineChanged(bool offlineEditing)
Q_INVOKABLE void sendToVehicle(void)
bool userSelectedManualCreation(void) const
static constexpr const char * kJsonRallyPointsObjectKey
bool containsItems(void) const
virtual void postSaveToMissionJson(PlanMasterController *pController, QJsonObject &missionJson)
Allows custom builds to add custom items to the mission section of the plan file after the item is cr...
virtual void preLoadFromJson(PlanMasterController *pController, QJsonObject &json)
Allows custom builds to load custom items from the plan file before the document is parsed.
virtual void preSaveToMissionJson(PlanMasterController *pController, QJsonObject &missionJson)
Allows custom builds to add custom items to the mission section of the plan file before the item is c...
virtual void preSaveToJson(PlanMasterController *pController, QJsonObject &json)
Allows custom builds to add custom items to the plan file before the document is created.
virtual void postSaveToJson(PlanMasterController *pController, QJsonObject &json)
Allows custom builds to add custom items to the plan file after the document is created.
virtual QList< PlanCreator * > planCreators(PlanMasterController *planMasterController)
static QGCCorePlugin * instance()
virtual void postLoadFromJson(PlanMasterController *pController, QJsonObject &json)
Allows custom builds to load custom items from the plan file after the document is parsed.
static VehicleClass_t vehicleClass(MAV_TYPE mavType)
static FirmwareClass_t firmwareClass(MAV_AUTOPILOT autopilot)
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
bool containsItems(void) const final
void save(QJsonObject &json) final
bool supported(void) const final
true: controller is waiting for the current load to complete
void setDirty(bool dirty) final
bool showPlanFromManagerVehicle(void) final
bool syncInProgress(void) const final
bool dirty(void) const final
void start(bool flyView) final
Should be called immediately upon Component.onCompleted.
void removeAllFromVehicle(void) final
bool load(const QJsonObject &json, QString &errorString) final
void removeAll(void) final
Removes all from controller only.
void loadFromVehicle(void) final
void sendToVehicle(void) final
void sendComplete(bool error)
static SettingsManager * instance()
AppSettings * appSettings() const
WeakLinkInterfacePtr primaryLink() const
QGCMAVLink::VehicleClass_t vehicleClass(void) const
void initialPlanRequestCompleteChanged(bool initialPlanRequestComplete)
MAV_TYPE vehicleType() const
VehicleLinkManager * vehicleLinkManager()
MAV_AUTOPILOT firmwareType() const
GeoFenceManager * geoFenceManager()
RallyPointManager * rallyPointManager()
bool initialPlanRequestComplete() const
MissionManager * missionManager()
void vehicleTypeChanged()
bool validateExternalQGCJsonFile(const QJsonObject &jsonObject, const QString &expectedFileType, int minSupportedVersion, int maxSupportedVersion, int &version, QString &errorString)
bool validateKeys(const QJsonObject &jsonObject, const QList< KeyValidateInfo > &keyInfo, QString &errorString)
Validates that all required keys are present and that listed keys have the expected type.
bool isJsonFile(const QByteArray &bytes, QJsonDocument &jsonDoc, QString &errorString)
Determines whether an in-memory byte buffer contains parseable JSON content.
void saveQGCJsonFileHeader(QJsonObject &jsonObject, const QString &fileType, int version)
Saves the standard QGC file header (groundStation, fileType, version) into the json object.
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.