fix: preset 감지 오류 (BV 2배 차이 근본원인) + 도넛 비율 + placement 자동통과

- autoDetectPreset: 기기이름 뒤에서 3번째 글자로 각도타입 판별
  VBT26050301 → '3' → V2 (기존: VBT2604 매칭 실패 → V0 → 각도 0° → BV 절반)
- 도넛차트: level 단계별 → maxVolumeMl/maxVolume 연속 비율로 변경
- Placement "Almost there" 3회 연속 → 자동 GREEN 통과

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 16:13:13 +09:00
parent b73921eb2b
commit 1b70ec809d
3 changed files with 34 additions and 9 deletions
@@ -35,15 +35,20 @@ object PiezoHW {
var activePreset: DevicePreset = DevicePreset.V0 var activePreset: DevicePreset = DevicePreset.V0
fun autoDetectPreset(deviceName: String) { fun autoDetectPreset(deviceName: String) {
if (deviceName.startsWith("VBT2604") && deviceName.length >= 10) { if (deviceName.startsWith("VBT") && deviceName.length >= 4) {
when (deviceName[8]) { // VBT...n0x → 뒤에서 3번째 글자(n)가 각도 타입
'0' -> activePreset = DevicePreset.V0 // n=0 → V0, n=2 → V1, n=3 → V2
'2' -> activePreset = DevicePreset.V1 val n = deviceName[deviceName.length - 3]
'3' -> activePreset = DevicePreset.V2 activePreset = when (n) {
'0' -> DevicePreset.V0
'2' -> DevicePreset.V1
'3' -> DevicePreset.V2
else -> DevicePreset.V1 // 알 수 없으면 V1 기본
} }
} else if (deviceName.startsWith("2025MEDIP")) { } else if (deviceName.startsWith("2025MEDIP")) {
activePreset = DevicePreset.LEGACY_5CH activePreset = DevicePreset.LEGACY_5CH
} }
android.util.Log.d("PiezoHW", "autoDetectPreset: '$deviceName' → ${activePreset.name}")
} }
val centerCh = intArrayOf(0, 1, 2, 3) val centerCh = intArrayOf(0, 1, 2, 3)
@@ -761,9 +761,13 @@ fun PiezoMonitoringView(appState: AppState) {
// Donut chart with Bladdy // Donut chart with Bladdy
val donutSize = if (isCompact) 220.dp else 280.dp val donutSize = if (isCompact) 220.dp else 280.dp
// maxVolume 기준 비율 (0.0~1.0, 부드러운 연속값)
val maxVolumeForDonut = appState.piezoSettings.maxVolume.toDouble()
val donutProgress = if (maxVolumeForDonut > 0 && maxVolumeMl > 0)
(maxVolumeMl / maxVolumeForDonut).coerceIn(0.0, 1.0) else 0.0
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
DonutChartView( DonutChartView(
progress = appState.bladderLevel.progress, progress = donutProgress,
urgency = urgency, urgency = urgency,
lineWidth = if (isCompact) 22.dp else 30.dp, lineWidth = if (isCompact) 22.dp else 30.dp,
size = donutSize size = donutSize
@@ -96,6 +96,7 @@ fun PlacementGuideView(appState: AppState) {
// SWEEP 추가 state // SWEEP 추가 state
var sweepLastDetected by remember { mutableIntStateOf(0) } var sweepLastDetected by remember { mutableIntStateOf(0) }
var sweepEverSawAll by remember { mutableStateOf(false) } var sweepEverSawAll by remember { mutableStateOf(false) }
var almostThereCount by remember { mutableIntStateOf(0) }
// 화면 진입 시 상태 리셋 // 화면 진입 시 상태 리셋
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -110,7 +111,7 @@ fun PlacementGuideView(appState: AppState) {
rTracker.reset() rTracker.reset()
currentRMm = null; bestRMm = 0.0 currentRMm = null; bestRMm = 0.0
sagittalArgMaxCh = null; lateralOffsetMm = null sagittalArgMaxCh = null; lateralOffsetMm = null
sweepLastDetected = 0; sweepEverSawAll = false sweepLastDetected = 0; sweepEverSawAll = false; almostThereCount = 0
} }
// 화면 벗어날 때 LED OFF // 화면 벗어날 때 LED OFF
@@ -656,10 +657,25 @@ fun PlacementGuideView(appState: AppState) {
lastHintChangeMs = now lastHintChangeMs = now
} }
if (guideResult.isPass) { // "Almost there" 연속 3회 → 자동 GREEN 통과
val isAlmostThere = guideResult.hint.contains("Almost there")
if (isAlmostThere) almostThereCount++ else almostThereCount = 0
val autoPass = guideResult.isPass || almostThereCount >= 3
if (autoPass) {
isLocked = true isLocked = true
almostThereCount = 0
if (lastLedMode != 6) { bleManager.sendLedMode(6); lastLedMode = 6 } if (lastLedMode != 6) { bleManager.sendLedMode(6); lastLedMode = 6 }
bleManager.debugLogger.info("PLACEMENT_GREEN cv=${"%.3f".format(guideResult.cv)} lr_dev=${"%.3f".format(guideResult.lrDev)} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)}") if (guideResult.isPass) {
bleManager.debugLogger.info("PLACEMENT_GREEN cv=${"%.3f".format(guideResult.cv)} lr_dev=${"%.3f".format(guideResult.lrDev)} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)}")
} else {
// Auto-pass: 위치 OK인데 CV 노이즈로 3회 연속 미달
directionHint = "Green! Position locked"
directionIcon = "checkmark.circle"
placementScore = 70
placementPhase = PlacementPhase.GREEN
bleManager.debugLogger.info("PLACEMENT_GREEN_AUTO cv=${"%.3f".format(guideResult.cv)} almostThereCount=3")
}
} else { } else {
isLocked = false isLocked = false
if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 } if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 }