fix(ble): unblock reconnect + kable audit (logging, priority, backoff, StateFlow) (#5222)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com>
This commit is contained in:
James Rich
2026-04-22 18:35:22 -05:00
committed by GitHub
parent 939132f2ae
commit 3a4cae5d0b
16 changed files with 302 additions and 95 deletions

View File

@@ -20,9 +20,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
@@ -36,6 +34,7 @@ import org.meshtastic.core.ble.BleService
import org.meshtastic.core.ble.BleWriteType
import org.meshtastic.core.ble.BluetoothRepository
import org.meshtastic.core.ble.BluetoothState
import org.meshtastic.core.ble.DisconnectReason
import kotlin.time.Duration
import kotlin.uuid.Uuid
@@ -91,11 +90,10 @@ class FakeBleConnection :
override val device: BleDevice?
get() = _device.value
private val _deviceFlow = mutableSharedFlow<BleDevice?>(replay = 1)
override val deviceFlow: SharedFlow<BleDevice?> = _deviceFlow.asSharedFlow()
override val deviceFlow: StateFlow<BleDevice?> = _device.asStateFlow()
private val _connectionState = mutableSharedFlow<BleConnectionState>(replay = 1)
override val connectionState: SharedFlow<BleConnectionState> = _connectionState.asSharedFlow()
private val _connectionState = mutableStateFlow<BleConnectionState>(BleConnectionState.Disconnected())
override val connectionState: StateFlow<BleConnectionState> = _connectionState.asStateFlow()
/** When > 0, the next [failNextN] calls to [connectAndAwait] return [BleConnectionState.Disconnected]. */
var failNextN: Int = 0
@@ -109,6 +107,14 @@ class FakeBleConnection :
/** Number of times [disconnect] has been invoked. */
var disconnectCalls: Int = 0
/** Number of times [connectAndAwait] has been invoked (including failures). */
var connectAndAwaitCalls: Int = 0
/** Externally simulate a remote disconnect (e.g. node power-cycle) for tests that exercise reconnect. */
fun simulateRemoteDisconnect(reason: DisconnectReason = DisconnectReason.Timeout) {
_connectionState.value = BleConnectionState.Disconnected(reason)
}
/** Service UUIDs that should appear missing — `profile()` throws `NoSuchElementException` for these. */
val missingServices: MutableSet<Uuid> = mutableSetOf()
@@ -116,18 +122,18 @@ class FakeBleConnection :
override suspend fun connect(device: BleDevice) {
_device.value = device
_deviceFlow.emit(device)
_connectionState.emit(BleConnectionState.Connecting)
_connectionState.value = BleConnectionState.Connecting
if (device is FakeBleDevice) {
device.setState(BleConnectionState.Connecting)
}
_connectionState.emit(BleConnectionState.Connected)
_connectionState.value = BleConnectionState.Connected
if (device is FakeBleDevice) {
device.setState(BleConnectionState.Connected)
}
}
override suspend fun connectAndAwait(device: BleDevice, timeout: Duration): BleConnectionState {
connectAndAwaitCalls++
connectException?.let { throw it }
if (failNextN > 0) {
failNextN--
@@ -140,12 +146,11 @@ class FakeBleConnection :
override suspend fun disconnect() {
disconnectCalls++
val currentDevice = _device.value
_connectionState.emit(BleConnectionState.Disconnected())
_connectionState.value = BleConnectionState.Disconnected()
if (currentDevice is FakeBleDevice) {
currentDevice.setState(BleConnectionState.Disconnected())
}
_device.value = null
_deviceFlow.emit(null)
}
override suspend fun <T> profile(