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 dagger.hilt.android.HiltAndroidApp
|
||||||
import no.nordicsemi.android.analytics.AppAnalytics
|
import no.nordicsemi.android.analytics.AppAnalytics
|
||||||
import no.nordicsemi.android.analytics.AppOpenEvent
|
import no.nordicsemi.android.analytics.AppOpenEvent
|
||||||
|
import no.nordicsemi.android.gls.GlsServer
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
@@ -43,9 +44,14 @@ class NrfToolboxApplication : Application() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var analytics: AppAnalytics
|
lateinit var analytics: AppAnalytics
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var glsServer: GlsServer
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
analytics.logEvent(AppOpenEvent)
|
analytics.logEvent(AppOpenEvent)
|
||||||
|
|
||||||
|
glsServer.start(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ val ScannerDestination = defineDestination(ScannerDestinationId) {
|
|||||||
uuid = arg,
|
uuid = arg,
|
||||||
onResult = {
|
onResult = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DeviceSelected -> navigationViewModel.navigateUpWithResult(ScannerDestinationId, it.device)
|
is DeviceSelected -> navigationViewModel.navigateUpWithResult(ScannerDestinationId, it.scanResults.device)
|
||||||
ScanningCancelled -> navigationViewModel.navigateUp()
|
ScanningCancelled -> navigationViewModel.navigateUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.nordic.blek.client)
|
implementation(libs.nordic.blek.client)
|
||||||
implementation(libs.nordic.blek.profile)
|
implementation(libs.nordic.blek.profile)
|
||||||
|
implementation(libs.nordic.blek.server)
|
||||||
|
|
||||||
implementation(libs.chart)
|
implementation(libs.chart)
|
||||||
|
|
||||||
@@ -63,4 +64,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.material3)
|
implementation(libs.androidx.compose.material3)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.lifecycle.service)
|
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")
|
val GLS_SERVICE_UUID: UUID = UUID.fromString("00001808-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
private val GM_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb")
|
val GLUCOSE_MEASUREMENT_CHARACTERISTIC = UUID.fromString("00002A18-0000-1000-8000-00805f9b34fb")
|
||||||
private val GM_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb")
|
val GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC = UUID.fromString("00002A34-0000-1000-8000-00805f9b34fb")
|
||||||
private val GF_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb")
|
val GLUCOSE_FEATURE_CHARACTERISTIC = UUID.fromString("00002A51-0000-1000-8000-00805f9b34fb")
|
||||||
private val RACP_CHARACTERISTIC = UUID.fromString("00002A52-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")
|
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_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -202,7 +202,7 @@ internal class GLSViewModel @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun configureGatt(services: BleGattServices) {
|
private suspend fun configureGatt(services: BleGattServices) {
|
||||||
val glsService = services.findService(GLS_SERVICE_UUID)!!
|
val glsService = services.findService(GLS_SERVICE_UUID)!!
|
||||||
glucoseMeasurementCharacteristic = glsService.findCharacteristic(GM_CHARACTERISTIC)!!
|
glucoseMeasurementCharacteristic = glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CHARACTERISTIC)!!
|
||||||
recordAccessControlPointCharacteristic = glsService.findCharacteristic(RACP_CHARACTERISTIC)!!
|
recordAccessControlPointCharacteristic = glsService.findCharacteristic(RACP_CHARACTERISTIC)!!
|
||||||
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!
|
val batteryService = services.findService(BATTERY_SERVICE_UUID)!!
|
||||||
val batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
|
val batteryLevelCharacteristic = batteryService.findCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)!!
|
||||||
@@ -219,7 +219,7 @@ internal class GLSViewModel @Inject constructor(
|
|||||||
.catch { it.printStackTrace() }
|
.catch { it.printStackTrace() }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
glsService.findCharacteristic(GM_CONTEXT_CHARACTERISTIC)?.getNotifications()
|
glsService.findCharacteristic(GLUCOSE_MEASUREMENT_CONTEXT_CHARACTERISTIC)?.getNotifications()
|
||||||
?.mapNotNull { GlucoseMeasurementContextParser.parse(it) }
|
?.mapNotNull { GlucoseMeasurementContextParser.parse(it) }
|
||||||
?.onEach { _state.value = _state.value.copyWithNewContext(it) }
|
?.onEach { _state.value = _state.value.copyWithNewContext(it) }
|
||||||
?.catch { it.printStackTrace() }
|
?.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 {
|
versionCatalogs {
|
||||||
create("libs") {
|
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_ui")
|
||||||
include(":lib_utils")
|
include(":lib_utils")
|
||||||
|
|
||||||
//if (file("../Android-Common-Libraries").exists()) {
|
if (file("../Android-Common-Libraries").exists()) {
|
||||||
// includeBuild("../Android-Common-Libraries")
|
includeBuild("../Android-Common-Libraries")
|
||||||
//}
|
}
|
||||||
|
|
||||||
if (file("../Kotlin-BLE-Library").exists()) {
|
if (file("../Kotlin-BLE-Library").exists()) {
|
||||||
includeBuild("../Kotlin-BLE-Library")
|
includeBuild("../Kotlin-BLE-Library")
|
||||||
|
|||||||
Reference in New Issue
Block a user