fix: Placement LED 단계별 1회만 + 화살표 확대 + 도넛뷰 detachment LED

Placement:
- LED 명령: 매 스캔 → 단계 변경 시 1회만 (lastLedMode 추적)
  - 5=SEARCHING, 6=COMPLETE, 화면 나갈 때 0=OFF
- 화살표 크기: arrowLen 40→60, arrowHead 12→18, strokeWidth 4→5

Monitoring (도넛뷰):
- 매 측정마다 DetachmentDetection.detect() 실행
- 미부착 감지 시 LED 4 (DETACH_WARNING) 전송 + 로그

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-30 17:29:37 +09:00
parent 30c1d4d8db
commit 002a99d163
2 changed files with 38 additions and 28 deletions
@@ -123,6 +123,15 @@ fun PiezoMonitoringView(appState: AppState) {
lastMultiChannelResult.clear() lastMultiChannelResult.clear()
lastMultiChannelResult.addAll(channels) lastMultiChannelResult.addAll(channels)
// Detachment 체크
val sortedBuffers = channels.sortedBy { it.channel }.map { it.buffer }
val detachResult = com.example.medilightv2android.managers.DetachmentDetection.detect(sortedBuffers)
if (detachResult.isDetached) {
Log.w("PiezoMonitor", "DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
bleManager.debugLogger.warn("DETACHED mean=${"%.1f".format(detachResult.meanAbs)} std=${"%.1f".format(detachResult.stdVal)}")
handler.post { bleManager.sendLedMode(4) }
}
var rawVolumeMl: Double? = null var rawVolumeMl: Double? = null
var rawLrRatio = 1.0 var rawLrRatio = 1.0
var totalWallCount = 0 var totalWallCount = 0
@@ -72,6 +72,7 @@ fun PlacementGuideView(appState: AppState) {
var lastGuidePhase by remember { mutableStateOf(PlacementPhase.VERTICAL) } var lastGuidePhase by remember { mutableStateOf(PlacementPhase.VERTICAL) }
var lastHintChangeMs by remember { mutableStateOf(0L) } var lastHintChangeMs by remember { mutableStateOf(0L) }
var showPlacementSettings by remember { mutableStateOf(false) } var showPlacementSettings by remember { mutableStateOf(false) }
var lastLedMode by remember { mutableIntStateOf(-1) }
// 2단계 순차 탐색: VERTICAL → LATERAL → GREEN // 2단계 순차 탐색: VERTICAL → LATERAL → GREEN
var placementPhase by remember { mutableStateOf(PlacementPhase.VERTICAL) } var placementPhase by remember { mutableStateOf(PlacementPhase.VERTICAL) }
@@ -234,33 +235,33 @@ fun PlacementGuideView(appState: AppState) {
// ── 방향 화살표 ── // ── 방향 화살표 ──
if (!greenZone) { if (!greenZone) {
val arrowColor = Color(0xFFFF9800) val arrowColor = Color(0xFFFF9800)
val arrowLen = 40f val arrowLen = 60f
val arrowHead = 12f val arrowHead = 18f
when { when {
directionIcon.contains("up") -> { directionIcon.contains("up") -> {
val ax = cx; val ay = sensorY - 20f val ax = cx; val ay = sensorY - 20f
drawLine(arrowColor, Offset(ax, ay + arrowLen), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax, ay + arrowLen), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax - arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax - arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax + arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax + arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("down") -> { directionIcon.contains("down") -> {
val ax = cx; val ay = sensorY + sensorH + 20f val ax = cx; val ay = sensorY + sensorH + 20f
drawLine(arrowColor, Offset(ax, ay - arrowLen), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax, ay - arrowLen), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax - arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax - arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax + arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax + arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("left") -> { directionIcon.contains("left") -> {
val ax = cx - sensorW / 2 - 25f; val ay = sensorY + sensorH / 2 val ax = cx - sensorW / 2 - 25f; val ay = sensorY + sensorH / 2
drawLine(arrowColor, Offset(ax + arrowLen, ay), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax + arrowLen, ay), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax + arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax + arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax + arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax + arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("right") -> { directionIcon.contains("right") -> {
val ax = cx + sensorW / 2 + 25f; val ay = sensorY + sensorH / 2 val ax = cx + sensorW / 2 + 25f; val ay = sensorY + sensorH / 2
drawLine(arrowColor, Offset(ax - arrowLen, ay), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax - arrowLen, ay), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax - arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax - arrowHead, ay - arrowHead), Offset(ax, ay), strokeWidth = 5f)
drawLine(arrowColor, Offset(ax - arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 4f) drawLine(arrowColor, Offset(ax - arrowHead, ay + arrowHead), Offset(ax, ay), strokeWidth = 5f)
} }
} }
} }
@@ -534,11 +535,11 @@ fun PlacementGuideView(appState: AppState) {
if (guideResult.isPass) { if (guideResult.isPass) {
isLocked = true isLocked = true
bleManager.sendLedMode(6) // ALIGN_COMPLETE 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)}") 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 { } else {
isLocked = false isLocked = false
bleManager.sendLedMode(5) // ALIGN_SEARCHING if (lastLedMode != 5) { bleManager.sendLedMode(5); lastLedMode = 5 }
bleManager.debugLogger.info("PLACEMENT_${guideResult.phase.name} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)} hint=${guideResult.hint}") bleManager.debugLogger.info("PLACEMENT_${guideResult.phase.name} score=${guideResult.score} otsu=${"%.0f".format(analyzer.lastOtsuThreshold)} hint=${guideResult.hint}")
} }
} }
@@ -798,27 +799,27 @@ private fun SensorLayoutCanvas(
when { when {
directionIcon.contains("up") -> { directionIcon.contains("up") -> {
val ax = cx; val ay = cy - 190f val ax = cx; val ay = cy - 190f
drawLine(orangeColor, Offset(ax, ay + al), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax, ay + al), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("down") -> { directionIcon.contains("down") -> {
val ax = cx; val ay = cy + 190f val ax = cx; val ay = cy + 190f
drawLine(orangeColor, Offset(ax, ay - al), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax, ay - al), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("left") -> { directionIcon.contains("left") -> {
val ax = cx - 190f; val ay = cy val ax = cx - 190f; val ay = cy
drawLine(orangeColor, Offset(ax + al, ay), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax + al, ay), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax + 14f, ay - 14f), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax + 14f, ay + 14f), Offset(ax, ay), strokeWidth = 5f)
} }
directionIcon.contains("right") -> { directionIcon.contains("right") -> {
val ax = cx + 190f; val ay = cy val ax = cx + 190f; val ay = cy
drawLine(orangeColor, Offset(ax - al, ay), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax - al, ay), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax - 14f, ay - 14f), Offset(ax, ay), strokeWidth = 5f)
drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 4f) drawLine(orangeColor, Offset(ax - 14f, ay + 14f), Offset(ax, ay), strokeWidth = 5f)
} }
} }
} }