docs: 5개 md 일괄 sync (버전/날짜/package path/V2 6-stage/CH3 flicker)

- USER_GUIDE.md
  * versionCode 24 → 26, App 1.0.0-design → 1.2.0-demo
  * Last Updated 2026-05-26 → 2026-07-13
  * Recommended FW VBTFW0116 → VBTFW0120+ (mim FIFO 필수)
  * PinView.kt / AppState.kt link path com.example → com.medithings.vesiscan
- docs/BLE_PROTOCOL_REFERENCE.md
  * 작성일 2026-06-05 → 2026-07-13, 검증 FW VBTFW0116 → VBTFW0120+
  * package path 52건 com.example → com.medithings.vesiscan (모든 코드 링크 복구)
- docs/ALGORITHM_COMPARISON.md
  * 3-Stage 를 "V1 (일반 진입)" 로 명시
  * "V2 6-Stage Guide (AlignGuide4Stage)" 신규 섹션: phase 표 + CH3 flicker
    3-Layer (majority / relaxed / soft-hint) + Python replay 검증 요약
  * 헤더에 2026-07-13 update 표시
- docs/FLAVOR_DEMO_STABLE.md
  * example source path 표기 com/example/... → com/medithings/vesiscan/...
- VesiScan_Android_Pipeline_Summary.md
  * 남아있던 com/example/ 경로 1건 정리

기능 변화 없음. 문서-코드 일관성 확보 (기존 링크가 리네임 이후 broken 상태였음).
This commit is contained in:
2026-07-13 15:28:43 +09:00
parent eb25c01c97
commit 1cf45df80c
5 changed files with 92 additions and 62 deletions
+53 -53
View File
@@ -1,8 +1,8 @@
# BLE Protocol Reference — VBT Device
VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0116+ 펌웨어)와 주고받는 BLE 명령/응답 전체 정리. 코드 위치는 모두 `app/src/main/java/com/example/medilightv2android/ble/` 기준.
VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0120+ 펌웨어)와 주고받는 BLE 명령/응답 전체 정리. 코드 위치는 모두 `app/src/main/java/com/medithings/vesiscan/ble/` 기준.
작성일 / 검증 FW: 2026-06-05 / **VBTFW0116** (reb 신구조 적용)
작성일 / 검증 FW: 2026-07-13 / **VBTFW0120+** (mim FIFO + mtb queue overrun 3-layer guard 반영)
---
@@ -48,7 +48,7 @@ VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0116+ 펌웨어)와 주고
## 2. BLE 연결 Lifecycle
### 2.1 UUID (Nordic UART Service)
파일: [`BleManager.kt:45-48`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L45)
파일: [`BleManager.kt:45-48`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L45)
| 역할 | UUID |
|---|---|
@@ -88,7 +88,7 @@ VesiScan Basic Android 앱이 VBT 디바이스(VBTFW0116+ 펌웨어)와 주고
- 두 협상 모두 디스커버리 + CCCD 활성화 이후에 진행. 너무 일찍 호출하면 reject.
### 2.4 자동 재연결 (silent)
파일: [`BleManager.kt:601` scheduleAutoReconnect](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L601)
파일: [`BleManager.kt:601` scheduleAutoReconnect](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L601)
- 의도하지 않은 disconnect → `scheduleAutoReconnect()` 자동 호출
- 마지막 연결 디바이스 주소를 `SharedPreferences`에 저장 → scan + reconnect 시도
@@ -144,7 +144,7 @@ val isMtbBusy: Boolean
## 3. 명령 빌더 — CRC16-CCITT
파일: [`CRC16.kt`](../app/src/main/java/com/example/medilightv2android/ble/CRC16.kt)
파일: [`CRC16.kt`](../app/src/main/java/com/medithings/vesiscan/ble/CRC16.kt)
```
Polynomial: 0x1021
@@ -189,26 +189,26 @@ fun verify(data: ByteArray): Boolean
| 명령 | 빌더 | Send 함수 | 응답 prefix | 용도 |
|---|---|---|---|---|
| `msr?` | ASCII " " | `disconnectAndUnbond()` 내부 ([L276](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L276)) | — | 본딩 wipe + reboot |
| `mpa?` | BE [freq, cycles] | `sendPiezoPowerOn(freq=2, cycles=5)` ([L404](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L404)) | `rpa:` | Piezo power ON |
| `mps?` | ASCII " " | `sendPiezoStop()` ([L408](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L408)) | — | Piezo stop |
| `mec?` | BE [freq, delay, 140, cycles, 1, ch] | `sendBurst(...)` ([L412](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L412)) | `reb:` 단일 채널 | Single-channel burst |
| `maa?` | ASCII " " | `sendAllChannels()` / `sendChannelsOnly()` ([L416, L513](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L416)) | reb×6 + raa | 6채널 sweep (도넛/placement) |
| `mtb?` | ASCII " " | `sendMtb()` ([L430](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L430)) | reb×6 + raa + **rim** | 6채널 + IMU (★ Clinical) |
| `mbb?` | ASCII " " | `sendFullMeasurement()` ([L480](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L480)) | rbb + reb×6 + raa | 측정 헤더(batt+temp+IMU) + 6채널 |
| `mta?` | BE [0 or 1] | `sendNirsPowerOn()/Off()` ([L441/445](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L441)) | `rta:` | NIRS power |
| `mqq?` | ASCII " " | `sendMqqQuery()` ([L449](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L449)) | `rqq:` | NIRS sensor activate |
| `mag?` | ASCII " " | `sendMagQuery()` ([L453](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L453)) | `rag:` | NIRS gain calibration |
| `mcj?` | ASCII " " | `sendMcjQuery()` ([L457](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L457)) | `rcj:` | NIRS MCJ raw |
| `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 모드 변경 |
| `msr?` | ASCII " " | `disconnectAndUnbond()` 내부 ([L276](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L276)) | — | 본딩 wipe + reboot |
| `mpa?` | BE [freq, cycles] | `sendPiezoPowerOn(freq=2, cycles=5)` ([L404](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L404)) | `rpa:` | Piezo power ON |
| `mps?` | ASCII " " | `sendPiezoStop()` ([L408](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L408)) | — | Piezo stop |
| `mec?` | BE [freq, delay, 140, cycles, 1, ch] | `sendBurst(...)` ([L412](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L412)) | `reb:` 단일 채널 | Single-channel burst |
| `maa?` | ASCII " " | `sendAllChannels()` / `sendChannelsOnly()` ([L416, L513](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L416)) | reb×6 + raa | 6채널 sweep (도넛/placement) |
| `mtb?` | ASCII " " | `sendMtb()` ([L430](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L430)) | reb×6 + raa + **rim** | 6채널 + IMU (★ Clinical) |
| `mbb?` | ASCII " " | `sendFullMeasurement()` ([L480](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L480)) | rbb + reb×6 + raa | 측정 헤더(batt+temp+IMU) + 6채널 |
| `mta?` | BE [0 or 1] | `sendNirsPowerOn()/Off()` ([L441/445](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L441)) | `rta:` | NIRS power |
| `mqq?` | ASCII " " | `sendMqqQuery()` ([L449](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L449)) | `rqq:` | NIRS sensor activate |
| `mag?` | ASCII " " | `sendMagQuery()` ([L453](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L453)) | `rag:` | NIRS gain calibration |
| `mcj?` | ASCII " " | `sendMcjQuery()` ([L457](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L457)) | `rcj:` | NIRS MCJ raw |
| `msn?` | BE [0] | `sendBatteryQuery()` ([L463](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L463)) | `rsn:` | 배터리 mV 조회 |
| `mid?` | ASCII " " | `sendDeviceInfoQuery()` ([L467](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L467)) | `rid:` | FW/HW/Serial 조회 |
| `mls?` | BE [state] | `sendLedMode(state)` ([L471](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L471)) | `rls:` | LED 모드 변경 |
| ~~`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)
파일: [`BleManager.kt:494-509`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L494)
파일: [`BleManager.kt:494-509`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L494)
`maa?`, `mtb?`, `sendChannelsOnly()`는 한 채널 그룹 응답이 280~420ms 소요되므로 다음 가드를 통과해야만 송신:
@@ -241,28 +241,28 @@ fun sendMtb(): Boolean {
`onCharacteristicChanged` → `processReceivedData(data)` → prefix 4글자 추출 → when 분기.
파일: [`BleManager.kt:986-1097`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L986)
파일: [`BleManager.kt:986-1097`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L986)
| Prefix | 의미 | 패킷 길이 | 파싱 위치 | 콜백 |
|---|---|---|---|---|
| `rta:` `rta!` | NIRS power ON ack | — | [L991](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L991) | `onNirsPowerOnReceived` |
| `rsh:` `rsh!` `rqq:` `rqq!` | Sensor activated | — | [L995](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L995) | `onNirsSensorActivated` |
| `rag:` `rag!` | NIRS gain calibration | data.size - 6 = N×2 | [L999](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L999) | `onNirsMagReceived` |
| `rcj:` `rcj!` | NIRS MCJ raw | — | [L1003](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1003) | `onNirsMcjReceived` |
| `rpa:` | Piezo ack | — | [L1007](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1007) | `onPiezoDataReceived` |
| `rer:` | Preliminary header | — | [L1011](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1011) | (로그만) |
| `reb:` | Piezo ADC raw (1채널) | **210B** (신구조) | [L1014](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1014) | `piezoCollector.addPacket` |
| `red:` | Piezo continuation | — | [L1022](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1022) | `piezoCollector.addPacket` |
| `ree:` | Single-ch end | 8B | [L1026](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1026) | `piezoCollector.addPacket` |
| `raa:` | **All-ch complete** marker | 8B | [L1026](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1026) | `piezoCollector.addPacket` → set finalize |
| `rsn:` | Battery (mV) | ≥6B | [L1031](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1031) | `batteryLevel.value` 갱신 |
| `rid:` | Device info (FW/HW/SN) | 가변 | [L1045](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1045) | `firmwareVersion/...value` 갱신 |
| `rls:` | LED state | ≥5B | [L1067](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1067) | (로그만) |
| `rxs:` | Cmd not supported (echo) | 가변 | [L1071](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1071) | (로그만) |
| `rbb:` | Full measurement header | 22B | [L1075](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1075) | `onMbbHeaderReceived` + collector |
| `rsp:` | IMU 단일 샘플 | ≥16B | [L1080](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1080) | `onImuReceived` |
| `rim:` | **IMU 15 samples** | 188B | [L1086](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1086) | `imuCollector.parseRim` |
| `???` | Unknown | — | [L1091](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1091) | hex 로그 + warn |
| `rta:` `rta!` | NIRS power ON ack | — | [L991](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L991) | `onNirsPowerOnReceived` |
| `rsh:` `rsh!` `rqq:` `rqq!` | Sensor activated | — | [L995](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L995) | `onNirsSensorActivated` |
| `rag:` `rag!` | NIRS gain calibration | data.size - 6 = N×2 | [L999](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L999) | `onNirsMagReceived` |
| `rcj:` `rcj!` | NIRS MCJ raw | — | [L1003](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1003) | `onNirsMcjReceived` |
| `rpa:` | Piezo ack | — | [L1007](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1007) | `onPiezoDataReceived` |
| `rer:` | Preliminary header | — | [L1011](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1011) | (로그만) |
| `reb:` | Piezo ADC raw (1채널) | **210B** (신구조) | [L1014](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1014) | `piezoCollector.addPacket` |
| `red:` | Piezo continuation | — | [L1022](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1022) | `piezoCollector.addPacket` |
| `ree:` | Single-ch end | 8B | [L1026](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1026) | `piezoCollector.addPacket` |
| `raa:` | **All-ch complete** marker | 8B | [L1026](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1026) | `piezoCollector.addPacket` → set finalize |
| `rsn:` | Battery (mV) | ≥6B | [L1031](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1031) | `batteryLevel.value` 갱신 |
| `rid:` | Device info (FW/HW/SN) | 가변 | [L1045](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1045) | `firmwareVersion/...value` 갱신 |
| `rls:` | LED state | ≥5B | [L1067](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1067) | (로그만) |
| `rxs:` | Cmd not supported (echo) | 가변 | [L1071](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1071) | (로그만) |
| `rbb:` | Full measurement header | 22B | [L1075](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1075) | `onMbbHeaderReceived` + collector |
| `rsp:` | IMU 단일 샘플 | ≥16B | [L1080](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1080) | `onImuReceived` |
| `rim:` | **IMU 15 samples** | 188B | [L1086](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1086) | `imuCollector.parseRim` |
| `???` | Unknown | — | [L1091](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1091) | hex 로그 + warn |
⚠️ prefix 매칭은 **`String(data, 0, 4, US_ASCII)`** 로 정확히 4글자 (`"reb:"`처럼 colon 포함).
@@ -270,7 +270,7 @@ fun sendMtb(): Boolean {
## 6. Piezo 응답 상세 (reb / red / ree / raa)
파일: [`PiezoPacketCollector.kt`](../app/src/main/java/com/example/medilightv2android/ble/PiezoPacketCollector.kt)
파일: [`PiezoPacketCollector.kt`](../app/src/main/java/com/medithings/vesiscan/ble/PiezoPacketCollector.kt)
### 6.1 reb 신구조 (210B, FW 2026-06-02+)
@@ -332,7 +332,7 @@ state: 펌웨어 상태 코드. 현재 클라이언트는 사용 안 함 — 마
## 7. IMU 응답 상세 (rim)
파일: [`ImuPacketCollector.kt`](../app/src/main/java/com/example/medilightv2android/ble/ImuPacketCollector.kt)
파일: [`ImuPacketCollector.kt`](../app/src/main/java/com/medithings/vesiscan/ble/ImuPacketCollector.kt)
### 7.1 패킷 구조 (188B)
```
@@ -380,7 +380,7 @@ if (payloadBytes < expectedBytes) {
## 8. 디바이스 정보 (rid)
파일: [`BleManager.kt:1045-1066`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1045)
파일: [`BleManager.kt:1045-1066`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1045)
### 8.1 패킷 구조
```
@@ -412,7 +412,7 @@ val sn = parts.firstOrNull { it.startsWith("VBT") && !it.startsWith("VBTHW") &&
## 9. 배터리 (rsn)
파일: [`BleManager.kt:1031-1043`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1031)
파일: [`BleManager.kt:1031-1043`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1031)
### 9.1 패킷 구조
```
@@ -442,7 +442,7 @@ if (pct <= batteryLevel.value || batteryLevel.value == 0) {
- ⚠️ 충전 중 상승은 반영 안 됨 — 의도된 동작
### 9.4 Polling 정책 (★ 2026-06-05 변경)
파일: [`BleManager.kt:517-565`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L517)
파일: [`BleManager.kt:517-565`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L517)
```
1. 연결 직후 즉시 1회 msn? 송신
@@ -456,7 +456,7 @@ if (pct <= batteryLevel.value || batteryLevel.value == 0) {
## 10. 측정 헤더 (rbb)
파일: [`BleManager.kt:1075-1078`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L1075)
파일: [`BleManager.kt:1075-1078`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L1075)
```
[tag 4B "rbb:"][batt 2B][IMU 2B][temp 2B][crc 2B] = 22B
@@ -485,7 +485,7 @@ if (pct <= batteryLevel.value || batteryLevel.value == 0) {
`!` 변형은 펌웨어 측 에러/특수 상태. 처리 자체는 `:` 와 동일하지만 디버그 로그에 prefix 그대로 기록.
NIRS 자세한 파싱은 [`NirsManager`](../app/src/main/java/com/example/medilightv2android/managers/NirsManager.kt) 참고 (Vivamyo 화면 전용).
NIRS 자세한 파싱은 [`NirsManager`](../app/src/main/java/com/medithings/vesiscan/managers/NirsManager.kt) 참고 (Vivamyo 화면 전용).
---
@@ -495,7 +495,7 @@ NIRS 자세한 파싱은 [`NirsManager`](../app/src/main/java/com/example/medili
표준 GATT 연결만으로 끝. 별도 페어링 명령 없음. CCCD enable 완료 시 service ready.
### 12.2 Unpair (`disconnectAndUnbond()`)
파일: [`BleManager.kt:262-310`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt#L262)
파일: [`BleManager.kt:262-310`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt#L262)
```
1. isUserDisconnect = true ← auto-reconnect 차단 flag
@@ -540,7 +540,7 @@ NIRS 자세한 파싱은 [`NirsManager`](../app/src/main/java/com/example/medili
- `ImuCollector` — rim sample count
### 14.2 ble.log (텍스트 디버그 로그)
파일: [`BleDebugLogger.kt`](../app/src/main/java/com/example/medilightv2android/services/BleDebugLogger.kt)
파일: [`BleDebugLogger.kt`](../app/src/main/java/com/medithings/vesiscan/services/BleDebugLogger.kt)
각 세션 폴더에 `ble.log` 자동 작성:
```
@@ -610,10 +610,10 @@ T+600 App delay(600 - 290 = 310ms) 끝 → 다시 sendMtb()
---
**원본 소스**: `app/src/main/java/com/example/medilightv2android/ble/`
- [`BleManager.kt`](../app/src/main/java/com/example/medilightv2android/ble/BleManager.kt)
- [`CRC16.kt`](../app/src/main/java/com/example/medilightv2android/ble/CRC16.kt)
- [`PiezoPacketCollector.kt`](../app/src/main/java/com/example/medilightv2android/ble/PiezoPacketCollector.kt)
- [`ImuPacketCollector.kt`](../app/src/main/java/com/example/medilightv2android/ble/ImuPacketCollector.kt)
**원본 소스**: `app/src/main/java/com/medithings/vesiscan/ble/`
- [`BleManager.kt`](../app/src/main/java/com/medithings/vesiscan/ble/BleManager.kt)
- [`CRC16.kt`](../app/src/main/java/com/medithings/vesiscan/ble/CRC16.kt)
- [`PiezoPacketCollector.kt`](../app/src/main/java/com/medithings/vesiscan/ble/PiezoPacketCollector.kt)
- [`ImuPacketCollector.kt`](../app/src/main/java/com/medithings/vesiscan/ble/ImuPacketCollector.kt)
**관련 문서**: [iOS Porting Clinical Measurement](iOS_PORTING_CLINICAL_MEASUREMENT.md), [labdb.md](../labdb.md)