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";
266void PlanMasterController::_sendMissionComplete(
void)
269 _sendGeoFence =
false;
270 _sendRallyPoints =
true;
272 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start GeoFence sendToVehicle";
275 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle GeoFence not supported skipping";
276 _sendGeoFenceComplete();
281void PlanMasterController::_sendGeoFenceComplete(
void)
283 if (_sendRallyPoints) {
284 _sendRallyPoints =
false;
286 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start rally sendToVehicle";
289 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Points not support skipping";
290 _sendRallyPointsComplete();
295void PlanMasterController::_sendRallyPointsComplete(
void)
297 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle Rally Point send complete";
298 _setDirtyForUpload(
false);
299 if (_deleteWhenSendCompleted) {
308 if (sharedLink->linkConfiguration()->isHighLatency()) {
318 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while offline";
320 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle called while syncInProgress";
322 qCDebug(PlanMasterControllerLog) <<
"PlanMasterController::sendToVehicle start mission sendToVehicle";
323 _sendGeoFence =
true;
331 QString errorMessage = tr(
"Error loading Plan file (%1). %2").arg(filename).arg(
"%1");
333 if (filename.isEmpty()) {
337 QFileInfo fileInfo(filename);
338 QFile file(filename);
340 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
341 errorString = file.errorString() + QStringLiteral(
" ") + filename;
346 bool success =
false;
354 QJsonDocument jsonDoc;
355 QByteArray bytes = file.readAll();
362 QJsonObject json = jsonDoc.object();
372 QList<JsonParsing::KeyValidateInfo> rgKeyInfo = {
395 _currentPlanFile = QString::asprintf(
"%s/%s.%s", fileInfo.path().toLocal8Bit().data(), fileInfo.completeBaseName().toLocal8Bit().data(),
AppSettings::planFileExtension);
396 const bool currentNameChanged = (_currentPlanFileName != fileInfo.completeBaseName());
397 const bool originalNameChanged = (_originalPlanFileName != fileInfo.completeBaseName());
398 _currentPlanFileName = fileInfo.completeBaseName();
399 _originalPlanFileName = _currentPlanFileName;
400 _setDirtyStates(
false ,
true );
402 if (currentNameChanged) {
405 if (originalNameChanged) {
412 const bool hadFile = !_currentPlanFile.isEmpty();
413 const bool hadCurrentName = !_currentPlanFileName.isEmpty();
414 const bool hadOriginalName = !_originalPlanFileName.isEmpty();
416 _currentPlanFile.clear();
417 _currentPlanFileName.clear();
418 _originalPlanFileName.clear();
422 if (hadCurrentName) {
425 if (hadOriginalName) {
436 QJsonObject planJson;
438 QJsonObject missionJson;
439 QJsonObject fenceJson;
440 QJsonObject rallyJson;
444 _missionController.
save(missionJson);
447 _geoFenceController.
save(fenceJson);
448 _rallyPointController.
save(rallyJson);
453 return QJsonDocument(planJson);
459 if (!_currentPlanFile.isEmpty()) {
460 const bool saveSuccess =
saveToFile(_currentPlanFile);
469 if (filename.isEmpty()) {
473 QString planFilename = filename;
474 if (!QFileInfo(filename).fileName().contains(
".")) {
478 QFile file(planFilename);
480 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
484 const QByteArray saveBytes =
saveToJson().toJson();
485 const qint64 bytesWritten = file.write(saveBytes);
486 if (bytesWritten != saveBytes.size()) {
490 if(_currentPlanFile != planFilename) {
491 _currentPlanFile = planFilename;
495 const QString savedBaseName = QFileInfo(planFilename).completeBaseName();
496 if (_currentPlanFileName != savedBaseName) {
497 _currentPlanFileName = savedBaseName;
500 if (_originalPlanFileName != savedBaseName) {
501 _originalPlanFileName = savedBaseName;
507 _setDirtyForSave(
false);
515 if (filename.isEmpty()) {
519 QString kmlFilename = filename;
520 if (!QFileInfo(filename).fileName().contains(
".")) {
524 QFile file(kmlFilename);
526 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
531 QTextStream stream(&file);
532 stream << planKML.toString();
539 _suppressOverallDirtyUpdate =
true;
544 _geoFenceController.
setDirty(
false);
545 _rallyPointController.
setDirty(
false);
546 _suppressOverallDirtyUpdate =
false;
548 _setDirtyStates(
false,
false);
565 _setDirtyForUpload(
false);
568 qCCritical(PlanMasterControllerLog) <<
"PlanMasterController::removeAllFromVehicle called while offline";
578void PlanMasterController::_updateShowCreateFromTemplate(
void)
583 _userSelectedManualCreation =
false;
587 if (show != _showCreateFromTemplate) {
588 _showCreateFromTemplate = show;
601 QString sanitized = name.trimmed();
603 if (sanitized.endsWith(ext, Qt::CaseInsensitive)) {
604 sanitized.chop(ext.length());
605 sanitized = sanitized.trimmed();
607 sanitized.remove(QRegularExpression(QStringLiteral(
"[/\\\\:*?\"<>|]")));
608 if (_currentPlanFileName != sanitized) {
610 _currentPlanFileName = sanitized;
620 if (_currentPlanFileName.isEmpty()) {
628 return !_originalPlanFileName.isEmpty() && _currentPlanFileName != _originalPlanFileName;
633 if (_currentPlanFileName.isEmpty()) {
636 return QFile::exists(_resolvedPlanFilePath());
639QString PlanMasterController::_resolvedPlanFilePath()
const
641 const QString dir = _currentPlanFile.isEmpty()
643 : QFileInfo(_currentPlanFile).path();
644 return QStringLiteral(
"%1/%2.%3").arg(dir, _currentPlanFileName,
fileExtension());
647void PlanMasterController::_clearFileNames()
649 const bool hadFile = !_currentPlanFile.isEmpty();
650 const bool hadCurrentName = !_currentPlanFileName.isEmpty();
651 const bool hadOriginalName = !_originalPlanFileName.isEmpty();
653 _currentPlanFile.clear();
654 _currentPlanFileName.clear();
655 _originalPlanFileName.clear();
659 if (hadCurrentName) {
662 if (hadOriginalName) {
689 filters << tr(
"Plan Files (*.%1)").arg(
fileExtension()) << tr(
"All Files (*)");
702void PlanMasterController::_showPlanFromManagerVehicle(
void)
718 _geoFenceController.
setDirty(
false);
719 _rallyPointController.
setDirty(
false);
720 _setDirtyStates(
false,
false);
732 return _missionController.
isEmpty() &&
733 _geoFenceController.
isEmpty() &&
734 _rallyPointController.
isEmpty();
737void PlanMasterController::_updateOverallDirty(
void)
743 const bool saveDirty = _missionController.
dirty() || _geoFenceController.
dirty() || _rallyPointController.
dirty();
745 _setDirtyForSave(
true);
749void PlanMasterController::_setDirtyForSave(
bool dirtyForSave)
756 _setDirtyForUpload(
true);
761void PlanMasterController::_setDirtyForUpload(
bool dirtyForUpload)
769void PlanMasterController::_setDirtyStates(
bool dirtyForSave,
bool dirtyForUpload)
771 const bool saveChanged = (_dirtyForSave !=
dirtyForSave);
785void PlanMasterController::_updatePlanCreatorsList(
void)
791 const auto vehicleClass = _managerVehicle->
vehicleClass();
794 if (_planCreators && _planCreatorsVehicleClass == vehicleClass) {
798 if (!_planCreators) {
804 _planCreatorsVehicleClass = vehicleClass;
811 if (creator->supportsVehicleClass(vehicleClass)) {
812 _planCreators->
append(creator);
825 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - No new vehicle, clear any previous plan";
829 qCDebug(PlanMasterControllerLog) <<
"showPlanFromManagerVehicle: Plan View - New vehicle available, show plan from new manager vehicle";
830 _showPlanFromManagerVehicle();
842 if (show != _showCreateFromTemplate) {
843 _showCreateFromTemplate = show;
851 if (archivePath.isEmpty()) {
855 if (!QFile::exists(archivePath)) {
865 const QString tempPath = QDir::temp().filePath(QStringLiteral(
"qgc_plan_") + QString::number(QDateTime::currentMSecsSinceEpoch()));
866 if (!QDir().mkpath(tempPath)) {
871 _extractionOutputDir = tempPath;
873 if (_extractionJob ==
nullptr) {
876 this, &PlanMasterController::_handleExtractionFinished);
882void PlanMasterController::_handleExtractionFinished(
bool success)
885 const QString
error = _extractionJob !=
nullptr ? _extractionJob->
errorString() : tr(
"Extraction failed");
887 QDir(_extractionOutputDir).removeRecursively();
888 _extractionOutputDir.clear();
894 QDirIterator it(_extractionOutputDir, {planExt}, QDir::Files, QDirIterator::Subdirectories);
896 planPath = it.next();
899 if (planPath.isEmpty()) {
901 QDir(_extractionOutputDir).removeRecursively();
902 _extractionOutputDir.clear();
906 qCDebug(PlanMasterControllerLog) <<
"Found plan file in archive:" << planPath;
909 QDir(_extractionOutputDir).removeRecursively();
910 _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.