diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index a94fd434f..ae6bedbb1 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -26,6 +26,67 @@ class BleCommands { static const int CONTROL_CODEC = 0x05; } +/// 判断编解码控制指令是否成功 +/// +/// 根据 rawData 第二字节是否为非 0 来判断: +/// - 成功:第二字节不为 0 +/// - 失败或无法解析:返回 false +bool _isCodecControlSuccess(Map data) { + final dynamic raw = data['rawData']; + if (raw == null) return false; + + int? secondByte; + + if (raw is List) { + if (raw.length >= 2) { + final s = raw[1]; + if (s is int) { + secondByte = s; + } else if (s is num) { + secondByte = s.toInt(); + } else if (s is String) { + final t = s.trim(); + if (t.startsWith('0x') || t.startsWith('0X')) { + secondByte = int.tryParse(t.substring(2), radix: 16); + } else { + secondByte = int.tryParse(t); + } + } + } + } else if (raw is String) { + final normalized = raw.trim().replaceAll('[', '').replaceAll(']', ''); + final parts = + normalized.split(RegExp(r'[,\s]+')).where((e) => e.isNotEmpty).toList(); + if (parts.length >= 2) { + final t = parts[1].trim(); + if (t.startsWith('0x') || t.startsWith('0X')) { + secondByte = int.tryParse(t.substring(2), radix: 16); + } else { + secondByte = int.tryParse(t); + } + } + } else if (raw is Map) { + final dynamic s = raw['second'] ?? raw['status2'] ?? raw['result']; + if (s is int) { + secondByte = s; + } else if (s is num) { + secondByte = s.toInt(); + } else if (s is String) { + final t = s.trim(); + if (t.startsWith('0x') || t.startsWith('0X')) { + secondByte = int.tryParse(t.substring(2), radix: 16); + } else { + secondByte = int.tryParse(t); + } + } + } else { + // 无法解析的类型 + return false; + } + + return secondByte != null && secondByte != 0; +} + /// 蓝牙设备信息缓存模型 class DeviceInfo { final String address; @@ -305,7 +366,17 @@ class BleManager extends GetxService { Logger.info('lxm--处理编解码控制命令'); // 处理通话状态 print(" CONTROL_CODEC: $commandId,$data"); - //_isCall.value = data['callStatus'] as bool? ?? false; + + final bool codecSuccess = _isCodecControlSuccess(data); + + if (codecSuccess) { + print("lxm--编解码控制成功 (rawData 第二字节非 0)"); + Logger.info('lxm--编解码控制成功 (rawData 第二字节非 0)'); + } else { + print("lxm--编解码控制失败 (rawData 为 [162, 0] 或无法解析)"); + Logger.warning('lxm--编解码控制失败 (rawData 为 [162, 0] 或无法解析)'); + } + break; default: Logger.warning('lxm--未处理的命令ID: $commandId,数据: $data'); 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 692f83ada..fba29431d 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 @@ -183,8 +183,13 @@ class BleCommandSender { } /** - * 处理设备响应数据 - 从BleService移动过来 - * @param data 响应数据 + * 处理设备响应数据(统一兼容失败无长度的场景) + * + * 帧格式通常为: [HEADER(0)=0xBB][CMD(1)][LEN/STATUS(2)][DATA(len)][CRC(最后1字节)] + * - 当 LEN/STATUS 为 0(例如收到 0xA2, 0x00),表示该命令执行失败,直接回调失败并返回。 + * - 正常响应时进行 CRC 校验(若帧长度不足则跳过校验并告警),拷贝数据使用安全长度以避免越界。 + * + * @param data 设备返回的原始字节数组 */ @SuppressLint("StaticFieldLeak") @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @@ -200,63 +205,64 @@ class BleCommandSender { 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 lenByte = data[2].toInt() and 0xFF + + + + // 正常响应处理:尽量进行 CRC 校验 + var crcOk = true + if (data.size >= 4) { + val payload = data.sliceArray(0 until data.size - 1) + val calculatedCrc = calculateCrc8Maxim(payload, payload.size) + val receivedCrc = data[data.size - 1].toInt() and 0xFF + crcOk = (calculatedCrc.toInt() and 0xFF) == receivedCrc + if (!crcOk) { + Log.e( + TAG, + "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}" + ) + return + } + } else { + Log.w(TAG, "设备响应长度不足以包含CRC,跳过CRC校验") } + // 统一失败判定 + if (lenByte != data.size-4) { + Log.w(TAG, "命令 0x${cmdType.toString(16)} 执行失败(无数据长度)") + val resultMap = mapOf( + "success" to true, + "command" to cmdType, + "rawData" to byteArrayOf(data[2], data[data.size - 2]) // 取第二位和倒数第二位 + ) + callback?.notifyDeviceInfoReceived(cmdType, resultMap) + onCommandResponse(cmdType) + return + } + // 安全提取响应数据,避免越界 + val actualPayloadLen = (data.size - 4).coerceAtLeast(0) + val copyLen = minOf(lenByte, actualPayloadLen) + val responseData = if (copyLen > 0) ByteArray(copyLen) else ByteArray(0) - // 正常响应处理 - 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( + if (copyLen < lenByte) { + Log.w( TAG, - "设备响应CRC校验失败: 计算=${calculatedCrc.toInt() and 0xFF}, 接收=${receivedCrc}" + "响应长度字段(${lenByte})与实际负载长度(${actualPayloadLen})不一致,按实际长度处理" ) - return } - // 提取响应数据 - val responseData = if (length > 0) ByteArray(length) else ByteArray(0) - if (length > 0) { - System.arraycopy(data, 3, responseData, 0, length) + if (copyLen > 0) { + System.arraycopy(data, 3, responseData, 0, copyLen) } Log.d( TAG, "收到设备响应: CMD=0x${cmdType.toString(16)}, 数据=${ - responseData.joinToString(", ") { - "0x${ - (it.toInt() and 0xFF).toString(16) - }" - } + responseData.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" } }" ) - // 通知指令发送器处理响应 + onCommandResponse(cmdType) - // 根据命令类型处理不同的响应 + when (cmdType) { BleConst.CMD_GET_VERSION -> { val resultMap = mutableMapOf(