From 85e0c56a35520f2a47e71837bcdc7053bda12e3c Mon Sep 17 00:00:00 2001 From: DonLakeFlyer Date: Thu, 15 Mar 2018 14:08:54 -0700 Subject: [PATCH] Simple mission terrain support --- src/JsonHelper.cc | 2 +- src/JsonHelper.h | 2 +- src/MissionManager/MissionController.cc | 46 ++- src/MissionManager/MissionController.h | 6 +- src/MissionManager/PlanMasterController.h | 8 +- src/MissionManager/SimpleMissionItem.cc | 326 ++++++++++++-------- src/MissionManager/SimpleMissionItem.h | 73 +++-- src/MissionManager/SimpleMissionItemTest.cc | 68 ++-- src/MissionManager/SimpleMissionItemTest.h | 12 +- src/MissionManager/VisualMissionItem.h | 7 +- src/PlanView/PlanView.qml | 16 + src/PlanView/SimpleItemEditor.qml | 46 ++- src/Terrain.cc | 2 +- 13 files changed, 384 insertions(+), 230 deletions(-) diff --git a/src/JsonHelper.cc b/src/JsonHelper.cc index e7d47a618..94858a42c 100644 --- a/src/JsonHelper.cc +++ b/src/JsonHelper.cc @@ -97,7 +97,7 @@ bool JsonHelper::validateKeyTypes(const QJsonObject& jsonObject, const QStringLi QString valueKey = keys[i]; if (jsonObject.contains(valueKey)) { const QJsonValue& jsonValue = jsonObject[valueKey]; - if (types[i] == QJsonValue::Null && jsonValue.type() == QJsonValue::Double) { + if (jsonValue.type() == QJsonValue::Null && types[i] == QJsonValue::Double) { // Null type signals a NaN on a double value continue; } diff --git a/src/JsonHelper.h b/src/JsonHelper.h index 955392bd0..c540cdb90 100644 --- a/src/JsonHelper.h +++ b/src/JsonHelper.h @@ -106,7 +106,7 @@ public: static bool parseEnum(const QJsonObject& jsonObject, QStringList& enumStrings, QStringList& enumValues, QString& errorString, QString valueName = QString()); - /// Returns NaN if the value is null, or it not the double value + /// Returns NaN if the value is null, or if not, the double value static double possibleNaNJsonValue(const QJsonValue& value); static const char* jsonVersionKey; diff --git a/src/MissionManager/MissionController.cc b/src/MissionManager/MissionController.cc index b093c3f58..75b91960a 100644 --- a/src/MissionManager/MissionController.cc +++ b/src/MissionManager/MissionController.cc @@ -343,13 +343,13 @@ int MissionController::insertSimpleMissionItem(QGeoCoordinate coordinate, int i) } } newItem->setDefaultsForCommand(); - if ((MAV_CMD)newItem->command() == MAV_CMD_NAV_WAYPOINT) { - double prevAltitude; - MAV_FRAME prevFrame; + if (newItem->specifiesAltitude()) { + double prevAltitude; + int prevAltitudeMode; - if (_findPreviousAltitude(i, &prevAltitude, &prevFrame)) { - newItem->missionItem().setFrame(prevFrame); - newItem->missionItem().setParam7(prevAltitude); + if (_findPreviousAltitude(i, &prevAltitude, &prevAltitudeMode)) { + newItem->altitude()->setRawValue(prevAltitude); + newItem->setAltitudeMode((SimpleMissionItem::AltitudeMode)prevAltitudeMode); } } newItem->setMissionFlightStatus(_missionFlightStatus); @@ -372,12 +372,12 @@ int MissionController::insertROIMissionItem(QGeoCoordinate coordinate, int i) newItem->setDefaultsForCommand(); newItem->setCoordinate(coordinate); - double prevAltitude; - MAV_FRAME prevFrame; + double prevAltitude; + int prevAltitudeMode; - if (_findPreviousAltitude(i, &prevAltitude, &prevFrame)) { - newItem->missionItem().setFrame(prevFrame); - newItem->missionItem().setParam7(prevAltitude); + if (_findPreviousAltitude(i, &prevAltitude, &prevAltitudeMode)) { + newItem->altitude()->setRawValue(prevAltitude); + newItem->setAltitudeMode((SimpleMissionItem::AltitudeMode)prevAltitudeMode); } _visualItems->insert(i, newItem); @@ -924,6 +924,18 @@ bool MissionController::loadTextFile(QFile& file, QString& errorString) return true; } +bool MissionController::readyForSaveSend(void) const +{ + for (int i=0; i<_visualItems->count(); i++) { + VisualMissionItem* visualItem = qobject_cast(_visualItems->get(i)); + if (!visualItem->readyForSave()) { + return false; + } + } + + return true; +} + void MissionController::save(QJsonObject& json) { json[JsonHelper::jsonVersionKey] = _missionFileVersion; @@ -1648,11 +1660,11 @@ void MissionController::_inProgressChanged(bool inProgress) emit syncInProgressChanged(inProgress); } -bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude, MAV_FRAME* prevFrame) +bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude, int* prevAltitudeMode) { bool found = false; double foundAltitude; - MAV_FRAME foundFrame; + int foundAltitudeMode; if (newIndex > _visualItems->count()) { return false; @@ -1665,9 +1677,9 @@ bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude if (visualItem->specifiesCoordinate() && !visualItem->isStandaloneCoordinate()) { if (visualItem->isSimpleItem()) { SimpleMissionItem* simpleItem = qobject_cast(visualItem); - if ((MAV_CMD)simpleItem->command() == MAV_CMD_NAV_WAYPOINT) { - foundAltitude = simpleItem->exitCoordinate().altitude(); - foundFrame = simpleItem->missionItem().frame(); + if (simpleItem->specifiesAltitude()) { + foundAltitude = simpleItem->altitude()->rawValue().toDouble(); + foundAltitudeMode = simpleItem->altitudeMode(); found = true; break; } @@ -1677,7 +1689,7 @@ bool MissionController::_findPreviousAltitude(int newIndex, double* prevAltitude if (found) { *prevAltitude = foundAltitude; - *prevFrame = foundFrame; + *prevAltitudeMode = foundAltitudeMode; } return found; diff --git a/src/MissionManager/MissionController.h b/src/MissionManager/MissionController.h index 390a4e8a6..875d96df5 100644 --- a/src/MissionManager/MissionController.h +++ b/src/MissionManager/MissionController.h @@ -118,6 +118,10 @@ public: /// @param sequenceNumber - index for new item, -1 to clear current item Q_INVOKABLE void setCurrentPlanViewIndex(int sequenceNumber, bool force); + /// Determines if the mission has all data needed to be saved or sent to the vehicle. Currently the only case where this + /// would return false is when it is still waiting on terrain data to determine correct altitudes. + bool readyForSaveSend(void) const; + /// Sends the mission items to the specified vehicle static void sendItemsToVehicle(Vehicle* vehicle, QmlObjectListModel* visualMissionItems); @@ -218,7 +222,7 @@ private: void _setupActiveVehicle(Vehicle* activeVehicle, bool forceLoadFromVehicle); void _calcPrevWaypointValues(double homeAlt, VisualMissionItem* currentItem, VisualMissionItem* prevItem, double* azimuth, double* distance, double* altDifference); static double _calcDistanceToHome(VisualMissionItem* currentItem, VisualMissionItem* homeItem); - bool _findPreviousAltitude(int newIndex, double* prevAltitude, MAV_FRAME* prevFrame); + bool _findPreviousAltitude(int newIndex, double* prevAltitude, int* prevAltitudeMode); static double _normalizeLat(double lat); static double _normalizeLon(double lon); void _addMissionSettings(QmlObjectListModel* visualItems, bool addToCenter); diff --git a/src/MissionManager/PlanMasterController.h b/src/MissionManager/PlanMasterController.h index 30dd38144..2bdef2ad7 100644 --- a/src/MissionManager/PlanMasterController.h +++ b/src/MissionManager/PlanMasterController.h @@ -47,10 +47,14 @@ public: /// Should be called immediately upon Component.onCompleted. /// @param editMode true: controller being used in Plan view, false: controller being used in Fly view - Q_INVOKABLE virtual void start(bool editMode); + Q_INVOKABLE void start(bool editMode); /// Starts the controller using a single static active vehicle. Will not track global active vehicle changes. - Q_INVOKABLE virtual void startStaticActiveVehicle(Vehicle* vehicle); + Q_INVOKABLE void startStaticActiveVehicle(Vehicle* vehicle); + + /// Determines if the plan has all data needed to be saved or sent to the vehicle. Currently the only case where this + /// would return false is when it is still waiting on terrain data to determine correct altitudes. + Q_INVOKABLE bool readyForSaveSend(void) const { return _missionController.readyForSaveSend(); } /// Sends a plan to the specified file /// @param[in] vehicle Vehicle we are sending a plan to diff --git a/src/MissionManager/SimpleMissionItem.cc b/src/MissionManager/SimpleMissionItem.cc index cc3ee0783..10c16d9ce 100644 --- a/src/MissionManager/SimpleMissionItem.cc +++ b/src/MissionManager/SimpleMissionItem.cc @@ -27,6 +27,10 @@ FactMetaData* SimpleMissionItem::_frameMetaData = NULL; FactMetaData* SimpleMissionItem::_latitudeMetaData = NULL; FactMetaData* SimpleMissionItem::_longitudeMetaData = NULL; +const char* SimpleMissionItem::_jsonAltitudeModeKey = "AltitudeMode"; +const char* SimpleMissionItem::_jsonAltitudeKey = "Altitude"; +const char* SimpleMissionItem::_jsonAMSLAltAboveTerrainKey = "AMSLAltAboveTerrain"; + struct EnumInfo_s { const char * label; MAV_FRAME frame; @@ -48,29 +52,28 @@ static const struct EnumInfo_s _rgMavFrameInfo[] = { }; SimpleMissionItem::SimpleMissionItem(Vehicle* vehicle, QObject* parent) - : VisualMissionItem(vehicle, parent) - , _rawEdit(false) - , _dirty(false) - , _ignoreDirtyChangeSignals(false) - , _speedSection(NULL) - , _cameraSection(NULL) - , _commandTree(qgcApp()->toolbox()->missionCommandTree()) - , _altitudeRelativeToHomeFact (0, "Altitude is relative to home", FactMetaData::valueTypeUint32) - , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) - , _param1MetaData(FactMetaData::valueTypeDouble) - , _param2MetaData(FactMetaData::valueTypeDouble) - , _param3MetaData(FactMetaData::valueTypeDouble) - , _param4MetaData(FactMetaData::valueTypeDouble) - , _param5MetaData(FactMetaData::valueTypeDouble) - , _param6MetaData(FactMetaData::valueTypeDouble) - , _param7MetaData(FactMetaData::valueTypeDouble) - , _syncingAltitudeRelativeToHomeAndFrame (false) - , _syncingHeadingDegreesAndParam4 (false) + : VisualMissionItem (vehicle, parent) + , _rawEdit (false) + , _dirty (false) + , _ignoreDirtyChangeSignals (false) + , _speedSection (NULL) + , _cameraSection (NULL) + , _commandTree (qgcApp()->toolbox()->missionCommandTree()) + , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) + , _altitudeMode (AltitudeRelative) + , _altitudeFact (0, "Altitude", FactMetaData::valueTypeDouble) + , _amslAltAboveTerrainFact (0, "Alt above terrain", FactMetaData::valueTypeDouble) + , _param1MetaData (FactMetaData::valueTypeDouble) + , _param2MetaData (FactMetaData::valueTypeDouble) + , _param3MetaData (FactMetaData::valueTypeDouble) + , _param4MetaData (FactMetaData::valueTypeDouble) + , _param5MetaData (FactMetaData::valueTypeDouble) + , _param6MetaData (FactMetaData::valueTypeDouble) + , _param7MetaData (FactMetaData::valueTypeDouble) + , _syncingHeadingDegreesAndParam4 (false) { _editorQml = QStringLiteral("qrc:/qml/SimpleItemEditor.qml"); - _altitudeRelativeToHomeFact.setRawValue(true); - _setupMetaData(); _connectSignals(); _updateOptionalSections(); @@ -81,35 +84,37 @@ SimpleMissionItem::SimpleMissionItem(Vehicle* vehicle, QObject* parent) connect(&_missionItem, &MissionItem::specifiedFlightSpeedChanged, this, &SimpleMissionItem::specifiedFlightSpeedChanged); connect(this, &SimpleMissionItem::sequenceNumberChanged, this, &SimpleMissionItem::lastSequenceNumberChanged); - connect(this, &SimpleMissionItem::cameraSectionChanged, this, &SimpleMissionItem::_setDirtyFromSignal); + connect(this, &SimpleMissionItem::cameraSectionChanged, this, &SimpleMissionItem::_setDirty); connect(this, &SimpleMissionItem::cameraSectionChanged, this, &SimpleMissionItem::_updateLastSequenceNumber); } SimpleMissionItem::SimpleMissionItem(Vehicle* vehicle, bool editMode, const MissionItem& missionItem, QObject* parent) - : VisualMissionItem(vehicle, parent) - , _missionItem(missionItem) - , _rawEdit(false) - , _dirty(false) - , _ignoreDirtyChangeSignals(false) - , _speedSection(NULL) - , _cameraSection(NULL) - , _commandTree(qgcApp()->toolbox()->missionCommandTree()) - , _altitudeRelativeToHomeFact (0, "Altitude is relative to home", FactMetaData::valueTypeUint32) - , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) - , _param1MetaData(FactMetaData::valueTypeDouble) - , _param2MetaData(FactMetaData::valueTypeDouble) - , _param3MetaData(FactMetaData::valueTypeDouble) - , _param4MetaData(FactMetaData::valueTypeDouble) - , _param5MetaData(FactMetaData::valueTypeDouble) - , _param6MetaData(FactMetaData::valueTypeDouble) - , _param7MetaData(FactMetaData::valueTypeDouble) - , _syncingAltitudeRelativeToHomeAndFrame (false) + : VisualMissionItem (vehicle, parent) + , _missionItem (missionItem) + , _rawEdit (false) + , _dirty (false) + , _ignoreDirtyChangeSignals (false) + , _speedSection (NULL) + , _cameraSection (NULL) + , _commandTree (qgcApp()->toolbox()->missionCommandTree()) + , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) + , _altitudeMode (missionItem.relativeAltitude() ? AltitudeRelative : AltitudeAMSL) + , _altitudeFact (0, "Altitude", FactMetaData::valueTypeDouble) + , _amslAltAboveTerrainFact (0, "Alt above terrain", FactMetaData::valueTypeDouble) + , _param1MetaData (FactMetaData::valueTypeDouble) + , _param2MetaData (FactMetaData::valueTypeDouble) + , _param3MetaData (FactMetaData::valueTypeDouble) + , _param4MetaData (FactMetaData::valueTypeDouble) + , _param5MetaData (FactMetaData::valueTypeDouble) + , _param6MetaData (FactMetaData::valueTypeDouble) + , _param7MetaData (FactMetaData::valueTypeDouble) , _syncingHeadingDegreesAndParam4 (false) { _editorQml = QStringLiteral("qrc:/qml/SimpleItemEditor.qml"); - _altitudeRelativeToHomeFact.setRawValue(true); _isCurrentItem = missionItem.isCurrentItem(); + _altitudeFact.setRawValue(specifiesCoordinate() ? _missionItem._param7Fact.rawValue() : qQNaN()); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); // In !editMode we skip some of the intialization to save memory if (editMode) { @@ -117,76 +122,67 @@ SimpleMissionItem::SimpleMissionItem(Vehicle* vehicle, bool editMode, const Miss } _connectSignals(); _updateOptionalSections(); - _syncFrameToAltitudeRelativeToHome(); if (editMode) { _rebuildFacts(); } } SimpleMissionItem::SimpleMissionItem(const SimpleMissionItem& other, QObject* parent) - : VisualMissionItem(other, parent) - , _missionItem(other._vehicle) - , _rawEdit(false) - , _dirty(false) - , _ignoreDirtyChangeSignals(false) - , _speedSection(NULL) - , _cameraSection(NULL) - , _commandTree(qgcApp()->toolbox()->missionCommandTree()) - , _altitudeRelativeToHomeFact (0, "Altitude is relative to home", FactMetaData::valueTypeUint32) - , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) - , _param1MetaData(FactMetaData::valueTypeDouble) - , _param2MetaData(FactMetaData::valueTypeDouble) - , _param3MetaData(FactMetaData::valueTypeDouble) - , _param4MetaData(FactMetaData::valueTypeDouble) - , _syncingAltitudeRelativeToHomeAndFrame (false) + : VisualMissionItem (other, parent) + , _missionItem (other._vehicle) + , _rawEdit (false) + , _dirty (false) + , _ignoreDirtyChangeSignals (false) + , _speedSection (NULL) + , _cameraSection (NULL) + , _commandTree (qgcApp()->toolbox()->missionCommandTree()) + , _supportedCommandFact (0, "Command:", FactMetaData::valueTypeUint32) + , _altitudeMode (other._altitudeMode) + , _altitudeFact (0, "Altitude", FactMetaData::valueTypeDouble) + , _amslAltAboveTerrainFact (qQNaN(), "Alt above terrain", FactMetaData::valueTypeDouble) + , _param1MetaData (FactMetaData::valueTypeDouble) + , _param2MetaData (FactMetaData::valueTypeDouble) + , _param3MetaData (FactMetaData::valueTypeDouble) + , _param4MetaData (FactMetaData::valueTypeDouble) , _syncingHeadingDegreesAndParam4 (false) { _editorQml = QStringLiteral("qrc:/qml/SimpleItemEditor.qml"); + _altitudeFact.setRawValue(other._altitudeFact.rawValue()); + _amslAltAboveTerrainFact.setRawValue(other._amslAltAboveTerrainFact.rawValue()); + _setupMetaData(); _connectSignals(); _updateOptionalSections(); - *this = other; - _rebuildFacts(); } -const SimpleMissionItem& SimpleMissionItem::operator=(const SimpleMissionItem& other) -{ - VisualMissionItem::operator=(other); - - setRawEdit(other._rawEdit); - setDirty(other._dirty); - setHomePositionSpecialCase(other._homePositionSpecialCase); - _syncFrameToAltitudeRelativeToHome(); - _rebuildFacts(); - - return *this; -} - void SimpleMissionItem::_connectSignals(void) { // Connect to change signals to track dirty state - connect(&_missionItem._param1Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param2Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param3Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param4Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param5Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param6Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._param7Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._frameFact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem._commandFact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - connect(&_missionItem, &MissionItem::sequenceNumberChanged, this, &SimpleMissionItem::_setDirtyFromSignal); - - // Values from these facts must propagate back and forth between the real object storage - connect(&_altitudeRelativeToHomeFact, &Fact::valueChanged, this, &SimpleMissionItem::_syncAltitudeRelativeToHomeToFrame); - connect(&_missionItem._frameFact, &Fact::valueChanged, this, &SimpleMissionItem::_syncFrameToAltitudeRelativeToHome); + connect(&_missionItem._param1Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param2Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param3Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param4Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param5Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param6Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._param7Fact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._frameFact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem._commandFact, &Fact::valueChanged, this, &SimpleMissionItem::_setDirty); + connect(&_missionItem, &MissionItem::sequenceNumberChanged, this, &SimpleMissionItem::_setDirty); + connect(this, &SimpleMissionItem::altitudeModeChanged,this, &SimpleMissionItem::_setDirty); + + // These changes may need to trigger terrain queries + connect(&_altitudeFact, &Fact::valueChanged, this, &SimpleMissionItem::_altitudeChanged); + connect(this, &SimpleMissionItem::altitudeModeChanged,this, &SimpleMissionItem::_altitudeModeChanged); + + connect(this, &SimpleMissionItem::terrainAltitudeChanged,this, &SimpleMissionItem::_terrainAltChanged); // These are coordinate parameters, they must emit coordinateChanged signal - connect(&_missionItem._param5Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); - connect(&_missionItem._param6Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); - connect(&_missionItem._param7Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); + connect(&_missionItem._param5Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); + connect(&_missionItem._param6Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); + connect(&_missionItem._param7Fact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCoordinateChanged); // The following changes may also change friendlyEditAllowed connect(&_missionItem._autoContinueFact, &Fact::valueChanged, this, &SimpleMissionItem::_sendFriendlyEditAllowedChanged); @@ -203,12 +199,11 @@ void SimpleMissionItem::_connectSignals(void) connect(&_missionItem._commandFact, &Fact::valueChanged, this, &SimpleMissionItem::isStandaloneCoordinateChanged); // Whenever these properties change the ui model changes as well - connect(this, &SimpleMissionItem::commandChanged, this, &SimpleMissionItem::_rebuildFacts); - connect(this, &SimpleMissionItem::rawEditChanged, this, &SimpleMissionItem::_rebuildFacts); + connect(this, &SimpleMissionItem::commandChanged, this, &SimpleMissionItem::_rebuildFacts); + connect(this, &SimpleMissionItem::rawEditChanged, this, &SimpleMissionItem::_rebuildFacts); // These fact signals must alway signal out through SimpleMissionItem signals connect(&_missionItem._commandFact, &Fact::valueChanged, this, &SimpleMissionItem::_sendCommandChanged); - connect(&_missionItem._frameFact, &Fact::valueChanged, this, &SimpleMissionItem::_sendFrameChanged); // Sequence number is kept in mission iteem, so we need to propagate signal up as well connect(&_missionItem, &MissionItem::sequenceNumberChanged, this, &SimpleMissionItem::sequenceNumberChanged); @@ -260,6 +255,7 @@ void SimpleMissionItem::_setupMetaData(void) _missionItem._commandFact.setMetaData(_commandMetaData); _missionItem._frameFact.setMetaData(_frameMetaData); + _altitudeFact.setMetaData(_altitudeMetaData); } SimpleMissionItem::~SimpleMissionItem() @@ -276,6 +272,14 @@ void SimpleMissionItem::save(QJsonArray& missionItems) MissionItem* item = items[i]; QJsonObject saveObject; item->save(saveObject); + if (i == 0) { + // This is the main simple item, save the alt/terrain data + if (specifiesCoordinate()) { + saveObject[_jsonAltitudeModeKey] = _altitudeMode; + saveObject[_jsonAltitudeKey] = _altitudeFact.rawValue().toDouble(); + saveObject[_jsonAMSLAltAboveTerrainKey] = _amslAltAboveTerrainFact.rawValue().toDouble(); + } + } missionItems.append(saveObject); item->deleteLater(); } @@ -285,6 +289,11 @@ bool SimpleMissionItem::load(QTextStream &loadStream) { bool success; if ((success = _missionItem.load(loadStream))) { + if (specifiesCoordinate()) { + _altitudeMode = _missionItem.relativeAltitude() ? AltitudeRelative : AltitudeAMSL; + _altitudeFact.setRawValue(_missionItem._param7Fact.rawValue()); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); + } _updateOptionalSections(); } return success; @@ -292,11 +301,34 @@ bool SimpleMissionItem::load(QTextStream &loadStream) bool SimpleMissionItem::load(const QJsonObject& json, int sequenceNumber, QString& errorString) { - bool success; - if ((success = _missionItem.load(json, sequenceNumber, errorString))) { - _updateOptionalSections(); + if (!_missionItem.load(json, sequenceNumber, errorString)) { + return false; } - return success; + + if (specifiesCoordinate()) { + if (json.contains(_jsonAltitudeModeKey) || json.contains(_jsonAltitudeKey) || json.contains(_jsonAMSLAltAboveTerrainKey)) { + QList keyInfoList = { + { _jsonAltitudeModeKey, QJsonValue::Double, true }, + { _jsonAltitudeKey, QJsonValue::Double, true }, + { _jsonAMSLAltAboveTerrainKey, QJsonValue::Double, true }, + }; + if (!JsonHelper::validateKeys(json, keyInfoList, errorString)) { + return false; + } + + _altitudeMode = (AltitudeMode)json[_jsonAltitudeModeKey].toDouble(); + _altitudeFact.setRawValue(JsonHelper::possibleNaNJsonValue(json[_jsonAltitudeKey])); + _amslAltAboveTerrainFact.setRawValue(JsonHelper::possibleNaNJsonValue(json[_jsonAltitudeKey])); + } else { + _altitudeMode = _missionItem.relativeAltitude() ? AltitudeRelative : AltitudeAMSL; + _altitudeFact.setRawValue(_missionItem._param7Fact.rawValue()); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); + } + } + + _updateOptionalSections(); + + return true; } bool SimpleMissionItem::isStandaloneCoordinate(void) const @@ -429,12 +461,6 @@ void SimpleMissionItem::_rebuildTextFieldFacts(void) } } - if (uiInfo->specifiesCoordinate() || uiInfo->specifiesAltitudeOnly()) { - _missionItem._param7Fact._setName("Altitude"); - _missionItem._param7Fact.setMetaData(_altitudeMetaData); - _textFieldFacts.append(&_missionItem._param7Fact); - } - _ignoreDirtyChangeSignals = false; } } @@ -489,15 +515,9 @@ void SimpleMissionItem::_rebuildNaNFacts(void) } } -void SimpleMissionItem::_rebuildCheckboxFacts(void) +bool SimpleMissionItem::specifiesAltitude(void) const { - _checkboxFacts.clear(); - - if (rawEdit()) { - _checkboxFacts.append(&_missionItem._autoContinueFact); - } else if ((specifiesCoordinate() || specifiesAltitudeOnly()) && !_homePositionSpecialCase) { - _checkboxFacts.append(&_altitudeRelativeToHomeFact); - } + return specifiesCoordinate() || specifiesAltitudeOnly(); } void SimpleMissionItem::_rebuildComboBoxFacts(void) @@ -541,7 +561,6 @@ void SimpleMissionItem::_rebuildFacts(void) { _rebuildTextFieldFacts(); _rebuildNaNFacts(); - _rebuildCheckboxFacts(); _rebuildComboBoxFacts(); } @@ -597,7 +616,7 @@ void SimpleMissionItem::setDirty(bool dirty) } } -void SimpleMissionItem::_setDirtyFromSignal(void) +void SimpleMissionItem::_setDirty(void) { if (!_ignoreDirtyChangeSignals) { setDirty(true); @@ -609,30 +628,76 @@ void SimpleMissionItem::_sendCoordinateChanged(void) emit coordinateChanged(coordinate()); } -void SimpleMissionItem::_syncAltitudeRelativeToHomeToFrame(const QVariant& value) +void SimpleMissionItem::_altitudeModeChanged(void) { - if (!_syncingAltitudeRelativeToHomeAndFrame) { - _syncingAltitudeRelativeToHomeAndFrame = true; - _missionItem.setFrame(value.toBool() ? MAV_FRAME_GLOBAL_RELATIVE_ALT : MAV_FRAME_GLOBAL); - emit coordinateHasRelativeAltitudeChanged(value.toBool()); - _syncingAltitudeRelativeToHomeAndFrame = false; + switch (_altitudeMode) { + case AltitudeAboveTerrain: + // Terrain altitudes are AMSL + _missionItem.setFrame(MAV_FRAME_GLOBAL); + // Clear any old values + _missionItem._param7Fact.setRawValue(qQNaN()); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); + _altitudeChanged(); + break; + case AltitudeAMSL: + _missionItem.setFrame(MAV_FRAME_GLOBAL); + break; + case AltitudeRelative: + _missionItem.setFrame(MAV_FRAME_GLOBAL_RELATIVE_ALT); + break; } + emit coordinateHasRelativeAltitudeChanged(_altitudeMode == AltitudeRelative); } -void SimpleMissionItem::_syncFrameToAltitudeRelativeToHome(void) +void SimpleMissionItem::_altitudeChanged(void) { - if (!_syncingAltitudeRelativeToHomeAndFrame) { - _syncingAltitudeRelativeToHomeAndFrame = true; - _altitudeRelativeToHomeFact.setRawValue(relativeAltitude()); - emit coordinateHasRelativeAltitudeChanged(_altitudeRelativeToHomeFact.rawValue().toBool()); - _syncingAltitudeRelativeToHomeAndFrame = false; + if (!specifiesAltitude()) { + return; + } + + if (_altitudeMode == AltitudeAboveTerrain) { + _terrainAltChanged(); + } else { + _missionItem._param7Fact.setRawValue(_altitudeFact.rawValue()); } } +void SimpleMissionItem::_terrainAltChanged(void) +{ + if (!specifiesAltitude() || _altitudeMode != AltitudeAboveTerrain) { + // We don't need terrain data + return; + } + + if (!qIsNaN(_amslAltAboveTerrainFact.rawValue().toDouble())) { + // We already have terrain values set. Don't do it again to prevent dirty bit changing. + return; + } + + if (qIsNaN(terrainAltitude())) { + // Set NaNs to signal we are waiting on terrain data + _missionItem._param7Fact.setRawValue(qQNaN()); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); + } else { + double aboveTerrain = terrainAltitude() + _altitudeFact.rawValue().toDouble(); + _missionItem._param7Fact.setRawValue(aboveTerrain); + _amslAltAboveTerrainFact.setRawValue(aboveTerrain); + } +} + +bool SimpleMissionItem::readyForSave(void) const +{ + return !specifiesAltitude() || !qIsNaN(_missionItem._param7Fact.rawValue().toDouble()); +} + void SimpleMissionItem::setDefaultsForCommand(void) { // We set these global defaults first, then if there are param defaults they will get reset - _missionItem.setParam7(qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue().toDouble()); + _altitudeMode = AltitudeRelative; + double defaultAlt = qgcApp()->toolbox()->settingsManager()->appSettings()->defaultMissionItemAltitude()->rawValue().toDouble(); + _altitudeFact.setRawValue(defaultAlt); + _missionItem._param7Fact.setRawValue(defaultAlt); + _amslAltAboveTerrainFact.setRawValue(qQNaN()); MAV_CMD command = (MAV_CMD)this->command(); const MissionCommandUIInfo* uiInfo = _commandTree->getUIInfo(_vehicle, command); @@ -667,11 +732,6 @@ void SimpleMissionItem::setDefaultsForCommand(void) setRawEdit(false); } -void SimpleMissionItem::_sendFrameChanged(void) -{ - emit frameChanged(_missionItem.frame()); -} - void SimpleMissionItem::_sendCommandChanged(void) { emit commandChanged(command()); @@ -846,3 +906,11 @@ void SimpleMissionItem::setMissionFlightStatus(MissionController::MissionFlightS } } } + +void SimpleMissionItem::setAltitudeMode(AltitudeMode altitudeMode) +{ + if (altitudeMode != _altitudeMode) { + _altitudeMode = altitudeMode; + emit altitudeModeChanged(); + } +} diff --git a/src/MissionManager/SimpleMissionItem.h b/src/MissionManager/SimpleMissionItem.h index c316804de..3f1c4c447 100644 --- a/src/MissionManager/SimpleMissionItem.h +++ b/src/MissionManager/SimpleMissionItem.h @@ -29,12 +29,21 @@ public: ~SimpleMissionItem(); - const SimpleMissionItem& operator=(const SimpleMissionItem& other); + enum AltitudeMode { + AltitudeRelative, + AltitudeAMSL, + AltitudeAboveTerrain + }; + + Q_ENUM(AltitudeMode) Q_PROPERTY(QString category READ category NOTIFY commandChanged) Q_PROPERTY(bool friendlyEditAllowed READ friendlyEditAllowed NOTIFY friendlyEditAllowedChanged) Q_PROPERTY(bool rawEdit READ rawEdit WRITE setRawEdit NOTIFY rawEditChanged) ///< true: raw item editing with all params - Q_PROPERTY(bool relativeAltitude READ relativeAltitude NOTIFY frameChanged) + Q_PROPERTY(bool specifiesAltitude READ specifiesAltitude NOTIFY commandChanged) + Q_PROPERTY(Fact* altitude READ altitude CONSTANT) ///< Altitude as specified by altitudeMode. Not necessarily true mission item altitude + Q_PROPERTY(AltitudeMode altitudeMode READ altitudeMode WRITE setAltitudeMode NOTIFY altitudeModeChanged) + Q_PROPERTY(Fact* amslAltAboveTerrain READ amslAltAboveTerrain CONSTANT) ///< Actual AMSL altitude for item if altitudeMode == AltitudeAboveTerrain Q_PROPERTY(MavlinkQmlSingleton::Qml_MAV_CMD command READ command WRITE setCommand NOTIFY commandChanged) /// Optional sections @@ -42,7 +51,6 @@ public: Q_PROPERTY(QObject* cameraSection READ cameraSection NOTIFY cameraSectionChanged) // These properties are used to display the editing ui - Q_PROPERTY(QmlObjectListModel* checkboxFacts READ checkboxFacts CONSTANT) Q_PROPERTY(QmlObjectListModel* comboboxFacts READ comboboxFacts CONSTANT) Q_PROPERTY(QmlObjectListModel* textFieldFacts READ textFieldFacts CONSTANT) Q_PROPERTY(QmlObjectListModel* nanFacts READ nanFacts CONSTANT) @@ -60,15 +68,20 @@ public: MavlinkQmlSingleton::Qml_MAV_CMD command(void) const { return (MavlinkQmlSingleton::Qml_MAV_CMD)_missionItem._commandFact.cookedValue().toInt(); } bool friendlyEditAllowed (void) const; bool rawEdit (void) const; + bool specifiesAltitude (void) const; + AltitudeMode altitudeMode (void) const { return _altitudeMode; } + Fact* altitude (void) { return &_altitudeFact; } + Fact* amslAltAboveTerrain (void) { return &_amslAltAboveTerrainFact; } + CameraSection* cameraSection (void) { return _cameraSection; } SpeedSection* speedSection (void) { return _speedSection; } QmlObjectListModel* textFieldFacts (void) { return &_textFieldFacts; } QmlObjectListModel* nanFacts (void) { return &_nanFacts; } - QmlObjectListModel* checkboxFacts (void) { return &_checkboxFacts; } QmlObjectListModel* comboboxFacts (void) { return &_comboboxFacts; } void setRawEdit(bool rawEdit); + void setAltitudeMode(AltitudeMode altitudeMode); void setCommandByIndex(int index); @@ -82,8 +95,6 @@ public: bool load(QTextStream &loadStream); bool load(const QJsonObject& json, int sequenceNumber, QString& errorString); - bool relativeAltitude(void) { return _missionItem.frame() == MAV_FRAME_GLOBAL_RELATIVE_ALT; } - MissionItem& missionItem(void) { return _missionItem; } const MissionItem& missionItem(void) const { return _missionItem; } @@ -107,6 +118,7 @@ public: void appendMissionItems (QList& items, QObject* missionItemParent) final; void applyNewAltitude (double newAltitude) final; void setMissionFlightStatus (MissionController::MissionFlightStatus_t& missionFlightStatus) final; + bool readyForSave (void) const final; bool coordinateHasRelativeAltitude (void) const final { return _missionItem.relativeAltitude(); } bool exitCoordinateHasRelativeAltitude (void) const final { return coordinateHasRelativeAltitude(); } @@ -123,51 +135,51 @@ public slots: signals: void commandChanged (int command); - void frameChanged (int frame); void friendlyEditAllowedChanged (bool friendlyEditAllowed); void headingDegreesChanged (double heading); void rawEditChanged (bool rawEdit); void cameraSectionChanged (QObject* cameraSection); void speedSectionChanged (QObject* cameraSection); + void altitudeModeChanged (void); private slots: - void _setDirtyFromSignal (void); - void _sectionDirtyChanged (bool dirty); - void _sendCommandChanged (void); - void _sendCoordinateChanged (void); - void _sendFrameChanged (void); - void _sendFriendlyEditAllowedChanged (void); - void _syncAltitudeRelativeToHomeToFrame (const QVariant& value); - void _syncFrameToAltitudeRelativeToHome (void); - void _updateLastSequenceNumber (void); - void _rebuildFacts (void); - + void _setDirty (void); + void _sectionDirtyChanged (bool dirty); + void _sendCommandChanged (void); + void _sendCoordinateChanged (void); + void _sendFriendlyEditAllowedChanged(void); + void _altitudeChanged (void); + void _altitudeModeChanged (void); + void _terrainAltChanged (void); + void _updateLastSequenceNumber (void); + void _rebuildFacts (void); + void _rebuildTextFieldFacts (void); private: void _connectSignals (void); void _setupMetaData (void); void _updateOptionalSections(void); - void _rebuildTextFieldFacts (void); void _rebuildNaNFacts (void); - void _rebuildCheckboxFacts (void); void _rebuildComboBoxFacts (void); - MissionItem _missionItem; - bool _rawEdit; - bool _dirty; - bool _ignoreDirtyChangeSignals; + MissionItem _missionItem; + bool _rawEdit; + bool _dirty; + bool _ignoreDirtyChangeSignals; SpeedSection* _speedSection; CameraSection* _cameraSection; MissionCommandTree* _commandTree; - Fact _altitudeRelativeToHomeFact; - Fact _supportedCommandFact; + Fact _supportedCommandFact; + + AltitudeMode _altitudeMode; + Fact _altitudeFact; + Fact _amslAltAboveTerrainFact; QmlObjectListModel _textFieldFacts; QmlObjectListModel _nanFacts; - QmlObjectListModel _checkboxFacts; QmlObjectListModel _comboboxFacts; static FactMetaData* _altitudeMetaData; @@ -185,8 +197,11 @@ private: FactMetaData _param6MetaData; FactMetaData _param7MetaData; - bool _syncingAltitudeRelativeToHomeAndFrame; ///< true: already in a sync signal, prevents signal loop - bool _syncingHeadingDegreesAndParam4; ///< true: already in a sync signal, prevents signal loop + bool _syncingHeadingDegreesAndParam4; ///< true: already in a sync signal, prevents signal loop + + static const char* _jsonAltitudeModeKey; + static const char* _jsonAltitudeKey; + static const char* _jsonAMSLAltAboveTerrainKey; }; #endif diff --git a/src/MissionManager/SimpleMissionItemTest.cc b/src/MissionManager/SimpleMissionItemTest.cc index 453987813..54b15897c 100644 --- a/src/MissionManager/SimpleMissionItemTest.cc +++ b/src/MissionManager/SimpleMissionItemTest.cc @@ -15,43 +15,37 @@ const SimpleMissionItemTest::ItemInfo_t SimpleMissionItemTest::_rgItemInfo[] = { { MAV_CMD_NAV_WAYPOINT, MAV_FRAME_GLOBAL_RELATIVE_ALT }, - { MAV_CMD_NAV_LOITER_UNLIM, MAV_FRAME_GLOBAL_RELATIVE_ALT }, + { MAV_CMD_NAV_LOITER_UNLIM, MAV_FRAME_GLOBAL }, { MAV_CMD_NAV_LOITER_TURNS, MAV_FRAME_GLOBAL_RELATIVE_ALT }, - { MAV_CMD_NAV_LOITER_TIME, MAV_FRAME_GLOBAL_RELATIVE_ALT }, + { MAV_CMD_NAV_LOITER_TIME, MAV_FRAME_GLOBAL }, { MAV_CMD_NAV_LAND, MAV_FRAME_GLOBAL_RELATIVE_ALT }, - { MAV_CMD_NAV_TAKEOFF, MAV_FRAME_GLOBAL_RELATIVE_ALT }, + { MAV_CMD_NAV_TAKEOFF, MAV_FRAME_GLOBAL }, { MAV_CMD_DO_JUMP, MAV_FRAME_MISSION }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesWaypoint[] = { - { "Altitude", 70.1234567 }, { "Hold", 10.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterUnlim[] = { - { "Altitude", 70.1234567 }, { "Radius", 30.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterTurns[] = { - { "Altitude", 70.1234567 }, { "Radius", 30.1234567 }, { "Turns", 10.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLoiterTime[] = { - { "Altitude", 70.1234567 }, { "Radius", 30.1234567 }, { "Hold", 10.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesLand[] = { - { "Altitude", 70.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesTakeoff[] = { { "Pitch", 10.1234567 }, - { "Altitude", 70.1234567 }, }; const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesDoJump[] = { @@ -60,13 +54,14 @@ const SimpleMissionItemTest::FactValue_t SimpleMissionItemTest::_rgFactValuesDoJ }; const SimpleMissionItemTest::ItemExpected_t SimpleMissionItemTest::_rgItemExpected[] = { - { sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint)/sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint[0]), SimpleMissionItemTest::_rgFactValuesWaypoint, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim[0]), SimpleMissionItemTest::_rgFactValuesLoiterUnlim, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns[0]), SimpleMissionItemTest::_rgFactValuesLoiterTurns, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime[0]), SimpleMissionItemTest::_rgFactValuesLoiterTime, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesLand)/sizeof(SimpleMissionItemTest::_rgFactValuesLand[0]), SimpleMissionItemTest::_rgFactValuesLand, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff)/sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff[0]), SimpleMissionItemTest::_rgFactValuesTakeoff, true }, - { sizeof(SimpleMissionItemTest::_rgFactValuesDoJump)/sizeof(SimpleMissionItemTest::_rgFactValuesDoJump[0]), SimpleMissionItemTest::_rgFactValuesDoJump, false }, + // Text field facts count Fact Values Altitude Altitude Mode + { sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint)/sizeof(SimpleMissionItemTest::_rgFactValuesWaypoint[0]), SimpleMissionItemTest::_rgFactValuesWaypoint, 70.1234567, SimpleMissionItem::AltitudeRelative }, + { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterUnlim[0]), SimpleMissionItemTest::_rgFactValuesLoiterUnlim, 70.1234567, SimpleMissionItem::AltitudeAMSL }, + { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTurns[0]), SimpleMissionItemTest::_rgFactValuesLoiterTurns, 70.1234567, SimpleMissionItem::AltitudeRelative }, + { sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime)/sizeof(SimpleMissionItemTest::_rgFactValuesLoiterTime[0]), SimpleMissionItemTest::_rgFactValuesLoiterTime, 70.1234567, SimpleMissionItem::AltitudeAMSL }, + { sizeof(SimpleMissionItemTest::_rgFactValuesLand)/sizeof(SimpleMissionItemTest::_rgFactValuesLand[0]), SimpleMissionItemTest::_rgFactValuesLand, 70.1234567, SimpleMissionItem::AltitudeRelative }, + { sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff)/sizeof(SimpleMissionItemTest::_rgFactValuesTakeoff[0]), SimpleMissionItemTest::_rgFactValuesTakeoff, 70.1234567, SimpleMissionItem::AltitudeAMSL }, + { sizeof(SimpleMissionItemTest::_rgFactValuesDoJump)/sizeof(SimpleMissionItemTest::_rgFactValuesDoJump[0]), SimpleMissionItemTest::_rgFactValuesDoJump, qQNaN(), SimpleMissionItem::AltitudeRelative }, }; SimpleMissionItemTest::SimpleMissionItemTest(void) @@ -80,12 +75,12 @@ void SimpleMissionItemTest::init(void) VisualMissionItemTest::init(); rgSimpleItemSignals[commandChangedIndex] = SIGNAL(commandChanged(int)); - rgSimpleItemSignals[frameChangedIndex] = SIGNAL(frameChanged(int)); + rgSimpleItemSignals[altitudeModeChangedIndex] = SIGNAL(altitudeModeChanged()); rgSimpleItemSignals[friendlyEditAllowedChangedIndex] = SIGNAL(friendlyEditAllowedChanged(bool)); rgSimpleItemSignals[headingDegreesChangedIndex] = SIGNAL(headingDegreesChanged(double)); rgSimpleItemSignals[rawEditChangedIndex] = SIGNAL(rawEditChanged(bool)); - rgSimpleItemSignals[cameraSectionChangedIndex] = SIGNAL(rawEditChanged(bool)); - rgSimpleItemSignals[speedSectionChangedIndex] = SIGNAL(rawEditChanged(bool)); + rgSimpleItemSignals[cameraSectionChangedIndex] = SIGNAL(cameraSectionChanged(QObject*)); + rgSimpleItemSignals[speedSectionChangedIndex] = SIGNAL(speedSectionChanged(QObject*)); rgSimpleItemSignals[coordinateHasRelativeAltitudeChangedIndex] = SIGNAL(coordinateHasRelativeAltitudeChanged(bool)); MissionItem missionItem(1, // sequence number @@ -164,8 +159,10 @@ void SimpleMissionItemTest::_testEditorFacts(void) } QCOMPARE(factCount, expected->cFactValues); - int expectedCount = expected->relativeAltCheckbox ? 1 : 0; - QCOMPARE(simpleMissionItem.checkboxFacts()->count(), expectedCount); + if (!qIsNaN(expected->altValue)) { + QCOMPARE(simpleMissionItem.altitudeMode(), expected->altMode); + QCOMPARE(simpleMissionItem.altitude()->rawValue().toDouble(), expected->altValue); + } } delete vehicle; @@ -228,18 +225,8 @@ void SimpleMissionItemTest::_testSignals(void) QVERIFY(_spyVisualItem->checkOnlySignalByMask(dirtyChangedMask)); _spyVisualItem->clearAllSignals(); - // Check frameChanged signalling. Calling setFrame should signal: - // frameChanged - // dirtyChanged - // friendlyEditAllowedChanged - this signal is not very smart on when it gets sent - // coordinateHasRelativeAltitudeChanged - - missionItem.setFrame(MAV_FRAME_GLOBAL_RELATIVE_ALT); - QVERIFY(_spyVisualItem->checkNoSignals()); - QVERIFY(_spySimpleItem->checkNoSignals()); - - missionItem.setFrame(MAV_FRAME_GLOBAL); - QVERIFY(_spySimpleItem->checkOnlySignalByMask(frameChangedMask | dirtyChangedMask | friendlyEditAllowedChangedMask | coordinateHasRelativeAltitudeChangedMask)); + _simpleItem->setAltitudeMode(_simpleItem->altitudeMode() == SimpleMissionItem::AltitudeRelative ? SimpleMissionItem::AltitudeAMSL : SimpleMissionItem::AltitudeRelative); + QVERIFY(_spySimpleItem->checkOnlySignalByMask(dirtyChangedMask | friendlyEditAllowedChangedMask | altitudeModeChangedMask | coordinateHasRelativeAltitudeChangedMask)); _spySimpleItem->clearAllSignals(); _spyVisualItem->clearAllSignals(); @@ -320,3 +307,18 @@ void SimpleMissionItemTest::_testSpeedSection(void) QCOMPARE(_spyVisualItem->checkSignalsByMask(specifiedFlightSpeedChangedMask), true); QCOMPARE(_simpleItem->dirty(), true); } + +void SimpleMissionItemTest::_testAltitudePropogation(void) +{ + // Make sure that changes to altitude propogate to param 7 of the mission item + + _simpleItem->setAltitudeMode(SimpleMissionItem::AltitudeRelative); + _simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1); + QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7()); + QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL_RELATIVE_ALT); + + _simpleItem->setAltitudeMode(SimpleMissionItem::AltitudeAMSL); + _simpleItem->altitude()->setRawValue(_simpleItem->altitude()->rawValue().toDouble() + 1); + QCOMPARE(_simpleItem->altitude()->rawValue().toDouble(), _simpleItem->missionItem().param7()); + QCOMPARE(_simpleItem->missionItem().frame(), MAV_FRAME_GLOBAL); +} diff --git a/src/MissionManager/SimpleMissionItemTest.h b/src/MissionManager/SimpleMissionItemTest.h index f96db89a3..db67cb4f6 100644 --- a/src/MissionManager/SimpleMissionItemTest.h +++ b/src/MissionManager/SimpleMissionItemTest.h @@ -31,11 +31,12 @@ private slots: void _testSpeedSectionDirty(void); void _testCameraSection(void); void _testSpeedSection(void); + void _testAltitudePropogation(void); private: enum { commandChangedIndex = 0, - frameChangedIndex, + altitudeModeChangedIndex, friendlyEditAllowedChangedIndex, headingDegreesChangedIndex, rawEditChangedIndex, @@ -47,7 +48,7 @@ private: enum { commandChangedMask = 1 << commandChangedIndex, - frameChangedMask = 1 << frameChangedIndex, + altitudeModeChangedMask = 1 << altitudeModeChangedIndex, friendlyEditAllowedChangedMask = 1 << friendlyEditAllowedChangedIndex, headingDegreesChangedMask = 1 << headingDegreesChangedIndex, rawEditChangedMask = 1 << rawEditChangedIndex, @@ -70,9 +71,10 @@ private: } FactValue_t; typedef struct { - size_t cFactValues; - const FactValue_t* rgFactValues; - bool relativeAltCheckbox; + size_t cFactValues; + const FactValue_t* rgFactValues; + double altValue; + SimpleMissionItem::AltitudeMode altMode; } ItemExpected_t; SimpleMissionItem* _simpleItem; diff --git a/src/MissionManager/VisualMissionItem.h b/src/MissionManager/VisualMissionItem.h index c473adce1..eb1232cf7 100644 --- a/src/MissionManager/VisualMissionItem.h +++ b/src/MissionManager/VisualMissionItem.h @@ -43,7 +43,7 @@ public: Q_PROPERTY(bool homePosition READ homePosition CONSTANT) ///< true: This item is being used as a home position indicator Q_PROPERTY(QGeoCoordinate coordinate READ coordinate WRITE setCoordinate NOTIFY coordinateChanged) ///< This is the entry point for a waypoint line into the item. For a simple item it is also the location of the item - Q_PROPERTY(double terrainAltitude READ terrainAltitude NOTIFY terrainAltitudeChanged) ///< The altitude of terrain at the coordinate position, NaN if not known + Q_PROPERTY(double terrainAltitude READ terrainAltitude NOTIFY terrainAltitudeChanged) ///< The altitude of terrain at the coordinate position, NaN if not known Q_PROPERTY(bool coordinateHasRelativeAltitude READ coordinateHasRelativeAltitude NOTIFY coordinateHasRelativeAltitudeChanged) ///< true: coordinate.latitude is relative to home altitude Q_PROPERTY(QGeoCoordinate exitCoordinate READ exitCoordinate NOTIFY exitCoordinateChanged) ///< This is the exit point for a waypoint line coming out of the item. Q_PROPERTY(bool exitCoordinateHasRelativeAltitude READ exitCoordinateHasRelativeAltitude NOTIFY exitCoordinateHasRelativeAltitudeChanged) ///< true: coordinate.latitude is relative to home altitude @@ -130,6 +130,11 @@ public: virtual void setSequenceNumber (int sequenceNumber) = 0; virtual int lastSequenceNumber (void) const = 0; + /// Specifies whether the item has all the data it needs such that it can be saved. Currently the only + /// case where this returns false is if it has not determined terrain values yet. + /// @return true: Ready to save, false: Still waiting on information + virtual bool readyForSave(void) const { return true; } + /// Save the item(s) in Json format /// @param missionItems Current set of mission items, new items should be appended to the end virtual void save(QJsonArray& missionItems) = 0; diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 16b4209f0..6d097f3ce 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -162,7 +162,15 @@ QGCView { _missionController.setCurrentPlanViewIndex(0, true) } + function waitingOnDataMessage() { + _qgcView.showMessage(qsTr("Unable to Save/Upload"), qsTr("Plan is waiting on terrain data from server for correct altitude values."), StandardButton.Ok) + } + function upload() { + if (!readyForSaveSend()) { + waitingOnDataMessage() + return + } if (_activeVehicle && _activeVehicle.armed && _activeVehicle.flightMode === _activeVehicle.missionFlightMode) { _qgcView.showDialog(activeMissionUploadDialogComponent, qsTr("Plan Upload"), _qgcView.showDialogDefaultWidth, StandardButton.Cancel) } else { @@ -178,6 +186,10 @@ QGCView { } function saveToSelectedFile() { + if (!readyForSaveSend()) { + waitingOnDataMessage() + return + } fileDialog.title = qsTr("Save Plan") fileDialog.plan = true fileDialog.selectExisting = false @@ -190,6 +202,10 @@ QGCView { } function saveKmlToSelectedFile() { + if (!readyForSaveSend()) { + waitingOnDataMessage() + return + } fileDialog.title = qsTr("Save KML") fileDialog.plan = false fileDialog.selectExisting = false diff --git a/src/PlanView/SimpleItemEditor.qml b/src/PlanView/SimpleItemEditor.qml index 75c6da4f3..e63cc8453 100644 --- a/src/PlanView/SimpleItemEditor.qml +++ b/src/PlanView/SimpleItemEditor.qml @@ -17,6 +17,9 @@ Rectangle { color: qgcPal.windowShadeDark radius: _radius + property bool _specifiesAltitude: missionItem.specifiesAltitude + property bool _altModeIsTerrain: missionItem.altitudeMode === 2 + Column { id: valuesColumn anchors.margins: _margin @@ -68,9 +71,27 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right flow: GridLayout.TopToBottom - rows: missionItem.textFieldFacts.count + missionItem.nanFacts.count + (missionItem.speedSection.available ? 1 : 0) + rows: missionItem.textFieldFacts.count + + missionItem.nanFacts.count + + (missionItem.speedSection.available ? 1 : 0) + + (_specifiesAltitude ? 1 : 0) + + (_altModeIsTerrain ? 1 : 0) columns: 2 + QGCComboBox { + id: altCombo + model: [ qsTr("Alt (Rel)"), qsTr("AMSL"), qsTr("Above Terrain") ] + currentIndex: missionItem.altitudeMode + Layout.fillWidth: true + onActivated: missionItem.altitudeMode = index + visible: _specifiesAltitude + } + + QGCLabel { + text: qsTr("Actual AMSL Alt") + visible: _altModeIsTerrain + } + Repeater { model: missionItem.textFieldFacts @@ -95,6 +116,19 @@ Rectangle { visible: missionItem.speedSection.available } + + FactTextField { + showUnits: true + fact: missionItem.altitude + Layout.fillWidth: true + visible: _specifiesAltitude + } + + FactLabel { + fact: missionItem.amslAltAboveTerrain + visible: _altModeIsTerrain + } + Repeater { model: missionItem.textFieldFacts @@ -102,6 +136,7 @@ Rectangle { showUnits: true fact: object Layout.fillWidth: true + enabled: !object.readOnly } } @@ -124,15 +159,6 @@ Rectangle { } } - Repeater { - model: missionItem.checkboxFacts - - FactCheckBox { - text: object.name - fact: object - } - } - CameraSection { checked: missionItem.cameraSection.settingsSpecified visible: missionItem.cameraSection.available diff --git a/src/Terrain.cc b/src/Terrain.cc index cd2480cf9..3fb70ae7a 100644 --- a/src/Terrain.cc +++ b/src/Terrain.cc @@ -77,7 +77,7 @@ void TerrainBatchManager::_sendNextBatch(void) QUrlQuery query; query.addQueryItem(QStringLiteral("points"), points); - QUrl url(QStringLiteral("https://api.airmap.com/elevation/stage/srtm1/ele")); + QUrl url(QStringLiteral("https://api.airmap.com/elevation/v1/ele")); url.setQuery(query); QNetworkRequest request(url); -- 2.22.0