295 lines
11 KiB
C++
295 lines
11 KiB
C++
|
|
/****************************************************************************
|
||
|
|
*
|
||
|
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
|
||
|
|
*
|
||
|
|
* QGroundControl is licensed according to the terms in the file
|
||
|
|
* COPYING.md in the root of the source code directory.
|
||
|
|
*
|
||
|
|
****************************************************************************/
|
||
|
|
|
||
|
|
#include "MissionSettingsItem.h"
|
||
|
|
#include "JsonHelper.h"
|
||
|
|
#include "MissionController.h"
|
||
|
|
#include "QGCGeo.h"
|
||
|
|
#include "QGroundControlQmlGlobal.h"
|
||
|
|
#include "SimpleMissionItem.h"
|
||
|
|
#include "SettingsManager.h"
|
||
|
|
#include "AppSettings.h"
|
||
|
|
#include "MissionCommandUIInfo.h"
|
||
|
|
|
||
|
|
#include <QPolygonF>
|
||
|
|
|
||
|
|
QGC_LOGGING_CATEGORY(MissionSettingsComplexItemLog, "MissionSettingsComplexItemLog")
|
||
|
|
|
||
|
|
const char* MissionSettingsItem::_plannedHomePositionAltitudeName = "PlannedHomePositionAltitude";
|
||
|
|
|
||
|
|
QMap<QString, FactMetaData*> MissionSettingsItem::_metaDataMap;
|
||
|
|
|
||
|
|
MissionSettingsItem::MissionSettingsItem(Vehicle* vehicle, bool flyView, QObject* parent)
|
||
|
|
: ComplexMissionItem (vehicle, flyView, parent)
|
||
|
|
, _plannedHomePositionAltitudeFact (0, _plannedHomePositionAltitudeName, FactMetaData::valueTypeDouble)
|
||
|
|
, _cameraSection (vehicle)
|
||
|
|
, _speedSection (vehicle)
|
||
|
|
{
|
||
|
|
_isIncomplete = false;
|
||
|
|
_editorQml = "qrc:/qml/MissionSettingsEditor.qml";
|
||
|
|
|
||
|
|
if (_metaDataMap.isEmpty()) {
|
||
|
|
_metaDataMap = FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/MissionSettings.FactMetaData.json"), nullptr /* metaDataParent */);
|
||
|
|
}
|
||
|
|
|
||
|
|
_plannedHomePositionAltitudeFact.setMetaData (_metaDataMap[_plannedHomePositionAltitudeName]);
|
||
|
|
_plannedHomePositionAltitudeFact.setRawValue (_plannedHomePositionAltitudeFact.rawDefaultValue());
|
||
|
|
setHomePositionSpecialCase(true);
|
||
|
|
|
||
|
|
_cameraSection.setAvailable(true);
|
||
|
|
_speedSection.setAvailable(true);
|
||
|
|
|
||
|
|
connect(this, &MissionSettingsItem::specifyMissionFlightSpeedChanged, this, &MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber);
|
||
|
|
connect(&_cameraSection, &CameraSection::itemCountChanged, this, &MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber);
|
||
|
|
connect(&_speedSection, &CameraSection::itemCountChanged, this, &MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber);
|
||
|
|
connect(this, &MissionSettingsItem::terrainAltitudeChanged, this, &MissionSettingsItem::_setHomeAltFromTerrain);
|
||
|
|
connect(&_cameraSection, &CameraSection::dirtyChanged, this, &MissionSettingsItem::_sectionDirtyChanged);
|
||
|
|
connect(&_speedSection, &SpeedSection::dirtyChanged, this, &MissionSettingsItem::_sectionDirtyChanged);
|
||
|
|
connect(&_cameraSection, &CameraSection::specifiedGimbalYawChanged, this, &MissionSettingsItem::specifiedGimbalYawChanged);
|
||
|
|
connect(&_cameraSection, &CameraSection::specifiedGimbalPitchChanged, this, &MissionSettingsItem::specifiedGimbalPitchChanged);
|
||
|
|
connect(&_speedSection, &SpeedSection::specifiedFlightSpeedChanged, this, &MissionSettingsItem::specifiedFlightSpeedChanged);
|
||
|
|
connect(_vehicle, &Vehicle::homePositionChanged, this, &MissionSettingsItem::_updateHomePosition);
|
||
|
|
connect(&_plannedHomePositionAltitudeFact, &Fact::rawValueChanged, this, &MissionSettingsItem::_updateAltitudeInCoordinate);
|
||
|
|
|
||
|
|
_updateHomePosition(_vehicle->homePosition());
|
||
|
|
}
|
||
|
|
|
||
|
|
int MissionSettingsItem::lastSequenceNumber(void) const
|
||
|
|
{
|
||
|
|
int lastSequenceNumber = _sequenceNumber;
|
||
|
|
|
||
|
|
lastSequenceNumber += _cameraSection.itemCount();
|
||
|
|
lastSequenceNumber += _speedSection.itemCount();
|
||
|
|
|
||
|
|
return lastSequenceNumber;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::setDirty(bool dirty)
|
||
|
|
{
|
||
|
|
if (_dirty != dirty) {
|
||
|
|
_dirty = dirty;
|
||
|
|
if (!dirty) {
|
||
|
|
_cameraSection.setDirty(false);
|
||
|
|
_speedSection.setDirty(false);
|
||
|
|
}
|
||
|
|
emit dirtyChanged(_dirty);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::save(QJsonArray& missionItems)
|
||
|
|
{
|
||
|
|
QList<MissionItem*> items;
|
||
|
|
|
||
|
|
appendMissionItems(items, this);
|
||
|
|
|
||
|
|
// First item should be planned home position, we are not responsible for save/load
|
||
|
|
// Remaining items we just output as is
|
||
|
|
for (int i=1; i<items.count(); i++) {
|
||
|
|
MissionItem* item = items[i];
|
||
|
|
QJsonObject saveObject;
|
||
|
|
item->save(saveObject);
|
||
|
|
missionItems.append(saveObject);
|
||
|
|
item->deleteLater();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::setSequenceNumber(int sequenceNumber)
|
||
|
|
{
|
||
|
|
if (_sequenceNumber != sequenceNumber) {
|
||
|
|
_sequenceNumber = sequenceNumber;
|
||
|
|
emit sequenceNumberChanged(sequenceNumber);
|
||
|
|
emit lastSequenceNumberChanged(lastSequenceNumber());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MissionSettingsItem::load(const QJsonObject& complexObject, int sequenceNumber, QString& errorString)
|
||
|
|
{
|
||
|
|
Q_UNUSED(complexObject);
|
||
|
|
Q_UNUSED(sequenceNumber);
|
||
|
|
Q_UNUSED(errorString);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
double MissionSettingsItem::greatestDistanceTo(const QGeoCoordinate &other) const
|
||
|
|
{
|
||
|
|
Q_UNUSED(other);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MissionSettingsItem::specifiesCoordinate(void) const
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::appendMissionItems(QList<MissionItem*>& items, QObject* missionItemParent)
|
||
|
|
{
|
||
|
|
int seqNum = _sequenceNumber;
|
||
|
|
|
||
|
|
// IMPORTANT NOTE: If anything changes here you must also change MissionSettingsItem::scanForMissionSettings
|
||
|
|
|
||
|
|
// Planned home position
|
||
|
|
MissionItem* item = new MissionItem(seqNum++,
|
||
|
|
MAV_CMD_NAV_WAYPOINT,
|
||
|
|
MAV_FRAME_GLOBAL,
|
||
|
|
0, // Hold time
|
||
|
|
0, // Acceptance radius
|
||
|
|
0, // Not sure?
|
||
|
|
0, // Yaw
|
||
|
|
coordinate().latitude(),
|
||
|
|
coordinate().longitude(),
|
||
|
|
_plannedHomePositionAltitudeFact.rawValue().toDouble(),
|
||
|
|
true, // autoContinue
|
||
|
|
false, // isCurrentItem
|
||
|
|
missionItemParent);
|
||
|
|
items.append(item);
|
||
|
|
|
||
|
|
_cameraSection.appendSectionItems(items, missionItemParent, seqNum);
|
||
|
|
_speedSection.appendSectionItems(items, missionItemParent, seqNum);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MissionSettingsItem::addMissionEndAction(QList<MissionItem*>& /*items*/, int /*seqNum*/, QObject* /*missionItemParent*/)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool MissionSettingsItem::scanForMissionSettings(QmlObjectListModel* visualItems, int scanIndex)
|
||
|
|
{
|
||
|
|
bool foundSpeedSection = false;
|
||
|
|
bool foundCameraSection = false;
|
||
|
|
|
||
|
|
qCDebug(MissionSettingsComplexItemLog) << "MissionSettingsItem::scanForMissionSettings count:scanIndex" << visualItems->count() << scanIndex;
|
||
|
|
|
||
|
|
// Scan through the initial mission items for possible mission settings
|
||
|
|
foundCameraSection = _cameraSection.scanForSection(visualItems, scanIndex);
|
||
|
|
foundSpeedSection = _speedSection.scanForSection(visualItems, scanIndex);
|
||
|
|
|
||
|
|
return foundSpeedSection || foundCameraSection;
|
||
|
|
}
|
||
|
|
|
||
|
|
double MissionSettingsItem::complexDistance(void) const
|
||
|
|
{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_setDirty(void)
|
||
|
|
{
|
||
|
|
setDirty(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_setCoordinateWorker(const QGeoCoordinate& coordinate)
|
||
|
|
{
|
||
|
|
if (_plannedHomePositionCoordinate != coordinate) {
|
||
|
|
_plannedHomePositionCoordinate = coordinate;
|
||
|
|
emit coordinateChanged(coordinate);
|
||
|
|
emit exitCoordinateChanged(coordinate);
|
||
|
|
_plannedHomePositionAltitudeFact.setRawValue(coordinate.altitude());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::setHomePositionFromVehicle(Vehicle* vehicle)
|
||
|
|
{
|
||
|
|
// If the user hasn't moved the planned home position manually we use the value from the vehicle
|
||
|
|
if (!_plannedHomePositionMovedByUser) {
|
||
|
|
QGeoCoordinate coordinate = vehicle->homePosition();
|
||
|
|
// ArduPilot tends to send crap home positions at initial vehicle boot, discard them
|
||
|
|
if (coordinate.isValid() && (coordinate.latitude() != 0 || coordinate.longitude() != 0)) {
|
||
|
|
_plannedHomePositionFromVehicle = true;
|
||
|
|
_setCoordinateWorker(coordinate);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::setInitialHomePosition(const QGeoCoordinate& coordinate)
|
||
|
|
{
|
||
|
|
_plannedHomePositionMovedByUser = false;
|
||
|
|
_plannedHomePositionFromVehicle = false;
|
||
|
|
_setCoordinateWorker(coordinate);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::setInitialHomePositionFromUser(const QGeoCoordinate& coordinate)
|
||
|
|
{
|
||
|
|
_plannedHomePositionMovedByUser = true;
|
||
|
|
_plannedHomePositionFromVehicle = false;
|
||
|
|
_setCoordinateWorker(coordinate);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void MissionSettingsItem::setCoordinate(const QGeoCoordinate& coordinate)
|
||
|
|
{
|
||
|
|
if (coordinate != this->coordinate()) {
|
||
|
|
// The user is moving the planned home position manually. Stop tracking vehicle home position.
|
||
|
|
_plannedHomePositionMovedByUser = true;
|
||
|
|
_plannedHomePositionFromVehicle = false;
|
||
|
|
_setCoordinateWorker(coordinate);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_setDirtyAndUpdateLastSequenceNumber(void)
|
||
|
|
{
|
||
|
|
emit lastSequenceNumberChanged(lastSequenceNumber());
|
||
|
|
setDirty(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_sectionDirtyChanged(bool dirty)
|
||
|
|
{
|
||
|
|
if (dirty) {
|
||
|
|
setDirty(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
double MissionSettingsItem::specifiedGimbalYaw(void)
|
||
|
|
{
|
||
|
|
return _cameraSection.specifyGimbal() ? _cameraSection.gimbalYaw()->rawValue().toDouble() : std::numeric_limits<double>::quiet_NaN();
|
||
|
|
}
|
||
|
|
|
||
|
|
double MissionSettingsItem::specifiedGimbalPitch(void)
|
||
|
|
{
|
||
|
|
return _cameraSection.specifyGimbal() ? _cameraSection.gimbalPitch()->rawValue().toDouble() : std::numeric_limits<double>::quiet_NaN();
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_updateAltitudeInCoordinate(QVariant value)
|
||
|
|
{
|
||
|
|
double newAltitude = value.toDouble();
|
||
|
|
|
||
|
|
if (!qFuzzyCompare(_plannedHomePositionCoordinate.altitude(), newAltitude)) {
|
||
|
|
_plannedHomePositionCoordinate.setAltitude(newAltitude);
|
||
|
|
emit coordinateChanged(_plannedHomePositionCoordinate);
|
||
|
|
emit exitCoordinateChanged(_plannedHomePositionCoordinate);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
double MissionSettingsItem::specifiedFlightSpeed(void)
|
||
|
|
{
|
||
|
|
if (_speedSection.specifyFlightSpeed()) {
|
||
|
|
return _speedSection.flightSpeed()->rawValue().toDouble();
|
||
|
|
} else {
|
||
|
|
return std::numeric_limits<double>::quiet_NaN();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_setHomeAltFromTerrain(double terrainAltitude)
|
||
|
|
{
|
||
|
|
if (!_plannedHomePositionFromVehicle && !qIsNaN(terrainAltitude)) {
|
||
|
|
_plannedHomePositionAltitudeFact.setRawValue(terrainAltitude);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
QString MissionSettingsItem::abbreviation(void) const
|
||
|
|
{
|
||
|
|
return _flyView ? tr("L") : tr("Launch");
|
||
|
|
}
|
||
|
|
|
||
|
|
void MissionSettingsItem::_updateHomePosition(const QGeoCoordinate& homePosition)
|
||
|
|
{
|
||
|
|
if (_flyView) {
|
||
|
|
setCoordinate(homePosition);
|
||
|
|
}
|
||
|
|
}
|