Skip to content

Commit

Permalink
[Android] handle scan failure. also add verbose log level and remove …
Browse files Browse the repository at this point in the history
…unused log levels
  • Loading branch information
chipweinberger committed Jul 24, 2023
1 parent 369fc4f commit d14bfa4
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public class FlutterBluePlusPlugin implements

static final private UUID CCCD_ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
private final Map<String, BluetoothDeviceCache> mDevices = new HashMap<>();
private LogLevel logLevel = LogLevel.EMERGENCY;
private LogLevel logLevel = LogLevel.DEBUG;

private interface OperationOnPermission {
void op(boolean granted, String permission);
Expand Down Expand Up @@ -1347,6 +1347,8 @@ private ScanCallback getScanCallback()
@Override
public void onScanResult(int callbackType, ScanResult result)
{
log(LogLevel.VERBOSE, "[FBP-Android] onScanResult");

super.onScanResult(callbackType, result);

if(result != null){
Expand All @@ -1360,7 +1362,14 @@ public void onScanResult(int callbackType, ScanResult result)
macDeviceScanned.add(result.getDevice().getAddress());
}

invokeMethodUIThread("ScanResult", MessageMaker.bmScanResult(result.getDevice(), result));
// see BmScanResult
HashMap<String, Object> rr = MessageMaker.bmScanResult(result.getDevice(), result);

// see BmScanResponse
HashMap<String, Object> response = new HashMap<>();
response.put("result", rr);

invokeMethodUIThread("ScanResponse", response);
}
}

Expand All @@ -1373,7 +1382,21 @@ public void onBatchScanResults(List<ScanResult> results)
@Override
public void onScanFailed(int errorCode)
{
log(LogLevel.ERROR, "[FBP-Android] onScanFailed: " + scanFailedString(errorCode));

super.onScanFailed(errorCode);

// see: BmScanFailed
HashMap<String, Object> failed = new HashMap<>();
failed.put("success", 0);
failed.put("error_code", errorCode);
failed.put("error_string", scanFailedString(errorCode));

// see BmScanResponse
HashMap<String, Object> response = new HashMap<>();
response.put("failed", failed);

invokeMethodUIThread("ScanResponse", response);
}
};
}
Expand Down Expand Up @@ -1655,7 +1678,7 @@ private static String gattErrorString(int value) {
case BluetoothGatt.GATT_READ_NOT_PERMITTED : return "GATT_READ_NOT_PERMITTED";
case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED : return "GATT_REQUEST_NOT_SUPPORTED";
case BluetoothGatt.GATT_WRITE_NOT_PERMITTED : return "GATT_WRITE_NOT_PERMITTED";
default: return "UNKNOWN_ERROR (" + value + ")";
default: return "UNKNOWN_GATT_ERROR (" + value + ")";
}
}

Expand All @@ -1673,13 +1696,30 @@ private static String bluetoothStatusString(int value) {
case BluetoothStatusCodes.FEATURE_NOT_SUPPORTED : return "FEATURE_NOT_SUPPORTED";
case BluetoothStatusCodes.FEATURE_SUPPORTED : return "FEATURE_SUPPORTED";
case BluetoothStatusCodes.SUCCESS : return "SUCCESS";
default: return "UNKNOWN_ERROR (" + value + ")";
default: return "UNKNOWN_BLE_ERROR (" + value + ")";
}
}

private static String scanFailedString(int value) {
switch(value) {
case ScanCallback.SCAN_FAILED_ALREADY_STARTED : return "SCAN_FAILED_ALREADY_STARTED";
case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: return "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED";
case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED : return "SCAN_FAILED_FEATURE_UNSUPPORTED";
case ScanCallback.SCAN_FAILED_INTERNAL_ERROR : return "SCAN_FAILED_INTERNAL_ERROR";
case ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES : return "SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES";
case ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY : return "SCAN_FAILED_SCANNING_TOO_FREQUENTLY";
default: return "UNKNOWN_SCAN_ERROR (" + value + ")";
}
}

enum LogLevel
{
EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG
NONE, // 0
ERROR, // 1
WARNING, // 2
INFO, // 3
DEBUG, // 4
VERBOSE // 5
}

// BluetoothDeviceCache contains any other cached information not stored in Android Bluetooth API
Expand Down
28 changes: 16 additions & 12 deletions ios/Classes/FlutterBluePlusPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ - (NSString *)fullUUIDString
@end

typedef NS_ENUM(NSUInteger, LogLevel) {
emergency = 0,
alert = 1,
critical = 2,
error = 3,
warning = 4,
notice = 5,
info = 6,
debug = 7
none = 0,
error = 1,
warning = 2,
info = 3,
debug = 4,
verbose = 5,
};

@interface FlutterBluePlusPlugin ()
Expand All @@ -60,7 +58,7 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar
instance.servicesThatNeedDiscovered = [NSMutableArray new];
instance.characteristicsThatNeedDiscovered = [NSMutableArray new];
instance.dataWaitingToWriteWithoutResponse = [NSMutableDictionary new];
instance.logLevel = emergency;
instance.logLevel = debug;

[registrar addMethodCallDelegate:instance channel:methodChannel];
}
Expand Down Expand Up @@ -880,15 +878,21 @@ - (void)centralManager:(CBCentralManager *)central
advertisementData:(NSDictionary<NSString *, id> *)advertisementData
RSSI:(NSNumber *)RSSI
{
if (_logLevel >= debug) {
//NSLog(@"[FBP-iOS] centralManager didDiscoverPeripheral");
if (_logLevel >= verbose) {
NSLog(@"[FBP-iOS] centralManager didDiscoverPeripheral");
}

[self.scannedPeripherals setObject:peripheral forKey:[[peripheral identifier] UUIDString]];

// See BmScanResult
NSDictionary *result = [self toScanResultProto:peripheral advertisementData:advertisementData RSSI:RSSI];

[_methodChannel invokeMethod:@"ScanResult" arguments:result];
// See BmScanResponse
NSDictionary *response = @{
@"result": result,
};

[_methodChannel invokeMethod:@"ScanResponse" arguments:response];
}

- (void)centralManager:(CBCentralManager *)central
Expand Down
77 changes: 59 additions & 18 deletions lib/src/bluetooth_msgs.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
part of flutter_blue_plus;

void _printDbg(String s) {
// ignore: avoid_print
//print(s);
if (FlutterBluePlus.logLevel.index >= LogLevel.verbose.index) {
//ignore: avoid_print
print(s);
}
}

enum BmAdapterStateEnum {
Expand Down Expand Up @@ -36,15 +38,15 @@ class BmBluetoothAdapterState {
}

class BmAdvertisementData {
String? localName;
int? txPowerLevel;
bool connectable;
Map<int, List<int>> manufacturerData;
Map<String, List<int>> serviceData;
final String? localName;
final int? txPowerLevel;
final bool connectable;
final Map<int, List<int>> manufacturerData;
final Map<String, List<int>> serviceData;

// We use strings and not Guid because advertisement UUIDs can
// We use strings and not Guid because advertisement UUIDs can
// be 32-bit UUIDs, 64-bit, etc i.e. "FE56"
List<String> serviceUuids;
List<String> serviceUuids;

BmAdvertisementData({
required this.localName,
Expand Down Expand Up @@ -75,7 +77,7 @@ class BmAdvertisementData {
}

// Cast the data to the right type
// Note: we use strings and not Guid because advertisement UUIDs can
// Note: we use strings and not Guid because advertisement UUIDs can
// be 32-bit UUIDs, 64-bit, etc i.e. "FE56"
List<String> serviceUuids = [];
for (var val in rawServiceUuids) {
Expand All @@ -95,11 +97,11 @@ class BmAdvertisementData {
}

class BmScanSettings {
List<Guid> serviceUuids;
List<String> macAddresses;
bool allowDuplicates;
int androidScanMode;
bool androidUsesFineLocation;
final List<Guid> serviceUuids;
final List<String> macAddresses;
final bool allowDuplicates;
final int androidScanMode;
final bool androidUsesFineLocation;

BmScanSettings({
required this.serviceUuids,
Expand Down Expand Up @@ -127,10 +129,31 @@ class BmScanSettings {
}
}

class BmScanFailed {
final bool success;
final int? errorCode;
final String? errorString;

BmScanFailed({
required this.success,
required this.errorCode,
required this.errorString,
});

factory BmScanFailed.fromMap(Map<dynamic, dynamic> json) {
_printDbg("\BmScanFailed $json");
return BmScanFailed(
success: json['success'] != 0,
errorCode: json['error_code'],
errorString: json['error_string'],
);
}
}

class BmScanResult {
BmBluetoothDevice device;
BmAdvertisementData advertisementData;
int rssi;
final BmBluetoothDevice device;
final BmAdvertisementData advertisementData;
final int rssi;

BmScanResult({
required this.device,
Expand All @@ -148,6 +171,24 @@ class BmScanResult {
}
}

class BmScanResponse {
final BmScanResult? result;
final BmScanFailed? failed;

BmScanResponse({
required this.result,
required this.failed,
});

factory BmScanResponse.fromMap(Map<dynamic, dynamic> json) {
_printDbg("\BmScanResponse $json");
return BmScanResponse(
result: json['result'] != null ? BmScanResult.fromMap(json['result']) : null,
failed: json['failed'] != null ? BmScanFailed.fromMap(json['failed']) : null,
);
}
}

class BmConnectRequest {
String remoteId;
bool autoConnect;
Expand Down
68 changes: 37 additions & 31 deletions lib/src/flutter_blue_plus.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ class FlutterBluePlus {
// stream used for the scanResults public api
static final _StreamController<List<ScanResult>> _scanResults = _StreamController(initialValue: []);

// ScanResults are received from the system one-by-one from the method broadcast stream.
// ScanResponses are received from the system one-by-one from the method broadcast stream.
// This variable buffers all the results into a single-subscription stream.
// We store it at the top level so it can be closed by stopScan
static _BufferStream<ScanResult>? _scanResultsBuffer;
static _BufferStream<BmScanResponse>? _scanResponseBuffer;

// timeout for scanning that can be cancelled by stopScan
static Timer? _scanTimeout;
Expand Down Expand Up @@ -159,22 +159,21 @@ class FlutterBluePlus {
// Clear scan results list
_scanResults.add(<ScanResult>[]);

Stream<ScanResult> responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "ScanResult")
Stream<BmScanResponse> responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "ScanResponse")
.map((m) => m.arguments)
.map((buffer) => BmScanResult.fromMap(buffer))
.map((p) => ScanResult.fromProto(p))
.map((buffer) => BmScanResponse.fromMap(buffer))
.takeWhile((element) => _isScanning.value)
.doOnDone(stopScan);

// Start listening now, before invokeMethod, to ensure we don't miss any results
_scanResultsBuffer = _BufferStream.listen(responseStream);
_scanResponseBuffer = _BufferStream.listen(responseStream);

// Start timer *after* stream is being listened to, to make sure the
// timeout does not fire before _scanResultsBuffer is set
// timeout does not fire before _scanResponseBuffer is set
if (timeout != null) {
_scanTimeout = Timer(timeout, () {
_scanResultsBuffer?.close();
_scanResponseBuffer?.close();
_isScanning.add(false);
_invokeMethod('stopScan');
});
Expand All @@ -185,22 +184,31 @@ class FlutterBluePlus {
// push to isScanning stream after invokeMethod('startScan') is called
_isScanning.add(true);

await for (ScanResult item in _scanResultsBuffer!.stream) {
// update list of devices
List<ScanResult> list = List<ScanResult>.from(_scanResults.value);
if (list.contains(item)) {
// the list will have duplicates if allowDuplicates is set.
// However, we only care to about the most recent advertisment
// so here we replace old advertisements. 1 per device.
int index = list.indexOf(item);
list[index] = item;
} else {
list.add(item);
await for (BmScanResponse response in _scanResponseBuffer!.stream) {
// check for failure
if (response.failed != null) {
throw FlutterBluePlusException("scan", response.failed!.errorCode, response.failed!.errorString);
}

_scanResults.add(list);
if (response.result != null) {
ScanResult item = ScanResult.fromProto(response.result!);

yield item;
// update list of devices
List<ScanResult> list = List<ScanResult>.from(_scanResults.value);
if (list.contains(item)) {
// the list will have duplicates if allowDuplicates is set.
// However, we only care to about the most recent advertisment
// so here we replace old advertisements. 1 per device.
int index = list.indexOf(item);
list[index] = item;
} else {
list.add(item);
}

_scanResults.add(list);

yield item;
}
}
}

Expand Down Expand Up @@ -233,7 +241,7 @@ class FlutterBluePlus {
/// Stops a scan for Bluetooth Low Energy devices
static Future stopScan() async {
await _invokeMethod('stopScan');
_scanResultsBuffer?.close();
_scanResponseBuffer?.close();
_scanTimeout?.cancel();
_isScanning.add(false);
}
Expand Down Expand Up @@ -281,14 +289,12 @@ class FlutterBluePlus {

/// Log levels for FlutterBlue
enum LogLevel {
emergency, // 0
alert, // 1
critical, // 2
error, // 3
warning, // 4
notice, // 5
info, // 6
debug, // 7
none, //0
error, // 1
warning, // 2
info, // 3
debug, // 4
verbose, //5
}

/// State of the bluetooth adapter.
Expand Down

0 comments on commit d14bfa4

Please sign in to comment.