0

I am able to connect to my BLE device and send data from my Android app, but I am not able to read the data from the BLE (I need to display this data in a graph), but when I try to retrieve the values, I get a null pointer.

Here is the code for the activity page:

package com.example.lightrdetect

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattCharacteristic.*
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.example.lightrdetect.ble.ConnectionEventListener
import com.example.lightrdetect.ble.isReadable
import com.github.mikephil.charting.charts.ScatterChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.components.YAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.ScatterData
import com.github.mikephil.charting.data.ScatterDataSet
import com.punchthrough.blestarterappandroid.ble.ConnectionManager
import kotlinx.android.synthetic.main.activity_home_page.image_lightr
import kotlinx.android.synthetic.main.activity_tracking_page.*
import org.jetbrains.anko.alert
import java.text.SimpleDateFormat
import java.util.*


class TrackingPageActivity : AppCompatActivity() {

    private lateinit var device : BluetoothDevice
    private val dateFormatter = SimpleDateFormat("MMM d, HH:mm:ss", Locale.FRANCE)
    private var listeners: MutableSet<WeakReference<ConnectionEventListener>> = mutableSetOf()
    private val deviceGattMap = ConcurrentHashMap<BluetoothDevice, BluetoothGatt>()
    private val operationQueue = ConcurrentLinkedQueue<BleOperationType>()
    private var pendingOperation: BleOperationType? = null
    private val characteristic by lazy {
        ConnectionManager.servicesOnDevice(device)?.flatMap { service ->
            service.characteristics ?: listOf()
        } ?: listOf()
    }

    private val characteristicProperty by lazy {
        characteristic.map { characteristic->
            characteristic to mutableListOf<CharacteristicProperty>().apply {
                if(characteristic.isNotifiable()) add(CharacteristicProperty.Notifiable)
                if (characteristic.isIndicatable()) add(CharacteristicProperty.Indicatable)
                if(characteristic.isReadable()) add(CharacteristicProperty.Readable)
            }.toList()
        }.toMap()
    }

    private val characteristicAdapter: CharacteristicAdapter by lazy {
        CharacteristicAdapter(characteristic){characteristicProperty ->

        }
    }

    companion object{
        //var UUID_Read_notification = UUID.fromString("D973F2E1-B19E-11E2-9E96-0800200C9A66")
        var UUID_Read = "D973F2E1-B19E-11E2-9E96-0800200C9A66"
    }


    private var notifyingCharacteristics = mutableListOf<UUID>()

    override fun onCreate(savedInstanceState: Bundle?) {
        ConnectionManager.registerListener(connectionEventListener)
        super.onCreate(savedInstanceState)
        device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
            ?: error("Missing BluetoothDevice from Home Page Activity")
        setContentView(R.layout.activity_tracking_page)

        image_lightr.setOnClickListener {
            finish()
        }

        window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
        actionBar?.hide()
        supportActionBar?.hide()

        ScatterChartData()
    }


    private fun ScatterChartData(){
        readSensor(UUID_Read)

        val scatterEntry = ArrayList<Entry>()
        scatterEntry.add(Entry(0f, 3f))


        val sensorPosition = ArrayList<Entry>()
        sensorPosition.add(Entry(0f, 0f))

        val scatterDataSet_sensor = ScatterDataSet(sensorPosition, "Sensor")
        scatterDataSet_sensor.color = resources.getColor(R.color.white)
        scatterDataSet_sensor.setScatterShape(ScatterChart.ScatterShape.CHEVRON_DOWN)
        scatterDataSet_sensor.scatterShapeSize = 30f

        val scatterDataSet = ScatterDataSet(scatterEntry, "Target")
        scatterDataSet.color = resources.getColor(R.color.jaune_woodoo)
        scatterDataSet.setScatterShape(ScatterChart.ScatterShape.CIRCLE)
        scatterDataSet.valueTextColor = resources.getColor(R.color.transparent      )
        scatterDataSet.scatterShapeSize = 30f

        val scatterlistfinal = ArrayList<ScatterDataSet>()
        scatterlistfinal.add(scatterDataSet)
        scatterlistfinal.add(scatterDataSet_sensor)


        val scatterData = ScatterData(scatterlistfinal as List<ScatterDataSet>)
        chart1.data = scatterData
        chart1.setBackgroundColor(resources.getColor(R.color.transparent))
        chart1.animateXY(1000, 1000)
        chart1.legend.isEnabled = false


        val xAxis : XAxis = chart1.xAxis
        xAxis.position = XAxis.XAxisPosition.TOP
        //xAxis.setDrawGridLines(true)
        xAxis.axisLineColor = resources.getColor(R.color.white)
        xAxis.axisMaximum = 90f
        xAxis.axisMinimum = -90f
        xAxis.textColor = resources.getColor(R.color.white)
        xAxis.axisLineWidth = 5f

        val yAxisL : YAxis = chart1.axisLeft
        yAxisL.textColor = resources.getColor(R.color.white)
        yAxisL.isInverted = true
        yAxisL.axisMaximum = 5f
        yAxisL.axisMinimum = 0f
        yAxisL.axisLineWidth = 0f
        yAxisL.setLabelCount(6, true)
        yAxisL.axisLineColor = resources.getColor(R.color.transparent)

        val yAxisR : YAxis = chart1.axisRight
        yAxisR.textColor = resources.getColor(R.color.white)
        yAxisR.isInverted = true
        yAxisR.axisMaximum = 5f
        yAxisR.axisMinimum = 0f
        yAxisR.axisLineWidth = 0f
        yAxisR.setLabelCount(6, true)
        yAxisR.axisLineColor = resources.getColor(R.color.transparent)
    }
    

    private fun showCharacteristicOptions(characteristic: BluetoothGattCharacteristic) {
        characteristicProperty[characteristic]?.let { properties ->
            selector("Select an action to perform", properties.map { it.action }) { _, i ->
                when (properties[i]) {
                    CharacteristicProperty.Readable -> {
                        //log("Reading from ${characteristic.uuid}")
                        ConnectionManager.readCharacteristic(device, characteristic)
                    }
                    CharacteristicProperty.Notifiable, CharacteristicProperty.Indicatable -> {
                        if (notifyingCharacteristics.contains(characteristic.uuid)) {
                            //log("Disabling notifications on ${characteristic.uuid}")
                            ConnectionManager.disableNotifications(device, characteristic)
                        } else {
                            //log("Enabling notifications on ${characteristic.uuid}")
                            ConnectionManager.enableNotifications(device, characteristic)
                        }
                    }
                }
            }
        }
    }

    private fun readSensor(characteristic: String){
        var gattCharacteristic = BluetoothGattCharacteristic(UUID.fromString(characteristic), PROPERTY_READ, PERMISSION_READ_ENCRYPTED)
        showCharacteristicOptions(gattCharacteristic)
        var data : String
        if (gattCharacteristic !=null) {
            ConnectionManager.enableNotifications(device, gattCharacteristic)
            data = ConnectionManager.readCharacteristic(device, gattCharacteristic).toString()
            Log.d("sensor", "value " + data)
        }

    }


    private val connectionEventListener by lazy {
        ConnectionEventListener().apply {
            onDisconnect = {
                runOnUiThread {
                    alert {
                        title = "Disconnected"
                        message = "Disconnected from device."
                        positiveButton("ok"){onBackPressed()}
                    }.show()
                }
            }

            onCharacteristicRead = {_, characteristic ->
                Log.i("Tracking page","Read from ${characteristic.uuid}: ${characteristic.value.toHexString()}")
            }

            onNotificationsEnabled = {_,characteristic ->
                Log.i("Tracking page","Enabled notifications on ${characteristic.uuid}")
                notifyingCharacteristics.add(characteristic.uuid)
            }
        }
    }

    private enum class CharacteristicProperty {
        Readable,
        Writable,
        WritableWithoutResponse,
        Notifiable,
        Indicatable;

        val action
            get() = when (this) {
                Readable -> "Read"
                Writable -> "Write"
                WritableWithoutResponse -> "Write Without Response"
                Notifiable -> "Toggle Notifications"
                Indicatable -> "Toggle Indications"
            }
    }

}

and there is the error that I have

2022-03-17 10:49:54.768 31034-31034/com.example.lightrdetect E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.lightrdetect, PID: 31034
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.lightrdetect/com.example.lightrdetect.TrackingPageActivity}: java.lang.NullPointerException: gattCharacteristic.getValue() must not be null
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3851)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4027)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:247)
        at android.app.ActivityThread.main(ActivityThread.java:8676)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
     Caused by: java.lang.NullPointerException: gattCharacteristic.getValue() must not be null
        at com.example.lightrdetect.TrackingPageActivity.ScatterChartData(TrackingPageActivity.kt:89)
        at com.example.lightrdetect.TrackingPageActivity.onCreate(TrackingPageActivity.kt:78)
        at android.app.Activity.performCreate(Activity.java:8215)
        at android.app.Activity.performCreate(Activity.java:8199)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3824)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4027) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:247) 
        at android.app.ActivityThread.main(ActivityThread.java:8676) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130) 

this is a screenshot of the nRF application that shows me all the features: nRF screen shot

I checked with the BLE module support and they told me that:

The Android application can write on the Rx characteristic and automatically the data will be sent on the UART (Tera Term or a µC connected on UART) The µC or Tera Term to push data will have to emit on the Tx, this is what the Application code of the ST Serial Port Profile code does, when it receives on the UART an end of string character (CR+LF) (to be set in the Tera Term or in the STM32 Application code). However, for the Android application to receive the data, it must be registered on the notifications (slide 10 of the doc - the slide refers to an Ios mobile on Android to activate the notifications, you must click on the 3 arrows)

I checked with Tera Term, I can see the data on nRF. My question now is how can I read the characteristic notification?

Best regard.

P.C
  • 19
  • 1
  • 8
  • Does the characteristic allow reading? Please try reading the data using a generic BLE scanner such as [nRF Connect](https://www.nordicsemi.com/Products/Development-tools/nrf-connect-for-mobile) – Michael Kotzjan Mar 16 '22 at 15:09
  • Hi @MichaelKotzjan , I tried your application, and I have the correct feature (00002A04-0000-1000-8000-00805F9B34FB) on the application they use UUID: 0x2A04. But I am not able to read the data I send to my BLE module, I don't understand why. When I use the BLE module evaluation KIT, I can send and receive data. – P.C Mar 16 '22 at 15:30
  • @MichaelKotzjan – P.C Mar 16 '22 at 15:49
  • The characteristic with the UUID 0x2A04 is supposed to be the "Peripheral Preferred Connection Parameters" and should be read only. nRF COnnect should show this with a downwards arrow to the right of the characteristic like in [this](https://stackoverflow.com/questions/47868605/ble-device-characteristic-says-it-read-only-but-log-says-otherwise) question. – Michael Kotzjan Mar 17 '22 at 05:24
  • I have different features that are supposed to give me the ability to read the data from the BLE module: UUID = 0x2902 in the generic attribute, UUID = 0x2A04 as you mention and UUID = 0x2902 in the custom feature but none of them give me access to read the data and I have the same error on android studio. @MichaelKotzjan – P.C Mar 17 '22 at 07:50
  • Could you please add a screenshot of your characteristics in nRF Connect to your question? – Michael Kotzjan Mar 17 '22 at 08:18
  • I edited the question, added the screenshot.@MichaelKotzjan – P.C Mar 17 '22 at 09:35
  • As a gatt client, you can't just create a BluetoothGattCharacteristic object and extract its value. You must obtain such an object through service discovery. Then you need to call readCharacteristic and wait for the onCharacteristicRead callback. – Emil Mar 17 '22 at 09:57
  • I have no idea how to do that. @Emil – P.C Mar 17 '22 at 10:06

1 Answers1

0

The provided image shows the 3 services your device offers:

  • Generic Attribute
  • Generic Access
  • Unknown Service

The first two are pretty standard for a BLE device. The Generic Attribute Service offers you one characteristic called "Service Changed" which allows to get notified if your device changes something about its services during runtime. The Generic Access Service contains the device name and other information.

You probably want to talk to the Service labeled "Unknown Service" by nRF Connect. This simply means that the UUID used for this service is not in its list of know services. nRF Connect shows two characteristics for this service, one to receive data from which also allows receiving notifications, and one to send data. It basically looks like a UART over BLE implementation.

Based on your source code it seems like you are using the wrong UUID. Your companion object refers to the correct UUID for notifications, but not the correct one for reading:

companion object{
    var UUID_Read_notification = UUID.fromString("D973F2E1-B19E-11E2-9E96-0800200C9A66")
    var UUID_Read = UUID.fromString("00002A04-0000-1000-8000-00805F9B34FB")
}

The UUID to read from is the same as the notify UUID: D973F2E1-B19E-11E2-9E96-0800200C9A66. If you also want to write to the device you have to use the UUID D973F2E2-B19E-11E2-9E96-0800200C9A66.

As I said in the comments, the UUID you used before (00002A04-0000-1000-8000-00805F9B34FB) belongs to the "Peripheral Preferred Connection Parameters" characteristic of the Generic Access Service and only allows reading.

Michael Kotzjan
  • 2,093
  • 2
  • 14
  • 23
  • I also tried with this UUID: `D973F2E1-B19E-11E2-9E96-0800200C9A66` and I have the same problem `java.lang.NullPointerException: gattCharacteristic.getValue() must not be null`.and yes there is UART communication on the BLE module and I'm using it to send and receive data from my STM32 to my android application, at the moment I can only receive data from the BLE module. – P.C Mar 17 '22 at 15:02
  • Ok, I just realized that you create your own gattCharacteristic object using `BluetoothGattCharacteristic(UUID_Read, PROPERTY_READ, PERMISSION_READ)`. You have to discover the services and characteristics on the device first. After that you can use the found service to read the characteristic. I can't see that you are discovering the services anywhere. Just connecting and reading – Michael Kotzjan Mar 17 '22 at 19:01
  • Ok, but I don't understand why it works when I send data from the android app, I use the exact same method. I will try to find out the services and features and try to read @Michael – P.C Mar 18 '22 at 12:28
  • Most of my project (for BLE connection and management come from this tutorial) but I don't understand how to discover the service and feature on a page activity. do you have a code example to discover and read the feature? – P.C Mar 18 '22 at 13:30
  • how about the official documentation provided by Google: https://developer.android.com/guide/topics/connectivity/bluetooth/find-ble-devices#kotlin – Michael Kotzjan Mar 19 '22 at 20:25
  • I also try with this documentation, but there are some variables that I can't find where they are declared, like the variable mGattCharacteristic. I end up with code errors because the variables are not declared – P.C Mar 21 '22 at 10:12
  • They also explain how to create functions to discover services and features, but they don't show how to use these functions. using as a function parameter, a BluetoothGattCharacteristic variable, implies that I have to define a variable with a specific UUID as I did. – P.C Mar 21 '22 at 10:17
  • Awesome... Google should really take a look at these examples then... Maybe using a BLE library works better for you. I found this one: https://github.com/JuulLabs/kable – Michael Kotzjan Mar 21 '22 at 10:27
  • I think I found why I can't read the data from the BLE, I think it's because there is no READ characteristic in the customer service, I check on the BLE code and I find the Write characteristic declaration but there is only the NOTIFICATION characteristic declaration. I'll try with the library – P.C Mar 21 '22 at 12:41
  • Hey Michael, i have updated the question. – P.C Mar 23 '22 at 16:43
  • @P.Congré in case you are using the library now: https://github.com/JuulLabs/kable#observation – Michael Kotzjan Mar 23 '22 at 19:53
  • No, I don't use the library for the moment because I have built all my applications around another library. – P.C Mar 24 '22 at 15:27
  • Then maybe this helps you: https://stackoverflow.com/questions/67100669/not-receiving-notifications-even-when-notifications-are-enabled-for-ble-device – Michael Kotzjan Mar 24 '22 at 16:57
  • It only helps me to have on the logcat the change value of the notification carateristic but I need to convert it to ascii then separate the values and store it as a float and print it in a graph. – P.C Mar 29 '22 at 15:30
  • @P.Congré this gets to complicated to manage in the comments section. please ask a new question – Michael Kotzjan Mar 29 '22 at 18:04