怎样优化网站 优帮云,公司官网图片,临沂网络建设,建设银行租房网站Ble蓝牙App#xff08;五#xff09;数据操作 前言正文一、操作内容处理二、读取数据① 概念② 实操 三、写入数据① 概念② 实操 四、打开通知一、概念二、实操三、收到数据 五、源码 前言 关于低功耗蓝牙的服务、特性、属性、描述符都已经讲清楚了#xff0c;而下面就是使… Ble蓝牙App五数据操作 前言正文一、操作内容处理二、读取数据① 概念② 实操 三、写入数据① 概念② 实操 四、打开通知一、概念二、实操三、收到数据 五、源码 前言 关于低功耗蓝牙的服务、特性、属性、描述符都已经讲清楚了而下面就是使用这些知识进行数据的读取、写入、通知等操作。
正文 首先要做的就是根据操作内容进行相应的处理目前常见的操作有Read、Write、Write no response、Notify和Indicate。
一、操作内容处理 首先要修改MainActivity中的onPropertyOperate()函数 override fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String) {if (!bleCore.isConnected()) showMsg(设备已断开连接)when (operateName) {READ - {}WRITE, WRITE_NO_RESPONSE - {}NOTIFY, INDICATE - {}BROADCAST, AUTHENTICATED_SIGNED_WRITES, EXTENDED_PROPERTIES - showMsg(operateName)}}这里着重看刚才提到的5个操作在操作之前我们最好判断一下当前是否处于连接中在BleCore中增加isConnected()函数代码如下所示 fun isConnected() mIsConnected二、读取数据
① 概念 在BLEBluetooth Low Energy通信中Ble Read读操作是一种用于从BLE服务器设备读取数据的操作。
当一个BLE设备称为客户端需要获取另一个BLE设备称为服务器上的数据时可以使用Ble Read操作。客户端向服务器发送读取请求并等待服务器返回所请求的数据。
Ble Read操作具有以下特点 请求-回复模式Ble Read操作是一种请求-回复模式的操作客户端向服务器发送读取请求服务器则回复所请求的数据。这种模式保证了数据传输的可靠性和顺序性。 单次数据传输Ble Read操作一次只能读取一个数据值或一个数据块。如果需要读取多个数据值客户端需要连续发送多个读取请求。 数据的访问权限Ble Read操作只能读取具有权限允许的数据。服务器可以设定数据的访问权限例如只允许读取、只允许写入、或者读写均允许。 需要注意的是Read操作可能会引入一定的延迟因为客户端需要等待服务器的响应。此外Read操作的成功取决于服务器是否支持读取请求并且客户端是否具有读取权限。
② 实操 当特性拥有Read的属性时我们就可以读取特性的value在的BleCore的BleGattCallback中重写onCharacteristicRead()函数代码如下所示 /*** 读取特性回调 Android 13及以上使用*/override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {if (status ! BluetoothGatt.GATT_SUCCESS) returndeviceInfo(读取特性值(Android 13及以上)${BleUtils.bytesToHex(value, true)})}/*** 读取特性回调 Android 12及以下使用*/Deprecated(Deprecated in Java)override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {if (status ! BluetoothGatt.GATT_SUCCESS) returndeviceInfo(读取特性值(Android 12及以下)${BleUtils.bytesToHex(characteristic.value, true)})}bytesToHex()是将byte[]转成Hex的函数还有hexToBytes的函数我们在BleUtils中增加这两个函数代码如下所示 /*** byte[] to hex* param isAdd 是否添加 0x 头*/fun bytesToHex(byteArray: ByteArray, isAdd: Boolean false): String {val hexChars 0123456789ABCDEFval hexString StringBuilder()for (byte in byteArray) {val value byte.toInt() and 0xFFval firstIndex value shr 4 and 0x0Fval secondIndex value and 0x0FhexString.append(hexChars[firstIndex])hexString.append(hexChars[secondIndex])}return (if (isAdd) 0x else ) hexString.toString()}/*** hex to byte[]*/fun hexToBytes(hexString: String): ByteArray {val cleanHexString hexString.replace(\\s.toRegex(), )val byteArray ByteArray(cleanHexString.length / 2)for (i in byteArray.indices) {val index i * 2val byteString cleanHexString.substring(index, index 2)val byteValue byteString.toInt(16).toByte()byteArray[i] byteValue}return byteArray}读取特性之后如果状态正常我们就显示一下读取的内容当我们调用Gatt的readCharacteristic()函数时就会触发这个回调。下面在BleCore中增加readCharacteristic()函数代码如下所示 fun readCharacteristic(characteristic: BluetoothGattCharacteristic) {deviceInfo(读取特性: ${BleUtils.getShortUUID(characteristic.uuid)})mGatt?.readCharacteristic(characteristic)}然后修改onPropertyOperate()函数代码如下所示 override fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String) {when (operateName) {READ - bleCore.readCharacteristic(characteristic)...}}下面我们运行一下 三、写入数据 读取数据写好了下面我们来看写入数据写入数据要看写入的方式有Write和Wirte No Response我们先了解这两种方式的区别
① 概念 在BLE通信中有两种常用的写操作方式Ble Write带回复的写操作和Write No Response无回复的写操作。 Ble Write带回复的写操作当一个BLE设备称为客户端想要向另一个BLE设备称为服务器发送数据时可以使用Ble Write操作。客户端向服务器发送数据并等待服务器发送确认回复Acknowledgment来表示数据已经被成功接收。这种写操作是一种可靠的方式确保数据传输的可靠性。 Write No Response无回复的写操作在某些情况下客户端发送的数据并不需要服务器的确认回复或者在时间上要求更加紧凑的传输。这时可以使用Write No Response操作。客户端向服务器发送数据后并不会等待服务器的确认回复。这种写操作通常用于实时传输等不需要确认的数据以减少通信延迟和增加通信吞吐量。 需要注意的是Write No Response操作在数据传输过程中不提供任何保障机制例如数据的可靠性、顺序性或幂等性等。因此使用Write No Response操作时需要确保应用场景的需求和通信的可靠性。
② 实操 写入数据需要有一个输入框因此我就写了一个弹窗来进行操作首先写弹窗布局在layout下新建一个dialog_write_data.xml代码如下所示
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autostylestyle/Widget.MaterialComponents.TextInputLayout.OutlinedBoxandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:backgroundcolor/whitecom.google.android.material.appbar.MaterialToolbarandroid:idid/toolbarandroid:layout_widthmatch_parentandroid:layout_height?attr/actionBarSizeapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfparentapp:title写入数据 /com.google.android.material.textfield.TextInputLayoutandroid:idid/data_layoutstylestyle/Widget.MaterialComponents.TextInputLayout.OutlinedBoxandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_marginStart16dpandroid:layout_marginEnd16dpapp:boxStrokeColorcolor/blackapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toBottomOfid/toolbarapp:prefixText0xcom.google.android.material.textfield.TextInputEditTextandroid:idid/et_dataandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:hintHEX数据android:inputTypetext|textCapCharactersandroid:lines1android:singleLinetrue //com.google.android.material.textfield.TextInputLayoutButtonandroid:idid/btn_negativeandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginEnd18dpandroid:layout_weight1android:text取消app:layout_constraintEnd_toStartOfid/btn_positiveapp:layout_constraintTop_toTopOfid/btn_positive /Buttonandroid:idid/btn_positiveandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop16dpandroid:layout_marginBottom16dpandroid:layout_weight1android:text发送app:layout_constraintBottom_toBottomOfparentapp:layout_constraintEnd_toEndOfid/data_layoutapp:layout_constraintTop_toBottomOfid/data_layout //androidx.constraintlayout.widget.ConstraintLayout布局内容比较简单只需要一个输入框两个按钮即可下面我们在MainActivity中写一个函数来加载这个布局xml显示弹窗代码如下所示 /*** 显示写入数据弹窗*/private fun showWriteDataDialog(characteristic: BluetoothGattCharacteristic, operateName: String) {val dialog BottomSheetDialog(this, R.style.BottomSheetDialogStyle)val writeDataBinding DialogWriteDataBinding.inflate(layoutInflater)writeDataBinding.toolbar.title if (operateName WRITE) 写入数据 else 写入无需响应数据writeDataBinding.btnPositive.setOnClickListener {val inputData writeDataBinding.etData.text.toString()if (inputData.isEmpty()) {writeDataBinding.dataLayout.error 请输入数据returnsetOnClickListener}if (!BleUtils.isHexFormat(inputData)) {writeDataBinding.dataLayout.error 请输入有效数据returnsetOnClickListener}bleCore.writeCharacteristic(characteristic, inputData, operateName)dialog.dismiss()}writeDataBinding.btnNegative.setOnClickListener {dialog.dismiss()}dialog.setContentView(writeDataBinding.root)dialog.show()}在弹窗中根据传入的操作名判断要以什么方式写入数据同时对写入的数据进行了格式校验在BleUtils中增加函数代码如下所示
fun isHexFormat(str: String) Regex(^([\\dA-Fa-f]{2})$).matches(str)当检查数据无误之后我们就可以写入数据了调用bleCore.writeCharacteristic(characteristic, inputData, operateName)在BleCore中增加这个函数代码如下所示 /*** 写入特性* param characteristic 特性* param data Hex数据* param operateName 操作名决定写入的是 Write 还是 Write No Response*/fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, data: String, operateName: String) {deviceInfo(写入特性${BleUtils.getShortUUID(characteristic.uuid)}value0x$data)//写入类型val writeType if (operateName BleConstant.WRITE) BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT else BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE//写入数据val byteArray BleUtils.hexToBytes(data)//根据Android版本进行不同的写入方式 Android 13及以上和以下不同val executionResult if (isAndroid13()) {mGatt?.writeCharacteristic(characteristic, byteArray, writeType) BluetoothStatusCodes.SUCCESS} else {characteristic.writeType writeTypecharacteristic.value byteArraymGatt?.writeCharacteristic(characteristic)}//执行写入动作成功不代表写入数据成功执行写入动作失败写入数据一定失败deviceInfo(if (executionResult true) 执行写入动作成功 else 执行写入动作失败)}这个函数相对的内容多一些首先是根据操作名得到写入的类型然后获取写入的数据再根据Android的版本去写入数据最终调用Gatt的writeCharacteristic()函数进行写入写入属于一个执行动作有失败的可能性可以根据返回值进行判断Android13以前返回的是BooleanAndroid13及以上返回的是Int这里要注意一下。执行之后如果成功了则会触发GattCallback的onCharacteristicWrite()回调下面在BleGattCallback中重写这个函数代码如下所示 override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {if (status ! BluetoothGatt.GATT_SUCCESS) returnif (BleUtils.isAndroid13()) {gatt.readCharacteristic(characteristic)} else {deviceInfo(写入成功${BleUtils.bytesToHex(characteristic.value)})}}这个函数中如果是Android 13及以上版本写入回调中的value是null需要通过readCharacteristic()函数去获取写入的值但是要确保这个特性有Read属性否则读取不了这个地方也是我觉得不合理得地方也有可能是我没找到对应得方式吧。最后我们修改MainActivity中的onPropertyOperate()函数中的代码如下所示 override fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String) {when (operateName) {WRITE, WRITE_NO_RESPONSE - showWriteDataDialog(characteristic, operateName)...}}最后我们再修复一个bug没错前面写的时候这个bug忽略掉了那就是在CharacteristicAdapter的onBindViewHolder()函数中之前在这里对属性的点击进行了回调当时是传进去一个特性和一个操作名称如图所示 这里通过position获取到特性而这里的position是属性适配器而我们要的是特性适配器的position这样做的问题就在于使用的时候如果只有一个属性的话那么无论有几个特性position都是0也是在调试中发现的这个问题改完之后代码如下所示 override fun onBindViewHolder(holder: ViewHolder, position: Int) {val characteristic characteristics[position]val characteristicName BleUtils.getCharacteristicsName(characteristic.uuid)holder.binding.tvCharacterName.text characteristicNameholder.binding.tvCharacterUuid.text if (characteristicName ! UNKNOWN_CHARACTERISTICS) BleUtils.getShortUUID(characteristic.uuid) else characteristic.uuid.toString()//加载特性下的属性holder.binding.rvProperty.apply {layoutManager LinearLayoutManager(context).apply { orientation LinearLayoutManager.HORIZONTAL }val properties: ListString BleUtils.getProperties(characteristic.properties)adapter PropertyAdapter(properties, object : OnItemClickListener {//点击属性override fun onItemClick(view: View?, position: Int) { callback.onPropertyOperate(characteristic, properties[position]) }})}//加载特性下的描述if (characteristic.descriptors.isEmpty()) {holder.binding.layDescriptors.visibility View.GONEreturn}holder.binding.rvDescriptor.apply {layoutManager LinearLayoutManager(context)adapter DescriptorAdapter(characteristic.descriptors)}}为了方便查看动作我们在修改一下BleCore中的deviceInfo()函数代码加一个日志打印代码如下所示 private fun deviceInfo(info: String) {Log.d(TAG, deviceInfo: $info)mBleCallback?.deviceInfo(info)}下面运行一下 日志如下所示 四、打开通知 实际上打开通知的意义就是能够收到蓝牙设备返回的数据先了解以下相关的概念知识。
一、概念 Ble Enable Notify是指在蓝牙低功耗BLE通信中使能通知功能的操作。当设备之间建立了蓝牙连接后设备可以通过特征Characteristic来交换数据。通知Notification是一种特征的属性允许一个设备向另一个设备发送数据而不需要另一个设备主动请求。 当一个设备使能了通知功能Enable Notify它就可以向另一个设备发送通知另一个设备只需要注册监听这个特征的通知即可接收到数据。这样可以实现数据的异步传输一旦数据发生变化发送方会自动发出通知接收方就可以及时获取到最新的数据。在BLE开发中通常需要通过操作特征的属性来使能或禁用通知功能。
二、实操 下面我们来实际操作一下首先在BleCore中增加一个函数代码如下所示 /*** 开启或者关闭通知* param characteristic 特性* param descriptorUuid 描述UUID* param operateName 操作名 决定通过那种方式开启通知*/fun notifyEnable(characteristic: BluetoothGattCharacteristic, descriptorUuid: UUID, operateName: String) {//设置特性通知这一点很重要if (mGatt?.setCharacteristicNotification(characteristic,true) false) return//描述val descriptor characteristic.getDescriptor(descriptorUuid)//写入描述值val value if (!mIsEnabled) {if (operateName BleConstant.INDICATE) BluetoothGattDescriptor.ENABLE_INDICATION_VALUE else BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE} else {BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE}val executionResult if (isAndroid13()) {mGatt?.writeDescriptor(descriptor, value) BluetoothStatusCodes.SUCCESS} else {descriptor.value valuemGatt?.writeDescriptor(descriptor)}deviceInfo((if (executionResult true) 执行启用动作成功 else 执行启用动作失败) value: ${BleUtils.bytesToHex(value, true)} )}因为当前的项目环境是基于Android13所在在蓝牙的一些API处理上我们都要考虑兼容的问题我觉得奇怪的是为什么不在Android12的版本中顺便加上去这些改动的API也不重要开发者就是这个命这里的代码实际上比较简单就是根据操作名进行enable的方式通过一个变量mIsEnabled来决定你是打开通知还是关闭通知这个变量我们定义在companion object中代码如下所示 companion object {.../*** 是否开启通知*/private var mIsEnabled false}调用writeDescriptor()会触发描述符写入回调在BleGattCallback中增加这个回调代码如下所示 /*** 描述符写入回调*/override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {if (status ! BluetoothGatt.GATT_SUCCESS) returnif (BleUtils.isAndroid13()) {gatt.readDescriptor(descriptor) //读取描述符} else {mIsEnabled !descriptor.value.contentEquals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)deviceInfo(写入描述符成功${BleUtils.bytesToHex(descriptor.value, true)})}}在回调中处理mIsEnabled的赋值因为在Android 13中没有办法直接获取描述符结果而是需要通过readDescriptor()函数获取使用这个函数则会触发另一个回调函数同样是在BleGattCallback中增加这个回调代码如下所示 /*** 读取描述符回调 Android 13及以上使用*/override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {if (status ! BluetoothGatt.GATT_SUCCESS) returnmIsEnabled !value.contentEquals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)deviceInfo(读取描述符成功(Android 13及以上使用)${BleUtils.bytesToHex(value, true)})}/*** 读取描述符回调 Android 12及以上下使用*/Deprecated(Deprecated in Java)override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {if (status ! BluetoothGatt.GATT_SUCCESS) returnmIsEnabled !descriptor.value.contentEquals(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)deviceInfo(读取描述符成功(Android 12及以下使用)${BleUtils.bytesToHex(descriptor.value, true)})}关于mIsEnabled的参数我们还需要修改一下一个地方那就是在连接设备之后如果发现mIsEnabled 为true我们改成false。 fun connect(device: BluetoothDevice) {deviceInfo(连接中...)if (mIsEnabled) mIsEnabled false...}然后我们再修改一下MainActivity中的onPropertyOperate()函数代码如下所示 /*** 属性操作*/override fun onPropertyOperate(characteristic: BluetoothGattCharacteristic, operateName: String) {if (!bleCore.isConnected()) showMsg(设备已断开连接)Log.d(TAG, onPropertyOperate: ${characteristic.uuid})when (operateName) {READ - bleCore.readCharacteristic(characteristic)WRITE, WRITE_NO_RESPONSE - showWriteDataDialog(characteristic, operateName)NOTIFY, INDICATE - bleCore.notifyEnable(characteristic, characteristic.descriptors[0].uuid, operateName)BROADCAST, AUTHENTICATED_SIGNED_WRITES, EXTENDED_PROPERTIES - showMsg(operateName)}}那么到现在为止我们就写好了基本的操作方式。
三、收到数据 下面我们写一下接收通知的回调同样是在BleGattCallback中增加这个回调代码如下所示 /*** 收到数据回调 Android 13及以上使用*/override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {deviceInfo(收到特性值(Android 13及以上)${BleUtils.getShortUUID(characteristic.uuid)}${BleUtils.bytesToHex(value, true)})}/*** 收到数据回调 Android 12及以下使用*/Deprecated(Deprecated in Java)override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {deviceInfo(收到特性值(Android 12及以下)${BleUtils.getShortUUID(characteristic.uuid)}${BleUtils.bytesToHex(characteristic.value, true)})}下面我们运行一下这里你要以自己的实际设备为准比如我用的这个设备包括数据的交互都是厂商自定义的下面我先开启Notify然后写入数据再看是否有数据返回。 我们再看一下控制台日志 可以看到在执行写入动作成功之后就收到了设备所回复的特征值数据然后再是收到写入成功的日志打印。
五、源码
如果对你有所帮助的话不妨 Star 或 Fork山高水长后会有期~
源码地址GoodBle