Browse Source

优化代码结构

weicu
fdp 1 year ago
parent
commit
bc30bddcc0
  1. 405
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleCommandSender.kt
  2. 469
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

405
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<String, Any>)
/**
* 通知唤醒信号接收
*/
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<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")
}
callback?.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")
}
callback?.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")
}
callback?.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 "未充电"})"
)
}
callback?.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
) {
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<String, Any>(
"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<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 "未充电"})"
)
}
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)
}
}
}
/**
* 清空命令队列
*/

469
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<Byte>()
//private val AUDIO_BUFFER_SIZE = 1280// 1280字节缓存阈值
// 音频数据发送相关
private val audioSendQueue = LinkedBlockingQueue<ByteArray>()
@ -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<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回调")
}
/**
* 写入外部音频数据到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<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 解码相关
@ -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()
}
/**
* 向所有回调监听器分发扫描结果
*/

Loading…
Cancel
Save