BLE fix: 배터리 polling 실제로 polling — 첫 응답 재시도 + 30s 주기
증상: 연결 직후 가끔 배터리 값이 0%로 남아 표시 안 됨 / 의미 없는 값. 원인: startBatteryPolling()이 이름과 달리 연결 시 1회 msn? 송신만 함. 그 한 번이 priority HIGH 협상 직전 큐로 들어가거나, 직후 mtb?/maa? 측정 루프와 큐 경합으로 손실되면 영원히 batteryLevel.value = 0 으로 남음. 이후 측정 응답에 배터리가 안 들어있어 갱신 기회 없음. 수정: 세 단계 보장 구조로 변경 1. 즉시 1회 송신 (기존 동작 유지) 2. 3s 뒤 batteryLevel == 0 이면 최대 2회 재송신 (첫 응답 누락 보강) 3. 30s 주기 정상 polling 추가 (충전 반영 + 장기 추적) stopBatteryPolling()도 retry/poll 양쪽 타이머 정리하도록 확장. 끊김 시 isConnected.value == false이면 콜백에서 조기 return하여 좀비 송신 방지. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,10 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
val debugLogger = com.example.medilightv2android.services.BleDebugLogger.getInstance(context)
|
val debugLogger = com.example.medilightv2android.services.BleDebugLogger.getInstance(context)
|
||||||
private var scanTimer: Runnable? = null
|
private var scanTimer: Runnable? = null
|
||||||
private var batteryTimer: 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 connectionTimer: Runnable? = null
|
||||||
private var isUserDisconnect = false
|
private var isUserDisconnect = false
|
||||||
var lastConnectedAddress: String? = null
|
var lastConnectedAddress: String? = null
|
||||||
@@ -514,13 +518,54 @@ class BleManager private constructor(private val context: Context) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배터리 polling 시작.
|
||||||
|
*
|
||||||
|
* 이전엔 "1회 송신만" 이었고, 그 한 번이 큐 경합/타이밍 문제로 손실되면 배터리가
|
||||||
|
* 영원히 0%로 표시되는 이슈가 있었음. 이제 세 단계로 보장:
|
||||||
|
* 1. 즉시 1회 송신 (best case)
|
||||||
|
* 2. 3초 내 응답 없으면(batteryLevel.value == 0) 최대 2회 재송신 (첫 응답 누락 보강)
|
||||||
|
* 3. 30초 주기 정상 polling (충전 반영 + 장기 추적)
|
||||||
|
*
|
||||||
|
* BLE 끊김 / 측정 시작 시 stopBatteryPolling()으로 정리.
|
||||||
|
*/
|
||||||
fun startBatteryPolling() {
|
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() {
|
fun stopBatteryPolling() {
|
||||||
batteryTimer?.let { handler.removeCallbacks(it) }
|
batteryTimer?.let { handler.removeCallbacks(it) }
|
||||||
batteryTimer = null
|
batteryTimer = null
|
||||||
|
batteryRetryTimer?.let { handler.removeCallbacks(it) }
|
||||||
|
batteryRetryTimer = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Watchdog (zombie connection detection, runs on separate thread) ──
|
// ── Watchdog (zombie connection detection, runs on separate thread) ──
|
||||||
|
|||||||
Reference in New Issue
Block a user