This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

I'm connecting to device but unable to communicate - Android BLE Nordic Library

I have an Android application running with RxAndroidBle library and I want to change it to Nordic-Ble-Android library. I'm not that good with Bluetooth and the actual version of the app is not developed by me so I'm in trouble trying to make it work.

Our connection with  the wireless bluetooth sensor is following a few steps:

  1.  The APP establishes a connection with the wireless sensor
  2. The APP send a request  connection command frame to wireless sensor
  3. The APP send a command to the wireless sensor to start blinking
  4. User tap on a button on the app to confirm that the APP is blinking
  5. The APP send one more command to confirm the connection and now it's done

If the user does not confirm that the sensor is blinking, the wireless sensor will automatically timeout within 10 seconds.

I'm successfully establishing a connection with the sensor using this method:

   val man = MyBleManager(this@BluetoothService)
                man.setGattCallbacks(this@BluetoothService)
                bleManagers?.put(device, man)

                man.connect(device)
                        .done {

                            Log.d("BLESERVICE", "success")

                            //not working
                            man.sendRequestConnectionCommand(DeviceType.SENSOR)

                            //not working
                            startBlinkingDeviceLed(man, DeviceType.SENSOR)

                            connectionFlowListeners.forEach { it.onConnectionEstablished(device) }


                        }
                        .fail { d, status ->
                            Log.d("BLESERVICE", "fail")
                            devicesList?.remove(device)
                            bleManagers?.remove(device)
                        }
                        .timeout(10000)
                        .enqueue()

The issue is writing characteristcs after that, It seems that I'm doing something wrong.

The current version of the connection with RxAndroidBle is written below:

    fun connectToDevice(device: BraincareDevice, pairColor: Int) {
        BleLogHelper.writeLog("Connecting to ${device.name}")

        val deviceType = if (device is Sensor) DeviceType.SENSOR else DeviceType.DONGLE
        if (deviceType == DeviceType.SENSOR) {
            sensorConnectionSubscription?.dispose()
        } else {
            dongleConnectionSubscription?.dispose()
        }

        val connectionSubscription = device.device.establishConnection(false)
                .flatMapSingle { connection ->
                    if (device is Sensor) {
                        sensorConnection = connection
                        connectedSensor = device
                    } else if (device is Dongle) {
                        dongleConnection = connection
                        connectedDongle = device
                    }
                    BleLogHelper.writeLog("Send Request Connection Command $deviceType")
                    val command = BraincareBluetoothCommandProtocol.createRequestConnectionCommandFrame(deviceType)
                    connection.writeCharacteristic(BraincareBluetoothProtocol.rxCharacteristicUUID, command)
                }
                .delay(300, TimeUnit.MILLISECONDS)
                .subscribe({
                    BleLogHelper.writeLog("Connection Established ${device.type}")

                    if (device.device.connectionState == RxBleConnection.RxBleConnectionState.CONNECTED) {
                        startBlinkingDeviceLed(deviceType, pairColor)
                        connectionFlowListeners.forEach { it.onConnectionEstablished(device) }
                    }
                    
                }

You can see that it's using the connection to write the characteristic that sends the request command frame within .flatMapSingle 

Also, the command to startBlinking is inside the .subscribe method. You can see as well that it's not using any service at all to use the characteristic rxCharacteristicUUID, and looking on Blinky and Toolbox examples given, it seems that I have to initialize the characteristic using gatt.getService, and I got confused because there is no need to use a service for these commands on actual application.

I tried to do exactly like this snippet, but using Android-Ble-Nordic library, but I could not really understand how I have to do that.

Below is my entire version of my BleManager following the examples given on Android-nRF-Blinky and Android-nRF-Toolbox.

package io.b4c.brain4care.modules.ble

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.graphics.Color
import android.support.annotation.NonNull
import android.support.annotation.Nullable
import android.util.Log
import io.b4c.brain4care.BraincareApplication
import io.b4c.brain4care.modules.monitoringservice.BraincareBluetoothManager
import io.b4c.brain4care.modules.monitoringservice.protocol.BraincareBluetoothCommandProtocol
import io.b4c.brain4care.modules.monitoringservice.protocol.BraincareBluetoothProtocol
import io.b4c.brain4care.util.constants.AppConstants
import io.b4c.brain4care.util.enums.DeviceType
import io.b4c.brain4care.util.extensions.toByteStringRepresentation
import io.b4c.brain4care.util.helpers.BleLogHelper
import io.reactivex.schedulers.Schedulers
import no.nordicsemi.android.ble.BleManager


import no.nordicsemi.android.ble.BleManagerCallbacks
import no.nordicsemi.android.ble.data.Data
import no.nordicsemi.android.log.ILogSession
import no.nordicsemi.android.log.LogContract
import no.nordicsemi.android.log.Logger

class MyBleManager(context: Context): BleManager<BleManagerCallbacks>(context) {


    override fun getGattCallback(): BleManagerGattCallback {
        return mGattCallback
    }

    private var mLogSession: ILogSession? = null
    private var rxCharacteristic: BluetoothGattCharacteristic? = null

    private val mGattCallback = object : BleManagerGattCallback() {
        override fun onDeviceDisconnected() {

        }

        override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {

            //?? i don't need this i guess
            val service = gatt.getService(BraincareBluetoothProtocol.serviceDeviceInformationUUID)

            return true
        }

        protected override fun initialize() {
            super.initialize()

            Log.d("BLESERVICE","initializing")

            //I not so sure if I need this
            setNotificationCallback(rxCharacteristic).with(mLedCallback)
            enableNotifications(rxCharacteristic).with(commandCallback)
            enableIndications(rxCharacteristic).with(commandCallback)

        }

    }


    private val commandCallback = object : CommandSentCallback {
        override fun onDataSent(device: BluetoothDevice, data: Data) {
            super.onDataSent(device, data)
            Log.d("BLESERVICE", "sent something")
        }
    }

    private val mLedCallback = object : ManufectureDataCallback() {
        override fun onReceived(device: BluetoothDevice, value: Int) {
            Log.d("BLESERVICE", "received something")
        }

    }

    fun sendRequestConnectionCommand(deviceType: DeviceType){
        Log.d("BLESERVICE","sending request command")
        val command = BraincareBluetoothCommandProtocol.createRequestConnectionCommandFrame(deviceType)
        writeCharacteristic(rxCharacteristic, command).enqueue()
    }

    private fun getRandomPairColor(): Int {
        val colorOptions = AppConstants.lighsyncColorOptions
        val colorIndex = (0 until colorOptions.size).shuffled().first()
        return context.getColor(colorOptions[colorIndex])
    }

    fun sendSetLedProfileCommand(deviceType: DeviceType, color: Int) {
        BleLogHelper.writeLog("Device Blinked $deviceType")
        Log.d("BLESERVICE","sending blink command")

        val command = BraincareBluetoothCommandProtocol.createBlinkLedCommandFrame(Color.red(color), Color.green(color), Color.blue(color), 2)
        writeCharacteristic(rxCharacteristic, command).with(commandCallback).enqueue()

        /*val connection = if (deviceType == DeviceType.SENSOR) BraincareBluetoothManager.sensorConnection else BraincareBluetoothManager.dongleConnection
        BraincareBluetoothManager.connectionFlowListeners.forEach { it.onDeviceBlinkSuccess(color) }
        val disposable = connection?.writeCharacteristic(BraincareBluetoothProtocol.rxCharacteristicUUID, command)
                ?.subscribeOn(Schedulers.io())
                ?.subscribe({
                }, {
                    BleLogHelper.writeError("Error on sendSetLedProfileCommand", it)
                })
        BraincareBluetoothManager.sensorCompositeDisposable.add(disposable ?: return)*/
    }

    /**
     * Sets the log session to log into.
     *
     * @param session nRF Logger log session to log inti, or null, if nRF Logger is not installed.
     */
    fun setLogger(@Nullable session: ILogSession) {
        mLogSession = session
    }

    override fun log(priority: Int, @NonNull message: String) {
        Logger.log(mLogSession, LogContract.Log.Level.fromPriority(priority), message)
        Log.println(priority, "BleManager", message)
    }
}

  • Hello,

    In initialize() you need to obtain the instance of the characteristic your using. You're obtaining the service correctly. After you have the instance, you need to do rxCharacteristic = service.getCharacteristic(some uuid)

    Without it, your always trying to write to null, as the reference isn't set anywhere.

  • Hi Aleksander, thank you for the response.

    I edited the code, but it seems that my wireless sensor is not receiving commands anyway.

      override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
                gatt.beginReliableWrite()
                val service = gatt.getService(BraincareBluetoothProtocol.serviceDeviceInformationUUID)
                rxCharacteristic = service.getCharacteristic(BraincareBluetoothProtocol.rxCharacteristicUUID)
                return true
            }
    
            override fun initialize() {
                setNotificationCallback(rxCharacteristic).with(mLedCallback)
                readCharacteristic(rxCharacteristic).with(mLedCallback).enqueue()
                //enableNotifications(rxCharacteristic).with(mLedCallback).enqueue()
                val color = getRandomPairColor()
                val command = BraincareBluetoothCommandProtocol.createBlinkLedCommandFrame(Color.red(color), Color.green(color), Color.blue(color), 2)
                writeCharacteristic(rxCharacteristic, command).with(object: DataSentCallback{
                    override fun onDataSent(device: BluetoothDevice, data: Data) {
                        Log.d("BLESERVICE", "DATA SENT")
                    }
    
                }).enqueue()
            }
    

    This Log on data sent isn't being fired. I'm confused because before, with RxAndroidBle, I did not specify which service to use, just to write characteristic using the connection object

  • The isRequiredServiceSupported method should return rxCharacteristic != null.

    Also, your should release the reference to this char in onDeviceDisconnected.

    What are the properties of your characteristic? Seems like you're writing it, reading it and seeing notifications callback (enabling notifications is commented out). Are you sure the rxCharacteristic is not null there?

    Could you post a screenshot from nRF Connect with your service and inner characteristics?

  • Ok, I realized that I was trying to find the rxCharacteristc within Device Information Service, but Noridc UART Service was the right one. I'm getting an error 130 now though, "Error on writing characteristic" (Error (0x82): GATT WRONG STATE).

    My connection method on BluetoothService class:

           private fun connect(device: BluetoothDevice, session: ILogSession?) {
                Log.d("BLESERVICE", "connection called")
                // If a device is in managed devices it means that it's already connected, or was connected
                // using autoConnect and the link was lost but Android is already trying to connect to it.
                if (devicesList?.contains(device) == true)
                    return
                devicesList?.add(device)
    
                var manager = bleManagers?.get(device)
                if (manager != null) {
                    if (session != null) {
                        //manager.setLogger(session)
                    }
                    manager.connect(device).enqueue()
                } else {
                    //manager = MyBleManager(application)
                    val man = MyBleManager(this@BluetoothService)
                    man.setGattCallbacks(this@BluetoothService)
                    bleManagers?.put(device, man)
    
                    man.connect(device)
                            .done {
    
                                Log.d("BLESERVICE", "success")
    
                                //Returning error 130 - Error on writing characteristic
                                man.sendRequestConnectionCommand(DeviceType.SENSOR)
    
    
                                connectionFlowListeners.forEach { it.onConnectionEstablished(device) }
    
    
                            }
                            .fail { d, status ->
                                Log.d("BLESERVICE", "fail")
                                devicesList?.remove(device)
                                bleManagers?.remove(device)
                            }
                            .timeout(10000)
                            .enqueue()
                    
                }
            }

    My changes on BluetoothManager class:

    package io.b4c.brain4care.modules.ble
    
    import android.bluetooth.BluetoothDevice
    import android.bluetooth.BluetoothGatt
    import android.bluetooth.BluetoothGattCharacteristic
    import android.content.Context
    import android.graphics.Color
    import android.support.annotation.NonNull
    import android.support.annotation.Nullable
    import android.util.Log
    import io.b4c.brain4care.BraincareApplication
    import io.b4c.brain4care.modules.monitoringservice.BraincareBluetoothManager
    import io.b4c.brain4care.modules.monitoringservice.protocol.BraincareBluetoothCommandProtocol
    import io.b4c.brain4care.modules.monitoringservice.protocol.BraincareBluetoothProtocol
    import io.b4c.brain4care.util.constants.AppConstants
    import io.b4c.brain4care.util.enums.DeviceType
    import io.b4c.brain4care.util.extensions.toByteStringRepresentation
    import io.b4c.brain4care.util.helpers.BleLogHelper
    import io.reactivex.schedulers.Schedulers
    import no.nordicsemi.android.ble.BleManager
    
    
    import no.nordicsemi.android.ble.BleManagerCallbacks
    import no.nordicsemi.android.ble.callback.DataSentCallback
    import no.nordicsemi.android.ble.data.Data
    import no.nordicsemi.android.log.ILogSession
    import no.nordicsemi.android.log.LogContract
    import no.nordicsemi.android.log.Logger
    
    class MyBleManager(context: Context): BleManager<BleManagerCallbacks>(context) {
    
    
    
        override fun getGattCallback(): BleManagerGattCallback {
            return mGattCallback
        }
    
        private var mLogSession: ILogSession? = null
        private var rxCharacteristic: BluetoothGattCharacteristic? = null
    
        private val mGattCallback = object : BleManagerGattCallback() {
            override fun onDeviceDisconnected() {
                rxCharacteristic = null
            }
    
            override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
                val service = gatt.getService(BraincareBluetoothProtocol.serviceNordicUart)
                rxCharacteristic = service.getCharacteristic(BraincareBluetoothProtocol.rxCharacteristicUUID)
                return rxCharacteristic != null
            }
    
            override fun initialize() {
                setNotificationCallback(rxCharacteristic).with(mLedCallback)
                readCharacteristic(rxCharacteristic).with(mLedCallback).enqueue()
                enableNotifications(rxCharacteristic).with(commandCallback).enqueue()
            }
    
        }
    
    
        private val commandCallback = object : CommandaDataCallback {
            override fun onCommandSent(device: BluetoothDevice) {
                //not triggering
                Log.d("BLESERVICE", "on command sent")
            }
    
            override fun onDataReceived(device: BluetoothDevice, data: Data) {
                Log.d("BLESERVICE", "on data received")
            }
    
            override fun onInvalidDataReceived(device: BluetoothDevice, data: Data) {
                Log.d("BLESERVICE", "on invalid data received")
            }
        }
    
        private val mLedCallback = object : ManufectureDataCallback() {
            override fun onReceived(device: BluetoothDevice, value: Int) {
                Log.d("BLESERVICE", "received something")
    
            }
    
        }
    
    
        fun sendRequestConnectionCommand(deviceType: DeviceType){
            Log.d("BLESERVICE","sending request command")
            val command = BraincareBluetoothCommandProtocol.createRequestConnectionCommandFrame(deviceType)
            writeCharacteristic(rxCharacteristic, command).with(commandCallback).enqueue()
        }
    
        fun sendSetLedProfileCommand(deviceType: DeviceType, color: Int) {
            BleLogHelper.writeLog("Device Blinked $deviceType")
            Log.d("BLESERVICE","sending blink command")
            val command = BraincareBluetoothCommandProtocol.createBlinkLedCommandFrame(Color.red(color), Color.green(color), Color.blue(color), 2)
            writeCharacteristic(rxCharacteristic, command).with(commandCallback).enqueue()
        }
    
    
    
        override fun log(priority: Int, @NonNull message: String) {
            Logger.log(mLogSession, LogContract.Log.Level.fromPriority(priority), message)
            Log.println(priority, "BleManager", message)
        }
    }
    

  • Ok I figured out, I was comparing with the wrogn data size on DataCallback, so it was always calling onInvalidDataReceived.

    if (data.size() != 1) { // always true
                onInvalidDataReceived(device, data)
                return
            }

Related