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:
@@ -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") })
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user