Compare commits

..

3 Commits

Author SHA1 Message Date
Roberto Viola
f49ac87ff8 Update wahookickrsnapbike.cpp 2025-09-18 10:19:07 +02:00
Roberto Viola
2aba7ddfe1 adding debug popup 2025-09-18 09:38:30 +02:00
Roberto Viola
d02ca5a934 Wahoo Commands Queue 2025-09-16 15:45:30 +02:00
27 changed files with 137 additions and 1331 deletions

View File

@@ -371,5 +371,4 @@ 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
- #usa le qdebug invece che le emit debug
* Add the property at the END of the properties list

View File

@@ -4455,7 +4455,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1161;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4655,7 +4655,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1161;
DEBUG_INFORMATION_FORMAT = dwarf;
DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES;
DEVELOPMENT_TEAM = 6335M7T29D;
@@ -4891,7 +4891,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1161;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4987,7 +4987,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1165;
CURRENT_PROJECT_VERSION = 1161;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -5079,7 +5079,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1166;
CURRENT_PROJECT_VERSION = 1161;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5195,7 +5195,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1166;
CURRENT_PROJECT_VERSION = 1161;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -845,6 +845,7 @@ Page {
text: qsTr("Finish")
onClicked: {
settings.tile_gears_enabled = true;
settings.gears_gain = 0.5;
stackViewLocal.push(finalStepComponent);
}
}
@@ -903,6 +904,7 @@ Page {
text: qsTr("Finish")
onClicked: {
settings.tile_gears_enabled = true;
settings.gears_gain = 1;
stackViewLocal.push(finalStepComponent);
}
}

View File

@@ -106,16 +106,6 @@
android:name=".ScreenCaptureService"
android:foregroundServiceType="mediaProjection" />
<service android:name=".VirtualGearingService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/virtual_gearing_service_config" />
</service>
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="ocr" />

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="virtual_gearing_service_description">Virtual Gearing Service for QZ - Enables touch simulation for virtual shifting in cycling apps</string>
</resources>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/virtual_gearing_service_description"
android:packageNames="@null"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true" />

View File

@@ -1,116 +0,0 @@
package org.cagnulen.qdomyoszwift;
import org.cagnulen.qdomyoszwift.QLog;
public class AppConfiguration {
private static final String TAG = "AppConfiguration";
public static class TouchCoordinate {
public final double xPercent;
public final double yPercent;
public TouchCoordinate(double xPercent, double yPercent) {
this.xPercent = xPercent;
this.yPercent = yPercent;
}
public int getX(int screenWidth) {
return (int) (screenWidth * xPercent);
}
public int getY(int screenHeight) {
return (int) (screenHeight * yPercent);
}
}
public static class AppConfig {
public final String appName;
public final String packageName;
public final TouchCoordinate shiftUp;
public final TouchCoordinate shiftDown;
public AppConfig(String appName, String packageName, TouchCoordinate shiftUp, TouchCoordinate shiftDown) {
this.appName = appName;
this.packageName = packageName;
this.shiftUp = shiftUp;
this.shiftDown = shiftDown;
}
}
// Predefined configurations based on SwiftControl
private static final AppConfig[] SUPPORTED_APPS = {
// MyWhoosh - coordinates from SwiftControl repository
new AppConfig(
"MyWhoosh",
"com.mywhoosh.whooshgame",
new TouchCoordinate(0.98, 0.94), // Shift Up - bottom right corner
new TouchCoordinate(0.80, 0.94) // Shift Down - more to the left
),
// IndieVelo / TrainingPeaks
new AppConfig(
"IndieVelo",
"com.indieVelo.client",
new TouchCoordinate(0.66, 0.74), // Shift Up - center right
new TouchCoordinate(0.575, 0.74) // Shift Down - center left
),
// Biketerra.com
new AppConfig(
"Biketerra",
"biketerra",
new TouchCoordinate(0.8, 0.5), // Generic coordinates for now
new TouchCoordinate(0.2, 0.5)
),
// Default configuration for unrecognized apps
new AppConfig(
"Default",
"*",
new TouchCoordinate(0.85, 0.9), // Conservative coordinates
new TouchCoordinate(0.15, 0.9)
)
};
public static AppConfig getConfigForPackage(String packageName) {
// Use custom coordinates from settings instead of hardcoded values
return getCurrentConfig();
}
// Get current configuration from user settings
public static AppConfig getCurrentConfig() {
try {
double shiftUpX = VirtualGearingBridge.getVirtualGearingShiftUpX();
double shiftUpY = VirtualGearingBridge.getVirtualGearingShiftUpY();
double shiftDownX = VirtualGearingBridge.getVirtualGearingShiftDownX();
double shiftDownY = VirtualGearingBridge.getVirtualGearingShiftDownY();
int appIndex = VirtualGearingBridge.getVirtualGearingApp();
String appName = "Custom";
if (appIndex >= 0 && appIndex < SUPPORTED_APPS.length) {
appName = SUPPORTED_APPS[appIndex].appName;
}
QLog.d(TAG, "Using custom coordinates: shiftUp(" + shiftUpX + "," + shiftUpY +
") shiftDown(" + shiftDownX + "," + shiftDownY + ") for " + appName);
return new AppConfig(
appName,
"*", // Package name not relevant for custom config
new TouchCoordinate(shiftUpX, shiftUpY),
new TouchCoordinate(shiftDownX, shiftDownY)
);
} catch (Exception e) {
QLog.e(TAG, "Error getting custom config, using fallback", e);
return getDefaultConfig();
}
}
public static AppConfig getDefaultConfig() {
return SUPPORTED_APPS[SUPPORTED_APPS.length - 1]; // Last element is the default
}
public static AppConfig[] getAllConfigs() {
return SUPPORTED_APPS;
}
}

View File

@@ -1,145 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import org.cagnulen.qdomyoszwift.QLog;
public class VirtualGearingBridge {
private static final String TAG = "VirtualGearingBridge";
public static boolean isAccessibilityServiceEnabled(Context context) {
String settingValue = Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
QLog.d(TAG, "Enabled accessibility services: " + settingValue);
if (settingValue != null) {
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
splitter.setString(settingValue);
while (splitter.hasNext()) {
String service = splitter.next();
QLog.d(TAG, "Checking service: " + service);
if (service.contains("org.cagnulen.qdomyoszwift/.VirtualGearingService") ||
service.contains("VirtualGearingService")) {
QLog.d(TAG, "VirtualGearingService is enabled");
return true;
}
}
}
QLog.d(TAG, "VirtualGearingService is not enabled");
return false;
}
public static void openAccessibilitySettings(Context context) {
try {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
QLog.d(TAG, "Opened accessibility settings");
} catch (Exception e) {
QLog.e(TAG, "Failed to open accessibility settings", e);
}
}
public static void simulateShiftUp() {
QLog.d(TAG, "Simulating shift up with app-specific coordinates");
VirtualGearingService.shiftUpSmart();
}
public static void simulateShiftDown() {
QLog.d(TAG, "Simulating shift down with app-specific coordinates");
VirtualGearingService.shiftDownSmart();
}
public static String getCurrentAppPackageName(Context context) {
try {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
ActivityManager.RunningAppProcessInfo myProcess = new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(myProcess);
// For Android 5.0+ we should use UsageStatsManager, but for simplicity
// we use a more direct approach via current foreground process
// In a complete implementation we should use UsageStatsManager
// For now return null and let the service detect the app
return null;
}
} catch (Exception e) {
QLog.e(TAG, "Error getting current app package name", e);
}
return null;
}
public static int[] getScreenSize(Context context) {
try {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
return new int[]{displayMetrics.widthPixels, displayMetrics.heightPixels};
} catch (Exception e) {
QLog.e(TAG, "Error getting screen size", e);
return new int[]{1080, 1920}; // Default fallback
}
}
public static void simulateTouch(int x, int y) {
QLog.d(TAG, "Simulating touch at (" + x + ", " + y + ")");
VirtualGearingService.simulateKeypress(x, y);
}
public static boolean isServiceRunning() {
boolean running = VirtualGearingService.isServiceEnabled();
QLog.d(TAG, "Service running: " + running);
return running;
}
// Native methods to get settings from C++ side
public static native double getVirtualGearingShiftUpX();
public static native double getVirtualGearingShiftUpY();
public static native double getVirtualGearingShiftDownX();
public static native double getVirtualGearingShiftDownY();
public static native int getVirtualGearingApp();
// Methods to get coordinates that will be/were sent
public static String getShiftUpCoordinates() {
try {
AppConfiguration.AppConfig config = AppConfiguration.getCurrentConfig();
// Use VirtualGearingService to get screen size (it has access to service context)
int[] screenSize = VirtualGearingService.getScreenSize();
int x = config.shiftUp.getX(screenSize[0]);
int y = config.shiftUp.getY(screenSize[1]);
return x + "," + y;
} catch (Exception e) {
QLog.e(TAG, "Error getting shift up coordinates", e);
return "0,0";
}
}
public static String getShiftDownCoordinates() {
try {
AppConfiguration.AppConfig config = AppConfiguration.getCurrentConfig();
// Use VirtualGearingService to get screen size (it has access to service context)
int[] screenSize = VirtualGearingService.getScreenSize();
int x = config.shiftDown.getX(screenSize[0]);
int y = config.shiftDown.getY(screenSize[1]);
return x + "," + y;
} catch (Exception e) {
QLog.e(TAG, "Error getting shift down coordinates", e);
return "0,0";
}
}
public static String getLastTouchCoordinates() {
// For now, return the last coordinates that would be sent for shift up
// This could be enhanced to track actual last touch
return getShiftUpCoordinates();
}
}

View File

@@ -1,152 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.graphics.Path;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import org.cagnulen.qdomyoszwift.QLog;
public class VirtualGearingService extends AccessibilityService {
private static final String TAG = "VirtualGearingService";
private static VirtualGearingService instance;
@Override
public void onCreate() {
super.onCreate();
instance = this;
QLog.d(TAG, "VirtualGearingService created");
}
@Override
public void onDestroy() {
super.onDestroy();
instance = null;
QLog.d(TAG, "VirtualGearingService destroyed");
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// Capture foreground app package name for smart coordinates
if (event != null && event.getPackageName() != null) {
String packageName = event.getPackageName().toString();
if (!packageName.equals(currentPackageName)) {
currentPackageName = packageName;
QLog.d(TAG, "App changed to: " + packageName);
}
}
}
@Override
public void onInterrupt() {
QLog.d(TAG, "VirtualGearingService interrupted");
}
public static boolean isServiceEnabled() {
return instance != null;
}
public static void simulateKeypress(int x, int y) {
if (instance == null) {
QLog.w(TAG, "Service not enabled, cannot simulate keypress");
return;
}
try {
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(x, y);
path.lineTo(x + 1, y);
GestureDescription.StrokeDescription stroke = new GestureDescription.StrokeDescription(
path, 0, ViewConfiguration.getTapTimeout(), false);
gestureBuilder.addStroke(stroke);
instance.dispatchGesture(gestureBuilder.build(), null, null);
QLog.d(TAG, "Simulated keypress at (" + x + ", " + y + ")");
} catch (Exception e) {
QLog.e(TAG, "Error simulating keypress", e);
}
}
// Legacy methods for backward compatibility
public static void shiftUp() {
QLog.d(TAG, "Using legacy shiftUp - consider using shiftUpSmart()");
simulateKeypress(100, 200);
}
public static void shiftDown() {
QLog.d(TAG, "Using legacy shiftDown - consider using shiftDownSmart()");
simulateKeypress(100, 300);
}
// New smart methods with app-specific coordinates
public static void shiftUpSmart() {
if (instance == null) {
QLog.w(TAG, "Service not enabled, cannot simulate smart shift up");
return;
}
try {
// Try to detect app from package name of last AccessibilityEvent
String currentPackage = getCurrentPackageName();
AppConfiguration.AppConfig config = AppConfiguration.getConfigForPackage(currentPackage);
// Calculate coordinates based on screen dimensions
int[] screenSize = getScreenSize();
int x = config.shiftUp.getX(screenSize[0]);
int y = config.shiftUp.getY(screenSize[1]);
QLog.d(TAG, "Smart shift up for " + config.appName + " at (" + x + ", " + y + ")");
simulateKeypress(x, y);
} catch (Exception e) {
QLog.e(TAG, "Error in shiftUpSmart, falling back to legacy", e);
shiftUp();
}
}
public static void shiftDownSmart() {
if (instance == null) {
QLog.w(TAG, "Service not enabled, cannot simulate smart shift down");
return;
}
try {
String currentPackage = getCurrentPackageName();
AppConfiguration.AppConfig config = AppConfiguration.getConfigForPackage(currentPackage);
int[] screenSize = getScreenSize();
int x = config.shiftDown.getX(screenSize[0]);
int y = config.shiftDown.getY(screenSize[1]);
QLog.d(TAG, "Smart shift down for " + config.appName + " at (" + x + ", " + y + ")");
simulateKeypress(x, y);
} catch (Exception e) {
QLog.e(TAG, "Error in shiftDownSmart, falling back to legacy", e);
shiftDown();
}
}
private static String currentPackageName = null;
private static String getCurrentPackageName() {
return currentPackageName != null ? currentPackageName : "unknown";
}
public static int[] getScreenSize() {
if (instance != null) {
try {
android.content.res.Resources resources = instance.getResources();
android.util.DisplayMetrics displayMetrics = resources.getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
QLog.d(TAG, "Screen size: " + width + "x" + height + " (density=" + displayMetrics.density + ")");
return new int[]{width, height};
} catch (Exception e) {
QLog.e(TAG, "Error getting screen size from service", e);
}
}
QLog.w(TAG, "Using fallback screen size");
return new int[]{1080, 1920}; // Default fallback
}
}

View File

@@ -2,7 +2,6 @@
#include "devices/bike.h"
#include "qdebugfixup.h"
#include "homeform.h"
#include "virtualgearingdevice.h"
#include <QSettings>
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
@@ -467,81 +466,7 @@ double bike::gearsZwiftRatio() {
case 23:
return 5.14;
case 24:
return 5.49;
return 5.49;
}
return 1;
}
void bike::gearUp() {
QSettings settings;
// Check if virtual gearing device is enabled
if (settings.value(QZSettings::virtual_gearing_device, QZSettings::default_virtual_gearing_device).toBool()) {
#ifdef Q_OS_ANDROID
VirtualGearingDevice* vgd = VirtualGearingDevice::instance();
if (vgd) {
// Check if accessibility service is enabled
if (!vgd->isAccessibilityServiceEnabled()) {
static bool warned = false;
if (!warned) {
qDebug() << "bike::gearUp() - VirtualGearingService not enabled in accessibility settings";
qDebug() << "Please enable the Virtual Gearing Service in Android Accessibility Settings";
warned = true;
}
} else if (vgd->isServiceRunning()) {
qDebug() << "bike::gearUp() - Using virtual gearing device";
QString coordinates = vgd->getShiftUpCoordinates();
vgd->simulateShiftUp();
// Show toast with coordinates
homeform::singleton()->setToastRequested("Virtual Gear Up → " + coordinates);
return;
} else {
qDebug() << "bike::gearUp() - Virtual gearing service not running, falling back to normal gearing";
}
}
#endif
}
// Normal gearing logic
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() + (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}
void bike::gearDown() {
QSettings settings;
// Check if virtual gearing device is enabled
if (settings.value(QZSettings::virtual_gearing_device, QZSettings::default_virtual_gearing_device).toBool()) {
#ifdef Q_OS_ANDROID
VirtualGearingDevice* vgd = VirtualGearingDevice::instance();
if (vgd) {
// Check if accessibility service is enabled
if (!vgd->isAccessibilityServiceEnabled()) {
static bool warned = false;
if (!warned) {
qDebug() << "bike::gearDown() - VirtualGearingService not enabled in accessibility settings";
qDebug() << "Please enable the Virtual Gearing Service in Android Accessibility Settings";
warned = true;
}
} else if (vgd->isServiceRunning()) {
qDebug() << "bike::gearDown() - Using virtual gearing device";
QString coordinates = vgd->getShiftDownCoordinates();
vgd->simulateShiftDown();
// Show toast with coordinates
homeform::singleton()->setToastRequested("Virtual Gear Down → " + coordinates);
return;
} else {
qDebug() << "bike::gearDown() - Virtual gearing service not running, falling back to normal gearing";
}
}
#endif
}
// Normal gearing logic
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() - (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}

View File

@@ -64,8 +64,18 @@ class bike : public bluetoothdevice {
void changeInclination(double grade, double percentage) override;
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
void gearUp();
void gearDown();
void gearUp() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() + (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}
void gearDown() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
setGears(gears() - (gears_zwift_ratio ? 1 :
settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble()));
}
Q_SIGNALS:
void bikeStarted();

View File

@@ -450,8 +450,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::toorx_srx_3500, QZSettings::default_toorx_srx_3500).toBool() ||
settings.value(QZSettings::hop_sport_hs_090h_bike, QZSettings::default_hop_sport_hs_090h_bike).toBool() ||
settings.value(QZSettings::toorx_bike_srx_500, QZSettings::default_toorx_bike_srx_500).toBool() ||
settings.value(QZSettings::hertz_xr_770, QZSettings::default_hertz_xr_770).toBool() ||
settings.value(QZSettings::taurua_ic90, QZSettings::default_taurua_ic90).toBool()) &&
settings.value(QZSettings::hertz_xr_770, QZSettings::default_hertz_xr_770).toBool()) &&
!toorx_ftms;
bool snode_bike = settings.value(QZSettings::snode_bike, QZSettings::default_snode_bike).toBool();
bool fitplus_bike = settings.value(QZSettings::fitplus_bike, QZSettings::default_fitplus_bike).toBool() ||

View File

@@ -290,94 +290,6 @@ void proformbike::forceResistance(resistance_t requestResistance) {
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
writeCharacteristic((uint8_t *)noOpData7, sizeof(noOpData7), QStringLiteral("resrequest"), false, false);
switch (requestResistance) {
case 1:
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
break;
case 2:
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
break;
case 3:
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
break;
case 4:
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
break;
case 5:
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
break;
case 6:
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
break;
case 7:
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
break;
case 8:
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
break;
case 9:
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
break;
case 10:
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
break;
case 11:
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
break;
case 12:
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
break;
case 13:
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
break;
case 14:
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
break;
case 15:
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
break;
case 16:
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
break;
}
} else if (proform_csx210) {
// ProForm CSX210 specific resistance frames (1-16)
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02,
0x00, 0x10, 0x01, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02,
0x00, 0x10, 0x03, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x52, 0x07, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xc3, 0x09, 0x00, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x34, 0x0c, 0x00, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xa5, 0x0e, 0x00, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x16, 0x11, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x87, 0x13, 0x00, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xf8, 0x15, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x69, 0x18, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xda, 0x1a, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x4b, 0x1d, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0xbc, 0x1f, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x2d, 0x22, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x9e, 0x24, 0x00, 0xd9, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x0f, 0x27, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
writeCharacteristic((uint8_t *)noOpData7, sizeof(noOpData7), QStringLiteral("resrequest"), false, false);
switch (requestResistance) {
case 1:
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
@@ -983,24 +895,10 @@ void proformbike::update() {
uint8_t noOpData5_proform_xbike[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x00,
0x03, 0x80, 0x00, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00};
// proform_csx210
uint8_t noOpData1_proform_csx210[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2_proform_csx210[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData3_proform_csx210[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0xb9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4_proform_csx210[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData5_proform_csx210[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x96, 0x71, 0x00, 0x10, 0x40, 0x40, 0x00, 0x80};
uint8_t noOpData6_proform_csx210[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xfd, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
if (proform_csx210) {
writeCharacteristic(noOpData1_proform_csx210, sizeof(noOpData1_proform_csx210), QStringLiteral("noOp"));
} else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx || proform_xbike || proform_225_csx_PFEX32925_INT_0) {
if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx || proform_xbike || proform_225_csx_PFEX32925_INT_0) {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData1_proform_bike_PFEVEX71316_0, sizeof(noOpData1_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
@@ -1009,9 +907,7 @@ void proformbike::update() {
}
break;
case 1:
if (proform_csx210) {
writeCharacteristic(noOpData2_proform_csx210, sizeof(noOpData2_proform_csx210), QStringLiteral("noOp"));
} else if (proform_xbike) {
if (proform_xbike) {
writeCharacteristic(noOpData2_proform_xbike, sizeof(noOpData2_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData2_proform_studio, sizeof(noOpData2_proform_studio), QStringLiteral("noOp"));
@@ -1045,9 +941,7 @@ void proformbike::update() {
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
if (proform_csx210) {
writeCharacteristic(noOpData3_proform_csx210, sizeof(noOpData3_proform_csx210), QStringLiteral("noOp"));
} else if (proform_xbike) {
if (proform_xbike) {
writeCharacteristic(noOpData3_proform_xbike, sizeof(noOpData3_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData3_proform_studio, sizeof(noOpData3_proform_studio), QStringLiteral("noOp"));
@@ -1081,9 +975,7 @@ void proformbike::update() {
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
break;
case 3:
if (proform_csx210) {
writeCharacteristic(noOpData4_proform_csx210, sizeof(noOpData4_proform_csx210), QStringLiteral("noOp"));
} else if (proform_xbike) {
if (proform_xbike) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
@@ -1106,9 +998,7 @@ void proformbike::update() {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
if (proform_csx210) {
writeCharacteristic(noOpData5_proform_csx210, sizeof(noOpData5_proform_csx210), QStringLiteral("noOp"));
} else if (proform_xbike) {
if (proform_xbike) {
writeCharacteristic(noOpData5_proform_xbike, sizeof(noOpData5_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData5_proform_studio, sizeof(noOpData5_proform_studio), QStringLiteral("noOp"));
@@ -1136,9 +1026,7 @@ void proformbike::update() {
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
if (proform_csx210) {
writeCharacteristic(noOpData6_proform_csx210, sizeof(noOpData6_proform_csx210), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData6_proform_studio, sizeof(noOpData6_proform_studio), QStringLiteral("noOp"));
else if (proform_tour_de_france_clc) {
writeCharacteristic(noOpData6_proform_tour_de_france_clc, sizeof(noOpData6_proform_tour_de_france_clc),
@@ -1201,7 +1089,7 @@ void proformbike::update() {
requestResistance == -1) {
// this bike sends the frame noOpData7 only when it needs to change the resistance
counterPoll = 0;
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx || proform_xbike || proform_csx210)) {
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx || proform_xbike)) {
counterPoll = 0;
}
@@ -2079,13 +1967,10 @@ void proformbike::btinit() {
proform_bike_PFEVEX71316_0 = settings.value(QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0).toBool();
proform_xbike = settings.value(QZSettings::proform_xbike, QZSettings::default_proform_xbike).toBool();
proform_225_csx_PFEX32925_INT_0 = settings.value(QZSettings::proform_225_csx_PFEX32925_INT_0, QZSettings::default_proform_225_csx_PFEX32925_INT_0).toBool();
proform_csx210 = settings.value(QZSettings::proform_csx210, QZSettings::default_proform_csx210).toBool();
if(nordictrack_GX4_5_bike)
max_resistance = 25;
if(proform_csx210)
max_resistance = 16;
if (settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool()) {
@@ -3055,178 +2940,6 @@ void proformbike::btinit() {
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (proform_csx210) {
// ProForm CSX210 initialization sequence with 16 max resistance
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x80, 0x8b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x88, 0x93,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x0b, 0x02};
uint8_t initData6[] = {0xff, 0x0b, 0x02, 0x04, 0x02, 0x07, 0x02, 0x07, 0x82, 0x00,
0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData10[] = {0xfe, 0x02, 0x2c, 0x04};
// Execute initial setup sequence
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Main initialization sequence
uint8_t initData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x04,
0x00, 0xb2, 0xf4, 0x34, 0x72, 0xbe, 0x08, 0x40, 0x9e, 0xea};
uint8_t initData12[] = {0x01, 0x12, 0x3c, 0x8c, 0xda, 0x26, 0x90, 0xc8, 0x26, 0x82,
0xe4, 0x44, 0xa2, 0x0e, 0x98, 0xf0, 0x4e, 0xda, 0x2c, 0xbc};
uint8_t initData13[] = {0xff, 0x08, 0x0a, 0x96, 0x20, 0x80, 0x02, 0x00, 0x00, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Service discovery and configuration sequence
uint8_t initData14[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData15[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData16[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData20[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x00, 0x10, 0x00, 0xc0, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
uint8_t initData21[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Additional configuration and status frames
uint8_t initData22[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x96, 0x71, 0x00, 0x10, 0x40, 0x40, 0x00, 0x80};
uint8_t initData23[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xfd, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData24[] = {0xfe, 0x02, 0x11, 0x02};
uint8_t initData25[] = {0xff, 0x11, 0x02, 0x04, 0x02, 0x0d, 0x07, 0x0d, 0x02, 0x05,
0x00, 0x00, 0x00, 0x00, 0x08, 0x58, 0x02, 0x00, 0x7d, 0x00};
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Final status and configuration frames
uint8_t initData26[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData27[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0xb9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData28[] = {0xfe, 0x02, 0x10, 0x02};
uint8_t initData29[] = {0xff, 0x10, 0x02, 0x04, 0x02, 0x0c, 0x07, 0x0c, 0x02, 0x04,
0x00, 0x00, 0x00, 0x02, 0x98, 0x21, 0x00, 0xd4, 0x00, 0x00};
uint8_t initData30[] = {0xfe, 0x02, 0x10, 0x02};
uint8_t initData31[] = {0xff, 0x10, 0x02, 0x04, 0x02, 0x0c, 0x07, 0x0c, 0x02, 0x05,
0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x2b, 0x00, 0x00};
uint8_t initData32[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData33[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData34[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData35[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData36[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x96, 0x71, 0x00, 0x10, 0x40, 0x40, 0x00, 0x80};
uint8_t initData37[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xfd, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData28, sizeof(initData28), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData29, sizeof(initData29), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData26, sizeof(initData26), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData27, sizeof(initData27), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData30, sizeof(initData30), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData31, sizeof(initData31), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData32, sizeof(initData32), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData33, sizeof(initData33), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData34, sizeof(initData34), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData35, sizeof(initData35), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData36, sizeof(initData36), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData37, sizeof(initData37), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else {
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07,

View File

@@ -96,7 +96,6 @@ class proformbike : public bike {
bool proform_bike_PFEVEX71316_0 = false;
bool proform_xbike = false;
bool proform_225_csx_PFEX32925_INT_0 = false;
bool proform_csx210 = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -186,10 +186,6 @@ void trxappgateusbbike::update() {
noOpData[4] = crc;
pollCounter += 0x0c;
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
} else if (bike_type == TYPE::TAURUA_IC90) {
const uint8_t noOpData[] = {0xf0, 0xa2, 0x01, 0x31, 0xc4};
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
} else {
const uint8_t noOpData[] = {0xf0, 0xa2, 0x23, 0xd3, 0x88};
@@ -821,24 +817,6 @@ void trxappgateusbbike::btinit(bool startTape) {
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData8, sizeof(initData8), QStringLiteral("init"), false, true);
QThread::msleep(400);
} else if (bike_type == TYPE::TAURUA_IC90) {
const uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x00, 0x91};
const uint8_t initData2[] = {0xf0, 0xa0, 0x01, 0x31, 0xc2};
const uint8_t initData3[] = {0xf0, 0xa1, 0x01, 0x31, 0xc3};
const uint8_t initData4[] = {0xf0, 0xa0, 0x01, 0x31, 0xc2};
const uint8_t initData5[] = {0xf0, 0xa1, 0x01, 0x31, 0xc3};
const uint8_t initData6[] = {0xf0, 0xa3, 0x01, 0x31, 0x01, 0xc6};
const uint8_t initData7[] = {0xf0, 0xa4, 0x01, 0x31, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xd0};
const uint8_t initData8[] = {0xf0, 0xa5, 0x01, 0x31, 0x02, 0xc9};
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData5, sizeof(initData5), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData6, sizeof(initData6), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData7, sizeof(initData7), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData8, sizeof(initData8), QStringLiteral("init"), false, true);
} else {
const uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x01, 0x92};
@@ -1126,7 +1104,6 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bool enerfit_SPX_9500 = settings.value(QZSettings::enerfit_SPX_9500, QZSettings::default_enerfit_SPX_9500).toBool();
bool hop_sport_hs_090h_bike = settings.value(QZSettings::hop_sport_hs_090h_bike, QZSettings::default_hop_sport_hs_090h_bike).toBool();
bool toorx_bike_srx_500 = settings.value(QZSettings::toorx_bike_srx_500, QZSettings::default_toorx_bike_srx_500).toBool();
bool taurua_ic90 = settings.value(QZSettings::taurua_ic90, QZSettings::default_taurua_ic90).toBool();
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
// if(device.name().startsWith("TOORX") || device.name().startsWith("V-RUN") || device.name().startsWith("FS-")
@@ -1176,11 +1153,6 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bike_type = TYPE::TOORX_SRX_500;
qDebug() << QStringLiteral("TOORX_SRX_500 bike found");
} else if(taurua_ic90) {
refresh->start(500ms);
bike_type = TYPE::TAURUA_IC90;
qDebug() << QStringLiteral("TAURUA_IC90 bike found");
} else if (device.name().toUpper().startsWith(QStringLiteral("REEBOK"))) {
bike_type = TYPE::REEBOK;
qDebug() << QStringLiteral("REEBOK bike found");

View File

@@ -116,7 +116,6 @@ class trxappgateusbbike : public bike {
PASYOU = 27,
FAL_SPORTS = 28,
HAMMER_SPEED_BIKE_S = 29,
TAURUA_IC90 = 30,
} TYPE;
TYPE bike_type = TRXAPPGATE;

View File

@@ -34,6 +34,17 @@ wahookickrsnapbike::wahookickrsnapbike(bool noWriteResistance, bool noHeartServi
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
wheelCircumference::GearTable g;
g.printTable();
// Setup op timeout handler for serialized commands
_opTimeout.setSingleShot(true);
connect(&_opTimeout, &QTimer::timeout, this, [this]() {
// Timeout waiting for ack; clear and try next pending to avoid stall
if (_currentOp != WahooOp::None) {
emit debug(QStringLiteral("Ack timeout; releasing op and continuing queue"));
}
_currentOp = WahooOp::None;
processNextPending();
});
}
void wahookickrsnapbike::restoreDefaultWheelDiameter() {
@@ -305,10 +316,8 @@ void wahookickrsnapbike::update() {
if(KICKR_SNAP) {
inclinationChanged(lastGrade, lastGrade);
} else {
QByteArray a = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setWheelCircumference", false, false);
// Queue wheel circumference change (higher priority)
sendWheelCircumferenceNow(wheelCircumference::gearsToWheelDiameter(gears()));
lastGrade = 999; // to force a change
}
}
@@ -331,7 +340,7 @@ void wahookickrsnapbike::update() {
}
auto virtualBike = this->VirtualBike();
if (requestResistance != currentResistance().value() && requestResistance != -1 &&
if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
lastForcedResistance = requestResistance;
@@ -341,14 +350,11 @@ void wahookickrsnapbike::update() {
writeCharacteristic(b, a.length(), "setResistance", false, false);
} else if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) && lastGearValue != gears()) {
emit debug(QStringLiteral("writing resistance due to gears changed ") + QString::number(lastForcedResistance));
if(lastForcedResistance == -1)
lastForcedResistance = 1;
lastForcedResistance = ((double)lastForcedResistance + (gears() - lastGearValue));
QByteArray a = setResistanceMode(lastForcedResistance / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
emit debug(QStringLiteral("writing resistance due to gears changed ") + QString::number(lastForcedResistance));
QByteArray a = setResistanceMode(((double)lastForcedResistance + (gears() - lastGearValue)) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
} else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) {
inclinationChanged(lastGrade, lastGrade);
}
@@ -445,6 +451,26 @@ void wahookickrsnapbike::handleCharacteristicValueChanged(const QBluetoothUuid &
qDebug() << QStringLiteral(" << ") << newValue.toHex(' ') << uuid;
// Detect acks for serialized commands (format: 0x01 <cmdId> 0x01 0x00 ...)
if (newValue.size() >= 3 && (uint8_t)newValue.at(0) == 0x01) {
uint8_t cmd = (uint8_t)newValue.at(1);
uint8_t status = (uint8_t)newValue.at(2);
if ((cmd == _setSimGrade || cmd == _setWheelCircumference) && status == 0x01) {
if(cmd == _setWheelCircumference) {
homeform::singleton()->setToastRequested("Gear accepted from the trainer");
}
// Ack received for our tracked op; release and continue
if ((_currentOp == WahooOp::SimGrade && cmd == _setSimGrade) ||
(_currentOp == WahooOp::WheelCircumference && cmd == _setWheelCircumference)) {
_opTimeout.stop();
_currentOp = WahooOp::None;
processNextPending();
}
}
}
if (uuid == QBluetoothUuid::CyclingPowerMeasurement) {
lastPacket = newValue;
@@ -967,10 +993,7 @@ void wahookickrsnapbike::inclinationChanged(double grade, double percentage) {
emit debug(QStringLiteral("writing inclination ") + QString::number(grade));
double g = grade;
g += gears();
QByteArray a = setSimGrade(g);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
sendSimGradeNow(g);
} else {
if(lastCommandErgMode) {
lastGrade = grade + 1; // to force a refresh
@@ -990,14 +1013,59 @@ void wahookickrsnapbike::inclinationChanged(double grade, double percentage) {
g += gears() * 0.5;
qDebug() << "adding gear offset so " << g;
}
QByteArray a = setSimGrade(g);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
sendSimGradeNow(g);
lastCommandErgMode = false;
}
}
// Send or enqueue: SimGrade
void wahookickrsnapbike::sendSimGradeNow(double grade) {
// If an operation is in flight, store latest grade and return
if (_currentOp != WahooOp::None) {
_pendingSimGrade = true;
_pendingSimGradeValue = grade;
return;
}
QByteArray a = setSimGrade(grade);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
_currentOp = WahooOp::SimGrade;
// Send without blocking; wait for explicit ack in handleCharacteristicValueChanged
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
_opTimeout.start(1000);
}
// Send or enqueue: Wheel Circumference
void wahookickrsnapbike::sendWheelCircumferenceNow(double mm) {
// If an operation is in flight, prefer to hold latest wheel circ (priority for next send)
if (_currentOp != WahooOp::None) {
_pendingWheelCirc = true;
_pendingWheelCircValue = mm;
return;
}
QByteArray a = setWheelCircumference(mm);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
_currentOp = WahooOp::WheelCircumference;
writeCharacteristic(b, a.length(), "setWheelCircumference", false, false);
_opTimeout.start(1000);
}
// Process next pending item, wheel circumference has priority
void wahookickrsnapbike::processNextPending() {
if (_currentOp != WahooOp::None) return;
if (_pendingWheelCirc) {
_pendingWheelCirc = false;
sendWheelCircumferenceNow(_pendingWheelCircValue);
return;
}
if (_pendingSimGrade) {
_pendingSimGrade = false;
sendSimGradeNow(_pendingSimGradeValue);
return;
}
}
bool wahookickrsnapbike::inclinationAvailableByHardware() {
return KICKR_BIKE;
}

View File

@@ -81,6 +81,18 @@ class wahookickrsnapbike : public bike {
bool writeCharacteristic(uint8_t *data, uint8_t data_len, QString info, bool disable_log = false,
bool wait_for_response = false);
// Serialized command handling for setSimGrade and setWheelCircumference
enum class WahooOp { None, SimGrade, WheelCircumference };
WahooOp _currentOp = WahooOp::None;
bool _pendingSimGrade = false;
double _pendingSimGradeValue = 0.0;
bool _pendingWheelCirc = false;
double _pendingWheelCircValue = 0.0;
QTimer _opTimeout;
void processNextPending();
void sendSimGradeNow(double grade);
void sendWheelCircumferenceNow(double mm);
uint16_t wattsFromResistance(double resistance);
metric ResistanceFromFTMSAccessory;
void startDiscover();

View File

@@ -39,8 +39,6 @@
<array>
<string>gcm-ciq</string>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.healthcare-fitness</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>

View File

@@ -30,7 +30,6 @@
#include "mqttpublisher.h"
#include "androidstatusbar.h"
#include "fontmanager.h"
#include "virtualgearingdevice.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
@@ -783,12 +782,6 @@ int main(int argc, char *argv[]) {
bikeResistanceOffset,
bikeResistanceGain); // FIXED: clang-analyzer-cplusplus.NewDeleteLeaks - potential leak
#ifdef Q_OS_ANDROID
// Initialize VirtualGearingDevice for Android keypress simulation
VirtualGearingDevice* vgd = new VirtualGearingDevice();
Q_UNUSED(vgd)
#endif
QString mqtt_host = settings.value(QZSettings::mqtt_host, QZSettings::default_mqtt_host).toString();
int mqtt_port = settings.value(QZSettings::mqtt_port, QZSettings::default_mqtt_port).toInt();
QString mqtt_username = settings.value(QZSettings::mqtt_username, QZSettings::default_mqtt_username).toString();
@@ -814,7 +807,6 @@ int main(int argc, char *argv[]) {
#endif
{
AndroidStatusBar::registerQmlType();
VirtualGearingDevice::registerQmlType();
#ifdef Q_OS_ANDROID
FontManager fontManager;

View File

@@ -9,7 +9,6 @@ import org.cagnulein.qdomyoszwift 1.0
import QtQuick.Window 2.12
import Qt.labs.platform 1.1
import AndroidStatusBar 1.0
import VirtualGearingDevice 1.0
ApplicationWindow {
id: window

View File

@@ -869,7 +869,6 @@ DISTFILES += \
$$PWD/android/libs/ciq-companion-app-sdk-2.0.3.aar \
$$PWD/android/libs/zaplibrary-debug.aar \
$$PWD/android/res/xml/device_filter.xml \
$$PWD/android/src/AppConfiguration.java \
$$PWD/android/src/BikeChannelController.java \
$$PWD/android/src/BleAdvertiser.java \
$$PWD/android/src/CSafeRowerUSBHID.java \
@@ -884,8 +883,6 @@ DISTFILES += \
$$PWD/android/src/QLog.java \
$$PWD/android/src/ScreenCaptureService.java \
$$PWD/android/src/Shortcuts.java \
$$PWD/android/src/VirtualGearingBridge.java \
$$PWD/android/src/VirtualGearingService.java \
$$PWD/android/src/WearableController.java \
$$PWD/android/src/WearableMessageListenerService.java \
$$PWD/android/src/ZapClickLayer.java \
@@ -984,14 +981,12 @@ ios {
HEADERS += \
mqttpublisher.h \
androidstatusbar.h \
fontmanager.h \
virtualgearingdevice.h
fontmanager.h
SOURCES += \
mqttpublisher.cpp \
androidstatusbar.cpp \
fontmanager.cpp \
virtualgearingdevice.cpp
fontmanager.cpp
include($$PWD/purchasing/purchasing.pri)
INCLUDEPATH += purchasing/qmltypes

View File

@@ -981,17 +981,9 @@ const QString QZSettings::chart_display_mode = QStringLiteral("chart_display_mod
const QString QZSettings::calories_active_only = QStringLiteral("calories_active_only");
const QString QZSettings::calories_from_hr = QStringLiteral("calories_from_hr");
const QString QZSettings::height = QStringLiteral("height");
const QString QZSettings::virtual_gearing_device = QStringLiteral("virtual_gearing_device");
const QString QZSettings::virtual_gearing_shift_up_x = QStringLiteral("virtual_gearing_shift_up_x");
const QString QZSettings::virtual_gearing_shift_up_y = QStringLiteral("virtual_gearing_shift_up_y");
const QString QZSettings::virtual_gearing_shift_down_x = QStringLiteral("virtual_gearing_shift_down_x");
const QString QZSettings::virtual_gearing_shift_down_y = QStringLiteral("virtual_gearing_shift_down_y");
const QString QZSettings::virtual_gearing_app = QStringLiteral("virtual_gearing_app");
const QString QZSettings::taurua_ic90 = QStringLiteral("taurua_ic90");
const QString QZSettings::proform_csx210 = QStringLiteral("proform_csx210");
const uint32_t allSettingsCount = 813;
const uint32_t allSettingsCount = 805;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1817,15 +1809,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::calories_active_only, QZSettings::default_calories_active_only},
{QZSettings::calories_from_hr, QZSettings::default_calories_from_hr},
{QZSettings::height, QZSettings::default_height},
{QZSettings::taurua_ic90, QZSettings::default_taurua_ic90},
{QZSettings::proform_csx210, QZSettings::default_proform_csx210},
{QZSettings::toorxtreadmill_discovery_completed, QZSettings::default_toorxtreadmill_discovery_completed},
{QZSettings::virtual_gearing_device, QZSettings::default_virtual_gearing_device},
{QZSettings::virtual_gearing_shift_up_x, QZSettings::default_virtual_gearing_shift_up_x},
{QZSettings::virtual_gearing_shift_up_y, QZSettings::default_virtual_gearing_shift_up_y},
{QZSettings::virtual_gearing_shift_down_x, QZSettings::default_virtual_gearing_shift_down_x},
{QZSettings::virtual_gearing_shift_down_y, QZSettings::default_virtual_gearing_shift_down_y},
{QZSettings::virtual_gearing_app, QZSettings::default_virtual_gearing_app},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {

View File

@@ -2693,30 +2693,6 @@ class QZSettings {
static const QString height;
static constexpr double default_height = 175.0;
static const QString virtual_gearing_device;
static constexpr bool default_virtual_gearing_device = false;
// Virtual Gearing - Generic coordinate settings (app-agnostic)
static const QString virtual_gearing_shift_up_x;
static constexpr double default_virtual_gearing_shift_up_x = 0.98;
static const QString virtual_gearing_shift_up_y;
static constexpr double default_virtual_gearing_shift_up_y = 0.94;
static const QString virtual_gearing_shift_down_x;
static constexpr double default_virtual_gearing_shift_down_x = 0.80;
static const QString virtual_gearing_shift_down_y;
static constexpr double default_virtual_gearing_shift_down_y = 0.94;
// Virtual Gearing - App selection
static const QString virtual_gearing_app;
static constexpr int default_virtual_gearing_app = 0; // 0=MyWhoosh default
static const QString taurua_ic90;
static constexpr bool default_taurua_ic90 = false;
static const QString proform_csx210;
static constexpr bool default_proform_csx210 = false;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

View File

@@ -5,7 +5,6 @@ import QtQuick.Controls.Material 2.0
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.0
import Qt.labs.platform 1.1
import VirtualGearingDevice 1.0
//Page {
ScrollView {
@@ -1206,14 +1205,6 @@ import VirtualGearingDevice 1.0
property int chart_display_mode: 0
property bool zwift_play_vibration: true
property bool toorxtreadmill_discovery_completed: false
property bool taurua_ic90: false
property bool proform_csx210: false
property bool virtual_gearing_device: false
property double virtual_gearing_shift_up_x: 0.98
property double virtual_gearing_shift_up_y: 0.94
property double virtual_gearing_shift_down_x: 0.80
property double virtual_gearing_shift_down_y: 0.94
property int virtual_gearing_app: 0
}
@@ -4019,8 +4010,7 @@ import VirtualGearingDevice 1.0
"Nordictrack GX 4.4 Pro",
"TDF 1.0 PFEVEX71316.0",
"Proform XBike",
"Proform 225 CSX PFEX32925 INT.0",
"Proform CSX210"
"Proform 225 CSX PFEX32925 INT.0"
]
// Initialize when the accordion content becomes visible
@@ -4055,8 +4045,7 @@ import VirtualGearingDevice 1.0
settings.nordictrack_gx_44_pro ? 15 :
settings.proform_bike_PFEVEX71316_0 ? 16 :
settings.proform_xbike ? 17 :
settings.proform_225_csx_PFEX32925_INT_0 ? 18 :
settings.proform_csx210 ? 19 : 0;
settings.proform_225_csx_PFEX32925_INT_0 ? 18 : 0;
console.log("bikeModelComboBox selected model: " + selectedModel);
if (selectedModel >= 0) {
@@ -4089,7 +4078,6 @@ import VirtualGearingDevice 1.0
settings.proform_bike_PFEVEX71316_0 = false;
settings.proform_xbike = false;
settings.proform_225_csx_PFEX32925_INT_0 = false;
settings.proform_csx210 = false;
// Set corresponding setting for selected model
switch (currentIndex) {
@@ -4111,7 +4099,6 @@ import VirtualGearingDevice 1.0
case 16: settings.proform_bike_PFEVEX71316_0 = true; break;
case 17: settings.proform_xbike = true; break;
case 18: settings.proform_225_csx_PFEX32925_INT_0 = true; break;
case 19: settings.proform_csx210 = true; break;
}
window.settings_restart_to_apply = true;
@@ -8679,21 +8666,7 @@ import VirtualGearingDevice 1.0
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.hop_sport_hs_090h_bike = checked; window.settings_restart_to_apply = true; }
}
IndicatorOnlySwitch {
text: qsTr("Taurua IC90 Bike")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.taurua_ic90
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.taurua_ic90 = checked; window.settings_restart_to_apply = true; }
}
}
IndicatorOnlySwitch {
id: jtxFitnessSprintTreadmillDelegate
@@ -12885,145 +12858,6 @@ import VirtualGearingDevice 1.0
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
id: virtualGearingDeviceDelegate
text: qsTr("Virtual Gearing Device")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.virtual_gearing_device
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
visible: Qt.platform.os === "android"
onClicked: {
settings.virtual_gearing_device = checked;
if (checked) {
// Auto-enable Android notification and fake bike when virtual gearing is enabled
settings.android_notification = true;
settings.virtual_device_enabled = true;
}
window.settings_restart_to_apply = true;
}
}
Label {
text: qsTr("Android Only: enables virtual gearing through keypress simulation for third-party apps like MyWhoosh and indieVelo. Uses Zwift Play/Click controls to send shift commands.")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
visible: Qt.platform.os === "android"
}
Button {
text: qsTr("Open Accessibility Settings")
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
visible: settings.virtual_gearing_device && Qt.platform.os === "android"
onClicked: {
VirtualGearingDevice.openAccessibilitySettings()
}
}
// App Selection ComboBox
Row {
visible: settings.virtual_gearing_device && Qt.platform.os === "android"
Layout.fillWidth: true
spacing: 10
Label {
text: qsTr("Target App:")
anchors.verticalCenter: parent.verticalCenter
}
ComboBox {
id: virtualGearingAppCombo
model: ["MyWhoosh", "IndieVelo", "Biketerra", "RGT Cycling", "Zwift"]
currentIndex: settings.virtual_gearing_app
onCurrentIndexChanged: {
settings.virtual_gearing_app = currentIndex;
// Auto-populate coordinates based on selected app
if (currentIndex === 0) { // MyWhoosh
settings.virtual_gearing_shift_up_x = 0.98;
settings.virtual_gearing_shift_up_y = 0.94;
settings.virtual_gearing_shift_down_x = 0.80;
settings.virtual_gearing_shift_down_y = 0.94;
} else if (currentIndex === 1) { // IndieVelo
settings.virtual_gearing_shift_up_x = 0.66;
settings.virtual_gearing_shift_up_y = 0.74;
settings.virtual_gearing_shift_down_x = 0.575;
settings.virtual_gearing_shift_down_y = 0.74;
} else if (currentIndex === 2) { // Biketerra
settings.virtual_gearing_shift_up_x = 0.8;
settings.virtual_gearing_shift_up_y = 0.5;
settings.virtual_gearing_shift_down_x = 0.2;
settings.virtual_gearing_shift_down_y = 0.5;
} else { // RGT Cycling, Zwift and others
settings.virtual_gearing_shift_up_x = 0.95;
settings.virtual_gearing_shift_up_y = 0.85;
settings.virtual_gearing_shift_down_x = 0.75;
settings.virtual_gearing_shift_down_y = 0.85;
}
}
}
}
// Coordinate Customization
GridLayout {
visible: settings.virtual_gearing_device && Qt.platform.os === "android"
Layout.fillWidth: true
columns: 4
Label { text: qsTr("Shift Up X:") }
TextField {
text: settings.virtual_gearing_shift_up_x.toFixed(3)
onAccepted: settings.virtual_gearing_shift_up_x = parseFloat(text)
validator: DoubleValidator { bottom: 0.0; top: 1.0; decimals: 3 }
}
Label { text: qsTr("Shift Up Y:") }
TextField {
text: settings.virtual_gearing_shift_up_y.toFixed(3)
onAccepted: settings.virtual_gearing_shift_up_y = parseFloat(text)
validator: DoubleValidator { bottom: 0.0; top: 1.0; decimals: 3 }
}
Label { text: qsTr("Shift Down X:") }
TextField {
text: settings.virtual_gearing_shift_down_x.toFixed(3)
onAccepted: settings.virtual_gearing_shift_down_x = parseFloat(text)
validator: DoubleValidator { bottom: 0.0; top: 1.0; decimals: 3 }
}
Label { text: qsTr("Shift Down Y:") }
TextField {
text: settings.virtual_gearing_shift_down_y.toFixed(3)
onAccepted: settings.virtual_gearing_shift_down_y = parseFloat(text)
validator: DoubleValidator { bottom: 0.0; top: 1.0; decimals: 3 }
}
}
Label {
visible: settings.virtual_gearing_device && Qt.platform.os === "android"
text: qsTr("Coordinates are percentages (0.0-1.0) of screen dimensions. Select an app above to auto-populate with default values, then customize as needed.")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Android Force Documents/QZ Folder")
spacing: 0

View File

@@ -1,202 +0,0 @@
#include "virtualgearingdevice.h"
#include "qzsettings.h"
#include <QDebug>
#include <QQmlEngine>
#include <QSettings>
#ifdef Q_OS_ANDROID
#include <QtAndroid>
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>
#include <jni.h>
#endif
VirtualGearingDevice* VirtualGearingDevice::m_instance = nullptr;
VirtualGearingDevice::VirtualGearingDevice(QObject *parent) : QObject(parent)
{
m_instance = this;
}
VirtualGearingDevice* VirtualGearingDevice::instance()
{
return m_instance;
}
void VirtualGearingDevice::registerQmlType()
{
qmlRegisterSingletonType<VirtualGearingDevice>("VirtualGearingDevice", 1, 0, "VirtualGearingDevice",
[](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* {
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
return instance();
});
}
bool VirtualGearingDevice::isAccessibilityServiceEnabled()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
return QAndroidJniObject::callStaticMethod<jboolean>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"isAccessibilityServiceEnabled",
"(Landroid/content/Context;)Z",
activity.object<jobject>());
}
#endif
return false;
}
void VirtualGearingDevice::openAccessibilitySettings()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
QAndroidJniObject::callStaticMethod<void>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"openAccessibilitySettings",
"(Landroid/content/Context;)V",
activity.object<jobject>());
}
#endif
}
void VirtualGearingDevice::simulateShiftUp()
{
#ifdef Q_OS_ANDROID
qDebug() << "VirtualGearingDevice: Simulating shift up";
QAndroidJniObject::callStaticMethod<void>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"simulateShiftUp",
"()V");
#endif
}
void VirtualGearingDevice::simulateShiftDown()
{
#ifdef Q_OS_ANDROID
qDebug() << "VirtualGearingDevice: Simulating shift down";
QAndroidJniObject::callStaticMethod<void>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"simulateShiftDown",
"()V");
#endif
}
void VirtualGearingDevice::simulateTouch(int x, int y)
{
#ifdef Q_OS_ANDROID
qDebug() << "VirtualGearingDevice: Simulating touch at (" << x << ", " << y << ")";
QAndroidJniObject::callStaticMethod<void>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"simulateTouch",
"(II)V",
x, y);
#endif
}
bool VirtualGearingDevice::isServiceRunning()
{
#ifdef Q_OS_ANDROID
return QAndroidJniObject::callStaticMethod<jboolean>(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"isServiceRunning",
"()Z");
#endif
return false;
}
#ifdef Q_OS_ANDROID
// JNI implementations for settings access
extern "C" {
JNIEXPORT jdouble JNICALL
Java_org_cagnulen_qdomyoszwift_VirtualGearingBridge_getVirtualGearingShiftUpX(JNIEnv *env, jclass clazz)
{
Q_UNUSED(env)
Q_UNUSED(clazz)
QSettings settings;
return settings.value(QZSettings::virtual_gearing_shift_up_x, QZSettings::default_virtual_gearing_shift_up_x).toDouble();
}
JNIEXPORT jdouble JNICALL
Java_org_cagnulen_qdomyoszwift_VirtualGearingBridge_getVirtualGearingShiftUpY(JNIEnv *env, jclass clazz)
{
Q_UNUSED(env)
Q_UNUSED(clazz)
QSettings settings;
return settings.value(QZSettings::virtual_gearing_shift_up_y, QZSettings::default_virtual_gearing_shift_up_y).toDouble();
}
JNIEXPORT jdouble JNICALL
Java_org_cagnulen_qdomyoszwift_VirtualGearingBridge_getVirtualGearingShiftDownX(JNIEnv *env, jclass clazz)
{
Q_UNUSED(env)
Q_UNUSED(clazz)
QSettings settings;
return settings.value(QZSettings::virtual_gearing_shift_down_x, QZSettings::default_virtual_gearing_shift_down_x).toDouble();
}
JNIEXPORT jdouble JNICALL
Java_org_cagnulen_qdomyoszwift_VirtualGearingBridge_getVirtualGearingShiftDownY(JNIEnv *env, jclass clazz)
{
Q_UNUSED(env)
Q_UNUSED(clazz)
QSettings settings;
return settings.value(QZSettings::virtual_gearing_shift_down_y, QZSettings::default_virtual_gearing_shift_down_y).toDouble();
}
JNIEXPORT jint JNICALL
Java_org_cagnulen_qdomyoszwift_VirtualGearingBridge_getVirtualGearingApp(JNIEnv *env, jclass clazz)
{
Q_UNUSED(env)
Q_UNUSED(clazz)
QSettings settings;
return settings.value(QZSettings::virtual_gearing_app, QZSettings::default_virtual_gearing_app).toInt();
}
} // extern "C"
#endif
QString VirtualGearingDevice::getLastTouchCoordinates()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"getLastTouchCoordinates",
"()Ljava/lang/String;");
if (result.isValid()) {
return result.toString();
}
#endif
return "0,0";
}
QString VirtualGearingDevice::getShiftUpCoordinates()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"getShiftUpCoordinates",
"()Ljava/lang/String;");
if (result.isValid()) {
return result.toString();
}
#endif
return "0,0";
}
QString VirtualGearingDevice::getShiftDownCoordinates()
{
#ifdef Q_OS_ANDROID
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/VirtualGearingBridge",
"getShiftDownCoordinates",
"()Ljava/lang/String;");
if (result.isValid()) {
return result.toString();
}
#endif
return "0,0";
}

View File

@@ -1,31 +0,0 @@
#ifndef VIRTUALGEARINGDEVICE_H
#define VIRTUALGEARINGDEVICE_H
#include <QObject>
#include <QQmlEngine>
class VirtualGearingDevice : public QObject
{
Q_OBJECT
public:
explicit VirtualGearingDevice(QObject *parent = nullptr);
static VirtualGearingDevice* instance();
static void registerQmlType();
public slots:
bool isAccessibilityServiceEnabled();
void openAccessibilitySettings();
void simulateShiftUp();
void simulateShiftDown();
void simulateTouch(int x, int y);
bool isServiceRunning();
QString getLastTouchCoordinates();
QString getShiftUpCoordinates();
QString getShiftDownCoordinates();
private:
static VirtualGearingDevice* m_instance;
};
#endif // VIRTUALGEARINGDEVICE_H