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 a006fac..523c1a2 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -58,6 +58,10 @@ class BleManager private constructor(private val context: Context) { val debugLogger = com.example.medilightv2android.services.BleDebugLogger.getInstance(context) private var scanTimer: Runnable? = null private var batteryTimer: Runnable? = null + private var batteryRetryTimer: Runnable? = null + private val BATTERY_POLL_INTERVAL_MS = 30_000L // 정상 polling 간격 + private val BATTERY_RETRY_DELAY_MS = 3_000L // 첫 응답 재시도 지연 + private val BATTERY_RETRY_MAX = 2 // 최대 재시도 횟수 private var connectionTimer: Runnable? = null private var isUserDisconnect = false var lastConnectedAddress: String? = null @@ -514,13 +518,54 @@ class BleManager private constructor(private val context: Context) { return true } + /** + * 배터리 polling 시작. + * + * 이전엔 "1회 송신만" 이었고, 그 한 번이 큐 경합/타이밍 문제로 손실되면 배터리가 + * 영원히 0%로 표시되는 이슈가 있었음. 이제 세 단계로 보장: + * 1. 즉시 1회 송신 (best case) + * 2. 3초 내 응답 없으면(batteryLevel.value == 0) 최대 2회 재송신 (첫 응답 누락 보강) + * 3. 30초 주기 정상 polling (충전 반영 + 장기 추적) + * + * BLE 끊김 / 측정 시작 시 stopBatteryPolling()으로 정리. + */ fun startBatteryPolling() { - sendBatteryQuery() // 연결 시 1회만 + stopBatteryPolling() // 기존 타이머 안전 정리 + sendBatteryQuery() // 1단계: 즉시 1회 + + // 2단계: 첫 응답 재시도 — batteryLevel.value가 0인 동안 재송신 + var retries = 0 + val retryRunnable = object : Runnable { + override fun run() { + if (!isConnected.value) return + if (batteryLevel.value == 0 && retries < BATTERY_RETRY_MAX) { + retries++ + logd { "battery query retry #$retries (no response yet)" } + sendBatteryQuery() + handler.postDelayed(this, BATTERY_RETRY_DELAY_MS) + } + } + } + batteryRetryTimer = retryRunnable + handler.postDelayed(retryRunnable, BATTERY_RETRY_DELAY_MS) + + // 3단계: 30초 주기 polling + val pollRunnable = object : Runnable { + override fun run() { + if (!isConnected.value) return + sendBatteryQuery() + handler.postDelayed(this, BATTERY_POLL_INTERVAL_MS) + } + } + batteryTimer = pollRunnable + handler.postDelayed(pollRunnable, BATTERY_POLL_INTERVAL_MS) } fun stopBatteryPolling() { batteryTimer?.let { handler.removeCallbacks(it) } batteryTimer = null + batteryRetryTimer?.let { handler.removeCallbacks(it) } + batteryRetryTimer = null } // ── Watchdog (zombie connection detection, runs on separate thread) ──