mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
17 Commits
android36
...
nordictrac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8f744c7b | ||
|
|
1dbdd63b3c | ||
|
|
6b8d96cf7c | ||
|
|
a0bcd8caab | ||
|
|
e46e4daf64 | ||
|
|
8fbd55262d | ||
|
|
487ec5d187 | ||
|
|
090e68979e | ||
|
|
23eebc8be1 | ||
|
|
2eee3e3cc3 | ||
|
|
1f371248d5 | ||
|
|
2bb1cb20de | ||
|
|
16b8805164 | ||
|
|
ae149876a5 | ||
|
|
9042f4857d | ||
|
|
45e06cc807 | ||
|
|
21e341d3d4 |
150
.github/workflows/main.yml
vendored
150
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user