feat: Placement Guide 실시간 연속 스캔 (3초 간격)
- 수동 1회 → 자동 반복 스캔 (Start/Stop 토글) - 3초마다 maa 전송 → Score 실시간 갱신 - 사용자가 센서 움직이면서 점수 변화를 즉시 확인 가능 - "Scanning every 3s — move sensor and watch the score" 안내 문구 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+82
-58
@@ -57,6 +57,7 @@ fun PlacementGuideView(appState: AppState) {
|
||||
val analyzer = remember { PiezoEchoAnalyzer.shared }
|
||||
|
||||
var isScanning by remember { mutableStateOf(false) }
|
||||
var isContinuousMode by remember { mutableStateOf(false) }
|
||||
val channelDetected = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||
val channelUrineLen = remember { mutableStateListOf(0, 0, 0, 0, 0, 0) }
|
||||
var directionHint by remember { mutableStateOf("") }
|
||||
@@ -280,75 +281,85 @@ fun PlacementGuideView(appState: AppState) {
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Check Position button
|
||||
Button(
|
||||
onClick = {
|
||||
if (!isScanning) {
|
||||
isScanning = true
|
||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||
isScanning = false
|
||||
scanCount++
|
||||
// Scan function (reusable)
|
||||
fun performScan() {
|
||||
if (isScanning) return
|
||||
isScanning = true
|
||||
bleManager.piezoCollector.onMultiChannelComplete = { channels ->
|
||||
isScanning = false
|
||||
scanCount++
|
||||
|
||||
val detected = MutableList(6) { false }
|
||||
val urineLens = MutableList(6) { 0 }
|
||||
val detected = MutableList(6) { false }
|
||||
val urineLens = MutableList(6) { 0 }
|
||||
|
||||
for (ch in channels) {
|
||||
if (ch.buffer.isNotEmpty() && ch.channel < 6) {
|
||||
val analysis = analyzer.analyzeChannel(ch.buffer, ch.channel)
|
||||
if (analysis.isValid && analysis.result != null) {
|
||||
detected[ch.channel] = true
|
||||
urineLens[ch.channel] = analysis.result.urineLen
|
||||
} else {
|
||||
val gate = UrinAI.detect(ch.buffer)
|
||||
if (gate.detected) {
|
||||
detected[ch.channel] = true
|
||||
urineLens[ch.channel] = gate.bw - gate.fw
|
||||
}
|
||||
}
|
||||
for (ch in channels) {
|
||||
if (ch.buffer.isNotEmpty() && ch.channel < 6) {
|
||||
val analysis = analyzer.analyzeChannel(ch.buffer, ch.channel)
|
||||
if (analysis.isValid && analysis.result != null) {
|
||||
detected[ch.channel] = true
|
||||
urineLens[ch.channel] = analysis.result.urineLen
|
||||
} else {
|
||||
val gate = UrinAI.detect(ch.buffer)
|
||||
if (gate.detected) {
|
||||
detected[ch.channel] = true
|
||||
urineLens[ch.channel] = gate.bw - gate.fw
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0 until 6) {
|
||||
channelDetected[i] = detected[i]
|
||||
channelUrineLen[i] = urineLens[i]
|
||||
}
|
||||
lastChannelData = channels
|
||||
|
||||
// Direction guidance (urineLen-aware)
|
||||
val guideResult = computePlacementGuide(detected, urineLens)
|
||||
directionHint = guideResult.hint
|
||||
directionIcon = guideResult.icon
|
||||
placementScore = guideResult.score
|
||||
|
||||
// Lock if score >= 70 or 4+ detected
|
||||
if (guideResult.score >= 70 || detected.count { it } >= 4) {
|
||||
isLocked = true
|
||||
}
|
||||
|
||||
Log.d("PlacementGuide", "Placement: ${detected.count { it }}/6 -- ${
|
||||
detected.mapIndexed { idx, d -> if (d) "CH$idx OK" else "CH$idx X" }.joinToString(" ")
|
||||
}")
|
||||
|
||||
// Save placement log
|
||||
logService.logPlacement(
|
||||
channelDetected = detected,
|
||||
direction = directionHint
|
||||
)
|
||||
}
|
||||
bleManager.piezoCollector.onLog = { msg ->
|
||||
Log.d("PlacementGuide", msg)
|
||||
}
|
||||
bleManager.sendAllChannels()
|
||||
}
|
||||
|
||||
for (i in 0 until 6) {
|
||||
channelDetected[i] = detected[i]
|
||||
channelUrineLen[i] = urineLens[i]
|
||||
}
|
||||
lastChannelData = channels
|
||||
|
||||
val guideResult = computePlacementGuide(detected, urineLens)
|
||||
directionHint = guideResult.hint
|
||||
directionIcon = guideResult.icon
|
||||
placementScore = guideResult.score
|
||||
|
||||
if (guideResult.score >= 70 || detected.count { it } >= 4) {
|
||||
isLocked = true
|
||||
}
|
||||
|
||||
Log.d("PlacementGuide", "Scan #$scanCount: ${detected.count { it }}/6, score=$placementScore%")
|
||||
|
||||
logService.logPlacement(
|
||||
channelDetected = detected,
|
||||
direction = directionHint
|
||||
)
|
||||
}
|
||||
bleManager.piezoCollector.onLog = { msg -> Log.d("PlacementGuide", msg) }
|
||||
bleManager.sendAllChannels()
|
||||
}
|
||||
|
||||
// Continuous scan loop (3초 간격)
|
||||
LaunchedEffect(isContinuousMode) {
|
||||
if (isContinuousMode) {
|
||||
performScan()
|
||||
while (isContinuousMode) {
|
||||
kotlinx.coroutines.delay(3000)
|
||||
if (isContinuousMode && !isScanning) performScan()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start/Stop button
|
||||
Button(
|
||||
onClick = {
|
||||
if (isContinuousMode) {
|
||||
isContinuousMode = false
|
||||
} else {
|
||||
isContinuousMode = true
|
||||
}
|
||||
},
|
||||
enabled = !isScanning,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (isScanning) Color.Gray else MlPrimary,
|
||||
disabledContainerColor = Color.Gray
|
||||
containerColor = if (isContinuousMode) Color.Red else MlPrimary
|
||||
)
|
||||
) {
|
||||
if (isScanning) {
|
||||
@@ -360,13 +371,26 @@ fun PlacementGuideView(appState: AppState) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
Text(
|
||||
text = if (isScanning) "Scanning..." else "Check Position",
|
||||
text = when {
|
||||
isContinuousMode -> "Stop Scanning"
|
||||
scanCount == 0 -> "Start Scanning"
|
||||
else -> "Resume Scanning"
|
||||
},
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
if (isContinuousMode) {
|
||||
Text(
|
||||
"Scanning every 3s — move sensor and watch the score",
|
||||
fontSize = 11.sp, color = MlSecondaryText,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
// Start Measurement button (when 4+ channels detected)
|
||||
if (isLocked || detectedCount >= 4) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Reference in New Issue
Block a user