이전: 15 (stand-up settling motion 5~10 dps 차단용)
변경: 10 (보행 50+ dps 와 충분히 분리되면서 짧은 보행도 잡음)
GreenZoneConstants.walkingStartWmean + PostureTuningPrefs.D_WALK_START_WMEAN
양쪽 같이 갱신.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
이전: Back/Skip → discardAndDeleteFolder (저장 X)
변경: Back/Skip → endMeasurement (Complete 와 동일) — measurement.json + labdb 업로드
→ 사용자가 측정 도중 화면 떠나도 그때까지 누적된 baseline + 4-phase frame 보존
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
#1 placements.jsonl phase 라벨 통일
- 기존: placementPhase.name (V1 phase) 만 저장 → V2 활성이어도 V2 phase 안 보임
- 변경: V2 활성이면 v2Aligner.phase.name 사용 (INITIAL_ACCUM 등 4-stage)
- measurement.json 의 align_phase 와도 동일 라벨로 통일
#2 Step 0 baseline 데이터 캡쳐
- Clinical alignment session 진입 시 isContinuousMode 자동 ON
(waitingForStart 는 true 유지 — 사용자 Start 누를 때까지)
- baseline cycles 의 phase = "STEP_0_BASELINE" 로 라벨링
- V2 algorithm push 는 !waitingForStart 일 때만 호출 — silent phase 진행 차단
- Start 누를 때 v2Aligner.reset() 추가 (fresh 시작 보장)
#3 Back / Skip 버튼 분기 — clinical alignment session 인 경우 CLINICAL_HOME 으로
- 기존: Back → PIEZO_PERSONALIZATION, Skip → PIEZO_MONITORING (잘못된 화면 이동)
- 변경: clinical alignment session 진입 상태면 discardAndDeleteFolder + CLINICAL_HOME
(Back/Skip = 저장 의도 X — Complete Alignment 버튼만 저장)
- 일반 placement guide 동작은 그대로 유지
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- urgency message Text 자리에 자세 chip (아이콘 + 라벨) 배치
- 색·아이콘·라벨이 currentPosture 에 따라 Crossfade 300ms 로 부드럽게 전환
- top bar 의 작은 inline posture Row 제거 (중복 표시 방지)
- UNKNOWN 상태 가시성 ↑ : tint #616161 + 라벨 'Detecting' (회색 투명+'—' 대비)
- Raw BLE 녹화 버튼은 dev 모드에서만 노출 (legacy 유지)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- DetachmentDetection.isDetached 가 true 가 되는 rising edge 에 Dialog 팝업
- wakeup 알람과 동일한 주황(Material 500) 톤 + Warning 아이콘
- 재부착 시 팝업 자동 dismiss (사용자 확인 부담 ↓)
- 기존 directionHint / LED mode 4 / phase reset 동작은 그대로 유지
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Start Hold: 0.2..5 → 0.5..10 (default 6s 정상 표시 + 8~10s 확장 가능)
- Start Wmean: 1..20 → 1..30 (더 strict 한 튜닝 가능)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: Dev settings 의 walking/posture 임계 슬라이더를 조정해도 다음 앱 실행
시 default 로 리셋. 사용자: "save해도 변경이 안되는 것 같음".
원인: GreenZoneConstants 의 임계들은 @Volatile var (메모리 변수). 슬라이더가
즉시 값을 GreenZoneConstants 에 쓰지만 SharedPreferences 등 영속
저장소엔 안 들어감 → 앱 재시작 시 default 로 다시 초기화.
수정:
1) PostureTuningPrefs.kt 신규
- loadAll(context): SharedPreferences → GreenZoneConstants 로드
- saveAll(context): 현재 GreenZoneConstants → SharedPreferences 저장
- resetAll(context): defaults 로 복원
- 11개 키 (pitch lying / acc min/max / poll ms / walking 7개)
2) PiezoMonitoring LaunchedEffect(Unit) 에서 loadAll 호출
- 앱 진입 시 저장된 사용자 튜닝 값 자동 로드
3) 11개 slider 모두 onValueChange 마지막에 saveAll 호출
- 슬라이더 변경 즉시 영속 저장 (별도 save 버튼 불필요)
4) Reset Thresholds 버튼 추가 (Verification 섹션 위)
- resetAll 호출 → defaults 로 복원 + prefs 저장
- Toast 로 안내 ("close & reopen Settings to refresh sliders" —
slider 의 remember state 는 reopen 해야 갱신됨)
테스트:
1. Dev settings → Start Wmean = 25 변경 → 앱 종료
2. 앱 재실행 → Dev settings 다시 열기 → Start Wmean = 25 유지 확인 ✓
3. 분류기도 25 값으로 동작
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
알고리즘 검증 인프라.
신규 PostureEventLogger.kt:
- PiezoMonitoring 진입 시 새 CSV 파일 자동 생성
경로: Downloads/VesiScan_Posture/yyyy-MM-dd_HHmmss.csv
- 매 cycle 자세/walking 결과 + 신호값 1 row
- 알람 발동 시 별도 row (walking_start, walking_end, wakeup_alarm_lying_Ns)
- 사용자 annotation row (ground truth marker)
- 컬럼: timestamp_ms, type, posture, isWalking, pitch, wmean, wstd,
accMag, gyroMag, alarm_event, annotation
PiezoMonitoring 연결:
- LaunchedEffect(Unit) 에서 startSession 호출
- imuCollector.onComplete: logCycle 매 cycle 호출
- WALKING 전이: logAlarm("walking_start" / "walking_end")
- Wakeup alarm: logAlarm("wakeup_alarm_lying_Ns")
Dev settings — Verification 섹션:
- 5개 annotation 버튼: LIE / STAND / WALK↑ / WALK↓ / SIT
- 누르면 timestamp + 라벨이 CSV 에 ANNOTATION row 로 기록
- Toast 로 "Marked: 걷기시작" 같은 피드백
- 파일 경로 안내 텍스트
사용 흐름:
1. PiezoMonitoring 진입 (파일 자동 생성)
2. 동작 직전 dev settings 의 해당 버튼 누름 (예: WALK↑)
3. 동작 수행 (걷기)
4. CSV 회수: adb pull /sdcard/Download/VesiScan_Posture/
5. Excel / pandas 에서 분석:
- ANNOTATION row 의 timestamp = ground truth
- CYCLE row 의 isWalking 변화 = detection
- 두 시간 차 = latency
- 미발동 = FN, 과발동 = FP → confusion matrix
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 앉아있다 일어서기만 해도 walking false-trigger.
원인: 사용자 실측 데이터 분석 — stand-up + 자세 조정 motion 이 wmean
5-10 dps 로 약 3-5초 지속. 이전 default (Start Wmean=4, Hold=1s) 는
이 motion 을 walking 으로 잡음.
수정:
Start Wmean: 4 → 15 (stand-up 의 typical wmean 보다 위)
Start Hold: 1초 → 6초 (stand-up settling 보다 김)
근거:
- 사용자 측정 stand-up motion: wmean 5-10 (저강도) ~ 100 (spike)
- 진짜 보행 (body-mounted, 예상): wmean 50+ 지속
- Hold 6초 — 정상 보행은 무한 지속이라 자동 만족, stand-up 은 5초 미만
비고:
- 임계는 여전히 dev slider 로 조정 가능 (이전 commit 18a4cee)
- 사용자별 / 디바이스별 튜닝 가능
- 향후 periodicity check (FFT) 추가 시 임계 다시 완화 가능
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 앉아있다 일어서기만 해도 WALKING false-trigger (두 번 연속 발생).
원인 (로그 분석):
앉아있음(pitch=20-30°) → 일어남 burst (gyro 90-220 dps) → 서있음(pitch=82°)
타임라인:
T=0 pitch=37° wmean=49 (burst 시작, pitch < 45 → pitchOk=false)
T=1 pitch=14° wmean=110 (더 큰 burst, pitchOk=false)
T=2 pitch=32° wmean=174 (burst 최고, pitchOk=false)
T=3 pitch=82° wmean=190 ← pitchOk=true 된 순간 window 에 burst 가득
→ start cond 즉시 만족, 카운트 시작
T=4 pitch=50° wmean=82 (burst 빠지는 중)
T=5 pitch=46° wmean=12 (거의 빠짐, 그래도 > Start Wmean(4))
T=6 pitch=57° wmean=24 → "WALKING start (held 3091ms)" ❌
핵심: pitch 가 45° 통과한 순간 window 안엔 이미 burst data. burst 가 다
빠지기 전에 (~1.5초) + 3초 hold 누적 = false-trigger.
수정: WalkingDetector 에 lastPitchOk 추적 + false→true 전이 시
buffer.clear() + startCondSinceMs=null.
if (pitchOk && !lastPitchOk && !walking) {
buffer.clear() // burst data 폐기
startCondSinceMs = null // hold 카운트 리셋
}
결과: pitch 가 45° 넘는 순간부터 fresh sample 만 누적. 사용자가 일어서서
가만히 있으면 wmean=1-2 (정상 정지값) → cond 안 met → trigger 안 됨.
실제 보행 시작하면 새 wmean 이 30-50+ 까지 올라가 → 3초 hold → trigger.
walking 중에는 pitchOk false→true 가 발생해도 (잠깐 앉았다 일어남 등) buffer
안 비움 (walking 상태 유지 — keep cond 가 처리).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
사용자: walking 이 너무 예민하게 뜸. 모든 임계를 dev settings 에서 조절하고 싶음.
추가된 슬라이더 (Pitch Threshold 아래에 그룹별 정렬):
1) Posture
- Pitch Threshold (lying ↔ non-lying) 기존
- Poll Interval 기존
2) Walking Thresholds
- Min Pitch (walking 인정 최소 pitch) 기존
- Start Wmean (시작 평균 gyro) 기존
- Start Wstd (시작 표준편차) 기존
- Keep Wmean (유지 평균) — range 0.5~30 (이전 10) 신규 범위↑
- **Start Hold** (시작 조건 hold 시간, 0.2~5초) ⭐신규
- **Keep Grace** (종료 grace 시간, 0.5~10초) ⭐신규
- **Window** (sliding window 크기, 0.5~5초) ⭐신규
3) Acc Magnitude Gate
- **Min** (UNKNOWN 하한, 0.3~1.0g) ⭐신규
- **Max** (UNKNOWN 상한, 1.0~3.0g) ⭐신규
내부 변경:
- GreenZoneConstants 에 postureAccMagMin/Max 추가
- ImuPostureClassifier 의 const ACC_MAG_MIN/MAX → GreenZoneConstants 참조
(런타임 변경 가능)
총 11개 슬라이더 — 사용자가 실측하며 튜닝 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 로그캣 분석 — 활발히 걷는 중 (accMag > 1.2g) 일수록 화면에 안 보임.
11:24:03 NON_LYING → WALKING 성공
11:24:04 accMag=1.80 → UNKNOWN, walking 도 grace 들어감
11:24:05 grace 2초 초과 → walking 종료
→ 활발 보행 = 가장 위험한 순간에 알람 사라짐 (정반대)
원인 두 가지:
1) Classifier 가 acc gate fail 시 pitch=0 반환
→ WalkingDetector 가 pitch=0 받아서 pitchOk=false → grace 진입
2) Composite 로직: r.posture==UNKNOWN 이면 WALKING 무시하고 UNKNOWN 표시
수정:
1) ImuPostureClassifier: pitch 계산을 acc gate 이전으로 옮김
→ UNKNOWN Result 에도 실제 pitch 값 포함
→ WalkingDetector 가 정확한 pitch 로 grace 판단 가능
2) PiezoMonitoringView composite 순서 변경:
when {
isWalkingNow -> WALKING // 우선 (자체 게이트 신뢰)
r.posture==UNKNOWN -> UNKNOWN
else -> r.posture
}
walking detector 가 walking 이라고 하면 acc gate 실패해도 표시.
walking 자체에 시간/wmean/wstd 게이트 있어 false-positive 거의 없음.
테스트 시나리오:
정지 lying → 흔들면서 걷기 (pitch>45°) → walking 검출 → 격하게 흔들기
(accMag>1.2g) 도 → "Walking" 계속 표시 + 빨간 팝업 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
새 flowchart 의 walking 검출 추가:
1) Posture enum: UNKNOWN/LYING/NON_LYING + **WALKING** (4-state)
2) WalkingDetector.kt 신규 (flowchart Path B 구현)
- Sliding window (default 1.5초) buffer 에 gyro magnitude 누적
- 매 cycle 마다 wmean (이동평균) + wstd (이동표준편차) 계산
- State machine:
IDLE → WALKING:
|pitch| > 45° AND wmean > 4 AND wstd > 2
→ 1초 이상 연속 만족 시 전이
WALKING → IDLE:
|pitch| > 45° AND wmean > 2
→ 미달 후 2초 grace 지나면 전이
3) GreenZoneConstants 에 walking 임계 7개 추가 (모두 dev 조정 가능):
- walkingMinPitchDeg=45, walkingStartWmean=4, walkingStartWstd=2,
walkingStartHoldMs=1000, walkingKeepWmean=2, walkingKeepHoldMs=2000,
walkingWindowMs=1500
4) PiezoMonitoring:
- walkingDetector 인스턴스
- imuCollector.onComplete: classifier + detector 동시 호출,
composite posture (WALKING > NON_LYING > LYING) 결정
- NON_WALKING → WALKING 전이 시 critical 알람 trigger
5) UI:
- 상단 status bar: Walking 아이콘 (DirectionsWalk, 빨강) 추가
- 기존 wakeup 팝업 색: 빨강 → 주황 (warning level)
- 신규 walking 팝업: 빨강 + DirectionsWalk + "환자가 걷고 있습니다 — fall risk"
6) Dev settings: walking 임계 슬라이더 4개 추가
(Min Pitch / Start Wmean / Start Wstd / Keep Wmean)
sliding window / hold 시간은 default 유지 (필요 시 추가)
2단계 알람 체계 (의료적):
- 주황 (warning): LYING N초 → NON_LYING/WALKING 전이 (일어남 감지)
- 빨강 (critical): NON_WALKING → WALKING 전이 (보행 시작 — fall risk 최고)
Tag phase2-pre-walking (28a8769) 이전 상태로 복원 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
낙상 예방 시나리오상 자세 변화 즉시 감지 중요 → 1초 polling 으로 단축.
canSendMaa() 500ms throttle + mtb 응답 ~400ms 이라 1초가 사실상 한계.
사용자가 slider 로 더 늦출 수도 있음 (1~15초 범위).
BLE 부담: device active ~40% (mtb 한 cycle ≈ 400ms × 매초).
배터리 영향은 짧은 모니터링 세션 (수십분~수시간) 수준에선 무시 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 기상 알람 테스트하려면 LYING 으로 3분 (default 180s) 유지해야 팝업
뜸 — 매번 너무 길어 테스트 비효율.
수정: PiezoMonitoring dev settings 의 기상 알람 토글 바로 아래에 Lying
Duration 슬라이더 추가.
- 범위: 5초 ~ 600초 (10분)
- 표시: "5초" / "3분 0초" 형식 자동
- 변경 시:
1) WakeupAlarmPrefs.setLyingDurationSec(context, sec) — 저장
2) wakeupDetector.lyingDurationSec = sec — 즉시 적용
- 테스트: 슬라이더로 5초 정도로 줄임 → 평평히 5초 → 세움 → 즉시 팝업
상수가 GreenZoneConstants 대신 SharedPreferences 라서 앱 재시작 후에도
설정 유지. wakeupDetector 인스턴스에 동시 반영해 즉시 효과 확인 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상 1: 디바이스 평평하게 둠 → "Upright" / 세움 → "Lying" (또는 기존 빌드의
"Sitting") 으로 사용자 직관과 반대.
진단: 사용자 raw 데이터 분석
- 평평 (AZ dominant, AZ ≈ 1g): pitch ≈ 0° → 코드가 NON_LYING (Upright) 반환
- 세움 (AX dominant, AX ≈ 1g): pitch ≈ 80~90° → 코드가 LYING 반환
사용자 직관: 평평=Lying, 세움=Upright → 코드 inversion.
수정 1: ImuPostureClassifier 의 분류 조건 invert
- 기존: |pitch| > threshold → LYING
- 신규: |pitch| ≤ threshold → LYING (작은 pitch = AZ 가 중력 = 센서 평평)
- dev slider hint 도 "|pitch|≤N°→Lying" 으로 동기화
수정 2: PiezoMonitoring 의 dev settings overlay 에도 기상 알람 토글 추가
- 기존: PiezoPersonalizationView 에만 존재
- 신규: dev settings panel (Pitch Threshold slider 아래) 에 동일 토글
- 두 위치 모두 같은 WakeupAlarmPrefs 사용 — sync 됨
- 사용자가 monitoring 페이지에서 바로 켜고 끌 수 있음
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 디바이스를 lying 상태로 둬도 "Upright" 표시가 안 바뀜.
진단 (raw 로그 분석):
- 디바이스 정지 상태에서도 gyro y 축 ≈ -25 dps 일관 표시
- 이는 IMU 칩의 **calibration bias** (정지 시 zero offset 안 됨)
- flowchart 의 gyro gate (9.16 dps) 가 항상 fail → classifier 영구 UNKNOWN
- currentPosture 가 UNKNOWN 시 업데이트 안 되는 로직 때문에 첫 valid
값 (NON_LYING, 사용자가 손에 들고 진입한 순간) 으로 stuck
- 사용자가 눕혀도 분류 자체가 안 되니 "Upright" 그대로
수정:
1) ImuPostureClassifier: gyro gate 제거
- accel magnitude gate (0.8-1.2g) 가 큰 모션은 이미 거름
- gyro magnitude 는 Result/log 에 계속 노출 (디버그용)
- flowchart 그대로 따르려면 gyro 도 활용해야 하지만, bias 큰
디바이스에선 무용지물이라 실용성 우선
2) PiezoMonitoringView: UNKNOWN streak 카운터
- 3회 연속 UNKNOWN 이면 currentPosture = UNKNOWN 으로 강제 표시
- silent stuck 상태 (잘못된 자세 계속 보여줌) 방지
- 잠시 1-2 cycle 동안 motion artifact 로 UNKNOWN 떠도 직전
자세 유지 (이전 동작 보존)
3) Log: unknownStreak 도 출력 → 디버그 시 stuck 케이스 가시화
테스트:
- lying → upright 전환 시 즉시 "Upright" 표시되어야 함
- 정지 시 "Lying" 또는 "Upright" 안정적 유지
- 큰 모션 (들어올림 등) 중에는 3 cycle 후 "—" 표시
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
용도: IMU FSR / 채널 누락 / packet 구조 진단을 위해 BLE notification 의 raw
bytes 를 가공 없이 그대로 파일로 저장.
신규 RawBleLogger.kt:
- startCapture() / stopCapture() / isCapturing
- 파일: Downloads/VesiScan_RawBLE/yyyy-MM-dd_HHmmss_raw.log
- 포맷: "HH:mm:ss.SSS dir=RX prefix=rim len=188 hex=72 69 6D 3A ..."
- 매 notification 마다 한 줄
- 파일 시작/끝에 헤더/푸터 (start, stop time, packet count)
BleManager.processReceivedData:
- 진입 직후 RawBleLogger.write(data) 호출
- capturing 아니면 즉시 return (no overhead)
PiezoMonitoringView 상단 status bar:
- posture 아이콘 옆에 토글 IconButton
- 활성: 빨간 RadioButtonChecked, 비활성: 회색 FiberManualRecord
- 토글 시 Toast 로 파일 경로 / packet count 안내
사용:
1) PiezoMonitoring 진입 → 빨간 ● 버튼 탭 → "Raw BLE capture started"
2) BLE 측정 진행 (alignment / live / single scan)
3) ● 다시 탭 → "N packets saved to YYYY-MM-DD_HHmmss_raw.log"
4) adb pull /sdcard/Download/VesiScan_RawBLE/ 로 파일 회수
5) 텍스트 에디터로 hex 분석
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상:
- JSON 의 imu_samples 에 gz=0 같은 의심값
- sqrt(ax²+ay²+az²) 가 1g 아닌 ~0.5g 일관 표시
- 펌웨어 FSR vs 앱 변환 (/8192) 불일치 가능성
수정: ImuPacketCollector.parseRim 에 raw dump 로깅 추가.
- 전체 byte hex dump (188B)
- 첫 sample + 마지막 sample 의 raw int16 값 (division 전)
- 참고용으로 /8192 시 g 환산값 같이 표시
logcat:
adb logcat | grep ImuRaw
기대 출력:
ImuRaw: rim FULL HEX (188B): 72 69 6D 3A 00 0F ...
ImuRaw: rim sample[0] raw int16: ax=-170 ay=4034 az=735 gx=-8 gy=16 gz=0
(g if /8192: ax=-0.0208 ay=0.4925 az=0.0898)
진단 판정:
- raw az ≈ 8192 → ±4g FSR 정상
- raw az ≈ 4096 → ±8g FSR (앱 변환식과 mismatch, FSR=±8g)
- raw az ≈ 2048 → ±16g FSR
- raw gz=0 일관적 → 펌웨어가 GZ 안 보내거나 sensitivity 다름
- bytes 순서 다르면 endian/offset 문제
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: msp 명령이 펌웨어에서 rsp: 응답 안 보내는 듯 — adb logcat 에 rsp/POSTURE
로그 한 줄도 안 보임. msp 자체가 작동 안 함.
수정: msp 대신 mtb (alignment 모드, rim: 응답 보장) 로 polling.
PiezoPacketCollector:
var skipNextComplete: Boolean = false 추가
raa: complete 시 skipNextComplete 가 true 면 onComplete invoke 건너뛰고
flag reset. 자동 1회 use.
PiezoMonitoringView LaunchedEffect:
- msp(5초) → mtb(10초) 로 전환
- mtb 전에 piezoCollector.skipNextComplete = true 설정 → user 의
측정 callback 가 posture poll 때문에 spurious fire 되는 거 차단
- sendMtb() throttled 면 (canSendMaa fail) flag rollback
- 측정 중 (isMeasuring/isAutoMeasuring/isSpotInProgress) 일 땐 skip
→ 사용자 측정과 충돌 방지
trade-off: BLE 트래픽 ↑ (10초마다 mtb = piezo 6ch + IMU). msp 보다 무거움.
하지만 msp 미작동이라 다른 선택지 없음.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: PiezoMonitoring 진입해도 자세 ?_ 만 표시, logcat 에 rsp/POSTURE
로그 안 보임.
원인: debugLogger.tx/rx 는 내부 Entry 리스트에만 적재하고 android.util.Log
호출 안 함 → adb logcat | grep 에 안 보임.
수정: 핵심 IMU 흐름에 android.util.Log.d("ImuDbg", ...) 추가.
PiezoMonitoringView.LaunchedEffect:
- 시작 시 isConnected/isDemoMode 값 로그
- sendImuQuery 매 호출 "→ sendImuQuery (msp)"
- 연결 안 되어 있으면 "skip (not connected)"
- imuCollector.onComplete 결과 (자세 + pitch/acc/gyro)
- imuCollector.onLog 도 ImuDbg 로 forward (parseRsp 로그 보임)
BleManager:
- rsp: 응답 시 hex dump 도 ImuDbg 로 (debugLogger 와 별개)
- rxs: (cmd_not_supported) 응답 시 ImuDbg 로 warn
rxs: 중복 핸들러 정리 (기존 1개 → 새 1개로 통합)
테스트: "adb logcat | grep ImuDbg" 한 줄로 전체 흐름 확인 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: PiezoMonitoring 진입 시 자세 아이콘이 항상 ?_ (UNKNOWN) 만 뜸.
원인: sendImuQuery() 가 보내는 msp 명령은 rsp: 응답을 받는데, 기존 코드는
rim: (mtb alignment 응답) 만 imuCollector.parseRim() 으로 처리.
rsp: 는 onImuReceived callback 으로만 가는데 어디서도 set 안 됨
(dead callback) → 자세 분류기에 sample 절대 도달 못 함 → UNKNOWN.
수정:
1) ImuPacketCollector.parseRsp() 신규
rsp: format spec 미확정이라 defensive parsing 으로 두 변형 시도:
- 변형 1 (multi-sample): rim: 와 동일 [tag 4B][num 2B][N×12B][crc 2B]
- 변형 2 (single sample): [tag 4B][12B sample][crc 2B] = 18B
실패 시 hex dump 로그 출력 (다음 디버깅용)
성공 시 imuCollector.onComplete 정상 호출 → 분류기 작동.
2) BleManager rsp: 핸들러
- parseRsp() 라우팅
- rx 로그에 hex 추가 (실제 format 확인용)
기존 rim: 처리는 그대로 (alignment 모드 영향 없음).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: ch3 확인 통과 후 LR_BALANCE 단계에서 좌/우 화살표가 빠른
간격으로 번갈아 나타남.
원인:
AlignGuide4Stage.LR_BALANCE 의 hill-climbing 반전 조건이
imbal > lateralImbal 만 검사 → 1 sample 라도 늘면 즉시 반전.
- 노이즈/호흡 으로 imbal 가 ±1-2 fluctuate → 잘못된 반전
- sliding window 평균 (10-frame) 잔여 효과: 방향 바꿔도 직전
데이터 섞여 imbal 변화 못 따라옴 → 가짜 "악화" 판정
- 사용자 반응 지연 동안 imbal 더 누적 → 또 반전 → ←→←→ 진동
수정 (A + C 조합):
1) A. 반전 deadband (lrReverseDeadband = 3 samples ≈ 6 mm)
imbal 가 직전보다 "deadband 초과" 증가해야 악화로 카운트.
LAT_TOL(8) 의 약 1/3 — 정렬 완료 조건 위협하지 않으면서 노이즈
흡수.
2) C. 연속 N회 악화 요구 (lrReverseConsecutive = 2)
deadband 초과 증가가 2 commit 연속이어야 방향 반전.
단발 노이즈/sliding window 잔여 효과 흡수.
상태 추가: lateralWorseStreak Int (reset 포함).
로직:
- 개선 또는 deadband 안 변동 → 같은 방향 유지, streak = 0
- deadband 초과 증가 → streak++
- streak >= 2 → 방향 반전, streak = 0
lateralImbal 은 매 commit 갱신 (기존 동작 유지).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) hint 텍스트에서 채널 정보 제거:
"Slide up slightly ↑ (ch3 short)" → "Slide up slightly ↑"
"Slide down slightly ↓ (ch0 short)" → "Slide down slightly ↓"
사용자 피드백 — 채널 번호 표시 빼고 방향만.
2) edge 만 fail (CV·lrPass·rPeakOk 통과) 인 경우 GREEN 유지:
기존: 방향 화살표면 무조건 nextPhase = VERTICAL → step indicator 3 → 1 회귀.
변경: edgeOnlyFail 시 stayGreen=true → nextPhase = GREEN, step 3 유지.
이유: edge 비대칭은 mm 단위 미세조정 — 전체 vertical 재시작 불필요.
CV/lrPass 가 진짜 어긋난 경우는 기존대로 VERTICAL 회귀.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 270 mL 방광인데 220 mL 로 측정 (~50 mL 저평가).
원인 — 센서가 살짝 아래쪽에 부착돼 ch3 가 방광 하단 가장자리
만 보고 짧게 잡힘. ch0~ch3 CV 는 0.10 임계 안에 떨어져서
"In position!" 통과되지만 BV 결과는 저평가.
원리: CV 는 4채널 전체 변동성 — outlier 1개를 N=4 로 희석.
예: depths (75,78,75,65) → mean 73.25, std 4.79, cv 0.065
→ CV 0.10 통과지만 ch3 가 10mm 짧음.
수정: edge 대칭 게이트 (|d_ch0 - d_ch3| / mean(d0, d3) ≤ 0.08)
추가. CV 와 직교 — 균일 케이스는 영향 없고, edge outlier
케이스만 잡음.
- DEFAULT_EDGE_ASYM_THR = 0.08 도입
- computePlacementGuide GREEN 체크에 edgeAsym/edgePass 계산
- isPass = cv ≤ cvThr && lrPass && rPeakOk && **edgePass**
- hint 분기: edgeAsym > 0 → "Slide up slightly ↑ (ch3 short)"
edgeAsym < 0 → "Slide down slightly ↓ (ch0 short)"
- Log 에 edgeAsym/edgePass 추가
CV(0.10) 는 기존 유지. edge 만 보강.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 정렬 OK 직후 측정 시 ch3 미검출로 BV SKIP. 정렬은 통과했는데
측정에서 "No center channels" 가 자주 발생.
원인: 정렬 GREEN 게이트는 depths.size >= 2 만 요구 (2~3 center 채널
만으로도 CV/lr 통과 시 OK). 반면 측정 BV 게이트는
effectiveCenterWalls >= 4 필요. 정렬 OK 직후 ch3 가 borderline
이면 측정 단계에서 빠질 확률 높음.
수정: PlacementGuideView.computePlacementGuide() 의 GREEN 게이트를
depths.size < 4 로 강화 (측정 게이트와 동일). 4개 모두
잡힐 때만 CV/lr 평가로 진입.
빠진 채널 위치별 hint 분기:
- 2개 이상 missing → 일반 "adjust position"
- ch3 missing → 'Slide up slightly ↑' (사센서 너무 낮음)
- ch0 missing → 'Slide down slightly ↓' (센서 너무 높음)
- ch1/ch2 missing → generic 미세 조정
phase 는 GREEN→VERTICAL 로 회귀 (기존 < 2 분기와 동일).
효과: 정렬 OK 가 4-channel 안정 검출 필요 → 그 다음 measurement
에서 ch3 미검출로 BV SKIP 거의 사라짐.
트레이드오프: 정렬 단계가 다소 까다로워짐 (작은 방광/borderline
ch3 사용자 좌절 가능). 필요 시 사용자별 relax 조정.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: PiezoMonitoring 에서 single scan 후 home → 재진입 시 갑자기
150 mL (또는 50/100/200… 등 50 mL 배수) 가 큰 텍스트에 등장.
원인: 측정 성공 시 line 442-447 가 BV 값을 currentLevel 로
양자화해 SharedPreferences 에 persist (level = (bv/500)*10).
재진입 시 displayMaxVolumeMl 가 remember 라 0 으로 reset 되고,
큰 텍스트의 두 번째 fallback (appState.estimatedVolume = currentLevel/10 * 500)
이 발동 → 직전 측정의 quantized 값이 큰 글씨로 leak.
170 mL 측정 → currentLevel=3 → 재진입 시 150 mL 표시 등.
수정: 큰 볼륨 텍스트의 fallback 분기 (else if estimatedVolume>0)
삭제. displayMaxVolumeMl 가 set 됐을 때만 표시, 아니면 무조건
"— mL". level/urgency 시스템 자체는 그대로 유지 (도넛
차트, InfoCard, 알림 등 다른 곳에서 계속 사용).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: Start Alignment 버튼 누른 직후 Hint 2 (아래쪽 박스) 에
"Place VesiScan above the pubic bone, then press Start" 가
잠깐 표시됨. 이 메세지는 Hint 1 (위쪽 박스) 전용.
원인: Start onClick 핸들러가 waitingForStart=false 만 바꾸고
directionHint 는 그대로 둠. directionHint 의 초기값/리셋값이
"Place...above pubic bone" 이라 V1 placement 엔진이 첫
cycle 에 새 hint 를 쓸 때까지 그 stale 값이 Hint 2 로 leak.
(waitingForStart=false 가 되면 Hint 2 가시 조건 충족)
수정: Start onClick 에 directionHint="" + directionIcon=""
추가. displayHint 가 " " (whitespace) → isNotBlank()=false
→ hint2Visible=false → 박스 자체가 사라짐 (배경도 투명).
엔진이 첫 commit 에서 새 hint 를 쓰면 Hint 2 정상 등장.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) 시작 전 (waitingForStart) Step 표기:
"Step 1/3" → "Step 0/3" 으로 표시, 점/체크포인트 row 전체
숨김 (시작 후에만 점 등장).
2) 화살표 ↔ Hint 2 텍스트 mismatch 수정 (V1 path):
debounce 중 else 분기에서 directionIcon 을 guideResult.icon
으로 즉시 갱신 → 텍스트는 "위로 올려" 인데 화살표는 가끔
아래를 가리키는 깜빡임 발생.
수정: directionHint 텍스트에서 방향 추론 (phaseHoldHint 와
동일 로직) → 화면에 보이는 텍스트와 항상 일관.
V2 path 는 이미 동일 source(st.action) 라 영향 없음.
3) 화살표 위치 — 센서/방광 쪽으로 당김:
vertCloser = arrowDrawH * 0.45f 도입.
- 위 화살표 cya: 기존 + vertCloser (살짝 아래로)
- 아래 화살표 cya: 기존 - vertCloser (살짝 위로)
좌/우 화살표는 변경 없음.
4) 방광 oval 색 (센서 주위 테두리) — 미정렬 상태:
Cyan → Color(0xFFFFB74D) (Material orange 300, warm hint).
greenZone 진입 시는 기존 Green 4CAF50 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
수정: Hint 1 / Hint 2 의 background 색을 조건부로 전환.
- 텍스트 가시: 기존 색 (orange/green/teal)
- 텍스트 비가시: Color.Transparent
heightIn 으로 박스 공간(높이)은 그대로 유지 →
배경 카드만 사라지고 자리는 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
요구사항 (사용자 정의 순서):
Title / spacer / Hint1 / spacer / Illustration / spacer /
Step / spacer / Hint2 / spacer / Start / Skip
변경:
1) Hint 1 박스: if(waitingForStart) 외부 가드 제거 → 박스는
항상 렌더. 텍스트만 waitingForStart 시 표시. heightIn min
96dp (현재 자연 높이 ~64dp 의 1.5배).
2) Hint 2 박스: 동일 패턴 — 박스 항상 렌더, 텍스트는
hint2Visible 시만, heightIn min 102dp (28sp 텍스트 기준 1.5배).
3) Step ↔ Hint 2 간격: Spacer 8dp → weight 1f (5번째 분할).
4) Skip ↔ 하단: weight 1f → 고정 16dp (사용자 요구 — Skip 이
화면 아래 가깝게).
결과: 빈 공간이 5개 위치 (Title↔Hint1, Hint1↔Illustration,
Illustration↔Step, Step↔Hint2, Hint2↔Button) 에 균등 분배.
내용 없을 때도 Hint 박스 자리는 그대로 유지 (배경/높이).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: Hint 2 의 ". .. ..." 도트는 매 scanCount 마다 instant 변화,
화살표는 같은 scanCount 에 220ms tween 으로 fade — 같은 tick
에 시작하지만 fade 가 끌어서 시각적 박자가 어긋나 보임.
수정: animationSpec tween 220 → 0 (snap).
화살표가 도트와 동일하게 한 시각에 on/off 토글.
둘 다 scanCount driver 위에서 같은 박자로 움직임.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
기존 3-spacer 분배에서 2개 더 추가 → 5등분 균등 분배:
1) Title(top bar) ↔ Hint 1 ← 신규
2) Hint 1 ↔ 일러스트
3) 일러스트 ↔ Step 1/3 ← 신규 (Spacer 8dp → weight 1f)
4) Step/Hint 2 ↔ Start Alignment 버튼
5) Skip ↔ 화면 하단
결과: 화면 위/아래 어느 쪽에도 빈 공간 쏠림 없음.
인접 요소간 fixed gap (Hint 1 위 padding 등) 은 그대로 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 화면 아래쪽에만 큰 빈 공간 — 사용자 스크린샷 확인.
수정: 3개 위치의 fixed Spacer 를 Spacer(weight=1f) 로 교체 →
Column 의 남은 vertical 공간을 3등분 균등 분배.
1) Hint 1 ↔ 일러스트 (Spacer 16dp → weight 1f)
2) Step/Hint 2 ↔ Start Alignment 버튼 (Spacer 24dp → weight 1f)
3) Skip 아래 (Spacer 32dp → weight 1f)
결과:
- 화면 전체에 골고루 분포, 아래쪽 큰 빈 공간 해소
- 인접 요소간 fixed gap 은 그대로 유지 (illustration ↔ Step 의 8dp 등)
비고: dev mode (verticalScroll) 에서는 weight 가 0 으로 collapse 되어
자연 stacking. 일반 사용자 모드만 균등 분배 효과 적용.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: Hint 1 ↔ 일러스트 사이 간격이 (Sensor Alignment 타이틀 ↔ Hint 1) 보다
훨씬 커서 비대칭. 일러스트 바로 위만 공간이 비어 보임.
원인: 일러스트 Box 가 .weight(1f).aspectRatio(...) 조합 →
weight(1f) 가 Column 의 남은 vertical 공간을 모두 잡아먹음.
aspectRatio 로 Box 가 실제로 그려지는 높이는 width × 0.84 이지만,
weight 할당량 - 실제 높이 = 빈 공간이 Box 안쪽에 발생.
수정:
- 일러스트 Box 의 .weight(1f) 제거 — aspectRatio 만으로 자연 크기 결정
- Hint 1 ↔ 일러스트 사이 Spacer 8+2dp → 16dp (top bar 의 내부 vertical padding 과 매칭)
결과:
- 타이틀 ↔ Hint 1: 16dp
- Hint 1 ↔ 일러스트: 16dp (균등)
- 일러스트 하단 ~ 버튼: 자연 stacking (남는 공간은 화면 맨 아래로 모임)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) 힌트 메세지 박스 자체 숨김 (내용 없을 때)
기존: waitingForStart 시 Hint 2 = AnnotatedString(" ") — 빈 칸 자체는 유지
변경: 빈 텍스트면 Box 자체 렌더 X (Spacer 도 같이 제거).
Hint 1: waitingForStart 일 때만 렌더
Hint 2: !waitingForStart && displayHint.text.isNotBlank() 일 때만 렌더
레이아웃: 빈 칸이 사라지므로 일러스트 위/아래 여백이 자연스럽게 통합.
2) Hint 1/2 텍스트 상하 중앙 정렬
기존: Text 에 .padding(vertical=14dp) 만 — baseline 기준이라 일부 폰트에서
미세 비대칭 가능.
변경: Box(contentAlignment=Center) 로 감싸고 Text 를 그 안에 배치 →
박스 높이 = padding 14dp top + 텍스트 + 14dp bottom 정확히 매칭.
3) PiezoMonitoring → PlacementGuide 진입 시 항상 V1 (clinical 만 V2)
기존: currentSession.mode == ALIGNMENT 만 체크 → 잔여 세션이 있으면
일반 흐름에서도 V2 강제됨.
변경: !appState.placementFromMonitoring 조건도 같이 체크.
enterPlacementFromMonitoring() 가 placementFromMonitoring=true 설정 →
이 경우 항상 V1.
enterAlignmentSession() (clinical 전용) 만 placementFromMonitoring=false +
mode=ALIGNMENT 이라 V2 로 라우팅.
영향:
- PiezoMonitoring → "재정렬" 진입 시 항상 V1 ✓
- Clinical → Sensor Alignment → V2 ✓
- 위저드 흐름 + 잔여 세션 → V1 (위저드는 placementFromMonitoring 변경 안 함)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) Clinical alignment session 진입 시 자동 V2, 그 외 자동 V1
PlacementGuide LaunchedEffect 에서 currentSession.mode==ALIGNMENT 면 V2,
아니면 V1 로 GreenZoneConstants.alignmentAlgo 강제.
사용자 토글 불필요 — 컨텍스트로 자동 결정.
2) V2 활성 시 step indicator 4-stage (기존 3)
- V1: Step N/3 (VERTICAL / LATERAL / GREEN — placementPhase)
- V2: Step N/4 (INITIAL_ACCUM / VERTICAL_CLIMB / CH3_STABILIZE / LR_BALANCE
— v2Aligner.phase)
3) Hint 2 waitingForStart 시 공백 — pubic bone 중복 안 나오게
Hint 2 가 displayHint 표시하던 것을 waitingForStart 시 AnnotatedString(" ")
로 교체. 칸 높이 유지, 텍스트만 사라짐.
4) Hint 1 "pubic bone" 빨강 ExtraBold 강조
기존 plain Text → AnnotatedString 으로 변경 + buildAnnotatedString 의
"pubic bone" 부분에 SpanStyle(color=Red, fontWeight=ExtraBold) 적용.
5) 화살표 alpha 1.0 ↔ 0.85 → 1.0 ↔ 0.0 (완전 사라짐)
색상 #E64A19 (deep orange) → Color.Red.
tween 220ms 그대로 — 빨강 화살표가 깜빡임으로 명확히 사라짐.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
feature/tab-navigation 의 V2 코드를 demo-final 로 가져와 임상 alignment session
에서 V1/V2 둘 다 선택 가능하도록.
복사된 파일 (feature/tab-navigation 그대로):
walldetect/algo/SgSmoothGeneric.kt
walldetect/algo/methodd/MethodDParams.kt
walldetect/algo/methodd/MethodDPreprocessing.kt
walldetect/algo/methodd/MethodDSpan.kt
walldetect/algo/methodd/MethodDWallSelect.kt
walldetect/algo/methodd/MethodDTgc.kt
walldetect/MethodDDetector.kt
walldetect/MethodDRunner.kt
managers/AlignmentAdvisorV2.kt
(RollingAligner + AlignGuide4Stage + AlignmentAdvice — alignment.py 1:1)
확장:
walldetect/algo/Otsu.kt
+ otsu1dWithSeparability(values) — Python otsu_1d 의 (threshold, sep) tuple 호환
method_d span fallback gate 에서 사용
managers/GreenZoneConstants.kt
+ enum AlignmentAlgo { V1, V2 }
+ @Volatile var alignmentAlgo: AlignmentAlgo = V1 (default = legacy)
ui/views/monitoring/PlacementGuideView.kt
+ val v2Aligner = remember { RollingAligner() }
+ V2 분기 (alignmentAlgo == V2 시): raw 6ch → push → state(accum/commit)
→ directionHint/directionIcon/placementScore/LED 설정
→ bleManager.debugLogger.info("ALIGN_V2 phase=... state=... bestSet=...")
+ ⚙ Settings overlay 에 V1 (legacy) / V2 (new) 토글 (보라색)
+ addAlignmentFrame 호출 시 V2 활성이면 v2Aligner.phase.name 사용 (4-stage)
ui/views/clinical/ClinicalHomeView.kt
+ ClinicalSession.alignmentAlgo 를 현재 GreenZoneConstants.alignmentAlgo 값으로 채움
(이전 demo-final 은 "V1" 하드코딩)
→ V2 토글 후 Sensor Alignment 진입 시 alignment_algo="V2" 가 json 에 기록
영향:
- 기존 V1 동작 그대로 (default 가 V1, 토글 안 하면 동일)
- V2 선택 시 demo-final 에서도 4-stage 진행 가능 + json 라벨 V2 명시
- 알고리즘 토글은 dev 모드 (⚙ 버튼) 에서만 가능 — 일반 사용자 노출 X
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
새 흐름:
ClinicalHome → BLE 연결 + labdb 활성 확인
→ [Sensor Alignment] 버튼 (우측, 보라색)
→ PlacementGuide (alignment session 모드, V1 advisor)
→ 매 cycle phase/score/detected/urineLens/hint 누적
→ [Complete Alignment] 버튼
→ measurement.json (data_type='002') + labdb 자동 업로드
→ ClinicalHome 복귀
Live 6ch 측정은 기존대로:
ClinicalHome → [Live 6ch] → ClinicalLive → measurement.json (data_type='001')
models/ClinicalSession.kt:
+ SessionMode { ALIGNMENT, MEASUREMENT } enum
+ ClinicalSession.mode (default = MEASUREMENT, 기존 동작 유지)
+ ClinicalSession.alignmentAlgo ("V1" demo-final 에서)
+ folderName 에 mode == ALIGNMENT 시 "_ALIGNMODE_" 접미사
services/ClinicalSessionStore.kt:
+ MeasurementCycle 에 alignment 필드 6개 (phase/score/detected/urineLens/hint/icon)
+ addAlignmentFrame(piezo, imu, ...V1 advisor state) 새 메서드
+ writeMeasurementJson:
mode==ALIGNMENT → data_type='002' (R&D alignment)
그 외 = '001' (measurement)
각 cycle JSON 에 align_phase / align_score / align_detected /
align_urine_lens / align_hint / align_icon 첨부
AppState.kt:
+ enterAlignmentSession()
ui/views/clinical/ClinicalHomeView.kt:
+ Live 6ch + Sensor Alignment 2-button Row 로 교체
+ Sensor Alignment: BLE + labdb active 두 조건 모두 OK 시만 활성
+ 비활성 시 안내 텍스트
ui/views/monitoring/PlacementGuideView.kt:
+ 매 cycle ClinicalSessionStore.addAlignmentFrame 호출
(clinical alignment 모드 시) — phase=placementPhase.name, score, detected,
urineLens, hint, icon
+ Start Scanning 버튼이 clinical alignment 모드에선 "Complete Alignment" 로 전환
→ endMeasurement() → CLINICAL_HOME 복귀 (자동 labdb upload)
→ enabled 조건도 항상 활성 (사용자 직접 종료)
labdb 업로드: 기존 LabdbUploader 경로 그대로 — meta.data_type 으로 002/001 구분.
영향:
- 기존 Live 6ch flow: 영향 0 (mode=MEASUREMENT default)
- 기존 PlacementGuide UX: 영향 0 (clinical alignment 모드 아닐 때 분기 X)
- 신규: Clinical 내 alignment-only 세션 채집/업로드 가능
feature/tab-navigation 에 V2 advisor 버전이 별도 적용됨 (commit 173ff4d 까지).
demo-final 은 V1 advisor 만 가능 — V2/Method D/RollingAligner 코드 없음.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1) Auto scan interval default 600 → 500ms
PiezoSettings.autoScanIntervalMs default 변경. 사용자가 별도 조정 안 하면
기본 0.5초 간격으로 측정 사이클 진행.
2) BLE canSendMaa throttle 600 → 500ms
응답이 280~420ms 라 500ms = 평균 + 80~220ms 안전 마진. 디바이스에 무리 X.
throttle 도 같이 내려야 autoScanIntervalMs=500 이 실제로 적용됨.
3) PiezoMonitoring 도넛 상단 배터리 → 연결 끊기면 Disconnect 표시
기존: 배터리 바 + xx% (항상 표시, 비-demo)
변경: isConnected=false → Icons.BluetoothDisabled + "Disconnect" (빨강)
isConnected=true → 기존 배터리 바 + %
demo flavor 는 영향 0 (!isDemoMode 가드 그대로).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
이전: waitingForStart 분기로 hint1 또는 hint2 하나만 표시 → 시작 누르면
레이아웃이 위아래 이동.
변경: 두 hint 영역 모두 항상 렌더 → 일러스트/Step 위치 고정.
Hint 1 (일러스트 위): 'Place VesiScan above the pubic bone, then press Start'
(시작 안내, 고정)
Hint 2 (일러스트 아래): displayHint (동적 — 시작 전엔 안내문, 진행 중엔 방향)
waitingForStart 초기 상태에는 hint 1/2 가 비슷한 메시지 보일 수 있음 (의도된
동작 — 레이아웃 안정성 우선).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
레이아웃 순서 변경:
기존: 타이틀 → Step → 일러스트 → Hint
변경: 타이틀 → Hint1 → 일러스트 → Step → Hint2 → 버튼 → Skip
Hint 두 영역 분리:
Hint 1 (일러스트 위) — 시작 전 안내
"Place VesiScan above the pubic bone, then press Start"
waitingForStart=true 일 때만 표시.
Hint 2 (일러스트 아래) — 측정 중 방향 안내
"Slide up slightly ↑", "Centered, optimizing..." 등.
waitingForStart=false 일 때만 표시.
태블릿 기준 글자 크기:
타이틀 32 → 56sp (ExtraBold) — 화면폭 ≈ 60% 차지
Hint1 신규 26sp Bold
Hint2 22 → 28sp Bold
Step 22 → 28sp + 3-dot indicator 12/8 → 16/10dp
SWEEP/IMU 배지 10 → 13/12sp
Skip 15 → 20sp
기존 버튼 ("Start Alignment" 28sp, "Start Scanning" 28sp) 그대로 유지.
영향:
- 일반 사용자 (demo flavor): 태블릿 시연 화면 정렬 강화
- dev/feature 브랜치: 동일 패턴 cherry-pick 가능 (별개 commit)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
기존:
title "펌웨어 응답 없음"
subtitle "연결됐으나 mid? 응답 미수신. 펌웨어 호환 문제 가능 — 측정 안 될 수 있음."
변경:
title "기기 응답 없음"
subtitle 없음 (timedOut 케이스)
이유:
사내 테스트 피드백 — 일반 사용자에게 mid?/펌웨어 호환 같은 용어는 의미 없고
오히려 불안만 가중. 빨강 배너로 시각 신호는 유지하되 본문은 한 줄로.
outdated(주황) 케이스는 그대로 — 펌웨어 버전 안내가 actionable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
자산:
- drawable/arrow_transparent.png 신규 (132×112 RGBA, 우향 thin arrow)
코드:
- drawLine 3개로 구성하던 화살표 → drawImage(arrowBitmap) 한 번으로 교체
- 4방향은 rotate(degrees, pivot) 로 처리:
right=0° / down=90° / left=180° / up=-90°
- ColorFilter.tint(#E64A19 × arrowAlpha) — 일러스트 톤 매칭 + blink 유지
- arrowDrawW=90f, 높이는 icon aspect(132:112)로 비례
- center 위치는 bladder oval edge + sensorHalfH + gap + arrowSize/2
기존 blink 로직(scanCount % 2 → animateFloatAsState 180ms)은 그대로 동작 —
tint color 의 alpha 에 arrowAlpha 를 곱하므로 PNG 도 매 refresh 깜빡임.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
자산:
- drawable-xxhdpi/body_silhouette.png 신규 (finalstomachillust.png, 2240×1888 ≈ 7:6)
feature/tab-navigation 의 일러스트와 동일 — bezier 토르소를 PNG로 교체
UI:
- Top bar: Box overlay 구조로 ← + "Sensor Alignment" 화면 절대 가운데 (26sp ExtraBold)
- 본문 큰 타이틀(36sp) 제거 — top bar 와 중복
- Body 영역: bezier silhouette → Image(painter=body_silhouette) + 얇은 Canvas overlay
Canvas 는 oval(방광) + sensor PNG + 방향 화살표만 담당
- 비율 .aspectRatio(2240f / 1888f) 고정 → 화면비 무관 portrait 유지
화살표 refresh blink (사용자 요청):
- scanCount % 2 토글 → animateFloatAsState(1f ↔ 0.35f, tween 180ms)
- 매 cycle 마다 화살표 alpha 가 깜빡임 → "업데이트 됐는지" 시각화
- 색감 #FF5722 → #E64A19 (deep orange 700, 일러스트 톤 매칭)
- StrokeCap.Round + 굵기 9→10 (가독성)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>