|
|
@ -37,10 +37,11 @@ import java.util.concurrent.TimeUnit |
|
|
*/ |
|
|
*/ |
|
|
object BleService { |
|
|
object BleService { |
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// 常量定义 |
|
|
// 常量定义 |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
private const val TAG = "BleService" |
|
|
private const val TAG = "BleService" |
|
|
|
|
|
private const val REPLY_TIMEOUT_MS = 1000L // 响应超时时间1秒 |
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// 回调接口 |
|
|
// 回调接口 |
|
|
@ -118,7 +119,6 @@ object BleService { |
|
|
|
|
|
|
|
|
// 音频数据缓存 |
|
|
// 音频数据缓存 |
|
|
private val audioDataBuffer = mutableListOf<Byte>() |
|
|
private val audioDataBuffer = mutableListOf<Byte>() |
|
|
//private val AUDIO_BUFFER_SIZE = 1280// 1280字节缓存阈值 |
|
|
|
|
|
|
|
|
|
|
|
// 音频数据发送相关 |
|
|
// 音频数据发送相关 |
|
|
private val audioSendQueue = LinkedBlockingQueue<ByteArray>() |
|
|
private val audioSendQueue = LinkedBlockingQueue<ByteArray>() |
|
|
@ -140,11 +140,19 @@ object BleService { |
|
|
// 初始化状态 |
|
|
// 初始化状态 |
|
|
private var isInitialized = false |
|
|
private var isInitialized = false |
|
|
|
|
|
|
|
|
// 指令发送器 |
|
|
// ====================================================================================================== |
|
|
|
|
|
// 指令发送和管理相关变量(整合自BleCommandSender) |
|
|
|
|
|
// ====================================================================================================== |
|
|
|
|
|
|
|
|
|
|
|
/** 指令发送器实例 */ |
|
|
private val commandSender = BleCommandSender() |
|
|
private val commandSender = BleCommandSender() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 检查是否有蓝牙扫描权限 |
|
|
* 检查是否有蓝牙扫描权限 |
|
|
|
|
|
* Android 12+ 需要BLUETOOTH_SCAN权限,之前版本需要ACCESS_FINE_LOCATION权限 |
|
|
|
|
|
* |
|
|
|
|
|
* @return true 如果有相应权限,false 如果没有权限 |
|
|
*/ |
|
|
*/ |
|
|
private fun hasBluetoothScanPermission(): Boolean { |
|
|
private fun hasBluetoothScanPermission(): Boolean { |
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
@ -248,17 +256,58 @@ object BleService { |
|
|
*/ |
|
|
*/ |
|
|
private fun initializeCommandSender() { |
|
|
private fun initializeCommandSender() { |
|
|
commandSender.setCallback(object : BleCommandSender.CommandSenderCallback { |
|
|
commandSender.setCallback(object : BleCommandSender.CommandSenderCallback { |
|
|
|
|
|
/** |
|
|
|
|
|
* 检查是否已连接 |
|
|
|
|
|
*/ |
|
|
override fun isConnected(): Boolean { |
|
|
override fun isConnected(): Boolean { |
|
|
return checkConn() |
|
|
return checkConn() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取写入特征 |
|
|
|
|
|
*/ |
|
|
override fun getWriteCharacteristic(): BluetoothGattCharacteristic? { |
|
|
override fun getWriteCharacteristic(): BluetoothGattCharacteristic? { |
|
|
return writeChar |
|
|
return writeChar |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取蓝牙GATT连接 |
|
|
|
|
|
*/ |
|
|
override fun getBluetoothGatt(): BluetoothGatt? { |
|
|
override fun getBluetoothGatt(): BluetoothGatt? { |
|
|
return bluetoothGatt |
|
|
return bluetoothGatt |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 通知设备信息接收 |
|
|
|
|
|
* @param infoType 信息类型 |
|
|
|
|
|
* @param infoData 信息数据 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun notifyDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) { |
|
|
|
|
|
// 使用外部类的方法来分发设备信息,避免无限递归 |
|
|
|
|
|
this@BleService.notifyDeviceInfoReceived(infoType, infoData) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 通知唤醒信号接收 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun notifyWakeupSignalReceived() { |
|
|
|
|
|
// 使用外部类的方法来分发唤醒信号,避免无限递归 |
|
|
|
|
|
this@BleService.notifyWakeupSignalReceived() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取录音文件实例 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun getRecordFile(): RecordingFile? { |
|
|
|
|
|
return recordfile |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取录音文件实例1 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun getRecordFile1(): RecordingFile? { |
|
|
|
|
|
return recordfile1 |
|
|
|
|
|
} |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -283,6 +332,12 @@ object BleService { |
|
|
Log.d(TAG, "已清除所有BLE回调") |
|
|
Log.d(TAG, "已清除所有BLE回调") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 写入外部音频数据到Opus编码流 |
|
|
|
|
|
* 该函数用于将外部音频数据(如麦克风录音)写入到Opus编码器中 |
|
|
|
|
|
* |
|
|
|
|
|
* @param data 音频数据字节数组,通常为PCM格式 |
|
|
|
|
|
*/ |
|
|
fun writeExternalAudioData(data: ByteArray) { |
|
|
fun writeExternalAudioData(data: ByteArray) { |
|
|
// 检查OpusManager是否已初始化 |
|
|
// 检查OpusManager是否已初始化 |
|
|
if (opusManager == null) { |
|
|
if (opusManager == null) { |
|
|
@ -305,6 +360,11 @@ object BleService { |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// 扫描功能 |
|
|
// 扫描功能 |
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 重新初始化蓝牙适配器和扫描器 |
|
|
|
|
|
* 当蓝牙适配器状态发生变化时,需要重新获取最新的实例引用 |
|
|
|
|
|
*/ |
|
|
fun reinitializeBle() { |
|
|
fun reinitializeBle() { |
|
|
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
bluetoothAdapter = manager.adapter // 重新获取新引用 |
|
|
bluetoothAdapter = manager.adapter // 重新获取新引用 |
|
|
@ -812,11 +872,11 @@ startBytesStatistics() |
|
|
when { |
|
|
when { |
|
|
// 设备响应 (0xBB开头) |
|
|
// 设备响应 (0xBB开头) |
|
|
data.isNotEmpty() && data[0] == BleConst.DEVICE_RESPONSE_HEADER -> { |
|
|
data.isNotEmpty() && data[0] == BleConst.DEVICE_RESPONSE_HEADER -> { |
|
|
processDeviceResponse(data) |
|
|
commandSender.processDeviceResponse(data) |
|
|
} |
|
|
} |
|
|
// 设备主动上报 (0xCC开头) |
|
|
// 设备主动上报 (0xCC开头) |
|
|
data.isNotEmpty() && data[0] == BleConst.DEVICE_NOTIFY_HEADER -> { |
|
|
data.isNotEmpty() && data[0] == BleConst.DEVICE_NOTIFY_HEADER -> { |
|
|
processDeviceNotification(data) |
|
|
commandSender.processDeviceNotification(data) |
|
|
} |
|
|
} |
|
|
// 其他未知数据 |
|
|
// 其他未知数据 |
|
|
else -> { |
|
|
else -> { |
|
|
@ -841,6 +901,7 @@ startBytesStatistics() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override fun onCharacteristicWrite( |
|
|
override fun onCharacteristicWrite( |
|
|
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int |
|
|
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int |
|
|
) { |
|
|
) { |
|
|
@ -895,381 +956,6 @@ startBytesStatistics() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 处理设备响应数据 - 帧头为0xBB |
|
|
|
|
|
*/ |
|
|
|
|
|
@SuppressLint("StaticFieldLeak") |
|
|
|
|
|
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) |
|
|
|
|
|
private fun processDeviceResponse(data: ByteArray) { |
|
|
|
|
|
Log.d( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
|
|
) |
|
|
|
|
|
if (data.size < 3) { |
|
|
|
|
|
Log.e(TAG, "设备响应数据格式错误:数据长度过短") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val header = data[0] // 0xBB |
|
|
|
|
|
val cmdType = data[1].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 判断命令失败响应(长度为0) |
|
|
|
|
|
if (data.size == 4 && data[2].toInt() and 0xFF == 0) { |
|
|
|
|
|
Log.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败") |
|
|
|
|
|
// 创建失败结果Map |
|
|
|
|
|
val resultMap = mapOf( |
|
|
|
|
|
"success" to false, |
|
|
|
|
|
"command" to cmdType, |
|
|
|
|
|
"rawData" to byteArrayOf() |
|
|
|
|
|
) |
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
// 通知指令发送器处理响应 |
|
|
|
|
|
commandSender.onCommandResponse(cmdType) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 正常响应处理 |
|
|
|
|
|
val length = data[2].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 检查数据长度 |
|
|
|
|
|
if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC |
|
|
|
|
|
Log.e(TAG, "设备响应数据长度不匹配: 期望=${length + 4}, 实际=${data.size}") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 |
|
|
|
|
|
val payload = data.sliceArray(0 until data.size - 1) |
|
|
|
|
|
val calculatedCrc = crc8Maxim(payload, payload.size) |
|
|
|
|
|
val receivedCrc = data[data.size - 1].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { |
|
|
|
|
|
Log.e( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}" |
|
|
|
|
|
) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 提取响应数据 |
|
|
|
|
|
val responseData = if (length > 0) ByteArray(length) else ByteArray(0) |
|
|
|
|
|
if (length > 0) { |
|
|
|
|
|
System.arraycopy(data, 3, responseData, 0, length) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.d( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${ |
|
|
|
|
|
responseData.joinToString(", ") { |
|
|
|
|
|
"0x${ |
|
|
|
|
|
(it.toInt() and 0xFF).toString(16) |
|
|
|
|
|
}" |
|
|
|
|
|
} |
|
|
|
|
|
}" |
|
|
|
|
|
) |
|
|
|
|
|
// 通知指令发送器处理响应 |
|
|
|
|
|
commandSender.onCommandResponse(cmdType) |
|
|
|
|
|
// 根据命令类型处理不同的响应 |
|
|
|
|
|
when (cmdType) { |
|
|
|
|
|
BleConst.CMD_GET_VERSION -> { |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to "versionInfo", |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty()) { |
|
|
|
|
|
// 转换为字符串前,确认是否是有效的ASCII字符 |
|
|
|
|
|
val version = |
|
|
|
|
|
responseData |
|
|
|
|
|
.map { it.toInt() and 0xFF } // 转为无符号整数 (0-255) |
|
|
|
|
|
.dropWhile { it == 0xFF } // 仅丢弃开头的连续 255 |
|
|
|
|
|
.joinToString(".") { it.toString() } |
|
|
|
|
|
resultMap["version"] = version |
|
|
|
|
|
Log.i(TAG, "接收到目标版本号: $version") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BleConst.CMD_GET_PRODUCT_ID -> { |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to "productId", |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty()) { |
|
|
|
|
|
// 转换为字符串前,确认是否是有效的ASCII字符 |
|
|
|
|
|
val productId = |
|
|
|
|
|
responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } |
|
|
|
|
|
resultMap["productId"] = productId |
|
|
|
|
|
Log.i(TAG, "接收到产品ID: $productId") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BleConst.CMD_GET_COLOR_ID -> { |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to "colorInfo", |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty()) { |
|
|
|
|
|
val colorId = (responseData[0].toInt() and 0xFF).toString() |
|
|
|
|
|
resultMap["colorId"] = colorId |
|
|
|
|
|
Log.i(TAG, "接收到耳机颜色ID: $colorId") |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BleConst.CMD_GET_BATTERY_INFO -> { |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to "batteryInfo", |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty() && responseData.size >= 3) { |
|
|
|
|
|
// 处理电池信息 - 左耳、右耳、充电仓 |
|
|
|
|
|
val leftEarBattery = responseData[0].toInt() and 0x7F |
|
|
|
|
|
val leftEarCharging = (responseData[0].toInt() and 0x80) != 0 |
|
|
|
|
|
|
|
|
|
|
|
val rightEarBattery = |
|
|
|
|
|
if (responseData.size > 1) responseData[1].toInt() and 0x7F else 0 |
|
|
|
|
|
val rightEarCharging = |
|
|
|
|
|
if (responseData.size > 1) (responseData[1].toInt() and 0x80) != 0 else false |
|
|
|
|
|
|
|
|
|
|
|
val caseBattery = |
|
|
|
|
|
if (responseData.size > 2) responseData[2].toInt() and 0x7F else 0 |
|
|
|
|
|
val caseCharging = |
|
|
|
|
|
if (responseData.size > 2) (responseData[2].toInt() and 0x80) != 0 else false |
|
|
|
|
|
|
|
|
|
|
|
// 添加到结果Map |
|
|
|
|
|
resultMap["leftEarBattery"] = leftEarBattery |
|
|
|
|
|
resultMap["leftEarCharging"] = leftEarCharging |
|
|
|
|
|
resultMap["rightEarBattery"] = rightEarBattery |
|
|
|
|
|
resultMap["rightEarCharging"] = rightEarCharging |
|
|
|
|
|
resultMap["caseBattery"] = caseBattery |
|
|
|
|
|
resultMap["caseCharging"] = caseCharging |
|
|
|
|
|
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 |
|
|
|
|
|
|
|
|
|
|
|
Log.i( |
|
|
|
|
|
TAG, "接收到电量信息: 左耳=${leftEarBattery}% " + |
|
|
|
|
|
"(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + |
|
|
|
|
|
"(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + |
|
|
|
|
|
"(${if (caseCharging) "充电中" else "未充电"})" |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BleConst.CMD_CONTROL_CODEC -> { |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to cmdType, |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty() && responseData.size >= 2) { |
|
|
|
|
|
val codecStatus = responseData[0].toInt() and 0xFF |
|
|
|
|
|
val channelMode = responseData[1].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 解码状态描述 |
|
|
|
|
|
val statusDesc = when (codecStatus) { |
|
|
|
|
|
BleConst.CODEC_CONTROL_CLOSE -> "已关闭编解码" |
|
|
|
|
|
BleConst.CODEC_CONTROL_DECODE_ON -> "已打开解码" |
|
|
|
|
|
BleConst.CODEC_CONTROL_A2DP_PLAY -> "A2DP播放模式" |
|
|
|
|
|
BleConst.CODEC_CONTROL_CALL_RECORD_PLAY -> "通话记录播放模式" |
|
|
|
|
|
BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码" |
|
|
|
|
|
else -> "未知状态($codecStatus)" |
|
|
|
|
|
} |
|
|
|
|
|
if (codecStatus == BleConst.CODEC_CONTROL_DECODE_ON || |
|
|
|
|
|
codecStatus == BleConst.CODEC_CONTROL_A2DP_PLAY || |
|
|
|
|
|
codecStatus == BleConst.CODEC_CONTROL_CALL_RECORD_PLAY || |
|
|
|
|
|
codecStatus == BleConst.CODEC_CONTROL_ENCODE_ON |
|
|
|
|
|
) { |
|
|
|
|
|
recordfile1!!.closeFile() |
|
|
|
|
|
recordfile1!!.creatingFiles() |
|
|
|
|
|
recordfile!!.closeFile() |
|
|
|
|
|
recordfile!!.creatingFiles() |
|
|
|
|
|
} else if (codecStatus == BleConst.CODEC_CONTROL_CLOSE) { |
|
|
|
|
|
recordfile!!.closeFile() |
|
|
|
|
|
recordfile1!!.closeFile() |
|
|
|
|
|
} |
|
|
|
|
|
Log.d(TAG, "收到设备响应,channelMode: $channelMode") |
|
|
|
|
|
// 声道模式描述 |
|
|
|
|
|
val channelDesc = when (channelMode) { |
|
|
|
|
|
BleConst.AUDIO_CHANNEL_LEFT -> "左声道" |
|
|
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT -> "右声道" |
|
|
|
|
|
BleConst.AUDIO_CHANNEL_STEREO -> "立体声" |
|
|
|
|
|
else -> "未知声道($channelMode)" |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
resultMap["codecStatus"] = codecStatus |
|
|
|
|
|
resultMap["channelMode"] = channelMode |
|
|
|
|
|
resultMap["statusDesc"] = statusDesc |
|
|
|
|
|
resultMap["channelDesc"] = channelDesc |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.i(TAG, "编解码控制响应: $statusDesc, $channelDesc") |
|
|
|
|
|
}else if (responseData.isNotEmpty() && responseData.size == 1) { |
|
|
|
|
|
val codecStatus = responseData[0].toInt() and 0xFF |
|
|
|
|
|
if(codecStatus==0x00){ |
|
|
|
|
|
Log.d( |
|
|
|
|
|
"BleService", |
|
|
|
|
|
"收到设备响应:失败,codecStatus: $codecStatus" |
|
|
|
|
|
) |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to "Error", |
|
|
|
|
|
"rawData" to responseData |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
else -> { |
|
|
|
|
|
// 对于未知命令,返回原始数据 |
|
|
|
|
|
val resultMap = mapOf( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to cmdType, |
|
|
|
|
|
"rawData" to responseData, |
|
|
|
|
|
"isUnknown" to true |
|
|
|
|
|
) |
|
|
|
|
|
Log.d(TAG, "收到未知命令响应: 0x${cmdType.toString(16)}") |
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 处理设备主动上报数据 - 帧头为0xCC |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun processDeviceNotification(data: ByteArray) { |
|
|
|
|
|
Log.d( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
|
|
) |
|
|
|
|
|
if (data.size < 3) { |
|
|
|
|
|
Log.e(TAG, "设备主动上报数据格式错误:数据长度过短") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val header = data[0] // 0xCC |
|
|
|
|
|
val infoType = data[1].toInt() and 0xFF |
|
|
|
|
|
val length = data[2].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 检查数据长度 |
|
|
|
|
|
if (data.size != length + 4) { // 帧头 + 类型 + 长度 + 数据 + CRC |
|
|
|
|
|
Log.e(TAG, "设备主动上报数据长度不匹配: 期望=${length + 4}, 实际=${data.size}") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 计算CRC校验 - 修正:计算除最后一个CRC字节外的所有字节 |
|
|
|
|
|
val payload = data.sliceArray(0 until data.size - 1) |
|
|
|
|
|
val calculatedCrc = crc8Maxim(payload, payload.size) |
|
|
|
|
|
val receivedCrc = data[data.size - 1].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
if (calculatedCrc.toInt() and 0xFF != receivedCrc) { |
|
|
|
|
|
Log.e( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}" |
|
|
|
|
|
) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 提取上报数据 |
|
|
|
|
|
val notifyData = if (length > 0) ByteArray(length) else ByteArray(0) |
|
|
|
|
|
if (length > 0) { |
|
|
|
|
|
System.arraycopy(data, 3, notifyData, 0, length) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Log.d( |
|
|
|
|
|
TAG, |
|
|
|
|
|
"收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${ |
|
|
|
|
|
notifyData.joinToString(", ") { |
|
|
|
|
|
"0x${ |
|
|
|
|
|
(it.toInt() and 0xFF).toString(16) |
|
|
|
|
|
}" |
|
|
|
|
|
} |
|
|
|
|
|
}" |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// 根据上报类型处理 |
|
|
|
|
|
when (infoType) { |
|
|
|
|
|
BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息 |
|
|
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to infoType, |
|
|
|
|
|
"rawData" to notifyData, |
|
|
|
|
|
"isNotification" to true // 标记为主动上报 |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (notifyData.isNotEmpty() && notifyData.size >= 3) { |
|
|
|
|
|
// 处理电池信息 - 左耳、右耳、充电仓 |
|
|
|
|
|
val leftEarBattery = notifyData[0].toInt() and 0x7F |
|
|
|
|
|
val leftEarCharging = (notifyData[0].toInt() and 0x80) != 0 |
|
|
|
|
|
|
|
|
|
|
|
val rightEarBattery = |
|
|
|
|
|
if (notifyData.size > 1) notifyData[1].toInt() and 0x7F else 0 |
|
|
|
|
|
val rightEarCharging = |
|
|
|
|
|
if (notifyData.size > 1) (notifyData[1].toInt() and 0x80) != 0 else false |
|
|
|
|
|
|
|
|
|
|
|
val caseBattery = if (notifyData.size > 2) notifyData[2].toInt() and 0x7F else 0 |
|
|
|
|
|
val caseCharging = |
|
|
|
|
|
if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到结果Map |
|
|
|
|
|
resultMap["leftEarBattery"] = leftEarBattery |
|
|
|
|
|
resultMap["leftEarCharging"] = leftEarCharging |
|
|
|
|
|
resultMap["rightEarBattery"] = rightEarBattery |
|
|
|
|
|
resultMap["rightEarCharging"] = rightEarCharging |
|
|
|
|
|
resultMap["caseBattery"] = caseBattery |
|
|
|
|
|
resultMap["caseCharging"] = caseCharging |
|
|
|
|
|
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 |
|
|
|
|
|
|
|
|
|
|
|
Log.i( |
|
|
|
|
|
TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + |
|
|
|
|
|
"(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + |
|
|
|
|
|
"(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + |
|
|
|
|
|
"(${if (caseCharging) "充电中" else "未充电"})" |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(infoType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
BleConst.CMD_WAKE_UP -> { |
|
|
|
|
|
Log.i(TAG, "收到唤醒指令") |
|
|
|
|
|
// 接收到唤醒信号,打开编码器 (设备侧) |
|
|
|
|
|
|
|
|
|
|
|
notifyWakeupSignalReceived() |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
// 可以在这里添加其他主动上报类型的处理 |
|
|
|
|
|
else -> { |
|
|
|
|
|
// 对于未知上报类型,返回原始数据 |
|
|
|
|
|
val resultMap = mapOf( |
|
|
|
|
|
"success" to true, |
|
|
|
|
|
"command" to infoType, |
|
|
|
|
|
"rawData" to notifyData, |
|
|
|
|
|
"isUnknown" to true, |
|
|
|
|
|
"isNotification" to true // 标记为主动上报 |
|
|
|
|
|
) |
|
|
|
|
|
Log.d(TAG, "收到未知主动上报类型: 0x${infoType.toString(16)}") |
|
|
|
|
|
notifyDeviceInfoReceived(infoType, resultMap) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ====================================================================================================== |
|
|
// ====================================================================================================== |
|
|
// Opus 解码相关 |
|
|
// Opus 解码相关 |
|
|
@ -1811,29 +1497,6 @@ startBytesStatistics() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 计算CRC8/MAXIM校验值 |
|
|
|
|
|
* @param data 要校验的数据 |
|
|
|
|
|
* @param length 数据长度 |
|
|
|
|
|
* @return CRC8校验值 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun crc8Maxim(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() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 向所有回调监听器分发扫描结果 |
|
|
* 向所有回调监听器分发扫描结果 |
|
|
*/ |
|
|
*/ |
|
|
|