From fd4e5f5ce8a011c50600910cd308d8ce87a55dbc Mon Sep 17 00:00:00 2001 From: Jonas Bark Date: Sun, 1 Feb 2026 16:33:03 +0100 Subject: [PATCH] cleanup, proxy work --- .github/workflows/patch.yml | 4 +- lib/bluetooth/connection.dart | 40 ++++++----- lib/bluetooth/devices/bluetooth_device.dart | 6 +- lib/bluetooth/devices/proxy/proxy_device.dart | 66 +++++++++++++++++++ .../devices/zwift/zwift_clickv2.dart | 8 +-- lib/pages/unlock.dart | 40 +++++------ lib/widgets/menu.dart | 6 ++ prop | 2 +- 8 files changed, 128 insertions(+), 44 deletions(-) create mode 100644 lib/bluetooth/devices/proxy/proxy_device.dart diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index a8f31bd..1fdc185 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -101,7 +101,7 @@ jobs: with: platform: android release-version: latest - args: '--allow-asset-diffs --allow-native-diffs -- --dart-define=REVENUECAT_API_KEY_ANDROID=${{ secrets.REVENUECAT_API_KEY_ANDROID }}' + args: '--allow-asset-diffs --allow-native-diffs -- --obfuscate --split-debug-info=symbols --dart-define=REVENUECAT_API_KEY_ANDROID=${{ secrets.REVENUECAT_API_KEY_ANDROID }}' - name: 🚀 Shorebird Patch iOS uses: shorebirdtech/shorebird-patch@v1 @@ -187,4 +187,4 @@ jobs: with: platform: windows release-version: latest - args: '--allow-asset-diffs --allow-native-diffs' + args: '--allow-asset-diffs --allow-native-diffs -- --obfuscate --split-debug-info=symbols' diff --git a/lib/bluetooth/connection.dart b/lib/bluetooth/connection.dart index 1c0df10..275bc43 100644 --- a/lib/bluetooth/connection.dart +++ b/lib/bluetooth/connection.dart @@ -94,26 +94,36 @@ class Connection { if (_lastScanResult.none((e) => e.deviceId == result.deviceId && e.services.contentEquals(result.services))) { _lastScanResult.add(result); - if (false) { - debugPrint('Scan result: ${result.name} - ${result.deviceId}'); + if (kDebugMode) { + debugPrint('Scan result: ${result.name} - ${result.deviceId} - Services: ${result.services}'); } - final scanResult = BluetoothDevice.fromScanResult(result); + try { + final scanResult = BluetoothDevice.fromScanResult(result); - if (scanResult != null) { - _actionStreams.add( - LogNotification('Found new device: ${kIsWeb ? scanResult.toString() : scanResult.runtimeType}'), - ); - addDevices([scanResult]); - } else { - final manufacturerData = result.manufacturerDataList; - final data = manufacturerData - .firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID) - ?.payload; - if (data != null && kDebugMode) { + if (scanResult != null) { _actionStreams.add( - LogNotification('Found unknown device ${result.name} with identifier: ${data.firstOrNull}'), + LogNotification('Found new device: ${kIsWeb ? scanResult.toString() : scanResult.runtimeType}'), ); + addDevices([scanResult]); + } else { + final manufacturerData = result.manufacturerDataList; + final data = manufacturerData + .firstOrNullWhere((e) => e.companyId == ZwiftConstants.ZWIFT_MANUFACTURER_ID) + ?.payload; + if (data != null && kDebugMode) { + _actionStreams.add( + LogNotification('Found unknown device ${result.name} with identifier: ${data.firstOrNull}'), + ); + } + } + } catch (e, backtrace) { + _actionStreams.add( + LogNotification("Error processing scan result for device ${result.deviceId}: $e\n$backtrace"), + ); + if (kDebugMode) { + print(e); + print("backtrace: $backtrace"); } } } diff --git a/lib/bluetooth/devices/bluetooth_device.dart b/lib/bluetooth/devices/bluetooth_device.dart index ceb548a..5486be1 100644 --- a/lib/bluetooth/devices/bluetooth_device.dart +++ b/lib/bluetooth/devices/bluetooth_device.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:bike_control/bluetooth/ble.dart'; import 'package:bike_control/bluetooth/devices/base_device.dart'; import 'package:bike_control/bluetooth/devices/openbikecontrol/openbikecontrol_device.dart'; +import 'package:bike_control/bluetooth/devices/proxy/proxy_device.dart'; import 'package:bike_control/bluetooth/devices/shimano/shimano_di2.dart'; import 'package:bike_control/bluetooth/devices/sram/sram_axs.dart'; import 'package:bike_control/bluetooth/devices/wahoo/wahoo_kickr_bike_pro.dart'; @@ -47,8 +48,8 @@ abstract class BluetoothDevice extends BaseDevice { scanResult.name, uniqueId: scanResult.deviceId, availableButtons: allowMultiple - ? availableButtons.map((b) => b.copyWith(sourceDeviceId: scanResult.deviceId)).toList() - : availableButtons, + ? availableButtons.toList().map((b) => b.copyWith(sourceDeviceId: scanResult.deviceId)).toList() + : availableButtons.toList(), isBeta: isBeta, buttonPrefix: buttonPrefix, ) { @@ -123,6 +124,7 @@ abstract class BluetoothDevice extends BaseDevice { _ when scanResult.services.contains(ShimanoDi2Constants.SERVICE_UUID_ALTERNATIVE.toLowerCase()) => ShimanoDi2( scanResult, ), + _ when scanResult.services.containsAny(ProxyDevice.proxyServiceUUIDs) && kDebugMode => ProxyDevice(scanResult), _ when scanResult.services.contains(SramAxsConstants.SERVICE_UUID.toLowerCase()) => SramAxs( scanResult, ), diff --git a/lib/bluetooth/devices/proxy/proxy_device.dart b/lib/bluetooth/devices/proxy/proxy_device.dart new file mode 100644 index 0000000..8c62cfd --- /dev/null +++ b/lib/bluetooth/devices/proxy/proxy_device.dart @@ -0,0 +1,66 @@ +import 'dart:typed_data'; + +import 'package:bike_control/bluetooth/devices/bluetooth_device.dart'; +import 'package:prop/emulators/ftms_emulator.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:universal_ble/universal_ble.dart'; + +class ProxyDevice extends BluetoothDevice { + static final List proxyServiceUUIDs = [ + '0000180d-0000-1000-8000-00805f9b34fb', // Heart Rate + '00001818-0000-1000-8000-00805f9b34fb', // Cycling Power + '00001826-0000-1000-8000-00805f9b34fb', // Fitness Machine + ]; + + final FtmsEmulator emulator = FtmsEmulator(); + + ProxyDevice(super.scanResult) + : super( + availableButtons: const [], + isBeta: true, + ); + + late final List services; + + @override + Future handleServices(List services) async { + emulator.setScanResult(scanResult); + emulator.handleServices(services); + + emulator.startServer(); + } + + @override + Future processCharacteristic(String characteristic, Uint8List bytes) async { + emulator.processCharacteristic(characteristic, bytes); + } + + @override + Widget showInformation(BuildContext context) { + return Column( + spacing: 16, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + super.showInformation(context), + if (!isConnected) + Button.primary( + style: ButtonStyle.primary(size: ButtonSize.small), + onPressed: () { + super.connect(); + }, + child: Text('Proxy'), + ), + ], + ); + } + + @override + Future connect() async {} + + @override + Future disconnect() { + emulator.stop(); + return super.disconnect(); + } +} diff --git a/lib/bluetooth/devices/zwift/zwift_clickv2.dart b/lib/bluetooth/devices/zwift/zwift_clickv2.dart index ab71809..8441b10 100644 --- a/lib/bluetooth/devices/zwift/zwift_clickv2.dart +++ b/lib/bluetooth/devices/zwift/zwift_clickv2.dart @@ -12,7 +12,7 @@ import 'package:prop/prop.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:universal_ble/universal_ble.dart'; -final FtmsEmulator emulator = FtmsEmulator(); +final FtmsEmulator ftmsEmulator = FtmsEmulator(); class ZwiftClickV2 extends ZwiftRide { ZwiftClickV2(super.scanResult) @@ -31,7 +31,7 @@ class ZwiftClickV2 extends ZwiftRide { ZwiftButtons.shiftUpRight, ], ) { - emulator.setScanResult(scanResult); + ftmsEmulator.setScanResult(scanResult); } @override @@ -65,13 +65,13 @@ class ZwiftClickV2 extends ZwiftRide { @override Future handleServices(List services) async { - emulator.handleServices(services); + ftmsEmulator.handleServices(services); await super.handleServices(services); } @override Future processCharacteristic(String characteristic, Uint8List bytes) async { - if (!emulator.processCharacteristic(characteristic, bytes)) { + if (!ftmsEmulator.processCharacteristic(characteristic, bytes)) { await super.processCharacteristic(characteristic, bytes); } } diff --git a/lib/pages/unlock.dart b/lib/pages/unlock.dart index e9257b8..03315b2 100644 --- a/lib/pages/unlock.dart +++ b/lib/pages/unlock.dart @@ -36,7 +36,7 @@ class _UnlockPageState extends State with SingleTickerProviderStateM void _isConnectedUpdate() { setState(() {}); - if (emulator.isUnlocked.value) { + if (ftmsEmulator.isUnlocked.value) { _close(); } } @@ -47,8 +47,8 @@ class _UnlockPageState extends State with SingleTickerProviderStateM _isInTrialPhase = !IAPManager.instance.isPurchased.value && IAPManager.instance.isTrialExpired; _ticker = createTicker((_) { - if (emulator.waiting.value) { - final waitUntil = emulator.connectionDate!.add(Duration(minutes: 1)); + if (ftmsEmulator.waiting.value) { + final waitUntil = ftmsEmulator.connectionDate!.add(Duration(minutes: 1)); final secondsUntil = waitUntil.difference(DateTime.now()).inSeconds; if (mounted) { @@ -79,13 +79,13 @@ class _UnlockPageState extends State with SingleTickerProviderStateM core.settings.setObpMdnsEnabled(false); } - emulator.isUnlocked.value = false; - emulator.alreadyUnlocked.value = false; - emulator.waiting.value = false; - emulator.isConnected.addListener(_isConnectedUpdate); - emulator.isUnlocked.addListener(_isConnectedUpdate); - emulator.alreadyUnlocked.addListener(_isConnectedUpdate); - emulator.startServer().then((_) {}).catchError((e, s) { + ftmsEmulator.isUnlocked.value = false; + ftmsEmulator.alreadyUnlocked.value = false; + ftmsEmulator.waiting.value = false; + ftmsEmulator.isConnected.addListener(_isConnectedUpdate); + ftmsEmulator.isUnlocked.addListener(_isConnectedUpdate); + ftmsEmulator.alreadyUnlocked.addListener(_isConnectedUpdate); + ftmsEmulator.startServer().then((_) {}).catchError((e, s) { recordError(e, s, context: 'Emulator'); core.connection.signalNotification(AlertNotification(LogLevel.LOGLEVEL_ERROR, e.toString())); }); @@ -96,10 +96,10 @@ class _UnlockPageState extends State with SingleTickerProviderStateM void dispose() { _ticker.dispose(); if (!_isInTrialPhase) { - emulator.isConnected.removeListener(_isConnectedUpdate); - emulator.isUnlocked.removeListener(_isConnectedUpdate); - emulator.alreadyUnlocked.removeListener(_isConnectedUpdate); - emulator.stop(); + ftmsEmulator.isConnected.removeListener(_isConnectedUpdate); + ftmsEmulator.isUnlocked.removeListener(_isConnectedUpdate); + ftmsEmulator.alreadyUnlocked.removeListener(_isConnectedUpdate); + ftmsEmulator.stop(); if (_wasZwiftMdnsEmulatorActive) { core.zwiftMdnsEmulator.startServer(); @@ -171,30 +171,30 @@ class _UnlockPageState extends State with SingleTickerProviderStateM closeDrawer(context); }, ), - ] else if (!emulator.isConnected.value) ...[ + ] else if (!ftmsEmulator.isConnected.value) ...[ Text(AppLocalizations.of(context).unlock_openZwift).li, Text(AppLocalizations.of(context).unlock_connectToBikecontrol).li, SizedBox(height: 32), Text(AppLocalizations.of(context).unlock_bikecontrolAndZwiftNetwork).small, - ] else if (emulator.alreadyUnlocked.value) ...[ + ] else if (ftmsEmulator.alreadyUnlocked.value) ...[ Text(AppLocalizations.of(context).unlock_yourZwiftClickMightBeUnlockedAlready), SizedBox(height: 8), Text(AppLocalizations.of(context).unlock_confirmByPressingAButtonOnYourDevice).small, - ] else if (!emulator.isUnlocked.value) + ] else if (!ftmsEmulator.isUnlocked.value) Text(AppLocalizations.of(context).unlock_waitingForZwift) else Text('Zwift Click is unlocked! You can now close this page.'), SizedBox(height: 32), if (!_showManualSteps && !_isInTrialPhase) ...[ - if (emulator.waiting.value && _secondsRemaining >= 0) + if (ftmsEmulator.waiting.value && _secondsRemaining >= 0) Center(child: CircularProgressIndicator(value: 1 - (_secondsRemaining / 60), size: 20)) - else if (emulator.alreadyUnlocked.value) + else if (ftmsEmulator.alreadyUnlocked.value) Center(child: Icon(Icons.lock_clock)) else SmallProgressIndicator(), SizedBox(height: 20), ], - if (!emulator.isUnlocked.value && !_showManualSteps) ...[ + if (!ftmsEmulator.isUnlocked.value && !_showManualSteps) ...[ if (!_isInTrialPhase) ...[ SizedBox(height: 32), Center(child: Text(AppLocalizations.of(context).unlock_notWorking).small), diff --git a/lib/widgets/menu.dart b/lib/widgets/menu.dart index ec9e4f7..bf421dd 100644 --- a/lib/widgets/menu.dart +++ b/lib/widgets/menu.dart @@ -143,6 +143,12 @@ class BKMenuButton extends StatelessWidget { await core.settings.reset(); }, ), + MenuButton( + child: Text('Disconnect'), + onPressed: (c) async { + core.connection.disconnectAll(); + }, + ), MenuDivider(), ], if (currentPage == BCPage.logs) ...[ diff --git a/prop b/prop index 2283bd5..a2be5c5 160000 --- a/prop +++ b/prop @@ -1 +1 @@ -Subproject commit 2283bd5273a87340ee85fe3465e73810fd938d4b +Subproject commit a2be5c54e7a128fe48ab9b77c31cf9d92798d2df