feat(demo): SDK 暴露 rawJsonEvents 调试通道;Demo 日志显示 BLE 收发原始 JSON

- BleTypes.kt 新增 BleRawJsonEvent(TX/RX,含 cmd 名/十六进制类型/JSON 字符串),KDoc 标注调试用、非业务契约
- BleProtocolService.sendJSON 内 emit TX;新增 rawJsonEmitter 注入回调避免协议层反向依赖
- DuooomiBleSDK 公开 SharedFlow<BleRawJsonEvent>;handleIncomingMessage 入口尽力 UTF-8 解码 emit RX(非 JSON 字节流静默跳过)
- DemoScreen LaunchedEffect 订阅,前缀 → TX / ← RX 写入现有 logs 列表
- iOS 当前无对应通道,本通道为 Android Demo 调试增强

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-13 12:03:33 +08:00
parent e63865092c
commit c39d107520
4 changed files with 64 additions and 1 deletions

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.duooomi.ble.DuooomiBleSDK
import com.duooomi.ble.core.BleRawJsonEvent
import com.duooomi.ble.core.ConnectionState
import com.duooomi.ble.models.PlayMode
import kotlinx.coroutines.async
@@ -111,6 +112,13 @@ fun DemoScreen(
CDNHelper.cdnHost = sdk.config.normalizedCdnHost
}
LaunchedEffect(sdk) {
sdk.rawJsonEvents.collect { evt ->
val arrow = if (evt.direction == BleRawJsonEvent.Direction.TX) "→ TX" else "← RX"
log("$arrow [${evt.commandName}] ${evt.json}")
}
}
Scaffold(
topBar = {
TopAppBar(title = { Text("SDK Demo") })

View File

@@ -54,6 +54,13 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
/**
* BLE JSON 协议层原始收发事件流。**调试用**——日志/抓包/UI 联调展示,
* 不属业务行为契约iOS 版无对应通道)。集成方不要依赖它做业务逻辑。
*/
private val _rawJsonEvents = MutableSharedFlow<BleRawJsonEvent>(extraBufferCapacity = 32)
val rawJsonEvents: SharedFlow<BleRawJsonEvent> = _rawJsonEvents.asSharedFlow()
// MARK: - Internal Services
private val bleClient = BleClient(context)
@@ -85,6 +92,7 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
init {
BleLog.i("SDK initialized (apiHost=${config.apiHost}, brand=${config.brand})", "SDK")
setupDisconnectHandler()
protocolService.rawJsonEmitter = { evt -> _rawJsonEvents.tryEmit(evt) }
// 启动时把持久化记录回放到 StateFlow
_upgradeRecords.value = upgradeRecordStore.all
upgradeRecordStore.onChange = { records ->
@@ -702,6 +710,7 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
private fun handleIncomingMessage(commandType: Byte, data: ByteArray) {
val typeValue = commandType.toInt() and 0xFF
emitRawJsonRx(typeValue, data)
val opId = "$typeValue"
// Check for prepareTransfer with key-based opId
@@ -746,6 +755,26 @@ class DuooomiBleSDK(context: Context, val config: DuooomiBleConfig) {
throw DuooomiBleError.BrandMismatch(expected = expected, actual = actual)
}
private fun emitRawJsonRx(typeValue: Int, data: ByteArray) {
runCatching {
val cleaned = data.filter { it != 0.toByte() }.toByteArray()
if (cleaned.isEmpty()) return@runCatching
val jsonStr = String(cleaned, Charsets.UTF_8)
if (jsonStr.startsWith("{") || jsonStr.startsWith("[")) {
val name = CommandType.fromValue(typeValue)?.name
?: "UNKNOWN(0x%02X)".format(typeValue)
_rawJsonEvents.tryEmit(
BleRawJsonEvent(
direction = BleRawJsonEvent.Direction.RX,
commandType = typeValue,
commandName = name,
json = jsonStr
)
)
}
}
}
private fun parseJsonResponse(data: ByteArray): JSONObject {
// Device responses may contain null bytes
val cleaned = data.filter { it != 0.toByte() }.toByteArray()

View File

@@ -12,6 +12,19 @@ data class DiscoveredDevice(
val rssi: Int
)
/**
* BLE JSON 协议层的原始收发事件。**调试用**,不属业务行为契约——
* 不要根据它做业务决策,仅用于日志/抓包/联调展示。iOS 版暂未提供等价通道。
*/
data class BleRawJsonEvent(
val direction: Direction,
val commandType: Int,
val commandName: String,
val json: String
) {
enum class Direction { TX, RX }
}
sealed class DuooomiBleError(message: String) : Exception(message) {
class BluetoothNotPoweredOn(state: String) : DuooomiBleError("Bluetooth not powered on: $state")
object NotConnected : DuooomiBleError("No device connected")

View File

@@ -1,6 +1,7 @@
package com.duooomi.ble.services
import com.duooomi.ble.core.BleClient
import com.duooomi.ble.core.BleRawJsonEvent
import com.duooomi.ble.core.DuooomiBleError
import com.duooomi.ble.protocol.*
import com.duooomi.ble.utils.BleLog
@@ -21,6 +22,9 @@ internal class BleProtocolService(private val client: BleClient) {
private val _incomingMessages = MutableSharedFlow<Pair<Byte, ByteArray>>(extraBufferCapacity = 16)
val incomingMessages: SharedFlow<Pair<Byte, ByteArray>> = _incomingMessages
/** 调试用DuooomiBleSDK 注入回调以接收 TX 原始 JSON 事件 */
var rawJsonEmitter: ((BleRawJsonEvent) -> Unit)? = null
// MARK: - Start/Stop Listening
fun startListening(scope: CoroutineScope) {
@@ -74,7 +78,16 @@ internal class BleProtocolService(private val client: BleClient) {
}
suspend fun sendJSON(type: CommandType, payload: JSONObject) {
val jsonBytes = payload.toString().toByteArray(Charsets.UTF_8)
val jsonStr = payload.toString()
rawJsonEmitter?.invoke(
BleRawJsonEvent(
direction = BleRawJsonEvent.Direction.TX,
commandType = type.value,
commandName = type.name,
json = jsonStr
)
)
val jsonBytes = jsonStr.toByteArray(Charsets.UTF_8)
send(type = type.byte, data = jsonBytes)
}