7import QGroundControl.FactControls
8import QGroundControl.Controls
9import QGroundControl.PlanView
13 pageComponent: followPageComponent
20 id: followPageComponent
26 property Fact _followEnabled: controller.getParameterFact(-1, "FOLL_ENABLE")
27 property bool _followParamsAvailable: controller.parameterExists(-1, "FOLL_SYSID")
28 property Fact _followDistanceMax: controller.getParameterFact(-1, "FOLL_DIST_MAX", false /* reportMissing */)
29 property Fact _followSysId: controller.getParameterFact(-1, "FOLL_SYSID", false /* reportMissing */)
30 property Fact _followOffsetX: controller.getParameterFact(-1, "FOLL_OFS_X", false /* reportMissing */)
31 property Fact _followOffsetY: controller.getParameterFact(-1, "FOLL_OFS_Y", false /* reportMissing */)
32 property Fact _followOffsetZ: controller.getParameterFact(-1, "FOLL_OFS_Z", false /* reportMissing */)
33 property Fact _followOffsetType: controller.getParameterFact(-1, "FOLL_OFS_TYPE", false /* reportMissing */)
34 property Fact _followAltitudeType: controller.getParameterFact(-1, "FOLL_ALT_TYPE", false /* reportMissing */)
35 property Fact _followYawBehavior: controller.getParameterFact(-1, "FOLL_YAW_BEHAVE", false /* reportMissing */)
36 property int _followComboMaintainIndex: 0
37 property int _followComboSpecifyIndex: 1
38 property bool _followMaintain: followPositionCombo.currentIndex === _followComboMaintainIndex
39 property bool _supportedSetup: true
40 property bool _roverFirmware: controller.roverFirmware
41 property bool _showMainSetup: _followEnabled.rawValue == 1 && _supportedSetup
42 property bool _showOffsetsSetup: _showMainSetup && !_followMaintain
43 property real _textFieldWidth: ScreenTools.defaultFontPixelWidth * 15
44 property real _comboWidth: ScreenTools.defaultFontPixelWidth * 30
46 readonly property int _followYawBehaviorNone: 0
47 readonly property int _followYawBehaviorFace: 1
48 readonly property int _followYawBehaviorSame: 2
49 readonly property int _followYawBehaviorFlight: 3
50 readonly property int _followAltitudeTypeAbsolute: 0
51 readonly property int _followAltitudeTypeRelative: 1
52 readonly property int _followOffsetTypeRelative: 1
54 Component.onCompleted: _setUIFromParams()
56 function validateSupportedParamSetup() {
57 let followSysIdOk = _followSysId.rawValue == QGroundControl.settingsManager.mavlinkSettings.gcsMavlinkSystemID.rawValue
58 let followOffsetOk = _followOffsetType.rawValue == _followOffsetTypeRelative
59 let followAltOk = true
60 let followYawOk = true
61 if (!_roverFirmware) {
62 followAltOk = _followAltitudeType.rawValue == _followAltitudeTypeRelative
63 followYawOk = _followYawBehavior.rawValue == _followYawBehaviorNone || _followYawBehavior.rawValue == _followYawBehaviorFace || _followYawBehavior.rawValue == _followYawBehaviorFlight
65 _supportedSetup = followOffsetOk && followAltOk && followYawOk && followSysIdOk
66 console.log("_supportedSetup", _supportedSetup, followSysIdOk, followOffsetOk, followAltOk, followYawOk)
67 return _supportedSetup
70 function _setUIFromParams() {
71 if (!_followParamsAvailable || !validateSupportedParamSetup()) {
75 if (_followOffsetX.rawValue == 0 && _followOffsetY.rawValue == 0 && _followOffsetZ.rawValue == 0) {
76 followPositionCombo.currentIndex =_followComboMaintainIndex
77 controller.distance.rawValue = 0
78 controller.angle.rawValue = 0
79 controller.height.rawValue = 0
81 followPositionCombo.currentIndex =_followComboSpecifyIndex
82 let angleRadians = Math.atan2(_followOffsetX.rawValue, _followOffsetY.rawValue)
83 if (angleRadians == 0) {
84 controller.distance.rawValue = _followOffsetY.rawValue
86 controller.distance.rawValue = _followOffsetX.rawValue / Math.sin(angleRadians)
88 controller.angle.rawValue = _radiansToHeading(angleRadians)
90 controller.height.rawValue = -_followOffsetZ.rawValue
91 if (!_roverFirmware) {
93 for (let i=0; i<pointVehicleCombo.rgValues.length; i++) {
94 if (pointVehicleCombo.rgValues[i] == _followYawBehavior.rawValue) {
100 pointVehicleCombo.currentIndex = comboIndex
104 function _setFollowMeParamDefaults() {
105 _followSysId.rawValue = QGroundControl.settingsManager.mavlinkSettings.gcsMavlinkSystemID.rawValue
106 _followOffsetType.rawValue = _followOffsetTypeRelative
107 if (!_roverFirmware) {
108 _followAltitudeType.rawValue = _followAltitudeTypeRelative
109 _followYawBehavior.rawValue = _followYawBehaviorFace
112 controller.distance.value = controller.distance.defaultValue
113 controller.angle.value = controller.angle.defaultValue
114 controller.height.value = controller.height.defaultValue
116 _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
117 _followOffsetZ.rawValue = -controller.height.rawValue
121 function _setXYOffsetByAngleAndDistance(headingAngleDegrees, distance) {
123 _followOffsetX.rawValue = _followOffsetY.rawValue = 0
125 let angleRadians = _headingToRadians(headingAngleDegrees)
126 if (angleRadians == 0) {
127 _followOffsetX.rawValue = 0
128 _followOffsetY.rawValue = distance
130 _followOffsetX.rawValue = Math.sin(angleRadians) * distance
131 _followOffsetY.rawValue = Math.cos(angleRadians) * distance
132 if (Math.abs(_followOffsetX.rawValue) < 0.0001) {
133 _followOffsetX.rawValue = 0
135 if (Math.abs(_followOffsetY.rawValue) < 0.0001) {
136 _followOffsetY.rawValue = 0
142 function _radiansToHeading(radians) {
143 let geometricAngle = QGroundControl.unitsConversion.radiansToDegrees(radians)
144 let headingAngle = 90 - geometricAngle
145 if (headingAngle < 0) {
147 } else if (headingAngle > 360) {
153 function _headingToRadians(heading) {
154 let geometricAngle = -(heading - 90)
155 return QGroundControl.unitsConversion.degreesToRadians(geometricAngle)
158 APMFollowComponentController {
161 onMissingParametersAvailable: {
162 _followDistanceMax = controller.getParameterFact(-1, "FOLL_DIST_MAX")
163 _followSysId = controller.getParameterFact(-1, "FOLL_SYSID")
164 _followOffsetX = controller.getParameterFact(-1, "FOLL_OFS_X")
165 _followOffsetY = controller.getParameterFact(-1, "FOLL_OFS_Y")
166 _followOffsetZ = controller.getParameterFact(-1, "FOLL_OFS_Z")
167 _followOffsetType = controller.getParameterFact(-1, "FOLL_OFS_TYPE")
168 if (!_roverFirmware) {
169 _followAltitudeType = controller.getParameterFact(-1, "FOLL_ALT_TYPE")
170 _followYawBehavior = controller.getParameterFact(-1, "FOLL_YAW_BEHAVE")
173 _followParamsAvailable = true
174 vehicleParamRefreshLabel.visible = false
175 validateSupportedParamSetup()
179 QGCPalette { id: ggcPal; colorGroupEnabled: true }
182 text: qsTr("Enable Follow Me")
183 checked: _followEnabled.rawValue == 1
186 _followEnabled.rawValue = 1
187 let missingParameters = [ "FOLL_DIST_MAX", "FOLL_SYSID", "FOLL_OFS_X", "FOLL_OFS_Y", "FOLL_OFS_Z", "FOLL_OFS_TYPE" ]
188 if (!_roverFirmware) {
189 missingParameters.push("FOLL_ALT_TYPE", "FOLL_YAW_BEHAVE")
191 controller.getMissingParameters(missingParameters)
192 vehicleParamRefreshLabel.visible = true
194 _followEnabled.rawValue = 0
200 id: vehicleParamRefreshLabel
201 text: qsTr("Waiting for Vehicle to update")
206 width: offsetSetupLayout.width
207 spacing: ScreenTools.defaultFontPixelWidth
208 visible: !_supportedSetup
211 anchors.left: parent.left
212 anchors.right: parent.right
213 text: qsTr("The vehicle parameters required for follow me are currently set in a way which is not supported. Using follow with this setup may lead to unpredictable/hazardous results.")
214 wrapMode: Text.WordWrap
218 text: qsTr("Reset To Supported Settings")
219 onClicked: _setFollowMeParamDefaults()
224 Layout.fillWidth: true
225 spacing: ScreenTools.defaultFontPixelWidth
226 visible: _showMainSetup
229 title: qsTr("Follow Me Settings")
233 rowSpacing: ScreenTools.defaultFontPixelWidth
234 columnSpacing: ScreenTools.defaultFontPixelWidth
236 QGCLabel { text: qsTr("Vehicle Position") }
238 id: followPositionCombo
240 Layout.maximumWidth: _comboWidth
241 model: [ qsTr("Maintain Current Offsets"), qsTr("Specify Offsets")]
243 onActivated: (index) => {
245 _followOffsetX.rawValue = _followOffsetY.rawValue = _followOffsetZ.rawValue = 0
248 _setFollowMeParamDefaults()
254 text: qsTr("Point Vehicle")
255 visible: !_roverFirmware
258 id: pointVehicleCombo
260 Layout.maximumWidth: _comboWidth
262 visible: !_roverFirmware
263 onActivated: (index) => { _followYawBehavior.rawValue = rgValues[index] }
265 property var rgText: [ qsTr("Maintain current vehicle orientation"), qsTr("Point at ground station location"), qsTr("Same direction as ground station movement") ]
266 property var rgValues: [ _followYawBehaviorNone, _followYawBehaviorFace, _followYawBehaviorFlight ]
271 Layout.alignment: Qt.AlignHCenter
272 text: qsTr("Vehicle Offsets")
273 visible: !_followMaintain
278 visible: !_followMaintain
281 Layout.preferredWidth: _textFieldWidth
282 visible: !_followMaintain
283 fact: controller.angle
284 onUpdated: _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
288 text: qsTr("Distance")
289 visible: !_followMaintain
292 Layout.preferredWidth: _textFieldWidth
293 visible: !_followMaintain
294 fact: controller.distance
295 onUpdated: _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
300 visible: !_roverFirmware && !_followMaintain
303 Layout.preferredWidth: _textFieldWidth
304 visible: !_roverFirmware && !_followMaintain
305 fact: controller.height
306 onUpdated: _followOffsetZ.rawValue = -controller.height.rawValue
313 id: offsetSetupLayout
314 spacing: ScreenTools.defaultFontPixelWidth * 2
315 visible: _showOffsetsSetup
318 height: ScreenTools.defaultFontPixelWidth * 50
322 anchors.top: parent.top
323 anchors.bottom: parent.bottom
324 anchors.horizontalCenter: parent.horizontalCenter
326 color: qgcPal.windowShade
330 anchors.left: parent.left
331 anchors.right: parent.right
332 anchors.verticalCenter: parent.verticalCenter
334 color: qgcPal.windowShade
338 anchors.horizontalCenter: parent.horizontalCenter
339 anchors.topMargin: parent.height / 4
340 anchors.top: parent.top
341 text: qsTr("Click in the graphic to change angle")
347 anchors.centerIn: parent
348 source: "/res/QGCLogoArrow.svg"
351 fillMode: Image.PreserveAspectFit
352 height: ScreenTools.defaultFontPixelHeight * 2.5
353 sourceSize.height: height
360 transform: Rotation {
361 origin.x: vehicleHolder.width / 2
362 origin.y: vehicleHolder.height / 2
363 angle: controller.angle.rawValue
368 anchors.top: parent.top
369 anchors.horizontalCenter: parent.horizontalCenter
370 source: controller.vehicle.vehicleImageOpaque
372 height: ScreenTools.defaultFontPixelHeight * 2.5
373 sourceSize.height: height
374 fillMode: Image.PreserveAspectFit
376 transform: Rotation {
377 origin.x: vehicleIcon.width / 2
378 origin.y: vehicleIcon.height / 2
379 angle: _roverFirmware || !_followYawBehavior ? 0 :
380 (_followYawBehavior.rawValue == _followYawBehaviorNone ?
382 (_followYawBehavior.rawValue == _followYawBehaviorFace ?
384 -controller.angle.rawValue))
391 y: vehicleIcon.height
392 height: (parent.height / 2) - (vehicleIcon.height + (gcsIcon.height / 2))
398 anchors.top: parent.top
399 anchors.horizontalCenter: parent.horizontalCenter
400 width: ScreenTools.defaultFontPixelWidth * 2
406 anchors.bottom: parent.bottom
407 anchors.horizontalCenter: parent.horizontalCenter
408 width: ScreenTools.defaultFontPixelWidth * 2
416 anchors.centerIn: distanceLine
417 text: controller.distance.valueString + " " + QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString
419 transform: Rotation {
420 origin.x: distanceLabel.width / 2
421 origin.y: distanceLabel.height / 2
422 angle: -controller.angle.rawValue
430 onClicked: (mouse) => {
431 // Translate x,y to centered
432 let x = mouse.x - (width / 2)
433 let y = (height - mouse.y) - (height / 2)
434 controller.angle.rawValue = _radiansToHeading(Math.atan2(y, x))
435 _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
441 Layout.fillHeight: true
443 visible: !_roverFirmware
446 id: vehicleIconHeight
447 source: controller.vehicle.vehicleImageOpaque
449 height: ScreenTools.defaultFontPixelHeight * 2.5
450 sourceSize.height: height
451 fillMode: Image.PreserveAspectFit
453 transform: Rotation {
454 origin.x: vehicleIconHeight.width / 2
455 origin.y: vehicleIconHeight.height / 2
457 axis { x: 1; y: 0; z: 0 }
462 Layout.alignment: Qt.AlignHCenter
463 Layout.fillHeight: true
464 width: Math.max(ScreenTools.defaultFontPixelWidth * 2, heightValueLabel.width)
468 anchors.top: parent.top
469 anchors.bottom: parent.bottom
470 anchors.horizontalCenter: parent.horizontalCenter
476 anchors.top: parent.top
477 anchors.horizontalCenter: parent.horizontalCenter
478 width: ScreenTools.defaultFontPixelWidth * 2
484 anchors.bottom: parent.bottom
485 anchors.horizontalCenter: parent.horizontalCenter
486 width: ScreenTools.defaultFontPixelWidth * 2
494 anchors.centerIn: parent
495 text: controller.height.valueString + " " + QGroundControl.unitsConversion.appSettingsHorizontalDistanceUnitsString
499 MissionItemIndexLabel {
501 Layout.alignment: Qt.AlignHCenter
506 origin.x: launchIconHeight.width / 2
507 origin.y: launchIconHeight.height / 2
513 origin.x: launchIconHeight.width / 2
514 origin.y: launchIconHeight.height / 2
516 axis { x: 1; y: 0; z: 0 }