QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
PIDTuning.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtGraphs
4import QtQuick.Layouts
5
6import QGroundControl
7import QGroundControl.Controls
8import QGroundControl.FactControls
9
10RowLayout {
11 spacing: _margins
12
13 QGCPalette { id: qgcPal }
14
15 property real availableHeight
16 property real availableWidth
17 property var axis
18 property string unit
19 property string title
20 property var tuningMode
21 property double chartDisplaySec: 8 // number of seconds to display
22 property bool showAutoModeChange: false
23 property bool showAutoTuning: false
24 property bool useAutoTuning: false
25
26 property real _margins: ScreenTools.defaultFontPixelHeight / 2
27 property int _currentAxis: 0
28 property var _xAxis: xAxis
29 property var _yAxis: yAxis
30 property int _msecs: 0
31 property double _last_t: 0
32 property var _savedTuningParamValues: [ ]
33
34 readonly property int _tickSeparation: 5
35 readonly property int _maxTickSections: 10
36
37 property string _chartTitle: ""
38 readonly property var _seriesColors: ["#21be2b", "#c62828", "#1565c0", "#f9a825", "#6a1b9a", "#00838f"]
39 property var _legendModel: []
40
41 Component {
42 id: lineSeriesComponent
43 LineSeries { }
44 }
45
46 function adjustYAxisMin(yAxis, newValue) {
47 var newMin = Math.min(yAxis.min, newValue)
48 if (newMin % 5 != 0) {
49 newMin -= 5
50 newMin = Math.floor(newMin / _tickSeparation) * _tickSeparation
51 }
52 yAxis.min = newMin
53 }
54
55 function adjustYAxisMax(yAxis, newValue) {
56 var newMax = Math.max(yAxis.max, newValue)
57 if (newMax % 5 != 0) {
58 newMax += 5
59 newMax = Math.floor(newMax / _tickSeparation) * _tickSeparation
60 }
61 yAxis.max = newMax
62 }
63
64 function resetGraphs() {
65 for (var i = 0; i < chart.seriesList.length; ++i) {
66 chart.seriesList[i].clear()
67 }
68 _xAxis.min = 0
69 _xAxis.max = 0
70 _yAxis.min = 0
71 _yAxis.max = 0
72 _msecs = 0
73 _last_t = 0
74 }
75
76 // Save the current set of tuning values so we can reset to them
77 function saveTuningParamValues() {
78 _savedTuningParamValues = [ ]
79 for (var i=0; i<axis[_currentAxis].params.count; i++) {
80 var currentTuneParam = controller.getParameterFact(-1, axis[_currentAxis].params.get(i).param)
81 _savedTuningParamValues.push(currentTuneParam.valueString)
82 }
83 savedRepeater.model = _savedTuningParamValues
84 }
85
86 function resetToSavedTuningParamValues() {
87 for (var i=0; i<axis[_currentAxis].params.count; i++) {
88 var currentTuneParam = controller.getParameterFact(-1,
89 axis[_currentAxis].params.get(i).param)
90 currentTuneParam.value = _savedTuningParamValues[i]
91 }
92 }
93
94 function axisIndexChanged() {
95 while (chart.seriesList.length > 0) {
96 var s = chart.seriesList[0]
97 chart.removeSeries(s)
98 s.destroy()
99 }
100 var legendItems = []
101 axis[_currentAxis].plot.forEach(function(e, idx) {
102 var color = _seriesColors[idx % _seriesColors.length]
103 var series = lineSeriesComponent.createObject(chart, {name: e.name, color: color})
104 chart.addSeries(series)
105 legendItems.push({name: e.name, color: color})
106 })
107 _legendModel = legendItems
108 var chartTitle = axis[_currentAxis].plotTitle
109 if (chartTitle == null)
110 chartTitle = axis[_currentAxis].name
111 _chartTitle = chartTitle + " " + title
112 saveTuningParamValues()
113 resetGraphs()
114 }
115
116 Component.onCompleted: {
117 axisIndexChanged()
118 globals.activeVehicle.setPIDTuningTelemetryMode(tuningMode)
119 saveTuningParamValues()
120 }
121
122 Component.onDestruction: globals.activeVehicle.setPIDTuningTelemetryMode(Vehicle.ModeDisabled)
123 on_CurrentAxisChanged: axisIndexChanged()
124
125 Timer {
126 id: dataTimer
127 interval: 10
128 running: true
129 repeat: true
130
131 onTriggered: {
132 _xAxis.max = _msecs / 1000
133 _xAxis.min = _msecs / 1000 - chartDisplaySec
134
135 var firstPoint = _msecs == 0
136
137 var len = axis[_currentAxis].plot.length
138 for (var i = 0; i < len; ++i) {
139 var value = axis[_currentAxis].plot[i].value
140 if (!isNaN(value)) {
141 chart.seriesList[i].append(_msecs/1000, value)
142 if (firstPoint) {
143 _yAxis.min = value
144 _yAxis.max = value
145 } else {
146 adjustYAxisMin(_yAxis, value)
147 adjustYAxisMax(_yAxis, value)
148 }
149 // limit history
150 var minSec = _msecs/1000 - 3*60
151 while (chart.seriesList[i].count > 0 && chart.seriesList[i].at(0).x < minSec) {
152 chart.seriesList[i].remove(0)
153 }
154 }
155 }
156
157 var t = new Date().getTime() // in ms
158 if (_last_t > 0)
159 _msecs += t-_last_t
160 _last_t = t
161 }
162
163 property int _maxPointCount: 10000 / interval
164 }
165
166 Column {
167 id: leftPanel
168 Layout.fillWidth: true
169 Layout.alignment: Qt.AlignTop
170 spacing: ScreenTools.defaultFontPixelHeight / 4
171 clip: true // chart has redraw problems
172
173 QGCLabel {
174 id: chartTitleLabel
175 text: _chartTitle
176 font.pointSize: ScreenTools.defaultFontPointSize
177 font.family: ScreenTools.normalFontFamily
178 anchors.horizontalCenter: parent.horizontalCenter
179 }
180
181 GraphsView {
182 id: chart
183 width: Math.max(_minChartWidth, availableWidth - rightPanel.width - parent.spacing - _margins)
184 height: Math.max(_minChartHeight, availableHeight - leftPanelBottomColumn.height - chartTitleLabel.height - legendRow.height - parent.spacing * 3 - _margins)
185
186 property real _minChartWidth: ScreenTools.defaultFontPixelWidth * 40
187 property real _minChartHeight: ScreenTools.defaultFontPixelHeight * 15
188
189 theme: GraphsTheme {
190 colorScheme: qgcPal.globalTheme === QGCPalette.Light ? GraphsTheme.ColorScheme.Light : GraphsTheme.ColorScheme.Dark
191 plotAreaBackgroundColor: qgcPal.window
192 grid.mainColor: Qt.rgba(qgcPal.text.r, qgcPal.text.g, qgcPal.text.b, 0.5)
193 grid.subColor: Qt.rgba(qgcPal.text.r, qgcPal.text.g, qgcPal.text.b, 0.3)
194 grid.mainWidth: 1
195 labelBackgroundVisible: false
196 labelTextColor: qgcPal.text
197 }
198
199 axisX: ValueAxis {
200 id: xAxis
201 min: 0
202 max: 0
203 labelFormat: "%.1f"
204 titleText: ScreenTools.isShortScreen ? "" : qsTr("sec")
205 titleFont.pointSize: ScreenTools.defaultFontPointSize
206 titleFont.family: ScreenTools.normalFontFamily
207 }
208
209 axisY: ValueAxis {
210 id: yAxis
211 min: 0
212 max: 10
213 titleText: unit
214 tickInterval: _tickSeparation
215 titleFont.pointSize: ScreenTools.defaultFontPointSize
216 titleFont.family: ScreenTools.normalFontFamily
217 }
218
219 // enable mouse dragging
220 MouseArea {
221 property var _startPoint: undefined
222 property double _scaling: 0
223 anchors.fill: parent
224 onPressed: (mouse) => {
225 _startPoint = Qt.point(mouse.x, mouse.y)
226 if (chart.seriesList.length > 0) {
227 var start = chart.seriesList[0].dataPointCoordinatesAt(_startPoint.x, _startPoint.y)
228 var next = chart.seriesList[0].dataPointCoordinatesAt(mouse.x+1, mouse.y+1)
229 _scaling = next.x - start.x
230 }
231 }
232 onWheel: (wheel) => {
233 if (wheel.angleDelta.y > 0)
234 chartDisplaySec /= 1.2
235 else
236 chartDisplaySec *= 1.2
237 _xAxis.min = _xAxis.max - chartDisplaySec
238 }
239 onPositionChanged: (mouse) => {
240 if(_startPoint != undefined) {
241 dataTimer.running = false
242 var cp = Qt.point(mouse.x, mouse.y)
243 var dx = (cp.x - _startPoint.x) * _scaling
244 _startPoint = cp
245 _xAxis.max -= dx
246 _xAxis.min -= dx
247 }
248 }
249
250 onReleased: {
251 _startPoint = undefined
252 }
253 }
254 }
255
256 Row {
257 id: legendRow
258 spacing: ScreenTools.defaultFontPixelWidth
259 anchors.horizontalCenter: parent.horizontalCenter
260
261 Repeater {
262 model: _legendModel
263 Row {
264 spacing: ScreenTools.defaultFontPixelWidth / 2
265 Rectangle {
266 width: ScreenTools.defaultFontPixelHeight
267 height: ScreenTools.defaultFontPixelHeight / 3
268 color: modelData.color
269 anchors.verticalCenter: parent.verticalCenter
270 }
271 QGCLabel {
272 text: modelData.name
273 font.pointSize: ScreenTools.smallFontPointSize
274 }
275 }
276 }
277 }
278
279 Column {
280 id: leftPanelBottomColumn
281 spacing: ScreenTools.defaultFontPixelHeight / 4
282
283 RowLayout {
284 spacing: _margins
285
286 QGCButton {
287 text: qsTr("Clear")
288 onClicked: resetGraphs()
289 }
290
291 QGCButton {
292 text: dataTimer.running ? qsTr("Stop") : qsTr("Start")
293 onClicked: {
294 dataTimer.running = !dataTimer.running
295 _last_t = 0
296 if (showAutoModeChange && autoModeChange.checked) {
297 globals.activeVehicle.flightMode = dataTimer.running ? globals.activeVehicle.stabilizedFlightMode : globals.activeVehicle.pauseFlightMode
298 }
299 }
300 }
301 Connections {
302 target: globals.activeVehicle
303 onArmedChanged: {
304 if (armed && !dataTimer.running) { // start plotting on arming if not already running
305 dataTimer.running = true
306 _last_t = 0
307 }
308 }
309 }
310 }
311
312 QGCCheckBox {
313 visible: showAutoModeChange
314 id: autoModeChange
315 text: qsTr("Automatic Flight Mode Switching")
316 onClicked: {
317 if (checked)
318 dataTimer.running = false
319 }
320 }
321
322 Column {
323 visible: autoModeChange.checked
324 QGCLabel {
325 text: qsTr("Switches to 'Stabilized' when you click Start.")
326 font.pointSize: ScreenTools.smallFontPointSize
327 }
328
329 QGCLabel {
330 text: qsTr("Switches to '%1' when you click Stop.").arg(globals.activeVehicle.pauseFlightMode)
331 font.pointSize: ScreenTools.smallFontPointSize
332 }
333 }
334 }
335 }
336
337 ColumnLayout {
338 id: rightPanel
339 Layout.alignment: Qt.AlignTop
340
341 RowLayout {
342 visible: showAutoTuning
343
344 QGCRadioButton {
345 id: useAutoTuningRadio
346 text: qsTr("Use auto-tuning")
347 checked: useAutoTuning
348 onClicked: useAutoTuning = true
349 }
350 QGCRadioButton {
351 id: useManualTuningRadio
352 text: qsTr("Use manual tuning")
353 checked: !useAutoTuning
354 onClicked: useAutoTuning = false
355 }
356 }
357
358 AutotuneUI {
359 visible: showAutoTuning && useAutoTuningRadio.checked
360 }
361
362 ColumnLayout {
363 visible: !showAutoTuning || useManualTuningRadio.checked
364
365 Column {
366 RowLayout {
367 spacing: _margins
368 visible: axis.length > 1
369
370 QGCLabel { text: qsTr("Select Tuning:") }
371
372 Repeater {
373 model: axis
374 QGCRadioButton {
375 text: modelData.name
376 checked: index == _currentAxis
377 onClicked: _currentAxis = index
378 }
379 }
380 }
381 }
382
383 // Instantiate all sliders (instead of switching the model), so that
384 // values are not changed unexpectedly if they do not match with a tick value.
385 Repeater {
386 model: axis
387
388 Repeater {
389 id: paramRepeater
390 model: axis[index].params
391
392 property int axisIndex: index
393
394 SettingsGroupLayout {
395 id: tuningGroup
396 heading: title
397 headingDescription: description
398 visible: _currentAxis === paramRepeater.axisIndex
399 Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 40
400
401 FactSlider {
402 fact: controller.getParameterFact(-1, param)
403 from: min
404 to: max
405 majorTickStepSize: step
406 Layout.fillWidth: true
407 }
408 }
409 }
410 }
411
412 Column {
413 QGCLabel { text: qsTr("Clipboard Values:") }
414
415 GridLayout {
416 rows: savedRepeater.model.length
417 flow: GridLayout.TopToBottom
418 rowSpacing: 0
419 columnSpacing: _margins
420
421 Repeater {
422 model: axis[_currentAxis].params
423
424 QGCLabel { text: param }
425 }
426
427 Repeater {
428 id: savedRepeater
429
430 QGCLabel { text: modelData }
431 }
432 }
433 }
434
435 RowLayout {
436 spacing: _margins
437
438 QGCButton {
439 text: qsTr("Save To Clipboard")
440 onClicked: saveTuningParamValues()
441 }
442
443 QGCButton {
444 text: qsTr("Restore From Clipboard")
445 onClicked: resetToSavedTuningParamValues()
446 }
447 }
448 }
449 }
450
451} // RowLayout