Compare commits

..

5 Commits

Author SHA1 Message Date
Roberto Viola
ebe975321d Update Ant.java 2025-09-16 21:00:27 +02:00
Roberto Viola
397fe4f4de Update AntRemoteControl.java 2025-09-16 20:58:51 +02:00
Roberto Viola
d28fc0753f Update AntRemoteControl.java 2025-09-16 16:35:00 +02:00
Roberto Viola
597ef2259b Update AntRemoteControl.java 2025-09-16 15:52:34 +02:00
Roberto Viola
0394e56cd6 Ant Remote Controller 2025-09-16 14:51:57 +02:00
29 changed files with 652 additions and 1325 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

@@ -25,6 +25,7 @@ import android.widget.Toast;
import org.cagnulen.qdomyoszwift.QLog;
import android.content.Intent;
public class Ant {
private ChannelService.ChannelServiceComm mChannelService = null;
private boolean mChannelServiceBound = false;
@@ -33,23 +34,27 @@ public class Ant {
static boolean speedRequest = false;
static boolean heartRequest = false;
static boolean bikeRequest = false; // Added bike request flag
static boolean remoteControlRequest = false; // Added remote control request flag
static boolean garminKey = false;
static boolean treadmill = false;
static boolean technoGymGroupCycle = false;
static int antBikeDeviceNumber = 0;
static int antHeartDeviceNumber = 0;
static int antRemoteControlDeviceNumber = 0; // Added remote control device number
// Updated antStart method with BikeRequest parameter at the end
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill, boolean BikeRequest, boolean TechnoGymGroupCycle, int AntBikeDeviceNumber, int AntHeartDeviceNumber) {
// Updated antStart method with RemoteControlRequest parameter at the end
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill, boolean BikeRequest, boolean TechnoGymGroupCycle, int AntBikeDeviceNumber, int AntHeartDeviceNumber, boolean RemoteControlRequest, int AntRemoteControlDeviceNumber) {
QLog.v(TAG, "antStart");
speedRequest = SpeedRequest;
heartRequest = HeartRequest;
treadmill = Treadmill;
garminKey = GarminKey;
bikeRequest = BikeRequest; // Set bike request flag
remoteControlRequest = RemoteControlRequest; // Set remote control request flag
technoGymGroupCycle = TechnoGymGroupCycle;
antBikeDeviceNumber = AntBikeDeviceNumber;
antHeartDeviceNumber = AntHeartDeviceNumber;
antRemoteControlDeviceNumber = AntRemoteControlDeviceNumber;
activity = a;
if(a != null)
QLog.v(TAG, "antStart activity is valid");
@@ -159,14 +164,29 @@ public class Ant {
return mChannelService.isBikeConnected();
}
public void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
public void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
double inclination) {
if(mChannelService == null)
return;
QLog.v(TAG, "updateBikeTransmitterExtendedMetrics");
mChannelService.updateBikeTransmitterExtendedMetrics(distanceMeters, heartRate,
elapsedTimeSeconds, resistance,
mChannelService.updateBikeTransmitterExtendedMetrics(distanceMeters, heartRate,
elapsedTimeSeconds, resistance,
inclination);
}
// Remote Control methods
public boolean isRemoteControlConnected() {
if(mChannelService == null)
return false;
QLog.v(TAG, "isRemoteControlConnected");
return mChannelService.isRemoteControlConnected();
}
public void setRemoteControlDeviceNumber(int deviceNumber) {
if(mChannelService == null)
return;
QLog.v(TAG, "setRemoteControlDeviceNumber: " + deviceNumber);
mChannelService.setRemoteControlDeviceNumber(deviceNumber);
}
}

View File

@@ -0,0 +1,438 @@
/*
* ANT+ Remote Control implementation for QDomyos-Zwift
* Based on Golden Cheetah RemoteControl implementation
* Maps ANT+ remote control commands to workout controls
*/
package org.cagnulen.qdomyoszwift;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
import com.dsi.ant.channel.IAntChannelEventHandler;
import com.dsi.ant.message.ChannelId;
import com.dsi.ant.message.ChannelType;
import com.dsi.ant.message.EventCode;
import com.dsi.ant.message.fromant.AcknowledgedDataMessage;
import com.dsi.ant.message.fromant.BroadcastDataMessage;
import com.dsi.ant.message.fromant.ChannelEventMessage;
import com.dsi.ant.message.fromant.MessageFromAntType;
import com.dsi.ant.message.ipc.AntMessageParcel;
import android.os.RemoteException;
import android.util.Log;
import org.cagnulen.qdomyoszwift.QLog;
/**
* ANT+ Remote Control Channel Controller
* Handles ANT+ Control Device Profile (Device Type 0x10)
*/
public class AntRemoteControl {
private static final String TAG = "AntRemoteControl";
// ANT+ Control Device Profile constants
private static final int CONTROL_DEVICE_TYPE = 0x10;
private static final int CONTROL_TRANSMISSION_TYPE = 0x05;
private static final short CONTROL_PERIOD = 8192; // 4 Hz
private static final int CONTROL_FREQUENCY = 57; // 2457 MHz
// ANT+ Control Generic Command Page
private static final byte ANT_CONTROL_GENERIC_CMD_PAGE = 0x49;
// ANT+ Generic Commands (from Golden Cheetah)
private static final int ANT_CONTROL_GENERIC_CMD_MENU_UP = 0x00;
private static final int ANT_CONTROL_GENERIC_CMD_MENU_DOWN = 0x01;
private static final int ANT_CONTROL_GENERIC_CMD_MENU_SELECT = 0x02;
private static final int ANT_CONTROL_GENERIC_CMD_MENU_BACK = 0x03;
private static final int ANT_CONTROL_GENERIC_CMD_HOME = 0x04;
private static final int ANT_CONTROL_GENERIC_CMD_START = 0x20;
private static final int ANT_CONTROL_GENERIC_CMD_STOP = 0x21;
private static final int ANT_CONTROL_GENERIC_CMD_RESET = 0x22;
private static final int ANT_CONTROL_GENERIC_CMD_LENGTH = 0x23;
private static final int ANT_CONTROL_GENERIC_CMD_LAP = 0x24;
private static final int ANT_CONTROL_GENERIC_CMD_USER_1 = 0x8000;
private static final int ANT_CONTROL_GENERIC_CMD_USER_2 = 0x8001;
private static final int ANT_CONTROL_GENERIC_CMD_USER_3 = 0x8002;
private AntChannel mAntChannel;
private ChannelController mChannelController;
private boolean isChannelOpen = false;
private int deviceNumber = 0; // 0 means wildcard - accept any remote
// Native methods for communicating with Qt layer
public static native void nativeOnRemoteCommand(int command);
public static native void nativeGearUp();
public static native void nativeGearDown();
/**
* Channel Controller for handling ANT+ events
*/
private class ChannelController implements IAntChannelEventHandler {
@Override
public void onChannelDeath() {
QLog.w(TAG, "onChannelDeath: Remote Control Channel Death - cleaning up");
isChannelOpen = false;
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
QLog.v(TAG, "onReceiveMessage: messageType=" + messageType + ", parcel=" + antParcel);
switch(messageType) {
case ACKNOWLEDGED_DATA:
QLog.d(TAG, "onReceiveMessage: Received ACKNOWLEDGED_DATA");
AcknowledgedDataMessage ackMsg = new AcknowledgedDataMessage(antParcel);
byte[] ackPayload = ackMsg.getPayload();
QLog.v(TAG, "onReceiveMessage: ACKNOWLEDGED_DATA payload length=" + (ackPayload != null ? ackPayload.length : 0));
handleDataMessage(ackPayload);
break;
case BROADCAST_DATA:
QLog.d(TAG, "onReceiveMessage: Received BROADCAST_DATA");
BroadcastDataMessage broadcastMsg = new BroadcastDataMessage(antParcel);
byte[] broadcastPayload = broadcastMsg.getPayload();
QLog.v(TAG, "onReceiveMessage: BROADCAST_DATA payload length=" + (broadcastPayload != null ? broadcastPayload.length : 0));
handleDataMessage(broadcastPayload);
break;
case CHANNEL_EVENT:
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
QLog.d(TAG, "onReceiveMessage: CHANNEL_EVENT - eventCode=" + code);
switch(code) {
/* case CHANNEL_IN_WRONG_STATE:
QLog.w(TAG, "onReceiveMessage: CHANNEL_IN_WRONG_STATE error");
break;*/
case CHANNEL_COLLISION:
QLog.w(TAG, "onReceiveMessage: CHANNEL_COLLISION error");
break;
case TRANSFER_TX_FAILED:
QLog.w(TAG, "onReceiveMessage: TRANSFER_TX_FAILED error");
break;
case RX_SEARCH_TIMEOUT:
QLog.i(TAG, "onReceiveMessage: RX_SEARCH_TIMEOUT - no remote control found");
break;
case CHANNEL_CLOSED:
QLog.i(TAG, "onReceiveMessage: CHANNEL_CLOSED");
isChannelOpen = false;
break;
default:
QLog.v(TAG, "onReceiveMessage: Other channel event=" + code);
break;
}
break;
default:
QLog.d(TAG, "onReceiveMessage: Unhandled messageType=" + messageType + ", parcel=" + antParcel);
break;
}
}
/**
* Handle incoming data messages from ANT+ remote control
*/
private void handleDataMessage(byte[] payload) {
QLog.v(TAG, "handleDataMessage: called with payload=" + (payload != null ? "length=" + payload.length : "null"));
if (payload == null) {
QLog.w(TAG, "handleDataMessage: payload is null, ignoring");
return;
}
if (payload.length < 8) {
QLog.w(TAG, "handleDataMessage: payload too short (length=" + payload.length + "), expected 8 bytes");
return;
}
// Log raw payload for debugging
StringBuilder payloadHex = new StringBuilder();
for (int i = 0; i < payload.length; i++) {
payloadHex.append(String.format("%02X ", payload[i]));
}
QLog.v(TAG, "handleDataMessage: raw payload: " + payloadHex.toString());
// Check if this is a Generic Command Page
byte pageNumber = payload[0];
QLog.d(TAG, "handleDataMessage: pageNumber=0x" + Integer.toHexString(pageNumber & 0xFF) +
" (expected=0x" + Integer.toHexString(ANT_CONTROL_GENERIC_CMD_PAGE & 0xFF) + ")");
if (pageNumber != ANT_CONTROL_GENERIC_CMD_PAGE) {
QLog.w(TAG, "handleDataMessage: not a Generic Command Page, ignoring (pageNumber=0x" +
Integer.toHexString(pageNumber & 0xFF) + ")");
return;
}
// Extract command from payload
// Command is in bytes 1-2 (little endian)
int command = ((payload[2] & 0xFF) << 8) | (payload[1] & 0xFF);
QLog.i(TAG, "handleDataMessage: extracted ANT+ Remote Command=0x" + Integer.toHexString(command) +
" from bytes[1]=0x" + Integer.toHexString(payload[1] & 0xFF) +
", bytes[2]=0x" + Integer.toHexString(payload[2] & 0xFF));
// Map commands to actions
handleRemoteCommand(command);
}
/**
* Handle remote control commands and map them to QDomyos-Zwift actions
*/
private void handleRemoteCommand(int command) {
QLog.d(TAG, "handleRemoteCommand: processing command=0x" + Integer.toHexString(command));
switch(command) {
case ANT_CONTROL_GENERIC_CMD_MENU_UP:
QLog.i(TAG, "handleRemoteCommand: MENU_UP -> Gear Up (like Zwift Click)");
try {
nativeGearUp();
QLog.d(TAG, "handleRemoteCommand: nativeGearUp() called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeGearUp()", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_MENU_DOWN:
QLog.i(TAG, "handleRemoteCommand: MENU_DOWN -> Gear Down (like Zwift Click)");
try {
nativeGearDown();
QLog.d(TAG, "handleRemoteCommand: nativeGearDown() called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeGearDown()", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_MENU_SELECT:
QLog.i(TAG, "handleRemoteCommand: MENU_SELECT -> Select action");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(SELECT) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(SELECT)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_MENU_BACK:
QLog.i(TAG, "handleRemoteCommand: MENU_BACK -> Back action");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(BACK) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(BACK)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_HOME:
QLog.i(TAG, "handleRemoteCommand: HOME -> Home action");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(HOME) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(HOME)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_START:
QLog.i(TAG, "handleRemoteCommand: START -> Start workout");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(START) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(START)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_STOP:
QLog.i(TAG, "handleRemoteCommand: STOP -> Stop workout");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(STOP) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(STOP)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_LAP:
QLog.i(TAG, "handleRemoteCommand: LAP -> Lap marker");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(LAP) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(LAP)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_RESET:
QLog.i(TAG, "handleRemoteCommand: RESET -> Reset action");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(RESET) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(RESET)", e);
}
break;
case ANT_CONTROL_GENERIC_CMD_USER_1:
case ANT_CONTROL_GENERIC_CMD_USER_2:
case ANT_CONTROL_GENERIC_CMD_USER_3:
QLog.i(TAG, "handleRemoteCommand: USER_" + (command - ANT_CONTROL_GENERIC_CMD_USER_1 + 1) + " -> User action");
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(USER) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(USER)", e);
}
break;
default:
QLog.w(TAG, "handleRemoteCommand: Unknown/unmapped command=0x" + Integer.toHexString(command));
try {
nativeOnRemoteCommand(command);
QLog.d(TAG, "handleRemoteCommand: nativeOnRemoteCommand(UNKNOWN) called successfully");
} catch (Exception e) {
QLog.e(TAG, "handleRemoteCommand: Error calling nativeOnRemoteCommand(UNKNOWN)", e);
}
break;
}
}
}
/**
* Constructor
*/
public AntRemoteControl() {
QLog.i(TAG, "AntRemoteControl: constructor - initializing ANT+ Remote Control");
mChannelController = new ChannelController();
QLog.d(TAG, "AntRemoteControl: constructor completed, channel controller created");
}
/**
* Open ANT+ remote control channel
*/
public boolean openChannel(AntChannel antChannel) {
QLog.i(TAG, "openChannel: request to open ANT+ Remote Control channel");
if (isChannelOpen) {
QLog.w(TAG, "openChannel: Remote control channel already open, ignoring request");
return false;
}
if (antChannel == null) {
QLog.e(TAG, "openChannel: antChannel is null, cannot proceed");
return false;
}
mAntChannel = antChannel;
QLog.d(TAG, "openChannel: antChannel assigned");
try {
// Configure the channel
ChannelId channelId = new ChannelId(deviceNumber, CONTROL_DEVICE_TYPE, CONTROL_TRANSMISSION_TYPE);
QLog.d(TAG, "openChannel: created ChannelId - deviceNumber=" + deviceNumber +
", deviceType=0x" + Integer.toHexString(CONTROL_DEVICE_TYPE) +
", transmissionType=0x" + Integer.toHexString(CONTROL_TRANSMISSION_TYPE));
QLog.i(TAG, "openChannel: configuring ANT+ Remote Control channel" +
" (deviceNumber=" + deviceNumber + ", frequency=" + CONTROL_FREQUENCY +
"MHz, period=" + CONTROL_PERIOD + ")");
// Assign the channel with slave configuration
QLog.d(TAG, "openChannel: assigning channel as SLAVE_RECEIVE_ONLY");
mAntChannel.assign(ChannelType.SLAVE_RECEIVE_ONLY);
// Set the channel ID
QLog.d(TAG, "openChannel: setting channel ID");
mAntChannel.setChannelId(channelId);
// Set the period
QLog.d(TAG, "openChannel: setting period=" + CONTROL_PERIOD);
mAntChannel.setPeriod(CONTROL_PERIOD);
// Set the RF frequency
QLog.d(TAG, "openChannel: setting RF frequency=" + CONTROL_FREQUENCY);
mAntChannel.setRfFrequency(CONTROL_FREQUENCY);
// Register event handler
QLog.d(TAG, "openChannel: registering channel event handler");
mAntChannel.setChannelEventHandler(mChannelController);
// Open the channel
QLog.d(TAG, "openChannel: opening the channel");
mAntChannel.open();
isChannelOpen = true;
QLog.i(TAG, "openChannel: ANT+ Remote Control channel opened successfully, now listening for commands");
return true;
} catch (RemoteException e) {
QLog.e(TAG, "openChannel: RemoteException while opening ANT+ Remote Control channel", e);
isChannelOpen = false;
return false;
} catch (AntCommandFailedException e) {
QLog.e(TAG, "openChannel: AntCommandFailedException while opening ANT+ Remote Control channel", e);
isChannelOpen = false;
return false;
}
}
/**
* Close ANT+ remote control channel
*/
public void closeChannel() {
QLog.i(TAG, "closeChannel: request to close ANT+ Remote Control channel");
if (!isChannelOpen && mAntChannel == null) {
QLog.d(TAG, "closeChannel: channel not open and null, nothing to do");
return;
}
if (mAntChannel == null) {
QLog.w(TAG, "closeChannel: channel marked as open but mAntChannel is null, clearing flag");
isChannelOpen = false;
return;
}
try {
QLog.d(TAG, "closeChannel: closing ANT+ Remote Control channel");
mAntChannel.close();
QLog.d(TAG, "closeChannel: channel closed, releasing resources");
mAntChannel.release();
QLog.i(TAG, "closeChannel: ANT+ Remote Control channel closed and released successfully");
} catch (RemoteException e) {
QLog.e(TAG, "closeChannel: RemoteException while closing ANT+ Remote Control channel", e);
} catch (AntCommandFailedException e) {
QLog.e(TAG, "closeChannel: AntCommandFailedException while closing ANT+ Remote Control channel", e);
}
isChannelOpen = false;
mAntChannel = null;
QLog.d(TAG, "closeChannel: cleanup completed");
}
/**
* Check if channel is open
*/
public boolean isChannelOpen() {
boolean channelOpen = isChannelOpen;
QLog.v(TAG, "isChannelOpen: returning " + channelOpen);
return channelOpen;
}
/**
* Set the device number to search for (0 = wildcard)
*/
public void setDeviceNumber(int deviceNumber) {
QLog.d(TAG, "setDeviceNumber: changing deviceNumber from " + this.deviceNumber + " to " + deviceNumber);
if (deviceNumber == 0) {
QLog.i(TAG, "setDeviceNumber: using wildcard (0) to accept any remote control");
} else {
QLog.i(TAG, "setDeviceNumber: will only accept remote control with device number " + deviceNumber);
}
this.deviceNumber = deviceNumber;
}
/**
* Get the current device number
*/
public int getDeviceNumber() {
QLog.v(TAG, "getDeviceNumber: returning " + deviceNumber);
return deviceNumber;
}
}

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

@@ -59,6 +59,7 @@ public class ChannelService extends Service {
SDMChannelController sdmChannelController = null;
BikeChannelController bikeChannelController = null; // Added BikeChannelController reference
BikeTransmitterController bikeTransmitterController = null; // Added BikeTransmitterController reference
AntRemoteControl antRemoteControl = null; // Added AntRemoteControl reference
private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
@Override
@@ -307,6 +308,41 @@ public class ChannelService extends Service {
return "Bike transmitter not initialized";
}
// ========== REMOTE CONTROL METHODS ==========
/**
* Check if remote control is connected
*/
boolean isRemoteControlConnected() {
QLog.v(TAG, "ChannelServiceComm.isRemoteControlConnected");
return (antRemoteControl != null && antRemoteControl.isChannelOpen());
}
/**
* Set the remote control device number (0 = wildcard)
*/
void setRemoteControlDeviceNumber(int deviceNumber) {
QLog.v(TAG, "ChannelServiceComm.setRemoteControlDeviceNumber: " + deviceNumber);
if (antRemoteControl != null) {
antRemoteControl.setDeviceNumber(deviceNumber);
QLog.d(TAG, "Remote control device number updated to: " + deviceNumber);
} else {
QLog.w(TAG, "Remote control not initialized, cannot set device number");
}
}
/**
* Get remote control status info for debugging
*/
String getRemoteControlInfo() {
if (antRemoteControl != null) {
return "Remote control initialized, channel open: " + antRemoteControl.isChannelOpen() +
", device number: " + antRemoteControl.getDeviceNumber();
}
return "Remote control not initialized";
}
/**
* Closes all channels currently added.
*/
@@ -393,6 +429,37 @@ public class ChannelService extends Service {
bikeTransmitterController = null;
}
}
// Add initialization for AntRemoteControl
if (Ant.remoteControlRequest && antRemoteControl == null) {
QLog.i(TAG, "Initializing AntRemoteControl");
try {
// Create remote control instance
antRemoteControl = new AntRemoteControl();
// Set device number (0 = wildcard for any remote)
antRemoteControl.setDeviceNumber(Ant.antRemoteControlDeviceNumber);
// Acquire channel like other controllers
AntChannel remoteChannel = acquireChannel();
if (remoteChannel != null) {
boolean channelOpened = antRemoteControl.openChannel(remoteChannel);
if (channelOpened) {
QLog.i(TAG, "AntRemoteControl initialized and channel opened successfully");
} else {
QLog.e(TAG, "Failed to open AntRemoteControl channel");
antRemoteControl = null;
}
} else {
QLog.e(TAG, "Failed to acquire channel for AntRemoteControl");
antRemoteControl = null;
}
} catch (Exception e) {
QLog.e(TAG, "Failed to initialize AntRemoteControl: " + e.getMessage(), e);
antRemoteControl = null;
}
}
}
private void closeAllChannels() {
@@ -409,6 +476,9 @@ public class ChannelService extends Service {
if (bikeTransmitterController != null) { // Added closing bikeTransmitterController
bikeTransmitterController.close(); // Use close() method like other controllers
}
if (antRemoteControl != null) { // Added closing antRemoteControl
antRemoteControl.closeChannel();
}
heartChannelController = null;
powerChannelController = null;
@@ -416,6 +486,7 @@ public class ChannelService extends Service {
sdmChannelController = null;
bikeChannelController = null; // Added nullifying bikeChannelController
bikeTransmitterController = null; // Added nullifying bikeTransmitterController
antRemoteControl = null; // Added nullifying antRemoteControl
}
AntChannel acquireChannel() throws ChannelNotAvailableException {

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

@@ -331,7 +331,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 +341,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);
}

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

@@ -685,6 +685,8 @@ const QString QZSettings::ftms_treadmill = QStringLiteral("ftms_treadmill");
const QString QZSettings::default_ftms_treadmill = QStringLiteral("Disabled");
const QString QZSettings::ant_speed_offset = QStringLiteral("ant_speed_offset");
const QString QZSettings::ant_speed_gain = QStringLiteral("ant_speed_gain");
const QString QZSettings::ant_remote_control = QStringLiteral("ant_remote_control");
const QString QZSettings::ant_remote_control_device_number = QStringLiteral("ant_remote_control_device_number");
const QString QZSettings::proform_rower_sport_rl = QStringLiteral("proform_rower_sport_rl");
const QString QZSettings::strava_date_prefix = QStringLiteral("strava_date_prefix");
const QString QZSettings::race_mode = QStringLiteral("race_mode");
@@ -981,17 +983,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 = 807;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1570,6 +1564,8 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill},
{QZSettings::ant_speed_offset, QZSettings::default_ant_speed_offset},
{QZSettings::ant_speed_gain, QZSettings::default_ant_speed_gain},
{QZSettings::ant_remote_control, QZSettings::default_ant_remote_control},
{QZSettings::ant_remote_control_device_number, QZSettings::default_ant_remote_control_device_number},
{QZSettings::proform_rower_sport_rl, QZSettings::default_proform_rower_sport_rl},
{QZSettings::strava_date_prefix, QZSettings::default_strava_date_prefix},
{QZSettings::race_mode, QZSettings::default_race_mode},
@@ -1817,15 +1813,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

@@ -1930,6 +1930,12 @@ class QZSettings {
static const QString ant_speed_gain;
static constexpr float default_ant_speed_gain = 1;
static const QString ant_remote_control;
static constexpr bool default_ant_remote_control = false;
static const QString ant_remote_control_device_number;
static constexpr int default_ant_remote_control_device_number = 0;
static const QString race_mode;
static constexpr bool default_race_mode = false;
@@ -2693,30 +2699,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,8 @@ 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
property bool ant_remote_control: false
property int ant_remote_control_device_number: 0
}
@@ -4019,8 +4012,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 +4047,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 +4080,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 +4101,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;
@@ -4645,6 +4634,69 @@ import VirtualGearingDevice 1.0
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("ANT+ Remote Control")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.ant_remote_control
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.ant_remote_control = checked; window.settings_restart_to_apply = true; }
}
Label {
text: qsTr("Enable ANT+ Remote Control support (like Zwift Click). Menu Up/Down buttons control gear shifting. Works with standard ANT+ Control Device remotes. Default: Disabled")
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)
}
RowLayout {
spacing: 10
Label {
text: qsTr("ANT+ Remote Control Device Number (0 = any):")
Layout.fillWidth: true
}
TextField {
id: antRemoteControlDeviceNumberTextField
text: settings.ant_remote_control_device_number
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.ant_remote_control_device_number = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.ant_remote_control_device_number = antRemoteControlDeviceNumberTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
}
}
Label {
text: qsTr("Set device number to 0 to accept any ANT+ remote control, or specify a specific device number to pair with only that remote.")
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)
}
}
}
/*
@@ -8679,21 +8731,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 +12923,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