21#include <QtCore/QDirIterator>
22#include <QtCore/QFileInfo>
23#include <QtCore/QJsonDocument>
24#include <QtCore/QRegularExpression>
32 , _managerVehicle (_controllerVehicle)
33 , _missionController (this)
34 , _geoFenceController (this)
35 , _rallyPointController (this)
40#ifdef QGC_UNITTEST_BUILD
44 , _controllerVehicle (new
Vehicle(firmwareType, vehicleType))
45 , _managerVehicle (_controllerVehicle)
46 , _missionController (this)
47 , _geoFenceController (this)
48 , _rallyPointController (this)
54void PlanMasterController::_commonInit(
void)
82 _missionController.
start (_flyView);
83 _geoFenceController.
start (_flyView);
84 _rallyPointController.
start (_flyView);
89 _updatePlanCreatorsList();
95 _deleteWhenSendCompleted = deleteWhenSendCompleted;
96 _missionController.
start(_flyView);
97 _geoFenceController.
start(_flyView);
98 _rallyPointController.
start(_flyView);
99 _activeVehicleChanged(vehicle);
102void PlanMasterController::_activeVehicleChanged(
Vehicle* activeVehicle)
104 if (_managerVehicle == activeVehicle) {
109 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged" << activeVehicle;
111 if (_managerVehicle) {
113 disconnect(_managerVehicle->
missionManager(),
nullptr,
this,
nullptr);
114 disconnect(_managerVehicle->
geoFenceManager(),
nullptr,
this,
nullptr);
118 bool newOffline =
false;
119 if (activeVehicle ==
nullptr) {
121 _managerVehicle = _controllerVehicle;
125 _managerVehicle = activeVehicle;
141 _offline = newOffline;
149 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Fly View - No active vehicle, clearing stale plan";
153 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Fly View - New active vehicle, loading new plan from manager vehicle";
154 _showPlanFromManagerVehicle();
160 _setDirtyForUpload(
true);
165 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous dirty plan exists, no new active vehicle, sending promptForPlanUsageOnVehicleChange signal";
171 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous clean plan exists, no new active vehicle, clear stale plan";
175 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - Previous clean plan exists, new active vehicle, loading from new manager vehicle";
176 _showPlanFromManagerVehicle();
181 _setDirtyStates(
false,
false);
184 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - No previous plan, no longer connected to vehicle, nothing to do";
187 qCDebug(PlanMasterControllerLog) <<
"_activeVehicleChanged: Plan View - No previous plan, new active vehicle, loading from new manager vehicle";
188 _showPlanFromManagerVehicle();
199 _updatePlanCreatorsList();
206 if (sharedLink->linkConfiguration()->isHighLatency()) {
216 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called while offline";
217 }
else if (_flyView) {
218 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called from Fly view";
220 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle called while syncInProgress";
222 _loadGeoFence =
true;
223 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::loadFromVehicle calling _missionController.loadFromVehicle";
229void PlanMasterController::_loadMissionComplete(
void)
231 if (!_flyView && _loadGeoFence) {
232 _loadGeoFence =
false;
233 _loadRallyPoints =
true;
235 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadMissionComplete calling _geoFenceController.loadFromVehicle";
238 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadMissionComplete GeoFence not supported skipping";
240 _loadGeoFenceComplete();
245void PlanMasterController::_loadGeoFenceComplete(
void)
247 if (!_flyView && _loadRallyPoints) {
248 _loadRallyPoints =
false;
250 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadGeoFenceComplete calling _rallyPointController.loadFromVehicle";
253 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadMissionComplete Rally Points not supported skipping";
255 _loadRallyPointsComplete();
260void PlanMasterController::_loadRallyPointsComplete(
void)
262 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::_loadRallyPointsComplete";
265 _setDirtyStates(
false ,
false );
268void PlanMasterController::_sendMissionComplete(
void)
271 _sendGeoFence =
false;
272 _sendRallyPoints =
true;
274 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
277 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle GeoFence not supported skipping";
278 _sendGeoFenceComplete();
283void PlanMasterController::_sendGeoFenceComplete(
void)
285 if (_sendRallyPoints) {
286 _sendRallyPoints =
false;
288 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start rally sendToVehicle";
291 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Points not support skipping";
292 _sendRallyPointsComplete();
297void PlanMasterController::_sendRallyPointsComplete(
void)
299 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Point send complete";
300 _setDirtyForUpload(
false);
301 if (_deleteWhenSendCompleted) {
310 if (sharedLink->linkConfiguration()->isHighLatency()) {
320 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while offline";
322 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while syncInProgress";
324 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start mission sendToVehicle";
325 _sendGeoFence =
true;
333 QString errorMessage = tr(
"Error loading Plan file (%1). %2").arg(filename).arg(
"%1");
335 if (filename.isEmpty()) {
339 QFileInfo fileInfo(filename);
340 QFile file(filename);
342 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
343 errorString = file.errorString() + QStringLiteral(
" ") + filename;
348 bool success =
false;
356 QJsonDocument jsonDoc;
357 QByteArray bytes = file.readAll();
364 QJsonObject json = jsonDoc.object();
374 QList<JsonParsing::KeyValidateInfo> rgKeyInfo = {
397 _currentPlanFile = QString::asprintf(
"%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(),
AppSettings::planFileExtension);
398 const bool currentNameChanged = (_currentPlanFileName != fileInfo.completeBaseName());
399 const bool originalNameChanged = (_originalPlanFileName != fileInfo.completeBaseName());
400 _currentPlanFileName = fileInfo.completeBaseName();
401 _originalPlanFileName = _currentPlanFileName;
402 _setDirtyStates(
false ,
true );
404 if (currentNameChanged) {
407 if (originalNameChanged) {
414 const bool hadFile = !_currentPlanFile.isEmpty();
415 const bool hadCurrentName = !_currentPlanFileName.isEmpty();
416 const bool hadOriginalName = !_originalPlanFileName.isEmpty();
418 _currentPlanFile.clear();
419 _currentPlanFileName.clear();
420 _originalPlanFileName.clear();
424 if (hadCurrentName) {
427 if (hadOriginalName) {
438 QJsonObject planJson;
440 QJsonObject missionJson;
441 QJsonObject fenceJson;
442 QJsonObject rallyJson;
446 _missionController.
save(missionJson);
449 _geoFenceController.
save(fenceJson);
450 _rallyPointController.
save(rallyJson);
455 return QJsonDocument(planJson);
461 if (!_currentPlanFile.isEmpty()) {
462 const bool saveSuccess =
saveToFile(_currentPlanFile);
471 if (filename.isEmpty()) {
475 QString planFilename = filename;
476 if (!QFileInfo(filename).fileName().contains(
".")) {
480 QFile file(planFilename);
482 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
486 const QByteArray saveBytes =
saveToJson().toJson();
487 const qint64 bytesWritten = file.write(saveBytes);
488 if (bytesWritten != saveBytes.size()) {
492 if(_currentPlanFile != planFilename) {
493 _currentPlanFile = planFilename;
497 const QString savedBaseName = QFileInfo(planFilename).completeBaseName();
498 if (_currentPlanFileName != savedBaseName) {
499 _currentPlanFileName = savedBaseName;
502 if (_originalPlanFileName != savedBaseName) {
503 _originalPlanFileName = savedBaseName;
509 _setDirtyForSave(
false);
517 if (filename.isEmpty()) {
521 QString kmlFilename = filename;
522 if (!QFileInfo(filename).fileName().contains(
".")) {
526 QFile file(kmlFilename);
528 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
533 QTextStream stream(&file);
534 stream << planKML.toString();
541 _suppressOverallDirtyUpdate =
true;
546 _geoFenceController.
setDirty(
false);
547 _rallyPointController.
setDirty(
false);
548 _suppressOverallDirtyUpdate =
false;
550 _setDirtyStates(
false,
false);
567 _setDirtyForUpload(
false);
570 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::removeAllFromVehicle called while offline";
580void PlanMasterController::_updateShowCreateFromTemplate(
void)
585 _userSelectedManualCreation =
false;
589 if (show != _showCreateFromTemplate) {
590 _showCreateFromTemplate = show;
603 QString sanitized = name.trimmed();
605 if (sanitized.endsWith(ext, Qt::CaseInsensitive)) {
606 sanitized.chop(ext.length());
607 sanitized = sanitized.trimmed();
609 sanitized.remove(QRegularExpression(QStringLiteral(
"[/\\\\:*?\"<>|]")));
610 if (_currentPlanFileName != sanitized) {
612 _currentPlanFileName = sanitized;
622 if (_currentPlanFileName.isEmpty()) {
630 return !_originalPlanFileName.isEmpty() && _currentPlanFileName != _originalPlanFileName;
635 if (_currentPlanFileName.isEmpty()) {
638 return QFile::exists(_resolvedPlanFilePath());
641QString PlanMasterController::_resolvedPlanFilePath()
const
643 const QString dir = _currentPlanFile.isEmpty()
645 : QFileInfo(_currentPlanFile).path();
646 return QStringLiteral(
"%1/%2.%3").arg(dir, _currentPlanFileName,
fileExtension());
649void PlanMasterController::_clearFileNames()
651 const bool hadFile = !_currentPlanFile.isEmpty();
652 const bool hadCurrentName = !_currentPlanFileName.isEmpty();
653 const bool hadOriginalName = !_originalPlanFileName.isEmpty();
655 _currentPlanFile.clear();
656 _currentPlanFileName.clear();
657 _originalPlanFileName.clear();
661 if (hadCurrentName) {
664 if (hadOriginalName) {
691 filters << tr(
"Plan Files (*.%1)").arg(
fileExtension()) << tr(
"All Files (*)");
704void PlanMasterController::_showPlanFromManagerVehicle(
void)
720 _geoFenceController.
setDirty(
false);
721 _rallyPointController.
setDirty(
false);
722 _setDirtyStates(
false,
false);
734 return _missionController.
isEmpty() &&
735 _geoFenceController.
isEmpty() &&
736 _rallyPointController.
isEmpty();
739void PlanMasterController::_updateOverallDirty(
void)
745 const bool saveDirty = _missionController.
dirty() || _geoFenceController.
dirty() || _rallyPointController.
dirty();
747 _setDirtyForSave(
true);
751void PlanMasterController::_setDirtyForSave(
bool dirtyForSave)
758 _setDirtyForUpload(
true);
763void PlanMasterController::_setDirtyForUpload(
bool dirtyForUpload)
771void PlanMasterController::_setDirtyStates(
bool dirtyForSave,
bool dirtyForUpload)
773 const bool saveChanged = (_dirtyForSave !=
dirtyForSave);
787void PlanMasterController::_updatePlanCreatorsList(
void)
793 const auto vehicleClass = _managerVehicle->
vehicleClass();
796 if (_planCreators && _planCreatorsVehicleClass == vehicleClass) {
800 if (!_planCreators) {
806 _planCreatorsVehicleClass = vehicleClass;
813 if (creator->supportsVehicleClass(vehicleClass)) {
814 _planCreators->
append(creator);
827 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - No new vehicle, clear any previous plan";
831 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - New vehicle available, show plan from new manager vehicle";
832 _showPlanFromManagerVehicle();
844 if (show != _showCreateFromTemplate) {
845 _showCreateFromTemplate = show;
853 if (archivePath.isEmpty()) {
857 if (!QFile::exists(archivePath)) {
867 const QString tempPath = QDir::temp().filePath(QStringLiteral(
"qgc_plan_") + QString::number(QDateTime::currentMSecsSinceEpoch()));
868 if (!QDir().mkpath(tempPath)) {
873 _extractionOutputDir = tempPath;
875 if (_extractionJob ==
nullptr) {
878 this, &PlanMasterController::_handleExtractionFinished);
884void PlanMasterController::_handleExtractionFinished(
bool success)
887 const QString
error = _extractionJob !=
nullptr ? _extractionJob->
errorString() : tr(
"Extraction failed");
889 QDir(_extractionOutputDir).removeRecursively();
890 _extractionOutputDir.clear();
896 QDirIterator it(_extractionOutputDir, {planExt}, QDir::Files, QDirIterator::Subdirectories);
898 planPath = it.next();
901 if (planPath.isEmpty()) {
903 QDir(_extractionOutputDir).removeRecursively();
904 _extractionOutputDir.clear();
908 qCDebug(PlanMasterControllerLog) <<
"Found plan file in archive:" << planPath;
911 QDir(_extractionOutputDir).removeRecursively();
912 _extractionOutputDir.clear();
std::shared_ptr< LinkInterface > SharedLinkInterfacePtr
QObject wrapper for async compression operations using QtConcurrent/QPromise.
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static constexpr const char * planFileExtension
static constexpr const char * kmlFileExtension
static constexpr const char * waypointsFileExtension
QString missionSavePath()
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)
void planFileRenamedChanged(void)
Q_INVOKABLE bool saveWithCurrentName()
Save using the (possibly renamed) currentPlanFileName.
void setCurrentPlanFileName(const QString &name)
Q_INVOKABLE bool saveToCurrent()
void originalPlanFileNameChanged(void)
void containsItemsChanged()
QStringList saveNameFilters(void) const
void currentPlanFileNameChanged(void)
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
Q_INVOKABLE void loadFromArchive(const QString &archivePath)
void dirtyForSaveChanged(bool dirtyForSave)
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)
bool planFileRenamed(void) const
Q_INVOKABLE bool resolvedPlanFileExists() const
true if a file at the renamed path already exists on disk
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
QObject wrapper for compression operations with progress signals.
QString errorString() const
void finished(bool success)
void extractArchive(const QString &archivePath, const QString &outputDirectoryPath, qint64 maxBytes=0)
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
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.
bool isArchiveFile(const QString &filePath)
Check if file path indicates an archive file (.zip, .tar, .tar.gz, etc.)
void showAppMessage(const QString &message, const QString &title)
Modal application message. Queued if the UI isn't ready yet.