|
|
|
@ -79,6 +79,16 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
* @param progress 升级进度(0-100) |
|
|
|
*/ |
|
|
|
fun notifyUpgradeProgressReceived(progress: Int) |
|
|
|
|
|
|
|
/** |
|
|
|
* 通知升级成功 |
|
|
|
*/ |
|
|
|
fun notifyUpgradeSuccess() |
|
|
|
|
|
|
|
/** |
|
|
|
* 通知升级失败 |
|
|
|
*/ |
|
|
|
fun notifyUpgradeFailed() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
@ -115,33 +125,39 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
fun startWakeupVoiceUpgrade(bytes: ByteArray): Boolean { |
|
|
|
|
|
|
|
if (isUpgrading) { |
|
|
|
Log.e(TAG, "OTA启动失败: 正在升级中") |
|
|
|
Log.e(TAG, "liwei----------- OTA启动失败: 正在升级中") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
val cb = this.callback ?: run { |
|
|
|
Log.e(TAG, "OTA启动失败: 回调接口未设置") |
|
|
|
Log.e(TAG, "liwei----------- OTA启动失败: 回调接口未设置") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
if (!cb.isConnected()) { |
|
|
|
Log.e(TAG, "OTA启动失败: 设备未连接") |
|
|
|
Log.e(TAG, "liwei----------- OTA启动失败: 设备未连接") |
|
|
|
return false |
|
|
|
} |
|
|
|
if (bytes.isEmpty()) { |
|
|
|
Log.e(TAG, "liwei----------- OTA启动失败: 数据为空") |
|
|
|
return false |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化状态 |
|
|
|
otaBytes = bytes |
|
|
|
otaTotalPackets = if (bytes.isEmpty()) 0 else ((bytes.size + MAX_DATA_PER_PACKET - 1) / MAX_DATA_PER_PACKET) |
|
|
|
otaTotalPackets = ((bytes.size + MAX_DATA_PER_PACKET - 1) / MAX_DATA_PER_PACKET) |
|
|
|
currentSeq = 1 // 序号按文档使用1基准 |
|
|
|
handshakeAcked = false |
|
|
|
isUpgrading = true |
|
|
|
|
|
|
|
// 构建并发送握手包: 0xAA 0xBB 0xCC 0x08 0x02 [总包数(2byte)] CRC |
|
|
|
val totalHi = ((otaTotalPackets shr 8) and 0xFF).toByte() |
|
|
|
val totalLo = (otaTotalPackets and 0xFF).toByte() |
|
|
|
val payload = byteArrayOf(totalHi, totalLo) |
|
|
|
val handshake = buildWakeupPacket(0x08, payload) |
|
|
|
|
|
|
|
// 构建并发送握手包: 0xAA 0xBB 0xCC 0x20 0x04 [文件大小(4byte)] CRC |
|
|
|
val size = bytes.size |
|
|
|
val b3 = ((size shr 24) and 0xFF).toByte() |
|
|
|
val b2 = ((size shr 16) and 0xFF).toByte() |
|
|
|
val b1 = ((size shr 8) and 0xFF).toByte() |
|
|
|
val b0 = (size and 0xFF).toByte() |
|
|
|
val payload = byteArrayOf(b3, b2, b1, b0) |
|
|
|
val handshake = buildWakeupCmdPacket(0x20,0x04, payload) |
|
|
|
Log.e(TAG, "liwei----------- OTA唤醒词升级: size:${size} payload:${payload.joinToString(", ") { "0x%02X".format(it) }} otaTotalPackets:$otaTotalPackets") |
|
|
|
return sendRawPacket(handshake) |
|
|
|
} |
|
|
|
|
|
|
|
@ -185,84 +201,92 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
fun processDeviceNotification(data: ByteArray) { |
|
|
|
Log.d( |
|
|
|
TAG, |
|
|
|
"收到设备主动上报: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
"liwei------------- 收到唤醒词升级回应数据: ${data.joinToString(", ") { "0x${(it.toInt() and 0xFF).toString(16)}" }}" |
|
|
|
) |
|
|
|
if (data.size < 4) { |
|
|
|
Log.e(TAG, "OTA通知/响应数据格式错误:数据长度过短") |
|
|
|
Log.e(TAG, "Oliwei------------- TA通知/响应数据格式错误:数据长度过短") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
val header = data[0].toInt() and 0xFF |
|
|
|
val cmd = data[1].toInt() and 0xFF |
|
|
|
val length = data[2].toInt() and 0xFF |
|
|
|
// val length = data[2].toInt() and 0xFF |
|
|
|
|
|
|
|
if (data.size != length + 4) { |
|
|
|
Log.e(TAG, "OTA通知长度不匹配: 期望=${length + 4}, 实际=${data.size}") |
|
|
|
return |
|
|
|
} |
|
|
|
// if (data.size != length + 4) { |
|
|
|
// Log.e(TAG, "liwei------------- OTA通知长度不匹配: 期望=${length + 4}, 实际=${data.size}") |
|
|
|
// return |
|
|
|
// } |
|
|
|
|
|
|
|
// 校验CRC |
|
|
|
val crcPayload = data.sliceArray(0 until data.size - 1) |
|
|
|
val calc = calculateCrc8Maxim(crcPayload, crcPayload.size).toInt() and 0xFF |
|
|
|
val recv = data[data.size - 1].toInt() and 0xFF |
|
|
|
if (calc != recv) { |
|
|
|
Log.e(TAG, "OTA通知CRC校验失败: 计算=$calc, 接收=$recv") |
|
|
|
Log.e(TAG, "liwei------------- OTA通知CRC校验失败: 计算=$calc, 接收=$recv") |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// 握手/数据包响应都是以0xBB为头 |
|
|
|
if (header == (BleConst.DEVICE_RESPONSE_HEADER.toInt() and 0xFF)) { |
|
|
|
when (cmd) { |
|
|
|
0x08 -> { // 握手确认: 0xBB 0x08 0x03 [总包数(2byte)] [0x01] CRC |
|
|
|
if (length < 3) { |
|
|
|
Log.e(TAG, "握手响应长度错误: $length") |
|
|
|
return |
|
|
|
} |
|
|
|
val totalHi = data[3].toInt() and 0xFF |
|
|
|
val totalLo = data[4].toInt() and 0xFF |
|
|
|
val status = data[5].toInt() and 0xFF |
|
|
|
val totalFromDevice = (totalHi shl 8) or totalLo |
|
|
|
Log.d(TAG, "握手响应: total=$totalFromDevice, status=$status") |
|
|
|
0x20 -> { // 握手确认: 0xBB + 0x20 + 0x05 + OTA包总字节数(4Byte) + 0x01 + CRC |
|
|
|
Log.d(TAG, "liwei------------- 握手响应成功") |
|
|
|
handshakeAcked = true |
|
|
|
} |
|
|
|
0x21 -> { // 握手确认: 0xBB + 0x21 + 0x01 + 0x01 + CRC |
|
|
|
val status = data[3].toInt() and 0xFF |
|
|
|
if (status == 0x01) { |
|
|
|
Log.d(TAG, "liwei------------- 耳机UBOOT升级完: status=$status") |
|
|
|
handshakeAcked = true |
|
|
|
// 开始发送第一包 |
|
|
|
sendNextDataPacket() |
|
|
|
} else { |
|
|
|
Log.e(TAG, "握手失败,状态=$status") |
|
|
|
isUpgrading = false |
|
|
|
Log.e(TAG, "liwei------------- 耳机UBOOT升级失败: status=$status") |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
0x09 -> { // 数据包确认: 0xBB 0x09 0x03 [序号(2byte)] [0x01] CRC |
|
|
|
if (length < 3) { |
|
|
|
Log.e(TAG, "数据包响应长度错误: $length") |
|
|
|
return |
|
|
|
} |
|
|
|
0x22 -> { |
|
|
|
// 数据包确认: 0xBB + 0x22 + 0x03 + OTA包序号(2Byte) + 0x01 + CRC 成功 |
|
|
|
// 数据包确认: 0xBB + 0x22 + 0x03 + OTA包序号(2Byte) + 0x00 + CRC 失败 |
|
|
|
val seqHi = data[3].toInt() and 0xFF |
|
|
|
val seqLo = data[4].toInt() and 0xFF |
|
|
|
val status = data[5].toInt() and 0xFF |
|
|
|
val ackSeq = (seqHi shl 8) or seqLo |
|
|
|
Log.d(TAG, "数据包确认: seq=$ackSeq, status=$status") |
|
|
|
if (status == 0x01) { |
|
|
|
Log.d(TAG, "liwei------------- 数据包确认: seq=$ackSeq, status=$status") |
|
|
|
if (status == 0x01) { //成功 发送下一包 |
|
|
|
// 更新进度(1基准) |
|
|
|
val progress = ((ackSeq.coerceAtMost(otaTotalPackets)) * 100) / (otaTotalPackets.coerceAtLeast(1)) |
|
|
|
Log.i(TAG, "liwei------------- OTA进度: $progress%") |
|
|
|
callback?.notifyUpgradeProgressReceived(progress) |
|
|
|
// 继续发送下一包 |
|
|
|
if (ackSeq >= otaTotalPackets) { |
|
|
|
Log.i(TAG, "OTA完成,共发送${otaTotalPackets}包") |
|
|
|
Log.i(TAG, "liwei------------- OTA完成,共发送${otaTotalPackets}包") |
|
|
|
callback?.notifyUpgradeProgressReceived(100) |
|
|
|
isUpgrading = false |
|
|
|
sendEndDataPacket() |
|
|
|
} else { |
|
|
|
currentSeq = ackSeq + 1 |
|
|
|
sendNextDataPacket() |
|
|
|
} |
|
|
|
} else { |
|
|
|
Log.e(TAG, "数据包确认失败,状态=$status") |
|
|
|
} else { //失败重新发送当前包 |
|
|
|
Log.e(TAG, "liwei------------- 数据包确认失败,状态=$status") |
|
|
|
// 可按需重试,这里简单结束 |
|
|
|
isUpgrading = false |
|
|
|
} |
|
|
|
} |
|
|
|
0x23 -> { |
|
|
|
val status = data[3].toInt() and 0xFF |
|
|
|
Log.d(TAG, "liwei------------- OTA结束确认: status=$status") |
|
|
|
if (status == 0x01) { |
|
|
|
Log.i(TAG, "liwei------------- OTA升级成功") |
|
|
|
callback?.notifyUpgradeSuccess() |
|
|
|
} else { |
|
|
|
Log.e(TAG, "liwei------------- OTA升级失败") |
|
|
|
callback?.notifyUpgradeFailed() |
|
|
|
} |
|
|
|
isUpgrading = false |
|
|
|
} |
|
|
|
else -> { |
|
|
|
Log.d(TAG, "收到未知OTA响应CMD=0x${cmd.toString(16)}") |
|
|
|
Log.d(TAG, "liwei------------- 收到未知OTA响应CMD=0x${cmd.toString(16)}") |
|
|
|
} |
|
|
|
} |
|
|
|
return |
|
|
|
@ -270,7 +294,7 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
|
|
|
|
// 设备主动上报(0xCC)可以忽略或保留原逻辑; OTA场景下无特殊处理 |
|
|
|
if (header == (BleConst.DEVICE_NOTIFY_HEADER.toInt() and 0xFF)) { |
|
|
|
Log.d(TAG, "收到设备主动上报(OTA通道): CMD=0x${cmd.toString(16)} 长度=$length") |
|
|
|
Log.d(TAG, "liwei------------- 收到设备主动上报(OTA通道): CMD=0x${cmd.toString(16)}") |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -414,46 +438,75 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
return crc.toByte() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================== |
|
|
|
// OTA封包/发送辅助 |
|
|
|
// ========================================== |
|
|
|
private fun buildWakeupPacket(cmd: Int, payload: ByteArray): ByteArray { |
|
|
|
val totalLen = 4 + payload.size + 1 // 帧头(3字节) + CMD(1) + LEN(1) + PAYLOAD + CRC(1) |
|
|
|
private fun buildWakeupCmdPacket(cmd: Int,sub: Int, payload: ByteArray): ByteArray { |
|
|
|
|
|
|
|
val totalLen = 5 + payload.size + 1 // 帧头(3字节) + CMD(1) + LEN(1) + PAYLOAD + CRC(1) |
|
|
|
val packet = ByteArray(totalLen) |
|
|
|
packet[0] = BleConst.APP_REQUEST_HEADER // 0xAA |
|
|
|
packet[1] = BleConst.DEVICE_RESPONSE_HEADER // 0xBB |
|
|
|
packet[2] = BleConst.DEVICE_NOTIFY_HEADER // 0xCC |
|
|
|
packet[3] = (cmd and 0xFF).toByte() |
|
|
|
packet[4] = (payload.size and 0xFF).toByte() |
|
|
|
packet[4] = (sub and 0xFF).toByte() |
|
|
|
if (payload.isNotEmpty()) { |
|
|
|
System.arraycopy(payload, 0, packet, 5, payload.size) |
|
|
|
} |
|
|
|
val crc = calculateCrc8Maxim(packet.sliceArray(0 until packet.size - 1), packet.size - 1) |
|
|
|
packet[packet.size - 1] = crc |
|
|
|
Log.e(TAG, "liwei----------- 封装升级包: cmd=$cmd, sub=$sub, payload=${payload.size}, crc=${crc.toInt() and 0xFF}") |
|
|
|
return packet |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ========================================== |
|
|
|
// OTA封包/发送辅助 |
|
|
|
// ========================================== |
|
|
|
private fun buildWakeupPacket(cmd: Int,payload: ByteArray): ByteArray { |
|
|
|
|
|
|
|
val totalLen = 5 + payload.size + 1 // 帧头(3字节) + CMD(1) + LEN(1) + PAYLOAD + CRC(1) |
|
|
|
val packet = ByteArray(totalLen) |
|
|
|
packet[0] = BleConst.APP_REQUEST_HEADER // 0xAA |
|
|
|
packet[1] = BleConst.DEVICE_RESPONSE_HEADER // 0xBB |
|
|
|
packet[2] = BleConst.DEVICE_NOTIFY_HEADER // 0xCC |
|
|
|
packet[3] = (cmd and 0xFF).toByte() |
|
|
|
if (payload.isNotEmpty()) { |
|
|
|
System.arraycopy(payload, 0, packet, 4, payload.size) |
|
|
|
} |
|
|
|
val crc = calculateCrc8Maxim(packet.sliceArray(0 until packet.size - 1), packet.size - 1) |
|
|
|
packet[packet.size - 1] = crc |
|
|
|
Log.e(TAG, "liwei----------- 封装升级包: cmd=$cmd, payload=${payload.size}, crc=${crc.toInt() and 0xFF}") |
|
|
|
return packet |
|
|
|
} |
|
|
|
|
|
|
|
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) |
|
|
|
private fun sendRawPacket(packet: ByteArray): Boolean { |
|
|
|
val cb = this.callback ?: run { |
|
|
|
Log.e(TAG, "发送失败: 回调未设置") |
|
|
|
Log.e(TAG, "liwei----------- 发送失败: 回调未设置") |
|
|
|
return false |
|
|
|
} |
|
|
|
if (!cb.isConnected()) { |
|
|
|
Log.e(TAG, "发送失败: 未连接") |
|
|
|
Log.e(TAG, "liwei----------- 发送失败: 未连接") |
|
|
|
return false |
|
|
|
} |
|
|
|
val writeChar = cb.getWriteCharacteristic() ?: run { |
|
|
|
Log.e(TAG, "发送失败: 写特征为空") |
|
|
|
Log.e(TAG, "liwei----------- 发送失败: 写特征为空") |
|
|
|
return false |
|
|
|
} |
|
|
|
val gatt = cb.getBluetoothGatt() ?: run { |
|
|
|
Log.e(TAG, "发送失败: GATT为空") |
|
|
|
Log.e(TAG, "liwei----------- 发送失败: GATT为空") |
|
|
|
return false |
|
|
|
} |
|
|
|
writeChar.value = packet |
|
|
|
val ok = gatt.writeCharacteristic(writeChar) |
|
|
|
if (ok) { |
|
|
|
//打印一下发送数据包 |
|
|
|
val hex = packet.joinToString(" ") { String.format("%02X", it) } |
|
|
|
Log.d(TAG, "liwei----------- 发送数据包(${packet.size}B): $hex") |
|
|
|
isWaitingReply = false |
|
|
|
startReplyTimeout() |
|
|
|
} |
|
|
|
@ -483,7 +536,17 @@ class BleWakeupVoicesUpgradeSender { |
|
|
|
payload[1] = seqLo |
|
|
|
payload[2] = dataLen.toByte() |
|
|
|
System.arraycopy(chunk, 0, payload, 3, chunk.size) |
|
|
|
val packet = buildWakeupPacket(0x09, payload) |
|
|
|
val packet = buildWakeupPacket(0x22,payload) |
|
|
|
val ok = sendRawPacket(packet) |
|
|
|
if (!ok) { |
|
|
|
Log.e(TAG, "写入数据包失败: seq=$currentSeq") |
|
|
|
isUpgrading = false |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) |
|
|
|
private fun sendEndDataPacket() { |
|
|
|
val packet = buildWakeupCmdPacket(0x23,0x01, byteArrayOf()) |
|
|
|
val ok = sendRawPacket(packet) |
|
|
|
if (!ok) { |
|
|
|
Log.e(TAG, "写入数据包失败: seq=$currentSeq") |
|
|
|
|