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 lateinit var context: Context
private val commandQueue: Queue<ByteArray> = LinkedList()
// 蓝牙相关组件 // 蓝牙相关组件
private lateinit var bluetoothManager: BluetoothManager private lateinit var bluetoothManager: BluetoothManager
@ -141,6 +140,9 @@ object BleService {
// 初始化状态 // 初始化状态
private var isInitialized = false 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服务 * 初始化BLE服务
@ -222,6 +217,10 @@ object BleService {
Log.e(TAG, "OpusManager初始化失败: ${e.message}", e) Log.e(TAG, "OpusManager初始化失败: ${e.message}", e)
// 根据需要决定是否因为Opus初始化失败而返回false // 根据需要决定是否因为Opus初始化失败而返回false
} }
// 初始化指令发送器
initializeCommandSender()
isInitialized = true isInitialized = true
return true return true
} catch (e: Exception) { } catch (e: Exception) {
@ -244,6 +243,25 @@ object BleService {
return false 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 要移除的回调监听器 * @param callback 要移除的回调监听器
@ -604,7 +622,7 @@ object BleService {
Log.e(TAG, "关闭GATT连接异常: ${e.message}", e) Log.e(TAG, "关闭GATT连接异常: ${e.message}", e)
} }
isReply = true; isReply = true;
commandQueue.clear() commandSender.clearCommandQueue()
bluetoothGatt = null bluetoothGatt = null
notifyChar = null notifyChar = null
writeChar = null writeChar = null
@ -905,6 +923,8 @@ startBytesStatistics()
"rawData" to byteArrayOf() "rawData" to byteArrayOf()
) )
notifyDeviceInfoReceived(cmdType, resultMap) notifyDeviceInfoReceived(cmdType, resultMap)
// 通知指令发送器处理响应
commandSender.onCommandResponse(cmdType)
return return
} }
@ -946,10 +966,8 @@ startBytesStatistics()
} }
}" }"
) )
isReply = true // 通知指令发送器处理响应
cmdReplyType = cmdType commandSender.onCommandResponse(cmdType)
replyTimeoutHandler.removeCallbacks(replyTimeoutRunnable) // 移除超时回调
processNextCommand()
// 根据命令类型处理不同的响应 // 根据命令类型处理不同的响应
when (cmdType) { when (cmdType) {
BleConst.CMD_GET_VERSION -> { BleConst.CMD_GET_VERSION -> {
@ -1601,201 +1619,185 @@ startBytesStatistics()
} }
} }
// ====================================================================================================== // ======================================================================================================
// 公开的命令接口 // 设备控制方法 - 使用新的指令发送器
// ====================================================================================================== // ======================================================================================================
/** /**
* 获取耳机版本号 * 获取设备版本信息
* @return 是否成功发送命令
*/ */
fun getDeviceVersion(): Boolean { fun getDeviceVersion(): Boolean {
Log.i(TAG, "获取耳机版本号...") Log.i(TAG, "获取设备版本信息...")
return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte())) return commandSender.sendCommand(BleConst.CMD_GET_VERSION.toByte())
} }
/** /**
* 获取产品ID * 获取产品ID
* @return 是否成功发送命令
*/ */
fun getProductId(): Boolean { fun getProductId(): Boolean {
Log.i(TAG, "获取耳机产品ID...") Log.i(TAG, "获取产品ID...")
return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte())) return commandSender.sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte())
} }
/** /**
* 获取耳机颜色ID * 获取颜色ID
* @return 是否成功发送命令
*/ */
fun getColorId(): Boolean { fun getColorId(): Boolean {
Log.i(TAG, "获取耳机颜色ID...") Log.i(TAG, "获取颜色ID...")
return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte())) return commandSender.sendCommand(BleConst.CMD_GET_COLOR_ID.toByte())
} }
/** /**
* 获取耳机电量信息 * 获取电量信息
* @return 是否成功发送命令
*/ */
fun getBatteryInfo(): Boolean { fun getBatteryInfo(): Boolean {
Log.i(TAG, "获取耳机电量信息...") Log.i(TAG, "获取电量信息...")
return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte())) return commandSender.sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte())
} }
/** /**
* 控制编解码 - 关闭编解码 * 关闭编解码
* @return 是否成功发送命令
*/ */
fun closeCodec(): Boolean { fun closeCodec(): Boolean {
Log.i(TAG, "关闭编解码...") Log.i(TAG, "关闭编解码...")
return sendCommand( val cmdData = byteArrayOf(
BleConst.CMD_CONTROL_CODEC.toByte(), BleConst.CODEC_CONTROL_CLOSE.toByte(),
byteArrayOf( BleConst.AUDIO_CHANNEL_STEREO.toByte()
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
) // 或其他默认声道
) )
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
} }
/** /**
* 控制编解码 - 打开解码 * 打开解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/ */
fun openDecoder(): Boolean { fun openDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
// val opusStarted = startOpusStreamDecoding( Log.i(TAG, "打开解码器...")
// 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.
// )
startOpusStreamDecoding(false, 1, 16000, 40) startOpusStreamDecoding(false, 1, 16000, 40)
val cmdData = byteArrayOf(
Log.i(TAG, "打开解码0xA1") BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
return sendCommand( channelMode.toByte()
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
) )
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
} }
/** /**
* 控制编解码 - 打开解码 * 打开A2DP解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/ */
fun openA2DPDecoder(): Boolean { fun openA2DPDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开编码 0xA2") Log.i(TAG, "打开A2DP解码器...")
startOpusEncodeStream() startOpusEncodeStream()
//双声道 80字节
startOpusStreamDecoding(false, 2, 16000, 80) startOpusStreamDecoding(false, 2, 16000, 80)
// Log.i(TAG, "打开解码...") val cmdData = byteArrayOf(
return sendCommand( BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( channelMode.toByte()
BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
) )
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
} }
/** /**
* 控制编解码 - 打开解码 * 打开通话录音解码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/ */
fun openCallRecordDecoder(): Boolean { fun openCallRecordDecoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开编码 0xA3") Log.i(TAG, "打开通话录音解码器...")
//双声道 80字节
startOpusStreamDecoding(false, 2, 16000, 80) startOpusStreamDecoding(false, 2, 16000, 80)
// Log.i(TAG, "打开解码...") val cmdData = byteArrayOf(
return sendCommand( BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(),
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( channelMode.toByte()
BleConst.CODEC_CONTROL_CALL_RECORD_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
) )
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
} }
/** /**
* 控制编解码 - A2DP播放 * A2DP播放
* @return 是否成功发送命令
*/ */
fun playA2DP(): Boolean { fun playA2DP(): Boolean {
Log.i(TAG, "切换至A2DP播放模式...") Log.i(TAG, "A2DP播放...")
return sendCommand(BleConst.CMD_PLAY_A2DP.toByte()) return commandSender.sendCommand(BleConst.CMD_PLAY_A2DP.toByte())
} }
/** /**
* 控制编解码 - 打开编码 * 打开编码器
* @param channelMode 声道模式
* @return 是否成功发送命令
*/ */
fun openEncoder(): Boolean { fun openEncoder(channelMode: Int = BleConst.AUDIO_CHANNEL_STEREO): Boolean {
Log.i(TAG, "打开编码0xB1") Log.i(TAG, "打开编码器...")
startOpusStreamDecoding(false, 1, 16000, 40) startOpusStreamDecoding(false, 1, 16000, 40)
// // 目前仅发送命令通知设备开始编码。 val cmdData = byteArrayOf(
// return sendCommand( BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
// BleConst.CMD_CONTROL_CODEC.toByte(), channelMode.toByte()
// byteArrayOf( )
// BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
// BleConst.AUDIO_CHANNEL_RIGHT.toByte() // 或设备期望的声道
// )
// )
return true
} }
/** /**
* 设置声道模式 * 设置声道模式
* @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO * @param channelMode 声道模式
* @return 是否成功发送命令
*/ */
fun setChannelMode(channelMode: Int): Boolean { fun setChannelMode(channelMode: Int): Boolean {
when (channelMode) { Log.i(TAG, "设置声道模式: $channelMode")
BleConst.AUDIO_CHANNEL_LEFT -> Log.i(TAG, "设置左声道模式...") val cmdData = byteArrayOf(
BleConst.AUDIO_CHANNEL_RIGHT -> Log.i(TAG, "设置右声道模式...") BleConst.CODEC_CONTROL_ENCODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_STEREO -> Log.i(TAG, "设置立体声模式...") channelMode.toByte()
else -> {
Log.e(TAG, "无效的声道模式: $channelMode")
return false
}
}
// 高级控制:设置命令类型、长度和两个参数
return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
0xA1.toByte(), // 状态码 - 使用解码状态
channelMode.toByte() // 声道模式
)
) )
return commandSender.sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), cmdData)
} }
/** /**
* 音量增大 * 音量增大
* @return 是否成功发送命令
*/ */
fun volumeUp(): Boolean { fun volumeUp(): Boolean {
Log.i(TAG, "设备音量增加...") Log.i(TAG, "设备音量增加...")
return sendCommand(BleConst.CMD_VOLUME_UP.toByte()) return commandSender.sendCommand(BleConst.CMD_VOLUME_UP.toByte())
} }
/** /**
* 音量减小 * 音量减小
* @return 是否成功发送命令
*/ */
fun volumeDown(): Boolean { fun volumeDown(): Boolean {
Log.i(TAG, "设备音量减小...") Log.i(TAG, "设备音量减小...")
return sendCommand(BleConst.CMD_VOLUME_DOWN.toByte()) return commandSender.sendCommand(BleConst.CMD_VOLUME_DOWN.toByte())
} }
// ====================================================================================================== // ======================================================================================================
// 工具方法 // 连接状态和工具方法
// ====================================================================================================== // ======================================================================================================
/** /**
* 检查是否已连接 * 检查连接状态
* @return 是否已连接
*/ */
fun isConnected(): Boolean { private fun checkConn(): Boolean {
return connectionState.value == BleConst.STATE_CONNECTED return bluetoothGatt != null &&
connectionState.value == BleConst.STATE_CONNECTED &&
writeChar != null
} }
/** /**
* 连接状态检查 * 是否已连接
* @return 连接状态
*/ */
private fun checkConn(): Boolean = fun isConnected(): Boolean {
bluetoothGatt != null && writeChar != null && callWriteChar != null && return connectionState.value == BleConst.STATE_CONNECTED
connectionState.value == BleConst.STATE_CONNECTED }
/** /**
* 获取已连接设备地址 * 获取已连接设备的地址
* @return 设备地址,未连接时返回null
*/ */
fun getConnectedDeviceAddress(): String? { fun getConnectedDeviceAddress(): String? {
return bluetoothGatt?.device?.address return bluetoothGatt?.device?.address
@ -1803,6 +1805,7 @@ startBytesStatistics()
/** /**
* 获取蓝牙适配器 * 获取蓝牙适配器
* @return 蓝牙适配器实例
*/ */
fun getBluetoothAdapter(): BluetoothAdapter? { fun getBluetoothAdapter(): BluetoothAdapter? {
return bluetoothAdapter return bluetoothAdapter
@ -1813,129 +1816,38 @@ startBytesStatistics()
// 资源清理 // 资源清理
// ====================================================================================================== // ======================================================================================================
/** /**
* 清理所有资源 * 清理资源
*/ */
fun cleanup() { fun cleanup() {
Log.i(TAG, "清理BLE服务资源...")
// 重置指令发送器
commandSender.reset()
// 停止扫描
stopScan() stopScan()
scanHandler.removeCallbacksAndMessages(null)
disconnectGatt()
// 停止音频队列处理线程
audioQueueProcessorThread?.interrupt()
audioQueueProcessorThread = null
audioDataQueue.clear()
// 停止音频发送线程 // 断开连接
disconnect()
// 清理回调
clearCallbacks()
// 停止音频处理
stopAudioSendThread() stopAudioSendThread()
// 清理音频数据缓存
synchronized(audioDataBuffer) {
audioDataBuffer.clear()
Log.d(TAG, "已清理音频数据缓存")
}
Log.d(TAG, "音频解码线程和队列处理线程已清理")
// 释放OpusManager
stopOpusEncodeStream()
stopOpusStreamDecoding() stopOpusStreamDecoding()
opusManager?.release() stopOpusEncodeStream()
opusManager = null
option = null isInitialized = false
Log.d(TAG, "OpusManager已释放") Log.i(TAG, "BLE服务资源清理完成")
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
}
} }
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校验值 * 计算CRC8/MAXIM校验值
* @param data 要校验的数据
* @param length 数据长度
* @return CRC8校验值
*/ */
private fun crc8Maxim(data: ByteArray, length: Int): Byte { private fun crc8Maxim(data: ByteArray, length: Int): Byte {
var crc = 0 var crc = 0

Loading…
Cancel
Save