QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
ActuatorComponent.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtQuick.Layouts
5
6import QGroundControl
7import QGroundControl.Controls
8import QGroundControl.FactControls
9import QGroundControl.AutoPilotPlugins.PX4
10
11SetupPage {
12 id: actuatorPage
13 pageComponent: pageComponent
14 showAdvanced: true
15
16 property var actuators: globals.activeVehicle.actuators
17
18 property var _showAdvanced: advanced
19 readonly property real _margins: ScreenTools.defaultFontPixelHeight
20
21 Component {
22 id: pageComponent
23
24 Row {
25 spacing: ScreenTools.defaultFontPixelWidth * 4
26 property var _leftColumnWidth: Math.max(actuatorTesting.implicitWidth, mixerUi.implicitWidth) + (_margins * 2)
27
28 ColumnLayout {
29 spacing: ScreenTools.defaultFontPixelHeight
30 implicitWidth: _leftColumnWidth
31
32 // mixer ui
33 RowLayout {
34 width: _leftColumnWidth
35 visible: actuators.mixer.groups.count > 0
36 QGCLabel {
37 text: qsTr("Geometry") + (actuators.mixer.title ? ": " + actuators.mixer.title : "")
38 font.pointSize: ScreenTools.mediumFontPointSize
39 Layout.fillWidth: true
40 }
41 QGCLabel {
42 text: "<a href='"+actuators.mixer.helpUrl+"'>?</a>"
43 font.pointSize: ScreenTools.mediumFontPointSize
44 visible: actuators.mixer.helpUrl
45 textFormat: Text.RichText
46 onLinkActivated: (link) => {
47 Qt.openUrlExternally(link);
48 }
49 }
50 }
51
52 Rectangle {
53 implicitWidth: _leftColumnWidth
54 implicitHeight: mixerUi.height + (_margins * 2)
55 color: qgcPal.windowShade
56 visible: actuators.mixer.groups.count > 0
57
58 Column {
59 id: mixerUi
60 spacing: _margins
61 anchors {
62 left: parent.left
63 leftMargin: _margins
64 verticalCenter: parent.verticalCenter
65 }
66 enabled: !safetySwitch.checked && !actuators.motorAssignmentActive
67 Repeater {
68 model: actuators.mixer.groups
69 ColumnLayout {
70 property var mixerGroup: object
71
72 RowLayout {
73 QGCLabel {
74 text: mixerGroup.label
75 font.bold: true
76 rightPadding: ScreenTools.defaultFontPixelWidth * 3
77 }
78 ActuatorFact {
79 property var countParam: mixerGroup.countParam
80 visible: countParam != null
81 fact: countParam ? countParam.fact : null
82 }
83 }
84
85 GridLayout {
86 rows: 1 + mixerGroup.channels.count
87 columns: 1 + mixerGroup.channelConfigs.count
88
89 QGCLabel {
90 text: ""
91 }
92
93 // param config labels
94 Repeater {
95 model: mixerGroup.channelConfigs
96 QGCLabel {
97 text: object.label
98 visible: object.visible && (_showAdvanced || !object.advanced)
99 Layout.row: 0
100 Layout.column: 1 + index
101 }
102 }
103 // param instances
104 Repeater {
105 model: mixerGroup.channels
106 QGCLabel {
107 text: object.label + ":"
108 Layout.row: 1 + index
109 Layout.column: 0
110 }
111 }
112 Repeater {
113 model: mixerGroup.channels
114 Repeater {
115 property var channel: object
116 property var channelIndex: index
117
118 model: object.configInstances
119
120 ActuatorFact {
121 fact: object.fact
122 Layout.row: 1 + channelIndex
123 Layout.column: 1 + index
124 visible: object.config.visible && (_showAdvanced || !object.config.advanced) && object.visible
125 enabled: object.enabled
126 }
127 }
128 }
129 }
130
131 // extra group config params
132 Repeater {
133 model: mixerGroup.configParams
134
135 RowLayout {
136 spacing: ScreenTools.defaultFontPixelWidth
137 QGCLabel {
138 text: object.label + ":"
139 visible: _showAdvanced || !object.advanced
140 }
141 ActuatorFact {
142 fact: object.fact
143 visible: _showAdvanced || !object.advanced
144 }
145 }
146 }
147 }
148 }
149 }
150 }
151
152 // actuator image
153 Image {
154 id: actuatorImage
155 source: "image://actuators/geometry"+refreshFlag
156 sourceSize.width: imageSize
157 sourceSize.height: imageSize
158 Layout.preferredWidth: imageSize
159 Layout.preferredHeight: imageSize
160 Layout.alignment: Qt.AlignHCenter
161 visible: actuators.isMultirotor
162 cache: false
163
164 property var refreshFlag: actuators.imageRefreshFlag
165 readonly property real imageSize: 9 * ScreenTools.defaultFontPixelHeight
166
167 MouseArea {
168 anchors.fill: parent
169 onClicked: (mouse) => {
170 if (mouse.button == Qt.LeftButton) {
171 actuators.imageClicked(Qt.size(width, height), mouse.x, mouse.y);
172 }
173 }
174 }
175 }
176
177 // actuator testing
178 QGCLabel {
179 text: qsTr("Actuator Testing")
180 font.pointSize: ScreenTools.mediumFontPointSize
181 }
182
183 Rectangle {
184 implicitWidth: _leftColumnWidth
185 implicitHeight: actuatorTesting.height + (_margins * 2)
186 color: qgcPal.windowShade
187
188 Column {
189 id: actuatorTesting
190 spacing: _margins
191 anchors {
192 left: parent.left
193 leftMargin: _margins
194 verticalCenter: parent.verticalCenter
195 }
196
197 QGCLabel {
198 text: qsTr("Configure some outputs in order to test them.")
199 visible: actuators.actuatorTest.actuators.count == 0
200 }
201
202 Row {
203 spacing: ScreenTools.defaultFontPixelWidth
204 visible: actuators.actuatorTest.actuators.count > 0
205
206 Switch {
207 id: safetySwitch
208 enabled: !actuators.motorAssignmentActive && !actuators.actuatorTest.hadFailure
209 Connections {
210 target: actuators.actuatorTest
211 onHadFailureChanged: {
212 if (actuators.actuatorTest.hadFailure) {
213 safetySwitch.checked = false;
214 safetySwitch.switchUpdated();
215 }
216 }
217 }
218 onClicked: {
219 switchUpdated();
220 }
221 function switchUpdated() {
222 if (!checked) {
223 for (var channelIdx=0; channelIdx<sliderRepeater.count; channelIdx++) {
224 sliderRepeater.itemAt(channelIdx).stop();
225 }
226 if (allMotorsLoader.item != null)
227 allMotorsLoader.item.stop();
228 }
229 actuators.actuatorTest.setActive(checked);
230 }
231 }
232
233 QGCLabel {
234 color: qgcPal.warningText
235 text: safetySwitch.checked ? qsTr("Careful: Actuator sliders are enabled") : qsTr("Propellers are removed - Enable sliders")
236 }
237 } // Row
238
239 Row {
240 spacing: ScreenTools.defaultFontPixelWidth * 2
241 enabled: safetySwitch.checked
242
243 // (optional) slider for all motors
244 Loader {
245 id: allMotorsLoader
246 sourceComponent: actuators.actuatorTest.allMotorsActuator ? allMotorsComponent : null
247 Layout.alignment: Qt.AlignTop
248 }
249 Component {
250 id: allMotorsComponent
251 ActuatorSlider {
252 channel: actuators.actuatorTest.allMotorsActuator
253 rightPadding: ScreenTools.defaultFontPixelWidth * 3
254 onActuatorValueChanged: (value, sliderValue) => {
255 stopTimer();
256 for (var channelIdx=0; channelIdx<sliderRepeater.count; channelIdx++) {
257 var channelSlider = sliderRepeater.itemAt(channelIdx);
258 if (channelSlider.channel.isMotor) {
259 channelSlider.value = sliderValue;
260 }
261 }
262 }
263 }
264 }
265
266 // all channels
267 Repeater {
268 id: sliderRepeater
269 model: actuators.actuatorTest.actuators
270
271 ActuatorSlider {
272 channel: object
273 onActuatorValueChanged: (value) =>{
274 if (isNaN(value)) {
275 actuators.actuatorTest.stopControl(index);
276 stop();
277 } else {
278 actuators.actuatorTest.setChannelTo(index, value);
279 }
280 }
281 }
282 } // Repeater
283 } // Row
284
285 // actuator actions
286 Column {
287 visible: actuators.actuatorActions.count > 0
288 enabled: !safetySwitch.checked && !actuators.motorAssignmentActive
289 Row {
290 spacing: ScreenTools.defaultFontPixelWidth * 2
291 Repeater {
292 model: actuators.actuatorActions
293
294 QGCButton {
295 property var actionGroup: object
296 text: actionGroup.label
297 onClicked: actionMenu.popup()
298 QGCMenu {
299 id: actionMenu
300
301 Instantiator {
302 model: actionGroup.actions
303 QGCMenuItem {
304 text: object.label
305 onTriggered: object.trigger()
306 }
307 onObjectAdded: (index, object) => actionMenu.insertItem(index, object)
308 onObjectRemoved: (index, object) => actionMenu.removeItem(object)
309 }
310 }
311 }
312 }
313 }
314
315 } // Column
316
317 } // Column
318 } // Rectangle
319 }
320
321 // Right column
322 Column {
323 QGCLabel {
324 text: qsTr("Actuator Outputs")
325 font.pointSize: ScreenTools.mediumFontPointSize
326 bottomPadding: ScreenTools.defaultFontPixelHeight
327 }
328 QGCLabel {
329 text: qsTr("One or more actuator still needs to be assigned to an output.")
330 visible: actuators.hasUnsetRequiredFunctions
331 color: qgcPal.warningText
332 bottomPadding: ScreenTools.defaultFontPixelHeight
333 }
334
335
336 // actuator output selection tabs
337 QGCTabBar {
338 Repeater {
339 model: actuators.actuatorOutputs
340 QGCTabButton {
341 text: ' ' + object.label + ' '
342 width: implicitWidth
343 }
344 }
345 onCurrentIndexChanged: {
346 actuators.selectActuatorOutput(currentIndex)
347 }
348 }
349
350 // actuator outputs
351 Rectangle {
352 id: selActuatorOutput
353 implicitWidth: actuatorGroupColumn.width + (_margins * 2)
354 implicitHeight: actuatorGroupColumn.height + (_margins * 2)
355 color: qgcPal.windowShade
356
357 property var actuatorOutput: actuators.selectedActuatorOutput
358
359 Column {
360 id: actuatorGroupColumn
361 spacing: _margins
362 anchors.centerIn: parent
363
364 // Motor assignment
365 Row {
366 visible: actuators.isMultirotor
367 enabled: !safetySwitch.checked
368 anchors.right: parent.right
369 spacing: _margins
370 QGCButton {
371 text: qsTr("Identify & Assign Motors")
372 visible: !actuators.motorAssignmentActive && selActuatorOutput.actuatorOutput.groupsVisible
373 enabled: actuators.motorAssignmentEnabled
374 onClicked: {
375 var success = actuators.initMotorAssignment()
376 if (success) {
377 motorAssignmentConfirmDialog.open()
378 } else {
379 motorAssignmentFailureDialog.open()
380 }
381 }
382 MessageDialog {
383 id: motorAssignmentConfirmDialog
384 visible: false
385 //icon: StandardIcon.Warning
386 buttons: MessageDialog.Yes | MessageDialog.No
387 title: qsTr("Motor Order Identification and Assignment")
388 text: actuators.motorAssignmentMessage
389 onButtonClicked: function (button, role) {
390 switch (button) {
391 case MessageDialog.Yes:
392 actuators.startMotorAssignment()
393 break;
394 }
395 }
396 }
397 MessageDialog {
398 id: motorAssignmentFailureDialog
399 visible: false
400 //icon: StandardIcon.Critical
401 buttons: MessageDialog.Ok
402 title: qsTr("Error")
403 text: actuators.motorAssignmentMessage
404 }
405 }
406 QGCButton {
407 text: qsTr("Spin Motor Again")
408 visible: actuators.motorAssignmentActive
409 onClicked: {
410 actuators.spinCurrentMotor()
411 }
412 }
413 QGCButton {
414 text: qsTr("Abort")
415 visible: actuators.motorAssignmentActive
416 onClicked: {
417 actuators.abortMotorAssignment()
418 }
419 }
420 }
421
422 Column {
423 enabled: !safetySwitch.checked && !actuators.motorAssignmentActive
424 spacing: _margins
425
426 RowLayout {
427 property var enableParam: selActuatorOutput.actuatorOutput.enableParam
428 QGCLabel {
429 visible: parent.enableParam != null
430 text: parent.enableParam ? parent.enableParam.label + ":" : ""
431 }
432 ActuatorFact {
433 visible: parent.enableParam != null
434 fact: parent.enableParam ? parent.enableParam.fact : null
435 }
436 }
437
438
439 Repeater {
440 model: selActuatorOutput.actuatorOutput.subgroups
441
442 ColumnLayout {
443 property var subgroup: object
444 visible: selActuatorOutput.actuatorOutput.groupsVisible
445
446 RowLayout {
447 visible: subgroup.label != ""
448 QGCLabel {
449 text: subgroup.label
450 font.bold: true
451 rightPadding: ScreenTools.defaultFontPixelWidth * 3
452 }
453 ActuatorFact {
454 property var primaryParam: subgroup.primaryParam
455 visible: primaryParam != null
456 fact: primaryParam ? primaryParam.fact : null
457 }
458 }
459
460 GridLayout {
461 rows: 1 + subgroup.channels.count
462 columns: 1 + subgroup.channelConfigs.count
463
464 QGCLabel {
465 text: ""
466 }
467
468 // param config labels
469 Repeater {
470 model: subgroup.channelConfigs
471 QGCLabel {
472 text: object.label
473 visible: object.visible && (_showAdvanced || !object.advanced)
474 Layout.row: 0
475 Layout.column: 1 + index
476 }
477 }
478 // param instances
479 Repeater {
480 model: subgroup.channels
481 QGCLabel {
482 text: object.label + ":"
483 Layout.row: 1 + index
484 Layout.column: 0
485 }
486 }
487 Repeater {
488 model: subgroup.channels
489 Repeater {
490 property var channel: object
491 property var channelIndex: index
492 model: object.configInstances
493 ActuatorFact {
494 fact: object.fact
495 Layout.row: 1 + channelIndex
496 Layout.column: 1 + index
497 visible: object.config.visible && (_showAdvanced || !object.config.advanced)
498 }
499 }
500 }
501 }
502
503 // extra subgroup config params
504 Repeater {
505 model: subgroup.configParams
506
507 RowLayout {
508 QGCLabel {
509 text: object.label + ":"
510 }
511 ActuatorFact {
512 fact: object.fact
513 }
514 }
515 }
516
517 }
518 } // subgroup Repeater
519
520 // extra actuator config params
521 Repeater {
522 model: selActuatorOutput.actuatorOutput.configParams
523
524 RowLayout {
525 QGCLabel {
526 text: object.label + ":"
527 }
528 ActuatorFact {
529 fact: object.fact
530 }
531 }
532 }
533
534 // notes
535 Repeater {
536 model: selActuatorOutput.actuatorOutput.notes
537 ColumnLayout {
538 spacing: ScreenTools.defaultFontPixelHeight
539 QGCLabel {
540 text: modelData
541 color: qgcPal.warningText
542 }
543 }
544 }
545 }
546 }
547 } // Rectangle
548 } // Column
549 } // Row
550
551 }
552}