Giter VIP home page Giter VIP logo

flutter_reactive_ble's Introduction

Flutter reactive BLE library

flutter_reactive_ble version

Flutter library that handles BLE operations for multiple devices.

Contributing

Feel free to open an new issue or a pull request to make this project better

Setup

This project uses melos to manage all the packages inside this repo.

Install melos: dart pub global activate melos Setup melos to point to the dependencies in your local folder: melos bootstrap

Android

Library requires kotlin version 1.8.21.

Update kotlin version

To update the kotlin version open Android studio and go to Tools > Kotlin > Configure Kotlin plugin updates and update Update channel to 1.8.x.

Features

The reactive BLE lib supports the following:

  • BLE device discovery
  • Observe host device BLE status
  • Establishing a BLE connection
  • Maintaining connection status of multiple BLE devices
  • Discover services(will be implicit)
  • Read / write a characteristic
  • Subscribe to a characteristic
  • Clear GATT cache
  • Negotiate MTU size

Getting Started

Android

You need to add the following permissions to your AndroidManifest.xml file:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />

If you use BLUETOOTH_SCAN to determine location, modify your AndroidManfiest.xml file to include the following entry:

 <uses-permission android:name="android.permission.BLUETOOTH_SCAN" 
                     tools:remove="android:usesPermissionFlags"
                     tools:targetApi="s" />

If you use location services in your app, remove android:maxSdkVersion="30" from the location permission tags

Android ProGuard rules

In case you are using ProGuard add the following snippet to your proguard-rules.pro file:

-keep class com.signify.hue.** { *; }

This will prevent issues like #131.

iOS

For iOS it is required you add the following entries to the Info.plist file of your app. It is not allowed to access Core BLuetooth without this. See our example app on how to implement this. For more indepth details: Blog post on iOS bluetooth permissions

iOS13 and higher

  • NSBluetoothAlwaysUsageDescription

iOS12 and lower

  • NSBluetoothPeripheralUsageDescription

Usage

Initialization

Initializing the library should be done the following:

final flutterReactiveBle = FlutterReactiveBle();

Device discovery

Discovering BLE devices should be done like this:

flutterReactiveBle.scanForDevices(withServices: [serviceId], scanMode: ScanMode.lowLatency).listen((device) {
      //code for handling results
    }, onError: () {
      //code for handling error
    });
  

The withServices parameter specifies the advertised service IDs to look for. If an empty list is passed, all the advertising devices will be reported. The parameter scanMode is only used on Android and follows the conventions described on ScanSettings Android reference page. If scanMode is omitted the balanced scan mode will be used.

Observe host device BLE status

Use statusStream to retrieve updates about the BLE status of the host device (the device running the app) . This stream can be used in order to determine if the BLE is turned on, on the device or if the required permissions are granted. Example usage:

_ble.statusStream.listen((status) {
  //code for handling status update
});

Use _ble.status to get the current status of the host device.

See BleStatus for more info about the meaning of the different statuses.

Establishing connection

To interact with a device you first need to establish a connection:

flutterReactiveBle.connectToDevice(
      id: foundDeviceId,
      servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
      connectionTimeout: const Duration(seconds: 2),
    ).listen((connectionState) {
      // Handle connection state updates
    }, onError: (Object error) {
      // Handle a possible error
    });

For the required id parameter use a device ID retrieved through device discovery. On iOS the device ID is a UUID and on Android it is a MAC address (which may also be randomized, depending on the Android version). Supplying a map with service and characteristic IDs you want to discover may speed up the connection on iOS (otherwise all services and characteristics will be discovered). You can specify a connectionTimeout when the client will provide an error in case the connection cannot be established within the specified time.

There are numerous issues on the Android BLE stack that leave it hanging when you try to connect to a device that is not in range. To work around this issue use the method connectToAdvertisingDevice to first scan for the device and only if it is found connect to it.

flutterReactiveBle.connectToAdvertisingDevice(
    id: foundDeviceId,
    withServices: [serviceUuid],
    prescanDuration: const Duration(seconds: 5),
    servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
    connectionTimeout: const Duration(seconds:  2),
  ).listen((connectionState) {
    // Handle connection state updates
  }, onError: (dynamic error) {
    // Handle a possible error
  });

Besides the normal connection parameters that are described above this function also has 2 additional required parameters: withServices and prescanDuration. PreScanDuration is the amount of time the ble stack will scan for the device before it attempts to connect (if the device is found)

Read / write characteristics

Read characteristic

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
final response = await flutterReactiveBle.readCharacteristic(characteristic);

Write with response

Write a value to characteristic and await the response. The "response" in "write characteristic with response" means "an acknowledgement of reception". The write can either be acknowledged (success) or failed (an exception is thrown), thus the return type is void and there is nothing to print (though you can print("Write successful") and in a catch-clause print("Write failed: $e")).

BLE does not provide a request-response mechanism like you may know from HTTP out of the box. If you need to perform request-response calls, you will need to implement a custom mechanism on top of the basic BLE functionality. A typical approach is to implement a "control point": a characteristic that is writable and delivers notifications or indications, so that a request is written to it and a response is delivered back as a notification or an indication.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId); 
await flutterReactiveBle.writeCharacteristicWithResponse(characteristic, value: [0x00]);

Write without response

Use this operation if you want to execute multiple consecutive write operations in a small timeframe (e.g uploading firmware to device) or if the device does not provide a response. This is performance wise the fastest way of writing a value but there's a chance that the BLE device cannot handle that many consecutive writes in a row, so do a writeWithResponse once in a while.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
flutterReactiveBle.writeCharacteristicWithoutResponse(characteristic, value: [0x00]);

Subscribe to characteristic

Instead of periodically reading the characteristic you can also listen to the notifications (in case the specific service supports it) in case the value changes.

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
   flutterReactiveBle.subscribeToCharacteristic(characteristic).listen((data) {
      // code to handle incoming data
    }, onError: (dynamic error) {
      // code to handle errors
    });

Negotiate MTU size

You can increase or decrease the MTU size to reach a higher throughput. This operation will return the actual negotiated MTU size, but it is no guarantee that the requested size will be successfully negotiated. iOS has a default MTU size which cannot be negotiated, however you can still use this operation to get the current MTU.

final mtu = await flutterReactiveBle.requestMtu(deviceId: foundDeviceId, mtu: 250);

Android specific operations

The following operations will only have effect for Android and are not supported by iOS. When using these operations on iOS the library will throw an UnSupportedOperationException.

Request connection priority

On Android you can send a connection priority update to the BLE device. The parameter priority is an enum that uses the same spec as the BluetoothGatt Android spec. Using highPerformance will increase battery usage but will speed up GATT operations. Be cautious when setting the priority when communicating with multiple devices because if you set highperformance for all devices the effect of increasing the priority will be lower.

await flutterReactiveBle.requestConnectionPriority(deviceId: foundDeviceId, priority:  ConnectionPriority.highPerformance);

Clear GATT cache

The Android OS maintains a table per device of the discovered service in cache. Sometimes it happens that after a firmware update a new service is introduced but the cache is not updated. To invalidate the cache you can use the cleargattCache operation.

This is a hidden BLE operation and should be used with extreme caution since this operation is on the greylist.

await flutterReactiveBle.clearGattCache(foundDeviceId);

FAQ

How to handle the BLE undeliverable exception

On Android side we use the RxAndroidBle library of Polidea. After migration towards RxJava 2 some of the errors are not routed properly to their listeners and thus this will result in a BLE Undeliverable Exception. The root cause lies in the threading of the Android OS. As workaround RxJava has a hook where you can set the global errorhandler. For more info see RxJava docs .

A default workaround implementation in the Flutter app (needs to be in the Java / Kotlin part e.g. mainactivity) is shown below. For an example (in Java) see Polidea RxAndroidBle sample.

BleException is coming from Polidea RxAndroidBle, so make sure your application declares the following depedency: implementation "com.polidea.rxandroidble2:rxandroidble:1.11.1"

RxJavaPlugins.setErrorHandler { throwable ->
  if (throwable is UndeliverableException && throwable.cause is BleException) {
    return@setErrorHandler // ignore BleExceptions since we do not have subscriber
  }
  else {
    throw throwable
  }
}

Why doesn't the BLE stack directly connect to my peripheral

Before you are able to execute BLE operations the BLE-stack of the device makes sure everything is setup correctly and then reports ready for operation. For some devices this takes a bit longer than for others. When starting the app make sure that the BLE-stack is properly initialized before you execute BLE operations. The safest way to do this is by listening to the statusStream and wait for BleStatus.ready.

This will prevent issues like #147.

Unofficial example apps

  • Example implementation UART over BLE:link
  • Example implementation subscription to characteristic using StreamProvider: link

flutter_reactive_ble's People

Contributors

danielroek avatar danielstuart14 avatar dnfield avatar ened avatar epietrowicz avatar gabrielgarciagava avatar jalpedersen avatar jamesblasco avatar jason-simmons avatar jobfeikens avatar jonahwilliams avatar keenranger avatar ky1vstar avatar miguelcmedeiros avatar pieteraelse avatar remonh87 avatar rexios80 avatar robert-ancell avatar rtgrv avatar safield avatar spkersten avatar srawlins avatar swift-kim avatar taym95 avatar vbuberen avatar vsxed avatar wcoder avatar werediver avatar xvrh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

flutter_reactive_ble's Issues

Long Device Name Getting Cutoff

Describe the bug
I'm running into an issue with a long device name getting characters cut-off and I'm wondering if there's any debug steps I can take to work isolate where this is occurring. For example, I have a device who's name is: 11111111-2222-3333-4444-123456789012, it looks like the last couple of characters are getting cut off when the scanning result info returns. This is what I will get back: 11111111-2222-3333-4444-12345

To Reproduce
Steps to reproduce the behavior:

  1. Start scan using FlutterReactiveBle.scanForDevices(withServices: [])
  2. Listen for discovered devices and print to console device name:
listen((device) {
  developer.log('Name=${device.name}');
});
  1. Observe a long name has the last couple characters are cut off.

Expected behavior
Full name of device is returned

  • I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above

Smartphone / tablet

  • Device: Samsung Galaxy Note8

  • OS: Android 8

  • Package version: v2.2.0

  • Device: Google Pixel 2

  • OS: Android 8

  • Package version: v2.2.0

Peripheral device

  • Vendor, model: Raspberry Pi, Raspbian 2020-05-27
  • Does it run a custom firmware / software: no

Additional context
This issue does not affect iOS

Connecting with connectionTimeout parameter fails immediately with GATT ERROR 133

Describe the bug
Connecting to a device with a connectionTimeout value given, results most of the times in a GATT ERROR 133 (see the logcat log attached below). Not having a connectionTimeout parameter set results in a successful (and pretty fast) connection.

Seems like an android-only issue, the same code works with iOS just fine.

To Reproduce
Steps to reproduce the behavior:

  1. Connect to one device using .connectToDevice(id: ..., connectionTimeout: Duration(seconds: 10))
  2. See the failed attempt to connect in you log

Expected behavior
The expected behavior would be an established connection or a timeout after the set duration

  • I tried doing the same with a general BLE scanner application

Smartphone / tablet

  • Device: OnePlus 5t
  • OS: Android 9
  • Package version: 2.1.0

Peripheral device

  • Vendor, model: Custom
  • Does it run a custom firmware / software: no

Additional context

E/AccessibilityBridge( 7512): VirtualView node must not be the root node.
D/PluginController( 7512): Start connecting for device 78:DB:2F:D0:BC:D2
D/PluginController( 7512): Start connecting for device 78:DB:2F:D0:BC:D2
D/DeviceConnectionHandler( 7512): Try to connect to device 78:DB:2F:D0:BC:D2
D/DeviceConnectionHandler( 7512): Try to connect to device 78:DB:2F:D0:BC:D2
D/DeviceConnector( 7512): Connecting device 78:DB:2F:D0:BC:D2
D/DeviceConnector( 7512): Connecting device 78:DB:2F:D0:BC:D2
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=0
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=0
D/BluetoothGatt( 7512): connect() - device: 78:DB:2F:D0:BC:D2, auto: false
D/BluetoothGatt( 7512): registerApp()
D/BluetoothGatt( 7512): registerApp() - UUID=d1024742-f90f-4f43-ab6d-1f9fbfe29569
D/BluetoothGatt( 7512): onClientRegistered() - status=0 clientIf=9
I/flutter ( 7512): ### CONNECTING ###
E/AccessibilityBridge( 7512): VirtualView node must not be the root node.
E/AccessibilityBridge( 7512): VirtualView node must not be the root node.
D/BluetoothGatt( 7512): onClientConnectionState() - status=133 clientIf=9 device=78:DB:2F:D0:BC:D2
D/DeviceConnector$establishConnection( 7512): Connection established for device 78:DB:2F:D0:BC:D2
D/DeviceConnector$establishConnection( 7512): Connection established for device 78:DB:2F:D0:BC:D2
D/ReactiveBleClient$connectToDevice( 7512): Connect 78:DB:2F:D0:BC:D2 fails: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 133 (GATT_ERROR)
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=0
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=0
D/ReactiveBleClient$connectToDevice( 7512): Connect 78:DB:2F:D0:BC:D2 fails: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 133 (GATT_ERROR)
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Error conn device:  78:DB:2F:D0:BC:D2 error=Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 133 (GATT_ERROR)
I/chatty  ( 7512): uid=10560(com.camparound.truma.cooler.truma_cooler) identical 2 lines
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Error conn device:  78:DB:2F:D0:BC:D2 error=Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 133 (GATT_ERROR)
D/BluetoothManager( 7512): getConnectionState()
D/BluetoothManager( 7512): getConnectedDevices
I/flutter ( 7512): ### CONNECTING ###
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=2
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=2
D/BluetoothGatt( 7512): close()
D/BluetoothGatt( 7512): unregisterApp() - mClientIf=9
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=3
D/DeviceConnectionHandler$listenToConnectionChanges( 7512): Update device: 78:DB:2F:D0:BC:D2 status=3
D/PluginController( 7512): stop notifications
I/chatty  ( 7512): uid=10560(com.camparound.truma.cooler.truma_cooler) identical 12 lines
D/PluginController( 7512): stop notifications
I/flutter ( 7512): ### CONNECTION FAILED ###: GenericFailure<ConnectionError>(code: ConnectionError.failedToConnect, message: "Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 133 (GATT_ERROR)") ~~ with DeviceConnectionState.disconnected
D/PluginController( 7512): Disconnect device: 78:DB:2F:D0:BC:D2
D/PluginController( 7512): Disconnect device: 78:DB:2F:D0:BC:D2
E/AccessibilityBridge( 7512): VirtualView node must not be the root node.

Crash when disconect

Describe the bug
The lib crashed when i shutdown the connected peripheral.

Expected behavior
Crashed Stack

  Process: com.wellpay.deoxys, PID: 29020
    io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | com.polidea.rxandroidble2.exceptions.BleDisconnectedException: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status 8 (GATT_INSUF_AUTHORIZATION or GATT_CONN_TIMEOUT)
        at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.dispose(ObservableFlatMap.java:313)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.observable.ObservableReplay$ReplayObserver.dispose(ObservableReplay.java:271)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.observable.ObservableRefCount.timeout(ObservableRefCount.java:137)
        at io.reactivex.internal.operators.observable.ObservableRefCount.cancel(ObservableRefCount.java:104)
        at io.reactivex.internal.operators.observable.ObservableRefCount$RefCountObserver.dispose(ObservableRefCount.java:233)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.mixed.CompletableAndThenObservable$AndThenObservableObserver.dispose(CompletableAndThenObservable.java:86)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.mixed.SingleFlatMapObservable$FlatMapObserver.dispose(SingleFlatMapObservable.java:84)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.mixed.SingleFlatMapObservable$FlatMapObserver.dispose(SingleFlatMapObservable.java:84)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$InnerObserver.dispose(ObservableFlatMap.java:588)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.disposeAll(ObservableFlatMap.java:510)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.dispose(ObservableFlatMap.java:310)
        at io.reactivex.internal.operators.observable.ObservableDoOnEach$DoOnEachObserver.dispose(ObservableDoOnEach.java:79)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.disposeAll(ObservableFlatMap.java:504)
        at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.dispose(ObservableFlatMap.java:310)
        at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.dispose(ObservableObserveOn.java:146)
        at io.reactivex.internal.disposables.DisposableHelper.dispose(DisposableHelper.java:124)
        at io.reactivex.internal.observers.LambdaObserver.dispose(LambdaObserver.java:102)
        at com.signify.hue.flutterreactiveble.channelhandlers.CharNotificationHandler.unsubscribeFromNotifications(CharNotificationHandler.kt:46)
        at com.signify.hue.flutterreactiveble.PluginController.stopNotifications(PluginController.kt:231)
        at com.signify.hue.flutterreactiveble.PluginController.access$stopNotifications(PluginController.kt:25)
        at com.signify.hue.flutterreactiveble.PluginController$pluginMethods$11.invoke(PluginController.kt:37)
        at com.signify.hue.flutterreactiveble.PluginController$pluginMethods$11.invoke(PluginController.kt:25)
        at com.signify.hue.flutterreactiveble.PluginController.execute$flutter_reactive_ble_debug(PluginController.kt:75)
        at com.signify.hue.flutterreactiveble.ReactiveBlePlugin.onMethodCall(ReactiveBlePlugin.kt:43)
        at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:226)

Smartphone / tablet

  • Device: Pixel4
  • OS: Android 11
  • Package version: 2.2

Throw exception on subscription to non-readable characteristic

We spent a long time troubleshooting why we stopped receiving data from the BLE device. Turned out to be that a firmware update erroneously changed the characteristic to "write" only. Perhaps trying to subscribe to a characteristic that didn't allow reading should throw an exception because it cannot possibly ever actually receive any data.

Using device.name as a discriminant to select (or ignore) discoverable devices

It would be useful to have the ability to use device.name (or a portion, perhaps the first couple of characters) as a discriminant to select (or ignore) discoverable devices.

I explored your use of _isValidUuidInput in device_list.dart but I could not quite come up with an optimum alternative .

Could please help with an example along the lines of the following, which is obviously incorrect in Dart?

device.name.startsWith('xyz')

Thanks.

Alfredo

Improve our public API

Is your feature request related to a problem? (please describe)
When we developed the library we decided to have a facade FlutterReactiveBle where a user can interact with. Intuitively it feels more natural for operations like write, read, subscribe etc to be operated on a device. This is also closer to the approach of iOS (CBPeripheral) and Android (BluetoothGatt).

Describe the solution you'd like
When we scan for devices we will get a BlePeripheral or BluetoothDevice. On this bluetooth device we are able to execute the following operations:

  • connect and disconnect
  • observeConnectionState
  • read, write and subscribe to a characteristic
  • request MTU size
  • request connection priority

Example pseudo code

final device =  await _ble.scanForDevices(withServices: serviceIds).first;

await device.readCharacteristic(serviceUuid: service, characteristicUuid: charUuid);

await device.requestMtu(mtuSize:250);

Describe alternatives you've considered
Let's use this ticket as a discussion how we can improve our API and to reflect on our current API.

Peripheral mode support

Is your feature request related to a problem? (please describe)
It would be great if peripheral mode is supported

minSdk API lvl 24 as a lib requirement

Describe the bug
The libs minSdk requirement for Android should be lowered, since BLE has been introduced in API level 18/19, but the library is requiring API level 24

To Reproduce
Try to build the example app with minSdk set to API lvl 19

Expected behavior
Should be building while i drink my coffee

  • I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above

Smartphone / tablet

  • Device: Nexus 4, 4.4 Kitkat

Peripheral device

Additional context

Does it work in a separate dart isolate?

I have a use case where I need to do a scan periodically in a separate isolate and was wondering if flutter_reactive_ble is capable of doing that. Recently flutter_isolate stopped working with all the plugins I'm using so I guess something changed in flutter itself and am looking for something that is capable of running in a different isolate.
Thanks

Read characteristics on iOS is not working

Describe the bug
When a device connection is established, characteristics cannot be read on iOS, whereas on Android it works without any problem. Following error is printed:

To Reproduce
Steps to reproduce the behavior:

  1. Connect to one device using connectTo()
  2. Subscribe to a non-protected characteristic
  3. Observe a failure with following exception

Error unsubscribing from notifications: PlatformException(flutter_reactive_ble.Central.(unknown context at $103218a20).Failure:1, The operation couldn’t be completed. (flutter_reactive_ble.Central.(unknown context at $103218a20).Failure error 1.), {})

    _connectedDeviceStream.where((device) => device.connectionState == DeviceConnectionState.connected).listen((onData) async {
      var chars = _ble.subscribeToCharacteristic(
        QualifiedCharacteristic(characteristicId: _charUuid, serviceId: _serviceUuid, deviceId: deviceId),
      );
      var listOfChars = await chars.toList();
      print('chars ${listOfChars.runtimeType} $listOfChars');
      _values.add(listOfChars);
    });

Tablet

  • Device: iPad (7th generation)
  • OS: iOS 13.3.1

iOS always sends the same command, Android works OK

Following my problems reported in #95 my code works for Android, but in iOS it sends the first command correctly, then every following command is just the first command repeated. At the end of my connect routine, I need to get different state information from the bluetooth device so I send a set of commands:

void sendInitialCommands() async {
    print("====sendInitialCommands");
    sendString('y');
    sendString('x');
    sendString('h');
    print("=======================");
  }

  sendString(String dataStr) async {
    List<int> dataOut = asciiCodec.encode(dataStr);
    print("Sending " + asciiCodec.decode(dataOut));
    await _ble.writeCharacteristicWithResponse(txCharacteristic, value: dataOut);
  }

on Android I correctly get answers to those 3 commands:

D/BluetoothGatt(15802): connect() - device: A4:CF:12:78:10:3E, auto: true
D/BluetoothGatt(15802): registerApp()
D/BluetoothGatt(15802): registerApp() - UUID=18f9a898-54d2-4fcf-9960-29562c60ef2e
D/BluetoothGatt(15802): onClientRegistered() - status=0 clientIf=8
I/flutter (15802): ====SendInitialCommands
I/flutter (15802): Sending y
I/flutter (15802): Sending x
I/flutter (15802): Sending h
/flutter (15802): =======================
D/BluetoothGatt(15802): onClientConnectionState() - status=0 clientIf=8 device=A4:CF:12:78:10:3E
D/BluetoothGatt(15802): discoverServices() - device: A4:CF:12:78:10:3E
I/flutter (15802): connectToAdvertisingDevice error: TimeoutException after 0:00:06.000000: Future not completed
D/BluetoothGatt(15802): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt(15802): onSearchComplete() = Device=A4:CF:12:78:10:3E Status=0
D/BluetoothGatt(15802): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=30 latency=0 timeout=500 status=0
D/BluetoothGatt(15802): setCharacteristicNotification() - uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e enable: true
I/flutter (15802): Received: [y 10]
I/flutter (15802): Received: [x 8]
I/flutter (15802): Received: [h 160]

However, on iOS it reports the first command being correctly sent but subsequent commands only re-trigger the first command. I can see on the bluetooth device that the same command is transmitted repeatedly. Also, the Gatt reporting shown above for Android is missing on iOS.

flutter: Status update at: 2020-08-11T17:34:04.876624 to: ConnectionStateUpdate(deviceId: 0E76782B-8B93-89F0-B597-8F2963FAE171, connectionState: DeviceConnectionState.connected, error: null)
flutter: ====SendInitialCommands
flutter: Sending y
flutter: Sending x
flutter: Sending h
flutter: =======================
flutter: Received: [y 10]
flutter: Received: [y 10]
flutter: Received: [y 10]

How can I debug what is happening within
await _ble.writeCharacteristicWithResponse(txCharacteristic, value: dataOut);
or what am I doing wrong?

Scanning parameters are not set

Describe the bug
When I try to scan for devices it always throws an Exception. The demo application works fine but when I try to use it on my app it doesn't work.

To Reproduce
Initialize and then scan with scanForDevices

Expected behavior
Print Log a list of MAC addresses of the devices.

  • I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above

Smartphone / tablet

  • Device: MiPAD4Plus
  • OS: Android 10
  • Package version: 2.3.0 | git master

Prefer CBAdvertisementDataLocalName over peripheral.name

Describe the bug
We found a bug in an application using flutter_reactive_ble, this bug resulted in a device showing a different device's name. We believe the cause of the problem is described in the StackOverflow link below.

To Reproduce
Steps to reproduce the behavior:
When using peripheral.name to define a devices name, the iOS CoreBluetooth API is not able to change the name of the device. Therefore the CBAdvertisementDataLocalName should be used.
https://stackoverflow.com/questions/25938274/incorrect-ble-peripheral-name-with-ios

Expected behavior

  • I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above
    Yes, previously we used our own flutter_blues fork, this flutter package uses the CBAdvertisementDataLocalName and the bug occured after releasing our application after implementing flutter_reactive_ble

Smartphone / tablet

  • OS: iOS
  • Package version: [e.g. 2.3.0]

Peripheral device

  • Vendor, model: CUSTOM
  • Does it run a custom firmware / software: yes / no

Additional context
Add any other context about the problem here.

No catchable exception when BT is disabled in scanRepeater

Describe the bug
When BT is disabled in Android and the scan is started there is an exception internally but the exception is not handled to the calling program and therefore cannot be handled by the app.

To Reproduce
Steps to reproduce the behavior:

  1. In Android switch off BT
  2. start _scanner.startScan(null); from the example program
  3. Check the console for failures

Expected behavior
We need a stream event for exceptions and some additional information about the type of exception

Additional context

I/flutter (21735): ErrorValue: Exception: GenericFailure<ScanFailure>(code: ScanFailure.unknown, message: "Bluetooth disabled (code 1)")
I/flutter (21735): #0      Result.dematerialize.<anonymous closure> (package:flutter_reactive_ble/src/model/result.dart:22:13)
I/flutter (21735): #1      Result.iif (package:flutter_reactive_ble/src/model/result.dart:34:21)
I/flutter (21735): #2      Result.dematerialize (package:flutter_reactive_ble/src/model/result.dart:15:28)
I/flutter (21735): #3      FlutterReactiveBle.scanForDevices.<anonymous closure>.<anonymous closure> (package:flutter_reactive_ble/src/reactive_ble.dart:274:49)
I/flutter (21735): #4      _MapStream._handleData (dart:async/stream_pipe.dart:229:31)
I/flutter (21735): #5      _ForwardingStreamSubscription._handleData (dart:async/stream_pipe.dart:166:13)
I/flutter (21735): #6      _rootRunUnary (dart:async/zone.dart:1192:38)
I/flutter (21735): #7      _CustomZone.runUnary (dart:async/zone.dart:1085:19)
I/flutter (21735): #8      _CustomZone.runUnaryGuarded (dart:async/zone.dart:987:7)
I/flutter (21735): #9      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:339:11)
I/flutter (21735): #10     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:266:7)
I/flutter (21735): #11     _ForwardingStreamSubscription._add (dart:async/stream_pi

Ble Undeliverable Exception Documentation

Is your feature request related to a problem? (please describe)
The ReadMe section for Ble Undeliverable Exception should contain a complete working code sample and some more detailed instructions.

Describe the solution you'd like
To catch the Ble Undeliverable exception in Java you can do the following . . .

  1. Add implementation "com.polidea.rxandroidble2:rxandroidble:1.11.1" to the dependencies section of your build.gradle file (source).

  2. Then add the following to MainActivity.java

import io.flutter.embedding.android.FlutterActivity;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import com.polidea.rxandroidble2.exceptions.BleException;
import io.reactivex.exceptions.UndeliverableException;
import io.reactivex.plugins.RxJavaPlugins;

public class MainActivity extends FlutterActivity {
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        RxJavaPlugins.setErrorHandler(throwable -> {
            if (throwable instanceof UndeliverableException && throwable.getCause() instanceof BleException) {
                System.out.println('!' * 80);
                System.out.println("Caught UndeliverableException from flutter_reactive_ble.");
                System.out.println('!' * 80);
                return; // ignore BleExceptions as they were surely delivered at least once
            }
            throw new RuntimeException("Unexpected Throwable in RxJavaPlugins error handler", throwable);
        });
    }
}

Additional context
As for me, my experience with android is limited and so it took me a long time to build a complete working version from the code you had in your README. Also, you can find some helpful examples here in the RxAndroidBle library examples folder.

Subscribing to characteristics crashes on iOS, works fine on Android

First of all, thanks a lot for the library and the nice documentations :)
Unfortunately, I couldn't get it working as expected on iOS.

Describe the bug
After successfully discovering, then connecting to a Bluetooth device (Blood pressure monitoring device), I could not subscribe and read the characteristics of the device.

To Reproduce
Steps to reproduce the behavior:

  1. Connect to one device using connectToDevice()
  2. subscribe to a protected characteristic subscribeToCharacteristic()
  3. Observed a failure with the exception below
flutter: [BLE] subscribeToCharacteristic error is : PlatformException(flutter_reactive_ble.Central.(unknown context at $1071c0d30).Failure:2, The operation couldn’t be completed. (flutter_reactive_ble.Central.(unknown context at $1071c0d30).Failure error 2.), {})

Expected behavior
Reading blood pressure records similar to what happens on Android
(Receiving characteristics from the subscribeToCharacteristic function)

BlueTooth Service

class BluetoothService implements DisposableService {
  static FlutterReactiveBle _ble = FlutterReactiveBle();

  MedicalDataService _medicalDataService = locator<MedicalDataService>();

  BehaviorSubject<ConnectionStateUpdate> connectionStateUpdate =
      BehaviorSubject();
  BehaviorSubject<List<DiscoveredDevice>> discoveredDeviceList =
      BehaviorSubject();
  BehaviorSubject<int> syncedDataCount = BehaviorSubject();

  StreamSubscription<ConnectionStateUpdate> _connectToDeviceSub;

  StreamSubscription<DiscoveredDevice> _scanForDevicesSub;

  StreamSubscription<List<int>> _subscribeToCharacteristicSub;

  Logger logger = Logger('BLE');

  BluetoothService() {
    print('bluetooth service init');
    _ble.statusStream.listen((event) {
      logger.debug('event = ${event}');
    });
  }

  @override
  void dispose() {
    _connectToDeviceSub?.cancel();
    _scanForDevicesSub?.cancel();
    _subscribeToCharacteristicSub?.cancel();
  }

  discoverDevices(BluetoothDevice device) {
    discoveredDeviceList.add([]);
    _scanForDevicesSub = _ble.scanForDevices(
        withServices: [Uuid.parse(device.uuid)]).listen((discoveredDevice) {
      if (discoveredDeviceList.hasValue &&
          !discoveredDeviceList.value.contains(discoveredDevice)) {
        discoveredDeviceList
            .add(discoveredDeviceList.value..add(discoveredDevice));
      }
    }, onError: (e) {
      logger.debug('discoverDevices error is : ${e}');
    });
  }

  connectToDevice(String deviceId, BluetoothDevice device) {
    List<Uuid> characteristicsUUIDs = [Uuid.parse(device.charId)];
    _connectToDeviceSub = _ble
        .connectToDevice(
            id: deviceId,
            servicesWithCharacteristicsToDiscover: {
              Uuid.parse(device.uuid): characteristicsUUIDs
            },
            connectionTimeout: Duration(seconds: 30))
        .listen((connectionState) {
      connectionStateUpdate.add(connectionState);
      logger.debug(
          'Status update at: ${DateTime.now().toIso8601String()} - state is: ${connectionState}');
    }, onError: (e) {
      logger.debug('connectToDevice error is : ${e}');
    });
  }

  readCharacteristic(DiscoveredDevice discoveredDevice, BluetoothDevice device,
      String charId) async {
    Uuid characteristicsUUID = Uuid.parse(charId);
    QualifiedCharacteristic characteristic = QualifiedCharacteristic(
        characteristicId: characteristicsUUID,
        serviceId: Uuid.parse(device.uuid),
        deviceId: discoveredDevice.id);

    final List<int> response =
        await _ble.readCharacteristic(characteristic).catchError((e) {
      logger.debug('readCharacteristic error is : ${e}');
    });
    return response;
  }

  subscribeToCharacteristic(DiscoveredDevice discoveredDevice,
      BluetoothDevice device, String charId) async {
    syncedDataCount.add(0);
    Uuid characteristicsUUID = Uuid.parse(charId);
    QualifiedCharacteristic characteristic = QualifiedCharacteristic(
        characteristicId: characteristicsUUID,
        serviceId: Uuid.parse(device.uuid),
        deviceId: discoveredDevice.id);

    _subscribeToCharacteristicSub = await _ble
        .subscribeToCharacteristic(characteristic)
        .listen((data) async {
      List<MedDataRecord> records = BluetoothDevice.parse(data, device);
      await records.forEach((record) => _medicalDataService.post((record)));
      syncedDataCount.add(++syncedDataCount.value);
    }, onError: (e, s) {
      syncedDataCount.add(-1);
      logger.debug('subscribeToCharacteristic error is : ${e}');
      logger.debug('subscribeToCharacteristic stack is : ${s}');
    });
    _subscribeToCharacteristicSub.resume();
  }

  Future writeCharacteristic(DiscoveredDevice discoveredDevice,
      BluetoothDevice device, String charId, List<int> byteArray) async {
    Uuid characteristicsUUID = Uuid.parse(charId);
    QualifiedCharacteristic characteristic = QualifiedCharacteristic(
        characteristicId: characteristicsUUID,
        serviceId: Uuid.parse(device.uuid),
        deviceId: discoveredDevice.id);

    var response = await _ble.writeCharacteristicWithResponse(characteristic,
        value: byteArray); // Could be wrong
    return response;
  }
}

Smartphone / tablet

  • Device: Iphone 8 (Also tried with XR and iPad 11)
  • OS: iOS 13
  • Package version: [e.g. 1.0.0]

Peripheral device

  • Beurer, BM54, and other Bearer devices.
  • Does it run a custom firmware: no

Additional context

  • Android works fine with the same code
  • Made sure the pairing is successful using vendor's app (Beurer Health Manager)

Flutter Doctor
Running flutter doctor -v result:

[✓] Flutter (Channel stable, v1.17.1, on Mac OS X 10.14.6 18G103, locale en-DE)
    • Flutter version 1.17.1 at /Users/buelenthacioglu/sdk/flutter
    • Framework revision f7a6a7906b (3 weeks ago), 2020-05-12 18:39:00 -0700
    • Engine revision 6bc433c6b6
    • Dart version 2.8.2
 
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at /Users/buelenthacioglu/Library/Android/sdk
    • Platform android-29, build-tools 29.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 11.3.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.3.1, Build version 11C504
    • CocoaPods version 1.8.4
[!] Android Studio (version 3.5)
    • Android Studio at /Applications/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
[✓] IntelliJ IDEA Ultimate Edition (version 2019.3.2)
    • IntelliJ at /Applications/IntelliJ IDEA.app
    • Flutter plugin version 43.0.3
    • Dart plugin version 193.6015.53
[✓] Connected device (1 available)
    • Karim’s iPhone • 8418dbf9850aaef7c09c6c9760fbc1f02b5a74f8 • ios • iOS 13.3.1

Error 'Kotlin reflection is not available' when sending writeCharacteristicWithResponse

Describe the bug
Everytime a writeCharacteristicWithResponse is dispatched I see in the log:

D/PluginController$executeWriteAndPropagateResultToChannel( 8898): Value succesfully written, function writeCharacteristicWithResponse (Kotlin reflection is not available)

To Reproduce
Steps to reproduce the behavior:

  1. Dispatch writeCharacteristicWithResponse

Smartphone

  • Device: Samsung A107F
  • OS: Android 10
  • Package version: 2.3.0

Peripheral device

  • Vendor, model: raspberry pi 3 b+
  • Go code implementing go-ble

Additional context
It seems that everything works correctly even though the error float

Error while scanning for devices: Location Permission missing

Describe the bug
Get the following error when trying to scan:

E/flutter: [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Exception: GenericFailure(code: ScanFailure.unknown, message: "Location Permission missing (code 3)")

To Reproduce
Steps to reproduce the behavior:

  1. Installed flutter_reactive_ble: ^2.0.0
  2. Implemented bleScanner
  3. scannerObj.startScan();

Expected behaviour
Request for ACCESS_FINE_LOCATION permission

Smartphone / tablet

  • Device: Samsung A5 2017
  • OS: Android 8.0.0
  • Package version: 2.0.0

Peripheral device

  • Vendor, model: raspberry pi
  • Does it run a custom firmware / software: go-ble

Additional context
Setup is working with flutter_blue.

Example app scan doesn't work out of box - android

Describe the bug
Scanning returns no results when it should.

To Reproduce
Steps to reproduce the behavior:

  1. Build example app.
  2. Run on Android (Galaxy Note 8, Android 9).
  3. After starting running it, give location permission (which is a different issue).
  4. Attempt to scan for a common UUID (i.e. 1800, 1801)

Expected behavior
Scan returns devices.

  • [X ] I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above

Smartphone / tablet

  • Device: Galaxy Note 8
  • OS: Android 9
  • Package version: Current master, March 2

Peripheral device

  • Any! nRF Connect finds many devices around me.

Additional context
n/a

Restarting/Refreshing in dev mode makes it crash with alreadyInitialized error on iOS

I have tried this both with the example app and my own app with the same result. Both simulator (iOS) and a device (iPhone 8).

Using the restart command for flutter in dev mode makes this lib crash with the following output

[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: PlatformException(alreadyInitialized, already initialized, null)
#0      StandardMethodCodec.decodeEnvelope 
package:flutter/…/services/message_codecs.dart:569
#1      MethodChannel._invokeMethod 
package:flutter/…/services/platform_channel.dart:156
<asynchronous suspension>
#2      MethodChannel.invokeMethod 
package:flutter/…/services/platform_channel.dart:329
#3      FlutterReactiveBle.initialize 
package:flutter_reactive_ble/src/reactive_ble.dart:90
#4      FlutterReactiveBle._trackStatus 
package:flutter_reactive_ble/src/reactive_ble.dart:55
#5      new FlutterReactiveBle._ 
package:flutter_reactive_ble/src/reactive_ble.dart:28
#6      FlutterReactiveBle._sharedInstance 
package:flutter_reactive_ble/src/reactive_ble.dart:23
#7      FlutterReactiveBle._sharedInstance 
package:flutter_reactive_ble/src/reactive_ble.dart:23
#8      new FlutterReactiveBle (package:flutter_reactive_ble/src/reactive_ble.dart:25:3<…>

This is especially annoying during development as it hangs the whole simulator/device and the app needs to be recompiled and launched again. It does not happen when just using the hot reload functionality.

This shouldn't be a problem in production mode due to the fact that there is no way to "refresh" the app instance. Have not tried if it all applies on Android as well.

characteristic for Notification (subscribeToCharacteristic) not working on iOS

I have flutter code (laone) using flutter_reactive_ble v2.4.0 which works for Android 9 but raises an exception for iOS. The code allows the selection of a single device from a list of found devices, then when Connect is clicked for that device, it should connect and send some initial data requests, which are then received using a subscribeToCharacteristic method and processed.
On iOS, the exception seems to be due to the subscribeToCharacteristic failing because no trace of the incoming replies is found.
Since the flutter code works on Android, and I can communicate with the device using the iOS app Blue Chat, which also uses a characteristic subscription, the remote server responses seem valid. In the server log, I can see the initial data requests (10 of them) being received and replied to, so the iOS connection is made and the transmit aspect of flutter_reactive_ble is working on iOS.
I have tried with the rxUuid in both long form and 32 bit short form, as suggested in #76.

The connection code is:

static Uuid myServiceId = Uuid.parse('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
static List<Uuid> myServices = [myServiceId];
static Uuid txUuid = Uuid.parse('6e400002');
static Uuid rxUuid = Uuid.parse('6e400003');

connectDevice(DiscoveredDevice device) {
     _ble.connectToAdvertisingDevice(
          id: device.id,
          withServices: myServices,
          prescanDuration: const Duration(seconds: 1),
          servicesWithCharacteristicsToDiscover: {myServiceId: [txUuid, rxUuid]},
          connectionTimeout: const Duration(seconds:  2),
        ).listen((connectionState) {
          // Handle connection state updates
        }, onError: (dynamic error) {
          // Handle a possible error
        });
    rxCharacteristic = QualifiedCharacteristic(
        serviceId: myServiceId,
        characteristicId: rxUuid,
        deviceId: device.id);
    rxSubscription = _ble.subscribeToCharacteristic(rxCharacteristic).listen((data) {
        onDataReceived(data);
    };
    txCharacteristic = QualifiedCharacteristic(
        serviceId: myServiceId, characteristicId: txUuid, deviceId: device.id);
    sendInitialCommands();
    connectedDevice = device;
  }

For the working Android code, the console shows:

I/flutter (17626): connecting to 
I/flutter (17626): 0
I/flutter (17626): done
I/flutter (17626): disconnected device
I/flutter (17626): connecting to Zoom
I/flutter (17626): ====sendInitialCommands
I/flutter (17626): ====InitialCommands==Sent
D/BluetoothGatt(17626): connect() - device: A4:CF:12:78:10:3E, auto: true
D/BluetoothGatt(17626): registerApp()
D/BluetoothGatt(17626): registerApp() - UUID=1998bf31-c89d-4a37-b12e-f690c8d0334f
D/BluetoothGatt(17626): onClientRegistered() - status=0 clientIf=7
D/BluetoothGatt(17626): onClientConnectionState() - status=0 clientIf=7 device=A4:CF:12:78:10:3E
D/BluetoothGatt(17626): discoverServices() - device: A4:CF:12:78:10:3E
D/BluetoothGatt(17626): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=6 latency=0 timeout=500 status=0
D/BluetoothGatt(17626): onSearchComplete() = Device=A4:CF:12:78:10:3E Status=0
D/BluetoothGatt(17626): onConnectionUpdated() - Device=A4:CF:12:78:10:3E interval=30 latency=0 timeout=500 status=0
D/BluetoothGatt(17626): setCharacteristicNotification() - uuid: 6e400003-b5a3-f393-e0a9-e50e24dcca9e enable: true
I/flutter (17626): Received: [y 10]
I/flutter (17626): Received: [x 8]
I/flutter (17626): Received: [f 2.0.0-lc-e58]
I/flutter (17626): ** ZoomController.onDevicePropertyChange **
I/flutter (17626): Setter for firmwareVersion invoked
I/flutter (17626): ** ZoomController.onDevicePropertyChange **
I/flutter (17626): Received firmware version: 2.0.0-lc-e58
I/flutter (17626): Received: [h 160]

where the I/flutter lines are debug messages and the lower set show the responses being received from the first 3 of the 10 commands, and then processed, with the final line showing the 4th response being received.
The iOS log shows the same first 7 log lines and then the exception. I do not recognise the Uuid it claims is unknown in the final log line.

flutter: connecting to
flutter: 0
flutter: done
flutter: disconnected device
flutter: connecting to Zoom
flutter: ====sendInitialCommands
flutter: ====InitialCommands==Sent
flutter: RX error: PlatformException(flutter_reactive_ble.Central.(unknown context at $100d47ef0).Failure:1, The operation couldn’t be completed. (flutter_reactive_ble.Central.(unknown context at $100d47ef0).Failure error 1.), {})
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Exception: GenericFailure<WriteCharacteristicFailure>(code: WriteCharacteristicFailure.unknown, message: "A peripheral 0E76782B-8B93-89F0-B597-8F2963FAE171 is unknown (make sure it has been discovered)")
#0      Result.dematerialize.<anonymous closure> (package:flutter_reactive_ble/src/model/result.dart:22:13)
#1      Result.iif (package:flutter_reactive_ble/src/model/result.dart:34:21)
#2      Result.dematerialize (package:flutter_reactive_ble/src/model/result.dart:15:28)
#3      ConnectedDeviceOperation.writeCharacteristicWithResponse.<anonymous closure> (package:flutter_reactive_ble/src/connected_device_operation.dart:34:39)
#4      _rootRunUnary (dart:async/zone.dart:1192:38)
#5      _CustomZone.runUnary (dart:async/zone.dart:1085:19)
#6      _FutureListener.handleValue (dart:async/future_impl.dart:141:18)
#7      Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:682:45)
#8      Future._propagateToListeners (dart:async/future_impl.dart:711:32)
#9      Future._completeWithValue (dart:async/future_impl.dart:526:5)
#10     _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:36:15)
#11     _completeOnAsyncReturn (dart:async-patch/async_patch.dart:298:13)
#12     MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart)
<asynchronous suspension>
#13     MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
#14     PluginController.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/plugin_controller.dart:192:10)
#15     ConnectedDeviceOperation.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/connected_device_operation.dart:33:12)
#16     FlutterReactiveBle.writeCharacteristicWithResponse (package:flutter_reactive_ble/src/reactive_ble.dart:170:37)
<asynchronous suspension>
#17     ZoomController.sendString (package:laone/controllers/ZoomController.dart:520:12)
#18     ZoomController.sendCmdGetSwitchQty (package:laone/controllers/ZoomController.dart:343:11)
#19     ZoomController.sendInitialCommands (package:laone/controllers/ZoomController.dart:476:5)
#20     ZoomController.configureDeviceSettings (package:laone/controllers/ZoomController.dart:468:10)
#21     ZoomController.connectDevice.<anonymous closure> (package:laone/controllers/ZoomController.dart:437:9)
#22     new Future.delayed.<anonymous closure> (dart:async/future.dart:318:39)
#23     _rootRun (dart:async/zone.dart:1180:38)
#24     _CustomZone.run (dart:async/zone.dart:1077:19)
#25     _CustomZone.runGuarded (dart:async/zone.dart:979:7)
#26     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1019:23)
#27     _rootRun (dart:async/zone.dart:1184:13)
#28     _CustomZone.run (dart:async/zone.dart:1077:19)
#29     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1003:23)
#30     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: Exception: GenericFailure<WriteCharacteristicFailure>(code: WriteCharacteristicFailure.unknown, message: "A peripheral 0E76782B-8B93-89F0-B597-8F2963FAE171 is unknown (make sure it has been discovered)")

About value type for the write question

Is your feature request related to a problem? (please describe)

final characteristic = QualifiedCharacteristic(serviceId: serviceUuid, characteristicId: characteristicUuid, deviceId: foundDeviceId);
reactiveBleClient.writeCharacteristicWithoutResponse(characteristic, value: [0x00]);

The parameter must be type of int hex?

Describe the solution you'd like

The result obtained by bit operation

for example:

int result = ( 1 << 7 ) | 100;

result is 228;

The hexadecimal string is obtained by using the method toRadixString(16) , get e4 String

According to the document, the parameter should be 0xe4

But I can't find a way to convert e4 to 0xe4

Describe alternatives you've considered

Maybe hexadecimal strings can be supported?

Reactive ble refactoring

During code review we found out that the following methods are quite big and are candidates of being extracted into a separate component:

  • scanfordevices
  • connectToDevice
  • subSribeToCharacteristics

Improve architecture example app

Is your feature request related to a problem? (please describe)
The architecture does not reflect a super nice way how to user our library in the app

Describe the solution you'd like
Extract BLE related code into a seperate component (e.g. BLOC)

How to connect multiple different devices at the same time?

Is your feature request related to a problem? (please describe)
Connect multiple devices and write different information to different devices at the same time

Describe the solution you'd like
There is no information about multiple device connection writes in the documentation and examples

Describe alternatives you've considered
Nothing

Additional context
Nothing

Can't scan with location service disabled

Hi,
I'm unable to scan for devices when the location services are disabled:

D/ScanDevicesHandler$startDeviceScan$$inlined$let$lambda(26938): Error while scanning for devices: Location Services disabled (code 4)

I'm not sure what I'm doing wrong but I think I should be able to scan for devices when only the bluetoothservices are enabled.

I'm getting this on Android 8, with flutter_reactive_blue 1.1.0

I'm just doing something along the line of:

_scanSubscription = bleClient.scanForDevices(
  withService: BTDevice.serviceUuid,
  scanMode: ScanMode.lowLatency,
).listen(
  (device) {
  },
);

I also added all the permissions required in the AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

By the way, using FlutterBlue on the same device doesn't require location services to be on in the phone settins.

Ability to disconnect from a device

So I was digging in the code looking for a way to programmatically disconnect from a device and found the DisconnectFromDeviceRequestin the generated pb dart file. But it seems like this feature is not made available in the Method channel in any way? I would propose to add this as a feature making it possible to also disconnect from a device in the same way you're able to _ble.connectToDevice(...

iOS render overflow in example Scan for devices Enter UUID text

Firstly very many thanks for a wonderfully presented, designed and documented library.

Describe the bug
On iPhones with screen size 750 x 1334 pixels the example Scan for Devices screen has a line of text:
Enter a UUID above and tap start to begin scanning
which overflows to the right losing the word 'scanning'.
This causes an exception by the rendering library with a yellow warning on screen and the exception reported to console, but does not stop the app running and continuing.

To Reproduce
Steps to reproduce the behavior:

  1. Run the example on an iPhone6, iPhone6s, iPhone7, iPhone8, iPhoneSE with horizontal screen size 750 pixels.

Expected behavior
No text overflow.

Additional context
iOS13, Xcode 11.5

The console report is:

════════ Exception caught by rendering library
The following assertion was thrown during layout:
A RenderFlex overflowed by 76 pixels on the right.

The relevant error-causing widget was
Row
lib/…/ui/device_list.dart:117
The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex.

Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the RenderFlex to fit within the available space instead of being sized to their natural size.
This is considered an error condition because it indicates that there is content that cannot be seen. If the content is legitimately bigger than the available space, consider clipping it with a ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, like a ListView.

The specific RenderFlex in question is: RenderFlex#3e75a relayoutBoundary=up4 OVERFLOWING

Add permission documentation for iOS & Android

Because we need to add permissions to android & iOS before we can get this to work.

This should be added to the readme as well.

For android, it will be added automatically. But is that also the case for iOS?

When I Turn off BLE or disconnect a device my app crashes with flutter_reactive_ble v2.4

When I Turn off BLE or disconnect a device my app crashes on Flutter 1.20.1 (but not on 1.17.5)

Hello,

Firstly, thank you for your great job :)

I’ve an issue on flutter 1.20.1 but not on 1.17.5, I don't know if the issue comes from the flutter update or the library ?

My issue : When I turn off the BLE or if I disconnect manually/physically (not via code) a BLE device, my app crashes on flutter 1.20.1 (but not on 1.17.5).

--- EDIT ---

My issue : When I turn off the BLE or if I disconnect manually/physically (not via code) a BLE device, my app crashes with flutter_reactive_ble v2.4 (after subscribing to device characteristics).

It seems the issue comes from the v2.4 of this lib.

Flutter v1.17.5 + flutter_reactive_ble v2.3 = OK
Flutter v1.20.1 + flutter_reactive_ble v2.3 = OK
Flutter v1.17.5 + flutter_reactive_ble v2.4 = CRASH
Flutter v1.20.1 + flutter_reactive_ble v2.4 = CRASH

--- END EDIT ---

I don’t know how to handle this exception, do you have any idea ?

Thank you very much

info :
Device : Samsung Galaxy A10
OS : Android 10

com.polidea.rxandroidble2.exceptions.BleDisconnectedException com.polidea.rxandroidble2.exceptions.BleAdapterDisabledException

io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | com.polidea.rxandroidble2.exceptions.BleDisconnectedException: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status -1 (UNKNOWN) E/AndroidRuntime(21736): at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704) E/AndroidRuntime(21736): at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701) E/AndroidRuntime(21736): at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.checkTerminated(ObservableObserveOn.java:281) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.drainNormal(ObservableObserveOn.java:172) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableObserveOn$ObserveOnObserver.run(ObservableObserveOn.java:255) E/AndroidRuntime(21736): at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:124) E/AndroidRuntime(21736): at android.os.Handler.handleCallback(Handler.java:883) E/AndroidRuntime(21736): at android.os.Handler.dispatchMessage(Handler.java:100) E/AndroidRuntime(21736): at android.os.Looper.loop(Looper.java:237) E/AndroidRuntime(21736): at android.app.ActivityThread.main(ActivityThread.java:7948) E/AndroidRuntime(21736): at java.lang.reflect.Method.invoke(Native Method) E/AndroidRuntime(21736): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) E/AndroidRuntime(21736): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1075) E/AndroidRuntime(21736): Caused by: com.polidea.rxandroidble2.exceptions.BleDisconnectedException: Disconnected from MAC='XX:XX:XX:XX:XX:XX' with status -1 (UNKNOWN) E/AndroidRuntime(21736): at com.polidea.rxandroidble2.internal.connection.DisconnectionRouter$3.apply(DisconnectionRouter.java:53) E/AndroidRuntime(21736): at com.polidea.rxandroidble2.internal.connection.DisconnectionRouter$3.apply(DisconnectionRouter.java:50) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableFilter$FilterObserver.onNext(ObservableFilter.java:52) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableConcatMap$ConcatMapDelayErrorObserver$DelayErrorInnerObserver.onNext(ObservableConcatMap.java:506) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:62) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableRefCount$RefCountObserver.onNext(ObservableRefCount.java:227) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservablePublishAlt$PublishConnection.onNext(ObservablePublishAlt.java:177) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableUnsubscribeOn$UnsubscribeObserver.onNext(ObservableUnsubscribeOn.java:60) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58) E/AndroidRuntime(21736): at io.reactivex.internal.operators.observable.ObservableCreate$CreateEmitter.onNext(ObservableCreate.java:66) E/AndroidRuntime(21736): at com.polidea.rxandroidble2.RxBleAdapterStateObservable$1$1.onReceive(RxBleAdapterStateObservable.java:67) E/AndroidRuntime(21736): at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1646) E/AndroidRuntime(21736): at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2) E/AndroidRuntime(21736): ... 7 more E/AndroidRuntime(21736): Caused by: com.polidea.rxandroidble2.exceptions.BleAdapterDisabledException E/AndroidRuntime(21736): ... 21 more

Small inconsistency between iOS and Android with short/long UUID

Full disclosure: I don't really understand what I'm talking about :-) I'm just reporting this in case it helps someone.

Describe the bug
I noticed a small difference between Android and iOS when registering a characteristic for Notification (subscribeToCharacteristic).

My device "somehow" uses "short UUID" (16 bits) but I was using the long form (128bits) in my code (QualifiedCharacteristic).
It works for Read and Write on both Android and iOS. But for "Subscribe" it only works for Android.

On iOS I had no error but I never received the data.

With the short Uuid, the notification worked on both platform.

Expected behavior
It should work the same on both Android and iOS. The iOS code should probably map the short Uuid (sent by the device?) to the long form registered on the phone?

Smartphone / tablet

  • Device: iPhone 11 and Moto G5
  • OS: iOS 13, Android 8.1
  • Package version: 2.2.0

Peripheral device

  • Vendor, model: ESP32
  • Does it run a custom firmware / software: custom firmware based on ESP-IDF v4

How do I get a list of all services and characteristics for a device?

How do I get a list of all services and characteristics for a device? I've looked at the documentation but I could not find anything related to this. I see there is a way to read/write to a particular characteristic but no way to get all the services and characteristics.

Would appreciate any pointers.

Support iOS 13 in the example app

As reported in the issue 12, the example app needs some updates to "Info.plist" to properly work on iOS 13.

Specifically check NSBluetoothAlwaysUsageDescription and NSBluetoothPeripheralUsageDescription parameters.

No way of knowing if peripheral is connectable

Is your feature request related to a problem? (please describe)
Right now it's not possible to check if the found peripheral is connectable. There are devices that advertise without being connectable, so it would be good to filter out those and only allow connectable devices.

Describe the solution you'd like
For iOS, CBAdvertisementDataIsConnectable lets you know wether a peripheral is connectable. It would be great if this gets passed through.
However, since this plugin relies on RxAndroidBle, it is currently not possible to check if a peripheral is connectable on Android. See ticket dariuszseweryn/RxAndroidBle#668

Describe alternatives you've considered
x

Additional context
x

Improve doc comments for public api

We are still missing:

  Repeater: repeater.dart:5:1
  SerialDisposable: serial_disposable.dart:3:1
  isDisposed: serial_disposable.dart:22:3
  set: serial_disposable.dart:6:3
  dispose: serial_disposable.dart:15:3
  StreamSubscriptionSerialDisposable: serial_disposable.dart:38:1
  WriteCharacteristicInfo: write_characteristic_info.dart:6:1
  WriteCharacteristicFailure: write_characteristic_info.dart:17:1
  Uuid: uuid.dart:6:1
 convertScanModeToArgs: scan_mode.dart:17:1

Example app crashes when clearing GATT cache (on Android)

When I try to use the clearGATTcache method, using the edited example app on Android, the app crashes on a RX error that I think comes from the library:

2020-01-07 13:46:56.795 26969-27186/com.signify.hue.reactivebleexample E/AndroidRuntime: FATAL EXCEPTION: RxComputationThreadPool-6
Process: com.signify.hue.reactivebleexample, PID: 26969
io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: RxComputationThreadPool-6
at io.reactivex.plugins.RxJavaPlugins.onError(RxJavaPlugins.java:367)

example can't connect to iphone from android

Describe the bug
the example app can scan found my iphone, but can't connect to iphone from my android

Expected behavior
should connected to my iphone

  • [ yes ] I tried doing the same with a general BLE scanner application (e.g. nRF Connect) and it exhibits the expected behavior as described above

Smartphone / tablet

  • Device: iphone xs max, redmi 10x
  • OS: ios 13, MIUI 12.0.4
  • Package version: [e.g. 1.0.0]

Error compiling for iOS fatal error: 'flutter_reactive_ble/flutter_reactive_ble-Swift.h' file not found

When compiling v2.0.0 for iOS device, I receive the fatal error: .pub-cache/hosted/pub.dartlang.org/flutter_reactive_ble-2.0.0/ios/Classes/ReactiveBlePlugin.m:2:9: fatal error: 'flutter_reactive_ble/flutter_reactive_ble-Swift.h' file not found
#import <flutter_reactive_ble/flutter_reactive_ble-Swift.h>

I can't find the file in the package. Can you help me?

flutter doctor output:
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel master, v1.18.0-6.0.pre.115, on Mac OS X 10.15.4 19E287, locale de-DE)

[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 11.4.1)
[✓] Android Studio (version 3.5)
[✓] Connected device (1 available)

• No issues found!

Allow scanForDevices without service uuid

The README for scanForDevices states that "the withService parameter is required.".

I would like to scan for every BLE devices around. What is the reason to enforce the service filter?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.