From f85b5cfbed640293b0712e17039a1352e64aa5c0 Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 11 Jun 2026 15:34:09 +0900 Subject: [PATCH] =?UTF-8?q?ble:=20=EC=9D=BC=EB=B0=98=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=EB=8A=94=20KnownDeviceStore=20=EC=A0=80=EC=9E=A5=200?= =?UTF-8?q?=EA=B0=9C=20+=20=ED=8E=8C=EC=9B=A8=EC=96=B4=20=EB=AF=B8?= =?UTF-8?q?=EC=88=98=EC=8B=A0=20=EA=B2=BD=EA=B3=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 다중 기기 페어링 저장은 Clinical 흐름 전용. 일반 사용자는 매 세션 fresh scan + pair — 저장 기능 자체 미노출. - BleManager.connect 성공 시: ClinicalSessionStore.inClinicalFlow 체크 → false 면 KnownDeviceStore.add() skip - DeviceScanView: 같은 조건으로 Saved Devices 섹션 노출 X. knownDevices = emptyList() → 기존 단일 디바이스 리스트 UX + FirmwareWarningBanner 케이스 추가: 연결 후 8초간 mid? 응답 미수신 → 빨강 경고 "펌웨어 응답 없음 — 펌웨어 호환 문제 가능" 사용자가 정확한 원인 파악 가능 (구버전 != 응답 자체 안 옴). --- .../medilightv2android/ble/BleManager.kt | 14 +++-- .../ui/components/FirmwareWarningBanner.kt | 54 +++++++++++++------ .../ui/views/connection/DeviceScanView.kt | 6 ++- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt index 81b5ea0..fd90aef 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -869,11 +869,15 @@ class BleManager private constructor(private val context: Context) { cancelReconnectTimer() debugLogger.connected(connectedDeviceName.value, gatt.device.address) - try { - com.example.medilightv2android.services.KnownDeviceStore.add( - context, gatt.device.address, connectedDeviceName.value - ) - } catch (_: Exception) {} + // Clinical 흐름에서만 KnownDeviceStore 영속화. 일반 사용자는 + // 매 세션 fresh scan + pair — 저장 0개. + if (com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow) { + try { + com.example.medilightv2android.services.KnownDeviceStore.add( + context, gatt.device.address, connectedDeviceName.value + ) + } catch (_: Exception) {} + } // MTU 247 요청 (reb: 208B 패킷 드롭 방지) // onMtuChanged 콜백에서 discoverServices 호출 diff --git a/app/src/main/java/com/example/medilightv2android/ui/components/FirmwareWarningBanner.kt b/app/src/main/java/com/example/medilightv2android/ui/components/FirmwareWarningBanner.kt index 4e657d8..25d1aa3 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/components/FirmwareWarningBanner.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/components/FirmwareWarningBanner.kt @@ -13,7 +13,11 @@ import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -22,38 +26,58 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.medilightv2android.ble.BleManager +import kotlinx.coroutines.delay /** - * 펌웨어 구버전 경고 배너 — PlacementGuide / PiezoMonitoring 상단에 노출. - * isFirmwareOutdated 가 true 일 때만 렌더. 사용자가 측정 안 됨 원인을 즉시 파악하도록. + * 펌웨어 상태 배너 — PlacementGuide / PiezoMonitoring 상단에 노출. + * + * 표시 케이스: + * (1) 펌웨어 구버전 (build < MIN_FIRMWARE_BUILD) — 주황 경고 + * (2) 연결 후 8초 넘게 mid? 응답 미수신 — 빨강 경고 (펌웨어가 mid 미지원) + * (3) 정상 또는 미연결 — 안 보임 */ @Composable fun FirmwareWarningBanner(bleManager: BleManager, modifier: Modifier = Modifier) { val fw by bleManager.firmwareVersion - if (!bleManager.isFirmwareOutdated) return + val isConnected by bleManager.isConnected + + // (2) 연결됐는데 일정 시간 동안 펌웨어 미수신 추적 + var timedOut by remember { mutableStateOf(false) } + LaunchedEffect(isConnected, fw) { + timedOut = false + if (isConnected && fw.isEmpty()) { + delay(8_000) + if (bleManager.isConnected.value && bleManager.firmwareVersion.value.isEmpty()) { + timedOut = true + } + } + } + + val outdated = bleManager.isFirmwareOutdated + if (!outdated && !timedOut) return + + val bgColor = if (outdated) Color(0xFFFF9500) else Color(0xFFD32F2F) + val title = if (outdated) "펌웨어 업데이트 필요" else "펌웨어 응답 없음" + val subtitle = if (outdated) { + "현재: $fw · 필요: VBTFW${"%04d".format(BleManager.MIN_FIRMWARE_BUILD)} 이상. 측정이 정상 동작하지 않을 수 있습니다." + } else { + "연결됐으나 mid? 응답 미수신. 펌웨어 호환 문제 가능 — 측정 안 될 수 있음." + } + Row( modifier = modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 6.dp) .clip(RoundedCornerShape(10.dp)) - .background(Color(0xFFFF9500)) + .background(bgColor) .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { Icon(Icons.Default.Warning, "Warning", tint = Color.White) Spacer(modifier = Modifier.width(10.dp)) Column(modifier = Modifier.weight(1f)) { - Text( - "펌웨어 업데이트 필요", - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - color = Color.White - ) - Text( - "현재: $fw · 필요: VBTFW${"%04d".format(BleManager.MIN_FIRMWARE_BUILD)} 이상. 측정이 정상 동작하지 않을 수 있습니다.", - fontSize = 11.sp, - color = Color.White.copy(alpha = 0.9f) - ) + Text(title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = Color.White) + Text(subtitle, fontSize = 11.sp, color = Color.White.copy(alpha = 0.9f)) } } } diff --git a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt index 1673ca0..57dfdfc 100644 --- a/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt +++ b/app/src/main/java/com/example/medilightv2android/ui/views/connection/DeviceScanView.kt @@ -52,7 +52,11 @@ fun DeviceScanView(appState: AppState) { var connectingDeviceAddress by remember { mutableStateOf(null) } var permissionsGranted by remember { mutableStateOf(false) } - var knownDevices by remember { mutableStateOf(KnownDeviceStore.getAll(context)) } + // Clinical 흐름에서만 다중 기기 저장/표시 — 일반 사용자는 fresh scan & pair. + val isClinicalFlow = com.example.medilightv2android.services.ClinicalSessionStore.inClinicalFlow + var knownDevices by remember { + mutableStateOf(if (isClinicalFlow) KnownDeviceStore.getAll(context) else emptyList()) + } var pendingDelete by remember { mutableStateOf(null) } val knownAddresses = knownDevices.map { it.address }.toSet() val newDevices = bleManager.discoveredDevices