기존: meta.json에만 기록 (파일 단독으로 보면 펌웨어 확인 불가)
변경: adc.csv 컬럼 / placements.jsonl·events.jsonl 필드 / ble.log 헤더 블록 모두 노출
검사자/분석자가 각 파일 단독으로 열어도 어느 펌웨어 데이터인지 즉시 확인 가능.
변경:
- BleManager.current: companion에 instance accessor 추가 (logger 같은 context-free 컴포넌트에서 BLE state 읽기용)
- BleManager rid: 핸들러에서 debugLogger.deviceInfo(fw, hw, sn) 호출 (Headers 블록 기록)
- AdcCsvLogger: CSV 헤더에 firmware_version 컬럼 추가, 모든 행에 값 채움
scan_id,timestamp,clinical_label,firmware_version,volume_ml,lr_ratio,...
- MeasurementLogService.logMeasurement / logPlacement: JSON에 firmware_version 필드 추가
- BleDebugLogger.deviceInfo(): 새 메서드. rid: 응답 받은 후 ble.log에 다음 블록 기록:
─── Device Info ───
Firmware: VBTFW0116
Hardware: VBTHW0100
Serial: VBT26040001
───────────────────
BLE 미연결 / mid 응답 안 옴 시엔 필드/값 비어있음 (정상). 응답 받자마자 자동 갱신.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
연결 시 자동으로 받던 mid?/rid: device_info를 UI에 노출. 이전에는 BLE 디버그 로그에만
기록되어 사용자가 접근 불가했음.
BleManager:
- firmwareVersion / hardwareVersion / serialNumber 3개 MutableState 추가
- rid: 응답 파싱 — "VBTHW0100 VBT26040001 VBTFW0111" 형식을
공백 split + prefix 매칭(VBTHW/VBTFW/VBT)으로 안전 추출
- disconnect 시 3 필드 모두 clear
PiezoMonitoringView Settings overlay:
- 기존 "Version" 행을 "App" + "Firmware" 두 행으로 분리
- FW 없으면 "—" 표시 (연결 대기 중)
- dev 모드 한정 "Serial" 행 추가 (회사 자산 추적용)
ClinicalHomeView BLE 연결 카드:
- deviceName 아래 "FW: VBTFW0116" 한 줄 추가
- 검사자가 별도 클릭 없이 펌웨어 버전 즉시 확인 가능
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MTU 247 + CONN_PRIORITY HIGH 적용 후 한 사이클 응답이 ~280~420ms로 단축됨
(이전 ~1.2~1.4s 대비 약 4배). 응답 시간 대비 과도하게 보수적이었던
throttle/loop delay를 일괄 단축.
변경:
- BleManager.canSendMaa() 시간 게이트 1500ms → 600ms (응답 평균 + 안전 마진 ~200ms)
- BleManager.canSendMaa() FORCE 게이트 5000ms → 3000ms (응답 빨라진 만큼 deadlock 감지도 빠르게)
- PiezoMonitor Auto loop delay 1500ms → 600ms
- PlacementGuide continuous scan loop delay 1000ms → 600ms
state-based gate(!isComplete)가 안전망 역할을 하므로 응답이 늦어지면
자동으로 차단되어 데이터 유실 위험은 그대로. 정상 경우엔 사이클당 ~0.6~0.7s로
실시간 모니터링 반응성 2배 향상.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VBTFW0116 펌웨어가 pending slot 1→8로 확장해 TX queue 포화 시
패킷 보관/재전송으로 ADC 유실을 줄였음. 안드로이드 측에서는 Central의
Link Layer ACK 속도를 끌어올려 펌웨어 TX queue 포화 빈도 자체를 낮추는
방향이 가장 효과적.
CCCD write 완료(서비스 ready 시점)에 gatt.requestConnectionPriority(HIGH)
호출. peripheral이 수락하면 connection interval이 ~15ms로 협상되어
한 사이클 응답 시간 단축 가능. 거부되어도 패널티 없음.
실제 적용 여부는 sniffer로만 확인 가능. logcat의 "CONN_PRIORITY HIGH
requested (ok=...)" 로 호출 성공 여부만 확인 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 응답이 완료되기 전에 새 maa가 송신되면 piezoCollector.startMultiChannel(6)이
호출되며 collector가 reset → 큐에 떠다니던 reb 패킷이 폐기되어 BV_FAIL/BV_SKIP가
발생하던 문제 대응.
변경:
- 시간 단독 게이트(800ms) → state + 시간 복합 게이트
- (A) piezoCollector.isMultiChannel && !isComplete 이면 차단
단 5초 넘으면 강제 통과(영영 deadlock 방지)
- (B) 최소 간격 800ms → 1500ms (펌웨어 응답 시간 ~1.2~1.4s 반영)
- sendAllChannels/sendChannelsOnly가 공유하는 canSendMaa() 헬퍼로 일원화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[Placement]
- 센서 이미지 비율 기반 (너비 35%, BiasAlignment 0.44) — 태블릿 대응
- LED 깜빡임 수정: greenHeld/failCount<3 동안 LED 5 안 보냄
- 디바운스: phaseProtected 중 lastHintChangeMs 안 건드림 (화살표 안 나오던 버그)
- Step 2 전환 힌트 항상 "Checking left/right balance..."
- GREEN에서 ch4/ch5 개별 탈락 시 방향 안내 (lrDev NaN 처리)
[도넛차트]
- Fill 카드: 윗줄 65%, 아랫줄 325/500 mL
- ml → mL 통일
- Spot 완료 시 displayMaxVolumeMl 즉시 갱신
- Auto 윈도우 < 5일 때 Current Measurement "—"
[기타]
- V41 gated 체크 + 최소 len≥3 (ungated 피크 안 보임)
- CCCD 실패(status 133) 500ms 후 1회 재시도
- 기기 스캔 사전순 정렬
- 힌트 점 순환 (./.../...)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[Placement]
#1 센서 위치 타원 중앙 (offset y=65dp)
#5 skin 색 항상 동일 (greenZone 분기 제거)
#6 화살표 센서 밖으로 (sensorHalfH + 20f 간격)
#8 GREEN 체크마크 제거
센서 크기 192x120dp (80%)
[Donut]
#2 상단 버튼 1.5배 (48/56dp)
#3 배터리 아이콘 Canvas 그리기 (충전량 바)
#4 도넛-카드 간격 2배 (32/40dp)
#7 Bladdy 정중앙 (matchParentSize + Arrangement.Center)
[기능]
- Spot 중 Auto/Void 버튼 봉인 (enabled=false)
- BLE 페어링(bonding) 완료 확인 후 다음 화면 (30초 대기)
- R-peak gate Gradient 모드 비활성화
- GREEN 디버그 로그 (cv/lrPass/rPeakOk)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
handler.post로 isMeasuring 갱신해도 Compose state 레이스 해결 안 됨.
BleManager에서 lastMaaSentMs 타임스탬프로 1초 이내 재전송 무조건 차단.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- isMeasuring=false를 handler.post로 메인 스레드 갱신
(Thread 내 설정 → Compose state 지연 → 다음 measure() 통과 → maa 이중 전송)
- measure() 진입 시 isMeasuring 가드 추가 (중복 호출 방어)
- 채널 수신 로그 추가 (RECEIVED N channels)
- MTU 247 negotiation 추가 (reb: 208B 분할 전송 최소화)
- Spot 6ch 필터 추가 (allChannelsReceived)
- Auto Stop 시 값 유지 (재계산 안 함)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Foreground Service (BleForegroundService.kt):
- BLE 연결 시 자동 시작, 해제 시 자동 중단
- PARTIAL_WAKE_LOCK (최대 4시간)
- Notification: "VesiScan - BLE 기기 연결 중..."
- connectedDevice foregroundServiceType
- Doze 모드에서도 BLE 통신 유지
- keepScreenOn도 유지 (추가 안전장치)
Detachment 개선:
- THR_MEAN/STD 100→30 (완전 탈착만 감지, 거리 약화 무시)
- 6채널 모두 수신됐을 때만 체크 (BLE 거리 문제와 구분)
- Placement: if(false) 제거 → 활성화 + "센서가 부착되지 않았습니다"
- Monitoring: 정상 복귀 시 LED OFF
Placement 가이드 모드:
- Simple (기본): 3+채널 → 바로 좌우 (기존)
- Boundary: 올라갔다 내려오기 패턴 (경계 감지 → 최적점 복귀)
- 설정 톱니바퀴에 Simple/Boundary 스위치
BLE 로그:
- 역순 표시 (최신이 위) — 스크롤 위치 유지
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BLE:
- onCharacteristicChanged에서 handler.post 제거
- processReceivedData를 Binder 스레드에서 직접 실행
- 메인 스레드 큐 대기로 인한 reb 패킷 유실 방지
Auto:
- 최소 간격 1000ms → 1500ms (기기 연속 maa 채널 드롭 방지)
- 10개 × 1.5초 = ~15초마다 trimmed mean 업데이트
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
성능:
- 측정 콜백 분석(TGC/Otsu/BV)을 백그라운드 Thread로 이동
- UI 업데이트만 handler.post로 메인 스레드 복귀
- 메인 스레드 블로킹으로 인한 BLE 패킷 드롭 방지
- 분석 소요시간 로그 출력 (Analysis took Xms)
- Toast도 handler.post로 감싸서 Thread-safe
Placement:
- 6채널 그래프: 데이터 없는 채널도 "No data" 빈 칸 표시 (항상 6칸)
CSV:
- 일별 파일 → 세션별 파일 (YYYY-MM-DD_HHmmss.csv)
- BLE 연결 시 자동 newSession()
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BLE:
- watchdog idle heartbeat: 10초 무통신 시 IMU query로 keepalive
- 배터리: 3500-3700mV→0-5%, 3700-4100mV→5-100%, 증가 무시
- 배터리 표시: 아이콘→퍼센트 숫자 (색상 유지)
Placement:
- 힌트 디바운스: 같은 phase 2회 연속일 때만 변경 (깜빡임 방지)
- 방향 힌트 반전: CH3만 잡힘→아래로↓, CH0만 잡힘→위로↑
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
근본 원인: CH0 첫 샘플이 LE/BE 둘 다 4095 이하일 때
감지 불가 → LE로 잘못 판정 → 전체 채널 값 폭발 (30000~65000)
수정:
- 확실한 경우: 기존 로직 유지 (LE>4095→BE, BE>4095→LE)
- 애매한 경우: forceBigEndian 기본값 사용
- VBT* 기기: forceBigEndian=true (기기 연결 시 자동 설정)
- 2025MEDIP: forceBigEndian=false
- Endian 결정 로그 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
테스터 코드 분석 결과:
exe: mls? + [0x00, 0x05] (16비트 Big Endian) → LED 바뀜
앱: mls? + [0x05, 0x00] (16비트 Little Endian) → LED 안 바뀜
CRC16.buildCommandBE() 추가: 파라미터를 Big Endian으로 직렬화
sendLedMode(): buildCommandBE("mls", [state]) 사용
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
기존: mls? + [0x05, 0x00] (2바이트 LE 파라미터) → LED 안 바뀜
변경: mls?5 (ASCII 숫자) → exe 프로그램과 동일 포맷
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
mbb 응답(rbb:→reb:×6→raa:)에서 rbb: 헤더가 piezoCollector를
혼동시켜 그래프가 안 뜨는 문제 발생.
- Spot 단일 측정: sendChannelsOnly(maa) 사용
- 자동 측정 10초째: measure()→maa 사용 (mbb는 파싱 검증 후 전환)
- IMU 전송 시 isMeasuring 체크 추가 (측정 중 충돌 방지)
- sendChannelsOnly() 함수 추가 (기존 sendAllChannels와 동일)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
기기에서 rxs:mid? (unknown command) 응답 → 포맷 문제 가능성
buildCommand("mid") → buildCommandASCII("mid", " ") 로 변경하여 테스트
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. ADC 로그: 측정 완료 시 MeasurementLogService.logMeasurement() 호출
→ 6채널 raw ADC + 채널별 ant/post/score 저장 (JSONL)
2. 기기 정보: 연결 직후 mid? 전송 → rid: 응답 파싱 → 로그 기록
(HW Version, Serial Number, FW Version 일괄 조회)
3. LED 모드: Placement Guide 연동
- 스캔 시작 → mls?(5) ALIGN_SEARCHING
- PASS → mls?(6) ALIGN_COMPLETE
- 스캔 종료 → mls?(0) OFF
4. Reconnect: 3/5/10초 5회 → 3초 간격 무한 재시도
(연결됐다가 끊어진 경우, 사용자가 Home 돌아갈 때까지)
5. BLE 로그: mid/mls TX 파싱, rid:/rls: RX 파싱 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TX: "maa 6ch measure", "msn battery query", "mpa piezo power ON" 등
RX: "rsn battery=3850mV (100%)", "reb 100samples", "raa all-ch complete",
"rpa piezo ack", "rag gain calibration (24 values)" 등
펌웨어팀에 보낼 때 raw hex 없이도 통신 흐름을 이해할 수 있음
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WATCHDOG_STARTED/TIMEOUT/GATT_ERROR/RECONNECT 등 이벤트 로그는 유지.
5초마다 찍히던 WATCHDOG_CHECK/THREAD 상태 로그 제거.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
근본 원인: Android BLE가 disconnect 시 STATUS_DISCONNECTED가 아닌
GATT 에러(status≠0)로 콜백을 보냄. 기존 에러 경로에서는
reconnectAttempt>0일 때만 재연결 → 첫 끊김 시 재연결 안 됨.
수정:
- wasConnected 체크: 이전에 연결됨 + 사용자 disconnect 아님 → 자동 reconnect
- GATT_ERROR 로그 + DISCONNECTED(gatt_error) 로그 기록
- stopBatteryPolling/stopWatchdog 호출 추가
- watchdog: connected=false 감지 시 무한 루프 대신 break
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
watchdog이 실제로 돌고 있는지, 어디서 멈추는지 진단용.
매 5초: WATCHDOG_CHECK silence=Xms connected=true/false
interrupt 시: WATCHDOG_THREAD interrupted
flag 종료 시: WATCHDOG_THREAD stopped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
기존: handler.post 안에서 로그+disconnect → handler 블로킹 시 둘 다 안 됨
변경:
- debugLogger.error() → watchdog Thread에서 직접 호출 (CopyOnWriteArrayList 스레드 안전)
- gatt.disconnect()/close() → watchdog Thread에서 직접 호출 (GATT는 any thread OK)
- UI 업데이트 + reconnect만 handler.post로 전달
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CCCD 없는 경로(no CCCD, no RX)에서도 startWatchdog() + startBatteryPolling() 호출
- watchdog 시작 시 WATCHDOG_STARTED 로그 기록 (실제 동작 확인용)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
기존: handler.postDelayed로 watchdog 실행 → BLE 스택이 handler를
블로킹하면 watchdog도 멈춤 (좀비 상태 감지 불가)
변경: 별도 daemon Thread에서 5초마다 lastRxTimestamp 체크
→ BLE handler가 블로킹되어도 독립적으로 15초 타임아웃 감지
→ 감지 시 handler.post로 메인 스레드에서 forceDisconnectAndReconnect 실행
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
기존: connectGatt(autoConnect=true) — Android에서 매우 불안정, 수분 걸림
변경: BLE 스캔으로 기기를 다시 찾은 후 connectGatt(autoConnect=false)로 빠르게 연결
재연결 흐름:
1. watchdog 15초 타임아웃 → forceDisconnectAndReconnect()
2. scheduleAutoReconnect() → 3s/5s/10s 백오프
3. attemptAutoReconnect():
- BLE 스캔 시작 (기기 이름 or MAC 주소 매칭)
- 발견 시 즉시 connectGatt(autoConnect=false) → 빠른 연결
- 8초 스캔 타임아웃 → 다음 attempt
- 최대 5회 시도 후 실패 처리
4. 모든 단계 BleDebugLogger에 기록
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Android BLE가 disconnect 콜백을 안 보내는 경우(좀비 연결) 대응:
- 마지막 RX로부터 15초간 응답 없으면 강제 disconnect → auto reconnect
- processReceivedData()에서 lastRxTimestamp 갱신
- 5초마다 watchdog 체크
- WATCHDOG_TIMEOUT 이벤트 BleDebugLogger에 기록
- 수동 disconnect 시 watchdog 해제
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- BleManager: 배터리 폴링 30초→5초 (펌웨어 15초 타임아웃 방지, iOS 동일)
- BleManager: 자동 reconnect with 백오프 (3s→5s→10s, 최대 5회)
- scheduleAutoReconnect() + attemptAutoReconnect()
- GATT 에러 시에도 백오프 재시도
- 수동 disconnect 시 자동 reconnect 취소
- PiezoMonitoringView: 끊김 시 즉시 파란 배너(RECONNECTING)
- 성공 시 배너 자동 숨김 + 배터리 폴링 재시작
- 5회 실패 시 빨간 배너 + 수동 Reconnect 버튼
- 측정 중 끊김 → isMeasuring 리셋 + 자동측정 중지
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL: In multi-channel mode, "ree:" packets between channels
were silently dropped instead of saving the current channel data.
This caused channel data loss when channels are separated by "ree:".
Fixed: Added missing isMultiChannel + ree: branch that calls
saveCurrentChannel() — matching Swift behavior exactly.
Found via line-by-line Swift vs Kotlin verification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A. Reminder: ReminderService (AlarmManager chain), ReminderSettingsView,
bell icon in Piezo top bar, reschedule on catheterization
B. Consent: ConsentView (3 mandatory items), onboarding→consent→registration flow,
persistent consent storage, sex field in User + Registration Step 2
C. Diary: 24hUV, NUV, NPI, max capacity, day/night frequency metrics card
D. Design: DesignTokens.kt, 48dp touch targets, DonutChart semantics, pressScale modifier
E. Quality: BuildConfig.DEBUG log wrapping, DateFormatter caching, background I/O for diary store
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clear mcj callback immediately on stop to prevent stale responses from
triggering new polling chains. Delay battery polling resume by 500ms to
avoid command collision with in-flight mcj response. Add sendNirsPowerOff
for future use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix BLE API deprecation warnings with API 33+ version checks
- Move isServiceReady to onDescriptorWrite for reliable CCCD setup
- Fix battery polling: immediate query on connect, pause during NIRS streaming
- Add mcj retry mechanism for NIRS streaming reliability
- Add debug logging to BLE, NIRS parsing, and processing pipeline
- Add catheter count feature to Piezo monitoring (persistent count, add dialog, auto-decrement on catheterize)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>