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 1d71c6c..bd0f53f 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -1104,6 +1104,8 @@ class BleManager private constructor(private val context: Context) { private fun processReceivedData(data: ByteArray) { lastRxTimestamp = System.currentTimeMillis() + // Raw BLE capture (디버그 — 사용자가 토글로 enable 했을 때만 writes) + com.example.medilightv2android.services.RawBleLogger.write(data) val prefix = String(data, 0, minOf(4, data.size), Charsets.US_ASCII) logd { "Received: $prefix (${data.size} bytes)" } when (prefix) { diff --git a/app/src/main/java/com/example/medilightv2android/services/RawBleLogger.kt b/app/src/main/java/com/example/medilightv2android/services/RawBleLogger.kt new file mode 100644 index 0000000..8ab12d0 --- /dev/null +++ b/app/src/main/java/com/example/medilightv2android/services/RawBleLogger.kt @@ -0,0 +1,94 @@ +/* + * RawBleLogger — 모든 BLE notification 의 raw bytes 를 시간순으로 파일로 dump. + * + * 용도: IMU FSR / packet 구조 / loss 진단. JSON 가공 거치기 전 원본 그대로 저장. + * + * 파일: Downloads/VesiScan_RawBLE/yyyy-MM-dd_HHmmss_raw.log + * 포맷 (한 줄당 한 notification): + * HH:mm:ss.SSS dir=RX prefix=rim len=188 hex=72 69 6D 3A 00 0F ... + * + * 사용: startCapture() / stopCapture() 토글. 활성화 시 BleManager.processReceivedData + * 가 매 notification 마다 write() 호출. + */ +package com.example.medilightv2android.services + +import android.os.Environment +import java.io.File +import java.io.FileWriter +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.atomic.AtomicBoolean + +object RawBleLogger { + + private val sessionFmt = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US) + private val timeFmt = SimpleDateFormat("HH:mm:ss.SSS", Locale.US) + private val capturing = AtomicBoolean(false) + @Volatile private var sessionFile: File? = null + @Volatile private var packetCount: Int = 0 + + val isCapturing: Boolean get() = capturing.get() + val currentFile: File? get() = sessionFile + val count: Int get() = packetCount + + private fun getDir(): File { + val dir = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), + "VesiScan_RawBLE" + ) + if (!dir.exists()) dir.mkdirs() + return dir + } + + /** 새 파일 만들고 capture 시작. 이미 capturing 중이면 stop 후 새로 시작. */ + fun startCapture(): File { + if (capturing.get()) stopCapture() + val f = File(getDir(), "${sessionFmt.format(Date())}_raw.log") + try { + FileWriter(f, false).buffered().use { w -> + w.write("# RawBleLogger session — started ${timeFmt.format(Date())}") + w.newLine() + w.write("# Format: HH:mm:ss.SSS dir=RX prefix=XXX len=N hex=AA BB ...") + w.newLine() + } + } catch (_: Exception) {} + sessionFile = f + packetCount = 0 + capturing.set(true) + return f + } + + fun stopCapture(): File? { + if (!capturing.get()) return sessionFile + val f = sessionFile + try { + if (f != null) { + FileWriter(f, true).buffered().use { w -> + w.write("# stopped ${timeFmt.format(Date())} — $packetCount packets") + w.newLine() + } + } + } catch (_: Exception) {} + capturing.set(false) + return f + } + + /** BleManager 가 매 notification 받을 때마다 호출. capturing 아니면 즉시 return. */ + fun write(data: ByteArray) { + if (!capturing.get()) return + val f = sessionFile ?: return + try { + val ts = timeFmt.format(Date()) + val prefix = if (data.size >= 4) + String(data, 0, 4, Charsets.US_ASCII).replace(Regex("[^\\x20-\\x7E]"), "?") + else "?" + val hex = data.joinToString(" ") { "%02X".format(it) } + FileWriter(f, true).buffered().use { w -> + w.write("$ts dir=RX prefix=$prefix len=${data.size} hex=$hex") + w.newLine() + } + packetCount++ + } catch (_: Exception) {} + } +} diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt index b3985c5..62a657f 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/monitoring/PiezoMonitoringView.kt @@ -979,6 +979,38 @@ fun PiezoMonitoringView(appState: AppState) { } Spacer(modifier = Modifier.width(8.dp)) + // Raw BLE capture toggle — IMU/packet 디버그용 (Downloads/VesiScan_RawBLE/) + var rawBleCapturing by remember { + mutableStateOf(com.example.medilightv2android.services.RawBleLogger.isCapturing) + } + IconButton( + onClick = { + if (rawBleCapturing) { + val f = com.example.medilightv2android.services.RawBleLogger.stopCapture() + val n = com.example.medilightv2android.services.RawBleLogger.count + rawBleCapturing = false + android.widget.Toast.makeText(context, + "Raw BLE: $n packets saved to ${f?.name}", + android.widget.Toast.LENGTH_LONG).show() + } else { + val f = com.example.medilightv2android.services.RawBleLogger.startCapture() + rawBleCapturing = true + android.widget.Toast.makeText(context, + "Raw BLE capture started → ${f.name}", + android.widget.Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.size(28.dp) + ) { + Icon( + if (rawBleCapturing) Icons.Default.RadioButtonChecked else Icons.Default.FiberManualRecord, + contentDescription = if (rawBleCapturing) "Stop raw BLE capture" else "Start raw BLE capture", + tint = if (rawBleCapturing) Color.Red else Color.Gray.copy(alpha = 0.5f), + modifier = Modifier.size(20.dp) + ) + } + Spacer(modifier = Modifier.width(4.dp)) + // Catheter count (탭하면 추가 팝업) var showAddCatheter by remember { mutableStateOf(false) } val cathCount = appState.catheterCount.intValue