Compare commits

..

9 Commits

Author SHA1 Message Date
Roberto Viola
12920d10a8 to test 2025-09-18 13:53:40 +02:00
Roberto Viola
dd2bfc4e1b Update proformbike.cpp 2025-09-18 12:09:24 +02:00
Roberto Viola
06fd78378e Proform CSX210 2025-09-18 11:38:11 +02:00
Roberto Viola
f28574245c Update project.pbxproj 2025-09-18 09:58:38 +02:00
Roberto Viola
b964c523dd How to Make 10s Intervals Work with Virtual Shifting #3603 2025-09-18 09:50:06 +02:00
Roberto Viola
0721bc3ec5 Update project.pbxproj 2025-09-17 17:00:58 +02:00
Roberto Viola
3f783305b2 How to Make 10s Intervals Work with Virtual Shifting #3603 2025-09-17 17:00:09 +02:00
Roberto Viola
be29180e48 Update project.pbxproj 2025-09-17 12:22:46 +02:00
Roberto Viola
19c65d7d90 Taurua IC90 (#3697) 2025-09-17 12:20:21 +02:00
15 changed files with 765 additions and 640 deletions

View File

@@ -4455,7 +4455,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1161;
CURRENT_PROJECT_VERSION = 1164;
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 = 1161;
CURRENT_PROJECT_VERSION = 1164;
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 = 1161;
CURRENT_PROJECT_VERSION = 1164;
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 = 1161;
CURRENT_PROJECT_VERSION = 1164;
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 = 1161;
CURRENT_PROJECT_VERSION = 1164;
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 = 1161;
CURRENT_PROJECT_VERSION = 1164;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -25,7 +25,6 @@ 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;
@@ -34,27 +33,23 @@ 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 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) {
// 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) {
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");
@@ -164,29 +159,14 @@ public class Ant {
return mChannelService.isBikeConnected();
}
public void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
public void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
double inclination) {
if(mChannelService == null)
return;
QLog.v(TAG, "updateBikeTransmitterExtendedMetrics");
mChannelService.updateBikeTransmitterExtendedMetrics(distanceMeters, heartRate,
elapsedTimeSeconds, resistance,
mChannelService.updateBikeTransmitterExtendedMetrics(distanceMeters, heartRate,
elapsedTimeSeconds, resistance,
inclination);
}
// Remote Control methods
public boolean isRemoteControlConnected() {
if(mChannelService == null)
return false;
QLog.v(TAG, "isRemoteControlConnected");
return mChannelService.isRemoteControlConnected();
}
public void setRemoteControlDeviceNumber(int deviceNumber) {
if(mChannelService == null)
return;
QLog.v(TAG, "setRemoteControlDeviceNumber: " + deviceNumber);
mChannelService.setRemoteControlDeviceNumber(deviceNumber);
}
}

View File

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

View File

@@ -59,7 +59,6 @@ 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
@@ -308,41 +307,6 @@ 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.
*/
@@ -429,37 +393,6 @@ 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() {
@@ -476,9 +409,6 @@ 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;
@@ -486,7 +416,6 @@ public class ChannelService extends Service {
sdmChannelController = null;
bikeChannelController = null; // Added nullifying bikeChannelController
bikeTransmitterController = null; // Added nullifying bikeTransmitterController
antRemoteControl = null; // Added nullifying antRemoteControl
}
AntChannel acquireChannel() throws ChannelNotAvailableException {

View File

@@ -99,6 +99,10 @@ bluetooth::bluetooth(bool logs, const QString &deviceName, bool noWriteResistanc
discoveryAgent = new QBluetoothDeviceDiscoveryAgent(this);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &bluetooth::deviceDiscovered);
// Initialize Zwift device lists with "Auto" option
zwiftClickDevicesList.append("Auto");
zwiftPlayDevicesList.append("Auto");
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceUpdated, this, &bluetooth::deviceUpdated);
@@ -450,7 +454,8 @@ 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::hertz_xr_770, QZSettings::default_hertz_xr_770).toBool() ||
settings.value(QZSettings::taurua_ic90, QZSettings::default_taurua_ic90).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() ||
@@ -643,6 +648,27 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
}
if (!found) {
devices.append(device);
// Cache Zwift devices for ComboBox selection
QString deviceName = device.name().toUpper();
#if defined(Q_OS_DARWIN) || defined(Q_OS_IOS)
QString deviceAddress = device.deviceUuid().toString();
#else
QString deviceAddress = device.address().toString();
#endif
QString deviceDisplay = device.name() + " (" + deviceAddress + ")";
if (deviceName.startsWith("ZWIFT CLICK")) {
if (!zwiftClickDevicesList.contains(deviceDisplay)) {
zwiftClickDevicesList.append(deviceDisplay);
emit zwiftClickDevicesChanged();
}
} else if (deviceName.startsWith("ZWIFT PLAY") || deviceName.startsWith("ZWIFT RIDE") || deviceName.startsWith("ZWIFT SF2")) {
if (!zwiftPlayDevicesList.contains(deviceDisplay)) {
zwiftPlayDevicesList.append(deviceDisplay);
emit zwiftPlayDevicesChanged();
}
}
}
}
@@ -3010,10 +3036,24 @@ void bluetooth::connectedAndDiscovered() {
}
if(settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool()) {
QString zwiftClickName = settings.value(QZSettings::zwift_click_name, QZSettings::default_zwift_click_name).toString();
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("ZWIFT CLICK"))) && !zwiftClickRemote && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
// Check MAC address filter if not "Auto"
if (zwiftClickName != "Auto") {
#if defined(Q_OS_DARWIN) || defined(Q_OS_IOS)
QString deviceAddress = b.deviceUuid().toString();
#else
QString deviceAddress = b.address().toString();
#endif
QString deviceDisplay = b.name() + " (" + deviceAddress + ")";
if (deviceDisplay != zwiftClickName) {
continue; // Skip this device if it doesn't match the selected one
}
}
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 9. is it? " << int(b.manufacturerData(2378).at(0));
} else {
@@ -3055,20 +3095,49 @@ void bluetooth::connectedAndDiscovered() {
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
QString zwiftPlayLeftName = settings.value(QZSettings::zwift_play_left_name, QZSettings::default_zwift_play_left_name).toString();
QString zwiftPlayRightName = settings.value(QZSettings::zwift_play_right_name, QZSettings::default_zwift_play_right_name).toString();
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
// Determine if this is LEFT or RIGHT device
AbstractZapDevice::ZWIFT_PLAY_TYPE deviceType = AbstractZapDevice::ZWIFT_PLAY_TYPE::NONE;
if(b.manufacturerData(2378).size() > 0) {
deviceType = (int(b.manufacturerData(2378).at(0)) == 3 || int(b.manufacturerData(2378).at(0)) == 7) ?
AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT;
} else {
deviceType = (zwiftPlayDevice.length() == 0) ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT;
}
// Check MAC address filter based on device type
bool shouldConnect = true;
QString selectedDevice = (deviceType == AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT) ? zwiftPlayLeftName : zwiftPlayRightName;
if (selectedDevice != "Auto") {
#if defined(Q_OS_DARWIN) || defined(Q_OS_IOS)
QString deviceAddress = b.deviceUuid().toString();
#else
QString deviceAddress = b.address().toString();
#endif
QString deviceDisplay = b.name() + " (" + deviceAddress + ")";
if (deviceDisplay != selectedDevice) {
shouldConnect = false; // Skip this device if it doesn't match the selected one
}
}
if (!shouldConnect) {
continue;
}
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
int(b.manufacturerData(2378).at(0)) == 3 || int(b.manufacturerData(2378).at(0)) == 7 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
} else {
qDebug() << "manufacturer not found for ZWIFT CLICK";
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
zwiftPlayDevice.length() == 0 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
}
zwiftPlayDevice.append(new zwiftclickremote(this->device(), deviceType));
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug);
@@ -4182,3 +4251,11 @@ void bluetooth::deviceUpdated(const QBluetoothDeviceInfo &device, QBluetoothDevi
debug("deviceUpdated " + device.name() + " " + updateFields);
}
#endif
QStringList bluetooth::getZwiftClickDevices() const {
return zwiftClickDevicesList;
}
QStringList bluetooth::getZwiftPlayDevices() const {
return zwiftPlayDevicesList;
}

View File

@@ -161,6 +161,8 @@
class bluetooth : public QObject, public SignalHandler {
Q_OBJECT
Q_PROPERTY(QStringList zwiftClickDevices READ getZwiftClickDevices NOTIFY zwiftClickDevicesChanged)
Q_PROPERTY(QStringList zwiftPlayDevices READ getZwiftPlayDevices NOTIFY zwiftPlayDevicesChanged)
public:
bluetooth(const discoveryoptions &options);
explicit bluetooth(bool logs, const QString &deviceName = QLatin1String(""), bool noWriteResistance = false,
@@ -175,6 +177,9 @@ class bluetooth : public QObject, public SignalHandler {
bool onlyDiscover = false;
volatile bool homeformLoaded = false;
QStringList getZwiftClickDevices() const;
QStringList getZwiftPlayDevices() const;
private:
bool useDiscovery = false;
QFile *debugCommsLog = nullptr;
@@ -308,6 +313,10 @@ class bluetooth : public QObject, public SignalHandler {
elitesquarecontroller* eliteSquareController = nullptr;
QString filterDevice = QLatin1String("");
// Device discovery lists for Zwift devices
QStringList zwiftClickDevicesList;
QStringList zwiftPlayDevicesList;
bool testResistance = false;
bool noWriteResistance = false;
bool noHeartService = false;
@@ -365,6 +374,8 @@ class bluetooth : public QObject, public SignalHandler {
void bluetoothDeviceConnected(bluetoothdevice *b);
void bluetoothDeviceDisconnected();
void zwiftClickDevicesChanged();
void zwiftPlayDevicesChanged();
public slots:
void restart();
void debug(const QString &string);

View File

@@ -290,6 +290,94 @@ 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);
@@ -895,10 +983,24 @@ 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 (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 (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) {
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"));
@@ -907,7 +1009,9 @@ void proformbike::update() {
}
break;
case 1:
if (proform_xbike) {
if (proform_csx210) {
writeCharacteristic(noOpData2_proform_csx210, sizeof(noOpData2_proform_csx210), QStringLiteral("noOp"));
} else 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"));
@@ -941,7 +1045,9 @@ void proformbike::update() {
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
if (proform_xbike) {
if (proform_csx210) {
writeCharacteristic(noOpData3_proform_csx210, sizeof(noOpData3_proform_csx210), QStringLiteral("noOp"));
} else 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"));
@@ -975,7 +1081,9 @@ void proformbike::update() {
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
break;
case 3:
if (proform_xbike) {
if (proform_csx210) {
writeCharacteristic(noOpData4_proform_csx210, sizeof(noOpData4_proform_csx210), QStringLiteral("noOp"));
} else if (proform_xbike) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
@@ -998,7 +1106,9 @@ void proformbike::update() {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
if (proform_xbike) {
if (proform_csx210) {
writeCharacteristic(noOpData5_proform_csx210, sizeof(noOpData5_proform_csx210), QStringLiteral("noOp"));
} else 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"));
@@ -1026,7 +1136,9 @@ void proformbike::update() {
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
if (proform_studio || proform_tdf_10)
if (proform_csx210) {
writeCharacteristic(noOpData6_proform_csx210, sizeof(noOpData6_proform_csx210), QStringLiteral("noOp"));
} else 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),
@@ -1089,7 +1201,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)) {
} 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)) {
counterPoll = 0;
}
@@ -1967,10 +2079,13 @@ 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()) {
@@ -2940,6 +3055,178 @@ void proformbike::btinit() {
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (proform_csx210) {
// ProForm CSX210 initialization sequence with 16 max resistance
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x80, 0x8b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x88, 0x93,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x0b, 0x02};
uint8_t initData6[] = {0xff, 0x0b, 0x02, 0x04, 0x02, 0x07, 0x02, 0x07, 0x82, 0x00,
0x00, 0x00, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData10[] = {0xfe, 0x02, 0x2c, 0x04};
// Execute initial setup sequence
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Main initialization sequence
uint8_t initData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x04,
0x00, 0xb2, 0xf4, 0x34, 0x72, 0xbe, 0x08, 0x40, 0x9e, 0xea};
uint8_t initData12[] = {0x01, 0x12, 0x3c, 0x8c, 0xda, 0x26, 0x90, 0xc8, 0x26, 0x82,
0xe4, 0x44, 0xa2, 0x0e, 0x98, 0xf0, 0x4e, 0xda, 0x2c, 0xbc};
uint8_t initData13[] = {0xff, 0x08, 0x0a, 0x96, 0x20, 0x80, 0x02, 0x00, 0x00, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Service discovery and configuration sequence
uint8_t initData14[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData15[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData16[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData20[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x00, 0x10, 0x00, 0xc0, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
uint8_t initData21[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Additional configuration and status frames
uint8_t initData22[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x96, 0x71, 0x00, 0x10, 0x40, 0x40, 0x00, 0x80};
uint8_t initData23[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xfd, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData24[] = {0xfe, 0x02, 0x11, 0x02};
uint8_t initData25[] = {0xff, 0x11, 0x02, 0x04, 0x02, 0x0d, 0x07, 0x0d, 0x02, 0x05,
0x00, 0x00, 0x00, 0x00, 0x08, 0x58, 0x02, 0x00, 0x7d, 0x00};
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
QThread::msleep(400);
// Final status and configuration frames
uint8_t initData26[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData27[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0xb9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData28[] = {0xfe, 0x02, 0x10, 0x02};
uint8_t initData29[] = {0xff, 0x10, 0x02, 0x04, 0x02, 0x0c, 0x07, 0x0c, 0x02, 0x04,
0x00, 0x00, 0x00, 0x02, 0x98, 0x21, 0x00, 0xd4, 0x00, 0x00};
uint8_t initData30[] = {0xfe, 0x02, 0x10, 0x02};
uint8_t initData31[] = {0xff, 0x10, 0x02, 0x04, 0x02, 0x0c, 0x07, 0x0c, 0x02, 0x05,
0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x2b, 0x00, 0x00};
uint8_t initData32[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData33[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData34[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData35[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData36[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x96, 0x71, 0x00, 0x10, 0x40, 0x40, 0x00, 0x80};
uint8_t initData37[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xfd, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData28, sizeof(initData28), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData29, sizeof(initData29), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData26, sizeof(initData26), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData27, sizeof(initData27), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData30, sizeof(initData30), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData31, sizeof(initData31), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData32, sizeof(initData32), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData33, sizeof(initData33), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData34, sizeof(initData34), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData35, sizeof(initData35), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData36, sizeof(initData36), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData37, sizeof(initData37), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else {
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07,

View File

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

View File

@@ -186,6 +186,10 @@ 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};
@@ -817,6 +821,24 @@ 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};
@@ -1104,6 +1126,7 @@ 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-")
@@ -1153,6 +1176,11 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bike_type = TYPE::TOORX_SRX_500;
qDebug() << QStringLiteral("TOORX_SRX_500 bike found");
} else if(taurua_ic90) {
refresh->start(500ms);
bike_type = TYPE::TAURUA_IC90;
qDebug() << QStringLiteral("TAURUA_IC90 bike found");
} else if (device.name().toUpper().startsWith(QStringLiteral("REEBOK"))) {
bike_type = TYPE::REEBOK;
qDebug() << QStringLiteral("REEBOK bike found");

View File

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

View File

@@ -331,7 +331,7 @@ void wahookickrsnapbike::update() {
}
auto virtualBike = this->VirtualBike();
if (requestResistance != currentResistance().value() &&
if (requestResistance != currentResistance().value() && requestResistance != -1 &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
lastForcedResistance = requestResistance;
@@ -341,11 +341,14 @@ 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));
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);
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);
} else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) {
inclinationChanged(lastGrade, lastGrade);
}

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 6.0.2, 2025-08-20T15:38:07. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{568050d7-c503-4fc0-9ae4-06a869edadb0}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
<value type="QString" key="ClangCodeModel.WarningConfigId">Builtin.BuildSystem</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{2eb206bb-7c50-4d71-9631-4ca0a2afd6d4}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Desktop-Release</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory.shadowDir">/home/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Desktop-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<valuelist type="QVariantList" key="QtProjectManager.QMakeBuildStep.SelectedAbis"/>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
<value type="int" key="QtQuickCompiler">1</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:/home/cagnulein/qdomyos-zwift/src/qdomyos-zwift.pro</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">/home/cagnulein/qdomyos-zwift/src/qdomyos-zwift.pro</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Desktop-Release</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -685,8 +685,6 @@ 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");
@@ -983,9 +981,17 @@ 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::taurua_ic90 = QStringLiteral("taurua_ic90");
const QString QZSettings::proform_csx210 = QStringLiteral("proform_csx210");
const QString QZSettings::zwift_click_name = QStringLiteral("zwift_click_name");
const QString QZSettings::default_zwift_click_name = QStringLiteral("Auto");
const QString QZSettings::zwift_play_left_name = QStringLiteral("zwift_play_left_name");
const QString QZSettings::default_zwift_play_left_name = QStringLiteral("Auto");
const QString QZSettings::zwift_play_right_name = QStringLiteral("zwift_play_right_name");
const QString QZSettings::default_zwift_play_right_name = QStringLiteral("Auto");
const uint32_t allSettingsCount = 807;
const uint32_t allSettingsCount = 810;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1564,8 +1570,6 @@ 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},
@@ -1813,6 +1817,11 @@ 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::zwift_click_name, QZSettings::default_zwift_click_name},
{QZSettings::zwift_play_left_name, QZSettings::default_zwift_play_left_name},
{QZSettings::zwift_play_right_name, QZSettings::default_zwift_play_right_name},
{QZSettings::toorxtreadmill_discovery_completed, QZSettings::default_toorxtreadmill_discovery_completed},
};

View File

@@ -1930,12 +1930,6 @@ 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;
@@ -2699,6 +2693,30 @@ class QZSettings {
static const QString height;
static constexpr double default_height = 175.0;
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 Zwift Click device MAC address selection
*/
static const QString zwift_click_name;
static const QString default_zwift_click_name;
/**
* @brief Zwift Play left device MAC address selection
*/
static const QString zwift_play_left_name;
static const QString default_zwift_play_left_name;
/**
* @brief Zwift Play right device MAC address selection
*/
static const QString zwift_play_right_name;
static const QString default_zwift_play_right_name;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

View File

@@ -1205,8 +1205,11 @@ import Qt.labs.platform 1.1
property int chart_display_mode: 0
property bool zwift_play_vibration: true
property bool toorxtreadmill_discovery_completed: false
property bool ant_remote_control: false
property int ant_remote_control_device_number: 0
property bool taurua_ic90: false
property bool proform_csx210: false
property string zwift_click_name: "Auto"
property string zwift_play_left_name: "Auto"
property string zwift_play_right_name: "Auto"
}
@@ -4012,7 +4015,8 @@ import Qt.labs.platform 1.1
"Nordictrack GX 4.4 Pro",
"TDF 1.0 PFEVEX71316.0",
"Proform XBike",
"Proform 225 CSX PFEX32925 INT.0"
"Proform 225 CSX PFEX32925 INT.0",
"Proform CSX210"
]
// Initialize when the accordion content becomes visible
@@ -4047,7 +4051,8 @@ import Qt.labs.platform 1.1
settings.nordictrack_gx_44_pro ? 15 :
settings.proform_bike_PFEVEX71316_0 ? 16 :
settings.proform_xbike ? 17 :
settings.proform_225_csx_PFEX32925_INT_0 ? 18 : 0;
settings.proform_225_csx_PFEX32925_INT_0 ? 18 :
settings.proform_csx210 ? 19 : 0;
console.log("bikeModelComboBox selected model: " + selectedModel);
if (selectedModel >= 0) {
@@ -4080,6 +4085,7 @@ import Qt.labs.platform 1.1
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) {
@@ -4101,6 +4107,7 @@ import Qt.labs.platform 1.1
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;
@@ -4634,69 +4641,6 @@ import Qt.labs.platform 1.1
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)
}
}
}
/*
@@ -8731,7 +8675,21 @@ import Qt.labs.platform 1.1
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
@@ -11707,7 +11665,85 @@ import Qt.labs.platform 1.1
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
}
Label {
text: qsTr("Zwift Click Device")
visible: settings.zwift_click
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
}
ComboBox {
id: zwiftClickDeviceCombo
visible: settings.zwift_click
Layout.fillWidth: true
model: rootItem.bluetooth ? rootItem.bluetooth.zwiftClickDevices : ["Auto"]
currentIndex: {
if (rootItem.bluetooth && rootItem.bluetooth.zwiftClickDevices) {
return Math.max(0, rootItem.bluetooth.zwiftClickDevices.indexOf(settings.zwift_click_name))
}
return 0
}
onCurrentTextChanged: {
if (currentText && currentText !== settings.zwift_click_name) {
settings.zwift_click_name = currentText
window.settings_restart_to_apply = true
}
}
}
Label {
text: qsTr("Zwift Play Left Device")
visible: settings.zwift_play
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
}
ComboBox {
id: zwiftPlayLeftDeviceCombo
visible: settings.zwift_play
Layout.fillWidth: true
model: rootItem.bluetooth ? rootItem.bluetooth.zwiftPlayDevices : ["Auto"]
currentIndex: {
if (rootItem.bluetooth && rootItem.bluetooth.zwiftPlayDevices) {
return Math.max(0, rootItem.bluetooth.zwiftPlayDevices.indexOf(settings.zwift_play_left_name))
}
return 0
}
onCurrentTextChanged: {
if (currentText && currentText !== settings.zwift_play_left_name) {
settings.zwift_play_left_name = currentText
window.settings_restart_to_apply = true
}
}
}
Label {
text: qsTr("Zwift Play Right Device")
visible: settings.zwift_play
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
}
ComboBox {
id: zwiftPlayRightDeviceCombo
visible: settings.zwift_play
Layout.fillWidth: true
model: rootItem.bluetooth ? rootItem.bluetooth.zwiftPlayDevices : ["Auto"]
currentIndex: {
if (rootItem.bluetooth && rootItem.bluetooth.zwiftPlayDevices) {
return Math.max(0, rootItem.bluetooth.zwiftPlayDevices.indexOf(settings.zwift_play_right_name))
}
return 0
}
onCurrentTextChanged: {
if (currentText && currentText !== settings.zwift_play_right_name) {
settings.zwift_play_right_name = currentText
window.settings_restart_to_apply = true
}
}
}
IndicatorOnlySwitch {
text: qsTr("Buttons debouncing")