From 698e6bb16d12b1e7e926f07fcc386a093f8e59f9 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Thu, 13 Oct 2016 18:38:05 -0700 Subject: [PATCH] Switch ArduPIlot setup pages to new SetupPage usage Also added new FirmwarePlugin::brandImage support --- qgcresources.qrc | 2 + .../APM/APMAirframeComponent.cc | 4 +- .../APM/APMAirframeComponent.qml | 102 +- .../APM/APMCameraComponent.cc | 2 +- .../APM/APMCameraComponent.qml | 844 +++++++------ .../APM/APMFlightModesComponent.cc | 2 +- .../APM/APMFlightModesComponent.qml | 28 +- .../APM/APMPowerComponent.qml | 616 +++++----- .../APM/APMSafetyComponent.cc | 2 +- .../APM/APMSafetyComponentCopter.qml | 829 +++++++------ .../APM/APMSafetyComponentPlane.qml | 315 +++-- .../APM/APMSensorsComponent.cc | 3 +- .../APM/APMSensorsComponent.qml | 1084 +++++++++-------- .../APM/APMTuningComponent.cc | 2 +- .../APM/APMTuningComponentCopter.qml | 239 ++-- src/FirmwarePlugin/APM/APMBrandImage.png | Bin 0 -> 27565 bytes src/FirmwarePlugin/APM/APMFirmwarePlugin.h | 1 + src/FirmwarePlugin/FirmwarePlugin.h | 3 + src/FirmwarePlugin/PX4/PX4BrandImage.png | Bin 0 -> 4863 bytes src/FirmwarePlugin/PX4/PX4FirmwarePlugin.h | 1 + src/Vehicle/Vehicle.cc | 5 + src/Vehicle/Vehicle.h | 2 + src/ui/toolbar/MainToolBar.qml | 13 +- 23 files changed, 2000 insertions(+), 2099 deletions(-) create mode 100644 src/FirmwarePlugin/APM/APMBrandImage.png create mode 100644 src/FirmwarePlugin/PX4/PX4BrandImage.png diff --git a/qgcresources.qrc b/qgcresources.qrc index 418f773e9..da73e2a67 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -151,6 +151,8 @@ src/AutoPilotPlugins/Common/Images/ArrowDirection.svg src/AutoPilotPlugins/Common/Images/ArrowCW.svg src/AutoPilotPlugins/Common/Images/ArrowCCW.svg + src/FirmwarePlugin/APM/APMBrandImage.png + src/FirmwarePlugin/PX4/PX4BrandImage.png resources/Antenna_RC.svg diff --git a/src/AutoPilotPlugins/APM/APMAirframeComponent.cc b/src/AutoPilotPlugins/APM/APMAirframeComponent.cc index ccbb3ee0c..d6aedc86d 100644 --- a/src/AutoPilotPlugins/APM/APMAirframeComponent.cc +++ b/src/AutoPilotPlugins/APM/APMAirframeComponent.cc @@ -32,8 +32,8 @@ QString APMAirframeComponent::name(void) const QString APMAirframeComponent::description(void) const { - return tr("The Airframe Component is used to select the airframe which matches your vehicle. " - "This will in turn set up the various tuning values for flight parameters."); + return tr("Airframe Setup is used to select the airframe which matches your vehicle. " + "You can also the load default parameter values associated with known vehicle types."); } QString APMAirframeComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMAirframeComponent.qml b/src/AutoPilotPlugins/APM/APMAirframeComponent.qml index 62ae84b3e..29ec60771 100644 --- a/src/AutoPilotPlugins/APM/APMAirframeComponent.qml +++ b/src/AutoPilotPlugins/APM/APMAirframeComponent.qml @@ -11,26 +11,24 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QGroundControl.FactSystem 1.0 -import QGroundControl.FactControls 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: qgcView - viewPanel: panel - - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } +SetupPage { + id: airframePage + pageComponent: airframePageComponent property real _margins: ScreenTools.defaultFontPixelWidth property Fact _frame: controller.getParameterFact(-1, "FRAME") APMAirframeComponentController { id: controller - factPanel: panel + factPanel: airframePage.viewPanel } ExclusiveGroup { @@ -89,73 +87,47 @@ QGCView { } } - QGCViewPanel { - id: panel - anchors.fill: parent - - Item { - id: helpApplyRow - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: Math.max(helpText.contentHeight, applyButton.height) + Component { + id: airframePageComponent - QGCLabel { - id: helpText - anchors.rightMargin: _margins - anchors.left: parent.left - anchors.right: applyButton.right - text: qsTr("Please select your airframe type") - font.pointSize: ScreenTools.mediumFontPointSize - wrapMode: Text.WordWrap - } + Column { + width: availableWidth + height: 1000 + spacing: _margins - QGCButton { - id: applyButton + RowLayout { + anchors.left: parent.left anchors.right: parent.right - text: qsTr("Load common parameters") - onClicked: showDialog(applyRestartDialogComponent, qsTr("Load common parameters"), qgcView.showDialogDefaultWidth, StandardButton.Close) - } - } - - Item { - id: helpSpacer - anchors.top: helpApplyRow.bottom - height: parent.spacerHeight - width: 10 - } - - QGCFlickable { - id: scroll - anchors.top: helpSpacer.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - contentHeight: frameColumn.height - contentWidth: frameColumn.width - + spacing: _margins + QGCLabel { + font.pointSize: ScreenTools.mediumFontPointSize + wrapMode: Text.WordWrap + text: qsTr("Please select your airframe type") + Layout.fillWidth: true + } - Column { - id: frameColumn - spacing: _margins + QGCButton { + text: qsTr("Load common parameters") + onClicked: showDialog(applyRestartDialogComponent, qsTr("Load common parameters"), qgcView.showDialogDefaultWidth, StandardButton.Close) + } + } - Repeater { - model: controller.airframeTypesModel + Repeater { + model: controller.airframeTypesModel - QGCRadioButton { - text: object.name - checked: controller.currentAirframeType == object - exclusiveGroup: airframeTypeExclusive + QGCRadioButton { + text: object.name + checked: controller.currentAirframeType == object + exclusiveGroup: airframeTypeExclusive - onCheckedChanged: { - if (checked) { - controller.currentAirframeType = object - } + onCheckedChanged: { + if (checked) { + controller.currentAirframeType = object } } } } - } - } // QGCViewPanel -} // QGCView + } // Column + } // Component - pageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMCameraComponent.cc b/src/AutoPilotPlugins/APM/APMCameraComponent.cc index 8a850bb3c..5d597b5e8 100644 --- a/src/AutoPilotPlugins/APM/APMCameraComponent.cc +++ b/src/AutoPilotPlugins/APM/APMCameraComponent.cc @@ -29,7 +29,7 @@ QString APMCameraComponent::name(void) const QString APMCameraComponent::description(void) const { - return tr("The Camera Component is used to setup camera and gimbal settings."); + return tr("Camera setup is used to adjust camera and gimbal settings."); } QString APMCameraComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMCameraComponent.qml b/src/AutoPilotPlugins/APM/APMCameraComponent.qml index 1e7699f47..6ff44a5ce 100644 --- a/src/AutoPilotPlugins/APM/APMCameraComponent.qml +++ b/src/AutoPilotPlugins/APM/APMCameraComponent.qml @@ -17,440 +17,436 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _cameraView - viewPanel: panel - anchors.fill: parent - - FactPanelController { id: controller; factPanel: panel } - - QGCPalette { id: palette; colorGroupEnabled: enabled } - - property Fact _mountRetractX: controller.getParameterFact(-1, "MNT_RETRACT_X") - property Fact _mountRetractY: controller.getParameterFact(-1, "MNT_RETRACT_Y") - property Fact _mountRetractZ: controller.getParameterFact(-1, "MNT_RETRACT_Z") - - property Fact _mountNeutralX: controller.getParameterFact(-1, "MNT_NEUTRAL_X") - property Fact _mountNeutralY: controller.getParameterFact(-1, "MNT_NEUTRAL_Y") - property Fact _mountNeutralZ: controller.getParameterFact(-1, "MNT_NEUTRAL_Z") - - property Fact _mountRCInTilt: controller.getParameterFact(-1, "MNT_RC_IN_TILT") - property Fact _mountStabTilt: controller.getParameterFact(-1, "MNT_STAB_TILT") - property Fact _mountAngMinTilt: controller.getParameterFact(-1, "MNT_ANGMIN_TIL") - property Fact _mountAngMaxTilt: controller.getParameterFact(-1, "MNT_ANGMAX_TIL") - - property Fact _mountRCInRoll: controller.getParameterFact(-1, "MNT_RC_IN_ROLL") - property Fact _mountStabRoll: controller.getParameterFact(-1, "MNT_STAB_ROLL") - property Fact _mountAngMinRoll: controller.getParameterFact(-1, "MNT_ANGMIN_ROL") - property Fact _mountAngMaxRoll: controller.getParameterFact(-1, "MNT_ANGMAX_ROL") - - property Fact _mountRCInPan: controller.getParameterFact(-1, "MNT_RC_IN_PAN") - property Fact _mountStabPan: controller.getParameterFact(-1, "MNT_STAB_PAN") - property Fact _mountAngMinPan: controller.getParameterFact(-1, "MNT_ANGMIN_PAN") - property Fact _mountAngMaxPan: controller.getParameterFact(-1, "MNT_ANGMAX_PAN") - - property Fact _rc5Function: controller.getParameterFact(-1, "RC5_FUNCTION") - property Fact _rc6Function: controller.getParameterFact(-1, "RC6_FUNCTION") - property Fact _rc7Function: controller.getParameterFact(-1, "RC7_FUNCTION") - property Fact _rc8Function: controller.getParameterFact(-1, "RC8_FUNCTION") - property Fact _rc9Function: controller.getParameterFact(-1, "RC9_FUNCTION") - property Fact _rc10Function: controller.getParameterFact(-1, "RC10_FUNCTION") - property Fact _rc11Function: controller.getParameterFact(-1, "RC11_FUNCTION") - property Fact _rc12Function: controller.getParameterFact(-1, "RC12_FUNCTION") - property Fact _rc13Function: controller.getParameterFact(-1, "RC13_FUNCTION") - property Fact _rc14Function: controller.getParameterFact(-1, "RC14_FUNCTION") - - property bool _tiltEnabled: false - property bool _panEnabled: false - property bool _rollEnabled: false - - // Gimbal Settings not available on older firmware - property bool _showGimbaLSettings: controller.parameterExists(-1, "MNT_DEFLT_MODE") - - readonly property real _margins: ScreenTools.defaultFontPixelHeight - readonly property int _rcFunctionDisabled: 0 - readonly property int _rcFunctionMountPan: 6 - readonly property int _rcFunctionMountTilt: 7 - readonly property int _rcFunctionMountRoll: 8 - readonly property int _firstGimbalOutChannel: 5 - readonly property int _lastGimbalOutChannel: 14 - readonly property int _mountDefaultModeRCTargetting: 3 - - Component.onCompleted: { - if (_showGimbaLSettings) { - gimbalSettingsLoader.sourceComponent = gimbalSettings - } - calcGimbalOutValues() - } - - function setGimbalSettingsServoInfo(loader, channel) { - var rcPrefix = "RC" + channel + "_" - - loader.gimbalOutIndex = channel - 4 - loader.servoPWMMinFact = controller.getParameterFact(-1, rcPrefix + "MIN") - loader.servoPWMMaxFact = controller.getParameterFact(-1, rcPrefix + "MAX") - loader.servoReverseFact = controller.getParameterFact(-1, rcPrefix + "REV") - } - - /// Gimbal output channels are stored in RC#_FUNCTION parameters. We need to loop through those - /// to find them and setup the ui accordindly. - function calcGimbalOutValues() { - gimbalDirectionTiltLoader.gimbalOutIndex = 0 - gimbalDirectionPanLoader.gimbalOutIndex = 0 - gimbalDirectionRollLoader.gimbalOutIndex = 0 - _tiltEnabled = false - _panEnabled = false - _rollEnabled = false - for (var channel=_firstGimbalOutChannel; channel<=_lastGimbalOutChannel; channel++) { - var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") - if (functionFact.value == _rcFunctionMountTilt) { - _tiltEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionTiltLoader, channel) - } else if (functionFact.value == _rcFunctionMountPan) { - _panEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionPanLoader, channel) - } else if (functionFact.value == _rcFunctionMountRoll) { - _rollEnabled = true - setGimbalSettingsServoInfo(gimbalDirectionRollLoader, channel) - } - } - } - - function setRCFunction(channel, rcFunction) { - // First clear any previous settings for this function - for (var index=_firstGimbalOutChannel; index<=_lastGimbalOutChannel; index++) { - var functionFact = controller.getParameterFact(-1, "RC" + index + "_FUNCTION") - if (functionFact.value != _rcFunctionDisabled && functionFact.value == rcFunction) { - functionFact.value = _rcFunctionDisabled - } - } - - // Now set the function into the new channel - if (channel != 0) { - var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") - functionFact.value = rcFunction - } - } - - // Whenever any RC#_FUNCTION parameters chagnes we need to go looking for gimbal output channels again - Connections { target: _rc5Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc6Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc7Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc8Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc9Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc10Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc11Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc12Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc13Function; onValueChanged: calcGimbalOutValues() } - Connections { target: _rc14Function; onValueChanged: calcGimbalOutValues() } - - // Whenever an MNT_RC_IN_* setting is changed make sure to turn on RC targeting - Connections { - target: _mountRCInPan - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - Connections { - target: _mountRCInRoll - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - Connections { - target: _mountRCInTilt - onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting - } - - ListModel { - id: gimbalOutModel - ListElement { text: qsTr("Disabled"); value: 0 } - ListElement { text: qsTr("Channel 5"); value: 5 } - ListElement { text: qsTr("Channel 6"); value: 6 } - ListElement { text: qsTr("Channel 7"); value: 7 } - ListElement { text: qsTr("Channel 8"); value: 8 } - ListElement { text: qsTr("Channel 9"); value: 9 } - ListElement { text: qsTr("Channel 10"); value: 10 } - ListElement { text: qsTr("Channel 11"); value: 11 } - ListElement { text: qsTr("Channel 12"); value: 12 } - ListElement { text: qsTr("Channel 13"); value: 13 } - ListElement { text: qsTr("Channel 14"); value: 14 } - } +SetupPage { + id: cameraPage + pageComponent: cameraPageComponent Component { - id: gimbalDirectionSettings - - // The following properties must be set in the Loader - // property string directionTitle - // property bool directionEnabled - // property int gimbalOutIndex - // property Fact mountRcInFact - // property Fact mountStabFact - // property Fact mountAngMinFact - // property Fact mountAngMaxFact - // property Fact servoPWMMinFact - // property Fact servoPWMMaxFact - // property Fact servoReverseFact - // property int rcFunction - - Item { - width: rectangle.x + rectangle.width - height: rectangle.y + rectangle.height - - QGCLabel { - id: directionLabel - text: qsTr("Gimbal ") + directionTitle - font.family: ScreenTools.demiboldFontFamily - } - - Rectangle { - id: rectangle - anchors.topMargin: _margins / 2 - anchors.left: parent.left - anchors.top: directionLabel.bottom - width: mountAngMaxField.x + mountAngMaxField.width + _margins - height: servoPWMMaxField.y + servoPWMMaxField.height + _margins - color: palette.windowShade - - FactCheckBox { - id: mountStabCheckBox - anchors.topMargin: _margins - anchors.left: servoReverseCheckBox.left - anchors.top: parent.top - text: qsTr("Stabilize") - fact: mountStabFact - checkedValue: 1 - uncheckedValue: 0 - enabled: directionEnabled - } - - FactCheckBox { - id: servoReverseCheckBox - anchors.margins: _margins - anchors.top: mountStabCheckBox.bottom - anchors.right: parent.right - text: qsTr("Servo reverse") - checkedValue: 1 - uncheckedValue: 0 - fact: servoReverseFact - enabled: directionEnabled - } - - QGCLabel { - id: gimbalOutLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalOutCombo.baseline - text: qsTr("Output channel:") - } - - QGCComboBox { - id: gimbalOutCombo - anchors.margins: _margins - anchors.top: parent.top - anchors.left: gimbalOutLabel.right - width: mountAngMinField.width - model: gimbalOutModel - currentIndex: gimbalOutIndex - - onActivated: setRCFunction(gimbalOutModel.get(index).value, rcFunction) - } - - QGCLabel { - id: mountRcInLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mountRcInCombo.baseline - text: qsTr("Input channel:") - enabled: directionEnabled - } - - FactComboBox { - id: mountRcInCombo - anchors.topMargin: _margins / 2 - anchors.top: gimbalOutCombo.bottom - anchors.left: gimbalOutCombo.left - width: mountAngMinField.width - fact: mountRcInFact - indexModel: false - enabled: directionEnabled - } - - QGCLabel { - id: mountAngLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mountAngMinField.baseline - text: qsTr("Gimbal angle limits:") - enabled: directionEnabled - } - - QGCLabel { - id: mountAngMinLabel - anchors.margins: _margins - anchors.left: mountAngLabel.right - anchors.baseline: mountAngMinField.baseline - text: qsTr("min") - enabled: directionEnabled - } - - FactTextField { - id: mountAngMinField - anchors.margins: _margins - anchors.top: mountRcInCombo.bottom - anchors.left: mountAngMinLabel.right - fact: mountAngMinFact - enabled: directionEnabled - } - - QGCLabel { - id: mountAngMaxLabel - anchors.margins: _margins - anchors.left: mountAngMinField.right - anchors.baseline: mountAngMinField.baseline - text: qsTr("max") - enabled: directionEnabled - } - - FactTextField { - id: mountAngMaxField - anchors.leftMargin: _margins - anchors.top: mountAngMinField.top - anchors.left: mountAngMaxLabel.right - fact: mountAngMaxFact - enabled: directionEnabled - } - - QGCLabel { - id: servoPWMLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: servoPWMMinField.baseline - text: qsTr("Servo PWM limits:") - enabled: directionEnabled - } - - QGCLabel { - id: servoPWMMinLabel - anchors.left: mountAngMinLabel.left - anchors.baseline: servoPWMMinField.baseline - text: qsTr("min") - enabled: directionEnabled + id: cameraPageComponent + + Column { + spacing: _margins + width: availableWidth + + FactPanelController { id: controller; factPanel: cameraPage.viewPanel } + + QGCPalette { id: palette; colorGroupEnabled: true } + + property Fact _mountRetractX: controller.getParameterFact(-1, "MNT_RETRACT_X") + property Fact _mountRetractY: controller.getParameterFact(-1, "MNT_RETRACT_Y") + property Fact _mountRetractZ: controller.getParameterFact(-1, "MNT_RETRACT_Z") + + property Fact _mountNeutralX: controller.getParameterFact(-1, "MNT_NEUTRAL_X") + property Fact _mountNeutralY: controller.getParameterFact(-1, "MNT_NEUTRAL_Y") + property Fact _mountNeutralZ: controller.getParameterFact(-1, "MNT_NEUTRAL_Z") + + property Fact _mountRCInTilt: controller.getParameterFact(-1, "MNT_RC_IN_TILT") + property Fact _mountStabTilt: controller.getParameterFact(-1, "MNT_STAB_TILT") + property Fact _mountAngMinTilt: controller.getParameterFact(-1, "MNT_ANGMIN_TIL") + property Fact _mountAngMaxTilt: controller.getParameterFact(-1, "MNT_ANGMAX_TIL") + + property Fact _mountRCInRoll: controller.getParameterFact(-1, "MNT_RC_IN_ROLL") + property Fact _mountStabRoll: controller.getParameterFact(-1, "MNT_STAB_ROLL") + property Fact _mountAngMinRoll: controller.getParameterFact(-1, "MNT_ANGMIN_ROL") + property Fact _mountAngMaxRoll: controller.getParameterFact(-1, "MNT_ANGMAX_ROL") + + property Fact _mountRCInPan: controller.getParameterFact(-1, "MNT_RC_IN_PAN") + property Fact _mountStabPan: controller.getParameterFact(-1, "MNT_STAB_PAN") + property Fact _mountAngMinPan: controller.getParameterFact(-1, "MNT_ANGMIN_PAN") + property Fact _mountAngMaxPan: controller.getParameterFact(-1, "MNT_ANGMAX_PAN") + + property Fact _rc5Function: controller.getParameterFact(-1, "RC5_FUNCTION") + property Fact _rc6Function: controller.getParameterFact(-1, "RC6_FUNCTION") + property Fact _rc7Function: controller.getParameterFact(-1, "RC7_FUNCTION") + property Fact _rc8Function: controller.getParameterFact(-1, "RC8_FUNCTION") + property Fact _rc9Function: controller.getParameterFact(-1, "RC9_FUNCTION") + property Fact _rc10Function: controller.getParameterFact(-1, "RC10_FUNCTION") + property Fact _rc11Function: controller.getParameterFact(-1, "RC11_FUNCTION") + property Fact _rc12Function: controller.getParameterFact(-1, "RC12_FUNCTION") + property Fact _rc13Function: controller.getParameterFact(-1, "RC13_FUNCTION") + property Fact _rc14Function: controller.getParameterFact(-1, "RC14_FUNCTION") + + property bool _tiltEnabled: false + property bool _panEnabled: false + property bool _rollEnabled: false + + // Gimbal Settings not available on older firmware + property bool _showGimbaLSettings: controller.parameterExists(-1, "MNT_DEFLT_MODE") + + readonly property real _margins: ScreenTools.defaultFontPixelHeight + readonly property int _rcFunctionDisabled: 0 + readonly property int _rcFunctionMountPan: 6 + readonly property int _rcFunctionMountTilt: 7 + readonly property int _rcFunctionMountRoll: 8 + readonly property int _firstGimbalOutChannel: 5 + readonly property int _lastGimbalOutChannel: 14 + readonly property int _mountDefaultModeRCTargetting: 3 + + Component.onCompleted: { + if (_showGimbaLSettings) { + gimbalSettingsLoader.sourceComponent = gimbalSettings } + calcGimbalOutValues() + } - FactTextField { - id: servoPWMMinField - anchors.topMargin: _margins / 2 - anchors.leftMargin: _margins - anchors.top: mountAngMaxField.bottom - anchors.left: servoPWMMinLabel.right - fact: servoPWMMinFact - enabled: directionEnabled - } + function setGimbalSettingsServoInfo(loader, channel) { + var rcPrefix = "RC" + channel + "_" - QGCLabel { - id: servoPWMMaxLabel - anchors.margins: _margins - anchors.left: servoPWMMinField.right - anchors.baseline: servoPWMMinField.baseline - text: qsTr("max") - enabled: directionEnabled - } + loader.gimbalOutIndex = channel - 4 + loader.servoPWMMinFact = controller.getParameterFact(-1, rcPrefix + "MIN") + loader.servoPWMMaxFact = controller.getParameterFact(-1, rcPrefix + "MAX") + loader.servoReverseFact = controller.getParameterFact(-1, rcPrefix + "REV") + } - FactTextField { - id: servoPWMMaxField - anchors.leftMargin: _margins - anchors.top: servoPWMMinField.top - anchors.left: servoPWMMaxLabel.right - fact: servoPWMMaxFact - enabled: directionEnabled + /// Gimbal output channels are stored in RC#_FUNCTION parameters. We need to loop through those + /// to find them and setup the ui accordindly. + function calcGimbalOutValues() { + gimbalDirectionTiltLoader.gimbalOutIndex = 0 + gimbalDirectionPanLoader.gimbalOutIndex = 0 + gimbalDirectionRollLoader.gimbalOutIndex = 0 + _tiltEnabled = false + _panEnabled = false + _rollEnabled = false + for (var channel=_firstGimbalOutChannel; channel<=_lastGimbalOutChannel; channel++) { + var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") + if (functionFact.value == _rcFunctionMountTilt) { + _tiltEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionTiltLoader, channel) + } else if (functionFact.value == _rcFunctionMountPan) { + _panEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionPanLoader, channel) + } else if (functionFact.value == _rcFunctionMountRoll) { + _rollEnabled = true + setGimbalSettingsServoInfo(gimbalDirectionRollLoader, channel) + } } - } // Rectangle - } // Item - } // Component - gimbalDirectionSettings - - Component { - id: gimbalSettings - - Item { - width: rectangle.x + rectangle.width - height: rectangle.y + rectangle.height - - property Fact _mountDefaultMode: controller.getParameterFact(-1, "MNT_DEFLT_MODE") - property Fact _mountType: controller.getParameterFact(-1, "MNT_TYPE") - - QGCLabel { - id: settingsLabel - text: qsTr("Gimbal Settings") - font.family: ScreenTools.demiboldFontFamily } - Rectangle { - id: rectangle - anchors.topMargin: _margins / 2 - anchors.top: settingsLabel.bottom - width: gimbalModeCombo.x + gimbalModeCombo.width + _margins - height: gimbalModeCombo.y + gimbalModeCombo.height + _margins - color: palette.windowShade - - QGCLabel { - id: gimbalTypeLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalTypeCombo.baseline - text: qsTr("Type:") + function setRCFunction(channel, rcFunction) { + // First clear any previous settings for this function + for (var index=_firstGimbalOutChannel; index<=_lastGimbalOutChannel; index++) { + var functionFact = controller.getParameterFact(-1, "RC" + index + "_FUNCTION") + if (functionFact.value != _rcFunctionDisabled && functionFact.value == rcFunction) { + functionFact.value = _rcFunctionDisabled + } } - FactComboBox { - id: gimbalTypeCombo - anchors.topMargin: _margins - anchors.top: parent.top - anchors.left: gimbalModeCombo.left - width: gimbalModeCombo.width - fact: _mountType - indexModel: false + // Now set the function into the new channel + if (channel != 0) { + var functionFact = controller.getParameterFact(-1, "RC" + channel + "_FUNCTION") + functionFact.value = rcFunction } + } - QGCLabel { - id: rebootLabel - anchors.topMargin: _margins / 2 - anchors.leftMargin: _margins - anchors.rightMargin: _margins - anchors.left: parent.left - anchors.right: parent.right - anchors.top: gimbalTypeCombo.bottom - wrapMode: Text.WordWrap - text: qsTr("Gimbal Type changes takes affect next reboot of autopilot") - } + // Whenever any RC#_FUNCTION parameters chagnes we need to go looking for gimbal output channels again + Connections { target: _rc5Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc6Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc7Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc8Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc9Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc10Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc11Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc12Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc13Function; onValueChanged: calcGimbalOutValues() } + Connections { target: _rc14Function; onValueChanged: calcGimbalOutValues() } + + // Whenever an MNT_RC_IN_* setting is changed make sure to turn on RC targeting + Connections { + target: _mountRCInPan + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - QGCLabel { - id: gimbalModeLabel - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: gimbalModeCombo.baseline - text: qsTr("Default Mode:") - } + Connections { + target: _mountRCInRoll + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - FactComboBox { - id: gimbalModeCombo - anchors.margins: _margins - anchors.top: rebootLabel.bottom - anchors.left: gimbalModeLabel.right - width: ScreenTools.defaultFontPixelWidth * 15 - fact: _mountDefaultMode - indexModel: false - } - } // Rectangle - } // Item - } // Component - gimbalSettings + Connections { + target: _mountRCInTilt + onValueChanged: _mountDefaultMode.value = _mountDefaultModeRCTargetting + } - QGCViewPanel { - id: panel - anchors.fill: parent + ListModel { + id: gimbalOutModel + ListElement { text: qsTr("Disabled"); value: 0 } + ListElement { text: qsTr("Channel 5"); value: 5 } + ListElement { text: qsTr("Channel 6"); value: 6 } + ListElement { text: qsTr("Channel 7"); value: 7 } + ListElement { text: qsTr("Channel 8"); value: 8 } + ListElement { text: qsTr("Channel 9"); value: 9 } + ListElement { text: qsTr("Channel 10"); value: 10 } + ListElement { text: qsTr("Channel 11"); value: 11 } + ListElement { text: qsTr("Channel 12"); value: 12 } + ListElement { text: qsTr("Channel 13"); value: 13 } + ListElement { text: qsTr("Channel 14"); value: 14 } + } - QGCFlickable { - clip: true - anchors.fill: parent - contentWidth: gimbalDirectionTiltLoader.x + gimbalDirectionTiltLoader.width - contentHeight: _showGimbaLSettings ? gimbalSettingsLoader.y + gimbalSettingsLoader.height : gimbalDirectionPanLoader.y + gimbalDirectionPanLoader.height + Component { + id: gimbalDirectionSettings + + // The following properties must be set in the Loader + // property string directionTitle + // property bool directionEnabled + // property int gimbalOutIndex + // property Fact mountRcInFact + // property Fact mountStabFact + // property Fact mountAngMinFact + // property Fact mountAngMaxFact + // property Fact servoPWMMinFact + // property Fact servoPWMMaxFact + // property Fact servoReverseFact + // property int rcFunction + + Item { + width: rectangle.x + rectangle.width + height: rectangle.y + rectangle.height + + QGCLabel { + id: directionLabel + text: qsTr("Gimbal ") + directionTitle + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + id: rectangle + anchors.topMargin: _margins / 2 + anchors.left: parent.left + anchors.top: directionLabel.bottom + width: mountAngMaxField.x + mountAngMaxField.width + _margins + height: servoPWMMaxField.y + servoPWMMaxField.height + _margins + color: palette.windowShade + + FactCheckBox { + id: mountStabCheckBox + anchors.topMargin: _margins + anchors.left: servoReverseCheckBox.left + anchors.top: parent.top + text: qsTr("Stabilize") + fact: mountStabFact + checkedValue: 1 + uncheckedValue: 0 + enabled: directionEnabled + } + + FactCheckBox { + id: servoReverseCheckBox + anchors.margins: _margins + anchors.top: mountStabCheckBox.bottom + anchors.right: parent.right + text: qsTr("Servo reverse") + checkedValue: 1 + uncheckedValue: 0 + fact: servoReverseFact + enabled: directionEnabled + } + + QGCLabel { + id: gimbalOutLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalOutCombo.baseline + text: qsTr("Output channel:") + } + + QGCComboBox { + id: gimbalOutCombo + anchors.margins: _margins + anchors.top: parent.top + anchors.left: gimbalOutLabel.right + width: mountAngMinField.width + model: gimbalOutModel + currentIndex: gimbalOutIndex + + onActivated: setRCFunction(gimbalOutModel.get(index).value, rcFunction) + } + + QGCLabel { + id: mountRcInLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mountRcInCombo.baseline + text: qsTr("Input channel:") + enabled: directionEnabled + } + + FactComboBox { + id: mountRcInCombo + anchors.topMargin: _margins / 2 + anchors.top: gimbalOutCombo.bottom + anchors.left: gimbalOutCombo.left + width: mountAngMinField.width + fact: mountRcInFact + indexModel: false + enabled: directionEnabled + } + + QGCLabel { + id: mountAngLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mountAngMinField.baseline + text: qsTr("Gimbal angle limits:") + enabled: directionEnabled + } + + QGCLabel { + id: mountAngMinLabel + anchors.margins: _margins + anchors.left: mountAngLabel.right + anchors.baseline: mountAngMinField.baseline + text: qsTr("min") + enabled: directionEnabled + } + + FactTextField { + id: mountAngMinField + anchors.margins: _margins + anchors.top: mountRcInCombo.bottom + anchors.left: mountAngMinLabel.right + fact: mountAngMinFact + enabled: directionEnabled + } + + QGCLabel { + id: mountAngMaxLabel + anchors.margins: _margins + anchors.left: mountAngMinField.right + anchors.baseline: mountAngMinField.baseline + text: qsTr("max") + enabled: directionEnabled + } + + FactTextField { + id: mountAngMaxField + anchors.leftMargin: _margins + anchors.top: mountAngMinField.top + anchors.left: mountAngMaxLabel.right + fact: mountAngMaxFact + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: servoPWMMinField.baseline + text: qsTr("Servo PWM limits:") + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMMinLabel + anchors.left: mountAngMinLabel.left + anchors.baseline: servoPWMMinField.baseline + text: qsTr("min") + enabled: directionEnabled + } + + FactTextField { + id: servoPWMMinField + anchors.topMargin: _margins / 2 + anchors.leftMargin: _margins + anchors.top: mountAngMaxField.bottom + anchors.left: servoPWMMinLabel.right + fact: servoPWMMinFact + enabled: directionEnabled + } + + QGCLabel { + id: servoPWMMaxLabel + anchors.margins: _margins + anchors.left: servoPWMMinField.right + anchors.baseline: servoPWMMinField.baseline + text: qsTr("max") + enabled: directionEnabled + } + + FactTextField { + id: servoPWMMaxField + anchors.leftMargin: _margins + anchors.top: servoPWMMinField.top + anchors.left: servoPWMMaxLabel.right + fact: servoPWMMaxFact + enabled: directionEnabled + } + } // Rectangle + } // Item + } // Component - gimbalDirectionSettings + + Component { + id: gimbalSettings + + Item { + width: rectangle.x + rectangle.width + height: rectangle.y + rectangle.height + + property Fact _mountDefaultMode: controller.getParameterFact(-1, "MNT_DEFLT_MODE") + property Fact _mountType: controller.getParameterFact(-1, "MNT_TYPE") + + QGCLabel { + id: settingsLabel + text: qsTr("Gimbal Settings") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + id: rectangle + anchors.topMargin: _margins / 2 + anchors.top: settingsLabel.bottom + width: gimbalModeCombo.x + gimbalModeCombo.width + _margins + height: gimbalModeCombo.y + gimbalModeCombo.height + _margins + color: palette.windowShade + + QGCLabel { + id: gimbalTypeLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalTypeCombo.baseline + text: qsTr("Type:") + } + + FactComboBox { + id: gimbalTypeCombo + anchors.topMargin: _margins + anchors.top: parent.top + anchors.left: gimbalModeCombo.left + width: gimbalModeCombo.width + fact: _mountType + indexModel: false + } + + QGCLabel { + id: rebootLabel + anchors.topMargin: _margins / 2 + anchors.leftMargin: _margins + anchors.rightMargin: _margins + anchors.left: parent.left + anchors.right: parent.right + anchors.top: gimbalTypeCombo.bottom + wrapMode: Text.WordWrap + text: qsTr("Gimbal Type changes takes affect next reboot of autopilot") + } + + QGCLabel { + id: gimbalModeLabel + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: gimbalModeCombo.baseline + text: qsTr("Default Mode:") + } + + FactComboBox { + id: gimbalModeCombo + anchors.margins: _margins + anchors.top: rebootLabel.bottom + anchors.left: gimbalModeLabel.right + width: ScreenTools.defaultFontPixelWidth * 15 + fact: _mountDefaultMode + indexModel: false + } + } // Rectangle + } // Item + } // Component - gimbalSettings Loader { id: gimbalDirectionTiltLoader @@ -471,8 +467,6 @@ QGCView { Loader { id: gimbalDirectionRollLoader - anchors.margins: _margins - anchors.top: gimbalDirectionTiltLoader.bottom sourceComponent: gimbalDirectionSettings property string directionTitle: qsTr("Roll") @@ -490,8 +484,6 @@ QGCView { Loader { id: gimbalDirectionPanLoader - anchors.margins: _margins - anchors.top: gimbalDirectionRollLoader.bottom sourceComponent: gimbalDirectionSettings property string directionTitle: qsTr("Pan") @@ -508,10 +500,8 @@ QGCView { } Loader { - id: gimbalSettingsLoader - anchors.margins: _margins - anchors.top: gimbalDirectionPanLoader.bottom + id: gimbalSettingsLoader } - } // Flickable - } // QGCViewPanel -} // QGCView + } // Column + } // Component +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc b/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc index 7229a8ad9..6a2babe08 100644 --- a/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc +++ b/src/AutoPilotPlugins/APM/APMFlightModesComponent.cc @@ -26,7 +26,7 @@ QString APMFlightModesComponent::name(void) const QString APMFlightModesComponent::description(void) const { - return QStringLiteral("The Flight Modes Component is used to assign FLight Modes to Channel 5."); + return tr("Flight Modes Setup is used to configure the transmitter switches associated with Flight Modes."); } QString APMFlightModesComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml b/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml index f958c3c23..623be0903 100644 --- a/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml +++ b/src/AutoPilotPlugins/APM/APMFlightModesComponent.qml @@ -18,9 +18,9 @@ import QGroundControl.Controls 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: rootQGCView - viewPanel: panel +SetupPage { + id: flightModePage + pageComponent: flightModePageComponent readonly property string _modeChannelParam: controller.modeChannelParam readonly property string _modeParamPrefix: controller.modeParamPrefix @@ -33,26 +33,19 @@ QGCView { property bool _fltmodeChExists: controller.parameterExists(-1, _modeChannelParam) property Fact _fltmodeCh: _fltmodeChExists ? controller.getParameterFact(-1, _modeChannelParam) : _nullFact - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } + QGCPalette { id: qgcPal; colorGroupEnabled: true } APMFlightModesComponentController { id: controller - factPanel: panel + factPanel: flightModePage.viewPanel } - QGCViewPanel { - id: panel - anchors.fill: parent - - QGCFlickable { - anchors.fill: parent - clip: true - contentHeight: flowLayout.height - contentWidth: flowLayout.width + Component { + id: flightModePageComponent Flow { id: flowLayout - width: panel.width // parent.width doesn't work here for some reason! + width: availableWidth spacing: _margins Column { @@ -181,6 +174,5 @@ QGCView { } // Rectangle - Channel options } // Column - Channel options } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } // Component - flightModePageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMPowerComponent.qml b/src/AutoPilotPlugins/APM/APMPowerComponent.qml index 410945bf7..9a1c6b19e 100644 --- a/src/AutoPilotPlugins/APM/APMPowerComponent.qml +++ b/src/AutoPilotPlugins/APM/APMPowerComponent.qml @@ -11,6 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import QGroundControl.FactSystem 1.0 import QGroundControl.FactControls 1.0 @@ -18,384 +19,327 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: rootQGCView - viewPanel: panel - - property Fact battAmpPerVolt: controller.getParameterFact(-1, "BATT_AMP_PERVOLT") - property Fact battCapacity: controller.getParameterFact(-1, "BATT_CAPACITY") - property Fact battCurrPin: controller.getParameterFact(-1, "BATT_CURR_PIN") - property Fact battMonitor: controller.getParameterFact(-1, "BATT_MONITOR") - property Fact battVoltMult: controller.getParameterFact(-1, "BATT_VOLT_MULT") - property Fact battVoltPin: controller.getParameterFact(-1, "BATT_VOLT_PIN") - - property real _margins: ScreenTools.defaultFontPixelHeight - property bool _showAdvanced: sensorCombo.currentIndex == sensorModel.count - 1 - - Component.onCompleted: calcSensor() - - function calcSensor() { - for (var i=0; i 0 - text: qsTr("Loiter above Home for:") + QGCRadioButton { + id: returnAltRadio + anchors.topMargin: _margins + anchors.left: returnAtCurrentRadio.left + anchors.top: returnAtCurrentRadio.bottom + text: qsTr("Return at specified altitude:") + exclusiveGroup: returnAltRadioGroup + checked: _rtlAltFact.value != 0 - onClicked: _rtlLoitTimeFact.value = (checked ? 60 : 0) - } + onClicked: _rtlAltFact.value = 1500 + } - FactTextField { - id: landDelayField - anchors.topMargin: _margins * 1.5 - anchors.left: rltAltField.left - anchors.top: rltAltField.bottom - fact: _rtlLoitTimeFact - showUnits: true - enabled: homeLoiterCheckbox.checked === true - } + FactTextField { + id: rltAltField + anchors.leftMargin: _margins + anchors.left: returnAltRadio.right + anchors.baseline: returnAltRadio.baseline + fact: _rtlAltFact + showUnits: true + enabled: returnAltRadio.checked + } - QGCRadioButton { - id: landRadio - anchors.left: returnAtCurrentRadio.left - anchors.baseline: landSpeedField.baseline - text: qsTr("Land with descent speed:") - checked: _rtlAltFinalFact.value == 0 - exclusiveGroup: landLoiterRadioGroup + QGCCheckBox { + id: homeLoiterCheckbox + anchors.left: returnAtCurrentRadio.left + anchors.baseline: landDelayField.baseline + checked: _rtlLoitTimeFact.value > 0 + text: qsTr("Loiter above Home for:") - onClicked: _rtlAltFinalFact.value = 0 - } + onClicked: _rtlLoitTimeFact.value = (checked ? 60 : 0) + } - FactTextField { - id: landSpeedField - anchors.topMargin: _margins * 1.5 - anchors.top: landDelayField.bottom - anchors.left: rltAltField.left - fact: _landSpeedFact - showUnits: true - enabled: landRadio.checked - } + FactTextField { + id: landDelayField + anchors.topMargin: _margins * 1.5 + anchors.left: rltAltField.left + anchors.top: rltAltField.bottom + fact: _rtlLoitTimeFact + showUnits: true + enabled: homeLoiterCheckbox.checked === true + } - QGCRadioButton { - id: finalLoiterRadio - anchors.left: returnAtCurrentRadio.left - anchors.baseline: rltAltFinalField.baseline - text: qsTr("Final loiter altitude:") - exclusiveGroup: landLoiterRadioGroup + QGCRadioButton { + id: landRadio + anchors.left: returnAtCurrentRadio.left + anchors.baseline: landSpeedField.baseline + text: qsTr("Land with descent speed:") + checked: _rtlAltFinalFact.value == 0 + exclusiveGroup: landLoiterRadioGroup - onClicked: _rtlAltFinalFact.value = _rtlAltFact.value - } + onClicked: _rtlAltFinalFact.value = 0 + } - FactTextField { - id: rltAltFinalField - anchors.topMargin: _margins / 2 - anchors.left: rltAltField.left - anchors.top: landSpeedField.bottom - fact: _rtlAltFinalFact - enabled: finalLoiterRadio.checked - showUnits: true - } - } // Rectangle - RTL Settings - } // Column - RTL Settings + FactTextField { + id: landSpeedField + anchors.topMargin: _margins * 1.5 + anchors.top: landDelayField.bottom + anchors.left: rltAltField.left + fact: _landSpeedFact + showUnits: true + enabled: landRadio.checked + } - Column { - spacing: _margins / 2 + QGCRadioButton { + id: finalLoiterRadio + anchors.left: returnAtCurrentRadio.left + anchors.baseline: rltAltFinalField.baseline + text: qsTr("Final loiter altitude:") + exclusiveGroup: landLoiterRadioGroup - QGCLabel { - text: qsTr("Arming Checks") - font.family: ScreenTools.demiboldFontFamily + onClicked: _rtlAltFinalFact.value = _rtlAltFact.value } - Rectangle { - width: flowLayout.width - height: armingCheckInnerColumn.height + (_margins * 2) - color: ggcPal.windowShade - - Column { - id: armingCheckInnerColumn - anchors.margins: _margins - anchors.top: parent.top + FactTextField { + id: rltAltFinalField + anchors.topMargin: _margins / 2 + anchors.left: rltAltField.left + anchors.top: landSpeedField.bottom + fact: _rtlAltFinalFact + enabled: finalLoiterRadio.checked + showUnits: true + } + } // Rectangle - RTL Settings + } // Column - RTL Settings + + Column { + spacing: _margins / 2 + + QGCLabel { + text: qsTr("Arming Checks") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + width: flowLayout.width + height: armingCheckInnerColumn.height + (_margins * 2) + color: ggcPal.windowShade + + Column { + id: armingCheckInnerColumn + anchors.margins: _margins + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: _margins + + FactBitmask { + id: armingCheckBitmask anchors.left: parent.left anchors.right: parent.right - spacing: _margins - - FactBitmask { - id: armingCheckBitmask - anchors.left: parent.left - anchors.right: parent.right - firstEntryIsAll: true - fact: _armingCheck - } + firstEntryIsAll: true + fact: _armingCheck + } - QGCLabel { - id: armingCheckWarning - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - color: qgcPal.warningText - text: qsTr("Warning: Turning off arming checks can lead to loss of Vehicle control.") - visible: _armingCheck.value != 1 - } + QGCLabel { + id: armingCheckWarning + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + color: qgcPal.warningText + text: qsTr("Warning: Turning off arming checks can lead to loss of Vehicle control.") + visible: _armingCheck.value != 1 } - } // Rectangle - Arming checks - } // Column - Arming Checks - } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } + } // Rectangle - Arming checks + } // Column - Arming Checks + } // Flow + } // Component - safetyPageComponent +} // SetupView diff --git a/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml b/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml index d1934d3d1..6b1515b38 100644 --- a/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml +++ b/src/AutoPilotPlugins/APM/APMSafetyComponentPlane.qml @@ -18,180 +18,171 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _safetyView - viewPanel: panel - anchors.fill: parent +SetupPage { + id: safetyPage + pageComponent: safetyPageComponent - FactPanelController { id: controller; factPanel: panel } + Component { + id: safetyPageComponent - QGCPalette { id: palette; colorGroupEnabled: enabled } + Flow { + id: flowLayout + width: availableWidth + spacing: _margins - property Fact _failsafeBattMah: controller.getParameterFact(-1, "FS_BATT_MAH") - property Fact _failsafeBattVoltage: controller.getParameterFact(-1, "FS_BATT_VOLTAGE") - property Fact _failsafeThrEnable: controller.getParameterFact(-1, "THR_FAILSAFE") - property Fact _failsafeThrValue: controller.getParameterFact(-1, "THR_FS_VALUE") - property Fact _failsafeGCSEnable: controller.getParameterFact(-1, "FS_GCS_ENABL") + FactPanelController { id: controller; factPanel: safetyPage.viewPanel } - property Fact _rtlAltFact: controller.getParameterFact(-1, "ALT_HOLD_RTL") + QGCPalette { id: palette; colorGroupEnabled: true } - property real _margins: ScreenTools.defaultFontPixelHeight + property Fact _failsafeBattMah: controller.getParameterFact(-1, "FS_BATT_MAH") + property Fact _failsafeBattVoltage: controller.getParameterFact(-1, "FS_BATT_VOLTAGE") + property Fact _failsafeThrEnable: controller.getParameterFact(-1, "THR_FAILSAFE") + property Fact _failsafeThrValue: controller.getParameterFact(-1, "THR_FS_VALUE") + property Fact _failsafeGCSEnable: controller.getParameterFact(-1, "FS_GCS_ENABL") - ExclusiveGroup { id: returnAltRadioGroup } + property Fact _rtlAltFact: controller.getParameterFact(-1, "ALT_HOLD_RTL") - QGCViewPanel { - id: panel - anchors.fill: parent + property real _margins: ScreenTools.defaultFontPixelHeight - QGCFlickable { - clip: true - anchors.fill: parent - contentWidth: flowLayout.width - contentHeight: flowLayout.height + ExclusiveGroup { id: returnAltRadioGroup } - Flow { - id: flowLayout - width: panel.width // parent.width doesn't work for some reason - spacing: _margins + Column { + spacing: _margins / 2 - Column { - spacing: _margins / 2 + QGCLabel { + text: qsTr("Failsafe Triggers") + font.family: ScreenTools.demiboldFontFamily + } - QGCLabel { - text: qsTr("Failsafe Triggers") - font.family: ScreenTools.demiboldFontFamily + Rectangle { + width: throttlePWMField.x + throttlePWMField.width + _margins + height: gcsCheckbox.y + gcsCheckbox.height + _margins + color: palette.windowShade + + QGCCheckBox { + id: throttleEnableCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: throttlePWMField.baseline + text: qsTr("Throttle PWM threshold:") + checked: _failsafeThrEnable.value == 1 + + onClicked: _failsafeThrEnable.value = (checked ? 1 : 0) + } + + FactTextField { + id: throttlePWMField + anchors.margins: _margins + anchors.left: throttleEnableCheckBox.right + anchors.top: parent.top + fact: _failsafeThrValue + showUnits: true + enabled: throttleEnableCheckBox.checked + } + + QGCCheckBox { + id: voltageCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: voltageField.baseline + text: qsTr("Voltage threshold:") + checked: _failsafeBattVoltage.value != 0 + + onClicked: _failsafeBattVoltage.value = checked ? 10.5 : 0 + } + + FactTextField { + id: voltageField + anchors.topMargin: _margins + anchors.left: throttlePWMField.left + anchors.top: throttlePWMField.bottom + fact: _failsafeBattVoltage + showUnits: true + enabled: voltageCheckBox.checked + } + + QGCCheckBox { + id: mahCheckBox + anchors.margins: _margins + anchors.left: parent.left + anchors.baseline: mahField.baseline + text: qsTr("MAH threshold:") + checked: _failsafeBattMah.value != 0 + + onClicked: _failsafeBattMah.value = checked ? 600 : 0 + } + + FactTextField { + id: mahField + anchors.topMargin: _margins / 2 + anchors.left: throttlePWMField.left + anchors.top: voltageField.bottom + fact: _failsafeBattMah + showUnits: true + enabled: mahCheckBox.checked + } + + QGCCheckBox { + id: gcsCheckbox + anchors.margins: _margins + anchors.left: parent.left + anchors.top: mahField.bottom + text: qsTr("GCS failsafe") + checked: _failsafeGCSEnable.value != 0 + + onClicked: _failsafeGCSEnable.value = checked ? 1 : 0 + } + } // Rectangle - Failsafe trigger settings + } // Column - Failsafe trigger settings + + Column { + spacing: _margins / 2 + + QGCLabel { + text: qsTr("Return to Launch") + font.family: ScreenTools.demiboldFontFamily + } + + Rectangle { + width: rltAltField.x + rltAltField.width + _margins + height: rltAltField.y + rltAltField.height + _margins + color: palette.windowShade + + QGCRadioButton { + id: returnAtCurrentRadio + anchors.margins: _margins + anchors.left: parent.left + anchors.top: parent.top + text: qsTr("Return at current altitude") + checked: _rtlAltFact.value < 0 + exclusiveGroup: returnAltRadioGroup + + onClicked: _rtlAltFact.value = -1 } - Rectangle { - width: throttlePWMField.x + throttlePWMField.width + _margins - height: gcsCheckbox.y + gcsCheckbox.height + _margins - color: palette.windowShade - - QGCCheckBox { - id: throttleEnableCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: throttlePWMField.baseline - text: qsTr("Throttle PWM threshold:") - checked: _failsafeThrEnable.value == 1 - - onClicked: _failsafeThrEnable.value = (checked ? 1 : 0) - } - - FactTextField { - id: throttlePWMField - anchors.margins: _margins - anchors.left: throttleEnableCheckBox.right - anchors.top: parent.top - fact: _failsafeThrValue - showUnits: true - enabled: throttleEnableCheckBox.checked - } - - QGCCheckBox { - id: voltageCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: voltageField.baseline - text: qsTr("Voltage threshold:") - checked: _failsafeBattVoltage.value != 0 - - onClicked: _failsafeBattVoltage.value = checked ? 10.5 : 0 - } - - FactTextField { - id: voltageField - anchors.topMargin: _margins - anchors.left: throttlePWMField.left - anchors.top: throttlePWMField.bottom - fact: _failsafeBattVoltage - showUnits: true - enabled: voltageCheckBox.checked - } - - QGCCheckBox { - id: mahCheckBox - anchors.margins: _margins - anchors.left: parent.left - anchors.baseline: mahField.baseline - text: qsTr("MAH threshold:") - checked: _failsafeBattMah.value != 0 - - onClicked: _failsafeBattMah.value = checked ? 600 : 0 - } - - FactTextField { - id: mahField - anchors.topMargin: _margins / 2 - anchors.left: throttlePWMField.left - anchors.top: voltageField.bottom - fact: _failsafeBattMah - showUnits: true - enabled: mahCheckBox.checked - } - - QGCCheckBox { - id: gcsCheckbox - anchors.margins: _margins - anchors.left: parent.left - anchors.top: mahField.bottom - text: qsTr("GCS failsafe") - checked: _failsafeGCSEnable.value != 0 - - onClicked: _failsafeGCSEnable.value = checked ? 1 : 0 - } - } // Rectangle - Failsafe trigger settings - } // Column - Failsafe trigger settings - - Column { - spacing: _margins / 2 - - QGCLabel { - text: qsTr("Return to Launch") - font.family: ScreenTools.demiboldFontFamily + QGCRadioButton { + id: returnAltRadio + anchors.topMargin: _margins / 2 + anchors.left: returnAtCurrentRadio.left + anchors.top: returnAtCurrentRadio.bottom + text: qsTr("Return at specified altitude:") + exclusiveGroup: returnAltRadioGroup + checked: _rtlAltFact.value >= 0 + + onClicked: _rtlAltFact.value = 10000 } - Rectangle { - width: rltAltField.x + rltAltField.width + _margins - height: rltAltField.y + rltAltField.height + _margins - color: palette.windowShade - - QGCRadioButton { - id: returnAtCurrentRadio - anchors.margins: _margins - anchors.left: parent.left - anchors.top: parent.top - text: qsTr("Return at current altitude") - checked: _rtlAltFact.value < 0 - exclusiveGroup: returnAltRadioGroup - - onClicked: _rtlAltFact.value = -1 - } - - QGCRadioButton { - id: returnAltRadio - anchors.topMargin: _margins / 2 - anchors.left: returnAtCurrentRadio.left - anchors.top: returnAtCurrentRadio.bottom - text: qsTr("Return at specified altitude:") - exclusiveGroup: returnAltRadioGroup - checked: _rtlAltFact.value >= 0 - - onClicked: _rtlAltFact.value = 10000 - } - - FactTextField { - id: rltAltField - anchors.leftMargin: _margins - anchors.left: returnAltRadio.right - anchors.baseline: returnAltRadio.baseline - fact: _rtlAltFact - showUnits: true - enabled: returnAltRadio.checked - } - } // Rectangle - RTL Settings - } // Column - RTL Settings - } // Flow - } // QGCFlickable - } // QGCViewPanel -} // QGCView + FactTextField { + id: rltAltField + anchors.leftMargin: _margins + anchors.left: returnAltRadio.right + anchors.baseline: returnAltRadio.baseline + fact: _rtlAltFact + showUnits: true + enabled: returnAltRadio.checked + } + } // Rectangle - RTL Settings + } // Column - RTL Settings + } // Flow + } // Component +} // SetupView diff --git a/src/AutoPilotPlugins/APM/APMSensorsComponent.cc b/src/AutoPilotPlugins/APM/APMSensorsComponent.cc index 3f45eeca4..e5933b554 100644 --- a/src/AutoPilotPlugins/APM/APMSensorsComponent.cc +++ b/src/AutoPilotPlugins/APM/APMSensorsComponent.cc @@ -30,8 +30,7 @@ QString APMSensorsComponent::name(void) const QString APMSensorsComponent::description(void) const { - return tr("The Sensors Component allows you to calibrate the sensors within your vehicle. " - "Prior to flight you must calibrate the Magnetometer, Gyroscope and Accelerometer."); + return tr("Sensors Setup is used to calibrate the sensors within your vehicle."); } QString APMSensorsComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMSensorsComponent.qml b/src/AutoPilotPlugins/APM/APMSensorsComponent.qml index 533e0b143..bb87f6985 100644 --- a/src/AutoPilotPlugins/APM/APMSensorsComponent.qml +++ b/src/AutoPilotPlugins/APM/APMSensorsComponent.qml @@ -8,624 +8,628 @@ ****************************************************************************/ -import QtQuick 2.2 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Dialogs 1.2 - -import QGroundControl.FactSystem 1.0 -import QGroundControl.FactControls 1.0 -import QGroundControl.Palette 1.0 -import QGroundControl.Controls 1.0 -import QGroundControl.ScreenTools 1.0 -import QGroundControl.Controllers 1.0 - -QGCView { - id: qgcView - viewPanel: panel - - // Help text which is shown both in the status text area prior to pressing a cal button and in the - // pre-calibration dialog. - - readonly property string orientationHelpSet: "If the orientation is in the direction of flight, select None." - readonly property string orientationHelpCal: "Before calibrating make sure orientation settings are correct. " + orientationHelpSet - readonly property string compassRotationText: "If the compass or GPS module is mounted in flight direction, leave the default value (None)" - - readonly property string compassHelp: "For Compass calibration you will need to rotate your vehicle through a number of positions." - readonly property string gyroHelp: "For Gyroscope calibration you will need to place your vehicle on a surface and leave it still." - readonly property string accelHelp: "For Accelerometer calibration you will need to place your vehicle on all six sides on a perfectly level surface and hold it still in each orientation for a few seconds." - readonly property string levelHelp: "To level the horizon you need to place the vehicle in its level flight position and press OK." - readonly property string airspeedHelp: "For Airspeed calibration you will need to keep your airspeed sensor out of any wind and then blow across the sensor." - - readonly property string statusTextAreaDefaultText: "Start the individual calibration steps by clicking one of the buttons to the left." - - // Used to pass help text to the preCalibrationDialog dialog - property string preCalibrationDialogHelp - - property string _postCalibrationDialogText - property var _postCalibrationDialogParams - - readonly property string _badCompassCalText: "The calibration for Compass %1 appears to be poor. " + - "Check the compass position within your vehicle and re-do the calibration." - - readonly property int sideBarH1PointSize: ScreenTools.mediumFontPointSize - readonly property int mainTextH1PointSize: ScreenTools.mediumFontPointSize // Seems to be unused - - readonly property int rotationColumnWidth: 250 - - property Fact compass1Id: controller.getParameterFact(-1, "COMPASS_DEV_ID") - property Fact compass2Id: controller.getParameterFact(-1, "COMPASS_DEV_ID2") - property Fact compass3Id: controller.getParameterFact(-1, "COMPASS_DEV_ID3") - property Fact compass1ExternalFact: controller.getParameterFact(-1, "COMPASS_EXTERNAL") - property Fact compass1Rot: controller.getParameterFact(-1, "COMPASS_ORIENT") - - property Fact boardRot: controller.getParameterFact(-1, "AHRS_ORIENTATION") - - property bool accelCalNeeded: controller.accelSetupNeeded - property bool compassCalNeeded: controller.compassSetupNeeded - - - // The following parameters are not available in olders firmwares - - property bool compass2ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN2") - property bool compass3ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN3") - property bool compass2RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT2") - property bool compass3RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT3") - property bool compass1UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE") - property bool compass2UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE2") - property bool compass3UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE3") - - property Fact noFact: Fact { } - property Fact compass2ExternalFact: compass2ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN2") : noFact - property Fact compass3ExternalFact: compass3ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN3") : noFact - property Fact compass2Rot: compass2RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT2") : noFact - property Fact compass3Rot: compass3RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT3") : noFact - property Fact compass1UseFact: compass1UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE") : noFact - property Fact compass2UseFact: compass2UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE2") : noFact - property Fact compass3UseFact: compass3UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE3") : noFact - - // We track these values by binding through a separate property so we can handle missing params - property bool compass1External: compass1ExternalFact.value - property bool compass2External: compass2ExternalParamAvailable ? compass2ExternalFact.value : false // false: Simulate internal so we don't show rotation combos - property bool compass3External: compass3ExternalParamAvailable ? compass3ExternalFact.value : false // false: Simulate internal so we don't show rotation combos - property bool compass1Use: compass1UseParamAvailable ? compass1UseFact.value : true - property bool compass2Use: compass2UseParamAvailable ? compass2UseFact.value : true - property bool compass3Use: compass3UseParamAvailable ? compass3UseFact.value : true - - // Id > = signals compass available, rot < 0 signals internal compass - property bool showCompass1: compass1Id.value > 0 - property bool showCompass2: compass2Id.value > 0 - property bool showCompass3: compass3Id.value > 0 - - readonly property int _calTypeCompass: 1 ///< Calibrate compass - readonly property int _calTypeAccel: 2 ///< Calibrate accel - readonly property int _calTypeSet: 3 ///< Set orientations only - - property bool _orientationsDialogShowCompass: true - property string _orientationDialogHelp: orientationHelpSet - property int _orientationDialogCalType - - function validCompassOffsets(compassParamPrefix) { - var ofsX = controller.getParameterFact(-1, compassParamPrefix + "X") - var ofsY = controller.getParameterFact(-1, compassParamPrefix + "Y") - var ofsZ = controller.getParameterFact(-1, compassParamPrefix + "Z") - return Math.sqrt(ofsX.value^2 + ofsY.value^2 + ofsZ.value^2) < 600 - } - - function showOrientationsDialog(calType) { - var dialogTitle - var buttons = StandardButton.Ok - - _orientationDialogCalType = calType - switch (calType) { - case _calTypeCompass: - _orientationsDialogShowCompass = true - _orientationDialogHelp = orientationHelpCal - dialogTitle = qsTr("Calibrate Compass") - buttons |= StandardButton.Cancel - break - case _calTypeAccel: - _orientationsDialogShowCompass = false - _orientationDialogHelp = orientationHelpCal - dialogTitle = qsTr("Calibrate Accelerometer") - buttons |= StandardButton.Cancel - break - case _calTypeSet: - _orientationsDialogShowCompass = true - _orientationDialogHelp = orientationHelpSet - dialogTitle = qsTr("Sensor Settings") - break - } - - showDialog(orientationsDialogComponent, dialogTitle, qgcView.showDialogDefaultWidth, buttons) - } - - APMSensorsComponentController { - id: controller - factPanel: panel - statusLog: statusTextArea - progressBar: progressBar - compassButton: compassButton - accelButton: accelButton - compassMotButton: motorInterferenceButton - nextButton: nextButton - cancelButton: cancelButton - setOrientationsButton: setOrientationsButton - orientationCalAreaHelpText: orientationCalAreaHelpText - - onResetStatusTextArea: statusLog.text = statusTextAreaDefaultText - - onWaitingForCancelChanged: { - if (controller.waitingForCancel) { - showMessage(qsTr("Calibration Cancel"), qsTr("Waiting for Vehicle to response to Cancel. This may take a few seconds."), 0) - } else { - hideDialog() - } - } - - onCalibrationComplete: { - if (_orientationDialogCalType == _calTypeAccel) { - _postCalibrationDialogText = qsTr("Accelerometer calibration complete.") - _postCalibrationDialogParams = [ "INS_ACCSCAL_X", "INS_ACCSCAL_Y", "INS_ACCSCAL_Z", - "INS_ACC2SCAL_X", "INS_ACC2SCAL_Y", "INS_ACC2SCAL_Z", - "INS_ACC3SCAL_X", "INS_ACC3SCAL_Y", "INS_ACC3SCAL_Z", - "INS_GYROFFS_X", "INS_GYROFFS_Y", "INS_GYROFFS_Z", - "INS_GYR2OFFS_X", "INS_GYR2OFFS_Y", "INS_GYR2OFFS_Z", - "INS_GYR3OFFS_X", "INS_GYR3OFFS_Y", "INS_GYR3OFFS_Z" ] - showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) - } else if (_orientationDialogCalType == _calTypeCompass) { - _postCalibrationDialogText = qsTr("Compass calibration complete. ") - _postCalibrationDialogParams = []; - if (compass1Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 1) - } - _postCalibrationDialogParams.push("COMPASS_OFS_X") - _postCalibrationDialogParams.push("COMPASS_OFS_Y") - _postCalibrationDialogParams.push("COMPASS_OFS_Z") - } - if (compass2Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 2) - } - _postCalibrationDialogParams.push("COMPASS_OFS2_X") - _postCalibrationDialogParams.push("COMPASS_OFS2_Y") - _postCalibrationDialogParams.push("COMPASS_OFS2_Z") - } - if (compass3Id.value > 0) { - if (!validCompassOffsets("COMPASS_OFS_")) { - _postCalibrationDialogText += _badCompassCalText.replace("%1", 3) - } - _postCalibrationDialogParams.push("COMPASS_OFS3_X") - _postCalibrationDialogParams.push("COMPASS_OFS3_Y") - _postCalibrationDialogParams.push("COMPASS_OFS3_Z") - } - showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) - } - } - } - - Component.onCompleted: { - var usingUDP = controller.usingUDPLink() - if (usingUDP) { - console.log("onUsingUDPLink") - showMessage("Sensor Calibration", "Performing sensor calibration over a WiFi connection is known to be unreliable. You should disconnect and perform calibration using a direct USB connection instead.", StandardButton.Ok) - } - } - - QGCPalette { id: qgcPal; colorGroupEnabled: panel.enabled } +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 + +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controllers 1.0 + +SetupPage { + id: sensorsPage + pageComponent: sensorsPageComponent Component { - id: postCalibrationDialogComponent - - QGCViewDialog { - QGCLabel { - id: textLabel - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: _postCalibrationDialogText - } - - QGCCheckBox { - id: showValues - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: textLabel.bottom - text: qsTr("Show values") + id: sensorsPageComponent + + RowLayout { + width: 1000//availableWidth + height: 1000//availableHeight + spacing: ScreenTools.defaultFontPixelWidth / 2 + + // Help text which is shown both in the status text area prior to pressing a cal button and in the + // pre-calibration dialog. + + readonly property string orientationHelpSet: "If the orientation is in the direction of flight, select None." + readonly property string orientationHelpCal: "Before calibrating make sure orientation settings are correct. " + orientationHelpSet + readonly property string compassRotationText: "If the compass or GPS module is mounted in flight direction, leave the default value (None)" + + readonly property string compassHelp: "For Compass calibration you will need to rotate your vehicle through a number of positions." + readonly property string gyroHelp: "For Gyroscope calibration you will need to place your vehicle on a surface and leave it still." + readonly property string accelHelp: "For Accelerometer calibration you will need to place your vehicle on all six sides on a perfectly level surface and hold it still in each orientation for a few seconds." + readonly property string levelHelp: "To level the horizon you need to place the vehicle in its level flight position and press OK." + readonly property string airspeedHelp: "For Airspeed calibration you will need to keep your airspeed sensor out of any wind and then blow across the sensor." + + readonly property string statusTextAreaDefaultText: "Start the individual calibration steps by clicking one of the buttons to the left." + + // Used to pass help text to the preCalibrationDialog dialog + property string preCalibrationDialogHelp + + property string _postCalibrationDialogText + property var _postCalibrationDialogParams + + readonly property string _badCompassCalText: "The calibration for Compass %1 appears to be poor. " + + "Check the compass position within your vehicle and re-do the calibration." + + readonly property int sideBarH1PointSize: ScreenTools.mediumFontPointSize + readonly property int mainTextH1PointSize: ScreenTools.mediumFontPointSize // Seems to be unused + + readonly property int rotationColumnWidth: 250 + + property Fact compass1Id: controller.getParameterFact(-1, "COMPASS_DEV_ID") + property Fact compass2Id: controller.getParameterFact(-1, "COMPASS_DEV_ID2") + property Fact compass3Id: controller.getParameterFact(-1, "COMPASS_DEV_ID3") + property Fact compass1ExternalFact: controller.getParameterFact(-1, "COMPASS_EXTERNAL") + property Fact compass1Rot: controller.getParameterFact(-1, "COMPASS_ORIENT") + + property Fact boardRot: controller.getParameterFact(-1, "AHRS_ORIENTATION") + + property bool accelCalNeeded: controller.accelSetupNeeded + property bool compassCalNeeded: controller.compassSetupNeeded + + + // The following parameters are not available in olders firmwares + + property bool compass2ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN2") + property bool compass3ExternalParamAvailable: controller.parameterExists(-1, "COMPASS_EXTERN3") + property bool compass2RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT2") + property bool compass3RotParamAvailable: controller.parameterExists(-1, "COMPASS_ORIENT3") + property bool compass1UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE") + property bool compass2UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE2") + property bool compass3UseParamAvailable: controller.parameterExists(-1, "COMPASS_USE3") + + property Fact noFact: Fact { } + property Fact compass2ExternalFact: compass2ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN2") : noFact + property Fact compass3ExternalFact: compass3ExternalParamAvailable ? controller.getParameterFact(-1, "COMPASS_EXTERN3") : noFact + property Fact compass2Rot: compass2RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT2") : noFact + property Fact compass3Rot: compass3RotParamAvailable ? controller.getParameterFact(-1, "COMPASS_ORIENT3") : noFact + property Fact compass1UseFact: compass1UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE") : noFact + property Fact compass2UseFact: compass2UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE2") : noFact + property Fact compass3UseFact: compass3UseParamAvailable ? controller.getParameterFact(-1, "COMPASS_USE3") : noFact + + // We track these values by binding through a separate property so we can handle missing params + property bool compass1External: compass1ExternalFact.value + property bool compass2External: compass2ExternalParamAvailable ? compass2ExternalFact.value : false // false: Simulate internal so we don't show rotation combos + property bool compass3External: compass3ExternalParamAvailable ? compass3ExternalFact.value : false // false: Simulate internal so we don't show rotation combos + property bool compass1Use: compass1UseParamAvailable ? compass1UseFact.value : true + property bool compass2Use: compass2UseParamAvailable ? compass2UseFact.value : true + property bool compass3Use: compass3UseParamAvailable ? compass3UseFact.value : true + + // Id > = signals compass available, rot < 0 signals internal compass + property bool showCompass1: compass1Id.value > 0 + property bool showCompass2: compass2Id.value > 0 + property bool showCompass3: compass3Id.value > 0 + + readonly property int _calTypeCompass: 1 ///< Calibrate compass + readonly property int _calTypeAccel: 2 ///< Calibrate accel + readonly property int _calTypeSet: 3 ///< Set orientations only + + property bool _orientationsDialogShowCompass: true + property string _orientationDialogHelp: orientationHelpSet + property int _orientationDialogCalType + + function validCompassOffsets(compassParamPrefix) { + var ofsX = controller.getParameterFact(-1, compassParamPrefix + "X") + var ofsY = controller.getParameterFact(-1, compassParamPrefix + "Y") + var ofsZ = controller.getParameterFact(-1, compassParamPrefix + "Z") + return Math.sqrt(ofsX.value^2 + ofsY.value^2 + ofsZ.value^2) < 600 } - QGCFlickable { - anchors.topMargin: ScreenTools.defaultFontPixelHeight - anchors.top: showValues.bottom - anchors.bottom: parent.bottom - contentHeight: valueColumn.height - flickableDirection: Flickable.VerticalFlick - visible: showValues.checked - - Column { - id: valueColumn + function showOrientationsDialog(calType) { + var dialogTitle + var buttons = StandardButton.Ok + + _orientationDialogCalType = calType + switch (calType) { + case _calTypeCompass: + _orientationsDialogShowCompass = true + _orientationDialogHelp = orientationHelpCal + dialogTitle = qsTr("Calibrate Compass") + buttons |= StandardButton.Cancel + break + case _calTypeAccel: + _orientationsDialogShowCompass = false + _orientationDialogHelp = orientationHelpCal + dialogTitle = qsTr("Calibrate Accelerometer") + buttons |= StandardButton.Cancel + break + case _calTypeSet: + _orientationsDialogShowCompass = true + _orientationDialogHelp = orientationHelpSet + dialogTitle = qsTr("Sensor Settings") + break + } - Repeater { - model: _postCalibrationDialogParams + showDialog(orientationsDialogComponent, dialogTitle, qgcView.showDialogDefaultWidth, buttons) + } - QGCLabel { - text: fact.name +": " + fact.valueString + APMSensorsComponentController { + id: controller + factPanel: sensorsPage.viewPanel + statusLog: statusTextArea + progressBar: progressBar + compassButton: compassButton + accelButton: accelButton + compassMotButton: motorInterferenceButton + nextButton: nextButton + cancelButton: cancelButton + setOrientationsButton: setOrientationsButton + orientationCalAreaHelpText: orientationCalAreaHelpText + + onResetStatusTextArea: statusLog.text = statusTextAreaDefaultText + + onWaitingForCancelChanged: { + if (controller.waitingForCancel) { + showMessage(qsTr("Calibration Cancel"), qsTr("Waiting for Vehicle to response to Cancel. This may take a few seconds."), 0) + } else { + hideDialog() + } + } - property Fact fact: controller.getParameterFact(-1, modelData) + onCalibrationComplete: { + if (_orientationDialogCalType == _calTypeAccel) { + _postCalibrationDialogText = qsTr("Accelerometer calibration complete.") + _postCalibrationDialogParams = [ "INS_ACCSCAL_X", "INS_ACCSCAL_Y", "INS_ACCSCAL_Z", + "INS_ACC2SCAL_X", "INS_ACC2SCAL_Y", "INS_ACC2SCAL_Z", + "INS_ACC3SCAL_X", "INS_ACC3SCAL_Y", "INS_ACC3SCAL_Z", + "INS_GYROFFS_X", "INS_GYROFFS_Y", "INS_GYROFFS_Z", + "INS_GYR2OFFS_X", "INS_GYR2OFFS_Y", "INS_GYR2OFFS_Z", + "INS_GYR3OFFS_X", "INS_GYR3OFFS_Y", "INS_GYR3OFFS_Z" ] + showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) + } else if (_orientationDialogCalType == _calTypeCompass) { + _postCalibrationDialogText = qsTr("Compass calibration complete. ") + _postCalibrationDialogParams = []; + if (compass1Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 1) + } + _postCalibrationDialogParams.push("COMPASS_OFS_X") + _postCalibrationDialogParams.push("COMPASS_OFS_Y") + _postCalibrationDialogParams.push("COMPASS_OFS_Z") } + if (compass2Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 2) + } + _postCalibrationDialogParams.push("COMPASS_OFS2_X") + _postCalibrationDialogParams.push("COMPASS_OFS2_Y") + _postCalibrationDialogParams.push("COMPASS_OFS2_Z") + } + if (compass3Id.value > 0) { + if (!validCompassOffsets("COMPASS_OFS_")) { + _postCalibrationDialogText += _badCompassCalText.replace("%1", 3) + } + _postCalibrationDialogParams.push("COMPASS_OFS3_X") + _postCalibrationDialogParams.push("COMPASS_OFS3_Y") + _postCalibrationDialogParams.push("COMPASS_OFS3_Z") + } + showDialog(postCalibrationDialogComponent, qsTr("Calibration complete"), qgcView.showDialogDefaultWidth, StandardButton.Ok) } } } - } - } - Component { - id: orientationsDialogComponent - - QGCViewDialog { - id: orientationsDialog - - function accept() { - if (_orientationDialogCalType == _calTypeAccel) { - controller.calibrateAccel() - } else if (_orientationDialogCalType == _calTypeCompass) { - controller.calibrateCompass() + Component.onCompleted: { + var usingUDP = controller.usingUDPLink() + if (usingUDP) { + console.log("onUsingUDPLink") + showMessage("Sensor Calibration", "Performing sensor calibration over a WiFi connection is known to be unreliable. You should disconnect and perform calibration using a direct USB connection instead.", StandardButton.Ok) } - orientationsDialog.hideDialog() } - QGCFlickable { - anchors.fill: parent - contentHeight: columnLayout.height - clip: true + QGCPalette { id: qgcPal; colorGroupEnabled: true } - Column { - id: columnLayout - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: ScreenTools.defaultFontPixelHeight + Component { + id: postCalibrationDialogComponent + QGCViewDialog { QGCLabel { - width: parent.width - wrapMode: Text.WordWrap - text: _orientationDialogHelp + id: textLabel + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: _postCalibrationDialogText } - Column { - QGCLabel { - text: qsTr("Autopilot Orientation:") - } - - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: boardRot - } + QGCCheckBox { + id: showValues + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.top: textLabel.bottom + text: qsTr("Show values") } - Column { - visible: _orientationsDialogShowCompass && showCompass1 - - FactCheckBox { - text: "Use Compass 1" - fact: compass1UseFact - } + QGCFlickable { + anchors.topMargin: ScreenTools.defaultFontPixelHeight + anchors.top: showValues.bottom + anchors.bottom: parent.bottom + contentHeight: valueColumn.height + flickableDirection: Flickable.VerticalFlick + visible: showValues.checked Column { - visible: showCompass1Rot + id: valueColumn - QGCLabel { - text: qsTr("Compass 1 Orientation:") - } + Repeater { + model: _postCalibrationDialogParams + + QGCLabel { + text: fact.name +": " + fact.valueString - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass1Rot + property Fact fact: controller.getParameterFact(-1, modelData) + } } } } + } + } - Column { - visible: _orientationsDialogShowCompass && showCompass2 + Component { + id: orientationsDialogComponent - FactCheckBox { - text: "Use Compass 2" - fact: compass2UseFact + QGCViewDialog { + id: orientationsDialog + + function accept() { + if (_orientationDialogCalType == _calTypeAccel) { + controller.calibrateAccel() + } else if (_orientationDialogCalType == _calTypeCompass) { + controller.calibrateCompass() } + orientationsDialog.hideDialog() + } + + QGCFlickable { + anchors.fill: parent + contentHeight: columnLayout.height + clip: true Column { - visible: showCompass1Rot + id: columnLayout + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: ScreenTools.defaultFontPixelHeight QGCLabel { - text: qsTr("Compass 2 Orientation:") + width: parent.width + wrapMode: Text.WordWrap + text: _orientationDialogHelp } - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass2Rot + Column { + QGCLabel { + text: qsTr("Autopilot Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: boardRot + } } - } - } - Column { - visible: _orientationsDialogShowCompass && showCompass3 + Column { + visible: _orientationsDialogShowCompass && showCompass1 - FactCheckBox { - text: "Use Compass 3" - fact: compass3UseFact - } + FactCheckBox { + text: "Use Compass 1" + fact: compass1UseFact + } - Column { - visible: showCompass3Rot + Column { + visible: showCompass1Rot - QGCLabel { - text: qsTr("Compass 3 Orientation:") + QGCLabel { + text: qsTr("Compass 1 Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass1Rot + } + } } - FactComboBox { - width: rotationColumnWidth - indexModel: false - fact: compass3Rot + Column { + visible: _orientationsDialogShowCompass && showCompass2 + + FactCheckBox { + text: "Use Compass 2" + fact: compass2UseFact + } + + Column { + visible: showCompass1Rot + + QGCLabel { + text: qsTr("Compass 2 Orientation:") + } + + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass2Rot + } + } } - } - } - } // Column - } // QGCFlickable - } // QGCViewDialog - } // Component - orientationsDialogComponent - Component { - id: compassMotDialogComponent + Column { + visible: _orientationsDialogShowCompass && showCompass3 - QGCViewDialog { - id: compassMotDialog + FactCheckBox { + text: "Use Compass 3" + fact: compass3UseFact + } - function accept() { - controller.calibrateMotorInterference() - compassMotDialog.hideDialog() - } + Column { + visible: showCompass3Rot - QGCFlickable { - anchors.fill: parent - contentHeight: columnLayout.height - clip: true + QGCLabel { + text: qsTr("Compass 3 Orientation:") + } - Column { - id: columnLayout - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - spacing: ScreenTools.defaultFontPixelHeight + FactComboBox { + width: rotationColumnWidth + indexModel: false + fact: compass3Rot + } + } + } + } // Column + } // QGCFlickable + } // QGCViewDialog + } // Component - orientationsDialogComponent - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "This is recommended for vehicles that have only an internal compass and on vehicles where there is significant interference on the compass from the motors, power wires, etc. " + - "CompassMot only works well if you have a battery current monitor because the magnetic interference is linear with current drawn. " + - "It is technically possible to set-up CompassMot using throttle but this is not recommended." - } + Component { + id: compassMotDialogComponent - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Disconnect your props, flip them over and rotate them one position around the frame. " + - "In this configuration they should push the copter down into the ground when the throttle is raised." - } + QGCViewDialog { + id: compassMotDialog - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Secure the copter (perhaps with tape) so that it does not move." + function accept() { + controller.calibrateMotorInterference() + compassMotDialog.hideDialog() } - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Turn on your transmitter and keep throttle at zero." - } + QGCFlickable { + anchors.fill: parent + contentHeight: columnLayout.height + clip: true - QGCLabel { - anchors.left: parent.left - anchors.right: parent.right - wrapMode: Text.WordWrap - text: "Click Ok to start CompassMot calibration." - } - } // Column - } // QGCFlickable - } // QGCViewDialog - } + Column { + id: columnLayout + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: ScreenTools.defaultFontPixelHeight - QGCViewPanel { - id: panel - anchors.fill: parent + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "This is recommended for vehicles that have only an internal compass and on vehicles where there is significant interference on the compass from the motors, power wires, etc. " + + "CompassMot only works well if you have a battery current monitor because the magnetic interference is linear with current drawn. " + + "It is technically possible to set-up CompassMot using throttle but this is not recommended." + } - Column { - id: buttonColumn - spacing: ScreenTools.defaultFontPixelHeight / 2 + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Disconnect your props, flip them over and rotate them one position around the frame. " + + "In this configuration they should push the copter down into the ground when the throttle is raised." + } - readonly property int buttonWidth: ScreenTools.defaultFontPixelWidth * 15 + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Secure the copter (perhaps with tape) so that it does not move." + } - IndicatorButton { - id: accelButton - width: parent.buttonWidth - text: qsTr("Accelerometer") - indicatorGreen: !accelCalNeeded + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Turn on your transmitter and keep throttle at zero." + } - onClicked: showOrientationsDialog(_calTypeAccel) - } + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + wrapMode: Text.WordWrap + text: "Click Ok to start CompassMot calibration." + } + } // Column + } // QGCFlickable + } // QGCViewDialog + } // Component - compassMotDialogComponent - IndicatorButton { - id: compassButton - width: parent.buttonWidth - text: qsTr("Compass") - indicatorGreen: !compassCalNeeded + Column { + spacing: ScreenTools.defaultFontPixelHeight / 2 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop - onClicked: { - if (controller.accelSetupNeeded) { - showMessage(qsTr("Calibrate Compass"), qsTr("Accelerometer must be calibrated prior to Compass."), StandardButton.Ok) - } else { - showOrientationsDialog(_calTypeCompass) + readonly property int buttonWidth: ScreenTools.defaultFontPixelWidth * 15 + + IndicatorButton { + id: accelButton + width: parent.buttonWidth + text: qsTr("Accelerometer") + indicatorGreen: !accelCalNeeded + + onClicked: showOrientationsDialog(_calTypeAccel) + } + + IndicatorButton { + id: compassButton + width: parent.buttonWidth + text: qsTr("Compass") + indicatorGreen: !compassCalNeeded + + onClicked: { + if (controller.accelSetupNeeded) { + showMessage(qsTr("Calibrate Compass"), qsTr("Accelerometer must be calibrated prior to Compass."), StandardButton.Ok) + } else { + showOrientationsDialog(_calTypeCompass) + } } } - } - QGCButton { - id: motorInterferenceButton - width: parent.buttonWidth - text: qsTr("CompassMot") - onClicked: showDialog(compassMotDialogComponent, qsTr("CompassMot - Compass Motor Interference Calibration"), qgcView.showDialogFullWidth, StandardButton.Cancel | StandardButton.Ok) - } + QGCButton { + id: motorInterferenceButton + width: parent.buttonWidth + text: qsTr("CompassMot") + onClicked: showDialog(compassMotDialogComponent, qsTr("CompassMot - Compass Motor Interference Calibration"), qgcView.showDialogFullWidth, StandardButton.Cancel | StandardButton.Ok) + } - QGCButton { - id: nextButton - width: parent.buttonWidth - text: qsTr("Next") - enabled: false - onClicked: controller.nextClicked() - } + QGCButton { + id: nextButton + width: parent.buttonWidth + text: qsTr("Next") + enabled: false + onClicked: controller.nextClicked() + } - QGCButton { - id: cancelButton - width: parent.buttonWidth - text: qsTr("Cancel") - enabled: false - onClicked: controller.cancelCalibration() - } + QGCButton { + id: cancelButton + width: parent.buttonWidth + text: qsTr("Cancel") + enabled: false + onClicked: controller.cancelCalibration() + } - QGCButton { - id: setOrientationsButton - width: parent.buttonWidth - text: qsTr("Sensor Settings") - onClicked: showOrientationsDialog(_calTypeSet) - } - } // Column - Buttons - - Column { - anchors.leftMargin: ScreenTools.defaultFontPixelWidth / 2 - anchors.left: buttonColumn.right - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - ProgressBar { - id: progressBar - anchors.left: parent.left - anchors.right: parent.right - } + QGCButton { + id: setOrientationsButton + width: parent.buttonWidth + text: qsTr("Sensor Settings") + onClicked: showOrientationsDialog(_calTypeSet) + } + } // Column - Buttons - Item { height: ScreenTools.defaultFontPixelHeight; width: 10 } // spacer + Column { + anchors.top: parent.top + anchors.bottom: parent.bottom + Layout.fillWidth: true - Item { - id: centerPanel - width: parent.width - height: parent.height - y + ProgressBar { + id: progressBar + anchors.left: parent.left + anchors.right: parent.right + } - TextArea { - id: statusTextArea - anchors.fill: parent - readOnly: true - frameVisible: false - text: statusTextAreaDefaultText + Item { height: ScreenTools.defaultFontPixelHeight; width: 10 } // spacer - style: TextAreaStyle { - textColor: qgcPal.text - backgroundColor: qgcPal.windowShade - } - } + Item { + id: centerPanel + width: parent.width + height: parent.height - y - Rectangle { - id: orientationCalArea - anchors.fill: parent - visible: controller.showOrientationCalArea - color: qgcPal.windowShade + TextArea { + id: statusTextArea + anchors.fill: parent + readOnly: true + frameVisible: false + text: statusTextAreaDefaultText - QGCLabel { - id: orientationCalAreaHelpText - anchors.margins: ScreenTools.defaultFontPixelWidth - anchors.top: orientationCalArea.top - anchors.left: orientationCalArea.left - width: parent.width - wrapMode: Text.WordWrap - font.pointSize: ScreenTools.mediumFontPointSize + style: TextAreaStyle { + textColor: qgcPal.text + backgroundColor: qgcPal.windowShade + } } - Flow { - anchors.topMargin: ScreenTools.defaultFontPixelWidth - anchors.top: orientationCalAreaHelpText.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - spacing: ScreenTools.defaultFontPixelWidth - - property real indicatorWidth: (width / 3) - (spacing * 2) - property real indicatorHeight: (height / 2) - spacing - - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalDownSideVisible - calValid: controller.orientationCalDownSideDone - calInProgress: controller.orientationCalDownSideInProgress - calInProgressText: controller.orientationCalDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalDownSideRotate ? "qrc:///qmlimages/VehicleDownRotate.png" : "qrc:///qmlimages/VehicleDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalUpsideDownSideVisible - calValid: controller.orientationCalUpsideDownSideDone - calInProgress: controller.orientationCalUpsideDownSideInProgress - calInProgressText: controller.orientationCalUpsideDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalUpsideDownSideRotate ? "qrc:///qmlimages/VehicleUpsideDownRotate.png" : "qrc:///qmlimages/VehicleUpsideDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalNoseDownSideVisible - calValid: controller.orientationCalNoseDownSideDone - calInProgress: controller.orientationCalNoseDownSideInProgress - calInProgressText: controller.orientationCalNoseDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalNoseDownSideRotate ? "qrc:///qmlimages/VehicleNoseDownRotate.png" : "qrc:///qmlimages/VehicleNoseDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalTailDownSideVisible - calValid: controller.orientationCalTailDownSideDone - calInProgress: controller.orientationCalTailDownSideInProgress - calInProgressText: controller.orientationCalTailDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalTailDownSideRotate ? "qrc:///qmlimages/VehicleTailDownRotate.png" : "qrc:///qmlimages/VehicleTailDown.png" - } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalLeftSideVisible - calValid: controller.orientationCalLeftSideDone - calInProgress: controller.orientationCalLeftSideInProgress - calInProgressText: controller.orientationCalLeftSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalLeftSideRotate ? "qrc:///qmlimages/VehicleLeftRotate.png" : "qrc:///qmlimages/VehicleLeft.png" + Rectangle { + id: orientationCalArea + anchors.fill: parent + visible: controller.showOrientationCalArea + color: qgcPal.windowShade + + QGCLabel { + id: orientationCalAreaHelpText + anchors.margins: ScreenTools.defaultFontPixelWidth + anchors.top: orientationCalArea.top + anchors.left: orientationCalArea.left + width: parent.width + wrapMode: Text.WordWrap + font.pointSize: ScreenTools.mediumFontPointSize } - VehicleRotationCal { - width: parent.indicatorWidth - height: parent.indicatorHeight - visible: controller.orientationCalRightSideVisible - calValid: controller.orientationCalRightSideDone - calInProgress: controller.orientationCalRightSideInProgress - calInProgressText: controller.orientationCalRightSideRotate ? qsTr("Rotate") : qsTr("Hold Still") - imageSource: controller.orientationCalRightSideRotate ? "qrc:///qmlimages/VehicleRightRotate.png" : "qrc:///qmlimages/VehicleRight.png" + + Flow { + anchors.topMargin: ScreenTools.defaultFontPixelWidth + anchors.top: orientationCalAreaHelpText.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + spacing: ScreenTools.defaultFontPixelWidth + + property real indicatorWidth: (width / 3) - (spacing * 2) + property real indicatorHeight: (height / 2) - spacing + + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalDownSideVisible + calValid: controller.orientationCalDownSideDone + calInProgress: controller.orientationCalDownSideInProgress + calInProgressText: controller.orientationCalDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalDownSideRotate ? "qrc:///qmlimages/VehicleDownRotate.png" : "qrc:///qmlimages/VehicleDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalUpsideDownSideVisible + calValid: controller.orientationCalUpsideDownSideDone + calInProgress: controller.orientationCalUpsideDownSideInProgress + calInProgressText: controller.orientationCalUpsideDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalUpsideDownSideRotate ? "qrc:///qmlimages/VehicleUpsideDownRotate.png" : "qrc:///qmlimages/VehicleUpsideDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalNoseDownSideVisible + calValid: controller.orientationCalNoseDownSideDone + calInProgress: controller.orientationCalNoseDownSideInProgress + calInProgressText: controller.orientationCalNoseDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalNoseDownSideRotate ? "qrc:///qmlimages/VehicleNoseDownRotate.png" : "qrc:///qmlimages/VehicleNoseDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalTailDownSideVisible + calValid: controller.orientationCalTailDownSideDone + calInProgress: controller.orientationCalTailDownSideInProgress + calInProgressText: controller.orientationCalTailDownSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalTailDownSideRotate ? "qrc:///qmlimages/VehicleTailDownRotate.png" : "qrc:///qmlimages/VehicleTailDown.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalLeftSideVisible + calValid: controller.orientationCalLeftSideDone + calInProgress: controller.orientationCalLeftSideInProgress + calInProgressText: controller.orientationCalLeftSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalLeftSideRotate ? "qrc:///qmlimages/VehicleLeftRotate.png" : "qrc:///qmlimages/VehicleLeft.png" + } + VehicleRotationCal { + width: parent.indicatorWidth + height: parent.indicatorHeight + visible: controller.orientationCalRightSideVisible + calValid: controller.orientationCalRightSideDone + calInProgress: controller.orientationCalRightSideInProgress + calInProgressText: controller.orientationCalRightSideRotate ? qsTr("Rotate") : qsTr("Hold Still") + imageSource: controller.orientationCalRightSideRotate ? "qrc:///qmlimages/VehicleRightRotate.png" : "qrc:///qmlimages/VehicleRight.png" + } } } - } - } // Item - Cal display area - } // Column - cal display - } // QGCViewPanel -} // QGCView + } // Item - Cal display area + } // Column - cal display + } // Row + } // Component - sensorsPageComponent +} // SetupPage diff --git a/src/AutoPilotPlugins/APM/APMTuningComponent.cc b/src/AutoPilotPlugins/APM/APMTuningComponent.cc index 32bd528b9..980b57624 100644 --- a/src/AutoPilotPlugins/APM/APMTuningComponent.cc +++ b/src/AutoPilotPlugins/APM/APMTuningComponent.cc @@ -26,7 +26,7 @@ QString APMTuningComponent::name(void) const QString APMTuningComponent::description(void) const { - return tr("The Tuning Component is used to tune the flight characteristics of the Vehicle."); + return tr("Tuning Setup is used to tune the flight characteristics of the Vehicle."); } QString APMTuningComponent::iconResource(void) const diff --git a/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml b/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml index d1e70721e..2bb09e601 100644 --- a/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml +++ b/src/AutoPilotPlugins/APM/APMTuningComponentCopter.qml @@ -17,113 +17,109 @@ import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 -QGCView { - id: _safetyView - viewPanel: panel - anchors.fill: parent - - FactPanelController { id: controller; factPanel: panel } - - QGCPalette { id: palette; colorGroupEnabled: enabled } - - // Older firmwares use THR_MODE, newer use MOT_THST_HOVER - property bool _throttleMidExists: controller.parameterExists(-1, "THR_MID") - property Fact _hoverTuneParam: controller.getParameterFact(-1, _throttleMidExists ? "THR_MID" : "MOT_THST_HOVER") - property real _hoverTuneMin: _throttleMidExists ? 200 : 0 - property real _hoverTuneMax: _throttleMidExists ? 800 : 1 - property real _hoverTuneStep: _throttleMidExists ? 10 : 0.01 - - property Fact _rcFeel: controller.getParameterFact(-1, "RC_FEEL_RP") - property Fact _rateRollP: controller.getParameterFact(-1, "r.ATC_RAT_RLL_P") - property Fact _rateRollI: controller.getParameterFact(-1, "r.ATC_RAT_RLL_I") - property Fact _ratePitchP: controller.getParameterFact(-1, "r.ATC_RAT_PIT_P") - property Fact _ratePitchI: controller.getParameterFact(-1, "r.ATC_RAT_PIT_I") - property Fact _rateClimbP: controller.getParameterFact(-1, "ACCEL_Z_P") - property Fact _rateClimbI: controller.getParameterFact(-1, "ACCEL_Z_I") - - property Fact _ch7Opt: controller.getParameterFact(-1, "CH7_OPT") - property Fact _ch8Opt: controller.getParameterFact(-1, "CH8_OPT") - property Fact _ch9Opt: controller.getParameterFact(-1, "CH9_OPT") - property Fact _ch10Opt: controller.getParameterFact(-1, "CH10_OPT") - property Fact _ch11Opt: controller.getParameterFact(-1, "CH11_OPT") - property Fact _ch12Opt: controller.getParameterFact(-1, "CH12_OPT") - - readonly property int _firstOptionChannel: 7 - readonly property int _lastOptionChannel: 12 - - property Fact _autoTuneAxes: controller.getParameterFact(-1, "AUTOTUNE_AXES") - property int _autoTuneSwitchChannelIndex: 0 - readonly property int _autoTuneOption: 17 - - property real _margins: ScreenTools.defaultFontPixelHeight - - property bool _loadComplete: false - - ExclusiveGroup { id: fenceActionRadioGroup } - ExclusiveGroup { id: landLoiterRadioGroup } - ExclusiveGroup { id: returnAltRadioGroup } - - Component.onCompleted: { - // Qml Sliders have a strange behavior in which they first set Slider::value to some internal - // setting and then set Slider::value to the bound properties value. If you have an onValueChanged - // handler which updates your property with the new value, this first value change will trash - // your bound values. In order to work around this we don't set the values into the Sliders until - // after Qml load is done. We also don't track value changes until Qml load completes. - throttleHover.value = _hoverTuneParam.value - rollPitch.value = _rateRollP.value - climb.value = _rateClimbP.value - rcFeel.value = _rcFeel.value - _loadComplete = true - - calcAutoTuneChannel() - } - - /// The AutoTune switch is stored in one of the RC#_FUNCTION parameters. We need to loop through those - /// to find them and setup the ui accordindly. - function calcAutoTuneChannel() { - _autoTuneSwitchChannelIndex = 0 - for (var channel=_firstOptionChannel; channel<=_lastOptionChannel; channel++) { - var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") - if (optionFact.value == _autoTuneOption) { - _autoTuneSwitchChannelIndex = channel - _firstOptionChannel + 1 - break +SetupPage { + id: tuningPage + pageComponent: tuningPageComponent + + Component { + id: tuningPageComponent + + Column { + width: availableWidth + spacing: _margins + + FactPanelController { id: controller; factPanel: tuningPage.viewPanel } + + QGCPalette { id: palette; colorGroupEnabled: true } + + // Older firmwares use THR_MODE, newer use MOT_THST_HOVER + property bool _throttleMidExists: controller.parameterExists(-1, "THR_MID") + property Fact _hoverTuneParam: controller.getParameterFact(-1, _throttleMidExists ? "THR_MID" : "MOT_THST_HOVER") + property real _hoverTuneMin: _throttleMidExists ? 200 : 0 + property real _hoverTuneMax: _throttleMidExists ? 800 : 1 + property real _hoverTuneStep: _throttleMidExists ? 10 : 0.01 + + property Fact _rcFeel: controller.getParameterFact(-1, "RC_FEEL_RP") + property Fact _rateRollP: controller.getParameterFact(-1, "r.ATC_RAT_RLL_P") + property Fact _rateRollI: controller.getParameterFact(-1, "r.ATC_RAT_RLL_I") + property Fact _ratePitchP: controller.getParameterFact(-1, "r.ATC_RAT_PIT_P") + property Fact _ratePitchI: controller.getParameterFact(-1, "r.ATC_RAT_PIT_I") + property Fact _rateClimbP: controller.getParameterFact(-1, "ACCEL_Z_P") + property Fact _rateClimbI: controller.getParameterFact(-1, "ACCEL_Z_I") + + property Fact _ch7Opt: controller.getParameterFact(-1, "CH7_OPT") + property Fact _ch8Opt: controller.getParameterFact(-1, "CH8_OPT") + property Fact _ch9Opt: controller.getParameterFact(-1, "CH9_OPT") + property Fact _ch10Opt: controller.getParameterFact(-1, "CH10_OPT") + property Fact _ch11Opt: controller.getParameterFact(-1, "CH11_OPT") + property Fact _ch12Opt: controller.getParameterFact(-1, "CH12_OPT") + + readonly property int _firstOptionChannel: 7 + readonly property int _lastOptionChannel: 12 + + property Fact _autoTuneAxes: controller.getParameterFact(-1, "AUTOTUNE_AXES") + property int _autoTuneSwitchChannelIndex: 0 + readonly property int _autoTuneOption: 17 + + property real _margins: ScreenTools.defaultFontPixelHeight + + property bool _loadComplete: false + + ExclusiveGroup { id: fenceActionRadioGroup } + ExclusiveGroup { id: landLoiterRadioGroup } + ExclusiveGroup { id: returnAltRadioGroup } + + Component.onCompleted: { + // Qml Sliders have a strange behavior in which they first set Slider::value to some internal + // setting and then set Slider::value to the bound properties value. If you have an onValueChanged + // handler which updates your property with the new value, this first value change will trash + // your bound values. In order to work around this we don't set the values into the Sliders until + // after Qml load is done. We also don't track value changes until Qml load completes. + throttleHover.value = _hoverTuneParam.value + rollPitch.value = _rateRollP.value + climb.value = _rateClimbP.value + rcFeel.value = _rcFeel.value + _loadComplete = true + + calcAutoTuneChannel() } - } - } - - /// We need to clear AutoTune from any previous channel before setting it to a new one - function setChannelAutoTuneOption(channel) { - // First clear any previous settings for AutTune - for (var optionChannel=_firstOptionChannel; optionChannel<=_lastOptionChannel; optionChannel++) { - var optionFact = controller.getParameterFact(-1, "CH" + optionChannel + "_OPT") - if (optionFact.value == _autoTuneOption) { - optionFact.value = 0 + + /// The AutoTune switch is stored in one of the RC#_FUNCTION parameters. We need to loop through those + /// to find them and setup the ui accordindly. + function calcAutoTuneChannel() { + _autoTuneSwitchChannelIndex = 0 + for (var channel=_firstOptionChannel; channel<=_lastOptionChannel; channel++) { + var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") + if (optionFact.value == _autoTuneOption) { + _autoTuneSwitchChannelIndex = channel - _firstOptionChannel + 1 + break + } + } } - } - - // Now set the function into the new channel - if (channel != 0) { - var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") - optionFact.value = _autoTuneOption - } - } - - Connections { target: _ch7Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch8Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch9Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch10Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch11Opt; onValueChanged: calcAutoTuneChannel() } - Connections { target: _ch12Opt; onValueChanged: calcAutoTuneChannel() } - - QGCViewPanel { - id: panel - anchors.fill: parent - - QGCFlickable { - clip: true - anchors.fill: parent - contentHeight: autoTuneRect.y + autoTuneRect.height - flickableDirection: Flickable.VerticalFlick + + /// We need to clear AutoTune from any previous channel before setting it to a new one + function setChannelAutoTuneOption(channel) { + // First clear any previous settings for AutTune + for (var optionChannel=_firstOptionChannel; optionChannel<=_lastOptionChannel; optionChannel++) { + var optionFact = controller.getParameterFact(-1, "CH" + optionChannel + "_OPT") + if (optionFact.value == _autoTuneOption) { + optionFact.value = 0 + } + } + + // Now set the function into the new channel + if (channel != 0) { + var optionFact = controller.getParameterFact(-1, "CH" + channel + "_OPT") + optionFact.value = _autoTuneOption + } + } + + Connections { target: _ch7Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch8Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch9Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch10Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch11Opt; onValueChanged: calcAutoTuneChannel() } + Connections { target: _ch12Opt; onValueChanged: calcAutoTuneChannel() } QGCLabel { id: basicLabel @@ -133,10 +129,8 @@ QGCView { Rectangle { id: basicTuningRect - anchors.topMargin: _margins / 2 anchors.left: parent.left anchors.right: parent.right - anchors.top: basicLabel.bottom height: basicTuningColumn.y + basicTuningColumn.height + _margins color: palette.windowShade @@ -276,16 +270,15 @@ QGCView { } // Rectangle - Basic tuning Flow { - id: flowLayout - anchors.topMargin: _margins / 2 - width: panel.width // parent.width doesn't work here for some reason! - anchors.top: basicTuningRect.bottom - spacing: _margins + id: flowLayout + anchors.left: parent.left + anchors.right: parent.right + spacing: _margins Rectangle { - height: autoTuneLabel.height + autoTuneRect.height - width: autoTuneRect.width - color: palette.window + height: autoTuneLabel.height + autoTuneRect.height + width: autoTuneRect.width + color: palette.window QGCLabel { id: autoTuneLabel @@ -294,11 +287,11 @@ QGCView { } Rectangle { - id: autoTuneRect - width: autoTuneColumn.x + autoTuneColumn.width + _margins - height: autoTuneColumn.y + autoTuneColumn.height + _margins - anchors.top: autoTuneLabel.bottom - color: palette.windowShade + id: autoTuneRect + width: autoTuneColumn.x + autoTuneColumn.width + _margins + height: autoTuneColumn.y + autoTuneColumn.height + _margins + anchors.top: autoTuneLabel.bottom + color: palette.windowShade Column { id: autoTuneColumn @@ -417,6 +410,6 @@ QGCView { } // Rectangle - Channel 6 Tuning options } // Rectangle - Channel 6 Tuning options wrap } // Flow - Tune - } // QGCFlickable - } // QGCViewPanel -} // QGCView + } // Column + } // Component +} // SetupView diff --git a/src/FirmwarePlugin/APM/APMBrandImage.png b/src/FirmwarePlugin/APM/APMBrandImage.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4c59f6498ae43764ec9397c1c9be1eb5825331 GIT binary patch literal 27565 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�GGDb!E=Ya%9m;n= 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 +} + const char* VehicleGPSFactGroup::_hdopFactName = "hdop"; const char* VehicleGPSFactGroup::_vdopFactName = "vdop"; const char* VehicleGPSFactGroup::_courseOverGroundFactName = "courseOverGround"; diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index d88bd0e2e..c2b3bfbed 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -279,6 +279,7 @@ public: Q_PROPERTY(bool coaxialMotors READ coaxialMotors CONSTANT) Q_PROPERTY(bool xConfigMotors READ xConfigMotors CONSTANT) Q_PROPERTY(bool isOfflineEditingVehicle READ isOfflineEditingVehicle CONSTANT) + Q_PROPERTY(QString brandImage READ brandImage CONSTANT) /// true: Vehicle is flying, false: Vehicle is on ground Q_PROPERTY(bool flying READ flying WRITE setFlying NOTIFY flyingChanged) @@ -524,6 +525,7 @@ public: uint8_t baseMode () const { return _base_mode; } uint32_t customMode () const { return _custom_mode; } bool isOfflineEditingVehicle () const { return _offlineEditingVehicle; } + QString brandImage () const; Fact* roll (void) { return &_rollFact; } Fact* heading (void) { return &_headingFact; } diff --git a/src/ui/toolbar/MainToolBar.qml b/src/ui/toolbar/MainToolBar.qml index ad45e36ab..f8b4170e9 100644 --- a/src/ui/toolbar/MainToolBar.qml +++ b/src/ui/toolbar/MainToolBar.qml @@ -54,7 +54,7 @@ Rectangle { MainToolBarController { id: _controller } function checkSettingsButton() { - settingsButton.checked = true + settingsButton.checked = true } function checkSetupButton() { @@ -421,6 +421,17 @@ Rectangle { primary: true onClicked: activeVehicle.disconnectInactiveVehicle() } + + Image { + anchors.rightMargin: ScreenTools.defaultFontPixelWidth / 2 + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + visible: parent.x < x && !disconnectButton.visible && source != "" + fillMode: Image.PreserveAspectFit + source: activeVehicle ? activeVehicle.brandImage : "" + } + } // Progress bar -- 2.22.0