From 55b17857bef28cf29e9f288da8fe8116f44843e7 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 11 Feb 2026 11:14:46 -0600
Subject: [PATCH] chore(deps): update nordic.ble to v2.0.0-alpha13 (#4534)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: James Rich <2199651+jamesarich@users.noreply.github.com>
---
app/build.gradle.kts | 5 +-
.../bluetooth/BluetoothRepository.kt | 13 ++--
.../bluetooth/BluetoothRepositoryModule.kt | 13 ++--
.../mesh/repository/radio/BleError.kt | 2 +-
.../repository/radio/NordicBleInterface.kt | 29 ++++----
.../radio/NordicBleInterfaceDrainTest.kt | 14 ++--
.../radio/NordicBleInterfaceRetryTest.kt | 24 +++----
.../radio/NordicBleInterfaceTest.kt | 54 +++++++-------
.../meshtastic/buildlogic/KotlinAndroid.kt | 3 +-
feature/firmware/build.gradle.kts | 4 +-
.../feature/firmware/ota/BleOtaTransport.kt | 72 ++++++++-----------
.../firmware/ota/BleOtaTransportErrorTest.kt | 63 ++++++++--------
.../firmware/ota/BleOtaTransportMtuTest.kt | 18 ++---
.../ota/BleOtaTransportNordicMockTest.kt | 17 ++---
.../firmware/ota/BleOtaTransportTest.kt | 18 ++---
gradle/libs.versions.toml | 9 +--
16 files changed, 169 insertions(+), 189 deletions(-)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 8511eb515..22f5bb92b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -246,6 +246,7 @@ dependencies {
implementation(libs.kermit)
implementation(libs.nordic.client.android)
+ implementation(libs.nordic.environment.android)
debugImplementation(libs.androidx.compose.ui.test.manifest)
@@ -264,9 +265,9 @@ dependencies {
testImplementation(libs.mockk)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.nordic.client.android.mock)
- testImplementation(libs.nordic.client.mock)
+ testImplementation(libs.nordic.client.core.mock)
testImplementation(libs.nordic.core.mock)
- testImplementation(libs.nordic.core.android.mock)
+ testImplementation(libs.nordic.environment.android.mock)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.test.core)
}
diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt
index f41869beb..65d9f058f 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.repository.bluetooth
import android.annotation.SuppressLint
@@ -47,9 +46,9 @@ import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.seconds
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.toKotlinUuid
/** Repository responsible for maintaining and updating the state of Bluetooth availability. */
+@OptIn(ExperimentalUuidApi::class)
@Singleton
class BluetoothRepository
@Inject
@@ -95,7 +94,6 @@ constructor(
fun isValid(bleAddress: String): Boolean = BluetoothAdapter.checkBluetoothAddress(bleAddress)
/** Starts a BLE scan for Meshtastic devices. The results are published to the [scannedDevices] flow. */
- @OptIn(ExperimentalUuidApi::class)
@SuppressLint("MissingPermission")
fun startScan() {
if (isScanning.value) return
@@ -106,7 +104,7 @@ constructor(
scanJob =
processLifecycle.coroutineScope.launch(dispatchers.default) {
centralManager
- .scan(5.seconds) { ServiceUuid(BTM_SERVICE_UUID.toKotlinUuid()) }
+ .scan(5.seconds) { ServiceUuid(BTM_SERVICE_UUID) }
.distinctByPeripheral()
.map { it.peripheral }
.onStart { _isScanning.value = true }
@@ -146,7 +144,6 @@ constructor(
refreshState()
}
- @OptIn(ExperimentalUuidApi::class)
internal suspend fun updateBluetoothState() {
val hasPerms = application.hasBluetoothPermission()
val enabled = centralManager.state.value == Manager.State.POWERED_ON
@@ -170,11 +167,9 @@ constructor(
}
/** Checks if a peripheral is one of ours, either by its advertised name or by the services it provides. */
- @OptIn(ExperimentalUuidApi::class)
private fun isMatchingPeripheral(peripheral: Peripheral): Boolean {
val nameMatches = peripheral.name?.matches(Regex(BLE_NAME_PATTERN)) ?: false
- val hasRequiredService =
- peripheral.services(listOf(BTM_SERVICE_UUID.toKotlinUuid())).value?.isNotEmpty() ?: false
+ val hasRequiredService = peripheral.services(listOf(BTM_SERVICE_UUID)).value?.isNotEmpty() ?: false
return nameMatches || hasRequiredService
}
diff --git a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepositoryModule.kt b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepositoryModule.kt
index 2c1e5e569..16a42ea73 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepositoryModule.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/bluetooth/BluetoothRepositoryModule.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025 Meshtastic LLC
+ * Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-
package com.geeksville.mesh.repository.bluetooth
import android.content.Context
@@ -28,6 +27,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import no.nordicsemi.kotlin.ble.client.android.CentralManager
import no.nordicsemi.kotlin.ble.client.android.native
+import no.nordicsemi.kotlin.ble.environment.android.NativeAndroidEnvironment
import javax.inject.Singleton
@Module
@@ -35,8 +35,13 @@ import javax.inject.Singleton
object BluetoothRepositoryModule {
@Provides
@Singleton
- fun provideCentralManager(@ApplicationContext context: Context, coroutineScope: CoroutineScope): CentralManager =
- CentralManager.native(context, coroutineScope)
+ fun provideAndroidEnvironment(@ApplicationContext context: Context): NativeAndroidEnvironment =
+ NativeAndroidEnvironment.getInstance(context, isNeverForLocationFlagSet = true)
+
+ @Provides
+ @Singleton
+ fun provideCentralManager(environment: NativeAndroidEnvironment, coroutineScope: CoroutineScope): CentralManager =
+ CentralManager.native(environment, coroutineScope)
@Provides
@Singleton
diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/BleError.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/BleError.kt
index af35efae7..2e7c25f1f 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/radio/BleError.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/radio/BleError.kt
@@ -17,7 +17,6 @@
package com.geeksville.mesh.repository.radio
import com.geeksville.mesh.service.RadioNotConnectedException
-import no.nordicsemi.kotlin.ble.client.exception.BluetoothUnavailableException
import no.nordicsemi.kotlin.ble.client.exception.ConnectionFailedException
import no.nordicsemi.kotlin.ble.client.exception.InvalidAttributeException
import no.nordicsemi.kotlin.ble.client.exception.OperationFailedException
@@ -26,6 +25,7 @@ import no.nordicsemi.kotlin.ble.client.exception.ScanningException
import no.nordicsemi.kotlin.ble.client.exception.ValueDoesNotMatchException
import no.nordicsemi.kotlin.ble.core.ConnectionState
import no.nordicsemi.kotlin.ble.core.exception.BluetoothException
+import no.nordicsemi.kotlin.ble.core.exception.BluetoothUnavailableException
import no.nordicsemi.kotlin.ble.core.exception.GattException
import no.nordicsemi.kotlin.ble.core.exception.ManagerClosedException
diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt
index d7b9a71e2..eb191c2ae 100644
--- a/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt
+++ b/app/src/main/java/com/geeksville/mesh/repository/radio/NordicBleInterface.kt
@@ -52,9 +52,8 @@ import no.nordicsemi.kotlin.ble.client.exception.InvalidAttributeException
import no.nordicsemi.kotlin.ble.core.CharacteristicProperty
import no.nordicsemi.kotlin.ble.core.ConnectionState
import no.nordicsemi.kotlin.ble.core.WriteType
-import java.util.UUID
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.toKotlinUuid
+import kotlin.uuid.Uuid
/**
* A [IRadioInterface] implementation for BLE devices using Nordic Kotlin BLE Library.
@@ -67,6 +66,7 @@ import kotlin.uuid.toKotlinUuid
* @param service The [RadioInterfaceService] to use for handling radio events.
* @param address The BLE address of the device to connect to.
*/
+@OptIn(ExperimentalUuidApi::class)
@SuppressLint("MissingPermission")
class NordicBleInterface
@AssistedInject
@@ -251,23 +251,22 @@ constructor(
}
@Suppress("TooGenericExceptionCaught")
- @OptIn(ExperimentalUuidApi::class)
private fun discoverServicesAndSetupCharacteristics(peripheral: Peripheral) {
connectionScope.launch {
peripheral
- .services(listOf(BTM_SERVICE_UUID.toKotlinUuid()))
+ .services(listOf(BTM_SERVICE_UUID))
.onEach { services ->
- val meshtasticService = services?.find { it.uuid == BTM_SERVICE_UUID.toKotlinUuid() }
+ val meshtasticService = services?.find { it.uuid == BTM_SERVICE_UUID }
if (meshtasticService != null) {
toRadioCharacteristic =
- meshtasticService.characteristics.find { it.uuid == BTM_TORADIO_CHARACTER.toKotlinUuid() }
+ meshtasticService.characteristics.find { it.uuid == BTM_TORADIO_CHARACTER }
fromNumCharacteristic =
- meshtasticService.characteristics.find { it.uuid == BTM_FROMNUM_CHARACTER.toKotlinUuid() }
+ meshtasticService.characteristics.find { it.uuid == BTM_FROMNUM_CHARACTER }
fromRadioCharacteristic =
- meshtasticService.characteristics.find { it.uuid == BTM_FROMRADIO_CHARACTER.toKotlinUuid() }
+ meshtasticService.characteristics.find { it.uuid == BTM_FROMRADIO_CHARACTER }
logRadioCharacteristic =
- meshtasticService.characteristics.find { it.uuid == BTM_LOGRADIO_CHARACTER.toKotlinUuid() }
+ meshtasticService.characteristics.find { it.uuid == BTM_LOGRADIO_CHARACTER }
if (
listOf(toRadioCharacteristic, fromNumCharacteristic, fromRadioCharacteristic).all {
@@ -312,7 +311,6 @@ constructor(
// --- Notification Setup ---
- @OptIn(ExperimentalUuidApi::class)
private suspend fun setupNotifications() {
retryCall { fromNumCharacteristic?.subscribe() }
?.onStart { Logger.d { "[$address] Subscribing to fromNumCharacteristic" } }
@@ -451,11 +449,12 @@ constructor(
}
}
+@OptIn(ExperimentalUuidApi::class)
object BleConstants {
const val BLE_NAME_PATTERN = "^.*_([0-9a-fA-F]{4})$"
- val BTM_SERVICE_UUID: UUID = UUID.fromString("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
- val BTM_TORADIO_CHARACTER: UUID = UUID.fromString("f75c76d2-129e-4dad-a1dd-7866124401e7")
- val BTM_FROMNUM_CHARACTER: UUID = UUID.fromString("ed9da18c-a800-4f66-a670-aa7547e34453")
- val BTM_FROMRADIO_CHARACTER: UUID = UUID.fromString("2c55e69e-4993-11ed-b878-0242ac120002")
- val BTM_LOGRADIO_CHARACTER: UUID = UUID.fromString("5a3d6e49-06e6-4423-9944-e9de8cdf9547")
+ val BTM_SERVICE_UUID: Uuid = Uuid.parse("6ba1b218-15a8-461f-9fa8-5dcae273eafd")
+ val BTM_TORADIO_CHARACTER: Uuid = Uuid.parse("f75c76d2-129e-4dad-a1dd-7866124401e7")
+ val BTM_FROMNUM_CHARACTER: Uuid = Uuid.parse("ed9da18c-a800-4f66-a670-aa7547e34453")
+ val BTM_FROMRADIO_CHARACTER: Uuid = Uuid.parse("2c55e69e-4993-11ed-b878-0242ac120002")
+ val BTM_LOGRADIO_CHARACTER: Uuid = Uuid.parse("5a3d6e49-06e6-4423-9944-e9de8cdf9547")
}
diff --git a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceDrainTest.kt b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceDrainTest.kt
index 1d2778ed7..a575b757d 100644
--- a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceDrainTest.kt
+++ b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceDrainTest.kt
@@ -37,10 +37,8 @@ import no.nordicsemi.kotlin.ble.core.LegacyAdvertisingSetParameters
import no.nordicsemi.kotlin.ble.core.Permission
import org.junit.Ignore
import org.junit.Test
-import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.Uuid
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class NordicBleInterfaceDrainTest {
@@ -48,8 +46,6 @@ class NordicBleInterfaceDrainTest {
private val testDispatcher = StandardTestDispatcher()
private val address = "00:11:22:33:44:55"
- private fun UUID.toKotlinUuid(): Uuid = Uuid.parse(this.toString())
-
@Ignore("Flaky: relies on timing in the Nordic BLE mock library which causes intermittent CI failures")
@Test
fun `drainPacketQueueAndDispatch reads multiple packets until empty`() = runTest(testDispatcher) {
@@ -95,9 +91,9 @@ class NordicBleInterfaceDrainTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -107,18 +103,18 @@ class NordicBleInterfaceDrainTest {
)
fromNumHandle =
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
fromRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
diff --git a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceRetryTest.kt b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceRetryTest.kt
index 9d23cb2db..02ea3914c 100644
--- a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceRetryTest.kt
+++ b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceRetryTest.kt
@@ -38,10 +38,8 @@ import no.nordicsemi.kotlin.ble.core.LegacyAdvertisingSetParameters
import no.nordicsemi.kotlin.ble.core.Permission
import org.junit.Before
import org.junit.Test
-import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.Uuid
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class NordicBleInterfaceRetryTest {
@@ -49,8 +47,6 @@ class NordicBleInterfaceRetryTest {
private val testDispatcher = UnconfinedTestDispatcher()
private val address = "00:11:22:33:44:55"
- private fun UUID.toKotlinUuid(): Uuid = Uuid.parse(this.toString())
-
@Before
fun setup() {
Logger.setLogWriters(
@@ -106,10 +102,10 @@ class NordicBleInterfaceRetryTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
toRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -118,17 +114,17 @@ class NordicBleInterfaceRetryTest {
permission = Permission.WRITE,
)
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
@@ -211,10 +207,10 @@ class NordicBleInterfaceRetryTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
toRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -223,17 +219,17 @@ class NordicBleInterfaceRetryTest {
permission = Permission.WRITE,
)
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
diff --git a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceTest.kt b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceTest.kt
index d5c5344b7..0f011e97b 100644
--- a/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceTest.kt
+++ b/app/src/test/java/com/geeksville/mesh/repository/radio/NordicBleInterfaceTest.kt
@@ -39,10 +39,8 @@ import no.nordicsemi.kotlin.ble.core.Permission
import no.nordicsemi.kotlin.ble.core.and
import org.junit.Before
import org.junit.Test
-import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.Uuid
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class NordicBleInterfaceTest {
@@ -50,8 +48,6 @@ class NordicBleInterfaceTest {
private val testDispatcher = UnconfinedTestDispatcher()
private val address = "00:11:22:33:44:55"
- private fun UUID.toKotlinUuid(): Uuid = Uuid.parse(this.toString())
-
@Before
fun setup() {
Logger.setLogWriters(
@@ -107,28 +103,28 @@ class NordicBleInterfaceTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
permission = Permission.WRITE,
)
fromNumHandle =
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
fromRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
logRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
@@ -240,10 +236,10 @@ class NordicBleInterfaceTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
toRadioHandle =
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -257,17 +253,17 @@ class NordicBleInterfaceTest {
}
// Add other required chars to avoid discovery failure
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
@@ -338,9 +334,9 @@ class NordicBleInterfaceTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -349,17 +345,17 @@ class NordicBleInterfaceTest {
permission = Permission.WRITE,
)
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
@@ -429,27 +425,27 @@ class NordicBleInterfaceTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
// OMIT toRadio characteristic to force failure
/*
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties = setOf(CharacteristicProperty.WRITE, CharacteristicProperty.WRITE_WITHOUT_RESPONSE),
permission = Permission.WRITE
)
*/
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
@@ -513,9 +509,9 @@ class NordicBleInterfaceTest {
isBonded = true,
eventHandler = eventHandler,
cachedServices = {
- Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = BleConstants.BTM_SERVICE_UUID) {
Characteristic(
- uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_TORADIO_CHARACTER,
properties =
setOf(
CharacteristicProperty.WRITE,
@@ -524,17 +520,17 @@ class NordicBleInterfaceTest {
permission = Permission.WRITE,
)
Characteristic(
- uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMNUM_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_FROMRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.READ),
permission = Permission.READ,
)
Characteristic(
- uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
+ uuid = BleConstants.BTM_LOGRADIO_CHARACTER,
properties = setOf(CharacteristicProperty.NOTIFY),
permission = Permission.READ,
)
diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt
index e0cf40cf0..7d4dbfe92 100644
--- a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt
+++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt
@@ -106,7 +106,8 @@ private inline fun Project.configureKotlin() {
// Enable experimental coroutines APIs, including Flow
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xcontext-parameters",
- "-Xannotation-default-target=param-property"
+ "-Xannotation-default-target=param-property",
+ "-Xskip-prerelease-check"
)
}
}
diff --git a/feature/firmware/build.gradle.kts b/feature/firmware/build.gradle.kts
index 2650c8375..51c668d31 100644
--- a/feature/firmware/build.gradle.kts
+++ b/feature/firmware/build.gradle.kts
@@ -78,8 +78,8 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.nordic.client.android.mock)
- testImplementation(libs.nordic.client.mock)
- testImplementation(libs.nordic.core.android.mock)
+ testImplementation(libs.nordic.client.core.mock)
testImplementation(libs.nordic.core.mock)
+ testImplementation(libs.nordic.environment.android.mock)
testImplementation(libs.mockk)
}
diff --git a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransport.kt b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransport.kt
index db48737b6..1891858fb 100644
--- a/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransport.kt
+++ b/feature/firmware/src/main/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransport.kt
@@ -36,10 +36,9 @@ import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.client.distinctByPeripheral
import no.nordicsemi.kotlin.ble.core.ConnectionState
import no.nordicsemi.kotlin.ble.core.WriteType
-import java.util.UUID
import kotlin.time.Duration.Companion.seconds
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.toKotlinUuid
+import kotlin.uuid.Uuid
/**
* BLE transport implementation for ESP32 Unified OTA protocol. Uses Nordic Kotlin-BLE-Library for modern coroutine
@@ -49,6 +48,7 @@ import kotlin.uuid.toKotlinUuid
* - OTA Characteristic (Write): 62ec0272-3ec5-11eb-b378-0242ac130005
* - TX Characteristic (Notify): 62ec0272-3ec5-11eb-b378-0242ac130003
*/
+@OptIn(ExperimentalUuidApi::class)
class BleOtaTransport(
private val centralManager: CentralManager,
private val address: String,
@@ -74,38 +74,33 @@ class BleOtaTransport(
* ESP32 bootloaders may use the original MAC address OR increment the last byte by 1 for OTA mode, so we check both
* addresses.
*/
- @OptIn(ExperimentalUuidApi::class)
private suspend fun scanForOtaDevice(): Peripheral? {
// ESP32 OTA bootloader may use MAC address with last byte incremented by 1
val otaAddress = calculateOtaAddress(address)
- val targetAddresses = setOf(address, otaAddress)
- Logger.i { "BLE OTA: Will match addresses: $targetAddresses" }
+ Logger.i { "BLE OTA: Will match addresses: $address, $otaAddress" }
repeat(SCAN_RETRY_COUNT) { attempt ->
Logger.i { "BLE OTA: Scanning for device (attempt ${attempt + 1}/$SCAN_RETRY_COUNT)..." }
- // Scan without service UUID filter - ESP32 OTA bootloader may not advertise the UUID
- // Log all devices found during scan for debugging
- val foundDevices = mutableSetOf()
+ // Use the new scan DSL for better efficiency
val peripheral =
centralManager
- .scan(SCAN_TIMEOUT)
- .distinctByPeripheral()
- .map { it.peripheral }
- .onEach { p ->
- if (foundDevices.add(p.address)) {
- Logger.d { "BLE OTA: Scan found device: ${p.address} (name=${p.name})" }
+ .scan(SCAN_TIMEOUT) {
+ Any {
+ Address(address)
+ Address(otaAddress)
}
}
- .firstOrNull { it.address in targetAddresses }
+ .distinctByPeripheral()
+ .map { it.peripheral }
+ .onEach { p -> Logger.d { "BLE OTA: Scan found matching device: ${p.address} (name=${p.name})" } }
+ .firstOrNull()
if (peripheral != null) {
Logger.i { "BLE OTA: Found target device at ${peripheral.address}" }
return peripheral
}
- Logger.w { "BLE OTA: Target addresses $targetAddresses not in ${foundDevices.size} devices found" }
-
if (attempt < SCAN_RETRY_COUNT - 1) {
Logger.i { "BLE OTA: Device not found, waiting ${SCAN_RETRY_DELAY_MS}ms before retry..." }
kotlinx.coroutines.delay(SCAN_RETRY_DELAY_MS)
@@ -129,20 +124,18 @@ class BleOtaTransport(
}
/** Connect to the device and discover OTA service. */
- @OptIn(ExperimentalUuidApi::class)
@Suppress("LongMethod")
override suspend fun connect(): Result = runCatching {
Logger.i { "BLE OTA: Waiting ${REBOOT_DELAY_MS}ms for device to reboot into OTA mode..." }
kotlinx.coroutines.delay(REBOOT_DELAY_MS)
- Logger.i { "BLE OTA: Connecting to $address using Nordic BLE Library..." }
+ Logger.i { "BLE OTA: Connecting to $address using Nordic BLE Library v2..." }
// Scan for device by address - device must have rebooted into OTA mode
val p =
scanForOtaDevice()
?: throw OtaProtocolException.ConnectionFailed(
- "Device not found at address $address. " +
- "Ensure the device has rebooted into OTA mode and is advertising.",
+ "Device not found. Ensure the device has rebooted into OTA mode and is advertising.",
)
peripheral = p
@@ -164,33 +157,30 @@ class BleOtaTransport(
.launchIn(transportScope)
// Wait for connection or failure with timeout
- // Don't use drop(1) - we might already be connected by the time we start collecting
- val connectionState =
- try {
- withTimeout(CONNECTION_TIMEOUT_MS) {
- p.state.first { it is ConnectionState.Connected || it is ConnectionState.Disconnected }
- }
- } catch (@Suppress("SwallowedException") e: kotlinx.coroutines.TimeoutCancellationException) {
- Logger.w { "BLE OTA: Timed out waiting to connect to ${p.address}. Error: ${e.message}" }
- throw OtaProtocolException.Timeout("Timed out connecting to device at address ${p.address}")
+ try {
+ withTimeout(CONNECTION_TIMEOUT_MS) {
+ p.state.first { it is ConnectionState.Connected || it is ConnectionState.Disconnected }
}
+ } catch (@Suppress("SwallowedException") e: kotlinx.coroutines.TimeoutCancellationException) {
+ Logger.w { "BLE OTA: Timed out waiting to connect to ${p.address}" }
+ throw OtaProtocolException.Timeout("Timed out connecting to device at address ${p.address}")
+ }
- if (connectionState is ConnectionState.Disconnected) {
- Logger.w { "BLE OTA: Failed to connect to ${p.address} (state=$connectionState)" }
+ if (p.isConnected != true) {
+ Logger.w { "BLE OTA: Failed to connect to ${p.address} (state=${p.state.value})" }
throw OtaProtocolException.ConnectionFailed("Failed to connect to device at address ${p.address}")
}
Logger.i { "BLE OTA: Connected to ${p.address}, discovering services..." }
- // Discover services
- val services = p.services(listOf(SERVICE_UUID.toKotlinUuid())).filterNotNull().first()
+ // Discover services using kotlin.uuid.Uuid
+ val services = p.services(listOf(SERVICE_UUID)).filterNotNull().first()
val meshtasticOtaService =
- services.find { it.uuid == SERVICE_UUID.toKotlinUuid() }
+ services.find { it.uuid == SERVICE_UUID }
?: throw OtaProtocolException.ConnectionFailed("ESP32 OTA service not found")
- otaCharacteristic =
- meshtasticOtaService.characteristics.find { it.uuid == OTA_CHARACTERISTIC_UUID.toKotlinUuid() }
- val txChar = meshtasticOtaService.characteristics.find { it.uuid == TX_CHARACTERISTIC_UUID.toKotlinUuid() }
+ otaCharacteristic = meshtasticOtaService.characteristics.find { it.uuid == OTA_CHARACTERISTIC_UUID }
+ val txChar = meshtasticOtaService.characteristics.find { it.uuid == TX_CHARACTERISTIC_UUID }
if (otaCharacteristic == null || txChar == null) {
throw OtaProtocolException.ConnectionFailed("Required characteristics not found")
@@ -345,9 +335,9 @@ class BleOtaTransport(
companion object {
// Service and Characteristic UUIDs from ESP32 Unified OTA spec
- private val SERVICE_UUID = UUID.fromString("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
- private val OTA_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130005")
- private val TX_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130003")
+ private val SERVICE_UUID = Uuid.parse("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
+ private val OTA_CHARACTERISTIC_UUID = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130005")
+ private val TX_CHARACTERISTIC_UUID = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130003")
// Timeouts and retries
private val SCAN_TIMEOUT = 10.seconds
diff --git a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportErrorTest.kt b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportErrorTest.kt
index 9ad49d93e..2f1b6aab1 100644
--- a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportErrorTest.kt
+++ b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportErrorTest.kt
@@ -35,7 +35,6 @@ import no.nordicsemi.kotlin.ble.core.Permission
import no.nordicsemi.kotlin.ble.core.and
import org.junit.Assert.assertTrue
import org.junit.Test
-import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@@ -46,17 +45,16 @@ class BleOtaTransportErrorTest {
private val testDispatcher = StandardTestDispatcher()
private val address = "00:11:22:33:44:55"
- private val serviceUuid = UUID.fromString("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
- private val otaCharacteristicUuid = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130005")
- private val txCharacteristicUuid = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130003")
-
- private fun UUID.toKotlinUuid(): Uuid = Uuid.parse(this.toString())
+ private val serviceUuid = Uuid.parse("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
+ private val otaCharacteristicUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130005")
+ private val txCharacteristicUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130003")
@Test
fun `startOta fails when device rejects hash`() = runTest(testDispatcher) {
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
lateinit var otaPeripheral: PeripheralSpec
var txCharHandle: Int = -1
+ var otaCharHandle: Int = -1
val eventHandler =
object : PeripheralSpecEventHandler {
@@ -86,16 +84,17 @@ class BleOtaTransportErrorTest {
CompleteLocalName("ESP32-OTA")
}
connectable(name = "ESP32-OTA", eventHandler = eventHandler, isBonded = true) {
- Service(uuid = serviceUuid.toKotlinUuid()) {
- Characteristic(
- uuid = otaCharacteristicUuid.toKotlinUuid(),
- properties =
- CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
- permission = Permission.WRITE,
- )
+ Service(uuid = serviceUuid) {
+ otaCharHandle =
+ Characteristic(
+ uuid = otaCharacteristicUuid,
+ properties =
+ CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
+ permission = Permission.WRITE,
+ )
txCharHandle =
Characteristic(
- uuid = txCharacteristicUuid.toKotlinUuid(),
+ uuid = txCharacteristicUuid,
property = CharacteristicProperty.NOTIFY,
permission = Permission.READ,
)
@@ -120,6 +119,7 @@ class BleOtaTransportErrorTest {
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
lateinit var otaPeripheral: PeripheralSpec
var txCharHandle: Int = -1
+ var otaCharHandle: Int = -1
val eventHandler =
object : PeripheralSpecEventHandler {
@@ -146,16 +146,17 @@ class BleOtaTransportErrorTest {
CompleteLocalName("ESP32-OTA")
}
connectable(name = "ESP32-OTA", eventHandler = eventHandler, isBonded = true) {
- Service(uuid = serviceUuid.toKotlinUuid()) {
- Characteristic(
- uuid = otaCharacteristicUuid.toKotlinUuid(),
- properties =
- CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
- permission = Permission.WRITE,
- )
+ Service(uuid = serviceUuid) {
+ otaCharHandle =
+ Characteristic(
+ uuid = otaCharacteristicUuid,
+ properties =
+ CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
+ permission = Permission.WRITE,
+ )
txCharHandle =
Characteristic(
- uuid = txCharacteristicUuid.toKotlinUuid(),
+ uuid = txCharacteristicUuid,
property = CharacteristicProperty.NOTIFY,
permission = Permission.READ,
)
@@ -192,6 +193,7 @@ class BleOtaTransportErrorTest {
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
lateinit var otaPeripheral: PeripheralSpec
var txCharHandle: Int = -1
+ var otaCharHandle: Int = -1
val eventHandler =
object : PeripheralSpecEventHandler {
@@ -225,16 +227,17 @@ class BleOtaTransportErrorTest {
CompleteLocalName("ESP32-OTA")
}
connectable(name = "ESP32-OTA", eventHandler = eventHandler, isBonded = true) {
- Service(uuid = serviceUuid.toKotlinUuid()) {
- Characteristic(
- uuid = otaCharacteristicUuid.toKotlinUuid(),
- properties =
- CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
- permission = Permission.WRITE,
- )
+ Service(uuid = serviceUuid) {
+ otaCharHandle =
+ Characteristic(
+ uuid = otaCharacteristicUuid,
+ properties =
+ CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
+ permission = Permission.WRITE,
+ )
txCharHandle =
Characteristic(
- uuid = txCharacteristicUuid.toKotlinUuid(),
+ uuid = txCharacteristicUuid,
property = CharacteristicProperty.NOTIFY,
permission = Permission.READ,
)
diff --git a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportMtuTest.kt b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportMtuTest.kt
index 5a6b508ae..3fcf567d7 100644
--- a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportMtuTest.kt
+++ b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportMtuTest.kt
@@ -33,13 +33,8 @@ import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.client.android.ScanResult
import no.nordicsemi.kotlin.ble.core.ConnectionState
import org.junit.Test
-import java.util.UUID
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.toKotlinUuid
-
-private val SERVICE_UUID = UUID.fromString("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
-private val OTA_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130005")
-private val TX_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130003")
+import kotlin.uuid.Uuid
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class BleOtaTransportMtuTest {
@@ -57,15 +52,20 @@ class BleOtaTransportMtuTest {
val service: RemoteService = mockk(relaxed = true)
val scanResult: ScanResult = mockk(relaxed = true)
+ val serviceUuid = Uuid.parse("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
+ val otaCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130005")
+ val txCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130003")
+
every { scanResult.peripheral } returns peripheral
every { centralManager.scan(any(), any()) } returns flowOf(scanResult)
every { peripheral.address } returns address
every { peripheral.state } returns MutableStateFlow(ConnectionState.Connected)
+ every { peripheral.isConnected } returns true
coEvery { peripheral.services(any()) } returns MutableStateFlow(listOf(service))
- every { service.uuid } returns SERVICE_UUID.toKotlinUuid()
+ every { service.uuid } returns serviceUuid
every { service.characteristics } returns listOf(otaChar, txChar)
- every { otaChar.uuid } returns OTA_CHARACTERISTIC_UUID.toKotlinUuid()
- every { txChar.uuid } returns TX_CHARACTERISTIC_UUID.toKotlinUuid()
+ every { otaChar.uuid } returns otaCharUuid
+ every { txChar.uuid } returns txCharUuid
coEvery { centralManager.connect(any(), any()) } returns Unit
every { txChar.subscribe() } returns MutableSharedFlow()
diff --git a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportNordicMockTest.kt b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportNordicMockTest.kt
index 657cd18c4..42ee344b2 100644
--- a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportNordicMockTest.kt
+++ b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportNordicMockTest.kt
@@ -37,23 +37,16 @@ import no.nordicsemi.kotlin.ble.core.and
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
-import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
-private val SERVICE_UUID = UUID.fromString("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
-private val OTA_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130005")
-private val TX_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130003")
-
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class BleOtaTransportNordicMockTest {
private val testDispatcher = kotlinx.coroutines.test.StandardTestDispatcher()
private val address = "00:11:22:33:44:55"
- private fun java.util.UUID.toKotlinUuid(): Uuid = Uuid.parse(this.toString())
-
@Before
fun setup() {
Logger.setLogWriters(
@@ -75,6 +68,10 @@ class BleOtaTransportNordicMockTest {
var otaCharHandle: Int = -1
var txCharHandle: Int = -1
+ val serviceUuid = Uuid.parse("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
+ val otaCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130005")
+ val txCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130003")
+
// Use a property to hold the peripheral since we need it in the event handler
lateinit var otaPeripheral: PeripheralSpec
@@ -117,17 +114,17 @@ class BleOtaTransportNordicMockTest {
CompleteLocalName("ESP32-OTA")
}
connectable(name = "ESP32-OTA", eventHandler = eventHandler) {
- Service(uuid = SERVICE_UUID.toKotlinUuid()) {
+ Service(uuid = serviceUuid) {
otaCharHandle =
Characteristic(
- uuid = OTA_CHARACTERISTIC_UUID.toKotlinUuid(),
+ uuid = otaCharUuid,
properties =
CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
permission = Permission.WRITE,
)
txCharHandle =
Characteristic(
- uuid = TX_CHARACTERISTIC_UUID.toKotlinUuid(),
+ uuid = txCharUuid,
property = CharacteristicProperty.NOTIFY,
permission = Permission.READ,
)
diff --git a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportTest.kt b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportTest.kt
index c5edf8b39..b106e08dd 100644
--- a/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportTest.kt
+++ b/feature/firmware/src/test/kotlin/org/meshtastic/feature/firmware/ota/BleOtaTransportTest.kt
@@ -32,13 +32,8 @@ import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.client.android.ScanResult
import no.nordicsemi.kotlin.ble.core.ConnectionState
import org.junit.Test
-import java.util.UUID
import kotlin.uuid.ExperimentalUuidApi
-import kotlin.uuid.toKotlinUuid
-
-private val SERVICE_UUID = UUID.fromString("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
-private val OTA_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130005")
-private val TX_CHARACTERISTIC_UUID = UUID.fromString("62ec0272-3ec5-11eb-b378-0242ac130003")
+import kotlin.uuid.Uuid
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
class BleOtaTransportTest {
@@ -56,6 +51,10 @@ class BleOtaTransportTest {
val service: RemoteService = mockk(relaxed = true)
val scanResult: ScanResult = mockk()
+ val serviceUuid = Uuid.parse("4FAFC201-1FB5-459E-8FCC-C5C9C331914B")
+ val otaCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130005")
+ val txCharUuid = Uuid.parse("62ec0272-3ec5-11eb-b378-0242ac130003")
+
every { scanResult.peripheral } returns peripheral
// Mock the scan call. It takes a Duration and a lambda.
@@ -63,12 +62,13 @@ class BleOtaTransportTest {
every { peripheral.address } returns address
every { peripheral.state } returns MutableStateFlow(ConnectionState.Connected)
+ every { peripheral.isConnected } returns true
coEvery { peripheral.services(any()) } returns MutableStateFlow(listOf(service))
- every { service.uuid } returns SERVICE_UUID.toKotlinUuid()
+ every { service.uuid } returns serviceUuid
every { service.characteristics } returns listOf(otaChar, txChar)
- every { otaChar.uuid } returns OTA_CHARACTERISTIC_UUID.toKotlinUuid()
- every { txChar.uuid } returns TX_CHARACTERISTIC_UUID.toKotlinUuid()
+ every { otaChar.uuid } returns otaCharUuid
+ every { txChar.uuid } returns txCharUuid
coEvery { centralManager.connect(any(), any()) } returns Unit
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 442626d6b..994fb293f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -54,7 +54,7 @@ wire = "6.0.0-alpha02"
vico = "3.0.0-beta.3"
# Removed gradle-doctor
dependency-guard = "0.5.0"
-nordic-ble = "2.0.0-alpha12"
+nordic-ble = "2.0.0-alpha13"
[libraries]
@@ -78,7 +78,7 @@ androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref =
androidx-hilt-lifecycle-viewmodel-compose = { module = "androidx.hilt:hilt-lifecycle-viewmodel-compose", version.ref = "androidxHilt" }
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidxHilt" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" }
-androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" }
+androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version = "lifecycle" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
@@ -189,9 +189,10 @@ markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-rend
material = { module = "com.google.android.material:material", version = "1.13.0" }
nordic-client-android = { module = "no.nordicsemi.kotlin.ble:client-android", version.ref = "nordic-ble" }
nordic-client-android-mock = { module = "no.nordicsemi.kotlin.ble:client-android-mock", version.ref = "nordic-ble" }
-nordic-client-mock = { module = "no.nordicsemi.kotlin.ble:client-mock", version.ref = "nordic-ble" }
-nordic-core-android-mock = { module = "no.nordicsemi.kotlin.ble:core-android-mock", version.ref = "nordic-ble" }
+nordic-client-core-mock = { module = "no.nordicsemi.kotlin.ble:client-core-mock", version.ref = "nordic-ble" }
nordic-core-mock = { module = "no.nordicsemi.kotlin.ble:core-mock", version.ref = "nordic-ble" }
+nordic-environment-android = { module = "no.nordicsemi.kotlin.ble:environment-android", version.ref = "nordic-ble" }
+nordic-environment-android-mock = { module = "no.nordicsemi.kotlin.ble:environment-android-mock", version.ref = "nordic-ble" }
nordic-dfu = { module = "no.nordicsemi.android:dfu", version = "2.11.0" }
org-eclipse-paho-client-mqttv3 = { module = "org.eclipse.paho:org.eclipse.paho.client.mqttv3", version = "1.2.5" }
osmbonuspack = { module = "com.github.MKergall:osmbonuspack", version = "6.9.0" }