BlePacketSender.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. package com.narutohuo.xindazhou.ble.util
  2. import android.annotation.SuppressLint
  3. import android.bluetooth.BluetoothGatt
  4. import android.bluetooth.BluetoothGattCharacteristic
  5. import com.narutohuo.xindazhou.ble.model.FragmentBuffer
  6. import com.narutohuo.xindazhou.ble.model.Packet
  7. import com.narutohuo.xindazhou.ble.util.parseCommand
  8. import com.narutohuo.xindazhou.ble.util.parsePacket
  9. import com.narutohuo.xindazhou.ble.util.toHexString
  10. import org.json.JSONObject
  11. import com.narutohuo.xindazhou.core.log.ILog
  12. import kotlinx.coroutines.*
  13. import java.util.LinkedList
  14. import java.util.Queue
  15. import java.util.UUID
  16. import kotlin.random.Random
  17. /**
  18. * BLE数据包发送器
  19. *
  20. * 负责 BLE 写入、超时、重试以及队列管理
  21. */
  22. class BlePacketSender(
  23. private val gatt: BluetoothGatt,
  24. private var writeChar: BluetoothGattCharacteristic,
  25. private val splitter: BLEPacketSplitter = BLEPacketSplitter(),
  26. private val maxPayloadPerPacket: Int = 20,
  27. private val ackTimeoutMs: Long = 1000L,
  28. private val maxRetry: Int = 3,
  29. private val onPairing: ((Int) -> Unit)? = null,
  30. private val onHandshakeRequest: ((Int) -> Unit)? = null
  31. ) {
  32. private val tag = "BlePacketSender"
  33. // 发送队列
  34. private val pendingPackets: Queue<ByteArray> = LinkedList()
  35. // 用来标记当前正在等待的包序号(-1 表示空闲)
  36. @Volatile
  37. private var waitingSeq = -1
  38. // 分包接收缓存
  39. private val fragmentCache = mutableMapOf<UInt, FragmentBuffer>()
  40. // 业务消息处理回调
  41. private var onBusinessMessage: ((String, String, Map<String, String>, String?) -> Unit)? = null
  42. /**
  43. * 把完整指令数据放入发送队列,随后自动开始发送
  44. *
  45. * @param rawData 完整指令数据(不包含分包协议头尾)
  46. * @param businessId 业务标识(4字节)
  47. * @param isRequest true→请求,false→响应
  48. */
  49. fun enqueueAndStart(
  50. rawData: ByteArray,
  51. businessId: ByteArray,
  52. isRequest: Boolean = true
  53. ) {
  54. // 1️⃣ 分包
  55. val packets = splitter.split(rawData, businessId, isRequest)
  56. // 2️⃣ 入队
  57. pendingPackets.addAll(packets)
  58. // 3️⃣ 启动发送(如果当前没有正在发送的任务)
  59. if (waitingSeq == -1) {
  60. sendNextPacket()
  61. }
  62. }
  63. /**
  64. * 发送队列中的下一个包(如果有的话)
  65. */
  66. private fun sendNextPacket() {
  67. val packet = pendingPackets.peek() ?: run {
  68. // 队列为空 → 发送完毕
  69. onAllPacketsSent()
  70. return
  71. }
  72. // 取出包序号(第 7-8 个字节,大端序)
  73. waitingSeq = extractSeqFromPacket(packet)
  74. sendPacketWithRetry(packet, retryCount = 0)
  75. }
  76. /**
  77. * 实际写入并处理超时/重试
  78. */
  79. @OptIn(DelicateCoroutinesApi::class)
  80. @SuppressLint("MissingPermission")
  81. private fun sendPacketWithRetry(packet: ByteArray, retryCount: Int) {
  82. // 1️⃣ 写入(WRITE_TYPE_NO_RESPONSE)
  83. writeChar.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
  84. writeChar.value = packet
  85. val writeResult = gatt.writeCharacteristic(writeChar)
  86. ILog.d(tag, "发送数据: result=$writeResult, packet=${packet.toHexString()}")
  87. // 注意:即使 writeResult 为 false,也启动超时计时器
  88. // 因为在 Android BLE 中,使用 WRITE_TYPE_NO_RESPONSE 时,writeCharacteristic 可能返回 false
  89. // 但写入操作可能在后续完成,并通过 onCharacteristicWrite 回调确认
  90. // 如果 writeResult 为 false 且后续没有 onCharacteristicWrite 回调,超时后会重试
  91. // 2️⃣ 启动超时计时
  92. CoroutineScope(Dispatchers.Main).launch {
  93. try {
  94. withTimeout(ackTimeoutMs) {
  95. // 这里会被 onCharacteristicWrite 回调的 `waitingSeq` 置为 -1 打断
  96. while (waitingSeq != -1) {
  97. delay(50) // 轮询等待 ACK
  98. }
  99. }
  100. // 超时未抛异常 → 收到 ACK
  101. onPacketSuccess()
  102. } catch (e: TimeoutCancellationException) {
  103. // 超时未收到 ACK
  104. handleWriteFailure(packet, retryCount, "ACK 超时")
  105. }
  106. }
  107. }
  108. /**
  109. * 处理一次写入失败或超时后的重试/跳过逻辑
  110. */
  111. private fun handleWriteFailure(packet: ByteArray, retryCount: Int, reason: String) {
  112. if (retryCount < maxRetry - 1) {
  113. // 还有重试次数 → 再次发送
  114. ILog.w(tag, "包 seq=$waitingSeq 发送失败($reason),第 ${retryCount + 1} 次重试")
  115. sendPacketWithRetry(packet, retryCount + 1)
  116. } else {
  117. // 已经重试 maxRetry 次,直接放弃该包,进入下一个
  118. ILog.e(tag, "包 seq=$waitingSeq 重试 $maxRetry 次仍失败,跳过该包")
  119. pendingPackets.poll() // 移除当前包
  120. waitingSeq = -1
  121. sendNextPacket()
  122. }
  123. }
  124. /**
  125. * 当前包发送成功后调用
  126. */
  127. private fun onPacketSuccess() {
  128. ILog.d(tag, "包 seq=$waitingSeq 发送成功")
  129. pendingPackets.poll() // 移除已成功的包
  130. waitingSeq = -1
  131. sendNextPacket()
  132. }
  133. /**
  134. * 所有队列中的包都已发送完毕后调用
  135. */
  136. private fun onAllPacketsSent() {
  137. ILog.d(tag, "全部分包发送完毕")
  138. }
  139. /**
  140. * 必须在外部的 BluetoothGattCallback 中调用此方法
  141. * 当收到从设备的写响应(onCharacteristicWrite)时触发
  142. */
  143. fun onCharacteristicWrite(characteristic: BluetoothGattCharacteristic, status: Int) {
  144. ILog.d(tag, "onCharacteristicWrite: status=$status")
  145. if (characteristic.uuid != writeChar.uuid) return
  146. if (status == android.bluetooth.BluetoothGatt.GATT_SUCCESS) {
  147. ILog.d(tag, "设备返回写成功")
  148. // 从设备确认收到该包 → 结束等待
  149. waitingSeq = -1
  150. } else {
  151. ILog.w(tag, "设备返回写错误 status=$status,视为发送失败")
  152. waitingSeq = -1
  153. }
  154. }
  155. /**
  156. * 必须在外部的 BluetoothGattCallback 中调用此方法
  157. * 当收到从设备的通知(onCharacteristicChanged)时触发
  158. */
  159. @SuppressLint("MissingPermission")
  160. fun onCharacteristicNotify(characteristic: BluetoothGattCharacteristic) {
  161. val value = characteristic.value ?: return
  162. ILog.d(tag, "收到通知: ${value.toHexString()}")
  163. try {
  164. val packet = value.parsePacket()
  165. if (!packet.isValid()) {
  166. ILog.e(tag, "收到非法数据包,校验失败")
  167. return
  168. }
  169. if (packet.messageType() == 0x01.toByte()) { // 请求
  170. // 1. 自动发送 ACK 响应
  171. val ackPacket = packet.buildAckResponse()
  172. pendingPackets.add(ackPacket)
  173. if (waitingSeq == -1) sendNextPacket()
  174. // 2. 业务层分片缓存(去掉最低字节,只保留高 24 位)
  175. val cacheKey = packet.businessId and 0xFFFFFF00u
  176. val buffer = fragmentCache.getOrPut(cacheKey) {
  177. FragmentBuffer(totalPackets = packet.totalPackets.toInt())
  178. }
  179. // 3. 保存当前分片
  180. buffer.received[packet.packetSeq.toInt()] = packet.data
  181. // 4. 检查是否已收齐
  182. if (buffer.isComplete()) {
  183. val fullData = buffer.assemble()
  184. try {
  185. val command = fullData.parseCommand()
  186. ILog.d(tag, "完整指令解析成功,数据长度=${command.data.size}")
  187. // 5. 处理业务消息(JSON格式)
  188. handleBusinessMessage(command.data)
  189. } catch (e: Exception) {
  190. ILog.e(tag, "指令解析失败", e)
  191. }
  192. // 清理缓存
  193. fragmentCache.remove(cacheKey)
  194. } else {
  195. ILog.d(tag, "业务ID 0x${cacheKey.toString(16)} 已收到 ${buffer.received.size}/${buffer.totalPackets} 包")
  196. }
  197. } else if (packet.messageType() == 0x02.toByte()) { // 响应
  198. // 处理响应包(如果需要)
  199. ILog.d(tag, "收到响应包,seq=${packet.packetSeq}")
  200. }
  201. } catch (e: Exception) {
  202. ILog.e(tag, "解析通知数据失败", e)
  203. }
  204. }
  205. /**
  206. * 处理业务消息(JSON格式)
  207. */
  208. private fun handleBusinessMessage(data: ByteArray) {
  209. try {
  210. val jsonString = String(data, Charsets.UTF_8)
  211. ILog.d(tag, "收到业务消息: $jsonString")
  212. // 解析JSON
  213. val jsonObject = JSONObject(jsonString)
  214. val ty = jsonObject.optInt("TY", 0)
  215. val `in` = jsonObject.optInt("IN", 0)
  216. // 处理握手和配对
  217. if (ty == 3 && `in` == 3) {
  218. // 握手请求
  219. handleHandshakeRequest(jsonObject)
  220. } else if (ty == 3 && `in` == 4) {
  221. // 配对请求
  222. handlePairingRequest(jsonObject)
  223. }
  224. // 通知业务层
  225. val extras = mutableMapOf<String, String>()
  226. jsonObject.keys().forEach { key ->
  227. extras[key] = jsonObject.optString(key, "")
  228. }
  229. onBusinessMessage?.invoke(
  230. "业务消息",
  231. jsonString,
  232. extras,
  233. jsonObject.optString("ID", null)
  234. )
  235. } catch (e: Exception) {
  236. ILog.e(tag, "解析业务消息失败", e)
  237. }
  238. }
  239. /**
  240. * 处理握手请求
  241. */
  242. private fun handleHandshakeRequest(message: JSONObject) {
  243. try {
  244. val c = message.getJSONObject("C")
  245. val d = c.getJSONObject("D")
  246. val random = d.getInt("Random")
  247. ILog.d(tag, "收到握手请求,随机数: $random")
  248. // 通知业务层处理握手响应
  249. onHandshakeRequest?.invoke(random)
  250. } catch (e: Exception) {
  251. ILog.e(tag, "处理握手请求失败", e)
  252. }
  253. }
  254. /**
  255. * 处理配对请求
  256. */
  257. private fun handlePairingRequest(message: JSONObject) {
  258. try {
  259. val c = message.getJSONObject("C")
  260. val d = c.getJSONObject("D")
  261. val pairing = d.getInt("Pairing")
  262. ILog.d(tag, "收到配对请求,配对状态: $pairing")
  263. // 通知业务层
  264. onPairing?.invoke(pairing)
  265. } catch (e: Exception) {
  266. ILog.e(tag, "处理配对请求失败", e)
  267. }
  268. }
  269. /**
  270. * 设置业务消息监听器
  271. */
  272. fun setBusinessMessageListener(listener: (String, String, Map<String, String>, String?) -> Unit) {
  273. this.onBusinessMessage = listener
  274. }
  275. /**
  276. * 从包中提取序号
  277. */
  278. private fun extractSeqFromPacket(packet: ByteArray): Int {
  279. if (packet.size < 9) return -1
  280. val high = packet[7].toInt() and 0xFF
  281. val low = packet[8].toInt() and 0xFF
  282. return (high shl 8) or low
  283. }
  284. /**
  285. * 生成业务ID
  286. */
  287. fun generateBusinessId(): ByteArray {
  288. val timestamp = System.currentTimeMillis()
  289. val timePart = timestamp and 0xFFFFL
  290. val randomPart: Int = Random.nextInt(0xFF + 1)
  291. val combined = ((timePart shl 8) or randomPart.toLong()).toInt()
  292. val idBytes = ByteArray(4)
  293. idBytes[0] = (combined shr 16).toByte() // 高8位
  294. idBytes[1] = (combined shr 8).toByte() // 中8位
  295. idBytes[2] = combined.toByte() // 低8位
  296. idBytes[3] = 0x01.toByte() // 请求类型(会在split时设置)
  297. return idBytes
  298. }
  299. }