fdp 1 year ago
parent
commit
50f3327f52
  1. 297
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt
  2. 378
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

297
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt

@ -0,0 +1,297 @@
package com.yunqiinnovation.ble_service
import android.Manifest
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.RequiresPermission
import java.util.*
/**
* BLE指令发送器类:负责处理蓝牙设备的指令发送和队列管理
* 功能包括:
* 1. 指令队列管理
* 2. 指令封装和CRC校验
* 3. 指令发送和重试机制
* 4. 响应超时处理
*/
class BleCommandSender {
companion object {
private const val TAG = "BleCommandSender"
private const val REPLY_TIMEOUT_MS = 1000L // 响应超时时间1秒
}
// ======================================================================================================
// 成员变量
// ======================================================================================================
/** 指令队列 */
private val commandQueue: Queue<ByteArray> = LinkedList()
/** 是否正在等待回复 */
var isWaitingReply = true
private set
/** 回复的指令类型 */
var replyCommandType = 0xff
private set
/** 响应超时处理器 */
private val replyTimeoutHandler = Handler(Looper.getMainLooper())
/** 响应超时回调 */
private val replyTimeoutRunnable = Runnable {
if (!isWaitingReply) {
Log.w(TAG, "命令响应超时,自动重置 isWaitingReply 为 true")
isWaitingReply = true
processNextCommand()
}
}
/** 指令发送状态回调接口 */
interface CommandSenderCallback {
/**
* 检查连接状态
* @return 是否已连接
*/
fun isConnected(): Boolean
/**
* 获取写入特征
* @return 写入特征对象
*/
fun getWriteCharacteristic(): BluetoothGattCharacteristic?
/**
* 获取GATT连接
* @return GATT连接对象
*/
fun getBluetoothGatt(): BluetoothGatt?
}
private var callback: CommandSenderCallback? = null
// ======================================================================================================
// 公共方法
// ======================================================================================================
/**
* 设置回调接口
* @param callback 回调接口实现
*/
fun setCallback(callback: CommandSenderCallback) {
this.callback = callback
}
/**
* 发送命令到设备 - 通用方法
* @param cmdType 命令类型
* @param cmdData 命令数据
* @return 是否成功发送
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
val callback = this.callback ?: run {
Log.e(TAG, "发送命令失败: 回调接口未设置")
return false
}
if (!callback.isConnected()) {
Log.e(TAG, "发送命令失败: 设备未连接")
return false
}
try {
// 创建命令包
val cmdPacket = createCommandPacket(cmdType, cmdData)
// 记录命令详情
logCommandDetails(cmdType, cmdData, cmdPacket)
// 获取写入特征
val writeChar = callback.getWriteCharacteristic() ?: run {
Log.e(TAG, "发送命令失败: 写入特征未找到")
return false
}
// 设置数据并发送
writeChar.value = cmdPacket
return if (isWaitingReply) {
// 直接发送
val gatt = callback.getBluetoothGatt()
val isSuccess = gatt?.writeCharacteristic(writeChar) == true
if (isSuccess) {
isWaitingReply = false
startReplyTimeout()
true
} else {
Log.e(TAG, "发送命令失败: 写入特征失败")
false
}
} else {
// 加入队列等待发送
commandQueue.offer(cmdPacket)
Log.d(TAG, "命令已加入队列,当前队列长度: ${commandQueue.size}")
true
}
} catch (e: Exception) {
Log.e(TAG, "发送命令异常: ${e.message}", e)
return false
}
}
/**
* 处理命令响应,重置等待状态并处理下一个命令
* @param responseCommandType 响应的命令类型
*/
fun onCommandResponse(responseCommandType: Int) {
isWaitingReply = true
replyCommandType = responseCommandType
replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable)
processNextCommand()
}
/**
* 清空命令队列
*/
fun clearCommandQueue() {
commandQueue.clear()
Log.d(TAG, "命令队列已清空")
}
/**
* 获取当前队列长度
* @return 队列中待发送的命令数量
*/
fun getQueueSize(): Int = commandQueue.size
/**
* 重置发送状态
*/
fun reset() {
isWaitingReply = true
replyCommandType = 0xff
replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable)
clearCommandQueue()
Log.d(TAG, "指令发送器已重置")
}
// ======================================================================================================
// 私有方法
// ======================================================================================================
/**
* 创建命令包
* @param cmdType 命令类型
* @param cmdData 命令数据
* @return 完整的命令包(包含帧头、命令、长度、数据、CRC)
*/
private fun createCommandPacket(cmdType: Byte, cmdData: ByteArray): ByteArray {
val cmdLength = cmdData.size
val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC
// 帧头
cmdPacket[0] = BleConst.APP_REQUEST_HEADER
// 命令类型
cmdPacket[1] = cmdType
// 命令长度
cmdPacket[2] = cmdLength.toByte()
// 填充命令数据(如果有)
if (cmdLength > 0) {
System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
}
// 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身)
val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1)
val crc = calculateCrc8Maxim(crcPayload, crcPayload.size)
// 设置CRC
cmdPacket[3 + cmdLength] = crc
return cmdPacket
}
/**
* 记录命令详情
* @param cmdType 命令类型
* @param cmdData 命令数据
* @param cmdPacket 完整命令包
*/
private fun logCommandDetails(cmdType: Byte, cmdData: ByteArray, cmdPacket: ByteArray) {
val crc = cmdPacket[cmdPacket.size - 1]
Log.d(
TAG,
"发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=${cmdData.size}, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}"
)
}
/**
* 处理队列中的下一个命令
*/
private fun processNextCommand() {
if (commandQueue.isEmpty()) {
return
}
val callback = this.callback ?: return
val nextCmd = commandQueue.poll() ?: return
val writeChar = callback.getWriteCharacteristic() ?: run {
Log.e(TAG, "处理下一个命令失败: 写入特征未找到")
return
}
val gatt = callback.getBluetoothGatt() ?: run {
Log.e(TAG, "处理下一个命令失败: GATT连接未找到")
return
}
writeChar.value = nextCmd
val isSuccess = gatt.writeCharacteristic(writeChar)
if (isSuccess) {
isWaitingReply = false
startReplyTimeout()
Log.d(TAG, "发送队列中的下一个命令,剩余队列长度: ${commandQueue.size}")
} else {
Log.e(TAG, "发送队列中的命令失败")
}
}
/**
* 启动响应超时计时器
*/
private fun startReplyTimeout() {
replyTimeoutHandler.postDelayed(replyTimeoutRunnable, REPLY_TIMEOUT_MS)
}
/**
* 计算CRC8/MAXIM校验值
* @param data 要校验的数据
* @param length 数据长度
* @return CRC8校验值
*/
private fun calculateCrc8Maxim(data: ByteArray, length: Int): Byte {
var crc = 0
val polynomial = 0x31
for (i in 0 until length) {
crc = crc xor (data[i].toInt() and 0xFF)
for (j in 0 until 8) {
if (crc and 0x80 != 0) {
crc = ((crc shl 1) xor polynomial) and 0xFF
} else {
crc = (crc shl 1) and 0xFF
}
}
}
return crc.toByte()
}
}

378
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -68,7 +68,6 @@ object BleService {
// 上下文
private lateinit var context: Context
private val commandQueue: Queue<ByteArray> = LinkedList()
// 蓝牙相关组件
private lateinit var bluetoothManager: BluetoothManager
@ -141,6 +140,9 @@ object BleService {
// 初始化状态
private var isInitialized = false
// 指令发送器
private val commandSender = BleCommandSender()
/**
* 检查是否有蓝牙扫描权限
*/
@ -177,14 +179,7 @@ object BleService {
}
}
private val replyTimeoutHandler = Handler(Looper.getMainLooper())
private val replyTimeoutRunnable = Runnable {
if (!isReply) {
Log.w(TAG, "命令响应超时,自动重置 isReply 为 true")
isReply = true
processNextCommand()
}
}
/**
* 初始化BLE服务
@ -222,6 +217,10 @@ object BleService {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
// 根据需要决定是否因为Opus初始化失败而返回false
}
// 初始化指令发送器
initializeCommandSender()
isInitialized = true
return true
} catch (e: Exception) {
@ -244,6 +243,25 @@ object BleService {
return false
}
/**
* 初始化指令发送器
*/
private fun initializeCommandSender() {
commandSender.setCallback(object : BleCommandSender.CommandSenderCallback {
override fun isConnected(): Boolean {
return checkConn()
}
override fun getWriteCharacteristic(): BluetoothGattCharacteristic? {
return writeChar
}
override fun getBluetoothGatt(): BluetoothGatt? {
return bluetoothGatt
}
})
}
/**
* 移除回调监听器
* @param callback 要移除的回调监听器
@ -604,7 +622,7 @@ object BleService {
Log.e(TAG, "关闭GATT连接异常: ${e.message}", e)
}
isReply = true;
commandQueue.clear()
commandSender.clearCommandQueue()
bluetoothGatt = null
notifyChar = null
writeChar = null
@ -905,6 +923,8 @@ startBytesStatistics()
"rawData" to byteArrayOf()
)
notifyDeviceInfoReceived(cmdType, resultMap)
// 通知指令发送器处理响应
commandSender.onCommandResponse(cmdType)
return
}
@ -946,10 +966,8 @@ startBytesStatistics()
}
}"
)
isReply = true
cmdReplyType = cmdType
replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable) // 移除超时回调
processNextCommand()
// 通知指令发送器处理响应
commandSender.onCommandResponse(cmdType)
// 根据命令类型处理不同的响应
when (cmdType) {
BleConst.CMD_GET_VERSION -> {
@ -1601,201 +1619,185 @@ startBytesStatistics()
}
}
// ======================================================================================================
// 公开的命令接口
// 设备控制方法 - 使用新的指令发送器
// ======================================================================================================
/**
* 获取耳机版本号
* 获取设备版本信息
* @return 是否成功发送命令
*/
fun getDeviceVersion(): Boolean {
Log.i(TAG, "获取耳机版本号...")
return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte()))
Log.i(TAG, "获取设备版本信息...")
return commandSender.sendCommand(BleConst.CMD_GET_VERSION.toByte())
}
/**
* 获取产品ID
* @return 是否成功发送命令
*/
fun getProductId(): Boolean {
Log.i(TAG, "获取耳机产品ID...")
return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte()))
Log.i(TAG, "获取产品ID...")
return commandSender.sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte())
}
/**
* 获取耳机颜色ID
* 获取颜色ID
* @return 是否成功发送命令
*/
fun getColorId(): Boolean {
Log.i(TAG, "获取耳机颜色ID...")
return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte()))
Log.i(TAG, "获取颜色ID...")
return commandSender.sendCommand(BleConst.CMD_GET_COLOR_ID.toByte())
}
/**
* 获取耳机电量信息
* 获取电量信息
* @return 是否成功发送命令
*/
fun getBatteryInfo(): Boolean {
Log.i(TAG, "获取耳机电量信息...")
return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte()))
Log.i(TAG, "获取电量信息...")
return commandSender.sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte())
}
/**
* 控制编解码 - 关闭编解码
* 关闭编解码
* @return 是否成功发送命令
*/
fun closeCodec(): Boolean {
Log.i(TAG, "关闭编解码...")
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
) // 或其他默认声道
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_STEREO.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 控制编解码 - 打开解码
* 打开解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/
fun openDecoder(): Boolean {
// val opusStarted = startOpusStreamDecoding(
// hasHeader = false, // BLE传输的Opus数据通常无文件头
// channel = 1, // 单声道
// sampleRate = 16000,// Opus推荐16kHz或更高
// packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// // packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// // The library might expect this in a different unit or derive it.
// // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
// )
fun openDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开解码器...")
startOpusStreamDecoding(false, 1, 16000, 40)
Log.i(TAG, "打开解码0xA1")
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
channelMode.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 控制编解码 - 打开解码
* 打开A2DP解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/
fun openA2DPDecoder(): Boolean {
Log.i(TAG, "打开编码 0xA2")
fun openA2DPDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开A2DP解码器...")
startOpusEncodeStream()
//双声道 80字节
startOpusStreamDecoding(false, 2, 16000, 80)
// Log.i(TAG, "打开解码...")
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
channelMode.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 控制编解码 - 打开解码
* 打开通话录音解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/
fun openCallRecordDecoder(): Boolean {
Log.i(TAG, "打开编码 0xA3")
//双声道 80字节
fun openCallRecordDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开通话录音解码器...")
startOpusStreamDecoding(false, 2, 16000, 80)
// Log.i(TAG, "打开解码...")
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(),
channelMode.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 控制编解码 - A2DP播放
* A2DP播放
* @return 是否成功发送命令
*/
fun playA2DP(): Boolean {
Log.i(TAG, "切换至A2DP播放模式...")
return sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
Log.i(TAG, "A2DP播放...")
return commandSender.sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
}
/**
* 控制编解码 - 打开编码
* 打开编码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/
fun openEncoder(): Boolean {
Log.i(TAG, "打开编码0xB1")
fun openEncoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开编码器...")
startOpusStreamDecoding(false, 1, 16000, 40)
// // 目前仅发送命令通知设备开始编码。
// return sendCommand(
// BleConst.CMD_CONTROL_CODEC.toByte(),
// byteArrayOf(
// BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
// BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道
// )
// )
return true
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
channelMode.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 设置声道模式
* @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO
* @param channelMode 声道模式
* @return 是否成功发送命令
*/
fun setChannelMode(channelMode: Int): Boolean {
when (channelMode) {
BleConst.AUDIO_CHANNEL_LEFT -> Log.i(TAG, "设置左声道模式...")
BleConst.AUDIO_CHANNEL_RIGHT -> Log.i(TAG, "设置右声道模式...")
BleConst.AUDIO_CHANNEL_STEREO -> Log.i(TAG, "设置立体声模式...")
else -> {
Log.e(TAG, "无效的声道模式: $channelMode")
return false
}
}
// 高级控制:设置命令类型、长度和两个参数
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
0xA1.toByte(), // 状态码 - 使用解码状态
channelMode.toByte() // 声道模式
)
Log.i(TAG, "设置声道模式: $channelMode")
val cmdData = byteArrayOf(
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
channelMode.toByte()
)
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
}
/**
* 音量增大
* @return 是否成功发送命令
*/
fun volumeUp(): Boolean {
Log.i(TAG, "设备音量增加...")
return sendCommand(BleConst.CMD_VOLUME_UP.toByte())
return commandSender.sendCommand(BleConst.CMD_VOLUME_UP.toByte())
}
/**
* 音量减小
* @return 是否成功发送命令
*/
fun volumeDown(): Boolean {
Log.i(TAG, "设备音量减小...")
return sendCommand(BleConst.CMD_VOLUME_DOWN.toByte())
return commandSender.sendCommand(BleConst.CMD_VOLUME_DOWN.toByte())
}
// ======================================================================================================
// 工具方法
// 连接状态和工具方法
// ======================================================================================================
/**
* 检查是否已连接
* 检查连接状态
* @return 是否已连接
*/
fun isConnected(): Boolean {
return connectionState.value == BleConst.STATE_CONNECTED
private fun checkConn(): Boolean {
return bluetoothGatt != null &&
connectionState.value == BleConst.STATE_CONNECTED &&
writeChar != null
}
/**
* 连接状态检查
* 是否已连接
* @return 连接状态
*/
private fun checkConn(): Boolean =
bluetoothGatt != null && writeChar != null && callWriteChar != null &&
connectionState.value == BleConst.STATE_CONNECTED
fun isConnected(): Boolean {
return connectionState.value == BleConst.STATE_CONNECTED
}
/**
* 获取已连接设备地址
* 获取已连接设备的地址
* @return 设备地址,未连接时返回null
*/
fun getConnectedDeviceAddress(): String? {
return bluetoothGatt?.device?.address
@ -1803,6 +1805,7 @@ startBytesStatistics()
/**
* 获取蓝牙适配器
* @return 蓝牙适配器实例
*/
fun getBluetoothAdapter(): BluetoothAdapter? {
return bluetoothAdapter
@ -1813,129 +1816,38 @@ startBytesStatistics()
// 资源清理
// ======================================================================================================
/**
* 清理所有资源
* 清理资源
*/
fun cleanup() {
Log.i(TAG, "清理BLE服务资源...")
// 重置指令发送器
commandSender.reset()
// 停止扫描
stopScan()
scanHandler.removeCallbacksAndMessages(null)
disconnectGatt()
// 停止音频队列处理线程
audioQueueProcessorThread?.interrupt()
audioQueueProcessorThread = null
audioDataQueue.clear()
// 停止音频发送线程
// 断开连接
disconnect()
// 清理回调
clearCallbacks()
// 停止音频处理
stopAudioSendThread()
// 清理音频数据缓存
synchronized(audioDataBuffer) {
audioDataBuffer.clear()
Log.d(TAG, "已清理音频数据缓存")
}
Log.d(TAG, "音频解码线程和队列处理线程已清理")
// 释放OpusManager
stopOpusEncodeStream()
stopOpusStreamDecoding()
opusManager?.release()
opusManager = null
option = null
Log.d(TAG, "OpusManager已释放")
isInitialized = false // 标记为未初始化
}
// ======================================================================================================
// 协议命令实现
// ======================================================================================================
/**
* 发送命令到设备 - 通用方法
* @param cmdType 命令类型
* @param cmdData 命令数据
* @return 是否成功发送
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
private fun sendCommand(cmdType: Byte, cmdData: ByteArray = byteArrayOf()): Boolean {
if (!checkConn()) {
Log.e(TAG, "发送命令失败: 设备未连接")
return false
}
try {
// 计算命令长度
val cmdLength = cmdData.size
// 创建命令包
val cmdPacket = ByteArray(4 + cmdLength) // 帧头 + 命令 + 长度 + 数据 + CRC
// 帧头
cmdPacket[0] = BleConst.APP_REQUEST_HEADER
// 命令类型
cmdPacket[1] = cmdType
// 命令长度
cmdPacket[2] = cmdLength.toByte()
// 填充命令数据(如果有)
if (cmdLength > 0) {
System.arraycopy(cmdData, 0, cmdPacket, 3, cmdLength)
}
// 计算CRC - 对帧头到数据的所有字节进行校验 (不包括CRC自身)
val crcPayload = cmdPacket.sliceArray(0 until cmdPacket.size - 1)
val crc = crc8Maxim(crcPayload, crcPayload.size)
// 设置CRC
cmdPacket[3 + cmdLength] = crc
// 记录命令详情
Log.d(
TAG,
"发送命令: CMD=0x${(cmdType.toInt() and 0xFF).toString(16)}, 长度=$cmdLength, " +
"数据=${cmdData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}, " +
"CRC=0x${(crc.toInt() and 0xFF).toString(16)}"
)
// 发送数据
writeChar!!.value = cmdPacket
// 加入队列并触发发送
if (isReply) {
val isSuccess = bluetoothGatt?.writeCharacteristic(writeChar)
if (isSuccess == true) {
isReply = false
replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时
return true
} else {
commandQueue.poll()
return false
}
} else {
commandQueue.offer(cmdPacket)
}
return true
} catch (e: Exception) {
Log.e(TAG, "发送命令异常: ${e.message}", e)
return false
}
stopOpusEncodeStream()
isInitialized = false
Log.i(TAG, "BLE服务资源清理完成")
}
private fun processNextCommand() {
if (!commandQueue.isEmpty()) {
val nextCmd = commandQueue.poll()
writeChar!!.value = nextCmd
val isSuccess = bluetoothGatt?.writeCharacteristic(writeChar)
if (isSuccess == true) {
isReply = false
replyTimeoutHandler.postDelayed(replyTimeoutRunnable, 1000) // 设置1秒超时
Log.d(TAG, "发送队列中的下一个命令")
}
}
}
/**
* 计算CRC8/MAXIM校验值
* @param data 要校验的数据
* @param length 数据长度
* @return CRC8校验值
*/
private fun crc8Maxim(data: ByteArray, length: Int): Byte {
var crc = 0

Loading…
Cancel
Save