docs: v7 (2026-07-10) 반영 — msp 제거, 6-stage alignment, mtb queue fix, labdb 자동 재시도

이번 세션 대량 변경 사항을 5개 문서에 반영. 각 문서마다 stale 이던
섹션을 갱신하거나 신규 섹션 추가.

USER_GUIDE.md
  - Sensor Alignment 를 V1 (3-stage) / V2 (6-stage) 로 재구성
  - V2 6-stage 표 + relaxed mode / soft hint 설명
  - GREEN 진입 5초 hold + 10-strike 리셋 완화 명시
  - "최적의 위치입니다!" 문구 반영

docs/BLE_PROTOCOL_REFERENCE.md
  - msp 명령 취소선 처리 + mim 신규 명령 문서화
  - Watchdog timeout 25초 연장 명시 (2.5)
  - §2.6 신규: firmware VBTFW0121 mls mode 0 freeze 취약점 +
    앱 측 3-layer 회피 (isMtbBusy / mtb 3초 timeout / 자동 재연결)

VesiScan_Android_Pipeline_Summary.md
  - v7 (2026-07-10) 섹션 신규 추가 — BLE / Alignment / UI / labdb /
    tools 5개 카테고리로 변경 사항 정리
  - 권장 펌웨어 표기 VBTFW0116 → VBTFW0120+ 로 갱신

labdb.md
  - §12b 신규: 앱 측 Auto Retry Policy — endMeasurement 자동 업로드
    조건 완화, LabdbAutoRetry object, UI 배너, 재시도 안전성

tools/README.md
  - labdb_upload.py 섹션 신규 — 사용법 / 폴더 구조 / 재실행 안전성 /
    buildPayload 로직 / 활용 예 정리

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 11:25:26 +09:00
parent b822b49fc6
commit 60b630d3de
5 changed files with 367 additions and 32 deletions
+44 -4
View File
@@ -96,10 +96,49 @@ VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0116+ 펌웨어)와 주고
- 측정 중이었으면 측정 일시정지, 복구 시 사용자가 다시 시작
### 2.5 Watchdog (좀비 감지)
파일: [`BleManager.kt:528+` watchdogJob](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L528)
파일: [`BleManager.kt` watchdogJob](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt)
- 별도 스레드로 30초 주기 RX 마지막 수신 시간 확인
- N초 이상 무응답 + isConnected.value = true 라면 GATT 강제 reset → 재연결
- 별도 스레드가 5초 tick 으로 RX 마지막 수신 시각 확인.
- **2026-07-07**: watchdog timeout **15 → 25초** 연장. 실측 로그에서 재연결
후 첫 RX 가 14초 지연 후 도착하는 케이스 확인 — peripheral / OS BLE
스택의 좀비 회복 시간 허용. 15초로는 회복 직전에 forced reconnect 발동
→ 무한 재연결 루프 문제.
- Silence 10초~timeout 사이면 **heartbeat 발동**: 2026-07-08 부터 `msp` 대신
`sendImuFifoQuery()` (mim). `isMtbBusy` 시 skip.
- 정리는 UI thread 로 위임 (`handler.post`) — watchdog thread 에서 직접
GATT close 하면 UI thread 의 sendRaw 와 race.
### 2.6 ⚠ 알려진 firmware freeze (VBTFW0121) — mtb queue overrun
**증상**: `mtb` 응답 stream (reb×6 + raa + rim) 진행 중에 다른 명령
(`msn`, `mim`) 이 TX 로 끼어들면 firmware GATT queue 가 꼬여 이후 명령
응답이 실종. 특히 그 상태에서 `mls mode 0` 이 결정타가 되어 BLE
advertising 까지 죽는 **완전 freeze** 실측 확인 (2026-07-07 11:26 세션 로그).
**앱 측 3-layer 회피** (2026-07-08 커밋 9907931):
**Layer 1 — BleManager gating (`isMtbBusy`)**
```kotlin
val isMtbBusy: Boolean
get() = piezoCollector.isMultiChannel && !piezoCollector.isComplete
```
- `batteryTimer` / `batteryRetryTimer`: `sendBatteryQuery` 앞에 skip
- Watchdog silence heartbeat: `sendImuFifoQuery` 앞에 skip
- 상위 (View 레이어) 도 `mim` 폴링 앞에서 `isMtbBusy` 체크
**Layer 2 — mtb 3초 timeout**
- `sendMtb()` 후 3초 timeout runnable, `raa` 응답 오면 취소.
- Timeout 시 `consecutiveMtbTimeouts` 증가 + `piezoCollector.reset()` /
`imuCollector.reset()` 해제.
**Layer 3 — UI 안내 + 자동 재연결**
- `PlacementGuideView` 가 `consecutiveMtbTimeouts` 관찰.
- 1~2회: `"기기 응답 지연"` 배너
- **3회 연속: `forceDisconnectAndReconnect()` 자동 호출** — 사용자가 앱을
방치해도 자동 복구.
**Firmware 팀 리포트 대상**: VBTFW0121 의 `mls mode 0` handler 가 큐
스트레스 상태에서 취약. 앱 fix 로 회피는 되지만 근본은 firmware.
---
@@ -164,7 +203,8 @@ fun verify(data: ByteArray): Boolean
| `msn?` | BE [0] | `sendBatteryQuery()` ([L463](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L463)) | `rsn:` | 배터리 mV 조회 |
| `mid?` | ASCII " " | `sendDeviceInfoQuery()` ([L467](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L467)) | `rid:` | FW/HW/Serial 조회 |
| `mls?` | BE [state] | `sendLedMode(state)` ([L471](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L471)) | `rls:` | LED 모드 변경 |
| `msp?` | ASCII " " | `sendImuQuery()` ([L475](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L475)) | `rsp:` | IMU 단일 샘플 조회 |
| ~~`msp?`~~ | ~~ASCII " "~~ | ~~`sendImuQuery()`~~ | `rsp:` | ⚠ **2026-07-08 완전 제거** — 신 firmware 는 `mim` 만 사용. `rsp:` 파서는 legacy 응답 대비 유지. |
| `mim?` | ASCII " " | `sendImuFifoQuery()` | `rim:` (15 sample) | ★ IMU FIFO — piezo 무음, walking detector 시계열 확보용. `imuCollector.reset()` 을 먼저 호출하므로 mtb 진행 중 (isMtbBusy=true) 이면 caller 가 반드시 skip 해야 함 (mtb 의 rim 파괴 방지). |
### 4.1 maa / mtb / maa 송신 throttle (canSendMaa)