From 4f8762485e312de6dcad5f71f22d426fc449ec88 Mon Sep 17 00:00:00 2001 From: Gus Grubba Date: Mon, 29 Jul 2019 22:34:32 -0400 Subject: [PATCH] Initial work supporting a custom gimbal --- custom-example/custom.qrc | 4 + .../res/Custom/Widgets/CustomQuickButton.qml | 56 ++ custom-example/res/Custom/Widgets/qmldir | 17 +- custom-example/res/CustomCameraControl.qml | 632 ++++++++++-------- custom-example/res/CustomFlyView.qml | 170 +++-- custom-example/res/Images/gimbal_icon.svg | 26 + custom-example/res/Images/gimbal_pitch.svg | 21 + custom-example/res/Images/gimbal_position.svg | 30 + custom-example/src/CustomPlugin.cc | 3 +- custom-example/src/CustomPlugin.h | 3 + .../src/FirmwarePlugin/CustomCameraControl.cc | 29 +- .../src/FirmwarePlugin/CustomCameraControl.h | 1 + 12 files changed, 653 insertions(+), 339 deletions(-) create mode 100644 custom-example/res/Custom/Widgets/CustomQuickButton.qml create mode 100644 custom-example/res/Images/gimbal_icon.svg create mode 100644 custom-example/res/Images/gimbal_pitch.svg create mode 100644 custom-example/res/Images/gimbal_position.svg diff --git a/custom-example/custom.qrc b/custom-example/custom.qrc index c27db1eb7..f70475bc2 100644 --- a/custom-example/custom.qrc +++ b/custom-example/custom.qrc @@ -24,6 +24,9 @@ res/Images/compass_needle.svg res/Images/compass_pointer.svg res/Images/distance.svg + res/Images/gimbal_icon.svg + res/Images/gimbal_pitch.svg + res/Images/gimbal_position.svg res/Images/horizontal_speed.svg res/Images/microSD.svg res/Images/odometer.svg @@ -40,6 +43,7 @@ res/Custom/Widgets/CustomComboBox.qml res/Custom/Widgets/CustomIconButton.qml res/Custom/Widgets/CustomOnOffSwitch.qml + res/Custom/Widgets/CustomQuickButton.qml res/Custom/Widgets/CustomSignalStrength.qml res/Custom/Widgets/CustomToolBarButton.qml res/Custom/Widgets/CustomVehicleButton.qml diff --git a/custom-example/res/Custom/Widgets/CustomQuickButton.qml b/custom-example/res/Custom/Widgets/CustomQuickButton.qml new file mode 100644 index 000000000..1e8f56d56 --- /dev/null +++ b/custom-example/res/Custom/Widgets/CustomQuickButton.qml @@ -0,0 +1,56 @@ +/**************************************************************************** + * + * (c) 2009-2019 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + * @file + * @author Gus Grubba + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 + +import QGroundControl 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.ScreenTools 1.0 + +Button { + id: control + autoExclusive: true + checkable: true + + property string iconSource: "" + property real iconRatio: 0.5 + property real buttonRadius: ScreenTools.defaultFontPixelWidth * 0.5 + + background: Rectangle { + width: control.width + height: width + anchors.centerIn: parent + color: (mouseArea.pressed || control.checked) ? qgcPal.buttonHighlight : (qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(1,1,1,0.5) : Qt.rgba(0,0,0,0.5)) + radius: control.buttonRadius + } + contentItem: Item { + anchors.fill: control + QGCColoredImage { + source: iconSource + color: (mouseArea.pressed || control.checked) ? qgcPal.buttonHighlightText : qgcPal.buttonText + width: control.width * iconRatio + height: width + anchors.centerIn: parent + sourceSize.height: height + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { + if(checkable) + checked = true + control.clicked() + } + } +} diff --git a/custom-example/res/Custom/Widgets/qmldir b/custom-example/res/Custom/Widgets/qmldir index 9c838de0b..9e547ccf4 100644 --- a/custom-example/res/Custom/Widgets/qmldir +++ b/custom-example/res/Custom/Widgets/qmldir @@ -1,10 +1,11 @@ Module Custom.Widgets -CustomArtificialHorizon 1.0 CustomArtificialHorizon.qml -CustomAttitudeWidget 1.0 CustomAttitudeWidget.qml -CustomComboBox 1.0 CustomComboBox.qml -CustomIconButton 1.0 CustomIconButton.qml -CustomOnOffSwitch 1.0 CustomOnOffSwitch.qml -CustomSignalStrength 1.0 CustomSignalStrength.qml -CustomToolBarButton 1.0 CustomToolBarButton.qml -CustomVehicleButton 1.0 CustomVehicleButton.qml \ No newline at end of file +CustomArtificialHorizon 1.0 CustomArtificialHorizon.qml +CustomAttitudeWidget 1.0 CustomAttitudeWidget.qml +CustomComboBox 1.0 CustomComboBox.qml +CustomIconButton 1.0 CustomIconButton.qml +CustomOnOffSwitch 1.0 CustomOnOffSwitch.qml +CustomQuickButton 1.0 CustomQuickButton.qml +CustomSignalStrength 1.0 CustomSignalStrength.qml +CustomToolBarButton 1.0 CustomToolBarButton.qml +CustomVehicleButton 1.0 CustomVehicleButton.qml diff --git a/custom-example/res/CustomCameraControl.qml b/custom-example/res/CustomCameraControl.qml index 28f9f1758..be0052a5a 100644 --- a/custom-example/res/CustomCameraControl.qml +++ b/custom-example/res/CustomCameraControl.qml @@ -36,11 +36,9 @@ Item { visible: !QGroundControl.videoManager.fullScreen readonly property string _commLostStr: qsTr("NO CAMERA") - readonly property real buttonSize: ScreenTools.defaultFontPixelWidth * 4 - readonly property real buttonRadius: ScreenTools.defaultFontPixelWidth * 0.5 - readonly property real iconRatio: 0.666 + readonly property real buttonSize: ScreenTools.defaultFontPixelWidth * 5 - property real _spacers: ScreenTools.defaultFontPixelHeight + property real _spacers: ScreenTools.defaultFontPixelHeight * 0.5 property real _labelFieldWidth: ScreenTools.defaultFontPixelWidth * 28 property real _editFieldWidth: ScreenTools.defaultFontPixelWidth * 30 property real _editFieldHeight: ScreenTools.defaultFontPixelHeight * 2 @@ -60,9 +58,11 @@ Item { property bool _recordingVideo: _cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING property bool _settingsEnabled: !_communicationLost && _camera && _camera.cameraMode !== QGCCameraControl.CAM_MODE_UNDEFINED && _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_IDLE && !_recordingVideo property bool _hasZoom: _camera && _camera.hasZoom - property Fact _evFact: _camera ? _camera.ev : null property Fact _irPaletteFact: _camera ? _camera.irPalette : null - + property bool _isShortScreen: mainWindow.height / ScreenTools.realPixelDensity < 120 + property real _gimbalPitch: activeVehicle ? -activeVehicle.gimbalPitch : 0 + property real _gimbalYaw: activeVehicle ? activeVehicle.gimbalYaw : 0 + property bool _hasGimbal: activeVehicle && activeVehicle.gimbalData Connections { target: QGroundControl.multiVehicleManager.activeVehicle onConnectionLostChanged: { @@ -80,18 +80,26 @@ Item { //-- Main Column Column { id: mainColumn - spacing: ScreenTools.defaultFontPixelHeight + spacing: _spacers anchors.centerIn: parent //--------------------------------------------------------------------- //-- Quick Thermal Modes - Rectangle { + Item { id: backgroundRect width: buttonsRow.width + (ScreenTools.defaultFontPixelWidth * 4) height: buttonsRow.height + (ScreenTools.defaultFontPixelHeight) - color: qgcPal.window - radius: height * 0.5 - visible: _camera && _camera.modelName === "DSC-QX30" && QGroundControl.videoManager.hasThermal + visible: _irPaletteFact && QGroundControl.videoManager.hasThermal || _camera.vendor === "NextVision" anchors.horizontalCenter: parent.horizontalCenter + Component.onCompleted: { + if(_irPaletteFact && QGroundControl.videoManager.hasThermal) { + if(_camera.thermalMode === QGCCameraControl.THERMAL_OFF) + standardMode.checked = true + if(_camera.thermalMode === QGCCameraControl.THERMAL_PIP) + thermalPip.checked = true + if(_camera.thermalMode === QGCCameraControl.THERMAL_FULL) + thermalFull.checked = true + } + } ButtonGroup { id: buttonGroup exclusive: true @@ -99,327 +107,357 @@ Item { } Row { id: buttonsRow - spacing: ScreenTools.defaultFontPixelWidth * 0.25 + spacing: ScreenTools.defaultFontPixelWidth * 0.5 anchors.centerIn: parent //-- Standard - QGCHoverButton { + CustomQuickButton { + id: standardMode width: buttonSize - height: width - checkable: true - radius: buttonRadius + height: buttonSize + iconSource: "/custom/img/thermal-standard.svg" onClicked: { _camera.thermalMode = QGCCameraControl.THERMAL_OFF - //-- Restore EV to 0 - if(_evFact) { - _evFact.value = 6 - } - } - QGCColoredImage { - source: "/custom/img/thermal-standard.svg" - color: parent.checked ? qgcPal.buttonHighlightText : qgcPal.buttonText - width: parent.width * iconRatio - height: width - anchors.centerIn: parent - sourceSize.height: height } } //-- PIP - QGCHoverButton { + CustomQuickButton { + id: thermalPip width: buttonSize - height: width - checkable: true - radius: buttonRadius - onClicked: _camera.thermalMode = QGCCameraControl.THERMAL_PIP - QGCColoredImage { - source: "/custom/img/thermal-pip.svg" - color: parent.checked ? qgcPal.buttonHighlightText : qgcPal.buttonText - width: parent.width * iconRatio - height: width - anchors.centerIn: parent - sourceSize.height: height + height: buttonSize + visible: _camera.vendor !== "NextVision" + iconSource: "/custom/img/thermal-pip.svg" + onClicked: { + _camera.thermalMode = QGCCameraControl.THERMAL_PIP } } - // Visual with high brightness - QGCHoverButton { + // Thermal + CustomQuickButton { + id: thermalFull width: buttonSize - height: width - checkable: true - radius: buttonRadius - onClicked: { - _camera.thermalMode = QGCCameraControl.THERMAL_OFF - //-- Set EV to +1.3 - if(_evFact) { - _evFact.value = 10 - } - } - QGCColoredImage { - source: "/custom/img/thermal-brightness.svg" - color: parent.checked ? qgcPal.buttonHighlightText : qgcPal.buttonText - width: parent.width * iconRatio - height: width - anchors.centerIn: parent - sourceSize.height: height + height: buttonSize + iconSource: "/custom/img/thermal-brightness.svg" + onClicked: { + _camera.thermalMode = QGCCameraControl.THERMAL_FULL } } - - // Thermal with color-map - QGCHoverButton { + // Thermal palette options + CustomQuickButton { + checkable: false + enabled: thermalFull.checked || thermalPip.checked width: buttonSize - height: width - checkable: true - radius: buttonRadius - - function isDesiredThermalPalette(name) { - return name === 'Rainbow'; - } - onClicked: { + height: buttonSize + iconSource: "/custom/img/thermal-palette.svg" + onClicked: { if(_irPaletteFact) { - var entryIdx = _irPaletteFact.enumStrings.find(isDesiredThermalPalette) - if(entryIdx !== undefined) { - _irPaletteFact.value = entryIdx; - } - _camera.thermalMode = QGCCameraControl.THERMAL_FULL + thermalPalettes.open() } } - QGCColoredImage { - source: "/custom/img/thermal-palette.svg" - color: parent.checked ? qgcPal.buttonHighlightText : qgcPal.buttonText - width: parent.width * iconRatio - height: width - anchors.centerIn: parent - sourceSize.height: height - } } } } //--------------------------------------------------------------------- //-- Main Camera Control - Rectangle { - id: cameraRect - height: cameraCol.height - width: cameraCol.width + (ScreenTools.defaultFontPixelWidth * 4) - color: qgcPal.window - radius: ScreenTools.defaultFontPixelWidth * 0.5 + Row { + spacing: ScreenTools.defaultFontPixelWidth * 0.5 anchors.horizontalCenter: parent.horizontalCenter - Column { - id: cameraCol - spacing: _spacers - anchors.centerIn: parent - Item { - height: 1 - width: 1 - } - //----------------------------------------------------------------- - //-- Camera Name - QGCLabel { - text: activeVehicle ? (_camera && _camera.modelName !== "" ? _camera.modelName : _commLostStr) : _commLostStr - font.pointSize: ScreenTools.smallFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } - //----------------------------------------------------------------- - //-- Camera Mode - Item { - width: modeCol.width - height: modeCol.height - anchors.horizontalCenter: parent.horizontalCenter - Column { - id: modeCol - spacing: _spacers * 0.5 - QGCColoredImage { - height: ScreenTools.defaultFontPixelHeight * 1.25 - width: height - source: (_cameraModeUndefined || _cameraPhotoMode) ? "/custom/img/camera_photo.svg" : "/custom/img/camera_video.svg" - color: qgcPal.text - fillMode: Image.PreserveAspectFit - sourceSize.height: height - anchors.horizontalCenter: parent.horizontalCenter - } - QGCLabel { - text: _cameraVideoMode ? qsTr("Video") : qsTr("Photo") - font.pointSize: ScreenTools.smallFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } + Rectangle { + id: cameraRect + height: cameraCol.height + width: cameraCol.width + (ScreenTools.defaultFontPixelWidth * 4) + color: qgcPal.window + radius: ScreenTools.defaultFontPixelWidth * 0.5 + Column { + id: cameraCol + spacing: _spacers + anchors.centerIn: parent + Item { + height: 1 + width: 1 + } + //----------------------------------------------------------------- + //-- Camera Name + QGCLabel { + text: activeVehicle ? (_camera && _camera.modelName !== "" ? _camera.modelName : _commLostStr) : _commLostStr + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter } - MouseArea { - anchors.fill: parent - enabled: !_cameraModeUndefined && _camera && _camera.videoStatus !== QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING && _cameraPhotoIdle - onClicked: { - _camera.toggleMode() + QGCLabel { + text: { + if(_noSdCard) return qsTr("NONE") + if(_fullSD) return qsTr("FULL") + return _camera ? _camera.storageFreeStr : "" } + visible: _isShortScreen + color: (_noSdCard || _fullSD) ? qgcPal.colorOrange : qgcPal.text + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter } - } - //----------------------------------------------------------------- - //-- Shutter - Rectangle { - color: Qt.rgba(0,0,0,0) - width: height - height: ScreenTools.defaultFontPixelHeight * 4 - radius: width * 0.5 - border.color: qgcPal.buttonText - border.width: 2 - anchors.horizontalCenter: parent.horizontalCenter - Rectangle { - width: parent.width * 0.75 - height: width - radius: width * 0.5 - color: _cameraModeUndefined ? qgcPal.colorGrey : ( _cameraVideoMode ? qgcPal.colorRed : qgcPal.text ) - visible: !pauseVideo.visible - anchors.centerIn: parent - QGCColoredImage { - id: busyIndicator - height: parent.height * 0.75 - width: height - source: "/qmlimages/MapSync.svg" - sourceSize.height: height - fillMode: Image.PreserveAspectFit - mipmap: true - smooth: true - color: qgcPal.window - visible: { - if(_cameraPhotoMode && !_cameraPhotoIdle && !_cameraElapsedMode) { - return true - } - return false + //----------------------------------------------------------------- + //-- Camera Mode + Item { + width: modeCol.width + height: modeCol.height + anchors.horizontalCenter: parent.horizontalCenter + Column { + id: modeCol + spacing: _spacers + QGCColoredImage { + height: ScreenTools.defaultFontPixelHeight * 1.25 + width: height + source: (_cameraModeUndefined || _cameraPhotoMode) ? "/custom/img/camera_photo.svg" : "/custom/img/camera_video.svg" + color: qgcPal.text + fillMode: Image.PreserveAspectFit + sourceSize.height: height + anchors.horizontalCenter: parent.horizontalCenter } - anchors.centerIn: parent - RotationAnimation on rotation { - loops: Animation.Infinite - from: 360 - to: 0 - duration: 740 - running: busyIndicator.visible + QGCLabel { + text: _cameraVideoMode ? qsTr("Video") : qsTr("Photo") + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter } } - QGCLabel { - text: _camera ? _camera.photoLapse.toFixed(0) + 's' : qsTr('N/A') - font.family: ScreenTools.demiboldFontFamily - color: qgcPal.colorBlue - visible: _cameraElapsedMode - anchors.centerIn: parent + MouseArea { + anchors.fill: parent + enabled: !_cameraModeUndefined && _camera && _camera.videoStatus !== QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING && _cameraPhotoIdle + onClicked: { + _camera.toggleMode() + } } } + //----------------------------------------------------------------- + //-- Shutter Rectangle { - id: pauseVideo - width: parent.width * 0.5 - height: width - color: _cameraModeUndefined ? qgcPal.colorGrey : qgcPal.colorRed - visible: { - if(_cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) { - return true - } - if(_cameraPhotoMode) { - if(_camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IDLE || _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IN_PROGRESS) { + color: Qt.rgba(0,0,0,0) + width: height + height: ScreenTools.defaultFontPixelHeight * 4 + radius: width * 0.5 + border.color: qgcPal.buttonText + border.width: 2 + anchors.horizontalCenter: parent.horizontalCenter + Rectangle { + width: parent.width * 0.95 + height: width + radius: width * 0.5 + color: _cameraModeUndefined ? qgcPal.colorGrey : ( _cameraVideoMode ? qgcPal.colorRed : qgcPal.text ) + visible: !pauseVideo.visible + anchors.centerIn: parent + QGCColoredImage { + id: busyIndicator + height: parent.height * 0.75 + width: height + source: "/qmlimages/MapSync.svg" + sourceSize.height: height + fillMode: Image.PreserveAspectFit + mipmap: true + smooth: true + color: qgcPal.window + visible: { + if(_cameraPhotoMode && !_cameraPhotoIdle && !_cameraElapsedMode) { + return true + } + return false + } + anchors.centerIn: parent + RotationAnimation on rotation { + loops: Animation.Infinite + from: 360 + to: 0 + duration: 740 + running: busyIndicator.visible + } + } + QGCLabel { + text: _camera ? _camera.photoLapse.toFixed(0) + 's' : qsTr('N/A') + font.family: ScreenTools.demiboldFontFamily + color: qgcPal.colorBlue + visible: _cameraElapsedMode + anchors.centerIn: parent + } + } + Rectangle { + id: pauseVideo + width: parent.width * 0.5 + height: width + color: _cameraModeUndefined ? qgcPal.colorGrey : qgcPal.colorRed + visible: { + if(_cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) { return true } - } - return false - } - anchors.centerIn: parent - } - MouseArea { - anchors.fill: parent - enabled: !_noSdCard - onClicked: { - if(_cameraVideoMode) { - if(_camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) { - _camera.stopVideo() - } else { - if(!_fullSD) { - _camera.startVideo() + if(_cameraPhotoMode) { + if(_camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IDLE || _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IN_PROGRESS) { + return true + } + } + return false + } + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + enabled: !_noSdCard + onClicked: { + if(_cameraVideoMode) { + if(_camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) { + _camera.stopVideo() + } else { + if(!_fullSD) { + _camera.startVideo() + } } - } - } else { - if(_camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IDLE || _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IN_PROGRESS) { - _camera.stopTakePhoto() } else { - if(!_fullSD) { - _camera.takePhoto() + if(_camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IDLE || _camera.photoStatus === QGCCameraControl.PHOTO_CAPTURE_INTERVAL_IN_PROGRESS) { + _camera.stopTakePhoto() + } else { + if(!_fullSD) { + _camera.takePhoto() + } } } } } } - } - //----------------------------------------------------------------- - //-- Settings - Item { - width: settingsCol.width - height: settingsCol.height - anchors.horizontalCenter: parent.horizontalCenter - Column { - id: settingsCol - spacing: _spacers * 0.5 + //----------------------------------------------------------------- + //-- Settings + Item { + width: settingsCol.width + height: settingsCol.height anchors.horizontalCenter: parent.horizontalCenter + Column { + id: settingsCol + spacing: _spacers + anchors.horizontalCenter: parent.horizontalCenter + QGCColoredImage { + width: ScreenTools.defaultFontPixelHeight * 1.25 + height: width + sourceSize.width: width + source: "qrc:/custom/img/camera_settings.svg" + color: qgcPal.text + fillMode: Image.PreserveAspectFit + opacity: _settingsEnabled ? 1 : 0.5 + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("Settings") + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + } + MouseArea { + anchors.fill: parent + enabled: _settingsEnabled + onClicked: { + cameraSettings.open() + } + } + } + //----------------------------------------------------------------- + //-- microSD Card + Column { + spacing: _spacers + visible: !_isShortScreen + anchors.horizontalCenter: parent.horizontalCenter QGCColoredImage { width: ScreenTools.defaultFontPixelHeight * 1.25 height: width sourceSize.width: width - source: "qrc:/custom/img/camera_settings.svg" + source: "qrc:/custom/img/microSD.svg" color: qgcPal.text fillMode: Image.PreserveAspectFit opacity: _settingsEnabled ? 1 : 0.5 anchors.horizontalCenter: parent.horizontalCenter } QGCLabel { - text: qsTr("Settings") - font.pointSize: ScreenTools.smallFontPointSize - anchors.horizontalCenter: parent.horizontalCenter + text: { + if(_noSdCard) return qsTr("NONE") + if(_fullSD) return qsTr("FULL") + return _camera ? _camera.storageFreeStr : "" + } + color: (_noSdCard || _fullSD) ? qgcPal.colorOrange : qgcPal.text + font.pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter } } - MouseArea { - anchors.fill: parent - enabled: _settingsEnabled - onClicked: { - cameraSettings.open() - } + /* + //----------------------------------------------------------------- + //-- Recording Time / Images Captured + CustomLabel { + text: (_cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) ? _camera.recordTimeStr : "00:00:00" + visible: _cameraVideoMode + pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + CustomLabel { + text: activeVehicle && _cameraPhotoMode ? ('00000' + activeVehicle.cameraTriggerPoints.count).slice(-5) : "00000" + visible: _cameraPhotoMode + pointSize: ScreenTools.smallFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + */ + Item { + height: 1 + width: 1 } } - //----------------------------------------------------------------- - //-- microSD Card + } + //-- Gimbal Indicator + Rectangle { + width: _hasGimbal ? ScreenTools.defaultFontPixelWidth * 6 : 0 + height: _hasGimbal ? (gimbalCol.height + (ScreenTools.defaultFontPixelHeight * 2)) : 0 + visible: _hasGimbal + color: Qt.rgba(0,0,0,0.5) + radius: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.verticalCenter: cameraRect.verticalCenter Column { - spacing: _spacers * 0.5 - anchors.horizontalCenter: parent.horizontalCenter - QGCColoredImage { - width: ScreenTools.defaultFontPixelHeight * 1.25 - height: width - sourceSize.width: width - source: "qrc:/custom/img/microSD.svg" - color: qgcPal.text - fillMode: Image.PreserveAspectFit - opacity: _settingsEnabled ? 1 : 0.5 - anchors.horizontalCenter: parent.horizontalCenter + id: gimbalCol + spacing: ScreenTools.defaultFontPixelHeight * 0.75 + anchors.centerIn: parent + Image { + source: "/custom/img/gimbal_icon.svg" + width: ScreenTools.defaultFontPixelWidth * 2 + height: width + smooth: true + mipmap: true + antialiasing: true + fillMode: Image.PreserveAspectFit + sourceSize.width: width + anchors.horizontalCenter: parent.horizontalCenter } - QGCLabel { - text: { - if(_noSdCard) return qsTr("NONE") - if(_fullSD) return qsTr("FULL") - return _camera ? _camera.storageFreeStr : "" + Image { + id: pitchScale + height: cameraRect.height * 0.65 + source: "/custom/img/gimbal_pitch.svg" + fillMode: Image.PreserveAspectFit + sourceSize.height: height + smooth: true + mipmap: true + antialiasing: true + anchors.horizontalCenter: parent.horizontalCenter + Image { + id: yawIndicator + width: ScreenTools.defaultFontPixelWidth * 4 + source: "/custom/img/gimbal_position.svg" + fillMode: Image.PreserveAspectFit + sourceSize.width: width + y: (parent.height * _pitch / 105) + smooth: true + mipmap: true + anchors.horizontalCenter: parent.horizontalCenter + transform: Rotation { + origin.x: yawIndicator.width / 2 + origin.y: yawIndicator.height / 2 + angle: _gimbalYaw + } + property real _pitch: _gimbalPitch < -15 ? -15 : (_gimbalPitch > 90 ? 90 : _gimbalPitch) } - color: (_noSdCard || _fullSD) ? qgcPal.colorOrange : qgcPal.text - font.pointSize: ScreenTools.smallFontPointSize + } + QGCLabel { + id: gimbalLabel + text: _gimbalPitch ? _gimbalPitch.toFixed(0) : 0 + color: "#FFF" + font.pointSize: ScreenTools.smallFontPointSize anchors.horizontalCenter: parent.horizontalCenter } } - /* - //----------------------------------------------------------------- - //-- Recording Time / Images Captured - CustomLabel { - text: (_cameraVideoMode && _camera.videoStatus === QGCCameraControl.VIDEO_CAPTURE_STATUS_RUNNING) ? _camera.recordTimeStr : "00:00:00" - visible: _cameraVideoMode - pointSize: ScreenTools.smallFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } - CustomLabel { - text: activeVehicle && _cameraPhotoMode ? ('00000' + activeVehicle.cameraTriggerPoints.count).slice(-5) : "00000" - visible: _cameraPhotoMode - pointSize: ScreenTools.smallFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } - */ - Item { - height: 1 - width: 1 - } } } //-- Zoom Buttons @@ -496,7 +534,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter Column { id: cameraSettingsCol - spacing: ScreenTools.defaultFontPixelHeight * 0.5 + spacing: _spacers anchors.margins: ScreenTools.defaultFontPixelHeight anchors.horizontalCenter: parent.horizontalCenter //------------------------------------------- @@ -613,7 +651,7 @@ Item { height: repCol.height Column { id: repCol - spacing: ScreenTools.defaultFontPixelHeight * 0.5 + spacing: _spacers property var _fact: _camera.getFact(modelData) Row { height: visible ? undefined : 0 @@ -898,5 +936,57 @@ Item { } } } + //------------------------------------------------------------------------- + //-- Thermal Palettes + Popup { + id: thermalPalettes + width: Math.min(mainWindow.width * 0.666, ScreenTools.defaultFontPixelWidth * 40) + height: mainWindow.height * 0.5 + modal: true + focus: true + parent: Overlay.overlay + x: Math.round((mainWindow.width - width) * 0.5) + y: Math.round((mainWindow.height - height) * 0.5) + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + property int selectedIndex: 0 + background: Rectangle { + anchors.fill: parent + color: qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(1,1,1,0.95) : Qt.rgba(0,0,0,0.75) + border.color: qgcPal.text + radius: ScreenTools.defaultFontPixelWidth + } + Item { + anchors.fill: parent + anchors.margins: ScreenTools.defaultFontPixelHeight + QGCFlickable { + clip: true + anchors.fill: parent + width: comboListCol.width + (ScreenTools.defaultFontPixelWidth * 2) + contentHeight: comboListCol.height + contentWidth: comboListCol.width + anchors.horizontalCenter: parent.horizontalCenter + Column { + id: comboListCol + spacing: ScreenTools.defaultFontPixelHeight + anchors.margins: ScreenTools.defaultFontPixelHeight + anchors.horizontalCenter: parent.horizontalCenter + Repeater { + model: _irPaletteFact ? _irPaletteFact.enumStrings : [] + QGCButton { + text: modelData + width: ScreenTools.defaultFontPixelWidth * 30 + height: ScreenTools.defaultFontPixelHeight * 2 + checked: index === _irPaletteFact.value + anchors.horizontalCenter: parent.horizontalCenter + onClicked: { + _irPaletteFact.value = index + thermalPalettes.close() + } + } + } + } + } + } + } } diff --git a/custom-example/res/CustomFlyView.qml b/custom-example/res/CustomFlyView.qml index 461d64d17..940f84176 100644 --- a/custom-example/res/CustomFlyView.qml +++ b/custom-example/res/CustomFlyView.qml @@ -50,6 +50,7 @@ Item { property var _camera: _isCamera ? _dynamicCameras.cameras.get(_curCameraIndex) : null property bool _cameraPresent: _camera && _camera.cameraMode !== QGCCameraControl.CAM_MODE_UNDEFINED property var _flightPermit: QGroundControl.airmapSupported ? QGroundControl.airspaceManager.flightPlan.flightPermitStatus : null + property bool _hasGimbal: activeVehicle && activeVehicle.gimbalData property bool _airspaceIndicatorVisible: QGroundControl.airmapSupported && mainIsMap && _flightPermit && _flightPermit !== AirspaceFlightPlanProvider.PermitNone @@ -143,7 +144,7 @@ Item { connectionLostDisarmedDialog.close() } } - + //------------------------------------------------------------------------- MessageDialog { id: connectionLostDisarmedDialog title: qsTr("Communication Lost") @@ -153,7 +154,7 @@ Item { connectionLostDisarmedDialog.close() } } - + //------------------------------------------------------------------------- //-- Heading Indicator Rectangle { id: compassBar @@ -163,7 +164,7 @@ Item { radius: 2 clip: true anchors.top: parent.top - anchors.topMargin: ScreenTools.defaultFontPixelHeight * (_airspaceIndicatorVisible ? 3 : 2) + anchors.topMargin: ScreenTools.defaultFontPixelHeight * (_airspaceIndicatorVisible ? 3 : 1) anchors.horizontalCenter: parent.horizontalCenter visible: !mainIsMap Repeater { @@ -225,7 +226,7 @@ Item { anchors.topMargin: ScreenTools.defaultFontPixelHeight * -0.5 anchors.horizontalCenter: parent.horizontalCenter } - + //------------------------------------------------------------------------- //-- Camera Control Loader { id: camControlLoader @@ -234,9 +235,9 @@ Item { anchors.right: parent.right anchors.rightMargin: ScreenTools.defaultFontPixelWidth anchors.top: parent.top - anchors.topMargin: ScreenTools.defaultFontPixelHeight * 4 + anchors.topMargin: ScreenTools.defaultFontPixelHeight } - + //------------------------------------------------------------------------- //-- Map Scale MapScale { id: mapScale @@ -247,7 +248,7 @@ Item { mapControl: mainWindow.flightDisplayMap visible: rootBackground.visible && mainIsMap } - + //------------------------------------------------------------------------- //-- Vehicle Indicator Rectangle { id: vehicleIndicator @@ -457,7 +458,7 @@ Item { onDoubleClicked: _showAttitude = !_showAttitude } } - + //------------------------------------------------------------------------- //-- Attitude Indicator Rectangle { color: qgcPal.window @@ -485,7 +486,7 @@ Item { anchors.centerIn: parent } } - + //------------------------------------------------------------------------- //-- Multi Vehicle Selector Row { id: multiVehicleSelector @@ -507,47 +508,104 @@ Item { } } } - + //------------------------------------------------------------------------- //-- Gimbal Control - Item { + Rectangle { id: gimbalControl - visible: camControlLoader.visible && CustomQuickInterface.showGimbalControl + visible: camControlLoader.visible && CustomQuickInterface.showGimbalControl && _hasGimbal anchors.bottom: camControlLoader.bottom anchors.right: camControlLoader.left - anchors.rightMargin: ScreenTools.defaultFontPixelWidth * (QGroundControl.videoManager.hasThermal ? -4 : 2) + anchors.rightMargin: ScreenTools.defaultFontPixelWidth * (QGroundControl.videoManager.hasThermal ? -1 : 1) height: parent.width * 0.125 width: height - property real curPitch: 0 - property real curYaw: 0 + color: Qt.rgba(1,1,1,0.25) + radius: width * 0.5 + + property real _currentPitch: 0 + property real _currentYaw: 0 + property real time_last_seconds:0 + property real _lastHackedYaw: 0 + property real speedMultiplier: 5 + + property real maxRate: 20 + property real exponentialFactor:0.6 + property real kPFactor: 3 + + property real reportedYawDeg: activeVehicle ? activeVehicle.gimbalYaw : NaN + property real reportedPitchDeg: activeVehicle ? activeVehicle.gimbalPitch : NaN + Timer { interval: 100 //-- 10Hz running: gimbalControl.visible && activeVehicle repeat: true onTriggered: { if (activeVehicle) { - var p = Math.round(stick.yAxis * -90) - var y = Math.round(stick.xAxis * 180) - if(p !== gimbalControl.curPitch || y !== gimbalControl.curYaw) { - gimbalControl.curPitch = p - gimbalControl.curYaw = y - activeVehicle.gimbalControlValue(p, y) + var yaw = gimbalControl._currentYaw + var oldYaw = yaw; + var pitch = gimbalControl._currentPitch + var oldPitch = pitch; + var pitch_stick = (stick.yAxis * 2.0 - 1.0) + if(_camera && _camera.vendor === "NextVision") { + var time_current_seconds = ((new Date()).getTime())/1000.0 + if(gimbalControl.time_last_seconds === 0.0) + gimbalControl.time_last_seconds = time_current_seconds + var pitch_angle = gimbalControl._currentPitch + // Preparing stick input with exponential curve and maximum rate + var pitch_expo = (1 - gimbalControl.exponentialFactor) * pitch_stick + gimbalControl.exponentialFactor * pitch_stick * pitch_stick * pitch_stick + var pitch_rate = pitch_stick * gimbalControl.maxRate + var pitch_angle_reported = gimbalControl.reportedPitchDeg + // Integrate the angular rate to an angle time abstracted + pitch_angle += pitch_rate * (time_current_seconds - gimbalControl.time_last_seconds) + // Control the angle quicker by driving the gimbal internal angle controller into saturation + var pitch_angle_error = pitch_angle - pitch_angle_reported + pitch_angle_error = Math.round(pitch_angle_error) + var pitch_setpoint = pitch_angle + pitch_angle_error * gimbalControl.kPFactor + //console.info("error: " + pitch_angle_error + "; angle_state: " + pitch_angle) + pitch = pitch_setpoint + yaw += stick.xAxis * gimbalControl.speedMultiplier + yaw = clamp(yaw, -180, 180) + pitch = clamp(pitch, -45, 45) + //console.info("P: " + pitch + "; Y: " + yaw) + activeVehicle.gimbalControlValue(pitch, yaw); + gimbalControl._currentYaw = yaw + gimbalControl._currentPitch = pitch_angle + gimbalControl.time_last_seconds = time_current_seconds + } else { + yaw += stick.xAxis * gimbalControl.speedMultiplier + var hackedYaw = yaw + (stick.xAxis * gimbalControl.speedMultiplier * 25) + pitch += pitch_stick * gimbalControl.speedMultiplier + hackedYaw = clamp(hackedYaw, -180, 180) + yaw = clamp(yaw, -180, 180) + pitch = clamp(pitch, -90, 90) + if(gimbalControl._lastHackedYaw !== hackedYaw || gimbalControl.hackedYaw !== oldYaw || pitch !== oldPitch) { + activeVehicle.gimbalControlValue(pitch, hackedYaw) + gimbalControl._lastHackedYaw = hackedYaw + gimbalControl._currentPitch = pitch + gimbalControl._currentYaw = yaw + } } } } + function clamp(num, min, max) { + return Math.min(Math.max(num, min), max); + } } JoystickThumbPad { id: stick anchors.fill: parent - lightColors: true + lightColors: qgcPal.globalTheme === QGCPalette.Light + yAxisThrottle: true yAxisThrottleCentered: true - springYToCenter: false + xAxis: 0 + yAxis: 0.5 } } + //------------------------------------------------------------------------- //-- Connection Lost While Armed Popup { - id: connectionLostArmed + id: connectionLostArmed width: mainWindow.width * 0.666 - height: connectionLostArmedCol.height * 1.5 + height: connectionLostArmedCol.height * 1.5 modal: true focus: true parent: Overlay.overlay @@ -556,38 +614,38 @@ Item { closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside background: Rectangle { anchors.fill: parent - color: qgcPal.alertBackground - border.color: qgcPal.alertBorder + color: qgcPal.alertBackground + border.color: qgcPal.alertBorder radius: ScreenTools.defaultFontPixelWidth } - Column { - id: connectionLostArmedCol - spacing: ScreenTools.defaultFontPixelHeight * 3 - anchors.margins: ScreenTools.defaultFontPixelHeight - anchors.centerIn: parent - QGCLabel { - text: qsTr("Communication Lost") - font.family: ScreenTools.demiboldFontFamily - font.pointSize: ScreenTools.largeFontPointSize - color: qgcPal.alertText - anchors.horizontalCenter: parent.horizontalCenter - } - QGCLabel { - text: qsTr("Warning: Connection to vehicle lost.") - color: qgcPal.alertText - font.family: ScreenTools.demiboldFontFamily - font.pointSize: ScreenTools.mediumFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } - QGCLabel { - text: qsTr("The vehicle will automatically cancel the flight and return to land. Ensure a clear line of sight between transmitter and vehicle. Ensure the takeoff location is clear.") - width: connectionLostArmed.width * 0.75 - wrapMode: Text.WordWrap - color: qgcPal.alertText - font.family: ScreenTools.demiboldFontFamily - font.pointSize: ScreenTools.mediumFontPointSize - anchors.horizontalCenter: parent.horizontalCenter - } - } + Column { + id: connectionLostArmedCol + spacing: ScreenTools.defaultFontPixelHeight * 3 + anchors.margins: ScreenTools.defaultFontPixelHeight + anchors.centerIn: parent + QGCLabel { + text: qsTr("Communication Lost") + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.largeFontPointSize + color: qgcPal.alertText + anchors.horizontalCenter: parent.horizontalCenter + } + QGCLabel { + text: qsTr("Warning: Connection to vehicle lost.") + color: qgcPal.alertText + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter } + QGCLabel { + text: qsTr("The vehicle will automatically cancel the flight and return to land. Ensure a clear line of sight between transmitter and vehicle. Ensure the takeoff location is clear.") + width: connectionLostArmed.width * 0.75 + wrapMode: Text.WordWrap + color: qgcPal.alertText + font.family: ScreenTools.demiboldFontFamily + font.pointSize: ScreenTools.mediumFontPointSize + anchors.horizontalCenter: parent.horizontalCenter + } + } + } } diff --git a/custom-example/res/Images/gimbal_icon.svg b/custom-example/res/Images/gimbal_icon.svg new file mode 100644 index 000000000..e4273b7b0 --- /dev/null +++ b/custom-example/res/Images/gimbal_icon.svg @@ -0,0 +1,26 @@ + + + + +670134 +Created with Sketch. + + diff --git a/custom-example/res/Images/gimbal_pitch.svg b/custom-example/res/Images/gimbal_pitch.svg new file mode 100644 index 000000000..188295a10 --- /dev/null +++ b/custom-example/res/Images/gimbal_pitch.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/custom-example/res/Images/gimbal_position.svg b/custom-example/res/Images/gimbal_position.svg new file mode 100644 index 000000000..a0f47c4f2 --- /dev/null +++ b/custom-example/res/Images/gimbal_position.svg @@ -0,0 +1,30 @@ + + + + +gimbal position +Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/custom-example/src/CustomPlugin.cc b/custom-example/src/CustomPlugin.cc index e4e3bbb71..6fa59d080 100644 --- a/custom-example/src/CustomPlugin.cc +++ b/custom-example/src/CustomPlugin.cc @@ -218,7 +218,7 @@ CustomPlugin::adjustSettingMetaData(const QString& settingsGroup, FactMetaData& { if (settingsGroup == AppSettings::settingsGroup) { if (metaData.name() == AppSettings::appFontPointSizeName) { - #if defined(WIN32) + #if defined(Q_OS_LINUX) int defaultFontPointSize = 11; metaData.setRawDefaultValue(defaultFontPointSize); #endif @@ -231,7 +231,6 @@ CustomPlugin::adjustSettingMetaData(const QString& settingsGroup, FactMetaData& return true; } - const QColor CustomPlugin::_windowShadeEnabledLightColor("#FFFFFF"); const QColor CustomPlugin::_windowShadeEnabledDarkColor("#212529"); diff --git a/custom-example/src/CustomPlugin.h b/custom-example/src/CustomPlugin.h index 3d3131415..b089c3e62 100644 --- a/custom-example/src/CustomPlugin.h +++ b/custom-example/src/CustomPlugin.h @@ -42,6 +42,9 @@ class CustomOptions : public QGCOptions public: CustomOptions(CustomPlugin*, QObject* parent = nullptr); bool wifiReliableForCalibration () const final { return true; } +#if defined(Q_OS_LINUX) + double toolbarHeightMultiplier () final { return 1.25; } +#endif QUrl flyViewOverlay () const final { return QUrl::fromUserInput("qrc:/custom/CustomFlyView.qml"); } QUrl preFlightChecklistUrl () const final { return QUrl::fromUserInput("qrc:/custom/PreFlightCheckList.qml"); } //-- We have our own toolbar diff --git a/custom-example/src/FirmwarePlugin/CustomCameraControl.cc b/custom-example/src/FirmwarePlugin/CustomCameraControl.cc index cbb74227c..7817a46c3 100644 --- a/custom-example/src/FirmwarePlugin/CustomCameraControl.cc +++ b/custom-example/src/FirmwarePlugin/CustomCameraControl.cc @@ -17,7 +17,8 @@ QGC_LOGGING_CATEGORY(CustomCameraLog, "CustomCameraLog") QGC_LOGGING_CATEGORY(CustomCameraVerboseLog, "CustomCameraVerboseLog") -static const char* kCAM_IRPALETTE = "CAM_IRPALETTE"; +static const char* kCAM_IRPALETTE = "CAM_IRPALETTE"; +static const char* kCAM_NEXTVISION_IRPALETTE = "IR_SENS_POL"; //----------------------------------------------------------------------------- CustomCameraControl::CustomCameraControl(const mavlink_camera_information_t *info, Vehicle* vehicle, int compID, QObject* parent) @@ -104,6 +105,30 @@ CustomCameraControl::handleCaptureStatus(const mavlink_camera_capture_status_t& Fact* CustomCameraControl::irPalette() { - return (_paramComplete && _activeSettings.contains(kCAM_IRPALETTE)) ? getFact(kCAM_IRPALETTE) : nullptr; + if(_paramComplete) { + if(_activeSettings.contains(kCAM_IRPALETTE)) { + return getFact(kCAM_IRPALETTE); + } + else if(_activeSettings.contains(kCAM_NEXTVISION_IRPALETTE)) { + return getFact(kCAM_NEXTVISION_IRPALETTE); + } + } + return nullptr; } +//----------------------------------------------------------------------------- +void +CustomCameraControl::setThermalMode(ThermalViewMode mode) +{ + if(_paramComplete) { + if(vendor() == "NextVision" && _activeSettings.contains("CAM_SENSOR")) { + if(mode == THERMAL_FULL) { + getFact("CAM_SENSOR")->setRawValue(1); + } + else if(mode == THERMAL_OFF) { + getFact("CAM_SENSOR")->setRawValue(0); + } + } + } + QGCCameraControl::setThermalMode(mode); +} diff --git a/custom-example/src/FirmwarePlugin/CustomCameraControl.h b/custom-example/src/FirmwarePlugin/CustomCameraControl.h index 42ead7cad..475ed336c 100644 --- a/custom-example/src/FirmwarePlugin/CustomCameraControl.h +++ b/custom-example/src/FirmwarePlugin/CustomCameraControl.h @@ -38,6 +38,7 @@ public: void setVideoMode () override; void setPhotoMode () override; void handleCaptureStatus (const mavlink_camera_capture_status_t& capStatus) override; + void setThermalMode (ThermalViewMode mode) override; protected: void _setVideoStatus (VideoStatus status) override; -- 2.22.0