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

@@ -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()