mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-21 08:24:22 +01:00
Add tests to GLS profile
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package no.nordicsemi.android.nrftoolbox
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class ApplicationScopeModule {
|
||||
|
||||
@Provides
|
||||
fun applicationScope() = CoroutineScope(SupervisorJob())
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import no.nordicsemi.android.analytics.AppAnalytics
|
||||
import no.nordicsemi.android.analytics.AppOpenEvent
|
||||
import no.nordicsemi.android.gls.GlsServer
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
@@ -43,9 +44,14 @@ class NrfToolboxApplication : Application() {
|
||||
@Inject
|
||||
lateinit var analytics: AppAnalytics
|
||||
|
||||
@Inject
|
||||
lateinit var glsServer: GlsServer
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
analytics.logEvent(AppOpenEvent)
|
||||
|
||||
glsServer.start(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ val ScannerDestination = defineDestination(ScannerDestinationId) {
|
||||
uuid = arg,
|
||||
onResult = {
|
||||
when (it) {
|
||||
is DeviceSelected -> navigationViewModel.navigateUpWithResult(ScannerDestinationId, it.device)
|
||||
is DeviceSelected -> navigationViewModel.navigateUpWithResult(ScannerDestinationId, it.scanResults.device)
|
||||
ScanningCancelled -> navigationViewModel.navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ dependencies {
|
||||
|
||||
implementation(libs.nordic.blek.client)
|
||||
implementation(libs.nordic.blek.profile)
|
||||
implementation(libs.nordic.blek.server)
|
||||
|
||||
implementation(libs.chart)
|
||||
|
||||
@@ -63,4 +64,6 @@ dependencies {
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.lifecycle.service)
|
||||
|
||||
testImplementation(libs.junit4)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.gls.main.viewmodel.BATTERY_LEVEL_CHARACTERISTIC_UUID
|
||||
import no.nordicsemi.android.gls.main.viewmodel.BATTERY_SERVICE_UUID
|
||||
import no.nordicsemi.android.gls.main.viewmodel.GLS_SERVICE_UUID
|
||||
import no.nordicsemi.android.gls.main.viewmodel.GLUCOSE_MEASUREMENT_CHARACTERISTIC
|
||||
import no.nordicsemi.android.gls.main.viewmodel.GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC
|
||||
import no.nordicsemi.android.gls.main.viewmodel.RACP_CHARACTERISTIC
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.BleGattPermission
|
||||
import no.nordicsemi.android.kotlin.ble.core.data.BleGattProperty
|
||||
import no.nordicsemi.android.kotlin.ble.core.ext.toDisplayString
|
||||
import no.nordicsemi.android.kotlin.ble.profile.gls.RecordAccessControlPointInputParser
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.BleGattServer
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleGattServerServiceType
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattCharacteristic
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattCharacteristicConfig
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BleServerGattServiceConfig
|
||||
import no.nordicsemi.android.kotlin.ble.server.main.service.BluetoothGattServerConnection
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val STANDARD_DELAY = 1000L
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Singleton
|
||||
class GlsServer @Inject constructor(
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
|
||||
private val records = listOf(
|
||||
byteArrayOf(
|
||||
0x07,
|
||||
0x00,
|
||||
0x00,
|
||||
0xDC.toByte(),
|
||||
0x07,
|
||||
0x01,
|
||||
0x01,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x05,
|
||||
0x00,
|
||||
0x00,
|
||||
0x26,
|
||||
0xD2.toByte(),
|
||||
0x11
|
||||
),
|
||||
byteArrayOf(
|
||||
0x07,
|
||||
0x01,
|
||||
0x00,
|
||||
0xDC.toByte(),
|
||||
0x07,
|
||||
0x01,
|
||||
0x01,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x08,
|
||||
0x00,
|
||||
0x00,
|
||||
0x3D,
|
||||
0xD2.toByte(),
|
||||
0x11
|
||||
),
|
||||
byteArrayOf(
|
||||
0x07,
|
||||
0x02,
|
||||
0x00,
|
||||
0xDC.toByte(),
|
||||
0x07,
|
||||
0x01,
|
||||
0x01,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x0B,
|
||||
0x00,
|
||||
0x00,
|
||||
0x54,
|
||||
0xD2.toByte(),
|
||||
0x11
|
||||
),
|
||||
byteArrayOf(
|
||||
0x07,
|
||||
0x03,
|
||||
0x00,
|
||||
0xDC.toByte(),
|
||||
0x07,
|
||||
0x01,
|
||||
0x01,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x0E,
|
||||
0x00,
|
||||
0x00,
|
||||
0x6B,
|
||||
0xD2.toByte(),
|
||||
0x11
|
||||
),
|
||||
byteArrayOf(
|
||||
0x07,
|
||||
0x04,
|
||||
0x00,
|
||||
0xDC.toByte(),
|
||||
0x07,
|
||||
0x01,
|
||||
0x01,
|
||||
0x0C,
|
||||
0x1E,
|
||||
0x11,
|
||||
0x00,
|
||||
0x00,
|
||||
0x82.toByte(),
|
||||
0xD2.toByte(),
|
||||
0x11
|
||||
)
|
||||
)
|
||||
|
||||
private val racp = byteArrayOf(0x06, 0x00, 0x01, 0x01)
|
||||
|
||||
fun start(context: Context) = scope.launch {
|
||||
val gmCharacteristic = BleServerGattCharacteristicConfig(
|
||||
GLUCOSE_MEASUREMENT_CHARACTERISTIC,
|
||||
listOf(BleGattProperty.PROPERTY_NOTIFY),
|
||||
listOf()
|
||||
)
|
||||
|
||||
val gmContextCharacteristic = BleServerGattCharacteristicConfig(
|
||||
GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC,
|
||||
listOf(BleGattProperty.PROPERTY_NOTIFY),
|
||||
listOf()
|
||||
)
|
||||
|
||||
val racpCharacteristic = BleServerGattCharacteristicConfig(
|
||||
RACP_CHARACTERISTIC,
|
||||
listOf(BleGattProperty.PROPERTY_INDICATE, BleGattProperty.PROPERTY_WRITE),
|
||||
listOf(BleGattPermission.PERMISSION_WRITE)
|
||||
)
|
||||
|
||||
val serviceConfig = BleServerGattServiceConfig(
|
||||
GLS_SERVICE_UUID,
|
||||
BleGattServerServiceType.SERVICE_TYPE_PRIMARY,
|
||||
listOf(gmCharacteristic, gmContextCharacteristic, racpCharacteristic)
|
||||
)
|
||||
|
||||
val batteryLevelCharacteristic = BleServerGattCharacteristicConfig(
|
||||
BATTERY_LEVEL_CHARACTERISTIC_UUID,
|
||||
listOf(BleGattProperty.PROPERTY_READ, BleGattProperty.PROPERTY_NOTIFY),
|
||||
listOf(BleGattPermission.PERMISSION_READ)
|
||||
)
|
||||
|
||||
val batteryService = BleServerGattServiceConfig(
|
||||
BATTERY_SERVICE_UUID,
|
||||
BleGattServerServiceType.SERVICE_TYPE_PRIMARY,
|
||||
listOf(batteryLevelCharacteristic)
|
||||
)
|
||||
|
||||
val server = BleGattServer.create(
|
||||
context = context,
|
||||
config = arrayOf(serviceConfig, batteryService),
|
||||
mock = true
|
||||
)
|
||||
|
||||
launch {
|
||||
server.connections
|
||||
.mapNotNull { it.values.firstOrNull() }
|
||||
.collect { setUpConnection(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpConnection(connection: BluetoothGattServerConnection) {
|
||||
startGlsService(connection)
|
||||
startBatteryService(connection)
|
||||
}
|
||||
|
||||
private fun startGlsService(connection: BluetoothGattServerConnection) {
|
||||
val glsService = connection.services.findService(GLS_SERVICE_UUID)!!
|
||||
val glsCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!!
|
||||
val glsContextCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC)!!
|
||||
val racpCharacteristic = glsService.findCharacteristic(RACP_CHARACTERISTIC)!!
|
||||
|
||||
racpCharacteristic.value
|
||||
.filter { it.isNotEmpty() }
|
||||
.onEach {
|
||||
if (it.contentEquals(RecordAccessControlPointInputParser.reportAllStoredRecords().value)) {
|
||||
sendAll(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
} else if (it.contentEquals(RecordAccessControlPointInputParser.reportLastStoredRecord().value)) {
|
||||
sendLast(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
} else if (it.contentEquals(RecordAccessControlPointInputParser.reportFirstStoredRecord().value)) {
|
||||
sendFirst(glsCharacteristic)
|
||||
racpCharacteristic.setValue(racp)
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown value")
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
private fun sendFirst(characteristics: BleServerGattCharacteristic) {
|
||||
characteristics.setValue(records.first())
|
||||
}
|
||||
|
||||
private fun sendLast(characteristics: BleServerGattCharacteristic) {
|
||||
characteristics.setValue(records.last())
|
||||
}
|
||||
|
||||
private fun sendAll(characteristics: BleServerGattCharacteristic) = scope.launch {
|
||||
records.forEach {
|
||||
characteristics.setValue(it)
|
||||
delay(STANDARD_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startBatteryService(connection: BluetoothGattServerConnection) {
|
||||
val batteryService = connection.services.findService(BATTERY_SERVICE_UUID)!!
|
||||
val batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
|
||||
|
||||
scope.launch {
|
||||
repeat(100) {
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x61))
|
||||
delay(STANDARD_DELAY)
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x60))
|
||||
delay(STANDARD_DELAY)
|
||||
batteryLevelCharacteristic.setValue(byteArrayOf(0x5F))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,13 +87,13 @@ import javax.inject.Inject
|
||||
|
||||
val GLS_SERVICE_UUID: UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
private val GM_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb")
|
||||
private val GM_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb")
|
||||
private val GF_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb")
|
||||
private val RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb")
|
||||
val GLUCOSE_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb")
|
||||
val GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb")
|
||||
val GLUCOSE_FEATURE_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb")
|
||||
val RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@HiltViewModel
|
||||
@@ -202,7 +202,7 @@ internal class GLSViewModel @Inject constructor(
|
||||
|
||||
private suspend fun configureGatt(services: BleGattServices) {
|
||||
val glsService = services.findService(GLS_SERVICE_UUID)!!
|
||||
glucoseMeasurementCharacteristic = glsService.findCharacteristic(GM_CHARACTERISTIC)!!
|
||||
glucoseMeasurementCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!!
|
||||
recordAccessControlPointCharacteristic = glsService.findCharacteristic(RACP_CHARACTERISTIC)!!
|
||||
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!
|
||||
val batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
|
||||
@@ -219,7 +219,7 @@ internal class GLSViewModel @Inject constructor(
|
||||
.catch { it.printStackTrace() }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
glsService.findCharacteristic(GM_CONTEXT_CHARACTERISTIC)?.getNotifications()
|
||||
glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC)?.getNotifications()
|
||||
?.mapNotNull { GlucoseMeasurementContextParser.parse(it) }
|
||||
?.onEach { _state.value = _state.value.copyWithNewContext(it) }
|
||||
?.catch { it.printStackTrace() }
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package no.nordicsemi.android.gls
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ dependencyResolutionManagement {
|
||||
}
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from("no.nordicsemi.android.gradle:version-catalog:1.5.1")
|
||||
from("no.nordicsemi.android.gradle:version-catalog:1.5.2")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +75,9 @@ include(":lib_service")
|
||||
include(":lib_ui")
|
||||
include(":lib_utils")
|
||||
|
||||
//if (file("../Android-Common-Libraries").exists()) {
|
||||
// includeBuild("../Android-Common-Libraries")
|
||||
//}
|
||||
if (file("../Android-Common-Libraries").exists()) {
|
||||
includeBuild("../Android-Common-Libraries")
|
||||
}
|
||||
|
||||
if (file("../Kotlin-BLE-Library").exists()) {
|
||||
includeBuild("../Kotlin-BLE-Library")
|
||||
|
||||
Reference in New Issue
Block a user