Files
VesiscanClinicalAndroid/VesiScan_Android_Pipeline_Summary.md
T
dw.jang c114592ab1 feat: 6ch 알고리즘 포팅 + urineLen 기반 정밀 Placement Guide
6ch 알고리즘 (config_6ch.py / bv_estimation.py 포팅):
- PiezoHW: V0/V1/V2 프리셋 (Snell's law 굴절 각도)
- computeLrRatio(): CH4/CH5 lateral에서 타원 단면 비율 동적 계산
- estimateBladderVolume6ch(): center 4ch + lateral lr_ratio
- denoise(): TVD 제거, SG 필터만 사용
- LOW_ECHO_AMP: 1600→1150, distancePerSample: 1.771

Placement Guide 2단계:
- 1단계: 이진 판정 (잡힘/안잡힘) → 기본 방향
- 2단계: urineLen 기반 정밀 가이드 (대칭+균형+크기)
  - 세로: CH0/CH3 비율 1.5배 이상이면 방향 제시
  - 가로: CH4/CH5 차이 5칸 이상이면 방향 제시
- Placement Score = 세로대칭(30%) + 가로균형(30%) + 중앙크기(40%)
- 각 채널에 urineLen 숫자 표시

VesiScan_Android_Pipeline_Summary.md 문서 작성

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 10:22:57 +09:00

21 KiB
Raw Blame History

VesiScan Android App — 완전 가이드

작성일: 2026-04-23

작성자: dwjang

마지막 업데이트: 2026-04-23


1. 이 앱이 뭔가요?

배에 초음파 센서를 갖다 대면 방광에 소변이 얼마나 차 있는지 알려주는 앱입니다.

병원에 안 가고도 집에서 방광 상태를 확인할 수 있습니다.

iOS 버전과 동일한 기능을 Kotlin + Jetpack Compose로 구현한 Android 버전입니다.


2. iOS 버전과 달라진 점

항목 iOS Android 비고
전처리 TVD + SG 필터 SG 필터만 TVD 제거 (6ch 알고리즘)
LOW_ECHO_AMP 1600 1150 ADC range 변경
DISTANCE_PER_SAMPLE 1.974 1.771 6ch 기기 보정값
기기 프리셋 FLAT/CASE1/CASE2 V0/V1/V2 Snell's law 굴절 각도 적용
lr_ratio 고정 1.0 CH4/CH5에서 동적 계산 타원 단면 추정
BV 추정 5ch (CH0~CH4) 6ch (center 4ch + lateral lr_ratio) estimate_bladder_volume_6ch
배터리 폴링 5초 5초 동일 (keep-alive)
BLE reconnect 수동 자동 (3s→5s→10s, 5회) 백오프 재시도
Spot 자동측정 0.8초 롱프레스 롱프레스 (detectTapGestures) 30초 간격

3. 앱 사용 순서 (처음부터 끝까지)

① 앱 실행
    ↓
② 온보딩 (처음 한 번만)
    ↓
③ 개인정보 동의
    ↓
④ 사용자 등록 (이름, 나이, 성별, 키/몸무게)
    ↓
⑤ PIN 설정 (4자리, 셔플 키패드)
    ↓
⑥ 홈 화면
    ↓
⑦ "Start" 버튼 → 센서 종류 선택 (Piezo / NIRS)
    ↓
⑧ BLE 기기 스캔 → 연결 (2025* / VBT* 필터)
    ↓
⑨ 센서 설정 (자세, 위치, 최대 용량)
    ↓
⑩ ★ 센서 위치 안내 (Placement Guide)
    ↓
⑪ ★ 방광 측정 화면 (Piezo Monitoring)
    ↓
⑫ 결과 확인 + 기록

4. 기기 자동 감지 (프리셋)

BLE 기기 이름으로 센서 각도 preset을 자동 설정합니다.

기기 이름 규칙

VBT26040x0y
         │ │
         │ └─ y: 기기 번호 (1,2,3...)
         └─── x: 각도 타입
               0 = V0 (0,0,0,0,0,0) — BLE 테스트용
               2 = V1 (max 20° housing, Snell's law)
               3 = V2 (max 30° housing, Snell's law)

2025MEDIP = 기존 5ch 기기 (legacy)

Preset별 각도 (Snell's law 굴절 후)

Preset CH0 CH1 CH2 CH3 CH4(L) CH5(R) LR(CH4) LR(CH5)
V0 0° 0° 0° 0° 0° 0° 0° 0°
V1 6.89° 0° -6.89° -13.66° -6.87° -6.87° -3.42° 3.42°
V2 0° -6.89° -13.66° -20.20° -6.87° -6.87° -3.42° 3.42°
legacy 0° -2.2° -4.4° -6.6° -8.8° — 0° 0°

Housing 각도 → 실제 빔 각도 변환 (Snell's law):

  • V1 housing: [10, 0, -10, -20, -10, -10] → refracted: [6.89, 0, -6.89, -13.66, -6.87, -6.87]
  • V2 housing: [0, -10, -20, -30, -10, -10] → refracted: [0, -6.89, -13.66, -20.20, -6.87, -6.87]

센서 좌표 (mm)

Preset CH0 z CH1 z CH2 z CH3 z CH4 z CH5 z CH4 x CH5 x
V0 21.0 14.0 7.0 0.0 10.5 10.5 -10.0 10.0
V1 20.1 12.8 7.0 0.0 9.9 9.9 -10.0 10.0
V2 19.3 13.0 6.7 0.0 9.85 9.85 -10.0 10.0

5. 측정 파이프라인 상세

5.1 BLE 통신

사용하는 명령어

명령 응답 용도
mpa? rpa: 센서 전원 켜기
maa? reb: × 6 + raa: 6채널 전체 측정
msn? rsn: 배터리 확인 (5초마다, keep-alive)

패킷 구조 (208 바이트)

reb:(4바이트) + repeat_count(2바이트) + ADC데이터(200바이트) + CRC(2바이트)
└ "reb:" 문자  └ 항상 100     └ 100개 × 2바이트(16-bit)  └ 체크섬

Big Endian 자동 감지

reb: 패킷 수신 → 첫 번째 채널(CH0)의 첫 ADC 샘플 확인
  LE로 해석한 값 > 4095 AND BE로 해석한 값 ≤ 4095 → Big Endian
  그 외 → Little Endian

첫 채널에서 감지 → 나머지 CH1~CH5에 동일 적용

BLE 자동 재연결

예기치 않은 끊김 감지
    ↓
자동 재연결 시도 (최대 5회)
    ├── 1차: 3초 후
    ├── 2차: 5초 후
    ├── 3차: 10초 후
    ├── 4차: 10초 후
    └── 5차: 10초 후

성공 → 배너 자동 숨김 + 배터리 폴링 재시작
실패 → 빨간 배너 "Could not reconnect" + 수동 Reconnect 버튼

사용자가 수동 disconnect → 자동 재연결 안 함
측정 중 끊김 → isMeasuring 리셋 + 자동측정 중지

5.2 전처리 — 노이즈 제거

iOS와 다른 점

iOS:     TVD (Chambolle) → SG 필터
Android: SG 필터만 사용 (TVD 제거)

Savitzky-Golay Filter (5-tap, poly=2)

고정 계수: [-3, 12, 17, 12, -3] / 35

내부 (i=2~n-3):
  out[i] = (-3×sig[i-2] + 12×sig[i-1] + 17×sig[i] + 12×sig[i+1] - 3×sig[i+2]) / 35

Edge (i=0,1 / n-2,n-1):
  5개 점에 2차 다항식 피팅 → 가장자리 값 보간

효과: 미세한 울퉁불퉁 제거, 벽(높은 곳)은 유지

5.3 UrinAI — 소변 존재 판정 (문지기)

역할

"여기에 소변이 있나요?" → Yes / No

No이면 벽 찾기를 아예 안 함 → 엉뚱한 결과 방지.

상수 (GreenZoneConstants.kt)

상수 값 의미
liquidThrLoose 1400 이보다 낮으면 "액체" (이진화)
liquidThrStrict 1400 순수 소변 확인용
minLiquidRun 5 연속 소변 최소 5칸
minChord 15 앞벽~뒷벽 최소 15칸
contrastMin 0.15 벽과 소변의 차이 15% 이상
ringSkip 3 처음 3칸은 무시 (센서 자체 반사)
useSamples 90 앞 90개 샘플만 사용

알고리즘

1. 이진화: ADC < 1400 → 액체(true) / ≥ 1400 → 조직(false)

2. fw 찾기: 3번째 칸부터 시작해서
   "연속 3개가 전부 액체"인 첫 위치 = 소변 시작점

3. bw 찾기: fw 이후에서 "연속 3개가 전부 조직"인 그룹 중
   합계가 가장 큰 그룹의 최대값 위치 = 뒷벽
   (못 찾으면 2개로 완화 → 소방광 대응)

4. 순수 소변 확인: fw~bw 사이에서
   ADC < 1400이 연속 몇 칸인지 세기

5. 대비 계산: (벽 최대 - 소변 최소) / 벽 최대

6. 판정: 순수소변 ≥ 5칸 AND 깊이 ≥ 15칸 AND 대비 ≥ 0.15
   → 세 개 다 통과해야 "소변 있음!"

5.4 Low-Echo Detection — 벽 찾기 (Method B)

역할

UrinAI가 "소변 있다!" 하면, 정확히 어디서 어디까지인지 찾습니다.

상수 (PiezoEchoAnalyzer.kt)

상수 값 의미
lowEchoAmp 1150 denoised ≤ 이 값 = low-echo (iOS: 1600)
lowMinLen 3 low-echo span 최소 길이
mergeGapMax 3 인접 span 합치기 최대 gap
peakSearchWin 20 벽 peak 탐색 범위
postMaxIdx 80 후벽 최대 인덱스
minPeakMargin 30 peak 최소 높이 = low_mean + 30
minUrineLen 3 최소 소변 구간 길이
maxPeakCandidates 3 방향별 최대 후보 수

알고리즘 (Prominence 기반, 2026-04-20 업데이트)

1. 디노이징된 신호에서 1150 이하인 연속 구간 찾기
   → "여기가 소변이겠구나" (low-echo span)

2. 인접한 구간 합치기 (gap ≤ 3칸이면 합침)
   단, gap 안에 1155 이상인 peak이 있으면 합치지 않음
   (그건 벽일 수 있으니까!)

3. 양쪽에서 벽 peak 후보 찾기 (±20칸 범위)
   후보 중 prominence가 가장 큰 것을 선택

   prominence란?
   = peak 높이 - 옆 valley(골짜기) 깊이
   = "이 peak이 주변보다 얼마나 튀어나왔는가"

   전벽(ant): peak 오른쪽(소변 방향) valley 기준
   후벽(post): peak 왼쪽(소변 방향) valley 기준

4. 후벽이 80칸 넘으면 → 구간 뒤쪽 절반에서 다시 찾기

5. 결과: ant(전벽 위치), post(후벽 위치), urine_len(소변 길이)

파형 차트에서 보이는 것

색상         의미
─────────────────────
연한 파랑     Raw ADC (원본 신호)
진한 파랑     Denoised (노이즈 제거 후)
주황 점선     Threshold (1150)
노란 영역     Low-echo span (1150 이하 구간)
시안 영역     Urine region (ant ~ post)
초록 선+점    Anterior wall (전벽)
빨강 선+점    Posterior wall (후벽)

5.5 LR Ratio 계산 — 타원 단면 추정

iOS와 다른 점

iOS:     lr_ratio = 1.0 (고정, 원형 단면 가정)
Android: CH4/CH5 lateral에서 동적으로 타원 비율 계산

알고리즘

1. Center AP 직경 계산 (CH1, CH2 기준)
   - 각 이웃 채널의 (post-ant) × cos(θ) → 평균 = D_center
   - D_center ≤ 0 → lr_ratio = 1.2 (invalid fallback)

2. Lateral 직경 계산 (CH4, CH5 각각)
   - P = cos(alpha) × cos(beta)     [SI 보정]
   - Q = cos(alpha) × sin(beta)     [LR 성분]
   - D_lateral = (dFar - dNear) × P
   - y_mid = sensor_x + d_mid × Q   [LR 위치]
   - ratio = D_lateral / D_center

3. LR ratio 계산
   - 2개 lateral → 타원 방정식 풀기:
     u = yL × yR × (yR - yL) / denom
     d = -(u × (rL² - 1) + yL²) / (2 × yL)
     b² = u + d²
     lr_raw = 2√u / D_center

   - 1개 lateral → 중심 가정:
     b = |y_mid| / √(1 - r²)
     lr_raw = b / (D_center/2)

4. Shrinkage (Bayesian 사후확률)
   - confidence = ((1 - avg_ratio) / 0.10).clamp(0, 1)
   - lr_final = lr_prior(1.2) + (lr_raw - 1.2) × confidence

Fallback 값

상황 lr_ratio
lateral 데이터 없음 1.0 (원형)
lateral 있지만 계산 실패 1.2 (해부학적 기본값)
정상 계산 동적 값 (shrinkage 적용)

5.6 BV Estimation — 용적 계산

iOS와 다른 점

iOS:     estimateBladderVolume(5채널, lr_ratio=1.0)
Android: estimateBladderVolume6ch(6채널)
         → center 4채널 분리 + lateral lr_ratio 계산
         → estimateBladderVolume(4채널, lr_ratio=동적)

Frustum(절두체) 적분

        ┌──╮        ← Top Cap
       ╱    ╲
      │  CH3  │     ← 층 3 (절두체)
      │       │
      │  CH2  │     ← 층 2 (절두체)
      │       │
      │  CH1  │     ← 층 1 (절두체)
       ╲    ╱
        └──╯        ← Bottom Cap

각 층의 부피 = (높이/3) × (위 면적 + 아래 면적 + √(위×아래))
총 부피 = 바닥 + 층1 + 층2 + 층3 + 뚜껑

10단계 파이프라인

Step 뭘 하나? 수식
1 Edge 채널 필터 CH3이 CH1/CH2 대비 0.9배 미만이면 short
2 칸 번호 → 실제 거리(mm) d = 6.85 + 칸번호 × 1.771
3 방광 직경 구하기 (각도 보정) D = (뒷벽거리 - 앞벽거리) × cos(각도)
4 단면적 구하기 S = (π/4) × D² × lr_ratio
5 각 층의 높이 위치 y = 센서위치 + 중간점 × sin(각도)
6 아래→위 순서로 정렬 -
7 포물선으로 보정 2차 곡선 맞추기 + 이상치 수정
8 층별 부피 합산 절두체 공식
9 바닥 뚜껑 (항상 구) V = S×h/2 + π×h³/6
10 윗 뚜껑 (원뿔 or 구) 원뿔: V = S×h/3 / 구: 위와 동일

물리 상수

상수 값 의미
distancePerSample 1.771 mm 샘플 당 거리 (iOS: 1.974)
delayOffsetMm 6.85 mm ADC 전 고정 전파 지연
areaK π/4 원형 단면적 계수

6. Placement Guide — 센서 위치 안내

센서 배치도 (실제 기기 스펙)

         [CH3] 30°      ← 가장 아래 (치골쪽, y=0mm)
          │
    [CH4] [CH2] 20° [CH5]  ← 중앙 + 좌우 날개
     왼쪽   │        오른쪽
          [CH1] 10°     ← 위쪽
          │
         [CH0]  0°      ← 가장 위 (배꼽쪽, y=21mm)

방향 안내 로직 (2단계)

1단계: 이진 판정 (4채널 미만일 때)

물리적 프로브 배치 기준:

  • CH0 = 배꼽 방향 (위)
  • CH3 = 치골 방향 (아래)
CH0(배꼽) CH3(치골) CH4(왼) CH5(오) 안내
✅ ❌ - - "센서를 위로 올리세요" (UP)
❌ ✅ - - "센서를 아래로 내리세요" (DOWN)
- - ❌ ✅ "센서를 왼쪽으로 옮기세요" (LEFT)
- - ✅ ❌ "센서를 오른쪽으로 옮기세요" (RIGHT)
❌ ❌ ❌ ❌ "방광이 안 잡힙니다. 센서 접촉을 확인하세요"

2단계: urineLen 기반 정밀 가이드 (4채널 이상 잡힌 후)

4채널 이상 잡히더라도, 방광 중앙에 정확히 위치해야 측정이 정확합니다. 각 채널의 소변 구간 길이(urineLen)를 비교하여 정밀 가이드를 제공합니다.

잘 맞은 경우 (대칭):          치우친 경우 (비대칭):
CH0  ████████████  (긴)      CH0  ██████  (짧)
CH1  ████████████████(긴)    CH1  ████████████  (긴)
CH2  ████████████████(긴)    CH2  ██████████  (중간)
CH3  ████████████  (긴)      CH3  ████  (짧)
→ Score: 90%                  → Score: 45%, "Move UP"

세로 판정:

  • CH0의 urineLen이 CH3보다 1.5배 이상 → "Move UP" (방광이 위쪽)
  • CH3이 CH0보다 1.5배 이상 → "Move DOWN" (방광이 아래쪽)

가로 판정:

  • CH4가 CH5보다 5칸 이상 길면 → "Move RIGHT" (왼쪽 치우침)
  • CH5가 CH4보다 5칸 이상 길면 → "Move LEFT" (오른쪽 치우침)

Placement Score 계산

점수 = 세로대칭(30%) + 가로균형(30%) + 중앙크기(40%)

세로 대칭 = 1 - |CH0_len - CH3_len| / max(CH0_len, CH3_len)
가로 균형 = 1 - |CH4_len - CH5_len| / max(CH4_len, CH5_len)
중앙 크기 = avg(CH1_len, CH2_len) / 40  (40칸이면 만점)

Score ≥ 80%: "Perfect position! 🎯"
Score ≥ 70%: Lock (측정 시작 가능)
Score < 50%: 방향 가이드 표시

"위치 확인" 버튼을 누르면 무슨 일이?

1. 앱이 센서에 "maa" 명령 (6채널 전부 측정해!)
2. 각 채널마다:
   - PiezoEchoAnalyzer로 벽 탐지 + urineLen 계산
   - 실패 시 UrinAI.detect()로 fallback
3. 감지된 채널 = 초록 (urineLen 숫자 표시), 안 된 채널 = 빨강
4. 1단계 or 2단계 가이드 표시 + Placement Score
5. Score ≥ 70% 또는 4채널 이상 → "Start Measurement" 버튼 활성화

7. "Spot" 버튼 측정 흐름

Spot 버튼 탭
    │
    ▼
앱 → 센서: "maa" 명령 (6채널 전부 측정해!)
    │
    ▼ (~0.7초 후)
센서 → 앱: 6개 채널의 초음파 에코 데이터
    │
    ▼
[전처리] SG 필터만 (TVD 없음)
    │
    ▼
[벽 찾기] 각 채널에서 방광 앞벽/뒷벽 위치 탐색
    │         - 신호가 1150 이하로 떨어지는 구간 = 소변
    │         - 그 양옆의 prominence 가장 큰 peak = 벽
    │
    ▼
[lr_ratio] CH4/CH5 lateral에서 타원 비율 계산
    │         - 2개 lateral → 타원 방정식 풀기
    │         - 1개 → 중심 가정, 0개 → 1.0 (원형)
    │
    ▼
[용적 계산] Frustum(절두체) 적분
    │         - CH0~CH3 (center 4채널)만 사용
    │         - 단면적 = π/4 × D² × lr_ratio
    │         - 층별 절두체 공식 + Cap 보정
    │
    ▼
화면에 "305 ml" 표시! (도넛차트 + 레벨 업데이트)

롱프레스 자동측정

Spot 버튼을 길게 누르면 자동 측정 모드:

  • 30초마다 자동으로 측정
  • 빨간색 "Auto" 배지 표시
  • 한 번 탭하면 중지
  • 구현: detectTapGestures(onLongPress) + LaunchedEffect 30초 루프

측정 실패 시 자동 재시도

15초 타임아웃 → retry 1/2 (collector reset + 재전송)
    ↓
15초 타임아웃 → retry 2/2
    ↓
실패 → isMeasuring=false, 에러 로그

8. "Catheterize" 버튼 — 배뇨 기록

4가지 입력 방식 (iOS DiaryRecordSheet 스타일):

┌──────────────────────────────┐
│     배뇨 기록                 │
│     추정 용량: 305 ml         │
│                              │
│  ✏️ 수기 입력    (주황)      →  │
│  🎤 음성 입력    (파랑)      →  │
│  📷 카메라 입력  (보라)      →  │
│  ⚡ 빠른 저장    305 ml  →  │  ← Quick Save (NEW!)
│                              │
│         [취소]               │
└──────────────────────────────┘
  • 수기: 숫자 키패드로 ml 직접 입력
  • 음성: 음성인식 → 숫자 추출
  • 카메라: YOLO 소변컵 감지 + OCR 측정
  • 빠른 저장: 현재 추정 용량으로 즉시 저장 ⚡

9. 파일 구조

medilightv2android/
├── ble/
│   ├── BleManager.kt              ← BLE 통신 (자동 reconnect 포함)
│   ├── PiezoPacketCollector.kt     ← 멀티패킷 수집 + Endian 감지
│   └── CRC16.kt                   ← 체크섬 계산
│
├── managers/
│   ├── PiezoEchoAnalyzer.kt       ← Low-echo detection (벽 찾기)
│   ├── PiezoBVEstimator.kt        ← 6ch BV 추정 + lr_ratio 계산
│   ├── UrinAI.kt                  ← 소변 존재 판정 (문지기)
│   ├── GreenZoneConstants.kt      ← 공유 상수 (한 곳에서 관리)
│   └── PiezoEchoAnalyzer.kt       ← NIRS 산소 포화도 계산
│
├── models/
│   ├── BladderLevel.kt            ← 방광 레벨 (0~10)
│   ├── UrgencyLevel.kt            ← 긴급도 (Safe/Warning/Urgent)
│   └── PiezoSettings.kt           ← 센서 설정 (자세/위치/최대용량)
│
├── services/
│   ├── ReminderService.kt         ← 배뇨 알림 (5종 랜덤, 유동 스케줄)
│   ├── MeasurementLogService.kt   ← 측정 로그 자동 저장 (JSONL)
│   └── NotificationService.kt     ← 긴급도 알림
│
├── ui/views/
│   ├── monitoring/
│   │   ├── PiezoMonitoringView.kt     ← ★ 메인 측정 화면
│   │   ├── PlacementGuideView.kt      ← ★ 센서 위치 안내
│   │   ├── VivamyoMonitoringView.kt   ← NIRS 모니터링
│   │   ├── VoidingDiaryView.kt        ← 배뇨일지
│   │   └── UrineCameraScreen.kt       ← 소변컵 카메라 측정
│   ├── reminder/
│   │   └── ReminderSettingsView.kt    ← 알림 설정 (iOS 동기화)
│   └── (onboarding, registration, pin, ...)
│
└── AppState.kt                    ← 전역 상태 관리 + 화면 라우팅

포팅 원본 대조

Kotlin 파일 원본 원본 언어
PiezoEchoAnalyzer low_echo_detection_method_b.py Python (원은지)
PiezoBVEstimator bv_estimation.py (6ch) Python (원은지)
UrinAI UrinAI.kt (xBench) Kotlin (권오준)
GreenZoneConstants GreenZoneConstants.kt (xBench) Kotlin (권오준)
config (PiezoHW) config_6ch.py Python (원은지)

10. 상수 조정 가이드

증상별 조정

증상 원인 조정
"소변 있는데 No Signal" strict 임계값 너무 낮음 liquidThrStrict ↑
"소변 없는데 감지됨" loose 임계값 너무 높음 liquidThrLoose ↓
"작은 방광 못 잡음" 최소 기준 너무 높음 minChord ↓, minLiquidRun ↓
"벽을 못 찾음" low-echo 임계값 안 맞음 lowEchoAmp 조정
"용적이 너무 큼/작음" DPS 캘리브레이션 distancePerSample 조정

11. 측정 로그 자동 저장

저장 위치

Android: app내부저장소/vesiscan_logs/YYYY-MM-DD.jsonl

로그 이벤트

이벤트 내용
measurement 용적, 채널별 ant/post/score, BV 상세, lr_ratio
placement 6채널 감지 상태, 방향 안내
ble_connected 기기 이름, 연결 시각, preset
ble_disconnected 기기 이름, 해제 시각

보관 정책

  • 일별 파일 자동 생성
  • 90일 이후 자동 삭제

12. 향후 과제

항목 상태 비고
BLE 6채널 수신 ✅ 완료
Low-echo Detection (prominence) ✅ 완료 lowEchoAmp=1150
Frustum BV 추정 (6ch) ✅ 완료 center 4ch + lateral lr_ratio
lr_ratio 동적 계산 ✅ 완료 CH4/CH5 타원 추정
UrinAI Gate ✅ 완료
Placement Guide ✅ 완료 방향 가이드 수정 완료
기기 자동 감지 (V0/V1/V2) ✅ 완료 Snell's law 각도
Big Endian 자동 감지 ✅ 완료
BLE 자동 reconnect ✅ 완료 3s→5s→10s, 5회
배터리 폴링 5초 ✅ 완료 keep-alive
iOS 동기화 (UI) ✅ 완료 알림, Quick Save, 일지
금요일 APK 배포 🔜 예정 Gitea 릴리즈
Adaptive TGC 🔜 예정 Python에서 확인 필요
인체 임상 검증 🔜 예정 상수 재조정