简体   繁体   English

如何使用 Kotlin 从通知特性 BLE 读取数据

[英]How to read data from notification characteristic BLE with Kotlin

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.我能够连接到我的 BLE 设备并从我的 Android 应用程序发送数据,但我无法从 BLE 读取数据(我需要在图表中显示此数据),但是当我尝试检索值时,我得到一个 null 指针。

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 应用程序的屏幕截图,向我展示了所有功能:nRF 屏幕截图

I checked with the BLE module support and they told me that:我检查了 BLE 模块支持,他们告诉我:

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). Android 应用程序可以写入 Rx 特性,数据将自动发送到 UART(Tera Term 或 UART 上连接的 µC)推送数据的 µC 或 Tera Term 必须在 Tx 上发出,这就是应用程序当 ST 串行端口配置文件代码在 UART 上接收到字符串结尾字符 (CR+LF) 时(将在 Tera Term 或 STM32 应用程序代码中设置)。 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)但是,要让 Android 应用程序接收数据,它必须在通知上注册(文档的幻灯片 10 - 该幻灯片指的是 Android 上的 Ios 手机激活通知,您必须单击 3 个箭头)

I checked with Tera Term, I can see the data on nRF.我检查了 Tera Term,我可以看到 nRF 上的数据。 My question now is how can I read the characteristic notification?我现在的问题是如何阅读特征通知?

Best regard.最良好的问候。

The provided image shows the 3 services your device offers:提供的图像显示了您的设备提供的 3 种服务:

  • Generic Attribute通用属性
  • Generic Access通用访问
  • Unknown Service未知服务

The first two are pretty standard for a BLE device.前两个是 BLE 设备的标准配置。 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.您可能想与 nRF Connect 标记为“未知服务”的服务交谈。 This simply means that the UUID used for this service is not in its list of know services.这仅仅意味着用于此服务的 UUID 不在其已知服务列表中。 nRF Connect shows two characteristics for this service, one to receive data from which also allows receiving notifications, and one to send data. nRF Connect 显示了此服务的两个特征,一个是接收数据,也允许从中接收通知,另一个是发送数据。 It basically looks like a UART over BLE implementation.它基本上看起来像一个 UART over BLE 实现。

Based on your source code it seems like you are using the wrong UUID.根据您的源代码,您似乎使用了错误的 UUID。 Your companion object refers to the correct UUID for notifications, but not the correct one for reading:您的伴侣 object 指的是正确的通知 UUID,但不是正确的阅读 UUID:

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 .要读取的 UUID 与通知 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 .如果您还想写入设备,则必须使用 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.正如我在评论中所说,您之前使用的UUID( 00002A04-0000-1000-8000-00805F9B34FB )属于通用访问服务的“外设首选连接参数”特性,只允许读取。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM