mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(ble): Centralize BLE logic into a core module (#4550)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
7a68802bc2
commit
6bfa5b5f70
214 changed files with 3471 additions and 2405 deletions
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.concurrent
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class SequentialJobTest {
|
||||
|
||||
private val sequentialJob = SequentialJob()
|
||||
|
||||
@Test
|
||||
fun `launch cancels previous job`() = runTest {
|
||||
var job1Active = false
|
||||
var job1Cancelled = false
|
||||
|
||||
// Launch first job
|
||||
sequentialJob.launch(this) {
|
||||
try {
|
||||
job1Active = true
|
||||
delay(1000)
|
||||
} finally {
|
||||
job1Cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
advanceTimeBy(100)
|
||||
assertTrue("Job 1 should be active", job1Active)
|
||||
|
||||
// Launch second job
|
||||
sequentialJob.launch(this) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
advanceTimeBy(100)
|
||||
assertTrue("Job 1 should be cancelled", job1Cancelled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel stops the job`() = runTest {
|
||||
var jobActive = false
|
||||
var jobCancelled = false
|
||||
|
||||
sequentialJob.launch(this) {
|
||||
try {
|
||||
jobActive = true
|
||||
delay(1000)
|
||||
} finally {
|
||||
jobCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
advanceTimeBy(100)
|
||||
assertTrue("Job should be active", jobActive)
|
||||
|
||||
sequentialJob.cancel()
|
||||
|
||||
advanceTimeBy(100)
|
||||
assertTrue("Job should be cancelled", jobCancelled)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,25 +35,25 @@ import no.nordicsemi.kotlin.ble.client.mock.internal.MockRemoteCharacteristic
|
|||
import no.nordicsemi.kotlin.ble.core.CharacteristicProperty
|
||||
import no.nordicsemi.kotlin.ble.core.LegacyAdvertisingSetParameters
|
||||
import no.nordicsemi.kotlin.ble.core.Permission
|
||||
import org.junit.Ignore
|
||||
import no.nordicsemi.kotlin.ble.environment.android.mock.MockAndroidEnvironment
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMNUM_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.LOGRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.SERVICE_UUID
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.TORADIO_CHARACTERISTIC
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
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) {
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
||||
var fromRadioHandle: Int = -1
|
||||
|
|
@ -95,9 +95,9 @@ class NordicBleInterfaceDrainTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -107,18 +107,18 @@ class NordicBleInterfaceDrainTest {
|
|||
)
|
||||
fromNumHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
fromRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -38,19 +38,20 @@ 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 org.meshtastic.core.ble.BleError
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMNUM_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.LOGRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.SERVICE_UUID
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.TORADIO_CHARACTERISTIC
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
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 +107,10 @@ class NordicBleInterfaceRetryTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
toRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -118,17 +119,17 @@ class NordicBleInterfaceRetryTest {
|
|||
permission = Permission.WRITE,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -211,10 +212,10 @@ class NordicBleInterfaceRetryTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
toRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -223,17 +224,17 @@ class NordicBleInterfaceRetryTest {
|
|||
permission = Permission.WRITE,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,21 +37,23 @@ import no.nordicsemi.kotlin.ble.core.CharacteristicProperty
|
|||
import no.nordicsemi.kotlin.ble.core.LegacyAdvertisingSetParameters
|
||||
import no.nordicsemi.kotlin.ble.core.Permission
|
||||
import no.nordicsemi.kotlin.ble.core.and
|
||||
import no.nordicsemi.kotlin.ble.environment.android.mock.MockAndroidEnvironment
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
import org.meshtastic.core.ble.BleError
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMNUM_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.FROMRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.LOGRADIO_CHARACTERISTIC
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.SERVICE_UUID
|
||||
import org.meshtastic.core.ble.MeshtasticBleConstants.TORADIO_CHARACTERISTIC
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalUuidApi::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
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(
|
||||
|
|
@ -66,7 +68,8 @@ class NordicBleInterfaceTest {
|
|||
|
||||
@Test
|
||||
fun `full connection and notification flow`() = runTest(testDispatcher) {
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
||||
var fromNumHandle: Int = -1
|
||||
|
|
@ -107,28 +110,28 @@ class NordicBleInterfaceTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
CharacteristicProperty.WRITE and CharacteristicProperty.WRITE_WITHOUT_RESPONSE,
|
||||
permission = Permission.WRITE,
|
||||
)
|
||||
fromNumHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
fromRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
logRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -189,7 +192,8 @@ class NordicBleInterfaceTest {
|
|||
|
||||
@Test
|
||||
fun `handleSendToRadio writes to toRadioCharacteristic`() = runTest(testDispatcher) {
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
||||
var toRadioHandle: Int = -1
|
||||
|
|
@ -240,10 +244,10 @@ class NordicBleInterfaceTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
toRadioHandle =
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -257,17 +261,17 @@ class NordicBleInterfaceTest {
|
|||
}
|
||||
// Add other required chars to avoid discovery failure
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -308,7 +312,8 @@ class NordicBleInterfaceTest {
|
|||
|
||||
@Test
|
||||
fun `disconnection triggers onDisconnect`() = runTest(testDispatcher) {
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
|
||||
// Mock service
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
|
@ -338,9 +343,9 @@ class NordicBleInterfaceTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -349,17 +354,17 @@ class NordicBleInterfaceTest {
|
|||
permission = Permission.WRITE,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -401,7 +406,8 @@ class NordicBleInterfaceTest {
|
|||
|
||||
@Test
|
||||
fun `discovery fails if required characteristic missing`() = runTest(testDispatcher) {
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
|
||||
// Mock service
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
|
@ -429,27 +435,27 @@ class NordicBleInterfaceTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
// OMIT toRadio characteristic to force failure
|
||||
/*
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.WRITE, CharacteristicProperty.WRITE_WITHOUT_RESPONSE),
|
||||
permission = Permission.WRITE
|
||||
)
|
||||
*/
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -481,7 +487,8 @@ class NordicBleInterfaceTest {
|
|||
@Test
|
||||
fun `write exception triggers disconnect`() = runTest(testDispatcher) {
|
||||
val uniqueAddress = "11:22:33:44:55:66"
|
||||
val centralManager = CentralManager.Factory.mock(scope = backgroundScope)
|
||||
val mockEnvironment = MockAndroidEnvironment.Api31(isBluetoothEnabled = true)
|
||||
val centralManager = CentralManager.mock(mockEnvironment, scope = backgroundScope)
|
||||
|
||||
// Mock service
|
||||
val service = mockk<RadioInterfaceService>(relaxed = true)
|
||||
|
|
@ -513,9 +520,9 @@ class NordicBleInterfaceTest {
|
|||
isBonded = true,
|
||||
eventHandler = eventHandler,
|
||||
cachedServices = {
|
||||
Service(uuid = BleConstants.BTM_SERVICE_UUID.toKotlinUuid()) {
|
||||
Service(uuid = SERVICE_UUID) {
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_TORADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = TORADIO_CHARACTERISTIC,
|
||||
properties =
|
||||
setOf(
|
||||
CharacteristicProperty.WRITE,
|
||||
|
|
@ -524,17 +531,17 @@ class NordicBleInterfaceTest {
|
|||
permission = Permission.WRITE,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMNUM_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMNUM_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_FROMRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = FROMRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.READ),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
Characteristic(
|
||||
uuid = BleConstants.BTM_LOGRADIO_CHARACTER.toKotlinUuid(),
|
||||
uuid = LOGRADIO_CHARACTERISTIC,
|
||||
properties = setOf(CharacteristicProperty.NOTIFY),
|
||||
permission = Permission.READ,
|
||||
)
|
||||
|
|
@ -561,8 +568,9 @@ class NordicBleInterfaceTest {
|
|||
// Trigger write which will fail
|
||||
nordicInterface.handleSendToRadio(byteArrayOf(0x01))
|
||||
|
||||
// Wait for error propagation
|
||||
delay(500.milliseconds)
|
||||
// Wait for error propagation (retries take time!)
|
||||
// 3 attempts with 500ms delay between them = ~1000ms+
|
||||
delay(2500.milliseconds)
|
||||
|
||||
// Verify onDisconnect was called with error
|
||||
verify { service.onDisconnect(any<BleError>()) }
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ import org.meshtastic.core.service.ConnectionState
|
|||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34])
|
||||
class MeshServiceBroadcastsTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
|
|
|
|||
|
|
@ -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,12 +14,11 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui
|
||||
|
||||
import com.geeksville.mesh.model.getInitials
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.meshtastic.core.model.util.getInitials
|
||||
|
||||
class UIUnitTest {
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue