vibrate Zwift Play / Zwift Ride controller on gear shift (thanks @cagnulein, closes #16)

This commit is contained in:
Jonas Bark
2025-04-10 13:31:55 +02:00
parent 6bf83b1034
commit f1b8822e20
4 changed files with 34 additions and 12 deletions

View File

@@ -1,3 +1,6 @@
#### 2.0.4 (2025-04-10)
- vibrate Zwift Play / Zwift Ride controller on gear shift (thanks @cagnulein, closes #16)
#### 2.0.3 (2025-04-08)
- adjust TrainingPeaks Virtual key mapping (#12)
- attempt to reconnect device if connection is lost

View File

@@ -23,6 +23,7 @@ class Constants {
static const BC1 = 0x09;
static final RIDE_ON = Uint8List.fromList([0x52, 0x69, 0x64, 0x65, 0x4f, 0x6e]);
static final VIBRATE_PATTERN = Uint8List.fromList([0x12, 0x12, 0x08, 0x0A, 0x06, 0x08, 0x02, 0x10, 0x00, 0x18]);
// these don't actually seem to matter, its just the header has to be 7 bytes RIDEON + 2
static final REQUEST_START = Uint8List.fromList([0, 9]); //byteArrayOf(1, 2)

View File

@@ -27,6 +27,7 @@ abstract class BaseDevice {
bool supportsEncryption = true;
BleCharacteristic? syncRxCharacteristic;
Timer? _longPressTimer;
List<int> get startCommand => Constants.RIDE_ON + Constants.RESPONSE_START_CLICK;
@@ -114,7 +115,7 @@ abstract class BaseDevice {
final syncTxCharacteristic = customService.characteristics.firstOrNullWhere(
(characteristic) => characteristic.uuid == BleUuid.ZWIFT_SYNC_TX_CHARACTERISTIC_UUID,
);
final syncRxCharacteristic = customService.characteristics.firstOrNullWhere(
syncRxCharacteristic = customService.characteristics.firstOrNullWhere(
(characteristic) => characteristic.uuid == BleUuid.ZWIFT_SYNC_RX_CHARACTERISTIC_UUID,
);
@@ -135,15 +136,15 @@ abstract class BaseDevice {
BleInputProperty.indication,
);
await _setupHandshake(syncRxCharacteristic);
await _setupHandshake();
}
Future<void> _setupHandshake(BleCharacteristic syncRxCharacteristic) async {
Future<void> _setupHandshake() async {
if (supportsEncryption) {
await UniversalBle.writeValue(
device.deviceId,
customServiceId,
syncRxCharacteristic.uuid,
syncRxCharacteristic!.uuid,
Uint8List.fromList([
...Constants.RIDE_ON,
...Constants.REQUEST_START,
@@ -155,7 +156,7 @@ abstract class BaseDevice {
await UniversalBle.writeValue(
device.deviceId,
customServiceId,
syncRxCharacteristic.uuid,
syncRxCharacteristic!.uuid,
Constants.RIDE_ON,
BleOutputProperty.withoutResponse,
);
@@ -246,15 +247,11 @@ abstract class BaseDevice {
// we don't want to trigger the long press timer for the on/off buttons
_longPressTimer?.cancel();
_longPressTimer = Timer.periodic(const Duration(milliseconds: 250), (timer) async {
for (final action in buttonsClicked) {
actionStreamInternal.add(LogNotification(await actionHandler.performAction(action)));
}
_performActions(buttonsClicked, true);
});
}
for (final action in buttonsClicked) {
actionStreamInternal.add(LogNotification(await actionHandler.performAction(action)));
}
_performActions(buttonsClicked, false);
}
})
.catchError((e) {
@@ -265,4 +262,25 @@ abstract class BaseDevice {
}
Future<List<ZwiftButton>?> processClickNotification(Uint8List message);
Future<void> _performActions(List<ZwiftButton> buttonsClicked, bool repeated) async {
if (!repeated &&
buttonsClicked.any(((e) => e.action == InGameAction.shiftDown || e.action == InGameAction.shiftUp))) {
await _vibrate();
}
for (final action in buttonsClicked) {
actionStreamInternal.add(LogNotification(await actionHandler.performAction(action)));
}
}
Future<void> _vibrate() async {
final vibrateCommand = Uint8List.fromList([...Constants.VIBRATE_PATTERN, 0x20]);
await UniversalBle.writeValue(
device.deviceId,
customServiceId,
syncRxCharacteristic!.uuid,
supportsEncryption ? zapEncryption.encrypt(vibrateCommand) : vibrateCommand,
BleOutputProperty.withoutResponse,
);
}
}

View File

@@ -1,7 +1,7 @@
name: swift_control
description: "SwiftControl - Control your virtual riding"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 2.0.3+0
version: 2.0.4+0
environment:
sdk: ^3.7.0