From df42a47d7db2891fe1068e3a951fe92f1c83e210 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 23 Apr 2026 15:40:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20BLE=20=EB=A1=9C=EA=B7=B8=EC=97=90=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1=EB=90=9C=20=EA=B0=92=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(TX=20=EB=AA=85=EB=A0=B9=EC=96=B4=20+=20RX=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=EC=9D=98=EB=AF=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TX: "maa 6ch measure", "msn battery query", "mpa piezo power ON" 등 RX: "rsn battery=3850mV (100%)", "reb 100samples", "raa all-ch complete", "rpa piezo ack", "rag gain calibration (24 values)" 등 펌웨어팀에 보낼 때 raw hex 없이도 통신 흐름을 이해할 수 있음 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../medilightv2android/ble/BleManager.kt | 75 ++++++++++++++----- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt index eb4136d..5d5e78e 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -270,9 +270,21 @@ class BleManager private constructor(private val context: Context) { loge { "sendRaw: bluetoothGatt is null!" } return } - val cmdPreview = if (data.size >= 4) String(data, 0, 4, Charsets.US_ASCII).trimEnd('?') else "?" + val cmdPreview = if (data.size >= 4) String(data, 0, 3, Charsets.US_ASCII) else "?" + val cmdDetail = when (cmdPreview) { + "maa" -> "6ch measure" + "mpa" -> "piezo power ON" + "mps" -> "piezo stop" + "mec" -> "single burst" + "msn" -> "battery query" + "mta" -> if (data.size > 4 && data[4].toInt() == 1) "NIRS power ON" else "NIRS power OFF" + "mqq" -> "NIRS activate" + "mag" -> "NIRS gain query" + "mcj" -> "NIRS MCJ query" + else -> "" + } logd { "sendRaw: $cmdPreview (${data.size} bytes)" } - debugLogger.tx("$cmdPreview (${data.size}B)", data.size) + debugLogger.tx("$cmdPreview $cmdDetail", data.size) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val result = gatt.writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) logd { "sendRaw result: $result" } @@ -724,29 +736,58 @@ class BleManager private constructor(private val context: Context) { private fun processReceivedData(data: ByteArray) { lastRxTimestamp = System.currentTimeMillis() val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII) - debugLogger.rx(prefix.trimEnd(':', '!'), data.size) logd { "Received: $prefix (${data.size} bytes)" } when (prefix) { - "rta:", "rta!" -> onNirsPowerOnReceived?.invoke() - "rsh:", "rsh!", "rqq:", "rqq!" -> onNirsSensorActivated?.invoke() - "rag:", "rag!" -> onNirsMagReceived?.invoke(data) - "rcj:", "rcj!" -> onNirsMcjReceived?.invoke(data) - "rpa:" -> onPiezoDataReceived?.invoke(data) - // Piezo echo responses → route to packet collector - "rer:" -> { - // VesiScan-Basic preliminary header — just acknowledge, reb: follows - logd { "rer: preliminary header (${data.size} bytes)" } + "rta:", "rta!" -> { + debugLogger.rx("rta", data.size, "piezo power ON") + onNirsPowerOnReceived?.invoke() + } + "rsh:", "rsh!", "rqq:", "rqq!" -> { + debugLogger.rx(prefix.take(3), data.size, "sensor activated") + onNirsSensorActivated?.invoke() + } + "rag:", "rag!" -> { + debugLogger.rx("rag", data.size, "gain calibration (${(data.size - 6) / 2} values)") + onNirsMagReceived?.invoke(data) + } + "rcj:", "rcj!" -> { + debugLogger.rx("rcj", data.size, "MCJ packet") + onNirsMcjReceived?.invoke(data) + } + "rpa:" -> { + debugLogger.rx("rpa", data.size, "piezo ack") + onPiezoDataReceived?.invoke(data) + } + "rer:" -> { + debugLogger.rx("rer", data.size, "preliminary header") + } + "reb:" -> { + val samples = (data.size - 8) / 2 + debugLogger.rx("reb", data.size, "${samples}samples") + piezoCollector.addPacket(data) + } + "red:" -> { + debugLogger.rx("red", data.size, "continuation") + piezoCollector.addPacket(data) + } + "ree:", "raa:" -> { + val tag = prefix.take(3) + debugLogger.rx(tag, data.size, if (tag == "raa") "all-ch complete" else "single-ch end") + piezoCollector.addPacket(data) } - "reb:" -> piezoCollector.addPacket(data) - "red:" -> piezoCollector.addPacket(data) - "ree:", "raa:" -> piezoCollector.addPacket(data) "rsn:" -> { if (data.size >= 6) { val millivolts = ((data[4].toInt() and 0xFF) shl 8) or (data[5].toInt() and 0xFF) - batteryLevel.value = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100) + val pct = ((millivolts - 3700) * 100 / 500).coerceIn(0, 100) + batteryLevel.value = pct + debugLogger.rx("rsn", data.size, "battery=${millivolts}mV (${pct}%)") } } - else -> logw { "Unknown prefix: '$prefix' hex=${data.take(8).joinToString(" ") { "%02X".format(it) }}" } + else -> { + val hex = data.take(8).joinToString(" ") { "%02X".format(it) } + debugLogger.rx("???", data.size, "unknown prefix='$prefix' hex=$hex") + logw { "Unknown prefix: '$prefix' hex=$hex" } + } } } }