diff --git a/android/app/src/main/java/cloud/molberg/picar/BleClient.kt b/android/app/src/main/java/cloud/molberg/picar/BleClient.kt index c985ff5..cc8e093 100644 --- a/android/app/src/main/java/cloud/molberg/picar/BleClient.kt +++ b/android/app/src/main/java/cloud/molberg/picar/BleClient.kt @@ -12,6 +12,7 @@ import android.os.ParcelUuid import android.os.Handler import android.os.Looper import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withTimeout import org.json.JSONObject @@ -36,19 +37,35 @@ class BleClient(private val context: Context) { private val writes = ArrayDeque() private var writeInProgress = false private val handler = Handler(Looper.getMainLooper()) + private val reconnect: Runnable = Runnable { connect() } + private val restartScan: Runnable = Runnable { + if (!stopped && state.value == ConnectionState.SCANNING) { + bluetoothManager.adapter?.bluetoothLeScanner?.stopScan(scanCallback) + scheduleReconnect(1_000) + } + } private var stopped = true fun connect() { stopped = false - val adapter = bluetoothManager.adapter ?: return + handler.removeCallbacks(reconnect) + handler.removeCallbacks(restartScan) + val adapter = bluetoothManager.adapter ?: run { + state.value = ConnectionState.DISCONNECTED + scheduleReconnect() + return + } + adapter.bluetoothLeScanner?.stopScan(scanCallback) state.value = ConnectionState.SCANNING val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build() adapter.bluetoothLeScanner?.startScan(listOf(filter), ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(), scanCallback) + handler.postDelayed(restartScan, 15_000) } fun disconnect() { stopped = true - handler.removeCallbacksAndMessages(null) + handler.removeCallbacks(reconnect) + handler.removeCallbacks(restartScan) bluetoothManager.adapter?.bluetoothLeScanner?.stopScan(scanCallback) gatt?.close(); gatt = null; rx = null resetWrites() @@ -64,21 +81,30 @@ class BleClient(private val context: Context) { enqueue((message.toString() + "\n").toByteArray()) return try { withTimeout(20_000) { deferred.await() } + } catch (error: TimeoutCancellationException) { + handler.post { gatt?.disconnect() } + throw error } finally { pending.remove(id) } } - private val scanCallback = object : ScanCallback() { + private val scanCallback: ScanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { + handler.removeCallbacks(restartScan) bluetoothManager.adapter.bluetoothLeScanner?.stopScan(this) state.value = ConnectionState.CONNECTING + gatt?.close() gatt = result.device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) } - override fun onScanFailed(errorCode: Int) { state.value = ConnectionState.DISCONNECTED } + override fun onScanFailed(errorCode: Int) { + handler.removeCallbacks(restartScan) + state.value = ConnectionState.DISCONNECTED + scheduleReconnect() + } } - private val gattCallback = object : BluetoothGattCallback() { + private val gattCallback: BluetoothGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { this@BleClient.gatt = gatt @@ -88,8 +114,10 @@ class BleClient(private val context: Context) { if (!gatt.requestMtu(247)) gatt.discoverServices() } else { - rx = null; gatt.close(); resetWrites(); state.value = ConnectionState.DISCONNECTED - if (!stopped) handler.postDelayed({ connect() }, 3_000) + rx = null; gatt.close() + if (this@BleClient.gatt === gatt) this@BleClient.gatt = null + resetWrites(); state.value = ConnectionState.DISCONNECTED + scheduleReconnect() } } @@ -195,4 +223,10 @@ class BleClient(private val context: Context) { writes.clear() writeInProgress = false } + + private fun scheduleReconnect(delayMillis: Long = 3_000) { + if (stopped) return + handler.removeCallbacks(reconnect) + handler.postDelayed(reconnect, delayMillis) + } } diff --git a/apks/pi-car-companion-debug.apk b/apks/pi-car-companion-debug.apk index 8e4c5bb..8ecca12 100644 Binary files a/apks/pi-car-companion-debug.apk and b/apks/pi-car-companion-debug.apk differ