QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
APMFollowComponent.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtQuick.Layouts
5
6import QGroundControl
7import QGroundControl.FactControls
8import QGroundControl.Controls
9import QGroundControl.PlanView
10
11SetupPage {
12 id: followPage
13 pageComponent: followPageComponent
14
15 FactPanelController {
16 id: controller
17 }
18
19 Component {
20 id: followPageComponent
21
22 ColumnLayout {
23 id: flowLayout
24 spacing: _margins
25
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
45
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
53
54 Component.onCompleted: _setUIFromParams()
55
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
64 }
65 _supportedSetup = followOffsetOk && followAltOk && followYawOk && followSysIdOk
66 console.log("_supportedSetup", _supportedSetup, followSysIdOk, followOffsetOk, followAltOk, followYawOk)
67 return _supportedSetup
68 }
69
70 function _setUIFromParams() {
71 if (!_followParamsAvailable || !validateSupportedParamSetup()) {
72 return
73 }
74
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
80 } else {
81 followPositionCombo.currentIndex =_followComboSpecifyIndex
82 let angleRadians = Math.atan2(_followOffsetX.rawValue, _followOffsetY.rawValue)
83 if (angleRadians == 0) {
84 controller.distance.rawValue = _followOffsetY.rawValue
85 } else {
86 controller.distance.rawValue = _followOffsetX.rawValue / Math.sin(angleRadians)
87 }
88 controller.angle.rawValue = _radiansToHeading(angleRadians)
89 }
90 controller.height.rawValue = -_followOffsetZ.rawValue
91 if (!_roverFirmware) {
92 let comboIndex = -1
93 for (let i=0; i<pointVehicleCombo.rgValues.length; i++) {
94 if (pointVehicleCombo.rgValues[i] == _followYawBehavior.rawValue) {
95 comboIndex = i
96 break
97 }
98 }
99
100 pointVehicleCombo.currentIndex = comboIndex
101 }
102 }
103
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
110 }
111
112 controller.distance.value = controller.distance.defaultValue
113 controller.angle.value = controller.angle.defaultValue
114 controller.height.value = controller.height.defaultValue
115
116 _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
117 _followOffsetZ.rawValue = -controller.height.rawValue
118 _setUIFromParams()
119 }
120
121 function _setXYOffsetByAngleAndDistance(headingAngleDegrees, distance) {
122 if (distance == 0) {
123 _followOffsetX.rawValue = _followOffsetY.rawValue = 0
124 } else {
125 let angleRadians = _headingToRadians(headingAngleDegrees)
126 if (angleRadians == 0) {
127 _followOffsetX.rawValue = 0
128 _followOffsetY.rawValue = distance
129 } else {
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
134 }
135 if (Math.abs(_followOffsetY.rawValue) < 0.0001) {
136 _followOffsetY.rawValue = 0
137 }
138 }
139 }
140 }
141
142 function _radiansToHeading(radians) {
143 let geometricAngle = QGroundControl.unitsConversion.radiansToDegrees(radians)
144 let headingAngle = 90 - geometricAngle
145 if (headingAngle < 0) {
146 headingAngle += 360
147 } else if (headingAngle > 360) {
148 headingAngle -= 360
149 }
150 return headingAngle
151 }
152
153 function _headingToRadians(heading) {
154 let geometricAngle = -(heading - 90)
155 return QGroundControl.unitsConversion.degreesToRadians(geometricAngle)
156 }
157
158 function _distanceLabelText() {
159 return QGroundControl.unitsConversion.metersToAppSettingsHorizontalDistanceUnitsString(controller.distance.rawValue)
160 }
161
162 function _heightLabelText() {
163 return QGroundControl.unitsConversion.metersToAppSettingsVerticalDistanceUnitsString(controller.height.rawValue)
164 }
165
166 APMFollowComponentController {
167 id: controller
168
169 onMissingParametersAvailable: {
170 _followDistanceMax = controller.getParameterFact(-1, "FOLL_DIST_MAX")
171 _followSysId = controller.getParameterFact(-1, "FOLL_SYSID")
172 _followOffsetX = controller.getParameterFact(-1, "FOLL_OFS_X")
173 _followOffsetY = controller.getParameterFact(-1, "FOLL_OFS_Y")
174 _followOffsetZ = controller.getParameterFact(-1, "FOLL_OFS_Z")
175 _followOffsetType = controller.getParameterFact(-1, "FOLL_OFS_TYPE")
176 if (!_roverFirmware) {
177 _followAltitudeType = controller.getParameterFact(-1, "FOLL_ALT_TYPE")
178 _followYawBehavior = controller.getParameterFact(-1, "FOLL_YAW_BEHAVE")
179 }
180
181 _followParamsAvailable = true
182 vehicleParamRefreshLabel.visible = false
183 validateSupportedParamSetup()
184 }
185 }
186
187 QGCPalette { id: ggcPal; colorGroupEnabled: true }
188
189 QGCCheckBox {
190 text: qsTr("Enable Follow Me")
191 checked: _followEnabled.rawValue == 1
192 onClicked: {
193 if (checked) {
194 _followEnabled.rawValue = 1
195 let missingParameters = [ "FOLL_DIST_MAX", "FOLL_SYSID", "FOLL_OFS_X", "FOLL_OFS_Y", "FOLL_OFS_Z", "FOLL_OFS_TYPE" ]
196 if (!_roverFirmware) {
197 missingParameters.push("FOLL_ALT_TYPE", "FOLL_YAW_BEHAVE")
198 }
199 controller.getMissingParameters(missingParameters)
200 vehicleParamRefreshLabel.visible = true
201 } else {
202 _followEnabled.rawValue = 0
203 }
204 }
205 }
206
207 QGCLabel {
208 id: vehicleParamRefreshLabel
209 text: qsTr("Waiting for Vehicle to update")
210 visible: false
211 }
212
213 Column {
214 width: offsetSetupLayout.width
215 spacing: ScreenTools.defaultFontPixelWidth
216 visible: !_supportedSetup
217
218 QGCLabel {
219 anchors.left: parent.left
220 anchors.right: parent.right
221 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.")
222 wrapMode: Text.WordWrap
223 }
224
225 QGCButton {
226 text: qsTr("Reset To Supported Settings")
227 onClicked: _setFollowMeParamDefaults()
228 }
229 }
230
231 ColumnLayout {
232 Layout.fillWidth: true
233 spacing: ScreenTools.defaultFontPixelWidth
234 visible: _showMainSetup
235
236 QGCGroupBox {
237 title: qsTr("Follow Me Settings")
238
239 GridLayout {
240 columns: 2
241 rowSpacing: ScreenTools.defaultFontPixelWidth
242 columnSpacing: ScreenTools.defaultFontPixelWidth
243
244 QGCLabel { text: qsTr("Vehicle Position") }
245 QGCComboBox {
246 id: followPositionCombo
247 sizeToContents: true
248 Layout.maximumWidth: _comboWidth
249 model: [ qsTr("Maintain Current Offsets"), qsTr("Specify Offsets")]
250
251 onActivated: (index) => {
252 if (index == 0) {
253 _followOffsetX.rawValue = _followOffsetY.rawValue = _followOffsetZ.rawValue = 0
254 _setUIFromParams()
255 } else {
256 _setFollowMeParamDefaults()
257 }
258 }
259 }
260
261 QGCLabel {
262 text: qsTr("Point Vehicle")
263 visible: !_roverFirmware
264 }
265 QGCComboBox {
266 id: pointVehicleCombo
267 sizeToContents: true
268 Layout.maximumWidth: _comboWidth
269 model: rgText
270 visible: !_roverFirmware
271 onActivated: (index) => { _followYawBehavior.rawValue = rgValues[index] }
272
273 property var rgText: [ qsTr("Maintain current vehicle orientation"), qsTr("Point at ground station location"), qsTr("Same direction as ground station movement") ]
274 property var rgValues: [ _followYawBehaviorNone, _followYawBehaviorFace, _followYawBehaviorFlight ]
275 }
276
277 QGCLabel {
278 Layout.columnSpan: 2
279 Layout.alignment: Qt.AlignHCenter
280 text: qsTr("Vehicle Offsets")
281 visible: !_followMaintain
282 }
283
284 QGCLabel {
285 text: qsTr("Angle")
286 visible: !_followMaintain
287 }
288 FactTextField {
289 Layout.preferredWidth: _textFieldWidth
290 visible: !_followMaintain
291 fact: controller.angle
292 onUpdated: _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
293 }
294
295 QGCLabel {
296 text: qsTr("Distance")
297 visible: !_followMaintain
298 }
299 FactTextField {
300 Layout.preferredWidth: _textFieldWidth
301 visible: !_followMaintain
302 fact: controller.distance
303 onUpdated: _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
304 }
305
306 QGCLabel {
307 text: qsTr("Height")
308 visible: !_roverFirmware && !_followMaintain
309 }
310 FactTextField {
311 Layout.preferredWidth: _textFieldWidth
312 visible: !_roverFirmware && !_followMaintain
313 fact: controller.height
314 onUpdated: _followOffsetZ.rawValue = -controller.height.rawValue
315 }
316 } // GridLayout
317 } // QGCGroupBox
318 }
319
320 RowLayout {
321 id: offsetSetupLayout
322 spacing: ScreenTools.defaultFontPixelWidth * 2
323 visible: _showOffsetsSetup
324
325 Item {
326 height: ScreenTools.defaultFontPixelWidth * 50
327 width: height
328
329 Rectangle {
330 anchors.top: parent.top
331 anchors.bottom: parent.bottom
332 anchors.horizontalCenter: parent.horizontalCenter
333 width: 3
334 color: qgcPal.windowShade
335 }
336
337 Rectangle {
338 anchors.left: parent.left
339 anchors.right: parent.right
340 anchors.verticalCenter: parent.verticalCenter
341 height: 3
342 color: qgcPal.windowShade
343 }
344
345 QGCLabel {
346 anchors.horizontalCenter: parent.horizontalCenter
347 anchors.topMargin: parent.height / 4
348 anchors.top: parent.top
349 text: qsTr("Click in the graphic to change angle")
350 opacity: 0.5
351 }
352
353 Image {
354 id: gcsIcon
355 anchors.centerIn: parent
356 source: "/res/QGCLogoArrow.svg"
357 mipmap: true
358 antialiasing: true
359 fillMode: Image.PreserveAspectFit
360 height: ScreenTools.defaultFontPixelHeight * 2.5
361 sourceSize.height: height
362 }
363
364 Item {
365 id: vehicleHolder
366 anchors.fill: parent
367
368 transform: Rotation {
369 origin.x: vehicleHolder.width / 2
370 origin.y: vehicleHolder.height / 2
371 angle: controller.angle.rawValue
372 }
373
374 Image {
375 id: vehicleIcon
376 anchors.top: parent.top
377 anchors.horizontalCenter: parent.horizontalCenter
378 source: controller.vehicle.vehicleImageOpaque
379 mipmap: true
380 height: ScreenTools.defaultFontPixelHeight * 2.5
381 sourceSize.height: height
382 fillMode: Image.PreserveAspectFit
383
384 transform: Rotation {
385 origin.x: vehicleIcon.width / 2
386 origin.y: vehicleIcon.height / 2
387 angle: _roverFirmware || !_followYawBehavior ? 0 :
388 (_followYawBehavior.rawValue == _followYawBehaviorNone ?
389 0 :
390 (_followYawBehavior.rawValue == _followYawBehaviorFace ?
391 180 :
392 -controller.angle.rawValue))
393 }
394 }
395
396 Rectangle {
397 id: distanceLine
398 x: parent.width / 2
399 y: vehicleIcon.height
400 height: (parent.height / 2) - (vehicleIcon.height + (gcsIcon.height / 2))
401 width: 2
402 color: qgcPal.text
403 opacity: 0.4
404
405 Rectangle {
406 anchors.top: parent.top
407 anchors.horizontalCenter: parent.horizontalCenter
408 width: ScreenTools.defaultFontPixelWidth * 2
409 height: 2
410 color: qgcPal.text
411 }
412
413 Rectangle {
414 anchors.bottom: parent.bottom
415 anchors.horizontalCenter: parent.horizontalCenter
416 width: ScreenTools.defaultFontPixelWidth * 2
417 height: 2
418 color: qgcPal.text
419 }
420 }
421
422 QGCLabel {
423 id: distanceLabel
424 anchors.centerIn: distanceLine
425 text: _distanceLabelText()
426
427 transform: Rotation {
428 origin.x: distanceLabel.width / 2
429 origin.y: distanceLabel.height / 2
430 angle: -controller.angle.rawValue
431 }
432 }
433 }
434
435 MouseArea {
436 anchors.fill: parent
437
438 onClicked: (mouse) => {
439 // Translate x,y to centered
440 let x = mouse.x - (width / 2)
441 let y = (height - mouse.y) - (height / 2)
442 controller.angle.rawValue = _radiansToHeading(Math.atan2(y, x))
443 _setXYOffsetByAngleAndDistance(controller.angle.rawValue, controller.distance.rawValue)
444 }
445 }
446 }
447
448 ColumnLayout {
449 Layout.fillHeight: true
450 spacing: 0
451 visible: !_roverFirmware
452
453 Image {
454 id: vehicleIconHeight
455 source: controller.vehicle.vehicleImageOpaque
456 mipmap: true
457 height: ScreenTools.defaultFontPixelHeight * 2.5
458 sourceSize.height: height
459 fillMode: Image.PreserveAspectFit
460
461 transform: Rotation {
462 origin.x: vehicleIconHeight.width / 2
463 origin.y: vehicleIconHeight.height / 2
464 angle: 65
465 axis { x: 1; y: 0; z: 0 }
466 }
467 }
468
469 Item {
470 Layout.alignment: Qt.AlignHCenter
471 Layout.fillHeight: true
472 width: Math.max(ScreenTools.defaultFontPixelWidth * 2, heightValueLabel.width)
473
474 Rectangle {
475 id: heightLine
476 anchors.top: parent.top
477 anchors.bottom: parent.bottom
478 anchors.horizontalCenter: parent.horizontalCenter
479 width: 2
480 color: qgcPal.text
481 opacity: 0.4
482
483 Rectangle {
484 anchors.top: parent.top
485 anchors.horizontalCenter: parent.horizontalCenter
486 width: ScreenTools.defaultFontPixelWidth * 2
487 height: 2
488 color: qgcPal.text
489 }
490
491 Rectangle {
492 anchors.bottom: parent.bottom
493 anchors.horizontalCenter: parent.horizontalCenter
494 width: ScreenTools.defaultFontPixelWidth * 2
495 height: 2
496 color: qgcPal.text
497 }
498 }
499
500 QGCLabel {
501 id: heightValueLabel
502 anchors.centerIn: parent
503 text: _heightLabelText()
504 }
505 }
506
507 MissionItemIndexLabel {
508 id: launchIconHeight
509 Layout.alignment: Qt.AlignHCenter
510 label: qsTr("L")
511
512 transform: [
513 Scale {
514 origin.x: launchIconHeight.width / 2
515 origin.y: launchIconHeight.height / 2
516 xScale: 1.5
517 yScale: 2.5
518
519 },
520 Rotation {
521 origin.x: launchIconHeight.width / 2
522 origin.y: launchIconHeight.height / 2
523 angle: 75
524 axis { x: 1; y: 0; z: 0 }
525 } ]
526 }
527 }
528 }
529
530 Connections {
531 target: QGroundControl.unitsConversion
532
533 function onUnitsChanged() {
534 distanceLabel.text = Qt.binding(_distanceLabelText)
535 heightValueLabel.text = Qt.binding(_heightLabelText)
536 }
537 }
538 }
539 }
540}