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