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
192 changed files with 10895 additions and 2115 deletions

File diff suppressed because it is too large Load Diff

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

@@ -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

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

@@ -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

@@ -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

@@ -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;
}

View File

@@ -0,0 +1,18 @@
syntax = "proto3";
package com.ifit.glassos;
import "featuregates/GatedFeature.proto";
import "featuregates/FeatureGateResult.proto";
import "featuregates/FeatureGateRule.proto";
option java_package = "com.ifit.glassos.featuregates";
option java_outer_classname = "FeatureGateServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message GatedFeatureMessage {
GatedFeature gatedFeature = 1;
}
service FeatureGateService {
rpc CheckFeatureGateStatus(GatedFeatureMessage) returns (FeatureGateResult) {}
rpc GetFeatureGateRule(GatedFeatureMessage) returns (FeatureGateRule) {}
}

View File

@@ -0,0 +1,13 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.featuregates";
option java_outer_classname = "GatedFeatureProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum GatedFeature {
GATED_FEATURE_UNKNOWN = 0;
GATED_FEATURE_UPSELL = 1;
GATED_FEATURE_CHALLENGES = 2;
GATED_FEATURE_WORKOUT_CREATOR = 3;
}

View File

@@ -0,0 +1,442 @@
// Copyright 2020 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.
// Protos for logging firebase performance monitoring metrics collected by the
// Firebase performance sdk and transmitted to the backend using Firebase data transport.
//
// PRD: go/fireperf-prd
// SDK Data Model: go/fireperf-data-model
syntax = "proto2";
package firebase.perf.v1;
option java_multiple_files = true;
option java_outer_classname = "FirebasePerfMetricProto";
option java_package = "com.google.firebase.perf.v1";
// Firebase Perf Reporting Message
option objc_class_prefix = "FPRMSG";
// Single unit of performance data collected from firebase integrated 3P apps by the firebase
// performance sdk. This will be an an extension to GWSLogEntryProto and will correspond to one
// record in the RecordIO logs generated by firebase data transport. Every firebase performance
// related event logged to firebase data transport by the sdk will encapsulate one instance of this
// object.
//
// Next tag: 6
message PerfMetric {
// Additional metadata about an application and its state (including state of
// the device at runtime) that is not provided by firebase data transport.
optional ApplicationInfo application_info = 1;
// A metric which represents the performance statistics collected within an
// instrumented trace.
optional TraceMetric trace_metric = 2;
// A metric which represents the network latency, bandwidth and network
// connection info about a network request captured by the firebase sdk.
optional NetworkRequestMetric network_request_metric = 3;
// A metric which represents session gauges, such as cpu, memory, battery,
// within a session.
optional GaugeMetric gauge_metric = 4;
// A metric which represents the transport related information.
// When transport_info field is empty, it means the message is from Cct.
optional TransportInfo transport_info = 5;
}
// Metric which represents everything collected in the span of a trace. A trace
// may be further divided into subtraces.
// The trace can either be a default out of the box trace which is a
// part of the default instrumentation provided by the firebase performance sdk
// or a custom trace instrumented by the app developer using the sdk apis.
//
// Next tag: 10
message TraceMetric {
// The name of the trace. This could either be a user defined name for the
// developer instrumented custom traces or a default for traces automatically
// instrumented by the Firebase Performance SDK. The max length of 64
// characters will be enforced by the sdk.
optional string name = 1; // required.
// If true, then this is considered to be a trace automatically instrumented
// by the performance sdk. Otherwise, it is considered to be a custom trace
// instrumented by the developer using firebase perf sdk apis.
optional bool is_auto = 2;
// The timestamp in microseconds since epoch when the trace was started. This
// time is recorded using the device clock.
optional int64 client_start_time_us = 4; // required.
// The duration of the trace in microseconds.
optional int64 duration_us = 5; // required.
// A map of custom or default counter names to values.
map<string, int64> counters = 6;
// The metrics for subtraces within the trace.
// The following restrictions are currently enforced by the sdk on subtraces:
// Subtraces should only have 1 level of nesting.
// Subtraces should be non overlapping.
// Subtraces should be continuous, i.e no gaps between consecutive subtraces.
repeated TraceMetric subtraces = 7;
// A map of trace-level custom attribute names to values.
map<string, string> custom_attributes = 8;
// Sessions across which the trace spanned. A session lasts from one
// change in the app state (foreground/background) to the next. Basically
// every foreground and background session gets it's own session id. A trace
// may span across multiple such sessions. So we need a list to identify which
// sessions it spanned across.
repeated PerfSession perf_sessions = 9;
}
// Metric which represents the latency, bandwidth consumption and other details
// about a network request captured by the firebase sdk.
//
// Next tag: 14
message NetworkRequestMetric {
// The parameterless url to which the network request was made. The sdk will
// redact the unnecessary components of the URL and only log the components
// which are useful. For a url of the form
// scheme://host[:port]/path[?params][#fragment], the sdk should only log
// scheme://host[:port]/path
// Example:
// Captured Url: https://wwww.google.com/maps/cities#seattle?id=123
// Logged Url: https://wwww.google.com/maps/cities
optional string url = 1; // required.
// Supported HTTP methods for aggregating network requests. All network
// requests that can not be classified into the 9 methods below should be set
// to HTTP_METHOD_UNKNOWN.
enum HttpMethod {
HTTP_METHOD_UNKNOWN = 0;
GET = 1;
PUT = 2;
POST = 3;
DELETE = 4;
HEAD = 5;
PATCH = 6;
OPTIONS = 7;
TRACE = 8;
CONNECT = 9;
}
// The HTTP verb for the network request. Common values include GET,
// PUT, POST and DELETE
optional HttpMethod http_method = 2; // required.
// The size of the payload in the request.
optional int64 request_payload_bytes = 3;
// The size of the payload in the response.
optional int64 response_payload_bytes = 4;
// Info about the type of client error during network call.
enum NetworkClientErrorReason {
// Unspecified Network Client Error Reason.
NETWORK_CLIENT_ERROR_REASON_UNKNOWN = 0;
// No attempt made to classify the error.
GENERIC_CLIENT_ERROR = 1;
// Add the specific client error types below.
}
// The client error received from the networking library.
// Do not record a client error if we have HTTP response code available.
optional NetworkClientErrorReason network_client_error_reason = 11;
// The Http response code received from the server.
optional int32 http_response_code = 5; // required.
// The value of the content type header in the response.
optional string response_content_type = 6;
// The timestamp in microseconds since epoch when the network request was
// initiated. This time is recorded using the device clock.
optional int64 client_start_time_us = 7; // required.
// The time in microseconds since the start of the network request and the
// upload of the last request byte.
optional int64 time_to_request_completed_us = 8;
// The time in microseconds between the start of the network request and the
// receipt of the first byte of the response headers.
optional int64 time_to_response_initiated_us = 9;
// The time in microseconds between the start of the network request and the
// receipt of the last response byte.
optional int64 time_to_response_completed_us = 10; // required.
// A map of network-level custom attribute names to values.
map<string, string> custom_attributes = 12;
// Sessions across which the network request spanned. A session lasts
// from one change in the app state (foreground/background) to the next.
// Basically every foreground and background session gets it's own session id.
// A network request may span across multiple such sessions. So we need a list
// to identify which sessions it spanned across.
repeated PerfSession perf_sessions = 13;
}
// Metadata about a session and the amount of detail information it contains.
// See go/what-is-a-perf-session
message PerfSession {
// The id of a session.
optional string session_id = 1;
// The level of amount of detailed information that this session captures.
repeated SessionVerbosity session_verbosity = 2;
}
// Metric which represents gauges collected during the span of a session,
// including cpu, memory, battery, etc.
// The gauges will be collected by our own sdk and be purely numeric readings,
// user cannot pass any information here, so cannot contain PIIs.
//
// Next tag: 6
message GaugeMetric {
// Identifier of the session in which this gauge reading takes place.
// A session_id is specific to a device instance, and is used to tie gauge
// metrics to other peer traces and network requests that occurs during
// the session.
optional string session_id = 1;
// Metadata of gauge metrics whose value stay constant throughout the session.
optional GaugeMetadata gauge_metadata = 3;
// List of cpu gauge readings recorded in the session.
repeated CpuMetricReading cpu_metric_readings = 2;
// List of Android memory readings recorded, absent for iOS apps.
repeated AndroidMemoryReading android_memory_readings = 4;
}
// One reading of cpu gauge metric.
// See go/fireperf-cpu-gauge-metric.
//
// Next tag: 4
message CpuMetricReading {
// The timestamp in microseconds since epoch when this snapshot took place.
// This time is recorded using the device clock.
optional int64 client_time_us = 1;
// The total user cpu time since process started in microseconds.
optional int64 user_time_us = 2;
// The total system cpu time since process started in microseconds.
optional int64 system_time_us = 3;
}
// One reading of Android memory gauge metric.
// Note that this is cheap-to-capture memory reading, which is different from
// application's summary of memory usage (expensive to capture). Summary of
// memory usage will be captured at a much lower frequency in a different proto.
// See go/fireperf-sessions-memory.
//
// Next tag: 3
message AndroidMemoryReading {
// The timestamp in microseconds since epoch when this snapshot took place.
// This time is recorded using the device clock.
optional int64 client_time_us = 1;
// The amount of java heap memory that the app is using, in kilobytes.
optional int32 used_app_java_heap_memory_kb = 2;
}
// Metadata about gauges of a session.
// These are the gauge values that stay constant throughout the entire session.
// Examples include maxAppJavaHeapMemory (max memory allowed for the app) and
// cpuFrequency (frequency of cpu of the device that the app is running on).
// As long as one GaugeMetadata is sent for a session, these metadata will be
// available for all elements of the session. If multiple GaugeMetadata are sent
// for the same session, they are expected to be identical.
//
// Next tag: 7
message GaugeMetadata {
// Deprecated on 09/2022.
optional string process_name = 1 [deprecated = true];
// Clock rate of the cpu of the device, in kHz.
optional int32 cpu_clock_rate_khz = 2;
// The number of cpu cores that the device has.
optional int32 cpu_processor_count = 6;
// Size of RAM of the device, in kilobytes.
optional int32 device_ram_size_kb = 3;
// Maximum amount of memory the app can use before an OutOfMemoryException
// is triggered, in kilobytes.
// Only present for Android apps.
optional int32 max_app_java_heap_memory_kb = 4;
// The maximum amount of memory the app is encouraged to use to be properly
// respectful of the limits of the client device.
// Only present for Android apps.
optional int32 max_encouraged_app_java_heap_memory_kb = 5;
}
// Additional metadata about an application and its state (including state of
// the device at runtime) that is not provided by firebase data transport.
//
// Next tag: 8
message ApplicationInfo {
// Identifier for the application that has been registered with firebase.
// Contains pantheon project number, platform and the hash of the (package
// name or bundle id) fields in hex.
// [Version]:[Project Number]:[Platform]:[Hash(package_name/bundle_id)]
// The app id contains Pantheon project number which is a GAIA ID that
// identifies a particular organization or a customer.
optional string google_app_id = 1; // required.
// The App Instance Id which is used to compute the distinct users for which
// the metrics are recorded.
// Look at go/iid-features for more details about the instance id.
optional string app_instance_id = 2; // required.
// One of android_app_info, ios_app_info, and web_app_info is required.
// Additional information specific to an android app.
optional AndroidApplicationInfo android_app_info = 3;
// State of the application process during metric collection.
optional ApplicationProcessState application_process_state = 5; // required.
// A map of global-level custom attribute names to values.
map<string, string> custom_attributes = 6;
}
// Additional metadata about an android application that is not provided by
// firebase data transport.
//
// Next tag: 4
message AndroidApplicationInfo {
// The package name of the android application.
// e.g com.google.android.apps.maps
optional string package_name = 1; // required.
// The sdk version of the firebase perf android sdk.
optional string sdk_version = 2; // required.
// The versionName of the android application as shown on the play store.
// Firebase data transport logs the versionCode in the GWSLogEntryProto field:
// PlayExtension.client_info.android_client_info.application_build
// This field is necessary till the data transport supports logging version_name by
// default: b/32584283
optional string version_name = 3;
}
// To describe the network connectivity of the client.
// Copied from go/clientanalytics.proto
// Next tag: 3
message NetworkConnectionInfo {
enum NetworkType {
NONE = -1;
MOBILE = 0;
WIFI = 1;
MOBILE_MMS = 2;
MOBILE_SUPL = 3;
MOBILE_DUN = 4;
MOBILE_HIPRI = 5;
WIMAX = 6;
BLUETOOTH = 7;
DUMMY = 8;
ETHERNET = 9;
MOBILE_FOTA = 10;
MOBILE_IMS = 11;
MOBILE_CBS = 12;
WIFI_P2P = 13;
MOBILE_IA = 14;
MOBILE_EMERGENCY = 15;
PROXY = 16;
VPN = 17;
}
enum MobileSubtype {
UNKNOWN_MOBILE_SUBTYPE = 0;
GPRS = 1;
EDGE = 2;
UMTS = 3;
CDMA = 4;
EVDO_0 = 5;
EVDO_A = 6;
RTT = 7;
HSDPA = 8;
HSUPA = 9;
HSPA = 10;
IDEN = 11;
EVDO_B = 12;
LTE = 13;
EHRPD = 14;
HSPAP = 15;
GSM = 16;
TD_SCDMA = 17;
IWLAN = 18;
LTE_CA = 19;
// COMBINED has value -1 in NetworkIdentity.java, but is given the value
// 100 here to save (disk) space. The value -1 takes up the full 10 bytes in
// a varint for enums, but the value 100 only takes up 1 byte.
COMBINED = 100;
}
// The current network connectivity type when the event was logged in the
// client
optional NetworkType network_type = 1 [default = NONE];
// The current mobile connectivity subtype when the event was logged in the
// client
optional MobileSubtype mobile_subtype = 2 [default = UNKNOWN_MOBILE_SUBTYPE];
}
// Transport related metadata info.
// Next tag: 2
message TransportInfo {
// Dispatch destination for the event.
enum DispatchDestination {
SOURCE_UNKNOWN = 0; // Reserved
FL_LEGACY_V1 = 1; // Flg legacy endpoint
}
// Destination to which the events are sent.
optional DispatchDestination dispatch_destination = 1;
}
// Metadata about the state of application process during metrics collection.
//
enum ApplicationProcessState {
// Unspecified application process state.
APPLICATION_PROCESS_STATE_UNKNOWN = 0;
// Application process was in foreground
FOREGROUND = 1;
// Application process was in background
BACKGROUND = 2;
// Application process was both in foreground and background for the duration
// of metrics collection.
FOREGROUND_BACKGROUND = 3;
}
// The level of detailed information that is captured in a Perf Session, known
// as a session's verbosity. For different session we collect different levels
// of detailed information (or none at all) to avoid penalizing the same device
// constantly.
enum SessionVerbosity {
// Session doesn't have detailed information.
SESSION_VERBOSITY_NONE = 0;
// Session has gauges and system events information.
// See go/fireperf-samples-metrics-all for the list of gauges and
// system events that are captured within a session.
GAUGES_AND_SYSTEM_EVENTS = 1;
}

View File

@@ -0,0 +1,26 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "BrightnessServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "util/Util.proto";
message Brightness {
float brightnessPercent = 1;
}
message BrightnessResult {
oneof errorOrBrightness {
IFitError error = 1;
Brightness brightness = 2;
}
}
service BrightnessService {
rpc BrightnessChanged(Empty) returns (stream Brightness) {}
rpc SetBrightness(Brightness) returns (BrightnessResult) {}
rpc GetBrightness(Empty) returns (BrightnessResult) {}
}

View File

@@ -0,0 +1,39 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "DemoModeServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "util/Util.proto";
message DemoMode {
bool demoModeEnabled = 1;
}
message DemoModeResult {
oneof errorOrDemoMode {
IFitError error = 1;
DemoMode demoMode = 2;
}
}
message DemoModeScreensaverVolume {
float volume = 1;
}
message DemoModeScreensaverVolumeResult {
oneof errorOrDemoModeScreensaverVolume {
IFitError error = 1;
DemoModeScreensaverVolume demoModeScreensaverVolume = 2;
}
}
service DemoModeService {
rpc DemoModeChanged(Empty) returns (stream DemoMode) {}
rpc SetDemoMode(DemoMode) returns (DemoModeResult) {}
rpc GetDemoMode(Empty) returns (DemoModeResult) {}
rpc SetDemoModeScreensaverVolume(DemoModeScreensaverVolume) returns (DemoModeScreensaverVolumeResult) {}
rpc GetDemoModeScreensaverVolume(Empty) returns (DemoModeScreensaverVolumeResult) {}
}

View File

@@ -0,0 +1,29 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "FanStateServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum FanState {
FAN_STATE_OFF = 0;
FAN_STATE_LOW = 1;
FAN_STATE_MEDIUM = 2;
FAN_STATE_HIGH = 3;
FAN_STATE_AUTO = 4;
FAN_STATE_UNKNOWN = 5;
}
message FanStateMessage {
FanState state = 1;
}
service FanStateService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc GetFanState(Empty) returns (FanStateMessage) {}
rpc FanStateChanged(Empty) returns (stream FanStateMessage) {}
rpc SetFanState(FanStateMessage) returns (Empty) {}
rpc IsAutoFanStateSupported(Empty) returns (AvailabilityResponse) {}
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "MaxSpeedServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message MaxSpeedMessage {
double maxSpeedKph = 1;
}
service MaxSpeedService {
// sets the Max Speed for scaling workouts, we will not auto scale to a control set that would
// go above this value
rpc SetMaxSpeed(MaxSpeedMessage) returns (Empty) {}
// subscribe to active pulse state updates
rpc MaxSpeedSubscription(Empty) returns (stream MaxSpeedMessage) {}
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "SettingsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "settings/VideoQualityService.proto";
message Settings {
VideoQuality videoQuality = 1;
}

View File

@@ -0,0 +1,32 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/IFitError.proto";
import "util/Util.proto";
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "SystemUnitsServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum SystemUnits {
METRIC = 0;
STANDARD = 1;
}
message SystemUnitsMessage {
SystemUnits units = 1;
}
message SystemUnitsResult {
oneof errorOrSystemUnits {
IFitError error = 1;
SystemUnits systemUnits = 2;
}
}
service SystemUnitsService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc GetSystemUnits(Empty) returns (SystemUnitsMessage) {}
rpc SystemUnitsChanged(Empty) returns (stream SystemUnitsMessage) {}
rpc SetSystemUnits(SystemUnitsMessage) returns (Empty) {}
}

View File

@@ -0,0 +1,34 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "TimeZoneServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "util/Util.proto";
message TimeZone {
string id = 1;
string offsetString = 2;
int32 utcOffset = 3;
string name = 4;
}
message TimeZoneResult {
oneof errorOrTimeZone {
IFitError error = 1;
TimeZone timeZone = 2;
}
}
message TimeZoneList {
repeated TimeZone timeZones = 1;
}
service TimeZoneService {
rpc GetAllTimeZones(Empty) returns (TimeZoneList) {}
rpc SetTimeZone(TimeZone) returns (TimeZoneResult) {}
rpc GetCurrentTimeZone(Empty) returns (TimeZoneResult) {}
rpc TimeZoneChanged(Empty) returns (stream TimeZone) {}
}

View File

@@ -0,0 +1,35 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "VideoQualityServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
import "util/IFitError.proto";
import "util/Util.proto";
enum VideoQualityLevel {
OPTIMIZED = 0;
HIGHEST = 1;
HIGH = 2;
MEDIUM = 3;
LOW = 4;
LOWEST = 5;
}
message VideoQuality {
VideoQualityLevel value = 1;
}
message VideoQualityResult {
oneof errorOrVideoQuality {
IFitError error = 1;
VideoQuality videoQuality = 2;
}
}
service VideoQualityService {
rpc SetVideoQuality(VideoQuality) returns (VideoQualityResult) {}
rpc GetVideoQuality(Empty) returns (VideoQualityResult) {}
rpc VideoQualityChanged(Empty) returns (stream VideoQuality) {}
}

View File

@@ -0,0 +1,21 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
option java_package = "com.ifit.glassos.settings";
option java_outer_classname = "VolumeServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message VolumeMessage {
double percentage = 1;
}
service VolumeService {
rpc CanRead(Empty) returns (AvailabilityResponse) {}
rpc CanWrite(Empty) returns (AvailabilityResponse) {}
rpc GetVolume(Empty) returns (VolumeMessage) {}
rpc IncreaseVolume(Empty) returns (Empty) {}
rpc DecreaseVolume(Empty) returns (Empty) {}
rpc VolumeChanged(Empty) returns (stream VolumeMessage) {}
rpc SetVolume(VolumeMessage) returns (Empty) {}
}

View File

@@ -0,0 +1,54 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
option java_package = "com.ifit.glassos.settings.hdmi";
option java_outer_classname = "HdmiSoundServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum AqProfile {
AQ_PROFILE_DIALOG = 0;
AQ_PROFILE_MUSIC = 1;
AQ_PROFILE_MOVIE = 2;
AQ_PROFILE_CUSTOM = 3;
}
enum AqLevel {
AQ_LEVEL_LOWEST = 0;
AQ_LEVEL_LOW = 1;
AQ_LEVEL_MEDIUM = 2;
AQ_LEVEL_HIGH = 3;
AQ_LEVEL_HIGHEST = 4;
AQ_LEVEL_NOT_SET = 5;
}
message AqProfileMessage {
AqProfile profile = 1;
}
message AqLevelsMessage {
optional AqLevel bassLevel = 1;
optional AqLevel trebleLevel = 2;
optional AqLevel surroundLevel = 3;
optional AqLevel dialogLevel = 4;
}
message HdmiSoundStateMessage {
AqProfile profile = 1;
bool muted = 2;
AqLevel bassLevel = 3;
AqLevel trebleLevel = 4;
AqLevel surroundLevel = 5;
AqLevel dialogLevel = 6;
}
service HdmiSoundService {
rpc IsEnabled(Empty) returns (AvailabilityResponse) {}
rpc IsEnabledFlow(Empty) returns (stream AvailabilityResponse) {}
rpc SetMute(BooleanRequest) returns (Empty) {}
rpc SetAqProfile(AqProfileMessage) returns (Empty) {}
rpc SetAqLevels(AqLevelsMessage) returns (Empty) {}
rpc GetHdmiSoundState(Empty) returns (HdmiSoundStateMessage) {}
rpc HdmiSoundStateChanged(Empty) returns (stream HdmiSoundStateMessage) {}
}

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package com.ifit.glassos;
import "settings/heartrate/HeartRateZone.proto";
option java_package = "com.ifit.glassos.settings.heartrate";
option java_outer_classname = "HeartRateSettingsProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message HeartRateSettings {
int32 restingHeartRate = 1;
int32 maxHeartRate = 2;
int32 heartRateReserve = 3;
HeartRateZone zone0 = 4;
HeartRateZone zone1 = 5;
HeartRateZone zone2 = 6;
HeartRateZone zone3 = 7;
HeartRateZone zone4 = 8;
HeartRateZone zone5 = 9;
}

View File

@@ -0,0 +1,27 @@
syntax = "proto3";
package com.ifit.glassos;
import "util/Util.proto";
import "settings/heartrate/HeartRateSettings.proto";
option java_package = "com.ifit.glassos.settings.heartrate";
option java_outer_classname = "HeartRateSettingsServiceProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message HeartRateSettingsUpdate {
int32 restingHeartRate = 1;
int32 maxHeartRate = 2;
}
service HeartRateSettingsService {
// subscribe to heart rate settings changes
rpc HeartRateSettingsSubscription(Empty) returns (stream HeartRateSettings) {}
// reset heart rate settings to default values
rpc ResetHeartRateSettingsToDefault(Empty) returns (HeartRateSettings) {}
// updates heart rate settings
rpc UpdateHeartRateSettings(HeartRateSettingsUpdate) returns (HeartRateSettings) {}
// get the current heart rate settings
rpc GetHeartRateSettings(Empty) returns (HeartRateSettings) {}
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package com.ifit.glassos;
import "settings/heartrate/HeartRateZoneNumber.proto";
option java_package = "com.ifit.glassos.settings.heartrate";
option java_outer_classname = "HeartRateZoneProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
message HeartRateZone {
HeartRateZoneNumber zoneNumber = 1;
int32 minBpm = 2;
int32 maxBpm = 3;
int32 minPercentage = 4;
int32 maxPercentage = 5;
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package com.ifit.glassos;
option java_package = "com.ifit.glassos.settings.heartrate";
option java_outer_classname = "HeartRateZoneNumberProto";
option java_multiple_files = true;
option swift_prefix = "IFit";
enum HeartRateZoneNumber {
ZONE_0 = 0;
ZONE_1 = 1;
ZONE_2 = 2;
ZONE_3 = 3;
ZONE_4 = 4;
ZONE_5 = 5;
}

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