From e9c6ba6ed55b1ae323671bc77456d0de2bc5f795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?th=E1=BB=8Bnh?= Date: Mon, 18 Aug 2025 12:23:03 +0700 Subject: [PATCH] improve the BLE scan mechanism to reduce battery consumption by the mobile app, its basically by 8/10 --- .../pages/conversations/widgets/capture.dart | 19 -------- .../pages/onboarding/find_device/page.dart | 1 - app/lib/pages/onboarding/welcome/page.dart | 19 ++++---- app/lib/pages/settings/device_settings.dart | 1 - app/lib/providers/device_provider.dart | 35 ++++++-------- app/lib/providers/onboarding_provider.dart | 48 +++++++------------ app/lib/services/devices.dart | 4 +- .../services/devices/device_connection.dart | 2 +- 8 files changed, 46 insertions(+), 83 deletions(-) diff --git a/app/lib/pages/conversations/widgets/capture.dart b/app/lib/pages/conversations/widgets/capture.dart index 94f49cc6efa..6a55dfdb18a 100644 --- a/app/lib/pages/conversations/widgets/capture.dart +++ b/app/lib/pages/conversations/widgets/capture.dart @@ -1,12 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/pages/capture/widgets/widgets.dart'; import 'package:omi/providers/capture_provider.dart'; -import 'package:omi/providers/connectivity_provider.dart'; import 'package:omi/providers/device_provider.dart'; -import 'package:omi/providers/onboarding_provider.dart'; -import 'package:omi/services/services.dart'; import 'package:omi/utils/audio/wav_bytes.dart'; import 'package:provider/provider.dart'; @@ -28,23 +23,9 @@ class LiteCaptureWidgetState extends State with AutomaticKeep @override void initState() { WavBytesUtil.clearTempWavFiles(); - SchedulerBinding.instance.addPostFrameCallback((_) async { - if (context.read().connectedDevice != null) { - context.read().stopScanDevices(); - } - }); - super.initState(); } - Future _getAudioCodec(String deviceId) async { - var connection = await ServiceManager.instance().device.ensureConnection(deviceId); - if (connection == null) { - return BleAudioCodec.pcm8; - } - return connection.getAudioCodec(); - } - @override Widget build(BuildContext context) { super.build(context); diff --git a/app/lib/pages/onboarding/find_device/page.dart b/app/lib/pages/onboarding/find_device/page.dart index 1ce6ad3c020..4e3a3448f56 100644 --- a/app/lib/pages/onboarding/find_device/page.dart +++ b/app/lib/pages/onboarding/find_device/page.dart @@ -42,7 +42,6 @@ class _FindDevicesPageState extends State { @override dispose() { - _provider?.stopScanDevices(); _provider = null; super.dispose(); diff --git a/app/lib/pages/onboarding/welcome/page.dart b/app/lib/pages/onboarding/welcome/page.dart index 78709d4e81d..189a3407a9c 100644 --- a/app/lib/pages/onboarding/welcome/page.dart +++ b/app/lib/pages/onboarding/welcome/page.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:omi/providers/onboarding_provider.dart'; -import 'package:omi/utils/analytics/intercom.dart'; -import 'package:omi/utils/platform/platform_service.dart'; import 'package:omi/widgets/dialog.dart'; -import 'package:gradient_borders/box_borders/gradient_box_border.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; @@ -174,15 +171,19 @@ class _WelcomePageState extends State with TickerProviderStateMixin AnimatedContainer( duration: const Duration(milliseconds: 800), curve: Curves.easeInOut, - height: _isExpandingTop ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * _expansionAnimation.value, + height: _isExpandingTop + ? MediaQuery.of(context).size.height + : MediaQuery.of(context).size.height * _expansionAnimation.value, child: Container( width: double.infinity, decoration: BoxDecoration( image: DecorationImage( image: ResizeImage( const AssetImage('assets/images/onboarding-bg-5-1.jpg'), - width: (MediaQuery.of(context).size.width * MediaQuery.of(context).devicePixelRatio).round(), - height: (MediaQuery.of(context).size.height * MediaQuery.of(context).devicePixelRatio).round(), + width: + (MediaQuery.of(context).size.width * MediaQuery.of(context).devicePixelRatio).round(), + height: (MediaQuery.of(context).size.height * MediaQuery.of(context).devicePixelRatio) + .round(), ), fit: BoxFit.cover, ), @@ -268,8 +269,10 @@ class _WelcomePageState extends State with TickerProviderStateMixin image: DecorationImage( image: ResizeImage( const AssetImage('assets/images/onboarding-bg-5-2.jpg'), - width: (MediaQuery.of(context).size.width * MediaQuery.of(context).devicePixelRatio).round(), - height: (MediaQuery.of(context).size.height * MediaQuery.of(context).devicePixelRatio).round(), + width: (MediaQuery.of(context).size.width * MediaQuery.of(context).devicePixelRatio) + .round(), + height: (MediaQuery.of(context).size.height * MediaQuery.of(context).devicePixelRatio) + .round(), ), fit: BoxFit.cover, ), diff --git a/app/lib/pages/settings/device_settings.dart b/app/lib/pages/settings/device_settings.dart index 19ea2ee5572..4213c88adb7 100644 --- a/app/lib/pages/settings/device_settings.dart +++ b/app/lib/pages/settings/device_settings.dart @@ -136,7 +136,6 @@ class _DeviceSettingsState extends State { provider.setIsConnected(false); provider.setConnectedDevice(null); provider.updateConnectingStatus(false); - context.read().stopScanDevices(); Navigator.of(context).pop(); Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( diff --git a/app/lib/providers/device_provider.dart b/app/lib/providers/device_provider.dart index 1a1e405255e..31531ae0598 100644 --- a/app/lib/providers/device_provider.dart +++ b/app/lib/providers/device_provider.dart @@ -13,8 +13,8 @@ import 'package:omi/services/services.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/device.dart'; import 'package:omi/utils/logger.dart'; -import 'package:omi/widgets/confirmation_dialog.dart'; import 'package:omi/utils/platform/platform_manager.dart'; +import 'package:omi/widgets/confirmation_dialog.dart'; class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption { CaptureProvider? captureProvider; @@ -29,7 +29,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption bool _hasLowBatteryAlerted = false; Timer? _reconnectionTimer; DateTime? _reconnectAt; - final int _connectionCheckSeconds = 7; + final int _connectionCheckSeconds = 10; bool _havingNewFirmware = false; bool get havingNewFirmware => _havingNewFirmware && pairedDevice != null && isConnected; @@ -141,12 +141,8 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption Future periodicConnect(String printer) async { _reconnectionTimer?.cancel(); - _reconnectionTimer = Timer.periodic(Duration(seconds: _connectionCheckSeconds), (t) async { + scan(t) async { debugPrint("Period connect seconds: $_connectionCheckSeconds, triggered timer at ${DateTime.now()}"); - if (SharedPreferencesUtil().btDevice.id.isEmpty) { - t.cancel(); - return; - } if (_reconnectAt != null && _reconnectAt!.isAfter(DateTime.now())) { return; } @@ -159,27 +155,26 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption } else { t.cancel(); } - }); + } + + _reconnectionTimer = Timer.periodic(Duration(seconds: _connectionCheckSeconds), scan); + scan(_reconnectionTimer); } - Future _scanAndConnectDevice({bool autoConnect = true, bool timeout = false}) async { + Future _scanConnectDevice() async { var device = await _getConnectedDevice(); if (device != null) { return device; } - int timeoutCounter = 0; - while (true) { - if (timeout && timeoutCounter >= 10) return null; - await ServiceManager.instance().device.discover(desirableDeviceId: SharedPreferencesUtil().btDevice.id); - if (connectedDevice != null) { - return connectedDevice; - } + await ServiceManager.instance().device.discover(desirableDeviceId: SharedPreferencesUtil().btDevice.id); - // If the device is not found, wait for a bit before retrying. - await Future.delayed(const Duration(seconds: 2)); - timeoutCounter += 2; + // Waiting for the device connected (if any) + await Future.delayed(const Duration(seconds: 2)); + if (connectedDevice != null) { + return connectedDevice; } + return null; } Future scanAndConnectToDevice() async { @@ -198,7 +193,7 @@ class DeviceProvider extends ChangeNotifier implements IDeviceServiceSubsciption } // else - var device = await _scanAndConnectDevice(); + var device = await _scanConnectDevice(); Logger.debug('inside scanAndConnectToDevice $device in device_provider'); if (device != null) { var cDevice = await _getConnectedDevice(); diff --git a/app/lib/providers/onboarding_provider.dart b/app/lib/providers/onboarding_provider.dart index ebf65b511dd..9a47be52c6a 100644 --- a/app/lib/providers/onboarding_provider.dart +++ b/app/lib/providers/onboarding_provider.dart @@ -32,7 +32,6 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen String? connectingToDeviceId; List deviceList = []; late Timer _didNotMakeItTimer; - Timer? _findDevicesTimer; bool enableInstructions = false; Map foundDevicesMap = {}; @@ -403,20 +402,19 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen VoidCallback? goNext, }) async { try { - if (isClicked) return; // if any item is clicked, don't do anything - isClicked = true; // Prevent further clicks - connectingToDeviceId = device.id; // Mark this device as being connected to + if (isClicked) return; + isClicked = true; + + connectingToDeviceId = device.id; notifyListeners(); - var c = await ServiceManager.instance().device.ensureConnection(device.id, force: true); + await ServiceManager.instance().device.ensureConnection(device.id, force: true); debugPrint('Connected to device: ${device.name}'); deviceId = device.id; - // device = await device.getDeviceInfo(c); await SharedPreferencesUtil().btDeviceSet(device); deviceName = device.name; var cDevice = await _getConnectedDevice(deviceId); if (cDevice != null) { deviceProvider!.setConnectedDevice(cDevice); - // SharedPreferencesUtil().btDevice = cDevice; SharedPreferencesUtil().deviceName = cDevice.name; deviceProvider!.setIsConnected(true); } @@ -424,10 +422,9 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen var connectedDevice = deviceProvider!.connectedDevice; batteryPercentage = deviceProvider!.batteryLevel; isConnected = true; - isClicked = false; // Allow clicks again after finishing the operation + isClicked = false; connectingToDeviceId = null; // Reset the connecting device notifyListeners(); - stopScanDevices(); await Future.delayed(const Duration(seconds: 2)); SharedPreferencesUtil().btDevice = connectedDevice!; SharedPreferencesUtil().deviceName = connectedDevice.name; @@ -459,8 +456,13 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen notifyListeners(); } - void stopScanDevices() { - _findDevicesTimer?.cancel(); + // TODO: thinh, use connection directly + Future _getConnectedDevice(String deviceId) async { + if (deviceId.isEmpty) { + return null; + } + var connection = await ServiceManager.instance().device.ensureConnection(deviceId); + return connection?.device; } Future scanDevices({ @@ -470,6 +472,7 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen // it means the device has been unpaired deviceAlreadyUnpaired(); } + // check if bluetooth is enabled on both platforms if (!hasBluetoothPermission) { await askForBluetoothPermissions(); @@ -484,30 +487,11 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen }); ServiceManager.instance().device.subscribe(this, this); - - _findDevicesTimer?.cancel(); - _findDevicesTimer = Timer.periodic(const Duration(seconds: 4), (t) async { - if (deviceProvider?.isConnected ?? false) { - t.cancel(); - return; - } - - ServiceManager.instance().device.discover(); - }); - } - - // TODO: thinh, use connection directly - Future _getConnectedDevice(String deviceId) async { - if (deviceId.isEmpty) { - return null; - } - var connection = await ServiceManager.instance().device.ensureConnection(deviceId); - return connection?.device; + await deviceProvider?.periodicConnect("Come from Onboarding"); } @override void dispose() { - _findDevicesTimer?.cancel(); _didNotMakeItTimer.cancel(); ServiceManager.instance().device.unsubscribe(this); super.dispose(); @@ -528,11 +512,13 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen // If it's a new device, add it to the map. If it already exists, this will just update the entry. updatedDevicesMap[device.id] = device; } + // Remove devices that are no longer found foundDevicesMap.keys.where((id) => !updatedDevicesMap.containsKey(id)).toList().forEach(foundDevicesMap.remove); // Merge the new devices into the current map to maintain order foundDevicesMap.addAll(updatedDevicesMap); + // Convert the values of the map back to a list List orderedDevices = foundDevicesMap.values.toList(); if (orderedDevices.isNotEmpty) { diff --git a/app/lib/services/devices.dart b/app/lib/services/devices.dart index 0c0dde38b86..ff6ae9beeb9 100644 --- a/app/lib/services/devices.dart +++ b/app/lib/services/devices.dart @@ -212,7 +212,7 @@ class DeviceService implements IDeviceService { // connected var pongAt = _connection?.pongAt; - var shouldPing = (pongAt == null || pongAt.isBefore(DateTime.now().subtract(const Duration(seconds: 5)))); + var shouldPing = (pongAt == null || pongAt.isBefore(DateTime.now().subtract(const Duration(seconds: 10)))); if (shouldPing) { var ok = await _connection?.ping() ?? false; if (!ok) { @@ -227,7 +227,7 @@ class DeviceService implements IDeviceService { // Force if (deviceId == _connection?.device.id && _connection?.status == DeviceConnectionState.connected) { var pongAt = _connection?.pongAt; - var shouldPing = (pongAt == null || pongAt.isBefore(DateTime.now().subtract(const Duration(seconds: 5)))); + var shouldPing = (pongAt == null || pongAt.isBefore(DateTime.now().subtract(const Duration(seconds: 10)))); if (shouldPing) { var ok = await _connection?.ping() ?? false; if (!ok) { diff --git a/app/lib/services/devices/device_connection.dart b/app/lib/services/devices/device_connection.dart index d72b686b9df..a50d5daa922 100644 --- a/app/lib/services/devices/device_connection.dart +++ b/app/lib/services/devices/device_connection.dart @@ -122,7 +122,7 @@ abstract class DeviceConnection { Future ping() async { try { - int rssi = await bleDevice.readRssi(); + int rssi = await bleDevice.readRssi(timeout: 10); device.rssi = rssi; _pongAt = DateTime.now(); return true;