Compare commits

..

153 Commits

Author SHA1 Message Date
Roberto Viola
2a3696886f Merge 9bb1bd68cd into 40aedaec71 2026-01-03 14:52:39 +00:00
Roberto Viola
9bb1bd68cd Add documentation for grupetto disclaimer setting 2026-01-03 15:52:37 +01:00
Roberto Viola
61f3602980 Merge branch 'master' into peloton_v1_grupetto 2026-01-02 20:10:31 +01:00
Roberto Viola
ddab20e841 Update qzsettings.cpp 2025-11-05 11:22:06 +01:00
Roberto Viola
a0eb19690a fixing build 2025-11-05 11:00:12 +01:00
Roberto Viola
694c895fac Merge branch 'master' into peloton_v1_grupetto 2025-11-05 10:21:57 +01:00
Roberto Viola
f48f28df1f fixing speed 2025-11-05 10:19:34 +01:00
Roberto Viola
24f9b72875 disclaimer added 2025-08-31 08:55:37 +02:00
Roberto Viola
7c2f97fe31 fixing 0 metrics 2025-08-25 08:58:14 +02:00
Roberto Viola
ae7fe8d2db Update PelotonSensorHelper.java 2025-08-24 15:42:05 +02:00
Roberto Viola
2458d009bd Update PelotonSensorBinder.java 2025-08-24 07:03:08 +02:00
Roberto Viola
8b90ab8b00 first version 2025-08-23 15:14:47 +02:00
Roberto Viola
c7bace3112 fixing 2025-08-23 14:36:16 +02:00
Roberto Viola
9a97eee780 Update PelotonSensorBinder.java 2025-08-23 07:18:39 +02:00
Roberto Viola
ad39c8d51d fix 2025-08-22 15:57:53 +02:00
Roberto Viola
c0ba8dcf62 Merge branch 'peloton_gruppetto' of https://github.com/cagnulein/qdomyos-zwift into peloton_gruppetto 2025-08-22 15:19:06 +02:00
Roberto Viola
fda71cda7a fix 2025-08-22 15:18:42 +02:00
Roberto Viola
3c4c654378 Update bluetooth.cpp 2025-08-22 07:25:47 +02:00
Roberto Viola
40f9926ea0 Merge branch 'master' into peloton_gruppetto 2025-08-21 13:22:57 +02:00
Roberto Viola
730e78c042 feat(peloton): implement Peloton bike integration using Grupetto backend
## Summary
Integrate Peloton bike sensor reading using Grupetto project approach for direct hardware access on Android platform.

## Core Components
- **Java Backend**: PelotonSensorService, PelotonSensorBinder, PelotonSensorHelper classes
- **C++ Integration**: Modified pelotonbike class with JNI calls to Java backend
- **Auto-instantiation**: Enabled automatic pelotonbike creation for this branch
- **Build Configuration**: Updated Android manifest permissions and GitHub Actions

## Key Features
- Direct Peloton sensor access via Android system service binding
- Real-time metrics: power, cadence, resistance, speed
- Moving window filtering for resistance spike mitigation
- Power-to-speed conversion using Peloton V1 formula
- Fallback to OCR method when sensors unavailable

## Technical Implementation
- Based on Grupetto's sensor interface approach
- Follows existing NordicTrack GRPC integration pattern
- Uses Android IBinder for Peloton service communication
- Maintains QZ architecture compatibility

## Build Changes
- Android permissions: onepeloton.permission.ACCESS_SENSOR_SERVICE
- GitHub Actions: APK renamed to fdroid-android-peloton-bike-trial
- Auto-instantiation in bluetooth.cpp for pelotonSensorBranch

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 12:34:47 +02:00
Roberto Viola
6ea8ba581d fix(nordictrack): use debounce setting for refresh timer
The gear change detection was tied to a hardcoded 200ms refresh timer, which was causing a delay in detecting gear changes and making the debounce setting ineffective.

This commit changes the refresh timer to use the value from the `nordictrackadbbike_gear_debounce_ms` setting, ensuring that gear changes are detected promptly and that the debounce logic works as expected.
2025-07-16 13:50:16 +02:00
Roberto Viola
0499272421 nordictrackadbbike_gear_debounce_ms min set to 10 2025-07-13 16:45:33 +02:00
Roberto Viola
27f8883830 gear debouncing 2025-07-10 09:44:05 +02:00
Roberto Viola
539b930164 commenting fan readings 2025-07-08 09:59:43 +02:00
Roberto Viola
0663bca5e0 Merge branch 'master' into nordictrack-build-grpc 2025-07-08 09:46:04 +02:00
Roberto Viola
bcec1b1978 fix 3rd mode 2025-07-07 18:08:02 +00:00
Roberto Viola
b154e98289 Update nordictrackifitadbbike.cpp 2025-07-07 09:57:34 +02:00
Roberto Viola
2e98769ef1 Merge branch 'master' into nordictrack-build-grpc 2025-07-07 09:39:23 +02:00
Roberto Viola
e8f6ea07ac Add gear resistance mode for NordicTrack ADB bike
Introduces a new 'gear resistance mode' setting for the NordicTrack iFit ADB bike, allowing gears to control resistance separately from inclination via gRPC. Updates QML settings, QZSettings, and device logic to support this mode, including UI, settings storage, and device behavior. Documentation is updated to clarify property addition rules.
2025-07-07 09:34:51 +02:00
Roberto Viola
651cf6a59c add timing log 2025-07-07 08:26:36 +02:00
Roberto Viola
3c229b9ae8 Merge branch 'master' into nordictrack-build-grpc 2025-07-03 12:11:05 +02:00
Roberto Viola
fab3026b84 build fix 2025-07-02 09:54:36 +02:00
Roberto Viola
0ce8bc9efc Merge branch 'master' into nordictrack-build-grpc 2025-07-02 09:52:20 +02:00
Roberto Viola
4201478c59 Merge branch 'nordictrack-build-grpc' of https://github.com/cagnulein/qdomyos-zwift into nordictrack-build-grpc 2025-07-02 09:46:56 +02:00
Roberto Viola
67b845b5fe ip setting 2025-07-02 09:46:25 +02:00
Roberto Viola
c968d8ad57 Merge branch 'master' into nordictrack-build-grpc 2025-07-02 09:36:23 +02:00
Roberto Viola
8905b1ab4d https://github.com/cagnulein/qdomyos-zwift/pull/3478#issuecomment-3025129167 2025-07-02 09:35:25 +02:00
Roberto Viola
ef2c6f662b let's try to find zwift play 2025-06-30 08:59:41 +02:00
Roberto Viola
5596a6cd4f Revert "redeced classes"
This reverts commit fc00fbf9cc.
2025-06-30 08:54:01 +02:00
Roberto Viola
fef8abea6d Update nordictrackifitadbbike.cpp 2025-06-30 08:28:03 +02:00
Roberto Viola
3889fac141 Update defaults.pri 2025-06-30 08:16:34 +02:00
Roberto Viola
f9d8ba6925 Update nordictrackifitadbbike.cpp 2025-06-27 12:06:57 +02:00
Roberto Viola
40219ebda9 Update nordictrackifitadbbike.cpp 2025-06-27 11:24:20 +02:00
Roberto Viola
12b0cc7924 Update nordictrackifitadbbike.cpp 2025-06-27 10:52:56 +02:00
Roberto Viola
025406e170 fixing gears 2025-06-26 14:19:04 +02:00
Roberto Viola
df369471aa default_inclination_delay_seconds handled and set by default to 3 seconds 2025-06-26 13:55:15 +02:00
Roberto Viola
7df442b528 removing peloton old api change request 2025-06-26 13:30:23 +02:00
Roberto Viola
b5c4da9420 bump 2025-06-25 16:45:50 +02:00
Roberto Viola
d9e1d9a1be Update nordictrackifitadbbike.cpp 2025-06-25 12:31:01 +02:00
Roberto Viola
1c85feedca adding resistance instead of inclination 2025-06-25 12:27:40 +02:00
Roberto Viola
660f55ad48 rounding set incline 2025-06-25 12:25:33 +02:00
Roberto Viola
b871c795b8 fan handled 2025-06-24 11:54:09 +02:00
Roberto Viola
9256af6391 fixing resistance set also with inclination 2025-06-24 11:35:22 +02:00
Roberto Viola
c844276d86 fix erg mode 2025-06-24 11:15:55 +02:00
Roberto Viola
71648a6305 removing x86 from the build 2025-06-24 11:04:50 +02:00
Roberto Viola
a9b60bb193 rpm for bike 2025-06-24 11:03:13 +02:00
Roberto Viola
7f4f652a5d Update bluetooth.cpp 2025-06-23 14:46:27 +02:00
Roberto Viola
1cd106b026 target wattage fix 2025-06-23 14:17:16 +02:00
Roberto Viola
fc00fbf9cc redeced classes 2025-06-23 12:10:18 +02:00
Roberto Viola
2120ff6f6a target watt added 2025-06-23 11:27:03 +02:00
Roberto Viola
bd92a66e09 Update GrpcTreadmillService.java 2025-06-23 11:15:53 +02:00
Roberto Viola
ed45eac44a fixing watts and cadence 2025-06-23 11:08:09 +02:00
Roberto Viola
4d667e9ba4 Update nordictrackifitadbtreadmill.cpp 2025-06-21 08:59:14 +02:00
Roberto Viola
0b5c2745b7 Update bluetooth.cpp 2025-06-21 08:19:43 +02:00
Roberto Viola
3436a6e43c Update bluetooth.cpp 2025-06-21 07:38:32 +02:00
Roberto Viola
e8e64e040a Update bluetooth.cpp 2025-06-20 20:35:40 +02:00
Roberto Viola
11c6f3b52c Update nordictrackifitadbtreadmill.cpp 2025-06-20 20:01:27 +02:00
Roberto Viola
0b126a0aae auto resistance for bike 2025-06-20 20:00:46 +02:00
Roberto Viola
bfe296c3a3 permission and settings 2025-06-20 19:42:37 +02:00
Roberto Viola
7f474580a2 Update GrpcTreadmillService.java 2025-06-20 07:54:52 +02:00
Roberto Viola
828bb350d0 static functions for java class 2025-06-20 07:07:09 +02:00
Roberto Viola
4532b05e7e Merge branch 'nordictrack-build-grpc' of https://github.com/cagnulein/qdomyos-zwift into nordictrack-build-grpc 2025-06-20 06:52:15 +02:00
Roberto Viola
277d1d7390 Revert "adding java from android studio"
This reverts commit e37a6b28d6.
2025-06-20 06:52:02 +02:00
Roberto Viola
cd4e6b0335 Revert "java files"
This reverts commit 0b20087da6.
2025-06-20 06:51:58 +02:00
Roberto Viola
57f929a3bf adding QLog 2025-06-19 16:54:35 +02:00
Roberto Viola
9728af939e removing OCR 2025-06-19 16:53:32 +02:00
Roberto Viola
a3c4916ded Update proguard-rules.pro 2025-06-18 20:39:31 +02:00
Roberto Viola
e5b5ba1e1e Update build.gradle 2025-06-18 20:38:55 +02:00
Roberto Viola
326ea8c2a2 Update build.gradle 2025-06-18 19:54:59 +02:00
Roberto Viola
c9c8e2ce16 remove protobuf modules 2025-06-18 08:02:25 +02:00
Roberto Viola
8f6930709c Update build.gradle 2025-06-17 16:36:21 +02:00
Roberto Viola
a6c66ab9ee Update build.gradle 2025-06-17 15:41:27 +02:00
Roberto Viola
9f42d6a6ac Update build.gradle 2025-06-17 15:04:10 +02:00
Roberto Viola
0b20087da6 java files 2025-06-17 14:22:46 +02:00
Roberto Viola
e37a6b28d6 adding java from android studio 2025-06-17 13:46:16 +02:00
Roberto Viola
26c89b0d80 Reapply "removing firebase dir"
This reverts commit 0ff3fb3651.
2025-06-17 13:39:45 +02:00
Roberto Viola
7c8e411374 Revert "adding back java files"
This reverts commit fa2ff41e4e.
2025-06-17 13:39:43 +02:00
Roberto Viola
a31bf49121 Revert "Update qdomyos-zwift.pri"
This reverts commit c2ec6a9a9b.
2025-06-17 13:39:39 +02:00
Roberto Viola
c2ec6a9a9b Update qdomyos-zwift.pri 2025-06-17 13:34:16 +02:00
Roberto Viola
fa2ff41e4e adding back java files 2025-06-17 13:29:17 +02:00
Roberto Viola
0ff3fb3651 Revert "removing firebase dir"
This reverts commit df58ff226f.
2025-06-17 13:27:07 +02:00
Roberto Viola
39cc4f75f4 Update build.gradle 2025-06-17 13:06:51 +02:00
Roberto Viola
8b6ce6fa9d proguard 2025-06-17 12:32:04 +02:00
Roberto Viola
f84ec511ad Update build.gradle 2025-06-17 12:25:48 +02:00
Roberto Viola
019264c6c0 Revert "Update build.gradle"
This reverts commit b4a9369a43.
2025-06-17 12:24:58 +02:00
Roberto Viola
b4a9369a43 Update build.gradle 2025-06-17 09:18:53 +02:00
Roberto Viola
d1bd43ea2b manifest collision 2025-06-17 04:02:33 +00:00
Roberto Viola
21e7b0b1ce trying comparing the build.gradle.kts 2025-06-17 03:08:33 +00:00
Roberto Viola
6b85ba1d3a protobuf to 3.25.3 2025-06-17 03:03:24 +00:00
Roberto Viola
99eb5c5f57 Update build.gradle 2025-06-16 16:34:45 +02:00
Roberto Viola
59f9d0a553 Update nordictrackifitadbbike.cpp 2025-06-16 15:45:24 +02:00
Roberto Viola
9d3039d748 Update build.gradle 2025-06-16 15:09:23 +02:00
Roberto Viola
249e0191fb Update build.gradle 2025-06-16 14:31:48 +02:00
Roberto Viola
7a4861f265 Update build.gradle 2025-06-16 13:54:00 +02:00
Roberto Viola
f85e1fd39e Merge branch 'master' into nordictrack-build-grpc 2025-06-16 13:00:20 +02:00
Roberto Viola
a2ba9c69f7 Update build.gradle 2025-06-16 12:15:36 +02:00
Roberto Viola
df58ff226f removing firebase dir 2025-06-16 11:32:42 +02:00
Roberto Viola
bd95b67e06 adding missing modules in the gradle 2025-06-16 10:48:34 +02:00
Roberto Viola
f1d1929846 first implementation of the java service 2025-06-16 09:50:08 +02:00
Roberto Viola
fa4bdb2a6b adding java files and restoring the build.gradle 2025-06-16 08:57:21 +02:00
Roberto Viola
a84b57f1d9 Update build.gradle 2025-06-13 14:58:40 +02:00
Roberto Viola
cc86e26eac Update build.gradle 2025-06-13 13:44:28 +02:00
Roberto Viola
87a1e125ca Update build.gradle 2025-06-13 13:44:02 +02:00
Roberto Viola
6bdf6170c3 Update build.gradle 2025-06-13 11:30:40 +02:00
Roberto Viola
7369623dfd Update build.gradle 2025-06-13 10:43:20 +02:00
Roberto Viola
a00ddc5890 Update build.gradle 2025-06-13 08:46:14 +02:00
Roberto Viola
74fbfcda63 Update build.gradle 2025-06-12 15:34:13 +02:00
Roberto Viola
22b5ba6a02 Update build.gradle 2025-06-12 15:02:51 +02:00
Roberto Viola
49bdea89a3 Update build.gradle 2025-06-12 13:13:39 +02:00
Roberto Viola
42c9d170c3 Update build.gradle 2025-06-12 11:00:52 +02:00
Roberto Viola
89896c5ee9 Update build.gradle 2025-06-12 09:06:19 +02:00
Roberto Viola
1c9044a66d Update build.gradle 2025-06-12 08:06:29 +02:00
Roberto Viola
eb573d1029 Update build.gradle 2025-06-11 15:59:12 +02:00
Roberto Viola
29a93eb315 Update build.gradle 2025-06-11 13:41:55 +02:00
Roberto Viola
54bc585323 Update build.gradle 2025-06-11 11:06:56 +02:00
Roberto Viola
f5b26776d2 Update build.gradle 2025-06-11 09:13:37 +02:00
Roberto Viola
fccf1f2073 Update build.gradle 2025-06-10 16:18:59 +02:00
Roberto Viola
6e9093bc3c Update build.gradle 2025-06-10 15:36:50 +02:00
Roberto Viola
df37d4f2a6 Update main.yml 2025-06-10 13:08:10 +02:00
Roberto Viola
d9dbe5db20 Update main.yml 2025-06-10 11:09:26 +02:00
Roberto Viola
dd1c0c1cb0 Update main.yml 2025-06-10 09:19:34 +02:00
Roberto Viola
b8c0a560bf Update main.yml 2025-06-10 08:19:36 +02:00
Roberto Viola
89b62c8b6d Update build.gradle 2025-06-09 16:41:29 +02:00
Roberto Viola
70ea4bfc24 moving protos 2025-06-09 15:03:28 +02:00
Roberto Viola
00c9d28af0 adding proto dir in the gradle 2025-06-09 14:22:03 +02:00
Roberto Viola
752f3aaf19 adding files 2025-06-09 13:17:17 +02:00
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
353 changed files with 10148 additions and 14410 deletions

View File

@@ -21,7 +21,7 @@ on:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
window-build:
runs-on: windows-2022
runs-on: windows-latest
strategy:
matrix:
config:
@@ -37,6 +37,14 @@ jobs:
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -109,7 +117,7 @@ jobs:
- name: Build qthttpserver
run: |
cd src\qthttpserver
cd src\qthttpserver
qmake
make -j8
make install
@@ -124,8 +132,6 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
@@ -264,6 +270,13 @@ jobs:
# path: "src/smtpclient/"
# ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
#
# - uses: actions/checkout@v2
# - name: Checkout submodule repo
# uses: actions/checkout@v2
# with:
# repository: cagnulein/qmdnsengine
# path: "src/qmdnsengine/"
# ref: "zwift"
#
# - uses: msys2/setup-msys2@v2
# with:
@@ -372,6 +385,14 @@ jobs:
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -395,7 +416,7 @@ jobs:
with:
version: '5.15.2'
host: 'linux'
modules: 'qtnetworkauth qtcharts'
modules: 'qtnetworkauth qtcharts qtsql'
cache: 'true'
cache-key-prefix: 'install-qt-action-linux'
@@ -428,16 +449,7 @@ jobs:
if: failure()
with:
name: test_results_xml
path: tst/test-results/**/*.xml
- name: Upload test FIT files and database
uses: actions/upload-artifact@v4
if: always()
with:
name: test_fit_files_and_db
path: |
tst/test-artifacts/*.fit
tst/test-artifacts/*.sqlite
path: tst/test-results/**/*.xml
# - name: Test Peloton API
# if: github.event_name == 'push' || github.event_name == 'schedule'
@@ -532,6 +544,12 @@ jobs:
git submodule init
git submodule update --init --recursive
- name: Fix qmdnsengine submodule
run: |
cd src/qmdnsengine
git fetch
git checkout 602da51dc43c55bd9aa8a83c47ea3594a9b01b98
- 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
@@ -604,11 +622,9 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
cd ..
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
@@ -630,8 +646,8 @@ jobs:
- name: Archive apk binary
uses: actions/upload-artifact@v4
with:
name: fdroid-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk
name: peloton-v1-grupetto
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
android-emulator-test:
runs-on: ubuntu-latest
@@ -639,7 +655,7 @@ jobs:
strategy:
fail-fast: false
matrix:
api-level: [24, 26, 28, 29, 30, 31, 33, 35, 36]
api-level: [24, 26, 28, 29, 30, 31, 33, 34, 35, 36]
include:
- api-level: 24
target: default
@@ -669,6 +685,10 @@ jobs:
target: google_apis
arch: x86_64
android-version: "Android 13"
- api-level: 34
target: google_apis
arch: x86_64
android-version: "Android 14"
- api-level: 35
target: google_apis
arch: x86_64
@@ -692,7 +712,7 @@ jobs:
- name: Download APK Artifact
uses: actions/download-artifact@v4
with:
name: fdroid-android-trial
name: peloton-v1-grupetto
path: apk-debug
- name: Setup Java for Android Emulator
@@ -761,7 +781,7 @@ jobs:
adb shell am start -n org.cagnulen.qdomyoszwift/org.cagnulen.qdomyoszwift.CustomQtActivity
# Wait for app to start
sleep 90
sleep 60
# Verify the app is running
echo "Checking if app is running..."
@@ -822,7 +842,7 @@ jobs:
ios-build:
# The type of runner that the job will run on
runs-on: macos-14
runs-on: macos-latest
permissions:
contents: write
@@ -836,6 +856,14 @@ jobs:
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -875,11 +903,9 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
qmake CONFIG+=debug CONFIG+=iphonesimulator && make -j4
qmake CONFIG+=debug && make -j4
# causes iOS build on Mac to fail
# - name: Commit moc files
@@ -906,6 +932,14 @@ jobs:
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -980,8 +1014,6 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
@@ -1103,6 +1135,14 @@ jobs:
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
@@ -1161,11 +1201,9 @@ jobs:
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
cd ..
- name: Clone vcpkg
run: git clone https://github.com/microsoft/vcpkg.git
@@ -1259,8 +1297,6 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
@@ -1271,8 +1307,8 @@ jobs:
args: >
bash -c "
set -ex &&
for i in 1 2 3; do apt-get update && break || sleep 5; done &&
for i in 1 2 3; do apt-get install -y --fix-missing build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql && break || sleep 5; done &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
@@ -1320,8 +1356,6 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
@@ -1332,8 +1366,8 @@ jobs:
args: >
bash -c "
set -ex &&
for i in 1 2 3; do apt-get update && break || sleep 5; done &&
for i in 1 2 3; do apt-get install -y --fix-missing build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql && break || sleep 5; done &&
apt-get update &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
@@ -1429,8 +1463,6 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
cd ..
@@ -1545,20 +1577,7 @@ jobs:
nordictrack-build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
strategy:
matrix:
variant:
- name: treadmill
setting_key: nordictrack_2950_ip
setting_value: localhost
- name: bike
setting_key: tdf_10_ip
setting_value: localhost
- name: rower
setting_key: proform_rower_ip
setting_value: localhost
if: github.event_name == 'schedule'
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -1579,6 +1598,12 @@ jobs:
git submodule init
git submodule update --init --recursive
- name: Fix qmdnsengine submodule
run: |
cd src/qmdnsengine
git fetch
git checkout 602da51dc43c55bd9aa8a83c47ea3594a9b01b98
- 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
@@ -1622,12 +1647,8 @@ jobs:
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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
# Set variant-specific IP setting
sed -i 's/property string ${{ matrix.variant.setting_key }}: ""/property string ${{ matrix.variant.setting_key }}: "${{ matrix.variant.setting_value }}"/' settings.qml
cd ..
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
@@ -1641,232 +1662,31 @@ jobs:
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
cp src/android-qdomyos-zwift-deployment-settings.json src/android-qdomyos-zwift-nordictrack-${{ matrix.variant.name }}-deployment-settings.json
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-nordictrack-${{ matrix.variant.name }}-deployment-settings.json
sed -i 's/"android-debug"/"android-nordictrack-${{ matrix.variant.name }}"/g' src/android-qdomyos-zwift-nordictrack-${{ matrix.variant.name }}-deployment-settings.json
sed -i 's/android-debug\.apk/android-debug-nordictrack-${{ matrix.variant.name }}.apk/g' src/android-qdomyos-zwift-nordictrack-${{ matrix.variant.name }}-deployment-settings.json
cat src/android-qdomyos-zwift-nordictrack-${{ matrix.variant.name }}-deployment-settings.json
cp src/android-qdomyos-zwift-deployment-settings.json src/android-qdomyos-zwift-nordictrack-deployment-settings.json
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-nordictrack-deployment-settings.json
sed -i 's/"android-debug"/"android-nordictrack"/g' src/android-qdomyos-zwift-nordictrack-deployment-settings.json
sed -i 's/android-debug\.apk/android-debug-nordictrack.apk/g' src/android-qdomyos-zwift-nordictrack-deployment-settings.json
cat src/android-qdomyos-zwift-nordictrack-deployment-settings.json
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-nordictrack-${{ matrix.variant.name }}-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab; mv ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-nordictrack-${{ matrix.variant.name }}.apk
run: cd src; androiddeployqt --input android-qdomyos-zwift-nordictrack-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab; mv ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-nordictrack.apk
- name: Archive nordictrack binary
uses: actions/upload-artifact@v4
with:
name: nordictrack-${{ matrix.variant.name }}-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-nordictrack-${{ matrix.variant.name }}.apk
peloton-bike-plus-build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Xvfb install and run
run: |
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
- name: Checkout PR code
uses: actions/checkout@v3
with:
ref: refs/pull/3632/head
token: ${{ secrets.GITHUB_TOKEN }}
submodules: 'false' # Prima disattiva il checkout automatico dei submodule
- name: Checkout submodules with specific branches
run: |
git submodule init
git submodule update --init --recursive
- 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: Install Qt Android
uses: jdpurcell/install-qt-action@v5
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.0.23+9'
- 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 PELOTON_SECRET_KEY ${{ secrets.peloton_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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> 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
cp src/android-qdomyos-zwift-deployment-settings.json src/android-qdomyos-zwift-peloton-bike-plus-deployment-settings.json
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-peloton-bike-plus-deployment-settings.json
sed -i 's/"android-debug"/"android-peloton-bike-plus"/g' src/android-qdomyos-zwift-peloton-bike-plus-deployment-settings.json
sed -i 's/android-debug\.apk/android-debug-peloton-bike-plus.apk/g' src/android-qdomyos-zwift-peloton-bike-plus-deployment-settings.json
cat src/android-qdomyos-zwift-peloton-bike-plus-deployment-settings.json
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-peloton-bike-plus-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab; mv ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-peloton-bike-plus.apk
- name: Archive peloton-bike-plus binary
uses: actions/upload-artifact@v4
with:
name: peloton-bike-plus-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-peloton-bike-plus.apk
peloton-bike-build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Xvfb install and run
run: |
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
- name: Checkout PR code
uses: actions/checkout@v3
with:
ref: refs/pull/3639/head
token: ${{ secrets.GITHUB_TOKEN }}
submodules: 'false' # Prima disattiva il checkout automatico dei submodule
- name: Checkout submodules with specific branches
run: |
git submodule init
git submodule update --init --recursive
- 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: Install Qt Android
uses: jdpurcell/install-qt-action@v5
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.0.23+9'
- 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 PELOTON_SECRET_KEY ${{ secrets.peloton_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 "#define INTERVALSICU_CLIENT_ID ${{ secrets.intervalsicu_client_id }}" >> secret.h
echo "#define INTERVALSICU_CLIENT_SECRET ${{ secrets.intervalsicu_client_secret }}" >> 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
cp src/android-qdomyos-zwift-deployment-settings.json src/android-qdomyos-zwift-peloton-bike-deployment-settings.json
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-peloton-bike-deployment-settings.json
sed -i 's/"android-debug"/"android-peloton-bike"/g' src/android-qdomyos-zwift-peloton-bike-deployment-settings.json
sed -i 's/android-debug\.apk/android-debug-peloton-bike.apk/g' src/android-qdomyos-zwift-peloton-bike-deployment-settings.json
cat src/android-qdomyos-zwift-peloton-bike-deployment-settings.json
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-peloton-bike-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab; mv ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-peloton-bike.apk
- name: Archive peloton-bike binary
uses: actions/upload-artifact@v4
with:
name: peloton-bike-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-peloton-bike.apk
name: nordictrack-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-nordictrack.apk
upload_to_release:
permissions: write-all
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
needs: [window-msvc2019-build, window-msvc2022-build, window-build, android-build, raspberry-pi-build, nordictrack-build, peloton-bike-plus-build, peloton-bike-build, raspberry-pi-build-and-image-64bit]
#if: github.event_name == 'schedule'
if: always()
needs: [linux-x86-build, window-msvc2019-build, window-msvc2022-build, ios-build, window-build, android-build, raspberry-pi-build]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get current date
id: date
run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Download artifacts
uses: actions/download-artifact@v4
@@ -1875,9 +1695,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: nightly-${{ steps.date.outputs.date }}
tag_name: nightly
prerelease: false
name: 'QZ nightly build - ${{ steps.date.outputs.date }}'
name: 'QZ nightly build $$'
body: |
This is a nightly build of QZ.
@@ -1893,12 +1713,7 @@ jobs:
- **windows**: MinGW build (alternative version)
## Other Platforms:
- **fdroid-android-trial**: Android build
- **nordictrack-treadmill-android-trial**: Nordictrack Treadmill build for iFIT2 Tablets
- **nordictrack-bike-android-trial**: Nordictrack Bike build for iFIT2 Tablets
- **nordictrack-rower-android-trial**: Nordictrack Rower build for iFIT2 Tablets
- **peloton-bike-plus-android-trial**: Peloton Bike Plus build with Grupetto backend
- **peloton-bike-android-trial**: Peloton Bike build with Grupetto backend
- **peloton-v1-grupetto**: Android build with Peloton v1 Grupetto callback-based integration
- **raspberry-pi-binary**: Raspberry Pi build
__Please help us improve QZ by reporting any issues you encounter!__ :wink:
@@ -1910,11 +1725,6 @@ jobs:
windows-msvc2019-ai-server-binary/*
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/android-debug.apk
nordictrack-treadmill-android-trial/android-debug-nordictrack-treadmill.apk
nordictrack-bike-android-trial/android-debug-nordictrack-bike.apk
nordictrack-rower-android-trial/android-debug-nordictrack-rower.apk
peloton-bike-plus-android-trial/android-debug-peloton-bike-plus.apk
peloton-bike-android-trial/android-debug-peloton-bike.apk
peloton-v1-grupetto/*
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-binary/qdomyos-zwift-64bit

4
.gitmodules vendored
View File

@@ -5,6 +5,10 @@
path = src/smtpclient
url = https://github.com/cagnulein/SmtpClient-for-Qt.git
branch = cagnulein-patch-2
[submodule "src/qmdnsengine"]
path = src/qmdnsengine
url = https://github.com/cagnulein/qmdnsengine.git
branch = zwift
[submodule "tst/googletest"]
path = tst/googletest
url = https://github.com/google/googletest.git

View File

@@ -370,5 +370,7 @@ The ProForm 995i implementation serves as the reference example:
## Additional Memories
- When adding a new setting in QML (setting-tiles.qml), you must:
* Add the property at the END of the properties list
- When adding a new setting in QML (settings.qml), you must:
* Add the property at the END of the properties list (before the closing brace)
* NEVER add properties in the middle of the properties list
* This applies to ALL QML settings properties, not just setting-tiles.qml

View File

@@ -304,6 +304,7 @@
87646C2227B5065100F82131 /* moc_bhfitnesselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87646C2127B5065100F82131 /* moc_bhfitnesselliptical.cpp */; };
8767CA552DA3C1FD0003001F /* elitesquarecontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA532DA3C1FD0003001F /* elitesquarecontroller.cpp */; };
8767CA562DA3C1FD0003001F /* moc_elitesquarecontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA542DA3C1FD0003001F /* moc_elitesquarecontroller.cpp */; };
8767CA5D2DA7F5170003001F /* ios_wahookickrsnapbike.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */; };
8767CA602DA800590003001F /* ios_zwiftclickremote.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */; };
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */; };
8768C8BA2BBC11C80099DBE1 /* file_sync_client.c in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8768C89C2BBC11C70099DBE1 /* file_sync_client.c */; };
@@ -374,6 +375,7 @@
8772A0E625E43ADB0080718C /* trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */; };
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */; };
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */; };
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877350F62D1C08E50070CBD8 /* SmartControl.cpp */; };
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008129E876F7008E48B7 /* iconceptelliptical.cpp */; };
8775008529E87713008E48B7 /* moc_iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */; };
@@ -401,6 +403,7 @@
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */; };
8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */; };
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */; };
878895DB2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878895DA2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
@@ -459,8 +462,6 @@
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A4B75F25AF27CB0027EF3C /* metric.cpp */; };
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */; };
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */; };
87A892562F0C12EB00811D95 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A892552F0C12EB00811D95 /* deerruntreadmill.cpp */; };
87A892582F0C173600811D95 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A892572F0C173600811D95 /* sportsplusrower.cpp */; };
87ACBE9E2E250F7D00F1B6EA /* moc_androidstatusbar.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ACBE9D2E250F7D00F1B6EA /* moc_androidstatusbar.cpp */; };
87ACBE9F2E250F7D00F1B6EA /* androidstatusbar.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ACBE9C2E250F7D00F1B6EA /* androidstatusbar.cpp */; };
87ADD2BB27634C1500B7A0AB /* technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */; };
@@ -1272,6 +1273,8 @@
8767CA522DA3C1FD0003001F /* elitesquarecontroller.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = elitesquarecontroller.h; path = ../src/devices/elitesquarecontroller/elitesquarecontroller.h; sourceTree = SOURCE_ROOT; };
8767CA532DA3C1FD0003001F /* elitesquarecontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = elitesquarecontroller.cpp; path = ../src/devices/elitesquarecontroller/elitesquarecontroller.cpp; sourceTree = SOURCE_ROOT; };
8767CA542DA3C1FD0003001F /* moc_elitesquarecontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_elitesquarecontroller.cpp; sourceTree = "<group>"; };
8767CA5B2DA7F5170003001F /* ios_wahookickrsnapbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_wahookickrsnapbike.h; path = ../src/ios/ios_wahookickrsnapbike.h; sourceTree = SOURCE_ROOT; };
8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_wahookickrsnapbike.mm; path = ../src/ios/ios_wahookickrsnapbike.mm; sourceTree = SOURCE_ROOT; };
8767CA5E2DA800590003001F /* ios_zwiftclickremote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_zwiftclickremote.h; path = ../src/ios/ios_zwiftclickremote.h; sourceTree = SOURCE_ROOT; };
8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_zwiftclickremote.mm; path = ../src/ios/ios_zwiftclickremote.mm; sourceTree = SOURCE_ROOT; };
8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessor.cpp; path = ../src/characteristics/characteristicwriteprocessor.cpp; sourceTree = "<group>"; };
@@ -1385,6 +1388,7 @@
8772A0E525E43ADA0080718C /* trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbbike.cpp; path = ../src/devices/trxappgateusbbike/trxappgateusbbike.cpp; sourceTree = "<group>"; };
8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbbike.cpp; sourceTree = "<group>"; };
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_deerruntreadmill.cpp; sourceTree = "<group>"; };
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deerruntreadmill.cpp; sourceTree = "<group>"; };
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = deerruntreadmill.h; path = ../src/devices/deeruntreadmill/deerruntreadmill.h; sourceTree = SOURCE_ROOT; };
877350F52D1C08E50070CBD8 /* SmartControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SmartControl.h; path = ../src/devices/kineticinroadbike/SmartControl.h; sourceTree = SOURCE_ROOT; };
877350F62D1C08E50070CBD8 /* SmartControl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SmartControl.cpp; path = ../src/devices/kineticinroadbike/SmartControl.cpp; sourceTree = SOURCE_ROOT; };
@@ -1426,6 +1430,7 @@
8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_PlayerStateWrapper.cpp; sourceTree = "<group>"; };
8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_zwift_client_auth.cpp; sourceTree = "<group>"; };
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF22D09AADF005F86B4 /* sportsplusrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportsplusrower.h; path = ../src/devices/sportsplusrower/sportsplusrower.h; sourceTree = SOURCE_ROOT; };
878895D92DD48AB100BF5162 /* inclinationresistancetable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = inclinationresistancetable.h; path = ../src/inclinationresistancetable.h; sourceTree = SOURCE_ROOT; };
878895DA2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inclinationresistancetable.cpp; sourceTree = "<group>"; };
@@ -1520,8 +1525,6 @@
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sramAXSController.cpp; sourceTree = "<group>"; };
87A6825B2CE3AB4000586A2A /* sramAXSController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sramAXSController.h; path = ../src/devices/sramAXSController/sramAXSController.h; sourceTree = SOURCE_ROOT; };
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sramAXSController.cpp; path = ../src/devices/sramAXSController/sramAXSController.cpp; sourceTree = SOURCE_ROOT; };
87A892552F0C12EB00811D95 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = deerruntreadmill.cpp; path = ../src/devices/deeruntreadmill/deerruntreadmill.cpp; sourceTree = SOURCE_ROOT; };
87A892572F0C173600811D95 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sportsplusrower.cpp; path = ../src/devices/sportsplusrower/sportsplusrower.cpp; sourceTree = SOURCE_ROOT; };
87ACBE9B2E250F7D00F1B6EA /* androidstatusbar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = androidstatusbar.h; path = ../src/androidstatusbar.h; sourceTree = SOURCE_ROOT; };
87ACBE9C2E250F7D00F1B6EA /* androidstatusbar.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = androidstatusbar.cpp; path = ../src/androidstatusbar.cpp; sourceTree = SOURCE_ROOT; };
87ACBE9D2E250F7D00F1B6EA /* moc_androidstatusbar.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_androidstatusbar.cpp; sourceTree = "<group>"; };
@@ -2335,8 +2338,6 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87A892572F0C173600811D95 /* sportsplusrower.cpp */,
87A892552F0C12EB00811D95 /* deerruntreadmill.cpp */,
87CBCF0F2EFAA2F8004F5ECE /* garminconnect.h */,
87CBCF102EFAA2F8004F5ECE /* garminconnect.cpp */,
87CBCF112EFAA2F8004F5ECE /* moc_garminconnect.cpp */,
@@ -2373,6 +2374,8 @@
87F1BD702DC0D59600416506 /* moc_coresensor.cpp */,
8767CA5E2DA800590003001F /* ios_zwiftclickremote.h */,
8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */,
8767CA5B2DA7F5170003001F /* ios_wahookickrsnapbike.h */,
8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */,
87BFEA2D2CEDDEEE00BDD759 /* ios_echelonconnectsport.h */,
87BFEA2E2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm */,
8767CA522DA3C1FD0003001F /* elitesquarecontroller.h */,
@@ -2415,6 +2418,7 @@
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */,
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */,
87873AF22D09AADF005F86B4 /* sportsplusrower.h */,
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */,
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */,
870A5DB42CEF8FD200839641 /* technogymbike.cpp */,
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */,
@@ -2460,6 +2464,7 @@
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */,
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */,
877758B52C98629B00BB1697 /* sportstechelliptical.cpp */,
877758B42C98629B00BB1697 /* sportstechelliptical.h */,
@@ -3737,6 +3742,7 @@
871235BF26B297670012D0F2 /* kingsmithr1protreadmill.cpp in Compile Sources */,
87DA62AA2D2305F5008ADA0F /* characteristicwriteprocessor0003.cpp in Compile Sources */,
87DA62AB2D2305F5008ADA0F /* characteristicnotifier0002.cpp in Compile Sources */,
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */,
20A50533946A39CBD2C89104 /* bluetoothdevice.cpp in Compile Sources */,
87C5F0D126285E7E0067A1B5 /* moc_stagesbike.cpp in Compile Sources */,
873824E927E647A8004F1B46 /* mdns.cpp in Compile Sources */,
@@ -3846,6 +3852,7 @@
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */,
A4BD6DF51CFFF867B7B5AED4 /* fit_developer_field_definition.cpp in Compile Sources */,
87EB918B27EE5FE7002535E1 /* moc_inappproductqmltype.cpp in Compile Sources */,
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */,
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */,
3015F9B9FF4CA6D653D46CCA /* fit_developer_field_description.cpp in Compile Sources */,
878521D42E44B26600922796 /* moc_nordictrackifitadbrower.cpp in Compile Sources */,
@@ -3861,7 +3868,6 @@
87DC27F32D9BDC43007A1B9D /* moc_moxy5sensor.cpp in Compile Sources */,
87DC27F42D9BDC43007A1B9D /* moxy5sensor.cpp in Compile Sources */,
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
87A892582F0C173600811D95 /* sportsplusrower.cpp in Compile Sources */,
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */,
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */,
873CD22327EF8E18000131BC /* inappstoreqmltype.cpp in Compile Sources */,
@@ -3889,6 +3895,7 @@
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */,
87DA8465284933D200B550E9 /* fakeelliptical.cpp in Compile Sources */,
876E50F52B701C050080FAAF /* moc_zwiftclickremote.cpp in Compile Sources */,
8767CA5D2DA7F5170003001F /* ios_wahookickrsnapbike.mm in Compile Sources */,
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */,
8718CBAC263063CE004BF4EE /* moc_tcpclientinfosender.cpp in Compile Sources */,
873824B527E64707004F1B46 /* moc_provider_p.cpp in Compile Sources */,
@@ -4143,7 +4150,6 @@
E8B499F921FB0AB55C7A8A8B /* moc_gpx.cpp in Compile Sources */,
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */,
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */,
87A892562F0C12EB00811D95 /* deerruntreadmill.cpp in Compile Sources */,
878D83742A1F33C600D7F004 /* bkoolbike.cpp in Compile Sources */,
873824B727E64707004F1B46 /* moc_characteristicwriteprocessor.cpp in Compile Sources */,
87310B1F266FBB59008BA0D6 /* homefitnessbuddy.cpp in Compile Sources */,
@@ -4573,7 +4579,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4774,7 +4780,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -5011,7 +5017,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -5107,7 +5113,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5199,7 +5205,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5315,7 +5321,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5425,7 +5431,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -5516,7 +5522,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = QZWidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1274;
CURRENT_PROJECT_VERSION = 1241;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_NS_ASSERTIONS = NO;

View File

@@ -10,6 +10,6 @@ INCLUDEPATH += $$PWD/src/qmdnsengine/src/include
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/src/android
ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64
ANDROID_ABIS = arm64-v8a
#QMAKE_CXXFLAGS += -Werror=suggest-override

Binary file not shown.

View File

@@ -6,10 +6,9 @@
//
#import <Foundation/Foundation.h>
#import "IQConstants.h"
#import "IQDevice.h"
#import "IQApp.h"
#import <ConnectIQ/IQConstants.h>
#import <ConnectIQ/IQDevice.h>
#import <ConnectIQ/IQApp.h>
// --------------------------------------------------------------------------------
#pragma mark - PUBLIC TYPES
@@ -50,22 +49,9 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// @brief Called by the ConnectIQ SDK when an IQDevice's connection status has
/// changed.
///
/// When the device status is updated to ``IQDeviceStatus.IQDeviceStatus_Connected``
/// it does not mean the device services and characteristics have been discovered yet. To wait
/// till the services and characteristics to be discovered the client app has to wait on the delegate call
/// ``deviceCharacteristicsDiscovered:(IQDevice *)``. After that the client
/// app can start communicating with the device. The method ``deviceCharacteristicsDiscovered:``
/// was added to keep backwards compatibility for ``IQDeviceStatus``.
///
/// @param device The IQDevice whose status changed.
/// @param status The new status of the device.
- (void)deviceStatusChanged:(IQDevice *)device status:(IQDeviceStatus)status;
/// @brief Called by the ConnectIQ SDK when an IQDevice's charactersitics are discovered.
/// When this method is called the device is ready for communication with the client app.
///
/// @param device The IQDevice whose characteristics are discovered.
- (void)deviceCharacteristicsDiscovered:(IQDevice *)device;
@end
/// @brief Conforming to the IQAppMessageDelegate protocol indicates that an
@@ -102,11 +88,8 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
#pragma mark - INITIALIZATION
// --------------------------------------------------------------------------------
/// @brief Initializes the ConnectIQ SDK for use with a URL Scheme. See also
/// - (void)initializeWithUrlScheme:(NSString *)urlScheme
/// uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
/// stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// for comparison.
/// @brief Initializes the ConnectIQ SDK with startup parameters necessary for
/// its operation.
///
/// @param urlScheme The URL scheme for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
@@ -116,60 +99,6 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// is nil, the SDK's default UI will be used.
- (void)initializeWithUrlScheme:(NSString *)urlScheme uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate;
/// @brief Initializes the ConnectIQ SDK for use with a URL Scheme.
///
/// @param urlScheme The URL scheme for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this scheme.
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
/// @param restorationIdentifier The string which will be used as the value for
/// CBCentralManagerOptionRestoreIdentifierKey for the internal CBCentralManager.
/// The benefit of adding this identifier is that it allows the app to relaunch in the background
/// when BLE activity is detected on associated devices after being suspended by iOS. The SDK
/// does not currently handle the resulting call to willRestoreState because most CIQ companion apps
/// will reconnect to devices they are interested in during app launch.
- (void)initializeWithUrlScheme:(NSString *)urlScheme
uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// @brief Initializes the ConnectIQ SDK for use with Universal links. See also
/// - (void)initializeWithUniversalLinks:(NSString *)urlHost
/// uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
/// stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// for comparison.
///
/// @param urlHost The URL host for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this host. The host URL shall be added
/// to associated domains list and shall have an entry in apple-app-site-association
/// JSON file hosted on the same domain to be able to launch the companion app
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
- (void)initializeWithUniversalLinks:(NSString *)urlHost uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate;
/// @brief Initializes the ConnectIQ SDK for use with Universal links.
///
/// @param urlHost The URL host for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this host. The host URL shall be added
/// to associated domains list and shall have an entry in apple-app-site-association
/// JSON file hosted on the same domain to be able to launch the companion app
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
/// @param restorationIdentifier The string which will be used as the value for
/// CBCentralManagerOptionRestoreIdentifierKey for the internal CBCentralManager.
/// The benefit of adding this identifier is that it allows the app to relaunch in the background
/// when BLE activity is detected on associated devices after being suspended by iOS. The SDK
/// does not currently handle the resulting call to willRestoreState because most CIQ companion apps
/// will reconnect to devices they are interested in during app launch.
- (void)initializeWithUniversalLinks:(NSString *)urlHost
uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
stateRestorationIdentifier:(NSString *) restorationIdentifier;
// --------------------------------------------------------------------------------
#pragma mark - EXTERNAL LAUNCHING
// --------------------------------------------------------------------------------
@@ -295,21 +224,6 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// message operation is complete.
- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion;
/// @brief Begins sending a message to an app while allowing the message to be marked as transient. This method returns immediately.
///
/// @param message The message to send to the app. This message must be one of
/// the following types: NSString, NSNumber, NSNull, NSArray,
/// or NSDictionary. Arrays and dictionaries may be nested.
/// @param app The app to send the message to.
/// @param progress A progress block that will be triggered periodically
/// throughout the transfer. This is guaranteed to be triggered
/// at least once.
/// @param completion A completion block that will be triggered when the send
/// message operation is complete.
/// @param isTransient Flag to mark the message as transient.
- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress
completion:(IQSendMessageCompletion)completion isTransient:(BOOL)isTransient;
/// @brief Sends an open app request message request to the device. This method returns immediately.
///
/// @param app The app to open.

View File

@@ -6,9 +6,8 @@
//
#import <Foundation/Foundation.h>
#import "IQDevice.h"
#import "IQAppStatus.h"
#import <ConnectIQ/IQDevice.h>
#import <ConnectIQ/IQAppStatus.h>
/// @brief Represents an instance of a ConnectIQ app that is installed on a
/// Garmin device.

View File

@@ -42,9 +42,6 @@ typedef NS_ENUM(NSInteger, IQDeviceStatus){
/// Garmin Connect Mobile.
@property (nonatomic, readonly) NSString *friendlyName;
/// @brief The part number of the device per the Garmin catalog of devices.
@property (nonatomic, readonly) NSString *partNumber;
/// @brief Creates a new device instance.
///
/// @param uuid The UUID of the device to create.
@@ -54,17 +51,6 @@ typedef NS_ENUM(NSInteger, IQDeviceStatus){
/// @return A new IQDevice instance with the appropriate values set.
+ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName;
/// @brief Creates a new device instance with part number included.
///
/// @param uuid The UUID of the device to create.
/// @param modelName The model name of the device to create.
/// @param friendlyName The friendly name of the device to create.
/// @param partNumber The part number of the device to create.
///
/// @return A new IQDevice instance with the appropriate values set.
+ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName
partNumber:(NSString *)partNumber;
/// @brief Creates a new device instance by copying another device's values.
///
/// @param device The device to copy values from.

View File

@@ -6,10 +6,9 @@
//
#import <Foundation/Foundation.h>
#import "IQConstants.h"
#import "IQDevice.h"
#import "IQApp.h"
#import <ConnectIQ/IQConstants.h>
#import <ConnectIQ/IQDevice.h>
#import <ConnectIQ/IQApp.h>
// --------------------------------------------------------------------------------
#pragma mark - PUBLIC TYPES
@@ -50,22 +49,9 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// @brief Called by the ConnectIQ SDK when an IQDevice's connection status has
/// changed.
///
/// When the device status is updated to ``IQDeviceStatus.IQDeviceStatus_Connected``
/// it does not mean the device services and characteristics have been discovered yet. To wait
/// till the services and characteristics to be discovered the client app has to wait on the delegate call
/// ``deviceCharacteristicsDiscovered:(IQDevice *)``. After that the client
/// app can start communicating with the device. The method ``deviceCharacteristicsDiscovered:``
/// was added to keep backwards compatibility for ``IQDeviceStatus``.
///
/// @param device The IQDevice whose status changed.
/// @param status The new status of the device.
- (void)deviceStatusChanged:(IQDevice *)device status:(IQDeviceStatus)status;
/// @brief Called by the ConnectIQ SDK when an IQDevice's charactersitics are discovered.
/// When this method is called the device is ready for communication with the client app.
///
/// @param device The IQDevice whose characteristics are discovered.
- (void)deviceCharacteristicsDiscovered:(IQDevice *)device;
@end
/// @brief Conforming to the IQAppMessageDelegate protocol indicates that an
@@ -102,11 +88,8 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
#pragma mark - INITIALIZATION
// --------------------------------------------------------------------------------
/// @brief Initializes the ConnectIQ SDK for use with a URL Scheme. See also
/// - (void)initializeWithUrlScheme:(NSString *)urlScheme
/// uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
/// stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// for comparison.
/// @brief Initializes the ConnectIQ SDK with startup parameters necessary for
/// its operation.
///
/// @param urlScheme The URL scheme for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
@@ -116,60 +99,6 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// is nil, the SDK's default UI will be used.
- (void)initializeWithUrlScheme:(NSString *)urlScheme uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate;
/// @brief Initializes the ConnectIQ SDK for use with a URL Scheme.
///
/// @param urlScheme The URL scheme for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this scheme.
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
/// @param restorationIdentifier The string which will be used as the value for
/// CBCentralManagerOptionRestoreIdentifierKey for the internal CBCentralManager.
/// The benefit of adding this identifier is that it allows the app to relaunch in the background
/// when BLE activity is detected on associated devices after being suspended by iOS. The SDK
/// does not currently handle the resulting call to willRestoreState because most CIQ companion apps
/// will reconnect to devices they are interested in during app launch.
- (void)initializeWithUrlScheme:(NSString *)urlScheme
uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// @brief Initializes the ConnectIQ SDK for use with Universal links. See also
/// - (void)initializeWithUniversalLinks:(NSString *)urlHost
/// uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
/// stateRestorationIdentifier:(NSString *) restorationIdentifier;
/// for comparison.
///
/// @param urlHost The URL host for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this host. The host URL shall be added
/// to associated domains list and shall have an entry in apple-app-site-association
/// JSON file hosted on the same domain to be able to launch the companion app
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
- (void)initializeWithUniversalLinks:(NSString *)urlHost uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate;
/// @brief Initializes the ConnectIQ SDK for use with Universal links.
///
/// @param urlHost The URL host for this companion app. When Garmin Connect
/// Mobile is launched, it will return to the companion app by
/// launching a URL with this host. The host URL shall be added
/// to associated domains list and shall have an entry in apple-app-site-association
/// JSON file hosted on the same domain to be able to launch the companion app
/// @param delegate The delegate that the SDK will use for notifying the
/// companion app about events that require user input. If this
/// is nil, the SDK's default UI will be used.
/// @param restorationIdentifier The string which will be used as the value for
/// CBCentralManagerOptionRestoreIdentifierKey for the internal CBCentralManager.
/// The benefit of adding this identifier is that it allows the app to relaunch in the background
/// when BLE activity is detected on associated devices after being suspended by iOS. The SDK
/// does not currently handle the resulting call to willRestoreState because most CIQ companion apps
/// will reconnect to devices they are interested in during app launch.
- (void)initializeWithUniversalLinks:(NSString *)urlHost
uiOverrideDelegate:(id<IQUIOverrideDelegate>)delegate
stateRestorationIdentifier:(NSString *) restorationIdentifier;
// --------------------------------------------------------------------------------
#pragma mark - EXTERNAL LAUNCHING
// --------------------------------------------------------------------------------
@@ -295,21 +224,6 @@ typedef void (^IQSendMessageCompletion)(IQSendMessageResult result);
/// message operation is complete.
- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress completion:(IQSendMessageCompletion)completion;
/// @brief Begins sending a message to an app while allowing the message to be marked as transient. This method returns immediately.
///
/// @param message The message to send to the app. This message must be one of
/// the following types: NSString, NSNumber, NSNull, NSArray,
/// or NSDictionary. Arrays and dictionaries may be nested.
/// @param app The app to send the message to.
/// @param progress A progress block that will be triggered periodically
/// throughout the transfer. This is guaranteed to be triggered
/// at least once.
/// @param completion A completion block that will be triggered when the send
/// message operation is complete.
/// @param isTransient Flag to mark the message as transient.
- (void)sendMessage:(id)message toApp:(IQApp *)app progress:(IQSendMessageProgress)progress
completion:(IQSendMessageCompletion)completion isTransient:(BOOL)isTransient;
/// @brief Sends an open app request message request to the device. This method returns immediately.
///
/// @param app The app to open.

View File

@@ -6,9 +6,8 @@
//
#import <Foundation/Foundation.h>
#import "IQDevice.h"
#import "IQAppStatus.h"
#import <ConnectIQ/IQDevice.h>
#import <ConnectIQ/IQAppStatus.h>
/// @brief Represents an instance of a ConnectIQ app that is installed on a
/// Garmin device.

View File

@@ -42,9 +42,6 @@ typedef NS_ENUM(NSInteger, IQDeviceStatus){
/// Garmin Connect Mobile.
@property (nonatomic, readonly) NSString *friendlyName;
/// @brief The part number of the device per the Garmin catalog of devices.
@property (nonatomic, readonly) NSString *partNumber;
/// @brief Creates a new device instance.
///
/// @param uuid The UUID of the device to create.
@@ -54,17 +51,6 @@ typedef NS_ENUM(NSInteger, IQDeviceStatus){
/// @return A new IQDevice instance with the appropriate values set.
+ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName;
/// @brief Creates a new device instance with part number included.
///
/// @param uuid The UUID of the device to create.
/// @param modelName The model name of the device to create.
/// @param friendlyName The friendly name of the device to create.
/// @param partNumber The part number of the device to create.
///
/// @return A new IQDevice instance with the appropriate values set.
+ (IQDevice *)deviceWithId:(NSUUID *)uuid modelName:(NSString *)modelName friendlyName:(NSString *)friendlyName
partNumber:(NSString *)partNumber;
/// @brief Creates a new device instance by copying another device's values.
///
/// @param device The device to copy values from.

View File

@@ -6,11 +6,11 @@
<dict>
<key>Headers/ConnectIQ.h</key>
<data>
oktDCwqbdQQg6rdcptAN5TGhUZs=
yih4e2KjbC/GqavxdCZ3xQ4mHmA=
</data>
<key>Headers/IQApp.h</key>
<data>
CMQ9wDp2PKaw9dRd8NBYpX9xkzE=
NDlj8k5C84UPFmD+qEMz2WcZloY=
</data>
<key>Headers/IQAppStatus.h</key>
<data>
@@ -22,11 +22,11 @@
</data>
<key>Headers/IQDevice.h</key>
<data>
a4hkgIut7ETtkOJXPkn/nGElEYg=
bl545C/cu0mw2KlRmzojKmHPom0=
</data>
<key>Info.plist</key>
<data>
LeO8CbXcC4FrKgyl2zDm7R7nOj0=
YUOCJU/YBLc4CRWV1z8JHDjCx8M=
</data>
<key>Modules/module.modulemap</key>
<data>
@@ -300,14 +300,14 @@
<dict>
<key>hash2</key>
<data>
E2QDme6rWC+CJc/kKtxIVSpPzbE4ArUwNagnLG6Nxis=
kAenemss8n98vVLi54JqBUtGwaL1/i+HSejFBZgawHA=
</data>
</dict>
<key>Headers/IQApp.h</key>
<dict>
<key>hash2</key>
<data>
KhyZorkoK2Qipuzee5aE5ENCarHR+Ni21GdxCV3FQ0s=
bSRRooQ0FKFr3BgrFolAnkU402889YFHrH+6EEca3cg=
</data>
</dict>
<key>Headers/IQAppStatus.h</key>
@@ -328,7 +328,7 @@
<dict>
<key>hash2</key>
<data>
Xx+4dhu0JD6w2pd9UMvLXukYVQfKzaLJhU0paDUQyls=
4N4+64IHeb9iBwyziNxo0SMuCM75ez9Em4UfmtgtTHA=
</data>
</dict>
<key>Modules/module.modulemap</key>

View File

@@ -246,7 +246,7 @@ ColumnLayout {
elevationGain = elevationGain + (pathController.geopath.coordinateAt(i).altitude - pathController.geopath.coordinateAt(i-1).altitude)
lines[i] = pathController.geopath.coordinateAt(i)
}
distance.text = "Distance " + pathController.distance.toFixed(1) + " km Elevation Gain: " + elevationGain.toFixed(1) + " meters"
distance.text = "Distance " + (pathController.geopath.length() / 1000.0).toFixed(1) + " km Elevation Gain: " + elevationGain.toFixed(1) + " meters"
return lines;
}

View File

@@ -0,0 +1,91 @@
import QtQuick 2.12
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
Dialog {
id: disclaimerDialog
modal: true
focus: true
closePolicy: Dialog.NoAutoClose
width: Math.min(parent.width * 0.9, 600)
height: Math.min(parent.height * 0.8, 500)
anchors.centerIn: parent
property bool wasShown: settings.grupetto_disclaimer_shown || false
Material.theme: Material.Dark
Material.accent: Material.Orange
header: Rectangle {
height: 60
color: Material.color(Material.Orange)
Text {
anchors.centerIn: parent
text: "Legal Disclaimer - Grupetto Integration"
font.pixelSize: 18
font.bold: true
color: "white"
}
}
ScrollView {
anchors.fill: parent
contentWidth: availableWidth
Text {
width: parent.width
wrapMode: Text.WordWrap
color: "white"
font.pixelSize: 14
lineHeight: 1.3
text: "IMPORTANT LEGAL NOTICE - THIRD-PARTY CODE DISCLAIMER\n\n" +
"This application incorporates code derived from the Grupetto project " +
"(https://github.com/spencerpayne/grupetto), which enables communication " +
"with Peloton fitness equipment sensors.\n\n" +
"LIABILITY DISCLAIMER:\n\n" +
"1. The Grupetto-derived code is provided \"AS IS\" without any warranties " +
"of any kind, either expressed or implied.\n\n" +
"2. The author of QDomyos-Zwift DISCLAIMS ALL RESPONSIBILITY AND LIABILITY " +
"for any damages, losses, or issues arising from the use of Grupetto-derived code, " +
"including but not limited to:\n" +
" • Equipment damage or malfunction\n" +
" • Data loss or corruption\n" +
" • Personal injury\n" +
" • Software crashes or instability\n" +
" • Unauthorized access to device systems\n\n" +
"3. Users assume full responsibility and risk when using features that rely " +
"on Grupetto-derived code for Peloton sensor integration.\n\n" +
"4. This disclaimer does not affect the warranty or liability for other " +
"parts of QDomyos-Zwift not derived from Grupetto.\n\n" +
"5. By clicking 'OK', you acknowledge that you have read, understood, " +
"and agree to this disclaimer.\n\n" +
"ATTRIBUTION:\n" +
"Portions of this software are derived from Grupetto, developed by Spencer Payne. " +
"Original project: https://github.com/spencerpayne/grupetto"
}
}
standardButtons: Dialog.Ok
onAccepted: {
settings.grupetto_disclaimer_shown = true
close()
}
Component.onCompleted: {
if (!wasShown) {
open()
}
}
}

View File

@@ -42,27 +42,11 @@ class PathController : public QObject {
void centerChanged() W_SIGNAL(centerChanged)
double distance() const {
return mDistance;
}
void setDistance(double distance) {
if (qFuzzyCompare(distance, mDistance)) {
return;
}
mDistance = distance;
emit distanceChanged();
}
void distanceChanged() W_SIGNAL(distanceChanged)
private : QGeoPath mGeoPath;
QGeoCoordinate mCenter;
double mDistance = 0.0;
W_PROPERTY(QGeoPath, geopath READ geoPath WRITE setGeoPath NOTIFY geopathChanged)
W_PROPERTY(QGeoCoordinate, center READ center WRITE setCenter NOTIFY centerChanged)
W_PROPERTY(double, distance READ distance WRITE setDistance NOTIFY distanceChanged)
};
#endif // APPLICATION_PATHCONTROLLER_H

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.23" android:versionCode="1264" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.21" android:versionCode="1240" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
@@ -140,4 +140,8 @@
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<!-- Peloton sensor integration permissions (based on Grupetto analysis) -->
<uses-permission android:name="onepeloton.permission.ACCESS_SENSOR_SERVICE" />
<uses-permission android:name="onepeloton.permission.SUBSCRIPTION_TYPE_ACCESS" />
</manifest>

View File

@@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDeTCCAmGgAwIBAgIUbbOvLluQ8WhwXEL54Z4s9/T3BO4wDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTIz
MTAyNTIxMDkzM1oXDTMzMTAyMjIxMDkzM1owVjELMAkGA1UEBhMCQVUxEzARBgNV
BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
ZDEPMA0GA1UEAwwGdGVzdGNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAySGlxDrbSL9U1N65oYCNpnlXgFgY/uZViJ1wPN92xbsiYCKV5VBEKhA6fKh9
K9+VvMqxNycXMpXhXj4YI2hP6MktnOGkz/7RA5lKQGu7fCY/1tutGECfKmKhudWn
kvDgPJPxZr1mwqQjuFVSVcV0e763lGE/QdrdsndHjIjJOB5nZ1Q67Ga6tkXQYjtb
A6fw0LiZ9xJB/dpZ90wVIfaP22tFVgBBkFvnb91+/fA9dNsjtCRVgzz/qdoQbWF0
WMP8PE9jlA0x0cmd+yP6MIQaTqf1j3XSiLvPph/4DeWjcpA3R6Xh515iVRbAXrfO
tl5p44mjQYUpOxcZmrl7szGOqwIDAQABoz8wPTAMBgNVHRMEBTADAQH/MA4GA1Ud
DwEB/wQEAwICBDAdBgNVHQ4EFgQUpbZ5I+JmUaNH8Idzi8j4D9PiepkwDQYJKoZI
hvcNAQELBQADggEBAK+9zI1R56gAXv1bHsb6lQrMHHkWdY/xtiDBrTGC9WssKcx3
Lfzy9ajzb7T0tVwus2qfM1QUFD53WqusYpA969r3t17/J+7esIyld6193g3aPS5r
STrCn8LOmJ+GDgMWU57a2KFNgi3LxtZQeXP1wP10bBWZ8TbYZ5Z5rKbLsnVdc7su
gvdg/cH5XQol2jiA1QT076yiUereNkQHNnQW/XuPL30p11Lwzvm0mtBp7lohGZK3
zshpXndf741pjdjkUU0OJ/ZhJJycZs6j9xBvElZcFiPiA7S3fuE9APSHaXiTb/AZ
4ypwTg9TrqpWG/foB8OdtRe0nbpdOyVPZVC1kSk=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgIUXcT0gdvvszPRFgr0N1RpnEpZqkgwDQYJKoZIhvcNAQEL
BQAwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAwwGdGVzdGNhMB4XDTI0
MDcwMzE5MDc1NVoXDTM0MDcwMTE5MDc1NVowGzEZMBcGA1UEAwwQY29tLmlmaXQu
ZXJpYWRvcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALP7690AqHha
e2HQ+Adr6awIb9ebJd5g46Feu+WF4XAFOQGvVihXEOHANpriN1c6Rz6xuEPRTtZR
B+Wt82ajHPi/tWwfImWGvQd0wdqOs+hcdR5Hxg15CxHHFGvGdFWZumO3gSm62mvo
yBUlZX2RpZ0ZJYFuy8Z1GZQmiym4peQZpCNi8YLzKZQNefBXfqLra6/W9vwN35zt
UW82jMT4VQEWRU7PgF7U1Svbu8fja4cK5mh8JX/vESXXkIUOlLAonjCNJ+0eh+5k
+HEd3sxeKdb4bAkB4UixtUWSf4kzkqzRufwwC/0Mry3UE8byL8J+Bk5L4H5AT3Rl
sBMGPeYeWzMCAwEAAaNCMEAwHQYDVR0OBBYEFOZ3xbUHLCiCbX//Qj87HlmYhbvL
MB8GA1UdIwQYMBaAFKW2eSPiZlGjR/CHc4vI+A/T4nqZMA0GCSqGSIb3DQEBCwUA
A4IBAQBXjaqAEgOtaGkmmQLus0sNItE6hJH7r58tmHF19iQGcXnOaMYxyF36i9M2
rFBinybQUJ68A74Uz/R7YdOJxcOonSXC1A5/8mUJmlUAQmp+mkdgU68P/pZ1uxUV
tyHd+u+J6CUN1qJfmeb0dq532cVJD0TUK8/NbmySpvhsKpVFCIEnUh4DQinkvgAk
zheN/qabNwBYflUQOc9Ce5BPYYIGJM96KMofN0ZqbDjjtqgPqvq4SvDBAjvaof9y
4Wjiz2TTJCWwmE1/MnRs0N56j147BvTX+9r5k90CESWUv3sCiHYtTN81LcL01DxS
mBpnVS9EDui2Lm4FslkSkerCnTfa
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCz++vdAKh4Wnth
0PgHa+msCG/XmyXeYOOhXrvlheFwBTkBr1YoVxDhwDaa4jdXOkc+sbhD0U7WUQfl
rfNmoxz4v7VsHyJlhr0HdMHajrPoXHUeR8YNeQsRxxRrxnRVmbpjt4Eputpr6MgV
JWV9kaWdGSWBbsvGdRmUJospuKXkGaQjYvGC8ymUDXnwV36i62uv1vb8Dd+c7VFv
NozE+FUBFkVOz4Be1NUr27vH42uHCuZofCV/7xEl15CFDpSwKJ4wjSftHofuZPhx
Hd7MXinW+GwJAeFIsbVFkn+JM5Ks0bn8MAv9DK8t1BPG8i/CfgZOS+B+QE90ZbAT
Bj3mHlszAgMBAAECggEAUN959Cw/hxThK+rCCFOtA+gmmTLVqT7QCcqPk2q9CaDP
JLqsdCPrKgU8hAvx4fgF213v9kkuq45thf7Lx+qzMfKyiorS4dvRRHBqStKkdFxX
I+wMSjGBj9NskaDy1SPmZLgoCaA0VRicDyRmni27xQNvnuEyH1Ku06seDPkzUXKS
+7YtDdHjuh7rfZdN9phkwcM4qJ7ScElr+WP5DL42AhuL7e0bu8EYCZNrgdV826p+
/I8eRu4LNYEZ/XnNhKt6I+Qovlq2dLgb0cyMFqOUjPp2CDRkJFIC9E2Llg3AUOnX
jJCvBdNkXIh/PsUHx2C7pxg7cUuNvyqnUP/dyxSbgQKBgQDgpTxKEPnit412huRB
6J7XbcQHJWypzm2634rIguAKdf+lPFmBcAAVQAJ0mkzX0K9a+6xAlyimrjrMFwVn
WndFL9N8KKOsGPryDBMiUtCwROwYjZNQ4ToTMwtOB1Ih1+e6hWLqJWM38nlp1RW7
R0qpcYeRoqnl+sirw08DOoh+6wKBgQDNGuX4J1wWs049Kmq4v0BPUacMqq5T7Y1S
PgYn16A69lC2qW/cgAB2HAOoOBS+0i6GbQmF/tptN97XOD5an2c4vSQbKKqGkyYk
oXl46uqACJBMgR0WaergrcBKuKvnfURVpVNlG08+wsnEGb5apCiyIK4H+g68R/Qr
68jniWrS2QKBgQCv81u0W3WNiNzpICA6Kzv2Wgf23O4uVfwGKT6nbDKUnvV78zfb
tOCrxDXoJE7Znp8qMQMql/qECuUMo19dIzNV4m7PyXjgu7QZzzFRafIAjgsp9AGV
kMMO9KT/GabP0S60HfNql5wN3wIPzZE23VDyRHS9sd1Gv1Vbix8g1UDBvwKBgGBO
sg88xBPwq9sysJwBSbw09gCPoH3OPJ6Seyd4K0ekYy/yDZF3FUBgVSNG+g7D+I6s
Yl1l1sCUDHH4eebplHli7rJF/RRlwfJPVA+AFw55dvBFbBgbMevAClvLrQRsoIqq
r6b5FNO+eSk4gVZkYKuLhsw+EW89RhzdgR+fOea5AoGAPNa30OpFIRY1ViyAu+Nm
0bAKDHZXRajOSYzsSeJI7BjlNtRDNDJfcUjYtpJGk8SOFV2Y0IOIlN3GYCO1x/0V
G7U6EDAYYun+mlP91d8IHRAWcvIiZNuqP8IO2MZRen1jEOhTF9GKsrAdN+1moeB5
qziU9kATRT7PSCd0NhvhDXE=
-----END PRIVATE KEY-----

View File

@@ -26,11 +26,35 @@ apply plugin: 'com.google.protobuf'
def amazon = System.getenv('AMAZON')
println(amazon)
// FIXED: Force resolution con versioni consistenti
configurations.all {
resolutionStrategy {
// TUTTE le versioni devono essere consistenti
force 'com.google.protobuf:protobuf-javalite:3.25.3'
force 'io.grpc:grpc-okhttp:1.63.0'
force 'io.grpc:grpc-protobuf-lite:1.63.0'
force 'io.grpc:grpc-stub:1.63.0'
force 'io.grpc:grpc-core:1.63.0'
}
// Exclude full protobuf from ALL dependencies
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
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'
// Peloton sensor integration uses Android system service binding
// No additional external dependencies required beyond standard Android APIs
// FIXED: Una sola versione di protobuf-javalite
implementation 'com.google.protobuf:protobuf-javalite:3.25.3'
implementation 'io.grpc:grpc-okhttp:1.63.0'
implementation 'io.grpc:grpc-protobuf-lite:1.63.0'
implementation 'io.grpc:grpc-stub:1.63.0'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
if(amazon == "1") {
// amazon app store
@@ -47,12 +71,12 @@ dependencies {
implementation "com.android.billingclient:billing:8.0.0"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation files('libs/usb-serial-for-android-3.8.1.aar')
androidTestImplementation "com.android.support:support-annotations:28.0.0"
implementation 'com.google.android.gms:play-services-wearable:+'
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation files('libs/usb-serial-for-android-3.8.1.aar')
androidTestImplementation "com.android.support:support-annotations:28.0.0"
implementation 'com.google.android.gms:play-services-wearable:+'
implementation 'com.jakewharton.timber:timber:5.0.1'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.60'
@@ -66,7 +90,12 @@ def archSuffix = Os.isFamily(Os.FAMILY_MAC) ? ':osx-x86_64' : ''
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.1$archSuffix"
artifact = "com.google.protobuf:protoc:3.25.3$archSuffix"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.63.0"
}
}
generateProtoTasks {
all().configureEach { task ->
@@ -75,6 +104,11 @@ protobuf {
option "lite"
}
}
task.plugins {
grpc {
option "lite"
}
}
}
}
}
@@ -117,7 +151,7 @@ android {
lintOptions {
abortOnError false
checkReleaseBuilds false
checkReleaseBuilds false
}
// Do not compress Qt binary resources file
@@ -132,14 +166,43 @@ android {
targetSdkVersion = 36
}
tasks.all { task ->
if (task.name == 'compileDebugJavaWithJavac' && amazon == "1") {
task.dependsOn copyArm64Directory
task.dependsOn copyArm32Directory
}
}
}
// FIXED: Packaging options ottimizzato per gestire i conflitti protobuf
packagingOptions {
// EXCLUDE problematic META-INF files instead of pickFirst to avoid collisions
exclude 'META-INF/MANIFEST.MF'
exclude 'META-INF/INDEX.LIST'
exclude 'META-INF/io.netty.versions.properties'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/AL2.0'
exclude 'META-INF/LGPL2.1'
// Keep pickFirst only for files that are actually needed
pickFirst '**/META-INF/okio.kotlin_module'
pickFirst '**/META-INF/*.kotlin_module'
// CRITICAL: Handle duplicate protobuf classes - this is crucial for your error
pickFirst '**/com/google/protobuf/**'
// Handle native libraries
pickFirst '**/libprotobuf-lite.so'
pickFirst '**/libprotoc.so'
// Additional common conflicts
pickFirst '**/META-INF/services/**'
pickFirst '**/kotlin/**'
}
tasks.all { task ->
if (task.name == 'compileDebugJavaWithJavac' && amazon == "1") {
task.dependsOn copyArm64Directory
task.dependsOn copyArm32Directory
}
}
}
task copyArm64Directory(type: Copy) {
from "libs/arm64-v8a/"

0
src/android/proguard-rules.pro vendored Normal file
View File

View File

@@ -0,0 +1,239 @@
package org.cagnulen.qdomyoszwift;
import android.os.Parcel;
import android.os.Parcelable;
public class BikeData implements Parcelable {
public static final Creator<BikeData> CREATOR = new Creator<BikeData>() {
@Override // android.os.Parcelable.Creator
public BikeData createFromParcel(Parcel parcel) {
return new BikeData(parcel);
}
@Override // android.os.Parcelable.Creator
public BikeData[] newArray(int i) {
return new BikeData[i];
}
};
private long mRPM;
private long mPower;
private int mTargetResistance;
// For full compatibility, include all fields from original
private int mADValue;
private int mAppliedPositionOffset;
private String mBikeFrameSerial;
private int mCalibrationState;
private int mCurrentResistance;
private int mDataWriteCycle;
private String mDataWriteDate;
private String mDataWriteTime;
private int mEncoderAngle;
private int mError1Code;
private String mError1Time;
private int mError2Code;
private String mError2Time;
private int mError3Code;
private String mError3Time;
private int mError4Code;
private String mError4Time;
private int mError5Code;
private String mError5Time;
private int mErrorIndex;
private int[] mErrorMap;
private String mFWVersionNumber;
private String mHardwareVersion;
private int mLoadCellCalSpan;
private float mLoadCellOffset;
private long mLoadCellReading;
private String mLoadCellSerial;
private String mLoadCellTable;
private int mLoadCellTableCrc;
private int mLoadCellTableStatus;
private int mLoadCellTempCount;
private String mLoadCellVersion;
private int mLoadCellZeroData;
private String mPSerial;
private int mPZAFMaxResistanceSetPoint;
private int mPZAFMinUpdateRPM;
private int mPZAFRampDownRate;
private int mPZAFRampUpRate;
private byte[] mPacketData;
private String mPacketTime;
private int mPositionOffset;
private int mPowerZoneAutoFollowEnabled;
private int mPowerZoneAutoFollowPowerSetPoint;
private int mPowerZoneAutoFollowStatus;
private float mPowerZoneAutoFollowTargetResistance;
private String mQSerial;
private float mResistanceOffset;
private int mStallThreshold;
private int mStepperMotorEndPosition;
private long mStepperMotorPosition;
private int mStepperMotorStartPosition;
private int mSystemState;
private float mV1Resistance;
@Override // android.os.Parcelable
public int describeContents() {
return 0;
}
public long getRPM() {
return this.mRPM;
}
public long getPower() {
return this.mPower;
}
public int getTargetResistance() {
return this.mTargetResistance;
}
public int getCurrentResistance() {
return this.mCurrentResistance;
}
public void setRPM(long rpm) {
this.mRPM = rpm;
}
public void setPower(long power) {
this.mPower = power;
}
public void setTargetResistance(int resistance) {
this.mTargetResistance = resistance;
}
public void setCurrentResistance(int resistance) {
this.mCurrentResistance = resistance;
}
private BikeData(Parcel parcel) {
readFromParcel(parcel);
}
@Override // android.os.Parcelable
public void writeToParcel(Parcel parcel, int i) {
parcel.writeLong(this.mRPM);
parcel.writeLong(this.mPower);
parcel.writeLong(this.mStepperMotorPosition);
parcel.writeLong(this.mLoadCellReading);
parcel.writeInt(this.mCurrentResistance);
parcel.writeInt(this.mTargetResistance);
parcel.writeString(this.mFWVersionNumber);
parcel.writeByteArray(this.mPacketData);
parcel.writeString(this.mPacketTime);
parcel.writeInt(this.mStepperMotorStartPosition);
parcel.writeInt(this.mStepperMotorEndPosition);
parcel.writeInt(this.mCalibrationState);
parcel.writeInt(this.mEncoderAngle);
parcel.writeInt(this.mSystemState);
parcel.writeInt(this.mErrorIndex);
parcel.writeInt(this.mError1Code);
parcel.writeString(this.mError1Time);
parcel.writeInt(this.mError2Code);
parcel.writeString(this.mError2Time);
parcel.writeInt(this.mError3Code);
parcel.writeString(this.mError3Time);
parcel.writeInt(this.mError4Code);
parcel.writeString(this.mError4Time);
parcel.writeInt(this.mError5Code);
parcel.writeString(this.mError5Time);
parcel.writeIntArray(this.mErrorMap);
parcel.writeString(this.mLoadCellTable);
parcel.writeInt(this.mLoadCellTableCrc);
parcel.writeString(this.mPSerial);
parcel.writeString(this.mQSerial);
parcel.writeString(this.mBikeFrameSerial);
parcel.writeString(this.mLoadCellSerial);
parcel.writeFloat(this.mLoadCellOffset);
parcel.writeInt(this.mDataWriteCycle);
parcel.writeString(this.mDataWriteDate);
parcel.writeString(this.mDataWriteTime);
parcel.writeInt(this.mLoadCellZeroData);
parcel.writeInt(this.mLoadCellCalSpan);
parcel.writeInt(this.mLoadCellTempCount);
parcel.writeFloat(this.mResistanceOffset);
parcel.writeInt(this.mPositionOffset);
parcel.writeInt(this.mLoadCellTableStatus);
parcel.writeFloat(this.mV1Resistance);
parcel.writeString(this.mLoadCellVersion);
parcel.writeInt(this.mAppliedPositionOffset);
parcel.writeInt(this.mStallThreshold);
parcel.writeString(this.mHardwareVersion);
parcel.writeInt(this.mADValue);
parcel.writeInt(this.mPowerZoneAutoFollowEnabled);
parcel.writeInt(this.mPowerZoneAutoFollowPowerSetPoint);
parcel.writeFloat(this.mPowerZoneAutoFollowTargetResistance);
parcel.writeInt(this.mPowerZoneAutoFollowStatus);
parcel.writeInt(this.mPZAFRampUpRate);
parcel.writeInt(this.mPZAFRampDownRate);
parcel.writeInt(this.mPZAFMaxResistanceSetPoint);
parcel.writeInt(this.mPZAFMinUpdateRPM);
}
private void readFromParcel(Parcel parcel) {
this.mRPM = parcel.readLong();
this.mPower = parcel.readLong();
this.mStepperMotorPosition = parcel.readLong();
this.mLoadCellReading = parcel.readLong();
this.mCurrentResistance = parcel.readInt();
this.mTargetResistance = parcel.readInt();
this.mFWVersionNumber = parcel.readString();
this.mPacketData = parcel.createByteArray();
this.mPacketTime = parcel.readString();
this.mStepperMotorStartPosition = parcel.readInt();
this.mStepperMotorEndPosition = parcel.readInt();
this.mCalibrationState = parcel.readInt();
this.mEncoderAngle = parcel.readInt();
this.mSystemState = parcel.readInt();
this.mErrorIndex = parcel.readInt();
this.mError1Code = parcel.readInt();
this.mError1Time = parcel.readString();
this.mError2Code = parcel.readInt();
this.mError2Time = parcel.readString();
this.mError3Code = parcel.readInt();
this.mError3Time = parcel.readString();
this.mError4Code = parcel.readInt();
this.mError4Time = parcel.readString();
this.mError5Code = parcel.readInt();
this.mError5Time = parcel.readString();
int[] iArr = new int[15];
this.mErrorMap = iArr;
parcel.readIntArray(iArr);
this.mLoadCellTable = parcel.readString();
this.mLoadCellTableCrc = parcel.readInt();
this.mPSerial = parcel.readString();
this.mQSerial = parcel.readString();
this.mBikeFrameSerial = parcel.readString();
this.mLoadCellSerial = parcel.readString();
this.mLoadCellOffset = parcel.readFloat();
this.mDataWriteCycle = parcel.readInt();
this.mDataWriteDate = parcel.readString();
this.mDataWriteTime = parcel.readString();
this.mLoadCellZeroData = parcel.readInt();
this.mLoadCellCalSpan = parcel.readInt();
this.mLoadCellTempCount = parcel.readInt();
this.mResistanceOffset = parcel.readFloat();
this.mPositionOffset = parcel.readInt();
this.mLoadCellTableStatus = parcel.readInt();
this.mV1Resistance = parcel.readFloat();
this.mLoadCellVersion = parcel.readString();
this.mAppliedPositionOffset = parcel.readInt();
this.mStallThreshold = parcel.readInt();
this.mHardwareVersion = parcel.readString();
this.mADValue = parcel.readInt();
this.mPowerZoneAutoFollowEnabled = parcel.readInt();
this.mPowerZoneAutoFollowPowerSetPoint = parcel.readInt();
this.mPowerZoneAutoFollowTargetResistance = parcel.readFloat();
this.mPowerZoneAutoFollowStatus = parcel.readInt();
this.mPZAFRampUpRate = parcel.readInt();
this.mPZAFRampDownRate = parcel.readInt();
this.mPZAFMaxResistanceSetPoint = parcel.readInt();
this.mPZAFMinUpdateRPM = parcel.readInt();
}
}

View File

@@ -73,12 +73,6 @@ public class Garmin {
}
public static void init(Context c) {
if (connectIqReady || connectIqInitializing) {
QLog.d(TAG, "Garmin already initialized or initializing");
return;
}
connectIqInitializing = true;
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
@@ -90,7 +84,6 @@ public class Garmin {
@Override
public void onInitializeError(ConnectIQ.IQSdkErrorStatus errStatus) {
QLog.e(TAG, errStatus.toString());
connectIqInitializing = false;
connectIqReady = false;
}

View File

@@ -0,0 +1,863 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.okhttp.OkHttpChannelBuilder;
import io.grpc.stub.MetadataUtils;
import com.ifit.glassos.util.Empty;
import com.ifit.glassos.workout.SpeedMetric;
import com.ifit.glassos.workout.SpeedServiceGrpc;
import com.ifit.glassos.workout.SpeedRequest;
import com.ifit.glassos.workout.InclineMetric;
import com.ifit.glassos.workout.InclineServiceGrpc;
import com.ifit.glassos.workout.InclineRequest;
import com.ifit.glassos.workout.WattsMetric;
import com.ifit.glassos.workout.WattsServiceGrpc;
import com.ifit.glassos.console.constantwatts.ConstantWattsMessage;
import com.ifit.glassos.console.constantwatts.ConstantWattsServiceGrpc;
import com.ifit.glassos.workout.ResistanceMetric;
import com.ifit.glassos.workout.ResistanceServiceGrpc;
import com.ifit.glassos.workout.ResistanceRequest;
import com.ifit.glassos.workout.CadenceMetric;
import com.ifit.glassos.workout.CadenceServiceGrpc;
import com.ifit.glassos.workout.RpmMetric;
import com.ifit.glassos.workout.RpmServiceGrpc;
import com.ifit.glassos.settings.FanState;
import com.ifit.glassos.settings.FanStateMessage;
import com.ifit.glassos.settings.FanStateServiceGrpc;
import org.cagnulen.qdomyoszwift.QLog;
public class GrpcTreadmillService {
private static final String TAG = "GrpcTreadmillService";
// Singleton instance for static access
private static GrpcTreadmillService instance = null;
private static Context staticContext = null;
private static String serverHost = "localhost";
private static final int SERVER_PORT = 54321;
private static final int UPDATE_INTERVAL_MS = 500;
// Threading components
private Handler mainHandler;
private ExecutorService executorService;
private Runnable metricsUpdateRunnable;
// gRPC components
private ManagedChannel channel;
private SpeedServiceGrpc.SpeedServiceBlockingStub speedStub;
private InclineServiceGrpc.InclineServiceBlockingStub inclineStub;
private WattsServiceGrpc.WattsServiceBlockingStub wattsStub;
private ConstantWattsServiceGrpc.ConstantWattsServiceBlockingStub constantWattsStub;
private ResistanceServiceGrpc.ResistanceServiceBlockingStub resistanceStub;
private CadenceServiceGrpc.CadenceServiceBlockingStub cadenceStub;
private RpmServiceGrpc.RpmServiceBlockingStub rpmStub;
private FanStateServiceGrpc.FanStateServiceBlockingStub fanStub;
// Control flags and current values
private volatile boolean isUpdating = false;
private volatile double currentSpeed = 0.0;
private volatile double currentIncline = 0.0;
private volatile double currentResistance = 0.0;
private volatile double currentWatts = 0.0;
private volatile double currentCadence = 0.0;
private volatile double currentRpm = 0.0;
private volatile int currentFanSpeed = 0;
// Context for accessing assets
private Context context;
// Metrics listener interface
public interface MetricsListener {
void onSpeedUpdated(double speed);
void onInclineUpdated(double incline);
void onWattsUpdated(double watts);
void onResistanceUpdated(double resistance);
void onCadenceUpdated(double cadence);
void onRpmUpdated(double rpm);
void onFanSpeedUpdated(int fanSpeed);
void onError(String metric, String error);
}
private MetricsListener metricsListener;
public GrpcTreadmillService(Context context) {
this.context = context;
this.mainHandler = new Handler(Looper.getMainLooper());
this.executorService = Executors.newSingleThreadExecutor();
}
public void setMetricsListener(MetricsListener listener) {
this.metricsListener = listener;
}
private void initializeInstance() throws Exception {
initializeGrpcConnection();
}
private void startMetricsUpdatesInstance() {
if (isUpdating) return;
isUpdating = true;
metricsUpdateRunnable = new Runnable() {
@Override
public void run() {
if (!isUpdating) return;
executorService.execute(() -> {
fetchAllMetricsFromServer();
if (isUpdating) {
mainHandler.postDelayed(metricsUpdateRunnable, UPDATE_INTERVAL_MS);
}
});
}
};
mainHandler.post(metricsUpdateRunnable);
QLog.i(TAG, "Started periodic metrics updates");
}
private void stopMetricsUpdatesInstance() {
isUpdating = false;
if (metricsUpdateRunnable != null) {
mainHandler.removeCallbacks(metricsUpdateRunnable);
}
QLog.i(TAG, "Stopped periodic metrics updates");
}
private void adjustSpeedInstance(double delta) {
executorService.execute(() -> {
try {
double newSpeed = Math.max(0.0, currentSpeed + delta);
Metadata headers = createHeaders();
SpeedServiceGrpc.SpeedServiceBlockingStub stubWithHeaders = speedStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
SpeedRequest request = SpeedRequest.newBuilder().setKph(newSpeed).build();
stubWithHeaders.setSpeed(request);
QLog.d(TAG, String.format("Set speed to %.1f km/h", newSpeed));
} catch (Exception e) {
QLog.e(TAG, "Failed to set speed", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("speed", e.getMessage()));
}
}
});
}
private void adjustInclineInstance(double delta) {
executorService.execute(() -> {
try {
double newIncline = Math.max(-50.0, currentIncline + delta);
Metadata headers = createHeaders();
InclineServiceGrpc.InclineServiceBlockingStub stubWithHeaders = inclineStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
InclineRequest request = InclineRequest.newBuilder().setPercent(newIncline).build();
stubWithHeaders.setIncline(request);
QLog.d(TAG, String.format("Set incline to %.1f%%", newIncline));
} catch (Exception e) {
QLog.e(TAG, "Failed to set incline", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("incline", e.getMessage()));
}
}
});
}
private void adjustResistanceInstance(double delta) {
executorService.execute(() -> {
try {
double newResistance = Math.max(0.0, currentResistance + delta);
Metadata headers = createHeaders();
ResistanceServiceGrpc.ResistanceServiceBlockingStub stubWithHeaders = resistanceStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
ResistanceRequest request = ResistanceRequest.newBuilder().setResistance(newResistance).build();
stubWithHeaders.setResistance(request);
QLog.d(TAG, String.format("Set resistance to %.0f level", newResistance));
} catch (Exception e) {
QLog.e(TAG, "Failed to set resistance", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("resistance", e.getMessage()));
}
}
});
}
private void setWattsInstance(double watts) {
executorService.execute(() -> {
try {
Metadata headers = createHeaders();
ConstantWattsServiceGrpc.ConstantWattsServiceBlockingStub stubWithHeaders = constantWattsStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
if (watts <= 0) {
// Disable constant watts mode when watts is 0 or negative
stubWithHeaders.disable(Empty.newBuilder().build());
QLog.d(TAG, "Disabled constant watts mode");
} else {
// Set target watts
int targetWatts = (int) watts;
ConstantWattsMessage request = ConstantWattsMessage.newBuilder().setWatts(targetWatts).build();
stubWithHeaders.setConstantWatts(request);
QLog.d(TAG, String.format("Set constant watts to %d", targetWatts));
}
} catch (Exception e) {
QLog.e(TAG, "Failed to set watts", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("watts", e.getMessage()));
}
}
});
}
private void disableConstantWattsInstance() {
executorService.execute(() -> {
try {
Metadata headers = createHeaders();
ConstantWattsServiceGrpc.ConstantWattsServiceBlockingStub stubWithHeaders = constantWattsStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
stubWithHeaders.disable(Empty.newBuilder().build());
QLog.d(TAG, "Explicitly disabled constant watts mode");
} catch (Exception e) {
QLog.e(TAG, "Failed to disable constant watts", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("watts", e.getMessage()));
}
}
});
}
private void setFanSpeedInstance(int fanSpeed) {
executorService.execute(() -> {
try {
Metadata headers = createHeaders();
FanStateServiceGrpc.FanStateServiceBlockingStub stubWithHeaders = fanStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
FanState fanState;
switch (fanSpeed) {
case 0:
fanState = FanState.FAN_STATE_OFF;
break;
case 1:
fanState = FanState.FAN_STATE_LOW;
break;
case 2:
fanState = FanState.FAN_STATE_MEDIUM;
break;
case 3:
fanState = FanState.FAN_STATE_HIGH;
break;
case 4:
fanState = FanState.FAN_STATE_AUTO;
break;
default:
fanState = FanState.FAN_STATE_OFF;
break;
}
FanStateMessage request = FanStateMessage.newBuilder().setState(fanState).build();
stubWithHeaders.setFanState(request);
QLog.d(TAG, String.format("Set fan speed to %d (%s)", fanSpeed, fanState.name()));
} catch (Exception e) {
QLog.e(TAG, "Failed to set fan speed", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("fan", e.getMessage()));
}
}
});
}
private void shutdownInstance() {
stopMetricsUpdates();
if (channel != null) {
try {
channel.shutdown();
if (!channel.awaitTermination(5, TimeUnit.SECONDS)) {
channel.shutdownNow();
}
} catch (InterruptedException e) {
QLog.e(TAG, "Error shutting down gRPC channel", e);
channel.shutdownNow();
}
}
if (executorService != null) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
QLog.e(TAG, "Error shutting down executor service", e);
executorService.shutdownNow();
}
}
}
private void initializeGrpcConnection() throws Exception {
AssetManager assets = context.getAssets();
String[] requiredFiles = {"client_cert.pem", "client_key.pem"};
for (String file : requiredFiles) {
try {
assets.open(file).close();
} catch (Exception e) {
throw new RuntimeException("Required certificate file missing: " + file +
". Please add it to app/src/main/assets/");
}
}
InputStream caCertStream = null;
try {
caCertStream = assets.open("ca_cert.pem");
} catch (Exception e) {
QLog.w(TAG, "ca_cert.pem not found, continuing with insecure mode");
}
InputStream clientCertStream = assets.open("client_cert.pem");
InputStream clientKeyStream = assets.open("client_key.pem");
QLog.i(TAG, "Loading TLS certificates (insecure server validation mode)...");
SSLContext sslContext = createSSLContext(caCertStream, clientCertStream, clientKeyStream);
channel = OkHttpChannelBuilder.forAddress(serverHost, SERVER_PORT)
.sslSocketFactory(sslContext.getSocketFactory())
.build();
if (caCertStream != null) caCertStream.close();
clientCertStream.close();
clientKeyStream.close();
speedStub = SpeedServiceGrpc.newBlockingStub(channel);
inclineStub = InclineServiceGrpc.newBlockingStub(channel);
wattsStub = WattsServiceGrpc.newBlockingStub(channel);
constantWattsStub = ConstantWattsServiceGrpc.newBlockingStub(channel);
resistanceStub = ResistanceServiceGrpc.newBlockingStub(channel);
cadenceStub = CadenceServiceGrpc.newBlockingStub(channel);
rpmStub = RpmServiceGrpc.newBlockingStub(channel);
fanStub = FanStateServiceGrpc.newBlockingStub(channel);
QLog.i(TAG, "gRPC connection initialized with client certificates");
}
private SSLContext createSSLContext(InputStream caCertStream, InputStream clientCertStream,
InputStream clientKeyStream) throws Exception {
QLog.d(TAG, "Creating SSL context with client certificates (insecure server validation)...");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate clientCert = (X509Certificate) cf.generateCertificate(clientCertStream);
QLog.d(TAG, "Loaded client certificate: " + clientCert.getSubjectDN());
byte[] keyData = readAllBytesCompat(clientKeyStream);
String keyString = new String(keyData, StandardCharsets.UTF_8);
keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(keyString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
QLog.d(TAG, "Loaded private key");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setKeyEntry("client", privateKey, "".toCharArray(), new Certificate[]{clientCert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, "".toCharArray());
javax.net.ssl.TrustManager[] insecureTrustManagers = new javax.net.ssl.TrustManager[] {
new javax.net.ssl.X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
QLog.d(TAG, "Accepting server certificate without validation (insecure mode)");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), insecureTrustManagers, new SecureRandom());
QLog.i(TAG, "SSL context created with client authentication but insecure server validation");
return sslContext;
}
private byte[] readAllBytesCompat(InputStream inputStream) throws Exception {
byte[] buffer = new byte[8192];
int bytesRead;
java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
while ((bytesRead = inputStream.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
return output.toByteArray();
}
private Metadata createHeaders() {
Metadata headers = new Metadata();
headers.put(Metadata.Key.of("client_id", Metadata.ASCII_STRING_MARSHALLER),
"com.ifit.eriador");
return headers;
}
private void fetchAllMetricsFromServer() {
long startTime = System.currentTimeMillis();
try {
QLog.d(TAG, "Making gRPC calls for all metrics...");
long headersStartTime = System.currentTimeMillis();
Metadata headers = createHeaders();
Empty request = Empty.newBuilder().build();
long headersEndTime = System.currentTimeMillis();
QLog.d(TAG, "Headers creation took: " + (headersEndTime - headersStartTime) + "ms");
// Fetch speed
try {
long speedStartTime = System.currentTimeMillis();
SpeedServiceGrpc.SpeedServiceBlockingStub speedStubWithHeaders = speedStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long speedInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Speed interceptor setup took: " + (speedInterceptorTime - speedStartTime) + "ms");
SpeedMetric speedResponse = speedStubWithHeaders.getSpeed(request);
long speedCallTime = System.currentTimeMillis();
QLog.d(TAG, "Speed gRPC call took: " + (speedCallTime - speedInterceptorTime) + "ms");
currentSpeed = speedResponse.getLastKph();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onSpeedUpdated(currentSpeed));
}
long speedEndTime = System.currentTimeMillis();
QLog.d(TAG, "Speed total processing took: " + (speedEndTime - speedStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch speed", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("speed", "Error"));
}
}
// Fetch inclination
try {
long inclineStartTime = System.currentTimeMillis();
InclineServiceGrpc.InclineServiceBlockingStub inclineStubWithHeaders = inclineStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long inclineInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Incline interceptor setup took: " + (inclineInterceptorTime - inclineStartTime) + "ms");
InclineMetric inclineResponse = inclineStubWithHeaders.getIncline(request);
long inclineCallTime = System.currentTimeMillis();
QLog.d(TAG, "Incline gRPC call took: " + (inclineCallTime - inclineInterceptorTime) + "ms");
currentIncline = inclineResponse.getLastInclinePercent();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onInclineUpdated(currentIncline));
}
long inclineEndTime = System.currentTimeMillis();
QLog.d(TAG, "Incline total processing took: " + (inclineEndTime - inclineStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch inclination", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("inclination", "Error"));
}
}
// Fetch watts
try {
long wattsStartTime = System.currentTimeMillis();
WattsServiceGrpc.WattsServiceBlockingStub wattsStubWithHeaders = wattsStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long wattsInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Watts interceptor setup took: " + (wattsInterceptorTime - wattsStartTime) + "ms");
WattsMetric wattsResponse = wattsStubWithHeaders.getWatts(request);
long wattsCallTime = System.currentTimeMillis();
QLog.d(TAG, "Watts gRPC call took: " + (wattsCallTime - wattsInterceptorTime) + "ms");
currentWatts = wattsResponse.getLastWatts();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onWattsUpdated(currentWatts));
}
long wattsEndTime = System.currentTimeMillis();
QLog.d(TAG, "Watts total processing took: " + (wattsEndTime - wattsStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch watts", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("watts", "Error"));
}
}
// Fetch resistance
try {
long resistanceStartTime = System.currentTimeMillis();
ResistanceServiceGrpc.ResistanceServiceBlockingStub resistanceStubWithHeaders = resistanceStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long resistanceInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Resistance interceptor setup took: " + (resistanceInterceptorTime - resistanceStartTime) + "ms");
ResistanceMetric resistanceResponse = resistanceStubWithHeaders.getResistance(request);
long resistanceCallTime = System.currentTimeMillis();
QLog.d(TAG, "Resistance gRPC call took: " + (resistanceCallTime - resistanceInterceptorTime) + "ms");
currentResistance = resistanceResponse.getLastResistance();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onResistanceUpdated(currentResistance));
}
long resistanceEndTime = System.currentTimeMillis();
QLog.d(TAG, "Resistance total processing took: " + (resistanceEndTime - resistanceStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch resistance", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("resistance", "Error"));
}
}
// Fetch RPM (for bikes)
try {
long rpmStartTime = System.currentTimeMillis();
RpmServiceGrpc.RpmServiceBlockingStub rpmStubWithHeaders = rpmStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long rpmInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "RPM interceptor setup took: " + (rpmInterceptorTime - rpmStartTime) + "ms");
RpmMetric rpmResponse = rpmStubWithHeaders.getRpm(request);
long rpmCallTime = System.currentTimeMillis();
QLog.d(TAG, "RPM gRPC call took: " + (rpmCallTime - rpmInterceptorTime) + "ms");
currentRpm = rpmResponse.getLastRpm();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onRpmUpdated(currentRpm));
}
long rpmEndTime = System.currentTimeMillis();
QLog.d(TAG, "RPM total processing took: " + (rpmEndTime - rpmStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch RPM", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("rpm", "Error"));
}
}
// Fetch cadence (for treadmills)
try {
long cadenceStartTime = System.currentTimeMillis();
CadenceServiceGrpc.CadenceServiceBlockingStub cadenceStubWithHeaders = cadenceStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long cadenceInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Cadence interceptor setup took: " + (cadenceInterceptorTime - cadenceStartTime) + "ms");
CadenceMetric cadenceResponse = cadenceStubWithHeaders.getCadence(request);
long cadenceCallTime = System.currentTimeMillis();
QLog.d(TAG, "Cadence gRPC call took: " + (cadenceCallTime - cadenceInterceptorTime) + "ms");
currentCadence = cadenceResponse.getLastStepsPerMinute();
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onCadenceUpdated(currentCadence));
}
long cadenceEndTime = System.currentTimeMillis();
QLog.d(TAG, "Cadence total processing took: " + (cadenceEndTime - cadenceStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch cadence", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("cadence", "Error"));
}
}
// Fetch fan speed
/*
try {
long fanStartTime = System.currentTimeMillis();
FanStateServiceGrpc.FanStateServiceBlockingStub fanStubWithHeaders = fanStub.withInterceptors(
MetadataUtils.newAttachHeadersInterceptor(headers)
);
long fanInterceptorTime = System.currentTimeMillis();
QLog.d(TAG, "Fan interceptor setup took: " + (fanInterceptorTime - fanStartTime) + "ms");
FanStateMessage fanResponse = fanStubWithHeaders.getFanState(request);
long fanCallTime = System.currentTimeMillis();
QLog.d(TAG, "Fan gRPC call took: " + (fanCallTime - fanInterceptorTime) + "ms");
int fanSpeed;
switch (fanResponse.getState()) {
case FAN_STATE_OFF:
fanSpeed = 0;
break;
case FAN_STATE_LOW:
fanSpeed = 1;
break;
case FAN_STATE_MEDIUM:
fanSpeed = 2;
break;
case FAN_STATE_HIGH:
fanSpeed = 3;
break;
case FAN_STATE_AUTO:
fanSpeed = 4;
break;
default:
fanSpeed = 0;
break;
}
currentFanSpeed = fanSpeed;
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onFanSpeedUpdated(currentFanSpeed));
}
long fanEndTime = System.currentTimeMillis();
QLog.d(TAG, "Fan total processing took: " + (fanEndTime - fanStartTime) + "ms");
} catch (Exception e) {
QLog.w(TAG, "Failed to fetch fan speed", e);
if (metricsListener != null) {
mainHandler.post(() -> metricsListener.onError("fan", "Error"));
}
}
*/
long totalEndTime = System.currentTimeMillis();
long totalTime = totalEndTime - startTime;
QLog.d(TAG, "=== TIMING SUMMARY ===");
QLog.d(TAG, "Total fetchAllMetricsFromServer execution time: " + totalTime + "ms");
QLog.d(TAG, "Completed all metrics fetch");
} catch (Exception e) {
long totalEndTime = System.currentTimeMillis();
long totalTime = totalEndTime - startTime;
QLog.e(TAG, "Failed to fetch metrics after " + totalTime + "ms", e);
if (metricsListener != null) {
mainHandler.post(() -> {
metricsListener.onError("speed", "Error");
metricsListener.onError("inclination", "Error");
metricsListener.onError("watts", "Error");
metricsListener.onError("resistance", "Error");
metricsListener.onError("cadence", "Error");
});
}
}
}
// Static wrapper methods for JNI calls
public static void initialize() {
initialize("localhost");
}
public static void initialize(String host) {
try {
if (staticContext == null) {
QLog.e(TAG, "Context not set. Call setContext() first.");
return;
}
serverHost = host;
if (instance == null) {
instance = new GrpcTreadmillService(staticContext);
}
instance.initializeInstance();
QLog.i(TAG, "Static initialize completed with host: " + host);
} catch (Exception e) {
QLog.e(TAG, "Static initialize failed", e);
}
}
public static void setContext(Context context) {
staticContext = context;
}
public static void startMetricsUpdates() {
if (instance != null) {
instance.startMetricsUpdatesInstance();
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void stopMetricsUpdates() {
if (instance != null) {
instance.stopMetricsUpdatesInstance();
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static double getCurrentSpeed() {
if (instance != null) {
return instance.currentSpeed;
}
return 0.0;
}
public static double getCurrentIncline() {
if (instance != null) {
return instance.currentIncline;
}
return 0.0;
}
public static double getCurrentWatts() {
if (instance != null) {
return instance.currentWatts;
}
return 0.0;
}
public static double getCurrentCadence() {
if (instance != null) {
return instance.currentCadence;
}
return 0.0;
}
public static double getCurrentRpm() {
if (instance != null) {
return instance.currentRpm;
}
return 0.0;
}
public static int getCurrentFanSpeed() {
if (instance != null) {
return instance.currentFanSpeed;
}
return 0;
}
public static double getCurrentResistance() {
if (instance != null) {
return instance.currentResistance;
}
return 0.0;
}
public static void adjustSpeed(double delta) {
if (instance != null) {
instance.adjustSpeedInstance(delta);
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void adjustIncline(double delta) {
if (instance != null) {
instance.adjustInclineInstance(delta);
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void adjustResistance(double delta) {
if (instance != null) {
instance.adjustResistanceInstance(delta);
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void setWatts(double watts) {
if (instance != null) {
instance.setWattsInstance(watts);
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void disableConstantWatts() {
if (instance != null) {
instance.disableConstantWattsInstance();
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void setFanSpeed(int fanSpeed) {
if (instance != null) {
instance.setFanSpeedInstance(fanSpeed);
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void shutdown() {
if (instance != null) {
instance.shutdownInstance();
instance = null;
}
}
}

View File

@@ -0,0 +1,295 @@
package org.cagnulen.qdomyoszwift;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import org.cagnulen.qdomyoszwift.QLog;
/**
* Callback-based sensor implementation from Grupetto v1 develop
* Based on: https://github.com/selalipop/grupetto/pull/10
* More efficient than polling - receives data only when it changes
*/
public abstract class PelotonCallbackSensor {
private static final String TAG = "PelotonCallbackSensor";
// Transaction codes from Grupetto v1 CallbackSensor.kt
private static final int TRANSACTION_REGISTER_CALLBACK = 1;
private static final int TRANSACTION_UNREGISTER_CALLBACK = 2;
// Interface descriptors from Grupetto v1
private static final String IV1_INTERFACE = "com.onepeloton.affernetservice.IV1Interface";
private static final String IV1_CALLBACK_INTERFACE = "com.onepeloton.affernetservice.IV1Callback";
private IBinder binder;
private boolean isRegistered = false;
private PelotonCallbackBinder callbackBinder;
// Callback interface for receiving sensor data
public interface SensorDataCallback {
void onSensorDataReceived(float value);
void onSensorError(long errorCode);
}
private SensorDataCallback callback;
public PelotonCallbackSensor(IBinder binder) {
this.binder = binder;
this.callbackBinder = new PelotonCallbackBinder();
}
public void setCallback(SensorDataCallback callback) {
this.callback = callback;
}
public void start() throws RemoteException {
if (isRegistered) {
QLog.w(TAG, "Sensor already started");
return;
}
registerCallback();
isRegistered = true;
QLog.d(TAG, "Callback sensor started successfully");
}
public void stop() {
if (!isRegistered) {
return;
}
try {
unregisterCallback();
isRegistered = false;
QLog.d(TAG, "Callback sensor stopped successfully");
} catch (Exception e) {
QLog.e(TAG, "Failed to stop callback sensor", e);
}
}
private void registerCallback() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IV1_INTERFACE);
data.writeStrongBinder(callbackBinder);
data.writeString("QDomyos-Zwift"); // Identifier like Grupetto
QLog.d(TAG, "Registering callback with interface: " + IV1_INTERFACE);
boolean success = binder.transact(TRANSACTION_REGISTER_CALLBACK, data, reply, 0);
if (success) {
reply.readException();
QLog.i(TAG, "Successfully registered callback");
} else {
throw new RemoteException("Failed to register callback");
}
} finally {
data.recycle();
reply.recycle();
}
}
private void unregisterCallback() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IV1_INTERFACE);
data.writeStrongBinder(callbackBinder);
data.writeString("QDomyos-Zwift"); // Identifier like Grupetto
boolean success = binder.transact(TRANSACTION_UNREGISTER_CALLBACK, data, reply, 0);
if (success) {
reply.readException();
QLog.d(TAG, "Successfully unregistered callback");
}
} catch (Exception e) {
QLog.w(TAG, "Error unregistering callback", e);
} finally {
data.recycle();
reply.recycle();
}
}
/**
* Extract the specific sensor value from BikeData
* Override in subclasses for different sensor types
*/
protected abstract float extractValue(BikeData bikeData);
/**
* Apply sensor-specific value mapping
* Override in subclasses if needed
*/
protected float mapValue(float rawValue) {
return rawValue;
}
/**
* Binder implementation for receiving callbacks from Peloton service
*/
private class PelotonCallbackBinder extends android.os.Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
QLog.d(TAG, "Callback onTransact called with code: " + code);
switch (code) {
case 1: // onSensorDataChange
try {
data.enforceInterface(IV1_CALLBACK_INTERFACE);
QLog.d(TAG, "Interface enforced successfully");
int hasData = data.readInt();
QLog.d(TAG, "Has data flag: " + hasData);
if (hasData != 0) {
QLog.d(TAG, "Creating BikeData from parcel");
BikeData bikeData = BikeData.CREATOR.createFromParcel(data);
float rawValue = extractValue(bikeData);
float mappedValue = mapValue(rawValue);
if (callback != null) {
callback.onSensorDataReceived(mappedValue);
}
QLog.i(TAG, "Received sensor data: " + mappedValue);
} else {
QLog.d(TAG, "No bike data received");
}
return true;
} catch (Exception e) {
QLog.e(TAG, "Error processing sensor data", e);
return false;
}
case 2: // onSensorError
try {
data.enforceInterface(IV1_CALLBACK_INTERFACE);
long errorCode = data.readLong();
QLog.w(TAG, "Sensor error: " + errorCode);
if (callback != null) {
callback.onSensorError(errorCode);
}
return true;
} catch (Exception e) {
QLog.e(TAG, "Error processing sensor error", e);
return false;
}
case 3: // onCalibrationStatus
try {
data.enforceInterface(IV1_CALLBACK_INTERFACE);
int status = data.readInt();
boolean success = data.readInt() != 0;
long errorCode = data.readLong();
QLog.d(TAG, "Calibration status: status=" + status + " success=" + success + " error=" + errorCode);
return true;
} catch (Exception e) {
QLog.e(TAG, "Error processing calibration status", e);
return false;
}
default:
QLog.d(TAG, "Unknown transaction code: " + code + ", calling super");
return super.onTransact(code, data, reply, flags);
}
}
}
/**
* Power sensor implementation
*/
public static class PowerSensor extends PelotonCallbackSensor {
public PowerSensor(IBinder binder) {
super(binder);
}
@Override
protected float extractValue(BikeData bikeData) {
return (float) bikeData.getPower();
}
@Override
protected float mapValue(float rawValue) {
// From Grupetto v1: divide by 100 to normalize power values
float normalizedValue = rawValue / 100.0f;
// Filter out spurious readings
if (normalizedValue < 0 || normalizedValue > 1000) {
QLog.w(TAG, "Filtering spurious power reading: " + normalizedValue);
return 0.0f;
}
return normalizedValue;
}
}
/**
* RPM sensor implementation
*/
public static class RpmSensor extends PelotonCallbackSensor {
public RpmSensor(IBinder binder) {
super(binder);
}
@Override
protected float extractValue(BikeData bikeData) {
return (float) bikeData.getRPM();
}
}
/**
* Resistance sensor implementation with moving window filtering
*/
public static class ResistanceSensor extends PelotonCallbackSensor {
// Moving window for resistance filtering (from Grupetto approach)
private static final int FILTER_WINDOW_SIZE = 3;
private float[] resistanceWindow = new float[FILTER_WINDOW_SIZE];
private int windowIndex = 0;
private boolean windowFilled = false;
public ResistanceSensor(IBinder binder) {
super(binder);
}
@Override
protected float extractValue(BikeData bikeData) {
return (float) bikeData.getTargetResistance();
}
@Override
protected float mapValue(float rawValue) {
// Add value to moving window
resistanceWindow[windowIndex] = rawValue;
windowIndex = (windowIndex + 1) % FILTER_WINDOW_SIZE;
if (!windowFilled && windowIndex == 0) {
windowFilled = true;
}
// If window not full yet, return current value
if (!windowFilled) {
return rawValue;
}
// Return minimum value from window (Grupetto strategy for spike filtering)
float minValue = resistanceWindow[0];
for (int i = 1; i < FILTER_WINDOW_SIZE; i++) {
if (resistanceWindow[i] < minValue) {
minValue = resistanceWindow[i];
}
}
return minValue;
}
}
}

View File

@@ -0,0 +1,106 @@
package org.cagnulen.qdomyoszwift;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import org.cagnulen.qdomyoszwift.QLog;
/**
* Service binder for connecting to Peloton Grupetto v1 callback-based service
* Based on: https://github.com/selalipop/grupetto/pull/10
* More efficient than polling - receives data only when it changes
*/
public class PelotonSensorBinder {
private static final String TAG = "PelotonSensorBinder";
// Peloton service constants (from Grupetto v1 develop - callback-based)
private static final String SERVICE_ACTION = "com.onepeloton.affernetservice.IV1Interface";
private static final String SERVICE_PACKAGE = "com.onepeloton.affernetservice";
private static final String SERVICE_INTENT = "com.onepeloton.affernetservice.AffernetService";
// Using callback-based sensors from Grupetto v1 develop
// No transaction codes needed here - handled by PelotonCallbackSensor
private Context context;
private IBinder serviceBinder = null;
private boolean isConnected = false;
public PelotonSensorBinder(Context context) {
this.context = context;
}
/**
* Asynchronously connects to the Peloton sensor service
* Based on Grupetto's v1 Binder.kt implementation
*/
public CompletableFuture<IBinder> getBinder() {
CompletableFuture<IBinder> future = new CompletableFuture<>();
CountDownLatch connectionLatch = new CountDownLatch(1);
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
QLog.i(TAG, "V1 service connected: " + name.getClassName());
serviceBinder = service;
isConnected = true;
future.complete(service);
connectionLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
QLog.w(TAG, "V1 service disconnected: " + name.getClassName());
serviceBinder = null;
isConnected = false;
}
@Override
public void onBindingDied(ComponentName name) {
QLog.e(TAG, "V1 service binding died: " + name.getClassName());
serviceBinder = null;
isConnected = false;
if (!future.isDone()) {
future.completeExceptionally(new RuntimeException("V1 service binding died"));
}
}
@Override
public void onNullBinding(ComponentName name) {
QLog.i(TAG, "V1 service null binding: " + name.getClassName());
if (!future.isDone()) {
future.completeExceptionally(new RuntimeException("V1 service null binding"));
}
}
};
Intent intent = new Intent(SERVICE_INTENT);
intent.setAction(SERVICE_ACTION);
intent.setPackage(SERVICE_PACKAGE);
boolean bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
QLog.e(TAG, "Failed to bind to Peloton V1 sensor service");
future.completeExceptionally(new RuntimeException("Failed to bind to V1 service"));
return future;
}
QLog.i(TAG, "Binding to Peloton V1 sensor service...");
return future;
}
public boolean isConnected() {
return isConnected;
}
public IBinder getServiceBinder() {
return serviceBinder;
}
}

View File

@@ -0,0 +1,279 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.IBinder;
import android.util.Log;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.cagnulen.qdomyoszwift.QLog;
import org.cagnulen.qdomyoszwift.PelotonSensorBinder;
import org.cagnulen.qdomyoszwift.PelotonCallbackSensor;
/**
* Peloton sensor helper class using callback-based approach from Grupetto v1 develop
* Based on: https://github.com/selalipop/grupetto/pull/10
* More efficient than polling - receives data only when it changes
*/
public class PelotonSensorHelper {
private static final String TAG = "PelotonSensorHelper";
// Singleton instance for static access
private static PelotonSensorHelper instance = null;
private static Context staticContext = null;
// Threading components (reduced need with callback approach)
private Handler mainHandler;
private ExecutorService executorService;
// Sensor components (callback-based from Grupetto v1)
private PelotonSensorBinder sensorBinder;
private PelotonCallbackSensor.PowerSensor powerSensor;
private PelotonCallbackSensor.RpmSensor rpmSensor;
private PelotonCallbackSensor.ResistanceSensor resistanceSensor;
// Control flags and current values
private volatile boolean isInitialized = false;
private volatile boolean isUpdating = false;
private volatile float currentPower = 0.0f;
private volatile float currentCadence = 0.0f;
private volatile float currentResistance = 0.0f;
private volatile float currentSpeed = 0.0f;
// Context for accessing system services
private Context context;
public PelotonSensorHelper(Context context) {
this.context = context;
this.mainHandler = new Handler(Looper.getMainLooper());
this.executorService = Executors.newSingleThreadExecutor();
this.sensorBinder = new PelotonSensorBinder(context);
}
private void initializeInstance() throws Exception {
QLog.i(TAG, "Initializing Peloton sensor connection...");
// Get binder to Peloton service (async operation)
IBinder serviceBinder = sensorBinder.getBinder().get(10, TimeUnit.SECONDS);
if (serviceBinder == null) {
throw new Exception("Failed to get service binder");
}
// Initialize individual callback-based sensors
powerSensor = new PelotonCallbackSensor.PowerSensor(serviceBinder);
rpmSensor = new PelotonCallbackSensor.RpmSensor(serviceBinder);
resistanceSensor = new PelotonCallbackSensor.ResistanceSensor(serviceBinder);
// Set up callbacks to receive sensor data
powerSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentPower = value;
currentSpeed = calculateSpeedFromPelotonV1Power(value);
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "Power sensor error: " + errorCode);
}
});
rpmSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentCadence = value;
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "RPM sensor error: " + errorCode);
}
});
resistanceSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentResistance = value;
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "Resistance sensor error: " + errorCode);
}
});
isInitialized = true;
QLog.i(TAG, "Peloton sensor initialization completed");
}
private void startSensorUpdatesInstance() {
if (isUpdating || !isInitialized) {
QLog.w(TAG, "Cannot start sensor updates - not ready");
return;
}
isUpdating = true;
try {
// Start callback-based sensors (no polling needed)
if (powerSensor != null) powerSensor.start();
if (rpmSensor != null) rpmSensor.start();
if (resistanceSensor != null) resistanceSensor.start();
QLog.i(TAG, "Started callback-based sensor updates");
} catch (Exception e) {
QLog.e(TAG, "Failed to start sensor updates", e);
isUpdating = false;
}
}
private void stopSensorUpdatesInstance() {
isUpdating = false;
// Stop callback-based sensors
if (powerSensor != null) powerSensor.stop();
if (rpmSensor != null) rpmSensor.stop();
if (resistanceSensor != null) resistanceSensor.stop();
QLog.i(TAG, "Stopped callback-based sensor updates");
}
// Sensor values are now updated via callbacks - no polling needed
/**
* Calculate speed from power using Peloton V1 bike formula
* Based on Grupetto's SensorInterface.kt implementation
*/
private float calculateSpeedFromPelotonV1Power(float power) {
if (power < 0.1f) {
return 0.0f;
}
// Use exact formula from Grupetto Peloton.kt
double pwrSqrt = Math.sqrt(power);
if (power < 26f) {
return (float)(0.057f - (0.172f * pwrSqrt) + (0.759f * Math.pow(pwrSqrt, 2)) - (0.079f * Math.pow(pwrSqrt, 3)));
} else {
return (float)(-1.635f + (2.325f * pwrSqrt) - (0.064f * Math.pow(pwrSqrt, 2)) + (0.001f * Math.pow(pwrSqrt, 3)));
}
}
private void shutdownInstance() {
stopSensorUpdates();
if (executorService != null) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
QLog.e(TAG, "Error shutting down executor service", e);
executorService.shutdownNow();
}
}
// Clean up sensors
powerSensor = null;
rpmSensor = null;
resistanceSensor = null;
sensorBinder = null;
isInitialized = false;
}
// Static wrapper methods for JNI calls
public static void initialize() {
try {
if (staticContext == null) {
QLog.e(TAG, "Context not set. Call setContext() first.");
return;
}
if (instance == null) {
instance = new PelotonSensorHelper(staticContext);
}
instance.initializeInstance();
QLog.i(TAG, "Static initialize completed");
} catch (Exception e) {
QLog.e(TAG, "Static initialize failed", e);
}
}
public static void setContext(Context context) {
staticContext = context;
}
public static void startSensorUpdates() {
if (instance != null) {
instance.startSensorUpdatesInstance();
} else {
QLog.e(TAG, "Helper not initialized. Call initialize() first.");
}
}
public static void stopSensorUpdates() {
if (instance != null) {
instance.stopSensorUpdatesInstance();
} else {
QLog.e(TAG, "Helper not initialized. Call initialize() first.");
}
}
// Getter methods for current sensor values
public static float getCurrentPower() {
if (instance != null) {
return instance.currentPower;
}
return 0.0f;
}
public static float getCurrentCadence() {
if (instance != null) {
return instance.currentCadence;
}
return 0.0f;
}
public static float getCurrentResistance() {
if (instance != null) {
return instance.currentResistance;
}
return 0.0f;
}
public static float getCurrentSpeed() {
if (instance != null) {
return instance.currentSpeed;
}
return 0.0f;
}
public static boolean isConnected() {
if (instance != null && instance.sensorBinder != null) {
return instance.sensorBinder.isConnected();
}
return false;
}
public static boolean isInitialized() {
if (instance != null) {
return instance.isInitialized;
}
return false;
}
public static void shutdown() {
if (instance != null) {
instance.shutdownInstance();
instance = null;
}
}
}

View File

@@ -0,0 +1,286 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.IBinder;
import android.util.Log;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.cagnulen.qdomyoszwift.QLog;
import org.cagnulen.qdomyoszwift.PelotonSensorBinder;
import org.cagnulen.qdomyoszwift.PelotonCallbackSensor;
/**
* Peloton sensor helper class using callback-based approach from Grupetto v1 develop
* Based on: https://github.com/selalipop/grupetto/pull/10
* More efficient than polling - receives data only when it changes
*/
public class PelotonSensorHelperV1 {
private static final String TAG = "PelotonSensorHelperV1";
// Singleton instance for static access
private static PelotonSensorHelperV1 instance = null;
private static Context staticContext = null;
// Threading components (reduced need with callback approach)
private Handler mainHandler;
private ExecutorService executorService;
// Sensor components (callback-based from Grupetto v1)
private PelotonSensorBinder sensorBinder;
private PelotonCallbackSensor.PowerSensor powerSensor;
private PelotonCallbackSensor.RpmSensor rpmSensor;
private PelotonCallbackSensor.ResistanceSensor resistanceSensor;
// Control flags and current values
private volatile boolean isInitialized = false;
private volatile boolean isUpdating = false;
private volatile float currentPower = 0.0f;
private volatile float currentCadence = 0.0f;
private volatile float currentResistance = 0.0f;
private volatile float currentSpeed = 0.0f;
// Context for accessing system services
private Context context;
public PelotonSensorHelperV1(Context context) {
this.context = context;
this.mainHandler = new Handler(Looper.getMainLooper());
this.executorService = Executors.newSingleThreadExecutor();
this.sensorBinder = new PelotonSensorBinder(context);
}
private void initializeInstance() throws Exception {
QLog.i(TAG, "Initializing Peloton V1 callback sensor connection...");
// Get binder to Peloton service (async operation)
IBinder serviceBinder = sensorBinder.getBinder().get(10, TimeUnit.SECONDS);
if (serviceBinder == null) {
throw new Exception("Failed to get service binder");
}
// Initialize individual callback-based sensors
powerSensor = new PelotonCallbackSensor.PowerSensor(serviceBinder);
rpmSensor = new PelotonCallbackSensor.RpmSensor(serviceBinder);
resistanceSensor = new PelotonCallbackSensor.ResistanceSensor(serviceBinder);
// Set up callbacks to receive sensor data
powerSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentPower = value;
currentSpeed = calculateSpeedFromPelotonV1Power(value);
QLog.d(TAG, "Power updated: " + value + "W, Speed: " + currentSpeed);
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "Power sensor error: " + errorCode);
}
});
rpmSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentCadence = value;
QLog.d(TAG, "Cadence updated: " + value + " RPM");
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "RPM sensor error: " + errorCode);
}
});
resistanceSensor.setCallback(new PelotonCallbackSensor.SensorDataCallback() {
@Override
public void onSensorDataReceived(float value) {
currentResistance = value;
QLog.d(TAG, "Resistance updated: " + value);
}
@Override
public void onSensorError(long errorCode) {
QLog.w(TAG, "Resistance sensor error: " + errorCode);
}
});
isInitialized = true;
QLog.i(TAG, "Peloton V1 callback sensor initialization completed");
}
private void startSensorUpdatesInstance() {
if (isUpdating || !isInitialized) {
QLog.w(TAG, "Cannot start sensor updates - not ready");
return;
}
isUpdating = true;
try {
// Start callback-based sensors (no polling needed)
if (powerSensor != null) powerSensor.start();
if (rpmSensor != null) rpmSensor.start();
if (resistanceSensor != null) resistanceSensor.start();
QLog.i(TAG, "Started callback-based sensor updates");
} catch (Exception e) {
QLog.e(TAG, "Failed to start sensor updates", e);
isUpdating = false;
}
}
private void stopSensorUpdatesInstance() {
isUpdating = false;
// Stop callback-based sensors
if (powerSensor != null) powerSensor.stop();
if (rpmSensor != null) rpmSensor.stop();
if (resistanceSensor != null) resistanceSensor.stop();
QLog.i(TAG, "Stopped callback-based sensor updates");
}
/**
* Calculate speed from power using Peloton V1 bike formula
* Based on Grupetto's SensorInterface.kt implementation
*/
private float calculateSpeedFromPelotonV1Power(float power) {
if (power < 0.1f) {
return 0.0f;
}
// Use exact formula from Grupetto Peloton.kt
double pwrSqrt = Math.sqrt(power);
if (power < 26f) {
return (float)(0.057f - (0.172f * pwrSqrt) + (0.759f * Math.pow(pwrSqrt, 2)) - (0.079f * Math.pow(pwrSqrt, 3)));
} else {
return (float)(-1.635f + (2.325f * pwrSqrt) - (0.064f * Math.pow(pwrSqrt, 2)) + (0.001f * Math.pow(pwrSqrt, 3)));
}
}
private void shutdownInstance() {
stopSensorUpdates();
if (executorService != null) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
QLog.e(TAG, "Error shutting down executor service", e);
executorService.shutdownNow();
}
}
// Clean up sensors
powerSensor = null;
rpmSensor = null;
resistanceSensor = null;
sensorBinder = null;
isInitialized = false;
}
// Static wrapper methods for JNI calls
public static void initialize() {
try {
if (staticContext == null) {
QLog.e(TAG, "Context not set. Call setContext() first.");
return;
}
if (instance == null) {
instance = new PelotonSensorHelperV1(staticContext);
}
instance.initializeInstance();
QLog.i(TAG, "Static V1 initialize completed");
} catch (Exception e) {
QLog.w(TAG, "Peloton V1 service not available - continuing without sensor integration: " + e.getMessage());
// Create instance anyway to provide fallback behavior
if (instance == null) {
instance = new PelotonSensorHelperV1(staticContext);
}
// Mark as not initialized but don't crash the app
instance.isInitialized = false;
}
}
public static void setContext(Context context) {
staticContext = context;
}
public static void startSensorUpdates() {
if (instance != null) {
instance.startSensorUpdatesInstance();
} else {
QLog.e(TAG, "Helper not initialized. Call initialize() first.");
}
}
public static void stopSensorUpdates() {
if (instance != null) {
instance.stopSensorUpdatesInstance();
} else {
QLog.e(TAG, "Helper not initialized. Call initialize() first.");
}
}
// Getter methods for current sensor values
public static float getCurrentPower() {
if (instance != null) {
return instance.currentPower;
}
return 0.0f;
}
public static float getCurrentCadence() {
if (instance != null) {
return instance.currentCadence;
}
return 0.0f;
}
public static float getCurrentResistance() {
if (instance != null) {
return instance.currentResistance;
}
return 0.0f;
}
public static float getCurrentSpeed() {
if (instance != null) {
return instance.currentSpeed;
}
return 0.0f;
}
public static boolean isConnected() {
if (instance != null && instance.sensorBinder != null) {
return instance.sensorBinder.isConnected();
}
return false;
}
public static boolean isInitialized() {
if (instance != null) {
return instance.isInitialized;
}
return false;
}
public static void shutdown() {
if (instance != null) {
instance.shutdownInstance();
instance = null;
}
}
}

View File

@@ -0,0 +1,379 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.ComponentName;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import org.cagnulen.qdomyoszwift.QLog;
public class PelotonSensorService {
private static final String TAG = "PelotonSensorService";
// Singleton instance for static access
private static PelotonSensorService instance = null;
private static Context staticContext = null;
// Peloton service action and permissions
private static final String PELOTON_SENSOR_ACTION = "android.intent.action.peloton.SensorData";
private static final String PELOTON_SENSOR_PERMISSION = "onepeloton.permission.ACCESS_SENSOR_SERVICE";
// Update interval for sensor reading
private static final int SENSOR_UPDATE_INTERVAL_MS = 200;
// Threading components
private Handler mainHandler;
private ExecutorService executorService;
private Runnable sensorUpdateRunnable;
// Service connection components
private IBinder sensorBinder = null;
private boolean isServiceConnected = false;
private boolean isUpdating = false;
// Sensor components (similar to Grupetto's implementation)
private PelotonPowerSensor powerSensor;
private PelotonRpmSensor rpmSensor;
private PelotonResistanceSensor resistanceSensor;
// Current sensor values
private volatile float currentPower = 0.0f;
private volatile float currentCadence = 0.0f;
private volatile float currentResistance = 0.0f;
private volatile float currentSpeed = 0.0f;
// Context for service binding
private Context context;
public PelotonSensorService(Context context) {
this.context = context;
this.mainHandler = new Handler(Looper.getMainLooper());
this.executorService = Executors.newSingleThreadExecutor();
}
private void initializeInstance() throws Exception {
QLog.i(TAG, "Initializing Peloton sensor service connection...");
// Check if required permission is available
if (context.checkSelfPermission(PELOTON_SENSOR_PERMISSION) !=
android.content.pm.PackageManager.PERMISSION_GRANTED) {
throw new Exception("Missing required permission: " + PELOTON_SENSOR_PERMISSION);
}
// Connect to Peloton sensor service
connectToSensorService();
}
private void connectToSensorService() throws Exception {
QLog.i(TAG, "Attempting to connect to Peloton sensor service...");
CompletableFuture<IBinder> binderFuture = new CompletableFuture<>();
CountDownLatch connectionLatch = new CountDownLatch(1);
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
QLog.i(TAG, "Connected to Peloton sensor service");
sensorBinder = service;
isServiceConnected = true;
binderFuture.complete(service);
connectionLatch.countDown();
// Initialize sensor components
try {
initializeSensors();
} catch (Exception e) {
QLog.e(TAG, "Failed to initialize sensors", e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
QLog.w(TAG, "Disconnected from Peloton sensor service");
sensorBinder = null;
isServiceConnected = false;
isUpdating = false;
}
@Override
public void onBindingDied(ComponentName name) {
QLog.e(TAG, "Peloton sensor service binding died");
sensorBinder = null;
isServiceConnected = false;
isUpdating = false;
}
};
Intent intent = new Intent();
intent.setAction(PELOTON_SENSOR_ACTION);
boolean bound = context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
throw new Exception("Failed to bind to Peloton sensor service");
}
// Wait for connection with timeout
try {
if (!connectionLatch.await(10, TimeUnit.SECONDS)) {
throw new Exception("Timeout waiting for Peloton sensor service connection");
}
} catch (InterruptedException e) {
throw new Exception("Interrupted while waiting for service connection", e);
}
}
private void initializeSensors() throws Exception {
if (sensorBinder == null) {
throw new Exception("Service binder not available");
}
// Initialize individual sensor components (similar to Grupetto approach)
powerSensor = new PelotonPowerSensor(sensorBinder);
rpmSensor = new PelotonRpmSensor(sensorBinder);
resistanceSensor = new PelotonResistanceSensor(sensorBinder);
QLog.i(TAG, "All sensors initialized successfully");
}
private void startSensorUpdatesInstance() {
if (isUpdating || !isServiceConnected) {
QLog.w(TAG, "Cannot start sensor updates - service not ready");
return;
}
isUpdating = true;
sensorUpdateRunnable = new Runnable() {
@Override
public void run() {
if (!isUpdating || !isServiceConnected) return;
executorService.execute(() -> {
try {
// Read all sensor values
if (powerSensor != null) {
currentPower = powerSensor.readValue();
}
if (rpmSensor != null) {
currentCadence = rpmSensor.readValue();
}
if (resistanceSensor != null) {
currentResistance = resistanceSensor.readValue();
}
// Calculate speed from power (similar to Grupetto approach)
currentSpeed = calculateSpeedFromPower(currentPower);
} catch (Exception e) {
QLog.w(TAG, "Error reading sensor values", e);
}
if (isUpdating && isServiceConnected) {
mainHandler.postDelayed(sensorUpdateRunnable, SENSOR_UPDATE_INTERVAL_MS);
}
});
}
};
mainHandler.post(sensorUpdateRunnable);
QLog.i(TAG, "Started periodic sensor updates");
}
private void stopSensorUpdatesInstance() {
isUpdating = false;
if (sensorUpdateRunnable != null) {
mainHandler.removeCallbacks(sensorUpdateRunnable);
}
QLog.i(TAG, "Stopped periodic sensor updates");
}
private float calculateSpeedFromPower(float power) {
if (power < 0.1f) {
return 0.0f;
}
// Use exact formula from Grupetto Peloton.kt
double pwrSqrt = Math.sqrt(power);
if (power < 26f) {
return (float)(0.057f - (0.172f * pwrSqrt) + (0.759f * Math.pow(pwrSqrt, 2)) - (0.079f * Math.pow(pwrSqrt, 3)));
} else {
return (float)(-1.635f + (2.325f * pwrSqrt) - (0.064f * Math.pow(pwrSqrt, 2)) + (0.001f * Math.pow(pwrSqrt, 3)));
}
}
private void shutdownInstance() {
stopSensorUpdates();
if (executorService != null) {
executorService.shutdown();
try {
if (!executorService.awaitTermination(2, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
QLog.e(TAG, "Error shutting down executor service", e);
executorService.shutdownNow();
}
}
// Cleanup sensors
powerSensor = null;
rpmSensor = null;
resistanceSensor = null;
// Unbind from service
if (isServiceConnected && context != null) {
try {
// Note: In real implementation, we'd need to properly unbind
// context.unbindService(serviceConnection);
} catch (Exception e) {
QLog.e(TAG, "Error unbinding service", e);
}
}
isServiceConnected = false;
sensorBinder = null;
}
// Static wrapper methods for JNI calls (similar to GrpcTreadmillService)
public static void initialize() {
try {
if (staticContext == null) {
QLog.e(TAG, "Context not set. Call setContext() first.");
return;
}
if (instance == null) {
instance = new PelotonSensorService(staticContext);
}
instance.initializeInstance();
QLog.i(TAG, "Static initialize completed");
} catch (Exception e) {
QLog.e(TAG, "Static initialize failed", e);
}
}
public static void setContext(Context context) {
staticContext = context;
}
public static void startSensorUpdates() {
if (instance != null) {
instance.startSensorUpdatesInstance();
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
public static void stopSensorUpdates() {
if (instance != null) {
instance.stopSensorUpdatesInstance();
} else {
QLog.e(TAG, "Service not initialized. Call initialize() first.");
}
}
// Getter methods for current sensor values
public static float getCurrentPower() {
if (instance != null) {
return instance.currentPower;
}
return 0.0f;
}
public static float getCurrentCadence() {
if (instance != null) {
return instance.currentCadence;
}
return 0.0f;
}
public static float getCurrentResistance() {
if (instance != null) {
return instance.currentResistance;
}
return 0.0f;
}
public static float getCurrentSpeed() {
if (instance != null) {
return instance.currentSpeed;
}
return 0.0f;
}
public static boolean isConnected() {
if (instance != null) {
return instance.isServiceConnected;
}
return false;
}
public static void shutdown() {
if (instance != null) {
instance.shutdownInstance();
instance = null;
}
}
// Inner classes for individual sensors (simplified versions based on Grupetto)
private static class PelotonPowerSensor {
private IBinder binder;
public PelotonPowerSensor(IBinder binder) {
this.binder = binder;
}
public float readValue() throws RemoteException {
// Implementation would call into Peloton service via binder
// This is a simplified version - actual implementation would need
// proper AIDL interface definitions
// For now, return mock data or attempt basic binder calls
// In real implementation, this would use proper service calls
return 0.0f; // Placeholder
}
}
private static class PelotonRpmSensor {
private IBinder binder;
public PelotonRpmSensor(IBinder binder) {
this.binder = binder;
}
public float readValue() throws RemoteException {
// Implementation would call into Peloton service via binder
return 0.0f; // Placeholder
}
}
private static class PelotonResistanceSensor {
private IBinder binder;
public PelotonResistanceSensor(IBinder binder) {
this.binder = binder;
}
public float readValue() throws RemoteException {
// Implementation would call into Peloton service via binder
return 0.0f; // Placeholder
}
}
}

View File

@@ -208,13 +208,14 @@ public class SDMChannelController {
byte[] payload = new byte[8];
payload[0] = (byte) 0x01;
payload[1] = (byte) ((lastTime % 1000) / 5); // time fractional: 0-199 in 1/200 sec units
payload[2] = (byte) ((lastTime / 1000) % 256); // time integer: seconds mod 256
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
payload[2] = (byte) ((lastTime % 256000) / 1000);
payload[3] = (byte) 0x00;
payload[4] = (byte) ((int)speedM_s & 0x0F); // speed integer in lower 4 bits only
payload[5] = (byte) Math.round((speedM_s - (double)((int)speedM_s)) * 256.0);
payload[6] = (byte) stride_count++;
payload[7] = (byte) 0; // update latency: no delay in real-time system
int speedFixed = (int) Math.round(speedM_s * 256.0);
payload[4] = (byte) (speedFixed & 0xFF); // low byte
payload[5] = (byte) ((speedFixed >> 8) & 0xFF); // high byte
payload[6] = (byte) stride_count++; // bad but it works on zwift
payload[7] = (byte) ((double)deltaTime * 0.03125);
if (mIsOpen) {
try {
@@ -257,13 +258,13 @@ public class SDMChannelController {
byte[] payload = new byte[8];
payload[0] = (byte) 0x01;
payload[1] = (byte) ((lastTime % 1000) / 5); // time fractional: 0-199 in 1/200 sec units
payload[2] = (byte) ((lastTime / 1000) % 256); // time integer: seconds mod 256
payload[1] = (byte) (((lastTime % 256000) / 5) & 0xFF);
payload[2] = (byte) ((lastTime % 256000) / 1000);
payload[3] = (byte) 0x00;
payload[4] = (byte) ((int)speedM_s & 0x0F); // speed integer in lower 4 bits only
payload[5] = (byte) Math.round((speedM_s - (double)((int)speedM_s)) * 256.0);
payload[6] = (byte) stride_count++;
payload[7] = (byte) 0; // update latency: no delay in real-time system
payload[4] = (byte) speedM_s;
payload[5] = (byte) ((speedM_s - (double)((int)speedM_s)) / (1.0/256.0));
payload[6] = (byte) stride_count;
payload[7] = (byte) ((double)deltaTime * 0.03125);
if (mIsOpen) {
try {

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package com.ifit.glassos;
import "activitylog/ActivityLogStats.proto";
import "activitylog/ActivityLogSummary.proto";
import "activitylog/ActivityLogMetadata.proto";
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ActivityLog {
ActivityLogMetadata metadata = 1;
string id = 2;
int32 softwareNumber = 3;
int64 startMsSinceEpoch = 4;
int64 endMsSinceEpoch = 5;
int32 durationMs = 6;
ActivityLogStats stats = 7;
ActivityLogSummary summary = 8;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogErrorProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ActivityLogErrorCode {
ACTIVITY_LOG_UNKNOWN_ERROR = 0;
ACTIVITY_LOG_NOT_FOUND_ERROR = 1;
ACTIVITY_LOG_INVALID_TYPE_ERROR = 2;
ACTIVITY_LOG_INVALID_DURATION_ERROR = 3;
}
message ActivityLogError {
ActivityLogErrorCode errorCode = 1;
string message = 2;
}

View File

@@ -0,0 +1,34 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogEventProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ActivityLogEventType {
ACTIVITY_LOG_EVENT_WORKOUT_STARTED = 0;
ACTIVITY_LOG_EVENT_WORKOUT_MINIMUMS_REACHED = 1;
ACTIVITY_LOG_EVENT_UPLOAD_STARTED = 2;
ACTIVITY_LOG_EVENT_UPLOAD_SUCCESSFUL = 3;
ACTIVITY_LOG_EVENT_WORKOUT_COMPLETED_NOT_UPLOADING = 4;
ACTIVITY_LOG_EVENT_UPLOAD_RECOVERABLE_ERROR = 5;
ACTIVITY_LOG_EVENT_UPLOAD_TERMINAL_ERROR = 6;
ACTIVITY_LOG_EVENT_UNDER_MINIMUM_DURATION_ERROR = 7;
ACTIVITY_LOG_EVENT_UNDER_MINIMUM_DISTANCE_ERROR = 8;
ACTIVITY_LOG_EVENT_METADATA_UPDATED = 9;
ACTIVITY_LOG_EVENT_WORKOUT_COMPLETED_ANONYMOUSLY_NOT_UPLOADING = 10;
}
message ActivityLogEvent {
ActivityLogEventType eventType = 1;
string workoutID = 2;
string contentID = 3;
bool shouldUploadLog = 4;
string activityLogID = 5;
string errorCode = 6;
int32 minimumDurationSeconds = 7;
int32 workoutDurationSeconds = 8;
int32 minimumDistanceMeters = 9;
int32 workoutDistanceMeters = 10;
string workoutDriverFQN = 11;
}

View File

@@ -0,0 +1,33 @@
syntax = "proto3";
package com.ifit.glassos;
import "activitylog/ActivityLogUtils.proto";
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogMetadataProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ActivityLogMetadata {
string workoutId = 1;
bool shouldUploadLog = 2;
string contentId = 3;
string title = 4;
string heroImageUrl = 5;
string socialImageUrl = 6;
string programId = 7;
string videoId = 8;
string listWorkoutId = 9;
string liveWorkoutId = 10;
string liveWorkoutScheduleId = 11;
ActivityLogOrigin origin = 12;
ActivityLogContext context = 13;
ActivityLogType type = 14;
string typeDetail = 15;
string externalType = 16;
repeated string completedMovements = 17;
bool redundant = 18;
int32 sleepScore = 19;
string seriesId = 20;
string challengeId = 21;
string workoutDriverFQN = 22;
string thirdPartyContentId = 23;
}

View File

@@ -0,0 +1,52 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
import "util/Util.proto";
import "activitylog/ActivityLog.proto";
import "activitylog/ActivityLogEvent.proto";
import "activitylog/ActivityLogMetadata.proto";
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ActivityLogResult {
oneof errorOrToken {
IFitError error = 1;
ActivityLog activityLog = 2;
ActivityLogMetadata metadata = 3;
}
}
message ActivityLogID {
string id = 1;
}
message ExternalUploadRequest {
ActivityLog log = 1;
string userId = 2;
}
message ContentID {
string id = 1;
}
message ActivityLogUploading {
bool isUploading = 1;
}
service ActivityLogService {
rpc HasUnprocessedUploadEventsSubscription(Empty) returns (stream BooleanResponse) {}
rpc PopMostRecentUploadEvent(Empty) returns (ActivityLogEvent) {}
rpc ActivityLogEventSubscription(Empty) returns (stream ActivityLogEvent) {}
rpc ActivityLogUploadingSubscription(Empty) returns (stream ActivityLogUploading) {}
rpc GetActivityLogMetadataByWorkoutId(WorkoutID) returns (ActivityLogResult) {}
rpc ChangeActivityLogMetadata(ActivityLogMetadata) returns (ActivityLogResult) {}
rpc GetActivityLogByWorkoutId(WorkoutID) returns (ActivityLogResult) {}
rpc GetLatestActivityLogByContentId(ContentID) returns (ActivityLogResult) {}
rpc GetActivityLogByActivityLogId(ActivityLogID) returns (ActivityLogResult) {}
rpc DeleteActivityLogByActivityLogId(ActivityLogID) returns (ActivityLogResult) {}
rpc UploadActivityLogFromExternalSource(ExternalUploadRequest) returns (ActivityLogResult) {}
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package com.ifit.glassos;
import "activitylog/ActivityLogUtils.proto";
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogStatsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ActivityLogStats {
repeated ActivityOffsetValue bpm = 1;
repeated ActivityOffsetValue calories = 2;
repeated ActivityOffsetValue elevation = 3;
repeated ActivityOffsetValue fiveHundredSplit = 4;
repeated ActivityOffsetValue incline = 5;
repeated ActivityOffsetValue meters = 6;
repeated ActivityOffsetValue mps = 7;
repeated ActivityOffsetValue resistance = 8;
repeated ActivityOffsetValue rpm = 9;
repeated ActivityOffsetValue watts = 10;
repeated ActivityOffsetValue cadence = 11;
}

View File

@@ -0,0 +1,27 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogSummaryProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ActivityLogSummary {
int32 averageBpm = 1;
int32 averageFiveHundredSplit = 2;
int32 averageResistance = 3;
int32 averageRespiration = 4;
int32 averageSpm = 5;
double averageWatts = 6;
int32 maxBpm = 7;
int32 maxFiveHundredSplit = 8;
int32 maxSpm = 9;
int32 maxWatts = 10;
int32 minFiveHundredSplit = 11;
float totalCalories = 12;
float totalElevationGain = 13;
float totalMeters = 14;
int32 totalMovements = 15;
int32 totalSteps = 16;
int32 averageCadence = 17;
int32 maxCadence = 18;
}

View File

@@ -0,0 +1,49 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.activitylog";
option java_outer_classname = "ActivityLogUtilsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ActivityLogContext {
ACT_LOG_CONTEXT_ON_DEMAND = 0;
ACT_LOG_CONTEXT_SCHEDULED_LIVE = 1;
ACT_LOG_CONTEXT_SCHEDULED_PRE = 2;
}
enum ActivityLogOrigin {
ACT_LOG_ORIGIN_BIKE = 0;
ACT_LOG_ORIGIN_DAILY = 1;
ACT_LOG_ORIGIN_ELLIPTICAL = 2;
ACT_LOG_ORIGIN_FUSION = 3;
ACT_LOG_ORIGIN_GARMIN = 4;
ACT_LOG_ORIGIN_GOOGLEFIT = 5;
ACT_LOG_ORIGIN_HEALTHKIT = 6;
ACT_LOG_ORIGIN_IFITAPP = 7;
ACT_LOG_ORIGIN_ROWER = 8;
ACT_LOG_ORIGIN_SLEEPSENSOR = 9;
ACT_LOG_ORIGIN_STATIONARYBIKE = 10;
ACT_LOG_ORIGIN_STRAVA = 11;
ACT_LOG_ORIGIN_STRIDER = 12;
ACT_LOG_ORIGIN_THIRDPARTY = 13;
ACT_LOG_ORIGIN_TREADMILL = 14;
ACT_LOG_ORIGIN_WEARABLE = 15;
ACT_LOG_ORIGIN_WEBSITE = 16;
ACT_LOG_ORIGIN_VALINOR = 17;
}
enum ActivityLogType {
ACT_LOG_TYPE_CARDIO = 0;
ACT_LOG_TYPE_CYCLE = 1;
ACT_LOG_TYPE_RUN = 2;
ACT_LOG_TYPE_PULLEY = 3;
ACT_LOG_TYPE_FUSION = 4;
ACT_LOG_TYPE_ROW = 5;
ACT_LOG_TYPE_DAILY_VIDEO = 6;
ACT_LOG_TYPE_STRENGTH = 7;
}
message ActivityOffsetValue {
float offset = 1;
float value = 2;
}

View File

@@ -0,0 +1,23 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.antplus";
option java_outer_classname = "AntPlusDeviceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message AntPlusDevice {
int32 deviceType = 1;
int32 deviceNumber = 2;
int32 signalStrength = 3;
int32 manufacturerID = 4;
int32 serialNumberLSB = 5;
int32 serialNumberMSB = 6;
int32 hardwareVersion = 7;
int32 softwareVersion = 8;
int32 modelNumber = 9;
int32 serialNumberCalculated = 10;
}
message AntPlusDeviceList {
repeated AntPlusDevice devices = 1;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.antplus";
option java_outer_classname = "AntPlusServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "antplus/AntPlusDevice.proto";
import "util/Util.proto";
message AntPlusScanDurationMessage {
int32 durationSeconds = 1;
}
service AntPlusService {
rpc ScanForDuration(AntPlusScanDurationMessage) returns (Empty) {}
rpc FoundAntPlusDevicesSubscription(Empty) returns (stream AntPlusDeviceList) {}
}

View File

@@ -0,0 +1,28 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
import "appnavigation/ForegroundFqns.proto";
import "appnavigation/TouchEvent.proto";
import "appnavigation/ForegroundClasses.proto";
import "appnavigation/ForegroundRequest.proto";
option java_package = "com.ifit.glassos.appnavigation";
option java_outer_classname = "AppNavigationServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
service AppNavigationService {
rpc ForegroundFQNsFlowSubscription(Empty) returns (stream ForegroundFqns) {}
rpc TouchFlowSubscription(Empty) returns (stream TouchEvent) {}
rpc EnabledSubscription(Empty) returns (stream BooleanResponse) {}
rpc KeyboardVisibleFlowSubscription(Empty) returns (stream BooleanResponse) {}
rpc ForegroundClassNameFlowSubscription(Empty) returns (stream ListStringResponse) {}
rpc ForegroundClassesFlowSubscription(Empty) returns (stream ForegroundClasses) {}
rpc PerformBackButton(Empty) returns (Empty) {}
rpc GetForegroundFqns(Empty) returns (ForegroundFqns) {}
rpc SetCurrentForegroundFQN(ForegroundFqnRequest) returns (Empty) {}
rpc RemoveCurrentForegroundFQN(ForegroundFqnRequest) returns (Empty) {}
rpc RemoveForegroundFQNFromHistory(ForegroundFqnRequest) returns (Empty) {}
rpc SetCurrentForegroundClass(ForegroundClassNameRequest) returns (Empty) {}
rpc NavigatedToThirdParty(ForegroundFqnRequest) returns (Empty) {}
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appnavigation";
option java_outer_classname = "ForegroundClassesProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ForegroundClasses {
ForegroundClass currentlyForegrounded = 1;
repeated ForegroundClass foregroundHistory = 2;
}
message ForegroundClass {
string className = 1;
int64 timestamp = 2;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appnavigation";
option java_outer_classname = "ForegroundFqnsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ForegroundFqns {
string currentFqn = 1;
repeated string historyFqns = 2;
ForegroundFqn currentlyForegrounded = 3;
repeated ForegroundFqn foregroundHistory = 4;
}
message ForegroundFqn {
string fqn = 1;
int64 timestamp = 2;
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appnavigation";
option java_outer_classname = "ForegroundRequestProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ForegroundFqnRequest {
string fqn = 1;
}
message ForegroundClassNameRequest {
string className = 1;
}

View File

@@ -0,0 +1,10 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appnavigation";
option java_outer_classname = "TouchEventProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message TouchEvent {
int64 timestamp = 1;
}

View File

@@ -0,0 +1,10 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "AppStoreActionRequestProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message AppStoreActionRequest {
string fqn = 1;
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package com.ifit.glassos;
import "appstore/AppStoreAppStatus.proto";
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "AppStoreAppProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message AppStoreApp {
string label = 1;
string icon = 2;
string fqn = 3;
string category = 4;
string version = 5;
bool installed = 6;
AppStoreAppStatus status = 7;
}
message AppStoreAppList {
repeated AppStoreApp appStoreApps = 1;
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "AppStoreAppStatusProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum AppStoreAppStatus {
NOT_INSTALLED = 0;
INSTALLED = 1;
PENDING = 2;
}

View File

@@ -0,0 +1,23 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
import "appstore/StorageStats.proto";
import "appstore/AppStoreApp.proto";
import "appstore/AppStoreActionRequest.proto";
import "appstore/AppStoreState.proto";
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "AppStoreServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
service AppStoreService {
rpc AppStoreStateFlowSubscription(Empty) returns (stream AppStoreState) {}
rpc AppsFlowSubscription(Empty) returns (stream AppStoreAppList) {}
rpc RequestAppInstall(AppStoreActionRequest) returns (Empty) {}
rpc RequestAppUninstall(AppStoreActionRequest) returns (Empty) {}
rpc GetApps(BooleanRequest) returns (Empty) {}
rpc GoIdle(Empty) returns (Empty) {}
rpc GetStorageStats(Empty) returns (StorageStats) {}
}

View File

@@ -0,0 +1,43 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "AppStoreStateProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message AppStoreState {
oneof state {
Idle idle = 1;
Loading loading = 2;
Checking checking = 3;
Uninstalling uninstalling = 4;
Error error = 5;
Downloading downloading = 6;
Installing installing = 7;
}
}
message Idle {}
message Loading {}
message Checking {}
message Uninstalling {
string fqn = 1;
}
message Error {
int32 errorCode = 1;
optional string fqn = 2;
}
message Downloading {
string fqn = 1;
float progress = 2;
}
message Installing {
string fqn = 1;
float progress = 2;
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.appstore";
option java_outer_classname = "StorageStatsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message StorageStats {
int64 totalBytes = 1;
int64 allocatableBytes = 2;
int64 reservedBytes = 3;
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.auth";
option java_outer_classname = "AuthErrorCodeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum AuthErrorCode {
AUTH_FAILURE = 0;
AUTH_LOGIN_REQUIRED = 1;
AUTH_NETWORK_ERROR = 2;
}
message AuthError {
AuthErrorCode errorCode = 1;
string message = 2;
}

View File

@@ -0,0 +1,116 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
import "util/Util.proto";
option java_package = "com.ifit.glassos.auth";
option java_outer_classname = "AuthServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message Username {
string username = 1;
}
message UserCredentials {
string username = 1;
string password = 2;
}
message AuthToken {
string username = 1;
string accessToken = 2;
int64 validUntilTimestampMs = 3;
}
message AuthCredentials {
string username = 1;
string accessToken = 2;
string refreshToken = 3;
int64 expiresIn = 4;
}
message GetCurrentTokenRequest {
bool forceRefresh = 1;
}
message AuthResult {
oneof errorOrToken {
IFitError error = 1;
AuthToken token = 2;
}
}
message MachineToken {
string accessToken = 1;
int64 validUntilTimestampMs = 2;
}
message MachineTokenResult {
oneof errorOrToken {
IFitError error = 1;
MachineToken token = 2;
}
}
message AuthTokenList {
repeated AuthToken tokens = 1;
}
message AuthQRCodeData {
string deviceCode = 1;
string userCode = 2;
string verificationUri = 3;
string verificationUriComplete = 4;
int64 expiresIn = 5;
}
message AuthQRCodeResult {
oneof errorOrData {
IFitError error = 1;
AuthQRCodeData data = 2;
}
}
message AuthQRCodePollingState {
oneof state {
AUTH_QR_CODE_POLLING_IDLE pollingIdle = 1;
AUTH_QR_CODE_POLLING_ACTIVE pollingActive = 2;
AUTH_QR_CODE_POLLING_EXPIRED pollingExpired = 3;
AUTH_QR_CODE_POLLING_USER_AUTHED pollingUserAuthed = 4;
AUTH_QR_CODE_POLLING_ERROR pollingError = 5;
AUTH_QR_CODE_POLLING_AUTH_ERROR pollingAuthError = 6;
}
}
message AUTH_QR_CODE_POLLING_IDLE {}
message AUTH_QR_CODE_POLLING_ACTIVE {}
message AUTH_QR_CODE_POLLING_EXPIRED {}
message AUTH_QR_CODE_POLLING_USER_AUTHED {
AuthToken token = 1;
}
message AUTH_QR_CODE_POLLING_ERROR {
int32 errorCode = 1;
optional string errorMessage = 2;
}
message AUTH_QR_CODE_POLLING_AUTH_ERROR {
int32 errorCode = 1;
optional string errorMessage = 2;
}
service AuthService {
rpc Login(UserCredentials) returns (AuthResult) {}
rpc SwitchUser(Username) returns (AuthResult) {}
rpc SetCredentials(AuthCredentials) returns (AuthResult) {}
rpc Logout(Empty) returns (Empty) {}
rpc GetQRCodeData(Empty) returns (AuthQRCodeResult) {}
rpc StopPollingForQRAuthToken(Empty) returns (Empty) {}
rpc QrCodePollingStateChanged(Empty) returns (stream AuthQRCodePollingState) {}
rpc TokenChanged(Empty) returns (stream AuthToken) {}
rpc GetCurrentToken(GetCurrentTokenRequest) returns (AuthResult) {}
rpc GetAllTokens(Empty) returns (AuthTokenList) {}
rpc MachineTokenChanged(Empty) returns (stream MachineToken) {}
rpc GetMachineToken(Empty) returns (MachineTokenResult) {}
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.bluetooth";
option java_outer_classname = "BluetoothConnectionStateProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum BluetoothConnectionState {
BLE_DEVICE_CONNECTED = 0;
BLE_DEVICE_CONNECTING = 1;
BLE_DEVICE_DISCONNECTED = 2;
}

View File

@@ -0,0 +1,38 @@
syntax = "proto3";
package com.ifit.glassos;
import "bluetooth/BluetoothDeviceType.proto";
import "bluetooth/BluetoothConnectionState.proto";
option java_package = "com.ifit.glassos.bluetooth";
option java_outer_classname = "BluetoothDeviceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message DeviceStreamRequest {
string macAddress = 1;
}
message DeviceConnectionStateResult {
BluetoothConnectionState connectionState = 1;
}
message DeviceRssiResult {
int32 rssi = 1;
}
message DeviceBatteryLevelResult {
int32 batteryLevel = 1;
}
message BluetoothDevice {
string deviceName = 1;
string macAddress = 2;
int32 rssi = 3;
int32 batteryLevel = 4;
BluetoothDeviceType deviceType = 5;
BluetoothConnectionState connectionState = 6;
string pairKey = 7;
}
message BluetoothDeviceList {
repeated BluetoothDevice devices = 1;
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.bluetooth";
option java_outer_classname = "BluetoothDeviceTypeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum BluetoothDeviceType {
BLE_IFIT_CONSOLE = 0;
BLE_HEART_RATE = 1;
BLE_HEADPHONE = 2;
BLE_OTHER = 3;
BLE_IFIT_VIRTUAL_CONSOLE = 4;
BLE_SMART_WATCH = 5;
ARCX_RING = 6;
BLE_PHONE_TABLET = 7;
}

View File

@@ -0,0 +1,66 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
import "util/Util.proto";
import "bluetooth/BluetoothDevice.proto";
import "bluetooth/BluetoothDeviceType.proto";
option java_package = "com.ifit.glassos.bluetooth";
option java_outer_classname = "BluetoothServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message BluetoothResult {
oneof errorOrSuccess {
IFitError error = 1;
bool success = 2;
}
}
message StartScanRequest {
int32 scanTimeoutSeconds = 1;
repeated BluetoothDeviceType deviceTypes = 2;
}
message BluetoothScanState {
bool scanning = 1;
}
message BluetoothServiceState {
repeated BluetoothDevice connectedDevices = 1;
}
message MACAddressConnectionRequest {
string macAddress = 1;
BluetoothDeviceType deviceType = 2;
}
message MACAddressConnectionResult {
oneof emptyOrDevice {
BluetoothDevice device = 1;
Empty empty = 2;
}
}
message DeviceIdentifierRequest {
string deviceIdentifier = 1;
}
service BluetoothService {
rpc ScanStateChanged(Empty) returns (stream BluetoothScanState) {}
rpc BluetoothServiceStateChanged(Empty) returns (stream BluetoothServiceState) {}
rpc FoundDevicesChanged(Empty) returns (stream BluetoothDevice) {}
rpc StartScan(StartScanRequest) returns (BluetoothResult) {}
rpc StopScan(Empty) returns (BluetoothResult) {}
rpc ConnectDevice(BluetoothDevice) returns (BluetoothResult) {}
rpc ConnectWithMACAddress(MACAddressConnectionRequest) returns (MACAddressConnectionResult) {}
rpc DisconnectDevice(BluetoothDevice) returns (BluetoothResult) {}
rpc ConnectToHRM(DeviceIdentifierRequest) returns (BluetoothResult) {}
rpc ConnectToRing(DeviceIdentifierRequest) returns (BluetoothResult) {}
rpc GetPairedDevices(Empty) returns (BluetoothDeviceList) {}
rpc BluetoothDeviceBatteryLevelChanged(DeviceStreamRequest) returns (stream DeviceBatteryLevelResult) {}
rpc BluetoothDeviceConnectionStateChanged(DeviceStreamRequest) returns (stream DeviceConnectionStateResult) {}
rpc BluetoothDeviceRSSIChanged(DeviceStreamRequest) returns (stream DeviceRssiResult) {}
}

View File

@@ -0,0 +1,95 @@
// Copyright 2021 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
syntax = "proto3";
package firebase.transport;
option java_multiple_files = true;
// Top level metrics for all client analytics metrics.
// These metrics should be sent as a part of every request that is uploaded to
// FireLog server. In more detail, an additional LogRequest should be added to
// the BatchedLogRequest, where the LogSource of the LogRequest should be
// GDT_CLIENT_METRICS and the LogRequest should have a single LogEvent whose
// payload is a ClientMetrics message.
//
// See go/firelog-client-analytics for more details.
message ClientMetrics {
// The window of time over which the metrics are evaluated.
TimeWindow window = 1;
repeated LogSourceMetrics log_source_metrics = 2;
GlobalMetrics global_metrics = 3;
// The bundle ID on Apple platforms (e.g., iOS) or the package name on Android
string app_namespace = 4;
}
// Represents an arbitrary window of time.
message TimeWindow {
// The time that the window first starts.
// start_ms is the number of milliseconds since the UNIX epoch
// (January 1, 1970 00:00:00 UTC)
int64 start_ms = 1;
// The time that the window ends.
// end_ms is the number of milliseconds since the UNIX epoch
// (January 1, 1970 00:00:00 UTC)
int64 end_ms = 2;
}
// Metrics per app, not per log source
message GlobalMetrics {
StorageMetrics storage_metrics = 1;
}
message StorageMetrics {
// The number of bytes of storage the event cache was consuming on the client
// at the time the request was sent.
int64 current_cache_size_bytes = 1;
// The maximum number of bytes to which the event cache is allowed to grow.
int64 max_cache_size_bytes = 2;
}
// Metrics per log source.
message LogSourceMetrics {
// A LogSource uniquely identifies a logging configuration. log_source should
// contains a string value of the LogSource from
// google3/wireless/android/play/playlog/proto/clientanalytics.proto
string log_source = 1;
repeated LogEventDropped log_event_dropped = 2;
}
message LogEventDropped {
// A count of how many log event have been dropped on the client.
int64 events_dropped_count = 1;
// The reason why log events have been dropped on the client.
enum Reason {
REASON_UNKNOWN = 0;
MESSAGE_TOO_OLD = 1;
CACHE_FULL = 2;
PAYLOAD_TOO_BIG = 3;
MAX_RETRIES_REACHED = 4;
INVALID_PAYLOD = 5;
SERVER_ERROR = 6;
}
Reason reason = 3;
}

View File

@@ -0,0 +1,84 @@
syntax = "proto3";
package com.ifit.glassos.club;
option java_package = "com.ifit.glassos.club";
option java_outer_classname = "ClubSettingsServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "settings/SystemUnitsService.proto";
import "util/Util.proto";
// Enum for UserRole
enum UserRole {
HOME_USER = 0;
CLUB_ADMIN = 1;
CLUB_GUEST = 2;
CLUB_USER = 3;
}
// Response message for getting club code
message GetClubCodeResponse {
oneof errorOrClubCode {
IFitError error = 1;
string clubCode = 2;
}
}
// Response message for getting video screensaver setting
message GetUseVideoScreensaverResponse {
oneof errorOrUseVideoScreensaver {
IFitError error = 1;
bool useVideoScreensaver = 2;
}
}
message UserRoleResponse {
UserRole role = 1;
}
// Request message for changing user role
message ChangeUserRoleRequest {
UserRole newRole = 1;
}
// Request message for saving club code
message SaveClubCodeRequest {
string clubCode = 1;
}
// Request message for saving video screensaver setting
message SaveUseVideoScreensaverRequest {
bool useVideoScreensaver = 1;
}
// Request message for saving default language
message SaveDefaultLanguageRequest {
string language = 1;
}
// Request message for saving default language
message IsEgymEnabledRequest {
bool featureFlagOnly = 1;
bool adminOnly = 2;
}
// Service definition for IFitClubSettingsService
service IFitClubSettingsService {
rpc ChangeUserRole(ChangeUserRoleRequest) returns (Empty) {}
rpc CurrentUserRole(Empty) returns (stream UserRoleResponse) {}
rpc RestoreClubOwnerDefaultSettings(Empty) returns (Empty) {}
rpc GetClubCode(Empty) returns (GetClubCodeResponse) {}
rpc SaveClubCode(SaveClubCodeRequest) returns (Empty) {}
rpc GetUseVideoScreensaver(Empty) returns (GetUseVideoScreensaverResponse) {}
rpc SaveUseVideoScreensaver(SaveUseVideoScreensaverRequest) returns (Empty) {}
rpc SaveDefaultSystemUnits(SystemUnitsMessage) returns (Empty) {}
rpc SaveDefaultLanguage(SaveDefaultLanguageRequest) returns (Empty) {}
rpc GetCurrentUserRole(Empty) returns (UserRoleResponse) {}
rpc SaveAdminEgymEnabledState(BooleanRequest) returns (Empty) {}
rpc IsClub(Empty) returns (BooleanResponse) {}
rpc IsClubUser(Empty) returns (BooleanResponse) {}
rpc IsClubFreeUser(Empty) returns (BooleanResponse) {}
rpc IsClubGuest(Empty) returns (BooleanResponse) {}
rpc IsClubPremiumUser(Empty) returns (BooleanResponse) {}
rpc IsEgymEnabled(IsEgymEnabledRequest) returns (BooleanResponse) {}
}

View File

@@ -0,0 +1,87 @@
@echo off
setlocal enabledelayedexpansion
REM Percorso al tuo protoc specifico
set PROTOC_EXE=C:\Users\violarob\Downloads\protoc-3.25.8-windows-x86_64.exe
REM Verifica che protoc esista
if not exist "%PROTOC_EXE%" (
echo ERRORE: protoc non trovato in: %PROTOC_EXE%
echo Verifica che il file esista e il percorso sia corretto.
pause
exit /b 1
)
REM Directory di output
set OUTPUT_DIR=..\java
REM Crea directory di output
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
echo ===============================
echo COMPILAZIONE PROTOBUF
echo ===============================
echo Protoc: %PROTOC_EXE%
echo Directory corrente: %CD%
echo Output in: %OUTPUT_DIR%
echo.
REM Verifica versione protoc
echo Versione protoc:
"%PROTOC_EXE%" --version
echo.
REM Contatori
set /a success_count=0
set /a error_count=0
REM Compila file .proto nella directory corrente
for %%f in (*.proto) do (
echo [INFO] Compilando: %%f
"%PROTOC_EXE%" --java_out=lite:"%OUTPUT_DIR%" --proto_path=. "%%f"
if errorlevel 1 (
echo [ERRORE] Fallito: %%f
set /a error_count+=1
) else (
echo [OK] Successo: %%f
set /a success_count+=1
)
echo.
)
REM Compila file .proto nelle sottocartelle
for /d %%d in (*) do (
if exist "%%d\*.proto" (
echo [INFO] Sottocartella trovata: %%d
for %%f in (%%d\*.proto) do (
echo [INFO] Compilando: %%f
"%PROTOC_EXE%" --java_out=lite:"%OUTPUT_DIR%" --proto_path=. "%%f"
if errorlevel 1 (
echo [ERRORE] Fallito: %%f
set /a error_count+=1
) else (
echo [OK] Successo: %%f
set /a success_count+=1
)
)
echo.
)
)
REM Riepilogo finale
echo ===============================
echo RIEPILOGO COMPILAZIONE:
echo File compilati con successo: %success_count%
echo File con errori: %error_count%
echo Directory output: %OUTPUT_DIR%
echo ===============================
if %error_count% gtr 0 (
echo ATTENZIONE: Compilazione completata con %error_count% errori!
pause
exit /b 1
) else (
echo SUCCESSO: Tutti i file compilati correttamente!
pause
exit /b 0
)

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console";
option java_outer_classname = "ConsoleErrorCodeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ConsoleErrorCode {
FITNESS_VALUE_UNSUPPORTED = 0;
VIRTUAL_CONSOLE_REQUIRED = 1;
NO_VALUE_SET = 2;
}
message ConsoleError {
ConsoleErrorCode errorCode = 1;
string message = 2;
}

View File

@@ -0,0 +1,62 @@
syntax = "proto3";
package com.ifit.glassos;
import "console/ConsoleType.proto";
import "settings/SystemUnitsService.proto";
option java_package = "com.ifit.glassos.console";
option java_outer_classname = "ConsoleInfoProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ConsoleInfo {
int32 modelNumber = 1;
int32 partNumber = 2;
int32 softwareVersion = 3;
int32 hardwareVersion = 4;
string firmwareVersion = 5;
int32 serialNumber = 6;
ConsoleType machineType = 7;
string name = 8;
string brainboardSerialNumber = 9;
int32 masterLibraryVersion = 10;
int32 masterLibraryBuild = 11;
SystemUnits systemUnits = 12;
double maxKph = 13;
double minKph = 14;
double maxInclinePercent = 15;
double minInclinePercent = 16;
double minResistance = 17;
double maxResistance = 18;
int32 minGear = 19;
int32 maxGear = 20;
double maxWeightKg = 21;
bool canSetSpeed = 22;
bool canSetIncline = 23;
bool canSetResistance = 24;
bool canSetGear = 25;
bool canSetActivationLock = 26;
bool supportsVerticalGain = 27;
bool supportsVerticalNet = 28;
bool supportsStartRequested = 29;
bool supportsRequireStartRequested = 30;
bool supportsKeyPressObserved = 31;
bool supportsPulse = 32;
double totalTimeSeconds = 33;
double warmUpTimeoutSeconds = 34;
double coolDownTimeoutSeconds = 35;
double pauseTimeoutSeconds = 36;
double totalDistanceKm = 37;
bool isClubUnit = 38;
double weightKg = 39;
bool supportsConstantWatts = 40;
string antPlusBootloaderVersion = 41;
string antPlusSerialNumber = 42;
string antPlusDeviceNumber = 43;
string antPlusRelaySoftwareVersion = 44;
string productSerialNumber = 45;
string controller1SoftwareVersion = 46;
string controller1SoftwarePartNumber = 47;
string controller4SoftwareVersion = 48;
string controller4SoftwarePartNumber = 49;
string controller40SoftwareVersion = 50;
string controller40SoftwarePartNumber = 51;
}

View File

@@ -0,0 +1,35 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
import "util/Util.proto";
import "console/ConsoleState.proto";
import "console/ConsoleInfo.proto";
option java_package = "com.ifit.glassos.console";
option java_outer_classname = "ConsoleServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message ConnectionResult {
oneof errorOrState {
IFitError error = 1;
ConsoleState consoleState = 2;
}
}
message ConsoleStateMessage {
ConsoleState consoleState = 1;
}
service ConsoleService {
rpc Connect(Empty) returns (ConnectionResult) {}
rpc Disconnect(Empty) returns (Empty) {}
rpc GetConsole(Empty) returns (ConsoleInfo) {}
rpc ConsoleChanged(Empty) returns (stream ConsoleInfo) {}
rpc GetConsoleState(Empty) returns (ConsoleStateMessage) {}
rpc ConsoleStateChanged(Empty) returns (stream ConsoleStateMessage) {}
rpc GetKnownConsoleInfo(Empty) returns (ConsoleInfo) {}
rpc RefreshKnownConsoleInfo(Empty) returns (ConsoleInfo) {}
}

View File

@@ -0,0 +1,23 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console";
option java_outer_classname = "ConsoleStateProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ConsoleState {
DISCONNECTED = 0;
CONSOLE_STATE_UNKNOWN = 1;
IDLE = 2;
WORKOUT = 3;
PAUSED = 4;
WORKOUT_RESULTS = 5;
SAFETY_KEY_REMOVED = 6;
WARM_UP = 7;
COOL_DOWN = 8;
RESUME = 9;
LOCKED = 10;
DEMO = 11;
SLEEP = 12;
ERROR = 13;
}

View File

@@ -0,0 +1,22 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console";
option java_outer_classname = "ConsoleTypeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum ConsoleType {
CONSOLE_TYPE_UNKNOWN = 0;
TREADMILL = 1;
INCLINE_TRAINER = 2;
ELLIPTICAL = 3;
BIKE = 4;
STRIDER = 5;
FREE_STRIDER = 6;
VERTICAL_ELLIPTICAL = 7;
SPIN_BIKE = 8;
ROWER = 9;
EQUIPMENTLESS = 10;
MIRROR = 11;
VIBRATION = 12;
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.calibration";
option java_outer_classname = "InclineCalibrationProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
enum CalibrateInclineState {
CALIBRATE_INCLINE_STATE_DONE = 0;
CALIBRATE_INCLINE_STATE_FAILED = 1;
CALIBRATE_INCLINE_STATE_IN_PROGRESS = 2;
CALIBRATE_INCLINE_STATE_WAITING = 3;
}
message InclineCalibrationStateResult {
CalibrateInclineState state = 1;
}
message InclineCalibrationStartedResult {
oneof errorOrBool {
IFitError error = 1;
bool calibrationStarted = 2;
}
}

View File

@@ -0,0 +1,13 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.calibration";
option java_outer_classname = "InclineCalibrationServiceProto";
option java_multiple_files = true;
import "console/calibration/InclineCalibration.proto";
import "util/Util.proto";
service InclineCalibrationService {
rpc CalibrateIncline(Empty) returns (Empty) {}
rpc InclineCalibrationStateChanged(Empty) returns (stream InclineCalibrationStateResult) {}
rpc InclineCalibrationStartedChanged(Empty) returns (stream InclineCalibrationStartedResult) {}
}

View File

@@ -0,0 +1,40 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.calibration";
option java_outer_classname = "ThrottleCalibrationProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
enum ThrottleCalibrationState {
CALIBRATE_THROTTLE_STATE_IDLE = 0;
CALIBRATE_THROTTLE_STATE_FAILED = 1;
CALIBRATE_THROTTLE_STATE_WAITING_FOR_NEUTRAL = 2;
CALIBRATE_THROTTLE_STATE_WAITING_FOR_GRADE_FORWARD = 3;
CALIBRATE_THROTTLE_STATE_WAITING_FOR_GRADE_BACKWARD = 4;
CALIBRATE_THROTTLE_STATE_WAITING_FOR_SPEED_FORWARD = 5;
CALIBRATE_THROTTLE_STATE_WAITING_FOR_SPEED_BACKWARD = 6;
CALIBRATE_THROTTLE_STATE_DONE = 7;
}
message ThrottleCalibrationValues {
int32 rawGradeReading = 1;
int32 rawSpeedReading = 2;
int32 gradeTopThreshold = 3;
int32 gradeHighThreshold = 4;
int32 gradeLowThreshold = 5;
int32 gradeBottomThreshold = 6;
int32 gradeFilterConstant = 7;
int32 speedTopThreshold = 8;
int32 speedHighThreshold = 9;
int32 speedLowThreshold = 10;
int32 speedBottomThreshold = 11;
int32 speedFilterConstant = 12;
}
message ThrottleCalibrationStateResult {
ThrottleCalibrationState state = 1;
optional string errorMessage = 2;
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.calibration";
option java_outer_classname = "ThrottleCalibrationServiceProto";
option java_multiple_files = true;
import "util/Util.proto";
import "console/calibration/ThrottleCalibration.proto";
service ThrottleCalibrationService {
rpc IsThrottleCalibrationAvailable(Empty) returns (AvailabilityResponse) {}
rpc CalibrateThrottles(Empty) returns (Empty) {}
rpc ConfirmThrottleState(Empty) returns (Empty) {}
rpc AbortCalibrateThrottles(Empty) returns (Empty) {}
rpc ThrottleCalibrationStateChanged(Empty) returns (stream ThrottleCalibrationStateResult) {}
rpc GetThrottleCalibrationValues(Empty) returns (ThrottleCalibrationValues) {}
rpc ThrottleCalibrationValuesChanged(Empty) returns (stream ThrottleCalibrationValues) {}
}

View File

@@ -0,0 +1,45 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.constantwatts";
option java_outer_classname = "ConstantWattsServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/Util.proto";
import "util/IFitError.proto";
message ConstantWattsMessage {
int32 watts = 1;
}
enum ConstantWattsState {
CONSTANT_WATTS_STATE_DISABLED = 0;
CONSTANT_WATTS_STATE_ENABLED = 1;
CONSTANT_WATTS_STATE_PAUSED = 2;
}
message ConstantWattsStateMessage {
oneof errorOrState {
IFitError error = 1;
ConstantWattsState state = 2;
}
}
service ConstantWattsService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc IsSupported(Empty) returns (AvailabilityResponse) {}
rpc GetConstantWatts(Empty) returns (ConstantWattsMessage) {}
rpc SetConstantWatts(ConstantWattsMessage) returns (AvailabilityResponse) {}
rpc GetState(Empty) returns (ConstantWattsStateMessage){}
rpc IsEquipmentSupported(Empty) returns (AvailabilityResponse){}
rpc IsWorkoutSupported(Empty) returns (AvailabilityResponse){}
rpc IsUserSupported(Empty) returns (AvailabilityResponse){}
rpc Pause(Empty) returns (Empty){}
rpc Resume(Empty) returns (Empty){}
rpc Enable(Empty) returns (Empty){}
rpc Disable(Empty) returns (Empty){}
rpc Increment(Empty) returns (Empty){}
rpc Decrement(Empty) returns (Empty){}
rpc OnStateChanged(Empty) returns (stream ConstantWattsStateMessage) {}
rpc ConstantWattsSubscription(Empty) returns (stream ConstantWattsMessage) {}
}

View File

@@ -0,0 +1,24 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.idlelockout";
option java_outer_classname = "IdleModeLockoutProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
enum IdleModeLockoutState {
LOCK_STATE_UNKNOWN = 0;
LOCK_STATE_UNLOCKED = 1;
LOCK_STATE_LOCKED = 2;
}
message IdleModeLockoutMessage {
IdleModeLockoutState state = 1;
}
message IdleModeLockoutResult {
oneof errorOrIdleModeLockoutState {
IFitError error = 1;
IdleModeLockoutState idleModeLockoutState = 2;
}
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.idlelockout";
option java_outer_classname = "IdleModeLockoutServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/idlelockout/IdleModeLockout.proto";
import "util/Util.proto";
service IdleModeLockoutService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc GetIdleModeLockout(Empty) returns (IdleModeLockoutResult) {}
rpc SetIdleModeLockout(IdleModeLockoutMessage) returns (IdleModeLockoutResult) {}
rpc IdleModeLockoutSubscription(Empty) returns (stream IdleModeLockoutMessage) {}
}

View File

@@ -0,0 +1,297 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.keypress";
option java_outer_classname = "KeyCodeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum KeyCode {
NO_KEY = 0;
STOP = 1;
START = 2;
SPEED_UP = 3;
SPEED_DOWN = 4;
INCLINE_UP = 5;
INCLINE_DOWN = 6;
RESISTANCE_UP = 7;
RESISTANCE_DOWN = 8;
GEAR_UP = 9;
GEAR_DOWN = 10;
WEIGHT_UP = 11;
WEIGHT_DOWN = 12;
AGE_UP = 13;
AGE_DOWN = 14;
SPEED_RESUME = 15;
INCLINE_RESUME = 16;
BLE_KEY = 17;
ON_RESET = 18;
PRIORITY_DISPLAY = 19;
BURN_RATE_UP = 20;
BURN_RATE_DOWN = 21;
RECOVERY = 22;
WORK = 23;
START_STOP = 24;
POWER_ON_OFF = 25;
FAN_UP = 50;
FAN_DOWN = 51;
FAN_OFF = 52;
FAN_MANUAL = 53;
FAN_AUTO = 54;
FAN_1 = 55;
FAN_2 = 56;
FAN_3 = 57;
FAN_4 = 58;
FAN_5 = 59;
PC_BACK = 100;
PC_MENU = 101;
PC_HOME = 102;
KEYPAD = 103;
DISPLAY = 104;
ENTER = 105;
UP = 106;
DOWN = 107;
LEFT = 108;
RIGHT = 109;
TV_POWER = 120;
TV_CHANNEL_UP = 121;
TV_CHANNEL_DOWN = 122;
TV_RECALL = 123;
TV_MENU = 124;
TV_SOURCE = 125;
TV_SEEK = 126;
TV_CLOSE_CAPTION = 127;
TV_VOLUME_UP = 128;
TV_VOLUME_DOWN = 129;
TV_MUTE = 130;
RIGHT_GEAR_UP = 150;
RIGHT_GEAR_DOWN = 151;
LEFT_GEAR_UP = 152;
LEFT_GEAR_DOWN = 153;
AUDIO_VOLUME_UP = 200;
AUDIO_VOLUME_DOWN = 201;
AUDIO_MUTE = 202;
AUDIO_EQUALIZER = 203;
AUDIO_SOURCE = 204;
NUMBER_PAD_0 = 300;
NUMBER_PAD_1 = 301;
NUMBER_PAD_2 = 302;
NUMBER_PAD_3 = 303;
NUMBER_PAD_4 = 304;
NUMBER_PAD_5 = 305;
NUMBER_PAD_6 = 306;
NUMBER_PAD_7 = 307;
NUMBER_PAD_8 = 308;
NUMBER_PAD_9 = 309;
NUMBER_PAD_STAR = 310;
NUMBER_PAD_DOT = 311;
NUMBER_PAD_HASH = 312;
NUMBER_PAD_OK = 313;
NUMBER_PAD_ENTER = 314;
ERGOFIT_TILT_FORWARD = 400;
ERGOFIT_TILT_BACK = 401;
ERGOFIT_UPRIGHT_UP = 402;
ERGOFIT_UPRIGHT_DOWN = 403;
ERGOFIT_MEMORY = 404;
ERGOFIT_USER_1 = 405;
ERGOFIT_USER_2 = 406;
ERGOFIT_USER_3 = 407;
ERGOFIT_USER_4 = 408;
SET_TO_SHIP = 500;
DEBUG_MODE = 501;
LOG_MODE = 502;
SETTINGS = 503;
INCLINE_DISPLAY = 600;
PULSE_DISPLAY = 601;
WATTS_DISPLAY = 602;
SPEED_DISPLAY = 603;
TIME_DISPLAY = 604;
PACE_DISPLAY = 605;
CALORIES_DISPLAY = 606;
DISTANCE_DISPLAY = 607;
SCAN_DISPLAY = 608;
MPH_1 = 1000;
MPH_2 = 1001;
MPH_3 = 1002;
MPH_4 = 1003;
MPH_5 = 1004;
MPH_6 = 1005;
MPH_7 = 1006;
MPH_8 = 1007;
MPH_9 = 1008;
MPH_10 = 1009;
MPH_11 = 1010;
MPH_12 = 1011;
MPH_13 = 1012;
MPH_14 = 1013;
MPH_15 = 1014;
KPH_1 = 1100;
KPH_2 = 1101;
KPH_3 = 1102;
KPH_4 = 1103;
KPH_5 = 1104;
KPH_6 = 1105;
KPH_7 = 1106;
KPH_8 = 1107;
KPH_9 = 1108;
KPH_10 = 1109;
KPH_11 = 1110;
KPH_12 = 1111;
KPH_13 = 1112;
KPH_14 = 1113;
KPH_15 = 1114;
KPH_16 = 1115;
KPH_17 = 1116;
KPH_18 = 1117;
KPH_19 = 1118;
KPH_20 = 1119;
KPH_21 = 1120;
KPH_22 = 1121;
KPH_23 = 1122;
KPH_24 = 1123;
INCLINE_NEG_30 = 1200;
INCLINE_NEG_29 = 1201;
INCLINE_NEG_28 = 1202;
INCLINE_NEG_27 = 1203;
INCLINE_NEG_26 = 1204;
INCLINE_NEG_25 = 1205;
INCLINE_NEG_24 = 1206;
INCLINE_NEG_23 = 1207;
INCLINE_NEG_22 = 1208;
INCLINE_NEG_21 = 1209;
INCLINE_NEG_20 = 1210;
INCLINE_NEG_19 = 1211;
INCLINE_NEG_18 = 1212;
INCLINE_NEG_17 = 1213;
INCLINE_NEG_16 = 1214;
INCLINE_NEG_15 = 1215;
INCLINE_NEG_14 = 1216;
INCLINE_NEG_13 = 1217;
INCLINE_NEG_12 = 1218;
INCLINE_NEG_11 = 1219;
INCLINE_NEG_10 = 1220;
INCLINE_NEG_9 = 1221;
INCLINE_NEG_8 = 1222;
INCLINE_NEG_7 = 1223;
INCLINE_NEG_6 = 1224;
INCLINE_NEG_5 = 1225;
INCLINE_NEG_4 = 1226;
INCLINE_NEG_3 = 1227;
INCLINE_NEG_2 = 1228;
INCLINE_NEG_1 = 1229;
INCLINE_0 = 1230;
INCLINE_1 = 1231;
INCLINE_2 = 1232;
INCLINE_3 = 1233;
INCLINE_4 = 1234;
INCLINE_5 = 1235;
INCLINE_6 = 1236;
INCLINE_7 = 1237;
INCLINE_8 = 1238;
INCLINE_9 = 1239;
INCLINE_10 = 1240;
INCLINE_11 = 1241;
INCLINE_12 = 1242;
INCLINE_13 = 1243;
INCLINE_14 = 1244;
INCLINE_15 = 1245;
INCLINE_16 = 1246;
INCLINE_17 = 1247;
INCLINE_18 = 1248;
INCLINE_19 = 1249;
INCLINE_20 = 1250;
INCLINE_21 = 1251;
INCLINE_22 = 1252;
INCLINE_23 = 1253;
INCLINE_24 = 1254;
INCLINE_25 = 1255;
INCLINE_26 = 1256;
INCLINE_27 = 1257;
INCLINE_28 = 1258;
INCLINE_29 = 1259;
INCLINE_30 = 1260;
INCLINE_31 = 1261;
INCLINE_32 = 1262;
INCLINE_33 = 1263;
INCLINE_34 = 1264;
INCLINE_35 = 1265;
INCLINE_36 = 1266;
INCLINE_37 = 1267;
INCLINE_38 = 1268;
INCLINE_39 = 1269;
INCLINE_40 = 1270;
INCLINE_41 = 1271;
INCLINE_42 = 1272;
INCLINE_43 = 1273;
INCLINE_44 = 1274;
INCLINE_45 = 1275;
INCLINE_46 = 1276;
INCLINE_47 = 1277;
INCLINE_48 = 1278;
INCLINE_49 = 1279;
INCLINE_50 = 1280;
RESISTANCE_0 = 1300;
RESISTANCE_1 = 1301;
RESISTANCE_2 = 1302;
RESISTANCE_3 = 1303;
RESISTANCE_4 = 1304;
RESISTANCE_5 = 1305;
RESISTANCE_6 = 1306;
RESISTANCE_7 = 1307;
RESISTANCE_8 = 1308;
RESISTANCE_9 = 1309;
RESISTANCE_10 = 1310;
RESISTANCE_11 = 1311;
RESISTANCE_12 = 1312;
RESISTANCE_13 = 1313;
RESISTANCE_14 = 1314;
RESISTANCE_15 = 1315;
RESISTANCE_16 = 1316;
RESISTANCE_17 = 1317;
RESISTANCE_18 = 1318;
RESISTANCE_19 = 1319;
RESISTANCE_20 = 1320;
RESISTANCE_21 = 1321;
RESISTANCE_22 = 1322;
RESISTANCE_23 = 1323;
RESISTANCE_24 = 1324;
RESISTANCE_25 = 1325;
RESISTANCE_26 = 1326;
RESISTANCE_27 = 1327;
RESISTANCE_28 = 1328;
RESISTANCE_29 = 1329;
RESISTANCE_30 = 1330;
MANUAL_WORKOUT = 11000;
MAP_WORKOUT = 11001;
TRAIN_WORKOUT = 11002;
COMPETE_WORKOUT = 11003;
TRACK_WORKOUT = 11004;
SET_A_GOAL_WORKOUT = 11005;
VIDEO_WORKOUT = 11006;
LOSE_WT_WORKOUT = 11007;
CALORIES_WORKOUT = 11008;
INTENSITY_WORKOUT = 11009;
INCLINE_WORKOUT = 11010;
SPEED_WORKOUT = 11011;
PULSE_WORKOUT = 11012;
PERFORMANCE_WORKOUT = 11013;
DAY_WORKOUT = 11014;
WEEK_WORKOUT = 11015;
MONTH_WORKOUT = 11016;
INTERVAL_WORKOUT = 11017;
TEMP_WORKOUT = 11018;
DUMMY_WORKOUT_1 = 11100;
DUMMY_WORKOUT_2 = 11101;
DUMMY_WORKOUT_3 = 11102;
DUMMY_WORKOUT_4 = 11103;
DUMMY_WORKOUT_5 = 11104;
DUMMY_WORKOUT_6 = 11105;
DUMMY_WORKOUT_7 = 11106;
DUMMY_WORKOUT_8 = 11107;
DUMMY_WORKOUT_9 = 11108;
DUMMY_WORKOUT_10 = 11109;
CALORIES_WORKOUT_0 = 12000;
CALORIES_WORKOUT_999 = 12999;
TIME_WORKOUT_0 = 13000;
TIME_WORKOUT_99 = 13099;
DUMMY = 9999;
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.keypress";
option java_outer_classname = "KeyPressProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/keypress/KeyCode.proto";
import "util/IFitError.proto";
message KeyPress {
KeyCode code = 1;
int32 timePressed = 2;
int32 durationHeld = 3;
}
message KeyPressResult {
oneof errorOrKeyPress {
IFitError error = 1;
KeyPress keyPress = 2;
}
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.keypress";
option java_outer_classname = "KeyPressServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/keypress/KeyPress.proto";
import "util/Util.proto";
service KeyPressService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWriteVirtual(Empty) returns (AvailabilityResponse) {}
rpc GetKeyPress(Empty) returns (KeyPressResult) {}
rpc KeyPressSubscription(Empty) returns (stream KeyPress) {}
rpc SetVirtualKeyPress(KeyPress) returns (KeyPressResult) {}
}

View File

@@ -0,0 +1,28 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
option java_package = "com.ifit.glassos.console.proximity";
option java_outer_classname = "ProximitySensingServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
service ProximitySensingService {
// check whether Movement Detection (PIR) is Readable
rpc CanReadMovementDetect(Empty) returns (AvailabilityResponse) {}
// check whether User Distance (LIDAR) is Readable
rpc CanReadUserDistance(Empty) returns (AvailabilityResponse) {}
// get the current Movement Detection (PIR) state
rpc GetMovementDetect(Empty) returns (BooleanResponse) {}
// subscribe to Movement Detection (PIR) updates
rpc MovementDetectSubscription(Empty) returns (stream BooleanResponse) {}
// get the current User Distance (LIDAR) in centimeters
rpc GetUserDistance(Empty) returns (FloatResponse) {}
// subscribe to User Distance (LIDAR) updates
rpc UserDistanceSubscription(Empty) returns (stream FloatResponse) {}
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.sleep";
option java_outer_classname = "SleepStateProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
enum SleepState {
SLEEP_STATE_UNKNOWN = 0;
SLEEP_STATE_AWAKE = 1;
SLEEP_STATE_INITIATE_SLEEP = 2;
SLEEP_STATE_SLEEPING = 3;
}
message SleepStateMessage {
SleepState state = 1;
}
message SleepStateResult {
oneof errorOrSleepState {
IFitError error = 1;
SleepState sleepState = 2;
}
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.sleep";
option java_outer_classname = "SleepStateServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/sleep/SleepState.proto";
import "util/Util.proto";
service SleepStateService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc GetSleepState(Empty) returns (SleepStateResult) {}
rpc SetSleepState(SleepStateMessage) returns (SleepStateResult) {}
rpc SleepStateSubscription(Empty) returns (stream SleepStateMessage) {}
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package com.ifit.glassos;
import "console/spoofing/SpoofPartNumberResult.proto";
import "util/Util.proto";
option java_package = "com.ifit.glassos.console.spoofing";
option java_outer_classname = "ConsoleSpoofingServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
service ConsoleSpoofingService {
rpc SetSpoofedPartNumber(IntRequest) returns (SpoofPartNumberResult) {}
rpc GetSpoofedPartNumber(Empty) returns (SpoofPartNumberResult) {}
rpc ClearSpoofedPartNumber(Empty) returns (SpoofPartNumberResult) {}
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
option java_package = "com.ifit.glassos.console.spoofing";
option java_outer_classname = "SpoofPartNumberResultProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message SpoofPartNumberResult {
oneof errorOrPartNumber {
IFitError error = 1;
int32 partNumber = 2;
}
}

View File

@@ -0,0 +1,26 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
option java_package = "com.ifit.glassos.console.tdf";
option java_outer_classname = "TDFChainRingConfigProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum TDFChainRingConfig {
INVALID_CHAIN_RING = 0;
COMPACT_34_50 = 1;
SUB_COMPACT_36_52 = 2;
STANDARD_39_53 = 3;
TRIPLE_30_39_53 = 4;
}
message TDFChainRingConfigList {
repeated TDFChainRingConfig chainRingConfigs = 1;
}
message TDFChainRingConfigsResult {
oneof errorOrChainRingConfigs {
IFitError error = 1;
TDFChainRingConfigList chainRingConfigs = 2;
}
}

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
option java_package = "com.ifit.glassos.console.tdf";
option java_outer_classname = "TDFGearProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message TDFGear {
int32 frontGear = 1;
int32 rearGear = 2;
}
message TDFGearResult {
oneof errorOrGear {
IFitError error = 1;
TDFGear gear = 2;
}
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package com.ifit.glassos;
import "console/tdf/TDFChainRingConfig.proto";
import "console/tdf/TDFRearCassetteConfig.proto";
import "util/IFitError.proto";
option java_package = "com.ifit.glassos.console.tdf";
option java_outer_classname = "TDFGearConfigProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message TDFGearConfig {
TDFChainRingConfig frontGearConfig = 1;
TDFRearCassetteConfig rearGearConfig = 2;
}
message TDFGearConfigResult {
oneof errorOrGearConfig {
IFitError error = 1;
TDFGearConfig gearConfig = 2;
}
}

View File

@@ -0,0 +1,29 @@
syntax = "proto3";
package com.ifit.glassos;
import "console/tdf/TDFChainRingConfig.proto";
import "console/tdf/TDFGearConfig.proto";
import "console/tdf/TDFGear.proto";
import "console/tdf/TDFRearCassetteConfig.proto";
import "util/Util.proto";
option java_package = "com.ifit.glassos.console.tdf";
option java_outer_classname = "TDFGearServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
service TDFGearService {
rpc IsSupported(Empty) returns (BooleanResponse) {}
rpc ResetGearConfig(Empty) returns (TDFGearConfigResult) {}
rpc ResetGearAndGearConfig(Empty) returns (BooleanResponse) {}
rpc SetGearConfig(TDFGearConfig) returns (TDFGearConfigResult) {}
rpc GetGearConfig(Empty) returns (TDFGearConfigResult) {}
rpc ListFrontGearConfigs(Empty) returns (TDFChainRingConfigsResult) {}
rpc ListRearGearConfigs(Empty) returns (TDFRearCassetteConfigsResult) {}
rpc GearConfigChangedSubscription(Empty) returns (stream TDFGearConfig) {}
rpc SetGear(TDFGear) returns (TDFGearResult) {}
rpc GetCurrentGear(Empty) returns (TDFGearResult) {}
rpc GearChangedSubscription(Empty) returns (stream TDFGear) {}
rpc GetGearRatio(Empty) returns (FloatResponse) {}
rpc GearRatioChangedSubscription(Empty) returns (stream FloatResponse) {}
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
option java_package = "com.ifit.glassos.console.tdf";
option java_outer_classname = "TDFRearCassetteConfigProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message TDFRearCassetteConfig {
int32 minTeeth = 1;
int32 maxTeeth = 2;
int32 speeds = 3;
repeated int32 teethAtGear = 4;
}
message TDFRearCassetteConfigList {
repeated TDFRearCassetteConfig cassetteConfigs = 1;
}
message TDFRearCassetteConfigsResult {
oneof errorOrCassetteConfigs {
IFitError error = 1;
TDFRearCassetteConfigList cassetteConfigs = 2;
}
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.update";
option java_outer_classname = "FirmwareTypeProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum FirmwareType {
FIRMWARE_TYPE_UNKNOWN = 0;
FIRMWARE_TYPE_BRAINBOARD = 1;
FIRMWARE_TYPE_ANT_PLUS_APPLICATION = 2;
FIRMWARE_TYPE_ANT_PLUS_BOOTLOADER = 3;
FIRMWARE_TYPE_MOTOR_CONTROLLER = 4;
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.update";
option java_outer_classname = "FirmwareUpdateFileProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/update/FirmwareType.proto";
message FirmwareUpdateFile {
FirmwareType updateType = 1;
string filePath = 2;
string fileName = 3;
string version = 4;
int32 partNumber = 5;
bool forceUpdate = 6;
}

View File

@@ -0,0 +1,17 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.update";
option java_outer_classname = "FirmwareUpdateServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/update/FirmwareUpdateFile.proto";
import "console/update/FirmwareUpdateState.proto";
import "console/update/FirmwareUpdateStatus.proto";
import "util/Util.proto";
service FirmwareUpdateService {
rpc GetFirmwareUpdateStatus(Empty) returns (FirmwareUpdateStatus) {}
rpc FirmwareUpdateStatusChangedSubscription(Empty) returns (stream FirmwareUpdateStatus) {}
rpc StartFirmwareUpdate(FirmwareUpdateFile) returns (FirmwareUpdateStatus) {}
}

View File

@@ -0,0 +1,16 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.update";
option java_outer_classname = "FirmwareUpdateStateProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum FirmwareUpdateState {
FIRMWARE_UPDATE_STATE_UNKNOWN = 0;
FIRMWARE_UPDATE_STATE_IDLE = 1;
FIRMWARE_UPDATE_STATE_PREPARING = 2;
FIRMWARE_UPDATE_STATE_UPDATING = 3;
FIRMWARE_UPDATE_STATE_VERIFYING = 4;
FIRMWARE_UPDATE_STATE_SUCCESSFUL = 5;
FIRMWARE_UPDATE_STATE_FAILED = 6;
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.update";
option java_outer_classname = "FirmwareUpdateStatusProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "google/protobuf/timestamp.proto";
import "console/update/FirmwareUpdateFile.proto";
import "console/update/FirmwareUpdateState.proto";
message FirmwareUpdateStatus {
FirmwareUpdateState state = 1;
string updateSessionId = 2;
FirmwareUpdateFile updateFile = 3;
google.protobuf.Timestamp startTime = 4;
google.protobuf.Timestamp endTime = 5;
string resultMessage = 6;
float percentComplete = 7;
}

View File

@@ -0,0 +1,28 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.useractivity";
option java_outer_classname = "UserActivityProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "google/protobuf/duration.proto";
message DurationResult {
google.protobuf.Duration duration = 1;
}
message UserActivityOverrideMessage {
string id = 1;
}
message SetDurationRequest {
google.protobuf.Duration duration = 1;
}
message UserActivityServiceResult {
oneof errorOrSuccess {
IFitError error = 1;
bool success = 2;
}
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.useractivity";
option java_outer_classname = "UserActivityServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "console/useractivity/UserActivity.proto";
import "util/Util.proto";
service UserActivityService {
rpc DurationSinceLastScreenTap(Empty) returns (stream DurationResult) {}
rpc StartUserActivityOverride(Empty) returns (UserActivityOverrideMessage) {}
rpc CompleteUserActivityOverride(UserActivityOverrideMessage) returns (UserActivityServiceResult) {}
rpc SetDurationSinceLastScreenTap(SetDurationRequest) returns (UserActivityServiceResult) {}
}

View File

@@ -0,0 +1,13 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.console.virtualdmk";
option java_outer_classname = "VirtualDMKServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "util/Util.proto";
service VirtualDMKService {
rpc GetDMKOverride(Empty) returns (BooleanResponse) {}
rpc SetDMKOverride(BooleanRequest) returns (IFitError) {}
}

View File

@@ -0,0 +1,64 @@
syntax = "proto3";
package com.ifit.glassos;
import "user/UserTier.proto";
import "console/ConsoleType.proto";
import "club/ClubSettingsService.proto";
option java_package = "com.ifit.glassos.featuregates";
option java_outer_classname = "FeatureGateFacetProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum FeatureGateFacet {
FEATURE_FACET_UNKNOWN = 0;
FEATURE_FACET_CHINA = 1;
FEATURE_FACET_CLUB_CONSOLE = 2;
FEATURE_FACET_CLUB_USER_ROLE = 3;
FEATURE_FACET_DEMO_MODE = 4;
FEATURE_FACET_ENTIRE_FEATURE = 5;
FEATURE_FACET_MOBILE = 6;
FEATURE_FACET_MOBILE_FORM_FACTOR = 7;
FEATURE_FACET_MODALITY = 8;
FEATURE_FACET_SOFTWARE_NUMBER = 9;
FEATURE_FACET_USER_TIER = 10;
}
message FacetMessage {
FeatureGateFacet featureGateFacet = 1;
oneof payload {
BooleanFacetPayload booleanFacetPayload = 2;
EnumeratedUserRoleFacetMessage enumeratedUserRoleFacetMessage = 3;
EnumeratedStringFacetMessage enumeratedStringFacetMessage = 4;
EnumeratedConsoleTypeFacetMessage enumeratedConsoleTypeFacetMessage = 5;
EnumeratedIntFacetMessage enumeratedIntFacetMessage = 6;
EnumeratedUserTierFacetMessage enumeratedUserTierFacetMessage = 7;
}
}
message BooleanFacetPayload {
bool enabled = 1;
}
message EnumeratedUserRoleFacetMessage {
repeated club.UserRole allowedValues = 1;
repeated club.UserRole disallowedValues = 2;
}
message EnumeratedStringFacetMessage {
repeated string allowedValues = 1;
repeated string disallowedValues = 2;
}
message EnumeratedConsoleTypeFacetMessage {
repeated ConsoleType allowedValues = 1;
repeated ConsoleType disallowedValues = 2;
}
message EnumeratedIntFacetMessage {
repeated int32 allowedValues = 1;
repeated int32 disallowedValues = 2;
}
message EnumeratedUserTierFacetMessage {
repeated UserTier allowedValues = 1;
repeated UserTier disallowedValues = 2;
}

View File

@@ -0,0 +1,26 @@
syntax = "proto3";
package com.ifit.glassos;
import "featuregates/FeatureGateFacet.proto";
import "featuregates/GatedFeature.proto";
import "club/ClubSettingsService.proto";
import "console/ConsoleType.proto";
import "user/UserTier.proto";
option java_package = "com.ifit.glassos.featuregates";
option java_outer_classname = "FeatureGateResultProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message FeatureGateResult {
GatedFeature gatedFeature = 1;
bool enabled = 2;
bool incomplete = 3;
FacetMessage featureGateFacet = 4;
oneof value {
bool booleanValue = 5;
club.UserRole userRole = 6;
string stringValue = 7;
int32 intValue = 8;
ConsoleType consoleType = 9;
UserTier userTier = 10;
}
}

View File

@@ -0,0 +1,14 @@
syntax = "proto3";
package com.ifit.glassos;
import "featuregates/FeatureGateFacet.proto";
import "featuregates/GatedFeature.proto";
option java_package = "com.ifit.glassos.featuregates";
option java_outer_classname = "FeatureGateRuleProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message FeatureGateRule {
GatedFeature gatedFeature = 1;
bool incomplete = 2;
repeated FacetMessage featureGateFacet = 3;
}

Some files were not shown because too many files have changed in this diff Show More