6import QGroundControl.Controls
7import QGroundControl.FactControls
11 pageComponent: pageComponent
14 readonly property int _maxServos: 16
15 readonly property real _margins: ScreenTools.defaultFontPixelHeight * 0.5
21 ServoOutputMonitorController {
27 colorGroupEnabled: true
30 // Keep the "Position (us)" bar a stable width so the table doesn't reflow.
31 readonly property int _positionBarWidth: ScreenTools.defaultFontPixelWidth * 10
33 function getFact(param) {
34 return controller.parameterExists(-1, param)
35 ? controller.getParameterFact(-1, param, false)
39 function servoExists(n) {
40 return controller.parameterExists(-1, "SERVO" + n + "_FUNCTION")
51 text: qsTr("Configure ArduPilot servo outputs.")
52 wrapMode: Text.WordWrap
57 title: qsTr("Servo Outputs")
61 rowSpacing: ScreenTools.defaultFontPixelHeight * 0.3
62 columnSpacing: ScreenTools.defaultFontPixelWidth * 2
64 // --- Headers (all explicitly in row 0) -----------------
65 QGCLabel { text: ""; Layout.row: 0; Layout.column: 0; Layout.alignment: Qt.AlignHCenter }
67 text: qsTr("Position")
70 Layout.alignment: Qt.AlignHCenter
72 QGCLabel { text: qsTr("Function"); Layout.row: 0; Layout.column: 2; Layout.alignment: Qt.AlignHCenter }
73 QGCLabel { text: qsTr("Min"); Layout.row: 0; Layout.column: 3; Layout.alignment: Qt.AlignHCenter }
74 QGCLabel { text: qsTr("Trim"); Layout.row: 0; Layout.column: 4; Layout.alignment: Qt.AlignHCenter }
75 QGCLabel { text: qsTr("Max"); Layout.row: 0; Layout.column: 5; Layout.alignment: Qt.AlignHCenter }
76 QGCLabel { text: qsTr("Reversed"); Layout.row: 0; Layout.column: 6; Layout.alignment: Qt.AlignHCenter }
78 // --- Column 0: Servo number ----------------------------
84 visible: servoExists(index + 1)
90 // --- Column 1: Position --------------------------------
96 readonly property int _servoIndex: index + 1
97 readonly property var _minFact: getFact("SERVO" + _servoIndex + "_MIN")
98 readonly property var _maxFact: getFact("SERVO" + _servoIndex + "_MAX")
100 property int pwmValue: servoMonitor.servoValue(index)
101 readonly property double _rawValue: pwmValue >= 0 ? pwmValue : NaN
102 readonly property double _minValue: _minFact ? _minFact.value : NaN
103 readonly property double _maxValue: _maxFact ? _maxFact.value : NaN
105 readonly property double _range: _maxValue - _minValue
106 readonly property double _ratio: (_range > 0 && !isNaN(_rawValue) && !isNaN(_minValue))
107 ? Math.max(0, Math.min(1, (_rawValue - _minValue) / _range))
110 height: ScreenTools.defaultFontPixelHeight * 0.95
111 Layout.preferredWidth: _positionBarWidth
112 Layout.fillWidth: false
113 width: _positionBarWidth
114 visible: servoExists(_servoIndex)
115 Layout.row: index + 1
118 readonly property color _trackColor: qgcPal.colorGrey
119 // Use a themed accent so the fill is always clearly distinct from the track
120 readonly property color _progressColor: qgcPal.colorGreen
122 readonly property bool _hasValidValue: pwmValue >= 0 && !isNaN(_rawValue) && !isNaN(_minValue) && !isNaN(_maxValue) && _range > 0
123 // Use the delegate width (cell width), not `parent.width` (Repeater/GridLayout width).
124 readonly property real _progressWidth: _hasValidValue ? Math.max(0, _ratio * width) : 0
126 // Keep the delegate's implicit width stable so GridLayout doesn't reflow
127 // based on changing label text lengths.
136 border.color: qgcPal.text
137 radius: ScreenTools.defaultBorderRadius
141 anchors.top: parent.top
142 anchors.bottom: parent.bottom
143 anchors.left: parent.left
144 width: _progressWidth
145 color: _progressColor
146 radius: ScreenTools.defaultBorderRadius
152 anchors.centerIn: parent
153 horizontalAlignment: Text.AlignHCenter
154 verticalAlignment: Text.AlignVCenter
155 text: _hasValidValue ? Math.round(_rawValue) : "-"
165 function onServoValueChanged(servo, pwmValue) {
166 const item = positionRepeater.itemAt(servo)
168 item.pwmValue = pwmValue
173 // --- Column 2: Function --------------------------------
178 fact: getFact("SERVO" + (index + 1) + "_FUNCTION")
181 visible: servoExists(index + 1)
182 Layout.row: index + 1
187 // --- Column 3: Min ---------------------------------------
192 spacing: ScreenTools.defaultFontPixelWidth / 2
193 visible: servoExists(index + 1)
194 Layout.row: index + 1
197 readonly property var spFact: getFact("SERVO" + (index + 1) + "_MIN")
198 property int _repeatDir: 0
205 onTriggered: repeatTimer.start()
213 onTriggered: if (spFact && _repeatDir !== 0) { spFact.value += _repeatDir }
222 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
223 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
231 repeatInitial.start()
239 FactTextField { fact: spFact; showUnits: false; Layout.fillWidth: true }
246 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
247 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
255 repeatInitial.start()
266 // --- Column 4: Trim --------------------------------------
271 spacing: ScreenTools.defaultFontPixelWidth / 2
272 visible: servoExists(index + 1)
273 Layout.row: index + 1
276 readonly property var spFact: getFact("SERVO" + (index + 1) + "_TRIM")
277 property int _repeatDir: 0
280 id: repeatInitialTrim
284 onTriggered: repeatTimerTrim.start()
292 onTriggered: if (spFact && _repeatDir !== 0) { spFact.value += _repeatDir }
301 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
302 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
310 repeatInitialTrim.start()
314 repeatInitialTrim.stop()
315 repeatTimerTrim.stop()
318 FactTextField { fact: spFact; showUnits: false; Layout.fillWidth: true }
325 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
326 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
334 repeatInitialTrim.start()
338 repeatInitialTrim.stop()
339 repeatTimerTrim.stop()
345 // --- Column 5: Max ---------------------------------------
350 spacing: ScreenTools.defaultFontPixelWidth / 2
351 visible: servoExists(index + 1)
352 Layout.row: index + 1
355 readonly property var spFact: getFact("SERVO" + (index + 1) + "_MAX")
356 property int _repeatDir: 0
363 onTriggered: repeatTimerMax.start()
371 onTriggered: if (spFact && _repeatDir !== 0) { spFact.value += _repeatDir }
380 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
381 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
389 repeatInitialMax.start()
393 repeatInitialMax.stop()
394 repeatTimerMax.stop()
397 FactTextField { fact: spFact; showUnits: false; Layout.fillWidth: true }
404 Layout.preferredWidth: ScreenTools.implicitTextFieldHeight
405 Layout.preferredHeight: ScreenTools.implicitTextFieldHeight
413 repeatInitialMax.start()
417 repeatInitialMax.stop()
418 repeatTimerMax.stop()
424 // --- Column 6: Reversed ---------------------------------
429 fact: getFact("SERVO" + (index + 1) + "_REVERSED")
430 visible: servoExists(index + 1)
431 Layout.row: index + 1
433 Layout.alignment: Qt.AlignHCenter