From 8c34005b6be425c7d4ee39d8d41a843ca4d916c6 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Wed, 24 Sep 2025 14:56:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ble_service/BleCommandSender.kt | 405 ++++++++++++++- .../yunqiinnovation/ble_service/BleService.kt | 469 +++--------------- 2 files changed, 470 insertions(+), 404 deletions(-) diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt index 35a241112..44bb4f630 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt @@ -1,6 +1,7 @@ package com.yunqiinnovation.ble_service import android.Manifest +import android.annotation.SuppressLint import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCharacteristic import android.os.Handler @@ -10,12 +11,13 @@ import androidx.annotation.RequiresPermission import java.util.* /** - * BLE指令发送器类:负责处理蓝牙设备的指令发送和队列管理 + * BLE指令发送器类:负责处理蓝牙设备的指令发送、接收和队列管理 * 功能包括: * 1. 指令队列管理 * 2. 指令封装和CRC校验 * 3. 指令发送和重试机制 * 4. 响应超时处理 + * 5. 设备响应和通知处理 */ class BleCommandSender { @@ -70,6 +72,30 @@ class BleCommandSender { * @return GATT连接对象 */ fun getBluetoothGatt(): BluetoothGatt? + + /** + * 通知设备信息接收 + * @param infoType 信息类型 + * @param infoData 信息数据 + */ + fun notifyDeviceInfoReceived(infoType: Int, infoData: Map) + + /** + * 通知唤醒信号接收 + */ + fun notifyWakeupSignalReceived() + + /** + * 获取录音文件对象 + * @return 录音文件对象 + */ + fun getRecordFile(): RecordingFile? + + /** + * 获取重新压缩录音文件对象 + * @return 重新压缩录音文件对象 + */ + fun getRecordFile1(): RecordingFile? } private var callback: CommandSenderCallback? = null @@ -156,6 +182,383 @@ class BleCommandSender { processNextCommand() } + /** + * 处理设备响应数据 - 从BleService移动过来 + * @param data 响应数据 + */ + @SuppressLint("StaticFieldLeak") + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + 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() + ) + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + // 通知指令发送器处理响应 + 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 = calculateCrc8Maxim(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) + }" + } + }" + ) + // 通知指令发送器处理响应 + onCommandResponse(cmdType) + // 根据命令类型处理不同的响应 + when (cmdType) { + BleConst.CMD_GET_VERSION -> { + val resultMap = mutableMapOf( + "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") + } + + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + } + + BleConst.CMD_GET_PRODUCT_ID -> { + val resultMap = mutableMapOf( + "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") + } + + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + } + + BleConst.CMD_GET_COLOR_ID -> { + val resultMap = mutableMapOf( + "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") + } + + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + } + + BleConst.CMD_GET_BATTERY_INFO -> { + val resultMap = mutableMapOf( + "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 "未充电"})" + ) + } + + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + } + + BleConst.CMD_CONTROL_CODEC -> { + val resultMap = mutableMapOf( + "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 + ) { + callback?.getRecordFile1()?.closeFile() + callback?.getRecordFile1()?.creatingFiles() + callback?.getRecordFile()?.closeFile() + callback?.getRecordFile()?.creatingFiles() + } else if (codecStatus == BleConst.CODEC_CONTROL_CLOSE) { + callback?.getRecordFile()?.closeFile() + callback?.getRecordFile1()?.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(TAG, "收到设备响应:失败,codecStatus: $codecStatus") + val resultMap = mutableMapOf( + "success" to true, + "command" to "Error", + "rawData" to responseData + ) + } + } + + callback?.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)}") + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + } + } + } + + /** + * 处理设备主动上报数据 - 帧头为0xCC + * 将可见性修改为internal,允许同包内的BleService类访问 + */ + 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 = calculateCrc8Maxim(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( + "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 "未充电"})" + ) + } + + callback?.notifyDeviceInfoReceived(infoType, resultMap) + } + + BleConst.CMD_WAKE_UP -> { + Log.i(TAG, "收到唤醒指令") + // 接收到唤醒信号,打开编码器 (设备侧) + + callback?.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)}") + callback?.notifyDeviceInfoReceived(infoType, resultMap) + } + } + } + + /** * 清空命令队列 */ diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index f10632901..aad017767 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -37,10 +37,11 @@ import java.util.concurrent.TimeUnit */ object BleService { - // ====================================================================================================== + // ====================================================================================================== // 常量定义 // ====================================================================================================== private const val TAG = "BleService" + private const val REPLY_TIMEOUT_MS = 1000L // 响应超时时间1秒 // ====================================================================================================== // 回调接口 @@ -118,7 +119,6 @@ object BleService { // 音频数据缓存 private val audioDataBuffer = mutableListOf() - //private val AUDIO_BUFFER_SIZE = 1280// 1280字节缓存阈值 // 音频数据发送相关 private val audioSendQueue = LinkedBlockingQueue() @@ -140,11 +140,19 @@ object BleService { // 初始化状态 private var isInitialized = false - // 指令发送器 + // ====================================================================================================== + // 指令发送和管理相关变量(整合自BleCommandSender) + // ====================================================================================================== + + /** 指令发送器实例 */ private val commandSender = BleCommandSender() + /** * 检查是否有蓝牙扫描权限 + * Android 12+ 需要BLUETOOTH_SCAN权限,之前版本需要ACCESS_FINE_LOCATION权限 + * + * @return true 如果有相应权限,false 如果没有权限 */ private fun hasBluetoothScanPermission(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -248,17 +256,58 @@ object BleService { */ private fun initializeCommandSender() { commandSender.setCallback(object : BleCommandSender.CommandSenderCallback { + /** + * 检查是否已连接 + */ override fun isConnected(): Boolean { return checkConn() } + /** + * 获取写入特征 + */ override fun getWriteCharacteristic(): BluetoothGattCharacteristic? { return writeChar } + /** + * 获取蓝牙GATT连接 + */ override fun getBluetoothGatt(): BluetoothGatt? { return bluetoothGatt } + + /** + * 通知设备信息接收 + * @param infoType 信息类型 + * @param infoData 信息数据 + */ + override fun notifyDeviceInfoReceived(infoType: Int, infoData: Map) { + // 使用外部类的方法来分发设备信息,避免无限递归 + 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回调") } + /** + * 写入外部音频数据到Opus编码流 + * 该函数用于将外部音频数据(如麦克风录音)写入到Opus编码器中 + * + * @param data 音频数据字节数组,通常为PCM格式 + */ fun writeExternalAudioData(data: ByteArray) { // 检查OpusManager是否已初始化 if (opusManager == null) { @@ -305,6 +360,11 @@ object BleService { // ====================================================================================================== // 扫描功能 // ====================================================================================================== + + /** + * 重新初始化蓝牙适配器和扫描器 + * 当蓝牙适配器状态发生变化时,需要重新获取最新的实例引用 + */ fun reinitializeBle() { val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = manager.adapter // 重新获取新引用 @@ -812,11 +872,11 @@ startBytesStatistics() when { // 设备响应 (0xBB开头) data.isNotEmpty() && data[0] == BleConst.DEVICE_RESPONSE_HEADER -> { - processDeviceResponse(data) + commandSender.processDeviceResponse(data) } // 设备主动上报 (0xCC开头) data.isNotEmpty() && data[0] == BleConst.DEVICE_NOTIFY_HEADER -> { - processDeviceNotification(data) + commandSender.processDeviceNotification(data) } // 其他未知数据 else -> { @@ -841,6 +901,7 @@ startBytesStatistics() } } + override fun onCharacteristicWrite( 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( - "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( - "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( - "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( - "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( - "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( - "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( - "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 解码相关 @@ -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() - } - /** * 向所有回调监听器分发扫描结果 */