회의 피드백: 분석 PC로 파일만 옮기면 어떤 측정인지 식별 불가.
폴더명에는 SUBJECT_TYPE/device/posture/step/datetime이 다 있는데
파일은 generic 이름이라 폴더에서 빼면 어디 측정인지 모름.
수정:
- meta.json → meta_{folderName}.json
- measurement.json → measurement_{folderName}.json
예: measurement_PHANTOM_VBT26050202_SUPINE_ALIGN_0CM_2026-06-09_15-30-00.json
ClinicalSessionStore에 metaJsonFile / measurementJsonFile 헬퍼 추가.
writeMeta / writeMeasurementJson 둘 다 헬퍼 사용.
LabdbUploader.uploadBlocking 파일 탐색 순서:
1. measurement_{folderName}.json (신규)
2. measurement.json (구 버전 호환)
3. measurement_*.json 패턴 매칭 (폴더명 다를 때 fallback)
→ 셋 다 못 찾으면 IllegalStateException
기존 폴더(구 파일명)는 그대로 업로드 가능. 새 폴더부터 새 형식 적용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 결정 2건:
1. 6채널 그래프 layout: 2×3 grid → 1열 6행 세로 일렬
- 각 그래프가 화면 width 가득 (가독성 ↑)
- 부모 Column이 이미 verticalScroll을 가지므로 자체 scroll 미적용 (nested scroll 회피)
- SixChannelGrid 내부 Row 분기 제거 → 단순 Column에 ChannelChart × 6 + 4dp spacing
2. Capture 기능 제거 (필요 없음)
- autoCapture = true 기본 + 매 사이클 자동 저장이라 Capture 버튼은 사실상 무용
- 하단 'Capture' 버튼 제거 (이전엔 Pause/Resume 옆에 위치)
- 'Auto Capture 매 사이클 자동 저장' Switch 토글 제거
- var autoCapture 상태 → val autoCapture = true 고정
- 콜백 자동 저장 로직(imuCollector.onComplete 안)은 그대로 — 토글이 없으니 항상 true 경로
- 상단 doc 코멘트도 'Pause/Resume / Capture / End' → 'Pause/Resume / Discard / End'로 갱신
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 도넛차트(PiezoMonitoring) Settings → Delete pairing 후 HOME 진입 시
'● Connected — Start to resume' chip이 여전히 표시.
원인: 어제 회의 결정으로 goHome()이 BLE 연결 유지(disconnect+isDeviceConnected
reset 제거)로 바뀜. 일반 Home 진입은 이게 맞지만, 명시적 Unpair는 진짜로 끊은
거라 isDeviceConnected를 false로 동기화해야 함.
ClinicalHome Unpair는 이전 fix에서 이미 처리됨. PiezoMonitoring은 누락.
수정: bleManager.disconnectAndUnbond() 직후 appState.isDeviceConnected=false
명시적 처리 후 goHome() 호출.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
스크린샷 피드백: 도넛이 화면을 거의 채우고 컨트롤 버튼이 자연스럽게
최하단으로 떨어지는 느낌이 목표.
이전 변경(420→600)은 한 단계로 부족 — 태블릿 가용 높이(1500dp+)에서
여전히 cap에 묶여 여백이 보임.
수정:
- nonDonutBudget: 280dp → 360dp
실측한 비-도넛 컨텐츠(top bar + status badge + info cards + control
buttons + Dev Tools toggle + spacings)에 더 가까운 값. 도넛 사이즈
계산이 정확해져 spacing 어색함 없음.
- max cap: 600dp → 820dp
큰 태블릿(1500dp+ 가용 높이)에서 도넛이 화면 위쪽을 거의 가득 채움.
- min: 160dp → 180dp (살짝 상향)
폰 영향: availableH-360이 어차피 cap보다 작은 값이라 자동 결정 → 변동 없음.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 스크린샷 피드백: 태블릿에서 도넛 아래 약 35% 빈 여백.
원인: donutFitSize = (availableH - 280).coerceIn(160, 420)
태블릿 가용 높이(1000dp+)가 충분한데 max=420에서 cap에 걸려 묶임.
수정: max cap 420dp → 600dp
- 태블릿: 도넛이 자연스럽게 더 커져 화면 채움
- 폰: availableH-280이 어차피 cap보다 작아 영향 없음
- min 160dp는 유지 (작은 화면 fallback)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 결정 2건:
1. Step/Posture 유지
증상: 2cm 측정 후 돌아오면 Step이 0cm로 자동 reset되어 매번 다시 선택해야 함.
원인: ClinicalHomeView가 매번 ALIGN_0CM/SUPINE으로 초기화.
수정: ClinicalSessionStore.lastPosture / lastStep 추가, startMeasurement 시 저장.
ClinicalHomeView 초기값을 store의 last 값으로.
다른 입력값(examiner, notes, subject_type 등)과 동일한 패턴.
2. BV 단계 제거
회의 결정: Step에서 BV는 불필요.
ClinicalStep enum에서 BV 제거 — 5단계(ALIGN_0CM ~ ALIGN_4CM)만 유지.
다른 직접 참조 없어 안전 (LabeledSegmented가 enum.values()로 자동 반영).
step.isAlignment 필드는 유지 (JSON meta 호환) — 이제 모든 step이 true.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 결정: 잘못 측정한 경우를 없앨 방법이 없었음. 두 시점 모두 제공.
ClinicalSessionStore:
- discardAndDeleteFolder(): 측정 중 즉시 폐기. endMeasurement 호출 안 하고
currentSession + cyclesBuffer 비우고 폴더 전체 삭제.
- deleteFinalizedFolder(): lastFinalizedFolder 로컬 삭제 + reset.
LabdbClient.deleteSession(testId):
DELETE /api/v1/sessions/{testId} — Cascade로 모든 records 함께 제거.
204/200/404 모두 성공 처리 (404 = 이미 없음, 멱등).
401 → 자격 clear + Reregister, 403 → PendingApproval, 429 → RateLimit.
LabdbUploader.deleteSessionForFolder(folder):
labdb_upload.json에 sessionId 있으면 그것을, 없으면 sanitizeTestId(폴더명).
미업로드 폴더에 대해서도 안전하게 호출 가능 (404 처리).
ClinicalLiveView:
- 기존 End Session 단일 버튼 → Discard + End Session 두 버튼 (50/50 Row)
- Discard: 빨간 outline. confirm dialog → 폴더 삭제 + ClinicalHome 복귀
- 사이클 진행 중에도 누를 수 있음 (잘못 측정 즉시 인지 시)
ClinicalHomeView:
- Last saved 카드에 Upload 버튼 옆 (또는 단독) Delete 버튼 추가
- confirm dialog → labdb 서버 DELETE 시도 → 로컬 폴더 삭제 → state reset
- 서버 실패해도 로컬은 진행, Toast로 결과 표시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 결정: 자유 텍스트 ID 입력 대신 측정 대상을 PHANTOM/HUMAN/ETC 세
카테고리로 명확히 구분. JSON/labdb 저장 방식도 함께 변경.
데이터 모델 (ClinicalSession.kt):
- SubjectType enum 추가: PHANTOM / HUMAN / ETC
- val subjectId: String 제거 → val subjectType: SubjectType
- 폴더명: "{subjectId}_{...}" → "{SUBJECT_TYPE}_{...}"
예: PHANTOM_VBT26050202_SUPINE_ALIGN_0CM_2026-06-08_15-30-00
JSON 변경 (meta.json + measurement.json):
- "subject_id": "S001" → "subject_type": "PHANTOM"
labdb payload (LabdbUploader):
- params.subject_id → params.subject_type (서버는 free JSON이라 코드 변경 불필요)
UI (ClinicalHomeView):
- Subject ID OutlinedTextField 제거
- LabeledSegmented "Subject Type" 3개 버튼 (Phantom/Human/ETC)
- 마지막 선택 기억 (ClinicalSessionStore.lastSubjectType)
ClinicalLive 상단 sub line: "S=S001 · TV=300mL · Ab=22mm"
→ "Phantom · TV=300mL · Ab=22mm"
호환성: 기존 측정 폴더는 그대로. 새 폴더부터 새 형식 적용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 피드백:
- 도넛차트 화면 비율 이상함 → 화면에 꽉 차게
- ml 숫자 너무 작아서 안 보임 → 키움
- dev mode와 일반 모드 도넛 크기 다름 → 통일
- 아래로 내렸을 때 로그/그래프 확인 가능하게
도넛 sizing 통일:
- nonDonutBudget: dev=280 / 일반=360 → 일관 280dp (도넛에 공간 더 할당)
- donutFitSize: coerceIn(120, 360) → (160, 420) — 화면 꽉 차게 max 키움
- baseDonutSize: dev 분기 제거 → 모두 donutFitSize 사용
- donutLineWidth/bladdySize: dev 분기 제거, 일반 모드 방식으로 통일
- volFontSize: dev=18 / 일반=24 → 일관 40sp (ml 크게)
Logs & Charts 토글:
- 토글 버튼: dev 모드 한정 → 일반 모드에서도 표시
(라벨: dev이면 "Developer Tools", 일반이면 "Logs & Charts")
- useScrollLayout = appState.isDevMode && showDevPanels → showDevPanels만
- verticalScroll: dev 조건 제거 → useScrollLayout 따름
- dev panels 본체: appState.isDevMode && showDevPanels → showDevPanels만
→ 일반 사용자도 토글로 아래 스크롤하여 BLE 로그/채널 그래프 확인 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 피드백: BLE 연결된 상태에서 HOME으로 가면 연결이 끊겨 다음 측정 때
DeviceScan부터 다시 거쳐야 하는 불편. 연결을 유지하고, Start 시 설정 단계
모두 skip하고 바로 도넛차트로 진입할 수 있도록.
AppState.goHome():
- bleManager.disconnect() 제거 — BLE 연결 유지
- isDeviceConnected reset도 제거 (실제 연결 상태 유지)
- Demo 모드인 경우만 isDeviceConnected=false (가짜 연결 정리)
- NIRS 스트리밍 정지 / 데모 종료 처리는 유지
AppState.startFromHome():
- 연결됨(isDeviceConnected==true) → PIEZO_MONITORING 직진 (도넛차트)
- 미연결 → DEVICE_SCAN (기존 흐름)
HomeView:
- 연결된 상태이면 "● Connected — Start to resume" chip 표시
사용자가 다음 Start가 바로 도넛차트로 갈 것임을 인지 가능
기존 동작 유지:
- ClinicalHome Unpair → appState.isDeviceConnected=false (어제 fix 그대로)
→ HOME 진입 시 chip 안 보임 → Start 시 DeviceScan으로 정상 진입
- PiezoMonitoring Disconnect 다이얼로그 → goHome() 그대로 호출되지만
이젠 BLE 끊기지 않음. 의도적으로 끊고 싶으면 disconnectAndUnbond 사용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
회의 피드백:
- 제목 'Auto Scan Interval' → 'Scan Interval'
- 한글 설명 → '측정 간격 조절'
- ms 단위 표시 → sec 기본 + min 토글
- 최소 600ms 고정 (펌웨어 응답 시간 + 안전 마진)
구현:
- SEC 모드: 0.6 ~ 5.0 s (0.2s step) — 짧은 간격, 실시간
- MIN 모드: 1 ~ 10 min (30s step) — 장시간 측정, 배터리 절약
- 토글 버튼(SEC/MIN) 활성 시 진입 모드의 최소값으로 reset
- 슬라이더/스테퍼/표시값 모두 모드에 맞춰 동적 변환
- 기본 모드 추론: 저장된 ms < 60000이면 SEC, 아니면 MIN
PiezoMonitoring in-monitoring settings 슬라이더는 sec-only로 그대로 유지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 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>