fix: 센서 비율 기반 + LED 깜빡임 + 디바운스 + gated 체크
[Placement] - 센서 이미지 비율 기반 (너비 35%, BiasAlignment 0.44) — 태블릿 대응 - LED 깜빡임 수정: greenHeld/failCount<3 동안 LED 5 안 보냄 - 디바운스: phaseProtected 중 lastHintChangeMs 안 건드림 (화살표 안 나오던 버그) - Step 2 전환 힌트 항상 "Checking left/right balance..." - GREEN에서 ch4/ch5 개별 탈락 시 방향 안내 (lrDev NaN 처리) [도넛차트] - Fill 카드: 윗줄 65%, 아랫줄 325/500 mL - ml → mL 통일 - Spot 완료 시 displayMaxVolumeMl 즉시 갱신 - Auto 윈도우 < 5일 때 Current Measurement "—" [기타] - V41 gated 체크 + 최소 len≥3 (ungated 피크 안 보임) - CCCD 실패(status 133) 500ms 후 1회 재시도 - 기기 스캔 사전순 정렬 - 힌트 점 순환 (./.../...) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -828,8 +828,30 @@ class BleManager private constructor(private val context: Context) {
|
||||
logd { "Service ready! (CCCD written)" }
|
||||
}
|
||||
} else {
|
||||
loge { "CCCD write failed with status: $status" }
|
||||
connectionError.value = "Notification setup failed (status $status)"
|
||||
loge { "CCCD write failed with status: $status — retrying in 500ms" }
|
||||
// GATT 133 등 일시적 오류 → 500ms 후 재시도
|
||||
handler.postDelayed({
|
||||
try {
|
||||
val rx = rxCharacteristic
|
||||
if (rx != null) {
|
||||
gatt.setCharacteristicNotification(rx, true)
|
||||
val cccd = rx.getDescriptor(CCCD_UUID)
|
||||
if (cccd != null) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
gatt.writeDescriptor(cccd, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
@Suppress("DEPRECATION")
|
||||
gatt.writeDescriptor(cccd)
|
||||
}
|
||||
logd { "CCCD retry sent" }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
connectionError.value = "Notification setup failed (status $status)"
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ fun DeviceScanView(appState: AppState) {
|
||||
|
||||
val filteredDevices = bleManager.discoveredDevices
|
||||
.filter { it.name.startsWith("2025") || it.name.startsWith("VBT") }
|
||||
.sortedByDescending { it.rssi }
|
||||
.sortedBy { it.name }
|
||||
|
||||
val isConnected by bleManager.isConnected
|
||||
val isServiceReady by bleManager.isServiceReady
|
||||
|
||||
+3
-1
@@ -202,7 +202,9 @@ fun PiezoMonitoringView(appState: AppState) {
|
||||
)
|
||||
val result = v41.detect(sweepInput)
|
||||
for (ch in result.perChannel) {
|
||||
if (ch.antIdx != null && ch.postIdx != null && ch.ch < 6) {
|
||||
val gated = ch.v41Diag?.gated == true
|
||||
if (gated && ch.antIdx != null && ch.postIdx != null && ch.ch < 6
|
||||
&& (ch.postIdx - ch.antIdx) >= 3) {
|
||||
allWalls[ch.ch] = Pair(ch.antIdx, ch.postIdx)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-21
@@ -360,20 +360,24 @@ fun PlacementGuideView(appState: AppState) {
|
||||
}
|
||||
|
||||
// 센서 기기 이미지 (방광 타원 중앙에 정렬)
|
||||
// 타원 중심 = (navelY+pubisY)/2 ≈ 높이의 68%. Alignment.Center = 50%. 차이 ≈ 18%
|
||||
val boxHeightDp = with(androidx.compose.ui.platform.LocalDensity.current) {
|
||||
(this@Box).let { 110.dp } // fallback
|
||||
// 센서 — 화면 비율 기반 크기/위치 (태블릿 대응)
|
||||
// 타원 중심 ≈ 높이 72%. BiasAlignment(0, 0.44) ≈ 72%
|
||||
// 센서 너비 = 박스 너비의 35%
|
||||
androidx.compose.foundation.layout.BoxWithConstraints(
|
||||
modifier = Modifier.matchParentSize()
|
||||
) {
|
||||
val sensorW = maxWidth * 0.35f
|
||||
val sensorH = sensorW * 0.625f
|
||||
androidx.compose.foundation.Image(
|
||||
painter = androidx.compose.ui.res.painterResource(com.example.medilightv2android.R.drawable.sensor_device),
|
||||
contentDescription = "Sensor",
|
||||
modifier = Modifier
|
||||
.size(width = sensorW, height = sensorH)
|
||||
.align(androidx.compose.ui.BiasAlignment(0f, 0.44f)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit,
|
||||
alpha = 0.95f
|
||||
)
|
||||
}
|
||||
androidx.compose.foundation.Image(
|
||||
painter = androidx.compose.ui.res.painterResource(com.example.medilightv2android.R.drawable.sensor_device),
|
||||
contentDescription = "Sensor",
|
||||
modifier = Modifier
|
||||
.size(width = 192.dp, height = 120.dp)
|
||||
.align(Alignment.Center)
|
||||
.offset(x = 0.dp, y = 95.dp),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit,
|
||||
alpha = 0.95f
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
@@ -666,22 +670,20 @@ fun PlacementGuideView(appState: AppState) {
|
||||
// Start 후 5초간 같은 phase 내 힌트는 즉시, phase 전환은 3초 유지
|
||||
val justStarted = (now - startPressedAtMs) < 5000
|
||||
val hintUpdate = justStarted && !phaseChanged && !phaseProtected
|
||||
if (hintUpdate || greenChanged || (phaseChanged && !greenHeld && debounceCount >= 2 && elapsed >= 3000) || (!phaseChanged && !phaseProtected && !greenHeld && debounceCount >= 2 && elapsed >= 1500)) {
|
||||
if (!phaseProtected && (hintUpdate || greenChanged || (phaseChanged && !greenHeld && debounceCount >= 2 && elapsed >= 3000) || (!phaseChanged && !greenHeld && debounceCount >= 2 && elapsed >= 1500))) {
|
||||
directionHint = guideResult.hint
|
||||
directionIcon = guideResult.icon
|
||||
placementScore = guideResult.score
|
||||
if (phaseChanged) {
|
||||
lastPhaseChangeMs = now
|
||||
phaseHoldScans = 3
|
||||
phaseHoldHint = guideResult.hint // 전환 힌트 저장
|
||||
phaseHoldHint = guideResult.hint
|
||||
}
|
||||
placementPhase = guideResult.nextPhase
|
||||
lastHintChangeMs = now
|
||||
if (!guideResult.cv.isNaN()) lastCv = guideResult.cv
|
||||
}
|
||||
|
||||
// phase 보호 중이면 전환 힌트 강제 유지 (무조건 덮어쓰기)
|
||||
if (phaseProtected && phaseHoldHint.isNotEmpty()) {
|
||||
} else if (phaseProtected && phaseHoldHint.isNotEmpty()) {
|
||||
// 보호 중 → 전환 힌트 강제 유지, lastHintChangeMs 안 건드림
|
||||
directionHint = phaseHoldHint
|
||||
directionIcon = "checkmark.circle"
|
||||
}
|
||||
@@ -727,8 +729,8 @@ fun PlacementGuideView(appState: AppState) {
|
||||
rTracker.reset()
|
||||
bleManager.debugLogger.info("PLACEMENT_GREEN_EXIT after 3 consecutive fails")
|
||||
}
|
||||
} else {
|
||||
// GREEN 아닌 상태에서 실패
|
||||
} else if (!greenHeld) {
|
||||
// GREEN 아닌 상태 + hold 아닌 상태에서 실패
|
||||
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}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user