From 9d32d134521474c6d7183fe293b78bce418efbea Mon Sep 17 00:00:00 2001 From: jjangddu Date: Thu, 23 Apr 2026 14:02:08 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20BLE=20reconnect=EB=A5=BC=20fresh=20scan?= =?UTF-8?q?=E2=86=92connect=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존: connectGatt(autoConnect=true) — Android에서 매우 불안정, 수분 걸림 변경: BLE 스캔으로 기기를 다시 찾은 후 connectGatt(autoConnect=false)로 빠르게 연결 재연결 흐름: 1. watchdog 15초 타임아웃 → forceDisconnectAndReconnect() 2. scheduleAutoReconnect() → 3s/5s/10s 백오프 3. attemptAutoReconnect(): - BLE 스캔 시작 (기기 이름 or MAC 주소 매칭) - 발견 시 즉시 connectGatt(autoConnect=false) → 빠른 연결 - 8초 스캔 타임아웃 → 다음 attempt - 최대 5회 시도 후 실패 처리 4. 모든 단계 BleDebugLogger에 기록 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../medilightv2android/ble/BleManager.kt | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 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 b4b7115..d96607b 100644 --- a/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt +++ b/app/src/main/java/com/example/medilightv2android/ble/BleManager.kt @@ -418,8 +418,11 @@ class BleManager private constructor(private val context: Context) { handler.postDelayed(timer, delay) } + private var reconnectScanCallback: ScanCallback? = null + private fun attemptAutoReconnect() { - val address = lastConnectedAddress ?: return + val targetAddress = lastConnectedAddress ?: return + val targetName = debugLogger.deviceName if (bluetoothAdapter?.isEnabled != true) { onReconnectionFailed?.invoke() isReconnecting.value = false @@ -427,15 +430,69 @@ class BleManager private constructor(private val context: Context) { } reconnectAttempt++ - logd { "Attempting auto-reconnect #$reconnectAttempt to $address" } + logd { "Auto-reconnect #$reconnectAttempt: scanning for $targetName ($targetAddress)" } + debugLogger.info("RECONNECT_SCAN #$reconnectAttempt scanning for $targetName") bluetoothGatt?.let { gatt -> try { gatt.close() } catch (_: Exception) {} bluetoothGatt = null } - val device = bluetoothAdapter.getRemoteDevice(address) - bluetoothGatt = device.connectGatt(context, true, gattCallback, BluetoothDevice.TRANSPORT_LE) + // Fresh scan to find the device again + val scanner = bluetoothAdapter.bluetoothLeScanner + if (scanner == null) { + scheduleAutoReconnect() + return + } + + // Stop any previous reconnect scan + reconnectScanCallback?.let { + try { scanner.stopScan(it) } catch (_: Exception) {} + } + + val callback = object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + val name = try { result.device.name ?: "" } catch (_: Exception) { "" } + val address = result.device.address + + if (address == targetAddress || (targetName.isNotEmpty() && name == targetName)) { + logd { "Reconnect scan found: $name ($address)" } + debugLogger.info("RECONNECT_FOUND $name ($address) RSSI=${result.rssi}") + + try { scanner.stopScan(this) } catch (_: Exception) {} + reconnectScanCallback = null + + // Connect directly (autoConnect=false for fast connection) + bluetoothGatt = result.device.connectGatt( + context, false, gattCallback, BluetoothDevice.TRANSPORT_LE + ) + } + } + } + reconnectScanCallback = callback + + try { + scanner.startScan(callback) + } catch (e: Exception) { + debugLogger.error("RECONNECT_SCAN_FAIL ${e.message}") + scheduleAutoReconnect() + return + } + + // Stop scan after 8 seconds → retry next attempt + handler.postDelayed({ + try { scanner.stopScan(callback) } catch (_: Exception) {} + reconnectScanCallback = null + + if (!isConnected.value && reconnectAttempt < maxReconnectAttempts) { + debugLogger.warn("RECONNECT_SCAN_TIMEOUT #$reconnectAttempt — device not found") + scheduleAutoReconnect() + } else if (!isConnected.value) { + debugLogger.reconnectFailed() + isReconnecting.value = false + onReconnectionFailed?.invoke() + } + }, 8000) } private fun cancelReconnectTimer() {