|
|
|
@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat |
|
|
|
import java.util.concurrent.ConcurrentHashMap |
|
|
|
import android.content.pm.ServiceInfo |
|
|
|
import java.util.concurrent.CopyOnWriteArrayList |
|
|
|
import com.yunqiinnovation.jl_opus.JlOpusPlugin |
|
|
|
|
|
|
|
/** |
|
|
|
* BLE服务类:提供蓝牙低功耗设备的扫描、连接和通信功能 |
|
|
|
@ -44,14 +45,14 @@ object BleService { |
|
|
|
interface Callback { |
|
|
|
// 扫描相关回调 |
|
|
|
fun onScanResult(devices: List<Map<String, Any>>) |
|
|
|
|
|
|
|
|
|
|
|
// 连接状态相关回调 |
|
|
|
fun onConnectionStateChanged(state: Int) |
|
|
|
|
|
|
|
|
|
|
|
// 数据相关回调 |
|
|
|
fun onAudioDataReceived(data: ByteArray) |
|
|
|
fun onWakeupSignalReceived() |
|
|
|
|
|
|
|
|
|
|
|
// 设备信息相关回调 - 统一回调接口 |
|
|
|
fun onDeviceInfoReceived(infoType: Int, infoData: Map<String, Any>) |
|
|
|
} |
|
|
|
@ -59,7 +60,7 @@ object BleService { |
|
|
|
// ====================================================================================================== |
|
|
|
// 成员变量 |
|
|
|
// ====================================================================================================== |
|
|
|
|
|
|
|
|
|
|
|
// 上下文 |
|
|
|
private lateinit var context: Context |
|
|
|
|
|
|
|
@ -84,8 +85,10 @@ object BleService { |
|
|
|
|
|
|
|
// 回调集合 - 使用线程安全的集合存储所有回调 |
|
|
|
private val callbacks = CopyOnWriteArrayList<Callback>() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取插件实例 |
|
|
|
private val opusPlugin = JlOpusPlugin() |
|
|
|
|
|
|
|
// 初始化状态 |
|
|
|
private var isInitialized = false |
|
|
|
|
|
|
|
@ -96,18 +99,19 @@ object BleService { |
|
|
|
*/ |
|
|
|
fun initialize(appContext: Context): Boolean { |
|
|
|
if (isInitialized) return true |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
this.context = appContext.applicationContext |
|
|
|
|
|
|
|
|
|
|
|
// 初始化蓝牙管理器和适配器 |
|
|
|
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
|
bluetoothManager = |
|
|
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager |
|
|
|
bluetoothAdapter = bluetoothManager.adapter |
|
|
|
bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner |
|
|
|
|
|
|
|
|
|
|
|
// 初始化Handler |
|
|
|
scanHandler = Handler(Looper.getMainLooper()) |
|
|
|
|
|
|
|
|
|
|
|
isInitialized = true |
|
|
|
FileLogger.d(TAG, "BLE服务初始化成功") |
|
|
|
return true |
|
|
|
@ -116,7 +120,7 @@ object BleService { |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 添加回调监听器 |
|
|
|
* @param callback 要添加的回调监听器 |
|
|
|
@ -130,7 +134,7 @@ object BleService { |
|
|
|
} |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 移除回调监听器 |
|
|
|
* @param callback 要移除的回调监听器 |
|
|
|
@ -143,7 +147,7 @@ object BleService { |
|
|
|
} |
|
|
|
return removed |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 移除所有回调监听器 |
|
|
|
*/ |
|
|
|
@ -165,7 +169,7 @@ object BleService { |
|
|
|
FileLogger.e(TAG, "BLE服务未初始化") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取一个局部引用,避免并发访问问题 |
|
|
|
val scanner = bluetoothLeScanner ?: return false |
|
|
|
if (isScanning) return false |
|
|
|
@ -211,17 +215,17 @@ object BleService { |
|
|
|
// 获取一个局部引用,避免并发访问问题 |
|
|
|
val scanner = bluetoothLeScanner ?: return false |
|
|
|
if (!isScanning) return false |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.i(TAG, "停止主动扫描") |
|
|
|
|
|
|
|
try { |
|
|
|
scanner.stopScan(scanCallback) |
|
|
|
scanHandler.removeCallbacksAndMessages(null) |
|
|
|
isScanning = false |
|
|
|
|
|
|
|
|
|
|
|
// 重新注册后台扫描,保证唤醒能力 |
|
|
|
registerBackgroundScan() |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(TAG, "停止扫描失败: ${e.message}", e) |
|
|
|
@ -235,24 +239,25 @@ object BleService { |
|
|
|
private val scanCallback = object : ScanCallback() { |
|
|
|
override fun onScanResult(type: Int, result: ScanResult) { |
|
|
|
val dev = result.device ?: return |
|
|
|
|
|
|
|
|
|
|
|
// 打印设备信息和服务UUID |
|
|
|
printDeviceInfo(result) |
|
|
|
|
|
|
|
// 直接上报每个扫描结果 |
|
|
|
try { |
|
|
|
// 将单个扫描结果转换为需要的格式 |
|
|
|
val uuids = result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList() |
|
|
|
val uuids = |
|
|
|
result.scanRecord?.serviceUuids?.map { it.uuid.toString() } ?: emptyList() |
|
|
|
val deviceMap = mapOf( |
|
|
|
"id" to dev.address, |
|
|
|
"name" to (dev.name ?: "未知"), |
|
|
|
"rssi" to result.rssi, |
|
|
|
"uuids" to uuids |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// 直接回调单个设备结果 |
|
|
|
notifyScanResult(listOf(deviceMap)) |
|
|
|
|
|
|
|
|
|
|
|
// 更新缓存 |
|
|
|
val idx = scanResults.indexOfFirst { it.device.address == dev.address } |
|
|
|
if (idx >= 0) scanResults[idx] = result else scanResults.add(result) |
|
|
|
@ -273,17 +278,20 @@ object BleService { |
|
|
|
private fun printDeviceInfo(result: ScanResult) { |
|
|
|
val device = result.device |
|
|
|
val scanRecord = result.scanRecord |
|
|
|
val serviceUuids = scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" |
|
|
|
|
|
|
|
FileLogger.d(TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + |
|
|
|
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]") |
|
|
|
|
|
|
|
val serviceUuids = |
|
|
|
scanRecord?.serviceUuids?.joinToString(", ") { it.uuid.toString() } ?: "无" |
|
|
|
|
|
|
|
FileLogger.d( |
|
|
|
TAG, "扫描到设备: 名称=${device.name ?: "未知"}, 地址=${device.address}, " + |
|
|
|
"RSSI=${result.rssi}, 服务UUID=[$serviceUuids]" |
|
|
|
) |
|
|
|
|
|
|
|
// 打印广播数据的详细信息 |
|
|
|
val scanData = scanRecord?.bytes |
|
|
|
if (scanData != null) { |
|
|
|
val hexString = scanData.joinToString("") { String.format("%02X", it) } |
|
|
|
FileLogger.d(TAG, "广播数据: $hexString") |
|
|
|
|
|
|
|
|
|
|
|
// 解析服务数据 |
|
|
|
scanRecord.serviceData?.forEach { (uuid, data) -> |
|
|
|
val dataHex = data.joinToString("") { String.format("%02X", it) } |
|
|
|
@ -304,13 +312,13 @@ object BleService { |
|
|
|
FileLogger.w(TAG, "已经连接中,忽略连接请求") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取一个局部引用,避免并发访问问题 |
|
|
|
val adapter = bluetoothAdapter ?: run { |
|
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 关闭现有连接 |
|
|
|
disconnectGatt() |
|
|
|
|
|
|
|
@ -372,10 +380,12 @@ object BleService { |
|
|
|
updateConnectionState(BleConst.STATE_CONNECTED) |
|
|
|
g.discoverServices() |
|
|
|
} |
|
|
|
|
|
|
|
newState == BluetoothProfile.STATE_DISCONNECTED -> { |
|
|
|
updateConnectionState(BleConst.STATE_DISCONNECTED) |
|
|
|
disconnectGatt() |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
|
disconnectGatt() |
|
|
|
@ -391,13 +401,14 @@ object BleService { |
|
|
|
|
|
|
|
// 获取主服务特征 |
|
|
|
val primarySvc = g.getService(BleConst.PRIMARY_SERVICE_UUID) |
|
|
|
|
|
|
|
notifyChar = primarySvc?.getCharacteristic(BleConst.NOTIFY_CHAR_UUID) |
|
|
|
writeChar = primarySvc?.getCharacteristic(BleConst.WRITE_CHAR_UUID) |
|
|
|
|
|
|
|
// 获取音频服务特征 |
|
|
|
val audioSvc = g.getService(BleConst.AUDIO_SERVICE_UUID) |
|
|
|
audioChar = audioSvc?.getCharacteristic(BleConst.RECEIVE_AUDIO_CHAR_UUID) |
|
|
|
|
|
|
|
|
|
|
|
if (notifyChar == null || writeChar == null) { |
|
|
|
FileLogger.e(TAG, "未找到主服务所需特征") |
|
|
|
updateConnectionState(BleConst.STATE_ERROR) |
|
|
|
@ -405,10 +416,7 @@ object BleService { |
|
|
|
} |
|
|
|
|
|
|
|
writeChar?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE |
|
|
|
|
|
|
|
// 设置主服务的通知 |
|
|
|
setupNotifications(g, notifyChar) |
|
|
|
|
|
|
|
//要先设置音频服务的通知,否则接收不到 |
|
|
|
// 设置音频服务的通知(如果存在) |
|
|
|
if (audioChar != null) { |
|
|
|
setupNotifications(g, audioChar) |
|
|
|
@ -416,13 +424,22 @@ object BleService { |
|
|
|
} else { |
|
|
|
FileLogger.w(TAG, "音频服务特征未找到") |
|
|
|
} |
|
|
|
|
|
|
|
// 设置主服务的通知 |
|
|
|
setupNotifications(g, notifyChar) |
|
|
|
//开启解码 |
|
|
|
opusPlugin?.startOpusDecodeStream(false, 1, 16000, 40, object : |
|
|
|
JlOpusPlugin.CallbackOpus { |
|
|
|
override fun onAudioDataReceivedOpus(pcmData: ByteArray) { |
|
|
|
// 回调给上层 |
|
|
|
notifyAudioDataReceived(pcmData) |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { |
|
|
|
FileLogger.d(TAG, "收到特征数据: ${c.uuid}, 数据=${c.value?.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") |
|
|
|
|
|
|
|
val data = c.value ?: return |
|
|
|
|
|
|
|
|
|
|
|
// 根据特征UUID区分处理 |
|
|
|
when (c.uuid) { |
|
|
|
// 音频特征数据 |
|
|
|
@ -443,13 +460,23 @@ object BleService { |
|
|
|
} |
|
|
|
// 其他未知数据 |
|
|
|
else -> { |
|
|
|
FileLogger.d(TAG, "收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}") |
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到未知数据: ${data.joinToString(", ") { "0x%02X".format(it) }}" |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 其他未知特征 |
|
|
|
else -> { |
|
|
|
FileLogger.d(TAG, "收到未知特征数据: ${c.uuid}, 数据=${data.joinToString(", ") { "0x%02X".format(it) }}") |
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到未知特征数据: ${c.uuid}, 数据=${ |
|
|
|
data.joinToString(", ") { |
|
|
|
"0x%02X".format(it) |
|
|
|
} |
|
|
|
}" |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -457,7 +484,10 @@ object BleService { |
|
|
|
override fun onCharacteristicWrite( |
|
|
|
g: BluetoothGatt, c: BluetoothGattCharacteristic, status: Int |
|
|
|
) { |
|
|
|
FileLogger.i(TAG, "写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}") |
|
|
|
FileLogger.i( |
|
|
|
TAG, |
|
|
|
"写入 ${if (status == BluetoothGatt.GATT_SUCCESS) "成功" else "失败"}" |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
override fun onDescriptorWrite( |
|
|
|
@ -476,7 +506,7 @@ object BleService { |
|
|
|
FileLogger.w(TAG, "特征不支持通知功能: ${characteristic?.uuid}") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
g.setCharacteristicNotification(characteristic, true) |
|
|
|
val desc = characteristic.getDescriptor(BleConst.CLIENT_CHAR_CONFIG_UUID) |
|
|
|
desc?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE |
|
|
|
@ -489,11 +519,9 @@ object BleService { |
|
|
|
*/ |
|
|
|
private fun processAudioData(data: ByteArray) { |
|
|
|
try { |
|
|
|
// 记录日志 |
|
|
|
FileLogger.d(TAG, "收到音频数据: ${data.size}字节") |
|
|
|
|
|
|
|
// 回调给上层 |
|
|
|
notifyAudioDataReceived(data) |
|
|
|
|
|
|
|
opusPlugin?.handleDecodeRequest(data) |
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
FileLogger.e(TAG, "处理音频数据异常: ${e.message}", e) |
|
|
|
} |
|
|
|
@ -503,15 +531,18 @@ object BleService { |
|
|
|
* 处理设备响应数据 - 帧头为0xBB |
|
|
|
*/ |
|
|
|
private fun processDeviceResponse(data: ByteArray) { |
|
|
|
FileLogger.d(TAG, "收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") |
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到设备响应: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
) |
|
|
|
if (data.size < 3) { |
|
|
|
FileLogger.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) { |
|
|
|
FileLogger.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败") |
|
|
|
@ -524,34 +555,46 @@ object BleService { |
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 正常响应处理 |
|
|
|
val length = data[2].toInt() and 0xFF |
|
|
|
|
|
|
|
|
|
|
|
// 检查数据长度 |
|
|
|
if (data.size != length + 4) { // 帧头 + 命令 + 长度 + 数据 + CRC |
|
|
|
FileLogger.e(TAG, "设备响应数据长度不匹配: 期望=${length+4}, 实际=${data.size}") |
|
|
|
FileLogger.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) { |
|
|
|
FileLogger.e(TAG, "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") |
|
|
|
FileLogger.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) |
|
|
|
} |
|
|
|
|
|
|
|
FileLogger.d(TAG, "收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${responseData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${ |
|
|
|
responseData.joinToString(", ") { |
|
|
|
"0x${ |
|
|
|
(it.toInt() and 0xFF).toString(16) |
|
|
|
}" |
|
|
|
} |
|
|
|
}" |
|
|
|
) |
|
|
|
|
|
|
|
// 根据命令类型处理不同的响应 |
|
|
|
when (cmdType) { |
|
|
|
BleConst.CMD_GET_VERSION -> { |
|
|
|
@ -560,64 +603,74 @@ object BleService { |
|
|
|
"command" to "versionInfo", |
|
|
|
"rawData" to responseData |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty()) { |
|
|
|
// 转换为字符串前,确认是否是有效的ASCII字符 |
|
|
|
val version = responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } |
|
|
|
val version = |
|
|
|
responseData.joinToString(".") { (it.toInt() and 0xFF).toString() } |
|
|
|
resultMap["version"] = version |
|
|
|
FileLogger.i(TAG, "接收到目标版本号: $version") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
} |
|
|
|
|
|
|
|
BleConst.CMD_GET_PRODUCT_ID -> { |
|
|
|
val resultMap = mutableMapOf<String, Any>( |
|
|
|
"success" to true, |
|
|
|
"command" to "productInfo", |
|
|
|
"rawData" to responseData |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
if (responseData.isNotEmpty() && responseData.size >= 2) { |
|
|
|
val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${(responseData[1].toInt() and 0xFF).toString(16)}" |
|
|
|
val productId = "0x${(responseData[0].toInt() and 0xFF).toString(16)}${ |
|
|
|
(responseData[1].toInt() and 0xFF).toString(16) |
|
|
|
}" |
|
|
|
resultMap["productId"] = productId |
|
|
|
FileLogger.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 |
|
|
|
FileLogger.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 |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
@ -626,26 +679,29 @@ object BleService { |
|
|
|
resultMap["caseBattery"] = caseBattery |
|
|
|
resultMap["caseCharging"] = caseCharging |
|
|
|
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 |
|
|
|
|
|
|
|
FileLogger.i(TAG, "接收到电量信息: 左耳=${leftEarBattery}% " + |
|
|
|
"(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + |
|
|
|
"(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + |
|
|
|
"(${if(caseCharging) "充电中" else "未充电"})") |
|
|
|
|
|
|
|
FileLogger.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 -> "已关闭编解码" |
|
|
|
@ -654,7 +710,7 @@ object BleService { |
|
|
|
BleConst.CODEC_CONTROL_ENCODE_ON -> "已打开编码" |
|
|
|
else -> "未知状态($codecStatus)" |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 声道模式描述 |
|
|
|
val channelDesc = when (channelMode) { |
|
|
|
BleConst.AUDIO_CHANNEL_LEFT -> "左声道" |
|
|
|
@ -662,17 +718,18 @@ object BleService { |
|
|
|
BleConst.AUDIO_CHANNEL_STEREO -> "立体声" |
|
|
|
else -> "未知声道($channelMode)" |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
resultMap["codecStatus"] = codecStatus |
|
|
|
resultMap["channelMode"] = channelMode |
|
|
|
resultMap["statusDesc"] = statusDesc |
|
|
|
resultMap["channelDesc"] = channelDesc |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.i(TAG, "编解码控制响应: $statusDesc, $channelDesc") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(cmdType, resultMap) |
|
|
|
} |
|
|
|
|
|
|
|
else -> { |
|
|
|
// 对于未知命令,返回原始数据 |
|
|
|
val resultMap = mapOf( |
|
|
|
@ -686,45 +743,60 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 处理设备主动上报数据 - 帧头为0xCC |
|
|
|
*/ |
|
|
|
private fun processDeviceNotification(data: ByteArray) { |
|
|
|
FileLogger.d(TAG, "收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") |
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
) |
|
|
|
if (data.size < 3) { |
|
|
|
FileLogger.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 |
|
|
|
FileLogger.e(TAG, "设备主动上报数据长度不匹配: 期望=${length+4}, 实际=${data.size}") |
|
|
|
FileLogger.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) { |
|
|
|
FileLogger.e(TAG, "设备主动上报CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}") |
|
|
|
FileLogger.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) |
|
|
|
} |
|
|
|
|
|
|
|
FileLogger.d(TAG, "收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${notifyData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}") |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.d( |
|
|
|
TAG, |
|
|
|
"收到设备主动上报: 类型=0x${infoType.toString(16)}, 数据=${ |
|
|
|
notifyData.joinToString(", ") { |
|
|
|
"0x${ |
|
|
|
(it.toInt() and 0xFF).toString(16) |
|
|
|
}" |
|
|
|
} |
|
|
|
}" |
|
|
|
) |
|
|
|
|
|
|
|
// 根据上报类型处理 |
|
|
|
when (infoType) { |
|
|
|
BleConst.CMD_GET_BATTERY_INFO -> { // 电量信息 |
|
|
|
@ -734,19 +806,22 @@ object BleService { |
|
|
|
"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 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 |
|
|
|
|
|
|
|
|
|
|
|
val caseCharging = |
|
|
|
if (notifyData.size > 2) (notifyData[2].toInt() and 0x80) != 0 else false |
|
|
|
|
|
|
|
|
|
|
|
// 添加到结果Map |
|
|
|
resultMap["leftEarBattery"] = leftEarBattery |
|
|
|
resultMap["leftEarCharging"] = leftEarCharging |
|
|
|
@ -755,15 +830,18 @@ object BleService { |
|
|
|
resultMap["caseBattery"] = caseBattery |
|
|
|
resultMap["caseCharging"] = caseCharging |
|
|
|
resultMap["batteryLevel"] = leftEarBattery // 整体电量用左耳表示 |
|
|
|
|
|
|
|
FileLogger.i(TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + |
|
|
|
"(${if(leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + |
|
|
|
"(${if(rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + |
|
|
|
"(${if(caseCharging) "充电中" else "未充电"})") |
|
|
|
|
|
|
|
FileLogger.i( |
|
|
|
TAG, "收到主动上报电量信息: 左耳=${leftEarBattery}% " + |
|
|
|
"(${if (leftEarCharging) "充电中" else "未充电"}), 右耳=${rightEarBattery}% " + |
|
|
|
"(${if (rightEarCharging) "充电中" else "未充电"}), 充电仓=${caseBattery}% " + |
|
|
|
"(${if (caseCharging) "充电中" else "未充电"})" |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
notifyDeviceInfoReceived(infoType, resultMap) |
|
|
|
} |
|
|
|
|
|
|
|
BleConst.CMD_WAKE_UP -> { |
|
|
|
FileLogger.i(TAG, "收到唤醒指令") |
|
|
|
// 接收到唤醒信号,打开编码器 |
|
|
|
@ -797,7 +875,7 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "获取耳机版本号...") |
|
|
|
return sendCommand(BleConst.CMD_GET_VERSION.toByte(), byteArrayOf(0xFF.toByte())) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取产品ID |
|
|
|
*/ |
|
|
|
@ -805,7 +883,7 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "获取耳机产品ID...") |
|
|
|
return sendCommand(BleConst.CMD_GET_PRODUCT_ID.toByte(), byteArrayOf(0xFF.toByte())) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取耳机颜色ID |
|
|
|
*/ |
|
|
|
@ -813,7 +891,7 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "获取耳机颜色ID...") |
|
|
|
return sendCommand(BleConst.CMD_GET_COLOR_ID.toByte(), byteArrayOf(0xFF.toByte())) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取耳机电量信息 |
|
|
|
*/ |
|
|
|
@ -821,23 +899,29 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "获取耳机电量信息...") |
|
|
|
return sendCommand(BleConst.CMD_GET_BATTERY_INFO.toByte(), byteArrayOf(0xFF.toByte())) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 控制编解码 - 关闭编解码 |
|
|
|
*/ |
|
|
|
fun closeCodec(): Boolean { |
|
|
|
FileLogger.i(TAG, "关闭编解码...") |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte())) |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte()) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 控制编解码 - 打开解码 |
|
|
|
*/ |
|
|
|
fun openDecoder(): Boolean { |
|
|
|
FileLogger.i(TAG, "打开解码...") |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte())) |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte()) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 控制编解码 - A2DP播放 |
|
|
|
*/ |
|
|
|
@ -845,15 +929,21 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "切换至A2DP播放模式...") |
|
|
|
return sendCommand(BleConst.CMD_PLAY_A2DP.toByte()) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 控制编解码 - 打开编码 |
|
|
|
*/ |
|
|
|
fun openEncoder(): Boolean { |
|
|
|
FileLogger.i(TAG, "打开编码...") |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_ENCODE_ON.toByte())) |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), |
|
|
|
byteArrayOf( |
|
|
|
BleConst.CODEC_CONTROL_ENCODE_ON.toByte(), |
|
|
|
BleConst.AUDIO_CHANNEL_RIGHT.toByte() |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 设置声道模式 |
|
|
|
* @param channelMode 声道模式:AUDIO_CHANNEL_LEFT/AUDIO_CHANNEL_RIGHT/AUDIO_CHANNEL_STEREO |
|
|
|
@ -868,14 +958,16 @@ object BleService { |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 高级控制:设置命令类型、长度和两个参数 |
|
|
|
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
|
0xA1.toByte(), // 状态码 - 使用解码状态 |
|
|
|
channelMode.toByte() // 声道模式 |
|
|
|
)) |
|
|
|
return sendCommand( |
|
|
|
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( |
|
|
|
0xA1.toByte(), // 状态码 - 使用解码状态 |
|
|
|
channelMode.toByte() // 声道模式 |
|
|
|
) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 音量增大 |
|
|
|
*/ |
|
|
|
@ -883,7 +975,7 @@ object BleService { |
|
|
|
FileLogger.i(TAG, "设备音量增加...") |
|
|
|
return sendCommand(BleConst.CMD_VOLUME_UP.toByte()) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 音量减小 |
|
|
|
*/ |
|
|
|
@ -901,21 +993,21 @@ object BleService { |
|
|
|
fun isConnected(): Boolean { |
|
|
|
return connectionState.value == BleConst.STATE_CONNECTED |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 连接状态检查 |
|
|
|
*/ |
|
|
|
private fun checkConn(): Boolean = |
|
|
|
bluetoothGatt != null && writeChar != null && |
|
|
|
connectionState.value == BleConst.STATE_CONNECTED |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取已连接设备地址 |
|
|
|
*/ |
|
|
|
fun getConnectedDeviceAddress(): String? { |
|
|
|
return bluetoothGatt?.device?.address |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 获取蓝牙适配器 |
|
|
|
*/ |
|
|
|
@ -976,7 +1068,7 @@ object BleService { |
|
|
|
val scanIntent = Intent(BleConst.ACTION_SCAN_RESULT) |
|
|
|
.setClass(context, BleWakeReceiver::class.java) |
|
|
|
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND) |
|
|
|
|
|
|
|
|
|
|
|
val pi = PendingIntent.getBroadcast( |
|
|
|
context, 0, scanIntent, |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { |
|
|
|
@ -1022,34 +1114,37 @@ object BleService { |
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
// 记录命令详情 |
|
|
|
FileLogger.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)}") |
|
|
|
|
|
|
|
FileLogger.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 |
|
|
|
return bluetoothGatt?.writeCharacteristic(writeChar) ?: false |
|
|
|
@ -1091,7 +1186,7 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 向所有回调监听器分发连接状态变化 |
|
|
|
*/ |
|
|
|
@ -1104,7 +1199,7 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 向所有回调监听器分发音频数据 |
|
|
|
*/ |
|
|
|
@ -1117,7 +1212,7 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 向所有回调监听器分发唤醒信号 |
|
|
|
*/ |
|
|
|
@ -1130,7 +1225,7 @@ object BleService { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 向所有回调监听器分发设备信息 |
|
|
|
*/ |
|
|
|
|