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