feat(posture): idle 폴링을 msp → mim 로 전환 — 펌웨어 신규 IMU FIFO 명령 활용

## 배경
6/30 msp 전환으로 piezo 무음은 확보했지만, 1샘플/폴링 이라 WalkingDetector
1.5초 window 를 채우려면 200ms 주기 폴링 필요 → BLE 왕복 5배 트래픽 +
나이퀴스트 aliasing 위험 → walking 감지 실제로 어려움.

7/03 펌웨어 신규 명령 `mim?` 배포:
- 요청: mim? [tag 4B][space 1B][crc 2B] = 7B
- 응답: rim: [tag 4B][total 2B][sample 12B × 15][crc 2B] = 188B
- 15 sample (300ms 시계열) 무음 반환
- 응답 프레임은 mtb? 의 rim: 와 동일 스키마 → 파서 재사용

## 변경
- BleManager.sendImuFifoQuery() 추가 — mim? 명령 전송
  * imuCollector.reset() 선행 (frame buffer 초기화)
  * 기존 sendImuQuery(msp) 는 유지 (watchdog heartbeat 계속 사용)
  * sendRaw 디버그 라벨에 "mim" 추가
- PiezoMonitoringView idle 폴러 sendImuQuery → sendImuFifoQuery
- GreenZoneConstants.posturePollIntervalMs 200 → 1000ms
  * mim 1회에 300ms 시계열 얻으므로 1초 주기로도 walking window 충분히 채움
  * BLE 왕복 5배 감소 → 배터리 유리

## 결과 (예상)
- Idle 도넛 화면에서 walking 감지 정상 작동
- BLE 트래픽·전력 감소
- 응답 파서 (imuCollector.parseRim + onComplete) 그대로 재사용, 로직 무변경

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 10:50:58 +09:00
parent bcbc7b440c
commit 506d50d2c6
3 changed files with 30 additions and 13 deletions
@@ -469,6 +469,7 @@ class BleManager private constructor(private val context: Context) {
else "?"
}"
"msp" -> "IMU query"
"mim" -> "IMU FIFO (15 samples)"
"mbb" -> "full measurement (battery+IMU+temp+6ch)"
else -> ""
}
@@ -576,6 +577,18 @@ class BleManager private constructor(private val context: Context) {
sendRaw(CRC16.buildCommandASCII("msp", " "))
}
/**
* IMU FIFO 폴링 (2026-07-03 펌웨어 신규 mim?):
* - piezo burst 없음 (무음)
* - IMU 15 sample (300ms window) 를 rim: 프레임으로 반환
* - 응답 스키마는 mtb? 의 rim: 와 동일 → imuCollector.parseRim + onComplete 재사용
* idle 폴링에서 walking detector 의 sliding window 를 채우기 위해 사용.
*/
fun sendImuFifoQuery() {
imuCollector.reset()
sendRaw(CRC16.buildCommandASCII("mim", " "))
}
/** 전체 측정: 배터리+IMU+온도+6ch piezo (rbb→reb×6→raa) */
fun sendFullMeasurement() {
piezoCollector.startMultiChannel(6)
@@ -64,12 +64,15 @@ object GreenZoneConstants {
@Volatile var postureAccMagMax: Float = 1.2f
/**
* Posture polling 간격 (ms). PiezoMonitoring 에서 msp 보내는 주기.
* 6/30 변경: mtb (1Hz × 15sample) → msp (5Hz × 1sample) 로 전환. piezo burst 가청음 제거.
* Walking detector 의 1.5초 window 안에 7-8 sample 확보 위해 200ms 주기.
* Posture polling 간격 (ms). PiezoMonitoring idle 폴링 주기.
* 이력:
* 6/30 : mtb (1Hz × 15sample) → msp (5Hz × 1sample) — piezo 무음 확보, 주기 200ms
* (walking window 1.5초 안에 7-8 샘플 확보용)
* 7/03 : msp → mim (1Hz × 15sample, 무음) — piezo 무음 유지 + 시계열 15샘플 확보.
* 단일 폴링당 300ms 시계열 얻으므로 주기 1000ms 로 완화 (BLE 왕복 5배 감소).
* dev 모드에서 조정 가능.
*/
@Volatile var posturePollIntervalMs: Long = 200L
@Volatile var posturePollIntervalMs: Long = 1000L
// ── Walking detector 임계값 (flowchart 4-2 / 5-2) ─────────────────
/** Walking 인정 최소 pitch (deg). |pitch| > 이 값 이라야 walking 후보. */
@@ -758,11 +758,12 @@ fun PiezoMonitoringView(appState: AppState) {
com.medithings.vesiscan.services.PostureEventLogger.startSession(context)
}
// IMU polling + 자세 분류 — msp 명령 (rsp: 응답, IMU-only).
// 6/30: 이전엔 mtb (piezo 6ch + IMU) 폴링으로 매 cycle piezo burst 가청음 발생.
// 사용자앱에서 msp 의 rsp: 응답 정상 (실측 VBTFW0120, 18B = tag+12B sample+crc) 확정 후
// 데모도 동일 적용. WalkingDetector window (1.5초) 확보 위해 폴링 주기 1000→200ms.
// 둘 다 imuCollector.parseRsp/parseRim 가 같은 onComplete 콜백 트리거 → classifier 동일.
// IMU polling + 자세 분류 — 2026-07-03: msp → mim 전환 (펌웨어 신규 명령).
// 6/30 (msp): piezo 무음 확보했지만 1샘플/폴링이라 WalkingDetector 1.5초 window
// 확보 위해 200ms 폴링 → BLE 왕복 5배 트래픽 + 나이퀴스트 aliasing 위험.
// 7/03 (mim): 15샘플/폴링(300ms 시계열) + piezo 무음. WalkingDetector 가 요구하는
// 시계열 밀도 확보. 폴링 주기 200 → 1000ms 로 완화 (BLE 왕복 5배 감소, 배터리 유리).
// 응답 프레임 rim: 는 mtb? 와 동일 — imuCollector.parseRim + onComplete 그대로 재사용.
LaunchedEffect(isConnected, isDemoMode) {
Log.d("ImuDbg", "LaunchedEffect start isConnected=$isConnected isDemoMode=$isDemoMode")
if (isDemoMode) {
@@ -820,16 +821,16 @@ fun PiezoMonitoringView(appState: AppState) {
Log.d("ImuDbg", "imuCollector log: $msg")
}
while (isActive) {
// msp 는 piezo burst 없는 IMU-only — Auto/Single Scan 중에도 충돌 없이 가능.
// mim 은 piezo burst 없는 IMU FIFO (15 sample) — Auto/Single Scan 중에도 충돌 없이 가능.
// 단 isMeasuring (현재 measure() 호출 중) / isSpotInProgress 짧은 시간만 skip
// (BLE 큐 충돌 방어).
// Auto Scan 자체는 mtb 응답에 IMU 가 들어있어 자동으로 posture 갱신됨 (parseRim).
if (isConnected && !isMeasuring && !isSpotInProgress) {
if (!isAutoMeasuring) {
// idle — msp polling (무음)
bleManager.sendImuQuery()
// idle — mim FIFO 폴링 (15샘플/burst, 무음)
bleManager.sendImuFifoQuery()
}
// isAutoMeasuring=true 면 mtb 의 rim: 가 알아서 posture 갱신, msp skip
// isAutoMeasuring=true 면 mtb 의 rim: 가 알아서 posture 갱신, mim skip
}
kotlinx.coroutines.delay(GreenZoneConstants.posturePollIntervalMs)
}