diff --git a/qgcresources.qrc b/qgcresources.qrc index 5b6fa54b53d55297f9774605cfbb30482e0a7eb5..ff5c03e4dbebbbdfabfd3a0879844fd8c20c3774 100644 --- a/qgcresources.qrc +++ b/qgcresources.qrc @@ -171,6 +171,7 @@ src/FirmwarePlugin/APM/APMBrandImageSub.png src/FirmwarePlugin/PX4/PX4BrandImage.png src/FlightMap/Images/sub.png + resources/check.svg resources/action.svg diff --git a/qgroundcontrol.qrc b/qgroundcontrol.qrc index bf57edb25869f8b7509718a723e229ae89b3b8f6..50ba35cca779a19168b3bc623db7bf9d1d446a72 100644 --- a/qgroundcontrol.qrc +++ b/qgroundcontrol.qrc @@ -87,6 +87,7 @@ src/QmlControls/ParameterEditorDialog.qml src/PlanView/PlanToolBar.qml src/QmlControls/QGCButton.qml + src/QmlControls/QGCCheckListItem.qml src/QmlControls/QGCCheckBox.qml src/QmlControls/QGCColoredImage.qml src/QmlControls/QGCComboBox.qml diff --git a/resources/check.svg b/resources/check.svg new file mode 100644 index 0000000000000000000000000000000000000000..f5c4901ebe56008b699828a31f3c22c2cfa42e9e --- /dev/null +++ b/resources/check.svg @@ -0,0 +1,80 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/FlightDisplay/FlightDisplayView.qml b/src/FlightDisplay/FlightDisplayView.qml index 85a4876103580b9705f19228cbf6537d77fa8344..e9cdd56742c2c3144c0de5945fe68ff52f2283d7 100644 --- a/src/FlightDisplay/FlightDisplayView.qml +++ b/src/FlightDisplay/FlightDisplayView.qml @@ -504,8 +504,8 @@ QGCView { z: _panel.z + 4 title: qsTr("Fly") maxHeight: (_flightVideo.visible ? _flightVideo.y : parent.height) - toolStrip.y - buttonVisible: [ _guidedController.showTakeoff || !_guidedController.showLand, _guidedController.showLand && !_guidedController.showTakeoff, true, true, true, _guidedController.smartShotsAvailable ] - buttonEnabled: [ _guidedController.showTakeoff, _guidedController.showLand, _guidedController.showRTL, _guidedController.showPause, _anyActionAvailable, _anySmartShotAvailable ] + buttonVisible: [ QGroundControl.settingsManager.appSettings.useChecklist.rawValue, _guidedController.showTakeoff || !_guidedController.showLand, _guidedController.showLand && !_guidedController.showTakeoff, true, true, true, _guidedController.smartShotsAvailable ] + buttonEnabled: [ QGroundControl.settingsManager.appSettings.useChecklist.rawValue, _guidedController.showTakeoff, _guidedController.showLand, _guidedController.showRTL, _guidedController.showPause, _anyActionAvailable, _anySmartShotAvailable ] property bool _anyActionAvailable: _guidedController.showStartMission || _guidedController.showResumeMission || _guidedController.showChangeAlt || _guidedController.showLandAbort property bool _anySmartShotAvailable: _guidedController.showOrbit @@ -551,6 +551,11 @@ QGCView { ] model: [ + { + name: "Checklist", + iconSource: "/qmlimages/check.svg", + dropPanelComponent: checklistDropPanel + }, { name: _guidedController.takeoffTitle, iconSource: "/res/takeoff.svg", @@ -677,4 +682,292 @@ QGCView { visible: false } } -} + + Component { + id: checklistDropPanel + + Rectangle { + id: checklist + width: mainColumn.width + (ScreenTools.defaultFontPixelWidth * 4) + height: (headerColumn.height+mainColumn.height) * 1.07 + color: qgcPal.windowShade + radius: 20 + enabled: QGroundControl.multiVehicleManager.vehicles.count > 0; + + onBatPercentRemainingChanged: {if(_initialized) buttonBattery.updateItem();} + onGpsLockChanged: {buttonSensors.updateItem();} + + // Connections + Connections { + target: _activeVehicle + onUnhealthySensorsChanged: checklist.onUnhealthySensorsChanged(); + } + Connections { + target: QGroundControl.multiVehicleManager + onActiveVehicleChanged: checklist.onActiveVehicleChanged(); + onActiveVehicleAvailableChanged: {} + } + Connections { + target: QGroundControl.settingsManager.appSettings.audioMuted + onValueChanged: buttonSoundOutput.updateItem(); //TODO(philippoe): We are binding to a signal which is explicitly marked as "only for QT internal use" here. + } + Component.onCompleted: { + if(QGroundControl.multiVehicleManager.vehicles.count > 0) { + onActiveVehicleChanged(); + _initialized=true; + } + } + + function updateVehicleDependentItems() { + buttonSensors.updateItem(); + buttonBattery.updateItem(); + buttonRC.updateItem(); + buttonEstimator.updateItem(); + } + function onActiveVehicleChanged() { + buttonSoundOutput.updateItem(); // Just updated here for initialization once we connect to a vehicle + onUnhealthySensorsChanged(); // The health states could all have changed - need to update them. + } + function onUnhealthySensorsChanged() { + var unhealthySensorsStr = _activeVehicle.unhealthySensors; + + // Set to healthy per default + for(var i=0;i<32;i++) _healthFlags[i]=true; + + for(i=0;i 0; + opacity : 0.2+0.8*(QGroundControl.multiVehicleManager.vehicles.count > 0); + + // Checklist items: Standard + QGCCheckListItem { + id: buttonHardware + name: "Hardware" + defaulttext: "Props mounted? Wings secured? Tail secured?" + } + + QGCCheckListItem { + id: buttonBattery + name: "Battery" + pendingtext: "Healthy & charged > 40%. Battery connector firmly plugged?" + function updateItem() { + if (!_activeVehicle) { + _state = 0; + } else { + if (checklist._healthFlags[25] && batPercentRemaining>=40.0) _state = 1+3*(_nrClicked>0); + else { + if(!checklist._healthFlags[25]) buttonBattery.failuretext="Not healthy. Check console."; + else if(batPercentRemaining<40.0) buttonBattery.failuretext="Low (below 40%). Please recharge."; + buttonBattery._state = 3; + } + } + } + } + + QGCCheckListItem { + id: buttonSensors + name: "Sensors" + function updateItem() { + if (!_activeVehicle) { + _state = 0; + } else { + if(checklist._healthFlags[0] && + checklist._healthFlags[1] && + checklist._healthFlags[2] && + checklist._healthFlags[3] && + checklist._healthFlags[4] && + checklist._healthFlags[5]) { + if(!gpsLock) { + buttonSensors.pendingtext="Pending. Waiting for GPS lock."; + buttonSensors._state=1; + } else { + _state = 4; // All OK + } + } else { + if(!checklist._healthFlags[0]) failuretext="Failure. Gyroscope issues. Check console."; + else if(!checklist._healthFlags[1]) failuretext="Failure. Accelerometer issues. Check console."; + else if(!checklist._healthFlags[2]) failuretext="Failure. Magnetometer issues. Check console."; + else if(!checklist._healthFlags[3]) failuretext="Failure. Barometer issues. Check console."; + else if(!checklist._healthFlags[4]) failuretext="Failure. Airspeed sensor issues. Check console."; + else if(!checklist._healthFlags[5]) failuretext="Failure. No valid or low quality GPS signal. Check console."; + _state = 3; + } + } + } + } + QGCCheckListItem { + id: buttonRC + name: "Radio Control" + pendingtext: "Receiving signal. Perform range test & confirm." + failuretext: "No signal or invalid autopilot-RC config. Check RC and console." + function updateItem() { + if (!_activeVehicle) { + _state = 0; + } else { + if (_healthFlags[16]) {_state = 1+3*(_nrClicked>0);} + else {_state = 3;} + } + } + } + + QGCCheckListItem { + id: buttonEstimator + name: "Global position estimate" + function updateItem() { + if (!_activeVehicle) { + _state = 0; + } else { + if (_healthFlags[21]) {_state = 4;} + else {_state = 3;} + } + } + } + + // Arming header + //Rectangle {anchors.left:parent.left ; anchors.right:parent.right ; height:1 ; color:qgcPal.text} + QGCLabel {anchors.horizontalCenter:parent.horizontalCenter ; text:qsTr("Please arm the vehicle here.")} + //Rectangle {anchors.left:parent.left ; anchors.right:parent.right ; height:1 ; color:qgcPal.text} + + QGCCheckListItem { + id: buttonActuators + name: "Actuators" + group: 1 + defaulttext: "Move all control surfaces. Did they work properly?" + } + + QGCCheckListItem { + id: buttonMotors + name: "Motors" + group: 1 + defaulttext: "Propellers free? Then throttle up gently. Working properly?" + } + + QGCCheckListItem { + id: buttonMission + name: "Mission" + group: 1 + defaulttext: "Please confirm mission is valid (waypoints valid, no terrain collision)." + } + + QGCCheckListItem { + id: buttonSoundOutput + name: "Sound output" + group: 1 + pendingtext: "QGC audio output enabled. System audio output enabled, too?" + failuretext: "Failure, QGC audio output is disabled. Please enable it under application settings->general to hear audio warnings!" + function updateItem() { + if (!_activeVehicle) { + _state = 0; + } else { + if (QGroundControl.settingsManager.appSettings.audioMuted.rawValue) {_state = 3;_nrClicked=0;} + else {_state = 1+3*(_nrClicked>0);} + } + } + } + + // Directly before launch header + //Rectangle {anchors.left:parent.left ; anchors.right:parent.right ; height:1 ; color:qgcPal.text} + QGCLabel {anchors.horizontalCenter:parent.horizontalCenter ; text:qsTr("Last preparations before launch") ; opacity : 0.2+0.8*(_checkState >= 2);} + //Rectangle {anchors.left:parent.left ; anchors.right:parent.right ; height:1 ; color:qgcPal.text} + + QGCCheckListItem { + id: buttonPayload + name: "Payload" + group: 2 + defaulttext: "Configured and started?" + pendingtext: "Payload lid closed?" + } + + QGCCheckListItem { + id: buttonWeather + name: "Wind & weather" + group: 2 + defaulttext: "OK for your platform?" + pendingtext: "Launching into the wind?" + } + + QGCCheckListItem { + id: buttonFlightAreaFree + name: "Flight area" + group: 2 + defaulttext: "Launch area and path free of obstacles/people?" + } + + } // Column + + property bool _initialized:false + property var _healthFlags: [] + property int _checkState: _activeVehicle ? (_activeVehicle.armed ? 1 + (buttonActuators._state + buttonMotors._state + buttonMission._state + buttonSoundOutput._state) / 4 / 4 : 0) : 0 ; // Shows progress of checks inside the checklist - unlocks next check steps in groups + property bool gpsLock: _activeVehicle ? _activeVehicle.gps.lock.rawValue>=3 : 0 + property var batPercentRemaining: _activeVehicle ? _activeVehicle.battery.getFact("percentRemaining").value : 0 + + // TODO: Having access to MAVLINK enums (or at least QML consts) would be much cleaner than the code below + property int subsystem_type_gyro : 1 + property int subsystem_type_acc : 2 + property int subsystem_type_mag : 4 + property int subsystem_type_abspressure : 8 + property int subsystem_type_diffpressure : 16 + property int subsystem_type_gps : 32 + property int subsystem_type_positioncontrol : 16384 + property int subsystem_type_motorcontrol : 32768 + property int subsystem_type_rcreceiver : 65536 + property int subsystem_type_ahrs : 2097152 + property int subsystem_type_terrain : 4194304 + property int subsystem_type_reversemotor : 8388608 + property int subsystem_type_logging : 16777216 + property int subsystem_type_sensorbattery : 33554432 + property int subsystem_type_rangefinder : 67108864 + } //Rectangle + } //Component +} //QGC View diff --git a/src/QmlControls/DropPanel.qml b/src/QmlControls/DropPanel.qml index 91fe3dcc58206c681f06b75c3d2f3d36a5dab191..1de3e1c0a7f3d647f7edaefd0c6af839c9292cae 100644 --- a/src/QmlControls/DropPanel.qml +++ b/src/QmlControls/DropPanel.qml @@ -64,7 +64,7 @@ Item { } if (visible) { visible = false - _dropDownComponent = undefined + //_dropDownComponent = undefined //TODO (philippoe) such that drop down component state is not deleted - check with don gagne whether this is necessary toolStrip.uncheckAll() } } diff --git a/src/QmlControls/QGCCheckListItem.qml b/src/QmlControls/QGCCheckListItem.qml new file mode 100644 index 0000000000000000000000000000000000000000..cd1e82ccf2d240c336b15bd93614cbfc073b0d1b --- /dev/null +++ b/src/QmlControls/QGCCheckListItem.qml @@ -0,0 +1,76 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.4 + +import QGroundControl 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 + +QGCButton { + property string name: "" + property int _state: 0 + property var _color: qgcPal.button;//qgcPal.windowShade;//qgcPal.windowShadeDark;//Qt.rgba(0.5,0.5,0.5,1) //qgcPal.window;// + property int _nrClicked: 0 + property int group: 0 + property string defaulttext: "Not checked yet" + property string pendingtext: "" + property string failuretext: "Failure. Check console." + property string _text: qsTr(name)+ ": " + qsTr(defaulttext) + + enabled : (_activeVehicle==null || _activeVehicle.connectionLost) ? false : _checkState>=group + opacity : (_activeVehicle==null || _activeVehicle.connectionLost) ? 0.4 : 0.2+0.8*(_checkState >= group); + + width: parent.width + style: ButtonStyle { + background: Rectangle {color:_color; border.color: qgcPal.button; radius:3} + label: Label { + text: _text + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + color: _state>0 ? qgcPal.mapWidgetBorderLight : qgcPal.buttonText + } + } + + onClicked: { + if(_state<2) _nrClicked=_nrClicked+1; //Only allow click-counter to increase when not failed yet + updateItem(); + } + onPendingtextChanged: { if(_state==1) {getTextFromState(); getColorFromState();} } + onFailuretextChanged: { if(_state==3) {getTextFromState(); getColorFromState();} } + on_StateChanged: { getTextFromState(); getColorFromState(); } +// onEnabledChanged: { //Dont do this for now, because if we only accidentially lose connection, we don't want to delete the checklist state. Instead, we'd need to detect a re-connect, maybe based on the timesincesystemstart (i.e. when it decreases)? +// if(enabled==false && group > 0) { +// // Reset all check list items of group > 0 if it is disabled again (which e.g. happens after a vehicle reboot or disarm). +// _nrClicked = 0; +// _state = 0; +// } +// } + + function updateItem() { + // This is the default updateFunction. It assumes the item is a MANUAL check list item, i.e. one that + // only requires user clicks (one click if pendingtext="", two clicks otherwise) for completion. + //if(_nrClicked>0) _state = 4; + + if(_nrClicked===1) { + if(pendingtext.length === 0) _state = 4; + else _state = 1; + } else if(_nrClicked>1) _state = 4; + + getTextFromState(); + getColorFromState(); + } + function getTextFromState() { + if(_state === 0) {_text= qsTr(name) + ": " + qsTr(defaulttext)} // Not checked yet + else if(_state === 1) {_text= ""+qsTr(name)+"" +": " + qsTr(pendingtext)} // Pending + else if(_state === 2) {_text= ""+qsTr(name)+"" +": " + qsTr("Minor problem")} // Small problem or need further user action to resolve + else if(_state === 3) {_text= ""+qsTr(name)+"" +": " + qsTr(failuretext)} // Big problem + else {_text= ""+qsTr(name)+"" +": " + qsTr("OK")} // All OK + } + function getColorFromState() { + if(_state === 0) {_color=qgcPal.button} // Not checked yet + else if(_state === 1) {_color=Qt.rgba(0.9,0.47,0.2,1)} // Pending + else if(_state === 2) {_color=Qt.rgba(1.0,0.6,0.2,1)} // Small problem or need further user action to resolve + else if(_state === 3) {_color=Qt.rgba(0.92,0.22,0.22,1)} // Big problem + else {_color=Qt.rgba(0.27,0.67,0.42,1)} // All OK + } +} diff --git a/src/QmlControls/QGroundControl.Controls.qmldir b/src/QmlControls/QGroundControl.Controls.qmldir index 6bbe56f224fca0a8747be57936d101dd2d8c8551..22e8f166b9ef3bc29e8157778f170071aaa34dfb 100644 --- a/src/QmlControls/QGroundControl.Controls.qmldir +++ b/src/QmlControls/QGroundControl.Controls.qmldir @@ -34,6 +34,7 @@ ParameterEditor 1.0 ParameterEditor.qml ParameterEditorDialog 1.0 ParameterEditorDialog.qml PlanToolBar 1.0 PlanToolBar.qml QGCButton 1.0 QGCButton.qml +QGCCheckListItem 1.0 QGCCheckListItem.qml QGCCheckBox 1.0 QGCCheckBox.qml QGCColoredImage 1.0 QGCColoredImage.qml QGCComboBox 1.0 QGCComboBox.qml diff --git a/src/Settings/App.SettingsGroup.json b/src/Settings/App.SettingsGroup.json index 8e60817654335ff3cd472879bc6a075039e1d178..32d51f2bb4437097208fce78c1eec822d599a445 100644 --- a/src/Settings/App.SettingsGroup.json +++ b/src/Settings/App.SettingsGroup.json @@ -117,6 +117,13 @@ "type": "bool", "defaultValue": false }, +{ + "name": "UseChecklist", + "shortDescription": "Use preflight checklist", + "longDescription": "If this option is enabled the preflight checklist will be used.", + "type": "bool", + "defaultValue": false +}, { "name": "BaseDeviceFontPointSize", "shortDescription": "Application font size", diff --git a/src/Settings/AppSettings.cc b/src/Settings/AppSettings.cc index 56177c3196670e61d4d417281d1809b7675dd4f5..264181c8fd8fea423b9ca33d41ef90cde1aa7174 100644 --- a/src/Settings/AppSettings.cc +++ b/src/Settings/AppSettings.cc @@ -33,6 +33,7 @@ const char* AppSettings::indoorPaletteName = "StyleIs const char* AppSettings::showLargeCompassName = "ShowLargeCompass"; const char* AppSettings::savePathName = "SavePath"; const char* AppSettings::autoLoadMissionsName = "AutoLoadMissions"; +const char* AppSettings::useChecklistName = "UseChecklist"; const char* AppSettings::mapboxTokenName = "MapboxToken"; const char* AppSettings::esriTokenName = "EsriToken"; const char* AppSettings::defaultFirmwareTypeName = "DefaultFirmwareType"; @@ -75,6 +76,7 @@ AppSettings::AppSettings(QObject* parent) , _showLargeCompassFact (NULL) , _savePathFact (NULL) , _autoLoadMissionsFact (NULL) + , _useChecklistFact (NULL) , _mapboxTokenFact (NULL) , _esriTokenFact (NULL) , _defaultFirmwareTypeFact (NULL) @@ -220,6 +222,15 @@ Fact* AppSettings::audioMuted(void) return _audioMutedFact; } +Fact* AppSettings::useChecklist(void) +{ + if (!_useChecklistFact) { + _useChecklistFact = _createSettingsFact(useChecklistName); + } + + return _useChecklistFact; +} + Fact* AppSettings::appFontPointSize(void) { if (!_appFontPointSizeFact) { diff --git a/src/Settings/AppSettings.h b/src/Settings/AppSettings.h index d1a7e2fbbf38c301f5012d425d9dfb83ee4a819f..3b7b88fa6b1afe6aa24129ab3c462212dc1282a7 100644 --- a/src/Settings/AppSettings.h +++ b/src/Settings/AppSettings.h @@ -37,6 +37,7 @@ public: Q_PROPERTY(Fact* showLargeCompass READ showLargeCompass CONSTANT) Q_PROPERTY(Fact* savePath READ savePath CONSTANT) Q_PROPERTY(Fact* autoLoadMissions READ autoLoadMissions CONSTANT) + Q_PROPERTY(Fact* useChecklist READ useChecklist CONSTANT) Q_PROPERTY(Fact* mapboxToken READ mapboxToken CONSTANT) Q_PROPERTY(Fact* esriToken READ esriToken CONSTANT) Q_PROPERTY(Fact* defaultFirmwareType READ defaultFirmwareType CONSTANT) @@ -75,6 +76,7 @@ public: Fact* showLargeCompass (void); Fact* savePath (void); Fact* autoLoadMissions (void); + Fact* useChecklist (void); Fact* mapboxToken (void); Fact* esriToken (void); Fact* defaultFirmwareType (void); @@ -110,6 +112,7 @@ public: static const char* showLargeCompassName; static const char* savePathName; static const char* autoLoadMissionsName; + static const char* useChecklistName; static const char* mapboxTokenName; static const char* esriTokenName; static const char* defaultFirmwareTypeName; @@ -160,6 +163,7 @@ private: SettingsFact* _showLargeCompassFact; SettingsFact* _savePathFact; SettingsFact* _autoLoadMissionsFact; + SettingsFact* _useChecklistFact; SettingsFact* _mapboxTokenFact; SettingsFact* _esriTokenFact; SettingsFact* _defaultFirmwareTypeFact; diff --git a/src/ui/preferences/GeneralSettings.qml b/src/ui/preferences/GeneralSettings.qml index f302b3439b2a1275992a7a2af617b12078733d02..70aa6cb082c3aa9d0ae5b17cd66a5bac2ae4598d 100644 --- a/src/ui/preferences/GeneralSettings.qml +++ b/src/ui/preferences/GeneralSettings.qml @@ -411,6 +411,16 @@ QGCView { } } } + + //----------------------------------------------------------------- + //-- Checklist Settings + FactCheckBox { + text: qsTr("Use preflight checklist") + fact: _useChecklist + visible: _useChecklist.visible + + property Fact _useChecklist: QGroundControl.settingsManager.appSettings.useChecklist + } } }