From 21e341d3d46b33e3e2958cbd91ac17239063f062 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 11:35:18 +0200 Subject: [PATCH 01/14] Nordictrack OCR build --- .github/workflows/main.yml | 150 ++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16f55e228..f3f613c90 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -649,6 +649,153 @@ jobs: # asset_name: fdroid-android-trial.zip # asset_content_type: application/zip + android-nordictrack-build: + # The type of runner that the job will run on + runs-on: ubuntu-20.04 + + env: + AMAZON: "1" + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # - name: Cache Qt Linux Desktop + # id: cache-qt-linux-desktop + # uses: actions/cache@v1 + # with: + # path: '${{ github.workspace }}/output/linux-desktop/' + # key: ${{ runner.os }}-QtCache-Linux-Desktop + + # - name: Cache Qt Linux Android + # id: cache-qt-android + # uses: actions/cache@v1 + # with: + # path: '${{ github.workspace }}/output/android/' + # key: ${{ runner.os }}-QtCache-Android + + - name: Xvfb install and run + run: | + sudo apt-get install -y xvfb + Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 & + + - name: Checkout repository + uses: actions/checkout@v2 + with: + # This token is provided by Actions, you do not need to create your own token + token: ${{ secrets.GITHUB_TOKEN }} + submodules: recursive # or 'true' if you want to check out only immediate submodules + + - name: Install packages required to run QZ inside workflow + run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev + + # - name: Test Peloton API + # if: github.event_name == 'push' || github.event_name == 'schedule' + # run: cd /home/runner/work/qdomyos-zwift/qdomyos-zwift/src/; ./qdomyos-zwift -test-peloton -peloton-username ${{ secrets.peloton_username }} -peloton-password ${{ secrets.peloton_password }} + # timeout-minutes: 2 + + # - name: Test Home Fitness Buddy API + # run: cd /home/runner/work/qdomyos-zwift/qdomyos-zwift/src/; ./qdomyos-zwift -test-hfb + # timeout-minutes: 2 + + # - uses: actions/checkout@v2 + # with: + # repository: nttld/setup-ndk + # path: setup-ndk + # The packages.json in nttld/setup-ndk has already been updated, + # https://github.com/nttld/setup-ndk/commit/831db5b02a0f0cab80614619efe461a3dcc140e6 + # but `dist/*` has not been rebuilt yet. Build it. + # https://github.com/nttld/setup-ndk/tree/main/dist + # - name: Locally rebuilt setup-ndk + # run: | + # npm -prefix ./setup-ndk install + # npm -prefix ./setup-ndk run all + # Install using locally rebuilt setup-ndk + # - name: Setup Android NDK r21d + # uses: ./setup-ndk + #- uses: nttld/setup-ndk@v1 + # with: + # ndk-version: r21d + + # waiting github.com/jurplel/install-qt-action/issues/63 + - name: Install Qt Android + uses: jurplel/install-qt-action@v3 + with: + version: '5.15.0' + host: 'linux' + target: 'android' + arch: 'android' + modules: 'qtcharts qtnetworkauth' + dir: '${{ github.workspace }}/output/android/' + cache: 'true' + cache-key-prefix: 'install-qt-action-android' + + - name: Install Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '11' + + - name: patching qt for bluetooth + run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/ + + - name: download 3rd party files for qthttpserver + run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/ + + - name: Build qthttpserver + run: | + cd src/qthttpserver + qmake + make -j8 + make install + cd ../.. + + - name: Set Android NDK 21 && build + run: | + # Install NDK 21 after GitHub update + # https://github.com/actions/virtual-environments/issues/5595 + ANDROID_ROOT="/usr/local/lib/android" + ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" + SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" + echo "y" | $SDKMANAGER "ndk;21.4.7075529" + export ANDROID_NDK="${ANDROID_SDK_ROOT}/ndk-bundle" + export ANDROID_NDK_ROOT="${ANDROID_NDK}" + cd src + echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h + echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h + echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h + echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h + echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js + echo "#define LICENSE" >> secret.h + cd .. + + ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK + rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393 + qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install + sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json + cat src/android-qdomyos-zwift-deployment-settings.json + + - name: Build APK (not usable for production due to unpatched QT library) + run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab + + - name: Archive apk binary + uses: actions/upload-artifact@v2 + with: + name: nordictrack-android-trial + path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/ + + # - name: Exit if not on master branch + # if: github.ref == 'refs/heads/master' + # run: exit 1 + + # - name: upload windows artifact + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ github.token }} + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk + # asset_name: fdroid-android-trial.zip + # asset_content_type: application/zip + ios-build: # The type of runner that the job will run on runs-on: macos-12 @@ -1056,7 +1203,7 @@ jobs: permissions: write-all runs-on: ubuntu-20.04 if: github.event_name == 'schedule' - needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build] # Specify the job dependencies + needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build, android-nordictrack-build] # Specify the job dependencies steps: - name: Download artifacts uses: actions/download-artifact@v3 @@ -1085,3 +1232,4 @@ jobs: windows-binary-no-python/* windows-binary/* fdroid-android-trial/* + nordictrack-android-trial/* From 45e06cc8077e50c50706e8d5790d697132c8a7f8 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 12:13:40 +0200 Subject: [PATCH 02/14] trying if it works --- .github/workflows/main.yml | 2 +- src/android/build.gradle | 5 ++++- src/homeform.cpp | 14 ++++++++++++++ src/trainprogram.cpp | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f3f613c90..e9af11963 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -654,7 +654,7 @@ jobs: runs-on: ubuntu-20.04 env: - AMAZON: "1" + NORDICTRACK: "1" # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/src/android/build.gradle b/src/android/build.gradle index d8ddbe197..4ae032794 100644 --- a/src/android/build.gradle +++ b/src/android/build.gradle @@ -24,7 +24,10 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' def amazon = System.getenv('AMAZON') +def nordictrack = System.getenv('NORDICTRACK') println(amazon) +println(nordictrack) + dependencies { implementation "androidx.core:core:1.12.0" @@ -32,7 +35,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0" implementation 'com.google.protobuf:protobuf-javalite:3.25.1' - if(amazon == "1") { + if(amazon == "1" || nordictrack == "1") { // amazon app store implementation 'com.google.mlkit:text-recognition:16.0.0-beta6' } else { diff --git a/src/homeform.cpp b/src/homeform.cpp index e16f183a2..cd60e1ae7 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -4,6 +4,7 @@ #endif #include "localipaddress.h" #ifdef Q_OS_ANDROID +#include "androidactivityresultreceiver.h" #include "keepawakehelper.h" #include #endif @@ -683,6 +684,19 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) { "(Ljava/lang/String;Landroid/content/Context;)V", javaPath.object(), QtAndroid::androidContext().object()); #endif +#ifdef Q_OS_ANDROID + // TO REMOVE + { + AndroidActivityResultReceiver *a = new AndroidActivityResultReceiver(); + QAndroidJniObject MediaProjectionManager = QtAndroid::androidActivity().callObjectMethod( + "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", + QAndroidJniObject::fromString("media_projection").object()); + QAndroidJniObject intent = + MediaProjectionManager.callObjectMethod("createScreenCaptureIntent", "()Landroid/content/Intent;"); + QtAndroid::startActivity(intent, 100, a); + } +#endif + bluetoothManager->homeformLoaded = true; } diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index f2150c3ba..f9074417e 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -599,6 +599,25 @@ void trainprogram::scheduler() { } } + { + QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText"); + QString t = text.toString(); + QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended"); + // 2272 1027 + jint w = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", + "getImageWidth", "()I"); + jint h = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", + "getImageHeight", "()I"); + QString tExtended = textExtended.toString(); + QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName"); + QString packageName = packageNameJava.toString(); + + qDebug() << QStringLiteral("OCR") << packageName << t; + } + if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr || (bluetoothManager->device()->currentSpeed().value() <= 0 && !settings.value(QZSettings::continuous_moving, QZSettings::default_continuous_moving).toBool()) || From 9042f4857d4aa556623394e9bf21fc94f65c2112 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 13:49:20 +0200 Subject: [PATCH 03/14] Update trainprogram.cpp --- src/trainprogram.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index f9074417e..e42cff94d 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -605,6 +605,7 @@ void trainprogram::scheduler() { QString t = text.toString(); QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended"); + QString tt = textExtended.toString(); // 2272 1027 jint w = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", "getImageWidth", "()I"); @@ -615,7 +616,7 @@ void trainprogram::scheduler() { "org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName"); QString packageName = packageNameJava.toString(); - qDebug() << QStringLiteral("OCR") << packageName << t; + qDebug() << QStringLiteral("OCR") << packageName << tt; } if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr || From ae149876a5d4c07ed7a23cf1c22b6c46b23a09ca Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 13:55:53 +0200 Subject: [PATCH 04/14] Update trainprogram.cpp --- src/trainprogram.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index f9074417e..4208f0b70 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -18,6 +18,12 @@ #endif #include "localipaddress.h" +#include +#include +#include +#include + + using namespace std::chrono_literals; trainprogram::trainprogram(const QList &rows, bluetooth *b, QString *description, QString *tags, @@ -582,6 +588,57 @@ void trainprogram::pelotonOCRcomputeTime(QString t) { } } +struct DisplayValue { + QString value; + QRect rect; +}; + +static DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, bool leftSide) { + QStringList lines = ocrText.split("§§"); + QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); + + int xThreshold = leftSide ? imageWidth / 4 : 3 * imageWidth / 4; + + for (const QString& line : lines) { + QStringList parts = line.split("$$"); + if (parts.size() == 2) { + QString value = parts[0]; + QRegularExpressionMatch match = rectRegex.match(parts[1]); + + if (match.hasMatch()) { + int x1 = match.captured(1).toInt(); + int y1 = match.captured(2).toInt(); + int x2 = match.captured(3).toInt(); + int y2 = match.captured(4).toInt(); + + QRect rect(x1, y1, x2 - x1, y2 - y1); + + // Check if the rectangle is on the correct side of the image + if ((leftSide && rect.left() < xThreshold) || (!leftSide && rect.right() > xThreshold)) { + // Additional checks can be added here if needed + return {value, rect}; + } + } + } + } + + return {"", QRect()}; +} + +// Usage example +static void processOCROutput(const QString& ocrText, int imageWidth, int imageHeight) { + DisplayValue incline = extractValue(ocrText, imageWidth, imageHeight, true); + DisplayValue speed = extractValue(ocrText, imageWidth, imageHeight, false); + + if (!incline.value.isEmpty()) { + qDebug() << "Incline:" << incline.value; + } + + if (!speed.value.isEmpty()) { + qDebug() << "Speed:" << speed.value; + } +} + void trainprogram::scheduler() { QMutexLocker(&this->schedulerMutex); @@ -616,6 +673,7 @@ void trainprogram::scheduler() { QString packageName = packageNameJava.toString(); qDebug() << QStringLiteral("OCR") << packageName << t; + processOCROutput(tt, w, h); } if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr || From 2bb1cb20de28ce156d5486b3434cd3607e8d4f4b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 13:56:52 +0200 Subject: [PATCH 05/14] Update trainprogram.cpp --- src/trainprogram.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 8f9586a36..696b00db2 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -656,6 +656,7 @@ void trainprogram::scheduler() { } } +#ifdef Q_OS_ANDROID { QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod( "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText"); @@ -676,6 +677,7 @@ void trainprogram::scheduler() { qDebug() << QStringLiteral("OCR") << packageName << tt; processOCROutput(tt, w, h); } +#endif if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr || (bluetoothManager->device()->currentSpeed().value() <= 0 && From 1f371248d563d39e116ab91fd3f33a490556db17 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 14:04:39 +0200 Subject: [PATCH 06/14] Update trainprogram.cpp --- src/trainprogram.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 696b00db2..8b2f9dc78 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -590,14 +590,16 @@ void trainprogram::pelotonOCRcomputeTime(QString t) { struct DisplayValue { QString value; + QString label; QRect rect; }; -static DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, bool leftSide) { +DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, const QString& targetLabel) { QStringList lines = ocrText.split("§§"); QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); - int xThreshold = leftSide ? imageWidth / 4 : 3 * imageWidth / 4; + DisplayValue result; + int closestDistance = INT_MAX; for (const QString& line : lines) { QStringList parts = line.split("$$"); @@ -613,22 +615,29 @@ static DisplayValue extractValue(const QString& ocrText, int imageWidth, int ima QRect rect(x1, y1, x2 - x1, y2 - y1); - // Check if the rectangle is on the correct side of the image - if ((leftSide && rect.left() < xThreshold) || (!leftSide && rect.right() > xThreshold)) { - // Additional checks can be added here if needed - return {value, rect}; + // Check if this is the label we're looking for + if (value.contains(targetLabel, Qt::CaseInsensitive)) { + result.label = value; + result.rect = rect; + } + // If we've found the label, look for the closest value + else if (!result.label.isEmpty()) { + int distance = qAbs(rect.top() - result.rect.top()); + if (distance < closestDistance) { + closestDistance = distance; + result.value = value; + } } } } } - return {"", QRect()}; + return result; } -// Usage example static void processOCROutput(const QString& ocrText, int imageWidth, int imageHeight) { - DisplayValue incline = extractValue(ocrText, imageWidth, imageHeight, true); - DisplayValue speed = extractValue(ocrText, imageWidth, imageHeight, false); + DisplayValue incline = extractValue(ocrText, imageWidth, imageHeight, "INCLINE"); + DisplayValue speed = extractValue(ocrText, imageWidth, imageHeight, "SPEED"); if (!incline.value.isEmpty()) { qDebug() << "Incline:" << incline.value; From 2eee3e3cc33b06865c846f1814636bd05caeeafc Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 14:12:54 +0200 Subject: [PATCH 07/14] Update trainprogram.cpp --- src/trainprogram.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 8b2f9dc78..1b5c48a19 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -594,9 +594,10 @@ struct DisplayValue { QRect rect; }; -DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, const QString& targetLabel) { +static DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, const QString& targetLabel) { QStringList lines = ocrText.split("§§"); QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); + QRegularExpression numericRegex("^-?\\d+(\\.\\d+)?$"); DisplayValue result; int closestDistance = INT_MAX; @@ -620,8 +621,8 @@ DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeigh result.label = value; result.rect = rect; } - // If we've found the label, look for the closest value - else if (!result.label.isEmpty()) { + // If we've found the label, look for the closest numeric value + else if (!result.label.isEmpty() && numericRegex.match(value).hasMatch()) { int distance = qAbs(rect.top() - result.rect.top()); if (distance < closestDistance) { closestDistance = distance; @@ -641,10 +642,14 @@ static void processOCROutput(const QString& ocrText, int imageWidth, int imageHe if (!incline.value.isEmpty()) { qDebug() << "Incline:" << incline.value; + } else { + qDebug() << "Incline not found"; } if (!speed.value.isEmpty()) { qDebug() << "Speed:" << speed.value; + } else { + qDebug() << "Speed not found"; } } From 23eebc8be1112db0beb88d3266f30ca8655f88a0 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 14:22:45 +0200 Subject: [PATCH 08/14] Update trainprogram.cpp --- src/trainprogram.cpp | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 1b5c48a19..a57a22950 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -590,17 +590,17 @@ void trainprogram::pelotonOCRcomputeTime(QString t) { struct DisplayValue { QString value; - QString label; QRect rect; }; -static DisplayValue extractValue(const QString& ocrText, int imageWidth, int imageHeight, const QString& targetLabel) { +static DisplayValue extractValue(const QString& ocrText, int imageWidth, bool isIncline) { QStringList lines = ocrText.split("§§"); QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); QRegularExpression numericRegex("^-?\\d+(\\.\\d+)?$"); DisplayValue result; - int closestDistance = INT_MAX; + int minX = isIncline ? 0 : imageWidth - 200; + int maxX = isIncline ? 200 : imageWidth; for (const QString& line : lines) { QStringList parts = line.split("$$"); @@ -608,26 +608,17 @@ static DisplayValue extractValue(const QString& ocrText, int imageWidth, int ima QString value = parts[0]; QRegularExpressionMatch match = rectRegex.match(parts[1]); - if (match.hasMatch()) { + if (match.hasMatch() && numericRegex.match(value).hasMatch()) { int x1 = match.captured(1).toInt(); int y1 = match.captured(2).toInt(); int x2 = match.captured(3).toInt(); int y2 = match.captured(4).toInt(); - QRect rect(x1, y1, x2 - x1, y2 - y1); - - // Check if this is the label we're looking for - if (value.contains(targetLabel, Qt::CaseInsensitive)) { - result.label = value; - result.rect = rect; - } - // If we've found the label, look for the closest numeric value - else if (!result.label.isEmpty() && numericRegex.match(value).hasMatch()) { - int distance = qAbs(rect.top() - result.rect.top()); - if (distance < closestDistance) { - closestDistance = distance; - result.value = value; - } + // Check if the rectangle is within our target area + if (x1 >= minX && x2 <= maxX) { + result.value = value; + result.rect = QRect(x1, y1, x2 - x1, y2 - y1); + break; // Take the first matching value } } } @@ -636,9 +627,9 @@ static DisplayValue extractValue(const QString& ocrText, int imageWidth, int ima return result; } -static void processOCROutput(const QString& ocrText, int imageWidth, int imageHeight) { - DisplayValue incline = extractValue(ocrText, imageWidth, imageHeight, "INCLINE"); - DisplayValue speed = extractValue(ocrText, imageWidth, imageHeight, "SPEED"); +static void processOCROutput(const QString& ocrText, int imageWidth) { + DisplayValue incline = extractValue(ocrText, imageWidth, true); + DisplayValue speed = extractValue(ocrText, imageWidth, false); if (!incline.value.isEmpty()) { qDebug() << "Incline:" << incline.value; From 487ec5d1871a1e37d8116dbf5b94c2b0095d05cc Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Mon, 5 Aug 2024 19:09:54 +0200 Subject: [PATCH 09/14] Update main.yml --- .github/workflows/main.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a3e259929..4f8ef7f57 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -740,14 +740,6 @@ jobs: - name: download 3rd party files for qthttpserver run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/ - - name: Build qthttpserver - run: | - cd src/qthttpserver - qmake - make -j8 - make install - cd ../.. - - name: Set Android NDK 21 && build run: | # Install NDK 21 after GitHub update @@ -769,6 +761,14 @@ jobs: ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393 + + # QTHTTPSERVER must use the same NDK + cd src/qthttpserver + qmake + make -j8 + make install + cd ../.. + qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-deployment-settings.json cat src/android-qdomyos-zwift-deployment-settings.json From 8fbd55262d16ef25a1145b8069cc57b02f1bb025 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 6 Aug 2024 09:23:26 +0200 Subject: [PATCH 10/14] adding the logic inside the nordictrack treadmill class --- src/devices/bluetooth.cpp | 22 +++- .../nordictrackifitadbtreadmill.cpp | 113 ++++++++++++++++++ .../nordictrackifitadbtreadmill.h | 12 +- src/homeform.cpp | 14 --- src/trainprogram.cpp | 56 --------- 5 files changed, 141 insertions(+), 76 deletions(-) diff --git a/src/devices/bluetooth.cpp b/src/devices/bluetooth.cpp index 512faad44..ec4738df9 100644 --- a/src/devices/bluetooth.cpp +++ b/src/devices/bluetooth.cpp @@ -34,8 +34,7 @@ bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistanc this->useDiscovery = startDiscovery; - QString nordictrack_2950_ip = - settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString(); + const bool nordictrack = true; // to replace if (settings.value(QZSettings::peloton_bike_ocr, QZSettings::default_peloton_bike_ocr).toBool() && !pelotonBike) { pelotonBike = new pelotonbike(noWriteResistance, noHeartService); @@ -47,6 +46,18 @@ bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistanc } // this signal is not associated to anything in this moment, since the homeform is not loaded yet this->signalBluetoothDeviceConnected(pelotonBike); + } else if(nordictrack) { + nordictrackifitadbTreadmill = new nordictrackifitadbtreadmill(noWriteResistance, noHeartService); + emit deviceConnected(QBluetoothDeviceInfo()); + connect(nordictrackifitadbTreadmill, &bluetoothdevice::connectedAndDiscovered, this, + &bluetooth::connectedAndDiscovered); + connect(nordictrackifitadbTreadmill, &nordictrackifitadbtreadmill::debug, this, &bluetooth::debug); + if (this->discoveryAgent && !this->discoveryAgent->isActive()) { + emit searchingStop(); + } + // this signal is not associated to anything in this moment, since the homeform is not loaded yet + this->signalBluetoothDeviceConnected(nordictrackifitadbTreadmill); + return; } #ifdef TEST @@ -2558,6 +2569,7 @@ void bluetooth::connectedAndDiscovered() { } } #ifdef Q_OS_ANDROID + const bool nordictrack = true; // to replace if (settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool() || settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) { QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", @@ -2571,7 +2583,7 @@ void bluetooth::connectedAndDiscovered() { device()->deviceType() == bluetoothdevice::ELLIPTICAL); } - if (settings.value(QZSettings::android_notification, QZSettings::default_android_notification).toBool()) { + if (settings.value(QZSettings::android_notification, QZSettings::default_android_notification).toBool() || nordictrack) { QAndroidJniObject javaNotification = QAndroidJniObject::fromString("QZ is running!"); QAndroidJniObject::callStaticMethod( "org/cagnulen/qdomyoszwift/NotificationClient", "notify", "(Landroid/content/Context;Ljava/lang/String;)V", @@ -2579,10 +2591,10 @@ void bluetooth::connectedAndDiscovered() { } #endif -#ifdef Q_OS_ANDROID +#ifdef Q_OS_ANDROID if (settings.value(QZSettings::peloton_workout_ocr, QZSettings::default_peloton_workout_ocr).toBool() || settings.value(QZSettings::peloton_bike_ocr, QZSettings::default_peloton_bike_ocr).toBool() || - settings.value(QZSettings::zwift_ocr, QZSettings::default_zwift_ocr).toBool()) { + settings.value(QZSettings::zwift_ocr, QZSettings::default_zwift_ocr).toBool() || nordictrack) { AndroidActivityResultReceiver *a = new AndroidActivityResultReceiver(); QAndroidJniObject MediaProjectionManager = QtAndroid::androidActivity().callObjectMethod( "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", diff --git a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp index d258453c7..14f0f19c5 100644 --- a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp +++ b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp @@ -107,6 +107,96 @@ void nordictrackifitadbtreadmillLogcatAdbThread::runAdbTailCommand(QString comma #endif } +nordictrackifitadbtreadmill::DisplayValue nordictrackifitadbtreadmill::extractValue(const QString& ocrText, int imageWidth, bool isLeftSide) { + QStringList lines = ocrText.split("§§"); + QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); + QRegularExpression numericRegex("^-?\\d+(\\.\\d+)?$"); + + DisplayValue result; + int minX = isLeftSide ? 0 : imageWidth - 200; + int maxX = isLeftSide ? 200 : imageWidth; + QStringList targetLabels = isLeftSide ? QStringList{"INCLINE"} : QStringList{"SPEED", "RESISTANCE"}; + + QRect labelRect; + int closestDistance = INT_MAX; + + // First pass: find the label + for (const QString& line : lines) { + QStringList parts = line.split("$$"); + if (parts.size() == 2) { + QString value = parts[0]; + QRegularExpressionMatch match = rectRegex.match(parts[1]); + + if (match.hasMatch()) { + int x1 = match.captured(1).toInt(); + int x2 = match.captured(3).toInt(); + + if (x1 >= minX && x2 <= maxX) { + for (const QString& targetLabel : targetLabels) { + if (value.contains(targetLabel, Qt::CaseInsensitive)) { + labelRect = QRect(x1, match.captured(2).toInt(), + x2 - x1, match.captured(4).toInt() - match.captured(2).toInt()); + result.label = value; + break; + } + } + if (!result.label.isEmpty()) break; + } + } + } + } + + // Second pass: find the closest numeric value to the label + if (!labelRect.isNull()) { + for (const QString& line : lines) { + QStringList parts = line.split("$$"); + if (parts.size() == 2) { + QString value = parts[0]; + QRegularExpressionMatch match = rectRegex.match(parts[1]); + + if (match.hasMatch() && numericRegex.match(value).hasMatch()) { + int x1 = match.captured(1).toInt(); + int y1 = match.captured(2).toInt(); + int x2 = match.captured(3).toInt(); + int y2 = match.captured(4).toInt(); + + QRect valueRect(x1, y1, x2 - x1, y2 - y1); + + if (x1 >= minX && x2 <= maxX) { + int distance = qAbs(valueRect.center().y() - labelRect.center().y()); + if (distance < closestDistance) { + closestDistance = distance; + result.value = value; + result.rect = valueRect; + } + } + } + } + } + } + + return result; +} + +void nordictrackifitadbtreadmill::processOCROutput(const QString& ocrText, int imageWidth) { + DisplayValue leftValue = extractValue(ocrText, imageWidth, true); + DisplayValue rightValue = extractValue(ocrText, imageWidth, false); + + if (!leftValue.value.isEmpty()) { + qDebug() << "Left value (" << leftValue.label << "):" << leftValue.value; + Inclination = leftValue.label.toDouble(); + } else { + qDebug() << "Left value not found"; + } + + if (!rightValue.value.isEmpty()) { + qDebug() << "Right value (" << rightValue.label << "):" << rightValue.value; + Speed = rightValue.label.toDouble(); + } else { + qDebug() << "Right value not found"; + } +} + double nordictrackifitadbtreadmill::getDouble(QString v) { QChar d = QLocale().decimalPoint(); if (d == ',') { @@ -507,6 +597,29 @@ void nordictrackifitadbtreadmill::update() { // writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape"); requestStop = -1; } + +#ifdef Q_OS_ANDROID + { + QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText"); + QString t = text.toString(); + QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended"); + QString tt = textExtended.toString(); + // 2272 1027 + jint w = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", + "getImageWidth", "()I"); + jint h = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", + "getImageHeight", "()I"); + QString tExtended = textExtended.toString(); + QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod( + "org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName"); + QString packageName = packageNameJava.toString(); + + qDebug() << QStringLiteral("OCR") << packageName << tt; + processOCROutput(tt, w, h); + } +#endif } void nordictrackifitadbtreadmill::changeInclinationRequested(double grade, double percentage) { diff --git a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h index 22e3ec195..85aa34f01 100644 --- a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h +++ b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.h @@ -18,7 +18,8 @@ #include #include #include - +#include +#include #include "treadmill.h" #ifdef Q_OS_IOS @@ -64,6 +65,12 @@ class nordictrackifitadbtreadmill : public treadmill { double minStepSpeed() override { return 0.1; } private: + struct DisplayValue { + QString value; + QString label; + QRect rect; + }; + void forceIncline(double incline); void forceSpeed(double speed); double getDouble(QString v); @@ -91,6 +98,9 @@ class nordictrackifitadbtreadmill : public treadmill { nordictrackifitadbtreadmillLogcatAdbThread *logcatAdbThread = nullptr; #endif + DisplayValue extractValue(const QString& ocrText, int imageWidth, bool isLeftSide); + void processOCROutput(const QString& ocrText, int imageWidth); + int x14i_inclination_lookuptable(double reqInclination); #ifdef Q_OS_IOS diff --git a/src/homeform.cpp b/src/homeform.cpp index cd60e1ae7..e16f183a2 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -4,7 +4,6 @@ #endif #include "localipaddress.h" #ifdef Q_OS_ANDROID -#include "androidactivityresultreceiver.h" #include "keepawakehelper.h" #include #endif @@ -684,19 +683,6 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) { "(Ljava/lang/String;Landroid/content/Context;)V", javaPath.object(), QtAndroid::androidContext().object()); #endif -#ifdef Q_OS_ANDROID - // TO REMOVE - { - AndroidActivityResultReceiver *a = new AndroidActivityResultReceiver(); - QAndroidJniObject MediaProjectionManager = QtAndroid::androidActivity().callObjectMethod( - "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", - QAndroidJniObject::fromString("media_projection").object()); - QAndroidJniObject intent = - MediaProjectionManager.callObjectMethod("createScreenCaptureIntent", "()Landroid/content/Intent;"); - QtAndroid::startActivity(intent, 100, a); - } -#endif - bluetoothManager->homeformLoaded = true; } diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index a57a22950..35e7ce7aa 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -588,62 +588,6 @@ void trainprogram::pelotonOCRcomputeTime(QString t) { } } -struct DisplayValue { - QString value; - QRect rect; -}; - -static DisplayValue extractValue(const QString& ocrText, int imageWidth, bool isIncline) { - QStringList lines = ocrText.split("§§"); - QRegularExpression rectRegex("Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); - QRegularExpression numericRegex("^-?\\d+(\\.\\d+)?$"); - - DisplayValue result; - int minX = isIncline ? 0 : imageWidth - 200; - int maxX = isIncline ? 200 : imageWidth; - - for (const QString& line : lines) { - QStringList parts = line.split("$$"); - if (parts.size() == 2) { - QString value = parts[0]; - QRegularExpressionMatch match = rectRegex.match(parts[1]); - - if (match.hasMatch() && numericRegex.match(value).hasMatch()) { - int x1 = match.captured(1).toInt(); - int y1 = match.captured(2).toInt(); - int x2 = match.captured(3).toInt(); - int y2 = match.captured(4).toInt(); - - // Check if the rectangle is within our target area - if (x1 >= minX && x2 <= maxX) { - result.value = value; - result.rect = QRect(x1, y1, x2 - x1, y2 - y1); - break; // Take the first matching value - } - } - } - } - - return result; -} - -static void processOCROutput(const QString& ocrText, int imageWidth) { - DisplayValue incline = extractValue(ocrText, imageWidth, true); - DisplayValue speed = extractValue(ocrText, imageWidth, false); - - if (!incline.value.isEmpty()) { - qDebug() << "Incline:" << incline.value; - } else { - qDebug() << "Incline not found"; - } - - if (!speed.value.isEmpty()) { - qDebug() << "Speed:" << speed.value; - } else { - qDebug() << "Speed not found"; - } -} - void trainprogram::scheduler() { QMutexLocker(&this->schedulerMutex); From e46e4daf6430724fba5f4fea42316ed582a41f2b Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 6 Aug 2024 09:24:26 +0200 Subject: [PATCH 11/14] Update trainprogram.cpp --- src/trainprogram.cpp | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 35e7ce7aa..6834b1868 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -18,11 +18,6 @@ #endif #include "localipaddress.h" -#include -#include -#include -#include - using namespace std::chrono_literals; @@ -605,29 +600,6 @@ void trainprogram::scheduler() { } } -#ifdef Q_OS_ANDROID - { - QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod( - "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText"); - QString t = text.toString(); - QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod( - "org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended"); - QString tt = textExtended.toString(); - // 2272 1027 - jint w = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", - "getImageWidth", "()I"); - jint h = QAndroidJniObject::callStaticMethod("org/cagnulen/qdomyoszwift/ScreenCaptureService", - "getImageHeight", "()I"); - QString tExtended = textExtended.toString(); - QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod( - "org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName"); - QString packageName = packageNameJava.toString(); - - qDebug() << QStringLiteral("OCR") << packageName << tt; - processOCROutput(tt, w, h); - } -#endif - if (rows.count() == 0 || started == false || enabled == false || bluetoothManager->device() == nullptr || (bluetoothManager->device()->currentSpeed().value() <= 0 && !settings.value(QZSettings::continuous_moving, QZSettings::default_continuous_moving).toBool()) || From a0bcd8caab6d3b291a1038f56f1a119a0d97b739 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 6 Aug 2024 09:24:47 +0200 Subject: [PATCH 12/14] Update trainprogram.cpp --- src/trainprogram.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/trainprogram.cpp b/src/trainprogram.cpp index 6834b1868..f2150c3ba 100644 --- a/src/trainprogram.cpp +++ b/src/trainprogram.cpp @@ -18,7 +18,6 @@ #endif #include "localipaddress.h" - using namespace std::chrono_literals; trainprogram::trainprogram(const QList &rows, bluetooth *b, QString *description, QString *tags, From 6b8d96cf7c4eacb8a4dd5d618db3d63924b8abd8 Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 6 Aug 2024 09:53:04 +0200 Subject: [PATCH 13/14] Update nordictrackifitadbtreadmill.cpp --- .../nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp index 14f0f19c5..6d7dc34b9 100644 --- a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp +++ b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp @@ -617,7 +617,7 @@ void nordictrackifitadbtreadmill::update() { QString packageName = packageNameJava.toString(); qDebug() << QStringLiteral("OCR") << packageName << tt; - processOCROutput(tt, w, h); + processOCROutput(tt, w); } #endif } From 1dbdd63b3c491abcefd3ae78f5146463c5ce2a6a Mon Sep 17 00:00:00 2001 From: Roberto Viola Date: Tue, 6 Aug 2024 10:38:05 +0200 Subject: [PATCH 14/14] Update nordictrackifitadbtreadmill.cpp --- .../nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp index 6d7dc34b9..fb3eb3b95 100644 --- a/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp +++ b/src/devices/nordictrackifitadbtreadmill/nordictrackifitadbtreadmill.cpp @@ -115,7 +115,7 @@ nordictrackifitadbtreadmill::DisplayValue nordictrackifitadbtreadmill::extractVa DisplayValue result; int minX = isLeftSide ? 0 : imageWidth - 200; int maxX = isLeftSide ? 200 : imageWidth; - QStringList targetLabels = isLeftSide ? QStringList{"INCLINE"} : QStringList{"SPEED", "RESISTANCE"}; + QStringList targetLabels = isLeftSide ? QStringList{"INCLINE"} : QStringList{"SPEED", "RESISTANCE", "MPH", "KPH"}; QRect labelRect; int closestDistance = INT_MAX;