Git Product home page Git Product logo

blessed-android-coroutines's Introduction

BLESSED for Android with Coroutines - BLE made easy

Jitpack Link Downloads Android Build

BLESSED is a very compact Bluetooth Low Energy (BLE) library for Android 8 and higher, that makes working with BLE on Android very easy. It is powered by Kotlin's Coroutines and turns asynchronous GATT methods into synchronous methods! It is based on the Blessed Java library and has been rewritten in Kotlin using Coroutines.

Installation

This library is available on Jitpack. Include the following in your projects's build.gradle file:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Include the following in your app's build.gradle file under dependencies block:

dependencies {
    ...
    implementation "com.github.weliem:blessed-android-coroutines:$version"
}

where $version is the latest published version in Jitpack Jitpack

Adding permissions

If you plan on supporting older devices that are on Android 11 and below, then you need to add the below permissions to your AndroidManifest.xml file:

    <!-- Needed to target Android 11 and lower   -->
    <!-- Link: https://developer.android.com/guide/topics/connectivity/bluetooth/permissions#declare-android11-or-lower-->
    <uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:maxSdkVersion="30" />

    <!-- Link: https://developer.android.com/guide/topics/connectivity/bluetooth/permissions  -->
    <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

Overview of classes

The library consists of 5 core classes and corresponding callback abstract classes:

  1. BluetoothCentralManager, for scanning and connecting peripherals
  2. BluetoothPeripheral, for all peripheral related methods
  3. BluetoothPeripheralManager, and its companion abstract class BluetoothPeripheralManagerCallback
  4. BluetoothCentral
  5. BluetoothBytesParser

The BluetoothCentralManager class is used to scan for devices and manage connections. The BluetoothPeripheral class is a replacement for the standard Android BluetoothDevice and BluetoothGatt classes. It wraps all GATT related peripheral functionality.

The BluetoothPeripheralManager class is used to create your own peripheral running on an Android phone. You can add service, control advertising, and deal with requests from remote centrals, represented by the BluetoothCentral class. For more about creating your own peripherals see the separate guide: creating your own peripheral

The BluetoothBytesParser class is a utility class that makes parsing byte arrays easy. You can also use it to construct your own byte arrays by adding integers, floats, or strings.

Scanning

The BluetoothCentralManager class has several differrent scanning methods:

fun scanForPeripherals(resultCallback: (BluetoothPeripheral, ScanResult) -> Unit, scanError: (ScanFailure) -> Unit )
fun scanForPeripheralsWithServices(serviceUUIDs: Array<UUID>, resultCallback: (BluetoothPeripheral, ScanResult) -> Unit, scanError: (ScanFailure) -> Unit)
fun scanForPeripheralsWithNames(peripheralNames: Array<String>, resultCallback: (BluetoothPeripheral, ScanResult) -> Unit,  scanError: (ScanFailure) -> Unit)
fun scanForPeripheralsWithAddresses(peripheralAddresses: Array<String>, resultCallback: (BluetoothPeripheral, ScanResult) -> Unit, scanError: (ScanFailure) -> Unit)
fun scanForPeripheralsUsingFilters(filters: List<ScanFilter>,resultCallback: (BluetoothPeripheral, ScanResult) -> Unit, scanError: (ScanFailure) -> Unit)

They all work in the same way and take an array of either service UUIDs, peripheral names, or mac addresses. When a peripheral is found your callback lambda will be called with the BluetoothPeripheral object and a ScanResult object that contains the scan details. The method scanForPeripheralsUsingFilters is for scanning using your own list of filters. See Android documentation for more info on the use of ScanFilter. A second lambda is used to deliver any scan failures.

So in order to setup a scan for a device with the Bloodpressure service or HeartRate service, you do:

val BLP_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb")
val HRS_SERVICE_UUID = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb")

central.scanForPeripheralsWithServices(arrayOf(BLP_SERVICE_UUID, HRS_SERVICE_UUID)            
    { peripheral, scanResult ->
        Timber.i("Found peripheral '${peripheral.name}' with RSSI ${scanResult.rssi}")
        central.stopScan()
        connectPeripheral(peripheral)
    },
    { scanFailure -> Timber.e("scan failed with reason $scanFailure") })

The scanning functions are not suspending functions and simply use a lambda function to receive the results.

Note Only 1 of these 4 types of scans can be active at one time! So call stopScan() before calling another scan.

Connecting to devices

There are 3 ways to connect to a device:

suspend fun connectPeripheral(peripheral: BluetoothPeripheral): Unit
fun autoConnectPeripheral(peripheral: BluetoothPeripheral)
fun autoConnectPeripheralsBatch(batch: Set<BluetoothPeripheral>)

The method connectPeripheral is a suspending function that will try to immediately connect to a device that has already been found using a scan. This method will time out after 30 seconds or less, depending on the device manufacturer, and a ConnectionFailedException will be thrown. Note that there can be only 1 outstanding connectPeripheral. So if it is called multiple times only 1 will succeed.

scope.launch {
    try {
        central.connectPeripheral(peripheral)
    } catch (connectionFailed: ConnectionFailedException) {
        Timber.e("connection failed")
    }
}

The method autoConnectPeripheral will not suspend and is for re-connecting to known devices for which you already know the device's mac address. The BLE stack will automatically connect to the device when it sees it in its internal scan. Therefore, it may take longer to connect to a device but this call will never time out! So you can issue the autoConnect command and the device will be connected whenever it is found. This call will also work when the device is not cached by the Android stack, as BLESSED takes care of it! In contrary to connectPeripheral, there can be multiple outstanding autoConnectPeripheral requests.

The method autoConnectPeripheralsBatch is for re-connecting to multiple peripherals in one go. Since the normal autoConnectPeripheral may involve scanning, if peripherals are uncached, it is not suitable for calling very fast after each other, since it may trigger scanner limitations of Android. So use autoConnectPeripheralsBatch if you want to re-connect to many known peripherals.

If you know the mac address of your peripheral you can obtain a BluetoothPeripheral object using:

val peripheral = central.getPeripheral("CF:A9:BA:D9:62:9E")

After issuing a connect call, you can observe the connection state of peripherals:

central.observeConnectionState { peripheral, state ->
    Timber.i("Peripheral ${peripheral.name} has $state")
}

To disconnect or to cancel an outstanding connectPeripheral() or autoConnectPeripheral(), you call:

suspend fun cancelConnection(peripheral: BluetoothPeripheral): Unit

The function will suspend until the peripheral is disconnected.

Service discovery

The BLESSED library will automatically do the service discovery for you. When the CONNECTED state is reached, the services have also been discovered.

In order to get the services you can use methods like getServices() or getService(UUID). In order to get hold of characteristics you can call getCharacteristic(UUID) on the BluetoothGattService object or call getCharacteristic() on the BluetoothPeripheral object.

This callback is the proper place to start enabling notifications or read/write characteristics.

Reading and writing

Reading and writing to characteristics/descriptors is done using the following methods:

suspend fun readCharacteristic(serviceUUID: UUID, characteristicUUID: UUID): ByteArray
suspend fun readCharacteristic(characteristic: BluetoothGattCharacteristic): ByteArray
suspend fun writeCharacteristic(serviceUUID: UUID, characteristicUUID: UUID, value: ByteArray, writeType: WriteType): ByteArray
suspend fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, value: ByteArray, writeType: WriteType): ByteArray

suspend fun readDescriptor(descriptor: BluetoothGattDescriptor): ByteArray
suspend fun writeDescriptor(descriptor: BluetoothGattDescriptor, value: ByteArray): ByteArray

All methods are suspending and will return the result of the operation. The method readCharacteristic will return the ByteArray that has been read. It will throw IllegalArgumentException if the characteristic you provide is not readable, and it will throw GattException if the read was not successful.

If you want to write to a characteristic, you need to provide a value and a writeType. The writeType is usually WITH_RESPONSE or WITHOUT_RESPONSE. If the write type you specify is not supported by the characteristic it will throw IllegalArgumentException. The method will return the bytes that were written or an empty byte array in case something went wrong.

There are 2 ways to specify which characteristic to use in the read/write method:

  • Using its serviceUUID and characteristicUUID
  • Using the BluetoothGattCharacteristic reference directly

For example:

peripheral.getCharacteristic(DIS_SERVICE_UUID, MANUFACTURER_NAME_CHARACTERISTIC_UUID)?.let {
    val manufacturerName = peripheral.readCharacteristic(it).asString()
    Timber.i("Received: $manufacturerName")
}

val model = peripheral.readCharacteristic(DIS_SERVICE_UUID, MODEL_NUMBER_CHARACTERISTIC_UUID).asString()
Timber.i("Received: $model")

Note that there are also some extension methods like asString() and asUInt8() to quickly turn byte arrays in Strings or UInt8s.

Turning notifications on/off

You can observe notifications/indications and receive them in the callback lambda. All the necessary operations like writing to the Client Characteristic Configuration descriptor are handled by Blessed. So all you need to do is:

peripheral.getCharacteristic(BLP_SERVICE_UUID, BLOOD_PRESSURE_MEASUREMENT_CHARACTERISTIC_UUID)?.let {
    peripheral.observe(it) { value ->
        val measurement = BloodPressureMeasurement.fromBytes(value)
        ...
    }
}

To stop observing notifications you call peripheral.stopObserving(characteristic: BluetoothGattCharacteristic)

Bonding

BLESSED handles bonding for you and will make sure all bonding variants work smoothly. During the process of bonding, you will be informed of the process via a number of callbacks:

peripheral.observeBondState {
    Timber.i("Bond state is $it")
}

In most cases, the peripheral will initiate bonding either at the time of connection or when trying to read/write protected characteristics. However, if you want you can also initiate bonding yourself by calling createBond on a peripheral. There are two ways to do this:

  • Calling createBond when not yet connected to a peripheral. In this case, a connection is made and bonding is requested.
  • Calling createBond when already connected to a peripheral. In this case, only the bond is created.

It is also possible to remove a bond by calling removeBond. Note that this method uses a hidden Android API and may stop working in the future. When calling the removeBond method, the peripheral will also disappear from the settings menu on the phone.

Lastly, it is also possible to automatically issue a PIN code when pairing. Use the method central.setPinCodeForPeripheral to register a 6 digit PIN code. Once bonding starts, BLESSED will automatically issue the PIN code and the UI dialog to enter the PIN code will not appear anymore.

Requesting a higher MTU to increase throughput

The default MTU is 23 bytes, which allows you to send and receive byte arrays of MTU - 3 = 20 bytes at a time. The 3 bytes overhead are used by the ATT packet. If your peripheral supports a higher MTU, you can request that by calling:

val mtu = peripheral.requestMtu(185)

The method will return the negotiated MTU value. Note that you may not get the value you requested if the peripheral doesn't accept your offer. If you simply want the highest possible MTU, you can call peripheral.requestMtu(BluetoothPeripheral.MAX_MTU) and that will lead to receiving the highest possible MTU your peripheral supports.

Once the MTU has been set, you can always access it by calling peripheral.currentMtu. If you want to know the maximum length of the byte arrays that you can write, you can call the method peripheral.getMaximumWriteValueLength(). Note that the maximum value depends on the write type you want to use.

Long reads and writes

The library also supports so called 'long reads/writes'. You don't need to do anything special for them. Just read a characteristic or descriptor as you normally do, and if the characteristic's value is longer than MTU - 1, then a series of reads will be done by the Android BLE stack. But you will simply receive the 'long' characteristic value in the same way as normal reads.

Similarly, for long writes, you just write to a characteristic or descriptor and the Android BLE stack will take care of the rest. But keep in mind that long writes only work with WriteType.WITH_RESPONSE and the maximum length of your byte array should be 512 or less. Note that not all peripherals support long reads/writes so this is not guaranteed to work always.

Status codes

When connecting or disconnecting, the callback methods will contain a parameter HciStatus status. This enum class will have the value SUCCESS if the operation succeeded and otherwise it will provide a value indicating what went wrong.

Similarly, when doing GATT operations, the callbacks methods contain a parameter GattStatus status. These two enum classes replace the int status parameter that Android normally passes.

Bluetooth 5 support

As of Android 8, Bluetooth 5 is natively supported. One of the things that Bluetooth 5 brings, is new physical layer options, called Phy that either give more speed or longer range. The options you can choose are:

  • LE_1M, 1 mbit PHY, compatible with Bluetooth 4.0, 4.1, 4.2 and 5.0
  • LE_2M, 2 mbit PHY for higher speeds, requires Bluetooth 5.0
  • LE_CODED, Coded PHY for long range connections, requires Bluetooth 5.0

You can set a preferred Phy by calling:

suspend fun setPreferredPhy(txPhy: PhyType, rxPhy: PhyType, phyOptions: PhyOptions): Phy

By calling setPreferredPhy() you indicate what you would like to have but it is not guaranteed that you get what you ask for. That depends on what the peripheral will actually support and give you. If you are requesting LE_CODED you can also provide PhyOptions which has 3 possible values:

  • NO_PREFERRED, for no preference (use this when asking for LE_1M or LE_2M)
  • S2, for 2x long range
  • S8, for 4x long range

The result of this negotiation will be received as a Phy object that is returned by setPrefferedPhy

As you can see the Phy for sending and receiving can be different but most of the time you will see the same Phy for both. If you don't call setPreferredPhy(), Android seems to pick PHY_LE_2M if the peripheral supports Bluetooth 5. So in practice you only need to call setPreferredPhy if you want to use PHY_LE_CODED.

You can request the current values at any point by calling:

suspend fun readPhy(): Phy

It will return the current Phy

Example application

An example application is provided in the repo. It shows how to connect to Blood Pressure meters, Heart Rate monitors, Weight scales, Glucose Meters, Pulse Oximeters, and Thermometers, read the data, and show it on screen. It only works with peripherals that use the Bluetooth SIG services. Working peripherals include:

  • Beurer FT95 thermometer
  • GRX Thermometer (TD-1241)
  • Masimo MightySat
  • Nonin 3230
  • Indiehealth scale
  • A&D 352BLE scale
  • A&D 651BLE blood pressure meter
  • Beurer BM57 blood pressure meter
  • Soehnle Connect 300/400 blood pressure meter
  • Polar H7/H10/OH1 heartrate monitors
  • Contour Next One glucose meter
  • Accu-Chek Instant glucose meter

blessed-android-coroutines's People

Contributors

bbilger avatar cren90 avatar nisrulz avatar weliem 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

blessed-android-coroutines's Issues

Not detecting Heart Rate Sensor (0x180D)-> When when scanning with Service

I am trying to find my device using the following code
var deviceMap = HashMap<String, BluetoothPeripheral>()
val HEART_RATE_NOTIFY_SERVICE_UUID = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb")
val WEIGHT_SCALE_SERVICE = UUID.fromString("0000181d-0000-1000-8000-00805f9b34fb")
var list = arrayOf(
WEIGHT_SCALE_SERVICE,
HEART_RATE_NOTIFY_SERVICE_UUID
)
centralManager.scanForPeripheralsWithServices(list, { peripheral, scanResult ->
Log.d("Device", peripheral.address)
deviceMap.put(peripheral.address, peripheral)
}, { error ->
})

It is able to detect weight scale but not detecting heart rate.

BluetoothPeripheral GATT error NO_RESOURCES

E/BluetoothPeripheral: writing to characteristic <00002a2b-0000-1000-8000-00805f9b34fb> failed, status 'NO_RESOURCES'
E/BluetoothHandler$handlePeripheral: com.welie.blessed.GattException: GATT error NO_RESOURCES (128)
at com.welie.blessed.BluetoothPeripheral$writeCharacteristic$4$result$1.onCharacteristicWrite(BluetoothPeripheral.kt:861)
at com.welie.blessed.BluetoothPeripheral$bluetoothGattCallback$1$onCharacteristicWrite$1.invokeSuspend(BluetoothPeripheral.kt:201)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)

I am using Omron BP5450. Running the example program. The java version of the library works fine. Phone is Samsung Galaxy A52 5G.

Trying to get 16 byte from Characteristic but ouputs only zeros

I can can read from this characteristics in the NRF app as well as using the flutter_blue library, which we used for an older app before.

I'm using this code to read from a characteristic.

peripheral.getCharacteristic(SERVICE_UUID, NOTIFICATION_UUID)?.let {
   val test = peripheral.readCharacteristic(it)
   Timber.d("${test.size}")
   Timber.d("${test.asList()}")
}

As output, I only get zeros

/BluetoothPeripheral: reading characteristic <0000ff01-0000-1000-8000-00805f9b34fb>
D/BluetoothHandler: 16
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

That am I missing? I tried it on Android 11 and on an old Android 9 phone with the same result both times

Wrong implementation of bytesToFloat

Hello, I receive wrong values when trying to read a FORMAT_FLOAT as shown by this test

package com.welie.blessed

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder

class BluetoothBytesParserTest {

    @Test
    fun getFloatValueLE() {
        val byteParser = BluetoothBytesParser(
            ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putFloat(1.1f).array(),
            ByteOrder.LITTLE_ENDIAN
        )
        assertEquals(byteParser.getFloatValue(BluetoothBytesParser.FORMAT_FLOAT), 1.1f)
    }

    @Test
    fun getFloatValueBE() {
        val byteParser = BluetoothBytesParser(
            ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(1.1f).array(),
            ByteOrder.BIG_ENDIAN
        )
        assertEquals(byteParser.getFloatValue(BluetoothBytesParser.FORMAT_FLOAT), 1.1f)
    }

    @Test
    fun setFloatValueLE() {
        val byteParser = BluetoothBytesParser(ByteArray(4), ByteOrder.LITTLE_ENDIAN)
        val floatValue = byteParser.setFloatValue(1.1f, BluetoothBytesParser.FORMAT_FLOAT)
        assertTrue(floatValue)
        assertEquals(ByteBuffer.wrap(byteParser.value).order(ByteOrder.LITTLE_ENDIAN).float, 1.1f)
    }

    @Test
    fun setFloatValueBE() {
        val byteParser = BluetoothBytesParser(ByteArray(4), ByteOrder.BIG_ENDIAN)
        val floatValue = byteParser.setFloatValue(1.1f, BluetoothBytesParser.FORMAT_FLOAT)
        assertTrue(floatValue)
        assertEquals(ByteBuffer.wrap(byteParser.value).order(ByteOrder.BIG_ENDIAN).float, 1.1f)
    }
}

use library on android wear 3 -> bonding failed

so this is a special one ... i'm trying to use your library on an android wear 3 watch (samsung galaxy watch 4).

on my phone your demo-app is asking me if i want to bond with my omron evolv and after pressing yes everything works.
on my watch this popup is not shown and it immediately logs "bonding failed"

i don't think that you tried your library on a watch and especially not on wear os 3 but maybe you have an idea.

log from my watch:

2022-08-04 15:48:54.924 15479-15479/com.example.bt_wear_spielwiese D/BluetoothPeripheral: starting bonding with 'BLESmart_0000021FEC21E500D0D6' (EC:21:E5:00:D0:D6)
2022-08-04 15:48:54.928 15479-16079/com.example.bt_wear_spielwiese I/BluetoothHandler$connectPeripheral: Bond state is BONDING
2022-08-04 15:48:54.938 15479-15479/com.example.bt_wear_spielwiese D/BluetoothPeripheral: pairing request received: PAIRING_VARIANT_CONSENT (3)
2022-08-04 15:48:55.028 15479-15479/com.example.bt_wear_spielwiese E/BluetoothPeripheral: bonding failed for 'BLESmart_0000021FEC21E500D0D6', disconnecting device
2022-08-04 15:48:55.031 15479-16055/com.example.bt_wear_spielwiese D/BluetoothGatt: cancelOpen() - device: EC:21:E5:00:D0:D6
2022-08-04 15:48:55.031 15479-16079/com.example.bt_wear_spielwiese I/BluetoothHandler$connectPeripheral: Bond state is BONDING_FAILED
2022-08-04 15:48:55.060 15479-16038/com.example.bt_wear_spielwiese D/BluetoothGatt: onClientConnectionState() - status=0 clientIf=7 device=EC:21:E5:00:D0:D6
2022-08-04 15:48:55.083 15479-16038/com.example.bt_wear_spielwiese D/BluetoothPeripheral: disconnected 'BLESmart_0000021FEC21E500D0D6' on request
2022-08-04 15:48:55.085 15479-16038/com.example.bt_wear_spielwiese D/BluetoothGatt: close()
2022-08-04 15:48:55.097 15479-16038/com.example.bt_wear_spielwiese D/BluetoothGatt: unregisterApp() - mClientIf=7
2022-08-04 15:48:55.123 15479-16060/com.example.bt_wear_spielwiese E/BluetoothHandler: Peripheral BLESmart_0000021FEC21E500D0D6 has DISCONNECTED

Unreliable notifications

Hi
I have a device using a nRF52 chip that sends a notification immediately after the notification is enabled to initially populate some fields in my Android app. I noted that this is not reliable and could narrow it down to something happening on Android, as I control the nRF52 and could verify that the notification is indeed sent. There is no log entry, it just does not happen. I have 4 characteristics, but get notifications for any number between 0 and 4 when I initially connect. Later, the notifications do work.

What I tried so far:

  • Delaying the observe() call all together or individually spaced for up to 2 seconds in between calls
  • Delaying the response from the nRF52 in the same manner

My code to set up notifications look like that:
private suspend fun setupAmountNotification(peripheral: BluetoothPeripheral) { peripheral.getCharacteristic( UUID.fromString(SERVICE_UUID), UUID.fromString(CHAR_AMOUNT_UUID) )?.let { peripheral.observe(it) {value -> Log.i(TAG, "Notification for amount") val parser = BluetoothBytesParser(value, ByteOrder.LITTLE_ENDIAN) runOnUiThread{ onAmountNotification(parser.getIntValue(FORMAT_UINT32)) } } } }

In turn, I get this log (the other observe functions are analogous):
D/MainActivity: Peripheral eo-4095 has CONNECTED D/BluetoothGatt: setCharacteristicNotification() - uuid: e44e1403-14b3-457c-xxxx-34bf34932966 enable: true D/BluetoothGatt: setCharacteristicNotification() - uuid: e44e1402-14b3-457c-xxxx-34bf34932966 enable: true D/BluetoothGatt: setCharacteristicNotification() - uuid: e44e1401-14b3-457c-xxxx-34bf34932966 enable: true D/BluetoothGatt: setCharacteristicNotification() - uuid: e44e1404-14b3-457c-xxxx-34bf34932966 enable: true I/MainActivity: Notification for reservoir I/MainActivity: Notification for status

Write success after 2nd connect

Hello!

I am encountering a problem and am somewhat at odds as to how to solve it.

Scenario:
We have a bluetooth device that has been tried and tested. This year I wrote a new android app to connect to the device. Since we were often plagued with 133 errors I decided to give this library a try.

My problem:
If the Bluetoothdevice does a fresh boot and I try to write a characteristic then it does nothing. But it does not even throw an error. Even android's own BluetoothGatt write call returns status=0 ( whatever that is worth with writetype=NO_RESPONSE)...

Once I disconnect, rescan, reconnect to the device I can write without any problems until the bluetooth device gets bootet again.

I used the BluetoothHandler class from the examples. And since it is a singleton I really don't understand why it only works the second time around.

I tested this by added a writeCharacteristic to the handlePeripheral() method so that after a connection is made we can send some data and directly know if the device gets the data. And like I said, after the second connection the data always gets transmitted.

Anyone have a clue as to what is going on here? Or how I could try to further debug this situation?

Running this on a Samsung A20e phone

Greetings

Receiving UInt8 as ByteArray

The question is as follows:

I have devices, that send a uint8_t array as bytes to an Android device. In embedded C uint8_t and byte as the same and range from 0 to 255. However, this library uses ByteArrays, which are signed and accept values ranging from -127 to 127. So that happens if I connect to these devices and how can I make sure to receive the correct data?

Mark location permissions with maxSdkVersion=30

Location permissions are only required for API 30 and below. Marking those to be only for maxSdkVersion=30 should be part of the lib.

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

I already have a branch with the changes, but wanted to open an issue to discuss if there are any blockers or questions.

ReadCharacteristic returns truncated data

I'm using the library both for advertising from device1 and scanning from device2.

When attempting to read a characteristic on device1, I get the callback in device1 for BluetoothPeripheralManagerCallback.onCharacteristicRead and then perform characteristic.setValue(data) which is ~200 characters. I get the first 22 chars returned to device2 peripheral.readCharacteristic(uuid, uuid).

If I scan using the nRF app, on device2 I see the full data payload.
I've also tried requestMtu(MAX_MTU) and that hasn't helped.

It appears as though the suspendCoroutine in BluetoothPeripheral.readCharacteristic line 790 is resuming early on the first value being read.

compose ui

Has Blessed been tested on Compose UI?

Not showing temp or anything in the screen

Hello, I found out this by coincidence while studying Bluetooth and coroutines on android studio kotlin and I found out your library in java which is amazing and works amazingly, I found out this one too just now and wanted to run a test if it worked as smooth as the java one but the text when you measure temperature is not showing, didn't test anything else but that for now but I don't know if you are going to look into this, in the logcat it all works wonderful.

ScanRecord not available in BluetoothPeripheral

Hi,

I'm using your library and everything is fine but currently we have a request to check for advertising flags once we get scanning results. Currently method :
private fun sendScanResult(result: ScanResult) { scope.launch { if (isScanning) { val peripheral = getPeripheral(result.device.address) peripheral.setDevice(result.device) currentResultCallback?.invoke(peripheral, result) } } }

assigns only device (BluetoothDevice) but there is no support for ScanRecord.
Is this something that is in planned to be supported by the lib, or is there some workaround?
Are you ok to create a PR to support this?

Best regards,
Nemanja

BluetoothBytesParser array index out of bounds

E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-9
Process: com.welie.blessedexample, PID: 22490
java.lang.ArrayIndexOutOfBoundsException: length=7; index=7
at com.welie.blessed.BluetoothBytesParser.setCurrentTime(BluetoothBytesParser.kt:559)
at com.welie.blessedexample.BluetoothHandler$handlePeripheral$1.invokeSuspend(BluetoothHandler.kt:54)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:39)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

value size is reset to 7 in call to setDateTime() after being set to 10 in setCurrentTime()

fun setCurrentTime(calendar: Calendar): Boolean {
    value = ByteArray(10)
    setDateTime(calendar)
    value[7] = ((calendar[Calendar.DAY_OF_WEEK] + 5) % 7 + 1).toByte()
    value[8] = (calendar[Calendar.MILLISECOND] * 256 / 1000).toByte()
    value[9] = 1
    return true
}

/**
 * Sets the byte array to represent the current date in CurrentTime format
 *
 * @param calendar the calendar object representing the current date
 * @return flase if the calendar object was null, otherwise true
 */
fun setDateTime(calendar: Calendar): Boolean {
    value = ByteArray(7)
    value[0] = calendar[Calendar.YEAR].toByte()
    value[1] = (calendar[Calendar.YEAR] shr 8).toByte()
    value[2] = (calendar[Calendar.MONTH] + 1).toByte()
    value[3] = calendar[Calendar.DATE].toByte()
    value[4] = calendar[Calendar.HOUR_OF_DAY].toByte()
    value[5] = calendar[Calendar.MINUTE].toByte()
    value[6] = calendar[Calendar.SECOND].toByte()
    return true
}

Advertise Fails DATA_TOO_LARGE

I'm following the guide at Server.MD to POC an android app as a heart rate monitor. I get a DATA_TOO_LARGE error on attempting to advertise. Here are the pertinent pieces of the POC:

private val hrmService = UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb")
private val hrmCharacteristic = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb")
private val service = BluetoothGattService(hrmService, BluetoothGattService.SERVICE_TYPE_PRIMARY)
private val characteristic = BluetoothGattCharacteristic(hrmCharacteristic, PROPERTY_READ or PROPERTY_INDICATE, PERMISSION_READ_ENCRYPTED_MITM)
private val peripheralManager by lazy { BluetoothPeripheralManager(this, getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager, callback) }

fun setupAdvertising() {
        CoroutineScope(Dispatchers.IO).launch {
            hrFlow.collect {
                it?.let {
                    val bytes = ByteBuffer.allocate(Int.SIZE_BYTES).apply {
                        putShort(it)
                    }.array()
                    peripheralManager.notifyCharacteristicChanged(bytes, characteristic)
                }
            }
        }

        service.addCharacteristic(characteristic)
        peripheralManager.add(service)

        val advertiseSettings = AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
            .setConnectable(true)
            .setTimeout(0)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
            .build()

        val advertiseData = AdvertiseData.Builder()
            .setIncludeTxPowerLevel(true)
            .addServiceUuid(ParcelUuid(hrmService))
            .build()

        val scanResponse = AdvertiseData.Builder()
            .setIncludeDeviceName(true)
            .build()

        peripheralManager.startAdvertising(advertiseSettings, scanResponse, advertiseData)
    }

minSdk 26

Hello Weliem,
first of all, congrats on your SDK. My problem is that I'd like to import it into my app but since I need to support devices with at least Oreo on board (Api Level 24) it looks like I cannot because your SDK's minSdk is 26. Is there a reason for that? Thanks

Notify Listeners when Scan Fails

Currently, the BluetoothCentralManager gets the failure status and logs it, but doesn't send it to listeners:

    private fun sendScanFailed(scanFailure: ScanFailure) {
        currentCallback = null
        currentFilters = null
        scope.launch {
            Logger.e(TAG, "scan failed with error code %d (%s)", scanFailure.value, scanFailure)
         //   bluetoothCentralManagerCallback.onScanFailed(scanFailure)
        }
    }

Looks like it's just a matter of uncommenting line 124.

"IllegalStateException: Already resumed" on disconnected internal callback

Hi

Using 0.3.0, we sometime have this exception

Fatal Exception: java.lang.IllegalStateException: Already resumed
       at kotlin.coroutines.SafeContinuation.resumeWith(SafeContinuationJvm.kt:44)
       at com.welie.blessed.BluetoothCentralManager$cancelConnection$2$1.onDisconnectedPeripheral(BluetoothCentralManager.kt:538)
       at com.welie.blessed.BluetoothCentralManager$internalCallback$1$disconnected$1.invokeSuspend(BluetoothCentralManager.kt:209)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
       at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:39)
       at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)

There is no way to catch it outise of the lib because the exception raise in an internal callback here :
https://github.com/weliem/blessed-android-coroutines/blob/0.3.0/blessed/src/main/java/com/welie/blessed/BluetoothCentralManager.kt#L209

observeConnectionState - 5 second delay

Hi, First off, Thank you for writing this library. It has made dealing with BLE a much better experience.

My question is, is there a faster way to get feedback from observeConnectionState?
I have a total of 6 BLE devices that I am connected to and my requirement is to stop all of the connections if any of the devices gets disconnected.

The callback works great and I am able to disconnect all of the devices if 1 becomes disconnected but there's about a 5 second delay from when I pull the battery off 1 device until the time the callback gets called?

How to stop observing connection state

I am observing connection during onResume and I can't figure out way to stopObserving this. Is there any way to do so?

centralManager.observeConnectionState { peripheral, state ->
Log.d("Connected Device",peripheral.address)
}

Release name is wrong

I think it is supposed to be v0.1.3, unless there is a missing tag of 0.1.4 and the tag 0.1.3 got overwritten or something.

Screenshot 2021-11-29 at 13 02 54

Screenshot 2021-11-29 at 13 03 21

Get all records at once

Currently the setupGLXnotifications listens and returns all the records one by one, how can I retrieve all the records at once, or find the total records available in the device so i know which is the last one.

I tried returning the last record like that and then checking while it's observing when the last record appears. But on the device I am using it returns the results descending, so the last record appears as the first one.
val command = byteArrayOf(OP_CODE_REPORT_STORED_RECORDS, OPERATOR_LAST_RECORD)

I also tried with "first record" with Byte = 5 but it doesn't get returned. It returns the "Last record" again. I guess these are device specific issues because i have encountered similar behaviours on different devices. So I'm not sure what will be the correct way to handle that.

I would appreciate some pointers to how I can achieve that. How to know when all of the records have been returned so I don't miss any recording.

Send an array of bytes

Hi once again, Im again using your library and I was thinking on a way of, for example I have a device lets say is TD4289 in this example and I want to send an array of bytes such as [0x51, 0x26, 0x00, 0x00, 0x00, 0x00, 0xA3, 0x1A] and read the information that comes with it, should be some data. Could I possibly do that? Send that exact array and await/read the data from the device?

As I dont fully understand bluetooth and BLE yet if you could teach me how to achieve what I just said I would be really glad.

Much thanks.

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.