ble: 일반 사용자는 KnownDeviceStore 저장 0개 + 펌웨어 미수신 경고

다중 기기 페어링 저장은 Clinical 흐름 전용.
일반 사용자는 매 세션 fresh scan + pair — 저장 기능 자체 미노출.

- BleManager.connect 성공 시: ClinicalSessionStore.inClinicalFlow
  체크 → false 면 KnownDeviceStore.add() skip
- DeviceScanView: 같은 조건으로 Saved Devices 섹션 노출 X.
  knownDevices = emptyList() → 기존 단일 디바이스 리스트 UX

+ FirmwareWarningBanner 케이스 추가:
  연결 후 8초간 mid? 응답 미수신 → 빨강 경고
  "펌웨어 응답 없음 — 펌웨어 호환 문제 가능"
  사용자가 정확한 원인 파악 가능 (구버전 != 응답 자체 안 옴).
This commit is contained in:
2026-06-11 15:34:09 +09:00
parent 1492030333
commit f85b5cfbed
3 changed files with 53 additions and 21 deletions
@@ -869,11 +869,15 @@ class BleManager private constructor(private val context: Context) {
cancelReconnectTimer()
debugLogger.connected(connectedDeviceName.value, gatt.device.address)
// 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 호출
@@ -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))
}
}
}
@@ -52,7 +52,11 @@ fun DeviceScanView(appState: AppState) {
var connectingDeviceAddress by remember { mutableStateOf<String?>(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<KnownDevice?>(null) }
val knownAddresses = knownDevices.map { it.address }.toSet()
val newDevices = bleManager.discoveredDevices