fix(ble): mtb 응답 중 다른 명령 끼어들어 firmware freeze — 3-layer 근본 fix

실측 로그 (2026-07-08 11:18~11:19) 분석: mtb 응답 stream (reb×6 + raa
+ rim, 총 8개 패킷) 중간에 msn (battery) / mim (IMU) 이 TX 로 끼어들면
firmware GATT queue 가 꼬여 응답 실종 → 이어지는 mls mode 0 명령에서
완전 freeze (BLE 광고까지 중단, 재연결 10회 모두 실패).

핵심 원인:
- BleManager 의 batteryTimer / mim polling 이 시간 간격 기반이라 mtb
  응답 진행 중에도 무조건 TX 발사.
- 특히 sendImuFifoQuery 는 imuCollector.reset() 을 먼저 호출 → mtb 의
  rim (IMU) 이 파괴됨.

Layer 1 — BleManager gating
  - isMtbBusy 프로퍼티 추가 (piezoCollector.isMultiChannel && !isComplete)
  - batteryTimer / batteryRetryTimer: sendBatteryQuery 앞에 isMtbBusy skip
  - Watchdog silence heartbeat: sendImuQuery (msp) → sendImuFifoQuery
    (mim) 로 교체 + isMtbBusy skip
  - sendImuQuery 함수 자체 삭제 — msp 명령 코드에서 완전 제거
    (rsp 파서는 legacy 응답용 유지)

Layer 2 — PiezoMonitoringView mim polling gating
  - LaunchedEffect while 루프의 sendImuFifoQuery 앞에 isMtbBusy skip

Layer 3 — mtb 3초 timeout + UI 안내
  - sendMtb: 3초 timeout runnable, raa 응답 오면 취소
  - lastMtbTimeoutAt / consecutiveMtbTimeouts state 노출
  - PlacementGuideView 가 관찰 → 1~2회: "기기 응답 지연" 안내
    3회 연속: "재연결" 안내 + forceDisconnectAndReconnect 자동 호출
  - forceDisconnectAndReconnect 를 public 으로 승격

부가:
- disconnect 3곳 (GATT_ERROR / STATE_DISCONNECTED / watchdog forced)
  에서 mtbTimeoutRunnable 도 함께 정리.

Firmware VBTFW0121 의 mls mode 0 handler 취약점은 별도로 펌웨어 팀에
리포트 필요 (본 fix 는 큐 혼잡 방지로 근본적으로 회피).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 15:03:48 +09:00
parent deac2f50eb
commit 990793170e
5 changed files with 80 additions and 11 deletions
+2
View File
@@ -322,6 +322,8 @@
<string name="placement_skip">건너뛰기</string>
<string name="placement_in_position">최적의 위치입니다!</string>
<string name="placement_position_lost">위치를 놓쳤습니다. 치골 위에서 다시 시작해주세요.</string>
<string name="mtb_response_delayed">기기 응답이 지연되고 있습니다. 잠시만 기다려주세요…</string>
<string name="mtb_response_reconnect">기기가 응답하지 않습니다. 연결을 재시도합니다.</string>
<string name="placement_detached_restart">센서가 분리되었습니다. 치골 위에서 다시 시작해주세요.</string>
<string name="placement_signal_lost">신호를 놓쳤습니다 — 위로 올려주세요 ↑</string>
<string name="placement_sweep_start">배꼽 근처에서 시작해 천천히 아래로 내려주세요 ↓</string>