Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Speed up connectToAdvertisingDevice #874

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 60 additions & 30 deletions packages/flutter_reactive_ble/lib/src/device_connector.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:flutter_reactive_ble/src/device_scanner.dart';
import 'package:flutter_reactive_ble/src/rx_ext/repeater.dart';
Expand Down Expand Up @@ -110,49 +112,77 @@ class DeviceConnectorImpl implements DeviceConnector {
Duration? connectionTimeout,
List<Uuid> withServices,
Duration prescanDuration,
) {
) async* {
if (_deviceIsDiscoveredRecently(
deviceId: id, cacheValidity: _scanRegistryCacheValidityPeriod)) {
return connect(
yield* connect(
id: id,
servicesWithCharacteristicsToDiscover:
servicesWithCharacteristicsToDiscover,
connectionTimeout: connectionTimeout,
);
} else {
final scanSubscription = _deviceScanner
.scanForDevices(
withServices: withServices, scanMode: ScanMode.lowLatency)
.listen((DiscoveredDevice scanData) {}, onError: (Object _) {});
return;
}

// get a sync function context so we can cancel the stream on a simple
// timer
Stream<DiscoveredDevice> scanWithTimeout() {
final controller = StreamController<DiscoveredDevice>();

final stream = _deviceScanner.scanForDevices(
withServices: withServices, scanMode: ScanMode.lowLatency);

final scanSubscription =
stream.listen(controller.add, onError: controller.addError);
Future<void>.delayed(prescanDuration).then<void>((_) {
scanSubscription.cancel();
controller.close();
});

return _deviceScanner.currentScan!.future
.then((_) => true)
.catchError((Object _) => false)
.asStream()
.asyncExpand(
(succeeded) {
if (succeeded) {
return _connectIfRecentlyDiscovered(
id, servicesWithCharacteristicsToDiscover, connectionTimeout);
} else {
// When the scan fails 99% of the times it is due to violation of the scan threshold:
// https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983
//
// Previously we used "autoconnect" but that gives slow connection times (up to 2 min) on a lot of devices.
return Future<void>.delayed(_delayAfterScanFailure)
.asStream()
.asyncExpand((_) => _connectIfRecentlyDiscovered(
id,
servicesWithCharacteristicsToDiscover,
connectionTimeout,
));
}
},
return controller.stream;
}

var didScanDevice = false;
try {
await for (final device in scanWithTimeout()) {
if (device.id == id) {
didScanDevice = true;
break;
}
}
// ignore: avoid_catches_without_on_clauses
} catch (_) {
// When the scan fails 99% of the times it is due to violation of the scan threshold:
// https://blog.classycode.com/undocumented-android-7-ble-behavior-changes-d1a9bd87d983
//
// Previously we used "autoconnect" but that gives slow connection times (up to 2 min) on a lot of devices.
await Future<void>.delayed(_delayAfterScanFailure);

yield* _connectIfRecentlyDiscovered(
id,
servicesWithCharacteristicsToDiscover,
connectionTimeout,
);
return;
}

if (didScanDevice) {
yield* connect(
id: id,
servicesWithCharacteristicsToDiscover:
servicesWithCharacteristicsToDiscover,
connectionTimeout: connectionTimeout,
);
return;
}

yield ConnectionStateUpdate(
deviceId: id,
connectionState: DeviceConnectionState.disconnected,
failure: const GenericFailure(
code: ConnectionError.failedToConnect,
message: "Device is not advertising"),
);
}

Stream<ConnectionStateUpdate> _awaitCurrentScanAndConnect(
Expand Down
46 changes: 30 additions & 16 deletions packages/flutter_reactive_ble/test/device_connector_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,6 @@ void main() {
const deviceId = '123';
final uuidDeviceToScan = Uuid.parse('FEFF');
final uuidCurrentScan = Uuid.parse('FEFE');
final discoveredDevice = DiscoveredDevice(
id: 'deviceId',
manufacturerData: Uint8List.fromList([0]),
serviceUuids: const [],
name: 'test',
rssi: -39,
connectable: Connectable.unknown,
serviceData: const {},
);

setUp(() {
_connectionStateUpdateStream = Stream.fromIterable([
Expand Down Expand Up @@ -235,15 +226,23 @@ void main() {
});

group('And device is not discovered in a previous scan', () {
setUp(() {
when(_scanner.scanForDevices(
withServices: anyNamed('withServices'),
scanMode: anyNamed('scanMode'),
)).thenAnswer((_) => Stream.fromIterable([discoveredDevice]));
});

group('And device is not found after scanning', () {
setUp(() {
when(_scanner.scanForDevices(
withServices: anyNamed('withServices'),
scanMode: anyNamed('scanMode'),
)).thenAnswer((_) => Stream.fromIterable([
DiscoveredDevice(
id: 'deviceId',
manufacturerData: Uint8List.fromList([0]),
serviceUuids: const [],
name: 'test',
rssi: -39,
connectable: Connectable.unknown,
serviceData: const {},
)
]));

when(_registry.deviceIsDiscoveredRecently(
deviceId: deviceId,
cacheValidity: anyNamed('cacheValidity')))
Expand Down Expand Up @@ -272,6 +271,21 @@ void main() {
});
group('And device found after scanning', () {
setUp(() {
when(_scanner.scanForDevices(
withServices: anyNamed('withServices'),
scanMode: anyNamed('scanMode'),
)).thenAnswer((_) => Stream.fromIterable([
DiscoveredDevice(
id: deviceId,
manufacturerData: Uint8List.fromList([0]),
serviceUuids: const [],
name: 'test',
rssi: -39,
connectable: Connectable.unknown,
serviceData: const {},
)
]));

final responses = [false, true, true, true];
when(_registry.deviceIsDiscoveredRecently(
deviceId: deviceId,
Expand Down