증상: Clinical → Unpair → 메인 → Start → 도넛차트 진입 시 BLE 끊긴 상태로
도착. 새 페어링 없이 측정 시도 → 응답 없음.
원인: ClinicalHomeView의 Unpair 다이얼로그가 BleManager.disconnectAndUnbond()
만 호출하고 appState.isDeviceConnected는 안 건드림. BleManager.isConnected.value
는 false로 reset되지만 appState 측은 stale true 상태로 남아 있음.
AppState.startFromHome()이 isDeviceConnected==true 분기에서 DeviceScan을
건너뛰고 곧장 PIEZO_PERSONALIZATION으로 이동하기 때문에, 재페어링 단계
자체가 누락된 채 MaxCapacity → AutoScan → Placement(skip) → 도넛차트까지
미연결 상태로 진행.
수정: Unpair 확인 시 appState.isDeviceConnected = false 한 줄 추가.
이제 Start → DeviceScan 화면으로 가서 정상적으로 재페어링.
참고: PiezoMonitoring의 Disconnect 다이얼로그는 appState.goHome() 호출하고
그 안에서 isDeviceConnected=false 처리되므로 별도 fix 불필요.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 검증: ble.log에 rsn 응답이 보이지만 도넛차트는 0% 표시.
원인 1 — msn? 송신 포맷:
기존: CRC16.buildCommandBE("msn", intArrayOf(0))
→ bytes: 6D 73 6E 3F 00 00 03 FE (BE param 2B)
→ 펌웨어가 응답 안 함 (rsn: 자체가 안 옴)
다른 query 명령(mid?, mtb?, maa?, mqq?, mag?)은 모두 ASCII " " 포맷.
msn?만 BE param 포맷이라 펌웨어가 인식 못 한 듯.
수정: CRC16.buildCommandASCII("msn", " ")
→ bytes: 6D 73 6E 3F 20 ?? ?? (다른 query와 동일 패턴)
원인 2 — Clinical → Unpair → 새 연결 후 도넛차트 진입 시 batteryLevel=0:
disconnectAndUnbond()에서 batteryLevel.value=0 reset됨.
새 BLE 연결의 onDescriptorWrite → startBatteryPolling 호출은 PiezoMonitoring
진입 전에 이미 완료. PiezoMonitoring의 LaunchedEffect에서 등록하는
onConnectionStateChanged 콜백은 '상태 변경 시점'에만 트리거되므로
이미 connected인 상태로 진입하면 호출 안 됨.
수정: PiezoMonitoring 진입 시 isConnected.value==true이면 명시적으로
startBatteryPolling() 한 번 더 호출 (idempotent — stopBatteryPolling 후 재시작).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
방광 모형 테스트가 100% 통과하는 시점을 demo flavor로 동결하고, 모든
신규 작업은 dev에서만 진행하기 위한 빌드 분기 셋업.
gradle (app/build.gradle.kts):
- flavorDimensions: "channel"
- demo: applicationIdSuffix=".demo", versionNameSuffix="-demo",
BuildConfig.IS_DEMO=true, FLAVOR_LABEL="demo"
- dev: BuildConfig.IS_DEMO=false, FLAVOR_LABEL="dev"
- 두 flavor 동시 설치 가능 (applicationId 분리)
source set:
- src/demo/res/values/strings.xml — app_name "VesiScan Demo"
- src/dev/res/values/strings.xml — app_name "VesiScan Dev"
- src/demo/java/.gitkeep + src/dev/java/.gitkeep — 격리본 둘 위치 안내
운영 가이드: docs/FLAVOR_DEMO_STABLE.md
- Level 1 (BuildConfig 분기) vs Level 2 (source set 격리) 두 동결 방식
- 새 안정판 동결 시 git tag + src/demo/ 복사 절차
- 어떤 코드를 동결 후보로 둘지 (알고리즘/파라미터 권장, BLE/UI는 공유)
- 트러블슈팅 + 첫 동결 시점 권장 절차
.gitignore: /docs/ 제거 — 팀/iOS 개발자가 접근 가능하도록
- docs/BLE_PROTOCOL_REFERENCE.md
- docs/iOS_PORTING_CLINICAL_MEASUREMENT.md
- docs/FLAVOR_DEMO_STABLE.md 모두 신규 commit
검증:
./gradlew assembleDemoDebug + assembleDevDebug 둘 다 BUILD SUCCESSFUL
→ app/build/outputs/apk/demo/debug/app-demo-debug.apk
→ app/build/outputs/apk/dev/debug/app-dev-debug.apk
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 연결 직후 가끔 배터리 값이 0%로 남아 표시 안 됨 / 의미 없는 값.
원인: startBatteryPolling()이 이름과 달리 연결 시 1회 msn? 송신만 함.
그 한 번이 priority HIGH 협상 직전 큐로 들어가거나, 직후 mtb?/maa? 측정
루프와 큐 경합으로 손실되면 영원히 batteryLevel.value = 0 으로 남음.
이후 측정 응답에 배터리가 안 들어있어 갱신 기회 없음.
수정: 세 단계 보장 구조로 변경
1. 즉시 1회 송신 (기존 동작 유지)
2. 3s 뒤 batteryLevel == 0 이면 최대 2회 재송신 (첫 응답 누락 보강)
3. 30s 주기 정상 polling 추가 (충전 반영 + 장기 추적)
stopBatteryPolling()도 retry/poll 양쪽 타이머 정리하도록 확장.
끊김 시 isConnected.value == false이면 콜백에서 조기 return하여
좀비 송신 방지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사내 피드백: PiezoPersonalization 한 화면에 카드 3개를 모은 게 아니라
MaxCapacity 다음 단계로 Auto Scan Interval 화면을 독립적으로 띄우길 원함.
흐름 변경:
before: DeviceScan → PiezoPersonalization (MaxCapacity + Catheter + AutoScan) → Placement
after: DeviceScan → PiezoPersonalization (MaxCapacity + Catheter) → AutoScanSetup → Placement
변경:
- PiezoPersonalizationView 원복: 2카드 / verticalScroll 제거 / spacing 64dp 중앙 정렬
- AutoScanIntervalSetupView 신규 — Bladdy hero + 큰 카드 + Continue 버튼
- AppScreen.AUTO_SCAN_SETUP enum 추가
- personalizationComplete() → AUTO_SCAN_SETUP으로 이동
- autoScanSetupComplete() 신설 — PlacementGuide로 진행, piezoSettings persist
- backFromPlacementGuide() — setup 흐름이면 AUTO_SCAN_SETUP으로 돌아감
- MainActivity 라우팅 + Back handler 추가 (AUTO_SCAN_SETUP → PiezoPersonalization)
PiezoMonitoring의 in-monitoring settings 슬라이더는 그대로 유지 — 측정 중에도
같은 PiezoSettings.autoScanIntervalMs 값을 조정 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PiezoPersonalizationView:
- AutoScanIntervalCard 신규 — 600~5000ms 슬라이더 + ±200ms 스테퍼
기존 MaxVolume / CatheterThreshold 카드와 동일한 톤
- 3카드가 화면을 넘칠 수 있어 verticalScroll로 전환, spacing 64→24dp
PiezoMonitoringView / VivamyoMonitoringView:
- 사내 테스트 피드백: BLE 끊김 banner가 자주 튀어 거슬림. BleManager 측
scheduleAutoReconnect()가 이미 백그라운드로 재연결을 시도하므로 UI
알림은 불필요.
- onUnexpectedDisconnect/onReconnectionFailed/onConnectionStateChanged
콜백에서 banner state 토글 제거. 측정 일시정지 및 batteryPolling 재시작은
그대로 유지.
- 미사용 state (showDisconnectionBanner, reconnectionState) + 관련 import
정리. BleDisconnectionBanner.kt 파일 자체는 후속 사용 가능성 위해 남김.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: Single scan 309ml(노란색) 후 Auto scan에서 trimmed mean 280s가 잡히면
도넛 표시값은 maxVolumeMl=309로 유지되는데 색깔만 초록색으로 떨어지는 불일치.
원인: 도넛 progress/표시값은 maxVolumeMl(세션 최대) 기준이고 PiezoMonitoringView
1098 코멘트도 그렇게 명시하는데, bladderLevel.currentLevel(=urgency=색깔)만
lastVolumeMl(가장 최근 trimmed mean) 기반으로 업데이트되어 source mismatch.
수정: bladderLevel 업데이트도 maxVolumeMl 기반으로 통일. 한 번 도달한 충만도
색깔은 auto scan 노이즈로 인한 작은 측정값 변동에 영향받지 않고 유지됨.
resetLevel(카테터 처치) / 화면 재진입 시에만 초기화되는 기존 동작은 유지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cycle당 IMU 다수 샘플(FIFO 순)이 오는데, 첫 샘플을 쓰면 piezo 캡처
시점보다 ~240~300ms 전의 오래된 자세값이 labdb에 들어감. 마지막
샘플로 바꿔 piezo 측정 시점과 가장 가까운 자세값이 기록되도록 함.
imu_samples 전체는 그대로 보존.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사내 임상 테스트 진행자가 실시간 측정 중 wall peak 위치를 눈으로 확인하고
싶다는 요청 반영. raw 데이터 저장 우선 정책은 그대로 유지하고, 시각화 측면에만
PiezoMonitoring에서 쓰는 V4.1(walldetect.V41Detector) 알고리즘을 사이클마다
호출하여 결과를 채널 그래프 위에 오버레이.
표시:
- 각 채널 그래프 위 세로선 마커 — ant=green, post=orange (alpha 0.75)
- 채널 헤더에 'a{antIdx} p{postIdx}' 텍스트 (두 줄 한 칸 차지)
- V4.1 detection 실패 시(둘 중 하나 null) 마커/텍스트 모두 미표시
동작:
- 매 mtb 사이클(piezoCollector.onMultiChannelComplete) 시점에 detect 호출
- SweepInput probe profile은 PiezoMonitoring과 동일 (PiezoHW.presetName/degreeAll)
- runCatching으로 감싸 detect 실패가 측정 자체에 영향 없도록
저장 데이터는 변경 없음. measurement.json/labdb 업로드 payload는 그대로 raw ADC.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
펌웨어 업데이트로 reb 응답 패킷이 208B → 210B로 확장됨.
새 payload 앞에 ch_info 2B (= ch_session 1B + ch_num 1B) 필드 추가.
변경 내용:
reb (old): [tag 4B][num_sample 2B][adc 200B][crc 2B] = 208B
reb (new): [tag 4B][ch_session 1B][ch_num 1B][num_sample 2B][adc 200B][crc 2B] = 210B
수정 위치:
- BleManager.kt:970 — 디버그 로그용 sample count 신구조 기반 계산,
session/ch 정보를 ble.log에 함께 노출
- PiezoPacketCollector.processRebPacket — 전면 재작성
* 모든 offset +2 (num_sample → bytes[6..7], ADC → bytes[8..])
* Endian 감지 첫 ADC 샘플 위치 bytes[8..9]로 이동
* 채널 저장 방식: 도착 순서(currentChannel++) → ch_num 직접 indexing
* ch_session 추적 추가 — 같은 session의 6개 reb를 하나의 set으로 묶음
- processEndPacket(raa:) — 완전성 검사 강화
* isSetDropped == true → 콜백 안 부르고 폐기
* channelResults 중 null 존재 (불완전 set) → 폐기
* 6채널 모두 채워진 경우만 onMultiChannelComplete 호출
* 폐기/완료 후 currentSetSession=null로 reset → 다음 set 진입 대비
- red:, ree: — multi-channel 모드에서는 ch_num indexing이라 무시 (single-channel만 유지)
- currentChannel 필드 제거 (도착 순서 의존 폐기)
호환성:
- 신버전 펌웨어 전용. 구버전(208B) 단말 미지원.
- rbb/raa/rim 구조 변동 없음 — 손대지 않음.
- 온도 센서(TMP235 → IMU register) 변경은 FW 내부일 뿐 클라이언트 포맷 그대로.
무효 패킷 정책:
- session 불일치 (set 진행 중 다른 session 도착): 이전 set 폐기, 새 set 시작
- 같은 set 내 duplicate ch_num: isSetDropped=true, raa에서 무콜백 처리
- invalid ch_num (out of 0..5): set 폐기
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PiezoMonitoring의 'Delete' 패턴과 동일: confirm dialog → bleManager.disconnectAndUnbond().
ClinicalHome에 머무르므로 곧바로 BLE 카드가 Not Connected 상태로 전환되어
다음 사람 측정 전에 Connect 버튼 활성화.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
서버 로그에서 두 원인 확인:
[error] POST /devices/register invalid byte sequence for encoding UTF8: 0x00
[error] POST /upload/json unsupported Unicode escape sequence
PostgreSQL JSONB는 NULL escape를 거부 (HTTP 500). BLE rid: 응답에서
US-ASCII 디코딩 후 .trim()은 whitespace만 제거하고 NULL/제어문자는
남기므로, firmware/hw/serial 문자열에 padding NULL이 묻은 채 payload로 직렬화됨.
두 군데 방어:
1. BleManager.rid: 파싱에서 0x00~0x1F,0x7F를 공백으로 치환 후 trim
2. LabdbClient.sanitizeForJsonb 추가 — register/uploadJson 직전에
payload를 재귀적으로 순회하며 모든 string에서 C0 control + DEL 제거
(탭/개행/캐리지리턴은 보존)
원인 조사: 서버 SSH로 /var/log/labdb.log 직접 확인 +
server.js의 /upload/json 핸들러 코드 검토 결과 서버는 받은 sensor/channels
JSON을 그대로 JSONB 컬럼에 INSERT.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
INTERNAL_ERROR(500) 원인 좁히기 위해 LabdbClient에 logcat 추가:
- POST /devices/register 보내는 정확한 payload (TAG=LabdbClient)
- 모든 응답의 HTTP code + raw body (앞 500자)
- IOException 발생 시 스택 trace
logcat 필터: tag:LabdbClient
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
labdb 자료 구분 코드를 "000"(외부 일반 VesiScan) → "001"(내부 임상 R&D)로
변경. ClinicalSessionStore에 const DATA_TYPE을 두고, meta.json과
measurement.json의 meta 블록에 "data_type": "001"을 함께 기록 — 폴더만
보고도 내부 임상 데이터임을 즉시 식별 가능.
LabdbUploader는 measurement.json의 meta.data_type을 우선 사용하고
없으면 "001" fallback. 향후 dataType이 늘어나도 단일 진실 출처(meta)에서
파생되도록 분리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End Session 시 measurement.json을 https://labdb.medithings.net/api/v1/upload/json
에 자동 업로드. dataType="000" (VesiScan 6채널). cycle 1개 = record 1개.
testId는 폴더명 그대로 사용 (40자 초과 시 prefix+hash로 축약).
Files:
- LabdbCredentials: apiKey/deviceId를 EncryptedSharedPreferences에 안전 저장
- LabdbClient: register/checkStatus/uploadJson + 5가지 에러 분기
(PendingApproval / Reregister / RateLimit / ApiError / Network)
- LabdbUploader: measurement.json → payload 변환 + fire-and-forget 업로드
성공 시 labdb_upload.json, 실패 시 labdb_upload_error.json 마커 작성
ClinicalSessionStore.endMeasurement: cycle>0 + active 상태일 때만 자동 트리거.
ClinicalHomeView: labdb 상태 카드(Register/Refresh) + 직전 측정 업로드 상태
표시 + 수동 Upload 재시도 버튼.
오프라인 큐는 아직 없음 (Phase C는 후속).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ClinicalSessionStore에 MeasurementCycle 누적 버퍼 추가. End Session 시
6채널 piezo + 15 IMU 샘플을 단일 measurement.json으로 묶어 저장하여
분석 PC에서 한 줄(json.load) 로드 가능. 기존 CSV 로거는 유지.
ClinicalLiveView는 pendingPiezo 패턴으로 piezo(reb×6+raa)와
imu(rim)를 한 cycle로 묶어 addCycle() 호출.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vesiscan_test Section 2 프로토콜 패턴을 안드로이드 앱 안에서 직접 구현.
실제 몸에 센서 붙인 채 6채널 실시간 그래프 보면서 측정 + 저장 가능.
신규 화면 ClinicalLiveView:
- 600ms 간격 mtb? 자동 송신 (Piezo 6ch + IMU 동시)
- 2×3 grid 라이브 차트 (raw ADC waveform, CH0~CH5 색상별)
· y축 0~4095 (12-bit ADC) 고정 + 가로 grid 4분할
· 각 채널 peak 값 표시
- IMU 카드 — ImuAnalysis 기반 자세 + tilt + shake (흔들림 시 빨강 경고)
- 상단: 세션 라벨 + S=Subject TV=TrueVol Ab=Abdomen 한 줄 + #cycle ✓captured
- 컨트롤: Pause/Resume · Capture (수동 저장) · Auto Capture 토글 · End Session
ClinicalSession 모델 확장:
- subjectId / trueVolumeMl / abdomenThicknessMm 필드 추가
- folderName 에 subjectId prefix (예: "S001_VBT26050202_SUPINE_ALIGN_0CM_...")
- meta.json 에 subject_id / true_volume_ml / abdomen_thickness_mm 자동 기록
ClinicalSessionStore:
- lastSubjectId / lastTrueVolumeMl / lastAbdomenThicknessMm 기억 (다음 측정 자동 채움)
ClinicalHomeView 입력 폼 확장:
- Subject ID 텍스트필드
- True Volume (mL) + Abdomen (mm) 2-컬럼 숫자 입력
- Start Live Measurement 버튼 → CLINICAL_LIVE 화면으로 직행
(이전: alignment/BV step에 따라 PlacementGuide/PiezoMonitoring 분기)
AppState/MainActivity:
- AppScreen.CLINICAL_LIVE 추가
- MainActivity Back: LIVE 시 endMeasurement + ClinicalHome 복귀
데이터 흐름:
Start Live → ClinicalLiveView 진입 → mtb? 600ms loop
→ piezoCollector → 6채널 buffer (자동 또는 수동 capture)
→ AdcCsvLogger.log → adc.csv 누적
→ imuCollector → 15 IMU samples
→ ImuCsvLogger.log → imu.csv 누적
End/Back → endMeasurement → meta.json finalize → ClinicalHome 복귀
장점:
- 검사자가 실제 시연 중 6채널 신호 품질 즉시 확인
- Auto Capture로 매 사이클 자동 저장 (Pause로 일시 멈춤)
- subject_id 기반 폴더명으로 피험자별 grouping 자연스럽게
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 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>
R&D 분석 시 펌웨어 버전별 데이터 grouping 가능 (VBTFW0111 vs VBTFW0116 등).
writeMeta()에서 BleManager 인스턴스의 firmwareVersion/hardwareVersion/serialNumber
state를 읽어 meta.json에 기록:
- "firmware_version": "VBTFW0116"
- "hardware_version": "VBTHW0100"
- "serial_number": "VBT26040001"
값이 비어있으면(연결 안 됨/응답 안 옴) 필드 생략. 측정 종료 시점(endMeasurement)에
한 번 더 writeMeta 호출되므로 측정 중 BLE 응답 받으면 자동 갱신됨.
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>
빌드 111(e0f92eb)에서 의도적으로 제거했던 4방향 화살표(↑↓←→)를
사용자 요청으로 복원. directionIcon에 따라 센서/타원 바깥에 그려짐.
Hint 텍스트와 함께 시각적 방향 안내 제공.
색상 #FF5722 · arrowLen 70f · sw 9f (Charles 833c110 원본 사양 그대로)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.1 변경 요약 (1.0 이후):
- Clinical R&D Mode 신규 (Dev 한정)
- UI 3-phase 리디자인 (Critical/Important/Polish 12건)
- 측정 로그 자동 저장 (adc.csv / placements.jsonl / ble.log / meta.json)
- 외부 저장소 영구 보관 (Downloads/VesiScan_Sessions/, _Events/)
- Auto Scan interval 사용자 조절 가능 (600~5000ms)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 #12에서 Dev 모드 한정으로 격리했던 Skip 버튼을 원본대로 복원.
일반 사용에서도 필요한 기능이었음. 임상 안전 우려는 별도 방식으로 해결 필요.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
스크린샷에서 발견된 디테일 4건 fix.
9. Status badge 임상 모드 분기
- 일반 모드: urgency.message ("Enjoy your day!" 등) 그대로
- 임상 모드: "Supine · 0 cm" 같은 측정 라벨 표시
- 임상 시연 시 캐주얼 메시지가 부적절했던 문제 해결
- 검사자가 현재 어느 condition인지 즉시 인지 가능
10. 카테터 카운트에 "left" 라벨 추가
- 이전: 아이콘 + 숫자만 (의미 모호 — 잔여? 추가? 누적?)
- 새로: 아이콘 + 숫자 + "left" 텍스트 (10×fontScale, 0.7 alpha)
- 한눈에 "잔여 카테터" 의미 전달
11. 토르소 실루엣 모래시계 완화
- chestW 0.42 → 0.40 (어깨 살짝 축소)
- waistW 0.32 → 0.36 (허리 덜 잘록 — 다리처럼 보이던 원인)
- hipW 0.46 → 0.42 (골반 살짝 축소)
- 결과: 더 자연스러운 토르소 형태
12. Skip 버튼 Dev 모드 한정
- 이전: 항상 표시 (임상에선 정렬 우회 위험)
- 새로: appState.isDevMode일 때만 표시 + "Skip (Dev)" 라벨 + 크기 ↓
- 폰트 15→13sp, alpha 0.6 — 의도된 dev 도구임을 시각적으로 명시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ClinicalStep enum에 description 필드 추가하고 ClinicalHomeView에서
선택된 Step의 설명을 segmented control 아래에 표시.
설명:
- ALIGN_0CM: 기기 아래 끝이 치골 바로 위
- ALIGN_1CM ~ 4CM: 치골 위로 N cm
- BV: Cradle 부착 후 본측정
UI 표시 위치: Step 라디오 그룹 바로 아래 한 줄, MlPrimary 색상 + 화살표(→).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
문제: PlacementGuideView(Sensor Alignment, 정렬모드 0~4cm)에서는 AdcCsvLogger.log()를
호출하지 않아 adc.csv가 안 생성됨. R&D 정렬모드 알고리즘 개발은 raw ADC가 핵심인데
placements.jsonl + ble.log만 남고 ADC가 누락되던 상황.
원인: AdcCsvLogger.log()는 PiezoMonitoringView (BV 측정)에서만 호출됨. PlacementGuide는
미호출. 일반 사용에서 1초마다 도는 placement scan마다 ADC를 저장하면 파일이 폭주하므로
의도적으로 빠져 있었던 것.
변경:
- PlacementGuideView의 onMultiChannelComplete 콜백 끝에 임상 모드 한정 조건부 추가:
ClinicalSessionStore.currentSession != null 이면 raw ADC를 adc.csv에 저장
- 일반 사용 흐름은 영향 없음 (currentSession == null이므로 기존 동작 그대로)
- 임상 모드에선 한 alignment 측정(예: 30초)당 ~30-50개 scan이 adc.csv에 누적됨
부차적 fix:
- MainActivity.kt:87 typo 수정 (androidx.com+-pose → androidx.compose)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전: 한 세션이 9 conditions × 6 steps를 모두 묶고 마지막에 zip
새로: 한 측정마다 라벨 입력 → 측정 → 자동 finalize → 다음 측정 라벨
사용자 요청: "session 들어가면 일단 BLE 연결을 하고, 연결 이후에 내가 어떤 자세로
어느 위치 측정할건지를 직접 적고 테스트를 완료한 뒤에 로그가 저장되도록"
변경:
- ClinicalSession 모델: 단일 측정 단위로 재정의 (subjectId/device/posture/step
/examiner/notes/startedAt/endedAt). folderName에 라벨 전체 + timestamp 포함:
"S001_A_SUPINE_ALIGN_0CM_2026-05-28_14-30-15"
- ClinicalSessionStore: 단순화. zip 제거. startMeasurement/endMeasurement.
inClinicalFlow boolean 추가 (DeviceScan/Back 라우팅용).
lastSubjectId/Examiner/Notes 기억 (다음 측정 시 자동 채움).
lastFinalizedFolder (UI에 "Saved: ..." 표시용).
- 3 로거: stepLogDir() → currentLogDir() 단순화. 한 폴더에 모든 파일.
- ClinicalHomeView 전면 재작성:
* BLE 연결 상태 카드 (미연결 시 Connect 버튼 → DeviceScan)
* 직전 측정 결과 표시 (lastFinalizedFolder)
* 라벨 입력 폼 (Subject + Device/Posture/Step segmented control + Examiner + Notes)
* 라벨 preview 실시간
* Start Measurement 버튼 (BLE 연결 + Subject ID 있을 때만 활성)
- AppState.deviceConnected: 임상 흐름이면 ClinicalHome 복귀 (Personalization 우회)
- MainActivity Back handler:
* DEVICE_SCAN Back → inClinicalFlow면 ClinicalHome
* PLACEMENT_GUIDE/PIEZO_MONITORING Back → 활성 세션이면 endMeasurement +
ClinicalHome 복귀 (폴더 finalize)
* CLINICAL_HOME Back → inClinicalFlow 해제 + HOME
데이터 구조 (한 측정당):
Downloads/VesiScan_Sessions/{subjectId}_{device}_{posture}_{step}_{timestamp}/
├─ meta.json (라벨 + 앱/펌웨어/디바이스 버전 + 시작/종료 시각)
├─ adc.csv
├─ placements.jsonl (alignment) or events.jsonl (BV)
└─ ble.log
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회사 내 알고리즘 개발용 임상 시험(3 devices × 3 postures × 6 steps = 54 measurements
per subject)을 위한 데이터 라벨링/라우팅/zip 자동화. Dev 모드 켠 경우에만 노출.
신규 파일:
- models/ClinicalSession.kt — ClinicalDevice/Posture/Step 모델 + 완료 추적
- services/ClinicalSessionStore.kt — 전역 세션 store + 폴더 라우팅 + meta.json + zip
- ui/views/clinical/ClinicalHomeView.kt — 세션 메타 입력 다이얼로그 + 9-conditions
그리드 + 6 steps + Mark Complete + End Session(zip) 화면
로거 라우팅 (세션 존재 시 자동 분기):
- AdcCsvLogger: sessionRoot/{device}_{posture}/{step}/adc.csv +
CSV에 clinical_label 컬럼 추가 (S001_A_SUPINE_ALIGN_0CM 형태)
- MeasurementLogService: 내부 filesDir → Downloads/VesiScan_Events/ 영구 이동.
세션 시 step 폴더의 events.jsonl/placements.jsonl로 분기.
BuildConfig 대신 PackageInfo로 versionName/versionCode 정확 주입.
보관 90일 → 365일 (R&D 장기 보관)
- BleDebugLogger: step 변경 감지해서 ble.log 파일 자동 전환
UI/라우팅:
- AppScreen.CLINICAL_HOME + MainActivity Crossfade/BackHandler 분기
- HomeView에 dev 모드 한정 보라색 "Clinical Mode (Dev)" 버튼
- 측정 화면(PlacementGuide/PiezoMonitoring) Back 시 임상 세션 활성 → ClinicalHome 복귀
Placement 구조화:
- PlacementGuideView가 logService.logPlacement() 호출 시 urineLens/cv/phase/score
추가 인자 전달 → placements.jsonl에 구조화 기록
데이터 구조 (한 세션당):
Downloads/VesiScan_Sessions/{S001_2026-05-28_14:30}/
├─ meta.json
├─ A_SUPINE/align_0cm/{adc.csv, placements.jsonl, ble.log}
├─ A_SUPINE/align_1cm/...
├─ ...
├─ A_SUPINE/bv/{adc.csv, events.jsonl, ble.log}
├─ A_SITTING/... (총 9 conditions × 6 steps)
└─ session.zip (End Session 시 자동 생성)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 hardcoded 600ms를 사용자가 Settings에서 조절 가능하도록 변경.
추가:
- PiezoSettings.autoScanIntervalMs (default 600ms) 필드
- AppState.savePiezoSettings() 헬퍼 — storage에 piezoSettings 영속화
- Settings 패널에 슬라이더 (Catheter Threshold 다음, Bladder Capacity와 동일 톤)
- 범위 600~5000ms, 200ms 단위로 snap (roundToInt + 200 배수)
- 현재값 mL 표시 패턴과 동일하게 "X ms" 우측 표시
- Settings Save 버튼이 실제로 영속화하도록 savePiezoSettings() 호출 추가
(이전에는 showSettings=false만 호출 — maxVolume 등도 메모리만 변경되던 버그 동시 fix)
동작:
- PiezoMonitor Auto loop의 remaining = targetIntervalMs - elapsed
- BleManager throttle(600ms)이 하한이라 600 미만 설정해도 막힘 — 의미 있는 최소가 600
- 5000ms로 설정 시 거의 정적 모니터링 (배터리/CPU 절약)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a1042b8(v5) 이후 13개 커밋을 반영해 세 문서 일괄 갱신.
USER_GUIDE.md:
- 헤더: VBTAND0101 → 1.0.0-design (versionCode 24), 권장 펌웨어 VBTFW0116
- PIN DEMO 자동통과 안내 (배포 전 정리 필요)
- Personalization 평행 2-카드 (Maximum Bladder Capacity + Catheter Threshold)
- Sensor Alignment 리디자인 (38sp 큰 타이틀, pubic bone 빨강, 3-stop gradient, Canvas 화살표/아치 제거)
- Measurement Screen top bar에 Home 아이콘 + Voiding/ 텍스트 + 48sp Recorded Dialog
- Auto Scan 600ms로 단축 + 측정 사이클 ~330ms 실측 안내
- 설정 패널 폰/태블릿 자동 폰트 분기 (fontScale 표 추가)
- Dev Mode 추가 기능 (DeviceScan 측정 직진입 버튼, Status 더미 주입)
- Navigation에 placementFromMonitoring 라우팅 분기 설명 + startFromHome fix
VesiScan_Android_Pipeline_Summary.md:
- v6 주요 변경 요약 (헤더에 한눈 보기)
- §5.1 BLE 통신: Connection Parameter 협상 (MTU 247, CONN_PRIORITY HIGH), maa throttle
state-based gate, 측정 사이클 실측 표
- 측정 흐름: Auto/Single Scan 600ms 동기화 + Voiding 시 자동 stop
- 연속 스캔 동작: Placement loop 1000 → 600ms + v6 화면 디자인 변경 박스
- §14 BLE 명령어 포맷: VBTFW0116 신규 사항 (pending slot 1→8, 안드로이드 짝꿍 변경)
- §22 향후 과제: v6 완료 항목 12개 추가
docs/ALGORITHM_COMPARISON.md:
- Placement loop 1s → 600ms, canSendMaa 게이트 단계 명시
- v6 BLE 사이클 실측 (330ms 평균)
- Measurement Modes: 800 → 600ms, Voiding 자동 stop
- 신규 섹션: BLE maa Throttle — State-Based Gate (배경/구현/효과/logcat 키워드)
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>
빌드 114 적용: Home의 Bladdy 히어로를 3배 확대하고 BoxWithConstraints로
가용 영역의 90% 캡을 적용해 작은 화면에서 overflow가 나지 않도록 함.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
빌드 104-105 적용:
- DeviceScanView "No devices found" 화면에 dev 모드 한정 "Enter Measurement Mode" 버튼 추가 (BLE 없이 디자인 작업용)
- AppState.startFromHome() 라우팅 fix — 페어링 후 Personalization → Back → HOME → Start 시 Monitoring 직행 버그를 PIEZO_PERSONALIZATION 부터 시작하도록 변경
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
빌드 101-103 적용: PIN 화면 진입 시 LaunchedEffect로 즉시 appState.pinVerified()
호출(PIN 저장 안 함). 화면 본체는 if(false) {...}로 비표시 — 빈 MlBackground Box만 노출.
원본 UI는 보존하여 데모 후 깨끗한 롤백 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>