diff --git a/README.md b/README.md index 07541598..f566fa2a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,20 @@ The library uses Java 1.8 features. If you're using Android Studio below 4.2, ma ### Recent changes +See [Releases](https://github.com/NordicSemiconductor/Android-BLE-Library/releases) for details. +Below is short summary: + +
+ Version 2.6 + +1. `getGattCallback()` method has been deprecated. Instead, simply move the inner methods to the + `BleManager` class. See [this PR](https://github.com/NordicSemiconductor/Android-nRF-Blinky/pull/78). +2. Support for *server only* implementation using `attachClientConnection(BluetoothDevice)`. Call it instead of + `connect(BluetoothDevice)` to use the device as server-only. +3. Data provider for read requests (server side) was added. +4. Cancellation support for flows and suspended methods added to `:ble-kts`. +
+
Version 2.4 @@ -159,137 +173,7 @@ Check out [migration guide](MIGRATION.md). ## Usage -A `BleManager` instance is responsible for connecting and communicating with a single peripheral. -Having multiple instances of the manager is possible to connect with multiple devices simultaneously. -The `BleManager` must be extended with your implementation where you define the high level device's API. - -```java -class MyBleManager extends BleManager { - private static final String TAG = "MyBleManager"; - - private BluetoothGattCharacteristic fluxCapacitorControlPoint; - - public MyBleManager(@NonNull final Context context) { - super(context); - } - - @Override - public int getMinLogPriority() { - // Use to return minimal desired logging priority. - return Log.VERBOSE; - } - - @Override - public void log(int priority, @NonNull String message) { - // Log from here. - Log.println(priority, TAG, message); - } - - @NonNull - @Override - protected BleManagerGattCallback getGattCallback() { - return new MyGattCallbackImpl(); - } - - private class MyGattCallbackImpl extends BleManagerGattCallback { - @Override - protected boolean isRequiredServiceSupported(@NonNull BluetoothGatt gatt) { - // Here get instances of your characteristics. - // Return false if a required service has not been discovered. - BluetoothGattService fluxCapacitorService = gatt.getService(FLUX_SERVICE_UUID); - if (fluxCapacitorService != null) { - fluxCapacitorControlPoint = fluxCapacitorService.getCharacteristic(FLUX_CHAR_UUID); - } - return fluxCapacitorControlPoint != null; - } - - @Override - protected void initialize() { - // Initialize your device. - // This means e.g. enabling notifications, setting notification callbacks, - // sometimes writing something to some Control Point. - // Kotlin projects should not use suspend methods here, which require a scope. - requestMtu(517) - .enqueue(); - } - - @Override - protected void onServicesInvalidated() { - // This method is called when the services get invalidated, i.e. when the device - // disconnects. - // References to characteristics should be nullified here. - fluxCapacitorControlPoint = null; - } - } - - // Here you may add some high level methods for your device: - public void enableFluxCapacitor() { - // Do the magic. - writeCharacteristic(fluxCapacitorControlPoint, Flux.enable(), BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) - .enqueue() - } -} -``` - -The [BleManager](https://github.com/NordicSemiconductor/Android-BLE-Library/blob/master/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java) -class exposes high level API for connecting and communicating with Bluetooth LE peripherals. - -```java -connect(bluetoothDevice) - // Automatic retries are supported, in case of 133 error. - .retry(3 /* times, with */, 100 /* ms interval */) - // A connection timeout can be set. This is additional to the Android's connection timeout which is 30 seconds. - .timeout(15_000 /* ms */) - // The auto connect feature from connectGatt is available as well - .useAutoConnect(true) - // This API can be set on any Android version, but will only be used on devices running Android 8+ with - // support to the selected PHY. - .usePreferredPhy(PhyRequest.PHY_LE_1M_MASK | PhyRequest.PHY_LE_2M_MASK | PhyRequest.PHY_LE_CODED_MASK) - // A connection timeout can be also set. This is additional to the Android's connection timeout which is 30 seconds. - .timeout(15_000 /* ms */) - // Each request has number of callbacks called in different situations: - .before(device -> { /* called when the request is about to be executed */ }) - .done(device -> { /* called when the device has connected, has required services and has been initialized */ }) - .fail(device, status -> { /* called when the request has failed */ }) - .then(device -> { /* called when the request was finished with either success, or a failure */ }) - // Each request must be enqueued. - // Kotlin projects can use suspend() or suspendForResult() instead. - // Java projects can also use await() which is blocking. - .enqueue() -``` - -```java -writeCharacteristic(someCharacteristic, someData, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) - // Outgoing data can use automatic splitting. - .split(customSplitter, progressCallback /* optional */) - // .split() with no parameters uses the default MTU splitter. - // Kotlin projects can use .splitWithProgressAsFlow(customSplitter) to get the progress as Flow. - .before(device -> { /* called when the request is about to be executed */ }) - .with(device, data -> { /* called when the request has been executed */ }) - .done(device -> { /* called when the request has completed successfully */ }) - .fail(device, status -> { /* called when the request has failed */ }) - .invalid({ /* called when the request was invalid, i.e. the target device or given characteristic was null */ }) - .then(device -> { /* called when the request was finished with either success, or a failure */ }) - // Remember to enqueue each request. - .enqueue() -``` - -```java -readCharacteristic(someCharacteristic) - // Incoming data can use automatic merging. - .merge(customMerger, progressCallback /* optional */) - // Kotlin projects can use .mergeWithProgressAsFlow(customMerger) to get the progress as Flow. - // Incoming packets can also be filtered, so that not everything goes to the merger. - .filter(dataFilter) - // Complete, merged packets can also be filtered. - .filterPacket(packetFilter) - // [...] - .with(device, data -> { /* called when the data have been received */ }) - // [...] - // Once again, remember to enqueue each request! - .enqueue() -``` -All requests are automatically enqueued and executed sequentially. +See [Usage](USAGE.md) for more details. ## Examples diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 00000000..c914b471 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,244 @@ +# Usage + +A [BleManager](https://github.com/NordicSemiconductor/Android-BLE-Library/blob/main/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java) +instance is responsible for connecting and communicating with a single Bluetooth LE peripheral. +By having multiple instances of the manager it is possible to connect to multiple devices simultaneously. +Different Android devices have different maximum limit of connections, usually oscillating around 6. +It is possible to reuse the same manager instance to connect to a new peripheral +after the previous one was disconnected, but we recommend to create a new instance. + +The `BleManager` must be extended with your implementation where you define the high level device's API. + +```mermaid +flowchart LR; + device((Device)) + manager(BleManager) + app(Rest of the app) + device<-- Low level BLE API -->manager<-- High level API -->app; +``` + +The methods that need to be implemented are: +1. `isRequiredServiceSupported(BluetoothGatt gatt)` should search for required service(s) in + `gatt.getServices()`. This method should return whether the service and all characteristics are found. + This method should also acquire references to `BluetoothGattCharacteristic`s used by the manager. +2. `onServicesInvalidated()` should release the references as no longer valid. This method is called when + the device has disconnected or invalidated services. +3. `initialize()` should initialize the device. This may include enabling notifications, indications, + writing some require values, etc. This is optional. Enqueued operations will be performed before + `onDeviceReady()` and `done` callback for the `ConnectRequest` are called. + +Additionally, it should expose the high-level API of the device. For example, instead of "write characteristic" +the public method should be "turn LED on". + +Below is a sample implementation of `BleManager`: + +```java +class MyBleManager extends BleManager { + private static final String TAG = "MyBleManager"; + + public MyBleManager(@NonNull final Context context) { + super(context); + } + + // ==== Logging ===== + + @Override + public int getMinLogPriority() { + // Use to return minimal desired logging priority. + return Log.VERBOSE; + } + + @Override + public void log(int priority, @NonNull String message) { + // Log from here. + Log.println(priority, TAG, message); + } + + // ==== Required implementation ==== + + // This is a reference to a characteristic that the manager will use internally. + private BluetoothGattCharacteristic fluxCapacitorControlPoint; + + @Override + protected boolean isRequiredServiceSupported(@NonNull BluetoothGatt gatt) { + // Here obtain instances of your characteristics. + // Return false if a required service has not been discovered. + BluetoothGattService fluxCapacitorService = gatt.getService(FLUX_SERVICE_UUID); + if (fluxCapacitorService != null) { + fluxCapacitorControlPoint = fluxCapacitorService.getCharacteristic(FLUX_CHAR_UUID); + } + return fluxCapacitorControlPoint != null; + } + + @Override + protected void initialize() { + // Initialize your device. + // This means e.g. enabling notifications, setting notification callbacks, or writing + // something to a Control Point characteristic. + // Kotlin projects should not use suspend methods here, as this method does not suspend. + requestMtu(517) + .enqueue(); + } + + @Override + protected void onServicesInvalidated() { + // This method is called when the services get invalidated, i.e. when the device + // disconnects. + // References to characteristics should be nullified here. + fluxCapacitorControlPoint = null; + } + + // ==== Public API ==== + + // Here you may add some high level methods for your device: + public void enableFluxCapacitor() { + // Do the magic. + writeCharacteristic(fluxCapacitorControlPoint, Flux.enable(), BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) + .enqueue(); + } +} +``` + +## BLE API + +The [BleManager](https://github.com/NordicSemiconductor/Android-BLE-Library/blob/main/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java) +class exposes high level API for connecting and communicating with Bluetooth LE peripherals. + +
+ Java + +```java +connect(bluetoothDevice) + // Automatic retries are supported, in case of 133 error. + .retry(3 /* times, with */, 100 /* ms interval */) + // A connection timeout can be set. This is additional to the Android's connection timeout which is 30 seconds. + .timeout(15_000 /* ms */) + // The auto connect feature from connectGatt is available as well + .useAutoConnect(true) + // This API can be set on any Android version, but will only be used on devices running Android 8+ with + // support to the selected PHY. + .usePreferredPhy(PhyRequest.PHY_LE_1M_MASK | PhyRequest.PHY_LE_2M_MASK | PhyRequest.PHY_LE_CODED_MASK) + // A connection timeout can be also set. This is additional to the Android's connection timeout which is 30 seconds. + .timeout(15_000 /* ms */) + // Each request has number of callbacks called in different situations: + .before(device -> { /* called when the request is about to be executed */ }) + .done(device -> { /* called when the device has connected, has required services and has been initialized */ }) + .fail(device, status -> { /* called when the request has failed */ }) + .then(device -> { /* called when the request was finished with either success, or a failure */ }) + // Each request must be enqueued. + // Kotlin projects can use suspend() or suspendForResult() instead. + // Java projects can also use await() which is blocking. + .enqueue() +``` + +```java +writeCharacteristic(someCharacteristic, someData, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) + // Outgoing data can use automatic splitting. + .split(customSplitter, progressCallback /* optional */) + // .split() with no parameters uses the default MTU splitter. + // Kotlin projects can use .splitWithProgressAsFlow(customSplitter) to get the progress as Flow. + .before(device -> { /* called when the request is about to be executed */ }) + .with(device, data -> { /* called when the request has been executed */ }) + .done(device -> { /* called when the request has completed successfully */ }) + .fail(device, status -> { /* called when the request has failed */ }) + .invalid({ /* called when the request was invalid, i.e. the target device or given characteristic was null */ }) + .then(device -> { /* called when the request was finished with either success, or a failure */ }) + // Remember to enqueue each request. + .enqueue() +``` + +```java +readCharacteristic(someCharacteristic) + // Incoming data can use automatic merging. + .merge(customMerger, progressCallback /* optional */) + // Kotlin projects can use .mergeWithProgressAsFlow(customMerger) to get the progress as Flow. + // Incoming packets can also be filtered, so that not everything goes to the merger. + .filter(dataFilter) + // Complete, merged packets can also be filtered. + .filterPacket(packetFilter) + // [...] + .with(device, data -> { /* called when the data have been received */ }) + // [...] + // Once again, remember to enqueue each request! + .enqueue() +``` +All requests are automatically enqueued and executed sequentially. +
+ +
+ Kotlin + +Using `ble-ktx` module you may take advantage of Kotlin extension methods. + +```kotlin +connect(bluetoothDevice) + // Automatic retries are supported, in case of 133 error. + .retry(3 /* times, with */, 100 /* ms interval */) + // A connection timeout can be set. This is additional to the Android's connection timeout which is 30 seconds. + .timeout(15_000 /* ms */) + // The auto connect feature from connectGatt is available as well + .useAutoConnect(true) + // This API can be set on any Android version, but will only be used on devices running Android 8+ with + // support to the selected PHY. + .usePreferredPhy(PhyRequest.PHY_LE_1M_MASK | PhyRequest.PHY_LE_2M_MASK | PhyRequest.PHY_LE_CODED_MASK) + // A connection timeout can be also set. This is additional to the Android's connection timeout which is 30 seconds. + .timeout(15_000 /* ms */) + // To suspend until the connection AND initialization is complete, call suspend(). + .suspend() +``` + +As you see, when using `suspend()` the `before()`, ` with()`, `done()`, `fail()` and `then()` callbacks +won't work (they are used internally to implement suspending). Instead, simply write your code +before or after the suspended method. + +```kotlin +doSomethingBeforeWriting() + +try { + val dataSent = writeCharacteristic(someCharacteristic, someData, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) + // Outgoing data can use automatic splitting. + .split(customSplitter, progressCallback /* optional */) + // .split() with no parameters uses the default MTU splitter. + // Remember to call suspend(). + .suspend() + + doSomethingWith(dataSent) +} catch (e: Exception) { + // The request has failed. + handleException(e) +} +``` + +You may also use `suspendForResponse()` to parse the data to a type extending `WriteResponse`: + +```kotlin +try { + val request: MyRequest = writeCharacteristic(someCharacteristic, someData, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) + // Outgoing data can use automatic splitting. + .split(customSplitter, progressCallback /* optional */) + // .split() with no parameters uses the default MTU splitter. + .suspendForResponse() +} catch (e: Exception) { + // The request has failed. +} +``` + +or `ReadResponse`: + +```kotlin +val response: MyResponse = readCharacteristic(someCharacteristic) + // Incoming data can use automatic merging. + .merge(customMerger, progressCallback /* optional */) + // Kotlin projects can use .mergeWithProgressAsFlow(customMerger) to get the progress as Flow. + // Incoming packets can also be filtered, so that not everything goes to the merger. + .filter(dataFilter) + // Complete, merged packets can also be filtered. + .filterPacket(packetFilter) + // suspend() or suspendForResponse() will throw an exception if the request has failed. + .suspendForResponse() +``` + +To validate the received value use `suspendForValidResponse()` with a type extending `ProfileReadResponse`. +See an example [here](https://github.com/NordicSemiconductor/Android-nRF-Blinky/blob/8352e92ce2dd12af5a05a60643a64187473adbc0/blinky/ble/src/main/java/no/nordicsemi/android/blinky/ble/data/LedCallback.kt#L7). + +
\ No newline at end of file diff --git a/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java b/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java index 6046137c..c596ef1f 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattServer; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -182,6 +183,117 @@ public BleManager(@NonNull final Context context, @NonNull final Handler handler new IntentFilter("android.bluetooth.device.action.PAIRING_REQUEST")); } + /** + * This method should set up the request queue needed to initialize the profile. + * Enabling Service Change indications for bonded devices is handled before executing this + * queue. The queue may have requests that are not available, e.g. read an optional + * service when it is not supported by the connected device. Such call will trigger + * {@link Request#fail(FailCallback)}. + *

+ * This method is called when the services has been discovered and the device is supported + * (has required service). + *

+ * Remember to call {@link Request#enqueue()} for each request. + *

+ * A sample initialization should look like this: + *

+	 * @Override
+	 * protected void initialize() {
+	 *    requestMtu(MTU)
+	 *       .with((device, mtu) -> {
+	 *           ...
+	 *       })
+	 *       .enqueue();
+	 *    setNotificationCallback(characteristic)
+	 *       .with((device, data) -> {
+	 *           ...
+	 *       });
+	 *    enableNotifications(characteristic)
+	 *       .done(device -> {
+	 *           ...
+	 *       })
+	 *       .fail((device, status) -> {
+	 *           ...
+	 *       })
+	 *       .enqueue();
+	 * }
+	 * 
+ */ + protected void initialize() { + // Don't call super.initialize() when overriding this method. + requestHandler.initialize(); + } + + /** + * This method should return true when the gatt device supports the + * required services. + * + * @param gatt the gatt device with services discovered + * @return True when the device has the required service. + */ + protected boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) { + // Don't call super.isRequiredServiceSupported(gatt) when overriding this method. + return requestHandler.isRequiredServiceSupported(gatt); + } + + /** + * This method should return true when the gatt device supports the + * optional services. The default implementation returns false. + * + * @param gatt the gatt device with services discovered + * @return True when the device has the optional service. + */ + protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) { + // Don't call super.isOptionalServiceSupported(gatt) when overriding this method. + return requestHandler.isOptionalServiceSupported(gatt); + } + + /** + * In this method the manager should get references to server characteristics and descriptors + * that will use. The method is called after the service discovery of a remote device has + * finished and {@link #isRequiredServiceSupported(BluetoothGatt)} returned true. + *

+ * The references obtained in this method should be released in {@link #onServicesInvalidated()}. + *

+ * This method is called only when the server was set by + * {@link BleManager#useServer(BleServerManager)} and opened using {@link BleServerManager#open()}. + * + * @param server The GATT Server instance. Use {@link BluetoothGattServer#getService(UUID)} to + * obtain service instance. + */ + protected void onServerReady(@NonNull final BluetoothGattServer server) { + // Don't call super.onServerReady(server) when overriding this method. + requestHandler.onServerReady(server); + } + + /** + * This method should nullify all services and characteristics of the device. + *

+ * It's called when the services were invalidated and can no longer be used. Most probably the + * device has disconnected, Service Changed indication was received, or + * {@link #refreshDeviceCache()} request was executed, which has invalidated cached services. + */ + protected void onServicesInvalidated() { + // Don't call super.onServicesInvalidated() when overriding this method. + requestHandler.onServicesInvalidated(); + } + + /** + * Called when the initialization queue is complete. + */ + protected void onDeviceReady() { + // Don't call super.onDeviceReady() when overriding this method. + requestHandler.onDeviceReady(); + } + + /** + * Called each time the task queue gets cleared. + */ + protected void onManagerReady() { + // Don't call super.onManagerReady() when overriding this method. + requestHandler.onManagerReady(); + } + /** * Closes and releases resources. This method will be called automatically after * calling {@link #disconnect()}. When the device disconnected with link loss and @@ -303,16 +415,29 @@ protected void onPairingRequestReceived(@NonNull final BluetoothDevice device, } /** - * This method must return the GATT callback used by the manager. + * This method returns the GATT callback used by the manager. * - * The object must exist when this method is called, that is in the BleManager's constructor. - * Therefore, it cannot return a local field in the extending manager, as this is created after - * the constructor finishes. + * Since version 2.6 this method is private. The manager just can implement all inner methods + * directly, without additional object. * * @return The gatt callback object. + * @deprecated Implement all methods directly in your manager. */ + @Deprecated @NonNull - protected abstract BleManagerGattCallback getGattCallback(); + protected BleManagerGattCallback getGattCallback() { + return new BleManagerGattCallback() { + @Override + protected boolean isRequiredServiceSupported(@NonNull BluetoothGatt gatt) { + return false; + } + + @Override + protected void onServicesInvalidated() { + // empty default implementation + } + }; + } /** * Returns the context that the manager was created with. @@ -2006,8 +2131,8 @@ protected ReadRssiRequest readRssi() { * There is no callback indicating when the cache has been cleared. This library assumes * some time and waits. After the delay, it will start service discovery and clear the * task queue. When the service discovery finishes, the - * {@link BleManager.BleManagerGattCallback#isRequiredServiceSupported(BluetoothGatt)} and - * {@link BleManager.BleManagerGattCallback#isOptionalServiceSupported(BluetoothGatt)} will + * {@link BleManager#isRequiredServiceSupported(BluetoothGatt)} and + * {@link BleManager#isOptionalServiceSupported(BluetoothGatt)} will * be called and the initialization will be performed as if the device has just connected. *

* The returned request must be either enqueued using {@link Request#enqueue()} for diff --git a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java index d9c49c8e..0c501e73 100644 --- a/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java +++ b/ble/src/main/java/no/nordicsemi/android/ble/BleManagerHandler.java @@ -476,7 +476,7 @@ void attachClientConnection(BluetoothDevice clientDevice) { // Since we are opting to use server only connection, we must do this here instead. initializeServerAttributes(); // the other path also calls this part of the callbacks - initialize(); + manager.initialize(); } } @@ -500,7 +500,7 @@ private void initializeServerAttributes() { } } } - onServerReady(server); + manager.onServerReady(server); } } } @@ -1693,6 +1693,7 @@ final void overrideMtu(@IntRange(from = 23, to = 517) final int mtu) { * * @param gatt the gatt device with services discovered * @return True when the device has the required service. + * @deprecated Use {@link BleManager#isRequiredServiceSupported(BluetoothGatt)} instead. */ protected abstract boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt); @@ -1702,7 +1703,9 @@ final void overrideMtu(@IntRange(from = 23, to = 517) final int mtu) { * * @param gatt the gatt device with services discovered * @return True when the device has the optional service. + * @deprecated Use {@link BleManager#isOptionalServiceSupported(BluetoothGatt)} instead. */ + @Deprecated protected boolean isOptionalServiceSupported(@NonNull final BluetoothGatt gatt) { return false; } @@ -1733,35 +1736,12 @@ protected Deque initGatt(@NonNull final BluetoothGatt gatt) { * service when it is not supported by the connected device. Such call will trigger * {@link Request#fail(FailCallback)}. *

- * This method is called from the main thread when the services has been discovered and + * This method is called when the services has been discovered and * the device is supported (has required service). *

- * Remember to call {@link Request#enqueue()} for each request. - *

- * A sample initialization should look like this: - *

-	 * @Override
-	 * protected void initialize() {
-	 *    requestMtu(MTU)
-	 *       .with((device, mtu) -> {
-	 *           ...
-	 *       })
-	 *       .enqueue();
-	 *    setNotificationCallback(characteristic)
-	 *       .with((device, data) -> {
-	 *           ...
-	 *       });
-	 *    enableNotifications(characteristic)
-	 *       .done(device -> {
-	 *           ...
-	 *       })
-	 *       .fail((device, status) -> {
-	 *           ...
-	 *       })
-	 *       .enqueue();
-	 * }
-	 * 
+ * @deprecated Use {@link BleManager#initialize()} instead. */ + @Deprecated protected void initialize() { // empty initialization queue } @@ -1778,21 +1758,27 @@ protected void initialize() { * * @param server The GATT Server instance. Use {@link BluetoothGattServer#getService(UUID)} to * obtain service instance. + * @deprecated Use {@link BleManager#onServerReady(BluetoothGattServer)} instead. */ + @Deprecated protected void onServerReady(@NonNull final BluetoothGattServer server) { // empty initialization } /** * Called when the initialization queue is complete. + * @deprecated Use {@link BleManager#onDeviceReady()} instead. */ + @Deprecated protected void onDeviceReady() { // empty } /** * Called each time the task queue gets cleared. + * @deprecated Use {@link BleManager#onManagerReady()} instead. */ + @Deprecated protected void onManagerReady() { // empty } @@ -1808,6 +1794,7 @@ protected void onManagerReady() { */ @Deprecated protected void onDeviceDisconnected() { + // empty } /** @@ -1817,7 +1804,9 @@ protected void onDeviceDisconnected() { * device has disconnected, Service Changed indication was received, or * {@link BleManager#refreshDeviceCache()} request was executed, which has invalidated cached * services. + * @deprecated Use {@link BleManager#onServicesInvalidated()} instead. */ + @Deprecated protected abstract void onServicesInvalidated(); private void notifyDeviceDisconnected(@NonNull final BluetoothDevice device, final int status) { @@ -1871,7 +1860,7 @@ private void notifyDeviceDisconnected(@NonNull final BluetoothDevice device, fin dataProviders.clear(); batteryLevelNotificationCallback = null; batteryValue = -1; - onServicesInvalidated(); + manager.onServicesInvalidated(); onDeviceDisconnected(); } @@ -2188,10 +2177,10 @@ public void onServicesDiscovered(@NonNull final BluetoothGatt gatt, final int st if (status == BluetoothGatt.GATT_SUCCESS) { log(Log.INFO, () -> "Services discovered"); servicesDiscovered = true; - if (isRequiredServiceSupported(gatt)) { + if (manager.isRequiredServiceSupported(gatt)) { log(Log.VERBOSE, () -> "Primary service found"); deviceNotSupported = false; - final boolean optionalServicesFound = isOptionalServiceSupported(gatt); + final boolean optionalServicesFound = manager.isOptionalServiceSupported(gatt); if (optionalServicesFound) log(Log.VERBOSE, () -> "Secondary service found"); @@ -2253,7 +2242,7 @@ public void onServicesDiscovered(@NonNull final BluetoothGatt gatt, final int st } // End - initialize(); + manager.initialize(); nextRequest(true); } else { log(Log.WARN, () -> "Device is not supported"); @@ -2287,7 +2276,7 @@ public void onServiceChanged(@NonNull final BluetoothGatt gatt) { // Forbid enqueuing more operations. operationInProgress = true; // Invalidate all services and characteristics - onServicesInvalidated(); + manager.onServicesInvalidated(); onDeviceDisconnected(); // Clear queues, services are no longer valid. taskQueue.clear(); @@ -2556,7 +2545,7 @@ public void onCharacteristicChanged( // Forbid enqueuing more operations. operationInProgress = true; // Invalidate all services and characteristics - onServicesInvalidated(); + manager.onServicesInvalidated(); onDeviceDisconnected(); // Clear queues, services are no longer valid. taskQueue.clear(); @@ -3327,7 +3316,7 @@ private synchronized void nextRequest(final boolean force) { // will not start new nextRequest() call. operationInProgress = true; ready = true; - onDeviceReady(); + manager.onDeviceReady(); if (bluetoothDevice != null) { postCallback(c -> c.onDeviceReady(bluetoothDevice)); postConnectionStateChange(o -> o.onDeviceReady(bluetoothDevice)); @@ -3344,7 +3333,7 @@ private synchronized void nextRequest(final boolean force) { // No more tasks to perform operationInProgress = false; this.request = null; - onManagerReady(); + manager.onManagerReady(); return; } } @@ -3702,7 +3691,7 @@ private synchronized void nextRequest(final boolean force) { final BluetoothGatt bluetoothGatt = this.bluetoothGatt; if (connected && bluetoothGatt != null) { // Invalidate all services and characteristics - onServicesInvalidated(); + manager.onServicesInvalidated(); onDeviceDisconnected(); // And discover services again serviceDiscoveryRequested = true; diff --git a/moustache/README.mo b/moustache/README.mo index e7c2b009..eaf7e8f4 100644 --- a/moustache/README.mo +++ b/moustache/README.mo @@ -90,6 +90,20 @@ The library uses Java 1.8 features. If you're using Android Studio below 4.2, ma ### Recent changes +See [Releases](https://github.com/NordicSemiconductor/Android-BLE-Library/releases) for details. +Below is short summary: + +
+ Version 2.6 + +1. `getGattCallback()` method has been deprecated. Instead, simply move the inner methods to the + `BleManager` class. See [this PR](https://github.com/NordicSemiconductor/Android-nRF-Blinky/pull/78). +2. Support for *server only* implementation using `attachClientConnection(BluetoothDevice)`. Call it instead of + `connect(BluetoothDevice)` to use the device as server-only. +3. Data provider for read requests (server side) was added. +4. Cancellation support for flows and suspended methods added to `:ble-kts`. +
+
Version 2.4 @@ -139,15 +153,15 @@ setNotificationCallback(characteristic)
Version 2.2 -1. GATT Server support. This includes setting up the local GATT server on the Android device, new - requests for server operations: - * *wait for read*, - * *wait for write*, - * *send notification*, +1. GATT Server support. This includes setting up the local GATT server on the Android device, new + requests for server operations: + * *wait for read*, + * *wait for write*, + * *send notification*, * *send indication*, * *set characteristic value*, * *set descriptor value*. -2. New conditional requests: +2. New conditional requests: * *wait if*, * *wait until*. 3. BLE operations are no longer called from the main thread. @@ -159,137 +173,14 @@ Check out [migration guide](MIGRATION.md). ## Usage -A `BleManager` instance is responsible for connecting and communicating with a single peripheral. -Having multiple instances of the manager is possible to connect with multiple devices simultaneously. -The `BleManager` must be extended with your implementation where you define the high level device's API. - -```java -class MyBleManager extends BleManager { - private static final String TAG = "MyBleManager"; - - private BluetoothGattCharacteristic fluxCapacitorControlPoint; - - public MyBleManager(@NonNull final Context context) { - super(context); - } - - @Override - public int getMinLogPriority() { - // Use to return minimal desired logging priority. - return Log.VERBOSE; - } - - @Override - public void log(int priority, @NonNull String message) { - // Log from here. - Log.println(priority, TAG, message); - } - - @NonNull - @Override - protected BleManagerGattCallback getGattCallback() { - return new MyGattCallbackImpl(); - } - - private class MyGattCallbackImpl extends BleManagerGattCallback { - @Override - protected boolean isRequiredServiceSupported(@NonNull BluetoothGatt gatt) { - // Here get instances of your characteristics. - // Return false if a required service has not been discovered. - BluetoothGattService fluxCapacitorService = gatt.getService(FLUX_SERVICE_UUID); - if (fluxCapacitorService != null) { - fluxCapacitorControlPoint = fluxCapacitorService.getCharacteristic(FLUX_CHAR_UUID); - } - return fluxCapacitorControlPoint != null; - } - - @Override - protected void initialize() { - // Initialize your device. - // This means e.g. enabling notifications, setting notification callbacks, - // sometimes writing something to some Control Point. - // Kotlin projects should not use suspend methods here, which require a scope. - requestMtu(517) - .enqueue(); - } - - @Override - protected void onServicesInvalidated() { - // This method is called when the services get invalidated, i.e. when the device - // disconnects. - // References to characteristics should be nullified here. - fluxCapacitorControlPoint = null; - } - } - - // Here you may add some high level methods for your device: - public void enableFluxCapacitor() { - // Do the magic. - writeCharacteristic(fluxCapacitorControlPoint, Flux.enable(), BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) - .enqueue() - } -} -``` +See [Usage](USAGE.md) for more details. -The [BleManager](https://github.com/NordicSemiconductor/Android-BLE-Library/blob/master/ble/src/main/java/no/nordicsemi/android/ble/BleManager.java) -class exposes high level API for connecting and communicating with Bluetooth LE peripherals. - -```java -connect(bluetoothDevice) - // Automatic retries are supported, in case of 133 error. - .retry(3 /* times, with */, 100 /* ms interval */) - // A connection timeout can be set. This is additional to the Android's connection timeout which is 30 seconds. - .timeout(15_000 /* ms */) - // The auto connect feature from connectGatt is available as well - .useAutoConnect(true) - // This API can be set on any Android version, but will only be used on devices running Android 8+ with - // support to the selected PHY. - .usePreferredPhy(PhyRequest.PHY_LE_1M_MASK | PhyRequest.PHY_LE_2M_MASK | PhyRequest.PHY_LE_CODED_MASK) - // A connection timeout can be also set. This is additional to the Android's connection timeout which is 30 seconds. - .timeout(15_000 /* ms */) - // Each request has number of callbacks called in different situations: - .before(device -> { /* called when the request is about to be executed */ }) - .done(device -> { /* called when the device has connected, has required services and has been initialized */ }) - .fail(device, status -> { /* called when the request has failed */ }) - .then(device -> { /* called when the request was finished with either success, or a failure */ }) - // Each request must be enqueued. - // Kotlin projects can use suspend() or suspendForResult() instead. - // Java projects can also use await() which is blocking. - .enqueue() -``` +## Examples -```java -writeCharacteristic(someCharacteristic, someData, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) - // Outgoing data can use automatic splitting. - .split(customSplitter, progressCallback /* optional */) - // .split() with no parameters uses the default MTU splitter. - // Kotlin projects can use .splitWithProgressAsFlow(customSplitter) to get the progress as Flow. - .before(device -> { /* called when the request is about to be executed */ }) - .with(device, data -> { /* called when the request has been executed */ }) - .done(device -> { /* called when the request has completed successfully */ }) - .fail(device, status -> { /* called when the request has failed */ }) - .invalid({ /* called when the request was invalid, i.e. the target device or given characteristic was null */ }) - .then(device -> { /* called when the request was finished with either success, or a failure */ }) - // Remember to enqueue each request. - .enqueue() -``` +#### Trivia game -```java -readCharacteristic(someCharacteristic) - // Incoming data can use automatic merging. - .merge(customMerger, progressCallback /* optional */) - // Kotlin projects can use .mergeWithProgressAsFlow(customMerger) to get the progress as Flow. - // Incoming packets can also be filtered, so that not everything goes to the merger. - .filter(dataFilter) - // Complete, merged packets can also be filtered. - .filterPacket(packetFilter) - // [...] - .with(device, data -> { /* called when the data have been received */ }) - // [...] - // Once again, remember to enqueue each request! - .enqueue() -``` -All requests are automatically enqueued and executed sequentially. +The [Trivia game](https://github.com/NordicSemiconductor/Android-BLE-Library/tree/main/examples/trivia) +is an example demonstrating client and server. #### GATT Client Example @@ -300,6 +191,10 @@ demands encryption as an illustration of best-practice. You can run this client on one device and a complimenting server on another (see the next section). +> Note: + This project is not maintained actively. It is provided as an example only and may not be + migrated to latest version. + #### GATT Server example Starting from version 2.2 you may now define and use the GATT server in the BLE Library. @@ -309,6 +204,10 @@ server provided as a foreground service. There's a simple UI with a text field t the value of a characteristic that can be read and subscribed to. This characteristic also demands encryption as an illustration of best-practice. +> Note: + This project is not maintained actively. It is provided as an example only and may not be + migrated to latest version. + #### More examples Find the simple example here [Android nRF Blinky](https://github.com/NordicSemiconductor/Android-nRF-Blinky).