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