Compare commits

...

17 Commits

Author SHA1 Message Date
Roberto Viola
7e8f744c7b Merge branch 'master' into nordictrack-build-ocr 2025-06-09 13:10:15 +02:00
Roberto Viola
1dbdd63b3c Update nordictrackifitadbtreadmill.cpp 2024-08-06 10:38:05 +02:00
Roberto Viola
6b8d96cf7c Update nordictrackifitadbtreadmill.cpp 2024-08-06 09:53:04 +02:00
Roberto Viola
a0bcd8caab Update trainprogram.cpp 2024-08-06 09:24:47 +02:00
Roberto Viola
e46e4daf64 Update trainprogram.cpp 2024-08-06 09:24:26 +02:00
Roberto Viola
8fbd55262d adding the logic inside the nordictrack treadmill class 2024-08-06 09:23:26 +02:00
Roberto Viola
487ec5d187 Update main.yml 2024-08-05 19:09:54 +02:00
Roberto Viola
090e68979e Merge branch 'master' into nordictrack-build-ocr 2024-08-05 19:08:18 +02:00
Roberto Viola
23eebc8be1 Update trainprogram.cpp 2024-08-05 14:22:45 +02:00
Roberto Viola
2eee3e3cc3 Update trainprogram.cpp 2024-08-05 14:12:54 +02:00
Roberto Viola
1f371248d5 Update trainprogram.cpp 2024-08-05 14:04:39 +02:00
Roberto Viola
2bb1cb20de Update trainprogram.cpp 2024-08-05 13:56:52 +02:00
Roberto Viola
16b8805164 Merge branch 'nordictrack-build-ocr' of https://github.com/cagnulein/qdomyos-zwift into nordictrack-build-ocr 2024-08-05 13:56:26 +02:00
Roberto Viola
ae149876a5 Update trainprogram.cpp 2024-08-05 13:55:53 +02:00
Roberto Viola
9042f4857d Update trainprogram.cpp 2024-08-05 13:49:20 +02:00
Roberto Viola
45e06cc807 trying if it works 2024-08-05 12:13:40 +02:00
Roberto Viola
21e341d3d4 Nordictrack OCR build 2024-08-05 11:35:18 +02:00
5 changed files with 295 additions and 9 deletions

View File

@@ -736,6 +736,153 @@ jobs:
process_list.txt
if-no-files-found: warn
android-nordictrack-build:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
env:
NORDICTRACK: "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: 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
# 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
- 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-latest
@@ -1474,7 +1621,7 @@ jobs:
permissions: write-all
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
needs: [linux-x86-build, window-msvc2019-build, window-msvc2022-build, ios-build, window-build, android-build, raspberry-pi-build]
needs: [linux-x86-build, window-msvc2019-build, window-msvc2022-build, ios-build, window-build, android-build, raspberry-pi-build, android-nordictrack-build]
steps:
- name: Checkout code
uses: actions/checkout@v2
@@ -1518,5 +1665,6 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
nordictrack-android-trial/*
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-binary/qdomyos-zwift-64bit

View File

@@ -24,15 +24,18 @@ 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"
implementation "androidx.core:core-ktx:1.12.0"
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 {

View File

@@ -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
@@ -3026,6 +3037,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",
@@ -3040,7 +3052,7 @@ void bluetooth::connectedAndDiscovered() {
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool());
}
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<void>(
"org/cagnulen/qdomyoszwift/NotificationClient", "notify", "(Landroid/content/Context;Ljava/lang/String;)V",
@@ -3048,10 +3060,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;",

View File

@@ -115,6 +115,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", "MPH", "KPH"};
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 == ',') {
@@ -540,6 +630,29 @@ void nordictrackifitadbtreadmill::update() {
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
#ifdef Q_OS_ANDROID
{
QAndroidJniObject text = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastText");
QString t = text.toString();
QAndroidJniObject textExtended = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/ScreenCaptureService", "getLastTextExtended");
QString tt = textExtended.toString();
// 2272 1027
jint w = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/ScreenCaptureService",
"getImageWidth", "()I");
jint h = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/ScreenCaptureService",
"getImageHeight", "()I");
QString tExtended = textExtended.toString();
QAndroidJniObject packageNameJava = QAndroidJniObject::callStaticObjectMethod<jstring>(
"org/cagnulen/qdomyoszwift/MediaProjection", "getPackageName");
QString packageName = packageNameJava.toString();
qDebug() << QStringLiteral("OCR") << packageName << tt;
processOCROutput(tt, w);
}
#endif
}
void nordictrackifitadbtreadmill::changeInclinationRequested(double grade, double percentage) {

View File

@@ -18,7 +18,8 @@
#include <QString>
#include <QThread>
#include <QUdpSocket>
#include <QRect>
#include <QRegularExpression>
#include "treadmill.h"
#ifdef Q_OS_IOS
@@ -66,6 +67,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);
@@ -94,6 +101,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