feat: 자동측정 IMU 1초 + 전체측정(mbb) 10초 루프
자동측정 변경 (기존: 30초 maa → 신규: 1초 msp + 10초 mbb): - 1~9초: msp(IMU query) → rsp(accel+gyro) 1초 간격 - 10초째: mbb(전체측정) → rbb(헤더) + reb×6 + raa - 기존 maa → mbb(sendFullMeasurement)로 전환 - mbb 응답은 rbb: 헤더 후 reb:/raa: 동일 파이프라인 BLE 명령어 추가: - sendImuQuery(): msp 전송 - sendFullMeasurement(): mbb 전송 (piezoCollector 연동) - sendLedMode(state): mls 전송 응답 파싱 추가: - rxs: → "cmd_not_supported" (mid? 미지원 기기 대응) - rbb: → full measurement header - rsp: → IMU data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,8 @@ class BleManager private constructor(private val context: Context) {
|
||||
var onPiezoDataReceived: ((ByteArray) -> Unit)? = null
|
||||
var onUnexpectedDisconnect: (() -> Unit)? = null
|
||||
var onReconnectionFailed: (() -> Unit)? = null
|
||||
var onImuReceived: ((ByteArray) -> Unit)? = null
|
||||
var onMbbHeaderReceived: ((ByteArray) -> Unit)? = null
|
||||
|
||||
val isBluetoothReady: Boolean get() = bluetoothAdapter?.isEnabled == true
|
||||
|
||||
@@ -282,6 +284,8 @@ class BleManager private constructor(private val context: Context) {
|
||||
"mcj" -> "NIRS MCJ query"
|
||||
"mid" -> "device info query"
|
||||
"mls" -> "LED mode ${if (data.size > 4) data[4].toInt() and 0xFF else "?"}"
|
||||
"msp" -> "IMU query"
|
||||
"mbb" -> "full measurement (battery+IMU+temp+6ch)"
|
||||
else -> ""
|
||||
}
|
||||
logd { "sendRaw: $cmdPreview (${data.size} bytes)" }
|
||||
@@ -353,6 +357,16 @@ class BleManager private constructor(private val context: Context) {
|
||||
sendRaw(CRC16.buildCommand("mls", intArrayOf(state)))
|
||||
}
|
||||
|
||||
fun sendImuQuery() {
|
||||
sendRaw(CRC16.buildCommand("msp"))
|
||||
}
|
||||
|
||||
/** 전체 측정: 배터리+IMU+온도+6ch piezo (rbb→reb×6→raa) */
|
||||
fun sendFullMeasurement() {
|
||||
piezoCollector.startMultiChannel(6)
|
||||
sendRaw(CRC16.buildCommand("mbb"))
|
||||
}
|
||||
|
||||
fun startBatteryPolling() {
|
||||
batteryTimer?.let { handler.removeCallbacks(it) }
|
||||
sendBatteryQuery()
|
||||
@@ -789,6 +803,20 @@ class BleManager private constructor(private val context: Context) {
|
||||
val state = if (data.size >= 5) data[4].toInt() and 0xFF else -1
|
||||
debugLogger.rx("rls", data.size, "led_state=$state")
|
||||
}
|
||||
"rxs:" -> {
|
||||
val echoCmd = if (data.size > 4) String(data, 4, minOf(data.size - 4, 10), Charsets.US_ASCII).trim() else "?"
|
||||
debugLogger.rx("rxs", data.size, "cmd_not_supported: $echoCmd")
|
||||
}
|
||||
"rbb:" -> {
|
||||
debugLogger.rx("rbb", data.size, "full measurement header (battery+IMU+temp)")
|
||||
onMbbHeaderReceived?.invoke(data)
|
||||
}
|
||||
"rsp:" -> {
|
||||
if (data.size >= 16) {
|
||||
debugLogger.rx("rsp", data.size, "IMU data")
|
||||
}
|
||||
onImuReceived?.invoke(data)
|
||||
}
|
||||
else -> {
|
||||
val hex = data.take(8).joinToString(" ") { "%02X".format(it) }
|
||||
debugLogger.rx("???", data.size, "unknown prefix='$prefix' hex=$hex")
|
||||
|
||||
+22
-9
@@ -100,8 +100,8 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
return
|
||||
}
|
||||
|
||||
// Real BLE measurement — 6-channel
|
||||
Log.d("PiezoMonitor", "Sending maa (all channels)...")
|
||||
// Real BLE measurement — 6-channel (maa or mbb)
|
||||
Log.d("PiezoMonitor", "Sending measurement command...")
|
||||
|
||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||
handler.post {
|
||||
@@ -181,7 +181,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
bleManager.piezoCollector.onLog = { msg -> Log.d("PiezoCollector", msg) }
|
||||
|
||||
bleManager.sendAllChannels()
|
||||
bleManager.sendFullMeasurement()
|
||||
|
||||
// Timeout with auto-retry (15s)
|
||||
handler.postDelayed({
|
||||
@@ -190,7 +190,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
measureRetryCount++
|
||||
Log.w("PiezoMonitor", "Timeout — auto-retry $measureRetryCount/$maxMeasureRetries")
|
||||
bleManager.piezoCollector.reset()
|
||||
bleManager.sendAllChannels()
|
||||
bleManager.sendFullMeasurement()
|
||||
// Second timeout for retry
|
||||
handler.postDelayed({
|
||||
if (!isMeasuring) return@postDelayed
|
||||
@@ -214,21 +214,34 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
isMeasuring = false
|
||||
}
|
||||
|
||||
var autoTickCount by remember { mutableIntStateOf(0) }
|
||||
|
||||
fun startAutoMeasure() {
|
||||
isAutoMeasuring = true
|
||||
measure()
|
||||
autoTickCount = 0
|
||||
measure() // 즉시 첫 전체 측정
|
||||
}
|
||||
|
||||
fun stopAutoMeasure() {
|
||||
isAutoMeasuring = false
|
||||
autoTickCount = 0
|
||||
}
|
||||
|
||||
// Auto-measure: repeat every 30s while active
|
||||
// Auto-measure: 1초 IMU × 9 + 10초째 전체측정(mbb) 반복
|
||||
LaunchedEffect(isAutoMeasuring) {
|
||||
if (isAutoMeasuring) {
|
||||
while (isAutoMeasuring) {
|
||||
kotlinx.coroutines.delay(30_000)
|
||||
if (isAutoMeasuring && !isMeasuring) measure()
|
||||
kotlinx.coroutines.delay(1000)
|
||||
if (!isAutoMeasuring) break
|
||||
autoTickCount++
|
||||
|
||||
if (autoTickCount % 10 == 0) {
|
||||
// 10초째: 전체 측정 (mbb → rbb + reb×6 + raa)
|
||||
if (!isMeasuring) measure()
|
||||
} else {
|
||||
// 1~9초: IMU 쿼리 (msp → rsp)
|
||||
bleManager.sendImuQuery()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,7 +749,7 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
}
|
||||
|
||||
Text(
|
||||
if (isAutoMeasuring) "Measuring every 30s" else "Long press Spot for auto-measurement",
|
||||
if (isAutoMeasuring) "Auto: IMU 1s + Measure 10s (#$autoTickCount)" else "Long press for auto (IMU 1s + measure 10s)",
|
||||
fontSize = 11.sp,
|
||||
color = if (isAutoMeasuring) Color.Red.copy(alpha = 0.7f) else MlSecondaryText
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user