diff --git a/ChangeLog.md b/ChangeLog.md
index 9d2611a05af534068eb65c43684936fa52d54d62..91b287b92efed2288fe62a7b693f61552078f4ef 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -6,6 +6,7 @@ Note: This file only contains high level features or important fixes.
### 3.6.0 - Daily Build
+* ArduCopter/Rover: Follow Me setup page
* More performant flight path display algorithm. Mobile builds no longer show limited path length.
* ArduCopter/Rover: Add support for Follow Me
* ArduPilot: Add Motor Test vehicle setup page
diff --git a/qgcimages.qrc b/qgcimages.qrc
index e025d9c09b730e59d24846e64480bce57053d876..a224f5e2782b30a4bbef4ddb6280efb03edafb03 100644
--- a/qgcimages.qrc
+++ b/qgcimages.qrc
@@ -61,7 +61,7 @@
resources/camera.svg
src/Camera/images/camera_photo.svg
src/Camera/images/camera_video.svg
- src/AutoPilotPlugins/PX4/Images/CameraComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/CameraComponentIcon.png
src/ui/toolbar/Images/CameraIcon.svg
src/AutoPilotPlugins/PX4/Images/CameraTrigger.svg
resources/check.svg
@@ -78,7 +78,8 @@
src/ui/toolbar/Images/Disarmed.svg
src/ui/toolbar/Images/Disconnect.svg
src/VehicleSetup/FirmwareUpgradeIcon.png
- src/AutoPilotPlugins/PX4/Images/FlightModesComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/FlightModesComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/FlightModesComponentIcon.png
src/AutoPilotPlugins/APM/Images/bluerov-frame.png
src/AutoPilotPlugins/APM/Images/simple3-frame.png
src/AutoPilotPlugins/APM/Images/simple4-frame.png
@@ -132,10 +133,10 @@
src/AutoPilotPlugins/PX4/Images/PowerComponentBattery_04cell.svg
src/AutoPilotPlugins/PX4/Images/PowerComponentBattery_05cell.svg
src/AutoPilotPlugins/PX4/Images/PowerComponentBattery_06cell.svg
- src/AutoPilotPlugins/PX4/Images/PowerComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/PowerComponentIcon.png
src/FirmwarePlugin/PX4/PX4BrandImage.png
src/ui/toolbar/Images/Quad.svg
- src/AutoPilotPlugins/PX4/Images/RadioComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/RadioComponentIcon.png
src/ui/toolbar/Images/RC.svg
src/AutoPilotPlugins/PX4/Images/RCLoss.svg
src/AutoPilotPlugins/PX4/Images/RCLossLight.svg
@@ -144,12 +145,12 @@
src/FlightMap/Images/rollDialWhite.svg
src/FlightMap/Images/rollPointerWhite.svg
src/ui/toolbar/Images/RTK.svg
- src/AutoPilotPlugins/PX4/Images/SafetyComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/SafetyComponentIcon.png
src/FlightMap/Images/scale.png
src/FlightMap/Images/scale_end.png
src/FlightMap/Images/scale_endLight.png
src/FlightMap/Images/scaleLight.png
- src/AutoPilotPlugins/PX4/Images/SensorsComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/SensorsComponentIcon.png
src/ui/toolbar/Images/Signal0.svg
src/ui/toolbar/Images/Signal100.svg
src/ui/toolbar/Images/Signal20.svg
@@ -161,7 +162,7 @@
resources/CogWheels.png
src/FlightMap/Images/sub.png
src/ui/toolbar/Images/TelemRSSI.svg
- src/AutoPilotPlugins/PX4/Images/TuningComponentIcon.png
+ src/AutoPilotPlugins/Common/Images/TuningComponentIcon.png
src/FlightMap/Images/vehicleArrowOpaque.svg
src/FlightMap/Images/vehicleArrowOutline.svg
src/AutoPilotPlugins/PX4/Images/VehicleDown.png
diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro
index 6b0242d2eab3f2a86d2c654d4d9750aa0b4a7ee5..7957132db5213287f5832bfae807de71490cc47b 100644
--- a/qgroundcontrol.pro
+++ b/qgroundcontrol.pro
@@ -1068,6 +1068,8 @@ APMFirmwarePlugin {
src/AutoPilotPlugins/APM/APMCompassCal.h \
src/AutoPilotPlugins/APM/APMFlightModesComponent.h \
src/AutoPilotPlugins/APM/APMFlightModesComponentController.h \
+ src/AutoPilotPlugins/APM/APMFollowComponent.h \
+ src/AutoPilotPlugins/APM/APMFollowComponentController.h \
src/AutoPilotPlugins/APM/APMHeliComponent.h \
src/AutoPilotPlugins/APM/APMLightsComponent.h \
src/AutoPilotPlugins/APM/APMSubFrameComponent.h \
@@ -1093,6 +1095,8 @@ APMFirmwarePlugin {
src/AutoPilotPlugins/APM/APMCompassCal.cc \
src/AutoPilotPlugins/APM/APMFlightModesComponent.cc \
src/AutoPilotPlugins/APM/APMFlightModesComponentController.cc \
+ src/AutoPilotPlugins/APM/APMFollowComponent.cc \
+ src/AutoPilotPlugins/APM/APMFollowComponentController.cc \
src/AutoPilotPlugins/APM/APMHeliComponent.cc \
src/AutoPilotPlugins/APM/APMLightsComponent.cc \
src/AutoPilotPlugins/APM/APMSubFrameComponent.cc \
diff --git a/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.cc b/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.cc
index f311bbd6851629cc150590a5be2fe6b53282fed8..2c5c09d6f234ded9a9830b3a5858fc30cdd21e35 100644
--- a/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.cc
+++ b/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.cc
@@ -25,6 +25,7 @@
#include "APMCameraComponent.h"
#include "APMLightsComponent.h"
#include "APMSubFrameComponent.h"
+#include "APMFollowComponent.h"
#include "ESP8266Component.h"
#include "APMHeliComponent.h"
#include "QGCApplication.h"
@@ -51,6 +52,7 @@ APMAutoPilotPlugin::APMAutoPilotPlugin(Vehicle* vehicle, QObject* parent)
, _tuningComponent (nullptr)
, _esp8266Component (nullptr)
, _heliComponent (nullptr)
+ , _followComponent (nullptr)
{
#if !defined(NO_SERIAL_LINK) && !defined(__android__)
connect(vehicle->parameterManager(), &ParameterManager::parametersReadyChanged, this, &APMAutoPilotPlugin::_checkForBadCubeBlack);
@@ -101,6 +103,13 @@ const QVariantList& APMAutoPilotPlugin::vehicleComponents(void)
_safetyComponent->setupTriggerSignals();
_components.append(QVariant::fromValue((VehicleComponent*)_safetyComponent));
+ if ((qobject_cast(_vehicle->firmwarePlugin()) || qobject_cast(_vehicle->firmwarePlugin())) &&
+ _vehicle->parameterManager()->parameterExists(-1, QStringLiteral("FOLL_ENABLE"))) {
+ _followComponent = new APMFollowComponent(_vehicle, this);
+ _followComponent->setupTriggerSignals();
+ _components.append(QVariant::fromValue((VehicleComponent*)_followComponent));
+ }
+
if (_vehicle->vehicleType() == MAV_TYPE_HELICOPTER) {
_heliComponent = new APMHeliComponent(_vehicle, this);
_heliComponent->setupTriggerSignals();
diff --git a/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.h b/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.h
index cc1f21dcddd031b7bdd90ddff02eaf4e680f02ff..1d9d30de2201371aa003d27890a0540dfb6bfcb5 100644
--- a/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.h
+++ b/src/AutoPilotPlugins/APM/APMAutoPilotPlugin.h
@@ -27,6 +27,7 @@ class APMLightsComponent;
class APMSubFrameComponent;
class ESP8266Component;
class APMHeliComponent;
+class APMFollowComponent;
/// This is the APM specific implementation of the AutoPilot class.
class APMAutoPilotPlugin : public AutoPilotPlugin
@@ -56,6 +57,7 @@ protected:
APMTuningComponent* _tuningComponent;
ESP8266Component* _esp8266Component;
APMHeliComponent* _heliComponent;
+ APMFollowComponent* _followComponent;
#if !defined(NO_SERIAL_LINK) && !defined(__android__)
private slots:
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponent.FactMetaData.json b/src/AutoPilotPlugins/APM/APMFollowComponent.FactMetaData.json
new file mode 100644
index 0000000000000000000000000000000000000000..297b030d3374a828aef86a491f07ae3a8ede9803
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponent.FactMetaData.json
@@ -0,0 +1,30 @@
+[
+{
+ "name": "angle",
+ "shortDescription": "Angle from ground station to vehicle",
+ "type": "double",
+ "min": 0,
+ "max": 360,
+ "decimalPlaces": 1,
+ "units": "deg",
+ "defaultValue": 45
+},
+{
+ "name": "distance",
+ "shortDescription": "Horizontal distance from ground station to vehicle",
+ "type": "double",
+ "min": 0,
+ "decimalPlaces": 1,
+ "units": "m",
+ "defaultValue": 5
+},
+{
+ "name": "height",
+ "shortDescription": "Vertical distance from ground station to vehicle",
+ "type": "double",
+ "min": 0,
+ "decimalPlaces": 1,
+ "units": "m",
+ "defaultValue": 5
+}
+]
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponent.cc b/src/AutoPilotPlugins/APM/APMFollowComponent.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ff8caf4431bd4f36e820e17129986a2890883aef
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponent.cc
@@ -0,0 +1,44 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#include "APMFollowComponent.h"
+#include "APMAutoPilotPlugin.h"
+#include "APMAirframeComponent.h"
+#include "ParameterManager.h"
+
+APMFollowComponent::APMFollowComponent(Vehicle* vehicle, AutoPilotPlugin* autopilot, QObject* parent)
+ : VehicleComponent(vehicle, autopilot, parent),
+ _name(tr("Follow Me"))
+{
+}
+
+QString APMFollowComponent::name(void) const
+{
+ return _name;
+}
+
+QString APMFollowComponent::description(void) const
+{
+ return tr("Follow Me Setup is used to configure support for the vehicle following the ground station location.");
+}
+
+QString APMFollowComponent::iconResource(void) const
+{
+ return QStringLiteral("/qmlimages/FollowComponentIcon.png");
+}
+
+QUrl APMFollowComponent::setupSource(void) const
+{
+ return QUrl::fromUserInput(QStringLiteral("qrc:/qml/APMFollowComponent.qml"));
+}
+
+QUrl APMFollowComponent::summaryQmlSource(void) const
+{
+ return QUrl::fromUserInput(QStringLiteral("qrc:/qml/APMFollowComponentSummary.qml"));
+}
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponent.h b/src/AutoPilotPlugins/APM/APMFollowComponent.h
new file mode 100644
index 0000000000000000000000000000000000000000..11149cd7583b385565cc5d3b78614f25a489fd91
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponent.h
@@ -0,0 +1,38 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#pragma once
+
+#include "VehicleComponent.h"
+
+class APMFollowComponent : public VehicleComponent
+{
+ Q_OBJECT
+
+public:
+ APMFollowComponent(Vehicle* vehicle, AutoPilotPlugin* autopilot, QObject* parent = nullptr);
+
+ // Overrides from VehicleComponent
+ QStringList setupCompleteChangedTriggerList(void) const override { return QStringList(); }
+
+ // Virtuals from VehicleComponent
+ QString name (void) const override;
+ QString description (void) const override;
+ QString iconResource (void) const override;
+ bool requiresSetup (void) const override { return false; }
+ bool setupComplete (void) const override { return true; }
+ QUrl setupSource (void) const override;
+ QUrl summaryQmlSource (void) const override;
+ bool allowSetupWhileArmed (void) const override { return true; }
+ bool allowSetupWhileFlying (void) const override { return true; }
+
+private:
+ const QString _name;
+ QVariantList _summaryItems;
+};
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponent.qml b/src/AutoPilotPlugins/APM/APMFollowComponent.qml
new file mode 100644
index 0000000000000000000000000000000000000000..d602a2b2d671bd495cbba28587d3342d1ce2cb4e
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponent.qml
@@ -0,0 +1,453 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+
+import QtQuick 2.3
+import QtQuick.Controls 1.2
+import QtQuick.Dialogs 1.2
+import QtQuick.Layouts 1.2
+
+import QGroundControl 1.0
+import QGroundControl.FactSystem 1.0
+import QGroundControl.FactControls 1.0
+import QGroundControl.Palette 1.0
+import QGroundControl.Controls 1.0
+import QGroundControl.ScreenTools 1.0
+import QGroundControl.Controllers 1.0
+
+SetupPage {
+ id: followPage
+ pageComponent: followPageComponent
+
+ FactPanelController {
+ id: controller
+ }
+
+ Component {
+ id: followPageComponent
+
+ ColumnLayout {
+ id: flowLayout
+ spacing: _margins
+
+ property Fact _followEnabled: controller.getParameterFact(-1, "FOLL_ENABLE")
+ property bool _followParamsAvailable: controller.parameterExists(-1, "FOLL_SYSID")
+ property Fact _followDistanceMax: controller.getParameterFact(-1, "FOLL_DIST_MAX", false /* reportMissing */)
+ property Fact _followSysId: controller.getParameterFact(-1, "FOLL_SYSID", false /* reportMissing */)
+ property Fact _followOffsetX: controller.getParameterFact(-1, "FOLL_OFS_X", false /* reportMissing */)
+ property Fact _followOffsetY: controller.getParameterFact(-1, "FOLL_OFS_Y", false /* reportMissing */)
+ property Fact _followOffsetZ: controller.getParameterFact(-1, "FOLL_OFS_Z", false /* reportMissing */)
+ property Fact _followOffsetType: controller.getParameterFact(-1, "FOLL_OFS_TYPE", false /* reportMissing */)
+ property Fact _followAltitudeType: controller.getParameterFact(-1, "FOLL_ALT_TYPE", false /* reportMissing */)
+ property Fact _followYawBehavior: controller.getParameterFact(-1, "FOLL_YAW_BEHAVE", false /* reportMissing */)
+ property int _followComboMaintainIndex: 0
+ property int _followComboSpecifyIndex: 1
+ property bool _followMaintain: followPositionCombo.currentIndex === _followComboMaintainIndex
+ property bool _isFollowMeSetup: _followEnabled.rawValue == 1 && _followParamsAvailable && _followOffsetType.rawValue == 1 && _followAltitudeType.rawValue == 1 && _followSysId.rawValue == 0
+
+ readonly property int _followYawBehaviorNone: 0
+ readonly property int _followYawBehaviorFace: 1
+ readonly property int _followYawBehaviorSame: 2
+ readonly property int _followYawBehaviorFlight: 3
+
+ Component.onCompleted: _setUIFromParams()
+
+ function _setUIFromParams() {
+ if (!_followParamsAvailable) {
+ return
+ }
+
+ if (_followOffsetX.rawValue == 0 && _followOffsetY.rawValue == 0 && _followOffsetZ.rawValue == 0) {
+ followPositionCombo.currentIndex =_followComboMaintainIndex
+ controller.distance.rawValue = 0
+ controller.angle.rawValue = 0
+ controller.height.rawValue = 0
+ } else {
+ followPositionCombo.currentIndex =_followComboSpecifyIndex
+ var angleRadians = Math.atan2(_followOffsetX.rawValue, _followOffsetY.rawValue)
+ if (angleRadians == 0) {
+ controller.distance.rawValue = _followOffsetY.rawValue
+ } else {
+ controller.distance.rawValue = _followOffsetX.rawValue / Math.sin(angleRadians)
+ }
+ controller.angle.rawValue = _radiansToHeading(angleRadians)
+ }
+ controller.height.rawValue = -_followOffsetZ.rawValue
+ pointVehicleCombo.currentIndex = _followYawBehavior.rawValue
+ }
+
+ function _setFollowMeParamDefaults() {
+ _followOffsetType.rawValue = 1 // Relative to vehicle
+ _followAltitudeType.rawValue = 1 // Altitude is relative
+ _followSysId.rawValue = 0 // Follow first message sent
+
+ controller.distance.value = controller.distance.defaultValue
+ controller.angle.value = controller.angle.defaultValue
+ controller.height.value = controller.height.defaultValue
+
+ _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
+ _followOffsetZ.rawValue = -controller.height.rawValue
+ _setUIFromParams()
+ }
+
+ function _setXYOffsetByAngleAndDistance(headingAngleDegrees, distance) {
+ if (distance == 0) {
+ _followOffsetX.rawValue = _followOffsetY.rawValue = 0
+ } else {
+ var angleRadians = _headingToRadians(headingAngleDegrees)
+ if (angleRadians == 0) {
+ _followOffsetX.rawValue = 0
+ _followOffsetY.rawValue = distance
+ } else {
+ _followOffsetX.rawValue = Math.sin(angleRadians) * distance
+ _followOffsetY.rawValue = Math.cos(angleRadians) * distance
+ if (_followOffsetX.rawValue < 0.0001) {
+ _followOffsetX.rawValue = 0
+ }
+ if (_followOffsetY.rawValue < 0.0001) {
+ _followOffsetY.rawValue = 0
+ }
+ }
+ }
+ }
+
+ function _radiansToHeading(radians) {
+ var geometricAngle = QGroundControl.radiansToDegrees(radians)
+ var headingAngle = 90 - geometricAngle
+ if (headingAngle < 0) {
+ headingAngle += 360
+ } else if (headingAngle > 360) {
+ headingAngle -= 360
+ }
+ return headingAngle
+ }
+
+ function _headingToRadians(heading) {
+ var geometricAngle = -(heading - 90)
+ return QGroundControl.degreesToRadians(geometricAngle)
+ }
+
+ APMFollowComponentController {
+ id: controller
+
+ onMissingParametersAvailable: {
+ _followDistanceMax = controller.getParameterFact(-1, "FOLL_DIST_MAX")
+ _followSysId = controller.getParameterFact(-1, "FOLL_SYSID")
+ _followOffsetX = controller.getParameterFact(-1, "FOLL_OFS_X")
+ _followOffsetY = controller.getParameterFact(-1, "FOLL_OFS_Y")
+ _followOffsetZ = controller.getParameterFact(-1, "FOLL_OFS_Z")
+ _followOffsetType = controller.getParameterFact(-1, "FOLL_OFS_TYPE")
+ _followAltitudeType = controller.getParameterFact(-1, "FOLL_ALT_TYPE")
+ _followYawBehavior = controller.getParameterFact(-1, "FOLL_YAW_BEHAVE")
+ _followParamsAvailable = true
+ vehicleParamRefreshLabel.visible = false
+ _followYawBehavior.rawValue = 1 // Point at GCS
+ _setFollowMeParamDefaults()
+ }
+ }
+
+ QGCPalette { id: ggcPal; colorGroupEnabled: true }
+
+ QGCCheckBox {
+ text: qsTr("Enable Follow Me")
+ checked: _isFollowMeSetup
+ onClicked: {
+ if (checked) {
+ _followEnabled.rawValue = 1
+ controller.getMissingParameters([ "FOLL_DIST_MAX", "FOLL_SYSID", "FOLL_OFS_X", "FOLL_OFS_Y", "FOLL_OFS_Z", "FOLL_OFS_TYPE", "FOLL_ALT_TYPE", "FOLL_YAW_BEHAVE" ])
+ vehicleParamRefreshLabel.visible = true
+ } else {
+ _followEnabled.rawValue = 0
+ }
+ }
+ }
+
+ QGCLabel {
+ id: vehicleParamRefreshLabel
+ text: qsTr("Waiting for Vehicle to update")
+ visible: false
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: ScreenTools.defaultFontPixelWidth
+ visible: _isFollowMeSetup
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: ScreenTools.defaultFontPixelWidth
+ visible: _followParamsAvailable && _isFollowMeSetup
+
+ GridLayout {
+ Layout.fillWidth: true
+ columns: 2
+
+ QGCLabel { text: qsTr("Vehicle Position") }
+ QGCComboBox {
+ id: followPositionCombo
+ Layout.fillWidth: true
+ model: [ qsTr("Maintain Current Offsets"), qsTr("Specify Offsets")]
+
+ onActivated: {
+ if (index == 0) {
+ _followOffsetX.rawValue = _followOffsetY.rawValue = _followOffsetZ.rawValue = 0
+ _setUIFromParams()
+ } else {
+ _setFollowMeParamDefaults()
+ }
+ }
+ }
+
+ QGCLabel { text: qsTr("Point Vehicle") }
+ QGCComboBox {
+ id: pointVehicleCombo
+ Layout.fillWidth: true
+ // NOTE: The indices for the model below must match the values for FOLL_YAW_BEHAVE
+ model: [ qsTr("Maintain current vehicle orientation"), qsTr("Point at ground station location"), qsTr("Same orientation as ground station"), qsTr("Same direction as ground station movement") ]
+ onActivated: _followYawBehavior.rawValue = index
+ }
+ }
+
+ GridLayout {
+ Layout.fillWidth: true
+ columns: 4
+ visible: !_followMaintain
+
+ QGCLabel {
+ Layout.columnSpan: 2
+ Layout.alignment: Qt.AlignHCenter
+ text: qsTr("Vehicle Offsets")
+ }
+
+ QGCLabel { text: qsTr("Angle") }
+ FactTextField {
+ fact: controller.angle
+ onUpdated: { console.log("updated"); _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue) }
+ }
+
+ QGCLabel { text: qsTr("Distance") }
+ FactTextField {
+ fact: controller.distance
+ onUpdated: _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
+ }
+
+ QGCLabel { text: qsTr("Height") }
+ FactTextField {
+ fact: controller.height
+ visible: !_followMaintain
+ onUpdated: _followOffsetZ.rawValue = -controller.height.rawValue
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ spacing: ScreenTools.defaultFontPixelWidth * 2
+ visible: _isFollowMeSetup && !_followMaintain
+
+ Item {
+ height: ScreenTools.defaultFontPixelWidth * 50
+ width: height
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 3
+ color: qgcPal.windowShade
+ }
+
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ height: 3
+ color: qgcPal.windowShade
+ }
+
+ QGCLabel {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: parent.height / 4
+ anchors.top: parent.top
+ text: qsTr("Click in the graphic to change angle")
+ opacity: 0.5
+ }
+
+ Image {
+ id: gcsIcon
+ anchors.centerIn: parent
+ source: "/res/QGCLogoArrow"
+ mipmap: true
+ antialiasing: true
+ fillMode: Image.PreserveAspectFit
+ height: ScreenTools.defaultFontPixelHeight * 2.5
+ sourceSize.height: height
+ }
+
+ Item {
+ id: vehicleHolder
+ anchors.fill: parent
+
+ transform: Rotation {
+ origin.x: vehicleHolder.width / 2
+ origin.y: vehicleHolder.height / 2
+ angle: controller.angle.rawValue
+ }
+
+ Image {
+ id: vehicleIcon
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ source: controller.vehicle.vehicleImageOpaque
+ mipmap: true
+ height: ScreenTools.defaultFontPixelHeight * 2.5
+ sourceSize.height: height
+ fillMode: Image.PreserveAspectFit
+
+ transform: Rotation {
+ origin.x: vehicleIcon.width / 2
+ origin.y: vehicleIcon.height / 2
+ angle: _followYawBehavior.rawValue == _followYawBehaviorNone ?
+ 0 :
+ (_followYawBehavior.rawValue == _followYawBehaviorFace ?
+ 180 :
+ -controller.angle.rawValue)
+ }
+ }
+
+ Rectangle {
+ id: distanceLine
+ x: parent.width / 2
+ y: vehicleIcon.height
+ height: (parent.height / 2) - (vehicleIcon.height + (gcsIcon.height / 2))
+ width: 2
+ color: qgcPal.text
+ opacity: 0.4
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: ScreenTools.defaultFontPixelWidth * 2
+ height: 2
+ color: qgcPal.text
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: ScreenTools.defaultFontPixelWidth * 2
+ height: 2
+ color: qgcPal.text
+ }
+ }
+
+ QGCLabel {
+ id: distanceLabel
+ anchors.centerIn: distanceLine
+ text: controller.distance.valueString + " " + QGroundControl.appSettingsDistanceUnitsString
+
+ transform: Rotation {
+ origin.x: distanceLabel.width / 2
+ origin.y: distanceLabel.height / 2
+ angle: -controller.angle.rawValue
+ }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: {
+ // Translate x,y to centered
+ var x = mouse.x - (width / 2)
+ var y = (height - mouse.y) - (height / 2)
+ controller.angle.rawValue = _radiansToHeading(Math.atan2(y, x))
+ }
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillHeight: true
+ spacing: 0
+
+ Image {
+ id: vehicleIconHeight
+ source: controller.vehicle.vehicleImageOpaque
+ mipmap: true
+ height: ScreenTools.defaultFontPixelHeight * 2.5
+ sourceSize.height: height
+ fillMode: Image.PreserveAspectFit
+
+ transform: Rotation {
+ origin.x: vehicleIconHeight.width / 2
+ origin.y: vehicleIconHeight.height / 2
+ angle: 65
+ axis { x: 1; y: 0; z: 0 }
+ }
+ }
+
+ Item {
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillHeight: true
+ width: Math.max(ScreenTools.defaultFontPixelWidth * 2, heightLabel.width)
+
+ Rectangle {
+ id: heightLine
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: 2
+ color: qgcPal.text
+ opacity: 0.4
+
+ Rectangle {
+ anchors.top: parent.top
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: ScreenTools.defaultFontPixelWidth * 2
+ height: 2
+ color: qgcPal.text
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: ScreenTools.defaultFontPixelWidth * 2
+ height: 2
+ color: qgcPal.text
+ }
+ }
+
+ QGCLabel {
+ id: heightLabel
+ anchors.centerIn: parent
+ text: controller.height.valueString + " " + QGroundControl.appSettingsDistanceUnitsString
+ }
+ }
+
+ Image {
+ id: gcsIconHeight
+ source: "/res/QGCLogoArrow"
+ mipmap: true
+ antialiasing: true
+ fillMode: Image.PreserveAspectFit
+ height: ScreenTools.defaultFontPixelHeight * 2.5
+ sourceSize.height: height
+
+ transform: Rotation {
+ origin.x: gcsIconHeight.width / 2
+ origin.y: gcsIconHeight.height / 2
+ angle: 65
+ axis { x: 1; y: 0; z: 0 }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponentController.cc b/src/AutoPilotPlugins/APM/APMFollowComponentController.cc
new file mode 100644
index 0000000000000000000000000000000000000000..84815f53a63d39d110206d8c7eaf79ba8e1bd6b4
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponentController.cc
@@ -0,0 +1,24 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#include "APMFollowComponentController.h"
+
+const char* APMFollowComponentController::settingsGroup = "APMFollow";
+const char* APMFollowComponentController::angleName = "angle";
+const char* APMFollowComponentController::distanceName = "distance";
+const char* APMFollowComponentController::heightName = "height";
+
+APMFollowComponentController::APMFollowComponentController(void)
+ : _metaDataMap (FactMetaData::createMapFromJsonFile(QStringLiteral(":/json/APMFollowComponent.FactMetaData.json"), this))
+ , _angleFact (settingsGroup, _metaDataMap[angleName])
+ , _distanceFact (settingsGroup, _metaDataMap[distanceName])
+ , _heightFact (settingsGroup, _metaDataMap[heightName])
+{
+
+}
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponentController.h b/src/AutoPilotPlugins/APM/APMFollowComponentController.h
new file mode 100644
index 0000000000000000000000000000000000000000..405a209052f7347bf9061ea283d284b8db797ece
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponentController.h
@@ -0,0 +1,40 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+#pragma once
+
+#include "FactPanelController.h"
+
+class APMFollowComponentController : public FactPanelController
+{
+ Q_OBJECT
+
+public:
+ APMFollowComponentController(void);
+
+ Q_PROPERTY(Fact* angle READ angleFact CONSTANT)
+ Q_PROPERTY(Fact* distance READ distanceFact CONSTANT)
+ Q_PROPERTY(Fact* height READ heightFact CONSTANT)
+
+ Fact* angleFact (void) { return &_angleFact; }
+ Fact* distanceFact (void) { return &_distanceFact; }
+ Fact* heightFact (void) { return &_heightFact; }
+
+ static const char* settingsGroup;
+ static const char* angleName;
+ static const char* distanceName;
+ static const char* heightName;
+
+private:
+ QMap _metaDataMap;
+
+ SettingsFact _angleFact;
+ SettingsFact _distanceFact;
+ SettingsFact _heightFact;
+};
diff --git a/src/AutoPilotPlugins/APM/APMFollowComponentSummary.qml b/src/AutoPilotPlugins/APM/APMFollowComponentSummary.qml
new file mode 100644
index 0000000000000000000000000000000000000000..728bfd649bf564aa76494db2b423257335f03daf
--- /dev/null
+++ b/src/AutoPilotPlugins/APM/APMFollowComponentSummary.qml
@@ -0,0 +1,58 @@
+/****************************************************************************
+ *
+ * (c) 2009-2016 QGROUNDCONTROL PROJECT
+ *
+ * QGroundControl is licensed according to the terms in the file
+ * COPYING.md in the root of the source code directory.
+ *
+ ****************************************************************************/
+
+import QtQuick 2.3
+import QtQuick.Controls 1.2
+
+import QGroundControl.FactSystem 1.0
+import QGroundControl.FactControls 1.0
+import QGroundControl.Controls 1.0
+import QGroundControl.Palette 1.0
+
+Item {
+ anchors.fill: parent
+
+ FactPanelController { id: controller; }
+
+ property Fact _batt1Monitor: controller.getParameterFact(-1, "BATT_MONITOR")
+ property Fact _batt2Monitor: controller.getParameterFact(-1, "BATT2_MONITOR", false /* reportMissing */)
+ property bool _batt2MonitorAvailable: controller.parameterExists(-1, "BATT2_MONITOR")
+ property bool _batt1MonitorEnabled: _batt1Monitor.rawValue !== 0
+ property bool _batt2MonitorEnabled: _batt2MonitorAvailable && _batt2Monitor.rawValue !== 0
+ property Fact _battCapacity: controller.getParameterFact(-1, "BATT_CAPACITY", false /* reportMissing */)
+ property Fact _batt2Capacity: controller.getParameterFact(-1, "BATT2_CAPACITY", false /* reportMissing */)
+ property bool _battCapacityAvailable: controller.parameterExists(-1, "BATT_CAPACITY")
+
+ Column {
+ anchors.fill: parent
+
+ VehicleSummaryRow {
+ labelText: qsTr("Batt1 monitor")
+ valueText: _batt1Monitor.enumStringValue
+ }
+
+ VehicleSummaryRow {
+ labelText: qsTr("Batt1 capacity")
+ valueText: _batt1MonitorEnabled ? _battCapacity.valueString + " " + _battCapacity.units : ""
+ visible: _batt1MonitorEnabled
+ }
+
+ VehicleSummaryRow {
+ labelText: qsTr("Batt2 monitor")
+ valueText: _batt2MonitorAvailable ? _batt2Monitor.enumStringValue : ""
+ visible: _batt2MonitorAvailable
+ }
+
+ VehicleSummaryRow {
+ labelText: qsTr("Batt2 capacity")
+ valueText: _batt2MonitorEnabled ? _batt2Capacity.valueString + " " + _batt2Capacity.units : ""
+ visible: _batt2MonitorEnabled
+ }
+ }
+}
diff --git a/src/FactSystem/FactControls/FactPanelController.cc b/src/FactSystem/FactControls/FactPanelController.cc
index 7e90fa3b0296dd106676093ddcbe21104e45fed2..73dee24bc5792421b29672242eccde5ed7190495 100644
--- a/src/FactSystem/FactControls/FactPanelController.cc
+++ b/src/FactSystem/FactControls/FactPanelController.cc
@@ -29,6 +29,10 @@ FactPanelController::FactPanelController()
} else {
_vehicle = qgcApp()->toolbox()->multiVehicleManager()->offlineEditingVehicle();
}
+
+ _missingParametersTimer.setInterval(500);
+ _missingParametersTimer.setSingleShot(true);
+ connect(&_missingParametersTimer, &QTimer::timeout, this, &FactPanelController::_checkForMissingParameters);
}
void FactPanelController::_notifyPanelMissingParameter(const QString& missingParam)
@@ -114,3 +118,29 @@ void FactPanelController::_showInternalError(const QString& errorMsg)
qCWarning(FactPanelControllerLog) << "Internal Error" << errorMsg;
qgcApp()->showMessage(errorMsg);
}
+
+void FactPanelController::getMissingParameters(QStringList rgNames)
+{
+ for (const QString& name: rgNames) {
+ _missingParameterWaitList.append(name);
+ _vehicle->parameterManager()->refreshParameter(MAV_COMP_ID_AUTOPILOT1, name);
+ }
+
+ _missingParametersTimer.start();
+}
+
+void FactPanelController::_checkForMissingParameters(void)
+{
+ QStringList waitList = _missingParameterWaitList;
+ for (const QString& name: waitList) {
+ if (_vehicle->parameterManager()->parameterExists(MAV_COMP_ID_AUTOPILOT1, name)) {
+ _missingParameterWaitList.removeOne(name);
+ }
+ }
+
+ if (_missingParameterWaitList.isEmpty()) {
+ emit missingParametersAvailable();
+ } else {
+ _missingParametersTimer.start();
+ }
+}
diff --git a/src/FactSystem/FactControls/FactPanelController.h b/src/FactSystem/FactControls/FactPanelController.h
index 4fa7ce67d3283443d3c22dbbf018e6982fdc02a0..b9161b49e1d32b9d3e6229db6e70efbea2187754 100644
--- a/src/FactSystem/FactControls/FactPanelController.h
+++ b/src/FactSystem/FactControls/FactPanelController.h
@@ -34,6 +34,12 @@ public:
Q_INVOKABLE Fact* getParameterFact (int componentId, const QString& name, bool reportMissing = true);
Q_INVOKABLE bool parameterExists (int componentId, const QString& name);
+ /// Queries the vehicle for parameters which were not available on initial download but should be available now.
+ /// Signals missingParametersAvailable when done. Only works for MAV_COMP_ID_AUTOPILOT1 parameters.
+ Q_INVOKABLE void getMissingParameters(QStringList rgNames);
+
+signals:
+ void missingParametersAvailable(void);
protected:
/// Checks for existence of the specified parameters
@@ -47,10 +53,15 @@ protected:
UASInterface* _uas = nullptr;
AutoPilotPlugin* _autopilot = nullptr;
+private slots:
+ void _checkForMissingParameters(void);
+
private:
void _notifyPanelMissingParameter(const QString& missingParam);
void _notifyPanelErrorMsg(const QString& errorMsg);
void _showInternalError(const QString& errorMsg);
QStringList _delayedMissingParams;
+ QStringList _missingParameterWaitList;
+ QTimer _missingParametersTimer;
};
diff --git a/src/FirmwarePlugin/APM/APMParameterFactMetaData.Copter.3.6.xml b/src/FirmwarePlugin/APM/APMParameterFactMetaData.Copter.3.6.xml
index 004263c274007df29a5597ed717b1e7a8c988596..70e35ad38ee8ee0a56700d3766636801ace19263 100644
--- a/src/FirmwarePlugin/APM/APMParameterFactMetaData.Copter.3.6.xml
+++ b/src/FirmwarePlugin/APM/APMParameterFactMetaData.Copter.3.6.xml
@@ -3507,7 +3507,7 @@
m
meters
-
+
North-East-Down
Relative to lead vehicle heading
diff --git a/src/FirmwarePlugin/APM/APMResources.qrc b/src/FirmwarePlugin/APM/APMResources.qrc
index 3486e04e7f1229d4506fc1dc35298410ad934c37..ce35b100448da4aa6cc04290c492841c80b7bc84 100644
--- a/src/FirmwarePlugin/APM/APMResources.qrc
+++ b/src/FirmwarePlugin/APM/APMResources.qrc
@@ -7,10 +7,12 @@
../../AutoPilotPlugins/APM/APMCameraComponentSummary.qml
../../AutoPilotPlugins/APM/APMFlightModesComponent.qml
../../AutoPilotPlugins/APM/APMFlightModesComponentSummary.qml
+ ../../AutoPilotPlugins/APM/APMFollowComponent.qml
+ ../../AutoPilotPlugins/APM/APMFollowComponentSummary.qml
../../AutoPilotPlugins/APM/APMHeliComponent.qml
../../AutoPilotPlugins/APM/APMLightsComponent.qml
../../AutoPilotPlugins/APM/APMLightsComponentSummary.qml
- ../../AutoPilotPlugins/APM/APMMotorComponent.qml
+ ../../AutoPilotPlugins/APM/APMMotorComponent.qml
../../AutoPilotPlugins/APM/APMSubFrameComponent.qml
../../AutoPilotPlugins/APM/APMSubFrameComponentSummary.qml
../../AutoPilotPlugins/APM/APMSubMotorComponent.qml
@@ -37,6 +39,7 @@
MavCmdInfoRover.json
MavCmdInfoSub.json
MavCmdInfoVTOL.json
+ ../../AutoPilotPlugins/APM/APMFollowComponent.FactMetaData.json
APMParameterFactMetaData.Plane.3.8.xml