diff --git a/.github/workflows/build_package_Rock5.yml b/.github/workflows/build_package_Rock5.yml index fc5f7c812..203479dc4 100644 --- a/.github/workflows/build_package_Rock5.yml +++ b/.github/workflows/build_package_Rock5.yml @@ -2,9 +2,9 @@ name: build_package_rock5_debian on: push: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] pull_request: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] jobs: build: @@ -36,7 +36,7 @@ jobs: - name: Build Package run: | git clone https://github.com/OpenHD/ChrootCompilationTest /opt/ChrootCompilationTest - git clone -b 2.4-evo https://github.com/OpenHD/QOpenHD --recursive /opt/ChrootCompilationTest/additionalFiles + git clone -b 2.5-evo https://github.com/OpenHD/QOpenHD --recursive /opt/ChrootCompilationTest/additionalFiles echo $CLOUDSMITH_API_KEY > /opt/ChrootCompilationTest/additionalFiles/cloudsmith_api_key.txt cd /opt/ChrootCompilationTest/ sudo apt update diff --git a/.github/workflows/build_package_rpi.yml b/.github/workflows/build_package_rpi.yml index 2f30859ce..7655dd880 100644 --- a/.github/workflows/build_package_rpi.yml +++ b/.github/workflows/build_package_rpi.yml @@ -2,9 +2,9 @@ name: build_package_rpi on: push: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] pull_request: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) diff --git a/.github/workflows/build_package_x86_jammy.yml b/.github/workflows/build_package_x86_jammy.yml index 9fa74a550..03d9adf54 100644 --- a/.github/workflows/build_package_x86_jammy.yml +++ b/.github/workflows/build_package_x86_jammy.yml @@ -2,9 +2,9 @@ name: build_package_x86_22 on: push: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] pull_request: - branches: [ "2.4-evo" ] + branches: [ "2.5-evo" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) @@ -33,21 +33,10 @@ jobs: sudo rm -rf /var/lib/apt/lists/* sudo apt upgrade -y sudo add-apt-repository universe - sudo ./install_build_dep.sh ubuntu-x86 custom - - - + sudo ./install_build_dep.sh ubuntu-x86 - name: Build with make run: | - sudo rm -f /etc/ld.so.conf.d/qt.conf - sudo touch /etc/ld.so.conf.d/qt.conf - sudo sh -c 'echo "/opt/Qt5.15.7/lib/" >/etc/ld.so.conf.d/qt.conf' - sudo ldconfig - export PATH="$PATH:/opt/Qt5.15.7/bin/" - sudo rm -f /usr/bin/qmake - sudo ln -s /opt/Qt5.15.7/bin/qmake /usr/bin/qmake - echo "QT successfully linked" sudo ./package.sh x86_64 ubuntu jammy - name: Push diff --git a/.github/workflows/build_package_x86_usb_release.yml b/.github/workflows/build_package_x86_usb_release.yml index 762af45ee..06694e6a0 100644 --- a/.github/workflows/build_package_x86_usb_release.yml +++ b/.github/workflows/build_package_x86_usb_release.yml @@ -1,4 +1,4 @@ -name: build_package_x86_23.04 +name: build_package_LUNAR on: push: diff --git a/.github/workflows/build_windows.yml b/.github/workflows/build_windows.yml new file mode 100644 index 000000000..82a4337db --- /dev/null +++ b/.github/workflows/build_windows.yml @@ -0,0 +1,125 @@ +name: Windows Release + +on: [push] + +defaults: + run: + shell: cmd + +env: + SOURCE_DIR: ${{ github.workspace }} + ARTIFACT: QOpenHD-Evo.zip + +jobs: + build: + runs-on: windows-2019 + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + aqtversion: '==2.1.*' + version: '5.15.2' + host: 'windows' + target: 'desktop' + arch: 'win64_msvc2019_64' + tools: 'tools_ifw tools_qtcreator,qt.tools.qtcreator' + setup-python: false + + - name: Download JOM + uses: suisei-cn/actions-download-file@v1 + with: + url: http://download.qt.io/official_releases/jom/jom.zip + + - name: Unzip JOM + run: | + 7z x jom.zip -ojom + + - uses: ilammy/msvc-dev-cmd@v1 + - name: Build + run: | + ls D:\a\QOpenHD\Qt\5.15.2\msvc2019_64\bin\ + rm -rf build + mkdir build + cd build + qmake CONFIG+=release ..\QOpenHD.pro + ..\jom\jom -j2 + ls -a + cd release + cd + cd D:\a\QOpenHD\Qt\5.15.2\msvc2019_64\bin\ + cp *.dll D:\a\QOpenHD\QOpenHD\build\release\ + cd D:\a\QOpenHD\QOpenHD\build\release\ + mkdir platforms + cd D:\a\QOpenHD\Qt\5.15.2\msvc2019_64\plugins\platforms\ + cp *.dll D:\a\QOpenHD\QOpenHD\build\release\platforms\ + cd D:\a\QOpenHD\Qt\5.15.2\msvc2019_64\qml\ + cp -r * D:\a\QOpenHD\QOpenHD\build\release\ + cd D:\a\QOpenHD\QOpenHD\build\release\ + rm -Rf Qt3D + rm -Rf QtBluetooth + rm -Rf QtDataVisualization + rm -Rf QtGamepad + rm -Rf QtNfc + rm -Rf QtPurchasing + rm -Rf QtQuick3D + rm -Rf QtRemoteObjects + rm -Rf QtScxml + rm -Rf QtSensors + rm -Rf QtWebChannel + rm -Rf QtWebEngine + rm -Rf QtWebSockets + rm -Rf QtWebView + rm -Rf QtWinExtras + rm Qt5Bluetooth.dll + rm Qt5Bluetoothd.dll + rm QtGamepad.dll + rm QtGamepadd.dll + rm QtWebChannel.dll + rm QtWebChanneld.dll + rm Qt5WebSockets.dll + rm Qt5WebSocketsd.dll + rm Qt5WebView.dll + rm Qt5WebViewd.dll + rm Qt53DAnimation.dll + rm Qt53DAnimationd.dll + rm Qt53DACore.dll + rm Qt53DACored.dll + rm Qt53DExtras.dll + rm Qt53DExtrasd.dll + rm Qt53DInput.dll + rm Qt53DInputd.dll + rm Qt53DLogic.dll + rm Qt53DLogicd.dll + rm Qt53DQuick.dll + rm Qt53DQuickd.dll + rm Qt53DQuickAnimation.dll + rm Qt53DQuickAnimationd.dll + rm Qt53DQuickExtras.dll + rm Qt53DQuickExtrasd.dll + rm Qt53DQuickInput.dll + rm Qt53DQuickInputd.dll + rm Qt53DQuickRender.dll + rm Qt53DQuickRenderd.dll + rm Qt53DQuickScene2D.dll + rm Qt53DQuickScene2Dd.dll + rm Qt53DRender.dll + rm Qt53DRenderd.dll + ls -a + cd + + - name: Upload to Github + uses: 'actions/upload-artifact@v2' + with: + name: "QOpenHD" + path: | + D:\a\QOpenHD\QOpenHD\build\release\ + + + + diff --git a/.github/workflows/ios.yml.txt b/.github/workflows/ios.yml.txt index 5d74e4fee..6075fce5d 100644 --- a/.github/workflows/ios.yml.txt +++ b/.github/workflows/ios.yml.txt @@ -29,8 +29,7 @@ jobs: - name: Install Dependencies run: | - - sudo ./build_install_mavsdk_static.sh + # TODO ? - name: Build with make run: | diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 000000000..1f0f94ca9 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,41 @@ +name: Build MacOS + +on: + push: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + + - name: Checkout repo + uses: actions/checkout@v3 + with: + submodules: recursive + + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + + # - name: Install create-dmg + # run: brew install create-dmg + + + - name: Build + # Build your program with the given configuration + run: | + qmake CONFIG+=release QOpenHD.pro + + + - name: Upload to Github + uses: 'actions/upload-artifact@v3' + with: + name: "OpenHD Image Writer" + path: | + *.app diff --git a/.github/workflows/ubuntu22_build_test.yml b/.github/workflows/ubuntu22_build_test.yml index 53203d109..2eafcc0ec 100644 --- a/.github/workflows/ubuntu22_build_test.yml +++ b/.github/workflows/ubuntu22_build_test.yml @@ -30,7 +30,7 @@ jobs: sudo apt upgrade -y sudo apt remove libunwind-13-dev sudo apt install -y libunwind-dev libgstreamer-plugins-base1.0-dev - sudo ./install_build_dep.sh ubuntu-x86 custom + sudo ./install_build_dep.sh ubuntu-x86 qmake --version - name: Build with make run: | diff --git a/.gitmodules b/.gitmodules index a2e313452..794b06e4a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,4 @@ -[submodule "lib/MAVSDK"] - path = lib/MAVSDK - url = https://github.com/OpenHD/MAVSDK.git -[submodule "lib/mavsdk_prebuilts"] - path = lib/mavsdk_prebuilts - url = https://github.com/OpenHD/mavsdk_prebuilts.git +[submodule "lib/mavlink-headers"] + path = lib/mavlink-headers + url = https://github.com/OpenHD/mavlink-headers.git diff --git a/LICENSE b/LICENSE index 53d1f3d01..a59201f91 100644 --- a/LICENSE +++ b/LICENSE @@ -232,6 +232,10 @@ terms of section 4, provided that you also meet all of these conditions: interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + e) The "Credits" section of this App is subject to the following terms in addition to the GNU General Public License (GPL) version 3: + The content presented in the "Credits" section of this App is considered a copyrighted declaration. Unauthorized modifications, suppression, or removal of this content are strictly prohibited without explicit consent from the OpenHD development team. + + A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, diff --git a/QOpenHD.pro b/QOpenHD.pro index f3f0afc4e..c245d9714 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -68,7 +68,6 @@ LinuxBuild { # (aka all these really should come with pretty much any qt install) # In general, parts of QOpenHD that need additional libraries should have their code in a subdirectory with a .pri where those # dependencies are added such that you can easily compile the project even on systems that might lack some of those qt functionalities -# see app/adsb/adsb_lib.pri for an example QT +=core quick qml gui \ widgets QT += opengl @@ -77,7 +76,7 @@ INCLUDEPATH += $$PWD/lib INCLUDEPATH += $$PWD/app INCLUDEPATH += $$PWD/app/exp -# QOpenHD telemetry (mavlink, partially based on MAVSDK) features +# QOpenHD telemetry (mavlink) # REQUIRED - without it QOpenHD will compile, but be pretty much non functional include(app/telemetry/telemetry.pri) @@ -99,12 +98,6 @@ LinuxBuild { include(app/videostreaming/gstreamer/gst_video.pri) } -# adsb library -# Only tested on linux so far, but might work on other platforms already / with minimal effort, too -LinuxBuild { - include(app/adsb/adsb_lib.pri) -} - # All Generic files / files that literally have 0!! dependencies other than qt SOURCES += \ app/logging/hudlogmessagesmodel.cpp \ @@ -139,7 +132,6 @@ SOURCES += \ app/osd/horizonladder.cpp \ app/osd/speedladder.cpp \ app/osd/altitudeladder.cpp \ - app/osd/drawingcanvas.cpp \ app/osd/flightpathvector.cpp \ app/osd/aoagauge.cpp \ app/osd/performancehorizonladder.cpp \ @@ -149,7 +141,6 @@ HEADERS += \ app/osd/horizonladder.h \ app/osd/speedladder.h \ app/osd/altitudeladder.h \ - app/osd/drawingcanvas.h \ app/osd/flightpathvector.h \ app/osd/debug_overdraw.hpp \ app/osd/aoagauge.h \ @@ -182,15 +173,7 @@ DISTFILES += \ android/src/org/freedesktop/gstreamer/androidmedia/GstAmcOnFrameAvailableListener.java \ android/src/org/openhd/IsConnected.java \ android/src/org/openhd/LiveVideoPlayerWrapper.java \ - app/adsb/adsb_lib.pri \ - app/logging/README.txt \ - app/openhd_systems/README.md \ - app/osd_extra/Readme.txt \ - app/platform/README.md \ app/telemetry/telemetry.pri \ - app/util/README.md \ - app/videostreaming/README.md \ - app/videostreaming/README.txt \ app/videostreaming/gst_qmlglsink/gst_video.pri \ app/videostreaming/vscommon/vscommon.pri \ app/vs_android/videostreamingandroid.pri \ @@ -248,7 +231,7 @@ JetsonBuild { } WindowsBuild { - # This aparently makes qt use absolute paths, otherwise we get problems with mavsdk + # This aparently makes qt use absolute paths, otherwise we get compile issues ? QMAKE_PROJECT_DEPTH = 0 } diff --git a/README.md b/README.md index a75f6ba02..05465c24d 100644 --- a/README.md +++ b/README.md @@ -2,131 +2,93 @@ QOpenHD is the default OpenHD companion app that runs on the OHD Ground station or any other "external" devices connected to the ground station. -It is responsible for displaying the (main) video stream to the user, composed with the OSD and changing OpenHD settings. +It is responsible for displaying the main video stream to the user, composed with the OSD, and changing OpenHD settings. -As the name suspects, it is based on QT (5.15.X) and will not run on older Versions. +As the name suggests, it is based on QT (5.15.X) and will not run on older versions. -![temporary_screenshot](wiki/temporary_screenshot.png) +![temporary_screenshot](https://1945119839-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F8RIMU39m4Gt1vqzzt4lm%2Fuploads%2Fgit-blob-353dff94866ae2d4a81fa3329bf93290a0271b42%2FNorbertScreenshot.png?alt=media) -# Developer Design Overview (incomplete) -1) QOpenHD is not OpenHD (main). It can talk to a running OpenHD main instance (ground and/or air) via Mavlink and rceives the (primary / secondary / ++) video streams. -2) While QOpenHD is the default companion app, OpenHD MUST NOT assume there is a QOpenHD instance somewhere that initiates voodo settings / setup processes -( This is in contrast to OpenHD /QOpenHD releases before the "evo" series.) As a result of this limitation, Both OpenHD and QOpenHD can be developed independently from each other, and debugging becomes a lot easier. +## Developer Design Overview (incomplete) -# Compiling: -We have a CI setup that checks compilation on ubuntu. You can look at the steps it performs to build and run QOpenHD. -Other platforms than Linux are not supported right now. +1. QOpenHD is not OpenHD (main). It can communicate with a running OpenHD main instance (ground and/or air) via Mavlink and receives the (primary / secondary / ++) video streams. +2. While QOpenHD is the default companion app, OpenHD MUST NOT assume there is a QOpenHD instance somewhere that initiates voodoo settings / setup processes. This is in contrast to OpenHD/QOpenHD releases before the "evo" series. As a result of this limitation, both OpenHD and QOpenHD can be developed independently from each other, making debugging easier. -# Building QOpenHD Locally in QT (GSTREAMER MIGHT NOT BE REQUIRED as things progress) -#### Mac - -1. Install Xcode from the Mac App Store. - -2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer) - -3. Have the Qt Installer download Qt 5.15.0+ for Mac, and Qt Creator +## Installing -4. Clone the source code: +Like every OpenHD app or module, we publish packages into our [Cloudsmith Repository](https://cloudsmith.io/~openhd/repos/openhd-2-3-evo/). There are Packages for X86 (ubuntu 22.04,23.04), armhf (rpi, arm64 rockchip). Android releases are available on the Playstore and can be downloaded from there. - git clone --recurse-submodules https://github.com/OpenHD/QOpenHD.git +## Compiling -If you aren't using git from the command line but using a GUI git client instead, make sure -you update the submodules or QOpenHD will not build properly. - +We have a CI setup that checks compilation on Ubuntu. You can review the steps it performs to build and run QOpenHD. Other platforms than Linux are not supported at the moment. -Once you have those steps done you can open `QOpenHD.pro` with Qt Creator, build and run it. +### Building QOpenHD Locally in QT (CURRENTLY UNDER DEVELOPMENT) -#### iOS +#### Mac 1. Install Xcode from the Mac App Store. +2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer). +3. Download Qt 5.15.0+ for Mac and Qt Creator using the Qt Installer. +4. Clone the source code: -2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer) - -3. Have the Qt Installer download Qt 5.15.0+ for iOS, and Qt Creator - -4. You will need an Apple developer membership to install directly to an iOS device - -5. Clone the source code: - + ``` git clone --recurse-submodules https://github.com/OpenHD/QOpenHD.git + ``` -If you aren't using git from the command line but using a GUI git client instead, make sure -you update the submodules or QOpenHD will not build properly. - -Once you have those steps done you can open `QOpenHD.pro` with Qt Creator, build and run it. - -#### Windows - -1. Install Visual Studio 2019 (free) - -2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer) - -3. Have the Qt Installer download Qt 5.15.0+ and Qt Creator - -4. Download the [GStreamer development kit](https://gstreamer.freedesktop.org/download/) for Windows, both the Runtime and Development packages. You *must use* the 32-bit MinGW packages, NOT the one labeled MSVC, and it should be version 1.14.4. (QT will look for gstreamer in c:/gstreamer/1.0/x86 ) + If you use a GUI git client, ensure you update the submodules or QOpenHD won't build properly. -5. Clone the source code: +#### iOS - git clone --recurse-submodules https://github.com/OpenHD/QOpenHD.git +1. Install Xcode from the Mac App Store. +2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer). +3. Download Qt 5.15.0+ for iOS and Qt Creator using the Qt Installer. +4. You'll need an Apple developer membership to install directly on an iOS device. +5. Clone the source code as mentioned earlier. -If you aren't using git from the command line but using a GUI git client instead, make sure -you update the submodules or QOpenHD will not build properly. +#### Windows (CURRENTLY UNDER DEVELOPMENT) -Once you have those steps done you can open `QOpenHD.pro` with Qt Creator, build and run it. +1. Install Visual Studio 2019 (free). +2. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer). +3. Download Qt 5.15.0+ and Qt Creator using the Qt Installer. +4. Download the [GStreamer development kit](https://gstreamer.freedesktop.org/download/) for Windows, both the Runtime and Development packages (32-bit MinGW packages, version 1.14.4). QT will look for gstreamer in c:/gstreamer/1.0/x86. +5. Clone the source code as mentioned earlier. #### Linux -1. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer) - -2. Have it download Qt 5.15.0+ for Linux - -3. Install GStreamer development packages from the package manager. On Ubuntu this would be `apt install gstreamer1.0-gl libgstreamer1.0-dev libgstreamer-plugins-good1.0-dev gstreamer1.0-plugins-good libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-qt`. Those should pull in any others that are needed as well. +**QTCreator** -4. Clone the source code: - - git clone --recurse-submodules https://github.com/OpenHD/QOpenHD.git +1. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer). +2. Have it download Qt 5.15.0+ for Linux. +3. Install GStreamer development packages from the package manager (e.g., on Ubuntu, run `apt install gstreamer1.0-gl libgstreamer1.0-dev libgstreamer-plugins-good1.0-dev gstreamer1.0-plugins-good libgstreamer-plugins-base1.0-dev gstreamer1.0-plugins-base libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-qt`). +4. Clone the source code as mentioned earlier. -If you aren't using git from the command line but using a GUI git client instead, make sure -you update the submodules or QOpenHD will not build properly. +### Manual via qmake -Once you have those steps done you can open `QOpenHD.pro` with Qt Creator, build and run it. +1. Clone the source code. +2. Run 'bash install_build_dependencies.sh ARCHITECTURE' (ARCHITECTURE can be X86, rpi, rock5). +3. Run 'build_qmake.sh'. +4. Find the binary under build/releases/QOpenHD. #### Android -1. Install [Android Studio](https://developer.android.com/studio) - -2. Use Android Studio to install Android SDK level 28, along with NDK r18b and the associated build toolchain. - -3. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer) - -4. Have the Qt Installer download Qt 5.15.0+ *for Android*, not for the OS you're building on. - -5. Clone the source code: - - git clone --recurse-submodules https://github.com/OpenHD/QOpenHD.git - -If you aren't using git from the command line but using a GUI git client instead, make sure -you update the submodules or QOpenHD will not build properly. - -You can then open `QOpenHD.pro` using Qt Creator and set up the Android kit (left side, click the Projects tab), build and run the app on your device. - - - -# Building MAVSDK (REQUIRED) -QOpenHD relies on MAVSDK library. After recursively cloning qopenhd you have to build and install it once: - -`./build_install_mavsdk_static.sh` +1. Install [Android Studio](https://developer.android.com/studio). +2. Install Android SDK level 28 and NDK r18b using Android Studio. +3. Install Qt using the [Qt online installer](https://www.qt.io/download-qt-installer). +4. Download Qt 5.15.0+ for Android (not for the OS you're building on). +5. Clone the source code as mentioned earlier. -After that, you can build QOpenHD by either opening it in QT Creator (recommended) or building it with the following commands: +### Building QOpenHD -`mkdir build` -`cd build` -`qmake ..` +Step 1) clone this repository with --recurse-submodules +Step 2) install all dependencies +Step 3) recommended - open in QT creator. Otherwise, you can build QOpenHD via the command line: +``` +mkdir build +cd build +qmake .. +``` -# Contributing +## Contributing -**Thanks to all the people who already contributed!** +**Thanks to all the people who have contributed!** - - - +![Contributors](https://fra1.digitaloceanspaces.com/openhd-images/uploads/QOpenHD.svg) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 0d6c20c6d..f4a2a3979 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -1,6 +1,6 @@ - + @@ -106,6 +106,8 @@ + + diff --git a/app/adsb/ADSBVehicle.cpp b/app/adsb/ADSBVehicle.cpp deleted file mode 100644 index 40b643dcf..000000000 --- a/app/adsb/ADSBVehicle.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************** - * - * This file has been ported from QGroundControl project - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "ADSBVehicle.h" - -#include -#include - -ADSBVehicle::ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent) - : QObject (parent) - , _icaoAddress (vehicleInfo.icaoAddress) - , _altitude (qQNaN()) - , _heading (qQNaN()) - , _alert (false) -{ - update(vehicleInfo); -} - -void ADSBVehicle::update(const VehicleInfo_t& vehicleInfo) -{ - if (_icaoAddress != vehicleInfo.icaoAddress) { - qDebug() << "ICAO address mismatch expected:actual" << _icaoAddress << vehicleInfo.icaoAddress; - return; - } - if (vehicleInfo.availableFlags & CallsignAvailable) { - if (vehicleInfo.callsign != _callsign) { - _callsign = vehicleInfo.callsign; - emit callsignChanged(); - } - } - if (vehicleInfo.availableFlags & LocationAvailable) { - if (_coordinate != vehicleInfo.location) { - _coordinate = vehicleInfo.location; - emit coordinateChanged(); - } - } - if (vehicleInfo.availableFlags & AltitudeAvailable) { - if (!(qIsNaN(vehicleInfo.altitude) && qIsNaN(_altitude)) && !qFuzzyCompare(vehicleInfo.altitude, _altitude)) { - _altitude = vehicleInfo.altitude; - emit altitudeChanged(); - } - } - if (vehicleInfo.availableFlags & HeadingAvailable) { - if (!(qIsNaN(vehicleInfo.heading) && qIsNaN(_heading)) && !qFuzzyCompare(vehicleInfo.heading, _heading)) { - _heading = vehicleInfo.heading; - emit headingChanged(); - } - } - if (vehicleInfo.availableFlags & AlertAvailable) { - if (vehicleInfo.alert != _alert) { - _alert = vehicleInfo.alert; - emit alertChanged(); - } - } - if (vehicleInfo.availableFlags & VelocityAvailable) { - if (vehicleInfo.velocity != _velocity) { - _velocity = vehicleInfo.velocity; - emit velocityChanged(); - } - } - if (vehicleInfo.availableFlags & VerticalVelAvailable) { - if (vehicleInfo.verticalVel != _verticalVel) { - _verticalVel = vehicleInfo.verticalVel; - emit verticalVelChanged(); - } - } - if (vehicleInfo.availableFlags & LastContactAvailable) { - if (vehicleInfo.lastContact != _lastContact) { - _lastContact = vehicleInfo.lastContact; - emit lastContactChanged(); - } - } - if (vehicleInfo.availableFlags & DistanceAvailable) { - if (vehicleInfo.distance != _distance) { - _distance = vehicleInfo.distance; - emit distanceChanged(); - } - } - _lastUpdateTimer.restart(); -} - -bool ADSBVehicle::expired() -{ - return _lastUpdateTimer.hasExpired(expirationTimeoutMs); -} - -bool ADSBVehicle::tooFar() -{ - return _too_far; -} diff --git a/app/adsb/ADSBVehicle.h b/app/adsb/ADSBVehicle.h deleted file mode 100644 index 6a3acbc64..000000000 --- a/app/adsb/ADSBVehicle.h +++ /dev/null @@ -1,114 +0,0 @@ -/**************************************************************************** - * - * This file has been ported from QGroundControl project - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#pragma once - -#include -#include -#include - -class ADSBVehicle : public QObject -{ - Q_OBJECT - -public: - enum { - CallsignAvailable = 1 << 1, - LocationAvailable = 1 << 2, - AltitudeAvailable = 1 << 3, - HeadingAvailable = 1 << 4, - AlertAvailable = 1 << 5, - VelocityAvailable = 1 << 6, - VerticalVelAvailable = 1 << 7, - LastContactAvailable = 1 << 8, - DistanceAvailable = 1 << 8 - }; - - typedef struct { - uint32_t icaoAddress; // Required - QString callsign; - QGeoCoordinate location; - double altitude; - double velocity; - double heading; - int alert; - uint32_t availableFlags; - int lastContact; - double verticalVel; - double distance; - } VehicleInfo_t; - - ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent); - - Q_PROPERTY(int icaoAddress READ icaoAddress CONSTANT) - Q_PROPERTY(QString callsign READ callsign NOTIFY callsignChanged) - Q_PROPERTY(QGeoCoordinate coordinate READ coordinate NOTIFY coordinateChanged) - Q_PROPERTY(double lat READ lat NOTIFY coordinateChanged) - Q_PROPERTY(double lon READ lon NOTIFY coordinateChanged) - Q_PROPERTY(double altitude READ altitude NOTIFY altitudeChanged) // NaN for not available - Q_PROPERTY(double velocity READ velocity NOTIFY velocityChanged) // NaN for not available - Q_PROPERTY(double heading READ heading NOTIFY headingChanged) // NaN for not available - Q_PROPERTY(int alert READ alert NOTIFY alertChanged) // Collision path - Q_PROPERTY(int lastContact READ lastContact NOTIFY lastContactChanged) - Q_PROPERTY(double verticalVel READ verticalVel NOTIFY verticalVelChanged) - Q_PROPERTY(double distance READ distance NOTIFY distanceChanged) - - int icaoAddress (void) const { return static_cast(_icaoAddress); } - QString callsign (void) const { return _callsign; } - QGeoCoordinate coordinate (void) const { return _coordinate; } - double lat (void) const { return _coordinate.latitude(); } - double lon (void) const { return _coordinate.longitude(); } - double altitude (void) const { return _altitude; } - double velocity (void) const { return _velocity; } - double heading (void) const { return _heading; } - int alert (void) const { return _alert; } - int lastContact (void) const { return _lastContact; } - double verticalVel (void) const { return _verticalVel; } - double distance (void) const { return _distance; } - - void update(const VehicleInfo_t& vehicleInfo); - - /// check if the vehicle is expired and should be removed - bool expired(); - - bool tooFar(); - -signals: - void coordinateChanged (); - void callsignChanged (); - void altitudeChanged (); - void velocityChanged (); - void headingChanged (); - void alertChanged (); - void lastContactChanged (); - void verticalVelChanged (); - void distanceChanged (); - -private: - // This is the time in ms our vehicle will expire and thus removed from map - static constexpr qint64 expirationTimeoutMs = 25000; - - uint32_t _icaoAddress; - QString _callsign; - QGeoCoordinate _coordinate; - double _altitude; - double _velocity; - double _heading; - int _alert; - int _lastContact; - double _verticalVel; - double _distance; - - QElapsedTimer _lastUpdateTimer; - - bool _too_far = false; -}; - -Q_DECLARE_METATYPE(ADSBVehicle::VehicleInfo_t) - diff --git a/app/adsb/ADSBVehicleManager.cpp b/app/adsb/ADSBVehicleManager.cpp deleted file mode 100644 index 68b48b8cb..000000000 --- a/app/adsb/ADSBVehicleManager.cpp +++ /dev/null @@ -1,615 +0,0 @@ -/**************************************************************************** - * - * (c) 2009-2020 QGROUNDCONTROL PROJECT - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "ADSBVehicleManager.h" -//#include "localmessage.h" -//#include "logger.h" -#include "../telemetry/models/fcmavlinksystem.h" -#include "qmath.h" - -#include - -static ADSBVehicleManager* _instance = nullptr; - -ADSBVehicleManager* ADSBVehicleManager::instance() -{ - if ( _instance == nullptr ) { - _instance = new ADSBVehicleManager(); - } - return _instance; -} - -ADSBVehicleManager::ADSBVehicleManager(QObject *parent) : QObject(parent) -{ -} - -ADSBVehicleManager::~ADSBVehicleManager() -{ - // manually stop the threads - _internetLink->quit(); - _internetLink->wait(); - - _sdrLink->quit(); - _sdrLink->wait(); -} - -void ADSBVehicleManager::onStarted() -{ -// MavlinkTelemetry* mavlinktelemetry = MavlinkTelemetry::instance(); -// connect(mavlinktelemetry, &MavlinkTelemetry::adsbVehicleUpdate, this, &ADSBVehicleManager::adsbVehicleUpdate, Qt::QueuedConnection); - - qDebug() << "ADSBVehicleManager::onStarted()"; - - connect(&_adsbVehicleCleanupTimer, &QTimer::timeout, this, &ADSBVehicleManager::_cleanupStaleVehicles); - _adsbVehicleCleanupTimer.setSingleShot(false); - _adsbVehicleCleanupTimer.start(4500); - - _internetLink = new ADSBInternet(); - connect(_internetLink, &ADSBInternet::adsbVehicleUpdate, this, &ADSBVehicleManager::adsbVehicleUpdate, Qt::QueuedConnection); - connect(this, &ADSBVehicleManager::mapCenterChanged, _internetLink, &ADSBInternet::mapBoundsChanged, Qt::QueuedConnection); - connect(_internetLink, &ADSBInternet::adsbClearModelRequest, this, &ADSBVehicleManager::adsbClearModel, Qt::QueuedConnection); - - _sdrLink = new ADSBSdr(); - connect(_sdrLink, &ADSBSdr::adsbVehicleUpdate, this, &ADSBVehicleManager::adsbVehicleUpdate, Qt::QueuedConnection); - connect(this, &ADSBVehicleManager::mapCenterChanged, _sdrLink, &ADSBSdr::mapBoundsChanged, Qt::QueuedConnection); - connect(_sdrLink, &ADSBSdr::adsbClearModelRequest, this, &ADSBVehicleManager::adsbClearModel, Qt::QueuedConnection); -} - -// called from qml when the map is moved -void ADSBVehicleManager::newMapCenter(QGeoCoordinate center_coord) { - //qDebug() << "ADSBVehicleManager::newMapCenter"; - _api_center_coord = center_coord; - emit mapCenterChanged(center_coord); -} - -void ADSBVehicleManager::_cleanupStaleVehicles() -{ - // Remove all expired ADSB vehicles - for (int i=_adsbVehicles.count()-1; i>=0; i--) { - ADSBVehicle* adsbVehicle = _adsbVehicles.value(i); - if (adsbVehicle->expired()) { - // qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); - _adsbVehicles.removeAt(i); - _adsbICAOMap.remove(adsbVehicle->icaoAddress()); - adsbVehicle->deleteLater(); - } - } - // if more than 20 seconds with with no updates, set frontend indicator red - // if more than 60 seconds with no updates deactivate frontend indicator - if (_last_update_timer.elapsed() > 60000) { - _status = 0; - emit statusChanged(); - - } else if (_last_update_timer.elapsed() > 20000) { - _status = 1; - emit statusChanged(); - } -} - -//currently not used.. was for testing but could have future purpose to turn off display -void ADSBVehicleManager::adsbClearModel(){ - //qDebug() << "_adsbVehicles.clearAndDeleteContents"; - _adsbVehicles.clearAndDeleteContents(); -} - -void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo) -{ - //qDebug() << "ADSBVehicleManager::adsbVehicleUpdate"; - uint32_t icaoAddress = vehicleInfo.icaoAddress; - - //no point in continuing because no location. This is somewhat redundant with parser - //possible situation where we start to not get location.. and gets stale then removed - if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) { - - //decide if its new or needs update - if (_adsbICAOMap.contains(icaoAddress)) { - _adsbICAOMap[icaoAddress]->update(vehicleInfo); - } - else { - - ADSBVehicle* adsbVehicle = new ADSBVehicle(vehicleInfo, this); - _adsbICAOMap[icaoAddress] = adsbVehicle; - //qDebug() << "ADSBVehicleManager::adsbVehicleUpdate append vehicle"; - _adsbVehicles.append(adsbVehicle); - } - - // Show warnings if adsb reported traffic is too close - _evaluateTraffic(vehicleInfo.altitude, vehicleInfo.distance); - - _last_update_timer.restart(); - _status = 2; - emit statusChanged(); - } -} - -void ADSBVehicleManager::_evaluateTraffic(double traffic_alt, int traffic_distance) -{ - /* - * Centralise traffic threat detection here. Once threat is detected it should be - * labled and then sent over to the adsb widget - * - * need to calculate azimuth and bearing of any threats so that it can be shared - * and depicted in the adsb widget - */ - int drone_alt = FCMavlinkSystem::instance().altitude_msl_m(); - - if (traffic_alt - drone_alt < 300 && traffic_distance < 2) { -// LocalMessage::instance()->showMessage("Aircraft Traffic", 3); - - } else if (traffic_alt - drone_alt < 500 && traffic_distance < 5) { -// LocalMessage::instance()->showMessage("Aircraft Traffic", 4); - } -} - -ADSBapi::ADSBapi() - : QThread() -{ - moveToThread(this); - start(); -} - -ADSBapi::~ADSBapi(void) -{ - quit(); - wait(); -} - -void ADSBapi::run(void) -{ - init(); - exec(); -} - -void ADSBapi::init(void) { - qDebug() << "------------------>Adsbapi::init()<------------------------"; - - QNetworkAccessManager * manager = new QNetworkAccessManager(this); - - m_manager = manager; - - connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))) ; - - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &ADSBapi::requestData); - - // How frequently data is requested - timer->start(timer_interval); - mapBoundsChanged(QGeoCoordinate(40.48205, -3.35996)); -} - - -void ADSBapi::mapBoundsChanged(QGeoCoordinate center_coord) { - _api_center_coord= center_coord; - - //qDebug() << "ADSBapi::mapBoundsChanged center cord:" << _api_center_coord; - - qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); - - QGeoCoordinate qgeo_upper_left; - QGeoCoordinate qgeo_lower_right; - - qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); - qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); - - upperl_lat= QString::number(qgeo_upper_left.latitude()); - upperl_lon= QString::number(qgeo_upper_left.longitude()); - lowerr_lat= QString::number(qgeo_lower_right.latitude()); - lowerr_lon= QString::number(qgeo_lower_right.longitude()); -} - -void ADSBInternet::requestData(void) { - // If openskynetwork is disabled by settings don't make the request and return - if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - return; - } - qDebug() << "------------------>ADSBInternet::requestData<------------------------"; - _adsb_api_openskynetwork = _settings.value("adsb_api_openskynetwork").toBool(); - _show_adsb_internet = _settings.value("show_adsb").toBool(); - - - - adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; - - QNetworkRequest request; - QUrl api_request = adsb_url; - request.setUrl(api_request); - request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); - - qDebug() << "url=" << api_request; - m_manager->get(request); -} - -void ADSBInternet::processReply(QNetworkReply *reply) { - qDebug() << "------------------>ADSBInternet::processReply<------------------------"; - - - if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - qDebug() << "------------------>ADSBInternet::processReply EARLY RETURN <------------------------"; - - return; - } - - max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; - unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); - - //qDebug() << "MAX adsb distance=" << max_distance; - - if (reply->error()) { - qDebug() << "ADSB OpenSky request error!"; - qDebug() << reply->errorString(); -// LocalMessage::instance()->showMessage("ADSB OpenSky Reply Error", 4); - reply->deleteLater(); - return; - } - - QJsonParseError errorPtr; - QByteArray data = reply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); - - if (doc.isNull()) { - qDebug() << "ADSB Opensky network response: Parse failed"; -// LocalMessage::instance()->showMessage("ADSB OpenSky Parse Error", 4); - reply->deleteLater(); - return; - } - - if(!doc.isObject()){ - qDebug()<<"JSON is not an object."; - reply->deleteLater(); - return; - } - - QJsonObject jsonObject = doc.object(); - - if(jsonObject.isEmpty()){ - qDebug()<<"ADSB Openskynetwork response: JSON object is empty."; -// LocalMessage::instance()->showMessage("ADSB OpenSky empty object", 4); - reply->deleteLater(); - return; - } - - QJsonValue value = jsonObject.value("states"); - QJsonArray array = value.toArray(); - - foreach (const QJsonValue & v, array){ - - ADSBVehicle::VehicleInfo_t adsbInfo; - bool icaoOk; - - QJsonArray innerarray = v.toArray(); - QString icaoAux = innerarray[0].toString(); - adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); - - // Skip this element if icao number is not ok - if (!icaoOk) { - continue; - } - - // location comes in lat lon format, but we need it as QGeoCoordinate - if(innerarray[6].isNull() || innerarray[5].isNull()){ //skip if no lat lon - continue; - } - double lat = innerarray[6].toDouble(); - double lon = innerarray[5].toDouble(); - QGeoCoordinate location(lat, lon); - adsbInfo.location = location; - adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; - - //evaluate distance for INTERNET adsb traffic... this is redundant with sdr - double lat_1 = _api_center_coord.latitude(); - double lon_1 = _api_center_coord.longitude(); - - double latDistance = qDegreesToRadians(lat_1 - lat); - double lngDistance = qDegreesToRadians(lon_1 - lon); - - double a = qSin(latDistance / 2) * qSin(latDistance / 2) - + qCos(qDegreesToRadians(lat_1)) * qCos(qDegreesToRadians(lat)) - * qSin(lngDistance / 2) * qSin(lngDistance / 2); - - double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); - double distance = 6371 * c; - - adsbInfo.distance = distance; - //qDebug() << "adsb internet distance=" << distance; - adsbInfo.availableFlags |= ADSBVehicle::DistanceAvailable; - - // If aircraft beyond max distance than skip this one - if(distance>max_distance){ - qDebug() << "Beyond max SKIPPING"; - continue; - } - - // rest of fields - - // callsign - adsbInfo.callsign = innerarray[1].toString(); - if (adsbInfo.callsign.length() == 0) { - adsbInfo.callsign = "N/A"; - } - adsbInfo.availableFlags |= ADSBVehicle::CallsignAvailable; - - //altitude - if(innerarray[7].isDouble()){ - adsbInfo.altitude = innerarray[7].toDouble(); - //per setting eliminate all unknown alt - if (adsbInfo.altitude<5 && unknown_zero_alt==false){ - //skip this traffic - continue; - } - } - else { - //per setting eliminate all unknown alt - if (unknown_zero_alt==false){ - //skip this traffic - continue; - } - else { - adsbInfo.altitude=99999.9; - } - } - adsbInfo.availableFlags |= ADSBVehicle::AltitudeAvailable; - - //velocity - if(innerarray[9].isDouble()){ - adsbInfo.velocity = innerarray[9].toDouble() * 3.6; // m/s to km/h - } - else { - adsbInfo.velocity=99999.9; - } - adsbInfo.availableFlags |= ADSBVehicle::VelocityAvailable; - - //heading - if(innerarray[10].isDouble()){ - adsbInfo.heading = innerarray[10].toDouble(); - } - else { - adsbInfo.heading=0.0; - } - adsbInfo.availableFlags |= ADSBVehicle::HeadingAvailable; - - //last contact - if(innerarray[4].isNull()){ - adsbInfo.lastContact=0; - } - else { - adsbInfo.lastContact = innerarray[4].toInt(); - } - adsbInfo.availableFlags |= ADSBVehicle::LastContactAvailable; - - //vertical velocity - if(innerarray[11].isDouble()){ - adsbInfo.verticalVel = innerarray[11].toDouble(); - } - else { - adsbInfo.verticalVel=0.0; - } - adsbInfo.availableFlags |= ADSBVehicle::VerticalVelAvailable; - - // this is received on adsbvehicleupdate slot - emit adsbVehicleUpdate(adsbInfo); - } - reply->deleteLater(); -} - -ADSBSdr::ADSBSdr() - : ADSBapi() -{ - // we need to manage this properly - #if defined(__rasp_pi__) - _groundAddress = "127.0.0.1"; - #endif - - timer_interval = 2000; -} - -void ADSBSdr::requestData(void) { -// Logger::instance()->logData("request data", 1); - _adsb_api_sdr = _settings.value("adsb_api_sdr").toBool(); - _show_adsb_sdr = _settings.value("show_adsb").toBool(); - - // If sdr is disabled by settings don't make the request and return - if (!_adsb_api_sdr || !_show_adsb_sdr) { - return; - } - - adsb_url= "http://"+_groundAddress+":8080/data/aircraft.json"; - - QNetworkRequest request; - QUrl api_request = adsb_url; - request.setUrl(api_request); - request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); - - // qDebug() << "url=" << api_request; - m_manager->get(request); -} - -void ADSBSdr::processReply(QNetworkReply *reply) { -// Logger::instance()->logData("process reply", 1); - if (!_adsb_api_sdr || !_show_adsb_sdr) { - return; - } - - max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; - unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); - - //qDebug() << "MAX adsb distance=" << max_distance; - - if (reply->error()) { - qDebug() << "ADSB SDR request error!"; - qDebug() << reply->errorString(); -// LocalMessage::instance()->showMessage("ADSB SDR Reply Error", 4); - reply->deleteLater(); - return; - } - - QJsonParseError errorPtr; - QByteArray data = reply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); - - if (doc.isNull()) { - qDebug() << "ADSB SDR response: Parse failed"; -// LocalMessage::instance()->showMessage("ADSB SDR Parse Error", 4); - reply->deleteLater(); - return; - } - - if(!doc.isObject()){ - qDebug()<<"JSON is not an object."; - // LocalMessage::instance()->showMessage("ADSB SDR Json not an object", 4); - reply->deleteLater(); - return; - } - - QJsonObject jsonObject = doc.object(); - - if(jsonObject.isEmpty()){ - qDebug()<<"ADSB Openskynetwork response: JSON object is empty."; -// LocalMessage::instance()->showMessage("ADSB SDR Json empty", 4); - reply->deleteLater(); - return; - } - - QJsonArray array = jsonObject["aircraft"].toArray(); - - if(array.isEmpty()){ - qDebug()<<"JSON array is empty."; -// LocalMessage::instance()->showMessage("ADSB SDR Json array empty", 4); - reply->deleteLater(); - return; - } - - foreach (const QJsonValue & val, array){ -// Logger::instance()->logData("For Each Loop... /n", 1); - ADSBVehicle::VehicleInfo_t adsbInfo; - bool icaoOk; - - // TODO - // According to dump1090-mutability, this value can start - // by "~" in case it isn't a valid ICAO. How this will - // behave then? - QString icaoAux = val.toObject().value("hex").toString(); -// Logger::instance()->logData("icaoAux:"+icaoAux, 1); - adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); - - // Only continue if icao number is ok - if (icaoOk) { -// Logger::instance()->logData("icao ok!", 1); - - // location comes in lat lon format, but we need it as QGeoCoordinate - - if(val.toObject().value("lat").isNull() || val.toObject().value("lon").isNull()){ //skip if no lat lon - continue; - } - - double lat = val.toObject().value("lat").toDouble(); - double lon = val.toObject().value("lon").toDouble(); - - QGeoCoordinate location(lat, lon); - adsbInfo.location = location; - adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; - - //evaluate distance for SDR adsb traffic... this is redundant with internet - double lat_1 = _api_center_coord.latitude(); - double lon_1 = _api_center_coord.longitude(); - - double latDistance = qDegreesToRadians(lat_1 - lat); - double lngDistance = qDegreesToRadians(lon_1 - lon); - - double a = qSin(latDistance / 2) * qSin(latDistance / 2) - + qCos(qDegreesToRadians(lat_1)) * qCos(qDegreesToRadians(lat)) - * qSin(lngDistance / 2) * qSin(lngDistance / 2); - - double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); - double distance = 6371 * c; - - adsbInfo.distance = distance; - adsbInfo.availableFlags |= ADSBVehicle::DistanceAvailable; - - // If aircraft beyond max distance than skip this one - if(distance>max_distance){ - //qDebug() << "Beyond max SKIPPING"; - continue; - } - - // callsign - adsbInfo.callsign = val.toObject().value("flight").toString(); - - if (adsbInfo.callsign.length() == 0) { - adsbInfo.callsign = "N/A"; - } else { - adsbInfo.availableFlags |= ADSBVehicle::CallsignAvailable; - } - - //altitude - if(val.toObject().value("altitude").isNull()){ - //per setting eliminate unknown alt traffic - if (unknown_zero_alt==false){ - //skip this traffic - continue; - } else { - adsbInfo.altitude=99999.9; - } - } - else { - adsbInfo.altitude = val.toObject().value("altitude").toInt() * 0.3048;//feet to meters - //per setting eliminate all unknown alt - if (adsbInfo.altitude<5 && unknown_zero_alt==false){ - //skip this traffic - continue; - } - } - adsbInfo.availableFlags |= ADSBVehicle::AltitudeAvailable; - - //velocity - if(val.toObject().value("speed").isNull()){ - adsbInfo.velocity=99999.9; - } - else { - adsbInfo.velocity = round(val.toObject().value("speed").toDouble() * 1.852); // knots to km/h - } - adsbInfo.availableFlags |= ADSBVehicle::VelocityAvailable; - - //heading - if(val.toObject().value("track").isNull()){ - adsbInfo.heading=0.0; - } - else { - adsbInfo.heading = val.toObject().value("track").toDouble(); - } - adsbInfo.availableFlags |= ADSBVehicle::HeadingAvailable; - - //last contact - if(val.toObject().value("seen_pos").isNull()){ - adsbInfo.lastContact=0; - } - else { - adsbInfo.lastContact = val.toObject().value("seen_pos").toInt(); - } - adsbInfo.availableFlags |= ADSBVehicle::LastContactAvailable; - - //vertical velocity - if(val.toObject().value("vert_rate").isNull()){ - adsbInfo.verticalVel=0.0; - } - else { - adsbInfo.verticalVel = round(val.toObject().value("vert_rate").toDouble() * 0.00508); //feet/min to m/s - } - adsbInfo.availableFlags |= ADSBVehicle::VerticalVelAvailable; - - - // this is received on adsbvehicleupdate slot - emit adsbVehicleUpdate(adsbInfo); - } - else { -// Logger::instance()->logData("icao REJECTED! /n", 1); - qDebug()<<"ICAO number NOT OK!"; - } - } - reply->deleteLater(); -} diff --git a/app/adsb/ADSBVehicleManager.h b/app/adsb/ADSBVehicleManager.h deleted file mode 100644 index 0f865f5c8..000000000 --- a/app/adsb/ADSBVehicleManager.h +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include "QmlObjectListModel.h" -#include "ADSBVehicle.h" - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -// This is a base clase for inheriting the links for -// SDR and Opensky network apis -class ADSBapi : public QThread -{ - Q_OBJECT - -public: - ADSBapi(); - ~ADSBapi(); - -signals: - void adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo); - - void adsbClearModelRequest(); - -protected: - void run(void) final; - -public slots: - void mapBoundsChanged(QGeoCoordinate center_coord); - -protected slots: - virtual void processReply(QNetworkReply *reply) = 0; - virtual void requestData() = 0; - -protected: - void init(); - - // network - QNetworkAccessManager * m_manager; - QString adsb_url; - - // boundingbox parameters - QString upperl_lat; - QString upperl_lon; - QString lowerr_lat; - QString lowerr_lon; - - // timer for requests - int timer_interval; - QTimer *timer; - - QSettings _settings; - - QGeoCoordinate _api_center_coord; //private but duplicated across classes - - qreal max_distance; - bool unknown_zero_alt; -}; - -// This class gets the info from Openskynetwork api -class ADSBInternet: public ADSBapi { - Q_OBJECT - -public: - ADSBInternet() { timer_interval = 10000; } - ~ADSBInternet() {} - -private slots: - void processReply(QNetworkReply *reply) override; - void requestData() override; - -private: - bool _adsb_api_openskynetwork; - bool _show_adsb_internet; //wired to show widget setting. somewhat redundant -}; - -// This class gets the info from SDR -class ADSBSdr: public ADSBapi { - Q_OBJECT - -public: - ADSBSdr(); - ~ADSBSdr() {} - - void setGroundIP(QString address) { _groundAddress = address; } - -private slots: - void processReply(QNetworkReply *reply) override; - void requestData() override; - -private: - QString _groundAddress = ""; - bool _adsb_api_sdr; - bool _show_adsb_sdr; //wired to show widget setting. somewhat redundant - -}; - -class ADSBVehicleManager : public QObject { - Q_OBJECT - -public: - ADSBVehicleManager(QObject* parent = nullptr); - ~ADSBVehicleManager(); - static ADSBVehicleManager* instance(); - - Q_PROPERTY(QmlObjectListModel* adsbVehicles READ adsbVehicles CONSTANT) - Q_PROPERTY(QGeoCoordinate apiMapCenter READ apiMapCenter MEMBER _api_center_coord NOTIFY mapCenterChanged) - - // frontend indicator. 0 inactive, 1 red, 2 green - Q_PROPERTY(uint status READ status NOTIFY statusChanged) - - QmlObjectListModel* adsbVehicles(void) { return &_adsbVehicles; } - QGeoCoordinate apiMapCenter(void) { return _api_center_coord; } - uint status() { return _status; } - - // called from qml when the map has moved - Q_INVOKABLE void newMapCenter(QGeoCoordinate center_coord); - - Q_INVOKABLE void setGroundIP(QString address) { _sdrLink->setGroundIP(address); } - -signals: - // sent to ADSBapi to make requests based into this - void mapCenterChanged(QGeoCoordinate center_coord); - - // sent to adsbwidgetform.ui to update the status indicator - void statusChanged(void); - -public slots: - void adsbVehicleUpdate (const ADSBVehicle::VehicleInfo_t vehicleInfo); - void onStarted(); - void adsbClearModel(); - -private slots: - void _cleanupStaleVehicles(void); - -private: - void _evaluateTraffic(double traffic_alt, int traffic_distance); - - QmlObjectListModel _adsbVehicles; - QMap _adsbICAOMap; - QTimer _adsbVehicleCleanupTimer; - ADSBInternet* _internetLink = nullptr; - ADSBSdr* _sdrLink = nullptr; - QGeoCoordinate _api_center_coord; - QElapsedTimer _last_update_timer; - uint _status = 0; - - qreal distance = 0; - QSettings _settings; -}; diff --git a/app/adsb/QmlObjectListModel.cpp b/app/adsb/QmlObjectListModel.cpp deleted file mode 100644 index 16dbff9ba..000000000 --- a/app/adsb/QmlObjectListModel.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/**************************************************************************** - * - * This file has been ported from QGroundControl project - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#include "QmlObjectListModel.h" - -#include -#include - -const int QmlObjectListModel::ObjectRole = Qt::UserRole; -const int QmlObjectListModel::TextRole = Qt::UserRole + 1; - -QmlObjectListModel::QmlObjectListModel(QObject* parent) - : QAbstractListModel (parent) - , _dirty (false) - , _skipDirtyFirstItem (false) - , _externalBeginResetModel (false) -{ - -} - -QmlObjectListModel::~QmlObjectListModel() -{ - -} - -QObject* QmlObjectListModel::get(int index) -{ - if (index < 0 || index >= _objectList.count()) { - return nullptr; - } - return _objectList[index]; -} - -int QmlObjectListModel::rowCount(const QModelIndex& parent) const -{ - Q_UNUSED(parent); - - return _objectList.count(); -} - -QVariant QmlObjectListModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) { - return QVariant(); - } - - if (index.row() < 0 || index.row() >= _objectList.count()) { - return QVariant(); - } - - if (role == ObjectRole) { - return QVariant::fromValue(_objectList[index.row()]); - } else if (role == TextRole) { - return QVariant::fromValue(_objectList[index.row()]->objectName()); - } else { - return QVariant(); - } -} - -QHash QmlObjectListModel::roleNames(void) const -{ - QHash hash; - - hash[ObjectRole] = "object"; - hash[TextRole] = "text"; - - return hash; -} - -bool QmlObjectListModel::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (index.isValid() && role == ObjectRole) { - _objectList.replace(index.row(), value.value()); - emit dataChanged(index, index); - return true; - } - - return false; -} - -bool QmlObjectListModel::insertRows(int position, int rows, const QModelIndex& parent) -{ - Q_UNUSED(parent); - - if (position < 0 || position > _objectList.count() + 1) { - qWarning() << "Invalid position position:count" << position << _objectList.count(); - } - - beginInsertRows(QModelIndex(), position, position + rows - 1); - endInsertRows(); - - emit countChanged(count()); - - return true; -} - -bool QmlObjectListModel::removeRows(int position, int rows, const QModelIndex& parent) -{ - Q_UNUSED(parent); - - if (position < 0 || position >= _objectList.count()) { - qWarning() << "Invalid position position:count" << position << _objectList.count(); - } else if (position + rows > _objectList.count()) { - qWarning() << "Invalid rows position:rows:count" << position << rows << _objectList.count(); - } - - beginRemoveRows(QModelIndex(), position, position + rows - 1); - for (int row=0; row= _objectList.count()) { - return nullptr; - } - return _objectList[index]; -} - -const QObject* QmlObjectListModel::operator[](int index) const -{ - if (index < 0 || index >= _objectList.count()) { - return nullptr; - } - return _objectList[index]; -} - -void QmlObjectListModel::clear() -{ - if (!_externalBeginResetModel) { - beginResetModel(); - } - _objectList.clear(); - if (!_externalBeginResetModel) { - endResetModel(); - emit countChanged(count()); - } -} - -QObject* QmlObjectListModel::removeAt(int i) -{ - QObject* removedObject = _objectList[i]; - if(removedObject) { - // Look for a dirtyChanged signal on the object - if (_objectList[i]->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { - if (!_skipDirtyFirstItem || i != 0) { - QObject::disconnect(_objectList[i], SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); - } - } - } - removeRows(i, 1); - setDirty(true); - return removedObject; -} - -void QmlObjectListModel::insert(int i, QObject* object) -{ - if (i < 0 || i > _objectList.count()) { - qWarning() << "Invalid index index:count" << i << _objectList.count(); - } - if(object) { - QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); - // Look for a dirtyChanged signal on the object - if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { - if (!_skipDirtyFirstItem || i != 0) { - QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); - } - } - } - _objectList.insert(i, object); - insertRows(i, 1); - setDirty(true); -} - -void QmlObjectListModel::insert(int i, QList objects) -{ - if (i < 0 || i > _objectList.count()) { - qWarning() << "Invalid index index:count" << i << _objectList.count(); - } - - int j = i; - for (QObject* object: objects) { - QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); - - // Look for a dirtyChanged signal on the object - if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { - if (!_skipDirtyFirstItem || j != 0) { - QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); - } - } - j++; - - _objectList.insert(j, object); - } - - insertRows(i, objects.count()); - - setDirty(true); -} - -void QmlObjectListModel::append(QObject* object) -{ - insert(_objectList.count(), object); -} - -void QmlObjectListModel::append(QList objects) -{ - insert(_objectList.count(), objects); -} - -QObjectList QmlObjectListModel::swapObjectList(const QObjectList& newlist) -{ - QObjectList oldlist(_objectList); - if (!_externalBeginResetModel) { - beginResetModel(); - } - _objectList = newlist; - if (!_externalBeginResetModel) { - endResetModel(); - emit countChanged(count()); - } - return oldlist; -} - -int QmlObjectListModel::count() const -{ - return rowCount(); -} - -void QmlObjectListModel::setDirty(bool dirty) -{ - if (_dirty != dirty) { - _dirty = dirty; - if (!dirty) { - // Need to clear dirty from all children - for(QObject* object: _objectList) { - if (object->property("dirty").isValid()) { - object->setProperty("dirty", false); - } - } - } - emit dirtyChanged(_dirty); - } -} - -void QmlObjectListModel::_childDirtyChanged(bool dirty) -{ - _dirty |= dirty; - // We want to emit dirtyChanged even if the actual value of _dirty didn't change. It can be a useful - // signal to know when a child has changed dirty state - emit dirtyChanged(_dirty); -} - -void QmlObjectListModel::deleteListAndContents() -{ - for (int i=0; i<_objectList.count(); i++) { - _objectList[i]->deleteLater(); - } - deleteLater(); -} - -void QmlObjectListModel::clearAndDeleteContents() -{ - beginResetModel(); - for (int i=0; i<_objectList.count(); i++) { - _objectList[i]->deleteLater(); - } - clear(); - endResetModel(); -} - -void QmlObjectListModel::beginReset() -{ - if (_externalBeginResetModel) { - qWarning() << "QmlObjectListModel::beginReset already set"; - } - _externalBeginResetModel = true; - beginResetModel(); -} - -void QmlObjectListModel::endReset() -{ - if (!_externalBeginResetModel) { - qWarning() << "QmlObjectListModel::endReset begin not set"; - } - _externalBeginResetModel = false; - endResetModel(); -} diff --git a/app/adsb/QmlObjectListModel.h b/app/adsb/QmlObjectListModel.h deleted file mode 100644 index f99b524f3..000000000 --- a/app/adsb/QmlObjectListModel.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** - * - * This file has been ported from QGroundControl project - * - * QGroundControl is licensed according to the terms in the file - * COPYING.md in the root of the source code directory. - * - ****************************************************************************/ - -#pragma once - -#include - -class QmlObjectListModel : public QAbstractListModel -{ - Q_OBJECT - -public: - QmlObjectListModel(QObject* parent = nullptr); - ~QmlObjectListModel() override; - - Q_PROPERTY(int count READ count NOTIFY countChanged) - - /// Returns true if any of the items in the list are dirty. Requires each object to have - /// a dirty property and dirtyChanged signal. - Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) - - Q_INVOKABLE QObject* get(int index); - - // Property accessors - - int count () const; - bool dirty () const { return _dirty; } - - void setDirty (bool dirty); - void append (QObject* object); - void append (QList objects); - QObjectList swapObjectList (const QObjectList& newlist); - void clear (); - QObject* removeAt (int i); - QObject* removeOne (QObject* object) { return removeAt(indexOf(object)); } - void insert (int i, QObject* object); - void insert (int i, QList objects); - bool contains (QObject* object) { return _objectList.indexOf(object) != -1; } - int indexOf (QObject* object) { return _objectList.indexOf(object); } - - QObject* operator[] (int i); - const QObject* operator[] (int i) const; - template T value (int index) { return qobject_cast(_objectList[index]); } - QList* objectList () { return &_objectList; } - - /// Calls deleteLater on all items and this itself. - void deleteListAndContents (); - - /// Clears the list and calls deleteLater on each entry - void clearAndDeleteContents (); - - void beginReset (); - void endReset (); - -signals: - void countChanged (int count); - void dirtyChanged (bool dirtyChanged); - -private slots: - void _childDirtyChanged (bool dirty); - -private: - // Overrides from QAbstractListModel - int rowCount (const QModelIndex & parent = QModelIndex()) const override; - QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; - bool insertRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; - bool removeRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; - bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - QHash roleNames(void) const override; - -private: - QList _objectList; - - bool _dirty; - bool _skipDirtyFirstItem; - bool _externalBeginResetModel; - - static const int ObjectRole; - static const int TextRole; -}; - diff --git a/app/adsb/README.md b/app/adsb/README.md deleted file mode 100644 index a60c06d02..000000000 --- a/app/adsb/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## Summary - -ADSB code -Developer: @Luke -A bit dirty r.n diff --git a/app/adsb/adsb.cpp b/app/adsb/adsb.cpp deleted file mode 100644 index 05f5ddfa1..000000000 --- a/app/adsb/adsb.cpp +++ /dev/null @@ -1,396 +0,0 @@ -#include "adsb.h" -#include -#include -#include - -#ifndef __windows__ -#include -#endif - -#include -#include -#include - -#include - -#include -#include - - -#include -#include -#include -#include - -#include - -#include -#include -#include "markermodel.h" -#include -#include "../telemetry/models/fcmavlinksystem.h" -//#include "localmessage.h" - - -static Adsb* _instance = nullptr; - -Adsb* Adsb::instance() { - if (_instance == nullptr) { - _instance = new Adsb(); - } - return _instance; -} - -Adsb::Adsb(QObject *parent): QObject(parent) { - qDebug() << "Adsb::Adsb()"; -} - -void Adsb::onStarted() { - qDebug() << "------------------Adsb::onStarted()"; - - #if defined(__rasp_pi__) - groundAddress = "127.0.0.1"; - #endif - - auto markerModel = MarkerModel::instance(); - connect(this, &Adsb::addMarker, markerModel, &MarkerModel::addMarker); - connect(this, &Adsb::doneAddingMarkers, markerModel, &MarkerModel::doneAddingMarkers); - connect(this, &Adsb::removeAllMarkers, markerModel, &MarkerModel::removeAllMarkers); - - QNetworkAccessManager * manager = new QNetworkAccessManager(this); - - m_manager = manager; - - connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))) ; - - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &Adsb::requestData); - // How frequently data is requested - timer->start(timer_interval); -} - -void Adsb::setGroundIP(QString address) { - groundAddress = address; -} - -void Adsb::mapBoundsChanged(QGeoCoordinate center_coord) { - center_lat= center_coord.latitude(); - center_lon= center_coord.longitude(); - - // api_request_center_lat=setLatitude(center_lat); - //api_request_center_lon=setLongitude(center_lon); - - /* - need to limit the map bounds to a distance and - when map orients to drone it breaks api as it expects a box oriented north. - So defining our own bound box for the api... - */ - - auto adsb_distance_limit = settings.value("adsb_distance_limit").toInt(); - - QGeoCoordinate qgeo_upper_left; - QGeoCoordinate qgeo_lower_right; - - qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); - qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); - - upperl_lat= QString::number(qgeo_upper_left.latitude()); - upperl_lon= QString::number(qgeo_upper_left.longitude()); - lowerr_lat= QString::number(qgeo_lower_right.latitude()); - lowerr_lon= QString::number(qgeo_lower_right.longitude()); - - /* - qDebug() << "Adsb::lower right=" << lowerr_lat << " " << lowerr_lon; - qDebug() << "Adsb::upper left=" << upperl_lat << " " << upperl_lon; - qDebug() << "Adsb::Center=" << center_lat << " " << center_lon; -*/ -} - -void Adsb::set_adsb_api_coord(QGeoCoordinate adsb_api_coord){ - m_adsb_api_coord=adsb_api_coord; - //qDebug() << "adsb_api_coord=" << m_adsb_api_coord; - emit adsb_api_coord_changed(m_adsb_api_coord); -} - -void Adsb::requestData() { - - qDebug() << "Adsb::requestData()"; - auto show_adsb = settings.value("show_adsb", false).toBool(); - - adsb_api_sdr = settings.value("adsb_api_sdr").toBool(); - - - if (adsb_api_sdr == true){ - //qDebug() << "timer 1"; - timer->stop(); - timer->start(1000); - - if (groundAddress.isEmpty()) { - //LocalMessage::instance()->showMessage("No ADSB Ground Address", 4); - return; - } - - adsb_url= "http://"+groundAddress+":8080/data/aircraft.json"; - } - else { - //qDebug() << "timer 10"; - timer->stop(); - timer->start(10000); - set_adsb_api_coord(QGeoCoordinate(center_lat,center_lon)); - adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; - } - - if(show_adsb==false){ - emit removeAllMarkers(); - return; - } - qDebug() << "ADSB.cpp URL REQUEST:" << adsb_url; - QNetworkRequest request; - QUrl api_request= adsb_url; - request.setUrl(api_request); - request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); - qDebug() << "url=" << api_request; - QNetworkReply *reply = m_manager->get(request); -} - -void Adsb::processReply(QNetworkReply *reply){ - - QString callsign; - int contact; - double lat; - double lon; - int alt; - int track; - int velocity; - double vertical; - qreal distance; - - if (reply->error()) { - qDebug() << "ADSB request error!"; - qDebug() << reply->errorString(); - //LocalMessage::instance()->showMessage("ADSB Reply Error", 4); - reply->deleteLater(); - return; - } - - QByteArray data = reply->readAll(); - - QJsonParseError errorPtr; - QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); - - if (doc.isNull()) { - qDebug() << "Parse failed"; -// LocalMessage::instance()->showMessage("ADSB Parse Error", 4); - } - - if(doc.isNull()){ - qDebug()<<"Failed to create JSON doc."; - reply->deleteLater(); - return; - } - if(!doc.isObject()){ - qDebug()<<"JSON is not an object."; - reply->deleteLater(); - return; - } - - QJsonObject jsonObject = doc.object(); - - if(jsonObject.isEmpty()){ - qDebug()<<"JSON object is empty."; - reply->deleteLater(); - return; - } - - // --------------- PARSE FOR SDR --------------------------------- - - if (adsb_api_sdr == true){ - - QJsonArray array = jsonObject["aircraft"].toArray(); - - //QJsonArray array = doc.array(); - - if(array.isEmpty()){ - qDebug()<<"JSON array is empty."; - } - - qDebug() << "MYARRAY COUNT=" << array.count(); - - int current_row=0; - int last_row=array.count(); - - emit removeAllMarkers(); - - if (last_row==0){ - //no markers to add.. - reply->deleteLater(); - return; - } - - - foreach (const QJsonValue & val, array){ - - callsign=val.toObject().value("flight").toString(); - if (callsign.length() == 0) { - callsign = "N/A"; - } - - //sdr defaults to imperial and something wacky for speed from dump1090 - - contact=val.toObject().value("seen_pos").toInt(); - lat=val.toObject().value("lat").toDouble(); - lon=val.toObject().value("lon").toDouble(); - alt=val.toObject().value("altitude").toInt(); - alt=alt*0.3048; - velocity=val.toObject().value("speed").toDouble(); - velocity=round(velocity*.51418); - track=val.toObject().value("track").toDouble(); - vertical=val.toObject().value("vert_rate").toDouble(); - vertical=round(vertical*0.3048); - - - - - //calculate distance from center of map so we can sort in marker model - distance = calculateKmDistance(FCMavlinkSystem::instance().lat(), FCMavlinkSystem::instance().lon(), lat, lon); - emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); - current_row=current_row+1; - - if (lat!=0 || lon!=0) { - /*dont evaluate marker if position msg is missing - above we are adding markers with 0 lat lon cuz we dont know the last row count at loop start - those "0 position" aircraft are being eliminated in mapcomponent - */ - evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); - } -/* - qDebug() << "callsign=" << callsign; - qDebug() << "last_contact=" << contact; - qDebug() << "lat=" << lat; - qDebug() << "lon=" << lon; - qDebug() << "alt=" << alt; - qDebug() << "velocity=" << velocity; - qDebug() << "track=" << track; - qDebug() << "vertical=" << vertical; - qDebug() << "distance=" << distance; - - qDebug() << "----------------------------------------------------------"; -*/ - - - - } - - } - // --------------- PARSE FOR API --------------------------------- - else { - - QJsonValue value = jsonObject.value("states"); - QJsonArray array = value.toArray(); - - qDebug() << "ADSB.cpp OPENSKY ARRAY COUNT=" << array.count(); - - int current_row=0; - int last_row=array.count(); - - QString callsign; - int contact; - double lat; - double lon; - int alt; - int track; - int velocity; - double vertical; - qreal distance; - - emit removeAllMarkers(); - - if (last_row==0){ - //no markers to add.. either the api is not happy (too zoomed out) or no traffic to report - reply->deleteLater(); - return; - } - - foreach (const QJsonValue & v, array){ - QJsonArray innerarray = v.toArray(); - - callsign=innerarray[1].toString(); - if (callsign.length() == 0) { - callsign = "N/A"; - } - contact=innerarray[4].toInt(); - lat=innerarray[6].toDouble(); - lon=innerarray[5].toDouble(); - alt=innerarray[7].toDouble(); - velocity=innerarray[9].toDouble(); - track=innerarray[10].toDouble(); - vertical=innerarray[11].toDouble(); - - //calculate distance from center of map so we can sort in marker model - - distance = calculateKmDistance(FCMavlinkSystem::instance().lat(), FCMavlinkSystem::instance().lon(), lat, lon); - emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); - - evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); - - current_row=current_row+1; - -/* - qDebug() << "callsign=" << innerarray[1].toString(); - qDebug() << "last_contact=" << innerarray[4].toInt(); - qDebug() << "lat=" << innerarray[6].toDouble(); - qDebug() << "lon=" << innerarray[5].toDouble(); - qDebug() << "alt=" << innerarray[7].toDouble(); - qDebug() << "velocity=" << innerarray[9].toDouble(); - qDebug() << "track=" << innerarray[10].toDouble(); - qDebug() << "vertical=" << innerarray[11].toDouble(); - qDebug() << "distance=" << distance; - qDebug() << "----------------------------------------------------------"; -*/ - } - - } - //emit doneAddingMarkers(); - reply->deleteLater(); -} - -void Adsb::evaluateTraffic(QString traffic_callsign, - int traffic_contact, - double traffic_lat, - double traffic_lon, - double traffic_alt, - double traffic_velocity, - double traffic_track, - double traffic_vertical, - double traffic_distance) { - - /* - * Centralise traffic threat detection here. Once threat is detected it should be - * labled and then sent over to the adsb widget - * - * need to calculate azimuth and bearing of any threats so that it can be shared - * and depicted in the adsb widget - */ - int drone_alt = FCMavlinkSystem::instance().altitude_msl_m(); - - if (traffic_alt - drone_alt < 300 && traffic_distance < 2) { -// LocalMessage::instance()->showMessage("Aircraft Traffic", 3); - } else if (traffic_alt - drone_alt < 500 && traffic_distance < 5) { -// LocalMessage::instance()->showMessage("Aircraft Traffic", 4); - } -} - -int Adsb::calculateKmDistance(double lat_1, double lon_1, - double lat_2, double lon_2) { - - double latDistance = qDegreesToRadians(lat_1 - lat_2); - double lngDistance = qDegreesToRadians(lon_1 - lon_2); - - double a = qSin(latDistance / 2) * qSin(latDistance / 2) - + qCos(qDegreesToRadians(center_lat)) * qCos(qDegreesToRadians(lat_2)) - * qSin(lngDistance / 2) * qSin(lngDistance / 2); - - double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); - int distance=radius_earth_km * c; - return distance; -} diff --git a/app/adsb/adsb.h b/app/adsb/adsb.h deleted file mode 100644 index 300a4ebe9..000000000 --- a/app/adsb/adsb.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ADSB_H -#define ADSB_H - -#include -#include - - -#include -#include -#include - -#include -#include -#include -#include - -#include "markermodel.h" - - -class Adsb: public QObject { - Q_OBJECT - -public: - explicit Adsb(QObject *parent = nullptr); - static Adsb* instance(); - - Q_PROPERTY(QGeoCoordinate adsb_api_coord MEMBER m_adsb_api_coord WRITE set_adsb_api_coord NOTIFY adsb_api_coord_changed) - void set_adsb_api_coord(QGeoCoordinate adsb_api_coord); - -signals: - void addMarker(int current_row, int total_rows, const Traffic &traffic); - void doneAddingMarkers(); - void removeAllMarkers(); - void adsb_api_coord_changed(QGeoCoordinate adsb_api_coord); - -public slots: - void onStarted(); - void mapBoundsChanged(QGeoCoordinate center_coord); - - Q_INVOKABLE void setGroundIP(QString address); - -private slots: - void processReply(QNetworkReply *reply); - void requestData(); - int calculateKmDistance(double center_lat, double center_lon, - double marker_lat, double marker_lon); - void evaluateTraffic(QString callsign,int contact,double lat,double lon,double alt,double velocity, - double track,double vertical,double distance); - -private: - QGeoCoordinate m_adsb_api_coord; - QNetworkAccessManager * m_manager; - QString upperl_lat; - QString upperl_lon; - QString lowerr_lat; - QString lowerr_lon; - double center_lat; - double center_lon; - QSettings settings; - double radius_earth_km = 6371; - QString adsb_url; - int timer_interval = 5000; //get reset later if api or sdr selected - QTimer *timer; - bool adsb_api_sdr; - QString groundAddress; -}; - - - -#endif diff --git a/app/adsb/adsb_lib.pri b/app/adsb/adsb_lib.pri deleted file mode 100644 index e7203bbd3..000000000 --- a/app/adsb/adsb_lib.pri +++ /dev/null @@ -1,23 +0,0 @@ -INCLUDEPATH += $$PWD - -SOURCES += \ - $$PWD/adsb.cpp \ - $$PWD/markermodel.cpp \ - $$PWD/ADSBVehicle.cpp \ - $$PWD/ADSBVehicleManager.cpp \ - $$PWD/QmlObjectListModel.cpp \ - - -HEADERS += \ - $$PWD/adsb.h \ - $$PWD/markermodel.h \ - $$PWD/ADSBVehicle.h \ - $$PWD/ADSBVehicleManager.h \ - $$PWD/QmlObjectListModel.h \ - - -QT += positioning -QT += concurrent - -# used in main.cpp / qml to enable/disable adsb depending on weather the needed platform dependencies were found -DEFINES += QOPENHD_ENABLE_ADSB_LIBRARY diff --git a/app/adsb/markermodel.cpp b/app/adsb/markermodel.cpp deleted file mode 100644 index b6725c37a..000000000 --- a/app/adsb/markermodel.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include -#include -#include - -#include "markermodel.h" - - - -Traffic::Traffic(const QString &callsign, const int &contact, const double &lat, const double &lon, - const double &alt, const double &velocity, const double &track, const double &vertical, - const int &distance) - : m_callsign(callsign), m_contact(contact), m_lat(lat), m_lon(lon), m_alt(alt), - m_velocity(velocity), m_track(track), m_vertical(vertical), m_distance(distance) -{ -} - -QString Traffic::callsign() const{ - return m_callsign; -} -int Traffic::contact() const{ - return m_contact; -} -double Traffic::lat() const{ - return m_lat; -} -double Traffic::lon() const{ - return m_lon; -} -double Traffic::alt() const{ - return m_alt; -} -double Traffic::velocity() const{ - return m_velocity; -} -double Traffic::track() const{ - return m_track; -} -double Traffic::vertical() const{ - return m_vertical; -} -int Traffic::distance() const{ - return m_distance; -} - - -static MarkerModel* _instance = nullptr; - -MarkerModel* MarkerModel::instance() { - if (_instance == nullptr) { - _instance = new MarkerModel(); - } - return _instance; -} - -MarkerModel::MarkerModel(QObject *parent): QAbstractListModel(parent){ - qDebug() << "MarkerModel::MarkerModel()"; -} - -void MarkerModel::initMarkerModel() { - qDebug() << "MarkerModel::initMarkerModel()"; - //auto openSky = OpenSky::instance(); -} - -void MarkerModel::addMarker(int current_row, int total_rows, const Traffic &traffic){ - - if (current_row == 0){ - //LIMIT HOW MANY OF THE NEAREST MARKERS APPEAR - //m_row_limit = settings.value("adsb_marker_limit").toInt(); - - beginInsertRows(QModelIndex(), 0, total_rows-1); - m_traffic.insert(0, traffic); - } - else { - m_traffic.insert(current_row, traffic); - } - - if (current_row == total_rows-1){ - //the last entry has been made - endInsertRows(); - emit dataChanged(this->index(0),this->index(rowCount())); - } - /* old distance sort limiting the rows to nearest X results - int rowcount=rowCount(); - - if (rowcount>0){ - for (int i = 0; i < rowcount; ++i) { - Traffic compare_traffic=m_traffic.at(i); - if (traffic.distance() < compare_traffic.distance()){ - if(i<=m_row_limit){ - m_traffic.insert(i, traffic); - } - break; - } - if (i == rowcount-1){ - if(i+1<=m_row_limit){ - m_traffic.insert(i+1, traffic); - } - break; - } - } - } - else { - //first entry - m_traffic.insert(0, traffic); - } - */ -} - -void MarkerModel::doneAddingMarkers(){ - /* - //NO LONGER CALLED - - //qDebug() << "onDoneAddingMarkers rowcount=" << rowCount(); - - endInsertRows(); - - // this signal is probably redundant but doesnt seem to hurt. It is ussed by the ADSB widget - emit dataChanged(this->index(0),this->index(rowCount())); - - //get the last displayed row and distance of that object (it is most distant) - Traffic distant_traffic=m_traffic.at(rowCount()-1); - set_adsb_radius(distant_traffic.distance()*1000); - */ -} - -void MarkerModel::set_adsb_radius(int adsb_radius){ - //drawing a box now - m_adsb_radius=adsb_radius; - //qDebug() << "adsbradius=" << m_adsb_radius; - emit adsb_radius_changed(m_adsb_radius); -} - -void MarkerModel::removeAllMarkers(){ - - //qDebug() << "removeallmarker"; - //remove all rows before adding new - - beginResetModel(); - //qDeleteAll(m_traffic.begin(), m_traffic.end()); - //qDeleteAll(m_traffic); - m_traffic.clear(); - endResetModel(); - - emit dataChanged(this->index(0),this->index(rowCount())); - - /* - int removerowcount=rowCount(); - - if (removerowcount>0){ - //qDebug() << "begin rowcount= " <index(0),this->index(rowCount())); - } - */ -} - - -Traffic MarkerModel::getMarker(int index)const { - return m_traffic.at(index); -} - -int MarkerModel::rowCount(const QModelIndex &parent) const{ - if (parent.isValid()) - return 0; - return m_traffic.count(); -} - -QVariant MarkerModel::data(const QModelIndex &index, int role) const{ - if (index.row() < 0 || index.row() >= m_traffic.count()) - return QVariant(); - - const Traffic &traffic = m_traffic[index.row()]; - - if(role == Callsign) - return traffic.callsign(); - else if (role == Contact) - return traffic.contact(); - else if (role == Lat) - return traffic.lat(); - else if (role == Lon) - return traffic.lon(); - else if (role == Alt) - return traffic.alt(); - else if (role == Velocity) - return traffic.velocity(); - else if (role == Track) - return traffic.track(); - else if (role == Vertical) - return traffic.vertical(); - else if (role == Distance) - return traffic.distance(); - return QVariant(); -} - -QHash MarkerModel::roleNames() const{ - QHash roles; - roles[Callsign] = "callsign"; - roles[Contact] = "contact"; - roles[Lat] = "lat"; - roles[Lon] = "lon"; - roles[Alt] = "alt"; - roles[Velocity] = "velocity"; - roles[Track] = "track"; - roles[Vertical] = "vertical"; - roles[Distance] = "distance"; - return roles; -} diff --git a/app/adsb/markermodel.h b/app/adsb/markermodel.h deleted file mode 100644 index 56bbae3ba..000000000 --- a/app/adsb/markermodel.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef MARKERMODEL_H -#define MARKERMODEL_H -#include -#include -#include -#include - -class Traffic -{ -public: - Traffic(const QString &callsign, const int &contact, const double &lat, const double &lon, - const double &alt, const double &velocity, const double &track, const double &vertical, - const int &distance); - - QString callsign() const; - int contact() const; - double lat() const; - double lon() const; - double alt() const; - double velocity() const; - double track() const; - double vertical() const; - int distance() const; - -private: - QString m_callsign= "---"; - int m_contact= 0; - double m_lat= 0.0; - double m_lon= 0.0; - double m_alt= 99999; - double m_velocity= 0; - double m_track= 0.0; - double m_vertical= 0.0; - int m_distance= 0; -}; - - - -class MarkerModel : public QAbstractListModel { - Q_OBJECT - - -public: - explicit MarkerModel(QObject *parent = nullptr); - - static MarkerModel* instance(); - - enum MarkerRoles { - Callsign = Qt::UserRole + 1, - Contact, - Lat, - Lon, - Alt, - Velocity, - Track, - Vertical, - Distance - }; - - Q_INVOKABLE Traffic getMarker(int index) const; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - - QHash roleNames() const override; - - Q_PROPERTY(int adsb_radius MEMBER m_adsb_radius WRITE set_adsb_radius NOTIFY adsb_radius_changed) - void set_adsb_radius(int adsb_radius); - -signals: - void adsb_radius_changed(int adsb_radius); - -public slots: - void initMarkerModel(); - void addMarker(int current_row , int total_rows, const Traffic &traffic); - void doneAddingMarkers(); - void removeAllMarkers(); - //int getAdsbRadius(); - -private: - QList m_traffic; - int m_row_limit; - QSettings settings; - int m_adsb_radius; -}; - -#endif // MARKERMODEL_H diff --git a/app/common/ThreadsafeQueue.hpp b/app/common/ThreadsafeQueue.hpp deleted file mode 100644 index 3d1f35564..000000000 --- a/app/common/ThreadsafeQueue.hpp +++ /dev/null @@ -1,131 +0,0 @@ -// -// Created by consti10 on 13.02.22. -// - -#ifndef RV1126_OHD_SUSHI_THREADSAFEQUEUE_HPP -#define RV1126_OHD_SUSHI_THREADSAFEQUEUE_HPP - -#include -#include -#include -#include - -// NOTE: the item is wrapped as std::shared_ptr -template -class ThreadsafeQueue { - std::queue> queue_; - mutable std::mutex mutex_; - // Moved out of public interface to prevent races between this - // and pop(). - bool empty() const { - return queue_.empty(); - } -public: - ThreadsafeQueue() = default; - ThreadsafeQueue(const ThreadsafeQueue &) = delete ; - ThreadsafeQueue& operator=(const ThreadsafeQueue &) = delete ; - ThreadsafeQueue(ThreadsafeQueue&& other) { - std::lock_guard lock(mutex_); - queue_ = std::move(other.queue_); - } - virtual ~ThreadsafeQueue() { } - unsigned long size() const { - std::lock_guard lock(mutex_); - return queue_.size(); - } - // returns the oldest item in the queue if available - // nullptr otherwise - std::shared_ptr popIfAvailable() { - std::lock_guard lock(mutex_); - if (queue_.empty()) { - return std::shared_ptr(nullptr); - } - auto tmp = queue_.front(); - queue_.pop(); - return tmp; - } - // adds a new item to the queue - void push(std::shared_ptr item) { - std::lock_guard lock(mutex_); - queue_.push(std::move(item)); - } - // returns a list of all buffers currently inside the queue and removes them from the queue - // The first element in the returned list is the oldest element in the queue - std::vector> getAllAndClear(){ - std::lock_guard lock(mutex_); - std::vector> ret; - while (!queue_.empty()){ - ret.push_back(queue_.front()); - queue_.pop(); - } - return ret; - } -}; - - -// Not sure how to call this, from drmprime example -// blocking, cannot put a new buffer in until the last buffer has been consumed -// after creation, you can put in a buffer immediately -// Then, you have to wait until this buffer has been consumed before you can put in the next buffer -template -class ThreadsafeSingleBuffer{ -public: - ThreadsafeSingleBuffer(){ - sem_init(&q_sem_in, 0, 0); - sem_init(&q_sem_out, 0, 0); - // in the beginning, we can accept a new buffer immediately - int ret=sem_post(&q_sem_out); - /*if(ret!=0){ - MLOGD<<"Error\n"; - }else{ - MLOGD<<"Success\n"; - }*/ - } - ~ThreadsafeSingleBuffer(){ - sem_destroy(&q_sem_in); - sem_destroy(&q_sem_out); - } - // blocks until buffer is available - // or terminate() has been called from another thread - T getBuffer(){ - //MLOGD<<"A1\n"; - sem_wait(&q_sem_in); - auto ret=q_buffer; - q_buffer=NULL; - sem_post(&q_sem_out); - //MLOGD<<"A2\n"; - return ret; - } - // blocks until the current buffer has been consumed - // and therefore can be updated to a new value - void setBuffer(T newBuffer){ - //MLOGD<<"X1\n"; - sem_wait(&q_sem_out); - //assert(message==NULL); - q_buffer=newBuffer; - sem_post(&q_sem_in); - //MLOGD<<"X2\n"; - } - // terminate, getBuffer() will return immediately - void terminate(){ - q_terminate= true; - sem_post(&q_sem_in); - } - // returns true when terminated - bool terminated(){ - return q_terminate; - } - // be carefully, not synchronized at all - T unsafeGetFrame(){ - return q_buffer; - } -private: - sem_t q_sem_in; - sem_t q_sem_out; - // terminate - bool q_terminate=false; - T q_buffer=NULL; -}; - - -#endif //RV1126_OHD_SUSHI_THREADSAFEQUEUE_HPP diff --git a/app/logging/hudlogmessagesmodel.cpp b/app/logging/hudlogmessagesmodel.cpp index 2bb0011e3..91a6cd76f 100644 --- a/app/logging/hudlogmessagesmodel.cpp +++ b/app/logging/hudlogmessagesmodel.cpp @@ -106,6 +106,7 @@ void HUDLogMessagesModel::handle_cleanup() const auto age=std::chrono::steady_clock::now()-el.added_time_point; if(age>std::chrono::seconds(5)){ removeData(i); + i--; } } } diff --git a/app/logging/hudlogmessagesmodel.h b/app/logging/hudlogmessagesmodel.h index dbf428359..95a458a75 100644 --- a/app/logging/hudlogmessagesmodel.h +++ b/app/logging/hudlogmessagesmodel.h @@ -51,7 +51,7 @@ public slots: QVector m_data; QTimer* m_cleanup_timer; void handle_cleanup(); - static constexpr int MAX_N_ELEMENTS=5; + static constexpr int MAX_N_ELEMENTS=6; public: signals: void signalAddLogMessage(int severity,QString message); diff --git a/app/logging/logmessagesmodel.cpp b/app/logging/logmessagesmodel.cpp index ee1b97fb5..91c218b2a 100644 --- a/app/logging/logmessagesmodel.cpp +++ b/app/logging/logmessagesmodel.cpp @@ -108,10 +108,12 @@ void LogMessagesModel::removeData(int row) void LogMessagesModel::addData(LogMessageData logMessageData) { - // hacky temporary - if(logMessageData.message.contains("Scanning ")){ - HUDLogMessagesModel::instance().add_message_info(logMessageData.message); - }else if(logMessageData.message.contains("Cannot scan ")){ + // A few important log(s) we show in the HUD + if(logMessageData.message.contains("TX (likely) not supported by card(s)")){ + //HUDLogMessagesModel::instance().add_message_warning(logMessageData.message); + }else if(logMessageData.message.contains("Bind phrase mismatch")){ + HUDLogMessagesModel::instance().add_message_warning(logMessageData.message); + }else if(logMessageData.message.contains("error - unsupported resolution ?")){ HUDLogMessagesModel::instance().add_message_warning(logMessageData.message); } //qDebug()<<"LogMessagesModel::addData"< permissions({"android.permission.INTERNET", "android.permission.ACCESS_FINE_LOCATION"}); #endif -#ifdef QOPENHD_HAS_MAVSDK_MAVLINK_TELEMETRY #include "telemetry/models/fcmavlinksystem.h" +#include "telemetry/action/fcaction.h" +#include "telemetry/action/ohdaction.h" #include "telemetry/models/fcmavlinkmissionitemsmodel.h" -#include "telemetry/models/fcmavlinksettingsmodel.h" +#include "telemetry/action/fcmissionhandler.h" #include "telemetry/models/camerastreammodel.h" #include "telemetry/models/aohdsystem.h" #include "telemetry/models/wificard.h" #include "telemetry/MavlinkTelemetry.h" #include "telemetry/models/rcchannelsmodel.h" #include "telemetry/settings/mavlinksettingsmodel.h" -#include "telemetry/settings/synchronizedsettings.h" -#endif //QOPENHD_HAS_MAVSDK_MAVLINK_TELEMETRY - +#include "telemetry/settings/wblinksettingshelper.h" #include "osd/speedladder.h" #include "osd/altitudeladder.h" #include "osd/headingladder.h" #include "osd/horizonladder.h" #include "osd/flightpathvector.h" -#include "osd/drawingcanvas.h" #include "osd/aoagauge.h" #include "osd/performancehorizonladder.h" @@ -65,12 +63,6 @@ const QVector permissions({"android.permission.INTERNET", #include "util/WorkaroundMessageBox.h" #include "util/restartqopenhdmessagebox.h" -#ifdef QOPENHD_ENABLE_ADSB_LIBRARY -#include "adsb/ADSBVehicleManager.h" -#include "adsb/ADSBVehicle.h" -#include "adsb/QmlObjectListModel.h" -#endif - // Load all the fonts we use ?! static void load_fonts(){ @@ -263,7 +255,6 @@ int main(int argc, char *argv[]) { qmlRegisterType("OpenHD", 1, 0, "HeadingLadder"); qmlRegisterType("OpenHD", 1, 0, "HorizonLadder"); qmlRegisterType("OpenHD", 1, 0, "FlightPathVector"); - qmlRegisterType("OpenHD", 1, 0, "DrawingCanvas"); qmlRegisterType("OpenHD", 1, 0, "AoaGauge"); qmlRegisterType("OpenHD", 1, 0, "PerformanceHorizonLadder"); @@ -271,42 +262,44 @@ int main(int argc, char *argv[]) { QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("_qopenhd", &QOpenHD::instance()); QOpenHD::instance().setEngine(&engine); + write_platform_context_properties(engine); // Regster all the QT Mavlink system model(s) // it is a common practice for QT to prefix models from c++ with an underscore + // Needs to be registered first, otherwise we can have threading issue(s) + engine.rootContext()->setContextProperty("_messageBoxInstance", &WorkaroundMessageBox::instance()); + engine.rootContext()->setContextProperty("_restartqopenhdmessagebox", &RestartQOpenHDMessageBox::instance()); engine.rootContext()->setContextProperty("_qrenderstats", &QRenderStats::instance()); - write_platform_context_properties(engine); engine.rootContext()->setContextProperty("_ohdlogMessagesModel", &LogMessagesModel::instanceOHD()); engine.rootContext()->setContextProperty("_fclogMessagesModel", &LogMessagesModel::instanceFC()); engine.rootContext()->setContextProperty("_hudLogMessagesModel", &HUDLogMessagesModel::instance()); -#ifdef QOPENHD_HAS_MAVSDK_MAVLINK_TELEMETRY - // Telemetry + // Telemetry - first all the models engine.rootContext()->setContextProperty("_airCameraSettingsModel", &MavlinkSettingsModel::instanceAirCamera()); engine.rootContext()->setContextProperty("_airCameraSettingsModel2", &MavlinkSettingsModel::instanceAirCamera2()); - engine.rootContext()->setContextProperty("_airPiSettingsModel", &MavlinkSettingsModel::instanceAir()); - engine.rootContext()->setContextProperty("_groundPiSettingsModel", &MavlinkSettingsModel::instanceGround()); - // exp - //engine.rootContext()->setContextProperty("_fcSettingsModel", &MavlinkSettingsModel::instanceFC()); - engine.rootContext()->setContextProperty("_synchronizedSettings", &SynchronizedSettings::instance()); - engine.rootContext()->setContextProperty("_mavlinkTelemetry", &MavlinkTelemetry::instance()); + engine.rootContext()->setContextProperty("_ohdSystemAirSettingsModel", &MavlinkSettingsModel::instanceAir()); + engine.rootContext()->setContextProperty("_ohdSystemGroundSettings", &MavlinkSettingsModel::instanceGround()); + engine.rootContext()->setContextProperty("_wbLinkSettingsHelper", &WBLinkSettingsHelper::instance()); engine.rootContext()->setContextProperty("_fcMavlinkSystem", &FCMavlinkSystem::instance()); + engine.rootContext()->setContextProperty("_fcMavlinkAction", &FCAction::instance()); engine.rootContext()->setContextProperty("_fcMavlinkMissionItemsModel", &FCMavlinkMissionItemsModel::instance()); - engine.rootContext()->setContextProperty("_fcMavlinkkSettingsModel", &FCMavlinkSettingsModel::instance()); + engine.rootContext()->setContextProperty("_fcMavlinkMissionHandler", &FCMissionHandler::instance()); engine.rootContext()->setContextProperty("_rcchannelsmodelground", &RCChannelsModel::instanceGround()); engine.rootContext()->setContextProperty("_rcchannelsmodelfc", &RCChannelsModel::instanceFC()); engine.rootContext()->setContextProperty("_ohdSystemAir", &AOHDSystem::instanceAir()); engine.rootContext()->setContextProperty("_ohdSystemGround", &AOHDSystem::instanceGround()); engine.rootContext()->setContextProperty("_cameraStreamModelPrimary", &CameraStreamModel::instance(0)); engine.rootContext()->setContextProperty("_cameraStreamModelSecondary", &CameraStreamModel::instance(1)); + engine.rootContext()->setContextProperty("_ohdAction", &OHDAction::instance()); engine.rootContext()->setContextProperty("_wifi_card_gnd0", &WiFiCard::instance_gnd(0)); engine.rootContext()->setContextProperty("_wifi_card_gnd1", &WiFiCard::instance_gnd(1)); engine.rootContext()->setContextProperty("_wifi_card_gnd2", &WiFiCard::instance_gnd(2)); engine.rootContext()->setContextProperty("_wifi_card_gnd3", &WiFiCard::instance_gnd(3)); engine.rootContext()->setContextProperty("_wifi_card_air", &WiFiCard::instance_air()); -#endif //QOPENHD_HAS_MAVSDK_MAVLINK_TELEMETRY + // And then the main part + engine.rootContext()->setContextProperty("_mavlinkTelemetry", &MavlinkTelemetry::instance()); // Platform - dependend video begin ----------------------------------------------------------------- #ifdef QOPENHD_ENABLE_GSTREAMER_QMLGLSINK @@ -351,23 +344,6 @@ int main(int argc, char *argv[]) { // Platform - dependend video end ----------------------------------------------------------------- engine.rootContext()->setContextProperty("_decodingStatistics",&DecodingStatistcs::instance()); - // dirty - engine.rootContext()->setContextProperty("_messageBoxInstance", &WorkaroundMessageBox::instance()); - engine.rootContext()->setContextProperty("_restartqopenhdmessagebox", &RestartQOpenHDMessageBox::instance()); - -#ifdef QOPENHD_ENABLE_ADSB_LIBRARY - qmlRegisterUncreatableType("OpenHD", 1, 0, "QmlObjectListModel", "Reference only"); - engine.rootContext()->setContextProperty("QOPENHD_ENABLE_ADSB_LIBRARY", QVariant(true)); - engine.rootContext()->setContextProperty("EnableADSB", QVariant(true)); - engine.rootContext()->setContextProperty("LimitADSBMax", QVariant(true)); - auto adsbVehicleManager = ADSBVehicleManager::instance(); - engine.rootContext()->setContextProperty("AdsbVehicleManager", adsbVehicleManager); - //QObject::connect(openHDSettings, &OpenHDSettings::groundStationIPUpdated, adsbVehicleManager, &ADSBVehicleManager::setGroundIP, Qt::QueuedConnection); - adsbVehicleManager->onStarted(); -#else - engine.rootContext()->setContextProperty("QOPENHD_ENABLE_ADSB_LIBRARY", QVariant(false)); - engine.rootContext()->setContextProperty("EnableADSB", QVariant(false)); -#endif // This allows to use the defines as strings in qml engine.rootContext()->setContextProperty("QOPENHD_GIT_VERSION", @@ -392,6 +368,8 @@ int main(int argc, char *argv[]) { #endif qDebug() << "Running QML"; + // Now we start mavlink for the first time + MavlinkTelemetry::instance().start(); QRenderStats::instance().register_to_root_window(engine); diff --git a/app/osd/drawingcanvas.cpp b/app/osd/drawingcanvas.cpp deleted file mode 100644 index bbcf95a6a..000000000 --- a/app/osd/drawingcanvas.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "drawingcanvas.h" - -#include -#include -#include -#include -#include - -#include "debug_overdraw.hpp" - -DrawingCanvas::DrawingCanvas(QQuickItem *parent): QQuickPaintedItem(parent) { - //qDebug() << "DrawingCanvas::DrawingCanvas()"; - setRenderTarget(RenderTarget::FramebufferObject); - - //set font to pixels size early - m_font.setPixelSize(14); - m_fontNormal.setPixelSize(35); - - QSettings settings; -} - -void DrawingCanvas::paint(QPainter* painter) { - painter->save(); - if(ENABLE_DEBUG_OVERDRAW){ - setFillColor(QColor::fromRgb(255,0,0,128)); - } - - if (m_draw_request=="adsb"){ //statis for now, but here for future build out - auto pos_x= 130;//the middle - auto pos_y= 130; - - painter->setPen(m_color); - setOpacity(1.0); - - painter->translate(pos_x,pos_y); - - painter->rotate(-90);//glyph is oriented +90 - - - bool orientation_setting = settings.value("map_orientation").toBool(); - - if (orientation_setting == true){ //orienting map to drone - m_orientation = m_heading - m_drone_heading; - - if (m_orientation < 0) m_orientation += 360; - if (m_orientation >= 360) m_orientation -=360; - painter->rotate(m_orientation); - } - else{ //orienting map to north - m_orientation=0; - painter->rotate(m_heading); - } - - //draw speed tail - painter->setOpacity(0.5); - painter->fillRect(QRectF(0, -8, -m_speed/12, 4), "white"); - painter->setPen("grey"); - painter->drawRect(QRectF(0, -8, -m_speed/12, 4)); - - //add icon glyph of airplane - /* //for glow effect - painter->setOpacity(1.0); - painter->setPen("grey"); - painter->setFont(m_fontBig); - painter->drawText(0, 0, "\ue3d0"); - */ - - painter->setOpacity(1.0); - painter->setPen("black"); - painter->setFont(m_fontNormal); - - painter->drawText(0, 0, "\uf072"); - - //draw data block - - painter->translate(+50,-60); //+up -down, -left +right - - //de-rotate whatever was done above and the adjustment for the glyph - if (m_orientation!=0){ - painter->rotate(-m_orientation+90); - } - else { - painter->rotate(-m_heading+90); - } - - painter->translate(-33,-24); //preposition the text block - - painter->setOpacity(0.5); - QPainterPath path; - path.addRoundedRect(QRectF(0, 0, 80, 50), 10, 10); - QPen pen(Qt::white, 2); - painter->setPen(pen); - painter->fillPath(path, Qt::black); - painter->drawPath(path); - - painter->setOpacity(1.0); - painter->setPen("white"); - painter->setFont(m_font); - - painter->drawText(5, 15, m_name); - painter->drawText(10, 30, m_speed_text); - painter->drawText(10, 45, m_alt_text); - - painter->restore(); - } -} - - - -QColor DrawingCanvas::color() const { - return m_color; -} - -QColor DrawingCanvas::glow() const { - return m_glow; -} - -void DrawingCanvas::setColor(QColor color) { - m_color = color; - emit colorChanged(m_color); - update(); -} - -void DrawingCanvas::setGlow(QColor glow) { - m_glow = glow; - emit glowChanged(m_glow); - update(); -} - -void DrawingCanvas::setFpvInvertPitch(bool fpvInvertPitch) { - m_fpvInvertPitch = fpvInvertPitch; - emit fpvInvertPitchChanged(m_fpvInvertPitch); - update(); -} - -void DrawingCanvas::setFpvInvertRoll(bool fpvInvertRoll) { - m_fpvInvertRoll = fpvInvertRoll; - emit fpvInvertRollChanged(m_fpvInvertRoll); - update(); -} - -void DrawingCanvas::setHeading(int heading) { - m_heading = heading; - emit headingChanged(m_heading); - update(); -} - -void DrawingCanvas::setDroneHeading(int drone_heading) { - m_drone_heading = drone_heading; - emit droneHeadingChanged(m_drone_heading); - update(); -} - -void DrawingCanvas::setAlt(int alt) { - m_alt = alt; - - if(alt>9999){ - m_alt_text="---"; - } - else{ - imperial = settings.value("enable_imperial").toBool(); - if (imperial == false){ - m_alt_text = (QString::number(round(alt)))+" M"; - } - else{ - m_alt_text = (QString::number(round((alt) * 3.28084)))+" Ft"; - } - } - - emit altChanged(m_alt); - emit altTextChanged(m_alt_text); - update(); -} - -void DrawingCanvas::setAltText(QString alt_text) { - m_alt_text = alt_text; - emit altTextChanged(m_alt_text); - update(); -} - -void DrawingCanvas::setDroneAlt(int drone_alt) { - m_drone_alt = drone_alt; - emit droneAltChanged(m_drone_alt); - update(); -} - -void DrawingCanvas::setSpeed(int speed) { - if(speed>9999){ - m_speed=0; - m_speed_text="---"; - } - else{ - imperial = settings.value("enable_imperial").toBool(); - if (imperial == false){ - m_speed =round(speed* 3.6); - m_speed_text = (QString::number(round(speed* 3.6)))+" Kph"; - } - else{ - m_speed = round(speed* 2.23694); - m_speed_text = (QString::number(round((speed* 2.23694))))+" Mph"; - } - } - - emit speedTextChanged(m_speed_text); - emit speedChanged(m_speed); - update(); -} - -void DrawingCanvas::setSpeedText(QString speed_text) { - m_speed_text = speed_text; - emit speedTextChanged(m_speed_text); - update(); -} - -void DrawingCanvas::setVertSpd(int vert_spd) { - m_vert_spd = vert_spd; - emit vertSpdChanged(m_vert_spd); - update(); -} - -void DrawingCanvas::setRoll(int roll) { - m_roll = roll; - emit rollChanged(m_roll); - update(); -} - -void DrawingCanvas::setPitch(int pitch) { - m_pitch = pitch; - emit pitchChanged(m_pitch); - update(); -} - -void DrawingCanvas::setLateral(int lateral) { - m_lateral = lateral; - emit lateralChanged(m_lateral); - update(); -} - -void DrawingCanvas::setVertical(int vertical) { - m_vertical = vertical; - emit verticalChanged(m_vertical); - update(); -} - -void DrawingCanvas::setHorizonSpacing(int horizonSpacing) { - m_horizonSpacing = horizonSpacing; - emit horizonSpacingChanged(m_horizonSpacing); - update(); -} - -void DrawingCanvas::setHorizonWidth(double horizonWidth) { - m_horizonWidth = horizonWidth; - emit horizonWidthChanged(m_horizonWidth); - update(); -} - -void DrawingCanvas::setSize(double size) { - m_size = size; - emit sizeChanged(m_size); - update(); -} - -void DrawingCanvas::setName(QString name) { - m_name = name; - emit nameChanged(m_name); - update(); -} - -void DrawingCanvas::setVerticalLimit(double verticalLimit) { - m_verticalLimit = verticalLimit; - emit verticalLimitChanged(m_verticalLimit); - update(); -} - -void DrawingCanvas::setLateralLimit(double lateralLimit) { - m_lateralLimit = lateralLimit; - emit lateralLimitChanged(m_lateralLimit); - update(); -} - -void DrawingCanvas::setFontFamily(QString fontFamily) { - m_fontFamily = fontFamily; - emit fontFamilyChanged(m_fontFamily); - update(); -} diff --git a/app/osd/drawingcanvas.h b/app/osd/drawingcanvas.h deleted file mode 100644 index 5177f20b6..000000000 --- a/app/osd/drawingcanvas.h +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include -#include - -class DrawingCanvas : public QQuickPaintedItem { - Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) - Q_PROPERTY(QColor glow READ glow WRITE setGlow NOTIFY glowChanged) - Q_PROPERTY(bool fpvInvertPitch MEMBER m_fpvInvertPitch WRITE setFpvInvertPitch NOTIFY fpvInvertPitchChanged) - Q_PROPERTY(bool fpvInvertRoll MEMBER m_fpvInvertRoll WRITE setFpvInvertRoll NOTIFY fpvInvertRollChanged) - - Q_PROPERTY(int alt MEMBER m_alt WRITE setAlt NOTIFY altChanged) - Q_PROPERTY(QString alt_text MEMBER m_alt_text WRITE setAltText NOTIFY altTextChanged) - Q_PROPERTY(int drone_alt MEMBER m_drone_alt WRITE setDroneAlt NOTIFY droneAltChanged) - Q_PROPERTY(int speed MEMBER m_speed WRITE setSpeed NOTIFY speedChanged) - Q_PROPERTY(QString speed_text MEMBER m_speed_text WRITE setSpeedText NOTIFY speedTextChanged) - Q_PROPERTY(int vert_spd MEMBER m_vert_spd WRITE setVertSpd NOTIFY vertSpdChanged) - Q_PROPERTY(int heading MEMBER m_heading WRITE setHeading NOTIFY headingChanged) - Q_PROPERTY(int drone_heading MEMBER m_drone_heading WRITE setDroneHeading NOTIFY droneHeadingChanged) - Q_PROPERTY(int roll MEMBER m_roll WRITE setRoll NOTIFY rollChanged) - Q_PROPERTY(int pitch MEMBER m_pitch WRITE setPitch NOTIFY pitchChanged) - - Q_PROPERTY(int lateral MEMBER m_lateral WRITE setLateral NOTIFY lateralChanged) - Q_PROPERTY(int vertical MEMBER m_vertical WRITE setVertical NOTIFY verticalChanged) - - Q_PROPERTY(int horizonSpacing MEMBER m_horizonSpacing WRITE setHorizonSpacing NOTIFY horizonSpacingChanged) - Q_PROPERTY(double horizonWidth MEMBER m_horizonWidth WRITE setHorizonWidth NOTIFY horizonWidthChanged) - Q_PROPERTY(double size MEMBER m_size WRITE setSize NOTIFY sizeChanged) - - Q_PROPERTY(QString name MEMBER m_name WRITE setName NOTIFY nameChanged) - - Q_PROPERTY(double verticalLimit MEMBER m_verticalLimit WRITE setVerticalLimit NOTIFY verticalLimitChanged) - Q_PROPERTY(double lateralLimit MEMBER m_lateralLimit WRITE setLateralLimit NOTIFY lateralLimitChanged) - - Q_PROPERTY(QString fontFamily MEMBER m_fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) - -public: - explicit DrawingCanvas(QQuickItem* parent = nullptr); - - void paint(QPainter* painter) override; - - QColor color() const; - QColor glow() const; - -public slots: - void setColor(QColor color); - void setGlow(QColor glow); - void setFpvInvertPitch(bool fpvInvertPitch); - void setFpvInvertRoll(bool fpvInvertRoll); - - void setAlt(int alt); - void setAltText(QString alt_text); - void setDroneAlt(int drone_alt); - void setSpeed(int speed); - void setSpeedText(QString speed_text); - void setVertSpd(int vert_spd); - void setHeading(int heading); - void setDroneHeading(int drone_heading); - void setRoll(int roll); - void setPitch(int pitch); - - void setLateral(int lateral); - void setVertical(int vertical); - - void setHorizonSpacing(int horizonSpacing); - void setHorizonWidth(double horizonWidth); - void setSize(double size); - - void setName(QString name); - - void setVerticalLimit(double verticalLimit); - void setLateralLimit(double lateralLimit); - - void setFontFamily(QString fontFamily); - -signals: - void colorChanged(QColor color); - void glowChanged(QColor glow); - void fpvInvertPitchChanged(bool fpvInvertPitch); - void fpvInvertRollChanged(bool fpvInvertRoll); - - void altChanged(int alt); - void altTextChanged(QString alt_text); - void droneAltChanged(int drone_alt); - void speedChanged(int speed); - void speedTextChanged(QString speed_text); - void vertSpdChanged(int vert_spd); - void headingChanged(int heading); - void droneHeadingChanged(int drone_heading); - void rollChanged(int roll); - void pitchChanged(int pitch); - - void lateralChanged(int lateral); - void verticalChanged(int vertical); - - void horizonSpacingChanged(int horizonSpacing); - void horizonWidthChanged(double horizonWidth); - void sizeChanged(double size); - - void nameChanged(QString name); - - void verticalLimitChanged(double verticalLimit); - void lateralLimitChanged(double lateralLimit); - - void fontFamilyChanged(QString fontFamily); - -private: - QColor m_color; - QColor m_glow; - bool m_fpvInvertPitch; - bool m_fpvInvertRoll; - - int m_heading; - int m_drone_heading; - int m_alt; - QString m_alt_text; - int m_drone_alt; - int m_speed; - QString m_speed_text; - int m_vert_spd; - int m_roll; - int m_pitch; - - int m_lateral; - int m_vertical; - - int m_horizonSpacing; - double m_horizonWidth; - double m_size; - - QString m_name; - - double m_verticalLimit; - double m_lateralLimit; - - QSettings settings; - bool imperial; - - int m_orientation=0; - - QString m_fontFamily; - - QString m_draw_request="adsb"; //for future build out of more draw requests - - QFont m_font = QFont("Font Awesome 5 Free", 10, QFont::Bold, false); - QFont m_fontNormal = QFont("osdicons", 25 , QFont::PreferAntialias, true); - //QFont m_fontBig = QFont("osdicons", 25*1.1, QFont::PreferAntialias, true); - -}; diff --git a/app/platform/README.md b/app/platform/README.md deleted file mode 100644 index 5cad9121d..000000000 --- a/app/platform/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Summary -Here we can place code that is platform-dependendent.( and not already abstracted by QT). -For example, changing the screen brightness works differently on different platforms diff --git a/app/platform/appleplatform.h b/app/platform/appleplatform.h deleted file mode 100644 index 0bd8cb5af..000000000 --- a/app/platform/appleplatform.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef APPLEPLATFORM_H -#define APPLEPLATFORM_H - -#include - -class ApplePlatform: public QObject { - Q_OBJECT - -public: - explicit ApplePlatform(QObject *parent = nullptr); - static ApplePlatform* instance(); - -signals: - void willEnterForeground(); - void didEnterBackground(); - -public: - - void disableScreenLock(); - - void registerNotifications(); -}; - -#endif // APPLEPLATFORM_H diff --git a/app/platform/appleplatform.mm b/app/platform/appleplatform.mm deleted file mode 100644 index 5e1421409..000000000 --- a/app/platform/appleplatform.mm +++ /dev/null @@ -1,45 +0,0 @@ - - -#include "appleplatform.h" - -#import -#import - - -static ApplePlatform* _instance = nullptr; - -ApplePlatform* ApplePlatform::instance() { - if (_instance == nullptr) { - _instance = new ApplePlatform(); - } - return _instance; -} - - -ApplePlatform::ApplePlatform(QObject *parent): QObject(parent) { - -} - - -void ApplePlatform::disableScreenLock() { - [[UIApplication sharedApplication] setIdleTimerDisabled: YES]; -} - - -void ApplePlatform::registerNotifications() { - NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; - - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:mainQueue - usingBlock:^void(NSNotification *notification) { - emit didEnterBackground(); - }]; - - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification - object:nil - queue:mainQueue - usingBlock:^void(NSNotification *notification) { - emit willEnterForeground(); - }]; -} diff --git a/app/telemetry/MavlinkTelemetry.cpp b/app/telemetry/MavlinkTelemetry.cpp index fd88d9c8e..b8a70d995 100644 --- a/app/telemetry/MavlinkTelemetry.cpp +++ b/app/telemetry/MavlinkTelemetry.cpp @@ -1,71 +1,18 @@ #include "MavlinkTelemetry.h" -#include "common/openhd-util.hpp" -#include "mavsdk_helper.hpp" -#include "qopenhdmavlinkhelper.hpp" -#include "telemetry/openhd_defines.hpp" #include "models/aohdsystem.h" #include "models/fcmavlinksystem.h" #include "settings/mavlinksettingsmodel.h" -#include "../logging/logmessagesmodel.h" +#include "util/qopenhdmavlinkhelper.hpp" + +#include "action/fcmissionhandler.h" +#include "action/impl/cmdsender.h" +#include "action/fcmsgintervalhandler.h" +#include "action/impl/xparam.h" MavlinkTelemetry::MavlinkTelemetry(QObject *parent):QObject(parent) { - m_msg_interval_helper=std::make_unique(); - mavsdk::Mavsdk::Configuration config{QOpenHDMavlinkHelper::get_own_sys_id(),QOpenHDMavlinkHelper::get_own_comp_id(),false}; - mavsdk=std::make_shared(); - mavsdk->set_configuration(config); - mavsdk::log::subscribe([](mavsdk::log::Level level, // message severity level - const std::string& message, // message text - const std::string& file, // source file from which the message was sent - int line) { // line number in the source file - // process the log message in a way you like - qDebug()<<"MAVSDK::"<=mavsdk::log::Level::Warn){ - // LogMessagesModel::instance().addLogMessage("T",message.c_str(),0); - //} - // returning true from the callback disables printing the message to stdout - //return level < mavsdk::log::Level::Warn; - return true; - }); - // NOTE: subscribe before adding any connection(s) - mavsdk->subscribe_on_new_system([this]() { - std::lock_guard lock(systems_mutex); - // hacky, fucking hell. mavsdk might call this callback with more than 1 system added. - auto systems=mavsdk->systems(); - for(auto i=mavsdk_already_known_systems;ionNewSystem(new_system); - } - mavsdk_already_known_systems=systems.size(); - //qDebug()<mavsdk->systems().size(); - //auto system = this->mavsdk->systems().back(); - //this->onNewSystem(system); - }); - QSettings settings; - dev_use_tcp = settings.value("dev_mavlink_via_tcp",false).toBool(); - if(dev_use_tcp){ - dev_tcp_server_ip=settings.value("dev_mavlink_tcp_ip","0.0.0.0").toString().toStdString(); - if(!OHDUtil::is_valid_ip(dev_tcp_server_ip)){ - qWarning("%s not a valid ip, using default",dev_tcp_server_ip.c_str()); - dev_tcp_server_ip = "0.0.0.0"; - } - //dev_tcp_server_ip = "0.0.0.0"; - // We try connecting until success - m_tcp_connect_thread=std::make_unique(&MavlinkTelemetry::tcp_only_establish_connection,this); - }else{ - // default, udp, passive (like QGC) - mavsdk::ConnectionResult connection_result = mavsdk->add_udp_connection(QOPENHD_GROUND_CLIENT_UDP_PORT_IN); - std::stringstream ss; - ss<<"MAVSDK UDP connection: " << connection_result; - qDebug()<setContextProperty("_mavlinkTelemetry", &MavlinkTelemetry::instance()); -} - -void MavlinkTelemetry::onNewSystem(std::shared_ptr system){ - const auto components=system->component_ids(); - qDebug()<<"System found"<<(int)system->get_system_id()<<" with components:"<get_system_id()==OHD_SYS_ID_GROUND){ - qDebug()<<"Found OHD Ground station"; - systemOhdGround=system; - passtroughOhdGround=std::make_shared(system); - qDebug()<<"XX:"<get_target_sysid(); - passtroughOhdGround->intercept_incoming_messages_async([this](mavlink_message_t& msg){ - //qDebug()<<"Intercept:Got message"<intercept_outgoing_messages_async([](mavlink_message_t& msg){ - // //qDebug()<<"Intercept:send message"<get_system_id()==OHD_SYS_ID_AIR){ - qDebug()<<"Found OHD AIR station"; - systemOhdAir=system; - MavlinkSettingsModel::instanceAir().set_param_client(system); - MavlinkSettingsModel::instanceAirCamera().set_param_client(system); - MavlinkSettingsModel::instanceAirCamera2().set_param_client(system); - AOHDSystem::instanceAir().set_system(system); - // hacky, for connecting to the air unit directly - if(passtroughOhdGround==nullptr){ - passtroughOhdGround=std::make_shared(system); - passtroughOhdGround->intercept_incoming_messages_async([this](mavlink_message_t& msg){ - //qDebug()<<"Intercept:Got message"<has_autopilot()){ - else { - qDebug()<<"Got system id: "<<(int)system->get_system_id()<<" components:"<component_ids()).c_str(); - // By default, we assume there is one additional non-openhd system - the FC - bool is_fc=true; - QSettings settings; - const bool dirty_enable_mavlink_fc_sys_id_check=settings.value("dirty_enable_mavlink_fc_sys_id_check",false).toBool(); - if(dirty_enable_mavlink_fc_sys_id_check){ - // filtering, default off - const auto comp_ids=system->component_ids(); - is_fc=mavsdk::helper::any_comp_id_autopilot(comp_ids); - } - if(is_fc){ - qDebug()<<"Found FC"; - // we got the flight controller - FCMavlinkSystem::instance().set_system(system); - // hacky, for SITL testing - if(passtroughOhdGround==nullptr){ - passtroughOhdGround=std::make_shared(system); - passtroughOhdGround->intercept_incoming_messages_async([this](mavlink_message_t& msg){ - //qDebug()<<"Intercept:Got message"<get_system_id(); - } - //MavlinkSettingsModel::instanceFC().set_param_client(system); + QSettings settings; + const bool dev_use_tcp = settings.value("dev_mavlink_via_tcp",false).toBool(); + if(dev_use_tcp){ + // TODO + }else{ + // default, udp, passive (like QGC) + auto cb_udp=[this](mavlink_message_t msg){ + process_mavlink_message(msg); + }; + const auto ip="0.0.0.0"; //"127.0.0.1" + m_udp_connection=std::make_unique(ip,QOPENHD_GROUND_CLIENT_UDP_PORT_IN,cb_udp); + m_udp_connection->start(); } } @@ -163,192 +49,221 @@ bool MavlinkTelemetry::sendMessage(mavlink_message_t msg){ // probably a programming error, the message was not packed with the right comp id qDebug()<<"WARN Sending message with comp id:"< lock(systems_mutex); - if(passtroughOhdGround!=nullptr){ - passtroughOhdGround->send_message(msg); + if(m_udp_connection){ + m_udp_connection->send_message(msg); return true; }else{ - // If the passtrough is not created yet, a connection to the OHD ground unit has not yet been established. - //qDebug()<<"MAVSDK passtroughOhdGround not created"; - // only log it once, then not again to keep logcat clean - static bool first=true; - if(first){ - qDebug()<<"No OHD Ground unit connected"; - //first=false; + if(m_tcp_connection){ + m_tcp_connection->send_message(msg); + return true; } } return false; } -static int get_message_size(const mavlink_message_t msg){ +static int get_message_size(const mavlink_message_t& msg){ return sizeof(msg); } -void MavlinkTelemetry::onProcessMavlinkMessage(mavlink_message_t msg) +void MavlinkTelemetry::process_mavlink_message(const mavlink_message_t& msg) { m_tele_received_packets++; m_tele_received_bytes+=get_message_size(msg); set_telemetry_pps_in(m_tele_pps_in.get_last_or_recalculate(m_tele_received_packets)); set_telemetry_bps_in(m_tele_bitrate_in.get_last_or_recalculate(m_tele_received_bytes)); //qDebug()<<"MavlinkTelemetry::onProcessMavlinkMessage"<component_ids(); + //is_fc=mavsdk::helper::any_comp_id_autopilot(comp_ids); + } + if(is_fc){ + qDebug()<<"Found FC"; + // we got the flight controller + FCMavlinkSystem::instance().set_system_id(source_sysid); + m_fc_sys_id=source_sysid; + m_fc_comp_id=source_compid; + m_fc_found=true; + }else{ + qDebug()<<"Got weird system:"<check_acknowledgement(msg); - auto opt_command=m_msg_interval_helper->create_command_if_needed(); - if(opt_command.has_value()){ - auto command=opt_command.value(); - send_command_long_oneshot(command); - } - } + process_broadcast_message_fc(msg); + FCMsgIntervalHandler::instance().opt_send_messages(); + FCMissionHandler::instance().opt_send_messages(); }else{ qDebug()<<"MavlinkTelemetry received unmatched message "<std::chrono::seconds(1)){ - m_last_time_version_requested=std::chrono::steady_clock::now(); - if(AOHDSystem::instanceAir().should_request_version() || AOHDSystem::instanceGround().should_request_version()){ - request_openhd_version(); - } - }*/ } -void MavlinkTelemetry::tcp_only_establish_connection() +void MavlinkTelemetry::process_broadcast_message_openhd_air(const mavlink_message_t &msg) { - assert(dev_use_tcp); - qDebug()<<"tcp_only_establish_connection"; - while(true){ - { - std::stringstream ss; - ss<<"TCP try connecting to ["<add_tcp_connection(dev_tcp_server_ip,OHD_GROUND_SERVER_TCP_PORT); - std::stringstream ss; - ss<<"MAVSDK TCP connection result: " << connection_result; - qDebug()<(ip.toStdString(),QOPENHD_OPENHD_GROUND_TCP_SERVER_PORT,cb_tcp); + m_tcp_connection->start(); } -void MavlinkTelemetry::request_openhd_version() +void MavlinkTelemetry::enable_udp() { - mavlink_command_long_t command{}; - command.command=MAV_CMD_REQUEST_MESSAGE; - command.param1=static_cast(MAVLINK_MSG_ID_OPENHD_VERSION_MESSAGE); - send_command_long_oneshot(command); + m_tcp_connection=nullptr; + m_udp_connection=nullptr; + // default, udp, passive (like QGC) + auto cb_udp=[this](mavlink_message_t msg){ + process_mavlink_message(msg); + }; + const auto ip="0.0.0.0"; //"127.0.0.1" + m_udp_connection=std::make_unique(ip,QOPENHD_GROUND_CLIENT_UDP_PORT_IN,cb_udp); + m_udp_connection->start(); } -bool MavlinkTelemetry::send_command_long_oneshot(const mavlink_command_long_t &command) +void MavlinkTelemetry::ping_all_systems() { mavlink_message_t msg; - mavlink_msg_command_long_encode(QOpenHDMavlinkHelper::get_own_sys_id(),QOpenHDMavlinkHelper::get_own_comp_id(), &msg,&command); - return sendMessage(msg); + mavlink_timesync_t timesync{}; + timesync.tc1=0; + // Ardupilot seems to use us + m_last_timesync_out_us=QOpenHDMavlinkHelper::getTimeMicroseconds(); + timesync.ts1=m_last_timesync_out_us; + mavlink_msg_timesync_encode(QOpenHDMavlinkHelper::get_own_sys_id(),QOpenHDMavlinkHelper::get_own_comp_id(),&msg,×ync); + sendMessage(msg); } -bool MavlinkTelemetry::ohd_gnd_request_channel_scan(int freq_bands,int channel_widths) +MavlinkTelemetry::FCMavId MavlinkTelemetry::get_fc_mav_id() { - qDebug()<<"Channels can: "<(freq_bands); - command.param2=static_cast(channel_widths); - return send_command_long_oneshot(command);*/ - if(passtroughOhdGround){ - mavsdk::MavlinkPassthrough::CommandLong command{}; - command.target_sysid=OHD_SYS_ID_GROUND; - command.target_compid=OHD_COMP_ID_LINK_PARAM; - command.command=OPENHD_CMD_INITIATE_CHANNEL_SEARCH; - command.param1=static_cast(freq_bands); - command.param2=static_cast(channel_widths); - auto res=passtroughOhdGround->send_command_long(command); - return res==mavsdk::MavlinkPassthrough::Result::Success; + if(m_fc_found){ + return {m_fc_sys_id,m_fc_comp_id}; } - return false; + return {1,MAV_COMP_ID_AUTOPILOT1}; } void MavlinkTelemetry::re_apply_rates() { - if(m_msg_interval_helper){ - m_msg_interval_helper->restart(); - } + FCMsgIntervalHandler::instance().restart(); } diff --git a/app/telemetry/MavlinkTelemetry.h b/app/telemetry/MavlinkTelemetry.h index 011ce2c1a..c2ab97e9d 100644 --- a/app/telemetry/MavlinkTelemetry.h +++ b/app/telemetry/MavlinkTelemetry.h @@ -7,21 +7,23 @@ #include #include -#include "mavsdk_include.h" -#include "models/fcmessageintervalhelper.hpp" +#include "util/mavlink_include.h" #include "../../lib/lqtutils_master/lqtutils_prop.h" #include "../common/TimeHelper.hpp" +#include "connection/udp_connection.h" +#include "connection/tcp_connection.h" + /** - * Changed: Used to have custom UDP and TCP stuff, but now just uses MAVSDK - MAVSDK already has both TCP and UDP support. * - * @brief This is the one and only (mavlink telemetry) connection of QOpenHD to OpenHD - * (More specific, the OpenHD Ground Station - but since the Ground station forwards messages to the air pi, + * @brief Main entry / exit point for all mavlink telemetry + * (More specific, to / from the OpenHD Ground Station - but since the Ground station forwards messages to/from the air pi, * one can indirectly also reach the air pi via the ground pi, as well as the mavlink FC connected to the air pi). * Its functionalities are simple: + * 1) "Connecting to the ground unit (either UDP or TCP)" * 1) sending mavlink messages to OpenHD * 2) receiving mavlink messages from OpenHD - * 3) Handling the mavsdk "system discovery" for our OpenHD mavlink network, which is defined by + * 3) Handling the "system discovery" for our OpenHD mavlink network, which is defined by * a) OHD Air unit (own sys id) * b) OHD Ground unit (own sys id) * c) Optional FC connected to the air unit @@ -32,8 +34,7 @@ class MavlinkTelemetry : public QObject public: MavlinkTelemetry(QObject *parent = nullptr); static MavlinkTelemetry& instance(); - // Called in main.cpp such that we can call the couple of Q_INVOCABLE methods - static void register_for_qml(QQmlContext* qml_context); + void start(); /** * Send a message to the OHD ground unit. If no connection has been established (yet), this should return immediately. * The message can be aimed at either the OHD ground unit, the OHD air unit (forwarded by OpenHD) or the FC connected to the @@ -42,66 +43,50 @@ class MavlinkTelemetry : public QObject * @return true on success (this does not mean the message was received, but rather the message was sent out via the lossy connection) */ bool sendMessage(mavlink_message_t msg); + struct FCMavId{ + int comp_id; + int sys_id; + }; + // Returns default FC sys / comp id until FC is successfully discovered. + FCMavId get_fc_mav_id(); +public: + // ping all the systems (using timesync, since "ping" is deprecated) + Q_INVOKABLE void ping_all_systems(); + // re-apply all FC telemetry rate(s) + Q_INVOKABLE void re_apply_rates(); + // Switch from UDP to TCP + Q_INVOKABLE void add_tcp_connection_handler(QString ip); + // Back to udp + Q_INVOKABLE void enable_udp(); +public: // A couple of stats exposed as QT properties L_RO_PROP(int,telemetry_pps_in,set_telemetry_pps_in,-1) L_RO_PROP(int,telemetry_bps_in,set_telemetry_bps_in,-1) + // private: // We follow the same practice as QGrouncontroll: Listen for incoming data on a specific UDP port, // -> as soon as we got the first packet, we know the address to send data to for bidirectional communication static constexpr auto QOPENHD_GROUND_CLIENT_UDP_PORT_IN=14550; - // change requires restart, udp is used by default (not tcp) - bool dev_use_tcp=false; - std::string dev_tcp_server_ip="0.0.0.0"; - // workaround systems discovery is not thread safe - std::mutex systems_mutex; - int mavsdk_already_known_systems=0; - std::shared_ptr mavsdk=nullptr; - std::shared_ptr systemOhdGround=nullptr; - std::shared_ptr systemOhdAir=nullptr; - // MAVSDK is a bit stupid in this direction - passtrough(s) are for each system, but if there are multiple - // system(s) behind a connection, this pattern is completely broken. - // Normally, this passtrough is for the ground station - since we normally talk to both air and fc via the ground - // However, if there is no ground, we create the passtrough from the air or FC system too. - // This way one can also connect qopenhd to the FC without air / ground running and/or to the air unit without ground. - std::shared_ptr passtroughOhdGround=nullptr; - // called by mavsdk whenever a new system is detected - void onNewSystem(std::shared_ptr system); - // Called every time we get a mavlink message (from any system). Intended to be used for message types that don't - // work with mavsdk / their subscription based pattern. - void onProcessMavlinkMessage(mavlink_message_t msg); - // The mavsdk tcp connect does block, we therefore need to do it in its own thread - // (not block the UI thread) - void tcp_only_establish_connection(); - std::unique_ptr m_tcp_connect_thread= nullptr; + static constexpr auto QOPENHD_OPENHD_GROUND_TCP_SERVER_PORT=5760; + // Called every time we get a mavlink message (from any system). + void process_mavlink_message(const mavlink_message_t& msg); + void process_broadcast_message_openhd_air(const mavlink_message_t& msg); + void process_broadcast_message_openhd_gnd(const mavlink_message_t& msg); + void process_broadcast_message_fc(const mavlink_message_t& msg); + // timesync is handled extra independently + void process_message_timesync(const mavlink_message_t &msg); +private: + std::unique_ptr m_udp_connection=nullptr; + std::unique_ptr m_tcp_connection=nullptr; + int64_t m_last_timesync_out_us=0; + bool m_fc_found=false; + int m_fc_sys_id=-1; + int m_fc_comp_id=-1; + // For calculating input pps / bps int64_t m_tele_received_bytes=0; int64_t m_tele_received_packets=0; BitrateCalculator2 m_tele_bitrate_in; PacketsPerSecondCalculator m_tele_pps_in; -public: - // ping all the systems (using timesync, since "ping" is deprecated) - Q_INVOKABLE void ping_all_systems(); - // request the OpenHD version, both OpenHD air and ground unit will respond to that message. - Q_INVOKABLE void request_openhd_version(); - // send a command, to all connected systems - // doesn't reatransmitt - bool send_command_long_oneshot(const mavlink_command_long_t& command); -private: - int pingSequenceNumber=0; - int64_t lastTimeSyncOut=0; -private: - std::chrono::steady_clock::time_point m_last_time_version_requested=std::chrono::steady_clock::now(); -public: - // freq_bands: - // 0: 2.4G and 5.8G - // 1: 2.4G only - // 2: 5.8G only - // similar for channel widths - Q_INVOKABLE bool ohd_gnd_request_channel_scan(int freq_bands,int channel_widths); -private: - std::unique_ptr m_msg_interval_helper=nullptr; - void process_check_for_data_rates(const mavlink_message_t &msg); -public: - Q_INVOKABLE void re_apply_rates(); }; #endif // OHDMAVLINKCONNECTION_H diff --git a/app/telemetry/action/README.md b/app/telemetry/action/README.md new file mode 100644 index 000000000..9e1928b54 --- /dev/null +++ b/app/telemetry/action/README.md @@ -0,0 +1,4 @@ +Here we have code where qopenhd needs to actively talk to a mavlink system, +aka the FC, the openhd air unit or the openhd ground unit. + +(additionally to param / settings, which are in a different directory due to their complexity) diff --git a/app/telemetry/action/create_cmd_helper.hpp b/app/telemetry/action/create_cmd_helper.hpp new file mode 100644 index 000000000..b383a45df --- /dev/null +++ b/app/telemetry/action/create_cmd_helper.hpp @@ -0,0 +1,73 @@ +#ifndef CMD_HELPER_H +#define CMD_HELPER_H + +#include "../util/mavlink_include.h" + +// Here we have various util methods to create mavlink_command_long_t commands +namespace cmd::helper{ + +static mavlink_command_long_t create_cmd_reboot(int target_sysid,int target_compid,bool reboot){ + mavlink_command_long_t cmd{}; + cmd.target_system=target_sysid; + cmd.target_component=target_compid; + cmd.command=MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN; + cmd.param1=0; + cmd.param2=(reboot ? 1 : 2); + return cmd; +} + +static mavlink_command_long_t create_cmd_request_message(int target_sysid,int target_compid,int message_id){ + mavlink_command_long_t command{}; + command.command=MAV_CMD_REQUEST_MESSAGE; + command.target_system=target_sysid; + command.target_component=target_compid; + command.param1=static_cast(message_id); + return command; +} + +static mavlink_command_long_t create_cmd_request_openhd_version(int target_sysid,int target_compid){ + return create_cmd_request_message(target_sysid,target_compid,MAVLINK_MSG_ID_OPENHD_VERSION_MESSAGE); +} + +static mavlink_command_long_t create_cmd_arm(int fc_sys_id,int fc_comp_id,bool arm){ + mavlink_command_long_t cmd{}; + cmd.command=MAV_CMD_COMPONENT_ARM_DISARM; + cmd.target_system=fc_sys_id; + cmd.target_component=fc_comp_id; + //cmd.confirmation=false; + cmd.param1=arm ? 1 : 0; + cmd.param2=0; // do not force + return cmd; +} + +static mavlink_command_long_t create_cmd_do_set_flight_mode(int fc_sys_id,int fc_comp_id,int mode){ + mavlink_command_long_t cmd{}; + cmd.command = MAV_CMD_DO_SET_MODE; + + cmd.target_system= fc_sys_id; + cmd.target_component=fc_comp_id; + + cmd.param1=MAV_MODE_FLAG_CUSTOM_MODE_ENABLED; + cmd.param2=mode; + cmd.param3=0; + cmd.param4=0; + cmd.param5=0; + cmd.param6=0; + cmd.param7=0; + return cmd; +} + +static mavlink_command_long_t create_cmd_set_msg_interval(int target_system,int target_component,int msg_type,int interval_us){ + mavlink_command_long_t command{}; + command.target_system=target_system; + command.target_component=target_component; + command.command=MAV_CMD_SET_MESSAGE_INTERVAL; + command.confirmation=0; + command.param1=msg_type; + command.param2=interval_us; + return command; +} + +} + +#endif // CMD_HELPER_H diff --git a/app/telemetry/action/fcaction.cpp b/app/telemetry/action/fcaction.cpp new file mode 100644 index 000000000..140878d87 --- /dev/null +++ b/app/telemetry/action/fcaction.cpp @@ -0,0 +1,183 @@ +#include "fcaction.h" +#include "qdebug.h" + +#include "../../logging/hudlogmessagesmodel.h" +#include "../MavlinkTelemetry.h" +#include "impl/cmdsender.h" + +#include "create_cmd_helper.hpp" +#include "../models/fcmavlinksystem.h" +#include "util/mavlink_enum_to_string.h" + +FCAction::FCAction(QObject *parent) + : QObject{parent} +{ + +} + +FCAction &FCAction::instance() +{ + static FCAction instance; + return instance; +} + +void FCAction::arm_fc_async(bool arm) +{ + if(!FCMavlinkSystem::instance().is_alive()){ + HUDLogMessagesModel::instance().add_message_info("FC not alive"); + return; + } + const auto fc_id=MavlinkTelemetry::instance().get_fc_mav_id(); + const auto command=cmd::helper::create_cmd_arm(fc_id.sys_id,fc_id.comp_id,arm); + auto cb=[this,arm](CmdSender::RunCommandResult result){ + if(!result.opt_ack.has_value()){ + HUDLogMessagesModel::instance().add_message_info(arm ? "ARM - FC not reachable" : "DISARM - FC not reachable"); + }else if(result.is_accepted()){ + HUDLogMessagesModel::instance().add_message_info(arm ? "ARMED FC":"DISARMED FC"); + }else{ + HUDLogMessagesModel::instance().add_message_info(arm ? "ARM not possible":"DISARM not possible"); + } + }; + CmdSender::instance().send_command_long_async(command,cb); +} + +void FCAction::flight_mode_cmd_async(long cmd_msg) { + if(cmd_msg<0){ + // We get the flight mode command from qml, something is wrong with it + std::stringstream ss; + ss<<"Invalid FM "< create_fm_mapping(){ + std::map ret; + ret["RTL"]=XFlightMode{COPTER_MODE_RTL,PLANE_MODE_RTL}; + ret["STABILIZE"]=XFlightMode{COPTER_MODE_STABILIZE,PLANE_MODE_STABILIZE}; + ret["LOITER"]=XFlightMode{COPTER_MODE_LOITER,PLANE_MODE_LOITER}; + ret["CIRCLE"]=XFlightMode{COPTER_MODE_CIRCLE,PLANE_MODE_CIRCLE}; + ret["AUTO"]=XFlightMode{COPTER_MODE_AUTO,PLANE_MODE_AUTO}; + ret["AUTOTUNE"]=XFlightMode{COPTER_MODE_AUTOTUNE,PLANE_MODE_AUTOTUNE}; + ret["MANUAL"]=XFlightMode{-1,PLANE_MODE_MANUAL}; + ret["FBWA"]=XFlightMode{-1,PLANE_MODE_FLY_BY_WIRE_A}; + ret["FBWB"]=XFlightMode{-1,PLANE_MODE_FLY_BY_WIRE_B}; + ret["QSTABILIZE"]=XFlightMode{-1,PLANE_MODE_QSTABILIZE}; + ret["QHOVER"]=XFlightMode{-1,PLANE_MODE_QHOVER}; + ret["QLOITER"]=XFlightMode{-1,PLANE_MODE_QLOITER}; + ret["QLAND"]=XFlightMode{-1,PLANE_MODE_QLAND}; + ret["QRTL"]=XFlightMode{-1,PLANE_MODE_QRTL}; + ret["ALT_HOLD"]=XFlightMode{COPTER_MODE_ALT_HOLD,-1}; + ret["POS_HOLD"]=XFlightMode{COPTER_MODE_POSHOLD,-1}; + ret["ACRO"]=XFlightMode{COPTER_MODE_ACRO,PLANE_MODE_ACRO}; + + ret["GUIDED"]=XFlightMode{COPTER_MODE_GUIDED,PLANE_MODE_GUIDED}; + // (weird) copter only mode(s) + ret["LAND"]=XFlightMode{COPTER_MODE_LAND,-1}; + ret["SMART_RTL"]=XFlightMode{COPTER_MODE_SMART_RTL,-1}; + ret["ZIGZAG"]=XFlightMode{COPTER_MODE_ZIGZAG,-1}; + ret["AUTO_RTL"]=XFlightMode{COPTER_MODE_AUTO_RTL,-1}; + // (weird) plane only mode(s) + ret["CRUISE"]=XFlightMode{-1,PLANE_MODE_CRUISE}; + ret["TAKEOFF"]=XFlightMode{-1,PLANE_MODE_TAKEOFF}; + //ret[""]=XFlightMode{COPTER_MODE_,PLANE_MODE_}; + + return ret; +} + +static int flight_mode_string_to_int(const std::string& flight_mode,const int mav_type){ + if(mav_type<0){ + // mav type not yet known + return -1; + } + const auto fm_map=create_fm_mapping(); + if(fm_map.find(flight_mode)!=fm_map.end()){ + // mapped + const XFlightMode& x_fm=fm_map.at(flight_mode); + if(qopenhd::flight_mode_is_copter((MAV_TYPE)mav_type)){ + return x_fm.id_copter; + }else if(qopenhd::flight_mode_is_plane((MAV_TYPE)mav_type)){ + return x_fm.id_plane; + }else{ + qDebug()<<"FM mapped not to this mav type:"<<(int)mav_type; + } + }else{ + qDebug()<<"FM unmapped:"<=0){ + flight_mode_cmd_async(flight_mode_type); + }else{ + HUDLogMessagesModel::instance().add_message_warning("Invalid flight mode"); + } +} + +bool FCAction::has_mapping(QString flight_mode) +{ + int mapping=flight_mode_string_to_int(flight_mode.toStdString(),m_ardupilot_mav_type); + if(mapping>=0)return true; + return false; +} + +void FCAction::request_home_position_from_fc() +{ + const auto fc_id=MavlinkTelemetry::instance().get_fc_mav_id(); + const auto command=cmd::helper::create_cmd_request_message(fc_id.sys_id,fc_id.comp_id,MAVLINK_MSG_ID_HOME_POSITION); + const auto result=CmdSender::instance().send_command_long_blocking(command); + if(result==CmdSender::CMD_SUCCESS){ + HUDLogMessagesModel::instance().add_message_info("Request home success"); + }else{ + HUDLogMessagesModel::instance().add_message_info("Request home failure"); + } +} + +bool FCAction::send_command_reboot(bool reboot) +{ + const auto fc_id=MavlinkTelemetry::instance().get_fc_mav_id(); + auto command=cmd::helper::create_cmd_reboot(fc_id.sys_id,fc_id.comp_id,reboot); + const auto res=CmdSender::instance().send_command_long_blocking(command); + return res==CmdSender::Result::CMD_SUCCESS; +} diff --git a/app/telemetry/action/fcaction.h b/app/telemetry/action/fcaction.h new file mode 100644 index 000000000..430a94098 --- /dev/null +++ b/app/telemetry/action/fcaction.h @@ -0,0 +1,52 @@ +#ifndef FCACTION_H +#define FCACTION_H + +#include +#include + +#include "../util/mavlink_include.h" +#include "../../../lib/lqtutils_master/lqtutils_prop.h" + +/** + * This is the one and only class from which messages / actions can be sent to the FC. + * THE REST IS BROADCAST ! + */ +class FCAction : public QObject +{ + Q_OBJECT +public: + explicit FCAction(QObject *parent = nullptr); + static FCAction& instance(); +public: + L_RO_PROP(int,ardupilot_mav_type,set_ardupilot_mav_type,-1); +public: + // WARNING: Do not call any non-async send command methods from the same thread that is parsing the mavlink messages ! + // + // Try to change the arming state. + // The result (success/failure) is logged in the HUD once completed + Q_INVOKABLE void arm_fc_async(bool arm=false); + + // Sends a command to change the flight mode. Note that this is more complicated than it sounds at first, + // since copter and plane for example do have different flight mode enums. + // This function is async to not block the calling UI - the result is logged to the HUDLogMessageModel + // also, this method only allows one flight mode change queued up at a time + Q_INVOKABLE void flight_mode_cmd_async(long cmd_msg); + // FUCKING ANNOYING / DANGEROUS: + // The mapping of flight modes is completely different for copter/plane/... + // If we haven't mapped a (unique) flight mode string to the appropriate COPTER_, PLANE_ command + // we just log a warning and return. + Q_INVOKABLE void flight_mode_cmd_async_string(QString flight_mode); + // Returns true if we have a mapping for this flight mode (string id) + // taking the current mav type (copter, plane, ..) int account. + // This method is called from qml to only show FLightModeSlider elements that will work for the given mav type + Q_INVOKABLE bool has_mapping(QString flight_mode); + + // Some FC stop sending home position when armed, re-request the home position + Q_INVOKABLE void request_home_position_from_fc(); + + Q_INVOKABLE bool send_command_reboot(bool reboot); +private: + std::atomic m_has_currently_runnning_flight_mode_change=false; +}; + +#endif // FCACTION_H diff --git a/app/telemetry/action/fcmissionhandler.cpp b/app/telemetry/action/fcmissionhandler.cpp new file mode 100644 index 000000000..709a99260 --- /dev/null +++ b/app/telemetry/action/fcmissionhandler.cpp @@ -0,0 +1,226 @@ +#include "fcmissionhandler.h" +#include "../util/qopenhdmavlinkhelper.hpp" +#include "../util/telemetryutil.hpp" +#include "../MavlinkTelemetry.h" + +#include "../models/fcmavlinkmissionitemsmodel.h" + +FCMissionHandler::FCMissionHandler(QObject *parent): QObject(parent) +{ + m_mission_items.reserve(MAX_N_MISSION_ITEMS); + m_missing_items.reserve(MAX_N_MISSION_ITEMS); +} + +FCMissionHandler &FCMissionHandler::instance() +{ + static FCMissionHandler instance; + return instance; +} + +bool FCMissionHandler::process_message(const mavlink_message_t &msg) +{ + bool consumed=false; + switch(msg.msgid){ + case MAVLINK_MSG_ID_MISSION_CURRENT:{ + // https://mavlink.io/en/messages/common.html#MISSION_CURRENT + mavlink_mission_current_t mission_current; + mavlink_msg_mission_current_decode(&msg,&mission_current); + update_mission_current(mission_current); + consumed=true; + break; + } + case MAVLINK_MSG_ID_MISSION_COUNT:{ + //qDebug()<<"Got MAVLINK_MSG_ID_MISSION_COUNT"; + // https://mavlink.io/en/messages/common.html#MISSION_COUNT + mavlink_mission_count_t mission; + mavlink_msg_mission_count_decode(&msg,&mission); + update_mission_count(mission); + consumed=true; + break; + } + case MAVLINK_MSG_ID_MISSION_ITEM_INT:{ + mavlink_mission_item_int_t item; + mavlink_msg_mission_item_int_decode(&msg, &item); + update_mission(item); + consumed=true; + break; + } + } + return consumed; +} + +static mavlink_mission_request_list_t create_request_mission_count(int fc_sys_id,int fc_comp_id){ + mavlink_mission_request_list_t command{}; + command.mission_type=MAV_MISSION_TYPE_MISSION; + command.target_system=fc_sys_id; + command.target_component=fc_comp_id; + return command; +} + +static mavlink_message_t create_request_mission_count_msg(int fc_sys_id,int fc_comp_id){ + const auto tmp=create_request_mission_count(fc_sys_id,fc_comp_id); + mavlink_message_t message; + const auto sys_id=QOpenHDMavlinkHelper::get_own_sys_id(); + const auto comp_id=QOpenHDMavlinkHelper::get_own_comp_id(); + mavlink_msg_mission_request_list_encode(sys_id,comp_id,&message,&tmp); + return message; +} +static mavlink_message_t create_request_mission_msg(int fc_sys_id,int fc_comp_id,int sequence){ + mavlink_mission_request_int_t request{}; + request.mission_type=MAV_MISSION_TYPE_MISSION; + request.target_system=fc_sys_id; + request.target_component=fc_comp_id; + request.seq=sequence; + mavlink_message_t message; + const auto sys_id=QOpenHDMavlinkHelper::get_own_sys_id(); + const auto comp_id=QOpenHDMavlinkHelper::get_own_comp_id(); + mavlink_msg_mission_request_int_encode(sys_id,comp_id,&message,&request); + return message; +} + +void FCMissionHandler::opt_send_messages() +{ + std::lock_guard lock(m_mutex); + if(!m_has_mission_count){ + set_current_status("Requesting"); + const auto elapsed=std::chrono::steady_clock::now()-m_last_count_request; + if(elapsed>std::chrono::seconds(1)){ + m_last_count_request=std::chrono::steady_clock::now(); + const auto fc_id=MavlinkTelemetry::instance().get_fc_mav_id(); + auto message=create_request_mission_count_msg(fc_id.sys_id,fc_id.comp_id); + MavlinkTelemetry::instance().sendMessage(message); + //qDebug()<<"Requested"; + } + return; + } + // check if we are missing missions + if(m_missing_items.empty()){ + std::stringstream ss; + ss<<"Done, total:"<std::chrono::seconds(1)){ + m_last_item_request=std::chrono::steady_clock::now(); + const auto missing_missions=m_missing_items.size(); + //qDebug()<<"Missions missing: "< lock(m_mutex); + m_mission_items.resize(0); + m_missing_items.resize(0); + m_has_mission_count=false; + FCMavlinkMissionItemsModel::instance().p_initialize(0); +} + +void FCMissionHandler::update_mission_count(const mavlink_mission_count_t& mission_count) +{ + //qDebug()<<"Got MAVLINK_MSG_ID_MISSION_COUNT total:"< lock(m_mutex); + if(count==m_mission_items.size()){ + qDebug()<<"Same size"; + if(count==0){ + m_has_mission_count=true; + } + return; + } + qDebug()<<"Mission count changed from "<(mission_item.x)* 1e-7; + const double lon=static_cast(mission_item.y)* 1e-7; + /*if(lat==0.0 || lon==0.0){ + qDebug()<<"invalid mission item - invalid lat/lon"< lock(m_mutex); + if(! (mission_seq>=0 && mission_seq lock(m_mutex); + if(!(current_mission>=0 && current_mission<=m_mission_items.size())){ + qDebug()<<"Invalid current mission "< +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../../lib/lqtutils_master/lqtutils_prop.h" + +#include "../util/mavlink_include.h" + +/** + * Simple class to (semi-reliably) get all the mission waypoints from the FC and more. + */ +class FCMissionHandler: public QObject{ + Q_OBJECT +public: + explicit FCMissionHandler(QObject *parent = nullptr); + // singleton for accessing the model from c++ + static FCMissionHandler& instance(); + // Returns true if the message was "consumed" and does not need to be processed by anybody else, e.g. the main fc model + bool process_message(const mavlink_message_t& msg); + // Should be called every time a msg from the FC is received - this class takes care to not pollute the link + void opt_send_messages(); + Q_INVOKABLE void resync(); +public: + // We expose some variables as read-only for the OSD+ + // NOTE: the description "waypoints" is not exactly accurate, left in for now due to legacy reasons though + L_RO_PROP(int,mission_waypoints_current_total,set_mission_waypoints_current_total,-1); + L_RO_PROP(int,mission_waypoints_current,set_mission_waypoints_current,-1); + // Current mission type, verbose as string for the user + L_RO_PROP(QString,mission_current_type,set_mission_current_type,"Unknown"); + // For the user to + L_RO_PROP(QString,current_status,set_current_status,"N/A"); +private: + struct MItem{ + int mission_index=0; + double latitude; + double longitude; + double altitude_meter=0; + // Set to true once we got an update for this mission item + bool updated=false; + }; + std::mutex m_mutex; + std::vector m_mission_items; + std::vector m_missing_items; + static constexpr auto MAX_N_MISSION_ITEMS=200; + std::chrono::steady_clock::time_point m_last_count_request=std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point m_last_item_request=std::chrono::steady_clock::now(); + bool m_has_mission_count=false; + void update_mission_count(const mavlink_mission_count_t& mission_count); + void update_mission(const mavlink_mission_item_int_t& item); + void update_mission_current(const mavlink_mission_current_t& mission_current); + // Needs to be called with lock locked ! + void recalculate_missing(); +}; + +#endif // FCMISSIONHANDLER_H diff --git a/app/telemetry/action/fcmsgintervalhandler.cpp b/app/telemetry/action/fcmsgintervalhandler.cpp new file mode 100644 index 000000000..33df6b72e --- /dev/null +++ b/app/telemetry/action/fcmsgintervalhandler.cpp @@ -0,0 +1,76 @@ +#include "fcmsgintervalhandler.h" + +#include "create_cmd_helper.hpp" + +#include "../../logging/hudlogmessagesmodel.h" +#include "impl/cmdsender.h" +#include "../MavlinkTelemetry.h" + +FCMsgIntervalHandler &FCMsgIntervalHandler::instance() +{ + static FCMsgIntervalHandler instance; + return instance; +} + +void FCMsgIntervalHandler::opt_send_messages(){ + QSettings settings; + const bool set_mavlink_message_rates = settings.value("set_mavlink_message_rates",true).toBool(); + if(!set_mavlink_message_rates){ + return; + } + std::lock_guard lock(m_mutex); + if(m_ready_for_next_command){ + // last command was set successfully + if(m_next_rate_index lock(m_mutex); + if(result.is_accepted()){ + qDebug()<<"Succesfully set rate number"< lock(m_mutex); + // Sets the state to 0, again set all the rate(s) again - + // Needed if the user changes any rate + m_next_rate_index=0; + m_ready_for_next_command=true; +} + + diff --git a/app/telemetry/action/fcmsgintervalhandler.h b/app/telemetry/action/fcmsgintervalhandler.h new file mode 100644 index 000000000..b527872e5 --- /dev/null +++ b/app/telemetry/action/fcmsgintervalhandler.h @@ -0,0 +1,62 @@ +#ifndef FCMSGINTERVALHANDLER_H +#define FCMSGINTERVALHANDLER_H + +#include +#include +#include +#include +#include + +#include "../util/mavlink_include.h" + +/** + * We need to manually request / set the message interval(s) for specific messages on ardupilot + * (It doesn't send them by default). + * The way this works is not ideal in regards to reliability - but I implemented a similar pattern like QGroundControl here. + * Basically, we enqueue one message intervall command after each other, and stop in case one of them times out - + * but we use a really high retransmit intervall, so if the commands time out, we should probably stop. + */ +class FCMsgIntervalHandler{ +public: + static FCMsgIntervalHandler& instance(); + // should be called every time a message from the FC is received,this handler takes care of not polluting the link. + void opt_send_messages(); + // re-apply all rate(s) + void restart(); +private: + struct MessageInterval{ + // for which message + int msg_id; + // interval in hertz + int interval_hz; + }; + static constexpr int RATE_LOW=1; // Once per second + static constexpr int RATE_MEDIUM=5; // 5 times per second + // Intervals are in Hertz + const std::vector m_intervals={ + MessageInterval{MAVLINK_MSG_ID_SYS_STATUS,2}, // battery and more + MessageInterval{MAVLINK_MSG_ID_SYSTEM_TIME,1}, + MessageInterval{MAVLINK_MSG_ID_GPS_RAW_INT,1}, // we get hdop, vdop, usw from this - not lat /long though (they are from global position int,aka fused) + MessageInterval{MAVLINK_MSG_ID_ATTITUDE,30}, + MessageInterval{MAVLINK_MSG_ID_GLOBAL_POSITION_INT,2}, + MessageInterval{MAVLINK_MSG_ID_RC_CHANNELS,2}, + //MessageInterval{MAVLINK_MSG_ID_GPS_GLOBAL_ORIGIN,0}, + MessageInterval{MAVLINK_MSG_ID_VFR_HUD,2}, //(air) speed, climb, ... + MessageInterval{MAVLINK_MSG_ID_BATTERY_STATUS,1}, + MessageInterval{MAVLINK_MSG_ID_HOME_POSITION,1}, + MessageInterval{ MAVLINK_MSG_ID_WIND,1}, + MessageInterval{MAVLINK_MSG_ID_AOA_SSA,1}, + MessageInterval{MAVLINK_MSG_ID_SCALED_PRESSURE,1}, + MessageInterval{MAVLINK_MSG_ID_MISSION_CURRENT,1}, + //MessageInterval{MAVLINK_MSG_ID_MISSION_COUNT,1}, Doesn't work broadcast + // NOTE: We cannot set broadcast on this one ! + //MessageInterval{MAVLINK_MSG_ID_MISSION_ITEM_INT,1}, + //MessageInterval{0,0}, + //MessageInterval{0,0}, + }; + std::mutex m_mutex; + bool m_ready_for_next_command=true; + int m_next_rate_index=0; +}; + +#endif // FCMSGINTERVALHANDLER_H diff --git a/app/telemetry/action/impl/README.md b/app/telemetry/action/impl/README.md new file mode 100644 index 000000000..bfed1cb3e --- /dev/null +++ b/app/telemetry/action/impl/README.md @@ -0,0 +1 @@ +no qt ui code is allowed in here ! diff --git a/app/telemetry/action/impl/cmdsender.cpp b/app/telemetry/action/impl/cmdsender.cpp new file mode 100644 index 000000000..5647385a1 --- /dev/null +++ b/app/telemetry/action/impl/cmdsender.cpp @@ -0,0 +1,190 @@ +#include "cmdsender.h" + +#include + +#include "../../util/qopenhdmavlinkhelper.hpp" +#include "../../MavlinkTelemetry.h" +#include "util/mavlink_enum_to_string.h" +#include + +CmdSender::CmdSender() +{ + m_timeout_thread=std::make_unique([this](){ + loop_timeout(); + }); +} + +CmdSender::~CmdSender() +{ + m_timeout_thread_run=false; + m_timeout_thread->join(); + m_timeout_thread=nullptr; +} + +CmdSender &CmdSender::instance() +{ + static CmdSender instance; + return instance; +} + +bool CmdSender::process_message(const mavlink_message_t &msg) +{ + if(msg.msgid==MAVLINK_MSG_ID_COMMAND_ACK){ + mavlink_command_ack_t ack; + mavlink_msg_command_ack_decode(&msg,&ack); + const auto self_sysid=QOpenHDMavlinkHelper::get_own_sys_id(); + const auto self_compid=QOpenHDMavlinkHelper::get_own_comp_id(); + if(ack.target_system==self_sysid && ack.target_component==self_compid){ + return handle_cmd_ack(ack); + }else{ + qDebug()<<"Got cmd ack for someone else "<<(int)ack.target_system<<":"<<(int)ack.target_component; + return false; + } + } + return false; +} + + +bool CmdSender::send_command_long_async(mavlink_command_long_t cmd, RESULT_CB result_cb,std::chrono::milliseconds retransmit_delay,int n_wanted_retransmissions) +{ + assert(n_wanted_retransmissions>=1); + assert(retransmit_delay.count()>=10); + if(!result_cb){ + // the cb must not be nullptr + qDebug()<<"No result cb,using dummy"; + auto dummy_cb=[](RunCommandResult result){ + qDebug()< lock(m_mutex); + if(m_running_commands.size()>=MAX_N_SIMULTANOEUS_COMMANDS){ + std::stringstream ss; + ss<<"cannot enqueue, curr:"< prom; + auto fut = prom.get_future(); + auto cb=[&prom](RunCommandResult result){ + //qDebug()<> timed_out_commands{}; + { + std::lock_guard lock(m_mutex); + for (auto it=m_running_commands.begin(); it!=m_running_commands.end(); ++it){ + RunningCommand& running_cmd=*it; + const auto elapsed=std::chrono::steady_clock::now()-running_cmd.last_transmission; + if(elapsed>running_cmd.retransmit_delay){ + qDebug()<<"CMD Timeout"; + if(running_cmd.n_transmissions CmdSender::find_remove_running_command_threadsafe(int command_id) +{ + std::lock_guard lock(m_mutex); + for (auto it=m_running_commands.begin(); it!=m_running_commands.end(); ++it){ + const RunningCommand& cmd=*it; + if(cmd.cmd.command==command_id){ + RunningCommand tmp=cmd; + m_running_commands.erase(it); + return tmp; + } + } + return std::nullopt; +} + +void CmdSender::send_command(RunningCommand &cmd) +{ + send_mavlink_command_long(cmd.cmd); + cmd.last_transmission=std::chrono::steady_clock::now(); + cmd.n_transmissions++; + cmd.cmd.confirmation=cmd.n_transmissions; +} + +void CmdSender::send_mavlink_command_long(const mavlink_command_long_t &cmd) +{ + const auto self_sysid=QOpenHDMavlinkHelper::get_own_sys_id(); + const auto self_compid=QOpenHDMavlinkHelper::get_own_comp_id(); + mavlink_message_t msg{}; + mavlink_msg_command_long_encode(self_sysid,self_compid,&msg,&cmd); + MavlinkTelemetry::instance().sendMessage(msg); +} + +void CmdSender::loop_timeout() +{ + while(true){ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + handle_timeout(); + } +} + +std::string CmdSender::run_command_result_as_string(const RunCommandResult& res) +{ + std::stringstream ss; + if(!res.opt_ack.has_value()){ + ss<<"No ack after "< +#include +#include +#include +#include +#include + +/** + * Simple, threadsafe class for sending mavlink command (long) over a lossy link. + */ +class CmdSender +{ +public: + CmdSender(); + ~CmdSender(); + static CmdSender& instance(); + /** + * returns true if this message has been consumed, false otherwise. + */ + bool process_message(const mavlink_message_t& msg); + + struct RunCommandResult{ + // Response from the recipient, if there is any (otherwise, the message got lost on each re-transmit) + std::optional opt_ack; + // How often this command was transmitted until success / failure + int n_transmissions=-1; + bool is_accepted(){ + return opt_ack.has_value() && opt_ack.value().result==MAV_RESULT_ACCEPTED; + } + }; + typedef std::function RESULT_CB; + /** + * enqueues a commmand to be sent (with retransmissions). If the queue has reached its limit, false is returned and the result cb is not called. + * Otherwise, the result cb is quaranteed to be called after either + * an ack (success/denied) from the recipient is received or the command timed out after n retransmissions is exhausted (no response from the recipient) + */ + bool send_command_long_async(const mavlink_command_long_t cmd,RESULT_CB result,std::chrono::milliseconds retransmit_delay=std::chrono::milliseconds(500),int n_wanted_retransmissions=3); + + enum Result{ + QUEUE_FULL, // cannot enqueue command, max size reached + NO_RESPONSE, // no response from recipient + CMD_DENIED, // response from recipient, but negative (some error code) + CMD_SUCCESS // positive response from recipient + }; + /** + * This blocks the calling thread for up to X second (n retransmissions * timeout) + * But usefully in some cases. + * DO NOT CALL THIS FROM THE TELEMETRY RECEIVE THREAD ! + */ + Result send_command_long_blocking(const mavlink_command_long_t cmd,std::chrono::milliseconds retransmit_delay=std::chrono::milliseconds(500),int n_wanted_retransmissions=3); +private: + struct RunningCommand{ + mavlink_command_long_t cmd; + RESULT_CB cb; + // How often this command should be retransmitted + int n_wanted_retransmissions; + // Delay between each retransmission + std::chrono::milliseconds retransmit_delay; + // How often this command has already been sent + int n_transmissions=0; + // last time it was sent + std::chrono::steady_clock::time_point last_transmission=std::chrono::steady_clock::now(); + }; + std::mutex m_mutex; + std::list m_running_commands; + bool m_timeout_thread_run=true; + std::unique_ptr m_timeout_thread; + static constexpr auto MAX_N_SIMULTANOEUS_COMMANDS=5; +private: + // checks if we have a running command this ack is for - if yes, removes it and calls the result cb + bool handle_cmd_ack(const mavlink_command_ack_t& ack); + // searches for a command refering to this command id + // if found, remove the command and return it. + // ootherwise, return nullopt + std::optional find_remove_running_command_threadsafe(int command_id); + // Send command via link, increase (re)-transmit counter + void send_command(RunningCommand& cmd); + // util + void send_mavlink_command_long(const mavlink_command_long_t& cmd); + void loop_timeout(); + // Regulary called by the timeout thread - check if any running command timed out, + // and either retransmit or remove and call result cb with failure state + void handle_timeout(); + // util + static std::string run_command_result_as_string(const RunCommandResult& res); +}; + +#endif // CMDSENDER_H diff --git a/app/telemetry/action/impl/xparam.cpp b/app/telemetry/action/impl/xparam.cpp new file mode 100644 index 000000000..712989ae3 --- /dev/null +++ b/app/telemetry/action/impl/xparam.cpp @@ -0,0 +1,465 @@ +#include "xparam.h" +#include "util/qopenhdmavlinkhelper.hpp" + +#include +#include +#include + +#include +#include "../../MavlinkTelemetry.h" + +XParam::XParam() +{ + m_timeout_thread=std::make_unique([this](){ + loop_timeout(); + }); +} + +XParam::~XParam() +{ + m_timeout_thread_run=false; + m_timeout_thread->join(); + m_timeout_thread=nullptr; +} + +XParam &XParam::instance() +{ + static XParam instance; + return instance; +} + +bool XParam::process_message(const mavlink_message_t &msg) +{ + if(msg.msgid==MAVLINK_MSG_ID_PARAM_EXT_ACK){ + mavlink_param_ext_ack_t ack; + mavlink_msg_param_ext_ack_decode(&msg,&ack); + return handle_param_ext_ack(ack,msg.sysid,msg.compid); + }else if(msg.msgid==MAVLINK_MSG_ID_PARAM_EXT_VALUE){ + mavlink_param_ext_value_t response; + mavlink_msg_param_ext_value_decode(&msg,&response); + return handle_param_ext_value(response,msg.sysid,msg.compid); + } + return false; +} + +bool XParam::try_set_param_async(const mavlink_param_ext_set_t cmd, SET_PARAM_RESULT_CB result_cb,PROGRESS_CB opt_progress_cb, std::chrono::milliseconds retransmit_delay, int n_wanted_retransmissions) +{ + assert(n_wanted_retransmissions>=1); + assert(retransmit_delay.count()>=10); + if(!result_cb){ + // the cb must not be nullptr + qDebug()<<"No result cb,using dummy"; + auto dummy_cb=[](SetParamResult result){ + //qDebug()< lock(m_mutex); + if(m_running_commands.size()>=MAX_N_SIMULTANOEUS_COMMANDS){ + std::stringstream ss; + ss<<"cannot enqueue, curr:"< prom; + auto fut = prom.get_future(); + auto cb=[&prom](SetParamResult result){ + prom.set_value(result.is_accepted()); + }; + if(!try_set_param_async(cmd,cb,nullptr,retransmit_delay,n_wanted_retransmission)){ + qDebug()<<"Cannot enqueue param "; + return false; + } + return fut.get(); +} + +void XParam::try_get_param_all_async(const mavlink_param_ext_request_list_t cmd, GET_ALL_PARAM_RESULT_CB result_cb,PROGRESS_CB opt_progress_cb) +{ + if(!result_cb){ + // the cb must not be nullptr + qDebug()<<"No result cb,using dummy"; + auto dummy_cb=[](GetAllParamResult result){ + //qDebug()< lock(m_mutex); + RunningParamCmdGetAll running{cmd,result_cb,opt_progress_cb,std::chrono::milliseconds(3000),std::chrono::milliseconds(500),std::chrono::steady_clock::now(),{},10,0}; + m_running_get_all.push_back(running); + send_next_message_running_get_all(m_running_get_all.back()); +} + +std::optional> XParam::try_get_param_all_blocking(const int target_sysid, const int target_compid) +{ + std::promise>> prom; + auto fut = prom.get_future(); + auto cb=[&prom](GetAllParamResult result){ + if(result.success){ + prom.set_value(result.param_set); + }else{ + prom.set_value(std::nullopt); + } + }; + const auto command=create_cmd_get_all(target_sysid,target_compid); + try_get_param_all_async(command,cb); + return fut.get(); +} + +// allowed: up to 16 bytes, either including or excluding the 0-terminator +static void set_param_id(char* dest,const std::string& source){ + if(source.length()>16){ + qWarning("Invalid param name %s",source.c_str()); + } + const int len = source.length()>=16 ? 16 : source.length()+1; + std::memcpy(dest, source.c_str(),len); +} +static std::string get_param_id(const char* param_id){ + // The param_id field of the MAVLink struct has length 16 and can not be null terminated. + // Therefore, we make a 0 terminated copy first. + char param_id_long_enough[16 + 1] = {}; + std::memcpy(param_id_long_enough, param_id, 16); + return {param_id_long_enough}; +} +static void set_param_value_int(char* param_value,int value){ + std::memset(param_value,0,128); + int32_t tmp=value; + std::memcpy(param_value, &tmp, sizeof(tmp)); +} +static void set_param_value_string(char* param_value,const std::string& value){ + std::memset(param_value,0,128); + const int len = value.length()>=128 ? 128 : value.length()+1; + std::memcpy(param_value,value.c_str(),len); +} +static int get_param_value_int(const char* param_value){ + int32_t ret; + std::memcpy(&ret,param_value,sizeof(ret)); + return ret; +} +static std::string get_param_value_string(const char* param_value){ + char param_value_long_enough[128 + 1] = {}; + std::memcpy(param_value_long_enough, param_value,128); + return {param_value_long_enough}; +} + + +mavlink_param_ext_set_t XParam::create_cmd_set_int(int target_sysid, int target_compid, std::string param_name, int value) +{ + mavlink_param_ext_set_t cmd{}; + cmd.target_system=target_sysid; + cmd.target_component=target_compid; + set_param_id(cmd.param_id,param_name); + cmd.param_type=MAV_PARAM_EXT_TYPE_INT32; + set_param_value_int(cmd.param_value,value); + return cmd; +} + +mavlink_param_ext_set_t XParam::create_cmd_set_string(int target_sysid, int target_compid, std::string param_id, std::string value) +{ + mavlink_param_ext_set_t cmd{}; + cmd.target_system=target_sysid; + cmd.target_component=target_compid; + set_param_id(cmd.param_id,param_id); + cmd.param_type=MAV_PARAM_EXT_TYPE_CUSTOM; + set_param_value_string(cmd.param_value,value); + return cmd; +} + +mavlink_param_ext_request_list_t XParam::create_cmd_get_all(int target_sysid, int target_compid) +{ + mavlink_param_ext_request_list_t cmd{}; + cmd.target_system=target_sysid; + cmd.target_component=target_compid; + return cmd; +} + +std::vector XParam::parse_server_param_set(const std::vector ¶m_set) +{ + std::vector ret; + ret.reserve(param_set.size()); + for(int i=0;i valid_param_set; + for(auto& param:finished.server_param_set){ + assert(param.has_value()); + valid_param_set.push_back(param.value()); + } + GetAllParamResult result{true,valid_param_set}; + finished.cb(result); + return true; + } + return true; +} + + +std::optional XParam::find_remove_running_command_threadsafe(const mavlink_param_ext_ack_t &ack,int sender_sysid,int sender_compid) +{ + std::lock_guard lock(m_mutex); + for (auto it=m_running_commands.begin(); it!=m_running_commands.end(); ++it){ + const auto& running=*it; + const auto& cmd=running.cmd; + const auto safe_cmd_param_id=get_param_id(cmd.param_id); + const auto safe_ack_param_id=get_param_id(ack.param_id); + if(safe_cmd_param_id==safe_ack_param_id && cmd.param_type==ack.param_type && cmd.target_system==sender_sysid && cmd.target_component==sender_compid){ + RunningParamCmdSet tmp=*it; + m_running_commands.erase(it); + return tmp; + }else{ + //qDebug()<<"Self:"< XParam::find_remove_running_command_get_all_threadsafe(const mavlink_param_ext_value_t &response, int sender_sysid, int sender_compid) +{ + std::lock_guard lock(m_mutex); + for (auto it=m_running_get_all.begin(); it!=m_running_get_all.end(); ++it){ + RunningParamCmdGetAll& running=*it; + if(running.base_cmd.target_system==sender_sysid && running.base_cmd.target_component==sender_compid){ + // The response fromt the server can be cosumed by a running "Get all params" action + if(response.param_count<=0){ + qWarning()<<"Invalid param count"; + return std::nullopt; + } + if(response.param_index>=response.param_count){ + qWarning()<<"Invalid param index"; + return std::nullopt; + } + if(running.server_param_set.empty()){ + // Size is not yet known + running.server_param_set.resize(response.param_count); + running.server_param_set[response.param_index]=response; + update_progress_get_all(running); + }else{ + // Size is known, check if we already have this param + if(running.server_param_set[response.param_index]!=std::nullopt){ + // we already have this param - nothing to do, we could validate it though + return std::nullopt; + }else{ + running.server_param_set[response.param_index]=response; + update_progress_get_all(running); + const int missing=get_missing_count(running.server_param_set); + if(missing==0){ + qDebug()<<"No params missing, total:"<0); + const int n_missing=get_missing_count(running_cmd.server_param_set); + qDebug()<<"Still missing:"<= running_cmd.server_param_set.size()/2){ + // A lot are stil missing, request them all again + send_param_ext_request_list(running_cmd.base_cmd); + }else{ + // Request up to X specific missing cmds at a time + for(int i=0;i<10 && i > &server_param_set) +{ + int ret=0; + for(const auto& param:server_param_set){ + if(!param.has_value())ret++; + } + return ret; +} + +void XParam::update_progress_set(const RunningParamCmdSet &cmd,bool done) +{ + if(cmd.opt_progress_cb){ + if(done){ + cmd.opt_progress_cb(100.0); + }else{ + const float progress=(float)cmd.n_transmissions/(float)cmd.n_wanted_retransmissions*100.0; + cmd.opt_progress_cb(progress); + } + } +} + +void XParam::update_progress_get_all(const RunningParamCmdGetAll &cmd) +{ + if(cmd.opt_progress_cb){ + float progress=0; + if(cmd.server_param_set.size()>0){ + const int missing=get_missing_count(cmd.server_param_set); + if(missing<=0){ + progress=100; + }else{ + progress= (static_cast(cmd.server_param_set.size()-missing) / static_cast(cmd.server_param_set.size())*100.0f); + } + } + cmd.opt_progress_cb(progress); + } +} + + +void XParam::loop_timeout() +{ + while(true){ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + check_timeout_param_get_all(); + check_timeout_param_set(); + /*std::lock_guard lock(m_mutex); + const bool busy=!(m_running_commands.empty()&&m_running_get_all.empty()); + XParamUI::instance().set_is_busy(busy);*/ + } +} + +void XParam::check_timeout_param_set() +{ + // Optimization: Call cb without lock being hold + std::vector> timed_out_commands{}; + { + std::lock_guard lock(m_mutex); + for (auto it=m_running_commands.begin(); it!=m_running_commands.end(); ++it){ + RunningParamCmdSet& running_cmd=*it; + const auto elapsed=std::chrono::steady_clock::now()-running_cmd.last_transmission; + if(elapsed>running_cmd.retransmit_delay){ + qDebug()<<"Param cmd set timeout"; + if(running_cmd.n_transmissions> timed_out_commands{}; + { + std::lock_guard lock(m_mutex); + for (auto it=m_running_get_all.begin(); it!=m_running_get_all.end(); ++it){ + RunningParamCmdGetAll& running_cmd=*it; + const auto elapsed=std::chrono::steady_clock::now()-running_cmd.last_transmission; + if(elapsed>running_cmd.retransmit_delay){ + qDebug()<<"Param get all timeout"; + if(running_cmd.n_transmissions +#include +#include +#include +#include +#include +#include + +#include "../../util/mavlink_include.h" + +class XParam +{ +public: + explicit XParam(); + ~XParam(); + static XParam& instance(); + /** + * returns true if this message has been consumed, false otherwise. + */ + bool process_message(const mavlink_message_t& msg); +public: + // Progress CB - no heavy load must be performed on the progress cb, + // as well as any try_() method since lock might be hold + typedef std::function PROGRESS_CB; + + // Not easy to use API, but exposes pretty much all info one could need + struct SetParamResult{ + // Response from the recipient, if there is any (otherwise, the message got lost on each re-transmit) + std::optional response; + // How often this command was transmitted until success / failure + int n_transmissions=-1; + bool is_accepted(){ + return response.has_value() && response.value().param_result==PARAM_ACK_ACCEPTED; + } + }; + typedef std::function SET_PARAM_RESULT_CB; + bool try_set_param_async(const mavlink_param_ext_set_t cmd,SET_PARAM_RESULT_CB result,PROGRESS_CB opt_progress_cb=nullptr,std::chrono::milliseconds retransmit_delay=std::chrono::milliseconds(500),int n_wanted_retransmissions=3); + bool try_set_param_blocking(const mavlink_param_ext_set_t cmd,std::chrono::milliseconds retransmit_delay=std::chrono::milliseconds(500),int n_wanted_retransmissions=3); + + struct GetAllParamResult{ + bool success; + // Full server param set on success, empty param set otherwise + std::vector param_set; + }; + typedef std::function GET_ALL_PARAM_RESULT_CB; + void try_get_param_all_async(const mavlink_param_ext_request_list_t cmd,GET_ALL_PARAM_RESULT_CB result_cb,PROGRESS_CB opt_progress_cb=nullptr); + std::optional> try_get_param_all_blocking(const int target_sysid,const int target_compid); +public: + static mavlink_param_ext_set_t create_cmd_set_int(int target_sysid,int target_compid,std::string param_name,int value); + static mavlink_param_ext_set_t create_cmd_set_string(int target_sysid,int target_compid,std::string param_id,std::string value); + static mavlink_param_ext_request_list_t create_cmd_get_all(int target_sysid,int target_compid); + + struct ParamVariant{ + std::string param_id; + std::optional string_param; + std::optional int_param; + }; + static std::vector parse_server_param_set(const std::vector& param_set); +public: + // easy to use API + enum EasySetParamResult{ + NO_RESPONSE, // no response from recipient + VALUE_DENIED, // response from recipient, but negative (some error code) + VALUE_SUCCESS // positive response from recipient + }; + + //template + //bool try_set_param_async(const int target_sys_id,const int target_comp_id,) + +private: + std::mutex m_mutex; + // A currently active set param action + struct RunningParamCmdSet{ + mavlink_param_ext_set_t cmd; + SET_PARAM_RESULT_CB cb; + PROGRESS_CB opt_progress_cb; + // How often this command should be retransmitted + int n_wanted_retransmissions; + // Delay between each retransmission + std::chrono::milliseconds retransmit_delay; + // How often this command has already been sent + int n_transmissions=0; + // last time it was sent + std::chrono::steady_clock::time_point last_transmission=std::chrono::steady_clock::now(); + }; + // A currently active get all params action + struct RunningParamCmdGetAll{ + mavlink_param_ext_request_list_t base_cmd; + GET_ALL_PARAM_RESULT_CB cb; + PROGRESS_CB opt_progress_cb; + std::chrono::milliseconds max_delay_until_timeout; + std::chrono::milliseconds retransmit_delay; + std::chrono::steady_clock::time_point last_transmission=std::chrono::steady_clock::now(); + std::vector> server_param_set; + // + int n_wanted_retransmissions; + int n_transmissions=0; + }; + + bool handle_param_ext_ack(const mavlink_param_ext_ack_t& ack,int sender_sysid,int sender_compid); + bool handle_param_ext_value(const mavlink_param_ext_value_t& response,int sender_sysid,int sender_compid); + + // searches for a Running param set command that matches the given param ack + // if found, remove the command and return it. + // ootherwise, return nullopt + std::optional find_remove_running_command_threadsafe(const mavlink_param_ext_ack_t& ack,int sender_sysid,int sender_compid); + // Searches for a running get all command + // if found, checks if the param set is completely full - in this case, return the param set + // otherwise, leave the command on the queue (timeout will remvove it in case it finally times out) + std::optional find_remove_running_command_get_all_threadsafe(const mavlink_param_ext_value_t &response, int sender_sysid, int sender_compid); + // + void send_next_message_running_set(RunningParamCmdSet& cmd); + void send_next_message_running_get_all(RunningParamCmdGetAll& cmd); + void send_param_ext_set(const mavlink_param_ext_set_t &cmd); + void send_param_ext_request_list(const mavlink_param_ext_request_list_t& cmd); + void send_param_ext_request_read(const mavlink_param_ext_request_read_t& cmd); + static int get_missing_count(const std::vector>& server_param_set); + void update_progress_set(const RunningParamCmdSet& cmd,bool done); + void update_progress_get_all(const RunningParamCmdGetAll& cmd); +private: + std::list m_running_commands; + std::list m_running_get_all; + static constexpr auto MAX_N_SIMULTANOEUS_COMMANDS=5; +private: + std::unique_ptr m_timeout_thread; + bool m_timeout_thread_run=true; + void loop_timeout(); + void check_timeout_param_set(); + void check_timeout_param_get_all(); +}; + +#endif // XPARAM_H diff --git a/app/telemetry/action/ohdaction.cpp b/app/telemetry/action/ohdaction.cpp new file mode 100644 index 000000000..23d958956 --- /dev/null +++ b/app/telemetry/action/ohdaction.cpp @@ -0,0 +1,67 @@ +#include "ohdaction.h" + +#include "create_cmd_helper.hpp" +#include "../util/openhd_defines.hpp" +#include "impl/cmdsender.h" + +//#include "../models/aohdsystem.h" + +OHDAction::OHDAction(QObject *parent) + : QObject{parent} +{ + +} + +OHDAction& OHDAction::instance() +{ + static OHDAction instance; + return instance; +} + +/*void OHDAction::request_openhd_version_async() +{ + if(AOHDSystem::instanceAir().is_alive()){ + auto cmd_air=cmd::helper::create_cmd_request_openhd_version(OHD_SYS_ID_AIR,0); + CmdSender::instance().send_command_long_async(cmd_air,nullptr); + } + if(AOHDSystem::instanceGround().is_alive()){ + auto cmd_gnd=cmd::helper::create_cmd_request_openhd_version(OHD_SYS_ID_GROUND,0); + CmdSender::instance().send_command_long_async(cmd_gnd,nullptr); + } +}*/ + +bool OHDAction::send_command_reboot_air(bool reboot) +{ + auto command=cmd::helper::create_cmd_reboot(OHD_SYS_ID_AIR,0,reboot); + const auto res=CmdSender::instance().send_command_long_blocking(command); + return res==CmdSender::Result::CMD_SUCCESS; +} + +bool OHDAction::send_command_reboot_gnd(bool reboot) +{ + auto command=cmd::helper::create_cmd_reboot(OHD_SYS_ID_GROUND,0,reboot); + const auto res=CmdSender::instance().send_command_long_blocking(command); + return res==CmdSender::Result::CMD_SUCCESS; +} + +bool OHDAction::send_command_analyze_channels_blocking() +{ + mavlink_command_long_t cmd{}; + cmd.target_system=OHD_SYS_ID_GROUND; + cmd.target_component=OHD_COMP_ID_LINK_PARAM; + cmd.command=OPENHD_CMD_INITIATE_CHANNEL_ANALYZE; + const auto res=CmdSender::instance().send_command_long_blocking(cmd); + return res==CmdSender::Result::CMD_SUCCESS; +} + +bool OHDAction::send_command_start_scan_channels_blocking(int freq_bands, int channel_widths) +{ + mavlink_command_long_t cmd{}; + cmd.target_system=OHD_SYS_ID_GROUND; + cmd.target_component=OHD_COMP_ID_LINK_PARAM; + cmd.command=OPENHD_CMD_INITIATE_CHANNEL_SEARCH; + cmd.param1=static_cast(freq_bands); + cmd.param2=static_cast(channel_widths); + const auto res=CmdSender::instance().send_command_long_blocking(cmd); + return res==CmdSender::Result::CMD_SUCCESS; +} diff --git a/app/telemetry/action/ohdaction.h b/app/telemetry/action/ohdaction.h new file mode 100644 index 000000000..9a6ccb5de --- /dev/null +++ b/app/telemetry/action/ohdaction.h @@ -0,0 +1,32 @@ +#ifndef OHDACTION_H +#define OHDACTION_H + +#include + +/** + * @brief This is the only class (other than param /settings) where one can talk to the OpenHD air / ground unit. + * THE REST IS BROADCAST ! + */ +class OHDAction : public QObject +{ + Q_OBJECT +public: + explicit OHDAction(QObject *parent = nullptr); + + static OHDAction& instance(); +public: + // request the OpenHD version, both OpenHD air and ground unit will respond to that message. + // Deprecated, version is now broadcasted, too + //Q_INVOKABLE void request_openhd_version_async(); + // send the reboot / shutdown command to openhd air or ground unit + // @param system_id: 0 for ground, 1 for air, 2 for FC + Q_INVOKABLE bool send_command_reboot_air(bool reboot); + Q_INVOKABLE bool send_command_reboot_gnd(bool reboot); + + // Sent to the ground unit only + bool send_command_analyze_channels_blocking(); + bool send_command_start_scan_channels_blocking(int freq_bands,int channel_widths); +private: +}; + +#endif // OHDACTION_H diff --git a/app/telemetry/connection/tcp_connection.cpp b/app/telemetry/connection/tcp_connection.cpp new file mode 100644 index 000000000..b13f7d66e --- /dev/null +++ b/app/telemetry/connection/tcp_connection.cpp @@ -0,0 +1,167 @@ +#include "tcp_connection.h" + +#ifdef __windows__ +#define _WIN32_WINNT 0x0600 //TODO dirty +#include +#include // For InetPton +#else +#include +#include +#include +#include +#endif + +#include + + +TCPConnection::TCPConnection(const std::string remote_ip, const int remote_port, MAV_MSG_CB cb) + :m_remote_ip(remote_ip),m_remote_port(remote_port),m_cb(cb) +{ + +} + +TCPConnection::~TCPConnection() +{ + stop(); +} + +void TCPConnection::start() +{ + m_keep_receiving=true; + m_receive_thread=std::make_unique(&TCPConnection::loop_receive,this); +} + +void TCPConnection::stop() +{ + m_keep_receiving=false; +#ifdef __windows__ + shutdown(m_socket_fd, SD_BOTH); + + closesocket(m_socket_fd); + + WSACleanup(); +#else + // This should interrupt a recv/recvfrom call. + shutdown(m_socket_fd, SHUT_RDWR); + // But on Mac, closing is also needed to stop blocking recv/recvfrom. + close(m_socket_fd); +#endif + if(m_receive_thread){ + m_receive_thread->join(); + } + m_receive_thread=nullptr; +} + +void TCPConnection::send_message(const mavlink_message_t &msg) +{ + if(!m_is_connected){ + return; // Otherwise sendto blocks + } + struct sockaddr_in dest_addr {}; + dest_addr.sin_family = AF_INET; + inet_pton(AF_INET, m_remote_ip.c_str(), &dest_addr.sin_addr.s_addr); + dest_addr.sin_port = htons(m_remote_port); + + uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; + const uint16_t buffer_len = mavlink_msg_to_send_buffer(buffer, &msg); + + // TODO: remove this assert again + assert(buffer_len <= MAVLINK_MAX_PACKET_LEN); + auto flags = MSG_NOSIGNAL; + const auto send_len = sendto( + m_socket_fd, + reinterpret_cast(buffer), + buffer_len, + flags, + reinterpret_cast(&dest_addr), + sizeof(dest_addr)); + + if (send_len != buffer_len) { + qDebug()<<"Cannot send message"; + } +} + +void TCPConnection::process_data(const uint8_t *data, int data_len) +{ + for (int i = 0; i < data_len; i++) { + mavlink_message_t msg; + uint8_t res = mavlink_parse_char(1, (uint8_t)data[i], &msg, &m_recv_status); + if (res) { + process_mavlink_message(msg); + } + } +} + +void TCPConnection::process_mavlink_message(mavlink_message_t message) +{ + m_cb(message); +} + +void TCPConnection::loop_receive() +{ + while (m_keep_receiving) { + qDebug()<<"TCP start on "<(&remote_addr), sizeof(struct sockaddr_in)) <0) { + qDebug()<<"Socket connect failed: "< +#include +#include +#include +#include +#include +#include + +class TCPConnection +{ +public: + typedef std::function MAV_MSG_CB; + TCPConnection(const std::string remote_ip,const int remote_port,MAV_MSG_CB cb); + ~TCPConnection(); + + void start(); + void stop(); + + void send_message(const mavlink_message_t& msg); +private: + void process_data(const uint8_t* data,int data_len); + void process_mavlink_message(mavlink_message_t msg); + void loop_receive(); + bool setup_socket(); + void connect_once(); +private: + const std::string m_remote_ip; + const int m_remote_port; + const MAV_MSG_CB m_cb; + int m_socket_fd=-1; + mavlink_status_t m_recv_status{}; + std::unique_ptr m_receive_thread=nullptr; + std::atomic m_keep_receiving=false; + std::atomic m_is_connected=false; +}; + +#endif // TCPCONNECTION_H diff --git a/app/telemetry/connection/udp_connection.cpp b/app/telemetry/connection/udp_connection.cpp new file mode 100644 index 000000000..a2b73bfcf --- /dev/null +++ b/app/telemetry/connection/udp_connection.cpp @@ -0,0 +1,229 @@ +#include "udp_connection.h" + +#ifdef __windows__ +#define _WIN32_WINNT 0x0600 //TODO dirty +#include +#include // For InetPton +#else +#include +#include +#include +#include +#endif + +#include + +#ifdef WINDOWS +#define GET_ERROR(_x) WSAGetLastError() +#else +#define GET_ERROR(_x) strerror(_x) +#endif + +#ifdef __windows__ +#endif + + +UDPConnection::UDPConnection(const std::string local_ip,const int local_port,MAV_MSG_CB cb) + :m_local_ip(local_ip),m_local_port(local_port),m_cb(cb) +{ + +} + +UDPConnection::~UDPConnection() +{ + stop(); +} + + +void UDPConnection::start() +{ + m_keep_receiving=true; + m_receive_thread=std::make_unique(&UDPConnection::loop_receive,this); +} + +void UDPConnection::stop() +{ + qDebug()<<"UDP stop - begin"; + m_keep_receiving=false; +#ifdef __windows__ + shutdown(m_socket_fd, SD_BOTH); + + closesocket(m_socket_fd); + + WSACleanup(); +#else + // This should interrupt a recv/recvfrom call. + shutdown(m_socket_fd, SHUT_RDWR); + + // But on Mac, closing is also needed to stop blocking recv/recvfrom. + close(m_socket_fd); +#endif + if(m_receive_thread){ + m_receive_thread->join(); + } + m_receive_thread=nullptr; + qDebug()<<"UDP stop - end"; +} + +void UDPConnection::send_message(const mavlink_message_t &msg) +{ + auto opt_remote=get_current_remote(); + if(opt_remote.has_value()){ + const Remote& remote=opt_remote.value(); + struct sockaddr_in dest_addr {}; + dest_addr.sin_family = AF_INET; + inet_pton(AF_INET, remote.ip.c_str(), &dest_addr.sin_addr.s_addr); + dest_addr.sin_port = htons(remote.port); + + uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; + uint16_t buffer_len = mavlink_msg_to_send_buffer(buffer, &msg); + + const auto send_len = sendto( + m_socket_fd, + reinterpret_cast(buffer), + buffer_len, + 0, + reinterpret_cast(&dest_addr), + sizeof(dest_addr)); + + if (send_len != buffer_len) { + qDebug()<<"Cannot send data to "<(&addr), sizeof(addr)) != 0) { + qDebug()<<"Cannot bind port "<(&src_addr), + &src_addr_len); + //qDebug()<<"Gt data"; + + if (recv_len == 0) { + // This can happen when shutdown is called on the socket, + // therefore we check _should_exit again. + continue; + } + + if (recv_len < 0) { + // This happens on destruction when close(_socket_fd) is called, + // therefore be quiet. + // LogErr() << "recvfrom error: " << GET_ERROR(errno); + continue; + } + const std::string remote_ip=inet_ntoa(src_addr.sin_addr); + const int remote_port=ntohs(src_addr.sin_port); + set_remote(remote_ip,remote_port); + process_data(buffer,recv_len); + } + } + // TODO close socket + // set the remote back to unknown + clear_remote(); +} + +std::optional UDPConnection::get_current_remote() +{ + std::lock_guard lock(m_remote_nutex); + if(m_curr_remote.has_value()){ + return m_curr_remote.value(); + } + return std::nullopt; +} + + +void UDPConnection::set_remote(const std::string ip, int port) +{ + std::lock_guard lock(m_remote_nutex); + if(m_curr_remote.has_value()){ + auto& remote=m_curr_remote.value(); + if(remote.ip!=ip || remote.port != port){ + auto new_remote=Remote{ip,port}; + qDebug()<<"Remote chnged from "< lock(m_remote_nutex); + m_curr_remote=std::nullopt; +} + diff --git a/app/telemetry/connection/udp_connection.h b/app/telemetry/connection/udp_connection.h new file mode 100644 index 000000000..9d82e690e --- /dev/null +++ b/app/telemetry/connection/udp_connection.h @@ -0,0 +1,61 @@ +#ifndef MUDPLINK_H +#define MUDPLINK_H + +#include "../util/mavlink_include.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Mavlink udp connection - follows the semi-established pattern + * of using udp, listening but sending responses to whoever supplied + * us with data + */ +class UDPConnection +{ +public: + typedef std::function MAV_MSG_CB; + UDPConnection(const std::string local_ip,const int local_port,MAV_MSG_CB cb); + ~UDPConnection(); + + void start(); + + void stop(); + + void send_message(const mavlink_message_t& msg); +private: + void process_data(const uint8_t* data,int data_len); + void process_mavlink_message(mavlink_message_t msg); + void loop_receive(); + bool setup_socket(); + void connect_once(); + struct Remote{ + std::string ip; + int port; + std::string to_string()const{ + std::stringstream ss; + ss< get_current_remote(); + void set_remote(const std::string ip,int port); + void clear_remote(); +private: + const std::string m_local_ip; + const int m_local_port; + const MAV_MSG_CB m_cb; + int m_socket_fd=-1; + mavlink_status_t m_recv_status{}; + std::unique_ptr m_receive_thread=nullptr; + std::atomic m_keep_receiving=false; + std::mutex m_remote_nutex; + std::optional m_curr_remote; +}; + +#endif // MUDPLINK_H diff --git a/app/telemetry/mavsdk_helper.hpp b/app/telemetry/mavsdk_helper.hpp deleted file mode 100644 index 0c3d03300..000000000 --- a/app/telemetry/mavsdk_helper.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef MAVSDK_HELPER_H -#define MAVSDK_HELPER_H - -#include "mavsdk_include.h" -#include -#include - -namespace mavsdk::helper{ - - -static std::string to_string(const mavsdk::MavlinkPassthrough::Result& res){ - std::stringstream ss; - ss<& pass_through){ - mavsdk::MavlinkPassthrough::CommandLong cmd; - cmd.command = MAV_CMD_SET_MESSAGE_INTERVAL; - cmd.target_sysid= pass_through->get_target_sysid(); - cmd.target_compid=pass_through->get_target_compid(); - cmd.param1=message_type; // affects artificial horizon update rate - const int interval_us=std::chrono::duration_cast(std::chrono::milliseconds(interval_ms)).count(); - cmd.param2=interval_us; - const auto res=pass_through->send_command_long(cmd); - return res==mavsdk::MavlinkPassthrough::Result::Success; -} - -static bool set_all_message_update_rates(std::shared_ptr& pass_through){ - // broadcast home every 2 seconds - set_message_update_rate(MAVLINK_MSG_ID_ATTITUDE,std::chrono::seconds(2),pass_through); - - // artificial horizon - 50Hz - set_message_update_rate(MAVLINK_MSG_ID_ATTITUDE,std::chrono::milliseconds(20),pass_through); - - // location every 500ms - set_message_update_rate(MAVLINK_MSG_ID_GLOBAL_POSITION_INT,std::chrono::milliseconds(500),pass_through); - return false; -} - -static bool any_comp_id_autopilot(const std::vector& comp_ids){ - for (auto compid : comp_ids){ - if (compid == MAV_COMP_ID_AUTOPILOT1) { - return true; - } - } - return false; -} -static std::string comp_ids_to_string(const std::vector& comp_ids){ - std::stringstream ss; - ss<<"["; - for (auto compid : comp_ids){ - ss<<(int)compid<<","; - } - ss<<"]"; - return ss.str(); -} - -} - -#endif // MAVSDK_HELPER_H diff --git a/app/telemetry/mavsdk_include.h b/app/telemetry/mavsdk_include.h deleted file mode 100644 index a77547403..000000000 --- a/app/telemetry/mavsdk_include.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef MAVLINK_INCLUDE_H -#define MAVLINK_INCLUDE_H - -// We get mavlink from mavsdk -#include -#include -#include -#include -#include -#include - - -#endif // MAVLINK_INCLUDE_H diff --git a/app/telemetry/models/README.md b/app/telemetry/models/README.md index 8ef6eb30c..3f5b2c2a3 100644 --- a/app/telemetry/models/README.md +++ b/app/telemetry/models/README.md @@ -1,3 +1,6 @@ +NOTE: Code in here is strictly mavlink broadcast - aka no retransmission or quaranteed deliveries. +NOTE: See action and / or settings subdirectories for code that 'talks' to one or more of the mavlink systems. + Generally, we have 2 OpenHD systems (each wth their own unique mavlink system id and one or more component(s) and the FC system (if connected) diff --git a/app/telemetry/models/aohdsystem.cpp b/app/telemetry/models/aohdsystem.cpp index 73694954c..d15e63c7c 100644 --- a/app/telemetry/models/aohdsystem.cpp +++ b/app/telemetry/models/aohdsystem.cpp @@ -1,24 +1,25 @@ #include "aohdsystem.h" -#include "../qopenhdmavlinkhelper.hpp" #include "../../common/StringHelper.hpp" #include "../../common/TimeHelper.hpp" -#include "../telemetryutil.hpp" #include "QOpenHDVideoHelper.hpp" +#include "util/telemetryutil.hpp" #include "wificard.h" #include "rcchannelsmodel.h" #include "camerastreammodel.h" #include #include +#include #include #include #include "util/qopenhd.h" +#include "../util/qopenhdmavlinkhelper.hpp" #include <../util/WorkaroundMessageBox.h> -#include "../settings/synchronizedsettings.h" +#include "../settings/wblinksettingshelper.h" // From https://netbeez.net/blog/what-is-mcs-index/ static std::vector get_dbm_20mhz(){ @@ -35,12 +36,34 @@ static int get_required_dbm_for_rate(int channel_width,int mcs_index){ } return 0; } +// Bit field for boolean only value(s) +struct MonitorModeLinkBitfield { + unsigned int stbc:1; + unsigned int lpdc:1; + unsigned int short_guard:1; + unsigned int curr_rx_last_packet_status_good:1; + unsigned int unused:4; +} +#ifdef __windows__ +; +#else +__attribute__ ((packed)); +static_assert(sizeof(MonitorModeLinkBitfield)==1); +#endif +static MonitorModeLinkBitfield parse_monitor_link_bitfield(uint8_t bitfield){ + MonitorModeLinkBitfield ret{}; +#ifdef __windows__ +#else + std::memcpy((uint8_t*)&ret,&bitfield,1); +#endif + return ret; +} AOHDSystem::AOHDSystem(const bool is_air,QObject *parent) : QObject{parent},m_is_air(is_air) { - m_alive_timer = new QTimer(this); - QObject::connect(m_alive_timer, &QTimer::timeout, this, &AOHDSystem::update_alive); + m_alive_timer = std::make_unique(this); + QObject::connect(m_alive_timer.get(), &QTimer::timeout, this, &AOHDSystem::update_alive); m_alive_timer->start(1000); } @@ -56,71 +79,66 @@ AOHDSystem &AOHDSystem::instanceGround() return ground; } -void AOHDSystem::register_for_qml(QQmlContext *qml_context) -{ - qml_context->setContextProperty("_ohdSystemAir", &AOHDSystem::instanceAir()); - qml_context->setContextProperty("_ohdSystemGround", &AOHDSystem::instanceGround()); -} - bool AOHDSystem::process_message(const mavlink_message_t &msg) { - if(msg.sysid != get_own_sys_id()){ + if(msg.sysid != get_own_sys_id()){ // improper usage qDebug()<<"AOHDSystem::process_message: wron sys id"; return false; } m_last_message_ms=QOpenHDMavlinkHelper::getTimeMilliseconds(); + bool consumed=false; switch(msg.msgid){ case MAVLINK_MSG_ID_OPENHD_VERSION_MESSAGE:{ mavlink_openhd_version_message_t parsedMsg; mavlink_msg_openhd_version_message_decode(&msg,&parsedMsg); QString version(parsedMsg.version); set_openhd_version(version); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_ONBOARD_COMPUTER_STATUS:{ mavlink_onboard_computer_status_t parsedMsg; mavlink_msg_onboard_computer_status_decode(&msg,&parsedMsg); process_onboard_computer_status(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_MONITOR_MODE_WIFI_CARD:{ mavlink_openhd_stats_monitor_mode_wifi_card_t parsedMsg; mavlink_msg_openhd_stats_monitor_mode_wifi_card_decode(&msg,&parsedMsg); //qDebug()<<"Got MAVLINK_MSG_ID_OPENHD_WIFI_CARD"<<(int)parsedMsg.card_index<<" "<<(int)parsedMsg.rx_rssi; process_x0(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_MONITOR_MODE_WIFI_LINK:{ mavlink_openhd_stats_monitor_mode_wifi_link_t parsedMsg; mavlink_msg_openhd_stats_monitor_mode_wifi_link_decode(&msg,&parsedMsg); process_x1(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_TELEMETRY:{ mavlink_openhd_stats_telemetry_t parsedMsg; mavlink_msg_openhd_stats_telemetry_decode(&msg,&parsedMsg); process_x2(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_WB_VIDEO_AIR:{ mavlink_openhd_stats_wb_video_air_t parsedMsg; mavlink_msg_openhd_stats_wb_video_air_decode(&msg,&parsedMsg); process_x3(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_WB_VIDEO_AIR_FEC_PERFORMANCE:{ mavlink_openhd_stats_wb_video_air_fec_performance_t parsedMsg; mavlink_msg_openhd_stats_wb_video_air_fec_performance_decode(&msg,&parsedMsg); process_x3b(parsedMsg); - return true; + consumed=true; }break; - case MAVLINK_MSG_ID_OPENHD_CAMERA_STATUS:{ - mavlink_openhd_camera_status_t parsedMsg; - mavlink_msg_openhd_camera_status_decode(&msg,&parsedMsg); + case MAVLINK_MSG_ID_OPENHD_CAMERA_STATUS_AIR:{ + mavlink_openhd_camera_status_air_t parsedMsg; + mavlink_msg_openhd_camera_status_air_decode(&msg,&parsedMsg); if(msg.compid==OHD_COMP_ID_AIR_CAMERA_PRIMARY){ - CameraStreamModel::instance(0).update_mavlink_openhd_camera_stats(parsedMsg); + CameraStreamModel::instance(0).update_mavlink_openhd_camera_status_air(parsedMsg); }else if(msg.compid==OHD_COMP_ID_AIR_CAMERA_SECONDARY){ - CameraStreamModel::instance(1).update_mavlink_openhd_camera_stats(parsedMsg); + CameraStreamModel::instance(1).update_mavlink_openhd_camera_status_air(parsedMsg); // Feature - tell user to enable 2 cameras in qopenhd set_n_openhd_cameras(2); const int value_in_qopenhd=QOpenHDVideoHelper::get_qopenhd_n_cameras(); @@ -136,24 +154,24 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) } } - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_WB_VIDEO_GROUND:{ mavlink_openhd_stats_wb_video_ground_t parsedMsg; mavlink_msg_openhd_stats_wb_video_ground_decode(&msg,&parsedMsg); process_x4(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_STATS_WB_VIDEO_GROUND_FEC_PERFORMANCE:{ mavlink_openhd_stats_wb_video_ground_fec_performance_t parsedMsg; mavlink_msg_openhd_stats_wb_video_ground_fec_performance_decode(&msg,&parsedMsg); process_x4b(parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_OPENHD_ONBOARD_COMPUTER_STATUS_EXTENSION:{ mavlink_openhd_onboard_computer_status_extension_t parsedMsg; mavlink_msg_openhd_onboard_computer_status_extension_decode(&msg,&parsedMsg); - return true; + consumed=true; }break; case MAVLINK_MSG_ID_HEARTBEAT:{ mavlink_heartbeat_t parsedMsg; @@ -162,13 +180,13 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) if(parsedMsg.autopilot!=MAV_AUTOPILOT_INVALID){ qDebug()<<"Warning OpenHD systems should always set autopilot to none"; } - return true; + consumed=true; }break; case MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE:{ mavlink_rc_channels_override_t parsedMsg; mavlink_msg_rc_channels_override_decode(&msg,&parsedMsg); RCChannelsModel::instanceGround().update_all_channels(Telemetryutil::mavlink_msg_rc_channels_override_to_array(parsedMsg)); - return true; + consumed=true; };break; case MAVLINK_MSG_ID_STATUSTEXT:{ mavlink_statustext_t parsedMsg; @@ -179,7 +197,34 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) if(tmp.message.find("External device") != std::string::npos){ HUDLogMessagesModel::instance().add_message(tmp.level,tmp.message.c_str()); } - return true; + consumed=true; + }break; + case MAVLINK_MSG_ID_OPENHD_WIFBROADCAST_SUPPORTED_CHANNELS:{ + mavlink_openhd_wifbroadcast_supported_channels_t parsedMsg; + mavlink_msg_openhd_wifbroadcast_supported_channels_decode(&msg,&parsedMsg); + if(!m_is_air){ + WBLinkSettingsHelper::instance().process_message_openhd_wifibroadcast_supported_channels(parsedMsg); + } + consumed=true; + }break; + case MAVLINK_MSG_ID_OPENHD_WIFBROADCAST_ANALYZE_CHANNELS_PROGRESS:{ + mavlink_openhd_wifbroadcast_analyze_channels_progress_t parsedMsg; + mavlink_msg_openhd_wifbroadcast_analyze_channels_progress_decode(&msg,&parsedMsg); + if(!m_is_air){ + WBLinkSettingsHelper::instance().process_message_openhd_wifibroadcast_analyze_channels_progress(parsedMsg); + } + consumed=true; + }break; + case MAVLINK_MSG_ID_OPENHD_WIFBROADCAST_SCAN_CHANNELS_PROGRESS:{ + mavlink_openhd_wifbroadcast_scan_channels_progress_t parsedMsg; + mavlink_msg_openhd_wifbroadcast_scan_channels_progress_decode(&msg,&parsedMsg); + if(!m_is_air){ + WBLinkSettingsHelper::instance().process_message_openhd_wifibroadcast_scan_channels_progress(parsedMsg); + } + consumed=true; + }break; + default:{ + }break; /*case MAVLINK_MSG_ID_OPENHD_LOG_MESSAGE:{ mavlink_openhd_log_message_t parsedMsg; @@ -192,7 +237,7 @@ bool AOHDSystem::process_message(const mavlink_message_t &msg) break; }*/ } - return false; + return consumed; } void AOHDSystem::process_onboard_computer_status(const mavlink_onboard_computer_status_t &msg) @@ -206,6 +251,7 @@ void AOHDSystem::process_onboard_computer_status(const mavlink_onboard_computer_ set_curr_core_freq_mhz(msg.storage_type[3]); set_curr_v3d_freq_mhz(msg.storage_usage[0]); set_curr_space_left_mb(msg.storage_usage[1]); + set_rpi_undervolt_error(msg.link_tx_rate[0]==1); set_ina219_voltage_millivolt(msg.storage_usage[2]); set_ina219_current_milliamps(msg.storage_usage[3]); set_ram_usage_perc(msg.ram_usage); @@ -225,7 +271,7 @@ void AOHDSystem::process_x0(const mavlink_openhd_stats_monitor_mode_wifi_card_t } auto& card=WiFiCard::instance_air(); card.process_mavlink(msg); - set_current_rx_rssi(msg.rx_rssi_1); + set_current_rx_rssi(card.curr_rx_rssi_dbm()); }else{ if(msg.card_index<0 || msg.card_index>=4){ qDebug()<<"Gnd invalid card index"<=0 && active_tx_idxstd::chrono::seconds(5)){ - //TODO clear curr values - } - } -} - -bool AOHDSystem::send_command_long(mavsdk::Action::CommandLong command) -{ - if(!_action){ - return false; - } - const auto res=_action->send_command_long(command); - assert(command.target_system_id==get_own_sys_id()); - std::stringstream ss; - ss<<"Action: "< system) -{ - // once discovered, the system never changes ! - assert(_system==nullptr); - assert(system->get_system_id()==get_own_sys_id()); - _system=system; - _action=std::make_shared(system); -} - -bool AOHDSystem::send_command_reboot(bool reboot) -{ - mavsdk::Action::CommandLong command{}; - command.target_system_id= get_own_sys_id(); - command.target_component_id=0; // unused r.n - command.command=MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN; - command.params.maybe_param1=0; - command.params.maybe_param2=(reboot ? 1 : 2); - return send_command_long(command); } void AOHDSystem::send_message_hud_connection(bool connected){ diff --git a/app/telemetry/models/aohdsystem.h b/app/telemetry/models/aohdsystem.h index 9a2bbd552..15a00b8c4 100644 --- a/app/telemetry/models/aohdsystem.h +++ b/app/telemetry/models/aohdsystem.h @@ -4,13 +4,14 @@ #include #include #include -#include "../mavsdk_include.h" -#include "../openhd_defines.hpp" #include #include #include +#include "../util/mavlink_include.h" + #include "../../../lib/lqtutils_master/lqtutils_prop.h" +#include "util/openhd_defines.hpp" /** * Abstract OHD (Mavlink) system. @@ -29,13 +30,9 @@ class AOHDSystem : public QObject // Singletons for accessing the models from c++ static AOHDSystem& instanceAir(); static AOHDSystem& instanceGround(); - // Called in main.cpp to egister the models for qml - static void register_for_qml(QQmlContext* qml_context); //Process OpenHD custom flavour message(s) coming from either the OHD Air or Ground unit // Returns true if the passed message was processed (known message id), false otherwise bool process_message(const mavlink_message_t& msg); - // Set the mavlink system reference, once discovered - void set_system(std::shared_ptr system); public: // public for QT // NOTE: I wrote this class before I knew about the lqutils macros, which is why they are used sparingly here // @@ -66,6 +63,8 @@ class AOHDSystem : public QObject L_RO_PROP(int,curr_space_left_mb,set_curr_space_left_mb,0) L_RO_PROP(int,ram_usage_perc,set_ram_usage_perc,0) L_RO_PROP(int,ram_total,set_ram_total,0) + // RPI only + L_RO_PROP(bool,rpi_undervolt_error,set_rpi_undervolt_error,false) // needs ina219 sensor L_RO_PROP(int,ina219_voltage_millivolt,set_ina219_voltage_millivolt,0) L_RO_PROP(int,ina219_current_milliamps,set_ina219_current_milliamps,0) @@ -93,12 +92,13 @@ class AOHDSystem : public QObject // similar for channel / channel width L_RO_PROP(int,curr_channel_mhz,set_curr_channel_mhz,-1) L_RO_PROP(int,curr_channel_width_mhz,set_curr_channel_width_mhz,-1); - // We show a watermark if passive mode is enabled - L_RO_PROP(bool,tx_passive_mode,set_tx_passive_mode,false) + // 0: can do tx and rx, 1=card can (probably) only do rx 2=passive / listen only mode actively enabled + L_RO_PROP(int,tx_operating_mode,set_tx_operating_mode,0) // wifibroadcast options L_RO_PROP(bool,wb_stbc_enabled,set_wb_stbc_enabled,false) L_RO_PROP(bool,wb_lpdc_enabled,set_wb_lpdc_enabled,false) L_RO_PROP(bool,wb_short_guard_enabled,set_wb_short_guard_enabled,false) + L_RO_PROP(bool,curr_rx_last_packet_status_good,set_curr_rx_last_packet_status_good,false) // L_RO_PROP(QString,tx_packets_per_second_and_bits_per_second,set_tx_packets_per_second_and_bits_per_second,"N/A") L_RO_PROP(QString,rx_packets_per_second_and_bits_per_second,set_rx_packets_per_second_and_bits_per_second,"N/A") @@ -108,10 +108,12 @@ class AOHDSystem : public QObject L_RO_PROP(int,n_openhd_cameras,set_n_openhd_cameras,-1) // 0==no warning, 1== orange 2==red L_RO_PROP(int,dbm_too_low_warning,set_dbm_too_low_warning,0) + // + L_RO_PROP(int,wb_link_pollution,set_wb_link_pollution,-1) private: const bool m_is_air; // either true (for air) or false (for ground) uint8_t get_own_sys_id()const{ - return m_is_air ? OHD_SYS_ID_AIR : OHD_SYS_ID_GROUND; + return m_is_air ? OHD_SYS_ID_AIR : OHD_SYS_ID_GROUND; } // These are for handling the slight differences regarding air/ ground properly, if there are any // For examle, the onboard computer status is the same when coming from either air or ground, @@ -133,17 +135,10 @@ class AOHDSystem : public QObject // private: // Sets the alive boolean if no heartbeat / message has been received in the last X seconds - QTimer* m_alive_timer = nullptr; + std::unique_ptr m_alive_timer = nullptr; void update_alive(); std::chrono::steady_clock::time_point m_last_message_openhd_stats_total_all_wifibroadcast_streams=std::chrono::steady_clock::now(); // Model / fire and forget data only end -private: - // NOTE: nullptr until discovered !! - std::shared_ptr _system=nullptr; - std::shared_ptr _action=nullptr; - bool send_command_long(mavsdk::Action::CommandLong command); -public: - Q_INVOKABLE bool send_command_reboot(bool reboot); private: int64_t x_last_dropped_packets=-1; void send_message_hud_connection(bool connected); diff --git a/app/telemetry/models/camerastreammodel.cpp b/app/telemetry/models/camerastreammodel.cpp index 2fdfda2d2..4378ed4d5 100644 --- a/app/telemetry/models/camerastreammodel.cpp +++ b/app/telemetry/models/camerastreammodel.cpp @@ -1,7 +1,6 @@ #include "camerastreammodel.h" #include "qdebug.h" -#include "telemetryutil.hpp" -#include "util/WorkaroundMessageBox.h" +#include "../util/telemetryutil.hpp" #include "../videostreaming/vscommon/QOpenHDVideoHelper.hpp" #include @@ -42,7 +41,18 @@ void CameraStreamModel::update_mavlink_openhd_stats_wb_video_air(const mavlink_o set_curr_video_measured_encoder_bitrate(Telemetryutil::bitrate_bps_to_qstring(msg.curr_measured_encoder_bitrate)); set_curr_video_injected_bitrate(Telemetryutil::bitrate_bps_to_qstring(msg.curr_injected_bitrate)); set_curr_video0_injected_pps(Telemetryutil::pps_to_string(msg.curr_injected_pps)); - set_curr_video0_dropped_packets(msg.curr_dropped_frames); + set_total_n_tx_dropped_frames(msg.curr_dropped_frames); + if(m_last_tx_frame_drop_calculation_count<0){ + m_last_tx_frame_drop_calculation_count=msg.curr_dropped_frames; + }else{ + const auto elapsed=std::chrono::steady_clock::now()-m_last_tx_frame_drop_calculation; + if(elapsed>std::chrono::seconds(1)){ + const int diff=msg.curr_dropped_frames-m_last_tx_frame_drop_calculation_count; + m_last_tx_frame_drop_calculation_count=msg.curr_dropped_frames; + set_curr_delta_tx_dropped_frames(diff); + } + } + if(msg.curr_recommended_bitrate>1 && msg.curr_measured_encoder_bitrate>1 ){ //check for valid measured / set values const double recommended_kbits=static_cast(msg.curr_recommended_bitrate); // Measured and set encoder bitrate should match on a 20% basis @@ -73,16 +83,18 @@ void CameraStreamModel::update_mavlink_openhd_stats_wb_video_air(const mavlink_o set_air_tx_packets_per_second_and_bits_per_second(StringHelper::bitrate_and_pps_to_string(msg.curr_injected_bitrate,msg.curr_injected_pps).c_str()); } -void CameraStreamModel::update_mavlink_openhd_camera_stats(const mavlink_openhd_camera_status_t &msg) +void CameraStreamModel::update_mavlink_openhd_camera_status_air(const mavlink_openhd_camera_status_air_t &msg) { set_curr_curr_keyframe_interval(msg.encoding_keyframe_interval); set_air_recording_active(msg.air_recording_active); set_camera_type(msg.cam_type); - std::stringstream ss; - ss<<(int)msg.stream_w<<"x"<<(int)msg.stream_h<<"@"<=std::chrono::seconds(3)){ - m_last_hud_message_camera_restarting=std::chrono::steady_clock::now(); + m_last_hud_message_camera_status=std::chrono::steady_clock::now(); std::stringstream log; - log<<(secondary ? "CAM2" : "CAM1")<<" is restarting, please wait"; + log<<(secondary ? "CAM2" : "CAM1"); + log<<" is restarting, please wait"; HUDLogMessagesModel::instance().add_message_info(log.str().c_str()); } } @@ -131,6 +144,8 @@ void CameraStreamModel::update_mavlink_openhd_stats_wb_video_air_fec_performance Telemetryutil::us_min_max_avg_to_string(msg.curr_fec_encode_time_min_us,msg.curr_fec_encode_time_max_us,msg.curr_fec_encode_time_avg_us)); set_curr_video0_fec_block_length_min_max_avg( Telemetryutil::min_max_avg_to_string(msg.curr_fec_block_size_min,msg.curr_fec_block_size_max,msg.curr_fec_block_size_avg)); + set_curr_time_until_tx_min_max_avg( + Telemetryutil::us_min_max_avg_to_string(msg.curr_tx_delay_min_us,msg.curr_tx_delay_max_us,msg.curr_tx_delay_avg_us)); } void CameraStreamModel::update_mavlink_openhd_stats_wb_video_ground(const mavlink_openhd_stats_wb_video_ground_t &msg) @@ -151,8 +166,6 @@ void CameraStreamModel::update_mavlink_openhd_stats_wb_video_ground_fec_performa void CameraStreamModel::set_curr_recommended_bitrate_from_message(const int64_t curr_recommended_bitrate_kbits) { - // We use the fact that the current recommended bitrate is updated regularily to notify the user of - // changing rate(s) during flight if(m_curr_recomended_video_bitrate_kbits!= 0 && curr_recommended_bitrate_kbits!=0 && m_curr_recomended_video_bitrate_kbits != curr_recommended_bitrate_kbits){ QString message=m_camera_index==0 ? "Cam1 encoder:" : "Cam2 encoder:"; message+=Telemetryutil::bitrate_kbits_to_qstring(curr_recommended_bitrate_kbits); diff --git a/app/telemetry/models/camerastreammodel.h b/app/telemetry/models/camerastreammodel.h index 0c50e81ef..009481a0d 100644 --- a/app/telemetry/models/camerastreammodel.h +++ b/app/telemetry/models/camerastreammodel.h @@ -2,7 +2,8 @@ #define AIRCAMERAMODEL_H #include -#include "../mavsdk_include.h" + +#include "../util/mavlink_include.h" #include "../../../lib/lqtutils_master/lqtutils_prop.h" @@ -35,9 +36,14 @@ class CameraStreamModel : public QObject L_RO_PROP(QString,curr_video_measured_encoder_bitrate,set_curr_video_measured_encoder_bitrate,"N/A") L_RO_PROP(QString,curr_video_injected_bitrate,set_curr_video_injected_bitrate,"N/A") //includes FEC overhead L_RO_PROP(QString,curr_video0_injected_pps,set_curr_video0_injected_pps,"-1pps") //includes FEC overhead - L_RO_PROP(int,curr_video0_dropped_packets,set_curr_video0_dropped_packets,0) + // total n of frames that were dropped on the tx (hints at too high bitrate) + L_RO_PROP(int,total_n_tx_dropped_frames,set_total_n_tx_dropped_frames,0) + // calculated in fixed X second interval(s) - n of tx frames dropped during this interval + L_RO_PROP(int,curr_delta_tx_dropped_frames,set_curr_delta_tx_dropped_frames,0) + // DEV stats L_RO_PROP(QString,curr_video0_fec_encode_time_avg_min_max,set_curr_video0_fec_encode_time_avg_min_max,"avg na, min na, max na") L_RO_PROP(QString,curr_video0_fec_block_length_min_max_avg,set_curr_video0_fec_block_length_min_max_avg,"avg na, min na, max na") + L_RO_PROP(QString,curr_time_until_tx_min_max_avg,set_curr_time_until_tx_min_max_avg,"avg na, min na, max na") // Used to show the user a visual indication that the set and measured encoder bitrate are far apart // 0 - all okay, 1= bitrate is too low (yellow), 2= bitrate is too high (red) L_RO_PROP(int,curr_set_and_measured_bitrate_mismatch,set_curr_set_and_measured_bitrate_mismatch,0) @@ -62,15 +68,15 @@ class CameraStreamModel : public QObject public: // generated by wb / link void update_mavlink_openhd_stats_wb_video_air(const mavlink_openhd_stats_wb_video_air_t &msg); - // generated by the camera - void update_mavlink_openhd_camera_stats(const mavlink_openhd_camera_status_t & msg); - void update_mavlink_openhd_stats_wb_video_ground(const mavlink_openhd_stats_wb_video_ground_t &msg); - // void update_mavlink_openhd_stats_wb_video_air_fec_performance(const mavlink_openhd_stats_wb_video_air_fec_performance_t &msg); + void update_mavlink_openhd_stats_wb_video_ground(const mavlink_openhd_stats_wb_video_ground_t &msg); void update_mavlink_openhd_stats_wb_video_ground_fec_performance(const mavlink_openhd_stats_wb_video_ground_fec_performance_t &msg); - // Calls the appropriate member setter(s) - void set_curr_recommended_bitrate_from_message(int64_t curr_recommended_bitrate_kbits); + // generated by the camera + void update_mavlink_openhd_camera_status_air(const mavlink_openhd_camera_status_air_t & msg); private: + // We use the fact that the current recommended bitrate is updated regularily to notify the user of + // changing rate(s) during flight + void set_curr_recommended_bitrate_from_message(int64_t curr_recommended_bitrate_kbits); // Do not completely pollute the Log messages model int m_n_mismatch_has_been_logged=0; // FEAUTURE - Re-start decoding if resolution / framerate of a camera has changed @@ -92,7 +98,10 @@ class CameraStreamModel : public QObject }; static std::string resolution_framerate_to_string(const ResolutionFramerate& data); ResolutionFramerate m_curr_res_framerate{}; - std::chrono::steady_clock::time_point m_last_hud_message_camera_restarting=std::chrono::steady_clock::now(); + std::chrono::steady_clock::time_point m_last_hud_message_camera_status=std::chrono::steady_clock::now(); +private: + std::chrono::steady_clock::time_point m_last_tx_frame_drop_calculation=std::chrono::steady_clock::now(); + int m_last_tx_frame_drop_calculation_count=-1; }; #endif // AIRCAMERAMODEL_H diff --git a/app/telemetry/models/fcmavlinkmissionitemsmodel.cpp b/app/telemetry/models/fcmavlinkmissionitemsmodel.cpp index aede28210..c82ccaf14 100644 --- a/app/telemetry/models/fcmavlinkmissionitemsmodel.cpp +++ b/app/telemetry/models/fcmavlinkmissionitemsmodel.cpp @@ -1,6 +1,5 @@ #include "fcmavlinkmissionitemsmodel.h" #include "qdebug.h" - #include @@ -8,8 +7,7 @@ FCMavlinkMissionItemsModel::FCMavlinkMissionItemsModel(QObject *parent) : QAbstractListModel(parent) { connect(this, &FCMavlinkMissionItemsModel::signal_qt_ui_update_element, this, &FCMavlinkMissionItemsModel::qt_ui_update_element); - QSettings settings; - show_map=settings.value("show_map",false).toBool(); + connect(this, &FCMavlinkMissionItemsModel::signal_qt_ui_resize, this, &FCMavlinkMissionItemsModel::qt_ui_resize); } FCMavlinkMissionItemsModel& FCMavlinkMissionItemsModel::instance() @@ -19,13 +17,17 @@ FCMavlinkMissionItemsModel& FCMavlinkMissionItemsModel::instance() } -void FCMavlinkMissionItemsModel::update_mission(int mission_index,double lat,double lon,double alt_m,bool currently_active) +void FCMavlinkMissionItemsModel::p_initialize(int total_mission_count) { - // save performance if map is not enabled - if(!show_map)return; - emit signal_qt_ui_update_element(mission_index,lat,lon,alt_m,currently_active); + emit signal_qt_ui_resize(total_mission_count); } +void FCMavlinkMissionItemsModel::p_update(int mission_index, double lat, double lon, double alt_m) +{ + emit signal_qt_ui_update_element(mission_index,lat,lon,alt_m,false); +} + + int FCMavlinkMissionItemsModel::rowCount( const QModelIndex& parent) const { if (parent.isValid()) @@ -73,17 +75,6 @@ QHash FCMavlinkMissionItemsModel::roleNames() const return mapping; } -/*void FCMavlinkMissionItemsModel::removeData(int row) -{ - if (row < 0 || row >= m_data.count()) - return; - - //qDebug()<<"Removing "<MAX_N_ELEMENTS){ - qDebug()<<"We only support up to "<=n_elements){ - // add as many (dummy) elements as we need - for(int i=n_elements;i<=mission_index;i++){ - addData(FCMavlinkMissionItemsModel::Element{i,0,0,0,false,false}); - } - } - assert(mission_index #include #include -#include #include +#include +#include +#include - -// To not pollute the FCMavlinkSystem model class too much, we have an extra model for managing the -// dynamically sized mission waypoints. ("Map stuff) -// This model pretty much only exposes mission items (dynamic size) such that we can make use of them -// (draw them in .qml) for the map. -// each mission item is pretty much really easy to define - a lattitude, longitude and mission index -// NOTE: Elements in this model are by increasing mission index - if we don't have the data for a given mission index (yet), -// valid is set to false (aka if valid=false this is just a dummy waiting to be filled with valid mission data) +// NOTE: Even though placed here, this model is updated by mission handler (which deals with all the nasty reordering, requesting, ...) stuff +// This model INTENTIONALLY only offers the following public functionalities: +// initialize: Set a new size / count of mission (waypoints / items), resets all elements +// update: update a (valid) mission item by index. +// This way the changes that need to be propagated via QT are minimal - a reset only happens once the total mission count is known / changes (rarely) +// And an update is a relatively light operation. class FCMavlinkMissionItemsModel : public QAbstractListModel { Q_OBJECT public: explicit FCMavlinkMissionItemsModel(QObject *parent = nullptr); static FCMavlinkMissionItemsModel& instance(); - // This emits the proper signal in which we either update the mission - // or add as many elements as needed, then update the mission - void update_mission(int mission_index,double lat,double lon,double alt_m,bool currently_active); + // This model INTENTIONALLY only offers the following public functionalities: + // initialize: Set a new size / count of mission (waypoints / items), resets all elements + // update: update a (valid) mission item by index. + // This way the changes that need to be propagated via QT are minimal - a reset only happens once the total mission count is known / changes (rarely) + // And an update is a relatively light operation. + void p_initialize(int total_mission_count); + void p_update(int mission_index,double lat,double lon,double alt_m); private: struct Element{ int mission_index=0; @@ -59,13 +63,13 @@ public slots: public: signals: void signal_qt_ui_update_element(int mission_index,double lat,double lon,double alt_m,bool currently_active); + void signal_qt_ui_resize(int total_mission_count); private: // NOTE: NEEDS TO BE CALLED FROM QT UI THREAD (via signal) void qt_ui_update_element(int mission_index,double lat,double lon,double alt_m,bool currently_active); + void qt_ui_resize(int total_mission_count); // Memory safety check static constexpr int MAX_N_ELEMENTS=200; - // save performance if map is not enabled - bool show_map=false; }; #endif // FCMAVLINKMISSIONSMODEL_H diff --git a/app/telemetry/models/fcmavlinksettingsmodel.cpp b/app/telemetry/models/fcmavlinksettingsmodel.cpp deleted file mode 100644 index c4410879e..000000000 --- a/app/telemetry/models/fcmavlinksettingsmodel.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "fcmavlinksettingsmodel.h" -#include "util/WorkaroundMessageBox.h" - - -FCMavlinkSettingsModel::FCMavlinkSettingsModel(QObject *parent) -{ - -} - -FCMavlinkSettingsModel &FCMavlinkSettingsModel::instance() -{ - static FCMavlinkSettingsModel instance(nullptr); - return instance; -} - -void FCMavlinkSettingsModel::set_system(std::shared_ptr system) -{ - m_system=system; - param_client=std::make_shared(system,1,false); -} - -bool FCMavlinkSettingsModel::set_ardupilot_message_rates() -{ - if(param_client==nullptr){ - WorkaroundMessageBox::makePopupMessage("No FC"); - return false; - } - bool res=false; - res=param_client->set_param_int("SR1_EXTRA2",2)==mavsdk::Param::Result::Success; - if(!res)return false; - return true; -} - - diff --git a/app/telemetry/models/fcmavlinksettingsmodel.h b/app/telemetry/models/fcmavlinksettingsmodel.h deleted file mode 100644 index e7e7bc4eb..000000000 --- a/app/telemetry/models/fcmavlinksettingsmodel.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef FCMAVLINKSETTINGSMODEL_H -#define FCMAVLINKSETTINGSMODEL_H - -#include -#include "../mavsdk_include.h" - - -// For changing settings on the FC itself (not OpenHD !) -// Works a bit different -// ARGH - The prams I was initially after are int16_t (not int32_t) and therefore not supported in mavsdk param yet -// -> stub for now -class FCMavlinkSettingsModel: public QObject -{ - Q_OBJECT -public: - explicit FCMavlinkSettingsModel(QObject *parent = nullptr); - // singleton for accessing the model from c++ - static FCMavlinkSettingsModel& instance(); - // Called by fcmavlinksystem once discovered - void set_system(std::shared_ptr system); - // Ardupilot is a bit quirky if you connect to telem1 port - // Here we just set that the FC broadcasts all messages we need in fitting intervals. - Q_INVOKABLE bool set_ardupilot_message_rates(); -private: - // set by fcmavlinksystem once discovered - std::shared_ptr param_client=nullptr; - std::shared_ptr m_system=nullptr; -}; - -#endif // FCMAVLINKSETTINGSMODEL_H diff --git a/app/telemetry/models/fcmavlinksystem.cpp b/app/telemetry/models/fcmavlinksystem.cpp index d5f4b7ce7..6a2873de5 100644 --- a/app/telemetry/models/fcmavlinksystem.cpp +++ b/app/telemetry/models/fcmavlinksystem.cpp @@ -1,31 +1,30 @@ #include "fcmavlinksystem.h" - -#include "../qopenhdmavlinkhelper.hpp" #include "rcchannelsmodel.h" #include #include "util/qopenhd.h" -#include "../telemetryutil.hpp" #include -#include "../geodesi_helper.h" +#include "../util/geodesi_helper.h" #include #include #include -#include "mavsdk_helper.hpp" -#include "fcmavlinkmissionitemsmodel.h" -#include "fcmavlinksettingsmodel.h" + +#include "util/qopenhdmavlinkhelper.hpp" +#include "util/telemetryutil.hpp" + +#include "../action/fcaction.h" #include FCMavlinkSystem::FCMavlinkSystem(QObject *parent): QObject(parent) { - m_flight_time_timer = new QTimer(this); - QObject::connect(m_flight_time_timer, &QTimer::timeout, this, &FCMavlinkSystem::updateFlightTimer); + m_flight_time_timer = std::make_unique(this); + QObject::connect(m_flight_time_timer.get(), &QTimer::timeout, this, &FCMavlinkSystem::updateFlightTimer); m_flight_time_timer->start(1000); - m_alive_timer = new QTimer(this); - QObject::connect(m_alive_timer, &QTimer::timeout, this, &FCMavlinkSystem::update_alive); + m_alive_timer = std::make_unique(this); + QObject::connect(m_alive_timer.get(), &QTimer::timeout, this, &FCMavlinkSystem::update_alive); m_alive_timer->start(1000); } @@ -34,38 +33,16 @@ FCMavlinkSystem& FCMavlinkSystem::instance() { return *instance; } - -void FCMavlinkSystem::set_system(std::shared_ptr system) -{ - // The system is set once when discovered, then should not change !! - if(m_system!=nullptr){ - HUDLogMessagesModel::instance().add_message_warning("FC sys id conflict"); - return; - } - assert(m_system==nullptr); - m_system=system; - if(!m_system->has_autopilot()){ - qDebug()<<"FCMavlinkSystem::set_system WARNING no autopilot"; - } - const int tmp_sys_id=m_system->get_system_id(); - qDebug()<<"FCMavlinkSystem::set_system: FC SYS ID is:"<<(int)tmp_sys_id; - set_for_osd_sys_id(tmp_sys_id); - m_action=std::make_shared(system); - m_pass_thru=std::make_shared(system); - // must be manually enabled by the user to save resources - //m_mission=std::make_shared(system); - FCMavlinkSettingsModel::instance().set_system(system); -} - bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) { //qDebug()<<"FCMavlinkSystem::process_message"; - if(!m_system){ + const auto opt_set_sys_id=get_fc_sys_id(); + if(!opt_set_sys_id.has_value()){ qDebug()<<"WARNING the system must be set before FC model starts processing data"; return false; } - const auto fc_sys_id=get_fc_sys_id().value(); - if(fc_sys_id != msg.sysid){ + const auto set_sys_id=opt_set_sys_id.value(); + if(msg.sysid!=set_sys_id){ qDebug()<<"Do not pass messages not coming from the FC to the FC model"; return false; } @@ -89,13 +66,11 @@ bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) // heartbeat okay const auto info=opt_info.value(); set_flight_mode(info.flight_mode); - set_mav_type(info.mav_type); - set_autopilot_type(info.autopilot); - set_is_arducopter(info.is_arducopter); - set_is_arduvtol(m_is_arduvtol); - set_is_arduplane(info.is_arduplane); + set_autopilot_type_str(info.autopilot); + set_mav_type_str(info.mav_type); const bool armed=Telemetryutil::get_arm_mode_from_heartbeat(heartbeat); set_armed(armed); + FCAction::instance().set_ardupilot_mav_type(opt_info->ardupilot_mav_type); }else{ qDebug()<<"Weird heartbeat"; } @@ -165,11 +140,10 @@ bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) setDataStreamRate(MAV_DATA_STREAM_RAW_SENSORS, 2); setDataStreamRate(MAV_DATA_STREAM_RC_CHANNELS, 2); }*/ - //test_set_data_stream_rates(); break; } case MAVLINK_MSG_ID_PARAM_VALUE:{ - // handled by params mavsdk + // handled by XParam break; } case MAVLINK_MSG_ID_GPS_RAW_INT:{ @@ -207,7 +181,6 @@ bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) case MAVLINK_MSG_ID_ATTITUDE:{ mavlink_attitude_t attitude; mavlink_msg_attitude_decode (&msg, &attitude); - // Not handled by mavsdk telemetry callback(s) anymore m_n_messages_update_rate_mavlink_message_attitude++; const auto roll_deg=Telemetryutil::angle_mavlink_rad_to_degree(attitude.roll); const auto pitch_deg=Telemetryutil::angle_mavlink_rad_to_degree(attitude.pitch); @@ -280,46 +253,6 @@ bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) case MAVLINK_MSG_ID_SERVO_OUTPUT_RAW:{ break; } - case MAVLINK_MSG_ID_MISSION_CURRENT:{ - // https://mavlink.io/en/messages/common.html#MISSION_CURRENT - mavlink_mission_current_t mission_current; - mavlink_msg_mission_current_decode(&msg,&mission_current); - //qDebug()<<"Got MAVLINK_MSG_ID_MISSION_CURRENT"<(item.x)* 1e-7; - double lon=static_cast(item.y)* 1e-7; - double alt_m=100; - if(lat==0.0 || lon==0.0){ - //qDebug()<<"Weird mission item:"< FCMavlinkSystem::get_fc_sys_id() { - if(m_system){ - return m_system->get_system_id(); + if(!m_discovered){ + return std::nullopt; } - return std::nullopt; + auto sys_id=m_sys_id; + assert(sys_id>0); + return sys_id; } -void FCMavlinkSystem::telemetryStatusMessage(QString message, int level) { - //QOpenHD::instance().textToSpeech_sayMessage(message); +bool FCMavlinkSystem::set_system_id(int sys_id) +{ + if(sys_id<=0 || sys_id >= UINT8_MAX){ + qWarning()<<"Invalid sys id"; + return false; + } + m_sys_id=sys_id; + m_discovered=true; + set_for_osd_sys_id(sys_id); + return true; } void FCMavlinkSystem::updateFlightTimer() { @@ -867,195 +814,6 @@ void FCMavlinkSystem::updateWind(){ } } -void FCMavlinkSystem::arm_fc_async(bool arm) -{ - if(!m_action){ - qDebug()<<"No fc action module"; - HUDLogMessagesModel::instance().add_message_info("No FC"); - return; - } - qDebug()<<"FCMavlinkSystem::arm_fc_async "<<(arm ? "arm" : "disarm"); - // We listen for the armed / disarmed changes directly - auto cb=[this](mavsdk::Action::Result res){ - if(res!=mavsdk::Action::Result::Success){ - std::stringstream ss; - ss<<"ARM/Disarm failed:"<arm_async(cb); - }else{ - m_action->disarm_async(cb); - } -} - -void FCMavlinkSystem::send_return_to_launch_async() -{ //TODO ------this probably only works for px4--------- - if(!m_action){ - HUDLogMessagesModel::instance().add_message_info("No FC"); - return; - } - auto cb=[](mavsdk::Action::Result res){ - std::stringstream ss; - ss<<"send_return_to_launch: result: "<return_to_launch_async(cb); -} - -bool FCMavlinkSystem::send_command_reboot(bool reboot) -{ - if(!m_action){ - HUDLogMessagesModel::instance().add_message_info("No FC"); - return false; - } - mavsdk::Action::Result res{}; - if(reboot){ - res=m_action->reboot(); - }else{ - res=m_action->shutdown(); - } - if(res==mavsdk::Action::Result::Success){ - return true; - } - return false; -} - - -void FCMavlinkSystem::flight_mode_cmd(long cmd_msg) { - if(!m_pass_thru){ - HUDLogMessagesModel::instance().add_message_info("No FC"); - qDebug()<<"No fc pass_thru module"; - return; - } - if(cmd_msg<0){ - // We get the flight mode command from qml, something is wrong with it - std::stringstream ss; - ss<<"Invalid FM "<get_our_sysid(); - qDebug() << "flight_mode_cmd our comp id:" << _pass_thru->get_our_compid(); - qDebug() << "flight_mode_cmd target id:" << _pass_thru->get_target_sysid(); - qDebug() << "flight_mode_cmd target compid:" << _pass_thru->get_target_compid();*/ - mavsdk::MavlinkPassthrough::CommandLong cmd; - - cmd.command = MAV_CMD_DO_SET_MODE; - - cmd.target_sysid= m_pass_thru->get_target_sysid(); - cmd.target_compid=m_pass_thru->get_target_compid(); - - cmd.param1=MAV_MODE_FLAG_CUSTOM_MODE_ENABLED; - cmd.param2=cmd_msg; - cmd.param3=0; - cmd.param4=0; - cmd.param5=0; - cmd.param6=0; - cmd.param7=0; - - const auto res=m_pass_thru->send_command_long(cmd); - - //result is not really used right now as mavsdk will output errors - //----here for future use---- - if(res==mavsdk::MavlinkPassthrough::Result::Success){ - const auto msg="flight_mode_cmd Success!!"; - qDebug()<get_target_sysid(); - cmd.target_compid=m_pass_thru->get_target_compid(); - cmd.param1=MAVLINK_MSG_ID_HOME_POSITION; - const auto res=m_pass_thru->send_command_long(cmd); - if(res==mavsdk::MavlinkPassthrough::Result::Success){ - const auto msg="Request home Success!!"; - qDebug()<(m_system); - auto cb=[this](mavsdk::Mission::MissionProgress mp){ - //qDebug()<<"Mission progress: "<subscribe_mission_progress(cb); - const auto [res,plan]=m_mission->download_mission(); - if(res!=mavsdk::Mission::Result::Success){ - std::stringstream ss; - ss<<"Mission "<download_mission(); - if(res!=mavsdk::Mission::Result::Success){ - HUDLogMessagesModel::instance().add_message_info("Mission download failure"); - return; - } - HUDLogMessagesModel::instance().add_message_info("Mission download success");*/ - /*auto cb=[this](mavsdk::Mission::MissionProgress mp){ - //qDebug()<<"Mission progress: "<subscribe_mission_progress(cb);*/ -} - void FCMavlinkSystem::send_message_hud_connection(bool connected) { std::stringstream message; @@ -1086,46 +844,6 @@ void FCMavlinkSystem::update_alive() } } recalculate_efficiency(); - //test_set_data_stream_rates(); -} - -void FCMavlinkSystem::test_set_data_stream_rates() -{ - if(m_rate_success)return; - if(m_rate_n_times_tried>4){ - return; - } - qDebug()<<"test_set_data_stream_rates"; - if(!m_pass_thru){ - HUDLogMessagesModel::instance().add_message_info("No FC"); - qDebug()<<"No fc pass_thru module"; - return; - } - //qDebug() << "test_set_data_stream_rates:" << _pass_thru->get_target_sysid(); - //qDebug() << "test_set_data_stream_rates:" << _pass_thru->get_target_compid(); - mavsdk::MavlinkPassthrough::CommandLong cmd; - cmd.command = MAV_CMD_SET_MESSAGE_INTERVAL; - cmd.target_sysid= m_pass_thru->get_target_sysid(); - cmd.target_compid=m_pass_thru->get_target_compid(); - cmd.param1=MAVLINK_MSG_ID_ATTITUDE; // affects artificial horizon update rate - const int interval_us=std::chrono::duration_cast(std::chrono::milliseconds(10)).count(); - cmd.param2=interval_us; - const auto res=m_pass_thru->send_command_long(cmd); - if(res==mavsdk::MavlinkPassthrough::Result::Success){ - const auto msg="test_set_data_stream_rates() Success!!"; - qDebug()< #include #include -#include "../mavsdk_include.h" + +#include "../util/mavlink_include.h" + #include +#include +#include // Really nice, this way we don't have to write all the setters / getters / signals ourselves ! #include "../../../lib/lqtutils_master/lqtutils_prop.h" @@ -19,10 +23,8 @@ * the Flight Controller, but it is NOT a Flight Controller ;) * The corresponding qml element is called _fcMavlinkSystem. * - * NOTE: In the beginning, me (Consti10) and @luke experimented a bit with the mavsdk telemetry subscription feature - - * However, after some testing and discussion, we came to the conclusion that they are more annoying than - * usefull due to what seems to be a common lack of support for Ardupilot in MAVSDK. We parse the "broadcast" - * mavlink telemetry message(s) from the FC manually. + * NOTE: This is a c++ - write, qml - read only model - this greatly increases simplicity. + * You cannot 'talk' to the FC from here - use the action class for that. * * NOTE: When adding new values, please try and be specific about their unit - e.g. add a "volt" suffix if the value is in volts. */ @@ -37,13 +39,11 @@ class FCMavlinkSystem : public QObject // return true if we know what to do with this message type (aka this message type has been consumed) bool process_message(const mavlink_message_t& msg); // mavlink sys id of the FC. Pretty much always 1, but it is not a hard requirement that FC always use a sys id of 1. - // If the FC has not been discovered yet (mavsdk::system not yet set), return std::nullopt. + // If the FC has not been discovered yet, return std::nullopt. std::optional get_fc_sys_id(); // Set the mavlink system reference, once discovered. - // If we can get a telemetry value (e.g. the altitude) by subscribing to a mavlink message this is preferred over - // manually parsing the message, and we register the callbacks to mavsdk when this is called (since we need the "system" - // reference for it) - void set_system(std::shared_ptr system); + // NOTE: We only use the system to get broadcast message(s) (pass_through) and a few more things + bool set_system_id(int sys_id); public: // Stuff needs to be public for qt // These members can be written & read from c++, but are only readable from qml (which is a common recommendation for QT application(s)). // Aka we just set them in c++ by calling the setter declared from the macro, which then emits the changed signal if needed @@ -147,38 +147,19 @@ class FCMavlinkSystem : public QObject // This is not calculated by qopenhd, it comes from the vfr hud message L_RO_PROP(float,vertical_speed_indicator_mps,set_vertical_speed_indicator_mps,0) //m/s], positive is up // - L_RO_PROP(QString,mav_type,set_mav_type,"UNKNOWN"); - L_RO_PROP(QString,autopilot_type,set_autopilot_type,"UNKNOWN"); //R.n Generic (inav), ardu and pixhawk - // Set to true if this FC supports basic commands, like return to home usw - // R.N we only show those commands in the UI if this flag is set - // and the flag is set if the FC is PX4 or Ardupilot - // NOTE: this used to be done by .mav_type == "ARDUPLANE" ... in qml - please avoid that, just add another qt boolean here - // (for example is_copter, is_plane or similar) - L_RO_PROP(bool,supports_basic_commands,set_supports_basic_commands,true) - // These are for sending the right flight mode commands - // Weather it is any type of ardu-"copter,plane or vtol" - L_RO_PROP(bool,is_arducopter,set_is_arducopter,false); - L_RO_PROP(bool,is_arduplane,set_is_arduplane,false); - L_RO_PROP(bool,is_arduvtol,set_is_arduvtol,false); + L_RO_PROP(QString,mav_type_str,set_mav_type_str,"UNKNOWN"); + L_RO_PROP(QString,autopilot_type_str,set_autopilot_type_str,"UNKNOWN"); //R.n Generic (inav), ardu and pixhawk L_RO_PROP(QString, last_ping_result_flight_ctrl,set_last_ping_result_flight_ctrl,"NA") // update rate: here we keep track of how often we get the "MAVLINK_MSG_ID_ATTITUDE" messages. // (since it controlls the art. horizon). This is pretty much the only thing we perhaps need to manually set the update rate on L_RO_PROP(float,curr_update_rate_mavlink_message_attitude,set_curr_update_rate_mavlink_message_attitude,-1) // We expose the sys id for the OSD to show - note that this value should not be used by any c++ code L_RO_PROP(int,for_osd_sys_id,set_for_osd_sys_id,-1); - // TODO: We have 2 variables for the OSD to show - the current total n of waypoints and the current waypoint the FC is at. Depending on how things are broadcasted, - // The user might have to manually request the current total n of waypoints - // NOTE: the description "waypoints" is not exactly accurate, left in for now due to legacy reasons though - L_RO_PROP(int,mission_waypoints_current_total,set_mission_waypoints_current_total,-1); - L_RO_PROP(int,mission_waypoints_current,set_mission_waypoints_current,-1); - // Current mission type, verbose as string for the user - L_RO_PROP(QString,mission_current_type,set_mission_current_type,"Unknown"); L_RO_PROP(int,distance_sensor_distance_cm,set_distance_sensor_distance_cm,-1); // (GPS) reported time L_RO_PROP(quint64,sys_time_unix_usec,set_sys_time_unix_usec,0); L_RO_PROP(QString,sys_time_unix_as_str,set_sys_time_unix_as_str,"N/A"); public: - void telemetryStatusMessage(QString message, int level); void calculate_home_distance(); void calculate_home_course(); // Updates the flight time by increasing the time when armed @@ -209,16 +190,6 @@ class FCMavlinkSystem : public QObject void home_course_changed(int home_course); void home_heading_changed(int home_heading); private: - // NOTE: Null until system discovered - std::shared_ptr m_system=nullptr; - std::shared_ptr m_action=nullptr; - // We got rid of this submodule for a good reason (see above) - //std::shared_ptr _mavsdk_telemetry=nullptr; - std::shared_ptr m_pass_thru=nullptr; - // TODO: figure out if we shall use mission plugin (compatible with ardupilot) or not - // R.N: must be manually enabled by the user to save resources - std::shared_ptr m_mission=nullptr; - // other members bool m_armed = false; QString m_flight_mode = "------"; @@ -236,52 +207,18 @@ class FCMavlinkSystem : public QObject QElapsedTimer totalTime; QElapsedTimer flightTimeStart; - QTimer* m_flight_time_timer = nullptr; - - int m_mode = 0; - - int m_arm_disarm = 99; - - int m_reboot_shutdown=99; + std::unique_ptr m_flight_time_timer = nullptr; // - QTimer* m_alive_timer = nullptr; + std::unique_ptr m_alive_timer = nullptr; std::atomic m_last_heartbeat_ms = -1; std::atomic m_last_message_ms= -1; void update_alive(); std::chrono::steady_clock::time_point m_last_update_update_rate_mavlink_message_attitude=std::chrono::steady_clock::now(); int m_n_messages_update_rate_mavlink_message_attitude=0; -public: - // WARNING: Do not call any non-async send command methods from the same thread that is parsing the mavlink messages ! - // - // Try to change the arming state. - // The result (success/failure) is logged in the HUD once completed - Q_INVOKABLE void arm_fc_async(bool arm=false); - // Try to send a return to launch command. - // The result (success/failure) is logged in the HUD once completed - Q_INVOKABLE void send_return_to_launch_async(); - // return true on success, false otherwise - Q_INVOKABLE bool enable_disable_mission_updates(bool enable); - - // TODO document me - Q_INVOKABLE bool send_command_reboot(bool reboot); - // Sends a command to change the flight mode. Note that this is more complicated than it sounds at first, - // since copter and plane for example do have different flight mode enums. - // For RTL (which is really important) we have a extra impl. just to be sure - Q_INVOKABLE void flight_mode_cmd(long cmd_msg); - // Some FC stop sending home position when armed, re-request the home position - Q_INVOKABLE void request_home_position_from_fc(); - // This overwrites the current home lat / lon values with whatever the fc reported last as lat/lon - Q_INVOKABLE bool overwrite_home_to_current(); // ----------------------- private: void send_message_hud_connection(bool connected); void send_message_arm_change(bool armed); -private: - // The user can configure a specific rate for the artificial horizon updates in the UI - // (Since the artificial horizon may feel really sluggish otherwise, at least with the default update rate of mavlink, which is 10Hz) - void test_set_data_stream_rates(); - int m_rate_n_times_tried=0; - bool m_rate_success=false; private: static bool get_SHOW_FC_MESSAGES_IN_HUD(); // Used to calculate efficiency in mAh / km @@ -296,6 +233,9 @@ class FCMavlinkSystem : public QObject // log a warning int m_n_heartbeats=0; int m_n_attitude_messages=0; +private: + std::atomic m_discovered=false; + int m_sys_id=-1; }; diff --git a/app/telemetry/models/fcmessageintervalhelper.hpp b/app/telemetry/models/fcmessageintervalhelper.hpp deleted file mode 100644 index 93ec7ce47..000000000 --- a/app/telemetry/models/fcmessageintervalhelper.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef FCMESSAGEINTERVALHELPER_H -#define FCMESSAGEINTERVALHELPER_H - -#include -#include -#include -#include "../mavsdk_include.h" -#include -#include -#include "../../logging/hudlogmessagesmodel.h" -#include "qopenhdmavlinkhelper.hpp" - -/** - * We need to manually request / set the message interval(s) for specific messages on ardupilot - * (It doesn't send them by default). - * The way this works is not ideal in regards to reliability - but I implemented a similar pattern like QGroundControl here. - * Basically, call create_command_if_needed() and check_acknowledgement() every time a msg from the FC is received - * and at some point in the future, all rates should be set succesfully in almost all cases. - */ -class FCMessageIntervalHelper{ -public: - struct MessageInterval{ - // for which message - int msg_id; - // interval in hertz - int interval_hz; - }; - static mavlink_command_long_t create_cmd_set_msg_interval(int msg_type,int interval_us){ - mavlink_command_long_t command{}; - command.target_system=1; - command.target_component=0; - command.command=MAV_CMD_SET_MESSAGE_INTERVAL; - command.confirmation=0; - command.param1=msg_type; - command.param2=interval_us; - return command; - } - std::optional create_command_if_needed(){ - std::lock_guard lock(m_mutex); - // Can be disabled by the user - QSettings settings; - const bool set_mavlink_message_rates = settings.value("set_mavlink_message_rates",true).toBool(); - const bool mavlink_message_rates_high_speed=settings.value("mavlink_message_rates_high_speed",false).toBool(); - const bool mavlink_message_rates_high_speed_rc_channels=settings.value("mavlink_message_rates_high_speed_rc_channels",false).toBool(); - if(!set_mavlink_message_rates){ - return std::nullopt; - } - // Stop after we've tried setting a rate N times and never got an ack - if(m_n_times_already_sent>=20){ - if(!logged_once_fail){ - HUDLogMessagesModel::instance().add_message_info("Cannot set message interval(s)"); - logged_once_fail=true; - } - return std::nullopt; - } - // Send out once every second until done - const auto elapsed=std::chrono::steady_clock::now()-m_last_command; - if(elapsed lock(m_mutex); - if(msg.msgid==MAVLINK_MSG_ID_COMMAND_ACK){ - mavlink_command_ack_t ack; - mavlink_msg_command_ack_decode(&msg,&ack); - if(ack.command==MAV_CMD_SET_MESSAGE_INTERVAL && ack.result==MAV_RESULT_ACCEPTED && - ack.target_system== QOpenHDMavlinkHelper::get_own_sys_id()){ - qDebug()<<"Message interval acknowledged "< lock(m_mutex); - // Sets the state to 0, again set all the rate(s) again - - // Needed if the user changes any rate - m_last_successfully_set_rate=0; - m_n_times_already_sent=0; - logged_once_fail=false; - logged_once_success=false; - m_last_command=std::chrono::steady_clock::now(); - } -private: - static constexpr int RATE_LOW=1; // Once per second - static constexpr int RATE_MEDIUM=5; // 5 times per second - // Intervals are in Hertz - std::vector m_intervals={ - MessageInterval{MAVLINK_MSG_ID_SYS_STATUS,2}, // battery and more - MessageInterval{MAVLINK_MSG_ID_SYSTEM_TIME,1}, - MessageInterval{MAVLINK_MSG_ID_GPS_RAW_INT,1}, // we get hdop, vdop, usw from this - not lat /long though (they are from global position int,aka fused) - MessageInterval{MAVLINK_MSG_ID_ATTITUDE,30}, - MessageInterval{MAVLINK_MSG_ID_GLOBAL_POSITION_INT,2}, - MessageInterval{MAVLINK_MSG_ID_RC_CHANNELS,2}, - //MessageInterval{MAVLINK_MSG_ID_GPS_GLOBAL_ORIGIN,0}, - MessageInterval{MAVLINK_MSG_ID_VFR_HUD,2}, //(air) speed, climb, ... - MessageInterval{MAVLINK_MSG_ID_BATTERY_STATUS,1}, - MessageInterval{MAVLINK_MSG_ID_HOME_POSITION,1}, - MessageInterval{ MAVLINK_MSG_ID_WIND,1}, - MessageInterval{MAVLINK_MSG_ID_AOA_SSA,1}, - MessageInterval{MAVLINK_MSG_ID_SCALED_PRESSURE,1}, - MessageInterval{MAVLINK_MSG_ID_MISSION_CURRENT,1}, - // NOTE: We cannot set broadcast on this one ! - //MessageInterval{MAVLINK_MSG_ID_MISSION_ITEM_INT,1}, - //MessageInterval{0,0}, - //MessageInterval{0,0}, - }; - std::mutex m_mutex; - int m_last_successfully_set_rate=0; - int m_n_times_already_sent=0; - bool logged_once_fail=false; - bool logged_once_success=false; - std::chrono::steady_clock::time_point m_last_command=std::chrono::steady_clock::now(); -}; - -#endif // FCMESSAGEINTERVALHELPER_H diff --git a/app/telemetry/models/rcchannelsmodel.cpp b/app/telemetry/models/rcchannelsmodel.cpp index 450e81089..461ab2b88 100644 --- a/app/telemetry/models/rcchannelsmodel.cpp +++ b/app/telemetry/models/rcchannelsmodel.cpp @@ -1,6 +1,6 @@ #include "rcchannelsmodel.h" #include "qdebug.h" -#include "../qopenhdmavlinkhelper.hpp" +#include "../util/qopenhdmavlinkhelper.hpp" diff --git a/app/telemetry/models/wificard.cpp b/app/telemetry/models/wificard.cpp index 9aefe11b0..574ced1f7 100644 --- a/app/telemetry/models/wificard.cpp +++ b/app/telemetry/models/wificard.cpp @@ -1,13 +1,49 @@ #include "wificard.h" +#include + #include "../../logging/hudlogmessagesmodel.h" +#include "util/qopenhdmavlinkhelper.hpp" + + +static std::string wifi_card_type_to_string(const int card_type) { + switch (card_type) { + case 0: + return "RTL88X2AU_OHD"; + case 1: + return "RTL88X2BU_OHD"; + case 2: + return "RTL_88X2AU"; + case 3: + return "RTL_88X2BU"; + case 4: + return "ATHEROS"; + case 5: + return "MT_7921u"; + case 6: + return "RALINK"; + case 7: + return "INTEL"; + case 8: + return "BROADCOM"; + case 9: + default: + return "UNKNOWN"; + } +} + WiFiCard::WiFiCard(bool is_air,int card_idx,QObject *parent) : QObject{parent},m_is_air_card(is_air),m_card_idx(card_idx) { - + if(!m_is_air_card){ + m_alive_timer = std::make_unique(this); + QObject::connect(m_alive_timer.get(), &QTimer::timeout, this, &WiFiCard::update_alive); + m_alive_timer->start(1000); + } } + WiFiCard &WiFiCard::instance_gnd(int index) { if(index==0){ @@ -34,36 +70,61 @@ WiFiCard &WiFiCard::instance_air() void WiFiCard::process_mavlink(const mavlink_openhd_stats_monitor_mode_wifi_card_t &msg) { + m_last_mavlink_message=QOpenHDMavlinkHelper::getTimeMilliseconds(); set_alive(true); - set_curr_rx_rssi_dbm(msg.rx_rssi_1); + set_curr_rx_rssi_dbm(msg.rx_rssi); + set_curr_rx_rssi_dbm_antenna1(msg.rx_rssi_1); + set_curr_rx_rssi_dbm_antenna2(msg.rx_rssi_2); + set_n_received_packets(msg.count_p_received); set_packet_loss_perc(msg.curr_rx_packet_loss_perc); - if(m_tx_power >0 && m_tx_power!=msg.tx_power){ + if(m_tx_power >0 && m_tx_power!=msg.tx_power_current){ // TX power changed if(m_is_air_card){ std::stringstream ss; - ss<<"Air TX Power "<<(int)msg.tx_power; + ss<<"Air TX Power "<<(int)msg.tx_power_current; HUDLogMessagesModel::instance().add_message_info(ss.str().c_str()); }else{ // All gnd cards use the same tx power if(m_card_idx==0){ std::stringstream ss; - ss<<"GND TX Power "<<(int)msg.tx_power; + ss<<"GND TX Power "<<(int)msg.tx_power_current; HUDLogMessagesModel::instance().add_message_info(ss.str().c_str()); } } } - set_tx_power(msg.tx_power); + set_tx_power(msg.tx_power_current); + set_tx_power_armed(msg.tx_power_armed); + set_tx_power_disarmed(msg.tx_power_disarmed); const bool disconnected=msg.curr_status==1; if(disconnected){ const auto elapsed=std::chrono::steady_clock::now()-m_last_disconnected_warning; if(elapsed>=CARD_DISCONNECTED_WARNING_INTERVAL){ m_last_disconnected_warning=std::chrono::steady_clock::now(); std::stringstream message; + message<<(m_is_air_card ? "Air ":"Gnd "); message<<"Card "<<(int)msg.card_index<<" disconnected"; HUDLogMessagesModel::instance().add_message_warning(message.str().c_str()); } } + // Packets received in the last 1 second on this card + const auto diff=std::chrono::steady_clock::now()-m_last_packets_in_X_second_recalculation; + if(m_last_packets_in_X_second_value<=-1){ + m_last_packets_in_X_second_value=msg.count_p_received; + }else{ + if(diff>=std::chrono::seconds(1)){ + const int64_t delta=msg.count_p_received-m_last_packets_in_X_second_value; + set_n_received_packets_rolling(delta); + m_last_packets_in_X_second_value=msg.count_p_received; + m_last_packets_in_X_second_recalculation=std::chrono::steady_clock::now(); + } + } + set_card_type(msg.card_type); + set_card_type_as_string(wifi_card_type_to_string(msg.card_type).c_str()); + const int card_type=msg.card_type; + bool supported = false; + if(card_type==0 || card_type==1)supported=true; + set_card_type_supported(supported); } int WiFiCard::helper_get_gnd_curr_best_rssi() @@ -71,10 +132,19 @@ int WiFiCard::helper_get_gnd_curr_best_rssi() int best_rssi=-127; for(int i=0;i<4;i++){ auto& card=instance_gnd(i); - if(card.alive() && card.m_curr_rx_rssi_dbm>best_rssi){ - best_rssi=card.m_curr_rx_rssi_dbm; + const auto card_rssi=card.m_curr_rx_rssi_dbm; + if(card.alive() && card_rssi>best_rssi){ + best_rssi=card_rssi; } } return best_rssi; } +void WiFiCard::update_alive() +{ + const auto elapsed_since_last_message=QOpenHDMavlinkHelper::getTimeMilliseconds()-m_last_mavlink_message; + if(elapsed_since_last_message>5*1000){ + set_alive(false); + } +} + diff --git a/app/telemetry/models/wificard.h b/app/telemetry/models/wificard.h index 8582a20fa..e31e2a80b 100644 --- a/app/telemetry/models/wificard.h +++ b/app/telemetry/models/wificard.h @@ -1,10 +1,12 @@ #ifndef WIFICARD_H #define WIFICARD_H +#include #include +#include #include "../../../lib/lqtutils_master/lqtutils_prop.h" -#include "../mavsdk_include.h" +#include "../util/mavlink_include.h" // Stats unique per each connected (wifibroadcast) wfi card // Air has only one card, ground can have one or more card(s) @@ -17,6 +19,16 @@ class WiFiCard : public QObject L_RO_PROP(int,packet_loss_perc,set_packet_loss_perc,-1) L_RO_PROP(bool,is_active_tx,set_is_active_tx,false) L_RO_PROP(int,tx_power,set_tx_power,-1) + L_RO_PROP(int,tx_power_armed,set_tx_power_armed,-1) + L_RO_PROP(int,tx_power_disarmed,set_tx_power_disarmed,-1) + L_RO_PROP(int,n_received_packets_rolling,set_n_received_packets_rolling,0) + // A card might have more than one antenna + L_RO_PROP(int,curr_rx_rssi_dbm_antenna1,set_curr_rx_rssi_dbm_antenna1,-128) + L_RO_PROP(int,curr_rx_rssi_dbm_antenna2,set_curr_rx_rssi_dbm_antenna2,-128) + // + L_RO_PROP(int,card_type,set_card_type,-1) // -1 = no info available yet, otherwise, openhd card type (0..?) + L_RO_PROP(QString,card_type_as_string,set_card_type_as_string,"N/A") + L_RO_PROP(int,card_type_supported,set_card_type_supported,false) public: explicit WiFiCard(bool is_air,int card_idx,QObject *parent = nullptr); static constexpr int N_CARDS=4; @@ -33,8 +45,13 @@ class WiFiCard : public QObject static constexpr auto CARD_DISCONNECTED_WARNING_INTERVAL=std::chrono::seconds(3); const bool m_is_air_card; const int m_card_idx; - - + // On the OSD, we show how many packets were received on each card in X seconds intervals + std::chrono::steady_clock::time_point m_last_packets_in_X_second_recalculation=std::chrono::steady_clock::now(); + int64_t m_last_packets_in_X_second_value=-1; + // Card alive - ONLY USED FOR GROUND CARD(s) + std::unique_ptr m_alive_timer = nullptr; + std::atomic m_last_mavlink_message=0; + void update_alive(); }; #endif // WIFICARD_H diff --git a/app/telemetry/settings/documented_param.h b/app/telemetry/settings/documentedparam.cpp similarity index 55% rename from app/telemetry/settings/documented_param.h rename to app/telemetry/settings/documentedparam.cpp index dfd85c742..0370ff70d 100644 --- a/app/telemetry/settings/documented_param.h +++ b/app/telemetry/settings/documentedparam.cpp @@ -1,57 +1,40 @@ -#ifndef DOCUMENTED_PARAM_H -#define DOCUMENTED_PARAM_H - -#include "improvedintsetting.h" -#include "improvedstringsetting.h" -#include "param_names.h" - +#include "documentedparam.h" #include -#include - +#include +#include "param_names.h" // // Here we have the mapping (if available) and documentation for all parameters // // WAY BIGGER file than one would like, but there is no easy alternative -struct XParam{ - // each param has a unique name / ID. - // must be valid string - std::string param_name; - // if param is of type int, it can have a mapping with verbose user choices - std::optional improved_int; - // if param is of type string, it can have a mapping with verbose user choices - std::optional improved_string; - // it has a short description - std::string description; - // and this flag (if set) says changing the parameter requires a (manual) reboot - bool requires_reboot=false; - // and this flag (if set) says the parameter is read-only (cannot be changed) - bool is_read_only=false; -}; +using namespace DocumentedParam; // These are util methods for the most common cases for adding parameters to the stored parameters set -static void append_int(std::vector& list,std::string param_name,std::optional improved_int,std::string description,bool requires_reboot=false){ - list.push_back(XParam{param_name,improved_int,std::nullopt,description,requires_reboot}); +static void append_int(std::vector>& list,std::string param_name,ImprovedIntSetting improved_int,std::string description,bool requires_reboot=false){ + auto tmp=std::make_shared(param_name,improved_int,std::nullopt,description,requires_reboot,false); + list.push_back(tmp); } -static void append_string(std::vector& list,std::string param_name,std::optional improved_string,std::string description,bool requires_reboot=false){ - list.push_back(XParam{param_name,std::nullopt,improved_string,description,requires_reboot}); +static void append_string(std::vector>& list,std::string param_name,ImprovedStringSetting improved_string,std::string description,bool requires_reboot=false){ + auto tmp=std::make_shared(param_name,std::nullopt,improved_string,description,requires_reboot,false); + list.push_back(tmp); } -static void append_only_documented(std::vector& list,std::string param_name,std::string description,bool requires_reboot=false){ - list.push_back(XParam{param_name,std::nullopt,std::nullopt,description,requires_reboot}); +static void append_only_documented(std::vector>& list,std::string param_name,std::string description,bool requires_reboot=false){ + auto tmp=std::make_shared(param_name,std::nullopt,std::nullopt,description,requires_reboot,false); + list.push_back(tmp); } -static void append_documented_read_only(std::vector& list,std::string param_name,std::string description){ - list.push_back(XParam{param_name,std::nullopt,std::nullopt,description,false,true}); +static void append_documented_read_only(std::vector>& list,std::string param_name,std::string description){ + auto tmp=std::make_shared(param_name,std::nullopt,std::nullopt,description,false,true); + list.push_back(tmp); } - -static std::vector get_parameters_list(){ - std::vector ret; +static std::vector> get_parameters_list(){ + std::vector> ret; // These params do not exist, they are only for testing append_int(ret,"TEST_INT_0", ImprovedIntSetting::createEnumEnableDisable(), "test" - ); + ); { std::vector values{}; values.push_back("enum0"); @@ -68,7 +51,7 @@ static std::vector get_parameters_list(){ ImprovedIntSetting::createEnumEnableDisable(), "Recommend a matching bitrate to the encoder depending on the selected link parameters,and reduce bitrate on TX errors (failed injections). On by default, but only works on select cameras. On Cameras that" "don't support changing the bitrate / are bad at targeting a given bitrate, you have to adjust your link according to your camera needs." - ); + ); { std::vector values{}; values.push_back("Disable"); @@ -79,24 +62,30 @@ static std::vector get_parameters_list(){ ImprovedIntSetting::createEnum(values), "!! Advanced users only, changing this param can result in no connection !!. This param is not automatically synchronized between air/ground. " "Specifies the difference between the number of space-time streams and the number of spatial streams. Use +1 only if you have 2 antennas." - ); + ); } append_int(ret,openhd::WB_ENABLE_LDPC, ImprovedIntSetting::createEnumEnableDisable(), "!! Advanced users only, changing this param can result in no connection !!. This param is not automatically synchronized between air/ground. " "Enable Low density parity check. Needs to be supported by both your tx and rxes." - ); + ); append_int(ret,openhd::WB_ENABLE_SHORT_GUARD, ImprovedIntSetting::createEnum({"LONG_GI (default)","SHORT_GI"}), "!! Advanced users only !!. This param is not automatically synchronized between air/ground. A short guard intervall increases throughput, " "but increases packet collisions." - ); + ); append_int(ret,openhd::WB_PASSIVE_MODE, ImprovedIntSetting::createEnumEnableDisable(), "Enable passive mode if you want to use your GCS as a passive listener to an existing openhd air-ground link. E.g. if you want to tune into" "someone elses openhd link (if encryption is enabled, you need his encryption key) but not interfere with any RC/MAVLINK control." ); - + append_int(ret,openhd::WB_VIDEO_ENCRYPTION_ENABLE, + ImprovedIntSetting::createEnumEnableDisable(), + "Enable video encryption - by default, video is not encrypted (only validated) to save CPU performance (Telemetry is always encrypted though)." + "It is recommended to leave video encryption off unless you are using at least RPI 4 on air and are TOTALLY worried about someone listening to your video" + " - even with encryption disabled, it is not easy for an attacker to listen in on your openhd video " + "(and impossible to attack your video due to always on secure packet validation)." + ); { // Measurements of @Marcel Essers: //19: 10-12 mW @@ -110,49 +99,49 @@ static std::vector get_parameters_list(){ //55: 380-400 mW //58: 420-450 mW auto values_WB_TX_PWR_LEVEL=std::vector{ - {"LOW(~25mW)[22]",22}, - {"MEDIUM [37]",37}, - {"HIGH [53]",53}, - {"MAX1(!DANGER!)[58]",58}, - // Intentionally disabled, since it creates unusably high packet loss - // (e.g. the rf circuit is over-amplified) - //{"MAX2(!DANGER!)[63]",63}, - }; + {"LOW(~25mW)[22]",22}, + {"MEDIUM [37]",37}, + {"HIGH [53]",53}, + {"MAX1(!DANGER!)[58]",58}, + // Intentionally disabled, since it creates unusably high packet loss + // (e.g. the rf circuit is over-amplified) + //{"MAX2(!DANGER!)[63]",63}, + }; append_int(ret,openhd::WB_RTL8812AU_TX_PWR_IDX_OVERRIDE, ImprovedIntSetting(0,63,values_WB_TX_PWR_LEVEL), "NEW: Recommended to change TX_POWER_I_ARMED instead ! RTL8812AU TX power index (unitless). LOW:default,~25mW, legal in most countries." " NOTE: Too high power settings can overload your RF circuits and create packet loss/ destroy your card. Read the Wiki before changing the TX Power." " NOTE2: For high power cards, it is recommended to leave this param default and change TX_POWER_ARMED instead to avoid overheating on the bench." - ); + ); } { auto values_WB_TX_PWR_LEVEL_ARMED=std::vector{ - {"Disabled[0]",0}, - {"LOW(~25mW)[22]",22}, - {"MEDIUM [37]",37}, - {"HIGH [53]",53}, - {"MAX1(!DANGER!)[58]",58}, - // Intentionally disabled, since it creates unusably high packet loss - // (e.g. the rf circuit is over-amplified) - //{"MAX2(!DANGER!)[63]",63}, - }; + {"Disabled[0]",0}, + {"LOW(~25mW)[22]",22}, + {"MEDIUM [37]",37}, + {"HIGH [53]",53}, + {"MAX1(!DANGER!)[58]",58}, + // Intentionally disabled, since it creates unusably high packet loss + // (e.g. the rf circuit is over-amplified) + //{"MAX2(!DANGER!)[63]",63}, + }; append_int(ret,openhd::WB_RTL8812AU_TX_PWR_IDX_ARMED, ImprovedIntSetting(0,63,values_WB_TX_PWR_LEVEL_ARMED), "TX Power (in override indices units) that is applied when the FC is armed. When the FC is not armed, TX_POWER_I is applied." "When this param is disabled, TX_POWER_I is applied regardless if armed or not (default)." " This helps to avoid overheating of the WIFI card while openhd is powered on the bench / without airflow on the cards." - ); + ); } { auto default_values=std::vector{ - {"AUTO (Default)",0}, - {"FEC_K=8",8}, - {"FEC_K=10",10}, - {"FEC_K=12",12}, - {"FEC_K=16",16}, - {"FEC_K=20",20}, - {"FEC_K=50",50}, - }; + {"AUTO (Default)",0}, + {"FEC_K=8",8}, + {"FEC_K=10",10}, + {"FEC_K=12",12}, + {"FEC_K=16",16}, + {"FEC_K=20",20}, + {"FEC_K=50",50}, + }; append_int(ret,openhd::WB_VIDEO_FEC_BLOCK_LENGTH, ImprovedIntSetting(0,128,default_values), "Default AUTO (Uses biggest block sizes possible while not adding any latency).Otherwise: WB Video FEC block length, previous FEC_K. " @@ -160,24 +149,27 @@ static std::vector get_parameters_list(){ } { auto default_values=std::vector{ - {"DEFAULT",100}, - {"90%",90}, - {"80%",80}, - {"70%",70}, - }; + {"70% (lower)",70}, + {"80% (lower)",80}, + {"90% (slightly lower)",90}, + {"DEFAULT",100}, + {"110% (slightly higher)",110}, + {"120% (higher)",120}, + {"130% (higher)",130}, + }; append_int(ret,openhd::WB_VIDEO_RATE_FOR_MCS_ADJUSTMENT_PERC, ImprovedIntSetting(1,500,default_values), "Reduce used data rate per mcs index by fixed value (not needed in most cases)"); } { auto default_values=std::vector{ - {"10%",10}, - {"20% (low interf)",20}, - {"30%",30}, - {"40%",40}, - {"50% (high interf)",50}, - {"100%",100}, - }; + {"10%",10}, + {"20% (low interf)",20}, + {"30%",30}, + {"40%",40}, + {"50% (high interf)",50}, + {"100%",100}, + }; append_int(ret,openhd::WB_VIDEO_FEC_PERCENTAGE, ImprovedIntSetting(0,100,default_values), "WB Video FEC overhead, in percent. Increases link stability, but also the required link bandwidth (watch out for tx errors). " @@ -191,14 +183,14 @@ static std::vector get_parameters_list(){ append_int(ret,openhd::WB_CHANNEL_WIDTH, ImprovedIntSetting::createEnumSimple({val1,val2}), "!!!Editing this param manually without care will result in a broken link!!!" - ); + ); } append_int(ret,openhd::WB_MCS_INDEX, ImprovedIntSetting::createEnum({"MCS0","MCS1","MCS2","MCS3","MCS4(not rec)","MCS5(not rec)","MCS6(not rec)","MCS7(not rec)", "MCS8 (VHT0+2SS)", "MCS9 (VHT1+2SS)", "MCS10 (VHT2+2SS)", "MCS11 (VHT3+2SS)", }), "!!!Editing this param manually without care will result in a broken link!!!" - ); + ); append_int(ret,openhd::WB_MCS_INDEX_VIA_RC_CHANNEL, ImprovedIntSetting::createEnum({"Disable","Channel 1","CHannel 2","Channel 3","Channel 4","Channel 5", "Channel 6","Channel 7","Channel 8","Channel 9","Channel 10"}), @@ -222,16 +214,16 @@ static std::vector get_parameters_list(){ { { const auto choices_video_res_framerate=std::vector{ - "640x480@30", - "640x480@60", - "640x480@90", - //"960x720@30", - //"960x720@60", - "1280x720@30", - "1280x720@49", - "1280x720@60", - //"1440x1080@30", - "1920x1080@30", + "640x480@30", + "640x480@60", + "640x480@90", + //"960x720@30", + //"960x720@60", + "1280x720@30", + "1280x720@49", + "1280x720@60", + //"1440x1080@30", + "1920x1080@30", }; append_string(ret,"V_FORMAT",ImprovedStringSetting::create_from_keys_only(choices_video_res_framerate), "Video WIDTHxHEIGHT@FPS. You can enter any value you want here, but if you select a video format that is not supported by your camera, the video stream will stop"); @@ -243,10 +235,10 @@ static std::vector get_parameters_list(){ // SkyMaster HDR, // "normal" libcamera, explicitly set to imx708 detection only // SkyVision Pro, // "normal" libcamera, explicitly set to imx519 detection only NEEDS CUSTOM LIBCAMERA // LIBCAMERA_IMX477M, // "normal" libcamera, explicitly set to imx477 detection only NEEDS CUSTOM TUNING FILE - // LIBCAMERA_IMX477, // "normal" libcamera, explicitly set to imx477 detection only - // LIBCAMERA_IMX462, // "normal" libcamera, explicitly set to imx462 detection only - // LIBCAMERA_IMX327, // "normal" libcamera, explicitly set to imx327 detection only - // LIBCAMERA_IMX290, // "normal" libcamera, explicitly set to imx290 detection only + // LIBCAMERA_IMX477, // "normal" libcamera, explicitly set to imx477 detection only + // LIBCAMERA_IMX462, // "normal" libcamera, explicitly set to imx462 detection only + // LIBCAMERA_IMX327, // "normal" libcamera, explicitly set to imx327 detection only + // LIBCAMERA_IMX290, // "normal" libcamera, explicitly set to imx290 detection only // LIBCAMERA_AUTO, // standart libcamera with autodetect // LIBCAMERA_ARDUCAM_AUTO, // pivariety libcamera (arducam special)NEEDS CUSTOM LIBCAMERA // VEYE_2MP_CAMERAS // Veye IMX327 (never versions), VEYE series with 200W resolution @@ -254,81 +246,81 @@ static std::vector get_parameters_list(){ // VEYE_CSSC132, //Veye SC132 // VEYE_MVCAM, // Veye MV Cameras auto cam_config_items=std::vector{ - "Legacy(MMAL)", - "IMX462 Low Light Mini", - "SkyMaster HDR", - "SkyVision Pro", - "LIBCAMERA_IMX477M", - "LIBCAMERA_IMX477", - "LIBCAMERA_IMX462", - "LIBCAMERA_IMX327", - "LIBCAMERA_IMX290", - "LIBCAMERA_AUTO", - "LIBCAMERA_ARDUCAM_AUTO", - "VEYE_2MP_CAMERAS", - "VEYE_CSIMX307", - "VEYE_CSSC132", - "VEYE_MVCAM" + "Legacy(MMAL)", + "IMX462 Low Light Mini", + "SkyMaster HDR (708)", + "SkyVision Pro (519)", + "LIBCAMERA_IMX477M", + "LIBCAMERA_IMX477", + "LIBCAMERA_IMX462", + "LIBCAMERA_IMX327", + "LIBCAMERA_IMX290", + "LIBCAMERA_AUTO", + "LIBCAMERA_ARDUCAM_AUTO", + "VEYE_2MP_CAMERAS", + "VEYE_CSIMX307", + "VEYE_CSSC132", + "VEYE_MVCAM" }; append_int(ret,"V_OS_CAM_CONFIG", ImprovedIntSetting::createEnum(cam_config_items), "If your connected CSI camera is not detected (e.g. you see a dummy camera stream) you need to select the apropriate config here. " "Air will automatically reboot when you change this parameter" - ); + ); } append_int(ret,"VIDEO_CODEC", ImprovedIntSetting::createEnum( std::vector{"h264","h265","mjpeg"}), "Video codec. If your camera/ground station does not support HW accelerated encoding/decoding of the selected codec,it'l default to SW encode/decode. " "A reboot (air&ground) is recommended after changing this parameter." - ); + ); append_int(ret,"V_AIR_RECORDING", ImprovedIntSetting::createEnum( std::vector{"DISABLE","ENABLE","AUTO(armed)"}), "Record video data locally on your air unit. You can find the files under /home/openhd/Videos on the SD card and/or download them via the web ui." "When AUTO is set, air recording automatically starts (and stops) when you arm/disarm your drone (requires inav / ardupilot FC)." - ); + ); append_int(ret,"V_E_STREAMING", ImprovedIntSetting::createEnumEnableDisable(), "Enable / disable streaming for this camera. Note that this setting is persistent at run time - once you disable streaming for a camera, you won't have video" " until you re-enable streaming or reboot your air unit. On by default" - ); + ); { auto default_values=std::vector{ - {"2MBit/s",2}, - {"4MBit/s",4}, - {"6MBit/s",6}, - {"8MBit/s (default)",8}, - {"10MBit/s (high)",10}, - {"14MBit/s (high)",14}, - {"18MBit/s (high)",18}, - }; + {"2MBit/s",2}, + {"4MBit/s",4}, + {"6MBit/s",6}, + {"8MBit/s (default)",8}, + {"10MBit/s (high)",10}, + {"14MBit/s (high)",14}, + {"18MBit/s (high)",18}, + }; append_int(ret,"V_BITRATE_MBITS", ImprovedIntSetting(1,100,default_values), "Camera encoder bitrate, does not include FEC overhead. " "!! If variable bitrate is enabled (recommended), this value is ignored.!! Otherwise, you can manually set a fixed camera/encoder bitrate here. " "NOTE: If you are using a camera not listed on the OpenHD recommended cameras list, the bitrate might be fixed by the vendor and not changeable." - ); + ); append_only_documented(ret,"V_MJPEG_QUALITY", "Active if video codec== mjpeg. MJPEG has no encoder bitrate, only an arbitratry quality parameter (0..100)"); } { auto default_values=std::vector{ - {"2 (best recovery)",2}, - {"3 (best recovery)",3}, - {"5 (good recovery)",5}, - {"8 (good recovery)",8}, - {"10 (medium recovery)",10}, - {"15 (medium recovery)",15}, - {"20 (bad recovery)",20}, - }; + {"2 (best recovery)",2}, + {"3 (best recovery)",3}, + {"5 (good recovery)",5}, + {"8 (good recovery)",8}, + {"10 (medium recovery)",10}, + {"15 (medium recovery)",15}, + {"20 (bad recovery)",20}, + }; append_int(ret,"V_KEYFRAME_I", ImprovedIntSetting(0,100,default_values), "Keyframe / instantaneous decode refresh interval, in frames. E.g. if set to 15, every 15th frame will be a key frame. Higher values result in better image compression, but increase the likeliness of microfreezes." - ); + ); } append_int(ret,"V_FORCE_SW_ENC", ImprovedIntSetting::createEnumEnableDisable(), "Force SW encode for the given camera, only enable if your camera supports outputting an appropriate raw format." - ); + ); append_int(ret,"V_SWITCH_CAM", ImprovedIntSetting::createEnumEnableDisable(), "Requires reboot. Switch primary and secondary camera."); @@ -338,162 +330,162 @@ static std::vector get_parameters_list(){ append_int(ret,"V_HORIZ_FLIP", ImprovedIntSetting::createEnumEnableDisable(), "Flip video horizontally" - ); + ); append_int(ret,"V_VERT_FLIP", ImprovedIntSetting::createEnumEnableDisable(), "Flip video vertically" - ); + ); append_int(ret,"V_CAM_ROT_DEG", ImprovedIntSetting(0,270,{ - ImprovedIntSetting::Item{"0°(disable)",0}, - ImprovedIntSetting::Item{"90 (mmal only)°",90}, - ImprovedIntSetting::Item{"180°",180}, - ImprovedIntSetting::Item{"270°(mmal only)",270}}), + ImprovedIntSetting::Item{"0°(disable)",0}, + ImprovedIntSetting::Item{"90 (mmal only)°",90}, + ImprovedIntSetting::Item{"180°",180}, + ImprovedIntSetting::Item{"270°(mmal only)",270}}), "Rotate video by 90 degree increments" - ); + ); append_int(ret,"V_INTRA_REFRESH", ImprovedIntSetting(-1,2130706433,{ - ImprovedIntSetting::Item{"NONE",-1}, - ImprovedIntSetting::Item{"CYCLIC",0}, - ImprovedIntSetting::Item{"ADAPTIVE",1}, - ImprovedIntSetting::Item{"BOTH",2}, - ImprovedIntSetting::Item{"CYCLIC_ROWS",2130706433} - }), + ImprovedIntSetting::Item{"NONE",-1}, + ImprovedIntSetting::Item{"CYCLIC",0}, + ImprovedIntSetting::Item{"ADAPTIVE",1}, + ImprovedIntSetting::Item{"BOTH",2}, + ImprovedIntSetting::Item{"CYCLIC_ROWS",2130706433} + }), "Experimental,Default NONE, Type of Intra Refresh to use" - ); + ); append_int(ret,"V_N_CAMERAS", ImprovedIntSetting(1,2,{ImprovedIntSetting::Item{"SINGLE (default)",1},ImprovedIntSetting::Item{"DUALCAM",2}}), "Configure openhd for single / dualcam usage. The air unit will wait for a specific amount of time until it has found that many camera(s)," " if it cannot find enough camera(s) it creates as many dummy camera(s) as needec instead.", true - ); + ); append_only_documented(ret,"V_BRIGHTNESS","Image capture brightness, [0..100], default 50. Increase for a brighter Image. However, if available, it is recommended to tune AWB or EXP instead."); append_only_documented(ret,"V_ISO","ISO value to use (0 = Auto)"); { // rpicamsrc only for now auto gst_awb_modes=std::vector{ - "OFF", - "AUTO", - "SUNLIGHT", - "CLOUDY", - "SHADE", - "TUNGSTEN", - "FLUORESCENT", - "INCANDESCENT", - "FLASH", - "HORIZON" + "OFF", + "AUTO", + "SUNLIGHT", + "CLOUDY", + "SHADE", + "TUNGSTEN", + "FLUORESCENT", + "INCANDESCENT", + "FLASH", + "HORIZON" }; auto gst_exposure_modes=std::vector{ - "OFF", - "AUTO", - "NIGHT", - "NIGHTPREVIEW", - "BACKLIGHT", - "SPOTLIGHT", - "SPORTS", - "SNOW", - "BEACH", - "VERYLONG", - "FIXEDFPS", - "ANTISHAKE", - "FIREWORKS", + "OFF", + "AUTO", + "NIGHT", + "NIGHTPREVIEW", + "BACKLIGHT", + "SPOTLIGHT", + "SPORTS", + "SNOW", + "BEACH", + "VERYLONG", + "FIXEDFPS", + "ANTISHAKE", + "FIREWORKS", }; auto values_metering_mode=std::vector{ - "AVERAGE","SPOT","BACKLIST","MATRIX" + "AVERAGE","SPOT","BACKLIST","MATRIX" }; append_int(ret,"V_AWB_MODE", ImprovedIntSetting::createEnum(gst_awb_modes), "AWB Automatic white balance mode" - ); + ); append_int(ret,"V_EXP_MODE", ImprovedIntSetting::createEnum(gst_exposure_modes), "EXP Exposure mode" - ); + ); append_int(ret,"V_METERING_MODE", ImprovedIntSetting::createEnum(values_metering_mode), "Camera exposure metering mode to use. Default average." - ); + ); } { // libcamera only auto denoise_modes=std::vector{ - "AUTO", - "OFF", - "CDN_OFF", - "CDN_FAST", - "CDN_HQ", + "AUTO", + "OFF", + "CDN_OFF", + "CDN_FAST", + "CDN_HQ", }; append_int(ret,"DENOISE_INDEX_LC", ImprovedIntSetting::createEnum(denoise_modes), "Setting this to off reduces latency by ~1 Frame on the cost of slightly reduced image quality in dark situations." - ); + ); append_int(ret,"METERING_MODE_LC", ImprovedIntSetting::createEnum(std::vector{ - "centre (default)", "spot", "average", //crashes libcamera "custom" + "centre (default)", "spot", "average", //crashes libcamera "custom" }), "Libcamera Metering mode.") - ; + ; append_int(ret,"AWB_MODE_LC", ImprovedIntSetting::createEnum(std::vector{ - "auto (default)", "incandescent", "tungsten", "fluorescent", "indoor", "daylight", - "cloudy", "custom" + "auto (default)", "incandescent", "tungsten", "fluorescent", "indoor", "daylight", + "cloudy", "custom" }), "Libcamera AWB mode.") - ; + ; append_int(ret,"EXPOSURE_MODE_LC", ImprovedIntSetting::createEnum(std::vector{ - "normal (default)", "sport" + "normal (default)", "sport" }), "Libcamera exposure mode.") - ; + ; append_int(ret,"SHUTTER_US_LC", ImprovedIntSetting::createEnumSimple( - std::vector>{ - {"auto",0}, - {"example1 (1000us)",1000}, - {"example2 (5000us)",5000}, - {"example3 (16666us)",16666}, - {"example4 (33333us)",33333}, - }),"Libcamera shutter in microseconds. Normally seleceted automatically, but you can overwrite this value for more control."); + std::vector>{ + {"auto",0}, + {"example1 (1000us)",1000}, + {"example2 (5000us)",5000}, + {"example3 (16666us)",16666}, + {"example4 (33333us)",33333}, + }),"Libcamera shutter in microseconds. Normally seleceted automatically, but you can overwrite this value for more control."); append_int(ret,"CONTRAST_LC", ImprovedIntSetting::createEnumSimple( std::vector>{ - {"default(100)",100}, - {"higher (120)",120}, - {"higher (150)",150}, - {"lower(80)",80}, - {"lower (50)",50}, - }), + {"default(100)",100}, + {"higher (120)",120}, + {"higher (150)",150}, + {"lower(80)",80}, + {"lower (50)",50}, + }), "Libcamera contrast"); append_int(ret,"SATURATION_LC", ImprovedIntSetting::createEnumSimple( std::vector>{ - {"default(100)",100}, - {"higher (120)",120}, - {"higher (150)",150}, - {"lower(80)",80}, - {"lower (50)",50}, - }), + {"default(100)",100}, + {"higher (120)",120}, + {"higher (150)",150}, + {"lower(80)",80}, + {"lower (50)",50}, + }), "Libcamera saturation"); append_int(ret,"SHARPNESS_LC", ImprovedIntSetting::createEnumSimple( std::vector>{ - {"default(100)",100}, - {"higher (120)",120}, - {"higher (150)",150}, - {"lower(80)",80}, - {"lower (50)",50}, - }), + {"default(100)",100}, + {"higher (120)",120}, + {"higher (150)",150}, + {"lower(80)",80}, + {"lower (50)",50}, + }), "Libcamera sharpness"); append_int(ret,"EXPOSURE_LC", ImprovedIntSetting::createEnumSimple( std::vector>{ - {"default(0)",0}, - {"higher (5)",5}, - {"higher (10)",10}, - {"lower(-5)",-5}, - {"lower (-10)",-10}, - }), + {"default(0)",0}, + {"higher (5)",5}, + {"higher (10)",10}, + {"lower(-5)",-5}, + {"lower (-10)",-10}, + }), "Libcamera exposure value, [-10,10]"); } append_documented_read_only(ret,"V_CAM_TYPE","Detected camera type"); @@ -513,18 +505,18 @@ static std::vector get_parameters_list(){ { auto baud_rate_items=std::vector{ - {"9600",9600}, - {"19200",19200}, - {"38400",38400}, - {"57600",57600}, - {"115200",115200}, - {"230400",230400}, - {"460800",460800}, - {"500000",500000}, - {"576000",576000}, - {"921600",921600}, - {"1000000",1000000}, - }; + {"9600",9600}, + {"19200",19200}, + {"38400",38400}, + {"57600",57600}, + {"115200",115200}, + {"230400",230400}, + {"460800",460800}, + {"500000",500000}, + {"576000",576000}, + {"921600",921600}, + {"1000000",1000000}, + }; append_int(ret,"FC_UART_BAUD",ImprovedIntSetting(0,1000000,baud_rate_items), "RPI HW UART baud rate, needs to match the UART baud rate set on your FC"); } @@ -550,13 +542,13 @@ static std::vector get_parameters_list(){ // { auto fc_uart_conn_values=std::vector{ - {"disable",""}, - {"serial0","/dev/serial0"}, - {"serial1","/dev/serial1"}, - {"ttyUSB0","/dev/ttyUSB0"}, - {"ttyACM0","/dev/ttyACM0"}, - {"ttyACM1","/dev/ttyACM1"}, - {"ttyS7","/dev/ttyS7"} + {"disable",""}, + {"serial0","/dev/serial0"}, + {"serial1","/dev/serial1"}, + {"ttyUSB0","/dev/ttyUSB0"}, + {"ttyACM0","/dev/ttyACM0"}, + {"ttyACM1","/dev/ttyACM1"}, + {"ttyS7","/dev/ttyS7"} }; append_string(ret,"FC_UART_CONN",ImprovedStringSetting{fc_uart_conn_values}, "Telemetry FC<->Air unit. Make sure FC_UART_BAUD matches your FC. For rpi users, select serial0 for GPIO serial. " @@ -576,11 +568,11 @@ static std::vector get_parameters_list(){ {"EX3", "3,2,1,0,4,5,6,7"}, };*/ auto values=std::vector{ // 4,5,1,2,3 - {"default" ,"1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, - {"Steamdeck AETR","4,5,1,2,3,6,7,8,9,10,11,12,13,14,15,16,17,18"}, - {"EX1" ,"2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, - {"EX2" ,"2,1,4,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, - }; + {"default" ,"1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, + {"Steamdeck AETR","4,5,1,2,3,6,7,8,9,10,11,12,13,14,15,16,17,18"}, + {"EX1" ,"2,1,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, + {"EX2" ,"2,1,4,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18"}, + }; append_string(ret,"RC_CHAN_MAP",ImprovedStringSetting{values}, "Change which joystick 'channel' is taken for each RC channel. This is a list of numbers, where each number X at position N means take joystick input nr X for channel N." " For example, 1,4,... means take channel number 1 for the first channel, and channel number 4 for the second channel. Needs to have ! all! 18 channel elements seperated by a ','"); @@ -593,35 +585,133 @@ static std::vector get_parameters_list(){ return ret; } - -static std::map create_param_map(){ - auto tmp=get_parameters_list(); - std::map ret; - for(const auto& param:tmp){ - if(ret.find(param.param_name)!=ret.end()){ - qWarning("Param %s already exists !",param.param_name.c_str()); +static std::map> create_param_map(){ + //qWarning("Create param map"); + auto param_list=get_parameters_list(); + //qWarning("X"); + std::map> ret; + for(auto param:param_list){ + //qWarning("Y %s",param.param_name.c_str()); + if(ret.find(param->param_name)!=ret.end()){ + //qWarning("Param %s already exists !",param.param_name.c_str()); + assert(false); } - ret[param.param_name]=param; + ret[param->param_name]=param; //qDebug()<<"YY"< find_param(const std::string& param_name){ - //qDebug()<<"find_param"< cached=create_param_map(); - if(cached.find(param_name)!=cached.end()){ - XParam ret=cached.at(param_name); - //qDebug()<<"XXX Found "< DocumentedParam::find_param(const std::string ¶m_name) +{ + //qDebug()<<"find_param"<> cached=create_param_map(); + if(cached.find(param_name)!=cached.end()){ + auto ret=cached.at(param_name); + //qDebug()<<"XXX Found "<param_name.c_str(); + return *ret; + }else{ //qDebug()<<"XXX Didn't find "< DocumentedParam::get_improved_for_int(const std::string ¶m_id) +{ + const auto tmp=DocumentedParam::find_param(param_id); + if(tmp.has_value()){ + const auto& param=tmp.value(); + if(param.improved_int.has_value()){ + return param.improved_int.value(); + } + } + return std::nullopt; } +std::optional DocumentedParam::get_improved_for_string(const std::string param_id) +{ + const auto tmp=DocumentedParam::find_param(param_id); + if(tmp.has_value()){ + const auto& param=tmp.value(); + if(param.improved_string.has_value()){ + return param.improved_string.value(); + } + } + return std::nullopt; +} +std::optional DocumentedParam::int_param_to_enum_string_if_known(const std::string param_id, int value){ + const auto improved_opt=get_improved_for_int(param_id); + if(improved_opt.has_value()){ + const auto& improved=improved_opt.value(); + if(improved.has_enum_mapping()){ + return improved.value_to_string(value); + } + } + return std::nullopt; +} -#endif // DOCUMENTED_PARAM_H +std::optional DocumentedParam::string_param_to_enum_string_if_known(const std::string param_id, std::string value){ + const auto improved_opt=get_improved_for_string(param_id); + if(improved_opt.has_value()){ + const auto& improved=improved_opt.value(); + return improved.value_to_key(value); + } + return std::nullopt; +} + +bool DocumentedParam::requires_reboot(const std::string ¶m_name) +{ + const auto tmp=DocumentedParam::find_param(param_name); + if(tmp.has_value()){ + return tmp.value().requires_reboot; + } + return false; +} + +std::string DocumentedParam::get_short_description(const std::string ¶m_name) +{ + const auto tmp=DocumentedParam::find_param(param_name); + if(tmp.has_value()){ + return tmp.value().description.c_str(); + } + return "TODO"; +} + +// ---------- +static std::map get_whitelisted_params() +{ + std::map ret{}; + ret[openhd::WB_FREQUENCY]=nullptr; + ret[openhd::WB_CHANNEL_WIDTH]=nullptr; + ret[openhd::WB_MCS_INDEX]=nullptr; + ret["CONFIG_BOOT_AIR"]=nullptr; + ret[openhd::WB_MAX_FEC_BLOCK_SIZE_FOR_PLATFORM]=nullptr; + //ret[""]=nullptr; + return ret; +} + +bool DocumentedParam::is_param_whitelisted(const std::string ¶m_id) +{ + const auto tmp=get_whitelisted_params(); + if(tmp.find(param_id)!=tmp.end()){ + return true; + } + return false; +} diff --git a/app/telemetry/settings/documentedparam.h b/app/telemetry/settings/documentedparam.h new file mode 100644 index 000000000..8eb610e9e --- /dev/null +++ b/app/telemetry/settings/documentedparam.h @@ -0,0 +1,60 @@ +#ifndef DOCUMENTEDPARAM_H +#define DOCUMENTEDPARAM_H + +#include "improvedintsetting.h" +#include "improvedstringsetting.h" + +#include +#include + + + +namespace DocumentedParam { + +struct XParam{ + // each param has a unique name / ID. + // must be valid string + std::string param_name; + // if param is of type int, it can have a mapping with verbose user choices + std::optional improved_int; + // if param is of type string, it can have a mapping with verbose user choices + std::optional improved_string; + // it has a short description + std::string description; + // and this flag (if set) says changing the parameter requires a (manual) reboot + bool requires_reboot; + // and this flag (if set) says the parameter is read-only (cannot be changed) + bool is_read_only; + XParam(std::string param_name1,std::optional improved_int1,std::optional improved_string1, + std::string description1,bool requires_reboot1,bool is_read_only1): param_name(param_name1),improved_int(improved_int1), + improved_string(improved_string1),description(description1),requires_reboot(requires_reboot1),is_read_only(is_read_only1){ + } +}; + +std::optional find_param(const std::string& param_name); + +std::optional get_improved_for_int(const std::string& param_id); + +std::optional get_improved_for_string(const std::string param_id); + +std::optional int_param_to_enum_string_if_known(const std::string param_id,int value); +std::optional string_param_to_enum_string_if_known(const std::string param_id,std::string value); + +// By default, a param is not read-only +bool read_only(const std::string& param_name); + +// By default, a param doesn't require reboot +bool requires_reboot(const std::string& param_name); + +// Returns the param deocumentation if it exists, +// 'TODO' otherwise +std::string get_short_description(const std::string& param_name); + +// extra, we whitelist some params, aka they should not be exposed in the param list to the user +// (since they are changed in a different part in the UI) +bool is_param_whitelisted(const std::string& param_id); + +} + + +#endif // DOCUMENTEDPARAM_H diff --git a/app/telemetry/settings/improvedintsetting.cpp b/app/telemetry/settings/improvedintsetting.cpp index 0ac3e8015..e550f7665 100644 --- a/app/telemetry/settings/improvedintsetting.cpp +++ b/app/telemetry/settings/improvedintsetting.cpp @@ -1,11 +1,17 @@ #include "improvedintsetting.h" +#include + ImprovedIntSetting::ImprovedIntSetting(int min_value_int, int max_value_int, std::vector values_enum1): min_value_int(min_value_int),max_value_int(max_value_int), values_enum(values_enum1){ } +ImprovedIntSetting::ImprovedIntSetting(const ImprovedIntSetting &other): + min_value_int(other.min_value_int),max_value_int(other.max_value_int),values_enum(other.values_enum) +{ +} std::vector ImprovedIntSetting::convert_to_default_items(const std::vector &values){ std::vector ret{}; @@ -41,3 +47,28 @@ ImprovedIntSetting ImprovedIntSetting::createEnumSimple(std::vector ImprovedIntSetting::int_enum_values() const{ + QList ret; + for(const auto& item:values_enum){ + ret.append(item.value); + } + return ret; +} diff --git a/app/telemetry/settings/improvedintsetting.h b/app/telemetry/settings/improvedintsetting.h index 194b76cab..fc763658d 100644 --- a/app/telemetry/settings/improvedintsetting.h +++ b/app/telemetry/settings/improvedintsetting.h @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include // This is a "one type fits many class" where we can make changing specific int setting(s) @@ -25,9 +25,8 @@ class ImprovedIntSetting{ std::string name; int value; }; - ImprovedIntSetting(int min_value_int,int max_value_int,std::vector values_enum1); - ImprovedIntSetting()=default; + ImprovedIntSetting(const ImprovedIntSetting& other); public: // helper to create a mapping where first element =0,second element =1, ... static std::vector convert_to_default_items(const std::vector& values); @@ -47,33 +46,14 @@ class ImprovedIntSetting{ } // enum mapping, returns the int value as a string (wrapped in ?{..}) if we cannot find // a mapping for it - std::string value_to_string(int value)const{ - for(const auto& item:values_enum){ - if(item.value==value)return item.name; - } - std::stringstream ss; - ss<<"?{"< int_enum_values()const{ - QList ret; - for(const auto& item:values_enum){ - ret.append(item.value); - } - return ret; - } + std::string value_to_string(int value)const; + QStringList int_enum_keys()const; + QList int_enum_values()const; public: int min_value_int; int max_value_int; // wrapped int enum - std::vector values_enum; + std::vector values_enum{}; }; diff --git a/app/telemetry/settings/improvedstringsetting.cpp b/app/telemetry/settings/improvedstringsetting.cpp index 97c7bf705..cbfbc35d6 100644 --- a/app/telemetry/settings/improvedstringsetting.cpp +++ b/app/telemetry/settings/improvedstringsetting.cpp @@ -5,6 +5,10 @@ ImprovedStringSetting::ImprovedStringSetting(std::vector values):m_values_ } +ImprovedStringSetting::ImprovedStringSetting(const ImprovedStringSetting &other):m_values_enum(other.m_values_enum) +{ +} + ImprovedStringSetting ImprovedStringSetting::create_from_keys_only(const std::vector &keys){ std::vector values{}; for(const auto& key:keys){ diff --git a/app/telemetry/settings/improvedstringsetting.h b/app/telemetry/settings/improvedstringsetting.h index a9711c97c..0bacf24ba 100644 --- a/app/telemetry/settings/improvedstringsetting.h +++ b/app/telemetry/settings/improvedstringsetting.h @@ -18,7 +18,7 @@ class ImprovedStringSetting{ std::string value; }; ImprovedStringSetting(std::vector values); - ImprovedStringSetting()=default; + ImprovedStringSetting(const ImprovedStringSetting& other); // if key and value are the same static ImprovedStringSetting create_from_keys_only(const std::vector& keys); public: diff --git a/app/telemetry/settings/mavlinksettingsmodel.cpp b/app/telemetry/settings/mavlinksettingsmodel.cpp index e1cf58e3c..ba62a07d3 100644 --- a/app/telemetry/settings/mavlinksettingsmodel.cpp +++ b/app/telemetry/settings/mavlinksettingsmodel.cpp @@ -1,16 +1,17 @@ #include "mavlinksettingsmodel.h" #include "qdebug.h" -#include "../openhd_defines.hpp" -#include "param_names.h" -#include "documented_param.h" +#include "documentedparam.h" -#include "../../util/WorkaroundMessageBox.h" +#include "../../util/qopenhd.h" #include "improvedintsetting.h" #include "improvedstringsetting.h" +#include "util/openhd_defines.hpp" #include #include +#include "../action/impl/xparam.h" + MavlinkSettingsModel &MavlinkSettingsModel::instanceAirCamera() { static MavlinkSettingsModel* instanceAirCamera=new MavlinkSettingsModel(OHD_SYS_ID_AIR,OHD_COMP_ID_AIR_CAMERA_PRIMARY); @@ -40,216 +41,80 @@ MavlinkSettingsModel &MavlinkSettingsModel::instanceGround() return *instanceFc; }*/ -std::map MavlinkSettingsModel::get_whitelisted_params() +bool MavlinkSettingsModel::is_param_whitelisted(const std::string param_id)const { - std::map ret{}; - //dev_show_whitelisted_params QSettings settings; const auto dev_show_whitelisted_params =settings.value("dev_show_whitelisted_params", false).toBool(); if(dev_show_whitelisted_params){ // no param whitelisted - return ret; + return false; } - ret[openhd::WB_FREQUENCY]=nullptr; - ret[openhd::WB_CHANNEL_WIDTH]=nullptr; - ret[openhd::WB_MCS_INDEX]=nullptr; - ret["CONFIG_BOOT_AIR"]=nullptr; - ret[openhd::WB_MAX_FEC_BLOCK_SIZE_FOR_PLATFORM]=nullptr; - //ret[""]=nullptr; - return ret; -} - -bool MavlinkSettingsModel::is_param_whitelisted(const std::string param_id)const -{ if(param_id.empty()){ return false; } - const auto tmp=get_whitelisted_params(); - if(tmp.find(param_id)!=tmp.end()){ - return true; - } - return false; + return DocumentedParam::is_param_whitelisted(param_id); } -bool MavlinkSettingsModel::is_param_read_only(const std::string param_id)const -{ - // TODO ! - bool ret=false; - auto tmp=find_param(param_id); - if(tmp.has_value()){ - ret=tmp.value().is_read_only; - } - //qDebug()<<"Param"< get_improved_for_int(const std::string& param_id){ - auto tmp=find_param(param_id); - if(tmp.has_value()){ - XParam param=tmp.value(); - if(param.improved_int.has_value()){ - return param.improved_int.value(); - } - } - return std::nullopt; -} - -static std::optional get_improved_for_string(const std::string param_id){ - auto tmp=find_param(param_id); - if(tmp.has_value()){ - XParam param=tmp.value(); - if(param.improved_string.has_value()){ - return param.improved_string.value(); - } - } - return std::nullopt; -} - -static std::optional int_param_to_enum_string_if_known(const std::string param_id,int value){ - const auto improved_opt=get_improved_for_int(param_id); - if(improved_opt.has_value()){ - const auto& improved=improved_opt.value(); - if(improved.has_enum_mapping()){ - return improved.value_to_string(value); - } - } - return std::nullopt; -} -static std::optional string_param_to_enum_string_if_known(const std::string param_id,std::string value){ - const auto improved_opt=get_improved_for_string(param_id); - if(improved_opt.has_value()){ - const auto& improved=improved_opt.value(); - return improved.value_to_key(value); - } - return std::nullopt; -} - - MavlinkSettingsModel::MavlinkSettingsModel(uint8_t sys_id,uint8_t comp_id,QObject *parent) : QAbstractListModel(parent),m_sys_id(sys_id),m_comp_id(comp_id) { + qRegisterMetaType("ParamIntEnum"); + qRegisterMetaType("ParamStringEnum"); + + qRegisterMetaType("QtParamValue"); + qRegisterMetaType("QtParamSet"); //m_data.push_back({"VIDEO_WIDTH",0}); //m_data.push_back({"VIDEO_HEIGHT",1}); //m_data.push_back({"VIDEO_FPS",1}); + connect(this, &MavlinkSettingsModel::signal_ui_thread_replace_param_set, this, &MavlinkSettingsModel::ui_thread_replace_param_set); } -void MavlinkSettingsModel::set_param_client(std::shared_ptr system,bool autoload_all_params) +void MavlinkSettingsModel::set_ready() { - // only allow adding the param client once it is discovered, do not overwrite it once discovered. - // DO NOT REMOVE THIS NECCESSARY CHECK - this class is written under the assumption that the "param_client" pointer becomes valid - // at some point and then stays valid - assert(this->param_client==nullptr); - assert(system->get_system_id()==m_sys_id); - m_system=system; - param_client=std::make_shared(system,m_comp_id,true); - if(autoload_all_params){ - try_fetch_all_parameters(); - } + m_is_ready=true; } -bool MavlinkSettingsModel::try_fetch_all_parameters() +void MavlinkSettingsModel::try_refetch_all_parameters_async(bool log_result) { qDebug()<<"MavlinkSettingsModel::try_fetch_all_parameters()"; - if(param_client==nullptr){ - // not discovered yet - WorkaroundMessageBox::makePopupMessage("OHD System not found"); - return false; - } - if(param_client){ - // first, remove anything the QT model has cached - while(rowCount()>0){ - removeData(rowCount()-1); - } - qDebug()<<"Done removing old params"; - // now fetch all params using mavsdk (this talks to the OHD system(s). - //param_client->set_timeout(10); - const auto params=param_client->get_all_params(true); - // The order in which params show up is r.n controlled by how they are added here - - // TODO could be improved. For some reason, string params are generally the most important ones r.n, though - for(const auto& string_param:params.custom_params){ - MavlinkSettingsModel::SettingData data{QString(string_param.name.c_str()),string_param.value}; - addData(data); - } - for(const auto& int_param:params.int_params){ - MavlinkSettingsModel::SettingData data{QString(int_param.name.c_str()),int_param.value}; - addData(data); - } - if(!params.int_params.empty()){ - return true; - } - }else{ - // not dscovered yet - } - return false; -} - - -bool MavlinkSettingsModel::try_fetch_all_parameters_long_running() -{ - if(param_client==nullptr){ + if(!m_is_ready){ // not discovered yet - WorkaroundMessageBox::makePopupMessage("OHD System not found"); - return false; - } - const auto begin=std::chrono::steady_clock::now(); - while(std::chrono::steady_clock::now()-begin < std::chrono::seconds(8)){ - const auto success=try_fetch_all_parameters(); - if(success){ - return true; - }else{ - WorkaroundMessageBox::instance().set_text_and_show("Fetching parameters...",1); + if(log_result){ + QOpenHD::instance().show_toast("OHD System not found",false); } + return; } - return false; -} - -std::optional MavlinkSettingsModel::try_get_param_int_impl(const QString param_id) -{ - qDebug()<<"try_get_param_int_impl:"<get_param_int(param_id.toStdString()); - if(result.first==mavsdk::Param::Result::Success){ - auto new_value=result.second; - return new_value; + if(m_is_currently_busy){ + if(log_result){ + QOpenHD::instance().show_toast("Busy, please try again later",false); } + return; } - return std::nullopt; -} - -std::optional MavlinkSettingsModel::try_get_param_string_impl(const QString param_id) -{ - qDebug()<<"try_get_param_string_impl:"<get_param_custom(param_id.toStdString()); - if(result.first==mavsdk::Param::Result::Success){ - auto new_value=result.second; - return new_value; + m_is_currently_busy=true; + set_ui_is_busy(true); + set_curr_get_all_progress_perc(0); + auto cb=[this,log_result](XParam::GetAllParamResult result){ + if(result.success){ + const auto param_set=result.param_set; + remove_and_replace_param_set(param_set); + if(log_result){ + QOpenHD::instance().show_toast("Fetch all success",false); + } + }else{ + if(log_result){ + QOpenHD::instance().show_toast("Fetch all failed, is your uplink working ? Use the status view for more info..",true); + } } - } - return std::nullopt; -} - -bool MavlinkSettingsModel::try_refetch_parameter_int(QString param_id) -{ - qDebug()<<"try_fetch_parameter:"< extra_retransmit_params) +MavlinkSettingsModel::SetParamResult MavlinkSettingsModel::try_set_param_int_impl(const QString param_id, int value) { - if(!param_client)return SetParamResult::NO_CONNECTION; - if(extra_retransmit_params.has_value()){ - const double timeout_s=std::chrono::duration_cast(extra_retransmit_params.value().retransmit_timeout).count()/1000.0; - param_client->set_timeout(timeout_s); - param_client->set_n_retransmissions(extra_retransmit_params.value().n_retransmissions); - } - const auto result=param_client->set_param_int(param_id.toStdString(),value); - if(extra_retransmit_params.has_value()){ - // restores defaults - param_client->set_timeout(-1); - param_client->set_n_retransmissions(3); - } - if(result==mavsdk::Param::Result::ValueUnsupported)return SetParamResult::VALUE_UNSUPPORTED; - if(result==mavsdk::Param::Result::Timeout)return SetParamResult::NO_CONNECTION; - if(result==mavsdk::Param::Result::Success)return SetParamResult::SUCCESS; - if(result==mavsdk::Param::Result::ParamNameTooLong || result==mavsdk::Param::Result::ParamValueTooLong || result==mavsdk::Param::Result::WrongType){ - qDebug()<<"Improper use, fix your code!"; - } - return SetParamResult::UNKNOWN; + auto command=XParam::create_cmd_set_int(m_sys_id,m_comp_id,param_id.toStdString(),value); + m_is_currently_busy=true; + const auto result=XParam::instance().try_set_param_blocking(command); + m_is_currently_busy=false; + return result ? SetParamResult::SUCCESS : SetParamResult::NO_CONNECTION; } -MavlinkSettingsModel::SetParamResult MavlinkSettingsModel::try_set_param_string_impl(const QString param_id,QString value,std::optional extra_retransmit_params) +MavlinkSettingsModel::SetParamResult MavlinkSettingsModel::try_set_param_string_impl(const QString param_id,QString value) { - if(!param_client)return SetParamResult::NO_CONNECTION; - if(extra_retransmit_params.has_value()){ - const double timeout_s=std::chrono::duration_cast(extra_retransmit_params.value().retransmit_timeout).count()/1000.0; - param_client->set_timeout(timeout_s); - param_client->set_n_retransmissions(extra_retransmit_params.value().n_retransmissions); - } - const auto result=param_client->set_param_custom(param_id.toStdString(),value.toStdString()); - if(extra_retransmit_params.has_value()){ - // restores defaults - param_client->set_timeout(-1); - param_client->set_n_retransmissions(3); - } - if(result==mavsdk::Param::Result::ValueUnsupported)return SetParamResult::VALUE_UNSUPPORTED; - if(result==mavsdk::Param::Result::Timeout)return SetParamResult::NO_CONNECTION; - if(result==mavsdk::Param::Result::Success)return SetParamResult::SUCCESS; - if(result==mavsdk::Param::Result::ParamNameTooLong || result==mavsdk::Param::Result::ParamValueTooLong || result==mavsdk::Param::Result::WrongType){ - qDebug()<<"Improper use, fix your code!"; - } - return SetParamResult::UNKNOWN; + auto command=XParam::create_cmd_set_string(m_sys_id,m_comp_id,param_id.toStdString(),value.toStdString()); + m_is_currently_busy=true; + const auto result=XParam::instance().try_set_param_blocking(command); + m_is_currently_busy=false; + return result ? SetParamResult::SUCCESS : SetParamResult::NO_CONNECTION; } QString MavlinkSettingsModel::try_update_parameter_int(const QString param_id,int value) @@ -387,10 +224,10 @@ QVariant MavlinkSettingsModel::data(const QModelIndex &index, int role) const } return 1; } else if(role == ShortDescriptionRole){ - QString ret=get_short_description(data.unique_id); + QString ret=DocumentedParam::get_short_description(data.unique_id.toStdString()).c_str(); return ret; } else if(role ==ReadOnlyRole){ - return is_param_read_only({data.unique_id.toStdString()}); + return DocumentedParam::read_only(data.unique_id.toStdString()); } else return QVariant(); @@ -461,7 +298,7 @@ void MavlinkSettingsModel::addData(MavlinkSettingsModel::SettingData data) QString MavlinkSettingsModel::int_enum_get_readable(QString param_id, int value)const { - auto as_enum=int_param_to_enum_string_if_known(param_id.toStdString(),value); + auto as_enum=DocumentedParam::int_param_to_enum_string_if_known(param_id.toStdString(),value); if(as_enum.has_value()){ return QString(as_enum.value().c_str()); } @@ -472,7 +309,7 @@ QString MavlinkSettingsModel::int_enum_get_readable(QString param_id, int value) QString MavlinkSettingsModel::string_enum_get_readable(QString param_id,QString value) const { - auto as_enum=string_param_to_enum_string_if_known(param_id.toStdString(),value.toStdString()); + auto as_enum=DocumentedParam::string_param_to_enum_string_if_known(param_id.toStdString(),value.toStdString()); if(as_enum.has_value()){ return QString(as_enum.value().c_str()); } @@ -483,7 +320,7 @@ QString MavlinkSettingsModel::string_enum_get_readable(QString param_id,QString bool MavlinkSettingsModel::int_param_has_min_max(QString param_id) const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ // min max is a requirement for int param return true; @@ -493,7 +330,7 @@ bool MavlinkSettingsModel::int_param_has_min_max(QString param_id) const int MavlinkSettingsModel::int_param_get_min_value(QString param_id)const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ if(improved_opt->has_enum_mapping()){ return improved_opt->max_value_int; @@ -504,7 +341,7 @@ int MavlinkSettingsModel::int_param_get_min_value(QString param_id)const int MavlinkSettingsModel::int_param_get_max_value(QString param_id)const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ if(improved_opt->has_enum_mapping()){ return improved_opt->min_value_int; @@ -515,7 +352,7 @@ int MavlinkSettingsModel::int_param_get_max_value(QString param_id)const bool MavlinkSettingsModel::int_param_has_enum_keys_values(QString param_id)const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ if(improved_opt->has_enum_mapping()){ return true; @@ -526,7 +363,7 @@ bool MavlinkSettingsModel::int_param_has_enum_keys_values(QString param_id)const QStringList MavlinkSettingsModel::int_param_get_enum_keys(QString param_id) const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ const auto improved=improved_opt.value(); if(improved.has_enum_mapping()){ @@ -542,7 +379,7 @@ QStringList MavlinkSettingsModel::int_param_get_enum_keys(QString param_id) cons QList MavlinkSettingsModel::int_param_get_enum_values(QString param_id) const { - const auto improved_opt=get_improved_for_int(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); if(improved_opt.has_value()){ const auto improved=improved_opt.value(); if(improved.has_enum_mapping()){ @@ -554,10 +391,25 @@ QList MavlinkSettingsModel::int_param_get_enum_values(QString param_id) con return ret; } +MavlinkSettingsModel::ParamIntEnum MavlinkSettingsModel::int_param_get_enum(QString param_id) const +{ + ParamIntEnum ret{false,{},{}}; + const auto improved_opt=DocumentedParam::get_improved_for_int(param_id.toStdString()); + if(improved_opt.has_value()){ + auto improved=improved_opt.value(); + ret.values=improved.int_enum_values(); + ret.keys=improved.int_enum_keys(); + ret.valid=true; + return ret; + } + qDebug()<<"Error no enum mapping for this int param"; + return ret; +} + bool MavlinkSettingsModel::string_param_has_enum(QString param_id) const { - const auto improved_opt=get_improved_for_string(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_string(param_id.toStdString()); if(improved_opt.has_value()){ return true; } @@ -566,7 +418,7 @@ bool MavlinkSettingsModel::string_param_has_enum(QString param_id) const QStringList MavlinkSettingsModel::string_param_get_enum_keys(QString param_id) const { - const auto improved_opt=get_improved_for_string(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_string(param_id.toStdString()); if(improved_opt.has_value()){ return improved_opt->enum_keys(); } @@ -577,7 +429,7 @@ QStringList MavlinkSettingsModel::string_param_get_enum_keys(QString param_id) c QStringList MavlinkSettingsModel::string_param_get_enum_values(QString param_id) const { - const auto improved_opt=get_improved_for_string(param_id.toStdString()); + const auto improved_opt=DocumentedParam::get_improved_for_string(param_id.toStdString()); if(improved_opt.has_value()){ return improved_opt->enum_values(); } @@ -586,6 +438,21 @@ QStringList MavlinkSettingsModel::string_param_get_enum_values(QString param_id) return ret; } +MavlinkSettingsModel::ParamStringEnum MavlinkSettingsModel::string_param_get_enum(QString param_id) const +{ + ParamStringEnum ret{false,{},{}}; + const auto improved_opt=DocumentedParam::get_improved_for_string(param_id.toStdString()); + if(improved_opt.has_value()){ + auto improved=improved_opt.value(); + ret.values=improved.enum_values(); + ret.keys=improved.enum_keys(); + ret.valid=true; + return ret; + } + qDebug()<<"Error no enum mapping for this int param"; + return ret; +} + QString MavlinkSettingsModel::get_warning_before_safe(const QString param_id) { if(param_id=="V_OS_CAM_CONFIG"){ @@ -596,39 +463,47 @@ QString MavlinkSettingsModel::get_warning_before_safe(const QString param_id) bool MavlinkSettingsModel::get_param_requires_manual_reboot(QString param_id) { - auto tmp=find_param(param_id.toStdString()); - if(tmp.has_value()){ - return tmp.value().requires_reboot; - } - return false; -} - -bool MavlinkSettingsModel::set_param_keyframe_interval(int keyframe_interval) -{ - const auto ret=try_update_parameter_int("V_KEYFRAME_I",keyframe_interval); - if(ret=="")return true; - return false; + return DocumentedParam::requires_reboot(param_id.toStdString()); } -bool MavlinkSettingsModel::set_param_fec_percentage(int percent) +void MavlinkSettingsModel::remove_and_replace_param_set(const std::vector ¶m_set) { - const auto ret=try_update_parameter_int(openhd::WB_VIDEO_FEC_PERCENTAGE,percent); - if(ret=="")return true; - return false; -} - -bool MavlinkSettingsModel::set_param_video_resolution_framerate(QString res_str) -{ - const auto ret=try_update_parameter_string("V_FORMAT",res_str); - if(ret=="")return true; - return false; + // We might not be called from the UI thread, which is why we do the signal hoop to schedule something on + // the UI thread + const auto parsed_param_set=XParam::parse_server_param_set(param_set); + QtParamSet qt_param_set{}; + for(const auto& param:parsed_param_set){ + if(param.int_param.has_value()){ + auto tmp=QtParamValue{param.param_id.c_str(),param.int_param.value(),0}; + qt_param_set.param_set.push_back(tmp); + }else if(param.string_param.has_value()){ + auto tmp=QtParamValue{param.param_id.c_str(),param.string_param->c_str(),1}; + qt_param_set.param_set.push_back(tmp); + } + } + emit signal_ui_thread_replace_param_set(qt_param_set); } -QString MavlinkSettingsModel::get_short_description(const QString param_id)const +void MavlinkSettingsModel::ui_thread_replace_param_set(QtParamSet qt_param_set) { - auto tmp=find_param(param_id.toStdString()); - if(tmp.has_value()){ - return tmp.value().description.c_str(); + qDebug()<<"Replacing full param set, previous size:"<0){ + removeData(rowCount()-1); + } + qDebug()<<"Done removing old params"; + for(int i=0;i param_value; + if(param.type==0){ + int32_t value=param.param_value.value(); + param_value=value; + }else{ + QString value=param.param_value.value(); + param_value=value.toStdString(); + } + MavlinkSettingsModel::SettingData data{param_id,param_value}; + addData(data); } - return "TODO"; } diff --git a/app/telemetry/settings/mavlinksettingsmodel.h b/app/telemetry/settings/mavlinksettingsmodel.h index e1936ddcc..e84236e68 100644 --- a/app/telemetry/settings/mavlinksettingsmodel.h +++ b/app/telemetry/settings/mavlinksettingsmodel.h @@ -8,8 +8,8 @@ #include #include -#include "../mavsdk_include.h" - +#include "../util/mavlink_include.h" +#include "../../../lib/lqtutils_master/lqtutils_prop.h" // A QT wrapper around the mavlink extended / non-extended parameters protocoll on the client // (the side that changes parameter(s) provided by a specific system & component). @@ -34,59 +34,22 @@ class MavlinkSettingsModel : public QAbstractListModel // TODO theoretically, we could allow the user to change parameters on the FC itself // (aka what for example mission planner would show) //static MavlinkSettingsModel& instanceFC(); - - // parameters that need to be synchronized are white-listed - static std::map get_whitelisted_params(); - bool is_param_whitelisted(const std::string param_id)const; - // workaround read only - bool is_param_read_only(const std::string param_id)const; - explicit MavlinkSettingsModel(uint8_t sys_id,uint8_t comp_id,QObject *parent = nullptr); public: - // any instance of this class is only usable as soon as its corresponding system is set - void set_param_client(std::shared_ptr system,bool autoload_all_params=true); -private: - std::shared_ptr param_client=nullptr; - std::shared_ptr m_system=nullptr; + L_RO_PROP(int, curr_get_all_progress_perc,set_curr_get_all_progress_perc,-1); + // NOTE: This is only for the UI, not for c++ usage (non-atomic) + L_RO_PROP(bool,ui_is_busy,set_ui_is_busy,false) +public: + void set_ready(); public: - // Fetch a param value using mavsdk. Returns std::nullopt on failure, - // The param value otherwise. - // Does not update the cached parameter ! - std::optional try_get_param_int_impl(const QString param_id); - std::optional try_get_param_string_impl(const QString param_id); - // callable from QT. - // re-fetch all parameters from the server. Clears the cache, then re-fetches the whole parameter set. - Q_INVOKABLE bool try_fetch_all_parameters(); - - Q_INVOKABLE bool try_fetch_all_parameters_long_running(); - - // re-fetch a specific parameter from the server, Updates the parameter set accordingly. - Q_INVOKABLE bool try_refetch_parameter_int(QString param_id); - Q_INVOKABLE bool try_refetch_parameter_string(QString param_id); + // async with progress bar and result being prompted to the user + // re-fetches the complete param set - any changed values, types, ... are catched from it + Q_INVOKABLE void try_refetch_all_parameters_async(bool log_result=true); - // Set a param value using mavsdk. This means we send the "SET" command to the server - // and get its response (ok or rejected) or - in rare -cases - timeout. - // Returns true on success, false otherwise - // NOTE: This does not update the value cached in the QT model on the ground, use try_update_parameter..() instead - struct ExtraRetransmitParams{ - std::chrono::nanoseconds retransmit_timeout=std::chrono::milliseconds(500); - int n_retransmissions=3; - }; - // The error codes are a bit less than what mavsdk returns, since we can merge some of them into a "unknown-this should never happen" value - enum class SetParamResult{ - UNKNOWN, // Hints at a programmer's error - NO_CONNECTION, // Most likely all retransmitts failed, cannot be completely avoided - VALUE_UNSUPPORTED, // (openhd) rejected the param value, it is not valid / not supported by the HW - SUCCESS, //Param was successfully updated - }; - static std::string set_param_result_as_string(const SetParamResult& res); - SetParamResult try_set_param_int_impl(const QString param_id,int value,std::optional extra_retransmit_params=std::nullopt); - SetParamResult try_set_param_string_impl(const QString param_id,QString value,std::optional extra_retransmit_params=std::nullopt); - - // first updates the parameter on the server via MAVSDK (unless server rejects / rare timeout) - // then updates the internal cached parameter (if previous update was successfull). - // Kinda dirty, but since we use it from QML - returns an empty string "" on success, an error code otherwise + // first updates the parameter on the server via XParam (unless server rejects / rare timeout) + // then updates the internal cached parameter + // return is kinda dirty, but since we use it from QML - returns an empty string "" on success, an error code otherwise Q_INVOKABLE QString try_update_parameter_int(const QString param_id,int value); Q_INVOKABLE QString try_update_parameter_string(const QString param_id,QString value); @@ -127,6 +90,27 @@ public slots: QVector m_data; const uint8_t m_sys_id; const uint8_t m_comp_id; + bool is_param_whitelisted(const std::string param_id)const; + enum class SetParamResult{ + BUSY, // Too many params queued up + NO_CONNECTION, // Most likely all retransmitts failed, cannot be completely avoided + VALUE_UNSUPPORTED, // (openhd) rejected the param value, it is not valid / not supported by the HW + SUCCESS, //Param was successfully updated + }; + static std::string set_param_result_as_string(const SetParamResult& res); + SetParamResult try_set_param_int_impl(const QString param_id,int value); + SetParamResult try_set_param_string_impl(const QString param_id,QString value); +public: + struct ParamIntEnum{ + bool valid; + QStringList keys; + QList values; + }; + struct ParamStringEnum{ + bool valid; + QStringList keys; + QStringList values; + }; public: // These are for the UI to query more data about a specific params Q_INVOKABLE QString int_enum_get_readable(QString param_id,int value)const; @@ -144,11 +128,13 @@ public slots: // Should only be called when we actually have an enum mapping for this param Q_INVOKABLE QStringList int_param_get_enum_keys(QString param_id)const; Q_INVOKABLE QList int_param_get_enum_values(QString param_id)const; + Q_INVOKABLE ParamIntEnum int_param_get_enum(QString param_id)const; // similar to above, find a better solution Q_INVOKABLE bool string_param_has_enum(QString param_id)const; Q_INVOKABLE QStringList string_param_get_enum_keys(QString param_id)const; Q_INVOKABLE QStringList string_param_get_enum_values(QString param_id)const; + Q_INVOKABLE ParamStringEnum string_param_get_enum(QString param_id)const; // For some parameters, we have a string that is displayed to the user when he wants to edit this param // just too be sure he understands the risks @@ -156,18 +142,29 @@ public slots: Q_INVOKABLE QString get_warning_before_safe(QString param_id); Q_INVOKABLE bool get_param_requires_manual_reboot(QString param_id); - - // We have a special UI for changing the keyframe interval (a camera specific param) - // and for the fec percentage (a WB param) - Q_INVOKABLE bool set_param_keyframe_interval(int keyframe_interval); - Q_INVOKABLE bool set_param_fec_percentage(int percent); - Q_INVOKABLE bool set_param_video_resolution_framerate(QString res_str); - private: - QString get_short_description(QString param_id)const; - std::mutex m_update_all_async_mutex; - std::unique_ptr m_update_all_async_thread=nullptr; - + void remove_and_replace_param_set(const std::vector& param_set); +public: + struct QtParamValue{ + QString param_id; + QVariant param_value; + int type; + }; + struct QtParamSet{ + QVector param_set; + }; + //void ui_thread_replace_param_set(QVector param_set); + void ui_thread_replace_param_set(QtParamSet param_set); +signals: + void signal_ui_thread_replace_param_set(QtParamSet param_set); +private: + std::atomic m_is_ready=false; + std::atomic_bool m_is_currently_busy=false; }; +Q_DECLARE_METATYPE(MavlinkSettingsModel::ParamIntEnum); +Q_DECLARE_METATYPE(MavlinkSettingsModel::ParamStringEnum); +Q_DECLARE_METATYPE(MavlinkSettingsModel::QtParamValue); +Q_DECLARE_METATYPE(MavlinkSettingsModel::QtParamSet); + #endif // MavlinkSettingsModel_H diff --git a/app/telemetry/settings/param_names.h b/app/telemetry/settings/param_names.h index cae5e9948..2be18f662 100644 --- a/app/telemetry/settings/param_names.h +++ b/app/telemetry/settings/param_names.h @@ -16,6 +16,8 @@ static constexpr auto WB_VIDEO_FEC_PERCENTAGE="WB_V_FEC_PERC"; static constexpr auto WB_VIDEO_RATE_FOR_MCS_ADJUSTMENT_PERC="WB_V_RATE_PERC"; //wb_video_rate_for_mcs_adjustment_percent static constexpr auto WB_MAX_FEC_BLOCK_SIZE_FOR_PLATFORM="WB_MAX_D_BZ"; static constexpr auto WB_TX_POWER_MILLI_WATT="TX_POWER_MW"; +static constexpr auto WB_TX_POWER_MILLI_WATT_ARMED="TX_POWER_MW_ARM"; +static constexpr auto WB_VIDEO_ENCRYPTION_ENABLE="WB_VIDEO_ENCRYPT"; // annoying 16 char settings limit static constexpr auto WB_RTL8812AU_TX_PWR_IDX_OVERRIDE="TX_POWER_I"; static constexpr auto WB_RTL8812AU_TX_PWR_IDX_ARMED="TX_POWER_I_ARMED"; diff --git a/app/telemetry/settings/synchronizedsettings.cpp b/app/telemetry/settings/synchronizedsettings.cpp deleted file mode 100644 index e8f9392c0..000000000 --- a/app/telemetry/settings/synchronizedsettings.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "synchronizedsettings.h" - -#include "../models/aohdsystem.h" -#include "mavlinksettingsmodel.h" - -#include "../../util/WorkaroundMessageBox.h" -#include "../logging/hudlogmessagesmodel.h" -#include "../logging/logmessagesmodel.h" -#include -#include - - -SynchronizedSettings::SynchronizedSettings(QObject *parent) - : QObject{parent} -{ - -} - -SynchronizedSettings& SynchronizedSettings::instance() -{ - static SynchronizedSettings tmp; - return tmp; -} - -void SynchronizedSettings::validate_and_set_channel_mhz(int channel_mhz) -{ - if(channel_mhz<=1000){ - set_curr_channel_mhz(-1); - }else{ - set_curr_channel_mhz(channel_mhz); - } -} - -void SynchronizedSettings::validate_and_set_channel_width_mhz(int channel_width_mhz) -{ - if(channel_width_mhz==20 || channel_width_mhz==40){ - set_curr_channel_width_mhz(channel_width_mhz); - }else{ - set_curr_channel_width_mhz(-1); - } -} - -int SynchronizedSettings::get_param_int_air_and_ground_value(QString param_id) -{ - qDebug()<<"get_param_air_and_ground_value "< -#include -#include "../../../lib/lqtutils_master/lqtutils_prop.h" -#include "param_names.h" - -// Helper for settings that MUST STAY IN SYNC on the ground and air pi, since otherwise the wifibroadcast link is lost -// and the user needs to manually recover the link -// It does not fix the 2 general's problem (this is a unfixable problem) but it makes it really unlikely to happen. -// TODO: I think for these 3 (only) parameters that need to be kept in sync in evo, we should find a better way -// (e.g. exposing the param only on the ground, and having a more advanced wifibroadcast imlementation that establishes a connection/ -// handles frequency changes itself) -class SynchronizedSettings : public QObject -{ - Q_OBJECT -public: - explicit SynchronizedSettings(QObject *parent = nullptr); - - static SynchronizedSettings& instance(); - - // These are also in aohdsystem, their usage (and correct setting of them) is required here, too - L_RO_PROP(int,curr_channel_mhz,set_curr_channel_mhz,-1) - L_RO_PROP(int,curr_channel_width_mhz,set_curr_channel_width_mhz,-1); - -public: - void validate_and_set_channel_mhz(int channel); - void validate_and_set_channel_width_mhz(int channel_width_mhz); - - static constexpr auto PARAM_ID_WB_FREQ=openhd::WB_FREQUENCY; - static constexpr auto PARAM_ID_WB_CHANNEL_WIDTH=openhd::WB_CHANNEL_WIDTH; - static constexpr auto PARAM_ID_WB_MCS_INDEX=openhd::WB_MCS_INDEX; - - // Air and ground should always match, otherwise something weird has happenened. - // Note that this would be "really" weird, since on not matching params there should be no connectivitiy. - Q_INVOKABLE int get_param_int_air_and_ground_value(QString param_id); - Q_INVOKABLE int get_param_int_air_and_ground_value_freq(){ - return get_param_int_air_and_ground_value(PARAM_ID_WB_FREQ); - } - Q_INVOKABLE int get_param_int_air_and_ground_value_channel_width(){ - return get_param_int_air_and_ground_value(PARAM_ID_WB_CHANNEL_WIDTH); - } - - void change_param_air_and_ground(QString param_id,int value,bool allow_changing_without_connected_air_unit); - - Q_INVOKABLE void change_param_air_and_ground_frequency(int value){ - QSettings settings; - const bool qopenhd_allow_changing_ground_unit_frequency_no_sync = settings.value("qopenhd_allow_changing_ground_unit_frequency_no_sync",false).toBool(); - change_param_air_and_ground(PARAM_ID_WB_FREQ,value,qopenhd_allow_changing_ground_unit_frequency_no_sync); - } - - Q_INVOKABLE void change_param_air_and_ground_channel_width(int value){ - QSettings settings; - const bool qopenhd_allow_changing_ground_unit_channel_width_no_sync = settings.value("qopenhd_allow_changing_ground_unit_channel_width_no_sync",false).toBool(); - change_param_air_and_ground(PARAM_ID_WB_CHANNEL_WIDTH,value,qopenhd_allow_changing_ground_unit_channel_width_no_sync); - } - // MCS index does not need to match - 2.3.3 and upwards uses the lowest mcs index possible for uplink, and - // allows changing the MCS index of the downlink (e.g. the mcs index used for injecting packets on the air unit) - // @param use_hud: when true, errors are logged to the HUD, otherwise, they are logged via a message popup - Q_INVOKABLE void change_param_air_only_mcs(int value,bool use_hud); - Q_INVOKABLE int get_param_int_air_only_mcs(); -private: - void log_result_message(const std::string& result_message,bool use_hud); -}; - -#endif // SynchronizedSettings_H diff --git a/app/telemetry/settings/wblinksettingshelper.cpp b/app/telemetry/settings/wblinksettingshelper.cpp new file mode 100644 index 000000000..403ed549c --- /dev/null +++ b/app/telemetry/settings/wblinksettingshelper.cpp @@ -0,0 +1,492 @@ +#include "wblinksettingshelper.h" +#include "../action/impl/xparam.h" + +#include "../models/aohdsystem.h" + +#include "../logging/hudlogmessagesmodel.h" +#include +#include +#include "../action/ohdaction.h" +#include "../../util/qopenhd.h" + +static void tmp_log_result(bool enable,const std::string message){ + if(enable){ + HUDLogMessagesModel::instance().add_message_info(message.c_str()); + }else{ + qDebug()<<"Not logged to HUD:"<=8000){ + qDebug()<<"Invalid channel "< channels; + for(int i=0;i<60;i++){ + if(msg.channels[i]==0)break; + channels.push_back(msg.channels[i]); + } + if(channels.empty()){ + qDebug()<<"No valid channels from ground station - should never happen"; + return; + } + if(update_supported_channels(channels)){ + qDebug()<<"Supported channels changed"; + signal_ui_rebuild_model_when_possible(); + } +} + +void WBLinkSettingsHelper::process_message_openhd_wifibroadcast_analyze_channels_progress(const mavlink_openhd_wifbroadcast_analyze_channels_progress_t &msg) +{ + std::vector> analyzed_channels; + for(int i=0;i<30;i++){ + if(msg.channels_mhz[i]==0){ + break; + } + analyzed_channels.push_back(std::make_pair(msg.channels_mhz[i],msg.foreign_packets[i])); + } + if(analyzed_channels.size()==0){ + qDebug()<<"Perhaps malformed message analyze channels"; + return; + } + const uint16_t curr_channel_mhz=analyzed_channels[analyzed_channels.size()-1].first; + const uint16_t curr_channel_width_mhz=40; + const uint16_t curr_foreign_packets=analyzed_channels[analyzed_channels.size()-1].second; + { + std::stringstream ss; + ss<<"Analyzed "<<(int)curr_channel_mhz<<"@"<<(int)curr_channel_width_mhz; + //ss<<" Foreign:"<<(int)curr_foreign_packets<<"packets"; + ss<<" Progress:"<<(int)msg.progress_perc<<"%"; + HUDLogMessagesModel::instance().add_message_info(ss.str().c_str()); + } + //qDebug()<<"Got progress "<=100){ + ss<<"100%, Done"; + }else{ + ss<<(int)msg.progress_perc<<"%"; + } + qDebug()< openhd ground is either localhost or tcp, that should never be an issue + const auto command_gnd=XParam::create_cmd_set_int(OHD_SYS_ID_GROUND,OHD_COMP_ID_LINK_PARAM,param_id.toStdString(),value); + const bool ground_success=XParam::instance().try_set_param_blocking(command_gnd,std::chrono::milliseconds(200),5); + if(!ground_success){ + std::stringstream ss; + ss<<"Cannot change "< get_freq_descr(){ + std::vector ret{ + FrequencyItem{-1,2312,false,false,false}, + FrequencyItem{-1,2332,false,false,false}, + FrequencyItem{-1,2352,false,false,false}, + FrequencyItem{-1,2372,false,false,false}, + FrequencyItem{-1,2392,false,false,false}, + // ACTUAL 2G + FrequencyItem{1 ,2412,false,true,false}, + FrequencyItem{5 ,2432,false,true,false}, + FrequencyItem{9 ,2452,false,true,false}, + FrequencyItem{13,2472,false,true,false}, + FrequencyItem{14,2484,false,false,false}, + // ACTUAL 2G end + FrequencyItem{-1,2492,false,false,false}, + FrequencyItem{-1,2512,false,false,false}, + FrequencyItem{-1,2532,false,false,false}, + FrequencyItem{-1,2572,false,false,false}, + FrequencyItem{-1,2592,false,false,false}, + FrequencyItem{-1,2612,false,false,false}, + FrequencyItem{-1,2632,false,false,false}, + FrequencyItem{-1,2652,false,false,false}, + FrequencyItem{-1,2672,false,false,false}, + FrequencyItem{-1,2692,false,false,false}, + FrequencyItem{-1, 2712,false,false,false}, + // 5G begin + FrequencyItem{ 32,5160,false,false,false}, + FrequencyItem{ 36,5180,false,true ,false}, + FrequencyItem{ 40,5200,false,false,false}, + FrequencyItem{ 44,5220,false,true,false}, + FrequencyItem{ 48,5240,false,false,false}, + FrequencyItem{ 52,5260,true,true ,false}, + FrequencyItem{ 56,5280,true,false,false}, + FrequencyItem{ 60,5300,true,true,false}, + FrequencyItem{ 64,5320,true,false,false}, + // big break / part that is not allowed + FrequencyItem{100,5500,true,true,false}, + FrequencyItem{104,5520,true,false,false}, + FrequencyItem{108,5540,true,true,false}, + FrequencyItem{112,5560,true,false,false}, + FrequencyItem{116,5580,true,true,false}, + FrequencyItem{120,5600,true,false,false}, + FrequencyItem{124,5620,true,true,false}, + FrequencyItem{128,5640,true,false,false}, + FrequencyItem{132,5660,true,true,false}, + FrequencyItem{136,5680,true,false,false}, + FrequencyItem{140,5700,true,true,false}, + FrequencyItem{144,5720,true,false,false}, + // Here is the weird break + FrequencyItem{149,5745,false,true,true}, + FrequencyItem{153,5765,false,false,false}, + FrequencyItem{157,5785,false,true,true}, + FrequencyItem{161,5805,false,false,false}, + FrequencyItem{165,5825,false,true,true}, + // Depends + FrequencyItem{169,5845,false,false,false}, + FrequencyItem{173,5865,false,true,true}, + FrequencyItem{177,5885,false,false,false}, + FrequencyItem{181,5905,false,false,true} + }; + return ret; +} + +static FrequencyItem find_frequency_item(const int frequency){ + const auto frequency_items=get_freq_descr(); + for(const auto& item:frequency_items){ + if(item.frequency==frequency)return item; + } + return FrequencyItem{-1,-1,false,false,false}; +} + +static std::string spaced_string(int number){ + std::stringstream ss; + if(number<100)ss<<" "; + if(number<10)ss<<" "; + ss<(keyframe_interval),"KEYFRAME"); +} +void WBLinkSettingsHelper::set_param_video_resolution_framerate_async(bool primary,QString res_str) +{ + const std::string value=res_str.toStdString(); + const auto comp_id = primary ? OHD_COMP_ID_AIR_CAMERA_PRIMARY : OHD_COMP_ID_AIR_CAMERA_SECONDARY; + change_param_air_async(comp_id,"V_FORMAT",value,"VIDEO FORMAT"); +} +void WBLinkSettingsHelper::set_param_fec_percentage_async(int percent) +{ + change_param_air_async(OHD_COMP_ID_LINK_PARAM,openhd::WB_VIDEO_FEC_PERCENTAGE,static_cast(percent),"FEC PERCENTAGE"); +} +void WBLinkSettingsHelper::set_param_air_only_mcs_async(int mcs) +{ + change_param_air_async(OHD_COMP_ID_LINK_PARAM,openhd::WB_MCS_INDEX,static_cast(mcs),"MCS (RATE)"); +} + +bool WBLinkSettingsHelper::set_param_tx_power(bool ground,bool is_tx_power_index, bool is_for_armed_state, int value) +{ + qDebug()<<"set_param_tx_power "<<(is_tx_power_index ? "IDX" : "MW")<<" "<<(is_for_armed_state ? "ARMED" : "DISARMED")<<" "< supported_channels) +{ + std::lock_guard lock(m_supported_channels_mutex); + if(m_supported_channels!=supported_channels){ + m_supported_channels=supported_channels; + return true; + } + return false; +} + +bool WBLinkSettingsHelper::has_valid_reported_channel_data() +{ + std::lock_guard lock(m_supported_channels_mutex); + return !m_supported_channels.empty(); +} + +void WBLinkSettingsHelper::change_param_air_async(const int comp_id,const std::string param_id,std::variant param_value,const std::string tag) +{ + mavlink_param_ext_set_t command; + if(std::holds_alternative(param_value)){ + auto value=std::get(param_value); + command=XParam::create_cmd_set_int(OHD_SYS_ID_AIR,comp_id,param_id,value); + }else{ + auto value=std::get(param_value); + command=XParam::create_cmd_set_string(OHD_SYS_ID_AIR,comp_id,param_id,value); + } + if(!AOHDSystem::instanceAir().is_alive()){ + HUDLogMessagesModel::instance().add_message_warning("Air not alive - cannot change "+QString(tag.c_str())); + return; + } + auto res_cb=[tag,param_value](XParam::SetParamResult result){ + if(result.is_accepted()){ + std::stringstream ss; + ss<<"Changed "<(param_value)){ + ss<(param_value); + }else{ + ss<(param_value); + } + HUDLogMessagesModel::instance().add_message_info(ss.str().c_str()); + }else{ + std::stringstream ss; + ss<<"Cannot change "<(param_value)){ + ss<(param_value); + }else{ + ss<(param_value); + } + ss<<",please check uplink"; + HUDLogMessagesModel::instance().add_message_warning(ss.str().c_str()); + } + }; + const bool enqueue_success=XParam::instance().try_set_param_async(command,res_cb,nullptr,std::chrono::milliseconds(200),5); + if(!enqueue_success){ + HUDLogMessagesModel::instance().add_message_warning("Busy - cannot change "+QString(tag.c_str())+", try again later"); + return; + } +} + +void WBLinkSettingsHelper::change_param_air_channel_width_async(int value, bool log_result) +{ + if(!AOHDSystem::instanceAir().is_alive()){ + tmp_log_result(true,"Cannot change BW, AIR not alive"); + return; + } + change_param_air_async(OHD_COMP_ID_LINK_PARAM,PARAM_ID_WB_CHANNEL_WIDTH,static_cast(value),"BWIDTH"); +} + +QList WBLinkSettingsHelper::get_supported_frequencies() +{ + std::lock_guard lock(m_supported_channels_mutex); + QList ret; + for(auto& channel:m_supported_channels){ + ret.push_back(channel); + } + return ret; +} + +void WBLinkSettingsHelper::update_pollution(int frequency, int n_foreign_packets) +{ + std::lock_guard lock(m_pollution_elements_mutex); + auto search = m_pollution_elements.find(frequency); + if(search != m_pollution_elements.end()){ + m_pollution_elements[frequency].n_foreign_packets=n_foreign_packets; + }else{ + PollutionElement element{frequency,40,n_foreign_packets}; + m_pollution_elements.insert({frequency,element}); + } +} + +std::optional WBLinkSettingsHelper::get_pollution_for_frequency(int frequency) +{ + std::lock_guard lock(m_pollution_elements_mutex); + auto search = m_pollution_elements.find(frequency); + if(search != m_pollution_elements.end()){ + return search->second; + } + return std::nullopt; +} + +void WBLinkSettingsHelper::signal_ui_rebuild_model_when_possible() +{ + const bool valid_ground_channel_data=has_valid_reported_channel_data(); + if(m_curr_channel_mhz>0 && m_curr_channel_width_mhz>0 && valid_ground_channel_data){ + qDebug()<<"Signal UI Ready & should rebuild "< +#include +#include "../../../lib/lqtutils_master/lqtutils_prop.h" +#include "param_names.h" + +#include +#include + +#include "../util/mavlink_include.h" +#include "util/openhd_defines.hpp" + + +// DON'T ASK, THIS CLASS IS HUGE AND REALLY HARD TO DESCRIBE +// Helper for settings that MUST STAY IN SYNC on the ground and air pi, since otherwise the wifibroadcast link is lost +// and the user needs to manually recover the link +// It does not fix the 2 general's problem (this is a unfixable problem) but it makes it really unlikely to happen. +// TODO: I think for these 3 (only) parameters that need to be kept in sync in evo, we should find a better way +// (e.g. exposing the param only on the ground, and having a more advanced wifibroadcast imlementation that establishes a connection/ +// handles frequency changes itself) +class WBLinkSettingsHelper : public QObject +{ + Q_OBJECT +public: + explicit WBLinkSettingsHelper(QObject *parent = nullptr); + + static WBLinkSettingsHelper& instance(); + + // FLOW: Invalid until first message announcing channel frequency and width is received from the ground + // When we receive this type of message from the ground, we start requesting the supported frequencies from the ground unit + // (until successfully requested) + // Then we know the current frequency, channel width and which channels the ground is capable of - + // and are ready to populate the choices for the user. + // Whenever we want to change one of those params, we first try changing it on the air unit + // (Air unit will reject if it is not capable of the given frequency) + // and on success, we change the ground unit frequency. Since one can only select frequencies the ground unit supports, + // this should nevver fail. + + + // These are also in aohdsystem, their usage (and correct setting of them) is required here, too + L_RO_PROP(int,curr_channel_mhz,set_curr_channel_mhz,-1) + L_RO_PROP(int,curr_channel_width_mhz,set_curr_channel_width_mhz,-1); + + // Set to true once the channels from the ground have been succesfully fetched + //L_RO_PROP(bool,has_fetched_channels,set_has_fetched_channels,false); + // Dirty + L_RO_PROP(int,progress_scan_channels_perc,set_progress_scan_channels_perc,-1); + L_RO_PROP(int,progress_analyze_channels_perc,set_progress_analyze_channels_perc,-1); + L_RO_PROP(QString,text_for_qml,set_text_for_qml,"NONE"); + // Dirty, incremented to signal to the UI that it should rebuild the model(s) + L_RO_PROP(int,ui_rebuild_models,set_ui_rebuild_models,0) +public: + void process_message_openhd_wifibroadcast_supported_channels(const mavlink_openhd_wifbroadcast_supported_channels_t& msg); + void process_message_openhd_wifibroadcast_analyze_channels_progress(const mavlink_openhd_wifbroadcast_analyze_channels_progress_t& msg); + void process_message_openhd_wifibroadcast_scan_channels_progress(const mavlink_openhd_wifbroadcast_scan_channels_progress_t& msg); + void validate_and_set_channel_mhz(int channel); + void validate_and_set_channel_width_mhz(int channel_width_mhz); +public: + Q_INVOKABLE void set_simplify_channels(bool enable); + //Q_INVOKABLE void fetch_channels_if_needed(); + Q_INVOKABLE bool start_analyze_channels(); + // freq_bands: + // 0: 2.4G and 5.8G + // 1: 2.4G only + // 2: 5.8G only + // similar for channel widths + Q_INVOKABLE bool start_scan_channels(int freq_bands,int channel_widths); +private: + static constexpr auto PARAM_ID_WB_FREQ=openhd::WB_FREQUENCY; + static constexpr auto PARAM_ID_WB_CHANNEL_WIDTH=openhd::WB_CHANNEL_WIDTH; + // Returns 0 on success, error code otherwise. + // Viable error code(s): + // -1 : air unit reached, but rejected the value (value unsupported) + // -2 : air unit not reached + // -3 : really bad, we changed the value on air, but ground rejected + int change_param_air_and_ground_blocking(QString param_id,int value); + bool change_param_ground_only_blocking(QString param_id,int value); + void change_param_air_async(const int comp_id,const std::string param_id,std::variant param_value,const std::string tag); +public: + Q_INVOKABLE int change_param_air_and_ground_frequency(int value){ + return change_param_air_and_ground_blocking(PARAM_ID_WB_FREQ,value); + } + // + // Changing the channel width (bandwidth) is a bit special - read the documentation in openhd + // wb_link for more info. + // This method won't return any success / error message, since we update the UI not depending on the result, + // but what channel width the ground reports via broadcast + // + Q_INVOKABLE void change_param_air_channel_width_async(int value,bool log_result); + + Q_INVOKABLE bool change_param_ground_only_frequency(int value){ + return change_param_ground_only_blocking(PARAM_ID_WB_FREQ,value); + } + Q_INVOKABLE QList get_supported_frequencies(); + Q_INVOKABLE QString get_frequency_description(int frequency_mhz); + Q_INVOKABLE int get_frequency_pollution(int frequency_mhz); + Q_INVOKABLE bool get_frequency_radar(int frequency_mhz); + Q_INVOKABLE bool get_frequency_simplify(int frequency_mhz); + Q_INVOKABLE bool get_frequency_reccommended(int frequency_mhz); + // These params can be changed "on the fly" and are additionally their value(s) are broadcasted + // so we can update them completely async, log the result to the user + // and use the broadcasted value(s) to update the UI + Q_INVOKABLE void set_param_keyframe_interval_async(int keyframe_interval); + Q_INVOKABLE void set_param_fec_percentage_async(int percent); + Q_INVOKABLE void set_param_video_resolution_framerate_async(bool primary,QString res_str); + Q_INVOKABLE void set_param_air_only_mcs_async(int value); + // Extra + Q_INVOKABLE bool set_param_tx_power(bool ground,bool is_tx_power_index,bool is_for_armed_state,int value); +private: + struct SupportedChannel{ + uint16_t frequency; + int n_foreign_packets; + }; + // Written by telemetry, read by UI + std::mutex m_supported_channels_mutex; + std::vector m_supported_channels; + bool update_supported_channels(const std::vector supported_channels); + bool has_valid_reported_channel_data(); + std::atomic m_simplify_channels=false; +private: + struct PollutionElement{ + int frequency_mhz; + int width_mhz; + int n_foreign_packets; + }; + // Written by telemetry, read by UI + std::map m_pollution_elements; + std::mutex m_pollution_elements_mutex; + void update_pollution(int frequency,int n_foreign_packets); + std::optional get_pollution_for_frequency(int frequency); + void signal_ui_rebuild_model_when_possible(); +}; + +#endif // SynchronizedSettings_H diff --git a/app/telemetry/telemetry.pri b/app/telemetry/telemetry.pri index 20b937b4b..4341974b7 100644 --- a/app/telemetry/telemetry.pri +++ b/app/telemetry/telemetry.pri @@ -1,110 +1,59 @@ INCLUDEPATH += $$PWD -# NOTE: MAVSDK needs to be built and installed manually (since it doesn't support QMake's .pro) -# also note that mavlink (openhd flavour) comes with MAVSDK (since it is needed for building MAVSDK anyways) -# uncomment out below if you wanna use MAVSDK shared for some reason -#CONFIG += QOPENHD_LINK_MAVSDK_SHARED -QOPENHD_LINK_MAVSDK_SHARED { - # mavsdk needs to be built and installed locally with BUILD_SHARED_LIBS=ON - message(mavsdk shared) - message(not recommended and only supported on linux) - # We have the include path 2 times here, from MAVSDK docs: - # The mavsdk library installed via a .deb or .rpm file will be installed in /usr/ while the built library will be installed in /usr/local - INCLUDEPATH += /usr/local/include/mavsdk - INCLUDEPATH += /usr/include/mavsdk - LIBS += -L/usr/local/lib -lmavsdk -} else { - # mavsdk needs to be built and (semi-installed) locally with BUILD_SHARED_LIBS=OFF (aka as a static library) - # This is for packaging / releases / recommended for development, since we then have one fever package to install and no issues with updating - # QOpenHD and/or MAVSDK during development - # (compared to building and linking shared / dynamically) - message(mavsdk static (default)) - android { - message(mavsdk static android) - MAVSDK_PREBUILTS_PATH= $$PWD/../../lib/mavsdk_prebuilts - MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-arm/build/android-arm/install - #MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-arm64/build/android-arm64/install - - # Set the right folder for the compile arch - contains(ANDROID_TARGET_ARCH, armeabi-v7a) { - MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-arm/build/android-arm/install - } else:contains(ANDROID_TARGET_ARCH, arm64-v8a) { - MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-arm64/build/android-arm64/install - #MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-arm64-extra - } else:contains(ANDROID_TARGET_ARCH, x86_64) { - MAVSDK_PATH= $$MAVSDK_PREBUILTS_PATH/mavsdk-android-android-x86_64/build/android-x86_64/install - } else { - message(Unknown ANDROID_TARGET_ARCH $$ANDROID_TARGET_ARCH) - } - message(mavsdk path android) - message($$MAVSDK_PATH) - INCLUDEPATH += $$MAVSDK_PATH/include - LIBS += -L$$MAVSDK_PATH/lib/ -lmavsdk - #ANDROID_EXTRA_LIBS - } else: windows { - message(mavsdk static windows) - MAVSDK_PREBUILT_PATH = $$PWD/../../lib/mavsdk_prebuilts/windows/mavsdk-windows-x64-release - message(mavsdk path:) - message($$MAVSDK_PREBUILT_PATH) - MAVSDK_INCLUDE_PATH= $$MAVSDK_PREBUILT_PATH/include - MAVSDK_LIB_PATH= $$MAVSDK_PREBUILT_PATH/lib - INCLUDEPATH += $$MAVSDK_INCLUDE_PATH - LIBS += -L$$MAVSDK_LIB_PATH -lmavsdk - }else { - message(mavsdk static linux) - # We use the installation path mavsdk uses when it is built and installed on the host system - MAVSDK_INCLUDE_PATH= /usr/local/include/mavsdk - MAVSDK_LIB_PATH= /usr/local/lib/libmavsdk.a - exists($$MAVSDK_LIB_PATH){ - message(MAVSDK lib file exists) - }else{ - message(see the readme on how to build / install MAVSDK) - } - INCLUDEPATH += $$MAVSDK_INCLUDE_PATH - LIBS += -L$$MAVSDK_LIB_PATH -lmavsdk - # weird rpi - LIBS += -latomic - } -} +#INCLUDEPATH += $$PWD/../../lib/mavlink-headers SOURCES += \ + $$PWD/action/impl/cmdsender.cpp \ + $$PWD/action/fcaction.cpp \ + $$PWD/action/fcmissionhandler.cpp \ + $$PWD/action/fcmsgintervalhandler.cpp \ + $$PWD/action/ohdaction.cpp \ + $$PWD/connection/tcp_connection.cpp \ + $$PWD/connection/udp_connection.cpp \ $$PWD/models/fcmapmodel.cpp \ - $$PWD/models/fcmavlinksettingsmodel.cpp \ + $$PWD/settings/documentedparam.cpp \ + $$PWD/settings/wblinksettingshelper.cpp \ + $$PWD/action/impl/xparam.cpp \ app/telemetry/models/aohdsystem.cpp \ app/telemetry/models/camerastreammodel.cpp \ app/telemetry/models/rcchannelsmodel.cpp \ app/telemetry/models/wificard.cpp \ app/telemetry/settings/improvedintsetting.cpp \ app/telemetry/settings/improvedstringsetting.cpp \ - app/telemetry/settings/synchronizedsettings.cpp \ app/telemetry/MavlinkTelemetry.cpp \ app/telemetry/settings/mavlinksettingsmodel.cpp \ app/telemetry/models/fcmavlinksystem.cpp \ app/telemetry/models/fcmavlinkmissionitemsmodel.cpp \ HEADERS += \ - $$PWD/geodesi_helper.h \ - $$PWD/mavlink_enum_to_string.h \ + $$PWD/action/impl/cmdsender.h \ + $$PWD/action/create_cmd_helper.hpp \ + $$PWD/action/fcaction.h \ + $$PWD/action/fcmissionhandler.h \ + $$PWD/action/fcmsgintervalhandler.h \ + $$PWD/action/ohdaction.h \ + $$PWD/connection/tcp_connection.h \ + $$PWD/settings/documentedparam.h \ + $$PWD/action/impl/xparam.h \ + $$PWD/util/geodesi_helper.h \ + $$PWD/util/mavlink_enum_to_string.h \ + $$PWD//util/openhd_defines.hpp \ + $$PWD//util/qopenhdmavlinkhelper.hpp \ + $$PWD//util/telemetryutil.hpp \ + $$PWD/util/mavlink_include.h \ $$PWD/models/fcmapmodel.h \ - $$PWD/models/fcmavlinksettingsmodel.h \ - $$PWD/models/fcmessageintervalhelper.hpp \ - $$PWD/settings/documented_param.h \ - app/telemetry/mavsdk_helper.hpp \ - app/telemetry/mavsdk_include.h \ + $$PWD/settings/wblinksettingshelper.h \ app/telemetry/models/aohdsystem.h \ app/telemetry/models/camerastreammodel.h \ app/telemetry/models/rcchannelsmodel.h \ app/telemetry/models/wificard.h \ - app/telemetry/openhd_defines.hpp \ - app/telemetry/qopenhdmavlinkhelper.hpp \ app/telemetry/settings/improvedintsetting.h \ app/telemetry/settings/improvedstringsetting.h \ - app/telemetry/settings/synchronizedsettings.h \ - app/telemetry/telemetryutil.hpp \ app/telemetry/MavlinkTelemetry.h \ app/telemetry/settings/mavlinksettingsmodel.h \ app/telemetry/models/fcmavlinksystem.h \ app/telemetry/models/fcmavlinkmissionitemsmodel.h \ - app/telemetry/models/fcmessageintervalhelper.hpp \ -DEFINES += QOPENHD_HAS_MAVSDK_MAVLINK_TELEMETRY +WindowsBuild{ + LIBS += -lws2_32 +} diff --git a/app/telemetry/geodesi_helper.h b/app/telemetry/util/geodesi_helper.h similarity index 100% rename from app/telemetry/geodesi_helper.h rename to app/telemetry/util/geodesi_helper.h diff --git a/app/telemetry/mavlink_enum_to_string.h b/app/telemetry/util/mavlink_enum_to_string.h similarity index 92% rename from app/telemetry/mavlink_enum_to_string.h rename to app/telemetry/util/mavlink_enum_to_string.h index 33a6ced31..4d5daad7c 100644 --- a/app/telemetry/mavlink_enum_to_string.h +++ b/app/telemetry/util/mavlink_enum_to_string.h @@ -3,9 +3,11 @@ // A lot of bloat code -#include "../telemetry/mavsdk_include.h" +#include "mavlink_include.h" #include +#include + namespace qopenhd { static QString sub_mode_from_enum(SUB_MODE mode){ @@ -103,6 +105,8 @@ static QString copter_mode_from_enum(COPTER_MODE mode){ return "Avoid ADSB"; case COPTER_MODE_GUIDED: return "Guided"; + case COPTER_MODE_ZIGZAG: + return "ZIGZAG"; default: break; } @@ -377,6 +381,23 @@ static std::string detailed_battery_voltages_to_string(const uint16_t voltages[1 return ss.str(); } +static std::string mavlink_command_ack_to_string(const mavlink_command_ack_t& cmd_ack){ + std::stringstream ss; + ss<<"CMD_ACK{"<<(int)cmd_ack.target_system<<":"<<(int)cmd_ack.target_component<<":"< #include -#include "mavsdk_include.h" #include #include +#include "mavlink_include.h" namespace QOpenHDMavlinkHelper{ @@ -49,14 +49,6 @@ static double calclate_voltage_per_cell(double voltage){ return vehicle_battery_n_cells; } -// convert a mavlink message to the raw byte array that can be transmitted -static std::vector mavlinkMessageToSendBuffer(const mavlink_message_t& msg){ - std::vector buf(MAVLINK_MAX_PACKET_LEN); - auto size = mavlink_msg_to_send_buffer(buf.data(), &msg); - buf.resize(size); - return buf; -} - static QString safe_string(const char* text,int text_size){ QByteArray param_id(text,text_size); /* diff --git a/app/telemetry/telemetryutil.hpp b/app/telemetry/util/telemetryutil.hpp similarity index 96% rename from app/telemetry/telemetryutil.hpp rename to app/telemetry/util/telemetryutil.hpp index 0f68ecfb4..3166a1de4 100644 --- a/app/telemetry/telemetryutil.hpp +++ b/app/telemetry/util/telemetryutil.hpp @@ -4,11 +4,13 @@ #include #include -#include "../telemetry/mavsdk_include.h" -#include "../common/TimeHelper.hpp" -#include "../common/StringHelper.hpp" +#include "mavlink_include.h" +#include "../../common/TimeHelper.hpp" +#include "../../common/StringHelper.hpp" #include "mavlink_enum_to_string.h" #include +#include +#include // Various utility methods for telemetry that are static and take simple imputs. namespace Telemetryutil{ @@ -362,6 +364,8 @@ static int mavlink_rc_rssi_to_percent(uint8_t rssi){ struct HeartBeatInfo{ QString autopilot="Unknown"; QString flight_mode="Unknown"; + // Used to figure out ardupilot flight mode(s) + int ardupilot_mav_type=-1; QString mav_type="Unknown"; // These are for sending the right flight mode commands // Weather it is any type of "copter,plane or vtol" @@ -405,6 +409,7 @@ static std::optional parse_heartbeat(const mavlink_heartbeat_t& h }else if(type_and_flight_mode.is_vtol){ info.is_arduvtol=true; } + info.ardupilot_mav_type=uav_type; } } break; @@ -507,21 +512,6 @@ static QString min_max_avg_to_string(int32_t min,int32_t max,int32_t avg){ return QString(ss.str().c_str()); } -// OpenHD special -// We pack those 3 into a single uint8_t in the mavlink msg -struct StbcLpdcShortGuardBitfield { - unsigned int stbc:1; - unsigned int lpdc:1; - unsigned int short_guard:1; - unsigned int unused:5; -}__attribute__ ((packed)); -static_assert(sizeof(StbcLpdcShortGuardBitfield)==1); -static StbcLpdcShortGuardBitfield get_stbc_lpdc_shortguard_bitfield(uint8_t bitfield){ - StbcLpdcShortGuardBitfield ret{}; - std::memcpy((uint8_t*)&ret,&bitfield,1); - return ret; -} - } #endif // TELEMETRYUTIL_H diff --git a/app/util/WorkaroundMessageBox.cpp b/app/util/WorkaroundMessageBox.cpp index 14eb4a767..ff5cd17c5 100644 --- a/app/util/WorkaroundMessageBox.cpp +++ b/app/util/WorkaroundMessageBox.cpp @@ -28,7 +28,6 @@ void WorkaroundMessageBox::do_not_call_me_set_text_and_show(QString text,int opt if(optional_time_until_autoremove>0){ set_remaining_time_seconds(optional_time_until_autoremove); m_remove_after_delay_timer=std::make_unique(this); - //QTimer* timer_p=m_remove_after_delay_timer.get(); QObject::connect(m_remove_after_delay_timer.get(), &QTimer::timeout, this, &WorkaroundMessageBox::update_remaining_time); m_remove_after_delay_timer->start(1000); }else{ diff --git a/app/util/WorkaroundMessageBox.h b/app/util/WorkaroundMessageBox.h index 7e8736139..d75a9e136 100644 --- a/app/util/WorkaroundMessageBox.h +++ b/app/util/WorkaroundMessageBox.h @@ -29,12 +29,12 @@ class WorkaroundMessageBox: public QObject{ // @param optional_time_until_autoremove : // time until the message is automatically removed (for goggles users) - leave at -1 if the message shall stay there // until the user clicks okay - Q_INVOKABLE void set_text_and_show(QString text,int optional_time_until_autoremove=-1){ + Q_INVOKABLE void set_text_and_show(QString text,int optional_time_until_autoremove_seconds=-1){ // We might not be called from the UI thread, which is why we use the signal workaround - emit signal_set_text_and_show(text,optional_time_until_autoremove); + emit signal_set_text_and_show(text,optional_time_until_autoremove_seconds); } - static void makePopupMessage(QString text,int optional_time_until_autoremove=-1){ - WorkaroundMessageBox::instance().set_text_and_show(text,optional_time_until_autoremove); + static void makePopupMessage(QString text,int optional_time_until_autoremove_seconds=-1){ + WorkaroundMessageBox::instance().set_text_and_show(text,optional_time_until_autoremove_seconds); } public: L_RO_PROP(QString,text,set_text,"NONE"); diff --git a/app/util/qopenhd.cpp b/app/util/qopenhd.cpp index eb95cf9a7..e8780d35c 100644 --- a/app/util/qopenhd.cpp +++ b/app/util/qopenhd.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #endif #if defined(__android__) @@ -34,6 +35,8 @@ QOpenHD &QOpenHD::instance() QOpenHD::QOpenHD(QObject *parent) : QObject{parent} { + connect(this, &QOpenHD::signal_toast_add, this, &QOpenHD::do_not_call_toast_add); + #if defined(ENABLE_SPEECH) m_speech = new QTextToSpeech(this); QStringList engines = QTextToSpeech::availableEngines(); @@ -326,6 +329,17 @@ void QOpenHD::sysctl_openhd(int task) // not supported } +bool QOpenHD::is_valid_ip(QString ip) +{ +#ifdef __windows__ + //TODO fix windows + return true; +#else + return OHDUtil::is_valid_ip(ip.toStdString()); +#endif +} + + void QOpenHD::keep_screen_on(bool on) { #if defined(__android__) @@ -351,3 +365,43 @@ void QOpenHD::keep_screen_on(bool on) // Not needed on any other platform so far #endif //defined(__android__) } + +void QOpenHD::show_toast(QString message,bool long_toast) +{ + emit signal_toast_add(message,long_toast); +} + + +void QOpenHD::handle_toast_timeout() +{ + if(m_toast_message_queue.empty()){ + set_toast_text("I SHOULD NEVER APPEAR"); + set_toast_visible(false); + }else{ + auto front=m_toast_message_queue.front(); + m_toast_message_queue.pop_front(); + show_toast_and_add_remove_timer(front.text,front.long_toast); + } +} + +void QOpenHD::do_not_call_toast_add(QString text,bool long_toast) +{ + qDebug()<<"do_not_call_toast_add"< #endif +#include "../../lib/lqtutils_master/lqtutils_prop.h" + /** * Dirty, but for some reason stephen made translation(s) and a bit more work this way. * This singleton is for handling Appplication (QOpenHD, NOT OpenHD) - specific things that @@ -60,6 +62,16 @@ class QOpenHD : public QObject // runs systemctl start/stop/enable/disable openhd // opens error message if the openhd service file does not exist (e.g. false on all non linux platforms) Q_INVOKABLE void sysctl_openhd(int task); + + Q_INVOKABLE bool is_valid_ip(QString ip); + // + // Tries to mimic android toast as much as possible + // + Q_INVOKABLE void show_toast(QString message,bool long_toast=false); +public: + L_RO_PROP(QString,toast_text,set_toast_text,"NONE"); + L_RO_PROP(bool,toast_visible,set_toast_visible,false); +public: signals: void fontFamilyChanged(QString fontFamily); private: @@ -74,6 +86,19 @@ class QOpenHD : public QObject // We always want the screen to be kept "On" while QOpenHD is running - // but how to do that depends highly on the platform Q_INVOKABLE void keep_screen_on(bool on); +private: + struct ToastMessage{ + QString text; + bool long_toast; + }; + std::list m_toast_message_queue; + void handle_toast_timeout(); +public: +signals: + void signal_toast_add(QString text,bool long_toast); +private: + void do_not_call_toast_add(QString text,bool long_toast); + void show_toast_and_add_remove_timer(QString text,bool long_toast); }; #endif // QOPENHD_H diff --git a/app/videostreaming/avcodec/QSGVideoTextureItem.cpp b/app/videostreaming/avcodec/QSGVideoTextureItem.cpp index 76233054a..d15b44cec 100644 --- a/app/videostreaming/avcodec/QSGVideoTextureItem.cpp +++ b/app/videostreaming/avcodec/QSGVideoTextureItem.cpp @@ -6,7 +6,7 @@ #include #include -#include "../util/qrenderstats.h" +#include "util/qrenderstats.h" QSGVideoTextureItem::QSGVideoTextureItem(): diff --git a/app/videostreaming/avcodec/avcodec_decoder.cpp b/app/videostreaming/avcodec/avcodec_decoder.cpp index 7a74756e0..602486c2b 100644 --- a/app/videostreaming/avcodec/avcodec_decoder.cpp +++ b/app/videostreaming/avcodec/avcodec_decoder.cpp @@ -5,15 +5,14 @@ #include #include "avcodec_helper.hpp" -#include "../common/TimeHelper.hpp" +#include "common/TimeHelper.hpp" #include "common/util_fs.h" #include "texturerenderer.h" #include "decodingstatistcs.h" #include "common/SchedulingHelper.hpp" -#include "../util/WorkaroundMessageBox.h" -#include "../logging/hudlogmessagesmodel.h" -#include "../logging/logmessagesmodel.h" +#include "logging/hudlogmessagesmodel.h" +#include "logging/logmessagesmodel.h" #include "ExternalDecodeService.hpp" diff --git a/app/videostreaming/avcodec/texturerenderer.h b/app/videostreaming/avcodec/texturerenderer.h index 5236a531b..ce725b48f 100644 --- a/app/videostreaming/avcodec/texturerenderer.h +++ b/app/videostreaming/avcodec/texturerenderer.h @@ -11,7 +11,7 @@ #include "gl/gl_videorenderer.h" -#include "../common/TimeHelper.hpp" +#include "common/TimeHelper.hpp" class TextureRenderer : public QObject { diff --git a/app/videostreaming/vscommon/ExternalDecodeService.hpp b/app/videostreaming/vscommon/ExternalDecodeService.hpp index 1e44a8998..a637c9fb2 100644 --- a/app/videostreaming/vscommon/ExternalDecodeService.hpp +++ b/app/videostreaming/vscommon/ExternalDecodeService.hpp @@ -6,8 +6,8 @@ #include "common/util_fs.h" #include "decodingstatistcs.h" -#include <../logging/logmessagesmodel.h> -#include <../logging/hudlogmessagesmodel.h> +#include +#include #include diff --git a/before-install.sh b/before-install.sh new file mode 100644 index 000000000..7288ecf67 --- /dev/null +++ b/before-install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +#remove custom QT +dpkg -r openhd-qt-jammy || true diff --git a/build_install_mavsdk_static.sh b/build_install_mavsdk_static.sh deleted file mode 100755 index 52151446e..000000000 --- a/build_install_mavsdk_static.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# NOTE: requires cloning with --recurse-submodules -# Build and install MAVSDK - this needs to be done only once - -cd lib/MAVSDK || exit -cmake -Bbuild/default -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF -H. -cmake --build build/default -j4 -sudo cmake --build build/default --target install - -# Now the static libs are installed and can be found and statically linked by qmake \ No newline at end of file diff --git a/install_build_dep.sh b/install_build_dep.sh index c4fe9ece7..34d225b16 100755 --- a/install_build_dep.sh +++ b/install_build_dep.sh @@ -17,13 +17,10 @@ function install_pi_packages { PLATFORM_PACKAGES="" } function install_x86_packages { -PLATFORM_PACKAGES="" +PLATFORM_PACKAGES="qml-module-qt-labs-platform" } function install_rock_packages { PLATFORM_PACKAGES="qml-module-qt-labs-platform" -} -function install_mavsdk { -bash build_install_mavsdk_static.sh || exit 1 } # Add OpenHD Repository platform-specific packages @@ -67,6 +64,3 @@ bash build_install_mavsdk_static.sh || exit 1 # Installing python packages gem install fpm -# Building MAVSDK -install_mavsdk - diff --git a/lib/MAVSDK b/lib/MAVSDK deleted file mode 160000 index 0828f8a03..000000000 --- a/lib/MAVSDK +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0828f8a03b05754657af9da3b0bc206091f6e6e6 diff --git a/lib/Readme.md b/lib/Readme.md index 1949ff072..297b0ab89 100644 --- a/lib/Readme.md +++ b/lib/Readme.md @@ -1,9 +1,10 @@ Here are all the external libraries for QOpenHD -MAVSDK needs to be built and installed on your system to compile QOpenHD -The other libraries are built and included directly during the QOpenHD compilation process (qmake-compatible). +(Github submodules) + +Since QT is really bad at linking to non-qt libraries, we do not use any code / libraries we cannot build +diectly in this application (except ffmpeg / gstreamer for video) NOTE on mavlink: -We use a custom mavlink flavour (openhd). However, we also use MAVSDK for QOpenHD, and mavlink comes with MAVSDK by default. -Therefore, we don't have mavlink as a submodule here, but rather MAVSDK. +We use a custom mavlink flavour (openhd). diff --git a/lib/mavlink-headers b/lib/mavlink-headers new file mode 160000 index 000000000..58f77c0c0 --- /dev/null +++ b/lib/mavlink-headers @@ -0,0 +1 @@ +Subproject commit 58f77c0c003d241e6324f33a839d262dae497823 diff --git a/lib/mavsdk_prebuilts b/lib/mavsdk_prebuilts deleted file mode 160000 index c8a01a1d7..000000000 --- a/lib/mavsdk_prebuilts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c8a01a1d789099cbf66f542f5e7fb9438332064b diff --git a/package.sh b/package.sh index ab28378d2..363abaa8d 100755 --- a/package.sh +++ b/package.sh @@ -53,10 +53,7 @@ fi #Here are the dependencies depending on the platform if [[ "${DISTRO}" == "bullseye" ]] && [[ "${OS}" == "raspbian" ]] ; then #Raspberry -PLATFORM_PACKAGES="-d git -d openhd-userland -d libavcodec-dev -d libavformat-dev -d openhd-qt -d gst-plugins-good -d gst-openhd-plugins -d gstreamer1.0-gl" -elif [[ "${DISTRO}" == "jammy" ]] && [[ "${OS}" == "ubuntu" ]] && [[ "${PACKAGE_ARCH}" == "x86_64" ]]; then -#X86 -PLATFORM_PACKAGES="-d git -d qtgstreamer-plugins-qt5 -d gstreamer1.0-qt5 -d libgstreamer-plugins-base1.0-dev -d gstreamer1.0-plugins-good -d libavcodec-dev -d libavformat-dev -d openhd-qt-x86-jammy" +PLATFORM_PACKAGES="-d openhd-userland -d libavcodec-dev -d libavformat-dev -d openhd-qt -d gst-plugins-good -d gst-openhd-plugins -d gstreamer1.0-gl" else PLATFORM_PACKAGES="-d gstreamer1.0-gl -d qtgstreamer-plugins-qt5 -d gstreamer1.0-qt5 -d libqt5texttospeech5-dev -d qml-module-qt-labs-platform -d git -d libgstreamer-plugins-base1.0-dev -d gstreamer1.0-plugins-good -d libavcodec-dev -d libavformat-dev -d qml-module-qtquick-controls2 -d libqt5concurrent5 -d libqt5core5a -d libqt5dbus5 -d libqt5gui5 -d libqt5help5 -d libqt5location5 -d libqt5location5-plugins -d libqt5multimedia5 -d libqt5multimedia5-plugins -d libqt5multimediagsttools5 -d libqt5multimediawidgets5 -d libqt5network5 -d libqt5opengl5 -d libqt5opengl5-dev -d libqt5positioning5 -d libqt5positioning5-plugins -d libqt5positioningquick5 -d libqt5printsupport5 -d libqt5qml5 -d libqt5quick5 -d libqt5quickparticles5 -d libqt5quickshapes5 -d libqt5quicktest5 -d libqt5quickwidgets5 -d libqt5sensors5 -d libqt5sql5 -d libqt5sql5-sqlite -d libqt5svg5 -d libqt5test5 -d libqt5webchannel5 -d libqt5webkit5 -d libqt5widgets5 -d libqt5x11extras5 -d libqt5xml5 -d openshot-qt -d python3-pyqt5 -d python3-pyqt5.qtopengl -d python3-pyqt5.qtsvg -d python3-pyqt5.qtwebkit -d python3-pyqtgraph -d qml-module-qt-labs-settings -d qml-module-qtgraphicaleffects -d qml-module-qtlocation -d qml-module-qtpositioning -d qml-module-qtquick-controls -d qml-module-qtquick-dialogs -d qml-module-qtquick-extras -d qml-module-qtquick-layouts -d qml-module-qtquick-privatewidgets -d qml-module-qtquick-shapes -d qml-module-qtquick-window2 -d qml-module-qtquick2 -d qt5-gtk-platformtheme -d qt5-qmake -d qt5-qmake-bin -d qt5-qmltooling-plugins -d qtbase5-dev -d qtbase5-dev-tools -d qtchooser -d qtdeclarative5-dev -d qtdeclarative5-dev-tools -d qtpositioning5-dev -d qttranslations5-l10n" fi @@ -85,10 +82,11 @@ elif [[ "${PACKAGE_ARCH}" = "arm64" ]]; then cp rock_qt_eglfs_kms_config.json /tmp/qopenhd/usr/local/share/qopenhd/ || exit 1 fi -VERSION="2.3-evo-$(date '+%Y%m%d%H%M')-${VER2}" +VERSION="2.5-evo-$(date '+%Y%m%d%H%M')-${VER2}" rm ${PACKAGE_NAME}_${VERSION}_${PACKAGE_ARCH}.deb > /dev/null 2>&1 ls -a + fpm -a ${PACKAGE_ARCH} -s dir -t deb -n ${PACKAGE_NAME} -v ${VERSION} -C ${TMPDIR} \ -p qopenhd_VERSION_ARCH.deb \ --after-install after-install.sh \ diff --git a/platforms.pri b/platforms.pri index 480c9b74f..d6242d0ed 100644 --- a/platforms.pri +++ b/platforms.pri @@ -33,7 +33,7 @@ linux { DEFINES += __androidarm64__ } } else { - error("Compiler/platform not supported") + error("Compiler/platform not supported $$basename(QMAKESPEC)") } } diff --git a/qml/LICENSE_UI.txt b/qml/LICENSE_UI.txt deleted file mode 100644 index 53d1f3d01..000000000 --- a/qml/LICENSE_UI.txt +++ /dev/null @@ -1,675 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - diff --git a/qml/ui/configpopup/AboutPanel.qml b/qml/ui/configpopup/AboutPanel.qml deleted file mode 100644 index edcef3fac..000000000 --- a/qml/ui/configpopup/AboutPanel.qml +++ /dev/null @@ -1,385 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - - -Rectangle { - id: element2 - Layout.fillHeight: true - Layout.fillWidth: true - property alias license: license - - property int rowHeight: 64 - property int text_minHeight: 20 - - color: "#eaeaea" - - Card { - id: infoPanel - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.top: parent.top - anchors.topMargin: 12 - height: 128 - hasHeader: false - - cardBody: ColumnLayout { - Row { - spacing: 12 - leftPadding: 18 - - Image { - id: image - width: 48 - height: 48 - source: "qrc:/round.png" - fillMode: Image.PreserveAspectFit - anchors.verticalCenter: title.verticalCenter - } - - Text { - id: title - height: 48 - color: "#ff3a3a3a" - text: qsTr("QOpenHD-evo-2.5.0-beta") - font.pixelSize: 36 - } - } - - - Text { - id: qopenhd_version - width: 173 - height: 14 - color: "#ff3a3a3a" - text: QOPENHD_GIT_VERSION - font.pixelSize: 14 - leftPadding: 80 - } - - Text { - id: license - color: "#ff3a3a3a" - text: qsTr("License: GPLv3") - onLinkActivated: { - Qt.openUrlExternally("https://github.com/OpenHD/QOpenHD/blob/master/LICENSE") - } - font.pixelSize: 14 - leftPadding: 80 - } - - Text { - id: qopenhd_commit_hash - width: 173 - height: 14 - color: "#ff3a3a3a" - text: QOPENHD_GIT_COMMIT_HASH - font.pixelSize: 14 - leftPadding: 80 - } - } - } - - - RowLayout { - id: groundAndAirCardsId - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.top: infoPanel.bottom - anchors.topMargin: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - spacing: 6 - height: airBox.height - - - Card { - id: airBox - height: 128 - Layout.fillWidth: true - - cardName: qsTr("Air") - cardBody: - ColumnLayout { - // from https://doc.qt.io/qt-6/qml-qtquick-layouts-rowlayout.html - anchors.fill: parent - spacing: 2 - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("OpenHD Version:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemAir.openhd_version - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Last Ping:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemAir.last_ping_result_openhd - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Alive: ") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemAir.is_alive ? "Yes" : "No" - color: _ohdSystemAir.is_alive ? "GREEN" : "RED" - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - } - } - - Card { - id: groundBox - height: 128 - Layout.fillWidth: true - cardName: qsTr("Ground") - cardBody: - ColumnLayout { - // from https://doc.qt.io/qt-6/qml-qtquick-layouts-rowlayout.html - anchors.fill: parent - spacing: 2 - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("OpenHD Version:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemGround.openhd_version - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Last Ping:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemGround.last_ping_result_openhd - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Alive: ") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _ohdSystemGround.is_alive ? "Yes" : "No" - color: _ohdSystemGround.is_alive ? "GREEN" : "RED" - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - } - } - - // ------------ - Card { - id: flightControllBox - height: 150 - Layout.fillWidth: true - cardName: qsTr("FC") - cardBody: - ColumnLayout { - // from https://doc.qt.io/qt-6/qml-qtquick-layouts-rowlayout.html - anchors.fill: parent - spacing: 2 - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Autopilot:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _fcMavlinkSystem.autopilot_type - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("MAV type:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _fcMavlinkSystem.mav_type - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Last Ping:") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _fcMavlinkSystem.last_ping_result_flight_ctrl - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Alive: ") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _fcMavlinkSystem.is_alive ? "Yes" : "No" - color: _fcMavlinkSystem.is_alive ? "GREEN" : "RED" - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - RowLayout{ - Layout.fillWidth: true - Layout.minimumHeight: text_minHeight - spacing: 6 - Text { - text: qsTr("Sys id: ") - height: 24 - font.pixelSize: 14 - font.bold: true - leftPadding: 12 - } - Text { - text: _fcMavlinkSystem.for_osd_sys_id == -1 ? "na" : qsTr(""+_fcMavlinkSystem.for_osd_sys_id) - height: 24 - width: 256 - font.pixelSize: 14 - leftPadding: 6 - } - } - } - } - // -------------- - } - ColumnLayout{ - Layout.fillWidth: true - Layout.minimumHeight: 30 - spacing: 6 - anchors.top: groundAndAirCardsId.bottom - anchors.left: parent.left - anchors.margins: 10 - Button{ - height: 24 - text: "Ping all systems" - onClicked: _mavlinkTelemetry.ping_all_systems() - } - Button{ - height: 24 - text: "Request version" - onClicked: _mavlinkTelemetry.request_openhd_version() - } - } -} - -/*##^## -Designer { - D{i:1;anchors_width:224;anchors_x:71;anchors_y:8}D{i:2;anchors_height:15;anchors_width:488;anchors_x:8;anchors_y:91} -D{i:3;anchors_height:106;anchors_width:282;anchors_x:35;anchors_y:110} -} -##^##*/ diff --git a/qml/ui/configpopup/ChannelScanDialoque.qml b/qml/ui/configpopup/ChannelScanDialoque.qml deleted file mode 100644 index 6713fb4a9..000000000 --- a/qml/ui/configpopup/ChannelScanDialoque.qml +++ /dev/null @@ -1,171 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Dialogs 1.0 -import QtQuick.Controls.Material 2.12 - -import "../elements" - -// I fucking hate writing UIs in QT -Card { - id: dialoqueStartChannelScan - width: 360 - height: 340 - z: 5.0 - anchors.centerIn: parent - cardName: qsTr("Find Air Unit") - cardNameColor: "black" - visible: false - - property int m_curr_index: 0 - - function open_channel_scan_dialoque(){ - m_curr_index=0; - dialoqueStartChannelScan.visible=true - } - - ListModel{ - id: model_chann_2G_or_5G - ListElement {title: "All 2.4G and 5.8G channels (slow)"; value: 0} - ListElement {title: "All 2.4G channels"; value: 1} - ListElement {title: "All 5.8G channels"; value: 2} - } - - // like dji, we use 20 or 40Mhz bandwidth(s) but never 5 or 10 - ListModel{ - id: model_bandwidth_all_or_20_or_40 - ListElement {title: "20Mhz and 40Mhz (slow)"; value: 0} - ListElement {title: "20Mhz only"; value: 1} - ListElement {title: "40Mhz only"; value: 2} - } - - cardBody: Item{ - height: 200 - width: 320 - Text { - id: dialoqueStartChannelScan_text - text: "Initiate Channel Scan (Find a running air unit). Might take more than 1 minute if you are not specifying the generic band below. Otherwise,it'l take -max 30 seconds, usually less" - width: parent.width - height: parent.height-100 - leftPadding: 12 - rightPadding: 12 - wrapMode: Text.WordWrap - - visible: m_curr_index==0 - } - ComboBox { - width: parent.width - //height: 100 - anchors.top: dialoqueStartChannelScan_text.bottom - id: comboBoxWhichFrequencyToScan - model: model_chann_2G_or_5G - textRole: "title" - Material.background: { - (comboBoxWhichFrequencyToScan.currentIndex===0) ? Material.Orange : Material.Green - } - visible: m_curr_index==0 - onCurrentIndexChanged: { - } - } - ComboBox { - width: parent.width - //height: 100 - anchors.top: comboBoxWhichFrequencyToScan.bottom - id: comboBoxWhichChannelWidthsToScan - model: model_bandwidth_all_or_20_or_40 - textRole: "title" - Material.background: { - (comboBoxWhichChannelWidthsToScan.currentIndex===0) ? Material.Orange : Material.Green - } - visible: m_curr_index==0 - onCurrentIndexChanged: { - } - } - - - Text{ - id: dialoqueStartChannelScan_text2 - text: "Scanning channels, you can close this dialoque and go back to your OSD screen. This operation hapens in the background, progress is not implemented yet." - width: parent.width - height: parent.height-100 - leftPadding: 12 - rightPadding: 12 - wrapMode: Text.WordWrap - - visible: m_curr_index==1 - } - - } - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - width: 140 - height: 48 - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Green - highlighted: true - text: qsTr("Initiate") - visible: m_curr_index==0 - onPressed: { - if(m_curr_index==0){ - var how_many_freq_bands=comboBoxWhichFrequencyToScan.currentIndex - var how_many_bandwidths=comboBoxWhichChannelWidthsToScan.currentIndex - console.log("Initate channel scan "+how_many_freq_bands+","+how_many_bandwidths) - var result=_mavlinkTelemetry.ohd_gnd_request_channel_scan(how_many_freq_bands,how_many_bandwidths) - if(result){ - m_curr_index++; - //settings_panel.close_all() - }else{ - console.log("Cannot initiate channel scan"); - _messageBoxInstance.set_text_and_show("Please try again") - } - }else{ - dialoqueStartChannelScan.visible=false; - } - } - } - Button { - width: 140 - height: 48 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - text: qsTr("Cancel") - visible: m_curr_index==0 - onPressed: { - m_curr_index=0; - dialoqueStartChannelScan.visible=false; - } - } - - Button { - width: 140 - height: 48 - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Green - highlighted: true - text: qsTr("Close") - visible: m_curr_index==1 - onPressed: { - dialoqueStartChannelScan.visible=false; - } - } - } -} diff --git a/qml/ui/configpopup/ConfigPopup.qml b/qml/ui/configpopup/ConfigPopup.qml index cea71eb30..538a5489e 100644 --- a/qml/ui/configpopup/ConfigPopup.qml +++ b/qml/ui/configpopup/ConfigPopup.qml @@ -7,28 +7,56 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 +import "connect" +import "credits" +import "dev" +import "log" +import "qopenhd_settings" +import "openhd_settings" +import "rc" +import "status" + // Contains the selector on the left and a stack view for the panels on the right Rectangle { id: settings_form - property int eeInt : 0 + // The connect is only needed when qopenhd is not running on the ground system itself (e.g. android) + property bool m_show_connect_option: true // _qopenhd.is_android() + // size of the elements in the left bar - e.g. what allows switching between all the tabs property int left_sidebar_elements_height: 46 function openSettings() { - visible = true - focus = true + settings_form.visible=true + settings_form.focus=true; + sidebar.focus=true } function close_all(){ visible = false; focus=false; + hudOverlayGrid.regain_focus(); } function showAppSettings(i) { console.log("TEST show app settings:"+i); } + function side_bar_regain_focus(){ + sidebar.focus = true; + } + + /*Keys.onPressed: (event)=> { + console.log("ConfigPopup Key was pressed:"+event); + if (event.key == Qt.Key_Return) { + console.log("enter was pressed"); + event.accepted = true; + close_all() + //hudOverlayGrid.settingsButtonClicked(); + //settingsButton.focus=false; + } + }*/ + //anchors.fill: parent width: parent.width * settings.screen_settings_overlay_size_percent / 100; height: parent.height * settings.screen_settings_overlay_size_percent / 100; @@ -37,11 +65,6 @@ Rectangle { color: "transparent" - - MouseArea { - anchors.fill: parent - propagateComposedEvents: false - } Rectangle { id: spacerTopSpacer width: 132 @@ -82,7 +105,7 @@ Rectangle { color: closeButton.hovered ? "grey" : "white" // I update background color by this } onClicked: { - settings_form.visible=false + close_all(); } } } @@ -109,503 +132,96 @@ Rectangle { clip: true + Keys.onPressed: (event)=> { + console.log("Sidebar Key was pressed:"+event); + var tmp_index=mainStackLayout.currentIndex; + if(event.key==Qt.Key_Up){ + tmp_index--; + if(tmp_index<0)tmp_index=0; + mainStackLayout.currentIndex=tmp_index; + }else if (event.key == Qt.Key_Down) { + console.log("Down arrow"); + tmp_index++; + if(tmp_index>=mainStackLayout.count)tmp_index=0; + mainStackLayout.currentIndex=tmp_index; + }else if(event.key == Qt.Key_Left){ + close_all() + }else if(event.key == Qt.Key_Right){ + //mainStackLayout.childAt(mainStackLayout.currentIndex).focus=true + _qopenhd.show_toast("No joystick navigation for this panel"); + } + } + Column { width: parent.width - anchors.top: parent.top - // QOpenHD Settings - AppSettingsPanel - Item { - height: left_sidebar_elements_height - width: parent.width - - Button{ - id: appSettingsBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top - Text { - id: appIcon - text: "\uf013" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - Text { - id: appButton - text: qsTr("QOpenHD") - height: parent.height - anchors.left: appIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 0 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: appSettingsBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 0 - } - } + // We only need the connect panel on android (external device) + // On localhost, QOpenHD "automatically" connects due to udp localhost method + ConfigPopupSidebarButton{ + visible: m_show_connect_option + id: connect_button + m_icon_text: "\uf6ff" + m_description_text: "Connect" + m_selection_index: 0 } - // OpenHD Settings - MavlinkAllSettingsPanel - Item { - height: left_sidebar_elements_height - width: parent.width - Button{ - id: openhdSettingsBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: groundIcon - text: "\uf085" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - Text { - id: groundButton - text: qsTr("OpenHD") - height: parent.height - anchors.left: groundIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 1 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: openhdSettingsBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 1 - } - } + // Status + ConfigPopupSidebarButton{ + id: power + m_icon_text: "\uf21e" //"\uf011" + m_description_text: "Status" + m_selection_index: 1 } - // Log - Item { - height: left_sidebar_elements_height - width: parent.width - visible: true - Button{ - id: logBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: logIcon - text: "\uf0c9" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: logButton - text: qsTr("Log") - height: parent.height - anchors.left: logIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 2 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: logBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 2 - } - } + // QOpenHD Settings - AppSettingsPanel + ConfigPopupSidebarButton{ + id: qopenhd_button + m_icon_text: "\uf013" + m_description_text: "QOpenHD" + m_selection_index: 2 } - // Power - Item { - height: left_sidebar_elements_height - width: parent.width - Button{ - id: powerSettingsBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: powerIcon - text: "\uf011" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: powerButton - text: qsTr("Power") - height: parent.height - anchors.left: powerIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 3 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: powerSettingsBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 3 - } - } + // OpenHD Settings - MavlinkAllSettingsPanel + ConfigPopupSidebarButton{ + id: openhd_button + m_icon_text: "\uf085" + m_description_text: "OpenHD" + m_selection_index: 3 } - // About - Item { - height: left_sidebar_elements_height - width: parent.width - Button{ - id: aboutBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: aboutIcon - text: "\uf05a" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: aboutButton - text: qsTr("About") - height: parent.height - anchors.left: aboutIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 4 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: aboutBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 4 - if (eeInt > 8){ - eeItem.visible = true - eeInt = 0 - }else{ - eeItem.visible = false - eeInt = eeInt+1 - } - } - } + // Log + ConfigPopupSidebarButton{ + id: log_button + m_icon_text: "\uf0c9" + m_description_text: "Log" + m_selection_index: 4 } // RC - Item { - height: left_sidebar_elements_height - width: parent.width - Button{ - id: rcSettingsBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: rcIcon - text: "\uf05a" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: rcButton - text: qsTr("RC") - height: parent.height - anchors.left: rcIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 5 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: rcSettingsBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 5 - } - } + ConfigPopupSidebarButton{ + id: rc + m_icon_text: "\uf11b" + m_description_text: "RC" + m_selection_index: 5 } - // FC Setup - /*Item { - id: fcSetup - height: left_sidebar_elements_height - width: parent.width - Button{ - id: fcSetupButton - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - Text { - id: fcSetupIcon - text: "\uf05a" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: fcSetupButtonText - text: qsTr("FC Setup") - height: parent.height - anchors.left: fcSetupIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 6 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: fcSetupButton.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 6 - } - } - }*/ - - - - // Developer stats - Item { - height: left_sidebar_elements_height - width: parent.width - Button{ - id: devStatsBtn - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: developerStatsIcon - text: "\uf05a" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: developerStatsButton - text: qsTr("DEV") - height: parent.height - anchors.left: developerStatsIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 6 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: devStatsBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 6 - } - } + // Credits and copyright + ConfigPopupSidebarButton{ + id: credits + m_icon_text: "\uf005" + m_description_text: "Credits" + m_selection_index: 6 } - // connect (on android) - Item { - height: left_sidebar_elements_height - width: parent.width - // only show on android to not confuse users - visible: _qopenhd.is_android() - Button{ - id: connectB - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: connectIcon - text: "\uf6ff" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: connectBX - text: qsTr("Connect") - height: parent.height - anchors.left: connectIcon.right - anchors.leftMargin: 6 - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 7 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: connectB.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 7 - } - } - } - - Item { - id: eeItem - visible: false - height: left_sidebar_elements_height - width: parent.width - Button{ - id: eeBtn - - height: parent.height - width: parent.width - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Text { - id: eeIcon - text: "\uf05a" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Font Awesome 5 Free" - font.pixelSize: 18 - height: parent.height - width: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - color: "#dde4ed" - } - - Text { - id: eeButton - height: parent.height - anchors.left: eeIcon.right - anchors.leftMargin: 6 - - text: qsTr("EasterEgg") - font.pixelSize: 15 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - color: mainStackLayout.currentIndex == 8 ? "#33aaff" : "#dde4ed" - } - background: Rectangle { - opacity: .5 - radius: 5 - //later this can be changed to focus - color: eeBtn.hovered ? "grey" : "transparent" // I update background color by this - } - onClicked: { - mainStackLayout.currentIndex = 8 - } - } + // Developer stats + ConfigPopupSidebarButton{ + id: developerstats + m_icon_text: "\uf0ad" + m_description_text: "DEV" + m_selection_index: 7 } } } @@ -622,6 +238,16 @@ Rectangle { anchors.top: parent.top anchors.topMargin: 0 + // default index + currentIndex: m_show_connect_option ? 0 : 1 + + ConnectPanel{ + id: connectPanel + } + + PanelStatus { + id: statusPanel + } AppSettingsPanel { id: appSettingsPanel @@ -635,28 +261,16 @@ Rectangle { id: logMessagesStatusView } - PowerPanel { - id: powerPanel - } - - AboutPanel { - id: aboutPanel - } - RcInfoPanel { id: rcInfoPanel } - AppDeveloperStatsPanel { - id: appDeveloperStatsPanel + Credits { + id: creditspanel } - ConnectPanel{ - id: connectPanel - } - - EasterEggPanel { - id: easterEgPanel + AppDeveloperStatsPanel { + id: appDeveloperStatsPanel } } } diff --git a/qml/ui/configpopup/ConfigPopupSidebarButton.qml b/qml/ui/configpopup/ConfigPopupSidebarButton.qml new file mode 100644 index 000000000..38035d150 --- /dev/null +++ b/qml/ui/configpopup/ConfigPopupSidebarButton.qml @@ -0,0 +1,70 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Dialogs 1.3 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +// Special "Button" for the left selection bar +Item { + + // Filled with icon + property string m_icon_text: "IC" + // Filled with description + property string m_description_text: "DESCR" + + // Stack layout element that should be shown when this element is selected + property int m_selection_index: -1 + + id: credits + visible: true + height: 46 + width: parent.width + + Button{ + id: genericButton + + height: parent.height + width: parent.width + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + + Text { + id: genericIcon + text: m_icon_text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Font Awesome 5 Free" + font.pixelSize: 18 + height: parent.height + width: 24 + anchors.left: parent.left + anchors.leftMargin: 12 + color: "#dde4ed" + } + + Text { + id: genericDescription + height: parent.height + anchors.left: genericIcon.right + anchors.leftMargin: 6 + + text: qsTr(m_description_text) + font.pixelSize: 15 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: mainStackLayout.currentIndex == m_selection_index ? "#33aaff" : "#dde4ed" + } + background: Rectangle { + opacity: .5 + radius: 5 + //later this can be changed to focus + color: genericButton.hovered ? "grey" : "transparent" // I update background color by this + } + onClicked: { + mainStackLayout.currentIndex = m_selection_index + } + } +} diff --git a/qml/ui/configpopup/ConnectPanel.qml b/qml/ui/configpopup/ConnectPanel.qml deleted file mode 100644 index 43e4b3308..000000000 --- a/qml/ui/configpopup/ConnectPanel.qml +++ /dev/null @@ -1,117 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Dialogs 1.0 -import QtQuick.Controls.Material 2.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - -// This panel is only for users that run QOpenHD on an external device,e.g. android not on the ground station itself -Item { - Layout.fillHeight: true - Layout.fillWidth: true - - property int rowHeight: 64 - property int elementHeight: 48 - property int elementComboBoxWidth: 300 - - // Tab bar for selecting items in stack layout - TabBar { - id: selectItemInStackLayoutBar - width: parent.width - TabButton { - text: qsTr("INFO") - } - TabButton { - text: qsTr("USB Tether") - } - TabButton { - text: qsTr("Ethernet (USB Eth) passive") - } - TabButton { - text: qsTr("Ethernet (USB Eth) active") - } - } - - // placed right below the top bar - StackLayout { - width: parent.width - height: parent.height-selectItemInStackLayoutBar.height - anchors.top: selectItemInStackLayoutBar.bottom - anchors.left: selectItemInStackLayoutBar.left - anchors.bottom: parent.bottom - currentIndex: selectItemInStackLayoutBar.currentIndex - - Pane { - width: parent.width - height: parent.height - ColumnLayout{ - Layout.fillWidth: true - Layout.minimumHeight: 30 - spacing: 6 - Text{ - width:parent.width - height: 600 - wrapMode: Text.WordWrap - text: "When running QOpenHD on an external device (e.g. android phone)\nyou have to use one of the given choices to connect the device to the OpenHD ground station" - } - } - } - - Pane { - width: parent.width - height: parent.height - ColumnLayout{ - Layout.fillWidth: true - Layout.minimumHeight: 30 - spacing: 6 - Text{ - width:parent.width - height: 600 - wrapMode: Text.WordWrap - text: "1) Connect your phone via high quality USB cable to your ground station.\n -2) enable USB Tethering on your phone.\n -Requires a phone and cellular contract that allows USB tethering." - } - Button{ - text: "Open settings" - onClicked: _qopenhd.android_open_tethering_settings() - } - } - } - Pane { - width: parent.width - height: parent.height - Text{ - width:parent.width - height: 600 - wrapMode: Text.WordWrap - text: "1) Make sure ETH_HOTSPOT_E is disabled\n -2) Enable ETH_PASSIVE_F on your openhd ground unit\n -3) Connect your external device running QOpenHD to your ground station via ethernet\n(e.g. ethernet port on rpi 4).\n -4) Make sure to select 'share my internet with ...' when the android connection setup pops up\n -Video and telemetry forwarding should start automatically, and your GCS can get internet from your phone." - } - } - Pane { - width: parent.width - height: parent.height - Text{ - width:parent.width - height: 600 - wrapMode: Text.WordWrap - text: " -1) Make sure ETH_PASSIVE_F is disabled\n -2) Enable ETH_HOTSPOT_E on your openhd ground unit\n -3) Connect your external device running QOpenHD to your ground station via ethernet\n(e.g. ethernet port on rpi 4).\n -You might have to disable wifi and cellular on your phone !.\n -Video and telemetry forwarding should start automatically." - } - } - } -} diff --git a/qml/ui/configpopup/MavlinkExtraWBParamPanel.qml b/qml/ui/configpopup/MavlinkExtraWBParamPanel.qml deleted file mode 100644 index 94938f260..000000000 --- a/qml/ui/configpopup/MavlinkExtraWBParamPanel.qml +++ /dev/null @@ -1,356 +0,0 @@ -import QtQuick 2.0 - -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Dialogs 1.0 -import QtQuick.Controls.Material 2.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - -// This is an extra screen for changing the frequency / channel width - -// They both need to match ! -Rectangle{ - width: parent.width - height: parent.height - - //color: "transparent" - //color: settings.screen_settings_openhd_parameters_transparent ? "transparent" : "white" - //opacity: settings.screen_settings_openhd_parameters_transparent ? 0.2 : 1 - - // https://stackoverflow.com/questions/41991438/how-do-i-find-a-particular-listelement-inside-a-listmodel-in-qml - // For the models above (model with value) try to find the index of the first item where model[i].value===value - function find_index(model,value){ - for(var i = 0; i < model.count; ++i) if (model.get(i).value===value) return i - return -1 - } - // try and update the combobox to the retrieved value(value != index) - function update_combobox(_combobox,_value){ - var _index=find_index(_combobox.model,_value) - if(_index >= 0){ - _combobox.currentIndex=_index; - } - } - - function fc_is_armed(){ - return _fcMavlinkSystem.armed - } - - // Re-set the "disable sync" on init - Component.onCompleted: { - settings.qopenhd_allow_changing_ground_unit_channel_width_no_sync=false - settings.qopenhd_allow_changing_ground_unit_frequency_no_sync=false - } - - property string m_text_warning_nosync_frequency: "WARNING: THIS CHANGES YOUR GROUND UNIT FREQUENCY WITHOUT CHANGING YOUR AIR UNIT FREQUENCY ! -Only enable if you want to quickly change your ground unit's frequency to the already set frequency of a running air unit (And know both frequency and channel width on top of your head)"; - - property string m_text_warning_nosync_chanel_width: "WARNING: THIS CHANGES YOUR GROUND UNIT CHANNEL WIDTH WITHOUT CHANGING YOUR AIR UNIT CHANNEL WIDTH ! -Only enable if you want to quickly change your ground unit's channel width to the already set channel width of a running air unit (And know both frequency and channel width on top of your head)" - - - property string more_info_text: "After flashing,openhd uses the same default frequency, and your air and ground unit automatically connect."+ - "If you change the frequency / channel width here, both air and ground unit are set to the new frequency."+ -"If you changed the frequency of your air unit and are using a different Ground unit, use the FIND AIR UNIT feature (channel scan) to switch to the same frequency your air unit is running on." - - ScrollView { - id:mavlinkExtraWBParamPanel - width: parent.width - height: parent.height - contentHeight: wbParamColumn.height - clip: true - - Item { - anchors.fill: parent - - Column { - id:wbParamColumn - spacing: 0 - anchors.left: parent.left - anchors.right: parent.right - - // Dirty, for the 3 mavlink settings that need to be kept in sync on both air and ground - - // NOTE: WHile it would be possible to do the 2.4G frequencie(s) in 5Mhz increments (and OpenHD accepts those values) - // We do not expose them here, since it incredibly pollutes the UI and gives the user the false perception of there being a lot of 2.4G Channels - // (Even though they overlap, and therefore are not really usable - ListModel{ - id: frequenciesModel - ListElement {title: "2312Mhz [X] (Atheros)"; value: 2312} - //ListElement {title: "2317Mhz [X] (Atheros)"; value: 2317} - //ListElement {title: "2322Mhz [X] (Atheros)"; value: 2322} - //ListElement {title: "2327Mhz [X] (Atheros)"; value: 2327} - ListElement {title: "2332Mhz [X] (Atheros)"; value: 2332} - //ListElement {title: "2337Mhz [X] (Atheros)"; value: 2337} - //ListElement {title: "2342Mhz [X] (Atheros)"; value: 2342} - //ListElement {title: "2347Mhz [X] (Atheros)"; value: 2347} - ListElement {title: "2352Mhz [X] (Atheros)"; value: 2352} - //ListElement {title: "2357Mhz [X] (Atheros)"; value: 2357} - //ListElement {title: "2362Mhz [X] (Atheros)"; value: 2362} - //ListElement {title: "2367Mhz [X] (Atheros)"; value: 2367} - ListElement {title: "2372Mhz [X] (Atheros)"; value: 2372} - //ListElement {title: "2377Mhz [X] (Atheros)"; value: 2377} - //ListElement {title: "2382Mhz [X] (Atheros)"; value: 2382} - //ListElement {title: "2387Mhz [X] (Atheros)"; value: 2387} - ListElement {title: "2392Mhz [X] (Atheros)"; value: 2392} - //ListElement {title: "2397Mhz [X] (Atheros)"; value: 2397} - //ListElement {title: "2402Mhz [X] (Atheros)"; value: 2402} - //ListElement {title: "2407Mhz [X] (Atheros)"; value: 2407} - ListElement {title: "2412Mhz [1] (Normal 2.4G)"; value: 2412} - //ListElement {title: "2417Mhz [2] (Ralink/Atheros)"; value: 2417} - //ListElement {title: "2422Mhz [3] (Ralink/Atheros)"; value: 2422} - //ListElement {title: "2427Mhz [4] (Ralink/Atheros)"; value: 2427} - ListElement {title: "2432Mhz [5] (Normal 2.4G)"; value: 2432} - //ListElement {title: "2437Mhz [6] (Ralink/Atheros)"; value: 2437} - //ListElement {title: "2442Mhz [7] (Ralink/Atheros)"; value: 2442} - //ListElement {title: "2447Mhz [8] (Ralink/Atheros)"; value: 2447} - ListElement {title: "2452Mhz [9] (Normal 2.4G)"; value: 2452} - //ListElement {title: "2457Mhz [10](Ralink/Atheros)"; value: 2457} - //ListElement {title: "2462Mhz [11](Ralink/Atheros)"; value: 2462} - //ListElement {title: "2467Mhz [12](Ralink/Atheros)"; value: 2467} - ListElement {title: "2472Mhz [13](Normal 2.4G)"; value: 2472} - //ListElement {title: "2484Mhz [14](Ralink/Atheros)"; value: 2484} - //ListElement {title: "2477Mhz [X] (Atheros)"; value: 2477} - //ListElement {title: "2482Mhz [X] (Atheros)"; value: 2482} - //ListElement {title: "2487Mhz [X] (Atheros)"; value: 2487} - //ListElement {title: "2489Mhz [X] (Atheros)"; value: 2489} - ListElement {title: "2492Mhz [X] (Atheros)"; value: 2492} - //ListElement {title: "2494Mhz [X] (Atheros)"; value: 2494} - //ListElement {title: "2497Mhz [X] (Atheros)"; value: 2497} - //ListElement {title: "2499Mhz [X] (Atheros)"; value: 2499} - ListElement {title: "2512Mhz [X] (Atheros)"; value: 2512} - ListElement {title: "2532Mhz [X] (Atheros)"; value: 2532} - ListElement {title: "2572Mhz [X] (Atheros)"; value: 2572} - ListElement {title: "2592Mhz [X] (Atheros)"; value: 2592} - ListElement {title: "2612Mhz [X] (Atheros)"; value: 2612} - ListElement {title: "2632Mhz [X] (Atheros)"; value: 2632} - ListElement {title: "2652Mhz [X] (Atheros)"; value: 2652} - ListElement {title: "2672Mhz [X] (Atheros)"; value: 2672} - ListElement {title: "2692Mhz [X] (Atheros)"; value: 2692} - ListElement {title: "2712Mhz [X] (Atheros)"; value: 2712} - // - ListElement {title: "Unknown"; value: -1} - // 5G begin - ListElement {title: "5180Mhz [36] (DEFAULT)"; value: 5180} - ListElement {title: "5200Mhz [40]"; value: 5200} - ListElement {title: "5220Mhz [44]"; value: 5220} - ListElement {title: "5240Mhz [48]"; value: 5240} - ListElement {title: "5260Mhz [52] (DFS RADAR)"; value: 5260} - ListElement {title: "5280Mhz [56] (DFS RADAR)"; value: 5280} - ListElement {title: "5300Mhz [60] (DFS RADAR)"; value: 5300} - ListElement {title: "5320Mhz [64] (DFS RADAR)"; value: 5320} - ListElement {title: "5500Mhz [100] (DFS RADAR)"; value: 5500} - ListElement {title: "5520Mhz [104] (DFS RADAR)"; value: 5520} - ListElement {title: "5540Mhz [108] (DFS RADAR)"; value: 5540} - ListElement {title: "5560Mhz [112] (DFS RADAR)"; value: 5560} - ListElement {title: "5580Mhz [116] (DFS RADAR)"; value: 5580} - ListElement {title: "5600Mhz [120] (DFS RADAR)"; value: 5600} - ListElement {title: "5620Mhz [124] (DFS RADAR)"; value: 5620} - ListElement {title: "5640Mhz [128] (DFS RADAR)"; value: 5640} - ListElement {title: "5660Mhz [132] (DFS RADAR)"; value: 5660} - ListElement {title: "5680Mhz [136] (DFS RADAR)"; value: 5680} - ListElement {title: "5700Mhz [140] (DFS RADAR)"; value: 5700} - ListElement {title: "5745Mhz [149]"; value: 5745} - ListElement {title: "5765Mhz [153]"; value: 5765} - ListElement {title: "5785Mhz [157]"; value: 5785} - ListElement {title: "5805Mhz [161]"; value: 5805} - ListElement {title: "5825Mhz [165]"; value: 5825} - // These require patched kernel ! - ListElement {title: "5845Mhz [169]"; value: 5845} - ListElement {title: "5865Mhz [173]"; value: 5865} - ListElement {title: "5885Mhz [177]"; value: 5885} - //ListElement {title: "5905Mhz [181]"; value: 5905} - } - - ListModel{ - id: channelWidthModel - ListElement {title: "Unknown"; value: -1} - ListElement {title: "20MHz (default)"; value: 20} - ListElement {title: "40MHz (rtl8812au only)"; value: 40} - } - Text{ - width: parent.width - height: rowHeight / 2 - elide: Text.ElideLeft - wrapMode: Text.WordWrap - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text:{ - "NOTE: Frequency and channel width of air and ground unit BOTH need to match." - } - } - Button{ - text: "MORE INFO" - Material.background:Material.LightBlue - onClicked: { - _messageBoxInstance.set_text_and_show(more_info_text) - } - } - Rectangle { - width: parent.width - height: rowHeight - color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" - - RowLayout{ - anchors.verticalCenter: parent.verticalCenter - Button{ - text: "Find Air unit" - enabled: _ohdSystemGround.is_alive - onClicked: { - dialoqueStartChannelScan.m_curr_index=0 - dialoqueStartChannelScan.visible=true - } - } - Button{ - text: "INFO" - Material.background:Material.LightBlue - onClicked: { - var text="Scan all channels for a running Air unit. Might take up to 30seconds to complete (openhd supports a ton of channels, and we need to listen on each of them for a short timespan)" - _messageBoxInstance.set_text_and_show(text) - } - } - } - } - // Changing the wifi frequency, r.n only 5G - Rectangle { - width: parent.width - height: rowHeight - color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" - - RowLayout{ - anchors.verticalCenter: parent.verticalCenter - Button{ - text: "Fetch" - onClicked: { - var _res=_synchronizedSettings.get_param_int_air_and_ground_value_freq() - if(_res>=0){ - buttonSwitchFreq.enabled=true - } - //console.log("Got ",_res) - update_combobox(comboBoxFreq,_res); - } - } - ComboBox { - id: comboBoxFreq - model: frequenciesModel - textRole: "title" - implicitWidth: elementComboBoxWidth - // 5.8G is generally recommended and much more commonly used than 2.4G. Default to it when unknown, just like openhd does - currentIndex: 21 - - } - Button{ - text: "Switch Frequency" - id: buttonSwitchFreq - enabled: false - onClicked: { - if(_fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed && (!settings.dev_allow_freq_change_when_armed)){ - var text="Cannot change frequency while FC is armed."; - _messageBoxInstance.set_text_and_show(text,5); - return; - } - var selectedValue=frequenciesModel.get(comboBoxFreq.currentIndex).value - if(selectedValue<=100){ - _messageBoxInstance.set_text_and_show("Please select a valid frequency",5); - return; - } - _synchronizedSettings.change_param_air_and_ground_frequency(selectedValue); - } - //Material.background: fc_is_armed() ? Material.Red : Material.Normal; - } - Button{ - text: "INFO" - Material.background:Material.LightBlue - onClicked: { - var text="Frequency in Mhz and channel number. (DFS-RADAR) - also used by commercial plane(s) weather radar (most likely illegal). "+ - "[X] - Not a legal wifi frequency, AR9271 does them anyways."+ -"It is your responsibility to only change the frequency to values allowed in your country. You can use a frequency analyzer on your phone or the packet loss to find the best channel for your environemnt." - _messageBoxInstance.set_text_and_show(text) - } - } - Switch{ - text: "allow gnd only" - checked: settings.qopenhd_allow_changing_ground_unit_frequency_no_sync - onCheckedChanged: { - if(settings.qopenhd_allow_changing_ground_unit_frequency_no_sync != checked && checked){ - _messageBoxInstance.set_text_and_show(m_text_warning_nosync_frequency,10) - } - settings.qopenhd_allow_changing_ground_unit_frequency_no_sync = checked - } - } - } - } - Rectangle { - width: parent.width - height: rowHeight - color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" - - RowLayout{ - anchors.verticalCenter: parent.verticalCenter - Button{ - text: "Fetch" - onClicked: { - var _res=_synchronizedSettings.get_param_int_air_and_ground_value_channel_width() - if(_res>=0){ - buttonSwitchChannelWidth.enabled=true - } - //console.log("Got ",_res) - update_combobox(comboBoxChannelWidth,_res); - } - } - ComboBox { - id: comboBoxChannelWidth - model: channelWidthModel - textRole: "title" - implicitWidth: elementComboBoxWidth - } - Button{ - text: "Change Channel Width" - id: buttonSwitchChannelWidth - enabled: false - onClicked: { - if(_fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed && (!settings.dev_allow_freq_change_when_armed)){ - var text="Cannot change channel width while FC is armed." - _messageBoxInstance.set_text_and_show(text,5); - return; - } - var selectedValue=channelWidthModel.get(comboBoxChannelWidth.currentIndex).value - if(!(selectedValue===10 || selectedValue===20 || selectedValue===40 || selectedValue===80)){ - _messageBoxInstance.set_text_and_show("Please select a valid channel width",5); - return; - } - _synchronizedSettings.change_param_air_and_ground_channel_width(selectedValue) - } - //Material.background: fc_is_armed() ? Material.Red : Material.Normal; - //Material.background: Material.Light; - } - Button{ - text: "INFO" - Material.background:Material.LightBlue - onClicked: { - var text="Only supported on rtl8812au!\nA channel width of 40Mhz gives almost double the bandwidth, but uses 2x 20Mhz channels and therefore the likeliness of "+ -"interference from other stations sending on either of those channels is increased. It also slightly decreases sensitivity. Only changeable on rtl8812au." - _messageBoxInstance.set_text_and_show(text) - } - } - Switch{ - text: "allow gnd only" - checked: settings.qopenhd_allow_changing_ground_unit_channel_width_no_sync - onCheckedChanged: { - if(settings.qopenhd_allow_changing_ground_unit_channel_width_no_sync != checked && checked){ - _messageBoxInstance.set_text_and_show(m_text_warning_nosync_chanel_width,10) - } - settings.qopenhd_allow_changing_ground_unit_channel_width_no_sync = checked - } - } - } - } - } - } - } -} diff --git a/qml/ui/configpopup/MavlinkSetupPiCameraPanel.qml b/qml/ui/configpopup/MavlinkSetupPiCameraPanel.qml deleted file mode 100644 index 671c689f7..000000000 --- a/qml/ui/configpopup/MavlinkSetupPiCameraPanel.qml +++ /dev/null @@ -1,183 +0,0 @@ -import QtQuick 2.0 - -import QtQuick 2.12 -import QtQuick.Window 2.0 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Dialogs 1.0 -import QtQuick.Controls.Material 2.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - - -ScrollView { - anchors.fill: parent - clip:true - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AlwaysOff - contentHeight: gridViewRaspberry.height+gridViewArducam.height+gridViewVeye.height+gridViewOther.height+250 - - ColumnLayout { - id: columnLayout - x: 0 - y: 0 - width: parent.width - height: maximumHeight - } - Card { - id: infoBox - y: 15 - height: 75 - anchors.left: parent.left - anchors.leftMargin: 15 - anchors.right: parent.right - anchors.rightMargin: 30 - Layout.fillWidth: true - cardName: qsTr(" Camera Setup") - cardBody: - Text { - text: qsTr(" Here you can select the camera you want to use") - height: 24 - font.pixelSize: 14 - } - } - Text { - id: supplier1 - x: 15 - y: 115 - text: qsTr("Raspberry Cameras") - font.pixelSize: 15 - } - - GridView { - id: gridViewRaspberry - x: 15 - y: supplier1.y+45 - width: columnLayout.width-15 - height: 150 - model: PiCameraConfigListOriginal {} - // highlight: Rectangle { - // color: "lightsteelblue" - // opacity: 0.7 - // radius: 5 - // focus:true - // clip: true - // } - delegate: Column { - Image { - width: 120; height: 100 - source: portrait; anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - console.log(aString) - } - } - } - property string aString: raspOverlay - Text { text: name; anchors.horizontalCenter: parent.horizontalCenter } - } - cellWidth: 130 - - } - - Text { - id: supplier2 - x: 15 - y: gridViewRaspberry.y+gridViewRaspberry.height - text: qsTr("Arducam Cameras") - font.pixelSize: 15 - } - - GridView { - id: gridViewArducam - x: 15 - y: gridViewRaspberry.y+gridViewRaspberry.height+30 - width: columnLayout.width - height: 150 - model: PiCameraConfigListArducam {} - delegate: Column { - Image { - width: 120; height: 100 - source: portrait; anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - console.log(aString) - } - } - } - property string aString: raspOverlay - Text { text: name; anchors.horizontalCenter: parent.horizontalCenter } - } - cellWidth: 130 - } - - Text { - id: supplier3 - x: 15 - y: gridViewArducam.y+gridViewArducam.height - text: qsTr("Veye Imaging") - font.pixelSize: 15 - } - - GridView { - id: gridViewVeye - x: 15 - y: gridViewArducam.y+gridViewArducam.height+30 - width: columnLayout.width-15 - height: 150 - model: PiCameraConfigListVeye {} - delegate: Column { - Image { - width: 120; height: 100 - source: portrait; anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - console.log(aString) - } - } - } - property string aString: raspOverlay - Text { text: name; anchors.horizontalCenter: parent.horizontalCenter } - } - cellWidth: 130 - } - Text { - id: supplier4 - x: 15 - y: gridViewVeye.y+gridViewVeye.height - text: qsTr("Other Cameras") - font.pixelSize: 15 - } - GridView { - id: gridViewOther - x: 15 - y: gridViewVeye.y+gridViewVeye.height+30 - width: columnLayout.width-15 - height: 150 - model: PiCameraConfigListOther {} - delegate: Column { - Image { - width: 120; height: 100 - source: portrait; anchors.horizontalCenter: parent.horizontalCenter - MouseArea { - anchors.fill: parent - onClicked: { - console.log(aString) - } - } - } - property string aString: raspOverlay - Text { text: name; anchors.horizontalCenter: parent.horizontalCenter } - } - cellWidth: 130 - } - -} diff --git a/qml/ui/configpopup/PanelFCSetup.qml b/qml/ui/configpopup/PanelFCSetup.qml deleted file mode 100644 index 363793448..000000000 --- a/qml/ui/configpopup/PanelFCSetup.qml +++ /dev/null @@ -1,40 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - -Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - - property int rowHeight: 64 - property int text_minHeight: 20 - - color: "#eaeaea" - - ColumnLayout{ - Layout.fillWidth: true - Layout.minimumHeight: 30 - spacing: 6 - anchors.top: parent.top - anchors.left: parent.left - anchors.margins: 10 - - Button{ - height: 24 - text: "Set Ardupilot Message rates" - onClicked:{ - _messageBoxInstance.set_text_and_show("Not yet implemented") - //_fcMavlinkkSettingsModel.set_ardupilot_message_rates() - } - } - } -} - diff --git a/qml/ui/configpopup/PiCameraConfigListArducam.qml b/qml/ui/configpopup/PiCameraConfigListArducam.qml deleted file mode 100644 index ecb5c9808..000000000 --- a/qml/ui/configpopup/PiCameraConfigListArducam.qml +++ /dev/null @@ -1,21 +0,0 @@ -import QtQuick 2.15 - -ListModel { - - ListElement { - name: "IMX477(und mini)" - portrait: "../../resources/cameras/ArduIMX477.png" - raspOverlay: "libcamera_imx477" - } - ListElement { - name: "LowLight" - portrait: "../../resources/cameras/ArduIMX462.png" - raspOverlay: "libcamera_arducam" - } - ListElement { - name: "IMX519" - portrait: "../../resources/cameras/ArduIMX519.png" - raspOverlay: "libcamera_imx519" - } - -} diff --git a/qml/ui/configpopup/PiCameraConfigListOriginal.qml b/qml/ui/configpopup/PiCameraConfigListOriginal.qml deleted file mode 100644 index c518f7972..000000000 --- a/qml/ui/configpopup/PiCameraConfigListOriginal.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.15 - -ListModel { - - ListElement { - name: "Camera V1" - portrait: "../../resources/cameras/raspV1.png" - raspOverlay: "mmal" - } - ListElement { - name: "Camera V2" - portrait: "../../resources/cameras/raspV2.png" - raspOverlay: "mmal" - } - ListElement { - name: "HQ Camera" - portrait: "../../resources/cameras/raspHq.png" - raspOverlay: "mmal" - } - ListElement { - name: "other" - portrait: "../../resources/cameras/raspOther.png" - raspOverlay: "mmal" - } - -} diff --git a/qml/ui/configpopup/PiCameraConfigListOther.qml b/qml/ui/configpopup/PiCameraConfigListOther.qml deleted file mode 100644 index ee5d42861..000000000 --- a/qml/ui/configpopup/PiCameraConfigListOther.qml +++ /dev/null @@ -1,21 +0,0 @@ -import QtQuick 2.15 - -ListModel { - - ListElement { - name: "CSI HDMI" - portrait: "../../resources/cameras/otherHDMI.png" - raspOverlay: "notImplementedYet" - } - ListElement { - name: "Debug Camera" - portrait: "../../resources/cameras/otherTest.png" - raspOverlay: "notNeeded" - } - ListElement { - name: "IP Camera" - portrait: "../../resources/cameras/otherIP.png" - raspOverlay: "notNeeded" - } - -} diff --git a/qml/ui/configpopup/PiCameraConfigListVeye.qml b/qml/ui/configpopup/PiCameraConfigListVeye.qml deleted file mode 100644 index 9d9c13d85..000000000 --- a/qml/ui/configpopup/PiCameraConfigListVeye.qml +++ /dev/null @@ -1,31 +0,0 @@ -import QtQuick 2.15 - -ListModel { - - ListElement { - name: "Veye IMX290" - portrait: "../../resources/cameras/veyeIMX290.png" - raspOverlay: "veye327" - } - ListElement { - name: "Veye IMX327" - portrait: "../../resources/cameras/veyeIMX327.png" - raspOverlay: "veyecam2m" - } - ListElement { - name: "Veye IMX462" - portrait: "../../resources/cameras/veyeIMX462.png" - raspOverlay: "veyecam2m" - } - ListElement { - name: "CS-MIPI-SC132" - portrait: "../../resources/cameras/veyeSC132.png" - raspOverlay: "cssc132" - } - ListElement { - name: "CS-MIPI-IMX307" - portrait: "../../resources/cameras/veyeIMX307.png" - raspOverlay: "csimx307" - } - -} diff --git a/qml/ui/configpopup/PowerPanel.qml b/qml/ui/configpopup/PowerPanel.qml deleted file mode 100644 index 1ceee2822..000000000 --- a/qml/ui/configpopup/PowerPanel.qml +++ /dev/null @@ -1,168 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Dialogs 1.1 -import QtQuick.Controls.Material 2.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - -PowerPanelForm { - - property int powerAction: PowerPanel.PowerAction.RebootGround - - enum PowerAction { - RebootAir, - ShutdownAir, - RebootGround, - ShutdownGround, - RebootFC, - ShutdownFC - } - - Card { - id: powerDialog - height: 240 - width: 400 - z: 5.0 - anchors.centerIn: parent - cardNameColor: "black" - hasHeaderImage: true - - Component.onCompleted: visible = false - - property bool stateVisible: visible - - states: [ - State { - when: powerDialog.stateVisible; - PropertyChanges { - target: powerDialog - opacity: 1.0 - } - }, - State { - when: !powerDialog.stateVisible; - PropertyChanges { - target: powerDialog - opacity: 0.0 - } - } - ] - transitions: [ Transition { NumberAnimation { property: "opacity"; duration: 250}} ] - - cardName: qsTr("Confirm Power Change") - cardBody: Column { - height: powerDialog.height - width: powerDialog.width - - Text { - text: qsTr("If your drone is in the air, rebooting or shutting down may cause a crash or make it enter failsafe mode!") - width: parent.width - leftPadding: 12 - rightPadding: 12 - wrapMode: Text.WordWrap - } - } - - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - id: groundReboot - width: 96 - height: 48 - text: qsTr("Cancel") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - - onPressed: { - powerDialog.visible = false - } - } - - Button { - id: groundShutdown - width: 140 - height: 48 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - - text: { - if (powerAction == PowerPanel.PowerAction.RebootGround) { - return qsTr("Reboot Ground") - } - if (powerAction == PowerPanel.PowerAction.ShutdownGround) { - return qsTr("Shutdown Ground") - } - if (powerAction == PowerPanel.PowerAction.RebootAir) { - return qsTr("Reboot Air") - } - if (powerAction == PowerPanel.PowerAction.ShutdownAir) { - return qsTr("Shutdown Air") - } - if (powerAction == PowerPanel.PowerAction.RebootFC) { - return qsTr("Reboot Flight Controller") - } - if (powerAction == PowerPanel.PowerAction.ShutdownFC) { - return qsTr("Shutdown Flight Controller") - } - return qsTr("Yes") - } - - - - onPressed: { - var result=false - var messageForHUD="ERROR" - if (powerAction == PowerPanel.PowerAction.RebootGround) { - messageForHUD="Rebooting ground station" - result = _ohdSystemGround.send_command_reboot(true); - } - if (powerAction == PowerPanel.PowerAction.ShutdownGround) { - messageForHUD="Shutting down ground station" - result = _ohdSystemGround.send_command_reboot(false); - } - if (powerAction == PowerPanel.PowerAction.RebootAir) { - messageForHUD="Rebooting air pi" - result = _ohdSystemAir.send_command_reboot(true); - } - if (powerAction == PowerPanel.PowerAction.ShutdownAir) { - messageForHUD="Shutting down air pi" - result = _ohdSystemAir.send_command_reboot(false); - } - if (powerAction == PowerPanel.PowerAction.ShutdownFC) { //button commented out - messageForHUD="Shutting down Flight Controller" - result = _fcMavlinkSystem.send_command_reboot(false); - } - if (powerAction == PowerPanel.PowerAction.RebootFC) { - messageForHUD="Rebooting Flight Controller" - _fcMavlinkSystem.send_command_reboot(true); - } - if(result){ - settings_panel.visible = false; - powerDialog.visible = false - }else{ - console.log("Reboot/Shutdown failed, no response.") - messageForHUD="Reboot/Shutdown failed, no response" - _hudLogMessagesModel.add_message_warning(messageForHUD) - } - } - } - } - } -} diff --git a/qml/ui/configpopup/PowerPanelForm.qml b/qml/ui/configpopup/PowerPanelForm.qml deleted file mode 100644 index 64d6f1bc3..000000000 --- a/qml/ui/configpopup/PowerPanelForm.qml +++ /dev/null @@ -1,367 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Controls.Material 2.12 - -import Qt.labs.settings 1.0 - -import OpenHD 1.0 - -import "../../ui" as Ui -import "../elements" - -Rectangle { - Layout.fillHeight: true - Layout.fillWidth: true - - color: "#eaeaea" - - function voltage_as_string(voltage_mv){ - if(voltage_mv===0)return "N/A"; - return voltage_mv+" mV" - } - function current_as_string(current_ma){ - if(current_ma===0) return "N/A" - return current_ma+" mA" - } - - Card { - id: infoPanel - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.top: parent.top - anchors.topMargin: 12 - - hasHeader: false - height: infoColumn.height + 24 - - cardBody: Column { - id: infoColumn - spacing: 12 - width: infoPanel.width - - Text { - text: qsTr("Reboot or safely shut down the air/ground before removing power, or to apply settings changes when it is difficult to cycle power manually. If you have a power control board or sensors connected you may also see power supply information.") - wrapMode: Text.WordWrap - width: parent.width - leftPadding: 12 - rightPadding: 12 - topPadding: 12 - font.pixelSize: 14 - } - } - } - - RowLayout { - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.top: infoPanel.bottom - anchors.topMargin: 24 - anchors.left: parent.left - anchors.leftMargin: 12 - spacing: 6 - height: 192 - - Card { - id: airBox - height: 224 - Layout.fillWidth: true - cardName: qsTr("Air") - cardBody: ColumnLayout { - - RowLayout { - Layout.fillWidth: true - - Text { - text: qsTr("INA219 Voltage:") - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - leftPadding: 12 - } - - Text { - text: voltage_as_string(_ohdSystemAir.ina219_voltage_millivolt) - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - rightPadding: 12 - } - } - RowLayout { - Layout.fillWidth: true - - Text { - text: qsTr("INA219 Current:") - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - leftPadding: 12 - } - - Text { - text: current_as_string(_ohdSystemAir.ina219_current_milliamps) - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - rightPadding: 12 - } - } - } - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - id: airReboot - width: 96 - height: 48 - text: qsTr("Reboot") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - enabled: _ohdSystemAir.is_alive - - onPressed: { - powerAction = PowerPanel.PowerAction.RebootAir - powerDialog.visible = true - } - } - - Button { - id: airShutdown - width: 96 - height: 48 - text: qsTr("Shutdown") - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - enabled: _ohdSystemAir.is_alive - - onPressed: { - powerAction = PowerPanel.PowerAction.ShutdownAir - powerDialog.visible = true - } - } - } - } - - - Card { - id: groundBox - height: 224 - Layout.fillWidth: true - cardName: qsTr("Ground") - cardBody: ColumnLayout { - - RowLayout { - Layout.fillWidth: true - - Text { - text: qsTr("INA219 Voltage:") - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - leftPadding: 12 - } - - Text { - text: voltage_as_string(_ohdSystemGround.ina219_voltage_millivolt) - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - rightPadding: 12 - } - } - RowLayout { - Layout.fillWidth: true - - Text { - text: qsTr("INA219 Current:") - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - font.bold: true - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - leftPadding: 12 - } - - Text { - text: current_as_string(_ohdSystemGround.ina219_current_milliamps) - height: 24 - Layout.fillWidth: true - font.pixelSize: 14 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - rightPadding: 12 - } - } - } - - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - id: groundReboot - width: 96 - height: 48 - text: qsTr("Reboot") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - enabled: _ohdSystemGround.is_alive - - onPressed: { - powerAction = PowerPanel.PowerAction.RebootGround - powerDialog.visible = true - } - } - - Button { - id: groundShutdown - width: 96 - height: 48 - text: qsTr("Shutdown") - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - enabled: _ohdSystemGround.is_alive - - onPressed: { - powerAction = PowerPanel.PowerAction.ShutdownGround - powerDialog.visible = true - } - } - } - } - - - Card { - id: fcBox - visible: true - enabled: _fcMavlinkSystem.is_alive - height: 224 - Layout.fillWidth: true - cardName: qsTr("Flight Controller") - cardBody: ColumnLayout { - //empty.. - } - - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - id: fcReboot - width: 96 - height: 48 - text: qsTr("Reboot") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - - onPressed: { - powerAction = PowerPanel.PowerAction.RebootFC - powerDialog.visible = true - } - } -/* needs testing on actual FC. Shutdown is not accepted by SITL - Button { - id: fcShutdown - width: 96 - height: 48 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - - text: qsTr("Shutdown") - - onPressed: { - powerAction = PowerPanel.PowerAction.ShutdownFC - powerDialog.visible = true - } - } - */ - } - } - } - Button { - id: devCancelQOpenHD - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - - text: qsTr("DEV! Cancel QOpenHD") - - onPressed: { - // disables the service (such that qopenhd doesn't get restarted), then quits qopenhd - // NOTE: only works on images where the QT auto restart service is active - _qopenhd.disable_service_and_quit() - } - } - Button { - id: devRestartQOpenHD - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: devCancelQOpenHD.top - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Orange - highlighted: true - - text: qsTr("DEV! Restart QOpenHD") - - onPressed: { - // Will be restarted by the service - _qopenhd.quit_qopenhd() - } - } - - -} diff --git a/qml/ui/configpopup/connect/ConnectPanel.qml b/qml/ui/configpopup/connect/ConnectPanel.qml new file mode 100644 index 000000000..a5eb55766 --- /dev/null +++ b/qml/ui/configpopup/connect/ConnectPanel.qml @@ -0,0 +1,131 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +// This panel is only for users that run QOpenHD on an external device,e.g. android not on the ground station itself +Item { + Layout.fillHeight: true + Layout.fillWidth: true + + property int rowHeight: 64 + property int elementHeight: 48 + property int elementComboBoxWidth: 300 + + property bool m_is_connected_gnd: _ohdSystemGround.is_alive + + // Tab bar for selecting items in stack layout + TabBar { + id: selectItemInStackLayoutBar + width: parent.width + TabButton { + text: qsTr("INFO") + } + TabButton { + text: qsTr("USB Tether") + } + TabButton { + text: qsTr("(USB) Ethernet passive") + } + TabButton { + text: qsTr("(USB) Ethernet active") + } + TabButton { + text: qsTr("WiFi") + } + TabButton { + text: qsTr("Custom (TCP)") + } + } + + // placed right below the top bar + StackLayout { + width: parent.width + height: parent.height-selectItemInStackLayoutBar.height + anchors.top: selectItemInStackLayoutBar.bottom + anchors.left: selectItemInStackLayoutBar.left + anchors.bottom: parent.bottom + currentIndex: selectItemInStackLayoutBar.currentIndex + + Rectangle{ + width: parent.width + height: parent.height + ColumnLayout{ + anchors.fill: parent + spacing: 6 + Text{ + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + Layout.preferredHeight: 50 + //width:parent.width + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignTop + text: "When running QOpenHD on an external device (e.g. android phone)\n"+ + "you have to use one of the given choices to connect the device to the OpenHD ground station" + } + Text{ + Layout.preferredHeight: 50 + text: "You are already connected to your ground station - nothing to do." + color: "#706F1D" // dark green + visible: m_is_connected_gnd + } + Text{ + Layout.preferredHeight: 50 + text: "Looks like you are not connected to your ground station - please use one of the given choices to connect." + color: "red" + visible: !m_is_connected_gnd + } + Item{ + Layout.fillHeight: true + // filler + } + } + } + + /*InfoPane{ + m_info_text: { + return "When running QOpenHD on an external device (e.g. android phone)\n"+ + "you have to use one of the given choices to connect the device to the OpenHD ground station" + } + }*/ + + PaneUSBTether{ + id: pane_usb_tether + } + + InfoPane{ + m_info_text: { + return"1) Make sure ETH_HOTSPOT_E is disabled (Ground param)\n\n"+ + "2) Enable ETH_PASSIVE_F on your openhd ground unit (Ground param)\n\n"+ + "3) Connect your external device running QOpenHD to your ground station via ethernet\n(e.g. ethernet port on rpi 4).\n\n"+ + "4) Make sure to select 'share my internet with ...' when the android connection setup pops up\n\n"+ + "Video and telemetry forwarding should start automatically, and your GCS can get internet from your phone." + } + } + InfoPane{ + m_info_text: { + return "1) Make sure ETH_PASSIVE_F is disabled (Ground param)\n\n"+ + "2) Enable ETH_HOTSPOT_E on your openhd ground unit (Ground param)\n\n"+ + "3) Connect your external device running QOpenHD to your ground station via ethernet\n(e.g. ethernet port on rpi 4).\n\n"+ + "You might have to disable wifi and cellular on your phone !.\n\n"+ + "Video and telemetry forwarding should start automatically." + } + } + + PaneWIFI{ + id: wifi + } + + PaneCustom{ + id: pane_custom + } + } +} diff --git a/qml/ui/configpopup/connect/InfoPane.qml b/qml/ui/configpopup/connect/InfoPane.qml new file mode 100644 index 000000000..6157444f8 --- /dev/null +++ b/qml/ui/configpopup/connect/InfoPane.qml @@ -0,0 +1,46 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Pane { + width: parent.width + height: parent.height + //color: "green" + + property string m_info_text: "I SHOULD NEVER APPEAR" + + ScrollView { + id:mavlinkExtraWBParamPanel + width: parent.width + height: parent.height + contentHeight: mainItem.height + contentWidth: mainItem.width + clip: true + //ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.interactive: true + ScrollBar.horizontal.interactive: true + + ColumnLayout{ + anchors.fill: parent + id: mainItem + spacing: 6 + + Text{ + Layout.alignment: Qt.AlignTop + width:parent.width + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignTop + text: m_info_text + } + } + } +} diff --git a/qml/ui/configpopup/connect/PaneCustom.qml b/qml/ui/configpopup/connect/PaneCustom.qml new file mode 100644 index 000000000..2ae84be2e --- /dev/null +++ b/qml/ui/configpopup/connect/PaneCustom.qml @@ -0,0 +1,70 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Rectangle { + width: parent.width + height: parent.height + //color: "green" + + property string m_info_text: "CUSTOM" + + ColumnLayout{ + anchors.fill: parent + + spacing: 6 + Text{ + Layout.alignment: Qt.AlignTop + width:parent.width + wrapMode: Text.WordWrap + text: m_info_text + } + RowLayout{ + TextField { + id: textFieldip + validator: RegExpValidator { + regExp: /^((?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){0,3}(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/ + } + inputMethodHints: Qt.ImhFormattedNumbersOnly + text: "192.168.178.36" //settings.dev_mavlink_tcp_ip + } + ButtonIconInfoText{ + m_info_text: "Enter your grund station IP, then click 'CONNECT TCP'" + } + } + + Button{ + Layout.alignment: Qt.AlignTop + text: "CONNECT TCP" + onClicked: { + var ip_address=textFieldip.text + if(_qopenhd.is_valid_ip(ip_address)){ + _mavlinkTelemetry.add_tcp_connection_handler(ip_address) + }else{ + _qopenhd.show_toast("Please enter a valid ip") + } + } + } + Button{ + text: "RE-ENABLE UDP" + onClicked: { + _mavlinkTelemetry.enable_udp() + } + } + // padding to bottom + Item{ + Layout.fillHeight: true + Layout.fillWidth: true + } + + } +} diff --git a/qml/ui/configpopup/connect/PaneUSBTether.qml b/qml/ui/configpopup/connect/PaneUSBTether.qml new file mode 100644 index 000000000..c053e15db --- /dev/null +++ b/qml/ui/configpopup/connect/PaneUSBTether.qml @@ -0,0 +1,46 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Rectangle { + width: parent.width + height: parent.height + //color: "green" + + property string m_info_text: "1) Connect your phone via high quality USB cable to your ground station.\n\n"+ + "2) enable USB Tethering on your phone.\n\n"+ + "3) Telemetry and video forwarding is started automatically \n\n"+ + " ! Requires a phone and cellular contract that allows USB tethering. !" + + ColumnLayout{ + anchors.fill: parent + anchors.top: parent.top + spacing: 6 + + Text{ + Layout.alignment: Qt.AlignTop + width:parent.width + wrapMode: Text.WordWrap + text: m_info_text + } + Button{ + Layout.alignment: Qt.AlignTop + text: "Open settings" + onClicked: _qopenhd.android_open_tethering_settings() + } + // padding to bottom + Item{ + Layout.fillHeight: true + Layout.fillWidth: true + } + } +} diff --git a/qml/ui/configpopup/connect/PaneWIFI.qml b/qml/ui/configpopup/connect/PaneWIFI.qml new file mode 100644 index 000000000..e765c6ef3 --- /dev/null +++ b/qml/ui/configpopup/connect/PaneWIFI.qml @@ -0,0 +1,45 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Rectangle { + width: parent.width + height: parent.height + //color: "green" + + property string m_info_text: "1) Make sure WIFI_HOTSPOT_E is enabled (on air or ground unit)\n\n"+ + "2) Connect to WiFi: openhd pw openhdopenhd\n\n"+ + "3) Press 'Connect' button.\n\n"+ + "NOTE: You cannot use WIFI hotspot during flight (while armed)" + + ColumnLayout{ + anchors.fill: parent + + spacing: 6 + Text{ + Layout.alignment: Qt.AlignTop + width:parent.width + wrapMode: Text.WordWrap + text: m_info_text + } + Button{ + Layout.alignment: Qt.AlignTop + text: "Connect Air/Ground Hotspot" + onClicked: _mavlinkTelemetry.add_tcp_connection_handler("192.168.3.1") // ground / air pi address in hotspot mode + } + // padding to bottom + Item{ + Layout.fillHeight: true + Layout.fillWidth: true + } + } +} diff --git a/qml/ui/configpopup/EasterEggPanel.qml b/qml/ui/configpopup/credits/Credits.qml similarity index 90% rename from qml/ui/configpopup/EasterEggPanel.qml rename to qml/ui/configpopup/credits/Credits.qml index 9546f7783..9cd693906 100644 --- a/qml/ui/configpopup/EasterEggPanel.qml +++ b/qml/ui/configpopup/credits/Credits.qml @@ -7,8 +7,10 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" + +// This hereby functions as a copyrighted declaration. Any unpermitted alteration, suppression, or eradication of this page is expressly forbidden unless granted explicit authorization by the OpenHD development team. Rectangle { Layout.fillHeight: true @@ -40,7 +42,7 @@ Rectangle { Image { id: ee1 visible: true - source: "../../resources/master1.png" + source: "../../../resources/master1.png" width: consti.height * 0.7 height: consti.height * 0.7 } @@ -66,7 +68,7 @@ Rectangle { Image { id: ee3 visible: true - source: "../../resources/master4.png" + source: "../../../resources/master4.png" width: max.height * 0.7 height: max.height * 0.7 } @@ -97,7 +99,7 @@ Rectangle { Image { id: ee2 visible: true - source: "../../resources/master2.png" + source: "../../../resources/master2.png" width: rapha.height * 0.7 height: rapha.height * 0.7 } @@ -123,7 +125,7 @@ Rectangle { Image { id: ee4 visible: true - source: "../../resources/master5.png" + source: "../../../resources/master5.png" width: thomas.height * 0.7 height: thomas.height * 0.7 } @@ -153,7 +155,7 @@ Rectangle { Image { id: ee5 visible: true - source: "../../resources/master3.png" + source: "../../../resources/master3.png" width: rapha.height * 0.7 height: rapha.height * 0.7 } @@ -179,7 +181,7 @@ Rectangle { Image { id: ee6 visible: false - source: "../../resources/master4.png" + source: "../../../resources/master4.png" width: thomas.height * 0.7 height: thomas.height * 0.7 } diff --git a/qml/ui/configpopup/AppDeveloperStatsPanel.qml b/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml similarity index 94% rename from qml/ui/configpopup/AppDeveloperStatsPanel.qml rename to qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml index 99c9ae816..ca2030023 100644 --- a/qml/ui/configpopup/AppDeveloperStatsPanel.qml +++ b/qml/ui/configpopup/dev/AppDeveloperStatsPanel.qml @@ -7,8 +7,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" // Dirty here i place stuff that can be usefaully during development Rectangle { @@ -31,7 +31,6 @@ Rectangle { ret+="AVCODEC:"+yes_or_no_as_string(QOPENHD_ENABLE_VIDEO_VIA_AVCODEC)+", " ret+="MMAL:"+yes_or_no_as_string(QOPENHD_HAVE_MMAL)+", " ret+="GSTREAMER_QMLGLSINK:"+yes_or_no_as_string(QOPENHD_ENABLE_GSTREAMER_QMLGLSINK)+", " - ret+="ADSB:"+yes_or_no_as_string(QOPENHD_ENABLE_ADSB_LIBRARY) return ret; } @@ -82,6 +81,10 @@ Rectangle { id: test2 text: qsTr("video0 FEC encode: "+_cameraStreamModelPrimary.curr_video0_fec_encode_time_avg_min_max) } + Text { + id: testX + text: qsTr("video0 TX delay: "+_cameraStreamModelPrimary.curr_time_until_tx_min_max_avg) + } Text { id: test4 text: qsTr("video0 FEC block length: "+_cameraStreamModelPrimary.curr_video0_fec_block_length_min_max_avg) @@ -91,7 +94,6 @@ Rectangle { id: test3 text: qsTr("video0 FEC decode: "+_cameraStreamModelPrimary.curr_video0_fec_decode_time_avg_min_max) } - Text { id: test5 text: qsTr("FEATURES: "+get_features_string()) diff --git a/qml/ui/configpopup/LogMessagesStatusView.qml b/qml/ui/configpopup/log/LogMessagesStatusView.qml similarity index 98% rename from qml/ui/configpopup/LogMessagesStatusView.qml rename to qml/ui/configpopup/log/LogMessagesStatusView.qml index c1c1cd935..b180a70a1 100644 --- a/qml/ui/configpopup/LogMessagesStatusView.qml +++ b/qml/ui/configpopup/log/LogMessagesStatusView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" Item { Layout.fillHeight: true diff --git a/qml/ui/configpopup/openhd_settings/DIaloqueStartChannelScan.qml b/qml/ui/configpopup/openhd_settings/DIaloqueStartChannelScan.qml new file mode 100644 index 000000000..6b6e9493e --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/DIaloqueStartChannelScan.qml @@ -0,0 +1,208 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// I fucking hate writing UIs in QT +// Dialoque that initiates a channel scan once the user selected everything correctly / passed all the checks +Card { + id: dialoqueStartChannelScan + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: qsTr("Find Air Unit") + cardNameColor: "black" + visible: false + + property int m_curr_index: 0 + + function open_channel_scan_dialoque(){ + var is_armed= _fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed + if(is_armed){ + m_curr_index=0; + }else{ + m_curr_index=1; + } + dialoqueStartChannelScan.visible=true + } + + ListModel{ + id: model_chann_2G_or_5G + ListElement {title: "All 2.4G and 5.8G channels (slow)"; value: 0} + ListElement {title: "All 2.4G channels"; value: 1} + ListElement {title: "All 5.8G channels"; value: 2} + } + + // like dji, we use 20 or 40Mhz bandwidth(s) but never 5 or 10 + ListModel{ + id: model_bandwidth_all_or_20_or_40 + ListElement {title: "20Mhz and 40Mhz (slow)"; value: 0} + ListElement {title: "20Mhz only"; value: 1} + ListElement {title: "40Mhz only"; value: 2} + } + + cardBody: Item{ + height: 200 + width: 320 + // warn if armed dialoque + Item{ + id: dialoque1 + visible: m_curr_index==0 + width: parent.width + height: parent.height + Text{ + text: "WARNING ! Performing a channel scan while armed is not recommended, you'l loose connecion !" + width: parent.width + height: parent.height-100 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + // select and then initiate dialoque + Item{ + id: dialoque2 + visible: m_curr_index==1 + width: parent.width + height: parent.height + Text { + id: dialoqueStartChannelScan_text + text: "Initiate Channel Scan (Find a running air unit). Might take more than 1 minute if you are not specifying the generic band below. Otherwise,it'l take + max 30 seconds, usually less" + width: parent.width + height: parent.height-100 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + + } + ComboBox { + width: parent.width + //height: 100 + anchors.top: dialoqueStartChannelScan_text.bottom + id: comboBoxWhichFrequencyToScan + model: model_chann_2G_or_5G + textRole: "title" + Material.background: { + (comboBoxWhichFrequencyToScan.currentIndex===0) ? Material.Orange : Material.Green + } + onCurrentIndexChanged: { + } + } + ComboBox { + width: parent.width + //height: 100 + anchors.top: comboBoxWhichFrequencyToScan.bottom + id: comboBoxWhichChannelWidthsToScan + model: model_bandwidth_all_or_20_or_40 + textRole: "title" + Material.background: { + (comboBoxWhichChannelWidthsToScan.currentIndex===0) ? Material.Orange : Material.Green + } + onCurrentIndexChanged: { + } + } + } + // after initiate dialoque + Item{ + id: dialoque3 + visible: m_curr_index==2 + width: parent.width + height: parent.height + Text{ + id: dialoqueStartChannelScan_text2 + text: "Scanning channels, you can close this dialoque and go back to your OSD screen. PLEASE DO NOT CHANGE SETTINGS WHILE CHANNEL SCAN IS ACTIVE !" + width: parent.width + height: parent.height-100 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + + // First dialoque + RowLayout{ + anchors.fill: parent + visible: m_curr_index==0 + ButtonRed{ + text: "PROCCEED ANYWAY" + /*Layout.fillWidth: true + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft*/ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + onClicked: { + m_curr_index++; + } + } + ButtonGreen{ + text: "CANCEL" + /*Layout.fillWidth: true + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight*/ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + onClicked: { + dialoqueStartChannelScan.visible=false; + } + } + } + // Second dialoque + RowLayout{ + anchors.fill: parent + visible: m_curr_index==1 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("Cancel") + visible: m_curr_index==1 + onPressed: { + dialoqueStartChannelScan.visible=false; + } + } + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("Initiate") + onPressed: { + var how_many_freq_bands=comboBoxWhichFrequencyToScan.currentIndex + var how_many_bandwidths=comboBoxWhichChannelWidthsToScan.currentIndex + console.log("Initate channel scan "+how_many_freq_bands+","+how_many_bandwidths) + var result = _wbLinkSettingsHelper.start_scan_channels(how_many_freq_bands,how_many_bandwidths) + if(result){ + m_curr_index=2; + }else{ + console.log("Cannot initiate channel scan"); + _qopenhd.show_toast("Busy,please try again later",true) + } + } + } + } + RowLayout{ + anchors.fill: parent + visible: m_curr_index==2 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("Close") + onPressed: { + dialoqueStartChannelScan.visible=false; + } + } + } + } +} diff --git a/qml/ui/configpopup/openhd_settings/DialoqueChangeTxPower.qml b/qml/ui/configpopup/openhd_settings/DialoqueChangeTxPower.qml new file mode 100644 index 000000000..d64ac00b5 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/DialoqueChangeTxPower.qml @@ -0,0 +1,454 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// I fucking hate writing UIs in QT +// Dialoque that changes the tx power once the user selected everything correctly / passed all the checks +Card { + id: dialoqueTxPower + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: qsTr("Change TX PWR "+get_is_ground_as_string()) + cardNameColor: "black" + visible: false + + // check if all ground cards use the same chipset - otherwise, this wizard cannot be used + function gnd_check_all_active_cards_are_same_type(){ + var card0_type=_wifi_card_gnd0.card_type; + if(_wifi_card_gnd1.alive && (_wifi_card_gnd1.card_type != card0_type)){ + return false; + } + if(_wifi_card_gnd2.alive && (_wifi_card_gnd2.card_type != card0_type)){ + return false; + } + if(_wifi_card_gnd3.alive && (_wifi_card_gnd3.card_type != card0_type)){ + return false; + } + return true; + } + + function open_tx_power_dialoque(ground){ + var is_armed= _fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed + if(is_armed){ + // User has to click proceed anyways on armed warning + m_curr_index=0; + }else{ + m_curr_index=1; + } + m_is_ground=ground; + m_card_chip_type=-1; + m_card_manufacturer_type=-1 + comboBoxSelectedArmedDisarmed.currentIndex=0 + comboBoxCardSelectTxPower.currentIndex=0 + comboBoxCardSelectManufacturer.currentIndex=0; + // We figure out the chip type automatcally (info from openhd) + var wifi_card_chip_type=-1; + if(m_is_ground){ + // Check if all cards are of the same type + if(!gnd_check_all_active_cards_are_same_type()){ + _messageBoxInstance.set_text_and_show("This wizard is only available when you use the same card(s) on ground. Please unify your gnd station cards. +You can still use the RAW OpenHD AIR / GROUND Parameters to change your tx power."); + return; + } + wifi_card_chip_type=_wifi_card_gnd0.card_type; + //wifi_card_chip_type=1 + }else{ + wifi_card_chip_type=_wifi_card_air.card_type; + } + if(!(wifi_card_chip_type==0 || wifi_card_chip_type==1)){ + _messageBoxInstance.set_text_and_show("Card type unknown / unsupported - please use a supported card. You can still change the tx power manually via the param set, but probably +the card driver and/or the linux kernel doesn't support changing it."); + return; + } + // 0 is RTL8812AU, 1 is RTL8812BU + m_card_chip_type=wifi_card_chip_type; + //m_card_chip_type=0; // RTL8812AU + dialoqueTxPower.visible=true + } + + property int m_curr_index: 0 + + property bool m_is_ground: true + function get_is_ground_as_string(){ + return m_is_ground ? "GND" : "AIR"; + } + + ListModel{ + id: armed_or_disarmed_model + ListElement {title: "NOT SELECTED"; value: -1} + ListElement {title: "DISARMED"; value: 0} + ListElement {title: "ARMED (RECOMMENDED)"; value: 0} + } + property bool m_change_armed: false + function get_change_armed_as_string(){ + return m_change_armed ? "ARMED" : "DISARMED"; + } + + // Chip type is automatically refered from OpenHD + property int m_card_chip_type: -1; + function get_card_chip_type_as_string(){ + if(m_card_chip_type==0) return "RTL88XXAU"; + if(m_card_chip_type==1) return "RTL88XXBU"; + return "UNKNOWN"; + } + + // Manufacturer the user has to select + property int m_card_manufacturer_type: -1 + function get_card_manufacturer_type_as_string(){ + if(m_card_chip_type==0){ + // RTL8812AU + if(m_card_manufacturer_type==0)return "ASUS"; + if(m_card_manufacturer_type==1)return "ALIEXPRESS"; + if(m_card_manufacturer_type==2)return "OTHER"; + }else{ + // RTL8812BU + if(m_card_manufacturer_type==0)return "COMFAST"; + if(m_card_manufacturer_type==1)return "OTHER"; + } + return "UNKNWON"; + } + + ListModel{ + id: plcaeholder_model + ListElement {title: "I should never appear"; value: -1} + } + + ListModel{ + id: model_rtl8812au_manufacturers + ListElement {title: "NOT SELECTED"; value: -1} + ListElement {title: "ASUS"; value: 0} + ListElement {title: "ALIEXPRESS"; value: 1} + ListElement {title: "OTHER"; value: 2} + } + ListModel{ + id: model_rtl8812bu_manufacturers + ListElement {title: "NOT SELECTED"; value: -1} + ListElement {title: "COMFAST"; value: 0} + ListElement {title: "OTHER"; value: 1} + } + + function get_model_manufacturer_for_chip_type(){ + if(m_card_chip_type==0){ + return model_rtl8812au_manufacturers; + }else if(m_card_chip_type==1){ + return model_rtl8812bu_manufacturers; + } + return plcaeholder_model; + } + + property int tx_power_index_or_mw: -1 + + ListModel{ + id: model_rtl8812au_manufacturer_asus_txpower + ListElement {title: "Please select"; value: -1} + ListElement {title: "LOW [22] ~20mW (DEFAULT) "; value: 22} + ListElement {title: "MEDIUM [37] ~100mW "; value: 37} + ListElement {title: "HIGH [53] ~320mW "; value: 53} + ListElement {title: "MAX1 [58] ~420mW (MCS<=2!)"; value: 58} + ListElement {title: "MAX2 [63] ~500mW (MCS<=2!)"; value: 63} + } + ListModel{ + id: model_rtl8812au_manufacturer_aliexpress_hp + ListElement {title: "Please select"; value: -1} + ListElement {title: "LOW [16]"; value: 16} + ListElement {title: "MEDIUM [22]"; value: 22} + ListElement {title: "HIGH [24]"; value: 24} + ListElement {title: "MAX [26]"; value: 26} + } + ListModel{ + id: model_rtl8812au_manufacturer_generic + ListElement {title: "Please select"; value: -1} + ListElement {title: "[22] (DANGER,ARBITRARY)"; value: 22} + ListElement {title: "[37] (DANGER,ARBITRARY)"; value: 37} + ListElement {title: "[53] (DANGER,ARBITRARY)"; value: 53} + ListElement {title: "[58] (DANGER,ARBITRARY)"; value: 58} + ListElement {title: "[63] (DANGER,ARBITRARY)"; value: 63} + } + // RTL8812BU begin + ListModel{ + id: model_rtl8812bu_manufacturer_comfast + ListElement {title: "Please select"; value: -1} + ListElement {title: "~25mW"; value: 25} + ListElement {title: "~100mW"; value: 100} + ListElement {title: "~200mW"; value: 200} + ListElement {title: "~300mW"; value: 300} + } + ListModel{ + id: model_rtl8812bu_manufacturer_generic + ListElement {title: "Please select"; value: -1} + ListElement {title: "25mW"; value: 25} + ListElement {title: "100mW"; value: 100} + ListElement {title: "<=300mW"; value: 300} + ListElement {title: "<=1000mW"; value: 1000} + ListElement {title: "<=20000mW"; value: 2000} + } + + function get_model_select_tx_power_index_for_card_chip_type_and_manufacturer(){ + if(m_card_chip_type==0){ + // RTL8812AU + if(m_card_manufacturer_type==0){ + return model_rtl8812au_manufacturer_asus_txpower; + }else if(m_card_manufacturer_type==1){ + return model_rtl8812au_manufacturer_aliexpress_hp; + } + return model_rtl8812au_manufacturer_generic; + }else if(m_card_chip_type==1){ + // RTL8812BU + if(m_card_manufacturer_type==0){ + return model_rtl8812bu_manufacturer_comfast; + } + return model_rtl8812bu_manufacturer_generic; + } + return plcaeholder_model; + } + + function get_text_select_card_manufacturer(){ + var ret=""; + ret+="Your chipset: "+get_card_chip_type_as_string()+"\n"; + ret+="Please select your card manufacturer from the list below."; + if(m_card_chip_type==0){ + ret+="\nWARNING: IF YOU SELECT THE WRONG MANUFACTURER,YOU CAN EASILY DESTROY YOUR CARD - this card doesn't take tx power in mW, but a tx power index value "+ + "in arbitrary unit(s) depending on your manufacturer."; + }else if(m_card_chip_type==1){ + ret+="\nINFO: If you select any tx power in mW, depending on your manufacturer, your actual tx power will always be <= than the selected value."; + } + + return ret; + } + + function get_text_select_tx_power(){ + var ret=""; + ret += "Your chipset: "+get_card_chip_type_as_string()+"\n"; + ret += "Selected manufacturer: "+get_card_manufacturer_type_as_string()+"\n"; + ret+="Please select your wanted TX power applied when:\n"+get_change_armed_as_string()+" state"+"\n"; + return ret; + } + + cardBody: Rectangle{ + /*height: 340 - 100 + width: 360-10*/ + color: "orange" + //anchors.centerIn: parent + anchors.fill: parent + Item{ + width: parent.width + height: parent.height + visible: m_curr_index==0 + Text{ + text: ("WARNING ! Changing TX power while FC is armed is not recommended -\n"+ + " Validate / test your setup while disarmed, then set the apropate tx power for armed / disarmed state. !") + width: parent.width + height: parent.height / 2 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + ColumnLayout{ + width: parent.width + height: parent.height + visible: m_curr_index==1 + Text{ + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + text: ("Do you want to set the TX power in armed / disarmed state ?\n"+ +"NOTE: TX power armed is only applied when the FC is armed, TX power disarmed is applied when the FC is disarmed.") + wrapMode: Text.WordWrap + } + ComboBox { + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + id: comboBoxSelectedArmedDisarmed + model: armed_or_disarmed_model + textRole: "title" + onCurrentIndexChanged: { + if(comboBoxSelectedArmedDisarmed.currentIndex==1)m_change_armed=false; + if(comboBoxSelectedArmedDisarmed.currentIndex==2)m_change_armed=true; + } + } + } + ColumnLayout{ + width: parent.width + height: parent.height + visible: m_curr_index==2 + Text{ + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + id: card_select_manufcturer_description + text: get_text_select_card_manufacturer() + wrapMode: Text.WordWrap + } + ComboBox { + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + id: comboBoxCardSelectManufacturer + model: get_model_manufacturer_for_chip_type() + textRole: "title" + onCurrentIndexChanged: { + var manufacturer=comboBoxCardSelectManufacturer.model.get(comboBoxCardSelectManufacturer.currentIndex).value; + console.log("Selected manufacturerer "+get_card_chip_type_as_string()+" :"+manufacturer); + m_card_manufacturer_type=manufacturer; + } + } + } + + ColumnLayout{ + width: parent.width + height: parent.height + visible: m_curr_index==3 + Text{ + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + text: get_text_select_tx_power() + wrapMode: Text.WordWrap + } + ComboBox { + Layout.fillWidth: true + leftPadding: 12 + rightPadding: 12 + id: comboBoxCardSelectTxPower + model: get_model_select_tx_power_index_for_card_chip_type_and_manufacturer() + textRole: "title" + onCurrentIndexChanged: { + var tx_power=comboBoxCardSelectTxPower.model.get(comboBoxCardSelectTxPower.currentIndex).value; + tx_power_index_or_mw=tx_power; + console.log("Selected TX power (mW or index)"+tx_power_index_or_mw); + } + } + } + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + // change though armed + RowLayout{ + anchors.fill: parent + visible: m_curr_index==0 + ButtonRed{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: "CONTINUE ANYWAYS" + onClicked: { + m_curr_index=1 + } + } + ButtonGreen{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: "Cancel" + onClicked: { + txPowerDialoque.visible=false; + } + } + } + RowLayout{ + anchors.fill: parent + visible: m_curr_index==1 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: "CANCEL" + onClicked: { + txPowerDialoque.visible=false; + } + } + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: "Continue" + onClicked: { + if(comboBoxSelectedArmedDisarmed.currentIndex<=0){ + _qopenhd.show_toast("Please specify armed / disarmed",false); + }else{ + m_curr_index=2; + } + } + } + } + RowLayout{ + anchors.fill: parent + visible: m_curr_index==2 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: "CANCEL" + onClicked: { + txPowerDialoque.visible=false; + } + } + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: "CONTINUE" + onClicked: { + if(m_card_manufacturer_type<0){ + _qopenhd.show_toast("Please select your card manufacturer",false); + }else{ + m_curr_index=3; + } + } + } + } + RowLayout{ + anchors.fill: parent + visible: m_curr_index==3 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: "CANCEL" + onClicked: { + txPowerDialoque.visible=false; + } + } + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: "APPLY" + onClicked: { + if(tx_power_index_or_mw<0){ + _qopenhd.show_toast("Please select a valid tx power",false); + return; + } + var success=false + var is_tx_power_index_unit = m_card_chip_type==0; + + success = _wbLinkSettingsHelper.set_param_tx_power(m_is_ground,is_tx_power_index_unit,m_change_armed,tx_power_index_or_mw) + if(success==true){ + txPowerDialoque.visible=false; + var message = "Set TX POWER "+get_change_armed_as_string()+" to "; + if(is_tx_power_index_unit){ + message += ""+tx_power_index_or_mw+" tx power index (unitless)" + }else{ + message += ""+tx_power_index_or_mw+" mW" + } + if(m_change_armed){ + message += "\nNOTE: Only applied once you arm your FC !"; + } + _messageBoxInstance.set_text_and_show(message, 10); + }else{ + _qopenhd.show_toast("Cannot change TX power, please try again",true); + } + } + } + } + } +} diff --git a/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeArmed.qml b/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeArmed.qml new file mode 100644 index 000000000..a28489a6e --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeArmed.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// Dialoque that is shown to the user when the FC is armed and he wants to change frequency +// Once the user clicks the warning away, proceed as if FC is not armed +Card { + id: dialoqueFreqChangeArmed + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: get_card_title_string() + cardNameColor: "black" + visible: false + + property int m_wanted_frequency: -1 + + function initialize_and_show_frequency(frequency){ + m_wanted_frequency=frequency + m_wanted_channel_width=-1 + m_type=0; + dialoqueFreqChangeArmed.visible=true + } + + function get_card_title_string(){ + return "Frequency "+m_wanted_frequency+"Mhz" + } + + function get_card_body_string(){ + return "WARNING ! Changing frequency while armed is not recommended - while unlikely, changing them needs synchronization and therefore can fail, +after which you have to perform a channel scan to reconnect to your air unit."; + } + + cardBody: Item{ + height: 200 + width: 320 + + Text{ + width: parent.width + height: parent.height + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + text: get_card_body_string(); + } + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + RowLayout{ + anchors.fill: parent + ButtonRed{ + Layout.preferredWidth: 180 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("CONTINUE ANYWAY") + onPressed: { + dialoqueFreqChangeArmed.visible=false + // Call function from MavlinkExtraWBParamPanel + change_frequency_sync_otherwise_handle_error(m_wanted_frequency,true/*ignore armed state*/) + } + } + ButtonGreen{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("Okay") + onPressed: { + dialoqueFreqChangeArmed.visible=false + } + } + } + } +} + diff --git a/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeGndOnly.qml b/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeGndOnly.qml new file mode 100644 index 000000000..b615df7b2 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/DialoqueFreqChangeGndOnly.qml @@ -0,0 +1,124 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// I fucking hate writing UIs in QT +// Dialoque that is shown to the user with the option to manually change the grond station frequency. +Card { + id: dialoqueChangeFrequency + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: get_card_title_string() + cardNameColor: "black" + visible: false + + property int m_wanted_frequency: -1 + + // Set to 1 to show the final warning message after which channel frequency or channel width are applied + property int m_index: 0 + + property string m_original_error_message: "" + + function initialize_and_show_frequency(frequency,error_message){ + m_wanted_frequency=frequency + m_index=0 + m_original_error_message=error_message; + dialoqueChangeFrequency.visible=true + } + + property string m_info_string_frequency: "Please use the channel scan to find your air unit, then change frequency."+ +"Otherwise, you can manually change your ground station frequency,"+ +"leaving your air unit untouched - thiis can be quicker than a channel scan if you know your air unit frequency." + + property string m_info_ground_only: "WARNING: This changes your ground unit frequency without changing your air unit frequency !" + + property string m_last_warning_frequency: "WARNING: This changes your ground unit frequency without changing your air unit frequency !" + + function get_card_title_string(){ + return "Frequency "+m_wanted_frequency+"Mhz" + } + + function get_card_body_string(){ + if(m_index==0){ + // In Info mode + return m_original_error_message +"\n"+ m_info_string_frequency; + } + return m_last_warning_frequency + } + + cardBody: Item{ + height: 200 + width: 320 + + Text{ + width: parent.width + height: parent.height + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + + text: get_card_body_string(); + } + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + RowLayout{ + anchors.fill: parent + visible: m_index==0 + ButtonRed{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("GND Only") + onPressed: { + m_index=1 + } + } + ButtonGreen{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("Okay") + onPressed: { + dialoqueChangeFrequency.visible=false + } + } + } + RowLayout{ + anchors.fill: parent + visible: m_index==1 + ButtonGreen{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("YES,GND ONLY") + onPressed: { + console.log("Try changing ground only to frequency "+m_wanted_frequency) + var result = _wbLinkSettingsHelper.change_param_ground_only_frequency(m_wanted_frequency); + if(result){ + _qopenhd.show_toast("GND set to frequency "+m_wanted_frequency+"Mhz",false); + dialoqueChangeFrequency.visible=false; + }else{ + _qopenhd.show_toast("Failed, GND busy,please try again later",true); + } + } + } + ButtonRed{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("Cancel") + onPressed: { + dialoqueChangeFrequency.visible=false; + } + } + } + } +} diff --git a/qml/ui/configpopup/openhd_settings/DialoqueStartAnalyzeChannels.qml b/qml/ui/configpopup/openhd_settings/DialoqueStartAnalyzeChannels.qml new file mode 100644 index 000000000..4aa8ce965 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/DialoqueStartAnalyzeChannels.qml @@ -0,0 +1,119 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +import "../../elements" + +// I fucking hate writing UIs in QT +// Dialoque that initiates a channel/frequency analyze once the user selected everything correctly / passed all the checks +Card { + id: dialoqueAnalyzeChannels + width: 360 + height: 340 + z: 5.0 + anchors.centerIn: parent + cardName: qsTr("Analyze Channels") + cardNameColor: "black" + visible: false + + property int m_index: 0 + + function setup_and_show(){ + var is_armed= _fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed + if(is_armed){ + m_index=0; + }else{ + m_index=1; + } + dialoqueAnalyzeChannels.visible=true; + } + + property string m_armed_warning: "WARNING ! Analyze channels while armed is not recommended, you'l loose connection for a significant amount of time !" + + property string m_info_string: "Analyze channels for pollution by wifi access points. +NOTE: This only gives a hint at free channels, using a proper channel analyzer (e.g. on the phone) is recommended. +PLEASE DO NOT CHANGE SETTINGS WHILE ANALYZING." + + + cardBody: Item{ + height: 200 + width: 320 + // Dialoque one + Text { + visible: m_index==0 + text: m_armed_warning + width: parent.width + height: parent.height-100 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + // Dialoque two + Text { + visible: m_index==1 + text: m_info_string + width: parent.width + height: parent.height-100 + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + hasFooter: true + cardFooter: Item { + anchors.fill: parent + // Dialoque one + RowLayout{ + anchors.fill: parent + visible: m_index==0 + ButtonRed{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("PROCEED ANYWAY") + onPressed: { + m_index=1 + } + } + ButtonGreen{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 12 + text: qsTr("CANCEL") + onPressed: { + dialoqueAnalyzeChannels.visible=false; + } + } + } + // Dialoque 2 + RowLayout{ + anchors.fill: parent + visible: m_index==1 + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("CANCEL") + onPressed: { + dialoqueAnalyzeChannels.visible=false; + } + } + Button{ + Layout.preferredWidth: 140 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + text: qsTr("INITIATE") + onPressed: { + var result=_wbLinkSettingsHelper.start_analyze_channels() + if(result!==true){ + _qopenhd.show_toast("Busy,please try again later",true); + }else{ + dialoqueAnalyzeChannels.visible=false; + } + } + } + } + } +} diff --git a/qml/ui/configpopup/openhd_settings/FreqComboBoxRow.qml b/qml/ui/configpopup/openhd_settings/FreqComboBoxRow.qml new file mode 100644 index 000000000..ff89f9996 --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/FreqComboBoxRow.qml @@ -0,0 +1,90 @@ +import QtQuick 2.0 + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 +import QtQuick.Controls.Styles 1.4 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Rectangle{ + property int m_background_color_type: 0 + + property string m_main_text: "[] XXXXMhz" + + // 0: this row matches current openhd selected frequency + // 1: otherwise + property int m_selection_tpye: 0 + + property bool m_is_2G: false + property bool m_2G_5G_show: false + property bool m_show_radar: false + property bool m_show_good_channel: false + property string m_pollution_text: "P:XX" + property color m_pollution_color: "green" + + color: "transparent" + anchors.fill: parent + anchors.margins: 6 + // Background + Rectangle{ + anchors.fill: parent + //color: "transparent" + color: m_selection_tpye===0 ? "transparent" : "#c5c3c7" // light gray + //border.color: "red" + //color: "red" + } + // The acual row, contains text and icons + RowLayout{ + //width:parent.width + //height:parent.height + anchors.fill: parent + Text { + Layout.alignment: Qt.AlignLeft + Layout.preferredWidth: 110 + text: m_main_text + color: "black" + font: comboBoxFreq.font + } + Text{ + Layout.alignment: Qt.AlignLeft + text: m_is_2G ? "5.8G" : "2.4G" + //color: value > 3000 ? "green" : "#ff8c00" //"orange" + color: "#706F1D" // dark green + visible: m_2G_5G_show + } + Text { // Radar icon + Layout.alignment: Qt.AlignLeft + text: qsTr("\uf7c0"); + font.family: "Font Awesome 5 Free" + color: "red" + visible: m_show_radar + } + Item{ + Layout.fillWidth: true + // filler + } + Text{ // smiley icon - indicates good channel + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 30 + text: qsTr("\uf585") + visible: m_show_good_channel + font.family: "Font Awesome 5 Free" + color: "green" + } + Text{ + Layout.alignment: Qt.AlignRight + Layout.preferredWidth: 60 + text: m_pollution_text + color: m_pollution_color + font.family: "Font Awesome 5 Free" + } + } +} diff --git a/qml/ui/configpopup/MavlinkAllSettingsPanel.qml b/qml/ui/configpopup/openhd_settings/MavlinkAllSettingsPanel.qml similarity index 87% rename from qml/ui/configpopup/MavlinkAllSettingsPanel.qml rename to qml/ui/configpopup/openhd_settings/MavlinkAllSettingsPanel.qml index 3fbd50913..407a37f38 100644 --- a/qml/ui/configpopup/MavlinkAllSettingsPanel.qml +++ b/qml/ui/configpopup/openhd_settings/MavlinkAllSettingsPanel.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" // Parent panel for OpenHD Mavlink air and ground settings (!!!! NOT QOPENHD SETTINGS !!!) Rectangle { @@ -58,6 +58,7 @@ Rectangle { currentIndex: selectItemInStackLayoutBar.currentIndex MavlinkExtraWBParamPanel{ + //WBFrequencyPanel{ id: xX_WBLinkSettings } // MavlinkSetupPiCameraPanel{ @@ -65,27 +66,27 @@ Rectangle { // } MavlinkParamPanel{ id: x1_AirCameraSettingsPanel - m_name: "Camera1" + m_name: "CAMERA1" m_instanceMavlinkSettingsModel: _airCameraSettingsModel m_instanceCheckIsAvlie: _ohdSystemAir } // exp MavlinkParamPanel{ id: x1_AirCameraSettingsPanel2 - m_name: "Camera2" + m_name: "CAMERA2" m_instanceMavlinkSettingsModel: _airCameraSettingsModel2 m_instanceCheckIsAvlie: _ohdSystemAir } MavlinkParamPanel{ id: x2_AirSettingsPanel - m_name: "Air" - m_instanceMavlinkSettingsModel: _airPiSettingsModel + m_name: "AIR" + m_instanceMavlinkSettingsModel: _ohdSystemAirSettingsModel m_instanceCheckIsAvlie: _ohdSystemAir } MavlinkParamPanel{ id: x3_GroundSettingsPanel - m_name: "Ground" - m_instanceMavlinkSettingsModel: _groundPiSettingsModel + m_name: "GROUND" + m_instanceMavlinkSettingsModel: _ohdSystemGroundSettings m_instanceCheckIsAvlie: _ohdSystemGround } } diff --git a/qml/ui/configpopup/openhd_settings/MavlinkExtraWBParamPanel.qml b/qml/ui/configpopup/openhd_settings/MavlinkExtraWBParamPanel.qml new file mode 100644 index 000000000..3c3ad2b6f --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/MavlinkExtraWBParamPanel.qml @@ -0,0 +1,635 @@ +import QtQuick 2.0 + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 +import QtQuick.Controls.Styles 1.4 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +// This is an extra screen for changing the frequency / channel width - +// They both need to match ! +Rectangle{ + width: parent.width + height: parent.height + + //color: "transparent" + //color: settings.screen_settings_openhd_parameters_transparent ? "transparent" : "white" + //opacity: settings.screen_settings_openhd_parameters_transparent ? 0.2 : 1 + + // https://stackoverflow.com/questions/41991438/how-do-i-find-a-particular-listelement-inside-a-listmodel-in-qml + // For the models above (model with value) try to find the index of the first item where model[i].value===value + function find_index(model,value){ + for(var i = 0; i < model.count; ++i) if (model.get(i).value===value) return i + return -1 + } + // try and update the combobox to the retrieved value(value != index) + function update_combobox(_combobox,_value){ + var _index=find_index(_combobox.model,_value) + if(_index >= 0){ + _combobox.currentIndex=_index; + } + } + + function fc_is_armed(){ + return _fcMavlinkSystem.armed + } + + ListModel{ + id: supported_frequencies_model + ListElement {title: "Unknown"; value:-1; radar: false; recommended: false; pollution: -1} + } + + function show_popup_message(message){ + _messageBoxInstance.set_text_and_show(message) + } + + property bool m_simplify_enable:true + function create_list_model_supported(){ + supported_frequencies_model.clear() + //supported_frequencies_model.append({title: "Unknown", value: -1}) + const supported_frequencies=_wbLinkSettingsHelper.get_supported_frequencies(); + for(var i=0;i=0){ + comboBoxFreq.currentIndex=index; + }else{ + comboBoxFreq.currentIndex=0; + console.log("Seems not to be a valid channel "+_wbLinkSettingsHelper.curr_channel_mhz) + } + } + + // We get notified every time we should re-build the model(s) and their current selection + property int m_ui_rebuild_models : _wbLinkSettingsHelper.ui_rebuild_models + onM_ui_rebuild_modelsChanged: { + console.log(" onM_ui_rebuild_modelsChanged: "+_wbLinkSettingsHelper.ui_rebuild_models); + function_rebuild_ui() + } + + function function_rebuild_ui(){ + console.log("function_rebuild_ui:"+_wbLinkSettingsHelper.ui_rebuild_models); + if(_wbLinkSettingsHelper.ui_rebuild_models<=0)return + create_list_model_supported(); + } + + // + Component.onCompleted: { + + } + // + + // ------------------- PART HELPER FOR CURRENT LOSS / POLLUTION / THROTTLE BEGIN ------------------- + property bool m_is_ground_and_air_alive: _ohdSystemGround.is_alive && _ohdSystemAir.is_alive + property int m_loss_warning_level: { + if(!_ohdSystemAir.is_alive)return 0; // Info not available + var curr_rx_packet_loss_perc=_ohdSystemGround.curr_rx_packet_loss_perc; + if(curr_rx_packet_loss_perc>=8)return 2; + if(curr_rx_packet_loss_perc>=4)return 1; + return 0; + } + + property int m_pollution_warning_level: { + if(!_ohdSystemAir.is_alive)return 0; // Info not available + var wb_link_pollution=_ohdSystemGround.wb_link_pollution; + if(wb_link_pollution>=8)return 2; + if(wb_link_pollution>=3)return 1; + return 0; + } + + property int m_throttle_warning_level: { + if(!_ohdSystemAir.is_alive)return 0; // Info not available + var throttle=_ohdSystemAir.curr_n_rate_adjustments; + if(throttle>=3)return 2; + if(throttle>=1)return 1; + return 0; + } + function warning_level_to_color(level){ + if(level==2)return "red"; + if(level==1)return "green"; + return "black"; + } + + function get_text_current_loss(){ + if(!_ohdSystemGround.is_alive){ + return "No Ground unit" + } + if(!_ohdSystemAir.is_alive){ + return "No air unit"; + } + return "Curr Loss:"+_ohdSystemGround.curr_rx_packet_loss_perc+"%"; + } + + function get_text_current_pollution(){ + if(!m_is_ground_and_air_alive){ + return ""; + } + var wb_link_pollution=_ohdSystemGround.wb_link_pollution; + return "Pollution:"+wb_link_pollution+" %"; + } + function get_text_current_throttle(){ + if(!m_is_ground_and_air_alive){ + return ""; + } + var throttle=_ohdSystemAir.curr_n_rate_adjustments; + if(throttle<=-1)return ""; + if(throttle<=0){ + return " Throttle:None" + } + return " Throttle: -"+throttle; + } + // ------------------- PART HELPER FOR CURRENT LOSS / POLLUTION / THROTTLE END ------------------- + + + function get_combobox_text_color(element_index,curr_index,frequency){ + if(frequency===_wbLinkSettingsHelper.curr_channel_mhz){ + return "green"; + } + if(element_index===curr_index){ + // currently selected in the combobox (but not neccessarily applied in openhd) + return "blue"; + } + return "black"; + } + + function get_color_pollution(pollution){ + if(pollution<=0)return "green"; + if(pollution<=10) return "orange" + return "red"; + } + + function get_text_pollution(pollution){ + if(pollution<=-1)return ""; + if(pollution<=0)return "FREE"; + return "P:"+pollution; + } + function get_text_current_disarmed_armed(pwr_current,pwr_disarmed,pwr_armed){ + return "Curr:"+pwr_current+" Arm:"+pwr_armed+" Disarm:"+pwr_disarmed; + } + + function get_text_tx_power(ground){ + var card= ground ? _wifi_card_gnd0 : _wifi_card_air; + //var card= _wifi_card_gnd0; + var ret = ground ? "TX PWR GND: " : "TX PWR AIR: "; + var card_type=card.card_type; + if(!card.alive){ + ret+="No info"; + return ret; + } + if(card_type==1){ + ret+=get_text_current_disarmed_armed(card.tx_power,card.tx_power_disarmed,card.tx_power_armed); + ret+=" (unitless)" + return ret; + } + ret+=get_text_current_disarmed_armed(card.tx_power,card.tx_power_disarmed,card.tx_power_armed); + ret+=" MW"; + if(card_type<=0){ + ret+=" (maybe,unreliable)"; + } + return ret; + } + + function set_channel_width_async(channel_width_mhz){ + if(!_ohdSystemAir.is_alive){ + _qopenhd.show_toast("Cannot change BW:"+channel_width_mhz+"Mhz, AIR not alive") + return; + } + _wbLinkSettingsHelper.change_param_air_channel_width_async(channel_width_mhz,false); + } + + property string m_text_warning_nosync_frequency: "WARNING: THIS CHANGES YOUR GROUND UNIT FREQUENCY WITHOUT CHANGING YOUR AIR UNIT FREQUENCY ! +Only enable if you want to quickly change your ground unit's frequency to the already set frequency of a running air unit (And know both frequency and channel width on top of your head)"; + + property string m_text_warning_nosync_chanel_width: "WARNING: THIS CHANGES YOUR GROUND UNIT CHANNEL WIDTH WITHOUT CHANGING YOUR AIR UNIT CHANNEL WIDTH ! +Only enable if you want to quickly change your ground unit's channel width to the already set channel width of a running air unit (And know both frequency and channel width on top of your head)" + + + /*property string more_info_text: "After flashing,openhd uses the same default frequency, and your air and ground unit automatically connect."+ + "If you change the frequency / channel width here, both air and ground unit are set to the new frequency."+ +"If you changed the frequency of your air unit and are using a different Ground unit, use the FIND AIR UNIT feature (channel scan) to switch to the same frequency your air unit is running on."*/ + property string more_info_text: "Here you can easily change the openhd link frequency/bandwidth of you air and ground unit."+ +"After flashing, both air and ground unit start on the same frequency and automatically connect."+ +"If you changed the frequency of your air unit and are using a different Ground unit, use the FIND AIR UNIT feature (channel scan) to switch to the same frequency your air unit is running on." + + property string find_air_unit_text:"Scan all channels for a running Air unit. Might take up to 30seconds to complete (openhd supports a ton of channels, and we need to listen on each of them for a short timespan)" + + property string analyze_channels_text: "Listen for other WIFi packets packets on each frequency for a short amount of time - a lot of foreign packets hint at a polluted channel" + + property string m_info_text_change_frequency: "Frequency in Mhz and channel number. (DFS-RADAR) - also used by commercial plane(s) weather radar (not recommended unless you have ADSB). "+ +" ! OPENHD DOESN'T HAVE ANY RESTRICTIONS ! - It is your responsibility to use channels (frequencies) allowed in your country." + + property string m_info_text_change_channel_width: "A channel width of 40Mhz (40Mhz bandwitdh) gives almost double the bandwidth (2x video bitrate/image quality), but uses 2x 20Mhz channels and therefore the likeliness of "+ +"interference from other stations sending on either of those channels is increased. It also slightly decreases sensitivity. In general, we recommend 40Mhz unless you are flying ultra long range "+ +"or in a highly polluted (urban) environment." + + property string m_info_text_change_tx_power: "Change GND / AIR TX power. Higher Air TX power results in more range on the downlink (video,telemetry). +Higher GND TX power results in more range on the uplink (mavlink up). You can set different tx power for armed / disarmed state (requres FC), +but it is not possible to change the TX power during flight (due to the risk of misconfiguration / power outage)."+ + " ! OPENHD DOESN'T HAVE ANY RESTRICTIONS ON TX POWER - It is your responsibility to use a tx power allowed in your country. !" + + property string m_warning_text_no_gnd_unit: "GROUND not alive, settings uavailable. Please check status view." + property string m_warning_text_no_air_unit: "Make sure your air unit hardware is functioning properly. If you freshly flashed your air and ground unit, they use the same frequency +and automatically connect. Otherwise, use the 'FIND AIR UNIT' feature to scan all channels for your air unit." + + property string m_warning_info_text_polluted_channel: "OpenHD is broadcast and works best on a channel free of noise and interference. Sometimes you cannot find such a channel +(e.g. in urban environments), in which case you might want to use the city preset from WBLink widget. To find a free channel, use a frequency analyzer on your phone, +the analyze channels feature or experience - [169] 5845Mhz is a good bet in Europe, since it only allows 25mW." + + // Changes either the frequency or channel width + // This one need to be synced, so we have ( a bit complicated, but quite natural for the user) dialoque for the cases where we need to handle errors / show a warning + function change_frequency_sync_otherwise_handle_error(frequency_mhz,ignore_armed_state){ + console.log("change_frequency_sync_otherwise_handle_error: "+"FREQ:"+frequency_mhz+"Mhz"); + // Ground needs to be alive and well + if(!_ohdSystemGround.is_alive){ + _messageBoxInstance.set_text_and_show("Ground unit not alive",5); + return; + } + // Air needs to be alive and well - otherwise we show the "do you want to change gnd only" dialoque + if(!_ohdSystemAir.is_alive){ + var error_message_not_alive="AIR Unit not alive -" + dialoqueFreqChangeGndOnly.initialize_and_show_frequency(frequency_mhz,error_message_not_alive); + return; + } + // FC needs to be disarmed - otherwise show warning + const fc_currently_armed = (_fcMavlinkSystem.is_alive && _fcMavlinkSystem.armed)// || true; + if(fc_currently_armed && ignore_armed_state===false){ + dialoqueFreqChangeArmed.initialize_and_show_frequency(frequency_mhz) + return; + } + var result= _wbLinkSettingsHelper.change_param_air_and_ground_frequency(frequency_mhz); + if(result==0){ + var message="Succesfully set air and ground to FREQUENCY: "+frequency_mhz+"Mhz"; + _messageBoxInstance.set_text_and_show(message,5); + return; + }else if(result==-1){ + // Air unit rejected + _messageBoxInstance.set_text_and_show("Air unit does not support this value",5); + return; + }else if(result==-2){ + // Couldn't reach air unit + var error_message_not_reachable="Couldn't reach air unit -" + dialoqueFreqChangeGndOnly.initialize_and_show_frequency(frequency_mhz,error_message_not_reachable); + return; + } + // Really really bad + _messageBoxInstance.set_text_and_show("Something went wrong - please use 'FIND AIR UNIT' to fix"); + } + + function get_text_current_frequency(){ + if(!_ohdSystemGround.is_alive)return "N/A"; + return _wbLinkSettingsHelper.curr_channel_mhz+"@"+_wbLinkSettingsHelper.curr_channel_width_mhz+"Mhz"; + } + + + ScrollView { + id:mavlinkExtraWBParamPanel + width: parent.width + height: parent.height + contentHeight: mainItem.height + clip: true + //ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.interactive: true + + Item { + id: mainItem + width: parent.width + height: rowHeight*7 + + DIaloqueStartChannelScan{ + id: dialoqueStartChannelScan + } + DialoqueFreqChangeGndOnly{ + id: dialoqueFreqChangeGndOnly + } + DialoqueFreqChangeArmed{ + id: dialoqueFreqChangeArmed + } + DialoqueStartAnalyzeChannels{ + id: dialoqueAnalyzeChannels + } + + DialoqueChangeTxPower{ + id: txPowerDialoque + } + + Column { + id:wbParamColumn + spacing: 0 + anchors.left: parent.left + anchors.right: parent.right + + Rectangle { + width: parent.width + height: rowHeight + color: "#00000000" + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + Button{ + text: "MORE INFO" + Material.background:Material.LightBlue + onClicked: { + _messageBoxInstance.set_text_and_show(more_info_text) + } + } + Text{ + text: get_text_current_loss() + color: warning_level_to_color(m_loss_warning_level) + } + Text{ + text: get_text_current_pollution() + color: warning_level_to_color(m_pollution_warning_level) + } + Text{ + text: get_text_current_throttle() + color: warning_level_to_color(m_throttle_warning_level) + } + ButtonIconWarning{ + visible: !_ohdSystemGround.is_alive + onClicked: { + _messageBoxInstance.set_text_and_show(m_warning_text_no_gnd_unit) + } + } + ButtonIconWarning{ + visible: _ohdSystemGround.is_alive && !_ohdSystemAir.is_alive + onClicked: { + _messageBoxInstance.set_text_and_show(m_warning_text_no_air_unit) + } + } + ButtonIconWarning{ + visible: m_loss_warning_level>0 || m_pollution_warning_level>0 || m_throttle_warning_level>0; + onClicked: { + _messageBoxInstance.set_text_and_show(m_warning_info_text_polluted_channel) + } + } + } + } + + + // Changing the wifi frequency, r.n only 5G + Rectangle { + width: parent.width + height: rowHeight + //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + color: "#8cbfd7f3" + + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show(m_info_text_change_frequency) + } + } + ComboBox { + id: comboBoxFreq + model: supported_frequencies_model + textRole: "title" + implicitWidth: elementComboBoxWidth + currentIndex: 0 + // Customization + // https://stackoverflow.com/questions/31411844/how-to-limit-the-size-of-drop-down-of-a-combobox-in-qml + /*style: ComboBoxStyle { + id: comboBoxStyle + + // drop-down customization here + property Component __dropDownStyle: MenuStyle { + __maxPopupHeight: Math.max(55, //min value to keep it to a functional size even if it would not look nice + Math.min(400, + //limit the max size so the menu is inside the application bounds + comboBoxStyle.control.Window.height + - mapFromItem(comboBoxStyle.control, 0,0).y + - comboBoxStyle.control.height)) + __menuItemType: "comboboxitem" //not 100% sure if this is needed + } //Component __dropDownStyle: MenuStyle + } //style: ComboBoxStyle */ + delegate: ItemDelegate { + width: comboBoxFreq.width + contentItem: FreqComboBoxRow{ + m_main_text: title + m_selection_tpye: (value===_wbLinkSettingsHelper.curr_channel_mhz) ? 1 : 0 + m_is_2G: value > 3000 + m_2G_5G_show: value > 100 + m_show_radar: radar + m_show_good_channel: recommended + m_pollution_text: get_text_pollution(pollution) + m_pollution_color: get_color_pollution(pollution) + } + highlighted: comboBoxFreq.highlightedIndex === index + } + } + Button{ + text: "APPLY FREQ" + id: buttonSwitchFreq + //enabled: false + onClicked: { + var selectedValue=supported_frequencies_model.get(comboBoxFreq.currentIndex).value + if(selectedValue<=100){ + _messageBoxInstance.set_text_and_show("Please select a valid frequency",5); + return; + } + change_frequency_sync_otherwise_handle_error(selectedValue,-1,false); + } + //Material.background: fc_is_armed() ? Material.Red : Material.Normal; + enabled: _wbLinkSettingsHelper.ui_rebuild_models>=0 && _ohdSystemGround.is_alive; + } + Switch{ + text: "Simplify" + checked: true + onCheckedChanged: { + if(m_simplify_enable!=checked){ + m_simplify_enable=checked; + function_rebuild_ui(); + } + } + } + } + } + Rectangle { + width: parent.width + height: rowHeight + //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + color: "#8cbfd7f3" + + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show(m_info_text_change_channel_width) + } + } + /*FreqComboBoxRow{ + m_main_text: _fcM + }*/ + Text{ + text: "Curr:"+get_text_current_frequency(); + } + Button{ + text: "20Mhz" + onClicked: { + set_channel_width_async(20); + } + highlighted: _wbLinkSettingsHelper.curr_channel_width_mhz==20 + enabled: _wbLinkSettingsHelper.ui_rebuild_models>0 && _ohdSystemAir.is_alive + } + Button{ + text: "40Mhz" + onClicked: { + set_channel_width_async(40); + } + highlighted: _wbLinkSettingsHelper.curr_channel_width_mhz==40 + enabled: _wbLinkSettingsHelper.ui_rebuild_models>0 && _ohdSystemAir.is_alive + } + } + } + Rectangle { + width: parent.width + height: rowHeight + //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + color: "#00000000" + + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + Layout.preferredWidth: 200 + height: parent.height + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show(m_info_text_change_tx_power) + } + } + Button{ + text: "AIR TX PWR" + enabled: _ohdSystemAir.is_alive + onClicked: { + txPowerDialoque.open_tx_power_dialoque(false) + } + } + Button{ + text: "GND TX PWR" + enabled: _ohdSystemGround.is_alive + onClicked: { + txPowerDialoque.open_tx_power_dialoque(true) + } + } + ColumnLayout{ + Layout.fillWidth: true + Text{ + text: get_text_tx_power(true) + } + Text{ + text: get_text_tx_power(false) + } + } + } + } + + Rectangle { + width: parent.width + height: rowHeight + //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + color: "#00000000" + + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + height: parent.height + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show(find_air_unit_text) + } + } + Button{ + id: buttonFindAirUnitId + text: "FIND AIR UNIT" + enabled: _ohdSystemGround.is_alive + onClicked: { + dialoqueStartChannelScan.open_channel_scan_dialoque() + } + } + ProgressBar{ + Layout.fillWidth: true + Layout.rightMargin: 15 + Layout.leftMargin: 15 + height: parent.height + //indeterminate: true + from: 0 + to: 100 + value: _wbLinkSettingsHelper.progress_scan_channels_perc + } + } + // Highlight the button for the user + /*Rectangle{ + width: 100 + height: buttonFindAirUnitId.height + color: "red" + anchors.left: buttonFindAirUnitId.right + anchors.top: buttonFindAirUnitId.top + }*/ + } + + Rectangle { + width: parent.width + height: rowHeight + //color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + color: "#00000000" + RowLayout{ + anchors.verticalCenter: parent.verticalCenter + width: parent.width + height: parent.height + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show(analyze_channels_text) + } + } + Button{ + text: "ANALYZE" + enabled: _ohdSystemGround.is_alive + onClicked: { + dialoqueAnalyzeChannels.setup_and_show(); + } + } + ProgressBar{ + Layout.fillWidth: true + Layout.rightMargin: 15 + Layout.leftMargin: 15 + height: parent.height + //indeterminate: true + from: 0 + to: 100 + value: _wbLinkSettingsHelper.progress_analyze_channels_perc + } + } + } + Rectangle { + width: parent.width + height: rowHeight + Text{ + text: _wbLinkSettingsHelper.text_for_qml + } + } + } + } + } +} diff --git a/qml/ui/configpopup/MavlinkParamEditor.qml b/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml similarity index 96% rename from qml/ui/configpopup/MavlinkParamEditor.qml rename to qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml index 29438c91a..d1a0ce459 100644 --- a/qml/ui/configpopup/MavlinkParamEditor.qml +++ b/qml/ui/configpopup/openhd_settings/MavlinkParamEditor.qml @@ -9,8 +9,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" // Dirty. This is opened up every time the user wants to change a parameter. // R.n it supports int and string parameters. @@ -34,7 +34,7 @@ Rectangle{ visible: false // We set a the ground pi instance as default (such that the qt editor code completion helps us a bit), // but this can be replaced by the proper instance for air or camera - property var instanceMavlinkSettingsModel: _groundPiSettingsModel + property var instanceMavlinkSettingsModel: _ohdSystemGroundSettings property int customHeight: 50 @@ -200,6 +200,9 @@ Rectangle{ if(instanceMavlinkSettingsModel.string_param_has_enum(parameterId)){ console.log(parameterId+" has string enum mapping") // Populate the model with the key,value pairs for this enum + //const enum_mapping_string=instanceMavlinkSettingsModel.string_param_get_enum(parameterId); + //const keys=enum_mapping_string.keys; + //const values=enum_mapping_string.values; const keys=instanceMavlinkSettingsModel.string_param_get_enum_keys(parameterId) const values=instanceMavlinkSettingsModel.string_param_get_enum_values(parameterId); stringEnumDynamicListModel.clear(); @@ -278,7 +281,7 @@ Rectangle{ color: "white" font.bold: true font.pixelSize: 18 - horizontalAlignment: Qt.AlignCenter + horizontalAlignment: Qt.AlignHCenter Layout.alignment: Qt.AlignCenter } @@ -288,8 +291,8 @@ Rectangle{ font.pixelSize: 12 text: qsTr(parameterId+" "+get_param_type_readable()) color: "white" - horizontalAlignment: Qt.AlignCenter // dafuq https://stackoverflow.com/questions/35799944/text-type-alignment + horizontalAlignment: Qt.AlignHCenter Layout.alignment: Qt.AlignCenter } Button { @@ -449,10 +452,12 @@ Rectangle{ if(instanceMavlinkSettingsModel.get_param_requires_manual_reboot(parameterId)){ _messageBoxInstance.set_text_and_show("Please reboot to apply") } + var argh=paramValueType==0 ? (""+value_int) : (""+value_string); parameterEditor.visible=false + _qopenhd.show_toast("Set "+parameterId+" to ["+argh+"]"); }else{ console.log("Update failed") - _messageBoxInstance.set_text_and_show(res); + _qopenhd.show_toast(res,true); } set_description_enabled(false) } diff --git a/qml/ui/configpopup/MavlinkParamPanel.qml b/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml similarity index 68% rename from qml/ui/configpopup/MavlinkParamPanel.qml rename to qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml index 7d4e7fee5..d394b7bd2 100644 --- a/qml/ui/configpopup/MavlinkParamPanel.qml +++ b/qml/ui/configpopup/openhd_settings/MavlinkParamPanel.qml @@ -1,7 +1,6 @@ -import QtQuick 2.0 - import QtQuick 2.12 import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 import QtQuick.Dialogs 1.0 import QtQuick.Controls.Material 2.12 @@ -9,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" // Contains a list of all the settings on the left, and opens up a parameter editor instance on // the right if the user wants to edit any mavlink settings @@ -21,7 +20,7 @@ Rectangle { // We set a the ground pi instance as default (such that the qt editor code completion helps us a bit), // but this should be replaced by the proper instance for air or camera - property var m_instanceMavlinkSettingsModel: _groundPiSettingsModel + property var m_instanceMavlinkSettingsModel: _ohdSystemGroundSettings // figure out if the system is alive property var m_instanceCheckIsAvlie: _ohdSystemGround @@ -31,66 +30,79 @@ Rectangle { //color: "transparent" color: settings.screen_settings_openhd_parameters_transparent ? "transparent" : "white" - // Refetch all button - Button { - height: 48 + property int m_progress_perc : m_instanceMavlinkSettingsModel.curr_get_all_progress_perc; + + SimpleProgressBar{ + id: fetch_all_progress + width: parent.width + height: 15 anchors.top: parent.top - id: fetchAllButtonId - text:"ReFetch All "+m_name - enabled: m_instanceCheckIsAvlie.is_alive - onClicked: { - parameterEditor.visible=false - //var result=m_instanceMavlinkSettingsModel.try_fetch_all_parameters() - var result=m_instanceMavlinkSettingsModel.try_fetch_all_parameters_long_running() - if(!result){ - _messageBoxInstance.set_text_and_show("Fetch all failed, please try again",5) - }else{ - _messageBoxInstance.set_text_and_show("SUCCESS",1) - } - } + visible: m_progress_perc>=0 && m_progress_perc<=100 + impl_curr_progress_perc: m_progress_perc + //impl_curr_color: m_progress_perc>=100 ? "green" : "blue" } - Button { + + RowLayout{ + id: upper_action_row + width: parent.width height: 48 - anchors.top: parent.top - anchors.left: fetchAllButtonId.right - anchors.leftMargin: 20 - id: fetchAllButtonInfoId - text:"INFO" - Material.background:Material.LightBlue - onClicked: { - var text="Refresh your complete parameter set by fetching it from the openhd air/ground unit. Might need a couple of tries" - _messageBoxInstance.set_text_and_show(text) + anchors.top: fetch_all_progress.bottom + anchors.topMargin: 1 + ButtonIconWarning{ + onClicked: { + _messageBoxInstance.set_text_and_show(""+m_name+ " not alive, parameters unavailable. Please check status view."); + } + visible: !m_instanceCheckIsAvlie.is_alive } - } - Switch{ - id: screen_settings_openhd_parameters_transparentSwitch - anchors.top: parent.top - anchors.left: fetchAllButtonInfoId.right - anchors.leftMargin: 20 - checked: settings.screen_settings_openhd_parameters_transparent - onCheckedChanged: settings.screen_settings_openhd_parameters_transparent = checked - } - Button{ - id: bUp - anchors.top: parent.top - anchors.left: screen_settings_openhd_parameters_transparentSwitch.right - anchors.leftMargin: 20 - text:"DOWN" - onClicked: { - paramListScrollView.ScrollBar.vertical.position += 0.1 + Button { + text:"REFETCH "+m_name + visible: m_instanceCheckIsAvlie.is_alive + onClicked: { + parameterEditor.visible=false + m_instanceMavlinkSettingsModel.try_refetch_all_parameters_async() + } } - } - Button{ - id: bDown - anchors.top: parent.top - anchors.left: bUp.right - anchors.leftMargin: 20 - text:"UP" - onClicked: { - paramListScrollView.ScrollBar.vertical.position -= 0.1 + Button { + text:"INFO" + Material.background:Material.LightBlue + visible: m_instanceCheckIsAvlie.is_alive + onClicked: { + var text="Refresh your complete parameter set by fetching it from the openhd air/ground unit." + _messageBoxInstance.set_text_and_show(text) + } + } + Switch{ + checked: settings.screen_settings_openhd_parameters_transparent + onCheckedChanged: settings.screen_settings_openhd_parameters_transparent = checked + } + Button{ + text:"DOWN" + onClicked: { + paramListScrollView.ScrollBar.vertical.position += 0.1 + } + } + Button{ + text:"UP" + onClicked: { + paramListScrollView.ScrollBar.vertical.position -= 0.1 + } + } + Item{ // Filler + Layout.fillWidth: true + Layout.fillHeight: true } } + /*ProgressBar{ + width: parent.width + height: 50 + from: 0 + to: 100 + value: m_progress_perc + //visible: m_progress_perc>=0 + anchors.top: fetchAllButtonId.top + }*/ + Component { id: delegateMavlinkSettingsValue @@ -114,7 +126,7 @@ Rectangle { Text { anchors.verticalCenter: parent.verticalCenter //font.pixelSize: 20 - width:150 + width:160 text: model.unique_id font.bold: true //color: settings.screen_settings_openhd_parameters_transparent ? "green" : "black" @@ -123,7 +135,7 @@ Rectangle { styleColor: settings.color_glow } Text { - width:150 + width:160 //font.pixelSize: 20 text: model.extraValue font.bold: true @@ -174,8 +186,8 @@ Rectangle { Rectangle{ id: scrollViewRectangle width: parent.width - height: parent.height - fetchAllButtonId.height - anchors.top: fetchAllButtonId.bottom + height: parent.height - upper_action_row.height + anchors.top: upper_action_row.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right @@ -209,4 +221,12 @@ Rectangle { instanceMavlinkSettingsModel: m_instanceMavlinkSettingsModel } + BusyIndicator{ + width: 96 + height: 96 + anchors.centerIn: parent + running: m_instanceMavlinkSettingsModel.ui_is_busy + //visible: _xParamUI.is_busy + } + } diff --git a/qml/ui/configpopup/openhd_settings/README.md b/qml/ui/configpopup/openhd_settings/README.md new file mode 100644 index 000000000..250d7632f --- /dev/null +++ b/qml/ui/configpopup/openhd_settings/README.md @@ -0,0 +1,2 @@ +OpenHD settings (using mavlink) +complicated diff --git a/qml/ui/configpopup/AppDevSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppDevSettingsView.qml similarity index 99% rename from qml/ui/configpopup/AppDevSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppDevSettingsView.qml index f009794d9..535f60caa 100755 --- a/qml/ui/configpopup/AppDevSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppDevSettingsView.qml @@ -9,8 +9,8 @@ import Qt.labs.platform 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { diff --git a/qml/ui/configpopup/AppGeneralSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppGeneralSettingsView.qml similarity index 99% rename from qml/ui/configpopup/AppGeneralSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppGeneralSettingsView.qml index bc2c2f411..fd157118d 100755 --- a/qml/ui/configpopup/AppGeneralSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppGeneralSettingsView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { id: appGeneralSettingsView diff --git a/qml/ui/configpopup/AppScreenSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppScreenSettingsView.qml similarity index 99% rename from qml/ui/configpopup/AppScreenSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppScreenSettingsView.qml index 48f559f1f..a100bd914 100755 --- a/qml/ui/configpopup/AppScreenSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppScreenSettingsView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { id: appScreenSettingsView @@ -51,7 +51,7 @@ ScrollView { font.pixelSize: 14 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - from : 0.5 + from : 0.3 to : 2.0 // Important: Looks as without .1 steps, we can get rendering artfacts stepSize: .1 diff --git a/qml/ui/configpopup/AppSettingsPanel.qml b/qml/ui/configpopup/qopenhd_settings/AppSettingsPanel.qml similarity index 96% rename from qml/ui/configpopup/AppSettingsPanel.qml rename to qml/ui/configpopup/qopenhd_settings/AppSettingsPanel.qml index 8c05b4d79..69c827dcc 100755 --- a/qml/ui/configpopup/AppSettingsPanel.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppSettingsPanel.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" /* @@ -28,12 +28,12 @@ Rectangle { property int rowHeight: 64 property int elementHeight: 48 - Keys.onPressed: (event)=> { + /*Keys.onPressed: (event)=> { if (event.key == Qt.Key_Return) { console.log("enter was pressed from menu"); event.accepted = true; } - } + }*/ TabBar { id: appSettingsBar diff --git a/qml/ui/configpopup/AppVehicleSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppVehicleSettingsView.qml similarity index 93% rename from qml/ui/configpopup/AppVehicleSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppVehicleSettingsView.qml index 7dfec4bf6..a5c37e09f 100755 --- a/qml/ui/configpopup/AppVehicleSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppVehicleSettingsView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { id: appVehicleSettingsView @@ -74,7 +74,9 @@ ScrollView { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter checked: settings.set_mavlink_message_rates - onCheckedChanged: settings.set_mavlink_message_rates = checked + onCheckedChanged: { + settings.set_mavlink_message_rates = checked + } } } SettingBaseElement{ @@ -89,9 +91,9 @@ ScrollView { checked: settings.mavlink_message_rates_high_speed onCheckedChanged: { if(settings.mavlink_message_rates_high_speed != checked){ + settings.mavlink_message_rates_high_speed = checked _mavlinkTelemetry.re_apply_rates() } - settings.mavlink_message_rates_high_speed = checked } } } @@ -104,12 +106,12 @@ ScrollView { anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - checked: settings.mavlink_message_rates_high_speed + checked: settings.mavlink_message_rates_high_speed_rc_channels onCheckedChanged: { if(settings.mavlink_message_rates_high_speed_rc_channels != checked){ + settings.mavlink_message_rates_high_speed_rc_channels = checked _mavlinkTelemetry.re_apply_rates() } - settings.mavlink_message_rates_high_speed_rc_channels = checked } } } diff --git a/qml/ui/configpopup/AppVideoSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppVideoSettingsView.qml similarity index 99% rename from qml/ui/configpopup/AppVideoSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppVideoSettingsView.qml index ba1f5ee7a..a38441b32 100755 --- a/qml/ui/configpopup/AppVideoSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppVideoSettingsView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { id: appVideoSettingsView diff --git a/qml/ui/configpopup/AppWidgetSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml similarity index 97% rename from qml/ui/configpopup/AppWidgetSettingsView.qml rename to qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml index 6fd7d2963..56c0bdc04 100755 --- a/qml/ui/configpopup/AppWidgetSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { id: appWidgetSettingsView @@ -1407,38 +1407,6 @@ ScrollView { } } - Rectangle { - width: parent.width - height: rowHeight - color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" - //visible: EnableADSB - - Text { - text: qsTr("Show ADS-B Traffic") - font.weight: Font.Bold - font.pixelSize: 13 - anchors.leftMargin: 8 - verticalAlignment: Text.AlignVCenter - anchors.verticalCenter: parent.verticalCenter - width: 224 - height: elementHeight - anchors.left: parent.left - } - - Switch { - width: 32 - height: elementHeight - anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36 - - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - checked: settings.show_adsb - onCheckedChanged: { - settings.show_adsb = checked; - } - } - } - Rectangle { width: parent.width height: rowHeight diff --git a/qml/ui/configpopup/qopenhd_settings/README.md b/qml/ui/configpopup/qopenhd_settings/README.md new file mode 100644 index 000000000..5dc0ddcfe --- /dev/null +++ b/qml/ui/configpopup/qopenhd_settings/README.md @@ -0,0 +1 @@ +QOpenHD settings diff --git a/qml/ui/configpopup/RcDebugScreenFC.qml b/qml/ui/configpopup/rc/RcDebugScreenFC.qml similarity index 98% rename from qml/ui/configpopup/RcDebugScreenFC.qml rename to qml/ui/configpopup/rc/RcDebugScreenFC.qml index 52be649a0..aa4630511 100644 --- a/qml/ui/configpopup/RcDebugScreenFC.qml +++ b/qml/ui/configpopup/rc/RcDebugScreenFC.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" ScrollView { clip:true diff --git a/qml/ui/configpopup/RcDebugScreenOpenHD.qml b/qml/ui/configpopup/rc/RcDebugScreenOpenHD.qml similarity index 98% rename from qml/ui/configpopup/RcDebugScreenOpenHD.qml rename to qml/ui/configpopup/rc/RcDebugScreenOpenHD.qml index 00f0edab1..77f9fd2e1 100644 --- a/qml/ui/configpopup/RcDebugScreenOpenHD.qml +++ b/qml/ui/configpopup/rc/RcDebugScreenOpenHD.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" // For RC / joystick debugging. For ease of use, we have a simple model in c++ for it // diff --git a/qml/ui/configpopup/RcInfoPanel.qml b/qml/ui/configpopup/rc/RcInfoPanel.qml similarity index 96% rename from qml/ui/configpopup/RcInfoPanel.qml rename to qml/ui/configpopup/rc/RcInfoPanel.qml index 7df0dd2da..8c4a759ea 100644 --- a/qml/ui/configpopup/RcInfoPanel.qml +++ b/qml/ui/configpopup/rc/RcInfoPanel.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" Item { width: applicationWindow.width -165 diff --git a/qml/ui/configpopup/RcInfoScreen.qml b/qml/ui/configpopup/rc/RcInfoScreen.qml similarity index 57% rename from qml/ui/configpopup/RcInfoScreen.qml rename to qml/ui/configpopup/rc/RcInfoScreen.qml index c07e50914..3e23297cc 100644 --- a/qml/ui/configpopup/RcInfoScreen.qml +++ b/qml/ui/configpopup/rc/RcInfoScreen.qml @@ -8,8 +8,8 @@ import Qt.labs.settings 1.0 import OpenHD 1.0 -import "../../ui" as Ui -import "../elements" +import "../../../ui" as Ui +import "../../elements" Item { width: parent.width @@ -29,7 +29,7 @@ Item { Card { id: infoBox - height: 300 + height: 200 Layout.topMargin: 15 Layout.leftMargin: 15 Layout.rightMargin: 15 @@ -37,15 +37,16 @@ Item { cardName: qsTr("About") cardBody: Text { - text: qsTr("To enable RC over wifibroadcast go to OpenHD / Ground(TMP) and set ENABLE_JOY_RC=ENABLED, -connect a joystick (or a RC in joystick mode) and (optionally) reboot.\n -You can use the other screens to validate/debug your setup.\n -INFO: Channel mapping is not intuitive, but it works when done correctly. -If you cannot make it work, any proper RC controller (e.g. running EdgeTX / OpenTX) -supports more advanced channel mapping and works via USB !") - height: 50 + width: parent.width + height: parent.height + text: qsTr("To enable RC over wifibroadcast go to OpenHD / Ground (Ground parameters set) and set 'ENABLE_JOY_RC' to 'ENABLED',"+ +"connect a joystick (or a RC in joystick mode) and (optionally) reboot.\n"+ +"You can use the other screens to validate/debug your setup.\n"+ +"INFO: Channel mapping is not intuitive, but it works when done correctly.\n"+ +"If you cannot make it work, any proper RC controller (e.g. running EdgeTX / OpenTX)\n"+ +"supports more advanced channel mapping and works via USB !") font.pixelSize: 14 - leftPadding: 12 + wrapMode: Text.WordWrap } } } diff --git a/qml/ui/configpopup/status/ActionsColumn.qml b/qml/ui/configpopup/status/ActionsColumn.qml new file mode 100644 index 000000000..32d7bbb4f --- /dev/null +++ b/qml/ui/configpopup/status/ActionsColumn.qml @@ -0,0 +1,83 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +// Holds the ping, fetch, ... buttons for this panel +// NOTE : Not the power action buttons +Item{ + + Timer { + id: autopingTimer + running: false + interval: 1000 + repeat: true + onTriggered: { + _mavlinkTelemetry.ping_all_systems() + } + } + + ColumnLayout{ + // 2 Rows + RowLayout{ + id: actions_1 + width: parent.width + ButtonIconInfo{ + onClicked: { + _messageBoxInstance.set_text_and_show("Ping all systems, aka check if they respond to the mavlink ping command. Both OpenHD air and ground support + this command, FC only ardupilot / px4 support this command. The command is lossy, aka you might need to use it more than once to get a response from all systems. + No response after >10 tries is a hint that one of your systems is not functioning properly.") + } + } + Button{ + text: "Ping all systems" + onClicked: _mavlinkTelemetry.ping_all_systems() + } + Switch{ + text: "Auto-ping" + onCheckedChanged: { + if(checked){ + autopingTimer.start() + }else{ + autopingTimer.stop() + } + } + } + // Padding + Item{ + + } + } + + RowLayout { + width: parent.width + + Button { + //font.pixelSize: 14 + font.capitalization: Font.MixedCase + text: qsTr("DEV-Restart QOpenHD") + onPressed: { + qopenhdservicedialoque.open_dialoque(0) + } + } + Button{ + //font.pixelSize: 14 + font.capitalization: Font.MixedCase + text: qsTr("DEV-Cancel QOpenHD") + onPressed: { + qopenhdservicedialoque.open_dialoque(1) + } + } + Item{ + // padding + } + } + } +} diff --git a/qml/ui/configpopup/status/PanelStatus.qml b/qml/ui/configpopup/status/PanelStatus.qml new file mode 100644 index 000000000..861395411 --- /dev/null +++ b/qml/ui/configpopup/status/PanelStatus.qml @@ -0,0 +1,85 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Rectangle { + width: parent.width + height: parent.height + + color: "#eaeaea" + + function voltage_as_string(voltage_mv){ + if(voltage_mv===0)return "N/A"; + return voltage_mv+" mV" + } + function current_as_string(current_ma){ + if(current_ma===0) return "N/A" + return current_ma+" mA" + } + + function open_power_action_dialoque(system,reboot){ + powerDialog.open_dialoque(system,reboot) + } + + ScrollView { + id:mavlinkExtraWBParamPanel + width: parent.width + height: parent.height + contentHeight: mainItem.height + //contentWidth: mainItem.width + clip: true + //ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.interactive: true + + Item { + id: mainItem + //width: 1024 + width: parent.width + height: 500 + + QOpenHDVersionCard{ + id: qopenhdversioncard + height: 80 + //anchors.top: airBox.bottom + } + /*Item{ + id: qopenhdversioncard + width: parent.width + height: 80 + }*/ + + StatusCardsColumn{ + width: parent.width + height: 300 + + anchors.leftMargin: 12 + anchors.rightMargin: 12 + anchors.topMargin: 12 + + id: status_openhd_fc + anchors.top: qopenhdversioncard.bottom + } + + ActionsColumn{ + width:parent.width + height: 80 + anchors.top: status_openhd_fc.bottom + anchors.topMargin: 2 + } + } + } + PowerActionDialoque{ + id: powerDialog + } + QOpenHDServiceDialoque{ + id: qopenhdservicedialoque + } +} diff --git a/qml/ui/configpopup/status/PowerActionDialoque.qml b/qml/ui/configpopup/status/PowerActionDialoque.qml new file mode 100644 index 000000000..74dee2d15 --- /dev/null +++ b/qml/ui/configpopup/status/PowerActionDialoque.qml @@ -0,0 +1,182 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + + +// Dialoque that does all the power commands (reboot / shutdown) +Card { + id: powerDialog + height: 240 + width: 400 + z: 5.0 + anchors.centerIn: parent + cardNameColor: "black" + hasHeaderImage: true + + Component.onCompleted: visible = false + + property bool stateVisible: visible + + function open_dialoque(system,reboot){ + var action=PowerActionDialoque.PowerAction.RebootGround + if(system==0){ + // OHD GND + action = reboot ? PowerActionDialoque.PowerAction.RebootGround : PowerActionDialoque.PowerAction.ShutdownGround + }else if(system==1){ + action = reboot ? PowerActionDialoque.PowerAction.RebootAir : PowerActionDialoque.PowerAction.ShutdownAir + }else{ + action = reboot ? PowerActionDialoque.PowerAction.RebootFC : PowerActionDialoque.PowerAction.ShutdownFC + } + powerDialog.powerAction=action + powerDialog.visible=true + } + + enum PowerAction { + RebootAir, + ShutdownAir, + RebootGround, + ShutdownGround, + RebootFC, + ShutdownFC + } + property int powerAction: PowerActionDialoque.PowerAction.RebootGround + + + states: [ + State { + when: powerDialog.stateVisible; + PropertyChanges { + target: powerDialog + opacity: 1.0 + } + }, + State { + when: !powerDialog.stateVisible; + PropertyChanges { + target: powerDialog + opacity: 0.0 + } + } + ] + transitions: [ Transition { NumberAnimation { property: "opacity"; duration: 250}} ] + + cardName: qsTr("Confirm Power Change") + cardBody: Column { + height: powerDialog.height + width: powerDialog.width + + Text { + text: qsTr("If your drone is in the air, rebooting or shutting down may cause a crash or make it enter failsafe mode!") + width: parent.width + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + + hasFooter: true + cardFooter: Item { + anchors.fill: parent + Button { + id: groundReboot + width: 96 + height: 48 + text: qsTr("Cancel") + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + font.pixelSize: 14 + font.capitalization: Font.MixedCase + + onPressed: { + powerDialog.visible = false + } + } + + Button { + id: groundShutdown + width: 140 + height: 48 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + font.pixelSize: 14 + font.capitalization: Font.MixedCase + Material.accent: Material.Red + highlighted: true + + text: { + if (powerAction === PowerActionDialoque.PowerAction.RebootGround) { + return qsTr("Reboot Ground") + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownGround) { + return qsTr("Shutdown Ground") + } + if (powerAction === PowerActionDialoque.PowerAction.RebootAir) { + return qsTr("Reboot Air") + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownAir) { + return qsTr("Shutdown Air") + } + if (powerAction === PowerActionDialoque.PowerAction.RebootFC) { + return qsTr("Reboot FC") + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownFC) { + return qsTr("Shutdown FC") + } + return qsTr("Yes") + } + + + + onPressed: { + var result=false + var messageForHUD="ERROR" + if (powerAction === PowerActionDialoque.PowerAction.RebootGround) { + messageForHUD="Rebooting ground station" + result = _ohdAction.send_command_reboot_gnd(true) + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownGround) { + messageForHUD="Shutting down ground station" + result = _ohdAction.send_command_reboot_gnd(false); + } + if (powerAction === PowerActionDialoque.PowerAction.RebootAir) { + messageForHUD="Rebooting air pi" + result = _ohdAction.send_command_reboot_air(true); + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownAir) { + messageForHUD="Shutting down air pi" + result = _ohdAction.send_command_reboot_air(false); + } + if (powerAction === PowerActionDialoque.PowerAction.ShutdownFC) { //button commented out + messageForHUD="Shutting down Flight Controller" + result = _fcMavlinkAction.send_command_reboot(false); + } + if (powerAction === PowerActionDialoque.PowerAction.RebootFC) { + messageForHUD="Rebooting Flight Controller" + result = _fcMavlinkAction.send_command_reboot(true); + } + if(result){ + settings_panel.visible = false; + powerDialog.visible = false + }else{ + console.log("Reboot/Shutdown failed, no response.") + messageForHUD="Reboot/Shutdown failed, no response" + _hudLogMessagesModel.add_message_warning(messageForHUD) + } + } + } + } +} + diff --git a/qml/ui/configpopup/status/QOpenHDServiceDialoque.qml b/qml/ui/configpopup/status/QOpenHDServiceDialoque.qml new file mode 100644 index 000000000..1a280c2a9 --- /dev/null +++ b/qml/ui/configpopup/status/QOpenHDServiceDialoque.qml @@ -0,0 +1,106 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + + +// Dialoque for interacting with the QOpenHD service (depending on platform) +Card { + id: qopenhdpowerdialoque + height: 240 + width: 400 + z: 5.0 + anchors.centerIn: parent + cardNameColor: "black" + hasHeaderImage: true + + Component.onCompleted: visible = false + + property bool stateVisible: visible + + function open_dialoque(type){ + qopenhdpowerdialoque.m_type=type + qopenhdpowerdialoque.visible=true + } + + property int m_type : 0 + + function get_info_text(){ + if(m_type==0){ + return "Only for RPI - Stops QOpenHD and lets the autostart service restart it." + } + return "Only for RPI - Stops QOpenHD and disables the autostart service until the next reboot. Can be used to get into terminal on rpi." + } + + function get_button_text(){ + if(m_type==0){ + return "Restart QOpenHD" + } + return "Terminate QOpenHD" + } + + cardName: qsTr("QOpenHD") + cardBody: Column { + height: powerDialog.height + width: powerDialog.width + + Text { + text: get_info_text() + width: parent.width + leftPadding: 12 + rightPadding: 12 + wrapMode: Text.WordWrap + } + } + + hasFooter: true + cardFooter: Item { + anchors.fill: parent + Button { + width: 96 + height: 48 + text: qsTr("Cancel") + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + font.pixelSize: 14 + font.capitalization: Font.MixedCase + onPressed: { + qopenhdpowerdialoque.visible=false; + } + } + + Button { + width: 140 + height: 48 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + font.pixelSize: 14 + font.capitalization: Font.MixedCase + Material.accent: Material.Red + highlighted: true + + text: get_button_text() + + onPressed: { + if(m_type==0){ + _qopenhd.quit_qopenhd() + }else{ + _qopenhd.disable_service_and_quit() + } + } + } + } +} + diff --git a/qml/ui/configpopup/status/QOpenHDVersionCard.qml b/qml/ui/configpopup/status/QOpenHDVersionCard.qml new file mode 100644 index 000000000..a087d7145 --- /dev/null +++ b/qml/ui/configpopup/status/QOpenHDVersionCard.qml @@ -0,0 +1,75 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Card { + id: qopenhdVersionPanel + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.top: parent.top + anchors.topMargin: 12 + + hasHeader: false + + cardBody: ColumnLayout { + Row { + spacing: 12 + leftPadding: 18 + + Image { + id: image + width: 48 + height: 48 + source: "qrc:/round.png" + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: title.verticalCenter + } + + Text { + id: title + height: 48 + color: "#ff3a3a3a" + text: qsTr("QOpenHD-evo-2.5.0-beta3") + font.pixelSize: 36 + } + } + + RowLayout{ + height: 14 + width: parent.width + anchors.leftMargin: 18 + anchors.rightMargin: 18 + Text { + id: qopenhd_version + color: "#ff3a3a3a" + text: qsTr(QOPENHD_GIT_VERSION) + } + + Text { + id: license + color: "#ff3a3a3a" + text: qsTr("License: GPLv3") + onLinkActivated: { + Qt.openUrlExternally("https://github.com/OpenHD/QOpenHD/blob/master/LICENSE") + } + } + + Text { + id: qopenhd_commit_hash + color: "#ff3a3a3a" + text: qsTr(QOPENHD_GIT_COMMIT_HASH) + } + } + } +} diff --git a/qml/ui/configpopup/status/StatusCardBodyFC.qml b/qml/ui/configpopup/status/StatusCardBodyFC.qml new file mode 100644 index 000000000..3cb5a33b5 --- /dev/null +++ b/qml/ui/configpopup/status/StatusCardBodyFC.qml @@ -0,0 +1,127 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +ColumnLayout { + // from https://doc.qt.io/qt-6/qml-qtquick-layouts-rowlayout.html + anchors.fill: parent + spacing: 2 + + property int rowHeight: 64 + property int text_minHeight: 20 + + property bool m_is_alive: _fcMavlinkSystem.is_alive + function get_alive_text(){ + return m_is_alive ? "Yes" : "NOT ALIVE !" + } + function get_alive_text_color(){ + //return m_is_alive ? "green" : "red" + return m_is_alive ? "green" : "black" + } + + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + text: qsTr("Autopilot:") + height: 24 + font.pixelSize: 14 + font.bold: true + leftPadding: 12 + } + Text { + text: _fcMavlinkSystem.autopilot_type_str + height: 24 + width: 256 + font.pixelSize: 14 + leftPadding: 6 + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + text: qsTr("MAV type:") + height: 24 + font.pixelSize: 14 + font.bold: true + leftPadding: 12 + } + Text { + text: _fcMavlinkSystem.mav_type_str + height: 24 + width: 256 + font.pixelSize: 14 + leftPadding: 6 + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + text: qsTr("Last Ping:") + height: 24 + font.pixelSize: 14 + font.bold: true + leftPadding: 12 + } + Text { + text: _fcMavlinkSystem.last_ping_result_flight_ctrl + height: 24 + width: 256 + font.pixelSize: 14 + leftPadding: 6 + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + text: qsTr("Alive: ") + height: 24 + font.pixelSize: 14 + font.bold: true + leftPadding: 12 + } + Text { + text: get_alive_text() + color: get_alive_text_color() + height: 24 + width: 256 + font.pixelSize: 14 + leftPadding: 6 + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + text: qsTr("Sys id: ") + height: 24 + font.pixelSize: 14 + font.bold: true + leftPadding: 12 + } + Text { + text: _fcMavlinkSystem.for_osd_sys_id == -1 ? "na" : qsTr(""+_fcMavlinkSystem.for_osd_sys_id) + height: 24 + width: 256 + font.pixelSize: 14 + leftPadding: 6 + } + } +} diff --git a/qml/ui/configpopup/status/StatusCardBodyOpenHD.qml b/qml/ui/configpopup/status/StatusCardBodyOpenHD.qml new file mode 100644 index 000000000..c1db18f19 --- /dev/null +++ b/qml/ui/configpopup/status/StatusCardBodyOpenHD.qml @@ -0,0 +1,224 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Dialogs 1.1 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +ColumnLayout { + // from https://doc.qt.io/qt-6/qml-qtquick-layouts-rowlayout.html + anchors.fill: parent + spacing: 2 + + property bool m_is_ground: false + + property var m_model: m_is_ground ? _ohdSystemGround : _ohdSystemAir + + property string m_version: m_model.openhd_version + property string m_last_ping: m_model.last_ping_result_openhd + property bool m_is_alive: m_model.is_alive + property string m_qopenhd_version: "TODO" + + function get_alive_text(){ + return m_is_alive ? "Yes" : "NOT ALIVE !" + } + function get_alive_text_color(){ + //return m_is_alive ? "green" : "red" + return m_is_alive ? "green" : "black" + } + + function get_cards_text(){ + if(!m_is_ground){ + return _wifi_card_air.card_type_as_string; + } + // Ground + var ret=""; + if(_wifi_card_gnd0.alive){ + ret+="[1]"+_wifi_card_gnd0.card_type_as_string; + } + if(_wifi_card_gnd1.alive){ + ret+="\n"+"[2]"+_wifi_card_gnd1.card_type_as_string; + } + if(_wifi_card_gnd2.alive){ + ret+="\n"+"[3]"+_wifi_card_gnd2.card_type_as_string; + } + if(_wifi_card_gnd3.alive){ + ret+="\n"+"[4]"+_wifi_card_gnd3.card_type_as_string; + } + if(ret.length==0)return "N/A"; + return ret; + } + + function gnd_uplink_state(){ + if(!_ohdSystemGround.is_alive)return 0; + if(!_ohdSystemAir.is_alive)return 0; + if(!_ohdSystemAir.curr_rx_last_packet_status_good)return -1; + return 1; + } + function gnd_uplink_state_text(){ + var curr_gnd_uplink_state=gnd_uplink_state(); + if(curr_gnd_uplink_state==0)return "N/A"; + if(curr_gnd_uplink_state==1) return "OK"; + return "ERROR"; + } + + function get_text_qopenhd_openhd_ground_version_mismatch(){ + var ret="Your version of QOpenHD ["+m_qopenhd_version+"] is incompatible with your ground station OpenHD version: ["+m_version+"]"+ + "\nPlease update QOpenHD / your Ground station."; + return ret; + } + function get_text_openhd_air_ground_version_mismatch(){ + var ret="Your version of OpenHD AIR ["+m_qopenhd_version+"] is incompatible with OpenHD GROUND: ["+_ohdSystemGround.openhd_version+"]"+ + "\nPlease update your AIR / GROUND unit."; + return ret; + } + + property int text_minHeight: 30 + property int column_preferred_height: 50 + + property int left_part_preferred_with: 100 + + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + Layout.preferredWidth: left_part_preferred_with + text: qsTr("OHD Version:") + } + Text { + text: m_version + visible: !b_version_warning.visible + } + ButtonYellow{ + text: m_version + id: b_version_warning + onClicked: { + var text_warning= m_is_ground ? get_text_qopenhd_openhd_ground_version_mismatch() : get_text_openhd_air_ground_version_mismatch() + _messageBoxInstance.set_text_and_show(text_warning) + } + visible: { + if(m_is_ground){ + // Show if ground reported version is valid and there is a mismatch OpenHD ground / QOpenHD + if(_ohdSystemGround.openhd_version=="N/A"){ + return false; + } + return _ohdSystemGround.openhd_version != m_qopenhd_version; + }else{ + // Show if ground and air reported version is valid and there is a mismatch + if(_ohdSystemGround.openhd_version=="N/A" || _ohdSystemAir.openhd_version=="N/A"){ + return false; + } + return _ohdSystemGround.openhd_version != _ohdSystemAir.openhd_version + } + } + Layout.preferredHeight: text_minHeight + Layout.minimumHeight: text_minHeight + height: text_minHeight + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + Layout.preferredWidth: left_part_preferred_with + text: qsTr("Last Ping:") + } + Text { + text: m_last_ping + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + Layout.preferredWidth: left_part_preferred_with + text: qsTr("Alive: ") + } + Text { + text: get_alive_text() + color: get_alive_text_color() + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + Text { + Layout.preferredWidth: left_part_preferred_with + text: qsTr(m_is_ground ? "WB Card(s): " : "WB Card:") + } + Text { + text: get_cards_text() + visible: !b_unsupported_cards_warning.visible + } + ButtonYellow{ + id: b_unsupported_cards_warning + text: get_cards_text() + onClicked: { + var message="Using unsupported card(s) has side effects like non-working frequency changes, no uplink gnd-air or bad range. Be warned !"; + _messageBoxInstance.set_text_and_show(message); + } + visible: { + if(m_is_ground){ + if(_wifi_card_gnd0.alive && !_wifi_card_gnd0.card_type_supported)return true; + if(_wifi_card_gnd1.alive && !_wifi_card_gnd1.card_type_supported)return true; + if(_wifi_card_gnd2.alive && !_wifi_card_gnd2.card_type_supported)return true; + if(_wifi_card_gnd3.alive && !_wifi_card_gnd3.card_type_supported)return true; + return false; + } + // On air, there is nonly one card´ + if(_wifi_card_air.alive && !_wifi_card_air.card_type_supported){ + return true; + } + return false; + } + Layout.preferredHeight: text_minHeight + Layout.minimumHeight: text_minHeight + } + } + RowLayout{ + Layout.fillWidth: true + Layout.minimumHeight: text_minHeight + spacing: 6 + visible: m_is_ground + Text{ + Layout.preferredWidth: left_part_preferred_with + text: "Uplink:" + } + Text{ + text: gnd_uplink_state_text() + visible: { + var gnd_up_state=gnd_uplink_state() + if(gnd_up_state===0 || gnd_up_state===1)return true; + return false; + } + } + ButtonYellow{ + text: gnd_uplink_state_text() + onClicked: { + var message="Looks like your uplink (GND to AIR) is not functional - please use a supported card on your GND station"+ + " and make sure passive (listen only) mode is disabled on your ground station." + _messageBoxInstance.set_text_and_show(message) + } + visible: gnd_uplink_state()===-1; + Layout.preferredHeight: text_minHeight + Layout.minimumHeight: text_minHeight + height: text_minHeight + } + } + + // Padding + Item{ + Layout.fillWidth: true + Layout.fillHeight: true + } +} diff --git a/qml/ui/configpopup/status/StatusCardFooterGenericOHDFC.qml b/qml/ui/configpopup/status/StatusCardFooterGenericOHDFC.qml new file mode 100644 index 000000000..3ec086425 --- /dev/null +++ b/qml/ui/configpopup/status/StatusCardFooterGenericOHDFC.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +Item { + anchors.fill: parent + property int m_type: 0 // 0 = ground, 1= air, 2=FC + + function get_show_power_actions(){ + if(m_type==0){ + return _ohdSystemGround.is_alive + }else if(m_type==1){ + return _ohdSystemAir.is_alive + } + return _fcMavlinkSystem.is_alive + } + + function open_warning(){ + if(m_type==0){ + var text="Looks like you are not receiving any messages from your OpenHD ground unit - when running on an external device,"+ + "please use the connect tab to connect to your ground unit. Otherwise, please make sure OpenHD core is running on your ground station." + _messageBoxInstance.set_text_and_show(text) + }else if(m_type==1){ + var text="" + if(_ohdSystemGround.is_alive){ + text="Looks like you are not receiving any messages from your OpenHD air unit - please go to OpenHD settings (WB Link) and connect to your air unit "+ + "by performing a channel scan (FIND AIR UNIT)." + }else{ + text="Please make sure your ground unit is alive first." + } + _messageBoxInstance.set_text_and_show(text) + }else{ + var text="" + if(!(_ohdSystemGround.is_alive && _ohdSystemAir.is_alive)){ + text="Please make sure your air and ground unit are alive first." + }else{ + text="No FC detected - your FC needs to be connected to your AIR UNIT via UART and you have to set the matching baud rate / FC_UART_CONN type in the OpenHD AIR UNIT parameters set." + } + _messageBoxInstance.set_text_and_show(text) + } + } + + RowLayout{ + anchors.fill: parent + //visible: get_show_power_actions() + Button{ + visible: get_show_power_actions() + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 10 + text: qsTr("REBOOT") + onPressed: { + open_power_action_dialoque(m_type,true) + } + } + Button{ + visible: get_show_power_actions() + Layout.alignment: Qt.AlignRight + Layout.rightMargin: 10 + text: qsTr("SHUTDOWN") + onPressed: { + open_power_action_dialoque(m_type,false) + } + } + ButtonIconWarning{ + visible: !get_show_power_actions() + Layout.alignment: Qt.AlignCenter + onPressed: { + open_warning(); + } + } + } +} diff --git a/qml/ui/configpopup/status/StatusCardsColumn.qml b/qml/ui/configpopup/status/StatusCardsColumn.qml new file mode 100644 index 000000000..f3b7df07d --- /dev/null +++ b/qml/ui/configpopup/status/StatusCardsColumn.qml @@ -0,0 +1,78 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +import "../../../ui" as Ui +import "../../elements" + +// +// The 3 status cards (OpenHD AIR & GND, FC) +// next to each other +Item { + + RowLayout { + width: parent.width + height: parent.height + + Card { + id: groundBox + Layout.fillHeight: true + Layout.fillWidth: true + cardName: qsTr("OHD Ground station") + + m_style_error: !_ohdSystemGround.is_alive + //m_style_error: false + + cardBody: StatusCardBodyOpenHD{ + m_is_ground: true + } + + hasFooter: true + cardFooter: StatusCardFooterGenericOHDFC{ + m_type: 0 + } + } + + Card { + id: airBox + Layout.fillHeight: true + Layout.fillWidth: true + cardName: qsTr("OHD Air unit") + m_style_error: !_ohdSystemAir.is_alive + //m_style_error: false + cardBody: StatusCardBodyOpenHD{ + m_is_ground: false + } + + hasFooter: true + cardFooter: StatusCardFooterGenericOHDFC{ + m_type: 1 + } + } + + + Card { + id: fcBox + visible: true + Layout.fillHeight: true + Layout.fillWidth: true + cardName: qsTr("Flight Controller") + m_style_error: !_fcMavlinkSystem.is_alive + //m_style_error: false + cardBody: StatusCardBodyFC{ + + } + + hasFooter: true + cardFooter: StatusCardFooterGenericOHDFC{ + m_type: 2 + } + } + } // end OpenHD air, ground, FC status cards layout + +} diff --git a/qml/ui/delegates/BaseDelegate.ui.qml b/qml/ui/delegates/BaseDelegate.ui.qml deleted file mode 100644 index 1bd928d16..000000000 --- a/qml/ui/delegates/BaseDelegate.ui.qml +++ /dev/null @@ -1,90 +0,0 @@ -import QtQuick 2.13 - -import QtQuick.Layouts 1.13 -import QtQuick.Controls 2.13 -import QtQuick.Shapes 1.0 - -Rectangle { - id: baseDelegate - width: 504 - height: 64 - color: index % 2 == 0 ? "#8cbfd7f3" : "white" - - property string infoText: model.info - - property bool showingDetail: false - - clip: true - - Behavior on height { - NumberAnimation { duration: 200 } - } - - Text { - id: showInfoButton - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: 18 - - text: showingDetail ? "\uf106" : "\uf059" - - Behavior on text { - PropertyAnimation { duration: 200 } - } - - font.family: "Font Awesome 5 Free" - color: "#aa0648f9" - - width: 36 - height: 64 - MouseArea { - id: mouseArea - anchors.fill: parent - // @disable-check M222 - onClicked: { - //delegateInfo.open() - if (showingDetail) { - showingDetail = false; - baseDelegate.height -= 64; - } else { - showingDetail = true; - baseDelegate.height += 64; - } - } - } - } - - - Text { - text: model.title - anchors.leftMargin: 36 - anchors.top: parent.top - anchors.topMargin: 16 - verticalAlignment: Text.AlignVCenter - width: 192 - height: 32 - font.bold: true - font.pixelSize: 13 - anchors.left: parent.left - } - - Text { - anchors.left: parent.left - anchors.leftMargin: 36 - anchors.right: parent.right - anchors.rightMargin: 6 - anchors.top: parent.top - anchors.topMargin: 64 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - text: infoText - wrapMode: Text.WordWrap - font.pixelSize: 12 - visible: true - } -} - diff --git a/qml/ui/delegates/BoolSwitchSettingDelegate.ui.qml b/qml/ui/delegates/BoolSwitchSettingDelegate.ui.qml deleted file mode 100644 index 1bf5fb2aa..000000000 --- a/qml/ui/delegates/BoolSwitchSettingDelegate.ui.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.12 - -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - - Switch { - id: valueElement - anchors.right: parent.right - checked: model.value - font.pixelSize: 16 - topPadding: 0 - bottomPadding: 0 - anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36 - anchors.top: parent.top - anchors.topMargin: 12 - width: 32 - height: 32 - // @disable-check M223 - onCheckedChanged: { - model.value = checked - } - enabled: !model.disabled - } -} diff --git a/qml/ui/delegates/ChoiceSettingDelegate.ui.qml b/qml/ui/delegates/ChoiceSettingDelegate.ui.qml deleted file mode 100644 index e1ee157b8..000000000 --- a/qml/ui/delegates/ChoiceSettingDelegate.ui.qml +++ /dev/null @@ -1,97 +0,0 @@ -import QtQuick 2.12 - -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - // allow access to the index of the setting item within the ListModel within the onActivated - // signal, which has its own "index" argument - property var itemIndex: index - - ComboBox { - id: valueElement - textRole: "title" - model: itemModel.choiceValues - width: 212 - height: 40 - font.pixelSize: 14 - - anchors.right: parent.right - anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36 - anchors.top: parent.top - anchors.topMargin: 12 - enabled: !itemModel.disabled - - - // @disable-check M223 - Component.onCompleted: { - // @disable-check M223 - for (var i = 0; i < model.count; i++) { - // @disable-check M222 - var choice = model.get(i); - // @disable-check M223 - if (choice.value == value) { - currentIndex = i; - } - } - } - - // @disable-check M223 - onActivated: { - // @disable-check M222 - listModel.setProperty(itemIndex, "value", valueElement.model.get(index).value); - } - - /* - * These are mostly copied from the Qt source code in order to change some of the styling - * and alignment. If there is a better way to do that, these can be deleted. - * - */ - delegate: ItemDelegate { - id: itemDelegate - width: parent.width - // @disable-check M222 - text: valueElement.textRole ? (Array.isArray(valueElement.model) ? modelData[valueElement.textRole] : model[valueElement.textRole]) : modelData - font.weight: valueElement.currentIndex === index ? Font.DemiBold : Font.Normal - highlighted: valueElement.highlightedIndex === index - hoverEnabled: valueElement.hoverEnabled - - contentItem: Label { - text: itemDelegate.text - font: itemDelegate.font - elide: Label.ElideRight - verticalAlignment: Label.AlignVCenter - horizontalAlignment: Label.AlignRight - } - } - - contentItem: TextField { - leftPadding: !valueElement.mirrored ? 12 : valueElement.editable && activeFocus ? 3 : 1 - rightPadding: valueElement.mirrored ? 12 : valueElement.editable && activeFocus ? 3 : 1 - topPadding: 6 - valueElement.padding - bottomPadding: 6 - valueElement.padding - - text: valueElement.editable ? valueElement.editText : valueElement.displayText - - enabled: valueElement.editable - autoScroll: valueElement.editable - readOnly: valueElement.down - inputMethodHints: valueElement.inputMethodHints - validator: valueElement.validator - - font: valueElement.font - color: valueElement.editable ? valueElement.palette.text : valueElement.palette.buttonText - selectionColor: valueElement.palette.highlight - selectedTextColor: valueElement.palette.highlightedText - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignRight - - background: Rectangle { - visible: valueElement.enabled && valueElement.editable && !valueElement.flat - border.width: parent && parent.activeFocus ? 2 : 1 - border.color: parent && parent.activeFocus ? valueElement.palette.highlight : valueElement.palette.button - color: valueElement.palette.base - } - } - } -} diff --git a/qml/ui/delegates/NumberSettingDelegate.ui.qml b/qml/ui/delegates/NumberSettingDelegate.ui.qml deleted file mode 100644 index 641d9af11..000000000 --- a/qml/ui/delegates/NumberSettingDelegate.ui.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - - SpinBox { - id: choiceBox - width: 210 - height: 48 - font.pixelSize: 14 - from: model.lowerLimit - to: model.upperLimit - stepSize: 1 - value: model.value - anchors.right: parent.right - anchors.rightMargin: Qt.inputMethod.visible ? 78 : 18 - anchors.top: parent.top - anchors.topMargin: 8 - // @disable-check M223 - onValueChanged: { - model.value = value - } - enabled: !model.disabled - } -} diff --git a/qml/ui/delegates/RangeSettingDelegate.ui.qml b/qml/ui/delegates/RangeSettingDelegate.ui.qml deleted file mode 100644 index 21ba1c336..000000000 --- a/qml/ui/delegates/RangeSettingDelegate.ui.qml +++ /dev/null @@ -1,32 +0,0 @@ -import QtQuick 2.12 - -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - - Slider { - id: valueElement - width: 210 - height: 48 - from: model.from - to: model.to - stepSize: 1 - value: model.value - anchors.right: parent.right - anchors.rightMargin: Qt.inputMethod.visible ? 78 : 18 - anchors.top: parent.top - anchors.topMargin: 8 - - ToolTip { - parent: valueElement.handle - visible: valueElement.pressed - // @disable-check M222 - text: "%1%2".arg(valueElement.value).arg(model.unit) - } - onValueChanged: { - model.value = value - } - enabled: !model.disabled - } -} diff --git a/qml/ui/delegates/SwitchSettingDelegate.ui.qml b/qml/ui/delegates/SwitchSettingDelegate.ui.qml deleted file mode 100644 index 444acda26..000000000 --- a/qml/ui/delegates/SwitchSettingDelegate.ui.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick 2.12 - -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - - Switch { - id: valueElement - checked: model.checked - anchors.right: parent.right - anchors.rightMargin: Qt.inputMethod.visible ? 78 : 18 - anchors.top: parent.top - anchors.topMargin: 8 - width: 210 - height: 48 - // @disable-check M223 - onCheckedChanged: { - model.value = (checked == true) ? model.checkedValue : model.uncheckedValue; - } - enabled: !model.disabled - } -} diff --git a/qml/ui/delegates/TextSettingDelegate.ui.qml b/qml/ui/delegates/TextSettingDelegate.ui.qml deleted file mode 100644 index 20c317b1b..000000000 --- a/qml/ui/delegates/TextSettingDelegate.ui.qml +++ /dev/null @@ -1,31 +0,0 @@ -import QtQuick 2.12 - -import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - -BaseDelegate { - - TextField { - id: valueElement - anchors.right: parent.right - font.pixelSize: 14 - topPadding: 0 - bottomPadding: 8 - horizontalAlignment: Text.AlignRight - anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36; - anchors.top: parent.top - anchors.topMargin: 12 - width: 192 - height: 32 - // @disable-check M223 - Component.onCompleted: { - text = model.value - cursorPosition = 0 - } - // @disable-check M223 - onTextChanged: { - model.value = text; - } - enabled: !model.disabled - } -} diff --git a/qml/ui/elements/AppSettings.qml b/qml/ui/elements/AppSettings.qml index cb4d1c798..4b43d2d2b 100644 --- a/qml/ui/elements/AppSettings.qml +++ b/qml/ui/elements/AppSettings.qml @@ -109,7 +109,8 @@ Settings { property bool show_downlink_rssi: true //Const10 - property bool downlink_show_dbm_and_packets_per_card: false + property bool downlink_show_dbm_and_packets_per_card: true + property bool downlink_dbm_per_card_show_multiple_antennas:false property bool downlink_show_current_bitrate: false property bool downlink_cards_right: false property bool downlink_rssi_declutter: false @@ -120,6 +121,7 @@ Settings { property int downlink_packet_loss_perc_warn : 20 property bool downlink_dbm_warning: true property bool downlink_signal_quality_show: false + property bool downlink_pollution_show: false property bool show_uplink_rssi: true property bool uplink_rssi_declutter: false @@ -203,6 +205,7 @@ Settings { property bool show_ground_status: true property bool ground_status_declutter: false + property bool ground_status_show_undervolt_icon: true property double ground_status_cpu_caution: 50 property double ground_status_cpu_warn: 70 property double ground_status_temp_caution: 60 @@ -210,6 +213,7 @@ Settings { property bool show_air_status: true property bool air_status_declutter: false + property bool air_status_show_undervolt_icon: true property double air_status_cpu_caution: 50 property double air_status_cpu_warn: 70 property double air_status_temp_caution: 60 @@ -325,14 +329,7 @@ Settings { property bool bank_angle_indicator_widget_show_numbers: true property bool bank_angle_indicator_widget_sky_pointer: false - property bool show_adsb: false - property int adsb_distance_limit: 15000//meters. Bound box for api from map center (so x2) - //property int adsb_sdr_distance: 20 - property bool adsb_api_sdr: false - property bool adsb_api_openskynetwork: false - property bool adsb_show_unknown_or_zero_alt: false - - property bool show_mission: false + property bool show_mission: true property bool show_record_widget: true property double recordTextSize: 14 @@ -386,7 +383,7 @@ Settings { // really really dirty, i want to get rid of it as soon as possible property bool dirty_enable_inav_hacks: false - // FC discovery - annoying mavsdk + // FC discovery - can be annoying / tricky property bool dirty_enable_mavlink_fc_sys_id_check: false property int custom_cursor_type: 0 @@ -402,10 +399,6 @@ Settings { property int screen_settings_overlay_size_percent : 100 property bool screen_settings_openhd_parameters_transparent: false - // dangerous, but neccessary - property bool qopenhd_allow_changing_ground_unit_frequency_no_sync: false - property bool qopenhd_allow_changing_ground_unit_channel_width_no_sync: false - property double hide_identity_latitude_offset: 0.0 property double hide_identity_longitude_offset: 0.0 } diff --git a/qml/ui/elements/ArmDisarmSlider.qml b/qml/ui/elements/ArmDisarmSlider.qml new file mode 100644 index 000000000..30d4aa760 --- /dev/null +++ b/qml/ui/elements/ArmDisarmSlider.qml @@ -0,0 +1,51 @@ +import QtQuick 2.12 + +Item{ + width: 180 + height: 32 + + PillSlider{ + id: slider_arm + m_pill_description: "ARM" + visible: !_fcMavlinkSystem.armed + + Timer { + id: selectionResetTimer + running: false + interval: 3000 + repeat: false + onTriggered: { + slider_arm.reset_to_dragable() + } + } + + onDraggedRight : { + _fcMavlinkAction.arm_fc_async(true); + selectionResetTimer.start() + } + } + + PillSlider{ + id: slider_disarm + m_pill_description: "DISARM" + visible: _fcMavlinkSystem.armed + + Timer { + id: selectionResetTimer2 + running: false + interval: 3000 + repeat: false + onTriggered: { + slider_disarm.reset_to_dragable() + } + } + + onDraggedRight : { + _fcMavlinkAction.arm_fc_async(false); + selectionResetTimer2.start() + } + } + +} + + diff --git a/qml/ui/elements/ButtonGreen.qml b/qml/ui/elements/ButtonGreen.qml new file mode 100644 index 000000000..68c8d8ee8 --- /dev/null +++ b/qml/ui/elements/ButtonGreen.qml @@ -0,0 +1,14 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.0 +import QtGraphicalEffects 1.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +Button { + Material.accent: Material.Green + highlighted: true +} diff --git a/qml/ui/elements/ButtonIconInfo.qml b/qml/ui/elements/ButtonIconInfo.qml new file mode 100644 index 000000000..d3331f04f --- /dev/null +++ b/qml/ui/elements/ButtonIconInfo.qml @@ -0,0 +1,13 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +// Info icon and blue color +Button { + //text: "INFO" + text: "\uf05a" + font.family: "Font Awesome 5 Free" + Material.background:Material.LightBlue + //highlighted: true +} diff --git a/qml/ui/elements/ButtonIconInfoText.qml b/qml/ui/elements/ButtonIconInfoText.qml new file mode 100644 index 000000000..7ea3a548e --- /dev/null +++ b/qml/ui/elements/ButtonIconInfoText.qml @@ -0,0 +1,18 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Material 2.12 + +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +// Same as ButtonIconInfo, but if the user clicks this button, +// a programmer defined text is shown in the workaround message box (popup) +ButtonIconInfo{ + property string m_info_text: "I should never appear" // Intended to be overridden + + onClicked: { + _messageBoxInstance.set_text_and_show(m_info_text); + } +} diff --git a/qml/ui/elements/ButtonIconWarning.qml b/qml/ui/elements/ButtonIconWarning.qml new file mode 100644 index 000000000..97107b6ca --- /dev/null +++ b/qml/ui/elements/ButtonIconWarning.qml @@ -0,0 +1,12 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +// Used to hint at something's wrong - +// Warning icon and yellow color +Button { + text: "\uf071" + font.family: "Font Awesome 5 Free" + Material.background:Material.Lime +} diff --git a/qml/ui/elements/ButtonOrange.qml b/qml/ui/elements/ButtonOrange.qml new file mode 100644 index 000000000..f1b2af167 --- /dev/null +++ b/qml/ui/elements/ButtonOrange.qml @@ -0,0 +1,15 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.0 +import QtGraphicalEffects 1.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +// Orange button +Button { + Material.accent: Material.Orange + highlighted: true +} diff --git a/qml/ui/elements/ButtonRed.qml b/qml/ui/elements/ButtonRed.qml new file mode 100644 index 000000000..854b7c22a --- /dev/null +++ b/qml/ui/elements/ButtonRed.qml @@ -0,0 +1,15 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.0 +import QtGraphicalEffects 1.12 +import Qt.labs.settings 1.0 + +import OpenHD 1.0 + +// Red button +Button { + Material.accent: Material.Red + highlighted: true +} diff --git a/qml/ui/elements/ButtonYellow.qml b/qml/ui/elements/ButtonYellow.qml new file mode 100644 index 000000000..8564799dd --- /dev/null +++ b/qml/ui/elements/ButtonYellow.qml @@ -0,0 +1,9 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Dialogs 1.0 +import QtQuick.Controls.Material 2.12 + +// Yellow (Lime) background +Button { + Material.background:Material.Lime +} diff --git a/qml/ui/elements/Card.qml b/qml/ui/elements/Card.qml index 1a123ea6c..1fd247037 100644 --- a/qml/ui/elements/Card.qml +++ b/qml/ui/elements/Card.qml @@ -1,6 +1,130 @@ import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.12 -CardForm { -} +Item { + id: card + + property alias cardName: cardHeader.text + property Item cardBody: Item {} + property Item cardFooter: Item {} + property bool hasFooter: false + + property bool hasHeader: true + property bool hasHeaderImage: false + + property int cardRadius: 6 + property color cardNameColor: "#33aaff" + property color borderColor: "#3a000000" + + property bool m_style_error: false + + Rectangle { + id: background + radius: cardRadius + //color: m_style_error ? "red" : "white" + color: "white" + anchors.fill: parent + border.width: 1 + border.color: borderColor + //border.width: m_style_error ? 10: 1 + //border.color: m_style_error ? "red" : borderColor + } + + Item { + id: cardID + width: parent.width + height: hasHeader ? 32 : 0 + visible: hasHeader + + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + + Text { + id: cardHeaderImage + width: hasHeaderImage ? 24 : 0 + height: hasHeaderImage ? 24 : 0 + visible: hasHeaderImage + color: "orange" + font.bold: true + font.pixelSize: 16 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.top: parent.top + text: "\uf071" + font.family: "Font Awesome 5 Free" + topPadding: 5 + leftPadding: 12 + } + + Text { + id: cardHeader + width: parent.width + height: 24 + color: cardNameColor + font.bold: true + font.pixelSize: 16 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + anchors.top: parent.top + anchors.left: cardHeaderImage.right + leftPadding: 12 + topPadding: 3 + } + } + + Item { + id: cardBodyHolder + anchors.top: cardID.bottom + anchors.topMargin: 6 + //anchors.bottom: cardFooterInner.top + //anchors.bottomMargin: 3 + width: parent.width + children: cardBody + } + + Rectangle{ + anchors.fill: background + color: "gray" + opacity: 0.6 + visible: m_style_error + } + + Rectangle { + id: cardFooterInner + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + children: cardFooter + visible: hasFooter + height: 64 + color: "#f6f6f6" + border.width: 1 + border.color: borderColor + } + + /*Rectangle{ + anchors.fill: parent + color: "transparent" + border.color: "red" + border.width: 3 + radius: 6 + visible: m_style_error + }*/ + /*Rectangle{ + anchors.fill: cardBodyHolder + color: "gray" + opacity: 0.8 + visible: m_style_error + }*/ +} diff --git a/qml/ui/elements/CardForm.ui.qml b/qml/ui/elements/CardForm.ui.qml deleted file mode 100644 index a15428e89..000000000 --- a/qml/ui/elements/CardForm.ui.qml +++ /dev/null @@ -1,101 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 - - - -Item { - id: card - - property alias cardName: cardHeader.text - property Item cardBody: Item {} - property Item cardFooter: Item {} - property bool hasFooter: false - - property bool hasHeader: true - property bool hasHeaderImage: false - - property int cardRadius: 6 - property color cardNameColor: "#33aaff" - property color borderColor: "#3a000000" - - Rectangle { - id: innerCard - radius: cardRadius - color: "white" - anchors.fill: parent - border.width: 1 - border.color: borderColor - - } - - Item { - id: cardID - width: parent.width - height: hasHeader ? 32 : 0 - visible: hasHeader - - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - - Text { - id: cardHeaderImage - width: hasHeaderImage ? 24 : 0 - height: hasHeaderImage ? 24 : 0 - visible: hasHeaderImage - color: "orange" - font.bold: true - font.pixelSize: 16 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - anchors.top: parent.top - text: "\uf071" - font.family: "Font Awesome 5 Free" - topPadding: 5 - leftPadding: 12 - } - - Text { - id: cardHeader - width: parent.width - height: 24 - color: cardNameColor - font.bold: true - font.pixelSize: 16 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignLeft - anchors.top: parent.top - anchors.left: cardHeaderImage.right - leftPadding: 12 - topPadding: 3 - } - } - - Item { - anchors.top: cardID.bottom - anchors.topMargin: 6 - width: parent.width - children: cardBody - } - - Rectangle { - id: cardFooterInner - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - - children: cardFooter - visible: hasFooter - height: 64 - color: "#f6f6f6" - border.width: 1 - border.color: borderColor - } -} diff --git a/qml/ui/elements/CardToast.qml b/qml/ui/elements/CardToast.qml new file mode 100644 index 000000000..474be5d46 --- /dev/null +++ b/qml/ui/elements/CardToast.qml @@ -0,0 +1,69 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.12 + +// +// Similar behvaiour to android Toast +// See c++ QOpenHD instance for more info +// +Item { + id: card_toast + z: 22.0 + + property string m_text: "FILL ME TEXT" + + width: parent.width + height: parent.height + + function get_width(){ + var parent_width= parent.width + if(parent_width>500){ + return 500; + } + return parent.width + } + function get_bottom_margin(){ + //return 50; + var parent_height=parent.height + return parent_height * 0.15; + } + + Rectangle { + id: background + radius: 8 + //color: "green" + //color: "white" + color: "black" + opacity: 0.7 + border.width: 3 + border.color: "white" + + width: get_width() + height: 100 + + //anchors.centerIn: parent + anchors.bottom: parent.bottom + anchors.bottomMargin: get_bottom_margin() + anchors.horizontalCenter: parent.horizontalCenter + + Text{ + text: m_text + wrapMode: Text.WordWrap + width: parent.width-24 + height: parent.height-24 + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "white" + // + font.pointSize: 17 + minimumPointSize: 10 + fontSizeMode: Text.Fit + } + } + + + + +} diff --git a/qml/ui/elements/ConfirmSlider.qml b/qml/ui/elements/ConfirmSlider.qml deleted file mode 100644 index fc88f587d..000000000 --- a/qml/ui/elements/ConfirmSlider.qml +++ /dev/null @@ -1,42 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 - -import Qt.labs.settings 1.0 - - -ConfirmSliderForm { - - property bool checked: false - - property bool timer_reset: true - property var timer_time: 5000 - - property var slider_height:32 - property var slider_width: 180 - - property string text_off:"I'm a BUG" //prevent dev from accidentally forgetting to set it - property string text_on: text_off - - property var msg_id: 0 //the actual msg id sent from the widget - - property var text_color_on: "black" - property var text_color_off: "white" - - property var color_on: "green" - property var color_off: "#02324d" - - property var slider_x: 0 - - Timer { - id: selectionResetTimer - running: false - interval: timer_time - repeat: false - onTriggered: { - checked = false; - slider_x= 0; - } - } -} - diff --git a/qml/ui/elements/ConfirmSliderEnableDisable.qml b/qml/ui/elements/ConfirmSliderEnableDisable.qml deleted file mode 100644 index f867ade51..000000000 --- a/qml/ui/elements/ConfirmSliderEnableDisable.qml +++ /dev/null @@ -1,105 +0,0 @@ -import QtQuick 2.0 - -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 - -import OpenHD 1.0 - -// Slide element "to the right" to enable -// Slide element to the left to disable -// based on @Luke's confirm slider -Rectangle { - id: root - // public - signal clicked(bool checked); // onClicked:{root.checked = checked; - - // - property bool checked: false - - - property int slider_height:32 - property int slider_width: 180 - - property string text_off:"I'm a BUG" // don't forget to override - property string text_on: "I'm a BUG" //don't forget to override - - property color text_color_on: "black" - property color text_color_off: "white" - - property color color_on: "green" - property color color_off: "#02324d" - - property int slider_x: 0 - // - property bool error_last: false - - function on_user_wants_enable(){ - return false; - } - function on_user_wants_disable(){ - return false; - } - - // private - width: slider_width; - height: slider_height; - border.width: 0.05 * slider_height - radius: 0.5 * slider_height - color: checked? color_on : color_off // background - opacity: enabled && !mouseArea.pressed? 1: 0.5 // disabled/pressed state - - Text { - text: checked ? text_on : text_off - color: checked ? text_color_on : text_color_off - x: (checked ? 0: pill.width) + (parent.width - pill.width - width) / 2 - font.pixelSize: 0.5 * slider_height - anchors.verticalCenter: parent.verticalCenter - } - - Rectangle { // the moveable control - id: pill - color: "#15a4ef" - x: checked ? slider_width - slider_height : slider_x - - width: slider_height - height: width // square - border.width: parent.border.width - radius: parent.radius - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - - drag { - target: pill - axis: Drag.XAxis - maximumX: slider_width - slider_height - minimumX: 0 - } - - onReleased: { // releasing at the end of drag - if( checked && pill.x < slider_width - pill.width) { - // user dragged the pill to the left - he wants to disable something - root.clicked(false); - checked=false; - } - else if(!checked && pill.x < slider_width - pill.width - slider_width*.02){ //2 percent slop allowed - // user dragged the pill to the left - he wants to disable something - root.clicked(false); - checked=false; - } - else if(!checked && pill.x > slider_width - pill.width - slider_width*.02){ //2 percent slop allowed - // user dragged the pill to the right - he wants to enable something - root.clicked(true); - checked=true; - } - } - //onClicked: root.clicked(checked) // emit - } -} - - diff --git a/qml/ui/elements/ConfirmSliderForm.ui.qml b/qml/ui/elements/ConfirmSliderForm.ui.qml deleted file mode 100644 index f0f8d6b30..000000000 --- a/qml/ui/elements/ConfirmSliderForm.ui.qml +++ /dev/null @@ -1,80 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 - -import OpenHD 1.0 - -Rectangle { - id: root - // public - signal clicked(bool checked); // onClicked:{root.checked = checked; - - // private - width: slider_width; - height: slider_height; - border.width: 0.05 * slider_height - radius: 0.5 * slider_height - color: checked? color_on : color_off // background - opacity: enabled && !mouseArea.pressed? 1: 0.5 // disabled/pressed state - - Text { - text: checked ? text_on : text_off - color: checked ? text_color_on : text_color_off - x: (checked ? 0: pill.width) + (parent.width - pill.width - width) / 2 - font.pixelSize: 0.5 * slider_height - anchors.verticalCenter: parent.verticalCenter - } - - Rectangle { // the moveable control - id: pill - color: "#15a4ef" - x: checked ? slider_width - slider_height : slider_x - - width: slider_height - height: width // square - border.width: parent.border.width - radius: parent.radius - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - - drag { - target: pill - axis: Drag.XAxis - maximumX: slider_width - slider_height - minimumX: { //handle if this is checked you cant move it - if (checked==true){ - return slider_width - slider_height; - } else { - return 0; - } - - } - } - - onReleased: { // releasing at the end of drag - if( checked && pill.x < slider_width - pill.width) { - root.clicked(false); - checked=false; - } - else if(!checked && pill.x < slider_width - pill.width - slider_width*.02){ //2 percent slop allowed - root.clicked(false); - slider_x=0; - checked=false; - } - else if(!checked && pill.x > slider_width - pill.width - slider_width*.02){ //2 percent slop allowed - root.clicked(true); - checked=true; - if (timer_reset==true){//reset via timer - selectionResetTimer.start(); - } - } - } - //onClicked: root.clicked(checked) // emit - } -} - diff --git a/qml/ui/elements/FlightModeSlider.qml b/qml/ui/elements/FlightModeSlider.qml new file mode 100644 index 000000000..0390276dd --- /dev/null +++ b/qml/ui/elements/FlightModeSlider.qml @@ -0,0 +1,42 @@ +import QtQuick 2.12 + +PillSlider{ + id: slider + // Needs to be overridden + property string flight_mode_text: "TODO" + // If set to true, the flightModeWidget's popup is autmatically closed + // once this command completes. + property bool close_popup_on_success: true + + m_pill_description: flight_mode_text + + visible: false + + property string m_ardupilot_mav_type: _fcMavlinkAction.ardupilot_mav_type + + onM_ardupilot_mav_typeChanged: { + //console.log("onM_ardupilot_mav_typeChanged"); + var valid = _fcMavlinkAction.has_mapping(flight_mode_text); + slider.visible = valid; + } + + Timer { + id: selectionResetTimer + running: false + interval: 1500 + repeat: false + onTriggered: { + slider.reset_to_dragable() + } + } + + onDraggedRight : { + console.log("FM slider onDraggedRight"); + _fcMavlinkAction.flight_mode_cmd_async_string(flight_mode_text) + selectionResetTimer.start() + if(close_popup_on_success){ + flightModeWidget.bw_manually_close_action_popup() + } + } + +} diff --git a/qml/ui/elements/PillSlider.qml b/qml/ui/elements/PillSlider.qml new file mode 100644 index 000000000..1ecf423ba --- /dev/null +++ b/qml/ui/elements/PillSlider.qml @@ -0,0 +1,90 @@ +import QtQuick 2.12 + +// Allows dragging a pill to the right +Rectangle { + id: root + + // Should be overrridden + property string m_pill_description: "TODO" + + property int slider_height:32 + property int slider_width: 180 + + // square but shown as circle to the user + property int pill_w_h: slider_height + + width: slider_width; + height: slider_height; + border.width: 0.05 * slider_height + radius: 0.5 * slider_height + color: checked ? "green" : "#02324d" + + property bool checked : false + + signal draggedRight; + + function call_dragged_right(){ + console.log("call_dragged_right") + root.draggedRight() + } + + function reset_to_dragable(){ + console.log("Reset to be dragable again") + checked = false; + pill.x=0; + } + + Text { + text: m_pill_description + width: parent.width + height: parent.height + font.pixelSize: 0.5 * slider_height + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + //horizontalAlignment: checked ? Text.AlignLeft : Text.AlignRight + color: checked ? "black" : "white" + } + + Rectangle { // the moveable control + id: pill + color: "#15a4ef" + x: 0 + width: pill_w_h + height: pill_w_h // square + border.width: 1 + radius: pill.width * 0.5 + //visible: !checked + } + + MouseArea { + id: mouseArea + + anchors.fill: parent + + drag { + target: pill + axis: Drag.XAxis + maximumX: slider_width - slider_height + minimumX: { //handle if this is checked you cant move it + if (checked==true){ + return slider_width - slider_height; + } + return 0; + } + } + + onReleased: { // releasing at the end of drag + // check if the user dragged it further than the minimum required for a valid "change" input + if(pill.x > slider_width - pill.width - slider_width*0.05){ //X percent slop allowed 0.02 + checked=true + pill.x = (slider_width - pill.width); // moved all the way to the right + call_dragged_right(); + }else{ + console.log("Not dragged enough"); + pill.x=0; // moved all the way to the left + checked=false; + } + } + //onClicked: root.clicked(checked) // emit + } +} diff --git a/qml/ui/elements/RestartDialog.qml b/qml/ui/elements/RestartDialog.qml deleted file mode 100644 index 9b7156137..000000000 --- a/qml/ui/elements/RestartDialog.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Controls.Material 2.12 -import QtQuick.Layouts 1.0 -import QtGraphicalEffects 1.12 - -Card { - id: restartDialog - cardNameColor: "black" - hasHeaderImage: true - visible: false - - property bool stateVisible: visible - - states: [ - State { - when: restartDialog.stateVisible; - PropertyChanges { - target: restartDialog - opacity: 1.0 - } - }, - State { - when: !restartDialog.stateVisible; - PropertyChanges { - target: restartDialog - opacity: 0.0 - } - } - ] - transitions: [ Transition { NumberAnimation { property: "opacity"; duration: 250}} ] - - cardName: qsTr("Restart required") - cardBody: Column { - height: restartDialog.height - width: restartDialog.width - - Text { - text: qsTr("You must restart the app for your loaded configuration file to take effect") - width: parent.width - leftPadding: 12 - rightPadding: 12 - wrapMode: Text.WordWrap - } - } - - hasFooter: true - cardFooter: Item { - anchors.fill: parent - Button { - width: 96 - height: 48 - text: qsTr("Cancel") - anchors.left: parent.left - anchors.leftMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - - onPressed: { - restartDialog.visible = false - } - } - - Button { - width: 140 - height: 48 - anchors.right: parent.right - anchors.rightMargin: 12 - anchors.bottom: parent.bottom - anchors.bottomMargin: 6 - font.pixelSize: 14 - font.capitalization: Font.MixedCase - Material.accent: Material.Red - highlighted: true - - text: { - /*if (IsAndroid) { - return qsTr("Restart App") - }*/ - return qsTr("Close App") - } - - - onPressed: { - /*if (IsAndroid) { - ManageSettings.restartApp(); - return; - }*/ - Qt.quit(); - } - } - } -} diff --git a/qml/ui/elements/SimpleProgressBar.qml b/qml/ui/elements/SimpleProgressBar.qml new file mode 100644 index 000000000..0f2fdaa46 --- /dev/null +++ b/qml/ui/elements/SimpleProgressBar.qml @@ -0,0 +1,33 @@ +import QtQuick 2.12 + +Item { + + // Intended to be overridden + // Progress in [0..100] + property double impl_curr_progress_perc: 0 + // Color of the bar + property color impl_curr_color: "blue" + + Rectangle{ + id: progress_background + color: impl_curr_color + opacity: 0.5 + width: parent.width + height: parent.height + + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + } + + Rectangle{ + id: progress_bar + opacity: 1.0 + width: progress_background.width * (impl_curr_progress_perc / 100.0) + height: parent.height + anchors.left: parent.left + + color: impl_curr_color + + } +} diff --git a/qml/ui/elements/WorkaroundMessageBox.qml b/qml/ui/elements/WorkaroundMessageBox.qml index 6bf0e3e14..191fcf253 100644 --- a/qml/ui/elements/WorkaroundMessageBox.qml +++ b/qml/ui/elements/WorkaroundMessageBox.qml @@ -51,6 +51,7 @@ Card { text: qsTr("Okay") onPressed: { _messageBoxInstance.okay_button_clicked() + hudOverlayGrid.regain_focus() } // In case the message is removed automatically after 1 or less seconds, we don't show it visible: (_messageBoxInstance.remaining_time_seconds==-1) || (_messageBoxInstance.remaining_time_seconds>1) diff --git a/qml/ui/elements/colorwheel/ColorWheel.qml b/qml/ui/elements/colorwheel/ColorWheel.qml index 3f092f146..1c1dcea09 100644 --- a/qml/ui/elements/colorwheel/ColorWheel.qml +++ b/qml/ui/elements/colorwheel/ColorWheel.qml @@ -12,7 +12,7 @@ import "ColorUtils.js" as ColorUtils Item { id: root - focus: true + //focus: true // Color value in RGBA with floating point values between 0.0 and 1.0. diff --git a/qml/ui/elements/colorwheel/NumberBox.qml b/qml/ui/elements/colorwheel/NumberBox.qml index ace7140d8..d7a546913 100644 --- a/qml/ui/elements/colorwheel/NumberBox.qml +++ b/qml/ui/elements/colorwheel/NumberBox.qml @@ -57,7 +57,7 @@ Item { color: "#AAAAAA"; selectionColor: "#FF7777AA" font.pixelSize: 14 - focus: true + //focus: true onEditingFinished: { var newText = ColorUtils.clamp(parseFloat(inputBox.text), root.min, root.max).toString() if (newText != root.value) { diff --git a/qml/ui/widgets/AdsbWidget.qml b/qml/ui/widgets/AdsbWidget.qml deleted file mode 100644 index e39ae10f0..000000000 --- a/qml/ui/widgets/AdsbWidget.qml +++ /dev/null @@ -1,207 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 -import QtGraphicalEffects 1.12 -import Qt.labs.settings 1.0 -import QtQuick.Extras 1.4 - -import QtQml.Models 2.15 -import QtPositioning 5.2 -import QtLocation 5.12 - -import OpenHD 1.0 - -BaseWidget { - id: adsbWidget - width: 75 - height: 25 - - z: 4 - - visible: settings.show_adsb - - widgetIdentifier: "adsb_widget" - bw_verbose_name: "ADSB (REQUIRES INTERNET)" - - - defaultAlignment: 1 - // Placed below the SOC air / ground statistics by default - defaultXOffset: 128 - defaultYOffset: 50 - defaultHCenter: false - defaultVCenter: false - - hasWidgetDetail: true - hasWidgetAction: false - - property double lastData: 0 - - // Property status from adsbVehicleManager can be - // 0 - not active ( if more than 60 seconds since last update ) - // 1 - active but more than 20 seconds since last update - // 2 - active, less than 20 seconds since last update - property bool adsbStatus: AdsbVehicleManager.status ? true : false - property color adsbStatusColor: AdsbVehicleManager.status == 2 ? "green" : "red" - - widgetDetailComponent: ScrollView { - - contentHeight: idBaseWidgetDefaultUiControlElements.height - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - clip: true - - BaseWidgetDefaultUiControlElements{ - id: idBaseWidgetDefaultUiControlElements - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Source SDR") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.adsb_api_sdr - onCheckedChanged: { - settings.adsb_api_sdr = checked - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Source OpenSky") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.adsb_api_openskynetwork - onCheckedChanged: { - settings.adsb_api_openskynetwork = checked - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Max") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Text { - id: adsb_distance_text - text: settings.adsb_distance_limit/1000+"Km" - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.right: parent.right - verticalAlignment: Text.AlignVCenter - } - Slider { - id: adsb_distance_Slider - orientation: Qt.Horizontal - from: 5000 - value: settings.adsb_distance_limit - to: { - if (LimitADSBMax){ - return 20000; //this is to limit pi... 200km for testing - } - else{ - return 50000; - } - } - - stepSize: 1000 - height: parent.height - anchors.rightMargin: 0 - anchors.right: adsb_distance_text.left - width: parent.width - 96 - - onValueChanged: { - settings.adsb_distance_limit = adsb_distance_Slider.value - } - } - } - Item { - width: parent.width - height: 32 - Text { - text: qsTr("Uknown/Zero Alt Traffic") - color: "white" - height: parent.height - font.bold: true - font.pixelSize: detailPanelFontPixels - anchors.left: parent.left - verticalAlignment: Text.AlignVCenter - } - Switch { - width: 32 - height: parent.height - anchors.rightMargin: 6 - anchors.right: parent.right - checked: settings.adsb_show_unknown_or_zero_alt - onCheckedChanged: { - settings.adsb_show_unknown_or_zero_alt = checked - } - } - } - } - } - - Item { - id: widgetInner - visible: settings.show_adsb - anchors.fill: parent - scale: bw_current_scale - - Text { - id: adsb_text - color: settings.color_shape - opacity: bw_current_opacity - text: "ADS-B" - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignLeft - font.pixelSize: 14 - verticalAlignment: Text.AlignTop - wrapMode: Text.NoWrap - elide: Text.ElideRight - style: Text.Outline - styleColor: settings.color_glow - } - - StatusIndicator { - id: adsb_status - width: 15 - height: 15 - anchors.left: adsb_text.right - anchors.leftMargin: 5 - anchors.verticalCenter: parent.verticalCenter - color: adsbStatusColor - active: adsbStatus - visible: !settings.adsb_api_sdr - } - } -} diff --git a/qml/ui/widgets/AltitudeWidget.qml b/qml/ui/widgets/AltitudeWidget.qml index e66bccda2..69296c241 100644 --- a/qml/ui/widgets/AltitudeWidget.qml +++ b/qml/ui/widgets/AltitudeWidget.qml @@ -28,6 +28,7 @@ BaseWidget { hasWidgetDetail: true widgetDetailHeight: 250+100 + m_show_grid_when_dragging: true function get_altitude_value(){ var altitude_m_or_foot = settings.altitude_ladder_use_msl ? _fcMavlinkSystem.altitude_msl_m : _fcMavlinkSystem.altitude_rel_m; diff --git a/qml/ui/widgets/BaseWidget.qml b/qml/ui/widgets/BaseWidget.qml index edc4b841f..24f52cbad 100644 --- a/qml/ui/widgets/BaseWidget.qml +++ b/qml/ui/widgets/BaseWidget.qml @@ -68,6 +68,19 @@ BaseWidgetForm { property string bw_opacity_identifier: "%1_opacity".arg(widgetIdentifier); // Default opacity is 1, the value is persistent property double bw_current_opacity : settings.value(bw_opacity_identifier,1.0); + // Feature persist opacity end + // Feature: Show grid when dragging + property bool m_show_grid_when_dragging: false + onDraggingChanged: { + if(dragging && m_show_grid_when_dragging){ + hudOverlayGrid.m_show_horizontal_center_indicator=true; + hudOverlayGrid.m_show_vertical_center_indicator=true + }else{ + hudOverlayGrid.m_show_horizontal_center_indicator=false; + hudOverlayGrid.m_show_vertical_center_indicator=false; + } + } + // Updates the current base widget scale (unique per widgetIdentifier) and persist the value for later use function bw_set_current_opacity(opacity){ if(opacity <=0 || opacity>1){ @@ -85,6 +98,56 @@ BaseWidgetForm { // Needed for the HUD log messages element, since it sits above the secondary video and disabling its "mouse area" // Was the easiest fix property bool disable_dragging: false + // --------------------------------------------------------------------- + // Custom keyboard KeyNavigation + // Must be also of type BaseWidget + property var m_next_item: + // DIRTY + function dirty_open_action_popup(){ + widgetAction.open() + } + function dirty_close_action_popup(){ + widgetAction.close() + } + + function dirty_open_close_action_popup(){ + if(widgetAction.opened){ + widgetAction.close() + }else{ + widgetAction.open() + } + } + + + // Can be called by the imp. to manually close the action popup + function bw_manually_close_action_popup(){ + widgetAction.close() + } + + // react to the user opening the (currently focused) widget + Keys.onPressed: (event)=> { + console.log("BaseWidget "+widgetIdentifier+" Key was pressed:"+event); + if (event.key == Qt.Key_Return) { + console.log("enter was pressed"); + event.accepted = true; + dirty_open_action_popup() + }else if(event.key == Qt.Key_Left){ + console.log("left was pressed") + event.accepted=true; + // TODO: Go to the next item + event.accepted=true; + }else if(event.key == Qt.Key_Right){ + console.log("right was pressed") + event.accepted=true; + // TODO: Go to the next item + } + } + function set_focus_next_item(){ + if(m_next_item==undefined){ + console.log("Next item undefined") + return; + } + } //layer.enabled: false diff --git a/qml/ui/widgets/BaseWidgetForm.ui.qml b/qml/ui/widgets/BaseWidgetForm.qml similarity index 98% rename from qml/ui/widgets/BaseWidgetForm.ui.qml rename to qml/ui/widgets/BaseWidgetForm.qml index a70036749..ec8155bd8 100644 --- a/qml/ui/widgets/BaseWidgetForm.ui.qml +++ b/qml/ui/widgets/BaseWidgetForm.qml @@ -13,8 +13,8 @@ import OpenHD 1.0 Rectangle { color: "#00000000" - border.width: dragging ? 1 : 0 - border.color: dragging ? "white" : "#00000000" + border.width: m_special_highlight ? 3 : (dragging ? 1 : 0) + border.color: m_special_highlight ? "green" : (dragging ? "white" : "#00000000") //property alias widgetControls: widgetControls property alias widgetDetail: widgetDetail @@ -38,6 +38,8 @@ Rectangle { property Popup widgetPopup: Popup {} property bool hasWidgetPopup: false + // Highlight in a special way if this widget has focus + property bool m_special_highlight: activeFocus Behavior on width { NumberAnimation { duration: 200 } diff --git a/qml/ui/widgets/FlightModeWidget.qml b/qml/ui/widgets/FlightModeWidget.qml index f00358b31..9ec8fd080 100644 --- a/qml/ui/widgets/FlightModeWidget.qml +++ b/qml/ui/widgets/FlightModeWidget.qml @@ -28,11 +28,9 @@ BaseWidget { // Needs to be a lot bigger than default: widgetActionHeight: 164+ 450 - // The commands are a bit different depending on if the user is using arducopter or arduplane / ardu-vtol. - // QUITE ANNOYING FUCK !!!! - property bool m_is_arducopter : _fcMavlinkSystem.is_arducopter - property bool m_is_arduplane:_fcMavlinkSystem.is_arduplane - property bool m_is_arduvtol: _fcMavlinkSystem.is_arduvtol + function close_action_popup(){ + flightModeWidget.bw_manually_close_action_popup() + } widgetDetailComponent: ScrollView { @@ -74,348 +72,97 @@ BaseWidget { widgetActionComponent: ScrollView { ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.vertical.interactive: true clip: true contentHeight: flightModeCommandsColumn.height - /* - PLANE_MODE_MANUAL=0, - PLANE_MODE_CIRCLE=1, - PLANE_MODE_STABILIZE=2, - PLANE_MODE_TRAINING=3, - PLANE_MODE_ACRO=4, - PLANE_MODE_FLY_BY_WIRE_A=5, - PLANE_MODE_FLY_BY_WIRE_B=6, - PLANE_MODE_CRUISE=7, - PLANE_MODE_AUTOTUNE=8, - PLANE_MODE_AUTO=10, - PLANE_MODE_RTL=11, - PLANE_MODE_LOITER=12, - PLANE_MODE_TAKEOFF=13 - -VTOL -00017 17 : 'QSTABILIZE', -00018 18 : 'QHOVER', -00019 19 : 'QLOITER', -00020 20 : 'QLAND', -00021 21 : 'QRTL', - - COPTER_MODE_STABILIZE=0 - COPTER_MODE_ACRO=1 - COPTER_MODE_ALT_HOLD=2 - COPTER_MODE_AUTO=3 - COPTER_MODE_GUIDED=4 - COPTER_MODE_LOITER=5 - COPTER_MODE_RTL=6 - COPTER_MODE_CIRCLE=7 - COPTER_MODE_LAND=9 - COPTER_MODE_DRIFT=11 - COPTER_MODE_SPORT=13 - COPTER_MODE_FLIP=14 - COPTER_MODE_AUTOTUNE=15 - COPTER_MODE_POSHOLD=16 - COPTER_MODE_BRAKE=17 - COPTER_MODE_THROW=18 - COPTER_MODE_AVOID_ADSB=19 - COPTER_MODE_GUIDED_NOGPS=20 - COPTER_MODE_SMART_RTL=21 -*/ Column { id: flightModeCommandsColumn width: 200 + height: 900 spacing: 2 - height: 600 Text { - height: 32 - text: { - return qsTr("Only For Ardupilot"); - } + text: qsTr("Only For Ardupilot"); color: "white" font.bold: true font.pixelSize: detailPanelFontPixels - anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter } - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("RTL") - msg_id: { - if (m_is_arducopter){ - return 6; - } - if (m_is_arduplane || "VTOL"){ - return 11; - } - return -1; - } - onCheckedChanged: { - if (checked == true) { - //_fcMavlinkSystem.set_Requested_Flight_Mode(msg_id); - _fcMavlinkSystem.flight_mode_cmd(msg_id); - console.log("FLIGHT MODE MSD ID="); - } - } + FlightModeSlider{ + flight_mode_text: "RTL" } - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("STABILIZE") - msg_id: { - if (m_is_arducopter){ - return 0; - } - if (m_is_arduplane || "VTOL"){ - return 2; - } - return -1; - } - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "STABILIZE" } - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("LOITER") - msg_id: { - if (m_is_arducopter){ - return 5; - } - if (m_is_arduplane || "VTOL"){ - return 12; - } - return -1; - } - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "LOITER" } - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("CIRCLE") - msg_id: { - if (m_is_arducopter){ - return 7; - } - if (m_is_arduplane || m_is_arduvtol){ - return 1; - } - return -1; - } - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "CIRCLE" } - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("AUTO") - msg_id: { - if (m_is_arducopter){ - return 3; - } - if (m_is_arduplane || m_is_arduvtol){ - return 10; - } - } - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "AUTO" } - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("AUTOTUNE") - msg_id: { - if (m_is_arducopter){ - return 15; - } - if (m_is_arduplane || m_is_arduvtol){ - return 8; - } - } - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "AUTOTUNE" } - - //-----------------------DIFFERNT from plane to copter to vtol - - ConfirmSlider { - visible: { - if (m_is_arduplane || m_is_arduvtol){ - return true; - } else { - return false; - } - } - text_off: qsTr("MANUAL") - msg_id: 0 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "MANUAL" } - - ConfirmSlider { - visible: { - if (m_is_arduplane || m_is_arduvtol){ - return true; - } else { - return false; - } - } - text_off: qsTr("FBWA") - msg_id: 5 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "FBWA" } - - ConfirmSlider { - visible: { - if (m_is_arduplane || m_is_arduvtol){ - return true; - } else { - return false; - } - } - - text_off: qsTr("FBWB") - msg_id: 6 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "FBWB" } - - ConfirmSlider { - visible: m_is_arduvtol - - text_off: qsTr("QSTABILIZE") - msg_id: 17 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QSTABILIZE" } - - ConfirmSlider { - visible: m_is_arduvtol - - text_off: qsTr("QHOVER") - msg_id: 18 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QHOVER" } - - ConfirmSlider { - visible: m_is_arduvtol - - text_off: qsTr("QLOITER") - msg_id: 19 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QLOITER" } - - ConfirmSlider { - visible: m_is_arduvtol - - text_off: qsTr("QLAND") - msg_id: 20 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QLAND" } - - ConfirmSlider { - visible: m_is_arduvtol - - text_off: qsTr("QRTL") - msg_id: 21 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QRTL" } - - ConfirmSlider { - visible: m_is_arducopter - - text_off: qsTr("ALT_HOLD") - msg_id: 2 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "ALT_HOLD" } - ConfirmSlider { - visible: m_is_arducopter - - text_off: qsTr("POSHOLD") - msg_id: 16 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "POSHOLD" } - ConfirmSlider { - visible: m_is_arducopter - - text_off: qsTr("ACRO") - msg_id: 1 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "ACRO" + } + FlightModeSlider{ + flight_mode_text: "GUIDED" + } + FlightModeSlider{ + flight_mode_text: "LAND" + } + FlightModeSlider{ + flight_mode_text: "SMART_RTL" + } + FlightModeSlider{ + flight_mode_text: "ZIGZAG" + } + FlightModeSlider{ + flight_mode_text: "AUTO_RTL" + } + FlightModeSlider{ + flight_mode_text: "CRUISE" + } + FlightModeSlider{ + flight_mode_text: "TAKEOFF" } } } diff --git a/qml/ui/widgets/HomeDistanceWidget.qml b/qml/ui/widgets/HomeDistanceWidget.qml index 9eee0fda3..f6c05b6a9 100644 --- a/qml/ui/widgets/HomeDistanceWidget.qml +++ b/qml/ui/widgets/HomeDistanceWidget.qml @@ -32,12 +32,6 @@ BaseWidget { widgetActionWidth: 256 widgetActionHeight: 164+30 - // The commands are a bit different depending on if the user is using arducopter or arduplane / ardu-vtol. - // QUITE ANNOYING FUCK !!!! - property bool m_is_arducopter : _fcMavlinkSystem.is_arducopter - property bool m_is_arduplane:_fcMavlinkSystem.is_arduplane - property bool m_is_arduvtol: _fcMavlinkSystem.is_arduvtol - // Hides the stuff before decimal, aka returns // X.1234... function hide_before_decimals(number){ @@ -141,7 +135,6 @@ BaseWidget { verticalAlignment: Text.AlignVCenter } } - /*Item { height: 32 Text { @@ -152,39 +145,15 @@ BaseWidget { anchors.left: parent.left } }*/ - - ConfirmSlider { - visible: _fcMavlinkSystem.supports_basic_commands - - text_off: qsTr("RTL") - msg_id: { - if (m_is_arducopter){ - return 6; - } - if(m_is_arduplane || m_is_arduvtol){ - return 11; - } - return -1; - } - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "RTL" + close_popup_on_success: false } - - ConfirmSlider { - visible: _fcMavlinkSystem.mav_type == "VTOL" - - text_off: qsTr("QRTL") - msg_id: 21 - - onCheckedChanged: { - if (checked == true) { - _fcMavlinkSystem.flight_mode_cmd(msg_id); - } - } + FlightModeSlider{ + flight_mode_text: "QRTL" + close_popup_on_success: false } + RowLayout{ width: parent.width height: 32 @@ -193,16 +162,7 @@ BaseWidget { id: requestHomeButton text: "Request Home" onClicked: { - _fcMavlinkSystem.request_home_position_from_fc() - } - } - Button{ - height:32 - id: overwriteHome - text: "Overwrite" - visible: false - onClicked: { - _fcMavlinkSystem.overwrite_home_to_current() + _fcMavlinkAction.request_home_position_from_fc(); } } } diff --git a/qml/ui/widgets/HorizonWidget.qml b/qml/ui/widgets/HorizonWidget.qml index b68e42274..8cad30524 100644 --- a/qml/ui/widgets/HorizonWidget.qml +++ b/qml/ui/widgets/HorizonWidget.qml @@ -22,6 +22,8 @@ BaseWidget { hasWidgetDetail: true widgetDetailHeight: 250+250 + m_show_grid_when_dragging : true + widgetDetailComponent: ScrollView { contentHeight: idBaseWidgetDefaultUiControlElements.height diff --git a/qml/ui/widgets/LinkDownRSSIWidget.qml b/qml/ui/widgets/LinkDownRSSIWidget.qml index 450637f32..9f99efbce 100644 --- a/qml/ui/widgets/LinkDownRSSIWidget.qml +++ b/qml/ui/widgets/LinkDownRSSIWidget.qml @@ -44,6 +44,14 @@ BaseWidget { return 0; } + function int_to_string_N_chars_wide(value,n_chars){ + var ret=""+value; + for(var i=ret.length;i-127; + if(show_2_antenna_dbm_values){ + ret+=(card.curr_rx_rssi_dbm_antenna1+"/"+dbm_antenna2+" dBm"); + }else{ + ret += card.curr_rx_rssi_dbm_antenna1 + " dBm"; + }*/ if(card.is_active_tx){ ret +=" TX" } @@ -81,6 +103,9 @@ BaseWidget { return settings.color_text; } + function get_tx_error_text(){ + return "TX hint/dropped: "+_ohdSystemAir.count_tx_inj_error_hint+" "+_ohdSystemAir.count_tx_dropped_packets + } //----------------------------- DETAIL BELOW ---------------------------------- @@ -97,7 +122,7 @@ BaseWidget { width: parent.width height: 32 Text { - text: qsTr("Show dBm per card") + text: qsTr("Show stats per card") color: "white" height: parent.height font.bold: true @@ -114,6 +139,27 @@ BaseWidget { onCheckedChanged: settings.downlink_show_dbm_and_packets_per_card = checked } } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("dBm of each ant/card") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels; + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.downlink_dbm_per_card_show_multiple_antennas + onCheckedChanged: settings.downlink_dbm_per_card_show_multiple_antennas = checked + } + } Item { width: parent.width @@ -196,7 +242,28 @@ BaseWidget { width: parent.width height: 32 Text { - text: qsTr("EXP-signal quality %") + text: qsTr("Show pollution estimate %") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.downlink_pollution_show + onCheckedChanged: settings.downlink_pollution_show = checked + } + } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Show signal quality %") color: "white" height: parent.height font.bold: true @@ -226,7 +293,7 @@ BaseWidget { width:200 Text { //Layout.alignment: left - text: "TX error/dropped: "+_ohdSystemAir.count_tx_inj_error_hint+" "+_ohdSystemAir.count_tx_dropped_packets + text: get_tx_error_text(); color: "white" font.bold: true height: parent.height @@ -401,6 +468,19 @@ BaseWidget { style: Text.Outline styleColor: settings.color_glow } + Text { + visible: settings.downlink_pollution_show + text: settings.downlink_pollution_show? ("Pollution: "+_ohdSystemGround.wb_link_pollution+ "%") : "" + color: settings.color_text + verticalAlignment: Text.AlignVCenter + font.pixelSize: 12 + font.family: settings.font_text + horizontalAlignment: Text.AlignLeft + wrapMode: Text.NoWrap + elide: Text.ElideRight + style: Text.Outline + styleColor: settings.color_glow + } Text { visible: settings.downlink_signal_quality_show text: settings.downlink_signal_quality_show ? ("Quality: "+_ohdSystemGround.current_rx_signal_quality+ "%") : "" diff --git a/qml/ui/widgets/LinkUpRSSIWidget.qml b/qml/ui/widgets/LinkUpRSSIWidget.qml index 6bfff8a1a..e38c618df 100644 --- a/qml/ui/widgets/LinkUpRSSIWidget.qml +++ b/qml/ui/widgets/LinkUpRSSIWidget.qml @@ -27,8 +27,22 @@ BaseWidget { widgetActionHeight: 164+50 - // If openhd feature passive mode is enabled, show watermark instead - property bool m_passive_mode: _ohdSystemGround.tx_passive_mode + + function get_text_loss(){ + if(_ohdSystemGround.tx_operating_mode==1){ + return "TX UNSUPPORTED"; + } + if(_ohdSystemGround.tx_operating_mode==2){ + return "UPLINK DISABLED"; + } + if(_ohdSystemAir.is_alive){ + if(_ohdSystemAir.curr_rx_last_packet_status_good){ + return ("Loss: " + _ohdSystemAir.curr_rx_packet_loss_perc+"%") + } + return "NO UPLINK"; + } + return "N/A"; + } function get_text_dbm(){ var dbm=_ohdSystemAir.current_rx_rssi; @@ -202,7 +216,7 @@ BaseWidget { spacing:0 Text { visible: true - text: m_passive_mode ? "LISTEN ONLY" : ("Loss: " + _ohdSystemAir.curr_rx_packet_loss_perc+"%") + text: get_text_loss() color: settings.color_text verticalAlignment: Text.AlignVCenter font.pixelSize: 12 @@ -213,7 +227,8 @@ BaseWidget { style: Text.Outline styleColor: settings.color_glow } - Text { + // Quality and pollution on the uplink are not usable due to not enough packets in most cases + /*Text { visible: settings.downlink_signal_quality_show text: settings.downlink_signal_quality_show ? ("Quality: "+_ohdSystemAir.current_rx_signal_quality+ "%") : "" color: settings.color_text @@ -225,7 +240,20 @@ BaseWidget { elide: Text.ElideRight style: Text.Outline styleColor: settings.color_glow - } + }*/ + /*Text { + visible: settings.downlink_pollution_show + text: settings.downlink_pollution_show ? ("Pollution: "+_ohdSystemAir.wb_link_pollution+ "%") : "" + color: settings.color_text + verticalAlignment: Text.AlignVCenter + font.pixelSize: 12 + font.family: settings.font_text + horizontalAlignment: Text.AlignLeft + wrapMode: Text.NoWrap + elide: Text.ElideRight + style: Text.Outline + styleColor: settings.color_glow + }*/ } } } diff --git a/qml/ui/widgets/MissionWidget.qml b/qml/ui/widgets/MissionWidget.qml index ee3c77d52..19f6bfafc 100644 --- a/qml/ui/widgets/MissionWidget.qml +++ b/qml/ui/widgets/MissionWidget.qml @@ -64,34 +64,6 @@ BaseWidget { } } - ConfirmSliderEnableDisable { - id: confirmSliderEnableUpdates - visible: _fcMavlinkSystem.supports_basic_commands - text_off: qsTr("Enable updates") - text_on: qsTr("Disable updates"); - - onCheckedChanged: { - if(error_last){ - error_last=false; - return; - } - var res=false - if (checked == true) { - res=_fcMavlinkSystem.enable_disable_mission_updates(true); - if(res===false){ - error_last=true; - checked = !checked; - } - }else{ - res=_fcMavlinkSystem.enable_disable_mission_updates(false); - if(res===false){ - error_last=true; - checked = !checked; - } - } - } - } - Item { width: parent.width height: 20 @@ -105,7 +77,7 @@ BaseWidget { verticalAlignment: Text.AlignVCenter } Text { - text: qsTr(_fcMavlinkSystem.mission_waypoints_current+"/"+_fcMavlinkSystem.mission_waypoints_current_total) + text: qsTr(_fcMavlinkMissionHandler.mission_waypoints_current+"/"+_fcMavlinkMissionHandler.mission_waypoints_current_total) color: "white" font.bold: true height: parent.height @@ -127,7 +99,7 @@ BaseWidget { verticalAlignment: Text.AlignVCenter } Text { - text: qsTr(_fcMavlinkSystem.mission_current_type) + text: qsTr(_fcMavlinkMissionHandler.mission_current_type) color: "white" font.bold: true height: parent.height @@ -136,6 +108,38 @@ BaseWidget { verticalAlignment: Text.AlignVCenter } } + Item { + width: parent.width + height: 20 + Text { + text: qsTr("Sync status:") + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: qsTr(_fcMavlinkMissionHandler.current_status) + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + anchors.right: parent.right + verticalAlignment: Text.AlignVCenter + } + } + Item { + width: parent.width + height: 20 + Button{ + text: "RESYNC"; + onClicked: { + _fcMavlinkMissionHandler.resync() + } + } + } } } @@ -156,7 +160,7 @@ BaseWidget { height: 14 color: settings.color_text text: qsTr( - "Mission") + ": " + qsTr(_fcMavlinkSystem.mission_waypoints_current+"/"+_fcMavlinkSystem.mission_waypoints_current_total) + "Mission") + ": " + qsTr(_fcMavlinkMissionHandler.mission_waypoints_current+"/"+_fcMavlinkMissionHandler.mission_waypoints_current_total) anchors.bottom: parent.bottom anchors.bottomMargin: 0 verticalAlignment: Text.AlignBottom diff --git a/qml/ui/widgets/PerformanceHorizonWidget.qml b/qml/ui/widgets/PerformanceHorizonWidget.qml index cfac1f84e..e36f74326 100644 --- a/qml/ui/widgets/PerformanceHorizonWidget.qml +++ b/qml/ui/widgets/PerformanceHorizonWidget.qml @@ -20,6 +20,9 @@ BaseWidget { defaultVCenter: true hasWidgetDetail: true + + m_show_grid_when_dragging : true + widgetDetailComponent: ScrollView { contentHeight: idBaseWidgetDefaultUiControlElements.height diff --git a/qml/ui/widgets/RecordVideoWidget.qml b/qml/ui/widgets/RecordVideoWidget.qml index 6c18cbb1e..c8d1f3147 100644 --- a/qml/ui/widgets/RecordVideoWidget.qml +++ b/qml/ui/widgets/RecordVideoWidget.qml @@ -74,6 +74,10 @@ BaseWidget { camModel=_airCameraSettingsModel2; camString="CAM2" } + if(!_ohdSystemAir.is_alive){ + _hudLogMessagesModel.signalAddLogMessage(6,"Air unit not alive, cannot set recording for "+camString) + return; + } if(mode===0){ //mode off var result=camModel.try_update_parameter_int("V_AIR_RECORDING",0)==="" if(result){ diff --git a/qml/ui/widgets/SOCStatusWidgetAir.qml b/qml/ui/widgets/SOCStatusWidgetAir.qml index 211145ffe..b206a4ae6 100644 --- a/qml/ui/widgets/SOCStatusWidgetAir.qml +++ b/qml/ui/widgets/SOCStatusWidgetAir.qml @@ -41,6 +41,7 @@ BaseWidget { property int m_curr_core_freq_mhz : _ohdSystemAir.curr_core_freq_mhz property int m_curr_v3d_freq_mhz : _ohdSystemAir.curr_v3d_freq_mhz property int m_ram_usage_perc : _ohdSystemAir.ram_usage_perc + property bool m_rpi_undervolt_error: _ohdSystemAir.rpi_undervolt_error // 0 - no warning // 1 - caution @@ -105,6 +106,27 @@ BaseWidget { onCheckedChanged: settings.air_status_declutter = checked } } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Show undervolt icon") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.air_status_show_undervolt_icon + onCheckedChanged: settings.air_status_show_undervolt_icon = checked + } + } Item { width: parent.width height: 32 @@ -326,6 +348,15 @@ BaseWidget { font.pixelSize: detailPanelFontPixels verticalAlignment: Text.AlignVCenter } + Text { + //Layout.alignment: left + text: "RPI undervolt: "+(m_rpi_undervolt_error ? "Y" : "N") + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } } } @@ -336,7 +367,7 @@ BaseWidget { scale: bw_current_scale Text { - id: chip_icon_air + id: chip_icon y: 0 width: 24 height: 24 @@ -358,6 +389,29 @@ BaseWidget { style: Text.Outline styleColor: settings.color_glow } + Text{ + id: undervolt_error + width: 24 + height: 24 + opacity: bw_current_opacity + //text: "X" + text: String.fromCodePoint(0xf0e7)+"!" + anchors.right: chip_icon.left + anchors.top: chip_icon.top + anchors.rightMargin: 0 + anchors.topMargin: 2 + anchors.verticalCenter: parent.verticalCenter + font.family: "Font Awesome 5 Free" + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: "red" + color: "yellow" + //visible: true + visible: m_rpi_undervolt_error && settings.air_status_show_undervolt_icon + } Text { id: cpuload_air diff --git a/qml/ui/widgets/SOCStatusWidgetGround.qml b/qml/ui/widgets/SOCStatusWidgetGround.qml index 64898e001..a92a83627 100644 --- a/qml/ui/widgets/SOCStatusWidgetGround.qml +++ b/qml/ui/widgets/SOCStatusWidgetGround.qml @@ -41,6 +41,7 @@ BaseWidget { property int m_curr_core_freq_mhz : _ohdSystemGround.curr_core_freq_mhz property int m_curr_v3d_freq_mhz : _ohdSystemGround.curr_v3d_freq_mhz property int m_ram_usage_perc : _ohdSystemGround.ram_usage_perc + property bool m_rpi_undervolt_error: _ohdSystemGround.rpi_undervolt_error // 0 - no warning // 1 - caution @@ -105,6 +106,27 @@ BaseWidget { onCheckedChanged: settings.ground_status_declutter = checked } } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("Show undervolt icon") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Switch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.ground_status_show_undervolt_icon + onCheckedChanged: settings.ground_status_show_undervolt_icon = checked + } + } Item { width: parent.width height: 32 @@ -326,6 +348,15 @@ BaseWidget { font.pixelSize: detailPanelFontPixels verticalAlignment: Text.AlignVCenter } + Text { + //Layout.alignment: left + text: "RPI undervolt: "+(m_rpi_undervolt_error ? "Y" : "N") + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + verticalAlignment: Text.AlignVCenter + } } } @@ -336,7 +367,7 @@ BaseWidget { scale: bw_current_scale Text { - id: chip_icon_gnd + id: chip_icon y: 0 width: 24 height: 24 @@ -359,6 +390,29 @@ BaseWidget { styleColor: settings.color_glow } + Text{ + id: undervolt_error + width: 24 + height: 24 + opacity: bw_current_opacity + //text: "X" + text: String.fromCodePoint(0xf0e7)+"!" + anchors.right: chip_icon.left + anchors.top: chip_icon.top + anchors.rightMargin: 0 + anchors.topMargin: 2 + anchors.verticalCenter: parent.verticalCenter + font.family: "Font Awesome 5 Free" + verticalAlignment: Text.AlignVCenter + font.pixelSize: 14 + horizontalAlignment: Text.AlignRight + elide: Text.ElideRight + style: Text.Outline + styleColor: "red" + color: "yellow" + //visible: true + visible: m_rpi_undervolt_error && settings.ground_status_show_undervolt_icon + } Text { id: cpuload_gnd x: 0 diff --git a/qml/ui/widgets/SpeedWidget.qml b/qml/ui/widgets/SpeedWidget.qml index 77815b9f1..f47601112 100644 --- a/qml/ui/widgets/SpeedWidget.qml +++ b/qml/ui/widgets/SpeedWidget.qml @@ -26,6 +26,7 @@ BaseWidget { hasWidgetDetail: true widgetDetailHeight: 250+150 + m_show_grid_when_dragging: true function get_speed_number(){ var speed_m_per_second=settings.speed_ladder_use_groundspeed ? _fcMavlinkSystem.ground_speed_meter_per_second : _fcMavlinkSystem.air_speed_meter_per_second; diff --git a/qml/ui/widgets/ThrottleWidget.qml b/qml/ui/widgets/ThrottleWidget.qml index 128e86293..fbb719159 100644 --- a/qml/ui/widgets/ThrottleWidget.qml +++ b/qml/ui/widgets/ThrottleWidget.qml @@ -80,22 +80,8 @@ BaseWidget { anchors.left: parent.left } } - - ConfirmSlider { - - visible: _fcMavlinkSystem.supports_basic_commands - text_off: _fcMavlinkSystem.armed ? qsTr("DISARM") : qsTr("ARM") - - onCheckedChanged: { - if (checked == true){ //must be true since switch reverts to false - if (_fcMavlinkSystem.armed == true) { - _fcMavlinkSystem.arm_fc_async(false) - } - else { - _fcMavlinkSystem.arm_fc_async(true) - } - } - } + ArmDisarmSlider{ + id: arm_disarm_slider } } } diff --git a/qml/ui/widgets/VerticalSpeedGaugeWidget.qml b/qml/ui/widgets/VerticalSpeedGaugeWidget.qml index 42cbdd353..836bf66a1 100644 --- a/qml/ui/widgets/VerticalSpeedGaugeWidget.qml +++ b/qml/ui/widgets/VerticalSpeedGaugeWidget.qml @@ -25,7 +25,12 @@ BaseWidget { widgetIdentifier: "vertical_speed_gauge_widget" bw_verbose_name: "VERTICAL SPEED (CLIMB)" - property double m_vertical_speed_m_per_second: _fcMavlinkSystem.vertical_speed_indicator_mps + function get_vertical_speed_m_s_or_ft_s(){ + if(settings.enable_imperial){ + return _fcMavlinkSystem.vertical_speed_indicator_mps*3.28084; // ft /s + } + return _fcMavlinkSystem.vertical_speed_indicator_mps; // m/s + } hasWidgetDetail: true @@ -140,7 +145,7 @@ BaseWidget { maximumValue: settings.vertical_speed_gauge_widget_max Behavior on value {NumberAnimation { duration: settings.smoothing }} - value: m_vertical_speed_m_per_second + value: get_vertical_speed_m_s_or_ft_s() style: CircularGaugeStyle { labelInset: outerRadius * -.3 diff --git a/qml/ui/widgets/VerticalSpeedSimpleWidget.qml b/qml/ui/widgets/VerticalSpeedSimpleWidget.qml index 8a321c237..207013fe0 100644 --- a/qml/ui/widgets/VerticalSpeedSimpleWidget.qml +++ b/qml/ui/widgets/VerticalSpeedSimpleWidget.qml @@ -31,26 +31,42 @@ BaseWidget { property double m_vertical_speed_m_per_second: _fcMavlinkSystem.vertical_speed_indicator_mps - function get_text_vertical_speed(){ + function get_v_speed_number(){ var vertical_speed_m_per_second=_fcMavlinkSystem.vertical_speed_indicator_mps + if(settings.enable_imperial){ + // feet per second + return vertical_speed_m_per_second*3.28084; + } + return vertical_speed_m_per_second; + } + + function get_v_speed_unit(){ + if(settings.enable_imperial){ + return " ft/s"; + } + return " m/s"; + } + + function get_text_vertical_speed(){ + var vert_speed = get_v_speed_number() if(settings.vertical_speed_simple_widget_show_up_down_arrow){ - // remove the unit - if(vertical_speed_m_per_second<0){ - vertical_speed_m_per_second*=-1; + // remove the "-" since we show a icon instead + if(vert_speed<0){ + vert_speed*=-1; } } - var vs_as_str=Number(vertical_speed_m_per_second).toLocaleString(Qt.locale(), 'f', 1); - if(settings.vertical_speed_simple_widget_show_unit){ - vs_as_str+=" m/s"; + var ret=Number(vert_speed).toLocaleString( Qt.locale(), 'f', 0) + if(settings.vertical_speed_simple_widget_show_unit && vert_speed <99){ + ret+=get_v_speed_unit(); } - return vs_as_str; + return ret; } function get_text_icon_vertical_speed(){ - var vertical_speed_m_per_second=_fcMavlinkSystem.vertical_speed_indicator_mps - if(vertical_speed_m_per_second>0.0){ + var vert_speed=get_v_speed_number() + if(vert_speed>0.0){ return "\uf062"; - }else if(vertical_speed_m_per_second<0.0){ + }else if(vert_speed<0.0){ return "\uf063"; } return ""; diff --git a/qml/ui/widgets/VideoBitrateWidgetGeneric.qml b/qml/ui/widgets/VideoBitrateWidgetGeneric.qml index 6df474f8c..c0188eb83 100644 --- a/qml/ui/widgets/VideoBitrateWidgetGeneric.qml +++ b/qml/ui/widgets/VideoBitrateWidgetGeneric.qml @@ -47,19 +47,9 @@ BaseWidget { function set_camera_resolution(resolution_str){ if(m_is_for_primary_camera){ - var success= _airCameraSettingsModel.set_param_video_resolution_framerate(resolution_str) - if(!success){ - _hudLogMessagesModel.add_message_warning("Cannot change camera 1 resolutin"); - }else{ - m_curr_video_format=resolution_str; - } + _wbLinkSettingsHelper.set_param_video_resolution_framerate_async(true,resolution_str) }else{ - var success= _airCameraSettingsModel2.set_param_video_resolution_framerate(resolution_str) - if(!success){ - _hudLogMessagesModel.add_message_warning("Cannot change camera 2 resolutin"); - }else{ - m_curr_video_format=resolution_str; - } + _wbLinkSettingsHelper.set_param_video_resolution_framerate_async(false,resolution_str) } } @@ -175,6 +165,28 @@ BaseWidget { verticalAlignment: Text.AlignVCenter } } + Item { + width: parent.width + height: 32 + Text { + text: qsTr("TX drop (tot/curr):") + color: "white" + font.bold: true + height: parent.height + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + Text { + text: m_camera_stream_model.curr_delta_tx_dropped_frames+":"+m_camera_stream_model.total_n_tx_dropped_frames + color: "white"; + font.bold: true; + height: parent.height + font.pixelSize: detailPanelFontPixels; + anchors.right: parent.right + verticalAlignment: Text.AlignVCenter + } + } Item{ width: parent.width height: 150 diff --git a/qml/ui/widgets/WBLinkRateControlWidget.qml b/qml/ui/widgets/WBLinkRateControlWidget.qml index f88098bbb..ee857b08b 100644 --- a/qml/ui/widgets/WBLinkRateControlWidget.qml +++ b/qml/ui/widgets/WBLinkRateControlWidget.qml @@ -127,37 +127,37 @@ BaseWidget { } function set_keyframe_interval(interval){ - var success=_airCameraSettingsModel.set_param_keyframe_interval(interval) - if(success!==true){ - _hudLogMessagesModel.signalAddLogMessage(6,"cannot set cam1 keyframe interval") - return; - } - if(settings.dev_qopenhd_n_cameras==2){ - _airCameraSettingsModel2.set_param_keyframe_interval(interval) - _hudLogMessagesModel.signalAddLogMessage(6,"cannot set cam2 keyframe interval") - } + _wbLinkSettingsHelper.set_param_keyframe_interval_async(interval) } function set_fec_percentage(percentage){ - var success=_airPiSettingsModel.set_param_fec_percentage(percentage) - if(success!==true){ - _hudLogMessagesModel.signalAddLogMessage(6,"cannot set fec percentage") + _wbLinkSettingsHelper.set_param_fec_percentage_async(percentage) + } + function set_air_only_mcs(mcs_index){ + _wbLinkSettingsHelper.set_param_air_only_mcs_async(mcs_index) + } + + function set_channel_width_async(channel_width_mhz){ + if(!_ohdSystemAir.is_alive){ + _hudLogMessagesModel.add_message_warning("Cannot change BW:"+channel_width_mhz+"Mhz, AIR not alive"); + return; } + _wbLinkSettingsHelper.change_param_air_channel_width_async(channel_width_mhz,true); } property string m_DESCRIPTION_CHANNEL_WIDTH: " -A higher channel width (40Mhz) increases the bitrate significantly, but reduces the maximum range of the system. -In cntrast to the MCS index (see below), it can only be changed while disarmed (not during flight), -It is recommended to use a 40Mhz channel width if your hardware supports it, -and controll the MCS index for fine adjustments." +A higher bandwidth / 40Mhz channel width increases the bitrate significantly, but also increases interference and reduces the maximum range."+ +"It is recommended to use a 40Mhz channel width if possible,"+ +"and controll the MCS index for fine adjustments." property string m_DESCRIPTION_MCS: " -The lower the MCS (Modulation and coding) index, the less signal (dBm) is required to pick up data. If you want to, you can change this value using the RC channel switcher - -this allows you to quickly select a lower MCS index during flight (e.g. if you want to fly further or encounter issues like your plane going out of the corridor of your antenna tracker.)" +The lower the MCS (Modulation and coding) index, the less signal (dBm) is required to pick up data."+ +"This means that with a lower MCS index, you have a much greater range (but less bitrate).If you want to, you can change this value using the RC channel switcher -"+ +"this allows you to quickly select a lower MCS index during flight (e.g. if you want to fly further or encounter issues like your plane going out of the corridor of your antenna tracker.)" property string m_DESCRIPTION_STABILITY: " -Make the video more stable (less microfreezes) on the cost of less image quality. -Internally, this changes the encode keyframe interval and/ or FEC overhead in percent. DEFAULT is a good trade off regarding image quality and stability -and works in most cases. Use CITY/POLLUTED on polluted channels, DESERT if you have a completely clean channel." +Make the video more stable (less microfreezes) on the cost of less image quality."+ +"Internally, this changes the encode keyframe interval and/ or FEC overhead in percent. DEFAULT is a good trade off regarding image quality and stability"+ +"and works in most cases. Use CITY/POLLUTED on polluted channels, DESERT if you have a completely clean channel." widgetDetailComponent: ScrollView { @@ -321,29 +321,20 @@ and works in most cases. Use CITY/POLLUTED on polluted channels, DESERT if you h Button{ text: "20Mhz" onClicked: { - _synchronizedSettings.change_param_air_and_ground_channel_width(20) + set_channel_width_async(20) } highlighted: m_curr_channel_width==20 - enabled: !m_is_armed + //enabled: _ohdSystemAir.is_alive } Button{ text: "40Mhz" onClicked: { - _synchronizedSettings.change_param_air_and_ground_channel_width(40) + set_channel_width_async(40) } highlighted: m_curr_channel_width==40 - enabled: !m_is_armed + //enabled: _ohdSystemAir.is_alive } } - Text{ - text: "ARMED - not available" - width: parent.width - height: parent.height - visible : m_is_armed - color: "red" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } } } } @@ -398,28 +389,28 @@ and works in most cases. Use CITY/POLLUTED on polluted channels, DESERT if you h Button{ text: "MCS0" onClicked: { - _synchronizedSettings.change_param_air_only_mcs(0,true) + set_air_only_mcs(0) } highlighted: m_curr_mcs_index==0 } Button{ text: "MCS1" onClicked: { - _synchronizedSettings.change_param_air_only_mcs(1,true) + set_air_only_mcs(1) } highlighted: m_curr_mcs_index==1 } Button{ - text: "MCS2" + text: "MCS2\n(DEFAULT)" onClicked: { - _synchronizedSettings.change_param_air_only_mcs(2,true) + set_air_only_mcs(2) } highlighted: m_curr_mcs_index==2 } Button{ text: "MCS3" onClicked: { - _synchronizedSettings.change_param_air_only_mcs(3,true) + set_air_only_mcs(3) } highlighted: m_curr_mcs_index==3 } @@ -484,9 +475,9 @@ and works in most cases. Use CITY/POLLUTED on polluted channels, DESERT if you h text: "CITY" onClicked: { set_keyframe_interval(3) - set_fec_percentage(40) + set_fec_percentage(30) } - highlighted: m_curr_keyframe_i == 3 && m_curr_fec_perc==40 + highlighted: m_curr_keyframe_i == 3 && m_curr_fec_perc==30 } Button{ text: "DEFAULT" diff --git a/qml/ui/widgets/map/MapComponent.qml b/qml/ui/widgets/map/MapComponent.qml index 30782e503..2ccf1fccc 100644 --- a/qml/ui/widgets/map/MapComponent.qml +++ b/qml/ui/widgets/map/MapComponent.qml @@ -25,9 +25,11 @@ Map { property double userLon: 0.0 property double center_coord_lat: 0.0 property double center_coord_lon: 0.0 - property int track_count: 0; - property int track_skip: 1; - property int track_limit: 100; //max number of drone track points before it starts averaging + + property int track_limit: 200; //max number of drone track points before it starts averaging + // We start with a minimum distance of 3m, each time we perform a track reduction, the minimum distance is increased + property int min_distance_between_points_m: 3.0 + center { latitude: _fcMavlinkSystem.lat == 0.0 ? userLat : followDrone ? _fcMavlinkSystem.lat : 9000 @@ -62,9 +64,6 @@ Map { function findMapBounds(){ var center_coord = map.toCoordinate(Qt.point(map.width/2,map.height/2)) //console.log("Map component: center",center_coord.latitude, center_coord.longitude); - - AdsbVehicleManager.newMapCenter(center_coord); - } PositionSource { @@ -78,33 +77,42 @@ Map { } } + //this function keeps recycling points to preserve memory function addDroneTrack() { - //this function keeps recycling points to preserve memory - - // always remove last point unless it was significant - if (track_count != 0) { - droneTrack.removeCoordinate(droneTrack.pathLength()); - //console.log("Map component: total points=", droneTrack.pathLength()); + //console.log("Add drone track") + // only add track while armed + if(!_fcMavlinkSystem.armed){ + return; } - - // always add the current location so drone looks like its connected to line - droneTrack.addCoordinate(QtPositioning.coordinate(_fcMavlinkSystem.lat, _fcMavlinkSystem.lon)); - - track_count = track_count + 1; - - if (track_count == track_skip) { - track_count = 0; + var new_coordinate=QtPositioning.coordinate(_fcMavlinkSystem.lat, _fcMavlinkSystem.lon) + if(droneTrack.pathLength()<=1){ + // first ever 2 points + droneTrack.addCoordinate(new_coordinate); + return; } - - if (droneTrack.pathLength() === track_limit) { - //make line more coarse - track_skip = track_skip * 2; - //cut the points in the list by half - for (var i = 0; i < track_limit; ++i) { - if (i % 2) { - // it's odd - droneTrack.removeCoordinate(i); + var coordinate_prev1=droneTrack.coordinateAt(droneTrack.pathLength()-1); + var coordinate_prev2=droneTrack.coordinateAt(droneTrack.pathLength()-2); + var distance = coordinate_prev1.distanceTo(coordinate_prev2); + //console.log("Distance is:"+distance+"m"); + if(distancetrack_limit){ + console.log("Track limit reached"); + // make the line more coarse by removing every 2nd element, beginning from the end of the track (front of the list) + // but keep first and last point intact + for(var i=droneTrack.pathLength()-2;i>=1;i--){ + if(i % 2 == 1){ + droneTrack.removeCoordinate(i); + } } + // increase the min distance between points + min_distance_between_points_m+=3.0; + console.log("New min distance:"+min_distance_between_points_m+"m"); } } } @@ -140,140 +148,14 @@ Map { source: "qrc:/resources/homemarker.png" } } - // ? ADSB stuff ? - MapRectangle { - id: adsbSquare - topLeft : AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 315, 0.0) - bottomRight: AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 135, 0.0) - //enabled: false - visible: settings.adsb_api_openskynetwork - color: "white" - border.color: "red" - border.width: 5 - smooth: true - opacity: .3 - } - // ? ADSB stuff ? - MapItemView { - id: markerMapView - //TODO ADSB needs refactor - model: AdsbVehicleManager.adsbVehicles - delegate: markerComponentDelegate - //visible: false - - Component { - id: markerComponentDelegate - - MapItemGroup { - id: delegateGroup - - MapQuickItem { - id: marker - anchorPoint.x: 0 - anchorPoint.y: 0 - width: 260 - height: 260 - - - sourceItem: - - DrawingCanvas { - id: icon - anchors.centerIn: parent - - width: 260 - height: 260 - - color: settings.color_shape - glow: settings.color_glow - - name: object.callsign - - drone_heading: OpenHD.hdg; //need this to adjust orientation - - drone_alt: OpenHD.alt_msl; - - heading: object.heading; - - speed: object.velocity - - alt: object.altitude - -// { -/* check if traffic is a threat.. this should not be done here. Left as REF - if (object.altitude - OpenHD.alt_msl < 300 && model.distance < 2){ - //console.log("TRAFFIC WARNING"); - - //image.source="/airplanemarkerwarn.png"; - background.border.color = "red"; - background.border.width = 5; - background.opacity = 0.5; - } else if (object.altitude - OpenHD.alt_msl < 500 && model.distance < 5){ - //console.log("TRAFFIC ALERT"); - - //image.source="/airplanemarkeralert.png"; - background.border.color = "yellow"; - background.border.width = 5; - background.opacity = 0.5; - } -*/ - -/* *discovered issues when the object is referenced multiple times - *last attempt at putting altitude into a var still resulted in "nulls" - - var _adsb_alt; - - _adsb_alt=object.altitude; - - if ( _adsb_alt> 9999) { - //console.log("qml: model alt or vertical undefined") - return "---"; - } else { - if(object.verticalVel > .2){ //climbing - if (settings.enable_imperial === false){ - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue696" - } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue696" - } - } - else if (object.verticalVel < -.2){//descending - if (settings.enable_imperial === false){ - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue697" - } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue697" - } - } - else { - if (settings.enable_imperial === false){//level - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\u2501" - } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\u2501" - } - } - } - } - */ - } - //position everything - coordinate: object.coordinate; - - } - //Component.onCompleted: map.addMapItemGroup(this); - } - } - } //get coordinates on click... for future use MouseArea { anchors.fill: parent onClicked: { - var coord = map.toCoordinate(Qt.point(mouse.x, - mouse.y)) - console.log(coord.latitude, coord.longitude) + var coord = map.toCoordinate(Qt.point(mouse.x,mouse.y)) + console.log("Map clicked, "+coord.latitude+":"+coord.longitude) configureLargeMap() } } @@ -387,6 +269,13 @@ Map { // We cannot do waypoint track until we have proper sorting //waypointTrack.addCoordinate(coordinate); } + /*TapHandler { + id: tapHandler + //anchors.fill: sourceItem + onTapped: { + console.log("Clicked mission item"+model.index) + } + }*/ } /*MapCircle { id: innerCircle diff --git a/qml/ui/widgets/MapWidget.qml b/qml/ui/widgets/map/MapWidget.qml similarity index 99% rename from qml/ui/widgets/MapWidget.qml rename to qml/ui/widgets/map/MapWidget.qml index 58424b859..1e8cf6418 100644 --- a/qml/ui/widgets/MapWidget.qml +++ b/qml/ui/widgets/map/MapWidget.qml @@ -3,7 +3,7 @@ import QtQuick.Window 2.14 //import QtLocation 15.5 //import QtPositioning 15.5 -import "../elements"; +import "../../elements"; // The actual Map canvas is in map/MapComponent.qml // Here we just do integration with BaseWidget and stuff like diff --git a/qml/ui/widgets/MapWidgetForm.ui.qml b/qml/ui/widgets/map/MapWidgetForm.ui.qml similarity index 99% rename from qml/ui/widgets/MapWidgetForm.ui.qml rename to qml/ui/widgets/map/MapWidgetForm.ui.qml index 05a6f9618..ce5f1ce2b 100644 --- a/qml/ui/widgets/MapWidgetForm.ui.qml +++ b/qml/ui/widgets/map/MapWidgetForm.ui.qml @@ -9,6 +9,8 @@ import Qt.labs.settings 1.0 import QtQuick.Window 2.12 //import QtLocation 5.15 +import "../"; + import OpenHD 1.0 BaseWidget { diff --git a/qml/video/SecondaryVideoGStreamer.qml b/qml/video/SecondaryVideoGStreamer.qml index c077aea06..49af6a844 100644 --- a/qml/video/SecondaryVideoGStreamer.qml +++ b/qml/video/SecondaryVideoGStreamer.qml @@ -88,7 +88,7 @@ Item { width: 500 height: 300 modal: true - focus: true + //focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside /*background: Rectangle { color: "gray" diff --git a/systemd/rock5_h264_decode.service b/systemd/rock5_h264_decode.service index cb0d87f06..b4bb80f69 100644 --- a/systemd/rock5_h264_decode.service +++ b/systemd/rock5_h264_decode.service @@ -5,7 +5,7 @@ Description=rock_h264_decode User=root # Video decode via mpp, started by QOpenHD if needed (and stopped if needed) -ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)96, clock-rate=(int)90000, media=(string)video, encoding-name=(string)H264' ! rtph264depay ! h264parse ! mppvideodec ! kmssink plane-id=74" +ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)96, clock-rate=(int)90000, media=(string)video, encoding-name=(string)H264' ! rtph264depay ! h264parse ! mppvideodec format=23 fast-mode=true ! kmssink plane-id=102" Restart=always RestartSec=2 diff --git a/systemd/rock5_h265_decode.service b/systemd/rock5_h265_decode.service index e8c0854d4..12d7b0227 100644 --- a/systemd/rock5_h265_decode.service +++ b/systemd/rock5_h265_decode.service @@ -5,7 +5,7 @@ Description=rock_h265_decode User=root # Video decode via mpp, started by QOpenHD if needed (and stopped if needed) -ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)96, clock-rate=(int)90000, media=(string)video, encoding-name=(string)H265' ! rtph265depay ! h265parse ! mppvideodec ! kmssink plane-id=71" +ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)96, clock-rate=(int)90000, media=(string)video, encoding-name=(string)H265' ! rtph265depay ! h265parse ! mppvideodec format=23 fast-mode=true ! kmssink plane-id=102" Restart=always RestartSec=1 diff --git a/systemd/rock5_mjpeg_decode.service b/systemd/rock5_mjpeg_decode.service index 574a8c645..1f9bee4c5 100644 --- a/systemd/rock5_mjpeg_decode.service +++ b/systemd/rock5_mjpeg_decode.service @@ -5,7 +5,7 @@ Description=rock_mjpeg_decode User=root # Video decode via mpp, started by QOpenHD if needed (and stopped if needed) -ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)26, clock-rate=(int)90000, media=(string)video, encoding-name=(string)JPEG' ! rtpjpegdepay ! jpegdec ! kmssink plane-id=71" +ExecStart=/bin/sh -c "gst-launch-1.0 udpsrc port=5600 caps='application/x-rtp, payload=(int)26, clock-rate=(int)90000, media=(string)video, encoding-name=(string)JPEG' ! rtpjpegdepay ! jpegdec ! kmssink plane-id=102" Restart=always RestartSec=1 diff --git a/systemd/rock5_qopenhd.service b/systemd/rock5_qopenhd.service index 0101783fc..ea8303487 100644 --- a/systemd/rock5_qopenhd.service +++ b/systemd/rock5_qopenhd.service @@ -5,9 +5,10 @@ After=multi-user.target [Service] Type=simple Environment="QT_QPA_EGLFS_KMS_ATOMIC=1" -Environment="QT_QPA_EGLFS_DEBUG=1" -Environment="QT_QPA_EGLFS_KMS_ZPOS=2" +Environment="QT_LOGGING_RULES=qt.qpa.egl*=true" +Environment="QT_QPA_EGLFS_KMS_PLANE_INDEX=5" Environment="QT_QPA_EGLFS_FORCE888=1" +Environment="QT_QPA_EGLFS_SWAPINTERVAL=0" Environment="QT_QPA_EGLFS_KMS_CONFIG=/usr/local/share/qopenhd/rock_qt_eglfs_kms_config.json" ExecStart=/usr/local/bin/QOpenHD -platform eglfs User=root