/* * 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) {} } }