mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 23:41:50 +01:00
Compare commits
5 Commits
virtualgea
...
ant-remote
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebe975321d | ||
|
|
397fe4f4de | ||
|
|
d28fc0753f | ||
|
|
597ef2259b | ||
|
|
0394e56cd6 |
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
438
src/android/src/AntRemoteControl.java
Normal file
438
src/android/src/AntRemoteControl.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() ||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
237
src/settings.qml
237
src/settings.qml
@@ -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
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user