package com.medithings.vesiscan.services import com.medithings.vesiscan.ble.ImuSample import java.io.File import java.text.SimpleDateFormat import java.util.Date import java.util.Locale /** * IMU 샘플을 CSV로 저장. 임상 R&D 모드 한정. * * 파일 위치 (임상 모드일 때): * Downloads/VesiScan_Sessions/{folder}/imu.csv * * 한 번의 mtb? 응답에서 15 샘플 → CSV 15행. scan_id로 같은 측정의 piezo adc.csv와 매칭 가능. * * Sensitivity: * ax/ay/az: g (Accel ±4g, 1 LSB = 1/8192 g) * gx/gy/gz: dps (Gyro ±500 dps, 1 LSB = 1/65.536 dps) * * Sample 순서: 한 응답 안에서 oldest→newest. sample_idx 컬럼으로 명시. */ object ImuCsvLogger { private val timeFmt = SimpleDateFormat("HH:mm:ss.SSS", Locale.US) /** * 임상 모드 한정. ClinicalSessionStore.currentLogDir()가 있을 때만 저장. * samples를 oldest→newest 순서로 그대로 기록. */ fun log(samples: List, scanId: Int? = null) { val dir = ClinicalSessionStore.currentLogDir() ?: return if (samples.isEmpty()) return try { dir.mkdirs() val file = File(dir, "imu.csv") val isNew = !file.exists() || file.length() == 0L // 2026-08-04 §A fix: 호출자가 미리 발급한 scanId 우선. 미제공 시 legacy. val sid = scanId ?: AdcCsvLogger.lastScanId val timestamp = timeFmt.format(Date()) val clinicalLabel = ClinicalSessionStore.currentLabel() val fwVersion = com.medithings.vesiscan.ble.BleManager.current?.firmwareVersion?.value ?: "" file.bufferedWriter().use { _ -> } // ensure exists java.io.FileWriter(file, !isNew).buffered().use { writer -> if (isNew) { writer.write("scan_id,timestamp,clinical_label,firmware_version,sample_idx,ax_g,ay_g,az_g,gx_dps,gy_dps,gz_dps") writer.newLine() } for ((idx, s) in samples.withIndex()) { writer.write(buildString { append("$sid,$timestamp,$clinicalLabel,$fwVersion,$idx,") append("%.4f".format(s.ax)); append(",") append("%.4f".format(s.ay)); append(",") append("%.4f".format(s.az)); append(",") append("%.4f".format(s.gx)); append(",") append("%.4f".format(s.gy)); append(",") append("%.4f".format(s.gz)) }) writer.newLine() } } } catch (_: Exception) { // 로그는 측정 자체를 막으면 안 됨 — silent fail } } }